From 958cbf21065081c27e88ad0e7057d272756c467d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20P=C3=A9ter?= Date: Wed, 24 Jun 2026 12:21:36 +0200 Subject: [PATCH 01/51] Updated submodules. Fix ImVec2 math operator compilation conflict with ImGui 1.90+ --- .gitmodules | 4 +-- External/IconFontCppHeaders | 2 +- External/vcpkg | 2 +- SynapseEngine/Editor/Manager/GuiManager.cpp | 32 ++++++++++++------- SynapseEngine/Editor/Manager/GuiManager.h | 1 - SynapseEngine/Editor/Synapse.cpp | 6 ++-- .../ContentBrowser/ContentBrowserView.cpp | 6 ++-- .../Editor/View/MainMenu/MainMenuView.cpp | 28 ++++++++++++++++ .../Editor/View/MainMenu/MainMenuView.h | 25 +-------------- .../View/MaterialGraph/MaterialGraphView.cpp | 2 +- .../Editor/View/Viewport/ViewportView.cpp | 4 +-- SynapseEngine/Editor/Widgets/CardWidget.cpp | 4 +-- .../Editor/Widgets/Vector3Widget.cpp | 2 +- SynapseEngine/imgui.ini | 28 ++++++++-------- SynapseEngine/xmake.lua | 3 +- 15 files changed, 83 insertions(+), 66 deletions(-) diff --git a/.gitmodules b/.gitmodules index 9d652b94..ffacd728 100644 --- a/.gitmodules +++ b/.gitmodules @@ -8,8 +8,8 @@ path = External/ImGuiFileDialog url = https://github.com/aiekick/ImGuiFileDialog [submodule "External/imgui-node-editor"] - path = External/imgui-node-editor - url = https://github.com/thedmd/imgui-node-editor.git + path = External/imgui-node-editor + url = https://https://github.com/TamasPetii/imgui-node-editor [submodule "External/IconFontCppHeaders"] path = External/IconFontCppHeaders url = https://github.com/juliettef/IconFontCppHeaders.git diff --git a/External/IconFontCppHeaders b/External/IconFontCppHeaders index 3ee7f3d2..210b5a39 160000 --- a/External/IconFontCppHeaders +++ b/External/IconFontCppHeaders @@ -1 +1 @@ -Subproject commit 3ee7f3d295ae773c0046db8d7b89b886eb2526de +Subproject commit 210b5a399a64270674560d633638952d1e8d804d diff --git a/External/vcpkg b/External/vcpkg index 64e1fbee..930ecc42 160000 --- a/External/vcpkg +++ b/External/vcpkg @@ -1 +1 @@ -Subproject commit 64e1fbee7d9f40eab5d112aaff648c4dcffe9e47 +Subproject commit 930ecc42b512b564571d767f70775d284a6fa307 diff --git a/SynapseEngine/Editor/Manager/GuiManager.cpp b/SynapseEngine/Editor/Manager/GuiManager.cpp index eb739312..b9d462d4 100644 --- a/SynapseEngine/Editor/Manager/GuiManager.cpp +++ b/SynapseEngine/Editor/Manager/GuiManager.cpp @@ -31,11 +31,25 @@ namespace Syn { ImGui_ImplGlfw_InitForVulkan(window, false); - VkDescriptorPoolSize poolSizes[] = { { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1000 } }; + VkDescriptorPoolSize poolSizes[] = + { + { VK_DESCRIPTOR_TYPE_SAMPLER, 1000 }, + { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1000 }, + { VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1000 }, + { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1000 }, + { VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 1000 }, + { VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, 1000 }, + { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1000 }, + { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1000 }, + { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1000 }, + { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC, 1000 }, + { VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, 1000 } + }; + VkDescriptorPoolCreateInfo poolInfo = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO }; poolInfo.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT; - poolInfo.maxSets = 1000; - poolInfo.poolSizeCount = 1; + poolInfo.maxSets = 1000 * IM_ARRAYSIZE(poolSizes); + poolInfo.poolSizeCount = (uint32_t)IM_ARRAYSIZE(poolSizes); poolInfo.pPoolSizes = poolSizes; vkCreateDescriptorPool(device, &poolInfo, nullptr, &_imguiPool); @@ -48,11 +62,11 @@ namespace Syn { init_info.DescriptorPool = _imguiPool; init_info.MinImageCount = imageCount; init_info.ImageCount = imageCount; - init_info.MSAASamples = VK_SAMPLE_COUNT_1_BIT; init_info.UseDynamicRendering = true; - init_info.PipelineRenderingCreateInfo = { .sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO }; - init_info.PipelineRenderingCreateInfo.colorAttachmentCount = 1; - init_info.PipelineRenderingCreateInfo.pColorAttachmentFormats = &_colorFormat; + init_info.PipelineInfoMain.MSAASamples = VK_SAMPLE_COUNT_1_BIT; + init_info.PipelineInfoMain.PipelineRenderingCreateInfo = { .sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO }; + init_info.PipelineInfoMain.PipelineRenderingCreateInfo.colorAttachmentCount = 1; + init_info.PipelineInfoMain.PipelineRenderingCreateInfo.pColorAttachmentFormats = &_colorFormat; ImGui_ImplVulkan_LoadFunctions(VK_API_VERSION_1_4, [](const char* function_name, void* user_data) { return vkGetInstanceProcAddr(reinterpret_cast(user_data), function_name); @@ -299,8 +313,4 @@ namespace Syn { colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f); colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.35f); } - - void GuiManager::CreateFontTexture() { - ImGui_ImplVulkan_CreateFontsTexture(); - } } \ No newline at end of file diff --git a/SynapseEngine/Editor/Manager/GuiManager.h b/SynapseEngine/Editor/Manager/GuiManager.h index df5cb3c6..0bbed619 100644 --- a/SynapseEngine/Editor/Manager/GuiManager.h +++ b/SynapseEngine/Editor/Manager/GuiManager.h @@ -34,7 +34,6 @@ namespace Syn { GuiTextureManager* GetTextureManager() const { return _textureManager.get(); } IFileDialogApi* GetFileDialog() const { return _fileDialog.get(); } - void CreateFontTexture(); void SetWorkspace(EditorWorkspace workspace) { _currentWorkspace = workspace; } EditorWorkspace GetWorkspace() const { return _currentWorkspace; } diff --git a/SynapseEngine/Editor/Synapse.cpp b/SynapseEngine/Editor/Synapse.cpp index dbc876bc..4e03f83f 100644 --- a/SynapseEngine/Editor/Synapse.cpp +++ b/SynapseEngine/Editor/Synapse.cpp @@ -89,9 +89,11 @@ void Synapse::OnInit() { ); ImGuiIO& io = ImGui::GetIO(); - io.Fonts->AddFontDefault(); + ImFontConfig defaultConfig; + defaultConfig.SizePixels = 13.0f; + io.Fonts->AddFontDefault(&defaultConfig); + _iconManager->InitializeFontAwesome(io, Syn::PathUtils::GetAbsolutePathString(FONT_PATH), 16.0f); - _guiManager->CreateFontTexture(); _iconManager->LoadEngineIcons(Syn::PathUtils::GetAbsolutePathString(ICON_PATH)); std::string absoluteAssetsPath = std::filesystem::absolute(ASSET_PATH).generic_string(); diff --git a/SynapseEngine/Editor/View/ContentBrowser/ContentBrowserView.cpp b/SynapseEngine/Editor/View/ContentBrowser/ContentBrowserView.cpp index 32cfa734..4025e5c5 100644 --- a/SynapseEngine/Editor/View/ContentBrowser/ContentBrowserView.cpp +++ b/SynapseEngine/Editor/View/ContentBrowser/ContentBrowserView.cpp @@ -47,7 +47,7 @@ namespace Syn { float treeHeight = mainContentBottomY - ImGui::GetCursorScreenPos().y - 12.0f; if (treeHeight < 50.0f) treeHeight = 50.0f; - ImGui::BeginChild("FolderTreeScroll", ImVec2(0, treeHeight), ImGuiChildFlags_Borders, ImGuiWindowFlags_AlwaysUseWindowPadding); + ImGui::BeginChild("FolderTreeScroll", ImVec2(0, treeHeight), ImGuiChildFlags_Borders | ImGuiChildFlags_AlwaysUseWindowPadding, 0); RenderFolderTree(vm, state); ImGui::EndChild(); @@ -86,7 +86,7 @@ namespace Syn { float gridHeight = mainContentBottomY - ImGui::GetCursorScreenPos().y - 12.0f; if (gridHeight < 50.0f) gridHeight = 50.0f; - ImGui::BeginChild("ContentGridScroll", ImVec2(0, gridHeight), ImGuiChildFlags_Borders, ImGuiWindowFlags_AlwaysUseWindowPadding); + ImGui::BeginChild("ContentGridScroll", ImVec2(0, gridHeight), ImGuiChildFlags_Borders | ImGuiChildFlags_AlwaysUseWindowPadding, 0); RenderContentArea(vm, state); ImGui::EndChild(); @@ -272,7 +272,7 @@ namespace Syn { } ImVec2 itemMin = ImGui::GetItemRectMin(); - ImGui::SetItemAllowOverlap(); + ImGui::SetNextItemAllowOverlap(); ImTextureID iconID = GetIconForEntry(entry); if (iconID) { diff --git a/SynapseEngine/Editor/View/MainMenu/MainMenuView.cpp b/SynapseEngine/Editor/View/MainMenu/MainMenuView.cpp index f9efde2a..0e563c42 100644 --- a/SynapseEngine/Editor/View/MainMenu/MainMenuView.cpp +++ b/SynapseEngine/Editor/View/MainMenu/MainMenuView.cpp @@ -1 +1,29 @@ #include "MainMenuView.h" + +namespace Syn +{ + void MainMenuView::Draw(MainMenuViewModel& vm) { + + if (ImGui::BeginMainMenuBar()) { + + if (ImGui::BeginMenu("File")) { + + if (ImGui::MenuItem("New Scene", "Ctrl+N")) { + vm.Dispatch(NewSceneIntent{}); + } + + if (ImGui::MenuItem("Load Scene...", "Ctrl+O")) { + vm.Dispatch(LoadSceneIntent{}); + } + + if (ImGui::MenuItem("Save Scene", "Ctrl+S")) { + vm.Dispatch(SaveSceneIntent{}); + } + + ImGui::EndMenu(); + } + + ImGui::EndMainMenuBar(); + } + } +} \ No newline at end of file diff --git a/SynapseEngine/Editor/View/MainMenu/MainMenuView.h b/SynapseEngine/Editor/View/MainMenu/MainMenuView.h index 77e9a761..0b293cce 100644 --- a/SynapseEngine/Editor/View/MainMenu/MainMenuView.h +++ b/SynapseEngine/Editor/View/MainMenu/MainMenuView.h @@ -6,29 +6,6 @@ namespace Syn { class MainMenuView : public IView { public: - void Draw(MainMenuViewModel& vm) override { - - if (ImGui::BeginMainMenuBar()) { - - if (ImGui::BeginMenu("File")) { - - if (ImGui::MenuItem("New Scene", "Ctrl+N")) { - vm.Dispatch(NewSceneIntent{}); - } - - if (ImGui::MenuItem("Load Scene...", "Ctrl+O")) { - vm.Dispatch(LoadSceneIntent{}); - } - - if (ImGui::MenuItem("Save Scene", "Ctrl+S")) { - vm.Dispatch(SaveSceneIntent{}); - } - - ImGui::EndMenu(); - } - - ImGui::EndMainMenuBar(); - } - } + void Draw(MainMenuViewModel& vm) override; }; } \ No newline at end of file diff --git a/SynapseEngine/Editor/View/MaterialGraph/MaterialGraphView.cpp b/SynapseEngine/Editor/View/MaterialGraph/MaterialGraphView.cpp index 00c87ca5..e7407164 100644 --- a/SynapseEngine/Editor/View/MaterialGraph/MaterialGraphView.cpp +++ b/SynapseEngine/Editor/View/MaterialGraph/MaterialGraphView.cpp @@ -1,6 +1,6 @@ #include "MaterialGraphView.h" -#include #include +#include namespace ed = ax::NodeEditor; diff --git a/SynapseEngine/Editor/View/Viewport/ViewportView.cpp b/SynapseEngine/Editor/View/Viewport/ViewportView.cpp index c3805347..1977645b 100644 --- a/SynapseEngine/Editor/View/Viewport/ViewportView.cpp +++ b/SynapseEngine/Editor/View/Viewport/ViewportView.cpp @@ -67,7 +67,7 @@ namespace Syn { ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 6.0f); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8.0f, 6.0f)); - if (ImGui::BeginChild("##SimulationToolbar", ImVec2(toolbarWidth, toolbarHeight), false, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_AlwaysUseWindowPadding)) { + if (ImGui::BeginChild("##SimulationToolbar", ImVec2(toolbarWidth, toolbarHeight), ImGuiChildFlags_AlwaysUseWindowPadding, ImGuiWindowFlags_NoScrollbar)) { ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(4.0f, 6.0f)); @@ -121,7 +121,7 @@ namespace Syn { ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 6.0f); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(4.0f, 6.0f)); - if (ImGui::BeginChild("##FloatingToolbar", ImVec2(toolbarWidth, toolbarHeight), false, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_AlwaysUseWindowPadding)) { + if (ImGui::BeginChild("##FloatingToolbar", ImVec2(toolbarWidth, toolbarHeight), ImGuiChildFlags_AlwaysUseWindowPadding, ImGuiWindowFlags_NoScrollbar)) { ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(4.0f, 6.0f)); diff --git a/SynapseEngine/Editor/Widgets/CardWidget.cpp b/SynapseEngine/Editor/Widgets/CardWidget.cpp index 0e4a8a57..b417e7c0 100644 --- a/SynapseEngine/Editor/Widgets/CardWidget.cpp +++ b/SynapseEngine/Editor/Widgets/CardWidget.cpp @@ -9,7 +9,7 @@ namespace Syn::UI { ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 6.0f); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(12.0f, 12.0f)); - ImGuiChildFlags childFlags = ImGuiChildFlags_Borders; + ImGuiChildFlags childFlags = ImGuiChildFlags_Borders | ImGuiChildFlags_AlwaysUseWindowPadding; if (isOpen) { childFlags |= ImGuiChildFlags_AutoResizeY; @@ -20,7 +20,7 @@ namespace Syn::UI { float height = isOpen ? 0.0f : closedHeight; std::string childId = std::string("##Card_") + label; - ImGui::BeginChild(childId.c_str(), ImVec2(0, height), childFlags, ImGuiWindowFlags_AlwaysUseWindowPadding | ImGuiWindowFlags_NoScrollbar); + ImGui::BeginChild(childId.c_str(), ImVec2(0, height), childFlags, ImGuiWindowFlags_NoScrollbar); ImVec2 startPos = ImGui::GetCursorPos(); float availX = ImGui::GetContentRegionAvail().x; diff --git a/SynapseEngine/Editor/Widgets/Vector3Widget.cpp b/SynapseEngine/Editor/Widgets/Vector3Widget.cpp index 4276f6fe..49aa50b0 100644 --- a/SynapseEngine/Editor/Widgets/Vector3Widget.cpp +++ b/SynapseEngine/Editor/Widgets/Vector3Widget.cpp @@ -12,7 +12,7 @@ namespace Syn::UI { ImGui::PushID(id.c_str()); - float lineHeight = GImGui->Font->FontSize + GImGui->Style.FramePadding.y * 2.0f; + float lineHeight = ImGui::GetFontSize() + GImGui->Style.FramePadding.y * 2.0f; ImVec2 buttonSize = { lineHeight + 3.0f, lineHeight }; float availWidth = ImGui::GetContentRegionAvail().x; diff --git a/SynapseEngine/imgui.ini b/SynapseEngine/imgui.ini index 4405e2da..7f4b5672 100644 --- a/SynapseEngine/imgui.ini +++ b/SynapseEngine/imgui.ini @@ -61,7 +61,7 @@ Pos=0,23 Size=2304,1273 Collapsed=0 -[Window][###Content_Scene] +[Window][Content_Scene] Pos=439,932 Size=1411,364 Collapsed=0 @@ -70,7 +70,7 @@ DockId=0x00000002,1 [Table][0x51A78E48,2] RefScale=13 Column 0 Weight=1.0000 -Column 1 Width=48 +Column 1 Width=47 [Table][0xB6D16E5C,3] RefScale=13 @@ -80,44 +80,44 @@ Column 2 Weight=0.2443 [Table][0x3D1A1C69,2] RefScale=13 -Column 0 Width=100 +Column 0 Width=147 Column 1 Weight=1.0000 [Table][0x6925898D,2] RefScale=13 -Column 0 Width=217 +Column 0 Width=216 Column 1 Weight=1.0000 [Table][0xE847EDF4,2] RefScale=13 -Column 0 Width=100 +Column 0 Width=99 Column 1 Weight=1.0000 [Table][0x182B970D,2] RefScale=13 -Column 0 Width=100 +Column 0 Width=99 Column 1 Weight=-nan(ind) [Table][0x8556BC1A,2] RefScale=13 -Column 0 Width=100 +Column 0 Width=99 Column 1 Weight=-nan(ind) [Table][0x94CE371A,2] -RefScale=13 -Column 0 Width=100 +RefScale=16 +Column 0 Width=123 Column 1 Weight=1.0000 [Table][0x5A9ED35A,2] -RefScale=13 -Column 0 Width=100 +RefScale=16 +Column 0 Width=123 Column 1 Weight=1.0000 [Table][0x05A9070B,4] RefScale=13 -Column 0 Width=140 -Column 1 Width=60 -Column 2 Width=150 +Column 0 Width=139 +Column 1 Width=59 +Column 2 Width=149 Column 3 Weight=1.0000 [Table][0x5806762E,2] diff --git a/SynapseEngine/xmake.lua b/SynapseEngine/xmake.lua index e541db7a..11f15ab0 100644 --- a/SynapseEngine/xmake.lua +++ b/SynapseEngine/xmake.lua @@ -58,7 +58,8 @@ add_defines( "JPH_USE_LZCNT", "JPH_USE_TZCNT", "JPH_USE_F16C", - "JPH_USE_FMADD" + "JPH_USE_FMADD", + "IMGUI_DISABLE_MATH_OPERATORS" ) if is_plat("windows") then From da72e90af0c02b19369d914d33efea8b496e01d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20P=C3=A9ter?= Date: Wed, 24 Jun 2026 12:35:45 +0200 Subject: [PATCH 02/51] Update imgui-node-editor submodule to include math operator fix --- .gitmodules | 2 +- External/imgui-node-editor | 2 +- .../Engine/Scene/Source/Procedural/test_config.json | 4 ++-- SynapseEngine/imgui.ini | 8 ++++---- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.gitmodules b/.gitmodules index ffacd728..81590dcc 100644 --- a/.gitmodules +++ b/.gitmodules @@ -9,7 +9,7 @@ url = https://github.com/aiekick/ImGuiFileDialog [submodule "External/imgui-node-editor"] path = External/imgui-node-editor - url = https://https://github.com/TamasPetii/imgui-node-editor + url = https://github.com/TamasPetii/imgui-node-editor [submodule "External/IconFontCppHeaders"] path = External/IconFontCppHeaders url = https://github.com/juliettef/IconFontCppHeaders.git diff --git a/External/imgui-node-editor b/External/imgui-node-editor index 021aa0ea..6975a131 160000 --- a/External/imgui-node-editor +++ b/External/imgui-node-editor @@ -1 +1 @@ -Subproject commit 021aa0ea4da13fed864bafb2a92d4c5205076866 +Subproject commit 6975a1318873c52e43779b41e59dcb8022ff4922 diff --git a/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json b/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json index 738ac9f9..0880da3f 100644 --- a/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json +++ b/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json @@ -14,8 +14,8 @@ "shared_material_count": 100 }, "entities": { - "animated_characters": 2500, - "static_geometry": 100000, + "animated_characters": 1000, + "static_geometry": 1000000, "physics_boxes": 1000, "physics_spheres": 1000, "physics_capsules": 1000 diff --git a/SynapseEngine/imgui.ini b/SynapseEngine/imgui.ini index 7f4b5672..271badc0 100644 --- a/SynapseEngine/imgui.ini +++ b/SynapseEngine/imgui.ini @@ -104,13 +104,13 @@ Column 0 Width=99 Column 1 Weight=-nan(ind) [Table][0x94CE371A,2] -RefScale=16 -Column 0 Width=123 +RefScale=13 +Column 0 Width=99 Column 1 Weight=1.0000 [Table][0x5A9ED35A,2] -RefScale=16 -Column 0 Width=123 +RefScale=13 +Column 0 Width=99 Column 1 Weight=1.0000 [Table][0x05A9070B,4] From ebae8301e813c6ee6924ed6964c8b4c41732526d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20P=C3=A9ter?= Date: Fri, 26 Jun 2026 14:49:58 +0200 Subject: [PATCH 03/51] Resolved ssao texture compile errors, and imguizmo works!! --- SynapseEngine/Editor/Manager/GuiManager.cpp | 3 +- .../Editor/View/Viewport/ViewportView.cpp | 22 ++- SynapseEngine/Editor/Widgets/PropertyGrid.cpp | 2 +- .../Scene/Source/Procedural/test_config.json | 2 +- .../Engine/Shaders/Passes/Ssao/Ssao.comp | 4 +- SynapseEngine/Synapse_MaterialGraph.json | 2 +- SynapseEngine/imgui.ini | 156 ++++++++++-------- 7 files changed, 107 insertions(+), 84 deletions(-) diff --git a/SynapseEngine/Editor/Manager/GuiManager.cpp b/SynapseEngine/Editor/Manager/GuiManager.cpp index b9d462d4..5a9a7b75 100644 --- a/SynapseEngine/Editor/Manager/GuiManager.cpp +++ b/SynapseEngine/Editor/Manager/GuiManager.cpp @@ -1,9 +1,9 @@ #include "GuiManager.h" #include #include - #include #include +#include #include "GuiTextureManager.h" #include #include "Engine/ServiceLocator.h" @@ -104,6 +104,7 @@ namespace Syn { ImGui_ImplVulkan_NewFrame(); ImGui_ImplGlfw_NewFrame(); ImGui::NewFrame(); + ImGuizmo::BeginFrame(); } void GuiManager::UpdateAndDraw() { diff --git a/SynapseEngine/Editor/View/Viewport/ViewportView.cpp b/SynapseEngine/Editor/View/Viewport/ViewportView.cpp index 1977645b..f2bbbb98 100644 --- a/SynapseEngine/Editor/View/Viewport/ViewportView.cpp +++ b/SynapseEngine/Editor/View/Viewport/ViewportView.cpp @@ -30,7 +30,9 @@ namespace Syn { if (viewportPanelSize.y <= 0.0f) viewportPanelSize.y = 1.0f; if (state.textureId && !isResizing) { + ImGui::GetWindowDrawList()->AddCallback(ImGui::GetPlatformIO().DrawCallback_SetSamplerNearest, nullptr); ImGui::Image(state.textureId, viewportPanelSize); + ImGui::GetWindowDrawList()->AddCallback(ImGui::GetPlatformIO().DrawCallback_SetSamplerLinear, nullptr); } else { ImGui::Dummy(viewportPanelSize); @@ -43,14 +45,22 @@ namespace Syn { RenderFloatingToolbar(vm, state, imageStartPos, viewportPanelSize); RenderSimulationToolbar(vm, state, imageStartPos, viewportPanelSize); - if (isImageHovered && ImGui::IsMouseClicked(ImGuiMouseButton_Left) && !ImGuizmo::IsOver() && !ImGui::IsAnyItemHovered()) { - ImVec2 mousePos = ImGui::GetMousePos(); - uint32_t x = static_cast(mousePos.x - vMin.x); - uint32_t y = static_cast(mousePos.y - vMin.y); - vm.Dispatch(PickEntityIntent{ x, y }); + DrawGizmo(vm, state, imageStartPos, viewportPanelSize); + + if (!ImGuizmo::IsUsing()) { + + if (isImageHovered && ImGui::IsMouseClicked(ImGuiMouseButton_Left) && !ImGuizmo::IsOver() && !ImGui::IsAnyItemHovered()) { + ImVec2 mousePos = ImGui::GetMousePos(); + uint32_t x = static_cast(mousePos.x - imageStartPos.x); + uint32_t y = static_cast(mousePos.y - imageStartPos.y); + vm.Dispatch(PickEntityIntent{ x, y }); + } + + if (isImageHovered && ImGui::IsMouseDown(ImGuiMouseButton_Right)) { + + } } - DrawGizmo(vm, state, imageStartPos, viewportPanelSize); HandleShortcuts(vm); ImGui::End(); diff --git a/SynapseEngine/Editor/Widgets/PropertyGrid.cpp b/SynapseEngine/Editor/Widgets/PropertyGrid.cpp index 69e139ce..3308d96b 100644 --- a/SynapseEngine/Editor/Widgets/PropertyGrid.cpp +++ b/SynapseEngine/Editor/Widgets/PropertyGrid.cpp @@ -10,7 +10,7 @@ namespace Syn::UI { bool isOpen = ImGui::BeginTable(id, 2, flags); if (isOpen) { - ImGui::TableSetupColumn("Label", ImGuiTableColumnFlags_WidthFixed, 100.0f); + ImGui::TableSetupColumn("Label", ImGuiTableColumnFlags_WidthFixed); ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch); } else { diff --git a/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json b/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json index 0880da3f..316da4f9 100644 --- a/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json +++ b/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json @@ -15,7 +15,7 @@ }, "entities": { "animated_characters": 1000, - "static_geometry": 1000000, + "static_geometry": 10000, "physics_boxes": 1000, "physics_spheres": 1000, "physics_capsules": 1000 diff --git a/SynapseEngine/Engine/Shaders/Passes/Ssao/Ssao.comp b/SynapseEngine/Engine/Shaders/Passes/Ssao/Ssao.comp index 2f584e01..fe424798 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Ssao/Ssao.comp +++ b/SynapseEngine/Engine/Shaders/Passes/Ssao/Ssao.comp @@ -46,8 +46,8 @@ void main() { vec2 offsetV = vec2(0.0, 1.0 / float(ctx.screenHeight)); // Reconstruct view-space normal from depth gradients - vec3 viewPosRight = GetViewPosFromLinearZ(uv + offsetU, textureOffset(inHizPyramid, uv, ivec2(1, 0)).x, camera); - vec3 viewPosDown = GetViewPosFromLinearZ(uv + offsetV, textureOffset(inHizPyramid, uv, ivec2(0, 1)).x, camera); + vec3 viewPosRight = GetViewPosFromLinearZ(uv + offsetU, textureLodOffset(inHizPyramid, uv, 0.0, ivec2(1, 0)).x, camera); + vec3 viewPosDown = GetViewPosFromLinearZ(uv + offsetV, textureLodOffset(inHizPyramid, uv, 0.0, ivec2(0, 1)).x, camera); vec3 viewNormal = normalize(cross(viewPosRight - viewPos, viewPos - viewPosDown)); vec2 noiseScale = vec2(ctx.screenWidth / pc.noiseTextureWidth, ctx.screenHeight / pc.noiseTextureHeight); diff --git a/SynapseEngine/Synapse_MaterialGraph.json b/SynapseEngine/Synapse_MaterialGraph.json index 314f09cc..a72cb7f2 100644 --- a/SynapseEngine/Synapse_MaterialGraph.json +++ b/SynapseEngine/Synapse_MaterialGraph.json @@ -1 +1 @@ -{"nodes":{"node:1":{"location":{"x":0,"y":0}},"node:10":{"location":{"x":0,"y":0}},"node:12":{"location":{"x":0,"y":0}},"node:14":{"location":{"x":0,"y":0}},"node:16":{"location":{"x":0,"y":0}},"node:18":{"location":{"x":0,"y":0}},"node:20":{"location":{"x":0,"y":0}},"node:22":{"location":{"x":0,"y":0}},"node:24":{"location":{"x":0,"y":0}},"node:26":{"location":{"x":0,"y":0}},"node:28":{"location":{"x":0,"y":0}},"node:8":{"location":{"x":0,"y":0}}},"selection":["node:28"],"view":{"scroll":{"x":-0.000213623046875,"y":0},"visible_rect":{"max":{"x":2303.999755859375,"y":1273},"min":{"x":-0.000213623046875,"y":0}},"zoom":1}} \ No newline at end of file +{"nodes":{"node:1":{"location":{"x":0,"y":0}},"node:10":{"location":{"x":0,"y":0}},"node:12":{"location":{"x":0,"y":0}},"node:14":{"location":{"x":0,"y":0}},"node:16":{"location":{"x":0,"y":0}},"node:18":{"location":{"x":0,"y":0}},"node:20":{"location":{"x":0,"y":0}},"node:22":{"location":{"x":0,"y":0}},"node:24":{"location":{"x":0,"y":0}},"node:26":{"location":{"x":0,"y":0}},"node:28":{"location":{"x":0,"y":0}},"node:8":{"location":{"x":0,"y":0}}},"selection":null,"view":{"scroll":{"x":-731.410400390625,"y":-323.9666748046875},"visible_rect":{"max":{"x":1048.39306640625,"y":468.022216796875},"min":{"x":-487.60693359375,"y":-215.977783203125}},"zoom":1.5}} \ No newline at end of file diff --git a/SynapseEngine/imgui.ini b/SynapseEngine/imgui.ini index 271badc0..3c246a17 100644 --- a/SynapseEngine/imgui.ini +++ b/SynapseEngine/imgui.ini @@ -1,82 +1,81 @@ -[Window][WindowOverViewport_11111111] +[Window][HostWindow_Scene] Pos=0,23 Size=2304,1273 Collapsed=0 -[Window][Debug##Default] -Pos=60,60 -Size=400,400 -Collapsed=0 - [Window][ Inspector] -Pos=1852,23 -Size=452,633 +Pos=1906,23 +Size=398,804 Collapsed=0 DockId=0x00000005,0 [Window][ Viewport] -Pos=439,23 -Size=1411,907 +Pos=464,23 +Size=1440,834 Collapsed=0 -DockId=0x00000001,0 +DockId=0x00000003,0 -[Window][ Graphics & Environment] -Pos=1852,658 -Size=452,638 +[Window][Debug##Default] +Pos=60,60 +Size=400,400 Collapsed=0 -DockId=0x00000006,0 -[Window][Material Graph] -Pos=440,23 -Size=1410,907 +[Window][Content_Scene] +Pos=464,859 +Size=1440,437 Collapsed=0 -DockId=0x08BD597D,1 +DockId=0x00000004,0 + +[Window][ Graphics & Environment] +Pos=1906,829 +Size=398,467 +Collapsed=0 +DockId=0x00000006,0 [Window][ Scene Hierarchy] Pos=0,23 -Size=437,687 +Size=462,492 Collapsed=0 DockId=0x00000009,0 [Window][ Performance Profiler] -Pos=0,712 -Size=437,584 +Pos=0,517 +Size=462,779 Collapsed=0 DockId=0x0000000A,0 -[Window][ Content Browser] -Pos=440,932 -Size=1410,364 -Collapsed=0 -DockId=0x08BD597D,0 - [Window][ Output Log] -Pos=439,932 -Size=1411,364 +Pos=464,859 +Size=1440,437 Collapsed=0 -DockId=0x00000002,0 +DockId=0x00000004,1 -[Window][HostWindow_Scene] +[Window][HostWindow_Material] Pos=0,23 Size=2304,1273 Collapsed=0 -[Window][Content_Scene] -Pos=439,932 -Size=1411,364 +[Window][Content_Material] +Pos=0,1074 +Size=2304,222 Collapsed=0 -DockId=0x00000002,1 +DockId=0x0000000C,0 -[Table][0x51A78E48,2] -RefScale=13 -Column 0 Weight=1.0000 -Column 1 Width=47 +[Window][Material Graph] +Pos=0,23 +Size=2304,1049 +Collapsed=0 +DockId=0x0000000B,0 -[Table][0xB6D16E5C,3] -RefScale=13 -Column 0 Weight=0.7557 -Column 1 Width=91 -Column 2 Weight=0.2443 +[Window][HostWindow_Model] +Pos=0,23 +Size=2304,1273 +Collapsed=0 + +[Window][Content_Model] +Pos=60,60 +Size=276,222 +Collapsed=0 [Table][0x3D1A1C69,2] RefScale=13 @@ -85,57 +84,70 @@ Column 1 Weight=1.0000 [Table][0x6925898D,2] RefScale=13 -Column 0 Width=216 +Column 0 Width=217 Column 1 Weight=1.0000 [Table][0xE847EDF4,2] RefScale=13 -Column 0 Width=99 +Column 0 Width=140 Column 1 Weight=1.0000 [Table][0x182B970D,2] RefScale=13 -Column 0 Width=99 -Column 1 Weight=-nan(ind) +Column 0 Width=168 +Column 1 Weight=1.0000 [Table][0x8556BC1A,2] RefScale=13 -Column 0 Width=99 -Column 1 Weight=-nan(ind) +Column 0 Width=175 +Column 1 Weight=1.0000 + +[Table][0x51A78E48,2] +RefScale=13 +Column 0 Weight=1.0000 +Column 1 Width=44 + +[Table][0xB6D16E5C,3] +RefScale=13 +Column 0 Weight=0.6969 +Column 1 Width=56 +Column 2 Weight=0.3031 [Table][0x94CE371A,2] RefScale=13 -Column 0 Width=99 +Column 0 Width=63 Column 1 Weight=1.0000 [Table][0x5A9ED35A,2] RefScale=13 -Column 0 Width=99 +Column 0 Width=56 Column 1 Weight=1.0000 -[Table][0x05A9070B,4] -RefScale=13 -Column 0 Width=139 -Column 1 Width=59 -Column 2 Width=149 -Column 3 Weight=1.0000 - [Table][0x5806762E,2] RefScale=13 -Column 0 Width=100 +Column 0 Width=84 Column 1 Weight=1.0000 +[Table][0x05A9070B,4] +RefScale=13 +Column 0 Width=140 +Column 1 Width=60 +Column 2 Width=150 +Column 3 Weight=1.0000 + [Docking][Data] -DockSpace ID=0x08BD597D Pos=128,95 Size=2304,1273 CentralNode=1 +DockSpace ID=0x1B7E3B2F Pos=128,95 Size=2304,1273 Split=Y Selected=0xC55F7288 + DockNode ID=0x0000000B Parent=0x1B7E3B2F SizeRef=2304,1049 CentralNode=1 Selected=0xC55F7288 + DockNode ID=0x0000000C Parent=0x1B7E3B2F SizeRef=2304,222 Selected=0x4B58EA58 DockSpace ID=0x4713F8A8 Window=0xFBC4AE05 Pos=128,95 Size=2304,1273 Split=X Selected=0x1C1AF642 - DockNode ID=0x00000007 Parent=0x4713F8A8 SizeRef=437,1273 Split=Y Selected=0xF995F4A5 - DockNode ID=0x00000009 Parent=0x00000007 SizeRef=437,687 Selected=0xF995F4A5 - DockNode ID=0x0000000A Parent=0x00000007 SizeRef=437,584 Selected=0x02B8E2DB - DockNode ID=0x00000008 Parent=0x4713F8A8 SizeRef=1865,1273 Split=X - DockNode ID=0x00000003 Parent=0x00000008 SizeRef=1850,1273 Split=Y - DockNode ID=0x00000001 Parent=0x00000003 SizeRef=2304,907 CentralNode=1 Selected=0x1C1AF642 - DockNode ID=0x00000002 Parent=0x00000003 SizeRef=2304,364 Selected=0x81DECE6A - DockNode ID=0x00000004 Parent=0x00000008 SizeRef=452,1273 Split=Y Selected=0x57A55B3F - DockNode ID=0x00000005 Parent=0x00000004 SizeRef=452,633 Selected=0x70CE1A73 - DockNode ID=0x00000006 Parent=0x00000004 SizeRef=452,638 Selected=0x57A55B3F + DockNode ID=0x00000007 Parent=0x4713F8A8 SizeRef=462,1273 Split=Y Selected=0x02B8E2DB + DockNode ID=0x00000009 Parent=0x00000007 SizeRef=462,492 Selected=0xF995F4A5 + DockNode ID=0x0000000A Parent=0x00000007 SizeRef=462,779 Selected=0x02B8E2DB + DockNode ID=0x00000008 Parent=0x4713F8A8 SizeRef=1840,1273 Split=X + DockNode ID=0x00000001 Parent=0x00000008 SizeRef=1440,1273 Split=Y Selected=0x1C1AF642 + DockNode ID=0x00000003 Parent=0x00000001 SizeRef=1904,834 CentralNode=1 Selected=0x1C1AF642 + DockNode ID=0x00000004 Parent=0x00000001 SizeRef=1904,437 Selected=0x81DECE6A + DockNode ID=0x00000002 Parent=0x00000008 SizeRef=398,1273 Split=Y Selected=0x70CE1A73 + DockNode ID=0x00000005 Parent=0x00000002 SizeRef=398,804 Selected=0x70CE1A73 + DockNode ID=0x00000006 Parent=0x00000002 SizeRef=398,467 Selected=0x57A55B3F From 68aea1f92a4ec2624650af9114121cc5be1eef3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20P=C3=A9ter?= Date: Wed, 24 Jun 2026 12:21:36 +0200 Subject: [PATCH 04/51] Updated submodules. Fix ImVec2 math operator compilation conflict with ImGui 1.90+ --- .gitmodules | 4 +-- External/IconFontCppHeaders | 2 +- External/vcpkg | 2 +- SynapseEngine/Editor/Manager/GuiManager.cpp | 32 ++++++++++++------- SynapseEngine/Editor/Manager/GuiManager.h | 1 - SynapseEngine/Editor/Synapse.cpp | 6 ++-- .../ContentBrowser/ContentBrowserView.cpp | 6 ++-- .../Editor/View/MainMenu/MainMenuView.cpp | 28 ++++++++++++++++ .../Editor/View/MainMenu/MainMenuView.h | 25 +-------------- .../View/MaterialGraph/MaterialGraphView.cpp | 2 +- .../Editor/View/Viewport/ViewportView.cpp | 4 +-- SynapseEngine/Editor/Widgets/CardWidget.cpp | 4 +-- .../Editor/Widgets/Vector3Widget.cpp | 2 +- SynapseEngine/imgui.ini | 32 +++++++++---------- SynapseEngine/xmake.lua | 3 +- 15 files changed, 85 insertions(+), 68 deletions(-) diff --git a/.gitmodules b/.gitmodules index 9d652b94..ffacd728 100644 --- a/.gitmodules +++ b/.gitmodules @@ -8,8 +8,8 @@ path = External/ImGuiFileDialog url = https://github.com/aiekick/ImGuiFileDialog [submodule "External/imgui-node-editor"] - path = External/imgui-node-editor - url = https://github.com/thedmd/imgui-node-editor.git + path = External/imgui-node-editor + url = https://https://github.com/TamasPetii/imgui-node-editor [submodule "External/IconFontCppHeaders"] path = External/IconFontCppHeaders url = https://github.com/juliettef/IconFontCppHeaders.git diff --git a/External/IconFontCppHeaders b/External/IconFontCppHeaders index 3ee7f3d2..210b5a39 160000 --- a/External/IconFontCppHeaders +++ b/External/IconFontCppHeaders @@ -1 +1 @@ -Subproject commit 3ee7f3d295ae773c0046db8d7b89b886eb2526de +Subproject commit 210b5a399a64270674560d633638952d1e8d804d diff --git a/External/vcpkg b/External/vcpkg index 64e1fbee..930ecc42 160000 --- a/External/vcpkg +++ b/External/vcpkg @@ -1 +1 @@ -Subproject commit 64e1fbee7d9f40eab5d112aaff648c4dcffe9e47 +Subproject commit 930ecc42b512b564571d767f70775d284a6fa307 diff --git a/SynapseEngine/Editor/Manager/GuiManager.cpp b/SynapseEngine/Editor/Manager/GuiManager.cpp index 6ebf5759..c45307fa 100644 --- a/SynapseEngine/Editor/Manager/GuiManager.cpp +++ b/SynapseEngine/Editor/Manager/GuiManager.cpp @@ -31,11 +31,25 @@ namespace Syn { ImGui_ImplGlfw_InitForVulkan(window, false); - VkDescriptorPoolSize poolSizes[] = { { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1000 } }; + VkDescriptorPoolSize poolSizes[] = + { + { VK_DESCRIPTOR_TYPE_SAMPLER, 1000 }, + { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1000 }, + { VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1000 }, + { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1000 }, + { VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 1000 }, + { VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, 1000 }, + { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1000 }, + { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1000 }, + { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1000 }, + { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC, 1000 }, + { VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, 1000 } + }; + VkDescriptorPoolCreateInfo poolInfo = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO }; poolInfo.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT; - poolInfo.maxSets = 1000; - poolInfo.poolSizeCount = 1; + poolInfo.maxSets = 1000 * IM_ARRAYSIZE(poolSizes); + poolInfo.poolSizeCount = (uint32_t)IM_ARRAYSIZE(poolSizes); poolInfo.pPoolSizes = poolSizes; vkCreateDescriptorPool(device, &poolInfo, nullptr, &_imguiPool); @@ -48,11 +62,11 @@ namespace Syn { init_info.DescriptorPool = _imguiPool; init_info.MinImageCount = imageCount; init_info.ImageCount = imageCount; - init_info.MSAASamples = VK_SAMPLE_COUNT_1_BIT; init_info.UseDynamicRendering = true; - init_info.PipelineRenderingCreateInfo = { .sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO }; - init_info.PipelineRenderingCreateInfo.colorAttachmentCount = 1; - init_info.PipelineRenderingCreateInfo.pColorAttachmentFormats = &_colorFormat; + init_info.PipelineInfoMain.MSAASamples = VK_SAMPLE_COUNT_1_BIT; + init_info.PipelineInfoMain.PipelineRenderingCreateInfo = { .sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO }; + init_info.PipelineInfoMain.PipelineRenderingCreateInfo.colorAttachmentCount = 1; + init_info.PipelineInfoMain.PipelineRenderingCreateInfo.pColorAttachmentFormats = &_colorFormat; ImGui_ImplVulkan_LoadFunctions(VK_API_VERSION_1_4, [](const char* function_name, void* user_data) { return vkGetInstanceProcAddr(reinterpret_cast(user_data), function_name); @@ -303,8 +317,4 @@ namespace Syn { colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f); colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.35f); } - - void GuiManager::CreateFontTexture() { - ImGui_ImplVulkan_CreateFontsTexture(); - } } \ No newline at end of file diff --git a/SynapseEngine/Editor/Manager/GuiManager.h b/SynapseEngine/Editor/Manager/GuiManager.h index 202a8a7e..88e8319d 100644 --- a/SynapseEngine/Editor/Manager/GuiManager.h +++ b/SynapseEngine/Editor/Manager/GuiManager.h @@ -35,7 +35,6 @@ namespace Syn { GuiTextureManager* GetTextureManager() const { return _textureManager.get(); } IFileDialogApi* GetFileDialog() const { return _fileDialog.get(); } - void CreateFontTexture(); void SetWorkspace(EditorWorkspace workspace) { _currentWorkspace = workspace; } EditorWorkspace GetWorkspace() const { return _currentWorkspace; } diff --git a/SynapseEngine/Editor/Synapse.cpp b/SynapseEngine/Editor/Synapse.cpp index cfba5c59..5c9525fe 100644 --- a/SynapseEngine/Editor/Synapse.cpp +++ b/SynapseEngine/Editor/Synapse.cpp @@ -89,9 +89,11 @@ void Synapse::OnInit() { ); ImGuiIO& io = ImGui::GetIO(); - io.Fonts->AddFontDefault(); + ImFontConfig defaultConfig; + defaultConfig.SizePixels = 13.0f; + io.Fonts->AddFontDefault(&defaultConfig); + _iconManager->InitializeFontAwesome(io, Syn::PathUtils::GetAbsolutePathString(FONT_PATH), 16.0f); - _guiManager->CreateFontTexture(); _iconManager->LoadEngineIcons(Syn::PathUtils::GetAbsolutePathString(ICON_PATH)); std::string absoluteAssetsPath = std::filesystem::absolute(ASSET_PATH).generic_string(); diff --git a/SynapseEngine/Editor/View/ContentBrowser/ContentBrowserView.cpp b/SynapseEngine/Editor/View/ContentBrowser/ContentBrowserView.cpp index 32cfa734..4025e5c5 100644 --- a/SynapseEngine/Editor/View/ContentBrowser/ContentBrowserView.cpp +++ b/SynapseEngine/Editor/View/ContentBrowser/ContentBrowserView.cpp @@ -47,7 +47,7 @@ namespace Syn { float treeHeight = mainContentBottomY - ImGui::GetCursorScreenPos().y - 12.0f; if (treeHeight < 50.0f) treeHeight = 50.0f; - ImGui::BeginChild("FolderTreeScroll", ImVec2(0, treeHeight), ImGuiChildFlags_Borders, ImGuiWindowFlags_AlwaysUseWindowPadding); + ImGui::BeginChild("FolderTreeScroll", ImVec2(0, treeHeight), ImGuiChildFlags_Borders | ImGuiChildFlags_AlwaysUseWindowPadding, 0); RenderFolderTree(vm, state); ImGui::EndChild(); @@ -86,7 +86,7 @@ namespace Syn { float gridHeight = mainContentBottomY - ImGui::GetCursorScreenPos().y - 12.0f; if (gridHeight < 50.0f) gridHeight = 50.0f; - ImGui::BeginChild("ContentGridScroll", ImVec2(0, gridHeight), ImGuiChildFlags_Borders, ImGuiWindowFlags_AlwaysUseWindowPadding); + ImGui::BeginChild("ContentGridScroll", ImVec2(0, gridHeight), ImGuiChildFlags_Borders | ImGuiChildFlags_AlwaysUseWindowPadding, 0); RenderContentArea(vm, state); ImGui::EndChild(); @@ -272,7 +272,7 @@ namespace Syn { } ImVec2 itemMin = ImGui::GetItemRectMin(); - ImGui::SetItemAllowOverlap(); + ImGui::SetNextItemAllowOverlap(); ImTextureID iconID = GetIconForEntry(entry); if (iconID) { diff --git a/SynapseEngine/Editor/View/MainMenu/MainMenuView.cpp b/SynapseEngine/Editor/View/MainMenu/MainMenuView.cpp index f9efde2a..0e563c42 100644 --- a/SynapseEngine/Editor/View/MainMenu/MainMenuView.cpp +++ b/SynapseEngine/Editor/View/MainMenu/MainMenuView.cpp @@ -1 +1,29 @@ #include "MainMenuView.h" + +namespace Syn +{ + void MainMenuView::Draw(MainMenuViewModel& vm) { + + if (ImGui::BeginMainMenuBar()) { + + if (ImGui::BeginMenu("File")) { + + if (ImGui::MenuItem("New Scene", "Ctrl+N")) { + vm.Dispatch(NewSceneIntent{}); + } + + if (ImGui::MenuItem("Load Scene...", "Ctrl+O")) { + vm.Dispatch(LoadSceneIntent{}); + } + + if (ImGui::MenuItem("Save Scene", "Ctrl+S")) { + vm.Dispatch(SaveSceneIntent{}); + } + + ImGui::EndMenu(); + } + + ImGui::EndMainMenuBar(); + } + } +} \ No newline at end of file diff --git a/SynapseEngine/Editor/View/MainMenu/MainMenuView.h b/SynapseEngine/Editor/View/MainMenu/MainMenuView.h index 77e9a761..0b293cce 100644 --- a/SynapseEngine/Editor/View/MainMenu/MainMenuView.h +++ b/SynapseEngine/Editor/View/MainMenu/MainMenuView.h @@ -6,29 +6,6 @@ namespace Syn { class MainMenuView : public IView { public: - void Draw(MainMenuViewModel& vm) override { - - if (ImGui::BeginMainMenuBar()) { - - if (ImGui::BeginMenu("File")) { - - if (ImGui::MenuItem("New Scene", "Ctrl+N")) { - vm.Dispatch(NewSceneIntent{}); - } - - if (ImGui::MenuItem("Load Scene...", "Ctrl+O")) { - vm.Dispatch(LoadSceneIntent{}); - } - - if (ImGui::MenuItem("Save Scene", "Ctrl+S")) { - vm.Dispatch(SaveSceneIntent{}); - } - - ImGui::EndMenu(); - } - - ImGui::EndMainMenuBar(); - } - } + void Draw(MainMenuViewModel& vm) override; }; } \ No newline at end of file diff --git a/SynapseEngine/Editor/View/MaterialGraph/MaterialGraphView.cpp b/SynapseEngine/Editor/View/MaterialGraph/MaterialGraphView.cpp index 00c87ca5..e7407164 100644 --- a/SynapseEngine/Editor/View/MaterialGraph/MaterialGraphView.cpp +++ b/SynapseEngine/Editor/View/MaterialGraph/MaterialGraphView.cpp @@ -1,6 +1,6 @@ #include "MaterialGraphView.h" -#include #include +#include namespace ed = ax::NodeEditor; diff --git a/SynapseEngine/Editor/View/Viewport/ViewportView.cpp b/SynapseEngine/Editor/View/Viewport/ViewportView.cpp index c3805347..1977645b 100644 --- a/SynapseEngine/Editor/View/Viewport/ViewportView.cpp +++ b/SynapseEngine/Editor/View/Viewport/ViewportView.cpp @@ -67,7 +67,7 @@ namespace Syn { ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 6.0f); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8.0f, 6.0f)); - if (ImGui::BeginChild("##SimulationToolbar", ImVec2(toolbarWidth, toolbarHeight), false, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_AlwaysUseWindowPadding)) { + if (ImGui::BeginChild("##SimulationToolbar", ImVec2(toolbarWidth, toolbarHeight), ImGuiChildFlags_AlwaysUseWindowPadding, ImGuiWindowFlags_NoScrollbar)) { ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(4.0f, 6.0f)); @@ -121,7 +121,7 @@ namespace Syn { ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 6.0f); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(4.0f, 6.0f)); - if (ImGui::BeginChild("##FloatingToolbar", ImVec2(toolbarWidth, toolbarHeight), false, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_AlwaysUseWindowPadding)) { + if (ImGui::BeginChild("##FloatingToolbar", ImVec2(toolbarWidth, toolbarHeight), ImGuiChildFlags_AlwaysUseWindowPadding, ImGuiWindowFlags_NoScrollbar)) { ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(4.0f, 6.0f)); diff --git a/SynapseEngine/Editor/Widgets/CardWidget.cpp b/SynapseEngine/Editor/Widgets/CardWidget.cpp index 0e4a8a57..b417e7c0 100644 --- a/SynapseEngine/Editor/Widgets/CardWidget.cpp +++ b/SynapseEngine/Editor/Widgets/CardWidget.cpp @@ -9,7 +9,7 @@ namespace Syn::UI { ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 6.0f); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(12.0f, 12.0f)); - ImGuiChildFlags childFlags = ImGuiChildFlags_Borders; + ImGuiChildFlags childFlags = ImGuiChildFlags_Borders | ImGuiChildFlags_AlwaysUseWindowPadding; if (isOpen) { childFlags |= ImGuiChildFlags_AutoResizeY; @@ -20,7 +20,7 @@ namespace Syn::UI { float height = isOpen ? 0.0f : closedHeight; std::string childId = std::string("##Card_") + label; - ImGui::BeginChild(childId.c_str(), ImVec2(0, height), childFlags, ImGuiWindowFlags_AlwaysUseWindowPadding | ImGuiWindowFlags_NoScrollbar); + ImGui::BeginChild(childId.c_str(), ImVec2(0, height), childFlags, ImGuiWindowFlags_NoScrollbar); ImVec2 startPos = ImGui::GetCursorPos(); float availX = ImGui::GetContentRegionAvail().x; diff --git a/SynapseEngine/Editor/Widgets/Vector3Widget.cpp b/SynapseEngine/Editor/Widgets/Vector3Widget.cpp index 4276f6fe..49aa50b0 100644 --- a/SynapseEngine/Editor/Widgets/Vector3Widget.cpp +++ b/SynapseEngine/Editor/Widgets/Vector3Widget.cpp @@ -12,7 +12,7 @@ namespace Syn::UI { ImGui::PushID(id.c_str()); - float lineHeight = GImGui->Font->FontSize + GImGui->Style.FramePadding.y * 2.0f; + float lineHeight = ImGui::GetFontSize() + GImGui->Style.FramePadding.y * 2.0f; ImVec2 buttonSize = { lineHeight + 3.0f, lineHeight }; float availWidth = ImGui::GetContentRegionAvail().x; diff --git a/SynapseEngine/imgui.ini b/SynapseEngine/imgui.ini index dc6bf48b..903d18c0 100644 --- a/SynapseEngine/imgui.ini +++ b/SynapseEngine/imgui.ini @@ -61,16 +61,16 @@ Pos=0,23 Size=1920,986 Collapsed=0 -[Window][###Content_Scene] -Pos=407,705 -Size=1059,304 +[Window][Content_Scene] +Pos=439,932 +Size=1411,364 Collapsed=0 DockId=0x00000002,1 [Table][0x51A78E48,2] RefScale=13 Column 0 Weight=1.0000 -Column 1 Width=48 +Column 1 Width=47 [Table][0xB6D16E5C,3] RefScale=13 @@ -80,44 +80,44 @@ Column 2 Weight=0.2443 [Table][0x3D1A1C69,2] RefScale=13 -Column 0 Width=100 +Column 0 Width=147 Column 1 Weight=1.0000 [Table][0x6925898D,2] RefScale=13 -Column 0 Width=217 +Column 0 Width=216 Column 1 Weight=1.0000 [Table][0xE847EDF4,2] RefScale=13 -Column 0 Width=100 +Column 0 Width=99 Column 1 Weight=1.0000 [Table][0x182B970D,2] RefScale=13 -Column 0 Width=100 +Column 0 Width=99 Column 1 Weight=-nan(ind) [Table][0x8556BC1A,2] RefScale=13 -Column 0 Width=100 +Column 0 Width=99 Column 1 Weight=-nan(ind) [Table][0x94CE371A,2] -RefScale=13 -Column 0 Width=100 +RefScale=16 +Column 0 Width=123 Column 1 Weight=1.0000 [Table][0x5A9ED35A,2] -RefScale=13 -Column 0 Width=100 +RefScale=16 +Column 0 Width=123 Column 1 Weight=1.0000 [Table][0x05A9070B,4] RefScale=13 -Column 0 Width=140 -Column 1 Width=60 -Column 2 Width=150 +Column 0 Width=139 +Column 1 Width=59 +Column 2 Width=149 Column 3 Weight=1.0000 [Table][0x5806762E,2] diff --git a/SynapseEngine/xmake.lua b/SynapseEngine/xmake.lua index e541db7a..11f15ab0 100644 --- a/SynapseEngine/xmake.lua +++ b/SynapseEngine/xmake.lua @@ -58,7 +58,8 @@ add_defines( "JPH_USE_LZCNT", "JPH_USE_TZCNT", "JPH_USE_F16C", - "JPH_USE_FMADD" + "JPH_USE_FMADD", + "IMGUI_DISABLE_MATH_OPERATORS" ) if is_plat("windows") then From ea25f72b47d9c472834351588fbbb175db0f9e94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20P=C3=A9ter?= Date: Wed, 24 Jun 2026 12:35:45 +0200 Subject: [PATCH 05/51] Update imgui-node-editor submodule to include math operator fix --- .gitmodules | 2 +- External/imgui-node-editor | 2 +- .../Engine/Scene/Source/Procedural/test_config.json | 8 ++++---- SynapseEngine/imgui.ini | 8 ++++---- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.gitmodules b/.gitmodules index ffacd728..81590dcc 100644 --- a/.gitmodules +++ b/.gitmodules @@ -9,7 +9,7 @@ url = https://github.com/aiekick/ImGuiFileDialog [submodule "External/imgui-node-editor"] path = External/imgui-node-editor - url = https://https://github.com/TamasPetii/imgui-node-editor + url = https://github.com/TamasPetii/imgui-node-editor [submodule "External/IconFontCppHeaders"] path = External/IconFontCppHeaders url = https://github.com/juliettef/IconFontCppHeaders.git diff --git a/External/imgui-node-editor b/External/imgui-node-editor index 021aa0ea..6975a131 160000 --- a/External/imgui-node-editor +++ b/External/imgui-node-editor @@ -1 +1 @@ -Subproject commit 021aa0ea4da13fed864bafb2a92d4c5205076866 +Subproject commit 6975a1318873c52e43779b41e59dcb8022ff4922 diff --git a/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json b/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json index 9bafd2d3..bfabd175 100644 --- a/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json +++ b/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json @@ -15,10 +15,10 @@ }, "entities": { "animated_characters": 1000, - "static_geometry": 25000, - "physics_boxes": 500, - "physics_spheres": 500, - "physics_capsules": 500 + "static_geometry": 1000000, + "physics_boxes": 1000, + "physics_spheres": 1000, + "physics_capsules": 1000 }, "lights": { "directional_count": 1, diff --git a/SynapseEngine/imgui.ini b/SynapseEngine/imgui.ini index 903d18c0..2d4f4c80 100644 --- a/SynapseEngine/imgui.ini +++ b/SynapseEngine/imgui.ini @@ -104,13 +104,13 @@ Column 0 Width=99 Column 1 Weight=-nan(ind) [Table][0x94CE371A,2] -RefScale=16 -Column 0 Width=123 +RefScale=13 +Column 0 Width=99 Column 1 Weight=1.0000 [Table][0x5A9ED35A,2] -RefScale=16 -Column 0 Width=123 +RefScale=13 +Column 0 Width=99 Column 1 Weight=1.0000 [Table][0x05A9070B,4] From 323b4aabe01300872068f2d1b984b71228ed4152 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20P=C3=A9ter?= Date: Fri, 26 Jun 2026 14:49:58 +0200 Subject: [PATCH 06/51] Resolved ssao texture compile errors, and imguizmo works!! --- SynapseEngine/Editor/Manager/GuiManager.cpp | 3 ++- .../Editor/View/Viewport/ViewportView.cpp | 22 ++++++++++++++----- SynapseEngine/Editor/Widgets/PropertyGrid.cpp | 2 +- .../Scene/Source/Procedural/test_config.json | 2 +- .../Engine/Shaders/Passes/Ssao/Ssao.comp | 4 ++-- SynapseEngine/Synapse_MaterialGraph.json | 2 +- 6 files changed, 23 insertions(+), 12 deletions(-) diff --git a/SynapseEngine/Editor/Manager/GuiManager.cpp b/SynapseEngine/Editor/Manager/GuiManager.cpp index c45307fa..0206ca25 100644 --- a/SynapseEngine/Editor/Manager/GuiManager.cpp +++ b/SynapseEngine/Editor/Manager/GuiManager.cpp @@ -1,9 +1,9 @@ #include "GuiManager.h" #include #include - #include #include +#include #include "GuiTextureManager.h" #include #include "Engine/ServiceLocator.h" @@ -104,6 +104,7 @@ namespace Syn { ImGui_ImplVulkan_NewFrame(); ImGui_ImplGlfw_NewFrame(); ImGui::NewFrame(); + ImGuizmo::BeginFrame(); } void GuiManager::UpdateAndDraw() { diff --git a/SynapseEngine/Editor/View/Viewport/ViewportView.cpp b/SynapseEngine/Editor/View/Viewport/ViewportView.cpp index 1977645b..f2bbbb98 100644 --- a/SynapseEngine/Editor/View/Viewport/ViewportView.cpp +++ b/SynapseEngine/Editor/View/Viewport/ViewportView.cpp @@ -30,7 +30,9 @@ namespace Syn { if (viewportPanelSize.y <= 0.0f) viewportPanelSize.y = 1.0f; if (state.textureId && !isResizing) { + ImGui::GetWindowDrawList()->AddCallback(ImGui::GetPlatformIO().DrawCallback_SetSamplerNearest, nullptr); ImGui::Image(state.textureId, viewportPanelSize); + ImGui::GetWindowDrawList()->AddCallback(ImGui::GetPlatformIO().DrawCallback_SetSamplerLinear, nullptr); } else { ImGui::Dummy(viewportPanelSize); @@ -43,14 +45,22 @@ namespace Syn { RenderFloatingToolbar(vm, state, imageStartPos, viewportPanelSize); RenderSimulationToolbar(vm, state, imageStartPos, viewportPanelSize); - if (isImageHovered && ImGui::IsMouseClicked(ImGuiMouseButton_Left) && !ImGuizmo::IsOver() && !ImGui::IsAnyItemHovered()) { - ImVec2 mousePos = ImGui::GetMousePos(); - uint32_t x = static_cast(mousePos.x - vMin.x); - uint32_t y = static_cast(mousePos.y - vMin.y); - vm.Dispatch(PickEntityIntent{ x, y }); + DrawGizmo(vm, state, imageStartPos, viewportPanelSize); + + if (!ImGuizmo::IsUsing()) { + + if (isImageHovered && ImGui::IsMouseClicked(ImGuiMouseButton_Left) && !ImGuizmo::IsOver() && !ImGui::IsAnyItemHovered()) { + ImVec2 mousePos = ImGui::GetMousePos(); + uint32_t x = static_cast(mousePos.x - imageStartPos.x); + uint32_t y = static_cast(mousePos.y - imageStartPos.y); + vm.Dispatch(PickEntityIntent{ x, y }); + } + + if (isImageHovered && ImGui::IsMouseDown(ImGuiMouseButton_Right)) { + + } } - DrawGizmo(vm, state, imageStartPos, viewportPanelSize); HandleShortcuts(vm); ImGui::End(); diff --git a/SynapseEngine/Editor/Widgets/PropertyGrid.cpp b/SynapseEngine/Editor/Widgets/PropertyGrid.cpp index 69e139ce..3308d96b 100644 --- a/SynapseEngine/Editor/Widgets/PropertyGrid.cpp +++ b/SynapseEngine/Editor/Widgets/PropertyGrid.cpp @@ -10,7 +10,7 @@ namespace Syn::UI { bool isOpen = ImGui::BeginTable(id, 2, flags); if (isOpen) { - ImGui::TableSetupColumn("Label", ImGuiTableColumnFlags_WidthFixed, 100.0f); + ImGui::TableSetupColumn("Label", ImGuiTableColumnFlags_WidthFixed); ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch); } else { diff --git a/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json b/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json index bfabd175..38515bad 100644 --- a/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json +++ b/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json @@ -15,7 +15,7 @@ }, "entities": { "animated_characters": 1000, - "static_geometry": 1000000, + "static_geometry": 10000, "physics_boxes": 1000, "physics_spheres": 1000, "physics_capsules": 1000 diff --git a/SynapseEngine/Engine/Shaders/Passes/Ssao/Ssao.comp b/SynapseEngine/Engine/Shaders/Passes/Ssao/Ssao.comp index 2f584e01..fe424798 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Ssao/Ssao.comp +++ b/SynapseEngine/Engine/Shaders/Passes/Ssao/Ssao.comp @@ -46,8 +46,8 @@ void main() { vec2 offsetV = vec2(0.0, 1.0 / float(ctx.screenHeight)); // Reconstruct view-space normal from depth gradients - vec3 viewPosRight = GetViewPosFromLinearZ(uv + offsetU, textureOffset(inHizPyramid, uv, ivec2(1, 0)).x, camera); - vec3 viewPosDown = GetViewPosFromLinearZ(uv + offsetV, textureOffset(inHizPyramid, uv, ivec2(0, 1)).x, camera); + vec3 viewPosRight = GetViewPosFromLinearZ(uv + offsetU, textureLodOffset(inHizPyramid, uv, 0.0, ivec2(1, 0)).x, camera); + vec3 viewPosDown = GetViewPosFromLinearZ(uv + offsetV, textureLodOffset(inHizPyramid, uv, 0.0, ivec2(0, 1)).x, camera); vec3 viewNormal = normalize(cross(viewPosRight - viewPos, viewPos - viewPosDown)); vec2 noiseScale = vec2(ctx.screenWidth / pc.noiseTextureWidth, ctx.screenHeight / pc.noiseTextureHeight); diff --git a/SynapseEngine/Synapse_MaterialGraph.json b/SynapseEngine/Synapse_MaterialGraph.json index 314f09cc..a72cb7f2 100644 --- a/SynapseEngine/Synapse_MaterialGraph.json +++ b/SynapseEngine/Synapse_MaterialGraph.json @@ -1 +1 @@ -{"nodes":{"node:1":{"location":{"x":0,"y":0}},"node:10":{"location":{"x":0,"y":0}},"node:12":{"location":{"x":0,"y":0}},"node:14":{"location":{"x":0,"y":0}},"node:16":{"location":{"x":0,"y":0}},"node:18":{"location":{"x":0,"y":0}},"node:20":{"location":{"x":0,"y":0}},"node:22":{"location":{"x":0,"y":0}},"node:24":{"location":{"x":0,"y":0}},"node:26":{"location":{"x":0,"y":0}},"node:28":{"location":{"x":0,"y":0}},"node:8":{"location":{"x":0,"y":0}}},"selection":["node:28"],"view":{"scroll":{"x":-0.000213623046875,"y":0},"visible_rect":{"max":{"x":2303.999755859375,"y":1273},"min":{"x":-0.000213623046875,"y":0}},"zoom":1}} \ No newline at end of file +{"nodes":{"node:1":{"location":{"x":0,"y":0}},"node:10":{"location":{"x":0,"y":0}},"node:12":{"location":{"x":0,"y":0}},"node:14":{"location":{"x":0,"y":0}},"node:16":{"location":{"x":0,"y":0}},"node:18":{"location":{"x":0,"y":0}},"node:20":{"location":{"x":0,"y":0}},"node:22":{"location":{"x":0,"y":0}},"node:24":{"location":{"x":0,"y":0}},"node:26":{"location":{"x":0,"y":0}},"node:28":{"location":{"x":0,"y":0}},"node:8":{"location":{"x":0,"y":0}}},"selection":null,"view":{"scroll":{"x":-731.410400390625,"y":-323.9666748046875},"visible_rect":{"max":{"x":1048.39306640625,"y":468.022216796875},"min":{"x":-487.60693359375,"y":-215.977783203125}},"zoom":1.5}} \ No newline at end of file From 2fbcdc557257eacbe5180ef0554cb864caf3ee92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20P=C3=A9ter?= Date: Fri, 26 Jun 2026 15:11:38 +0200 Subject: [PATCH 07/51] Imgui math operator define disabled. --- SynapseEngine/xmake.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/SynapseEngine/xmake.lua b/SynapseEngine/xmake.lua index 11f15ab0..e541db7a 100644 --- a/SynapseEngine/xmake.lua +++ b/SynapseEngine/xmake.lua @@ -58,8 +58,7 @@ add_defines( "JPH_USE_LZCNT", "JPH_USE_TZCNT", "JPH_USE_F16C", - "JPH_USE_FMADD", - "IMGUI_DISABLE_MATH_OPERATORS" + "JPH_USE_FMADD" ) if is_plat("windows") then From c6fda70e7d80ec996a82c80483dd464fc883f0a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20P=C3=A9ter?= Date: Fri, 26 Jun 2026 17:57:52 +0200 Subject: [PATCH 08/51] Texture hierarchy, viewer graph and properties window and mvi layers implemented. --- .../Editor/EditorApi/EditorContext.cpp | 2 + .../Editor/EditorApi/EditorContext.h | 3 + .../Editor/EditorApi/Impl/RenderApiImpl.cpp | 1 + .../Editor/EditorApi/Impl/TextureApiImpl.cpp | 75 ++++++++++ .../Editor/EditorApi/Impl/TextureApiImpl.h | 26 ++++ SynapseEngine/Editor/Manager/EditorIcons.h | 2 + .../View/TextureGraph/TextureGraphView.cpp | 92 ++++++++++++ .../View/TextureGraph/TextureGraphView.h | 26 ++++ .../TextureHierarchy/TextureHierarchyView.cpp | 133 ++++++++++++++++++ .../TextureHierarchy/TextureHierarchyView.h | 17 +++ .../TexturePropertiesView.cpp | 66 +++++++++ .../TextureProperties/TexturePropertiesView.h | 16 +++ .../Editor/Workspace/TextureWorkspace.cpp | 31 +++- SynapseEngine/EditorCore/Api/ITextureApi.h | 27 ++++ .../TextureGraph/TextureGraphIntent.cpp | 1 + .../TextureGraph/TextureGraphIntent.h | 10 ++ .../TextureGraph/TextureGraphState.cpp | 1 + .../TextureGraph/TextureGraphState.h | 21 +++ .../TextureGraph/TextureGraphViewModel.cpp | 36 +++++ .../TextureGraph/TextureGraphViewModel.h | 22 +++ .../TextureHierarchyIntent.cpp | 1 + .../TextureHierarchy/TextureHierarchyIntent.h | 21 +++ .../TextureHierarchyState.cpp | 1 + .../TextureHierarchy/TextureHierarchyState.h | 17 +++ .../TextureHierarchyViewModel.cpp | 70 +++++++++ .../TextureHierarchyViewModel.h | 26 ++++ .../TexturePropertiesIntent.cpp | 1 + .../TexturePropertiesIntent.h | 10 ++ .../TexturePropertiesState.cpp | 1 + .../TexturePropertiesState.h | 17 +++ .../TexturePropertiesViewModel.cpp | 36 +++++ .../TexturePropertiesViewModel.h | 20 +++ .../Scene/Source/Procedural/test_config.json | 18 +-- 33 files changed, 836 insertions(+), 11 deletions(-) create mode 100644 SynapseEngine/Editor/EditorApi/Impl/TextureApiImpl.cpp create mode 100644 SynapseEngine/Editor/EditorApi/Impl/TextureApiImpl.h create mode 100644 SynapseEngine/Editor/View/TextureGraph/TextureGraphView.cpp create mode 100644 SynapseEngine/Editor/View/TextureGraph/TextureGraphView.h create mode 100644 SynapseEngine/Editor/View/TextureHierarchy/TextureHierarchyView.cpp create mode 100644 SynapseEngine/Editor/View/TextureHierarchy/TextureHierarchyView.h create mode 100644 SynapseEngine/Editor/View/TextureProperties/TexturePropertiesView.cpp create mode 100644 SynapseEngine/Editor/View/TextureProperties/TexturePropertiesView.h create mode 100644 SynapseEngine/EditorCore/Api/ITextureApi.h create mode 100644 SynapseEngine/EditorCore/ViewModels/TextureGraph/TextureGraphIntent.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/TextureGraph/TextureGraphIntent.h create mode 100644 SynapseEngine/EditorCore/ViewModels/TextureGraph/TextureGraphState.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/TextureGraph/TextureGraphState.h create mode 100644 SynapseEngine/EditorCore/ViewModels/TextureGraph/TextureGraphViewModel.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/TextureGraph/TextureGraphViewModel.h create mode 100644 SynapseEngine/EditorCore/ViewModels/TextureHierarchy/TextureHierarchyIntent.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/TextureHierarchy/TextureHierarchyIntent.h create mode 100644 SynapseEngine/EditorCore/ViewModels/TextureHierarchy/TextureHierarchyState.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/TextureHierarchy/TextureHierarchyState.h create mode 100644 SynapseEngine/EditorCore/ViewModels/TextureHierarchy/TextureHierarchyViewModel.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/TextureHierarchy/TextureHierarchyViewModel.h create mode 100644 SynapseEngine/EditorCore/ViewModels/TextureProperties/TexturePropertiesIntent.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/TextureProperties/TexturePropertiesIntent.h create mode 100644 SynapseEngine/EditorCore/ViewModels/TextureProperties/TexturePropertiesState.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/TextureProperties/TexturePropertiesState.h create mode 100644 SynapseEngine/EditorCore/ViewModels/TextureProperties/TexturePropertiesViewModel.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/TextureProperties/TexturePropertiesViewModel.h diff --git a/SynapseEngine/Editor/EditorApi/EditorContext.cpp b/SynapseEngine/Editor/EditorApi/EditorContext.cpp index e9d84e4f..7b0a0596 100644 --- a/SynapseEngine/Editor/EditorApi/EditorContext.cpp +++ b/SynapseEngine/Editor/EditorApi/EditorContext.cpp @@ -12,6 +12,7 @@ #include "Impl/SelectionApiImpl.h" #include "Impl/PointLightApiImpl.h" #include "Impl/SpotLightApiImpl.h" +#include "Impl/TextureApiImpl.h" namespace Syn { EditorContext::EditorContext(Engine* engine, GuiTextureManager* textureManager) { @@ -30,6 +31,7 @@ namespace Syn { _settingsApi = std::make_unique(sm); _pointLightApi = std::make_unique(sm); _spotLightApi = std::make_unique(sm); + _textureApi = std::make_unique(engine->GetImageManager(), textureManager); } EditorContext::~EditorContext() = default; diff --git a/SynapseEngine/Editor/EditorApi/EditorContext.h b/SynapseEngine/Editor/EditorApi/EditorContext.h index d520b59c..6413b165 100644 --- a/SynapseEngine/Editor/EditorApi/EditorContext.h +++ b/SynapseEngine/Editor/EditorApi/EditorContext.h @@ -16,6 +16,7 @@ #include "EditorCore/Api/ISettingsApi.h" #include "EditorCore/Api/IPointLightApi.h" #include "EditorCore/Api/ISpotLightApi.h" +#include "EditorCore/Api/ITextureApi.h" namespace Syn { class EditorContext { @@ -36,6 +37,7 @@ namespace Syn { ISettingsApi* GetSettingsApi() const { return _settingsApi.get(); } IPointLightApi* GetPointLightApi() const { return _pointLightApi.get(); } ISpotLightApi* GetSpotLightApi() const { return _spotLightApi.get(); } + ITextureApi* GetTextureApi() const { return _textureApi.get(); } private: std::unique_ptr _selectionApi; std::unique_ptr _tagApi; @@ -50,5 +52,6 @@ namespace Syn { std::unique_ptr _directionLightApi; std::unique_ptr _pointLightApi; std::unique_ptr _spotLightApi; + std::unique_ptr _textureApi; }; } \ No newline at end of file diff --git a/SynapseEngine/Editor/EditorApi/Impl/RenderApiImpl.cpp b/SynapseEngine/Editor/EditorApi/Impl/RenderApiImpl.cpp index 968ebeab..5465597c 100644 --- a/SynapseEngine/Editor/EditorApi/Impl/RenderApiImpl.cpp +++ b/SynapseEngine/Editor/EditorApi/Impl/RenderApiImpl.cpp @@ -62,6 +62,7 @@ namespace Syn { _viewportTextures[cacheKey] = handle; } } + return _textureManager->GetImGuiTextureID(_viewportTextures[cacheKey]); } diff --git a/SynapseEngine/Editor/EditorApi/Impl/TextureApiImpl.cpp b/SynapseEngine/Editor/EditorApi/Impl/TextureApiImpl.cpp new file mode 100644 index 00000000..5c8e793e --- /dev/null +++ b/SynapseEngine/Editor/EditorApi/Impl/TextureApiImpl.cpp @@ -0,0 +1,75 @@ +#include "TextureApiImpl.h" +#include "Engine/Image/SamplerNames.h" +#include + +namespace Syn { + + std::vector TextureApiImpl::GetAllTextures() const { + if (!_imageManager) return {}; + + std::vector result; + auto paths = _imageManager->GetResourcePaths(); + + for (uint32_t i = 0; i < paths.size(); ++i) { + if (_imageManager->GetEntryState(i) == ResourceState::Ready) { + TextureItemData data; + data.id = i; + data.path = paths[i]; + + std::filesystem::path p(paths[i]); + data.name = p.filename().string(); + + result.push_back(data); + } + } + + return result; + } + + uint32_t TextureApiImpl::GetSelectedTexture() const { + return _selectedTexture; + } + + void TextureApiImpl::SetSelectedTexture(uint32_t id) { + _selectedTexture = id; + } + + bool TextureApiImpl::GetTextureData(uint32_t id, CpuTextureData& outData) const { + if (!_imageManager || id == INVALID_TEXTURE_ID) return false; + + auto resource = _imageManager->GetResource(id); + if (resource) { + outData = resource->cpuData; + return true; + } + + return false; + } + + uint64_t TextureApiImpl::GetVersion() const { + return _imageManager ? _imageManager->GetVersion() : 0; + } + + TextureHandle TextureApiImpl::GetTextureHandle(uint32_t id) { + if (!_imageManager || id == INVALID_TEXTURE_ID) { + return InvalidTextureHandle; + } + + if (_textureHandleCache.find(id) == _textureHandleCache.end()) { + auto resource = _imageManager->GetResource(id); + if (!resource || !resource->image) { + return InvalidTextureHandle; + } + + auto sampler = _imageManager->GetSampler(SamplerNames::LinearClampEdge); + TextureHandle handle = _guiTextureManager->RegisterTexture( + resource->image->GetView(), + sampler->Handle() + ); + + _textureHandleCache[id] = handle; + } + + return _guiTextureManager->GetImGuiTextureID(_textureHandleCache[id]); + } +} \ No newline at end of file diff --git a/SynapseEngine/Editor/EditorApi/Impl/TextureApiImpl.h b/SynapseEngine/Editor/EditorApi/Impl/TextureApiImpl.h new file mode 100644 index 00000000..604984a9 --- /dev/null +++ b/SynapseEngine/Editor/EditorApi/Impl/TextureApiImpl.h @@ -0,0 +1,26 @@ +#pragma once +#include "EditorCore/Api/ITextureApi.h" +#include "Engine/Image/ImageManager.h" +#include "Editor/Manager/GuiTextureManager.h" + +namespace Syn { + class TextureApiImpl : public ITextureApi { + public: + TextureApiImpl(ImageManager* imageManager, GuiTextureManager* guiTextureManager) + : _imageManager(imageManager), _guiTextureManager(guiTextureManager) {} + + std::vector GetAllTextures() const override; + + uint32_t GetSelectedTexture() const override; + void SetSelectedTexture(uint32_t id) override; + + bool GetTextureData(uint32_t id, CpuTextureData& outData) const override; + uint64_t GetVersion() const override; + TextureHandle GetTextureHandle(uint32_t id) override; + private: + ImageManager* _imageManager; + GuiTextureManager* _guiTextureManager; + uint32_t _selectedTexture = INVALID_TEXTURE_ID; + std::unordered_map _textureHandleCache; + }; +} \ No newline at end of file diff --git a/SynapseEngine/Editor/Manager/EditorIcons.h b/SynapseEngine/Editor/Manager/EditorIcons.h index 6f16a235..b9934f71 100644 --- a/SynapseEngine/Editor/Manager/EditorIcons.h +++ b/SynapseEngine/Editor/Manager/EditorIcons.h @@ -62,6 +62,8 @@ constexpr const char* ASSET_PATH = "Assets"; #define SYN_ICON_TERMINAL ICON_FA_TERMINAL #define SYN_ICON_LEVEL_DOWN_ALT ICON_FA_LEVEL_DOWN_ALT #define SYN_ICON_DRAW_POLYGON ICON_FA_DRAW_POLYGON +#define SYN_ICON_SYNC ICON_FA_SYNC +#define SYN_ICON_PROJECT_DIAGRAM ICON_FA_PROJECT_DIAGRAM // Workspace Labels #define SYN_WS_SCENE SYN_ICON_GLOBE " Scene" diff --git a/SynapseEngine/Editor/View/TextureGraph/TextureGraphView.cpp b/SynapseEngine/Editor/View/TextureGraph/TextureGraphView.cpp new file mode 100644 index 00000000..d39785c0 --- /dev/null +++ b/SynapseEngine/Editor/View/TextureGraph/TextureGraphView.cpp @@ -0,0 +1,92 @@ +#include "TextureGraphView.h" +#include "Editor/Manager/EditorIcons.h" +#include +#include +#include + +namespace ed = ax::NodeEditor; + +namespace Syn { + + TextureGraphView::TextureGraphView() { + ed::Config config; + config.SettingsFile = "Synapse_TextureGraph.json"; + _context = ed::CreateEditor(&config); + } + + TextureGraphView::~TextureGraphView() { + if (_context) ed::DestroyEditor(_context); + } + + TextureGraphView::TextureGraphView(TextureGraphView&& other) noexcept : _context(other._context) { + other._context = nullptr; + } + + TextureGraphView& TextureGraphView::operator=(TextureGraphView&& other) noexcept { + if (this != &other) { + if (_context) ed::DestroyEditor(_context); + _context = other._context; + other._context = nullptr; + } + return *this; + } + + void TextureGraphView::Draw(TextureGraphViewModel& vm) { + const auto& state = vm.GetState(); + + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); + ImGuiWindowFlags windowFlags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse; + + bool isVisible = ImGui::Begin(SYN_ICON_PROJECT_DIAGRAM " Texture Graph", nullptr, windowFlags); + ImGui::PopStyleVar(); + + if (isVisible) { + ed::SetCurrentEditor(_context); + + ImVec2 canvasSize = ImGui::GetContentRegionAvail(); + if (canvasSize.x <= 0.0f) canvasSize.x = 1.0f; + if (canvasSize.y <= 0.0f) canvasSize.y = 1.0f; + + ed::Begin("Texture Node Canvas", canvasSize); + + if (state.previewNode.isVisible) { + ed::BeginNode(ed::NodeId(1)); + + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.4f, 0.8f, 1.0f, 1.0f)); + ImGui::Text(SYN_ICON_IMAGE " %s", state.previewNode.name.empty() ? "Selected Texture" : state.previewNode.name.c_str()); + ImGui::PopStyleColor(); + ImGui::Dummy(ImVec2(0, 4.0f)); + + float maxPreviewSize = 512.0f; + ImVec2 imageSize(512.0f, 512.0f); + if (state.previewNode.width > 0 && state.previewNode.height > 0) { + float aspect = (float)state.previewNode.width / (float)state.previewNode.height; + if (aspect > 1.0f) { + imageSize.x = maxPreviewSize; + imageSize.y = maxPreviewSize / aspect; + } + else { + imageSize.y = maxPreviewSize; + imageSize.x = maxPreviewSize * aspect; + } + } + + if (state.previewNode.textureHandle != InvalidTextureHandle) { + ImGui::Image(state.previewNode.textureHandle, imageSize); + } + else { + ImGui::Button("No Preview\nAvailable", imageSize); + } + + ImGui::Dummy(ImVec2(0, 8.0f)); + + ed::EndNode(); + } + + ed::End(); + ed::SetCurrentEditor(nullptr); + } + + ImGui::End(); + } +} \ No newline at end of file diff --git a/SynapseEngine/Editor/View/TextureGraph/TextureGraphView.h b/SynapseEngine/Editor/View/TextureGraph/TextureGraphView.h new file mode 100644 index 00000000..5b3a7826 --- /dev/null +++ b/SynapseEngine/Editor/View/TextureGraph/TextureGraphView.h @@ -0,0 +1,26 @@ +#pragma once +#include "Editor/View/IView.h" +#include "EditorCore/ViewModels/TextureGraph/TextureGraphViewModel.h" + +namespace ax { + namespace NodeEditor { + struct EditorContext; + } +} + +namespace Syn { + class TextureGraphView : public IView { + public: + TextureGraphView(); + ~TextureGraphView() override; + + TextureGraphView(const TextureGraphView&) = delete; + TextureGraphView& operator=(const TextureGraphView&) = delete; + TextureGraphView(TextureGraphView&& other) noexcept; + TextureGraphView& operator=(TextureGraphView&& other) noexcept; + + void Draw(TextureGraphViewModel& vm) override; + private: + ax::NodeEditor::EditorContext* _context = nullptr; + }; +} \ No newline at end of file diff --git a/SynapseEngine/Editor/View/TextureHierarchy/TextureHierarchyView.cpp b/SynapseEngine/Editor/View/TextureHierarchy/TextureHierarchyView.cpp new file mode 100644 index 00000000..0b7b0239 --- /dev/null +++ b/SynapseEngine/Editor/View/TextureHierarchy/TextureHierarchyView.cpp @@ -0,0 +1,133 @@ +#include "TextureHierarchyView.h" +#include "Editor/Manager/EditorIcons.h" +#include "Editor/Widgets/CardWidget.h" +#include + +namespace Syn { + void TextureHierarchyView::Draw(TextureHierarchyViewModel& vm) { + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8, 8)); + ImGuiWindowFlags windowFlags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse; + + if (ImGui::Begin(SYN_ICON_IMAGE " Textures", nullptr, windowFlags)) { + + auto getCardState = [this](const char* name) -> bool& { + std::string key(name); + if (_cardStates.find(key) == _cardStates.end()) _cardStates[key] = true; + return _cardStates[key]; + }; + + float mainContentBottomY = ImGui::GetWindowPos().y + ImGui::GetWindowContentRegionMax().y; + constexpr const char* CardTexturesTitle = "TextureListCard"; + + if (Syn::UI::BeginCard(CardTexturesTitle, SYN_ICON_IMAGE, getCardState(CardTexturesTitle))) { + + RenderTopBar(vm); + + const auto& state = vm.GetState(); + + ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.08f, 0.08f, 0.08f, 0.6f)); + ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 8.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); + + float currentY = ImGui::GetCursorScreenPos().y; + float tableHeight = mainContentBottomY - currentY - 12.0f; + if (tableHeight < 100.0f) tableHeight = 100.0f; + + ImGui::BeginChild("TextureTableContainer", ImVec2(0, tableHeight), ImGuiChildFlags_Borders, ImGuiWindowFlags_NoScrollbar); + + if (ImGui::BeginTable("TextureTable", 1, ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_ScrollY | ImGuiTableFlags_Resizable)) { + ImGui::TableSetupScrollFreeze(0, 1); + ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch); + + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); + ImGui::TableSetColumnIndex(0); + + float cellWidth = ImGui::GetColumnWidth(); + float textWidth = ImGui::CalcTextSize("Name").x; + ImVec2 startPos = ImGui::GetCursorPos(); + + ImGui::TableHeader(""); + ImGui::SetCursorPos(ImVec2(startPos.x + (cellWidth - textWidth) * 0.5f, startPos.y + 3.0f)); + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); + ImGui::Text("Name"); + ImGui::PopStyleColor(); + + ImGuiListClipper clipper; + clipper.Begin(static_cast(state.filteredNodes.size())); + + while (clipper.Step()) { + for (int row = clipper.DisplayStart; row < clipper.DisplayEnd; ++row) { + RenderTextureRow(vm, state.filteredNodes[row]); + } + } + + ImGui::EndTable(); + } + ImGui::EndChild(); + ImGui::PopStyleVar(2); + ImGui::PopStyleColor(); + } + Syn::UI::EndCard(); + + if (ImGui::IsMouseDown(ImGuiMouseButton_Left) && ImGui::IsWindowHovered()) { + vm.Dispatch(TextureSelectIntent{ INVALID_TEXTURE_ID }); + } + } + ImGui::End(); + ImGui::PopStyleVar(); + } + + void TextureHierarchyView::RenderTopBar(TextureHierarchyViewModel& vm) { + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(8, 6)); + + float barHeight = ImGui::GetFrameHeight(); + ImGui::BeginChild("TextureTopBar", ImVec2(0, barHeight), false, ImGuiWindowFlags_NoScrollbar); + + if (ImGui::Button(SYN_ICON_SYNC " Refresh")) { + vm.Dispatch(TextureRefreshIntent{}); + } + + ImGui::SameLine(); + ImGui::Dummy(ImVec2(8.0f, 0.0f)); + ImGui::SameLine(); + + ImGui::AlignTextToFramePadding(); + ImGui::TextDisabled(SYN_ICON_SEARCH); + ImGui::SameLine(); + + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + + const auto& state = vm.GetState(); + char searchBuffer[256]; + strncpy(searchBuffer, state.searchQuery.c_str(), sizeof(searchBuffer)); + searchBuffer[sizeof(searchBuffer) - 1] = '\0'; + + if (ImGui::InputTextWithHint("##SearchTextures", "Search...", searchBuffer, IM_ARRAYSIZE(searchBuffer))) { + vm.Dispatch(TextureSetSearchQueryIntent{ std::string(searchBuffer) }); + } + + ImGui::EndChild(); + ImGui::PopStyleVar(); + ImGui::Spacing(); + } + + void TextureHierarchyView::RenderTextureRow(TextureHierarchyViewModel& vm, const TextureNode& node) { + ImGui::PushID(node.id); + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + + ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen | ImGuiTreeNodeFlags_SpanFullWidth | ImGuiTreeNodeFlags_FramePadding; + if (vm.GetState().selectedTexture == node.id) { + flags |= ImGuiTreeNodeFlags_Selected; + } + + std::string label = node.icon + " " + node.name; + ImGui::TreeNodeEx((void*)(intptr_t)node.id, flags, "%s", label.c_str()); + + if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { + vm.Dispatch(TextureSelectIntent{ node.id }); + } + + ImGui::PopID(); + } +} \ No newline at end of file diff --git a/SynapseEngine/Editor/View/TextureHierarchy/TextureHierarchyView.h b/SynapseEngine/Editor/View/TextureHierarchy/TextureHierarchyView.h new file mode 100644 index 00000000..ecbf4bcc --- /dev/null +++ b/SynapseEngine/Editor/View/TextureHierarchy/TextureHierarchyView.h @@ -0,0 +1,17 @@ +#pragma once +#include "Editor/View/IView.h" +#include "EditorCore/ViewModels/TextureHierarchy/TextureHierarchyViewModel.h" +#include +#include + +namespace Syn { + class TextureHierarchyView : public IView { + public: + void Draw(TextureHierarchyViewModel& vm) override; + private: + void RenderTopBar(TextureHierarchyViewModel& vm); + void RenderTextureRow(TextureHierarchyViewModel& vm, const TextureNode& node); + private: + std::unordered_map _cardStates; + }; +} \ No newline at end of file diff --git a/SynapseEngine/Editor/View/TextureProperties/TexturePropertiesView.cpp b/SynapseEngine/Editor/View/TextureProperties/TexturePropertiesView.cpp new file mode 100644 index 00000000..333f2a60 --- /dev/null +++ b/SynapseEngine/Editor/View/TextureProperties/TexturePropertiesView.cpp @@ -0,0 +1,66 @@ +#include "TexturePropertiesView.h" +#include "Editor/Manager/EditorIcons.h" +#include "Editor/Widgets/CardWidget.h" +#include "Editor/Widgets/PropertyGrid.h" +#include +#include + +namespace Syn { + void TexturePropertiesView::Draw(TexturePropertiesViewModel& vm) { + const TexturePropertiesState& state = vm.GetState(); + + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8, 8)); + ImGuiWindowFlags windowFlags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse; + + if (ImGui::Begin(SYN_ICON_INFO_CIRCLE " Texture Properties", nullptr, windowFlags)) { + + auto getCardState = [this](const char* name) -> bool& { + std::string key(name); + if (_cardStates.find(key) == _cardStates.end()) _cardStates[key] = true; + return _cardStates[key]; + }; + + constexpr const char* CardTitle = "TextureDetailsCard"; + if (Syn::UI::BeginCard(CardTitle, SYN_ICON_IMAGE, getCardState(CardTitle))) { + + if (!state.hasSelection) { + ImGui::TextDisabled("No texture selected."); + } + else { + if (Syn::UI::BeginPropertyGrid("TexturePropsGrid")) + { + std::string dimensions = std::to_string(state.width) + " x " + std::to_string(state.height); + if (state.depth > 1) { + dimensions += " x " + std::to_string(state.depth); + } + DrawReadOnlyProperty("Dimensions", dimensions.c_str()); + DrawReadOnlyProperty("Mip Levels", std::to_string(state.mipLevels).c_str()); + + std::string formatStr = "VkFormat(" + std::to_string(state.format) + ")"; + DrawReadOnlyProperty("Format", formatStr.c_str()); + DrawReadOnlyProperty("Compressed", state.isCompressed ? "Yes" : "No"); + + Syn::UI::EndPropertyGrid(); + } + } + + Syn::UI::EndCard(); + } + } + + ImGui::End(); + ImGui::PopStyleVar(); + } + + void TexturePropertiesView::DrawReadOnlyProperty(const char* label, const char* value) { + ImGui::TableNextRow(); + + ImGui::TableNextColumn(); + ImGui::TextUnformatted(label); + + ImGui::TableNextColumn(); + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.7f, 0.7f, 0.7f, 1.0f)); + ImGui::TextUnformatted(value); + ImGui::PopStyleColor(); + } +} \ No newline at end of file diff --git a/SynapseEngine/Editor/View/TextureProperties/TexturePropertiesView.h b/SynapseEngine/Editor/View/TextureProperties/TexturePropertiesView.h new file mode 100644 index 00000000..d68b45af --- /dev/null +++ b/SynapseEngine/Editor/View/TextureProperties/TexturePropertiesView.h @@ -0,0 +1,16 @@ +#pragma once +#include "Editor/View/IView.h" +#include "EditorCore/ViewModels/TextureProperties/TexturePropertiesViewModel.h" +#include +#include + +namespace Syn { + class TexturePropertiesView : public IView { + public: + void Draw(TexturePropertiesViewModel& vm) override; + private: + void DrawReadOnlyProperty(const char* label, const char* value); + private: + std::unordered_map _cardStates; + }; +} \ No newline at end of file diff --git a/SynapseEngine/Editor/Workspace/TextureWorkspace.cpp b/SynapseEngine/Editor/Workspace/TextureWorkspace.cpp index 91e39a71..fe5d0465 100644 --- a/SynapseEngine/Editor/Workspace/TextureWorkspace.cpp +++ b/SynapseEngine/Editor/Workspace/TextureWorkspace.cpp @@ -4,17 +4,44 @@ #include "Editor/View/ContentBrowser/ContentBrowserView.h" #include "EditorCore/ViewModels/ContentBrowser/ContentBrowserViewModel.h" +#include "Editor/View/TextureHierarchy/TextureHierarchyView.h" +#include "EditorCore/ViewModels/TextureHierarchy/TextureHierarchyViewModel.h" + +#include "Editor/View/TextureProperties/TexturePropertiesView.h" +#include "EditorCore/ViewModels/TextureProperties/TexturePropertiesViewModel.h" + +#include "Editor/View/TextureGraph/TextureGraphView.h" +#include "EditorCore/ViewModels/TextureGraph/TextureGraphViewModel.h" + namespace Syn { TextureWorkspace::TextureWorkspace(EditorContext* context, IconManager* iconManager, const std::string& assetPath) : _context(context), _iconManager(iconManager), _assetPath(assetPath) {} - void TextureWorkspace::Initialize() { + void TextureWorkspace::Initialize() + { using ContentBrowserWin = EditorWindow; AddWindow( ContentBrowserView{ _iconManager, SYN_ICON_FOLDER_OPEN " Content Browser###Content_Texture" }, ContentBrowserViewModel{ _context->GetFileSystemApi(), _assetPath } ); - } + using TextureHierarchyWin = EditorWindow; + AddWindow( + TextureHierarchyView{}, + TextureHierarchyViewModel{ _context->GetTextureApi() } + ); + + using TexturePropertiesWin = EditorWindow; + AddWindow( + TexturePropertiesView{}, + TexturePropertiesViewModel{ _context->GetTextureApi() } + ); + + using TextureGraphWin = EditorWindow; + AddWindow( + TextureGraphView{}, + TextureGraphViewModel{ _context->GetTextureApi() } + ); + } } \ No newline at end of file diff --git a/SynapseEngine/EditorCore/Api/ITextureApi.h b/SynapseEngine/EditorCore/Api/ITextureApi.h new file mode 100644 index 00000000..9e3ac64a --- /dev/null +++ b/SynapseEngine/EditorCore/Api/ITextureApi.h @@ -0,0 +1,27 @@ +#pragma once +#include +#include +#include "Engine/Image/Data/Cpu/CpuTextureData.h" +#include "EditorCore/Types/TextureHandle.h" + +namespace Syn { + constexpr uint32_t INVALID_TEXTURE_ID = 0xFFFFFFFF; + + struct TextureItemData { + uint32_t id; + std::string name; + std::string path; + }; + + class ITextureApi { + public: + virtual ~ITextureApi() = default; + + virtual std::vector GetAllTextures() const = 0; + virtual uint32_t GetSelectedTexture() const = 0; + virtual void SetSelectedTexture(uint32_t id) = 0; + virtual bool GetTextureData(uint32_t id, CpuTextureData& outData) const = 0; + virtual uint64_t GetVersion() const = 0; + virtual TextureHandle GetTextureHandle(uint32_t id) = 0; + }; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/TextureGraph/TextureGraphIntent.cpp b/SynapseEngine/EditorCore/ViewModels/TextureGraph/TextureGraphIntent.cpp new file mode 100644 index 00000000..d528c720 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/TextureGraph/TextureGraphIntent.cpp @@ -0,0 +1 @@ +#include "TextureGraphIntent.h" \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/TextureGraph/TextureGraphIntent.h b/SynapseEngine/EditorCore/ViewModels/TextureGraph/TextureGraphIntent.h new file mode 100644 index 00000000..6704968c --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/TextureGraph/TextureGraphIntent.h @@ -0,0 +1,10 @@ +#pragma once +#include + +namespace Syn { + struct DummyTextureGraphIntent {}; + + using TextureGraphIntent = std::variant< + DummyTextureGraphIntent + >; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/TextureGraph/TextureGraphState.cpp b/SynapseEngine/EditorCore/ViewModels/TextureGraph/TextureGraphState.cpp new file mode 100644 index 00000000..54a6ac26 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/TextureGraph/TextureGraphState.cpp @@ -0,0 +1 @@ +#include "TextureGraphState.h" \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/TextureGraph/TextureGraphState.h b/SynapseEngine/EditorCore/ViewModels/TextureGraph/TextureGraphState.h new file mode 100644 index 00000000..77a25865 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/TextureGraph/TextureGraphState.h @@ -0,0 +1,21 @@ +#pragma once +#include +#include +#include "EditorCore/Types/TextureHandle.h" + +namespace Syn { + struct TextureNodeState { + bool isVisible = false; + std::string name = ""; + + uint32_t width = 0; + uint32_t height = 0; + + uint32_t engineTextureId = 0xFFFFFFFF; + TextureHandle textureHandle = InvalidTextureHandle; + }; + + struct TextureGraphState { + TextureNodeState previewNode; + }; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/TextureGraph/TextureGraphViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/TextureGraph/TextureGraphViewModel.cpp new file mode 100644 index 00000000..47754daa --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/TextureGraph/TextureGraphViewModel.cpp @@ -0,0 +1,36 @@ +#include "TextureGraphViewModel.h" + +namespace Syn { + TextureGraphViewModel::TextureGraphViewModel(ITextureApi* textureApi) + : _textureApi(textureApi) + {} + + void TextureGraphViewModel::SyncWithEngine() { + if (!_textureApi) return; + + uint32_t selectedId = _textureApi->GetSelectedTexture(); + + if (selectedId != _lastSelectedId) { + _lastSelectedId = selectedId; + + if (selectedId == 0xFFFFFFFF) { + _state.previewNode.isVisible = false; + _state.previewNode.textureHandle = InvalidTextureHandle; + } + else { + CpuTextureData texData; + if (_textureApi->GetTextureData(selectedId, texData)) { + _state.previewNode.isVisible = true; + _state.previewNode.engineTextureId = selectedId; + _state.previewNode.width = texData.width; + _state.previewNode.height = texData.height; + _state.previewNode.textureHandle = _textureApi->GetTextureHandle(selectedId); + } + } + } + } + + void TextureGraphViewModel::Dispatch(const TextureGraphIntent& intent) { + + } +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/TextureGraph/TextureGraphViewModel.h b/SynapseEngine/EditorCore/ViewModels/TextureGraph/TextureGraphViewModel.h new file mode 100644 index 00000000..f5c8eeb5 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/TextureGraph/TextureGraphViewModel.h @@ -0,0 +1,22 @@ +#pragma once +#include "EditorCore/ViewModels/IViewModel.h" +#include "TextureGraphState.h" +#include "TextureGraphIntent.h" +#include "EditorCore/Api/ITextureApi.h" + +namespace Syn { + class TextureGraphViewModel : public IViewModel { + public: + TextureGraphViewModel(ITextureApi* textureApi); + ~TextureGraphViewModel() override = default; + + const TextureGraphState& GetState() const override { return _state; } + + void SyncWithEngine() override; + void Dispatch(const TextureGraphIntent& intent) override; + private: + ITextureApi* _textureApi = nullptr; + uint32_t _lastSelectedId = 0xFFFFFFFF; + TextureGraphState _state; + }; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/TextureHierarchy/TextureHierarchyIntent.cpp b/SynapseEngine/EditorCore/ViewModels/TextureHierarchy/TextureHierarchyIntent.cpp new file mode 100644 index 00000000..b3141dd4 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/TextureHierarchy/TextureHierarchyIntent.cpp @@ -0,0 +1 @@ +#include "TextureHierarchyIntent.h" \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/TextureHierarchy/TextureHierarchyIntent.h b/SynapseEngine/EditorCore/ViewModels/TextureHierarchy/TextureHierarchyIntent.h new file mode 100644 index 00000000..6f248f6f --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/TextureHierarchy/TextureHierarchyIntent.h @@ -0,0 +1,21 @@ +#pragma once +#include +#include + +namespace Syn { + struct TextureSelectIntent { + uint32_t textureId; + }; + + struct TextureSetSearchQueryIntent { + std::string query; + }; + + struct TextureRefreshIntent {}; + + using TextureHierarchyIntent = std::variant< + TextureSelectIntent, + TextureSetSearchQueryIntent, + TextureRefreshIntent + >; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/TextureHierarchy/TextureHierarchyState.cpp b/SynapseEngine/EditorCore/ViewModels/TextureHierarchy/TextureHierarchyState.cpp new file mode 100644 index 00000000..1a44fbf6 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/TextureHierarchy/TextureHierarchyState.cpp @@ -0,0 +1 @@ +#include "TextureHierarchyState.h" \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/TextureHierarchy/TextureHierarchyState.h b/SynapseEngine/EditorCore/ViewModels/TextureHierarchy/TextureHierarchyState.h new file mode 100644 index 00000000..fc714a01 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/TextureHierarchy/TextureHierarchyState.h @@ -0,0 +1,17 @@ +#pragma once +#include +#include + +namespace Syn { + struct TextureNode { + uint32_t id; + std::string name; + std::string icon; + }; + + struct TextureHierarchyState { + std::vector filteredNodes; + uint32_t selectedTexture = 0xFFFFFFFF; + std::string searchQuery = ""; + }; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/TextureHierarchy/TextureHierarchyViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/TextureHierarchy/TextureHierarchyViewModel.cpp new file mode 100644 index 00000000..f6e72931 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/TextureHierarchy/TextureHierarchyViewModel.cpp @@ -0,0 +1,70 @@ +#include "TextureHierarchyViewModel.h" +#include "Editor/Manager/EditorIcons.h" +#include + +namespace Syn { + TextureHierarchyViewModel::TextureHierarchyViewModel(ITextureApi* textureApi) + : _textureApi(textureApi) + {} + + void TextureHierarchyViewModel::SyncWithEngine() { + if (!_textureApi) return; + + uint64_t currentVersion = _textureApi->GetVersion(); + uint32_t currentSelection = _textureApi->GetSelectedTexture(); + + if (currentVersion != _lastEngineVersion || _isDirty) { + RebuildList(); + _lastEngineVersion = currentVersion; + _isDirty = false; + } + + if (_state.selectedTexture != currentSelection) { + _state.selectedTexture = currentSelection; + } + } + + void TextureHierarchyViewModel::Dispatch(const TextureHierarchyIntent& intent) { + std::visit([this](auto&& arg) { + using T = std::decay_t; + + if constexpr (std::is_same_v) { + if (_textureApi) { + _textureApi->SetSelectedTexture(arg.textureId); + } + } + else if constexpr (std::is_same_v) { + if (_state.searchQuery != arg.query) { + _state.searchQuery = arg.query; + _isDirty = true; + } + } + else if constexpr (std::is_same_v) { + _isDirty = true; + } + }, intent); + } + + void TextureHierarchyViewModel::RebuildList() { + if (!_textureApi) return; + + _state.filteredNodes.clear(); + auto allTextures = _textureApi->GetAllTextures(); + + std::string searchLower = _state.searchQuery; + std::transform(searchLower.begin(), searchLower.end(), searchLower.begin(), ::tolower); + + for (const auto& tex : allTextures) { + std::string nameLower = tex.name; + std::transform(nameLower.begin(), nameLower.end(), nameLower.begin(), ::tolower); + + if (searchLower.empty() || nameLower.find(searchLower) != std::string::npos) { + TextureNode node; + node.id = tex.id; + node.name = tex.name; + node.icon = SYN_ICON_IMAGE; + _state.filteredNodes.push_back(node); + } + } + } +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/TextureHierarchy/TextureHierarchyViewModel.h b/SynapseEngine/EditorCore/ViewModels/TextureHierarchy/TextureHierarchyViewModel.h new file mode 100644 index 00000000..5496b281 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/TextureHierarchy/TextureHierarchyViewModel.h @@ -0,0 +1,26 @@ +#pragma once +#include "EditorCore/ViewModels/IViewModel.h" +#include "TextureHierarchyState.h" +#include "TextureHierarchyIntent.h" +#include "EditorCore/Api/ITextureApi.h" + +namespace Syn { + class TextureHierarchyViewModel : public IViewModel { + public: + TextureHierarchyViewModel(ITextureApi* textureApi); + ~TextureHierarchyViewModel() override = default; + + const TextureHierarchyState& GetState() const override { return _state; } + void SyncWithEngine() override; + void Dispatch(const TextureHierarchyIntent& intent) override; + + private: + void RebuildList(); + private: + ITextureApi* _textureApi = nullptr; + TextureHierarchyState _state; + + bool _isDirty = true; + uint64_t _lastEngineVersion = 0; + }; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/TextureProperties/TexturePropertiesIntent.cpp b/SynapseEngine/EditorCore/ViewModels/TextureProperties/TexturePropertiesIntent.cpp new file mode 100644 index 00000000..56a0c90f --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/TextureProperties/TexturePropertiesIntent.cpp @@ -0,0 +1 @@ +#include "TexturePropertiesIntent.h" \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/TextureProperties/TexturePropertiesIntent.h b/SynapseEngine/EditorCore/ViewModels/TextureProperties/TexturePropertiesIntent.h new file mode 100644 index 00000000..a2be1c3c --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/TextureProperties/TexturePropertiesIntent.h @@ -0,0 +1,10 @@ +#pragma once +#include + +namespace Syn { + struct DummyTexturePropertyIntent {}; + + using TexturePropertiesIntent = std::variant< + DummyTexturePropertyIntent + >; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/TextureProperties/TexturePropertiesState.cpp b/SynapseEngine/EditorCore/ViewModels/TextureProperties/TexturePropertiesState.cpp new file mode 100644 index 00000000..dcf395b1 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/TextureProperties/TexturePropertiesState.cpp @@ -0,0 +1 @@ +#include "TexturePropertiesState.h" \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/TextureProperties/TexturePropertiesState.h b/SynapseEngine/EditorCore/ViewModels/TextureProperties/TexturePropertiesState.h new file mode 100644 index 00000000..da4931b6 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/TextureProperties/TexturePropertiesState.h @@ -0,0 +1,17 @@ +#pragma once +#include "Engine/Image/Data/Cpu/CpuTextureData.h" +#include + +namespace Syn { + struct TexturePropertiesState { + bool hasSelection = false; + std::string textureName = ""; + + uint32_t width = 0; + uint32_t height = 0; + uint32_t depth = 1; + uint32_t mipLevels = 1; + uint32_t format = 0; + bool isCompressed = false; + }; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/TextureProperties/TexturePropertiesViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/TextureProperties/TexturePropertiesViewModel.cpp new file mode 100644 index 00000000..341a5a5f --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/TextureProperties/TexturePropertiesViewModel.cpp @@ -0,0 +1,36 @@ +#include "TexturePropertiesViewModel.h" + +namespace Syn { + TexturePropertiesViewModel::TexturePropertiesViewModel(ITextureApi* textureApi) + : _textureApi(textureApi) + {} + + void TexturePropertiesViewModel::SyncWithEngine() { + if (!_textureApi) return; + + uint32_t selectedId = _textureApi->GetSelectedTexture(); + + if (selectedId == INVALID_TEXTURE_ID) { + _state.hasSelection = false; + return; + } + + CpuTextureData texData; + if (_textureApi->GetTextureData(selectedId, texData)) { + _state.hasSelection = true; + _state.width = texData.width; + _state.height = texData.height; + _state.depth = texData.depth; + _state.mipLevels = texData.mipLevels; + _state.format = static_cast(texData.format); + _state.isCompressed = texData.isCompressed; + } + else { + _state.hasSelection = false; + } + } + + void TexturePropertiesViewModel::Dispatch(const TexturePropertiesIntent& intent) { + + } +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/TextureProperties/TexturePropertiesViewModel.h b/SynapseEngine/EditorCore/ViewModels/TextureProperties/TexturePropertiesViewModel.h new file mode 100644 index 00000000..ce8083b9 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/TextureProperties/TexturePropertiesViewModel.h @@ -0,0 +1,20 @@ +#pragma once +#include "EditorCore/ViewModels/IViewModel.h" +#include "TexturePropertiesState.h" +#include "TexturePropertiesIntent.h" +#include "EditorCore/Api/ITextureApi.h" + +namespace Syn { + class TexturePropertiesViewModel : public IViewModel { + public: + TexturePropertiesViewModel(ITextureApi* textureApi); + ~TexturePropertiesViewModel() override = default; + + const TexturePropertiesState& GetState() const override { return _state; } + void SyncWithEngine() override; + void Dispatch(const TexturePropertiesIntent& intent) override; + private: + ITextureApi* _textureApi = nullptr; + TexturePropertiesState _state; + }; +} \ No newline at end of file diff --git a/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json b/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json index 38515bad..d7ad2117 100644 --- a/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json +++ b/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json @@ -14,17 +14,17 @@ "shared_material_count": 100 }, "entities": { - "animated_characters": 1000, - "static_geometry": 10000, - "physics_boxes": 1000, - "physics_spheres": 1000, - "physics_capsules": 1000 + "animated_characters": 50, + "static_geometry": 500, + "physics_boxes": 50, + "physics_spheres": 50, + "physics_capsules": 50 }, "lights": { "directional_count": 1, - "point_count": 128, - "point_shadow_count": 32, - "spot_count": 128, - "spot_shadow_count": 32 + "point_count": 32, + "point_shadow_count": 16, + "spot_count": 32, + "spot_shadow_count": 16 } } \ No newline at end of file From 77665262020b228243831d04c25cd037faaae3ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20P=C3=A9ter?= Date: Fri, 26 Jun 2026 20:48:09 +0200 Subject: [PATCH 09/51] Implemented material workspace, material hierarchy window and properties panel, and new graph editor --- .../Editor/EditorApi/EditorContext.cpp | 2 +- .../Editor/EditorApi/Impl/MaterialApiImpl.cpp | 121 ++++++++------ .../Editor/EditorApi/Impl/MaterialApiImpl.h | 24 ++- SynapseEngine/Editor/Manager/EditorIcons.h | 2 +- .../View/MaterialGraph/MaterialGraphView.cpp | 152 ++++++++++++------ .../View/MaterialGraph/MaterialGraphView.h | 2 + .../MaterialHierarchyView.cpp | 133 +++++++++++++++ .../MaterialHierarchy/MaterialHierarchyView.h | 17 ++ .../MaterialPropertiesView.cpp | 137 ++++++++++++++++ .../MaterialPropertiesView.h | 16 ++ SynapseEngine/Editor/Widgets/PropertyGrid.cpp | 9 ++ SynapseEngine/Editor/Widgets/PropertyGrid.h | 3 + .../Editor/Workspace/MaterialWorkspace.cpp | 21 ++- SynapseEngine/EditorCore/Api/IMaterialApi.h | 22 ++- .../MaterialGraph/MaterialGraphState.h | 24 +-- .../MaterialGraph/MaterialGraphViewModel.cpp | 110 ++++++++----- .../MaterialGraph/MaterialGraphViewModel.h | 11 +- .../MaterialHierarchyIntent.cpp | 1 + .../MaterialHierarchyIntent.h | 21 +++ .../MaterialHierarchyState.cpp | 1 + .../MaterialHierarchyState.h | 17 ++ .../MaterialHierarchyViewModel.cpp | 70 ++++++++ .../MaterialHierarchyViewModel.h | 26 +++ .../MaterialPropertiesIntent.cpp | 1 + .../MaterialPropertiesIntent.h | 15 ++ .../MaterialPropertiesState.cpp | 1 + .../MaterialPropertiesState.h | 28 ++++ .../MaterialPropertiesViewModel.cpp | 64 ++++++++ .../MaterialPropertiesViewModel.h | 22 +++ SynapseEngine/imgui.ini | 28 ++-- 30 files changed, 917 insertions(+), 184 deletions(-) create mode 100644 SynapseEngine/Editor/View/MaterialHierarchy/MaterialHierarchyView.cpp create mode 100644 SynapseEngine/Editor/View/MaterialHierarchy/MaterialHierarchyView.h create mode 100644 SynapseEngine/Editor/View/MaterialProperties/MaterialPropertiesView.cpp create mode 100644 SynapseEngine/Editor/View/MaterialProperties/MaterialPropertiesView.h create mode 100644 SynapseEngine/EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyIntent.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyIntent.h create mode 100644 SynapseEngine/EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyState.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyState.h create mode 100644 SynapseEngine/EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyViewModel.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyViewModel.h create mode 100644 SynapseEngine/EditorCore/ViewModels/MaterialProperties/MaterialPropertiesIntent.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/MaterialProperties/MaterialPropertiesIntent.h create mode 100644 SynapseEngine/EditorCore/ViewModels/MaterialProperties/MaterialPropertiesState.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/MaterialProperties/MaterialPropertiesState.h create mode 100644 SynapseEngine/EditorCore/ViewModels/MaterialProperties/MaterialPropertiesViewModel.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/MaterialProperties/MaterialPropertiesViewModel.h diff --git a/SynapseEngine/Editor/EditorApi/EditorContext.cpp b/SynapseEngine/Editor/EditorApi/EditorContext.cpp index 7b0a0596..6468038f 100644 --- a/SynapseEngine/Editor/EditorApi/EditorContext.cpp +++ b/SynapseEngine/Editor/EditorApi/EditorContext.cpp @@ -25,7 +25,7 @@ namespace Syn { _fileSystemApi = std::make_unique(); _hierarchyApi = std::make_unique(sm); _loggerApi = std::make_unique(engine); - _materialApi = std::make_unique(engine); + _materialApi = std::make_unique(engine->GetMaterialManager()); _renderApi = std::make_unique(engine, textureManager, sm); _sceneApi = std::make_unique(sm); _settingsApi = std::make_unique(sm); diff --git a/SynapseEngine/Editor/EditorApi/Impl/MaterialApiImpl.cpp b/SynapseEngine/Editor/EditorApi/Impl/MaterialApiImpl.cpp index 44ee45f7..9156fb3d 100644 --- a/SynapseEngine/Editor/EditorApi/Impl/MaterialApiImpl.cpp +++ b/SynapseEngine/Editor/EditorApi/Impl/MaterialApiImpl.cpp @@ -1,67 +1,94 @@ #include "MaterialApiImpl.h" -#include "Engine/Material/MaterialManager.h" -#include "Engine/Image/ImageManager.h" -#include "Engine/Logger/SynLog.h" +#include "EditorCore/ViewModels/MaterialGraph/MaterialGraphState.h" +#include namespace Syn { - std::vector MaterialApiImpl::GetAllMaterials() const { - std::vector result; - auto materialManager = _engine->GetMaterialManager(); - if (!materialManager) return result; - for (const auto& path : materialManager->GetResourcePaths()) { - result.push_back({ materialManager->GetResourceIndex(path), path }); + std::vector MaterialApiImpl::GetAllMaterials() const { + if (!_materialManager) return {}; + + std::vector result; + auto paths = _materialManager->GetResourcePaths(); + + for (uint32_t i = 0; i < paths.size(); ++i) { + if (_materialManager->GetEntryState(i) == ResourceState::Ready) { + std::filesystem::path p(paths[i]); + result.push_back({ i, p.filename().string(), paths[i] }); + } } return result; } - std::vector MaterialApiImpl::GetAllTextures() const { - std::vector result; - auto imageManager = _engine->GetImageManager(); - if (!imageManager) return result; + uint32_t MaterialApiImpl::GetSelectedMaterial() const { return _selectedMaterial; } + void MaterialApiImpl::SetSelectedMaterial(uint32_t id) { _selectedMaterial = id; } + uint64_t MaterialApiImpl::GetVersion() const { return _materialManager ? _materialManager->GetVersion() : 0; } - for (const auto& path : imageManager->GetResourcePaths()) { - result.push_back({ imageManager->GetResourceIndex(path), path }); + std::string MaterialApiImpl::GetMaterialName(uint32_t materialId) const { + auto mats = GetAllMaterials(); + for (const auto& m : mats) { + if (m.id == materialId) return m.name; + } + return "Unknown Material"; + } + + uint32_t MaterialApiImpl::GetLinkedTexture(uint32_t materialId, uint32_t textureType) const { + if (!_materialManager || materialId == INVALID_MATERIAL_ID) return INVALID_MATERIAL_ID; + + auto mat = _materialManager->GetResource(materialId); + if (!mat) return INVALID_MATERIAL_ID; + + switch (static_cast(textureType)) { + case GraphPinType::Albedo: return mat->albedoTexture; + case GraphPinType::Normal: return mat->normalTexture; + case GraphPinType::Metalness: return mat->metalnessTexture; + case GraphPinType::Roughness: return mat->roughnessTexture; + case GraphPinType::MetallicRoughness: return mat->metallicRoughnessTexture; + case GraphPinType::Emissive: return mat->emissiveTexture; + case GraphPinType::AmbientOcclusion: return mat->ambientOcclusionTexture; + default: return INVALID_MATERIAL_ID; } - return result; } void MaterialApiImpl::LinkTextureToMaterial(uint32_t materialId, uint32_t textureType, uint32_t textureId) { - auto materialManager = _engine->GetMaterialManager(); - if (!materialManager) return; - auto material = materialManager->GetResource(materialId); - if (!material) return; - - switch (textureType) { - case 0: material->albedoTexture = textureId; break; - case 1: material->normalTexture = textureId; break; - case 2: material->metalnessTexture = textureId; break; - case 3: material->roughnessTexture = textureId; break; - case 4: material->metallicRoughnessTexture = textureId; break; - case 5: material->emissiveTexture = textureId; break; - case 6: material->ambientOcclusionTexture = textureId; break; - default: - Syn::Warning("MaterialApiImpl: Ismeretlen textúra slot ({}) a {} azonosítójú materialhoz!", textureType, materialId); - return; + if (!_materialManager || materialId == INVALID_MATERIAL_ID) return; + + auto mat = _materialManager->GetResource(materialId); + if (!mat) return; + + switch (static_cast(textureType)) { + case GraphPinType::Albedo: mat->albedoTexture = textureId; break; + case GraphPinType::Normal: mat->normalTexture = textureId; break; + case GraphPinType::Metalness: mat->metalnessTexture = textureId; break; + case GraphPinType::Roughness: mat->roughnessTexture = textureId; break; + case GraphPinType::MetallicRoughness: mat->metallicRoughnessTexture = textureId; break; + case GraphPinType::Emissive: mat->emissiveTexture = textureId; break; + case GraphPinType::AmbientOcclusion: mat->ambientOcclusionTexture = textureId; break; + default: break; } - Syn::Info("MaterialApiImpl: Textúra ({}) bekötve a Material ({}) {} slotjába.", textureId, materialId, textureType); } void MaterialApiImpl::UnlinkTextureFromMaterial(uint32_t materialId, uint32_t textureType) { - auto materialManager = _engine->GetMaterialManager(); - if (!materialManager) return; - auto material = materialManager->GetResource(materialId); - if (!material) return; - - switch (textureType) { - case 0: material->albedoTexture = UINT32_MAX; break; - case 1: material->normalTexture = UINT32_MAX; break; - case 2: material->metalnessTexture = UINT32_MAX; break; - case 3: material->roughnessTexture = UINT32_MAX; break; - case 4: material->metallicRoughnessTexture = UINT32_MAX; break; - case 5: material->emissiveTexture = UINT32_MAX; break; - case 6: material->ambientOcclusionTexture = UINT32_MAX; break; + LinkTextureToMaterial(materialId, textureType, 0xFFFFFFFF); + } + + bool MaterialApiImpl::GetMaterialData(uint32_t materialId, Material& outMaterial) const { + if (!_materialManager || materialId == INVALID_MATERIAL_ID) return false; + + auto resource = _materialManager->GetResource(materialId); + if (resource) { + outMaterial = *resource; + return true; + } + return false; + } + + void MaterialApiImpl::UpdateMaterialData(uint32_t materialId, const Material& material) { + if (!_materialManager || materialId == INVALID_MATERIAL_ID) return; + + auto resource = _materialManager->GetResource(materialId); + if (resource) { + *resource = material; + //_materialManager->MarkDirty(materialId) } - Syn::Info("MaterialApiImpl: Textúra kikötve a Material ({}) {} slotjából.", materialId, textureType); } } \ No newline at end of file diff --git a/SynapseEngine/Editor/EditorApi/Impl/MaterialApiImpl.h b/SynapseEngine/Editor/EditorApi/Impl/MaterialApiImpl.h index a99878fb..7d1b9fee 100644 --- a/SynapseEngine/Editor/EditorApi/Impl/MaterialApiImpl.h +++ b/SynapseEngine/Editor/EditorApi/Impl/MaterialApiImpl.h @@ -1,16 +1,30 @@ #pragma once #include "EditorCore/Api/IMaterialApi.h" -#include "Engine/Engine.h" +#include "Engine/Material/MaterialManager.h" namespace Syn { + enum class GraphPinType; + class MaterialApiImpl : public IMaterialApi { public: - MaterialApiImpl(Engine* engine) : _engine(engine) {} - std::vector GetAllMaterials() const override; - std::vector GetAllTextures() const override; + MaterialApiImpl(MaterialManager* materialManager) + : _materialManager(materialManager) {} + + std::vector GetAllMaterials() const override; + uint32_t GetSelectedMaterial() const override; + void SetSelectedMaterial(uint32_t id) override; + uint64_t GetVersion() const override; + + std::string GetMaterialName(uint32_t materialId) const override; + uint32_t GetLinkedTexture(uint32_t materialId, uint32_t textureType) const override; + void LinkTextureToMaterial(uint32_t materialId, uint32_t textureType, uint32_t textureId) override; void UnlinkTextureFromMaterial(uint32_t materialId, uint32_t textureType) override; + + bool GetMaterialData(uint32_t materialId, Material& outMaterial) const override; + void UpdateMaterialData(uint32_t materialId, const Material& material) override; private: - Engine* _engine; + MaterialManager* _materialManager; + uint32_t _selectedMaterial = INVALID_MATERIAL_ID; }; } \ No newline at end of file diff --git a/SynapseEngine/Editor/Manager/EditorIcons.h b/SynapseEngine/Editor/Manager/EditorIcons.h index b9934f71..96b105b3 100644 --- a/SynapseEngine/Editor/Manager/EditorIcons.h +++ b/SynapseEngine/Editor/Manager/EditorIcons.h @@ -64,7 +64,7 @@ constexpr const char* ASSET_PATH = "Assets"; #define SYN_ICON_DRAW_POLYGON ICON_FA_DRAW_POLYGON #define SYN_ICON_SYNC ICON_FA_SYNC #define SYN_ICON_PROJECT_DIAGRAM ICON_FA_PROJECT_DIAGRAM - +#define SYN_ICON_BRUSH ICON_FA_BRUSH // Workspace Labels #define SYN_WS_SCENE SYN_ICON_GLOBE " Scene" #define SYN_WS_MODEL SYN_ICON_DRAW_POLYGON " Model" diff --git a/SynapseEngine/Editor/View/MaterialGraph/MaterialGraphView.cpp b/SynapseEngine/Editor/View/MaterialGraph/MaterialGraphView.cpp index e7407164..fa5d010d 100644 --- a/SynapseEngine/Editor/View/MaterialGraph/MaterialGraphView.cpp +++ b/SynapseEngine/Editor/View/MaterialGraph/MaterialGraphView.cpp @@ -1,4 +1,5 @@ #include "MaterialGraphView.h" +#include "Editor/Manager/EditorIcons.h" #include #include @@ -8,7 +9,6 @@ namespace Syn { MaterialGraphView::MaterialGraphView() { ed::Config config; - config.SettingsFile = nullptr; config.SettingsFile = "Synapse_MaterialGraph.json"; _context = ed::CreateEditor(&config); } @@ -27,10 +27,7 @@ namespace Syn { MaterialGraphView& MaterialGraphView::operator=(MaterialGraphView&& other) noexcept { if (this != &other) { - if (_context) { - ed::DestroyEditor(_context); - } - + if (_context) ed::DestroyEditor(_context); _context = other._context; other._context = nullptr; } @@ -41,16 +38,12 @@ namespace Syn { const auto& state = vm.GetState(); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); - ImGuiWindowFlags windowFlags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse; - bool isVisible = ImGui::Begin("Material Graph", nullptr, windowFlags); + bool isVisible = ImGui::Begin(SYN_ICON_PROJECT_DIAGRAM " Material Graph", nullptr, windowFlags); ImGui::PopStyleVar(); if (isVisible) { - ImVec2 startPos = ImGui::GetCursorStartPos(); - ImGui::SetCursorPos(startPos); - ed::SetCurrentEditor(_context); ImVec2 canvasSize = ImGui::GetContentRegionAvail(); @@ -59,60 +52,48 @@ namespace Syn { ed::Begin("Material Editor Canvas", canvasSize); - for (const auto& node : state.nodes) { - ed::BeginNode(ed::NodeId(node.id)); - - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.8f, 0.2f, 1.0f)); - ImGui::TextUnformatted(node.name.c_str()); - ImGui::PopStyleColor(); - - if (node.type == GraphNodeType::Material) { - for (const auto& pin : node.pins) { - ed::BeginPin(ed::PinId(pin.id), ed::PinKind::Input); - ImGui::Text("-> %s", GetPinName(pin.type)); - ed::EndPin(); + if (!state.isMaterialSelected) { + ImGui::SetCursorPos(ImVec2(canvasSize.x * 0.5f - 80.0f, canvasSize.y * 0.5f)); + ImGui::TextDisabled("No Material Selected"); + } + else { + for (const auto& node : state.nodes) { + if (node.type == GraphNodeType::Material) { + DrawMaterialNode(node); } - } - else if (node.type == GraphNodeType::Texture) { - for (const auto& pin : node.pins) { - ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 20.0f); - ed::BeginPin(ed::PinId(pin.id), ed::PinKind::Output); - ImGui::Text("RGB ->"); - ed::EndPin(); + else if (node.type == GraphNodeType::Texture) { + DrawTextureNode(node); } } - ed::EndNode(); - } - - for (const auto& link : state.links) { - ed::Link(ed::LinkId(link.id), ed::PinId(link.startPinId), ed::PinId(link.endPinId)); - } - - if (ed::BeginCreate()) { - ed::PinId inputPinId, outputPinId; - if (ed::QueryNewLink(&inputPinId, &outputPinId)) { - if (inputPinId && outputPinId) { - if (ed::AcceptNewItem(ImVec4(0.2f, 1.0f, 0.2f, 1.0f), 2.0f)) { - GraphID startId = static_cast(inputPinId.Get()); - GraphID endId = static_cast(outputPinId.Get()); + for (const auto& link : state.links) { + ed::Link(ed::LinkId(link.id), ed::PinId(link.startPinId), ed::PinId(link.endPinId)); + } - vm.Dispatch(CreateLinkIntent{ startId, endId }); + if (ed::BeginCreate()) { + ed::PinId inputPinId, outputPinId; + if (ed::QueryNewLink(&inputPinId, &outputPinId)) { + if (inputPinId && outputPinId) { + if (ed::AcceptNewItem(ImVec4(0.2f, 1.0f, 0.2f, 1.0f), 2.0f)) { + GraphID startId = static_cast(inputPinId.Get()); + GraphID endId = static_cast(outputPinId.Get()); + vm.Dispatch(CreateLinkIntent{ startId, endId }); + } } } } - } - ed::EndCreate(); + ed::EndCreate(); - if (ed::BeginDelete()) { - ed::LinkId deletedLinkId; - while (ed::QueryDeletedLink(&deletedLinkId)) { - if (ed::AcceptDeletedItem()) { - vm.Dispatch(DeleteLinkIntent{ static_cast(deletedLinkId.Get()) }); + if (ed::BeginDelete()) { + ed::LinkId deletedLinkId; + while (ed::QueryDeletedLink(&deletedLinkId)) { + if (ed::AcceptDeletedItem()) { + vm.Dispatch(DeleteLinkIntent{ static_cast(deletedLinkId.Get()) }); + } } } + ed::EndDelete(); } - ed::EndDelete(); ed::End(); ed::SetCurrentEditor(nullptr); @@ -121,6 +102,73 @@ namespace Syn { ImGui::End(); } + void MaterialGraphView::DrawMaterialNode(const GraphNodeData& node) { + ed::BeginNode(ed::NodeId(node.id)); + + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.8f, 0.2f, 1.0f)); + ImGui::TextUnformatted((SYN_ICON_BRUSH " " + node.name).c_str()); + ImGui::PopStyleColor(); + ImGui::Spacing(); + + ImVec2 previewSize(256.0f, 256.0f); + ImVec2 cursorPos = ImGui::GetCursorScreenPos(); + ImDrawList* drawList = ImGui::GetWindowDrawList(); + + drawList->AddRectFilled(cursorPos, ImVec2(cursorPos.x + previewSize.x, cursorPos.y + previewSize.y), IM_COL32(25, 25, 25, 255), 4.0f); + + const char* previewText = "Material\nPreview"; + ImVec2 textSize = ImGui::CalcTextSize(previewText); + drawList->AddText(ImVec2(cursorPos.x + (previewSize.x - textSize.x) * 0.5f, cursorPos.y + (previewSize.y - textSize.y) * 0.5f), IM_COL32(120, 120, 120, 255), previewText); + ImGui::Dummy(previewSize); + + ImGui::Spacing(); + + for (const auto& pin : node.pins) { + ed::BeginPin(ed::PinId(pin.id), ed::PinKind::Input); + ImGui::Text("-> %s", GetPinName(pin.type)); + ed::EndPin(); + } + + ed::EndNode(); + } + + void MaterialGraphView::DrawTextureNode(const GraphNodeData& node) { + ed::BeginNode(ed::NodeId(node.id)); + + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.4f, 0.8f, 1.0f, 1.0f)); + ImGui::TextUnformatted((SYN_ICON_IMAGE " " + node.name).c_str()); + ImGui::PopStyleColor(); + ImGui::Spacing(); + + ImVec2 previewSize(256.0f, 256.0f); + if (node.textureHandle != InvalidTextureHandle) { + ImGui::Image(node.textureHandle, previewSize); + } + else { + ImVec2 cursorPos = ImGui::GetCursorScreenPos(); + ImDrawList* drawList = ImGui::GetWindowDrawList(); + drawList->AddRectFilled(cursorPos, ImVec2(cursorPos.x + previewSize.x, cursorPos.y + previewSize.y), IM_COL32(25, 25, 25, 255), 4.0f); + const char* previewText = "No Image"; + ImVec2 textSize = ImGui::CalcTextSize(previewText); + drawList->AddText(ImVec2(cursorPos.x + (previewSize.x - textSize.x) * 0.5f, cursorPos.y + (previewSize.y - textSize.y) * 0.5f), IM_COL32(255, 100, 100, 255), previewText); + ImGui::Dummy(previewSize); + } + + ImGui::Spacing(); + + for (const auto& pin : node.pins) { + ImVec2 pinTextSize = ImGui::CalcTextSize("RGB ->"); + + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + previewSize.x - pinTextSize.x); + + ed::BeginPin(ed::PinId(pin.id), ed::PinKind::Output); + ImGui::Text("RGB ->"); + ed::EndPin(); + } + + ed::EndNode(); + } + const char* MaterialGraphView::GetPinName(GraphPinType type) { switch (type) { case GraphPinType::Albedo: return "Albedo"; diff --git a/SynapseEngine/Editor/View/MaterialGraph/MaterialGraphView.h b/SynapseEngine/Editor/View/MaterialGraph/MaterialGraphView.h index ab39a798..8188a378 100644 --- a/SynapseEngine/Editor/View/MaterialGraph/MaterialGraphView.h +++ b/SynapseEngine/Editor/View/MaterialGraph/MaterialGraphView.h @@ -23,6 +23,8 @@ namespace Syn { void Draw(MaterialGraphViewModel& vm) override; private: const char* GetPinName(GraphPinType type); + void DrawMaterialNode(const GraphNodeData& node); + void DrawTextureNode(const GraphNodeData& node); private: ax::NodeEditor::EditorContext* _context = nullptr; }; diff --git a/SynapseEngine/Editor/View/MaterialHierarchy/MaterialHierarchyView.cpp b/SynapseEngine/Editor/View/MaterialHierarchy/MaterialHierarchyView.cpp new file mode 100644 index 00000000..275e41d1 --- /dev/null +++ b/SynapseEngine/Editor/View/MaterialHierarchy/MaterialHierarchyView.cpp @@ -0,0 +1,133 @@ +#include "MaterialHierarchyView.h" +#include "Editor/Manager/EditorIcons.h" +#include "Editor/Widgets/CardWidget.h" +#include + +namespace Syn { + void MaterialHierarchyView::Draw(MaterialHierarchyViewModel& vm) { + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8, 8)); + ImGuiWindowFlags windowFlags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse; + + if (ImGui::Begin(SYN_ICON_BRUSH " Materials", nullptr, windowFlags)) { + + auto getCardState = [this](const char* name) -> bool& { + std::string key(name); + if (_cardStates.find(key) == _cardStates.end()) _cardStates[key] = true; + return _cardStates[key]; + }; + + float mainContentBottomY = ImGui::GetWindowPos().y + ImGui::GetWindowContentRegionMax().y; + constexpr const char* CardMaterialsTitle = "MaterialListCard"; + + if (Syn::UI::BeginCard(CardMaterialsTitle, SYN_ICON_BRUSH, getCardState(CardMaterialsTitle))) { + + RenderTopBar(vm); + + const auto& state = vm.GetState(); + + ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.08f, 0.08f, 0.08f, 0.6f)); + ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 8.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); + + float currentY = ImGui::GetCursorScreenPos().y; + float tableHeight = mainContentBottomY - currentY - 12.0f; + if (tableHeight < 100.0f) tableHeight = 100.0f; + + ImGui::BeginChild("MaterialTableContainer", ImVec2(0, tableHeight), ImGuiChildFlags_Borders, ImGuiWindowFlags_NoScrollbar); + + if (ImGui::BeginTable("MaterialTable", 1, ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_ScrollY | ImGuiTableFlags_Resizable)) { + ImGui::TableSetupScrollFreeze(0, 1); + ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch); + + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); + ImGui::TableSetColumnIndex(0); + + float cellWidth = ImGui::GetColumnWidth(); + float textWidth = ImGui::CalcTextSize("Name").x; + ImVec2 startPos = ImGui::GetCursorPos(); + + ImGui::TableHeader(""); + ImGui::SetCursorPos(ImVec2(startPos.x + (cellWidth - textWidth) * 0.5f, startPos.y + 3.0f)); + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); + ImGui::Text("Name"); + ImGui::PopStyleColor(); + + ImGuiListClipper clipper; + clipper.Begin(static_cast(state.filteredNodes.size())); + + while (clipper.Step()) { + for (int row = clipper.DisplayStart; row < clipper.DisplayEnd; ++row) { + RenderMaterialRow(vm, state.filteredNodes[row]); + } + } + + ImGui::EndTable(); + } + ImGui::EndChild(); + ImGui::PopStyleVar(2); + ImGui::PopStyleColor(); + } + Syn::UI::EndCard(); + + if (ImGui::IsMouseDown(ImGuiMouseButton_Left) && ImGui::IsWindowHovered()) { + vm.Dispatch(MaterialSelectIntent{ INVALID_MATERIAL_ID }); + } + } + ImGui::End(); + ImGui::PopStyleVar(); + } + + void MaterialHierarchyView::RenderTopBar(MaterialHierarchyViewModel& vm) { + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(8, 6)); + + float barHeight = ImGui::GetFrameHeight(); + ImGui::BeginChild("MaterialTopBar", ImVec2(0, barHeight), false, ImGuiWindowFlags_NoScrollbar); + + if (ImGui::Button(SYN_ICON_SYNC " Refresh")) { + vm.Dispatch(MaterialRefreshIntent{}); + } + + ImGui::SameLine(); + ImGui::Dummy(ImVec2(8.0f, 0.0f)); + ImGui::SameLine(); + + ImGui::AlignTextToFramePadding(); + ImGui::TextDisabled(SYN_ICON_SEARCH); + ImGui::SameLine(); + + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + + const auto& state = vm.GetState(); + char searchBuffer[256]; + strncpy(searchBuffer, state.searchQuery.c_str(), sizeof(searchBuffer)); + searchBuffer[sizeof(searchBuffer) - 1] = '\0'; + + if (ImGui::InputTextWithHint("##SearchMaterials", "Search...", searchBuffer, IM_ARRAYSIZE(searchBuffer))) { + vm.Dispatch(MaterialSetSearchQueryIntent{ std::string(searchBuffer) }); + } + + ImGui::EndChild(); + ImGui::PopStyleVar(); + ImGui::Spacing(); + } + + void MaterialHierarchyView::RenderMaterialRow(MaterialHierarchyViewModel& vm, const MaterialNode& node) { + ImGui::PushID(node.id); + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + + ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen | ImGuiTreeNodeFlags_SpanFullWidth | ImGuiTreeNodeFlags_FramePadding; + if (vm.GetState().selectedMaterial == node.id) { + flags |= ImGuiTreeNodeFlags_Selected; + } + + std::string label = node.icon + " " + node.name; + ImGui::TreeNodeEx((void*)(intptr_t)node.id, flags, "%s", label.c_str()); + + if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { + vm.Dispatch(MaterialSelectIntent{ node.id }); + } + + ImGui::PopID(); + } +} \ No newline at end of file diff --git a/SynapseEngine/Editor/View/MaterialHierarchy/MaterialHierarchyView.h b/SynapseEngine/Editor/View/MaterialHierarchy/MaterialHierarchyView.h new file mode 100644 index 00000000..6b63ab46 --- /dev/null +++ b/SynapseEngine/Editor/View/MaterialHierarchy/MaterialHierarchyView.h @@ -0,0 +1,17 @@ +#pragma once +#include "Editor/View/IView.h" +#include "EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyViewModel.h" +#include +#include + +namespace Syn { + class MaterialHierarchyView : public IView { + public: + void Draw(MaterialHierarchyViewModel& vm) override; + private: + void RenderTopBar(MaterialHierarchyViewModel& vm); + void RenderMaterialRow(MaterialHierarchyViewModel& vm, const MaterialNode& node); + private: + std::unordered_map _cardStates; + }; +} \ No newline at end of file diff --git a/SynapseEngine/Editor/View/MaterialProperties/MaterialPropertiesView.cpp b/SynapseEngine/Editor/View/MaterialProperties/MaterialPropertiesView.cpp new file mode 100644 index 00000000..e3fd161f --- /dev/null +++ b/SynapseEngine/Editor/View/MaterialProperties/MaterialPropertiesView.cpp @@ -0,0 +1,137 @@ +#include "MaterialPropertiesView.h" +#include "Editor/Manager/EditorIcons.h" +#include "Editor/Widgets/CardWidget.h" +#include "Editor/Widgets/PropertyGrid.h" +#include + +namespace Syn { + void MaterialPropertiesView::Draw(MaterialPropertiesViewModel& vm) { + const auto& state = vm.GetState(); + + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8, 8)); + ImGuiWindowFlags windowFlags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse; + + if (ImGui::Begin(SYN_ICON_INFO_CIRCLE " Material Properties", nullptr, windowFlags)) { + + auto getCardState = [this](const char* name) -> bool& { + std::string key(name); + if (_cardStates.find(key) == _cardStates.end()) _cardStates[key] = true; + return _cardStates[key]; + }; + + if (!state.hasSelection) { + ImGui::TextDisabled("No material selected."); + ImGui::End(); + ImGui::PopStyleVar(); + return; + } + + Material editedMat = state.materialData; + bool isModified = false; + + constexpr const char* BasicPropsCard = "MatBasicPropsCard"; + if (Syn::UI::BeginCard(BasicPropsCard, SYN_ICON_SLIDERS_H, getCardState(BasicPropsCard))) { + if (Syn::UI::BeginPropertyGrid("MatPropsGrid")) { + + if (Syn::UI::PropertyColor4("Color", editedMat.color)) { + isModified = true; + } + + if (Syn::UI::PropertyColor3("Emissive Color", editedMat.emissiveColor)) { + isModified = true; + } + + if (Syn::UI::PropertyDragFloat("Emissive Intensity", editedMat.emissiveIntensity, 0.1f, 0.0f, 1000.0f, "%.2f")) { + isModified = true; + } + + if (Syn::UI::PropertyDragFloat2("UV Scale", editedMat.uvScale, 0.05f, 0.0f, 0.0f, "%.2f")) { + isModified = true; + } + + Syn::UI::PropertySeparator(); + + if (Syn::UI::PropertyDragFloat("Metalness", editedMat.metalness, 0.01f, 0.0f, 1.0f, "%.2f")) { + isModified = true; + } + + if (Syn::UI::PropertyDragFloat("Roughness", editedMat.roughness, 0.01f, 0.0f, 1.0f, "%.2f")) { + isModified = true; + } + + if (Syn::UI::PropertyDragFloat("AO Strength", editedMat.aoStrength, 0.01f, 0.0f, 1.0f, "%.2f")) { + isModified = true; + } + + Syn::UI::PropertySeparator(); + + if (Syn::UI::PropertyCheckbox("Double Sided", editedMat.doubleSided)) { + isModified = true; + } + + if (Syn::UI::PropertyCheckbox("Transparent", editedMat.isTransparent)) { + isModified = true; + } + + Syn::UI::EndPropertyGrid(); + } + Syn::UI::EndCard(); + } + + ImGui::Spacing(); + + constexpr const char* TexturesCard = "MatTexturesCard"; + if (Syn::UI::BeginCard(TexturesCard, SYN_ICON_IMAGE, getCardState(TexturesCard))) { + if (Syn::UI::BeginPropertyGrid("MatTexGrid")) { + + DrawTextureSlot("Albedo", editedMat.albedoTexture, state.albedoName, state.availableTextures, isModified); + DrawTextureSlot("Normal", editedMat.normalTexture, state.normalName, state.availableTextures, isModified); + DrawTextureSlot("Metalness", editedMat.metalnessTexture, state.metalnessName, state.availableTextures, isModified); + DrawTextureSlot("Roughness", editedMat.roughnessTexture, state.roughnessName, state.availableTextures, isModified); + DrawTextureSlot("MetallicRoughness", editedMat.metallicRoughnessTexture, state.metallicRoughnessName, state.availableTextures, isModified); + DrawTextureSlot("Emissive", editedMat.emissiveTexture, state.emissiveName, state.availableTextures, isModified); + DrawTextureSlot("Ambient Occlusion", editedMat.ambientOcclusionTexture, state.aoName, state.availableTextures, isModified); + + Syn::UI::EndPropertyGrid(); + } + Syn::UI::EndCard(); + } + + if (isModified) { + vm.Dispatch(UpdateMaterialPropertyIntent{ editedMat }); + } + } + + ImGui::End(); + ImGui::PopStyleVar(); + } + + void MaterialPropertiesView::DrawTextureSlot(const char* label, uint32_t& currentTexId, const std::string& currentName, const std::vector& options, bool& changed) { + + if (Syn::UI::BeginPropertyCombo(label, currentName.c_str())) { + + if (ImGui::Selectable("None", currentTexId == 0xFFFFFFFF)) { + if (currentTexId != 0xFFFFFFFF) { + currentTexId = 0xFFFFFFFF; + changed = true; + } + } + + for (const auto& opt : options) { + bool isSelected = (currentTexId == opt.id); + if (ImGui::Selectable(opt.name.c_str(), isSelected)) { + if (currentTexId != opt.id) { + currentTexId = opt.id; + changed = true; + } + } + + if (isSelected) { + ImGui::SetItemDefaultFocus(); + } + } + + Syn::UI::EndPropertyCombo(); + } + } +} \ No newline at end of file diff --git a/SynapseEngine/Editor/View/MaterialProperties/MaterialPropertiesView.h b/SynapseEngine/Editor/View/MaterialProperties/MaterialPropertiesView.h new file mode 100644 index 00000000..966ae678 --- /dev/null +++ b/SynapseEngine/Editor/View/MaterialProperties/MaterialPropertiesView.h @@ -0,0 +1,16 @@ +#pragma once +#include "Editor/View/IView.h" +#include "EditorCore/ViewModels/MaterialProperties/MaterialPropertiesViewModel.h" +#include +#include + +namespace Syn { + class MaterialPropertiesView : public IView { + public: + void Draw(MaterialPropertiesViewModel& vm) override; + private: + void DrawTextureSlot(const char* label, uint32_t& currentTexId, const std::string& currentName, const std::vector& options, bool& changed); + private: + std::unordered_map _cardStates; + }; +} \ No newline at end of file diff --git a/SynapseEngine/Editor/Widgets/PropertyGrid.cpp b/SynapseEngine/Editor/Widgets/PropertyGrid.cpp index 3308d96b..aa88ab8c 100644 --- a/SynapseEngine/Editor/Widgets/PropertyGrid.cpp +++ b/SynapseEngine/Editor/Widgets/PropertyGrid.cpp @@ -122,4 +122,13 @@ namespace Syn::UI { return ImGui::Checkbox(widgetId.c_str(), &value); } + bool BeginPropertyCombo(const char* label, const char* preview_value, int indentLevel) { + BeginProperty(label, indentLevel); + std::string widgetId = std::string("##") + label; + return ImGui::BeginCombo(widgetId.c_str(), preview_value); + } + + void EndPropertyCombo() { + ImGui::EndCombo(); + } } \ No newline at end of file diff --git a/SynapseEngine/Editor/Widgets/PropertyGrid.h b/SynapseEngine/Editor/Widgets/PropertyGrid.h index d476921c..2f54334b 100644 --- a/SynapseEngine/Editor/Widgets/PropertyGrid.h +++ b/SynapseEngine/Editor/Widgets/PropertyGrid.h @@ -24,4 +24,7 @@ namespace Syn::UI { bool PropertyColor4(const char* label, glm::vec4& color, int indentLevel = 0); bool PropertyCheckbox(const char* label, bool& value, int indentLevel = 0); + + bool BeginPropertyCombo(const char* label, const char* preview_value, int indentLevel = 0); + void EndPropertyCombo(); } \ No newline at end of file diff --git a/SynapseEngine/Editor/Workspace/MaterialWorkspace.cpp b/SynapseEngine/Editor/Workspace/MaterialWorkspace.cpp index 6367b1af..5b4641f1 100644 --- a/SynapseEngine/Editor/Workspace/MaterialWorkspace.cpp +++ b/SynapseEngine/Editor/Workspace/MaterialWorkspace.cpp @@ -3,9 +3,16 @@ #include "Editor/View/ContentBrowser/ContentBrowserView.h" #include "EditorCore/ViewModels/ContentBrowser/ContentBrowserViewModel.h" + #include "Editor/View/MaterialGraph/MaterialGraphView.h" #include "EditorCore/ViewModels/MaterialGraph/MaterialGraphViewModel.h" +#include "Editor/View/MaterialHierarchy/MaterialHierarchyView.h" +#include "EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyViewModel.h" + +#include "Editor/View/MaterialProperties/MaterialPropertiesView.h" +#include "EditorCore/ViewModels/MaterialProperties/MaterialPropertiesViewModel.h" + namespace Syn { MaterialWorkspace::MaterialWorkspace(EditorContext* context, IconManager* iconManager, const std::string& assetPath) @@ -18,10 +25,22 @@ namespace Syn { ContentBrowserViewModel{ _context->GetFileSystemApi(), _assetPath } ); + using MaterialHierarchyWin = EditorWindow; + AddWindow( + MaterialHierarchyView{}, + MaterialHierarchyViewModel{ _context->GetMaterialApi() } + ); + using MaterialGraphWin = EditorWindow; AddWindow( MaterialGraphView{}, - MaterialGraphViewModel{ _context->GetMaterialApi() } + MaterialGraphViewModel{ _context->GetMaterialApi(), _context->GetTextureApi() } + ); + + using MaterialPropertiesWin = EditorWindow; + AddWindow( + MaterialPropertiesView{}, + MaterialPropertiesViewModel{ _context->GetMaterialApi(), _context->GetTextureApi() } ); } diff --git a/SynapseEngine/EditorCore/Api/IMaterialApi.h b/SynapseEngine/EditorCore/Api/IMaterialApi.h index c3dfbd53..c4189b73 100644 --- a/SynapseEngine/EditorCore/Api/IMaterialApi.h +++ b/SynapseEngine/EditorCore/Api/IMaterialApi.h @@ -2,27 +2,35 @@ #include #include #include +#include "Engine/Material/Material.h" +//Todo: Domain Material!! + namespace Syn { - struct MaterialApiDesc { - uint32_t id; - std::string name; - }; + constexpr uint32_t INVALID_MATERIAL_ID = 0xFFFFFFFF; - struct TextureApiDesc { + struct MaterialItemData { uint32_t id; std::string name; + std::string path; }; class IMaterialApi { public: virtual ~IMaterialApi() = default; - virtual std::vector GetAllMaterials() const = 0; - virtual std::vector GetAllTextures() const = 0; + virtual std::vector GetAllMaterials() const = 0; + virtual uint32_t GetSelectedMaterial() const = 0; + virtual void SetSelectedMaterial(uint32_t id) = 0; + virtual uint64_t GetVersion() const = 0; + virtual std::string GetMaterialName(uint32_t materialId) const = 0; + virtual uint32_t GetLinkedTexture(uint32_t materialId, uint32_t textureType) const = 0; virtual void LinkTextureToMaterial(uint32_t materialId, uint32_t textureType, uint32_t textureId) = 0; virtual void UnlinkTextureFromMaterial(uint32_t materialId, uint32_t textureType) = 0; + + virtual bool GetMaterialData(uint32_t materialId, Material& outMaterial) const = 0; + virtual void UpdateMaterialData(uint32_t materialId, const Material& material) = 0; }; } \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/MaterialGraph/MaterialGraphState.h b/SynapseEngine/EditorCore/ViewModels/MaterialGraph/MaterialGraphState.h index 6e537f89..365941fc 100644 --- a/SynapseEngine/EditorCore/ViewModels/MaterialGraph/MaterialGraphState.h +++ b/SynapseEngine/EditorCore/ViewModels/MaterialGraph/MaterialGraphState.h @@ -2,25 +2,26 @@ #include #include #include +#include "EditorCore/Types/TextureHandle.h" namespace Syn { using GraphID = uint64_t; - enum class GraphNodeType { + enum class GraphNodeType { Material, Texture }; enum class GraphPinType { - Albedo, - Normal, - Metalness, - Roughness, - MetallicRoughness, - Emissive, - AmbientOcclusion, - TextureOutput + Albedo = 0, + Normal = 1, + Metalness = 2, + Roughness = 3, + MetallicRoughness = 4, + Emissive = 5, + AmbientOcclusion = 6, + TextureOutput = 99 }; struct GraphPinData { @@ -36,6 +37,7 @@ namespace Syn { uint32_t engineResourceId; std::string name; std::vector pins; + TextureHandle textureHandle = InvalidTextureHandle; }; struct GraphLinkData { @@ -45,6 +47,10 @@ namespace Syn { }; struct MaterialGraphState { + bool isMaterialSelected = false; + uint32_t selectedMaterialId = 0xFFFFFFFF; + std::string selectedMaterialName = ""; + std::vector nodes; std::vector links; GraphID nextId = 1; diff --git a/SynapseEngine/EditorCore/ViewModels/MaterialGraph/MaterialGraphViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/MaterialGraph/MaterialGraphViewModel.cpp index 3a43a37e..14223065 100644 --- a/SynapseEngine/EditorCore/ViewModels/MaterialGraph/MaterialGraphViewModel.cpp +++ b/SynapseEngine/EditorCore/ViewModels/MaterialGraph/MaterialGraphViewModel.cpp @@ -3,14 +3,21 @@ namespace Syn { - MaterialGraphViewModel::MaterialGraphViewModel(IMaterialApi* materialApi) - : _materialApi(materialApi) - { - BuildGraphFromEngine(); - } + MaterialGraphViewModel::MaterialGraphViewModel(IMaterialApi* materialApi, ITextureApi* textureApi) + : _materialApi(materialApi), _textureApi(textureApi) + {} void MaterialGraphViewModel::SyncWithEngine() { + if (!_materialApi) return; + + uint32_t currentSel = _materialApi->GetSelectedMaterial(); + uint64_t currentVer = _materialApi->GetVersion(); + if (currentSel != _lastSelectedMaterial || currentVer != _lastEngineVersion) { + RebuildGraphForSelectedMaterial(currentSel); + _lastSelectedMaterial = currentSel; + _lastEngineVersion = currentVer; + } } void MaterialGraphViewModel::Dispatch(const MaterialGraphIntent& intent) { @@ -26,67 +33,84 @@ namespace Syn { }, intent); } - void MaterialGraphViewModel::BuildGraphFromEngine() { + void MaterialGraphViewModel::RebuildGraphForSelectedMaterial(uint32_t materialId) { _state.nodes.clear(); _state.links.clear(); _state.nextId = 1; - auto materials = _materialApi->GetAllMaterials(); - for (const auto& mat : materials) { - GraphID matNodeId = _state.nextId++; - GraphNodeData matNode{ matNodeId, GraphNodeType::Material, mat.id, mat.name, {} }; - - matNode.pins.push_back({ _state.nextId++, matNodeId, GraphPinType::Albedo, true }); - matNode.pins.push_back({ _state.nextId++, matNodeId, GraphPinType::Normal, true }); - matNode.pins.push_back({ _state.nextId++, matNodeId, GraphPinType::Metalness, true }); - matNode.pins.push_back({ _state.nextId++, matNodeId, GraphPinType::Roughness, true }); - matNode.pins.push_back({ _state.nextId++, matNodeId, GraphPinType::Emissive, true }); - matNode.pins.push_back({ _state.nextId++, matNodeId, GraphPinType::AmbientOcclusion, true }); - _state.nodes.push_back(matNode); + if (materialId == INVALID_MATERIAL_ID) { + _state.isMaterialSelected = false; + return; } - auto textures = _materialApi->GetAllTextures(); + _state.isMaterialSelected = true; + _state.selectedMaterialId = materialId; + _state.selectedMaterialName = _materialApi->GetMaterialName(materialId); + + GraphID matNodeId = _state.nextId++; + GraphNodeData matNode{ matNodeId, GraphNodeType::Material, materialId, _state.selectedMaterialName, {} }; + + std::vector pinTypes = { + GraphPinType::Albedo, GraphPinType::Normal, GraphPinType::Metalness, + GraphPinType::Roughness, GraphPinType::MetallicRoughness, + GraphPinType::Emissive, GraphPinType::AmbientOcclusion + }; + + auto allTextures = _textureApi ? _textureApi->GetAllTextures() : std::vector(); + + for (auto pType : pinTypes) { + GraphID pinId = _state.nextId++; + matNode.pins.push_back({ pinId, matNodeId, pType, true }); + + uint32_t linkedTexId = _materialApi->GetLinkedTexture(materialId, static_cast(pType)); + + if (linkedTexId != 0xFFFFFFFF) { + + std::string texName = "Unknown Texture"; + for (const auto& t : allTextures) { + if (t.id == linkedTexId) { + texName = t.name; + break; + } + } + + GraphID texNodeId = _state.nextId++; + GraphNodeData texNode{ texNodeId, GraphNodeType::Texture, linkedTexId, texName, {} }; + + if (_textureApi) { + texNode.textureHandle = _textureApi->GetTextureHandle(linkedTexId); + } - for (const auto& tex : textures) { - GraphID texNodeId = _state.nextId++; - GraphNodeData texNode{ texNodeId, GraphNodeType::Texture, tex.id, tex.name, {} }; + GraphID texOutPinId = _state.nextId++; + texNode.pins.push_back({ texOutPinId, texNodeId, GraphPinType::TextureOutput, false }); - texNode.pins.push_back({ _state.nextId++, texNodeId, GraphPinType::TextureOutput, false }); - _state.nodes.push_back(texNode); + _state.nodes.push_back(texNode); + _state.links.push_back({ _state.nextId++, texOutPinId, pinId }); + } } + + matNode.textureHandle = InvalidTextureHandle; + _state.nodes.push_back(matNode); } void MaterialGraphViewModel::HandleCreateLink(const CreateLinkIntent& intent) { - const GraphPinData* startPin = nullptr; - const GraphNodeData* startNode = nullptr; - - const GraphPinData* endPin = nullptr; - const GraphNodeData* endNode = nullptr; + const GraphPinData* startPin = nullptr; const GraphNodeData* startNode = nullptr; + const GraphPinData* endPin = nullptr; const GraphNodeData* endNode = nullptr; for (const auto& node : _state.nodes) { for (const auto& pin : node.pins) { - if (pin.id == intent.startPinId) { - startPin = &pin; - startNode = &node; - } - if (pin.id == intent.endPinId) { - endPin = &pin; - endNode = &node; - } + if (pin.id == intent.startPinId) { startPin = &pin; startNode = &node; } + if (pin.id == intent.endPinId) { endPin = &pin; endNode = &node; } } } if (startNode && endNode && startPin && endPin) { - if (startNode->type == GraphNodeType::Texture && - endNode->type == GraphNodeType::Material) - { + if (startNode->type == GraphNodeType::Texture && endNode->type == GraphNodeType::Material) { _materialApi->LinkTextureToMaterial( endNode->engineResourceId, static_cast(endPin->type), startNode->engineResourceId ); - - _state.links.push_back({ _state.nextId++, intent.startPinId, intent.endPinId }); } } } @@ -109,8 +133,6 @@ namespace Syn { } } } - - _state.links.erase(it); } } } \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/MaterialGraph/MaterialGraphViewModel.h b/SynapseEngine/EditorCore/ViewModels/MaterialGraph/MaterialGraphViewModel.h index 6f144e66..3bd60ded 100644 --- a/SynapseEngine/EditorCore/ViewModels/MaterialGraph/MaterialGraphViewModel.h +++ b/SynapseEngine/EditorCore/ViewModels/MaterialGraph/MaterialGraphViewModel.h @@ -3,23 +3,28 @@ #include "MaterialGraphState.h" #include "MaterialGraphIntent.h" #include "EditorCore/Api/IMaterialApi.h" - +#include "EditorCore/Api/ITextureApi.h" namespace Syn { class MaterialGraphViewModel : public IViewModel { public: - MaterialGraphViewModel(IMaterialApi* materialApi); + MaterialGraphViewModel(IMaterialApi* materialApi, ITextureApi* textureApi); const MaterialGraphState& GetState() const override { return _state; } void SyncWithEngine() override; void Dispatch(const MaterialGraphIntent& intent) override; private: - void BuildGraphFromEngine(); + void RebuildGraphForSelectedMaterial(uint32_t materialId); void HandleCreateLink(const CreateLinkIntent& intent); void HandleDeleteLink(const DeleteLinkIntent& intent); private: IMaterialApi* _materialApi = nullptr; + ITextureApi* _textureApi = nullptr; + MaterialGraphState _state; + + uint32_t _lastSelectedMaterial = INVALID_MATERIAL_ID; + uint64_t _lastEngineVersion = 0; }; } \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyIntent.cpp b/SynapseEngine/EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyIntent.cpp new file mode 100644 index 00000000..7b9a9728 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyIntent.cpp @@ -0,0 +1 @@ +#include "MaterialHierarchyIntent.h" \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyIntent.h b/SynapseEngine/EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyIntent.h new file mode 100644 index 00000000..2bf23ed7 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyIntent.h @@ -0,0 +1,21 @@ +#pragma once +#include +#include + +namespace Syn { + struct MaterialSelectIntent { + uint32_t materialId; + }; + + struct MaterialSetSearchQueryIntent { + std::string query; + }; + + struct MaterialRefreshIntent {}; + + using MaterialHierarchyIntent = std::variant< + MaterialSelectIntent, + MaterialSetSearchQueryIntent, + MaterialRefreshIntent + >; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyState.cpp b/SynapseEngine/EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyState.cpp new file mode 100644 index 00000000..f05862df --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyState.cpp @@ -0,0 +1 @@ +#include "MaterialHierarchyState.h" \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyState.h b/SynapseEngine/EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyState.h new file mode 100644 index 00000000..f43d80ec --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyState.h @@ -0,0 +1,17 @@ +#pragma once +#include +#include + +namespace Syn { + struct MaterialNode { + uint32_t id; + std::string name; + std::string icon; + }; + + struct MaterialHierarchyState { + std::vector filteredNodes; + uint32_t selectedMaterial = 0xFFFFFFFF; + std::string searchQuery = ""; + }; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyViewModel.cpp new file mode 100644 index 00000000..47131f54 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyViewModel.cpp @@ -0,0 +1,70 @@ +#include "MaterialHierarchyViewModel.h" +#include "Editor/Manager/EditorIcons.h" +#include + +namespace Syn { + MaterialHierarchyViewModel::MaterialHierarchyViewModel(IMaterialApi* materialApi) + : _materialApi(materialApi) + {} + + void MaterialHierarchyViewModel::SyncWithEngine() { + if (!_materialApi) return; + + uint64_t currentVersion = _materialApi->GetVersion(); + uint32_t currentSelection = _materialApi->GetSelectedMaterial(); + + if (currentVersion != _lastEngineVersion || _isDirty) { + RebuildList(); + _lastEngineVersion = currentVersion; + _isDirty = false; + } + + if (_state.selectedMaterial != currentSelection) { + _state.selectedMaterial = currentSelection; + } + } + + void MaterialHierarchyViewModel::Dispatch(const MaterialHierarchyIntent& intent) { + std::visit([this](auto&& arg) { + using T = std::decay_t; + + if constexpr (std::is_same_v) { + if (_materialApi) { + _materialApi->SetSelectedMaterial(arg.materialId); + } + } + else if constexpr (std::is_same_v) { + if (_state.searchQuery != arg.query) { + _state.searchQuery = arg.query; + _isDirty = true; + } + } + else if constexpr (std::is_same_v) { + _isDirty = true; + } + }, intent); + } + + void MaterialHierarchyViewModel::RebuildList() { + if (!_materialApi) return; + + _state.filteredNodes.clear(); + auto allMaterials = _materialApi->GetAllMaterials(); + + std::string searchLower = _state.searchQuery; + std::transform(searchLower.begin(), searchLower.end(), searchLower.begin(), ::tolower); + + for (const auto& mat : allMaterials) { + std::string nameLower = mat.name; + std::transform(nameLower.begin(), nameLower.end(), nameLower.begin(), ::tolower); + + if (searchLower.empty() || nameLower.find(searchLower) != std::string::npos) { + MaterialNode node; + node.id = mat.id; + node.name = mat.name; + node.icon = SYN_ICON_BRUSH; + _state.filteredNodes.push_back(node); + } + } + } +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyViewModel.h b/SynapseEngine/EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyViewModel.h new file mode 100644 index 00000000..0e53fe72 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyViewModel.h @@ -0,0 +1,26 @@ +#pragma once +#include "EditorCore/ViewModels/IViewModel.h" +#include "MaterialHierarchyState.h" +#include "MaterialHierarchyIntent.h" +#include "EditorCore/Api/IMaterialApi.h" + +namespace Syn { + class MaterialHierarchyViewModel : public IViewModel { + public: + MaterialHierarchyViewModel(IMaterialApi* materialApi); + ~MaterialHierarchyViewModel() override = default; + + const MaterialHierarchyState& GetState() const override { return _state; } + void SyncWithEngine() override; + void Dispatch(const MaterialHierarchyIntent& intent) override; + + private: + void RebuildList(); + private: + IMaterialApi* _materialApi = nullptr; + MaterialHierarchyState _state; + + bool _isDirty = true; + uint64_t _lastEngineVersion = 0; + }; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/MaterialProperties/MaterialPropertiesIntent.cpp b/SynapseEngine/EditorCore/ViewModels/MaterialProperties/MaterialPropertiesIntent.cpp new file mode 100644 index 00000000..31fdf889 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/MaterialProperties/MaterialPropertiesIntent.cpp @@ -0,0 +1 @@ +#include "MaterialPropertiesIntent.h" \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/MaterialProperties/MaterialPropertiesIntent.h b/SynapseEngine/EditorCore/ViewModels/MaterialProperties/MaterialPropertiesIntent.h new file mode 100644 index 00000000..cf1aecce --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/MaterialProperties/MaterialPropertiesIntent.h @@ -0,0 +1,15 @@ +#pragma once +#include +#include "Engine/Material/Material.h" + +namespace Syn { + //Todo intents + + struct UpdateMaterialPropertyIntent { + Material updatedMaterial; + }; + + using MaterialPropertiesIntent = std::variant< + UpdateMaterialPropertyIntent + >; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/MaterialProperties/MaterialPropertiesState.cpp b/SynapseEngine/EditorCore/ViewModels/MaterialProperties/MaterialPropertiesState.cpp new file mode 100644 index 00000000..2675de15 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/MaterialProperties/MaterialPropertiesState.cpp @@ -0,0 +1 @@ +#include "MaterialPropertiesState.h" \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/MaterialProperties/MaterialPropertiesState.h b/SynapseEngine/EditorCore/ViewModels/MaterialProperties/MaterialPropertiesState.h new file mode 100644 index 00000000..61716f5c --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/MaterialProperties/MaterialPropertiesState.h @@ -0,0 +1,28 @@ +#pragma once +#include +#include +#include "Engine/Material/Material.h" + +namespace Syn { + struct TextureOption { + uint32_t id; + std::string name; + }; + + struct MaterialPropertiesState { + bool hasSelection = false; + uint32_t selectedMaterialId = 0xFFFFFFFF; + std::string materialName = ""; + + Material materialData; + std::vector availableTextures; + + std::string albedoName = "None"; + std::string normalName = "None"; + std::string metalnessName = "None"; + std::string roughnessName = "None"; + std::string metallicRoughnessName = "None"; + std::string emissiveName = "None"; + std::string aoName = "None"; + }; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/MaterialProperties/MaterialPropertiesViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/MaterialProperties/MaterialPropertiesViewModel.cpp new file mode 100644 index 00000000..742730b3 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/MaterialProperties/MaterialPropertiesViewModel.cpp @@ -0,0 +1,64 @@ +#include "MaterialPropertiesViewModel.h" + +namespace Syn { + MaterialPropertiesViewModel::MaterialPropertiesViewModel(IMaterialApi* materialApi, ITextureApi* textureApi) + : _materialApi(materialApi), _textureApi(textureApi) + {} + + void MaterialPropertiesViewModel::SyncWithEngine() { + if (!_materialApi) return; + + uint32_t selectedId = _materialApi->GetSelectedMaterial(); + + if (selectedId == 0xFFFFFFFF) { + _state.hasSelection = false; + return; + } + + Material matData; + if (_materialApi->GetMaterialData(selectedId, matData)) { + _state.hasSelection = true; + _state.selectedMaterialId = selectedId; + _state.materialName = _materialApi->GetMaterialName(selectedId); + _state.materialData = matData; + + _state.availableTextures.clear(); + auto texs = _textureApi ? _textureApi->GetAllTextures() : std::vector(); + for (const auto& t : texs) { + _state.availableTextures.push_back({ t.id, t.name }); + } + + auto getTexName = [&](uint32_t id) -> std::string { + if (id == 0xFFFFFFFF) return "None"; + for (const auto& t : texs) { + if (t.id == id) return t.name; + } + return "Unknown"; + }; + + _state.albedoName = getTexName(matData.albedoTexture); + _state.normalName = getTexName(matData.normalTexture); + _state.metalnessName = getTexName(matData.metalnessTexture); + _state.roughnessName = getTexName(matData.roughnessTexture); + _state.metallicRoughnessName = getTexName(matData.metallicRoughnessTexture); + _state.emissiveName = getTexName(matData.emissiveTexture); + _state.aoName = getTexName(matData.ambientOcclusionTexture); + } + else { + _state.hasSelection = false; + } + } + + void MaterialPropertiesViewModel::Dispatch(const MaterialPropertiesIntent& intent) { + std::visit([this](auto&& arg) { + using T = std::decay_t; + + if constexpr (std::is_same_v) { + if (_state.hasSelection) { + _materialApi->UpdateMaterialData(_state.selectedMaterialId, arg.updatedMaterial); + _state.materialData = arg.updatedMaterial; + } + } + }, intent); + } +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/MaterialProperties/MaterialPropertiesViewModel.h b/SynapseEngine/EditorCore/ViewModels/MaterialProperties/MaterialPropertiesViewModel.h new file mode 100644 index 00000000..b8d90d47 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/MaterialProperties/MaterialPropertiesViewModel.h @@ -0,0 +1,22 @@ +#pragma once +#include "EditorCore/ViewModels/IViewModel.h" +#include "MaterialPropertiesState.h" +#include "MaterialPropertiesIntent.h" +#include "EditorCore/Api/IMaterialApi.h" +#include "EditorCore/Api/ITextureApi.h" + +namespace Syn { + class MaterialPropertiesViewModel : public IViewModel { + public: + MaterialPropertiesViewModel(IMaterialApi* materialApi, ITextureApi* textureApi); + ~MaterialPropertiesViewModel() override = default; + + const MaterialPropertiesState& GetState() const override { return _state; } + void SyncWithEngine() override; + void Dispatch(const MaterialPropertiesIntent& intent) override; + private: + IMaterialApi* _materialApi = nullptr; + ITextureApi* _textureApi = nullptr; + MaterialPropertiesState _state; + }; +} \ No newline at end of file diff --git a/SynapseEngine/imgui.ini b/SynapseEngine/imgui.ini index 2d4f4c80..7bc663af 100644 --- a/SynapseEngine/imgui.ini +++ b/SynapseEngine/imgui.ini @@ -9,20 +9,20 @@ Size=400,400 Collapsed=0 [Window][ Inspector] -Pos=1468,23 -Size=452,490 +Pos=1852,23 +Size=452,633 Collapsed=0 DockId=0x00000005,0 [Window][ Viewport] Pos=407,23 -Size=1059,680 +Size=1443,967 Collapsed=0 DockId=0x00000001,0 [Window][ Graphics & Environment] -Pos=1468,515 -Size=452,494 +Pos=1852,658 +Size=452,638 Collapsed=0 DockId=0x00000006,0 @@ -34,13 +34,13 @@ DockId=0x08BD597D,1 [Window][ Scene Hierarchy] Pos=0,23 -Size=405,532 +Size=405,687 Collapsed=0 DockId=0x00000009,0 [Window][ Performance Profiler] -Pos=0,557 -Size=405,452 +Pos=0,712 +Size=405,584 Collapsed=0 DockId=0x0000000A,0 @@ -51,19 +51,19 @@ Collapsed=0 DockId=0x08BD597D,0 [Window][ Output Log] -Pos=407,705 -Size=1059,304 +Pos=407,992 +Size=1443,304 Collapsed=0 DockId=0x00000002,0 [Window][HostWindow_Scene] Pos=0,23 -Size=1920,986 +Size=2304,1273 Collapsed=0 [Window][Content_Scene] -Pos=439,932 -Size=1411,364 +Pos=407,992 +Size=1443,304 Collapsed=0 DockId=0x00000002,1 @@ -137,7 +137,7 @@ Column 1 Weight=1.0000 [Docking][Data] DockSpace ID=0x08BD597D Pos=128,95 Size=2304,1273 CentralNode=1 -DockSpace ID=0x4713F8A8 Window=0xFBC4AE05 Pos=-1920,237 Size=1920,986 Split=X Selected=0x1C1AF642 +DockSpace ID=0x4713F8A8 Window=0xFBC4AE05 Pos=128,95 Size=2304,1273 Split=X Selected=0x1C1AF642 DockNode ID=0x00000007 Parent=0x4713F8A8 SizeRef=405,1273 Split=Y Selected=0xF995F4A5 DockNode ID=0x00000009 Parent=0x00000007 SizeRef=437,687 Selected=0xF995F4A5 DockNode ID=0x0000000A Parent=0x00000007 SizeRef=437,584 Selected=0x02B8E2DB From 9e9af7e181d0667807cb9b21832e49bf8eac7d12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20P=C3=A9ter?= Date: Mon, 29 Jun 2026 11:10:54 +0200 Subject: [PATCH 10/51] Implemented model hierarchy mvi --- .gitignore | 2 +- .../Assets/{ => Engine}/CameraIcon.png | 0 .../{ => Engine}/DirectionLightIcon.png | 0 .../Assets/{ => Engine}/PointLightIcon.png | 0 .../Assets/{ => Engine}/SpotLightIcon.png | 0 SynapseEngine/Assets/dark_icon.svg | 6 + SynapseEngine/Assets/dark_icon_pink.svg | 6 + SynapseEngine/Assets/light_icon.svg | 6 + SynapseEngine/Assets/light_icon_pink.svg | 6 + .../Editor/Core/Windows/GlfwWindow.cpp | 11 ++ .../Editor/Core/Windows/GlfwWindow.h | 2 +- SynapseEngine/Editor/Core/Windows/Window.h | 2 +- .../Editor/EditorApi/EditorContext.cpp | 2 + .../Editor/EditorApi/EditorContext.h | 3 + .../Editor/EditorApi/Impl/ModelApiImpl.cpp | 52 ++++++ .../Editor/EditorApi/Impl/ModelApiImpl.h | 25 +++ SynapseEngine/Editor/Synapse.cpp | 13 ++ .../ModelHierarchy/ModelHierarchyView.cpp | 165 ++++++++++++++++++ .../View/ModelHierarchy/ModelHierarchyView.h | 17 ++ .../Editor/Workspace/ModelWorkspace.cpp | 10 ++ SynapseEngine/EditorCore/Api/IModelApi.h | 31 ++++ .../MaterialHierarchyViewModel.cpp | 2 +- .../ModelHierarchy/ModelHierarchyIntent.cpp | 1 + .../ModelHierarchy/ModelHierarchyIntent.h | 30 ++++ .../ModelHierarchy/ModelHierarchyState.cpp | 1 + .../ModelHierarchy/ModelHierarchyState.h | 28 +++ .../ModelHierarchyViewModel.cpp | 137 +++++++++++++++ .../ModelHierarchy/ModelHierarchyViewModel.h | 31 ++++ SynapseEngine/Engine/Engine.cpp | 5 + SynapseEngine/Engine/Engine.h | 2 + .../Engine/Image/Loader/SvgImageLoader.cpp | 74 ++++++++ .../Engine/Image/Loader/SvgImageLoader.h | 21 +++ .../Engine/Manager/ResourceManager.cpp | 2 + .../Converter/DefaultCpuModelExtractor.cpp | 1 + .../Converter/DefaultGpuModelConverter.cpp | 17 +- .../Mesh/Data/Common/MeshInstanceDescriptor.h | 2 + .../Engine/Mesh/Data/Cpu/CpuModelData.h | 1 + .../Engine/Mesh/Data/Gpu/GpuBatchedModel.h | 3 + .../Engine/Mesh/Loader/AssimpMeshLoader.cpp | 36 +++- .../Passes/Billboard/CameraBillboardPass.cpp | 2 +- .../Billboard/DirectionLightBillboardPass.cpp | 2 +- .../Billboard/PointLightBillboardPass.cpp | 2 +- .../Billboard/SpotLightBillboardPass.cpp | 2 +- .../Input/Binary/BinaryInputArchive.cpp | 4 + .../Archive/Input/Binary/BinaryInputArchive.h | 1 + .../Archive/Input/IInputArchive.h | 2 + .../Input/Json/NlohmannJsonInputArchive.cpp | 4 + .../Input/Json/NlohmannJsonInputArchive.h | 1 + .../Input/Toml/PlusPlusTomlInputArchive.cpp | 4 + .../Input/Toml/PlusPlusTomlInputArchive.h | 1 + .../Archive/Input/Xml/TinyXmlInputArchive.cpp | 4 + .../Archive/Input/Xml/TinyXmlInputArchive.h | 1 + .../Input/Yaml/YamlCppInputArchive.cpp | 7 + .../Archive/Input/Yaml/YamlCppInputArchive.h | 1 + .../Output/Binary/BinaryOutputArchive.cpp | 5 + .../Output/Binary/BinaryOutputArchive.h | 1 + .../Archive/Output/IOutputArchive.h | 2 + .../Output/Json/NlohmannJsonOutputArchive.cpp | 6 + .../Output/Json/NlohmannJsonOutputArchive.h | 1 + .../Output/Toml/PlusPlusTomlOutputArchive.cpp | 4 + .../Output/Toml/PlusPlusTomlOutputArchive.h | 1 + .../Output/Xml/TinyXmlOutputArchive.cpp | 4 + .../Archive/Output/Xml/TinyXmlOutputArchive.h | 1 + .../Output/Yaml/YamlCppOutputArchive.cpp | 4 + .../Output/Yaml/YamlCppOutputArchive.h | 1 + .../Schema/Models/CpuModelDataSchema.h | 4 + .../Schema/Models/GpuBatchedModelSchema.h | 2 + .../Models/MeshInstanceDescriptorSchema.h | 25 +++ SynapseEngine/imgui.ini | 151 ---------------- SynapseEngine/xmake.lua | 1 + 70 files changed, 831 insertions(+), 173 deletions(-) rename SynapseEngine/Assets/{ => Engine}/CameraIcon.png (100%) rename SynapseEngine/Assets/{ => Engine}/DirectionLightIcon.png (100%) rename SynapseEngine/Assets/{ => Engine}/PointLightIcon.png (100%) rename SynapseEngine/Assets/{ => Engine}/SpotLightIcon.png (100%) create mode 100644 SynapseEngine/Assets/dark_icon.svg create mode 100644 SynapseEngine/Assets/dark_icon_pink.svg create mode 100644 SynapseEngine/Assets/light_icon.svg create mode 100644 SynapseEngine/Assets/light_icon_pink.svg create mode 100644 SynapseEngine/Editor/EditorApi/Impl/ModelApiImpl.cpp create mode 100644 SynapseEngine/Editor/EditorApi/Impl/ModelApiImpl.h create mode 100644 SynapseEngine/Editor/View/ModelHierarchy/ModelHierarchyView.cpp create mode 100644 SynapseEngine/Editor/View/ModelHierarchy/ModelHierarchyView.h create mode 100644 SynapseEngine/EditorCore/Api/IModelApi.h create mode 100644 SynapseEngine/EditorCore/ViewModels/ModelHierarchy/ModelHierarchyIntent.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/ModelHierarchy/ModelHierarchyIntent.h create mode 100644 SynapseEngine/EditorCore/ViewModels/ModelHierarchy/ModelHierarchyState.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/ModelHierarchy/ModelHierarchyState.h create mode 100644 SynapseEngine/EditorCore/ViewModels/ModelHierarchy/ModelHierarchyViewModel.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/ModelHierarchy/ModelHierarchyViewModel.h create mode 100644 SynapseEngine/Engine/Image/Loader/SvgImageLoader.cpp create mode 100644 SynapseEngine/Engine/Image/Loader/SvgImageLoader.h create mode 100644 SynapseEngine/Engine/Serialization/Schema/Models/MeshInstanceDescriptorSchema.h delete mode 100644 SynapseEngine/imgui.ini diff --git a/.gitignore b/.gitignore index eac150c9..13156ebf 100644 --- a/.gitignore +++ b/.gitignore @@ -27,7 +27,7 @@ install_manifest.txt *.dae *.jpg -*.png +# *.png *.obj *.fbx *.mtl diff --git a/SynapseEngine/Assets/CameraIcon.png b/SynapseEngine/Assets/Engine/CameraIcon.png similarity index 100% rename from SynapseEngine/Assets/CameraIcon.png rename to SynapseEngine/Assets/Engine/CameraIcon.png diff --git a/SynapseEngine/Assets/DirectionLightIcon.png b/SynapseEngine/Assets/Engine/DirectionLightIcon.png similarity index 100% rename from SynapseEngine/Assets/DirectionLightIcon.png rename to SynapseEngine/Assets/Engine/DirectionLightIcon.png diff --git a/SynapseEngine/Assets/PointLightIcon.png b/SynapseEngine/Assets/Engine/PointLightIcon.png similarity index 100% rename from SynapseEngine/Assets/PointLightIcon.png rename to SynapseEngine/Assets/Engine/PointLightIcon.png diff --git a/SynapseEngine/Assets/SpotLightIcon.png b/SynapseEngine/Assets/Engine/SpotLightIcon.png similarity index 100% rename from SynapseEngine/Assets/SpotLightIcon.png rename to SynapseEngine/Assets/Engine/SpotLightIcon.png diff --git a/SynapseEngine/Assets/dark_icon.svg b/SynapseEngine/Assets/dark_icon.svg new file mode 100644 index 00000000..b2f994b0 --- /dev/null +++ b/SynapseEngine/Assets/dark_icon.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/SynapseEngine/Assets/dark_icon_pink.svg b/SynapseEngine/Assets/dark_icon_pink.svg new file mode 100644 index 00000000..30a0b22a --- /dev/null +++ b/SynapseEngine/Assets/dark_icon_pink.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/SynapseEngine/Assets/light_icon.svg b/SynapseEngine/Assets/light_icon.svg new file mode 100644 index 00000000..0083ecf3 --- /dev/null +++ b/SynapseEngine/Assets/light_icon.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/SynapseEngine/Assets/light_icon_pink.svg b/SynapseEngine/Assets/light_icon_pink.svg new file mode 100644 index 00000000..f37c0eca --- /dev/null +++ b/SynapseEngine/Assets/light_icon_pink.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/SynapseEngine/Editor/Core/Windows/GlfwWindow.cpp b/SynapseEngine/Editor/Core/Windows/GlfwWindow.cpp index 37fbd795..1246920b 100644 --- a/SynapseEngine/Editor/Core/Windows/GlfwWindow.cpp +++ b/SynapseEngine/Editor/Core/Windows/GlfwWindow.cpp @@ -160,4 +160,15 @@ namespace Syn { void GlfwWindow::SetCallbacks(const WindowCallbacks& callbacks) { _data.callbacks = callbacks; } + + void GlfwWindow::SetIcon(uint32_t width, uint32_t height, const uint8_t* pixels) { + if (!_window || !pixels) return; + + GLFWimage image; + image.width = static_cast(width); + image.height = static_cast(height); + image.pixels = const_cast(pixels); + + glfwSetWindowIcon(_window, 1, &image); + } } \ No newline at end of file diff --git a/SynapseEngine/Editor/Core/Windows/GlfwWindow.h b/SynapseEngine/Editor/Core/Windows/GlfwWindow.h index f0995baf..cabba29c 100644 --- a/SynapseEngine/Editor/Core/Windows/GlfwWindow.h +++ b/SynapseEngine/Editor/Core/Windows/GlfwWindow.h @@ -22,7 +22,7 @@ namespace Syn { void CreateSurface(VkInstance instance, VkSurfaceKHR* surface) override; void SetCallbacks(const WindowCallbacks& callbacks) override; - + void SetIcon(uint32_t width, uint32_t height, const uint8_t* pixels) override; void* GetNativePointer() const override { return _window; } private: void Init(const WindowConfig& config); diff --git a/SynapseEngine/Editor/Core/Windows/Window.h b/SynapseEngine/Editor/Core/Windows/Window.h index e122d9ab..c39a9560 100644 --- a/SynapseEngine/Editor/Core/Windows/Window.h +++ b/SynapseEngine/Editor/Core/Windows/Window.h @@ -47,7 +47,7 @@ namespace Syn virtual void CreateSurface(VkInstance instance, VkSurfaceKHR* surface) = 0; virtual void SetCallbacks(const WindowCallbacks& callbacks) = 0; - + virtual void SetIcon(uint32_t width, uint32_t height, const uint8_t* pixels) = 0; virtual void* GetNativePointer() const = 0; static std::unique_ptr Create(const WindowConfig& config); diff --git a/SynapseEngine/Editor/EditorApi/EditorContext.cpp b/SynapseEngine/Editor/EditorApi/EditorContext.cpp index 6468038f..f46c22c7 100644 --- a/SynapseEngine/Editor/EditorApi/EditorContext.cpp +++ b/SynapseEngine/Editor/EditorApi/EditorContext.cpp @@ -13,6 +13,7 @@ #include "Impl/PointLightApiImpl.h" #include "Impl/SpotLightApiImpl.h" #include "Impl/TextureApiImpl.h" +#include "Impl/ModelApiImpl.h" namespace Syn { EditorContext::EditorContext(Engine* engine, GuiTextureManager* textureManager) { @@ -32,6 +33,7 @@ namespace Syn { _pointLightApi = std::make_unique(sm); _spotLightApi = std::make_unique(sm); _textureApi = std::make_unique(engine->GetImageManager(), textureManager); + _modelApi = std::make_unique(engine->GetModelManager()); } EditorContext::~EditorContext() = default; diff --git a/SynapseEngine/Editor/EditorApi/EditorContext.h b/SynapseEngine/Editor/EditorApi/EditorContext.h index 6413b165..daa3ff14 100644 --- a/SynapseEngine/Editor/EditorApi/EditorContext.h +++ b/SynapseEngine/Editor/EditorApi/EditorContext.h @@ -17,6 +17,7 @@ #include "EditorCore/Api/IPointLightApi.h" #include "EditorCore/Api/ISpotLightApi.h" #include "EditorCore/Api/ITextureApi.h" +#include "EditorCore/Api/IModelApi.h" namespace Syn { class EditorContext { @@ -38,6 +39,7 @@ namespace Syn { IPointLightApi* GetPointLightApi() const { return _pointLightApi.get(); } ISpotLightApi* GetSpotLightApi() const { return _spotLightApi.get(); } ITextureApi* GetTextureApi() const { return _textureApi.get(); } + IModelApi* GetModelApi() const { return _modelApi.get(); } private: std::unique_ptr _selectionApi; std::unique_ptr _tagApi; @@ -53,5 +55,6 @@ namespace Syn { std::unique_ptr _pointLightApi; std::unique_ptr _spotLightApi; std::unique_ptr _textureApi; + std::unique_ptr _modelApi; }; } \ No newline at end of file diff --git a/SynapseEngine/Editor/EditorApi/Impl/ModelApiImpl.cpp b/SynapseEngine/Editor/EditorApi/Impl/ModelApiImpl.cpp new file mode 100644 index 00000000..706c6148 --- /dev/null +++ b/SynapseEngine/Editor/EditorApi/Impl/ModelApiImpl.cpp @@ -0,0 +1,52 @@ +#include "ModelApiImpl.h" +#include + +namespace Syn { + + std::vector ModelApiImpl::GetAllModels() const { + if (!_modelManager) return {}; + + std::vector result; + auto paths = _modelManager->GetResourcePaths(); + + for (uint32_t i = 0; i < paths.size(); ++i) { + if (_modelManager->GetEntryState(i) == ResourceState::Ready) { + std::filesystem::path p(paths[i]); + result.push_back({ i, p.filename().string(), paths[i] }); + } + } + return result; + } + + uint64_t ModelApiImpl::GetVersion() const { + return _modelManager ? _modelManager->GetVersion() : 0; + } + + void ModelApiImpl::SetSelected(uint32_t modelId, int32_t nodeIndex) { + _selectedModelId = modelId; + _selectedNodeIndex = nodeIndex; + } + + std::pair ModelApiImpl::GetSelected() const { + return { _selectedModelId, _selectedNodeIndex }; + } + + const CpuModelData* ModelApiImpl::GetModelCpuData(uint32_t modelId) const { + if (!_modelManager || modelId == INVALID_MODEL_ID) return nullptr; + + auto resource = _modelManager->GetResource(modelId); + if (resource) { + return &resource->cpuData; + } + + return nullptr; + } + + std::string ModelApiImpl::GetNodeName(uint32_t modelId, uint16_t nodeIndex) const { + if (!_modelManager || modelId == INVALID_MODEL_ID) + return "Node_" + std::to_string(nodeIndex); + + auto resource = _modelManager->GetResource(modelId); + return resource->cpuData.meshNodeDescriptors[nodeIndex].name; + } +} \ No newline at end of file diff --git a/SynapseEngine/Editor/EditorApi/Impl/ModelApiImpl.h b/SynapseEngine/Editor/EditorApi/Impl/ModelApiImpl.h new file mode 100644 index 00000000..4ed87882 --- /dev/null +++ b/SynapseEngine/Editor/EditorApi/Impl/ModelApiImpl.h @@ -0,0 +1,25 @@ +#pragma once +#include "EditorCore/Api/IModelApi.h" +#include "Engine/Mesh/ModelManager.h" + +namespace Syn { + class ModelApiImpl : public IModelApi { + public: + ModelApiImpl(ModelManager* modelManager) + : _modelManager(modelManager) {} + + std::vector GetAllModels() const override; + uint64_t GetVersion() const override; + + void SetSelected(uint32_t modelId, int32_t nodeIndex) override; + std::pair GetSelected() const override; + + const CpuModelData* GetModelCpuData(uint32_t modelId) const override; + std::string GetNodeName(uint32_t modelId, uint16_t nodeIndex) const override; + + private: + ModelManager* _modelManager; + uint32_t _selectedModelId = INVALID_MODEL_ID; + int32_t _selectedNodeIndex = INVALID_NODE_INDEX; + }; +} \ No newline at end of file diff --git a/SynapseEngine/Editor/Synapse.cpp b/SynapseEngine/Editor/Synapse.cpp index 5c9525fe..912bec20 100644 --- a/SynapseEngine/Editor/Synapse.cpp +++ b/SynapseEngine/Editor/Synapse.cpp @@ -17,6 +17,9 @@ #include "Engine/Utils/PathUtils.h" #include "Editor/View/IGuiWindow.h" +#include "Engine/Image/Loader/SvgImageLoader.h" +#include "Engine/Logger/SynLog.h" + Synapse::Synapse(const Syn::ApplicationConfig& config) : Syn::Application(config) { @@ -64,6 +67,16 @@ void Synapse::OnInit() { _engine = std::make_unique(params); + std::string iconPath = Syn::PathUtils::GetAbsolutePathString(std::string(ASSET_PATH) + "/dark_icon.svg"); + Syn::SvgImageLoader svgLoader; + if (auto rawImageOpt = svgLoader.LoadFile(iconPath)) { + const auto& rawImage = rawImageOpt.value(); + GetWindow().SetIcon(rawImage.width, rawImage.height, rawImage.pixels.data()); + } + else { + Syn::Error("Failed to load window icon from: {}", iconPath); + } + #ifndef SYN_PERFORMANCE auto vkContext = _engine->GetVkContext(); diff --git a/SynapseEngine/Editor/View/ModelHierarchy/ModelHierarchyView.cpp b/SynapseEngine/Editor/View/ModelHierarchy/ModelHierarchyView.cpp new file mode 100644 index 00000000..6b095113 --- /dev/null +++ b/SynapseEngine/Editor/View/ModelHierarchy/ModelHierarchyView.cpp @@ -0,0 +1,165 @@ +#include "ModelHierarchyView.h" +#include "Editor/Manager/EditorIcons.h" +#include "Editor/Widgets/CardWidget.h" +#include + +namespace Syn +{ + void ModelHierarchyView::Draw(ModelHierarchyViewModel& vm) { + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8, 8)); + ImGuiWindowFlags windowFlags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse; + + if (ImGui::Begin(SYN_ICON_CUBE " Model Hierarchy", nullptr, windowFlags)) { + + auto getCardState = [this](const char* name) -> bool& { + std::string key(name); + if (_cardStates.find(key) == _cardStates.end()) _cardStates[key] = true; + return _cardStates[key]; + }; + + float mainContentBottomY = ImGui::GetWindowPos().y + ImGui::GetWindowContentRegionMax().y; + constexpr const char* CardModelTitle = "ModelListCard"; + + if (Syn::UI::BeginCard(CardModelTitle, SYN_ICON_CUBE, getCardState(CardModelTitle))) { + + RenderTopBar(vm); + + const auto& state = vm.GetState(); + + ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.08f, 0.08f, 0.08f, 0.6f)); + ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 8.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); + + float currentY = ImGui::GetCursorScreenPos().y; + float tableHeight = mainContentBottomY - currentY - 12.0f; + if (tableHeight < 100.0f) tableHeight = 100.0f; + + ImGui::BeginChild("ModelHierarchyTableContainer", ImVec2(0, tableHeight), ImGuiChildFlags_Borders, ImGuiWindowFlags_NoScrollbar); + + if (ImGui::BeginTable("ModelTable", 2, ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_ScrollY | ImGuiTableFlags_Resizable)) { + ImGui::TableSetupScrollFreeze(0, 1); + ImGui::TableSetupColumn("Hierarchy", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("Tris", ImGuiTableColumnFlags_WidthFixed, 45.0f); + + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); + + ImGui::TableSetColumnIndex(0); + float cellWidth = ImGui::GetColumnWidth(); + float textWidth = ImGui::CalcTextSize("Hierarchy").x; + ImVec2 startPos = ImGui::GetCursorPos(); + + ImGui::TableHeader("##ColHierarchy"); + + ImGui::SetCursorPos(ImVec2(startPos.x + (cellWidth - textWidth) * 0.5f, startPos.y + 3.0f)); + ImGui::Text("Hierarchy"); + + ImGui::TableSetColumnIndex(1); + cellWidth = ImGui::GetColumnWidth(); + textWidth = ImGui::CalcTextSize("Tris").x; + startPos = ImGui::GetCursorPos(); + + ImGui::TableHeader("##ColTris"); + + ImGui::SetCursorPos(ImVec2(startPos.x + (cellWidth - textWidth) * 0.5f, startPos.y + 3.0f)); + ImGui::Text("Tris"); + + ImGuiListClipper clipper; + clipper.Begin(static_cast(state.flatNodes.size())); + + while (clipper.Step()) { + for (int row = clipper.DisplayStart; row < clipper.DisplayEnd; ++row) { + RenderNodeRow(vm, state.flatNodes[row]); + } + } + + ImGui::EndTable(); + } + ImGui::EndChild(); + ImGui::PopStyleVar(2); + ImGui::PopStyleColor(); + } + Syn::UI::EndCard(); + + if (ImGui::IsMouseDown(ImGuiMouseButton_Left) && ImGui::IsWindowHovered()) { + vm.Dispatch(ModelHierarchySelectIntent{ INVALID_MODEL_ID, -1 }); + } + } + ImGui::End(); + ImGui::PopStyleVar(); + } + + void ModelHierarchyView::RenderTopBar(ModelHierarchyViewModel& vm) { + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(8, 6)); + + float barHeight = ImGui::GetFrameHeight(); + ImGui::BeginChild("ModelTopBar", ImVec2(0, barHeight), false, ImGuiWindowFlags_NoScrollbar); + + if (ImGui::Button(SYN_ICON_SYNC " Refresh##ModelHierarchy")) { + vm.Dispatch(ModelHierarchyRefreshIntent{}); + } + + ImGui::SameLine(); + ImGui::Dummy(ImVec2(8.0f, 0.0f)); + ImGui::SameLine(); + + ImGui::AlignTextToFramePadding(); + ImGui::TextDisabled(SYN_ICON_SEARCH); + ImGui::SameLine(); + + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + + const auto& state = vm.GetState(); + char searchBuffer[256]; + strncpy(searchBuffer, state.searchQuery.c_str(), sizeof(searchBuffer)); + searchBuffer[sizeof(searchBuffer) - 1] = '\0'; + + if (ImGui::InputTextWithHint("##SearchModels", "Search...", searchBuffer, IM_ARRAYSIZE(searchBuffer))) { + vm.Dispatch(ModelHierarchySetSearchIntent{ std::string(searchBuffer) }); + } + + ImGui::EndChild(); + ImGui::PopStyleVar(); + ImGui::Spacing(); + } + + void ModelHierarchyView::RenderNodeRow(ModelHierarchyViewModel& vm, const ModelHierarchyNode& node) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + + ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_SpanFullWidth | ImGuiTreeNodeFlags_FramePadding | ImGuiTreeNodeFlags_NoTreePushOnOpen; + + if (!node.hasChildren) { + flags |= ImGuiTreeNodeFlags_Leaf; + } + + const auto& state = vm.GetState(); + if (state.selectedModelId == node.modelId && state.selectedDescriptorIndex == node.descriptorIndex) { + flags |= ImGuiTreeNodeFlags_Selected; + } + + ImGui::SetNextItemOpen(node.isExpanded, ImGuiCond_Always); + + float indentStep = 16.0f; + ImGui::Indent(node.depth * indentStep); + + std::string label = node.icon + " " + node.name + "##NodeID_" + std::to_string(node.modelId) + "_" + std::to_string(node.descriptorIndex); + + ImGui::TreeNodeEx(label.c_str(), flags); + + ImGui::Unindent(node.depth * indentStep); + + if (ImGui::IsItemClicked(ImGuiMouseButton_Left) && !ImGui::IsItemToggledOpen()) { + vm.Dispatch(ModelHierarchySelectIntent{ node.modelId, node.descriptorIndex }); + } + + if (ImGui::IsItemToggledOpen()) { + vm.Dispatch(ModelHierarchyToggleExpandIntent{ node.modelId, node.descriptorIndex, !node.isExpanded }); + } + + ImGui::TableNextColumn(); + + if (node.triangleCount > 0) { + ImGui::TextDisabled("%u", node.triangleCount); + } + } +} \ No newline at end of file diff --git a/SynapseEngine/Editor/View/ModelHierarchy/ModelHierarchyView.h b/SynapseEngine/Editor/View/ModelHierarchy/ModelHierarchyView.h new file mode 100644 index 00000000..b315935c --- /dev/null +++ b/SynapseEngine/Editor/View/ModelHierarchy/ModelHierarchyView.h @@ -0,0 +1,17 @@ +#pragma once +#include "Editor/View/IView.h" +#include "EditorCore/ViewModels/ModelHierarchy/ModelHierarchyViewModel.h" +#include +#include + +namespace Syn { + class ModelHierarchyView : public IView { + public: + void Draw(ModelHierarchyViewModel& vm) override; + private: + void RenderTopBar(ModelHierarchyViewModel& vm); + void RenderNodeRow(ModelHierarchyViewModel& vm, const ModelHierarchyNode& node); + private: + std::unordered_map _cardStates; + }; +} \ No newline at end of file diff --git a/SynapseEngine/Editor/Workspace/ModelWorkspace.cpp b/SynapseEngine/Editor/Workspace/ModelWorkspace.cpp index fc3fe9bd..a0ade482 100644 --- a/SynapseEngine/Editor/Workspace/ModelWorkspace.cpp +++ b/SynapseEngine/Editor/Workspace/ModelWorkspace.cpp @@ -4,17 +4,27 @@ #include "Editor/View/ContentBrowser/ContentBrowserView.h" #include "EditorCore/ViewModels/ContentBrowser/ContentBrowserViewModel.h" +#include "Editor/View/ModelHierarchy/ModelHierarchyView.h" +#include "EditorCore/ViewModels/ModelHierarchy/ModelHierarchyViewModel.h" + namespace Syn { ModelWorkspace::ModelWorkspace(EditorContext* context, IconManager* iconManager, const std::string& assetPath) : _context(context), _iconManager(iconManager), _assetPath(assetPath) {} void ModelWorkspace::Initialize() { + using ContentBrowserWin = EditorWindow; AddWindow( ContentBrowserView{ _iconManager, SYN_ICON_FOLDER_OPEN " Content Browser###Content_Model" }, ContentBrowserViewModel{ _context->GetFileSystemApi(), _assetPath } ); + + using ModelHierarchyWin = EditorWindow; + AddWindow( + ModelHierarchyView{}, + ModelHierarchyViewModel{ _context->GetModelApi() } + ); } } \ No newline at end of file diff --git a/SynapseEngine/EditorCore/Api/IModelApi.h b/SynapseEngine/EditorCore/Api/IModelApi.h new file mode 100644 index 00000000..08d5529b --- /dev/null +++ b/SynapseEngine/EditorCore/Api/IModelApi.h @@ -0,0 +1,31 @@ +#pragma once +#include +#include +#include +#include "Engine/Mesh/Data/Cpu/CpuModelData.h" + +namespace Syn +{ + constexpr uint32_t INVALID_MODEL_ID = 0xFFFFFFFF; + constexpr int32_t INVALID_NODE_INDEX = -1; + + struct ModelItemData { + uint32_t id; + std::string name; + std::string path; + }; + + class IModelApi { + public: + virtual ~IModelApi() = default; + + virtual std::vector GetAllModels() const = 0; + virtual uint64_t GetVersion() const = 0; + + virtual void SetSelected(uint32_t modelId, int32_t nodeIndex) = 0; + virtual std::pair GetSelected() const = 0; + + virtual const CpuModelData* GetModelCpuData(uint32_t modelId) const = 0; + virtual std::string GetNodeName(uint32_t modelId, uint16_t nodeIndex) const = 0; + }; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyViewModel.cpp index 47131f54..a888196c 100644 --- a/SynapseEngine/EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyViewModel.cpp +++ b/SynapseEngine/EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyViewModel.cpp @@ -1,5 +1,5 @@ #include "MaterialHierarchyViewModel.h" -#include "Editor/Manager/EditorIcons.h" +#include "Editor/Manager/EditorIcons.h" //Todo! #include namespace Syn { diff --git a/SynapseEngine/EditorCore/ViewModels/ModelHierarchy/ModelHierarchyIntent.cpp b/SynapseEngine/EditorCore/ViewModels/ModelHierarchy/ModelHierarchyIntent.cpp new file mode 100644 index 00000000..f6027834 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/ModelHierarchy/ModelHierarchyIntent.cpp @@ -0,0 +1 @@ +#include "ModelHierarchyIntent.h" \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/ModelHierarchy/ModelHierarchyIntent.h b/SynapseEngine/EditorCore/ViewModels/ModelHierarchy/ModelHierarchyIntent.h new file mode 100644 index 00000000..d6ddc749 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/ModelHierarchy/ModelHierarchyIntent.h @@ -0,0 +1,30 @@ +#pragma once +#include +#include +#include + +namespace Syn { + struct ModelHierarchySelectIntent { + uint32_t modelId; + int32_t descriptorIndex; + }; + + struct ModelHierarchyToggleExpandIntent { + uint32_t modelId; + int32_t descriptorIndex; + bool expand; + }; + + struct ModelHierarchySetSearchIntent { + std::string query; + }; + + struct ModelHierarchyRefreshIntent {}; + + using ModelHierarchyIntent = std::variant< + ModelHierarchySelectIntent, + ModelHierarchyToggleExpandIntent, + ModelHierarchySetSearchIntent, + ModelHierarchyRefreshIntent + >; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/ModelHierarchy/ModelHierarchyState.cpp b/SynapseEngine/EditorCore/ViewModels/ModelHierarchy/ModelHierarchyState.cpp new file mode 100644 index 00000000..4c8aadad --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/ModelHierarchy/ModelHierarchyState.cpp @@ -0,0 +1 @@ +#include "ModelHierarchyState.h" \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/ModelHierarchy/ModelHierarchyState.h b/SynapseEngine/EditorCore/ViewModels/ModelHierarchy/ModelHierarchyState.h new file mode 100644 index 00000000..8c1c8034 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/ModelHierarchy/ModelHierarchyState.h @@ -0,0 +1,28 @@ +#pragma once +#include +#include +#include + +namespace Syn { + struct ModelHierarchyNode { + uint32_t modelId = 0xFFFFFFFF; + int32_t descriptorIndex = -1; + + std::string name; + std::string icon; + + uint32_t triangleCount = 0; + bool isMeshNode = false; + + int depth = 0; + bool isExpanded = true; + bool hasChildren = false; + }; + + struct ModelHierarchyState { + std::vector flatNodes; + uint32_t selectedModelId = 0xFFFFFFFF; + int32_t selectedDescriptorIndex = -1; + std::string searchQuery = ""; + }; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/ModelHierarchy/ModelHierarchyViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/ModelHierarchy/ModelHierarchyViewModel.cpp new file mode 100644 index 00000000..68489d12 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/ModelHierarchy/ModelHierarchyViewModel.cpp @@ -0,0 +1,137 @@ +#include "ModelHierarchyViewModel.h" +#include "Editor/Manager/EditorIcons.h" +#include +#include + +namespace Syn { + ModelHierarchyViewModel::ModelHierarchyViewModel(IModelApi* modelApi) + : _modelApi(modelApi) + {} + + uint64_t ModelHierarchyViewModel::GetNodeKey(uint32_t modelId, int32_t descriptorIndex) const { + return (static_cast(modelId) << 32) | static_cast(descriptorIndex); + } + + void ModelHierarchyViewModel::SyncWithEngine() { + if (!_modelApi) return; + + uint64_t currentVersion = _modelApi->GetVersion(); + auto selection = _modelApi->GetSelected(); + + if (currentVersion != _lastEngineVersion || _isDirty) { + RebuildFlatList(); + _lastEngineVersion = currentVersion; + _isDirty = false; + } + + if (_state.selectedModelId != selection.first || _state.selectedDescriptorIndex != selection.second) { + _state.selectedModelId = selection.first; + _state.selectedDescriptorIndex = selection.second; + } + } + + void ModelHierarchyViewModel::Dispatch(const ModelHierarchyIntent& intent) { + std::visit([this](auto&& arg) { + using T = std::decay_t; + + if constexpr (std::is_same_v) { + if (_modelApi) _modelApi->SetSelected(arg.modelId, arg.descriptorIndex); + _state.selectedModelId = arg.modelId; + _state.selectedDescriptorIndex = arg.descriptorIndex; + } + else if constexpr (std::is_same_v) { + uint64_t key = GetNodeKey(arg.modelId, arg.descriptorIndex); + if (arg.expand) _expandedNodes.insert(key); + else _expandedNodes.erase(key); + _isDirty = true; + } + else if constexpr (std::is_same_v) { + if (_state.searchQuery != arg.query) { + _state.searchQuery = arg.query; + _isDirty = true; + } + } + else if constexpr (std::is_same_v) { + _isDirty = true; + } + }, intent); + } + + void ModelHierarchyViewModel::RebuildFlatList() { + if (!_modelApi) return; + + _state.flatNodes.clear(); + auto allModels = _modelApi->GetAllModels(); + + for (const auto& mod : allModels) { + if (const CpuModelData* cpuData = _modelApi->GetModelCpuData(mod.id)) { + + std::unordered_map> childMap; + for (uint32_t i = 0; i < cpuData->meshNodeDescriptors.size(); ++i) { + childMap[cpuData->meshNodeDescriptors[i].parentNodeIndex].push_back(i); + } + + TraverseAndFlatten(mod.id, -1, 0xFFFF, 0, *cpuData, childMap, mod.name); + } + } + } + + bool ModelHierarchyViewModel::TraverseAndFlatten(uint32_t modelId, int32_t descriptorIndex, uint16_t currentTransformIndex, int depth, const CpuModelData& cpuData, const std::unordered_map>& childMap, const std::string& nodeName) { + + bool matchesSearch = _state.searchQuery.empty() || + std::search(nodeName.begin(), nodeName.end(), _state.searchQuery.begin(), _state.searchQuery.end(), + [](char c1, char c2) { return std::tolower(static_cast(c1)) == std::tolower(static_cast(c2)); }) != nodeName.end(); + + bool hasChildren = childMap.contains(currentTransformIndex); + + uint64_t key = GetNodeKey(modelId, descriptorIndex); + bool isExpanded = _expandedNodes.contains(key) || !_state.searchQuery.empty(); + + size_t flattenIndex = _state.flatNodes.size(); + + ModelHierarchyNode node; + node.modelId = modelId; + node.descriptorIndex = descriptorIndex; + node.name = nodeName; + node.depth = depth; + node.hasChildren = hasChildren; + node.isExpanded = isExpanded; + + if (descriptorIndex == -1) { + node.icon = SYN_ICON_CUBE; + node.isMeshNode = false; + node.triangleCount = cpuData.globalIndexCount / 3; + } + else { + const auto& desc = cpuData.meshNodeDescriptors[descriptorIndex]; + node.icon = (desc.meshIndex != 0xFFFF) ? SYN_ICON_DRAW_POLYGON : SYN_ICON_PROJECT_DIAGRAM; + node.isMeshNode = (desc.meshIndex != 0xFFFF); + node.triangleCount = desc.indexCount / 3; + } + + _state.flatNodes.push_back(node); + + bool anyChildMatches = false; + + if (isExpanded && hasChildren) { + for (uint32_t childDescIdx : childMap.at(currentTransformIndex)) { + const auto& childDesc = cpuData.meshNodeDescriptors[childDescIdx]; + std::string childName = childDesc.name; + + if (childName.empty()) + childName = "Node_" + std::to_string(childDescIdx); + + if (TraverseAndFlatten(modelId, childDescIdx, childDesc.nodeIndex, depth + 1, cpuData, childMap, childName)) { + anyChildMatches = true; + } + } + } + + if (!_state.searchQuery.empty() && !matchesSearch && !anyChildMatches) { + _state.flatNodes.resize(flattenIndex); + return false; + } + + return true; + } +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/ModelHierarchy/ModelHierarchyViewModel.h b/SynapseEngine/EditorCore/ViewModels/ModelHierarchy/ModelHierarchyViewModel.h new file mode 100644 index 00000000..5e4cd572 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/ModelHierarchy/ModelHierarchyViewModel.h @@ -0,0 +1,31 @@ +#pragma once +#include "EditorCore/ViewModels/IViewModel.h" +#include "ModelHierarchyState.h" +#include "ModelHierarchyIntent.h" +#include "EditorCore/Api/IModelApi.h" +#include +#include + +namespace Syn { + class ModelHierarchyViewModel : public IViewModel { + public: + ModelHierarchyViewModel(IModelApi* modelApi); + ~ModelHierarchyViewModel() override = default; + + const ModelHierarchyState& GetState() const override { return _state; } + void SyncWithEngine() override; + void Dispatch(const ModelHierarchyIntent& intent) override; + + private: + void RebuildFlatList(); + bool TraverseAndFlatten(uint32_t modelId, int32_t descriptorIndex, uint16_t currentTransformIndex, int depth, const CpuModelData& cpuData, const std::unordered_map>& childMap, const std::string& nodeName); + uint64_t GetNodeKey(uint32_t modelId, int32_t descriptorIndex) const; + private: + IModelApi* _modelApi = nullptr; + ModelHierarchyState _state; + + bool _isDirty = true; + uint64_t _lastEngineVersion = 0; + std::unordered_set _expandedNodes; + }; +} \ No newline at end of file diff --git a/SynapseEngine/Engine/Engine.cpp b/SynapseEngine/Engine/Engine.cpp index 84fc4ea1..3ca5a36b 100644 --- a/SynapseEngine/Engine/Engine.cpp +++ b/SynapseEngine/Engine/Engine.cpp @@ -367,7 +367,12 @@ namespace Syn MaterialManager* Engine::GetMaterialManager() { return ServiceLocator::GetMaterialManager(); } + ImageManager* Engine::GetImageManager() { return ServiceLocator::GetImageManager(); } + + ModelManager* Engine::GetModelManager() { + return ServiceLocator::GetModelManager(); + } } \ No newline at end of file diff --git a/SynapseEngine/Engine/Engine.h b/SynapseEngine/Engine/Engine.h index 8a91cde0..efbf3210 100644 --- a/SynapseEngine/Engine/Engine.h +++ b/SynapseEngine/Engine/Engine.h @@ -27,6 +27,7 @@ namespace Syn { class Serializer; class MaterialManager; class ImageManager; + class ModelManager; } namespace Syn @@ -62,6 +63,7 @@ namespace Syn public: MaterialManager* GetMaterialManager(); ImageManager* GetImageManager(); + ModelManager* GetModelManager(); std::shared_ptr GetMemorySink() const { return _memorySink; } private: void Init(const EngineInitParams& params); diff --git a/SynapseEngine/Engine/Image/Loader/SvgImageLoader.cpp b/SynapseEngine/Engine/Image/Loader/SvgImageLoader.cpp new file mode 100644 index 00000000..81ed8e91 --- /dev/null +++ b/SynapseEngine/Engine/Image/Loader/SvgImageLoader.cpp @@ -0,0 +1,74 @@ +#include "SvgImageLoader.h" +#include "Engine/Logger/SynLog.h" + +#define NANOSVG_IMPLEMENTATION +#include +#define NANOSVGRAST_IMPLEMENTATION +#include + +namespace Syn +{ + std::optional SvgImageLoader::LoadFile(const std::filesystem::path& path) + { + NSVGimage* image = nsvgParseFromFile(path.string().c_str(), "px", 96.0f); + + if (!image) { + Error("Failed to parse SVG file: {}", path.string()); + return std::nullopt; + } + + return ProcessImage(image); + } + + std::optional SvgImageLoader::LoadMemory(const std::vector& data) + { + if (data.empty()) return std::nullopt; + + std::vector mutableData(data.begin(), data.end()); + mutableData.push_back('\0'); + + NSVGimage* image = nsvgParse(mutableData.data(), "px", 96.0f); + + if (!image) { + Error("SvgImageLoader failed to parse SVG from memory"); + return std::nullopt; + } + + return ProcessImage(image); + } + + std::optional SvgImageLoader::ProcessImage(NSVGimage* image) + { + int width = static_cast(image->width); + int height = static_cast(image->height); + + NSVGrasterizer* rast = nsvgCreateRasterizer(); + if (!rast) { + Error("Could not create SVG rasterizer"); + nsvgDelete(image); + return std::nullopt; + } + + RawImage rawImage{}; + rawImage.width = static_cast(width); + rawImage.height = static_cast(height); + rawImage.depth = 1; + rawImage.mipLevels = 1; + rawImage.isCompressed = false; + rawImage.format = VK_FORMAT_R8G8B8A8_UNORM; + + size_t imageSize = static_cast(width * height * 4); + rawImage.pixels.resize(imageSize); + + nsvgRasterize(rast, image, 0.0f, 0.0f, 1.0f, rawImage.pixels.data(), width, height, width * 4); + nsvgDeleteRasterizer(rast); + nsvgDelete(image); + + return rawImage; + } + + std::vector SvgImageLoader::GetSupportedExtensions() const + { + return { ".svg" }; + } +} \ No newline at end of file diff --git a/SynapseEngine/Engine/Image/Loader/SvgImageLoader.h b/SynapseEngine/Engine/Image/Loader/SvgImageLoader.h new file mode 100644 index 00000000..adcfb339 --- /dev/null +++ b/SynapseEngine/Engine/Image/Loader/SvgImageLoader.h @@ -0,0 +1,21 @@ +#pragma once +#include "IImageLoader.h" + +struct NSVGimage; + +namespace Syn +{ + class SYN_API SvgImageLoader : public IImageLoader + { + public: + SvgImageLoader() = default; + ~SvgImageLoader() override = default; + + std::optional LoadFile(const std::filesystem::path& path) override; + std::optional LoadMemory(const std::vector& data) override; + std::vector GetSupportedExtensions() const override; + + private: + std::optional ProcessImage(struct NSVGimage* image); + }; +} \ No newline at end of file diff --git a/SynapseEngine/Engine/Manager/ResourceManager.cpp b/SynapseEngine/Engine/Manager/ResourceManager.cpp index ba3c57ed..ee0e1f2e 100644 --- a/SynapseEngine/Engine/Manager/ResourceManager.cpp +++ b/SynapseEngine/Engine/Manager/ResourceManager.cpp @@ -22,6 +22,7 @@ #include "Engine/Image/Converter/DefaultImageCooker.h" #include "Engine/Image/Loader/StbImageLoader.h" #include "Engine/Image/Loader/GliImageLoader.h" +#include "Engine/Image/Loader/SvgImageLoader.h" #include "Engine/Image/Uploader/DefaultGpuImageUploader.h" #include "Engine/Animation/Loader/AnimationLoaderRegistry.h" @@ -67,6 +68,7 @@ namespace Syn { _imageBuilder->RegisterLoader(std::make_shared(), 1); _imageBuilder->RegisterLoader(std::make_shared(), 1); + _imageBuilder->RegisterLoader(std::make_shared(), 1); ServiceLocator::ProvideImageBuilder(_imageBuilder.get()); diff --git a/SynapseEngine/Engine/Mesh/Converter/DefaultCpuModelExtractor.cpp b/SynapseEngine/Engine/Mesh/Converter/DefaultCpuModelExtractor.cpp index ef0e605c..02bd6f82 100644 --- a/SynapseEngine/Engine/Mesh/Converter/DefaultCpuModelExtractor.cpp +++ b/SynapseEngine/Engine/Mesh/Converter/DefaultCpuModelExtractor.cpp @@ -53,5 +53,6 @@ namespace Syn } outCpuData.indices = gpuData.indexedData.indices; + outCpuData.meshNodeDescriptors = gpuData.meshNodeDescriptors; } } \ No newline at end of file diff --git a/SynapseEngine/Engine/Mesh/Converter/DefaultGpuModelConverter.cpp b/SynapseEngine/Engine/Mesh/Converter/DefaultGpuModelConverter.cpp index 27c1924d..ccc415ac 100644 --- a/SynapseEngine/Engine/Mesh/Converter/DefaultGpuModelConverter.cpp +++ b/SynapseEngine/Engine/Mesh/Converter/DefaultGpuModelConverter.cpp @@ -40,16 +40,20 @@ namespace Syn } } + uint32_t validMeshCounter = 0; for (size_t instanceIdx = 0; instanceIdx < cookedModel.meshNodeDescriptors.size(); ++instanceIdx) { const auto& instanceDesc = cookedModel.meshNodeDescriptors[instanceIdx]; - const uint32_t globalMeshID = globalMeshIdOffset + static_cast(instanceIdx); + if (instanceDesc.meshIndex == 0xFFFF) { + continue; + } + + const uint32_t globalMeshID = globalMeshIdOffset + static_cast(validMeshCounter); const auto& cookedMesh = cookedModel.meshes[instanceDesc.meshIndex]; uint32_t globalNodeIndex = globalNodeOffset + instanceDesc.nodeIndex; const uint32_t currentMeshVertexOffset = static_cast(result.vertexData.vertexPositions.size()); - // MESH COLLIDER BEMÁSOLÁSA GpuMeshCollider localMeshCollider{}; localMeshCollider.center = cookedMesh.collider.sphere.center; localMeshCollider.radius = cookedMesh.collider.sphere.radius; @@ -62,7 +66,6 @@ namespace Syn GpuMeshCollider modelSpaceCollider = MeshUtils::TransformCollider(localMeshCollider, nodeTransform); result.indexedData.meshColliders.push_back(modelSpaceCollider); - // VERTEX ADATOK MÁSOLÁSA ÉS INDEXEK ELTOLÁSA for (const auto& v : cookedMesh.vertices) { GpuVertexPosition pos; @@ -87,7 +90,7 @@ namespace Syn { const auto& lodData = cookedMesh.lods[lodLevel]; - { //Normal + { GpuMeshDescriptor meshDesc{}; meshDesc.vertexOffset = currentMeshVertexOffset; meshDesc.vertexCount = static_cast(cookedMesh.vertices.size()); @@ -103,7 +106,7 @@ namespace Syn } } - { //Mesh Shader + { GpuMeshletDrawDescriptor meshletDrawDesc{}; meshletDrawDesc.meshletOffset = static_cast(result.meshletData.meshletDescriptors.size()); meshletDrawDesc.meshletCount = static_cast(lodData.meshlets.size()); @@ -112,7 +115,6 @@ namespace Syn result.meshletData.drawDescriptors.push_back(meshletDrawDesc); lastValidMeshletDrawDesc = meshletDrawDesc; - // Apró Meshletek Adatainak Másolása for (const auto& cookedMeshlet : lodData.meshlets) { GpuMeshletDescriptor meshletDesc{}; @@ -158,12 +160,15 @@ namespace Syn result.indexedData.lodDescriptors[i].meshCount++; result.indexedData.lodDescriptors[i].indexCount = static_cast(result.indexedData.indices.size()); } + + validMeshCounter++; } result.globalVertexCount = static_cast(result.vertexData.vertexPositions.size()); result.globalIndexCount = static_cast(result.indexedData.indices.size()); result.globalAverageLodIndexCount = result.globalIndexCount / MAX_LODS; result.globalMeshCount = static_cast(result.indexedData.meshDescriptors.size() / 4); + result.meshNodeDescriptors = cookedModel.meshNodeDescriptors; return result; } diff --git a/SynapseEngine/Engine/Mesh/Data/Common/MeshInstanceDescriptor.h b/SynapseEngine/Engine/Mesh/Data/Common/MeshInstanceDescriptor.h index e7223343..7518e073 100644 --- a/SynapseEngine/Engine/Mesh/Data/Common/MeshInstanceDescriptor.h +++ b/SynapseEngine/Engine/Mesh/Data/Common/MeshInstanceDescriptor.h @@ -1,11 +1,13 @@ #pragma once #include "Engine/SynApi.h" #include +#include namespace Syn { struct SYN_API MeshInstanceDescriptor { + std::string name; uint16_t meshIndex; uint16_t nodeIndex; uint16_t parentNodeIndex; diff --git a/SynapseEngine/Engine/Mesh/Data/Cpu/CpuModelData.h b/SynapseEngine/Engine/Mesh/Data/Cpu/CpuModelData.h index 9f60c914..ec5f50a2 100644 --- a/SynapseEngine/Engine/Mesh/Data/Cpu/CpuModelData.h +++ b/SynapseEngine/Engine/Mesh/Data/Cpu/CpuModelData.h @@ -20,6 +20,7 @@ namespace Syn std::vector meshDescriptors; std::vector meshletDrawDescriptors; std::vector lodDescriptors; + std::vector meshNodeDescriptors; std::vector baseDrawCommands; std::vector meshMaterialIndices; diff --git a/SynapseEngine/Engine/Mesh/Data/Gpu/GpuBatchedModel.h b/SynapseEngine/Engine/Mesh/Data/Gpu/GpuBatchedModel.h index 2c6deed2..55cb1e8a 100644 --- a/SynapseEngine/Engine/Mesh/Data/Gpu/GpuBatchedModel.h +++ b/SynapseEngine/Engine/Mesh/Data/Gpu/GpuBatchedModel.h @@ -5,6 +5,7 @@ #include "GpuMeshletDrawData.h" #include "GpuNodeTransform.h" #include "../Common/MaterialInfo.h" +#include "../Common/MeshInstanceDescriptor.h" namespace Syn { @@ -20,6 +21,8 @@ namespace Syn uint32_t globalIndexCount = 0; uint32_t globalAverageLodIndexCount = 0; uint32_t globalMeshCount = 0; + + std::vector meshNodeDescriptors; }; } diff --git a/SynapseEngine/Engine/Mesh/Loader/AssimpMeshLoader.cpp b/SynapseEngine/Engine/Mesh/Loader/AssimpMeshLoader.cpp index 9e64349f..cbf9aa3a 100644 --- a/SynapseEngine/Engine/Mesh/Loader/AssimpMeshLoader.cpp +++ b/SynapseEngine/Engine/Mesh/Loader/AssimpMeshLoader.cpp @@ -80,20 +80,40 @@ namespace Syn outModel.nodeTransforms.push_back(rawNode); - for (uint32_t i = 0; i < currentNode->mNumMeshes; ++i) + if (currentNode->mNumMeshes == 0) { - uint32_t meshIndex = currentNode->mMeshes[i]; - aiMesh* ai_mesh = scene->mMeshes[meshIndex]; - MeshInstanceDescriptor descriptor{}; - descriptor.meshIndex = static_cast(meshIndex); + descriptor.name = currentNode->mName.C_Str(); + descriptor.meshIndex = UINT16_MAX; descriptor.nodeIndex = nodeIndex; descriptor.parentNodeIndex = parentNodeIndex; - descriptor.vertexCount = ai_mesh->mNumVertices; - descriptor.indexCount = ai_mesh->mNumFaces * 3; - + descriptor.vertexCount = 0; + descriptor.indexCount = 0; outModel.meshNodeDescriptors.push_back(descriptor); } + else + { + for (uint32_t i = 0; i < currentNode->mNumMeshes; ++i) + { + uint32_t meshIndex = currentNode->mMeshes[i]; + aiMesh* ai_mesh = scene->mMeshes[meshIndex]; + MeshInstanceDescriptor descriptor{}; + + descriptor.name = currentNode->mName.C_Str(); + + if (currentNode->mNumMeshes > 1) { + descriptor.name += "_" + std::to_string(i); + } + + descriptor.meshIndex = static_cast(meshIndex); + descriptor.nodeIndex = nodeIndex; + descriptor.parentNodeIndex = parentNodeIndex; + descriptor.vertexCount = ai_mesh->mNumVertices; + descriptor.indexCount = ai_mesh->mNumFaces * 3; + outModel.meshNodeDescriptors.push_back(descriptor); + } + + } for (uint32_t i = 0; i < currentNode->mNumChildren; ++i) queue.push({ currentNode->mChildren[i], nodeIndex }); diff --git a/SynapseEngine/Engine/Render/Passes/Billboard/CameraBillboardPass.cpp b/SynapseEngine/Engine/Render/Passes/Billboard/CameraBillboardPass.cpp index 3bdc5ce2..ab4cc39b 100644 --- a/SynapseEngine/Engine/Render/Passes/Billboard/CameraBillboardPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Billboard/CameraBillboardPass.cpp @@ -70,7 +70,7 @@ namespace Syn { .colorAttachmentCount = 2 }; - _iconTexture = ServiceLocator::GetImageManager()->LoadImageSync(PathUtils::GetAbsolutePathString("Assets/CameraIcon.png")); + _iconTexture = ServiceLocator::GetImageManager()->LoadImageSync(PathUtils::GetAbsolutePathString("Assets/Engine/CameraIcon.png")); } void CameraBillboardPass::PrepareFrame(const RenderContext& context) { diff --git a/SynapseEngine/Engine/Render/Passes/Billboard/DirectionLightBillboardPass.cpp b/SynapseEngine/Engine/Render/Passes/Billboard/DirectionLightBillboardPass.cpp index ea3b2f67..9bde82a6 100644 --- a/SynapseEngine/Engine/Render/Passes/Billboard/DirectionLightBillboardPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Billboard/DirectionLightBillboardPass.cpp @@ -69,7 +69,7 @@ namespace Syn { .colorAttachmentCount = 2 }; - _iconTexture = ServiceLocator::GetImageManager()->LoadImageSync(PathUtils::GetAbsolutePathString("Assets/DirectionLightIcon.png")); + _iconTexture = ServiceLocator::GetImageManager()->LoadImageSync(PathUtils::GetAbsolutePathString("Assets/Engine/DirectionLightIcon.png")); } void DirectionLightBillboardPass::PrepareFrame(const RenderContext& context) { diff --git a/SynapseEngine/Engine/Render/Passes/Billboard/PointLightBillboardPass.cpp b/SynapseEngine/Engine/Render/Passes/Billboard/PointLightBillboardPass.cpp index 840d7775..53838fed 100644 --- a/SynapseEngine/Engine/Render/Passes/Billboard/PointLightBillboardPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Billboard/PointLightBillboardPass.cpp @@ -69,7 +69,7 @@ namespace Syn { .colorAttachmentCount = 2, }; - _iconTexture = ServiceLocator::GetImageManager()->LoadImageSync(PathUtils::GetAbsolutePathString("Assets/PointLightIcon.png")); + _iconTexture = ServiceLocator::GetImageManager()->LoadImageSync(PathUtils::GetAbsolutePathString("Assets/Engine/PointLightIcon.png")); } void PointLightBillboardPass::PrepareFrame(const RenderContext& context) { diff --git a/SynapseEngine/Engine/Render/Passes/Billboard/SpotLightBillboardPass.cpp b/SynapseEngine/Engine/Render/Passes/Billboard/SpotLightBillboardPass.cpp index a1ca2744..5ef8b81b 100644 --- a/SynapseEngine/Engine/Render/Passes/Billboard/SpotLightBillboardPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Billboard/SpotLightBillboardPass.cpp @@ -69,7 +69,7 @@ namespace Syn { .colorAttachmentCount = 2 }; - _iconTexture = ServiceLocator::GetImageManager()->LoadImageSync(PathUtils::GetAbsolutePathString("Assets/SpotLightIcon.png")); + _iconTexture = ServiceLocator::GetImageManager()->LoadImageSync(PathUtils::GetAbsolutePathString("Assets/Engine/SpotLightIcon.png")); } void SpotLightBillboardPass::PrepareFrame(const RenderContext& context) { diff --git a/SynapseEngine/Engine/Serialization/Archive/Input/Binary/BinaryInputArchive.cpp b/SynapseEngine/Engine/Serialization/Archive/Input/Binary/BinaryInputArchive.cpp index 15dcbbd4..8f27764a 100644 --- a/SynapseEngine/Engine/Serialization/Archive/Input/Binary/BinaryInputArchive.cpp +++ b/SynapseEngine/Engine/Serialization/Archive/Input/Binary/BinaryInputArchive.cpp @@ -17,6 +17,10 @@ namespace Syn _stream.ReadRaw(&value, sizeof(uint8_t)); } + void BinaryInputArchive::PropertyUint16(const char*, uint16_t& value) { + _stream.ReadRaw(&value, sizeof(uint16_t)); + } + void BinaryInputArchive::PropertyInt32(const char*, int32_t& value) { _stream.ReadRaw(&value, sizeof(int32_t)); } diff --git a/SynapseEngine/Engine/Serialization/Archive/Input/Binary/BinaryInputArchive.h b/SynapseEngine/Engine/Serialization/Archive/Input/Binary/BinaryInputArchive.h index cbb6a5fc..4c139132 100644 --- a/SynapseEngine/Engine/Serialization/Archive/Input/Binary/BinaryInputArchive.h +++ b/SynapseEngine/Engine/Serialization/Archive/Input/Binary/BinaryInputArchive.h @@ -25,6 +25,7 @@ namespace Syn void PropertyBool(const char* name, bool& value) override; void PropertyUint8(const char* name, uint8_t& value) override; + void PropertyUint16(const char* name, uint16_t& value) override; void PropertyInt32(const char* name, int32_t& value) override; void PropertyUint32(const char* name, uint32_t& value) override; void PropertyInt64(const char* name, int64_t& value) override; diff --git a/SynapseEngine/Engine/Serialization/Archive/Input/IInputArchive.h b/SynapseEngine/Engine/Serialization/Archive/Input/IInputArchive.h index 5aa94d65..32bd07bc 100644 --- a/SynapseEngine/Engine/Serialization/Archive/Input/IInputArchive.h +++ b/SynapseEngine/Engine/Serialization/Archive/Input/IInputArchive.h @@ -26,6 +26,7 @@ namespace Syn virtual void PropertyBool(const char* name, bool& value) = 0; virtual void PropertyUint8(const char* name, uint8_t& value) = 0; + virtual void PropertyUint16(const char* name, uint16_t& value) = 0; virtual void PropertyInt32(const char* name, int32_t& value) = 0; virtual void PropertyUint32(const char* name, uint32_t& value) = 0; virtual void PropertyInt64(const char* name, int64_t& value) = 0; @@ -46,6 +47,7 @@ namespace Syn { if constexpr (std::is_same_v) PropertyBool(name, value); else if constexpr (std::is_same_v) PropertyUint8(name, value); + else if constexpr (std::is_same_v) PropertyUint16(name, value); else if constexpr (std::is_same_v) PropertyInt32(name, value); else if constexpr (std::is_same_v) PropertyUint32(name, value); else if constexpr (std::is_same_v) PropertyInt64(name, value); diff --git a/SynapseEngine/Engine/Serialization/Archive/Input/Json/NlohmannJsonInputArchive.cpp b/SynapseEngine/Engine/Serialization/Archive/Input/Json/NlohmannJsonInputArchive.cpp index cec2cc06..006d750a 100644 --- a/SynapseEngine/Engine/Serialization/Archive/Input/Json/NlohmannJsonInputArchive.cpp +++ b/SynapseEngine/Engine/Serialization/Archive/Input/Json/NlohmannJsonInputArchive.cpp @@ -79,6 +79,10 @@ namespace Syn ReadValue(name, value); } + void NlohmannJsonInputArchive::PropertyUint16(const char* name, uint16_t& value) { + ReadValue(name, value); + } + void NlohmannJsonInputArchive::PropertyInt32(const char* name, int32_t& value) { ReadValue(name, value); } diff --git a/SynapseEngine/Engine/Serialization/Archive/Input/Json/NlohmannJsonInputArchive.h b/SynapseEngine/Engine/Serialization/Archive/Input/Json/NlohmannJsonInputArchive.h index 9e909337..1b57304e 100644 --- a/SynapseEngine/Engine/Serialization/Archive/Input/Json/NlohmannJsonInputArchive.h +++ b/SynapseEngine/Engine/Serialization/Archive/Input/Json/NlohmannJsonInputArchive.h @@ -25,6 +25,7 @@ namespace Syn void PropertyBool(const char* name, bool& value) override; void PropertyUint8(const char* name, uint8_t& value) override; + void PropertyUint16(const char* name, uint16_t& value) override; void PropertyInt32(const char* name, int32_t& value) override; void PropertyUint32(const char* name, uint32_t& value) override; void PropertyInt64(const char* name, int64_t& value) override; diff --git a/SynapseEngine/Engine/Serialization/Archive/Input/Toml/PlusPlusTomlInputArchive.cpp b/SynapseEngine/Engine/Serialization/Archive/Input/Toml/PlusPlusTomlInputArchive.cpp index 1195588f..a513151d 100644 --- a/SynapseEngine/Engine/Serialization/Archive/Input/Toml/PlusPlusTomlInputArchive.cpp +++ b/SynapseEngine/Engine/Serialization/Archive/Input/Toml/PlusPlusTomlInputArchive.cpp @@ -77,6 +77,10 @@ namespace Syn value = static_cast(GetNextNode(name)->as_integer()->get()); } + void PlusPlusTomlInputArchive::PropertyUint16(const char* name, uint16_t& value) { + value = static_cast(GetNextNode(name)->as_integer()->get()); + } + void PlusPlusTomlInputArchive::PropertyInt32(const char* name, int32_t& value) { value = static_cast(GetNextNode(name)->as_integer()->get()); } diff --git a/SynapseEngine/Engine/Serialization/Archive/Input/Toml/PlusPlusTomlInputArchive.h b/SynapseEngine/Engine/Serialization/Archive/Input/Toml/PlusPlusTomlInputArchive.h index ce29f008..7c377262 100644 --- a/SynapseEngine/Engine/Serialization/Archive/Input/Toml/PlusPlusTomlInputArchive.h +++ b/SynapseEngine/Engine/Serialization/Archive/Input/Toml/PlusPlusTomlInputArchive.h @@ -25,6 +25,7 @@ namespace Syn void PropertyBool(const char* name, bool& value) override; void PropertyUint8(const char* name, uint8_t& value) override; + void PropertyUint16(const char* name, uint16_t& value) override; void PropertyInt32(const char* name, int32_t& value) override; void PropertyUint32(const char* name, uint32_t& value) override; void PropertyInt64(const char* name, int64_t& value) override; diff --git a/SynapseEngine/Engine/Serialization/Archive/Input/Xml/TinyXmlInputArchive.cpp b/SynapseEngine/Engine/Serialization/Archive/Input/Xml/TinyXmlInputArchive.cpp index b36ca28d..51568afd 100644 --- a/SynapseEngine/Engine/Serialization/Archive/Input/Xml/TinyXmlInputArchive.cpp +++ b/SynapseEngine/Engine/Serialization/Archive/Input/Xml/TinyXmlInputArchive.cpp @@ -88,6 +88,10 @@ namespace Syn value = static_cast(GetNextElement(name)->UnsignedText()); } + void TinyXmlInputArchive::PropertyUint16(const char* name, uint16_t& value) { + value = static_cast(GetNextElement(name)->UnsignedText()); + } + void TinyXmlInputArchive::PropertyInt32(const char* name, int32_t& value) { value = GetNextElement(name)->IntText(); } diff --git a/SynapseEngine/Engine/Serialization/Archive/Input/Xml/TinyXmlInputArchive.h b/SynapseEngine/Engine/Serialization/Archive/Input/Xml/TinyXmlInputArchive.h index 1c1484ff..f7478dfa 100644 --- a/SynapseEngine/Engine/Serialization/Archive/Input/Xml/TinyXmlInputArchive.h +++ b/SynapseEngine/Engine/Serialization/Archive/Input/Xml/TinyXmlInputArchive.h @@ -25,6 +25,7 @@ namespace Syn void PropertyBool(const char* name, bool& value) override; void PropertyUint8(const char* name, uint8_t& value) override; + void PropertyUint16(const char* name, uint16_t& value) override; void PropertyInt32(const char* name, int32_t& value) override; void PropertyUint32(const char* name, uint32_t& value) override; void PropertyInt64(const char* name, int64_t& value) override; diff --git a/SynapseEngine/Engine/Serialization/Archive/Input/Yaml/YamlCppInputArchive.cpp b/SynapseEngine/Engine/Serialization/Archive/Input/Yaml/YamlCppInputArchive.cpp index ec02e79e..f72a3514 100644 --- a/SynapseEngine/Engine/Serialization/Archive/Input/Yaml/YamlCppInputArchive.cpp +++ b/SynapseEngine/Engine/Serialization/Archive/Input/Yaml/YamlCppInputArchive.cpp @@ -78,6 +78,13 @@ namespace Syn value = static_cast(temp); } + void YamlCppInputArchive::PropertyUint16(const char* name, uint16_t& value) + { + uint32_t temp = 0; + ReadValue(name, temp); + value = static_cast(temp); + } + void YamlCppInputArchive::PropertyInt32(const char* name, int32_t& value) { ReadValue(name, value); } diff --git a/SynapseEngine/Engine/Serialization/Archive/Input/Yaml/YamlCppInputArchive.h b/SynapseEngine/Engine/Serialization/Archive/Input/Yaml/YamlCppInputArchive.h index 681f71ac..75ca78e5 100644 --- a/SynapseEngine/Engine/Serialization/Archive/Input/Yaml/YamlCppInputArchive.h +++ b/SynapseEngine/Engine/Serialization/Archive/Input/Yaml/YamlCppInputArchive.h @@ -25,6 +25,7 @@ namespace Syn void PropertyBool(const char* name, bool& value) override; void PropertyUint8(const char* name, uint8_t& value) override; + void PropertyUint16(const char* name, uint16_t& value) override; void PropertyInt32(const char* name, int32_t& value) override; void PropertyUint32(const char* name, uint32_t& value) override; void PropertyInt64(const char* name, int64_t& value) override; diff --git a/SynapseEngine/Engine/Serialization/Archive/Output/Binary/BinaryOutputArchive.cpp b/SynapseEngine/Engine/Serialization/Archive/Output/Binary/BinaryOutputArchive.cpp index 9b952891..4efcc866 100644 --- a/SynapseEngine/Engine/Serialization/Archive/Output/Binary/BinaryOutputArchive.cpp +++ b/SynapseEngine/Engine/Serialization/Archive/Output/Binary/BinaryOutputArchive.cpp @@ -23,6 +23,11 @@ namespace Syn _stream.WriteRaw(&value, sizeof(uint8_t)); } + void BinaryOutputArchive::PropertyUint16(const char*, uint16_t value) + { + _stream.WriteRaw(&value, sizeof(uint16_t)); + } + void BinaryOutputArchive::PropertyInt32(const char*, int32_t value) { _stream.WriteRaw(&value, sizeof(int32_t)); diff --git a/SynapseEngine/Engine/Serialization/Archive/Output/Binary/BinaryOutputArchive.h b/SynapseEngine/Engine/Serialization/Archive/Output/Binary/BinaryOutputArchive.h index 56a22b3c..74024d2b 100644 --- a/SynapseEngine/Engine/Serialization/Archive/Output/Binary/BinaryOutputArchive.h +++ b/SynapseEngine/Engine/Serialization/Archive/Output/Binary/BinaryOutputArchive.h @@ -23,6 +23,7 @@ namespace Syn void PropertyBool(const char* name, bool value) override; void PropertyUint8(const char* name, uint8_t value) override; + void PropertyUint16(const char* name, uint16_t value) override; void PropertyInt32(const char* name, int32_t value) override; void PropertyUint32(const char* name, uint32_t value) override; void PropertyInt64(const char* name, int64_t value) override; diff --git a/SynapseEngine/Engine/Serialization/Archive/Output/IOutputArchive.h b/SynapseEngine/Engine/Serialization/Archive/Output/IOutputArchive.h index f99a6f70..25501ee1 100644 --- a/SynapseEngine/Engine/Serialization/Archive/Output/IOutputArchive.h +++ b/SynapseEngine/Engine/Serialization/Archive/Output/IOutputArchive.h @@ -24,6 +24,7 @@ namespace Syn virtual void PropertyBool(const char* name, bool value) = 0; virtual void PropertyUint8(const char* name, uint8_t value) = 0; + virtual void PropertyUint16(const char* name, uint16_t value) = 0; virtual void PropertyInt32(const char* name, int32_t value) = 0; virtual void PropertyUint32(const char* name, uint32_t value) = 0; virtual void PropertyInt64(const char* name, int64_t value) = 0; @@ -43,6 +44,7 @@ namespace Syn void IOutputArchive::Property(const char* name, const T& value) { if constexpr (std::is_same_v) PropertyBool(name, value); else if constexpr (std::is_same_v) PropertyUint8(name, value); + else if constexpr (std::is_same_v) PropertyUint16(name, value); else if constexpr (std::is_same_v) PropertyInt32(name, value); else if constexpr (std::is_same_v) PropertyUint32(name, value); else if constexpr (std::is_same_v) PropertyInt64(name, value); diff --git a/SynapseEngine/Engine/Serialization/Archive/Output/Json/NlohmannJsonOutputArchive.cpp b/SynapseEngine/Engine/Serialization/Archive/Output/Json/NlohmannJsonOutputArchive.cpp index 758a8aca..e480d6b9 100644 --- a/SynapseEngine/Engine/Serialization/Archive/Output/Json/NlohmannJsonOutputArchive.cpp +++ b/SynapseEngine/Engine/Serialization/Archive/Output/Json/NlohmannJsonOutputArchive.cpp @@ -67,6 +67,12 @@ namespace Syn else parent[name] = value; } + void NlohmannJsonOutputArchive::PropertyUint16(const char* name, uint16_t value) { + auto& parent = *_stack.back(); + if (parent.is_array()) parent.push_back(value); + else parent[name] = value; + } + void NlohmannJsonOutputArchive::PropertyInt32(const char* name, int32_t value) { auto& parent = *_stack.back(); if (parent.is_array()) parent.push_back(value); diff --git a/SynapseEngine/Engine/Serialization/Archive/Output/Json/NlohmannJsonOutputArchive.h b/SynapseEngine/Engine/Serialization/Archive/Output/Json/NlohmannJsonOutputArchive.h index fb3b0530..72e4eb99 100644 --- a/SynapseEngine/Engine/Serialization/Archive/Output/Json/NlohmannJsonOutputArchive.h +++ b/SynapseEngine/Engine/Serialization/Archive/Output/Json/NlohmannJsonOutputArchive.h @@ -25,6 +25,7 @@ namespace Syn void PropertyBool(const char* name, bool value) override; void PropertyUint8(const char* name, uint8_t value) override; + void PropertyUint16(const char* name, uint16_t value) override; void PropertyInt32(const char* name, int32_t value) override; void PropertyUint32(const char* name, uint32_t value) override; void PropertyInt64(const char* name, int64_t value) override; diff --git a/SynapseEngine/Engine/Serialization/Archive/Output/Toml/PlusPlusTomlOutputArchive.cpp b/SynapseEngine/Engine/Serialization/Archive/Output/Toml/PlusPlusTomlOutputArchive.cpp index 07cfc89e..37b5d5cc 100644 --- a/SynapseEngine/Engine/Serialization/Archive/Output/Toml/PlusPlusTomlOutputArchive.cpp +++ b/SynapseEngine/Engine/Serialization/Archive/Output/Toml/PlusPlusTomlOutputArchive.cpp @@ -73,6 +73,10 @@ namespace Syn WriteValue(name, static_cast(value)); } + void PlusPlusTomlOutputArchive::PropertyUint16(const char* name, uint16_t value) { + WriteValue(name, static_cast(value)); + } + void PlusPlusTomlOutputArchive::PropertyInt32(const char* name, int32_t value) { WriteValue(name, value); } diff --git a/SynapseEngine/Engine/Serialization/Archive/Output/Toml/PlusPlusTomlOutputArchive.h b/SynapseEngine/Engine/Serialization/Archive/Output/Toml/PlusPlusTomlOutputArchive.h index 469dac5a..8473de5e 100644 --- a/SynapseEngine/Engine/Serialization/Archive/Output/Toml/PlusPlusTomlOutputArchive.h +++ b/SynapseEngine/Engine/Serialization/Archive/Output/Toml/PlusPlusTomlOutputArchive.h @@ -25,6 +25,7 @@ namespace Syn void PropertyBool(const char* name, bool value) override; void PropertyUint8(const char* name, uint8_t value) override; + void PropertyUint16(const char* name, uint16_t value) override; void PropertyInt32(const char* name, int32_t value) override; void PropertyUint32(const char* name, uint32_t value) override; void PropertyInt64(const char* name, int64_t value) override; diff --git a/SynapseEngine/Engine/Serialization/Archive/Output/Xml/TinyXmlOutputArchive.cpp b/SynapseEngine/Engine/Serialization/Archive/Output/Xml/TinyXmlOutputArchive.cpp index 599bbdc4..9908d4a0 100644 --- a/SynapseEngine/Engine/Serialization/Archive/Output/Xml/TinyXmlOutputArchive.cpp +++ b/SynapseEngine/Engine/Serialization/Archive/Output/Xml/TinyXmlOutputArchive.cpp @@ -72,6 +72,10 @@ namespace Syn WriteValue(name, static_cast(value)); } + void TinyXmlOutputArchive::PropertyUint16(const char* name, uint16_t value) { + WriteValue(name, static_cast(value)); + } + void TinyXmlOutputArchive::PropertyInt32(const char* name, int32_t value) { WriteValue(name, value); } diff --git a/SynapseEngine/Engine/Serialization/Archive/Output/Xml/TinyXmlOutputArchive.h b/SynapseEngine/Engine/Serialization/Archive/Output/Xml/TinyXmlOutputArchive.h index 45ab0ed3..750f7c3f 100644 --- a/SynapseEngine/Engine/Serialization/Archive/Output/Xml/TinyXmlOutputArchive.h +++ b/SynapseEngine/Engine/Serialization/Archive/Output/Xml/TinyXmlOutputArchive.h @@ -25,6 +25,7 @@ namespace Syn void PropertyBool(const char* name, bool value) override; void PropertyUint8(const char* name, uint8_t value) override; + void PropertyUint16(const char* name, uint16_t value) override; void PropertyInt32(const char* name, int32_t value) override; void PropertyUint32(const char* name, uint32_t value) override; void PropertyInt64(const char* name, int64_t value) override; diff --git a/SynapseEngine/Engine/Serialization/Archive/Output/Yaml/YamlCppOutputArchive.cpp b/SynapseEngine/Engine/Serialization/Archive/Output/Yaml/YamlCppOutputArchive.cpp index c33762c0..081353dc 100644 --- a/SynapseEngine/Engine/Serialization/Archive/Output/Yaml/YamlCppOutputArchive.cpp +++ b/SynapseEngine/Engine/Serialization/Archive/Output/Yaml/YamlCppOutputArchive.cpp @@ -69,6 +69,10 @@ namespace Syn WriteValue(name, static_cast(value)); } + void YamlCppOutputArchive::PropertyUint16(const char* name, uint16_t value) { + WriteValue(name, static_cast(value)); + } + void YamlCppOutputArchive::PropertyInt32(const char* name, int32_t value) { WriteValue(name, value); } diff --git a/SynapseEngine/Engine/Serialization/Archive/Output/Yaml/YamlCppOutputArchive.h b/SynapseEngine/Engine/Serialization/Archive/Output/Yaml/YamlCppOutputArchive.h index 475aad7d..a323dd82 100644 --- a/SynapseEngine/Engine/Serialization/Archive/Output/Yaml/YamlCppOutputArchive.h +++ b/SynapseEngine/Engine/Serialization/Archive/Output/Yaml/YamlCppOutputArchive.h @@ -25,6 +25,7 @@ namespace Syn void PropertyBool(const char* name, bool value) override; void PropertyUint8(const char* name, uint8_t value) override; + void PropertyUint16(const char* name, uint16_t value) override; void PropertyInt32(const char* name, int32_t value) override; void PropertyUint32(const char* name, uint32_t value) override; void PropertyInt64(const char* name, int64_t value) override; diff --git a/SynapseEngine/Engine/Serialization/Schema/Models/CpuModelDataSchema.h b/SynapseEngine/Engine/Serialization/Schema/Models/CpuModelDataSchema.h index b6f73b37..48ced476 100644 --- a/SynapseEngine/Engine/Serialization/Schema/Models/CpuModelDataSchema.h +++ b/SynapseEngine/Engine/Serialization/Schema/Models/CpuModelDataSchema.h @@ -7,6 +7,7 @@ #include "GpuMeshletDrawDataSchema.h" #include "MaterialInfoSchema.h" #include "MeshDrawBlueprintSchema.h" +#include "MeshInstanceDescriptorSchema.h" #include "Engine/Mesh/Data/Cpu/CpuModelData.h" @@ -66,6 +67,8 @@ namespace Syn BlitVector mlDrawDescs{ m.meshletDrawDescriptors }; ar.Property("meshletDrawDescriptors", mlDrawDescs); + ar.Property("meshNodeDescriptors", m.meshNodeDescriptors); + auto serializeNestedBlit = [&ar](const char* arrayName, auto& nestedVec) { uint32_t outerSize = static_cast(nestedVec.size()); ar.EnterArray(arrayName, outerSize); @@ -103,6 +106,7 @@ namespace Syn ar.Property("batchedIndicesPerLod", m.batchedIndicesPerLod); ar.Property("physicsIndicesPerLod", m.physicsIndicesPerLod); + ar.Property("meshNodeDescriptors", m.meshNodeDescriptors); } } }; diff --git a/SynapseEngine/Engine/Serialization/Schema/Models/GpuBatchedModelSchema.h b/SynapseEngine/Engine/Serialization/Schema/Models/GpuBatchedModelSchema.h index f1c66d02..0ef864b1 100644 --- a/SynapseEngine/Engine/Serialization/Schema/Models/GpuBatchedModelSchema.h +++ b/SynapseEngine/Engine/Serialization/Schema/Models/GpuBatchedModelSchema.h @@ -6,6 +6,7 @@ #include "GpuIndexedDrawDataSchema.h" #include "GpuMeshletDrawDataSchema.h" #include "MaterialInfoSchema.h" +#include "MeshInstanceDescriptorSchema.h" #include "Engine/Mesh/Data/Gpu/GpuNodeTransform.h" #include "Engine/Mesh/Data/Gpu/GpuBatchedModel.h" @@ -54,6 +55,7 @@ namespace Syn ar.Property("globalIndexCount", m.globalIndexCount); ar.Property("globalAverageLodIndexCount", m.globalAverageLodIndexCount); ar.Property("globalMeshCount", m.globalMeshCount); + ar.Property("meshNodeDescriptors", m.meshNodeDescriptors); } }; } \ No newline at end of file diff --git a/SynapseEngine/Engine/Serialization/Schema/Models/MeshInstanceDescriptorSchema.h b/SynapseEngine/Engine/Serialization/Schema/Models/MeshInstanceDescriptorSchema.h new file mode 100644 index 00000000..c7114488 --- /dev/null +++ b/SynapseEngine/Engine/Serialization/Schema/Models/MeshInstanceDescriptorSchema.h @@ -0,0 +1,25 @@ +#pragma once +#include "Engine/Serialization/Schema/Schema.h" +#include "Engine/Mesh/Data/Common/MeshInstanceDescriptor.h" + +namespace Syn +{ + template <> + struct Schema { + static constexpr bool exists = true; + + template + static void Invoke(Archive& ar, const char* name, U& val) + { + ScopedArchiveObject obj(ar, name); + auto& d = const_cast&>(val); + + ar.Property("name", d.name); + ar.Property("meshIndex", d.meshIndex); + ar.Property("nodeIndex", d.nodeIndex); + ar.Property("parentNodeIndex", d.parentNodeIndex); + ar.Property("vertexCount", d.vertexCount); + ar.Property("indexCount", d.indexCount); + } + }; +} \ No newline at end of file diff --git a/SynapseEngine/imgui.ini b/SynapseEngine/imgui.ini deleted file mode 100644 index 7bc663af..00000000 --- a/SynapseEngine/imgui.ini +++ /dev/null @@ -1,151 +0,0 @@ -[Window][WindowOverViewport_11111111] -Pos=0,23 -Size=2304,1273 -Collapsed=0 - -[Window][Debug##Default] -Pos=60,60 -Size=400,400 -Collapsed=0 - -[Window][ Inspector] -Pos=1852,23 -Size=452,633 -Collapsed=0 -DockId=0x00000005,0 - -[Window][ Viewport] -Pos=407,23 -Size=1443,967 -Collapsed=0 -DockId=0x00000001,0 - -[Window][ Graphics & Environment] -Pos=1852,658 -Size=452,638 -Collapsed=0 -DockId=0x00000006,0 - -[Window][Material Graph] -Pos=440,23 -Size=1410,907 -Collapsed=0 -DockId=0x08BD597D,1 - -[Window][ Scene Hierarchy] -Pos=0,23 -Size=405,687 -Collapsed=0 -DockId=0x00000009,0 - -[Window][ Performance Profiler] -Pos=0,712 -Size=405,584 -Collapsed=0 -DockId=0x0000000A,0 - -[Window][ Content Browser] -Pos=440,932 -Size=1410,364 -Collapsed=0 -DockId=0x08BD597D,0 - -[Window][ Output Log] -Pos=407,992 -Size=1443,304 -Collapsed=0 -DockId=0x00000002,0 - -[Window][HostWindow_Scene] -Pos=0,23 -Size=2304,1273 -Collapsed=0 - -[Window][Content_Scene] -Pos=407,992 -Size=1443,304 -Collapsed=0 -DockId=0x00000002,1 - -[Table][0x51A78E48,2] -RefScale=13 -Column 0 Weight=1.0000 -Column 1 Width=47 - -[Table][0xB6D16E5C,3] -RefScale=13 -Column 0 Weight=0.7557 -Column 1 Width=91 -Column 2 Weight=0.2443 - -[Table][0x3D1A1C69,2] -RefScale=13 -Column 0 Width=147 -Column 1 Weight=1.0000 - -[Table][0x6925898D,2] -RefScale=13 -Column 0 Width=216 -Column 1 Weight=1.0000 - -[Table][0xE847EDF4,2] -RefScale=13 -Column 0 Width=99 -Column 1 Weight=1.0000 - -[Table][0x182B970D,2] -RefScale=13 -Column 0 Width=99 -Column 1 Weight=-nan(ind) - -[Table][0x8556BC1A,2] -RefScale=13 -Column 0 Width=99 -Column 1 Weight=-nan(ind) - -[Table][0x94CE371A,2] -RefScale=13 -Column 0 Width=99 -Column 1 Weight=1.0000 - -[Table][0x5A9ED35A,2] -RefScale=13 -Column 0 Width=99 -Column 1 Weight=1.0000 - -[Table][0x05A9070B,4] -RefScale=13 -Column 0 Width=139 -Column 1 Width=59 -Column 2 Width=149 -Column 3 Weight=1.0000 - -[Table][0x5806762E,2] -RefScale=13 -Column 0 Width=100 -Column 1 Weight=1.0000 - -[Table][0x50E67774,2] -RefScale=13 -Column 0 Width=100 -Column 1 Weight=1.0000 - -[Table][0x78D40C46,2] -RefScale=13 -Column 0 Width=100 -Column 1 Weight=1.0000 - -[Docking][Data] -DockSpace ID=0x08BD597D Pos=128,95 Size=2304,1273 CentralNode=1 -DockSpace ID=0x4713F8A8 Window=0xFBC4AE05 Pos=128,95 Size=2304,1273 Split=X Selected=0x1C1AF642 - DockNode ID=0x00000007 Parent=0x4713F8A8 SizeRef=405,1273 Split=Y Selected=0xF995F4A5 - DockNode ID=0x00000009 Parent=0x00000007 SizeRef=437,687 Selected=0xF995F4A5 - DockNode ID=0x0000000A Parent=0x00000007 SizeRef=437,584 Selected=0x02B8E2DB - DockNode ID=0x00000008 Parent=0x4713F8A8 SizeRef=1513,1273 Split=X - DockNode ID=0x00000003 Parent=0x00000008 SizeRef=1059,1273 Split=Y - DockNode ID=0x00000001 Parent=0x00000003 SizeRef=2304,680 CentralNode=1 Selected=0x1C1AF642 - DockNode ID=0x00000002 Parent=0x00000003 SizeRef=2304,304 Selected=0x81DECE6A - DockNode ID=0x00000004 Parent=0x00000008 SizeRef=452,1273 Split=Y Selected=0x57A55B3F - DockNode ID=0x00000005 Parent=0x00000004 SizeRef=452,633 Selected=0x70CE1A73 - DockNode ID=0x00000006 Parent=0x00000004 SizeRef=452,638 Selected=0x57A55B3F - diff --git a/SynapseEngine/xmake.lua b/SynapseEngine/xmake.lua index e541db7a..e2736594 100644 --- a/SynapseEngine/xmake.lua +++ b/SynapseEngine/xmake.lua @@ -119,6 +119,7 @@ local vcpkg_packages = { "vcpkg::tinyxml2", "vcpkg::yaml-cpp", "vcpkg::tomlplusplus", + "vcpkg::nanosvg" } for _, pkg in ipairs(vcpkg_packages) do From d9c53d691d2f33016486b5499d560ef939f81437 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20P=C3=A9ter?= Date: Mon, 29 Jun 2026 11:50:20 +0200 Subject: [PATCH 11/51] Model properties window --- .gitignore | 2 +- .../ModelProperties/ModelPropertiesView.cpp | 65 +++++++++++++ .../ModelProperties/ModelPropertiesView.h | 16 ++++ SynapseEngine/Editor/Widgets/PropertyGrid.cpp | 5 + SynapseEngine/Editor/Widgets/PropertyGrid.h | 2 + .../Editor/Workspace/ModelWorkspace.cpp | 10 ++ .../ModelProperties/ModelPropertiesIntent.cpp | 1 + .../ModelProperties/ModelPropertiesIntent.h | 10 ++ .../ModelProperties/ModelPropertiesState.cpp | 1 + .../ModelProperties/ModelPropertiesState.h | 34 +++++++ .../ModelPropertiesViewModel.cpp | 91 +++++++++++++++++++ .../ModelPropertiesViewModel.h | 27 ++++++ 12 files changed, 263 insertions(+), 1 deletion(-) create mode 100644 SynapseEngine/Editor/View/ModelProperties/ModelPropertiesView.cpp create mode 100644 SynapseEngine/Editor/View/ModelProperties/ModelPropertiesView.h create mode 100644 SynapseEngine/EditorCore/ViewModels/ModelProperties/ModelPropertiesIntent.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/ModelProperties/ModelPropertiesIntent.h create mode 100644 SynapseEngine/EditorCore/ViewModels/ModelProperties/ModelPropertiesState.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/ModelProperties/ModelPropertiesState.h create mode 100644 SynapseEngine/EditorCore/ViewModels/ModelProperties/ModelPropertiesViewModel.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/ModelProperties/ModelPropertiesViewModel.h diff --git a/.gitignore b/.gitignore index 13156ebf..eac150c9 100644 --- a/.gitignore +++ b/.gitignore @@ -27,7 +27,7 @@ install_manifest.txt *.dae *.jpg -# *.png +*.png *.obj *.fbx *.mtl diff --git a/SynapseEngine/Editor/View/ModelProperties/ModelPropertiesView.cpp b/SynapseEngine/Editor/View/ModelProperties/ModelPropertiesView.cpp new file mode 100644 index 00000000..5c4bf8f3 --- /dev/null +++ b/SynapseEngine/Editor/View/ModelProperties/ModelPropertiesView.cpp @@ -0,0 +1,65 @@ +#include "ModelPropertiesView.h" +#include "Editor/Manager/EditorIcons.h" +#include "Editor/Widgets/CardWidget.h" +#include "Editor/Widgets/PropertyGrid.h" +#include + +namespace Syn { + void ModelPropertiesView::Draw(ModelPropertiesViewModel& vm) { + const auto& state = vm.GetState(); + + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8, 8)); + + if (ImGui::Begin(SYN_ICON_INFO_CIRCLE " Model Properties##ModelPropsWindow", nullptr)) { + + if (!state.hasSelection) { + ImGui::TextDisabled("No model or mesh selected."); + ImGui::End(); + ImGui::PopStyleVar(); + return; + } + + auto getCardState = [this](const char* name) -> bool& { + std::string key(name); + if (_cardStates.find(key) == _cardStates.end()) _cardStates[key] = true; + return _cardStates[key]; + }; + + if (Syn::UI::BeginCard("ModelGlobalPropsCard", SYN_ICON_CUBE, getCardState("ModelGlobalPropsCard"))) { + ImGui::Text("Model: "); ImGui::SameLine(); + ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f), "%s", state.modelName.c_str()); + + if (Syn::UI::BeginPropertyGrid("GlobalGrid")) { + DrawPropertyRow("Vertices", std::to_string(state.globalVertexCount)); + DrawPropertyRow("Indices", std::to_string(state.globalIndexCount)); + DrawPropertyRow("Triangles", std::to_string(state.globalIndexCount / 3)); + Syn::UI::EndPropertyGrid(); + } + Syn::UI::EndCard(); + } + + if (state.isNodeSelected) { + ImGui::Spacing(); + if (Syn::UI::BeginCard("NodePropsCard", SYN_ICON_PROJECT_DIAGRAM, getCardState("NodePropsCard"))) { + ImGui::Text("Node: "); ImGui::SameLine(); + ImGui::TextColored(ImVec4(0.4f, 1.0f, 0.6f, 1.0f), "%s", state.nodeName.c_str()); + + if (Syn::UI::BeginPropertyGrid("NodeGrid")) { + DrawPropertyRow("Mesh Index", state.meshIndex == 0xFFFF ? "None" : std::to_string(state.meshIndex)); + DrawPropertyRow("Triangles", std::to_string(state.nodeIndexCount / 3)); + DrawPropertyRow("Meshlets", std::to_string(state.nodeMeshletCount)); + Syn::UI::EndPropertyGrid(); + } + Syn::UI::EndCard(); + } + } + } + + ImGui::End(); + ImGui::PopStyleVar(); + } + + void ModelPropertiesView::DrawPropertyRow(const char* label, const std::string& value) { + Syn::UI::PropertyText(label, value.c_str()); + } +} \ No newline at end of file diff --git a/SynapseEngine/Editor/View/ModelProperties/ModelPropertiesView.h b/SynapseEngine/Editor/View/ModelProperties/ModelPropertiesView.h new file mode 100644 index 00000000..51cee299 --- /dev/null +++ b/SynapseEngine/Editor/View/ModelProperties/ModelPropertiesView.h @@ -0,0 +1,16 @@ +#pragma once +#include "Editor/View/IView.h" +#include "EditorCore/ViewModels/ModelProperties/ModelPropertiesViewModel.h" +#include +#include + +namespace Syn { + class ModelPropertiesView : public IView { + public: + void Draw(ModelPropertiesViewModel& vm) override; + private: + void DrawPropertyRow(const char* label, const std::string& value); + private: + std::unordered_map _cardStates; + }; +} \ No newline at end of file diff --git a/SynapseEngine/Editor/Widgets/PropertyGrid.cpp b/SynapseEngine/Editor/Widgets/PropertyGrid.cpp index aa88ab8c..1032cd5c 100644 --- a/SynapseEngine/Editor/Widgets/PropertyGrid.cpp +++ b/SynapseEngine/Editor/Widgets/PropertyGrid.cpp @@ -131,4 +131,9 @@ namespace Syn::UI { void EndPropertyCombo() { ImGui::EndCombo(); } + + void PropertyText(const char* label, const char* text, int indentLevel) { + BeginProperty(label, indentLevel); + ImGui::TextUnformatted(text); + } } \ No newline at end of file diff --git a/SynapseEngine/Editor/Widgets/PropertyGrid.h b/SynapseEngine/Editor/Widgets/PropertyGrid.h index 2f54334b..2b29a86a 100644 --- a/SynapseEngine/Editor/Widgets/PropertyGrid.h +++ b/SynapseEngine/Editor/Widgets/PropertyGrid.h @@ -27,4 +27,6 @@ namespace Syn::UI { bool BeginPropertyCombo(const char* label, const char* preview_value, int indentLevel = 0); void EndPropertyCombo(); + + void PropertyText(const char* label, const char* text, int indentLevel = 0); } \ No newline at end of file diff --git a/SynapseEngine/Editor/Workspace/ModelWorkspace.cpp b/SynapseEngine/Editor/Workspace/ModelWorkspace.cpp index a0ade482..701f5c8f 100644 --- a/SynapseEngine/Editor/Workspace/ModelWorkspace.cpp +++ b/SynapseEngine/Editor/Workspace/ModelWorkspace.cpp @@ -7,6 +7,10 @@ #include "Editor/View/ModelHierarchy/ModelHierarchyView.h" #include "EditorCore/ViewModels/ModelHierarchy/ModelHierarchyViewModel.h" +#include "Editor/View/ModelProperties/ModelPropertiesView.h" +#include "EditorCore/ViewModels/ModelProperties/ModelPropertiesViewModel.h" + + namespace Syn { ModelWorkspace::ModelWorkspace(EditorContext* context, IconManager* iconManager, const std::string& assetPath) @@ -25,6 +29,12 @@ namespace Syn { ModelHierarchyView{}, ModelHierarchyViewModel{ _context->GetModelApi() } ); + + using ModelPropertiesWin = EditorWindow; + AddWindow( + ModelPropertiesView{}, + ModelPropertiesViewModel{ _context->GetModelApi() } + ); } } \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/ModelProperties/ModelPropertiesIntent.cpp b/SynapseEngine/EditorCore/ViewModels/ModelProperties/ModelPropertiesIntent.cpp new file mode 100644 index 00000000..fe1ec0e2 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/ModelProperties/ModelPropertiesIntent.cpp @@ -0,0 +1 @@ +#include "ModelPropertiesIntent.h" \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/ModelProperties/ModelPropertiesIntent.h b/SynapseEngine/EditorCore/ViewModels/ModelProperties/ModelPropertiesIntent.h new file mode 100644 index 00000000..6daa7999 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/ModelProperties/ModelPropertiesIntent.h @@ -0,0 +1,10 @@ +#pragma once +#include + +namespace Syn { + struct ModelPropertiesRefreshIntent {}; + + using ModelPropertiesIntent = std::variant< + ModelPropertiesRefreshIntent + >; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/ModelProperties/ModelPropertiesState.cpp b/SynapseEngine/EditorCore/ViewModels/ModelProperties/ModelPropertiesState.cpp new file mode 100644 index 00000000..7c515e26 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/ModelProperties/ModelPropertiesState.cpp @@ -0,0 +1 @@ +#include "ModelPropertiesState.h" \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/ModelProperties/ModelPropertiesState.h b/SynapseEngine/EditorCore/ViewModels/ModelProperties/ModelPropertiesState.h new file mode 100644 index 00000000..5e120cde --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/ModelProperties/ModelPropertiesState.h @@ -0,0 +1,34 @@ +#pragma once +#include +#include + +namespace Syn { + struct ModelPropertiesState { + bool hasSelection = false; + uint32_t selectedModelId = 0xFFFFFFFF; + int32_t selectedDescriptorIndex = -1; + std::string modelName = ""; + + uint32_t globalVertexCount = 0; + uint32_t globalIndexCount = 0; + uint32_t globalMeshCount = 0; + glm::vec3 globalAabbMin = glm::vec3(0.0f); + glm::vec3 globalAabbMax = glm::vec3(0.0f); + glm::vec3 globalCenter = glm::vec3(0.0f); + float globalRadius = 0.0f; + + bool isNodeSelected = false; + std::string nodeName = ""; + uint32_t nodeVertexCount = 0; + uint32_t nodeIndexCount = 0; + int32_t meshIndex = -1; + int32_t nodeIndex = -1; + int32_t parentNodeIndex = -1; + + uint32_t nodeMeshletCount = 0; + uint32_t nodeMaterialIndex = 0; + glm::vec3 nodeAabbMin = glm::vec3(0.0f); + glm::vec3 nodeAabbMax = glm::vec3(0.0f); + float nodeRadius = 0.0f; + }; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/ModelProperties/ModelPropertiesViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/ModelProperties/ModelPropertiesViewModel.cpp new file mode 100644 index 00000000..382e4b5d --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/ModelProperties/ModelPropertiesViewModel.cpp @@ -0,0 +1,91 @@ +#include "ModelPropertiesViewModel.h" + +namespace Syn { + ModelPropertiesViewModel::ModelPropertiesViewModel(IModelApi* modelApi) + : _modelApi(modelApi) {} + + void ModelPropertiesViewModel::SyncWithEngine() { + if (!_modelApi) return; + + auto selection = _modelApi->GetSelected(); + if (selection.first != _lastModelId || selection.second != _lastDescriptorIndex) { + _lastModelId = selection.first; + _lastDescriptorIndex = selection.second; + UpdateState(); + } + } + + void ModelPropertiesViewModel::Dispatch(const ModelPropertiesIntent& intent) { + + } + + void ModelPropertiesViewModel::UpdateState() { + _state.hasSelection = (_lastModelId != 0xFFFFFFFF); + _state.selectedModelId = _lastModelId; + _state.selectedDescriptorIndex = _lastDescriptorIndex; + + if (!_state.hasSelection) return; + + const CpuModelData* cpuData = _modelApi->GetModelCpuData(_lastModelId); + if (!cpuData) { + _state.hasSelection = false; + return; + } + + auto models = _modelApi->GetAllModels(); + for (const auto& m : models) { + if (m.id == _lastModelId) { + _state.modelName = m.name; + break; + } + } + + _state.globalVertexCount = cpuData->globalVertexCount; + _state.globalIndexCount = cpuData->globalIndexCount; + _state.globalMeshCount = cpuData->globalMeshCount; + _state.globalAabbMin = cpuData->globalCollider.aabbMin; + _state.globalAabbMax = cpuData->globalCollider.aabbMax; + _state.globalCenter = cpuData->globalCollider.center; + _state.globalRadius = cpuData->globalCollider.radius; + + if (_lastDescriptorIndex >= 0 && _lastDescriptorIndex < cpuData->meshNodeDescriptors.size()) { + _state.isNodeSelected = true; + const auto& desc = cpuData->meshNodeDescriptors[_lastDescriptorIndex]; + + _state.nodeName = desc.name.empty() ? "Node_" + std::to_string(_lastDescriptorIndex) : desc.name; + _state.nodeVertexCount = desc.vertexCount; + _state.nodeIndexCount = desc.indexCount; + _state.meshIndex = desc.meshIndex; + _state.nodeIndex = desc.nodeIndex; + _state.parentNodeIndex = desc.parentNodeIndex; + + if (desc.meshIndex != 0xFFFF) { + uint32_t validMeshCounter = 0; + + for (int32_t i = 0; i < _lastDescriptorIndex; ++i) { + if (cpuData->meshNodeDescriptors[i].meshIndex != 0xFFFF) { + validMeshCounter++; + } + } + + uint32_t lod0Index = validMeshCounter * 4; + + if (lod0Index < cpuData->meshletDrawDescriptors.size()) { + _state.nodeMeshletCount = cpuData->meshletDrawDescriptors[lod0Index].meshletCount; + } + if (lod0Index < cpuData->meshDescriptors.size()) { + _state.nodeMaterialIndex = cpuData->meshDescriptors[lod0Index].materialIndex; + } + if (validMeshCounter < cpuData->meshColliders.size()) + { + _state.nodeAabbMin = cpuData->meshColliders[validMeshCounter].aabbMin; + _state.nodeAabbMax = cpuData->meshColliders[validMeshCounter].aabbMax; + _state.nodeRadius = cpuData->meshColliders[validMeshCounter].radius; + } + } + } + else { + _state.isNodeSelected = false; + } + } +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/ModelProperties/ModelPropertiesViewModel.h b/SynapseEngine/EditorCore/ViewModels/ModelProperties/ModelPropertiesViewModel.h new file mode 100644 index 00000000..a98aea05 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/ModelProperties/ModelPropertiesViewModel.h @@ -0,0 +1,27 @@ +#pragma once +#include "EditorCore/ViewModels/IViewModel.h" +#include "ModelPropertiesState.h" +#include "ModelPropertiesIntent.h" +#include "EditorCore/Api/IModelApi.h" + +namespace Syn { + class ModelPropertiesViewModel : public IViewModel { + public: + ModelPropertiesViewModel(IModelApi* modelApi); + ~ModelPropertiesViewModel() override = default; + + const ModelPropertiesState& GetState() const override { return _state; } + void SyncWithEngine() override; + void Dispatch(const ModelPropertiesIntent& intent) override; + + private: + void UpdateState(); + + private: + IModelApi* _modelApi = nullptr; + ModelPropertiesState _state; + + uint32_t _lastModelId = 0xFFFFFFFF; + int32_t _lastDescriptorIndex = -1; + }; +} \ No newline at end of file From d62f8f54fe297ed026d3b3a6ee75f0d3811f2781 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20P=C3=A9ter?= Date: Mon, 29 Jun 2026 14:05:14 +0200 Subject: [PATCH 12/51] Implemented isActive tracking in gpu/cpu-driven geometry and light culling. --- .../Editor/EditorApi/Impl/TagApiImpl.cpp | 4 +- .../Engine/Component/Core/TagComponent.cpp | 5 +- .../Engine/Component/Core/TagComponent.h | 7 +- .../Passes/Setup/GlobalFrameSetupPass.cpp | 5 +- SynapseEngine/Engine/Scene/BufferNames.h | 3 + SynapseEngine/Engine/Scene/Scene.cpp | 8 +- .../Scene/Source/Procedural/test_config.json | 12 +-- .../Includes/Common/FrameGlobalContext.glsl | 3 + .../Engine/Shaders/Includes/Common/Tag.glsl | 12 +++ .../DirectionLightShadowModelCulling.comp | 12 ++- ...irectionLightShadowMortonModelCulling.comp | 11 +- ...irectionLightShadowStaticModelCulling.comp | 12 ++- .../Geometry/GeometryModelCulling.comp | 9 +- .../Geometry/GeometryMortonModelCulling.comp | 11 +- .../Geometry/GeometryStaticModelCulling.comp | 9 +- .../Culling/PointLight/PointLightCulling.comp | 8 ++ .../PointLightShadowModelCulling.comp | 12 ++- .../PointLightShadowMortonModelCulling.comp | 11 +- .../PointLightShadowStaticModelCulling.comp | 14 ++- .../Culling/SpotLight/SpotLightCulling.comp | 8 ++ .../SpotLightShadowModelCulling.comp | 12 ++- .../SpotLightShadowMortonModelCulling.comp | 10 ++ .../SpotLightShadowStaticModelCulling.comp | 12 ++- .../Engine/System/Core/TagSystem.cpp | 102 ++++++++++++++++++ SynapseEngine/Engine/System/Core/TagSystem.h | 20 ++++ .../System/Core/TransformModelLinkSystem.cpp | 1 + .../Direction/DirectionLightCullingSystem.cpp | 17 ++- .../DirectionLightShadowCullingSystem.cpp | 14 ++- .../Light/Point/PointLightCullingSystem.cpp | 15 ++- .../Point/PointLightShadowCullingSystem.cpp | 14 ++- .../Light/Spot/SpotLightCullingSystem.cpp | 15 ++- .../Spot/SpotLightShadowCullingSystem.cpp | 14 ++- .../Rendering/ModelFrustumCullingSystem.cpp | 13 ++- 33 files changed, 396 insertions(+), 39 deletions(-) create mode 100644 SynapseEngine/Engine/Shaders/Includes/Common/Tag.glsl create mode 100644 SynapseEngine/Engine/System/Core/TagSystem.cpp create mode 100644 SynapseEngine/Engine/System/Core/TagSystem.h diff --git a/SynapseEngine/Editor/EditorApi/Impl/TagApiImpl.cpp b/SynapseEngine/Editor/EditorApi/Impl/TagApiImpl.cpp index 6a926998..d0d79add 100644 --- a/SynapseEngine/Editor/EditorApi/Impl/TagApiImpl.cpp +++ b/SynapseEngine/Editor/EditorApi/Impl/TagApiImpl.cpp @@ -25,11 +25,11 @@ namespace Syn { bool TagApiImpl::IsEntityEnabled(EntityID entity) const { return EditorApiUtils::ReadComponent(_sceneManager, entity, - [](const auto& c) { return c.enabled; }, true); + [](const auto& c) { return c.localEnabled; }, true); } void TagApiImpl::SetEntityEnabled(EntityID entity, bool enabled) { EditorApiUtils::ModifyComponent(_sceneManager, entity, - [&](auto& c, auto pool) { c.enabled = enabled; }); + [&](auto& c, auto pool) { c.localEnabled = enabled; }); } } \ No newline at end of file diff --git a/SynapseEngine/Engine/Component/Core/TagComponent.cpp b/SynapseEngine/Engine/Component/Core/TagComponent.cpp index e85b4e14..eafe4fff 100644 --- a/SynapseEngine/Engine/Component/Core/TagComponent.cpp +++ b/SynapseEngine/Engine/Component/Core/TagComponent.cpp @@ -5,7 +5,10 @@ namespace Syn TagComponent::TagComponent() : name("Entity"), tag("Untagged"), - enabled(true) + localEnabled(true), + globalEnabled(true), + castShadow(true), + receiveShadow(true) {} TagComponent::TagComponent(const std::string& name, const std::string& tag) : diff --git a/SynapseEngine/Engine/Component/Core/TagComponent.h b/SynapseEngine/Engine/Component/Core/TagComponent.h index 2823bbe3..5a62d19f 100644 --- a/SynapseEngine/Engine/Component/Core/TagComponent.h +++ b/SynapseEngine/Engine/Component/Core/TagComponent.h @@ -12,6 +12,11 @@ namespace Syn std::string name; std::string tag; - bool enabled; + + bool localEnabled = true; + bool globalEnabled = true; + + bool castShadow = true; + bool receiveShadow = true; }; } \ No newline at end of file diff --git a/SynapseEngine/Engine/Render/Passes/Setup/GlobalFrameSetupPass.cpp b/SynapseEngine/Engine/Render/Passes/Setup/GlobalFrameSetupPass.cpp index 0b1bb0f5..32083019 100644 --- a/SynapseEngine/Engine/Render/Passes/Setup/GlobalFrameSetupPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Setup/GlobalFrameSetupPass.cpp @@ -49,11 +49,14 @@ namespace Syn { ctx.cameraVisibleIndexBufferAddr = compManager->GetBufferAddr(BufferNames::CameraVisibleData, fIdx); ctx.cameraBufferAddr = compManager->GetBufferAddr(BufferNames::CameraData, fIdx); ctx.cameraSparseMapBufferAddr = compManager->GetBufferAddr(BufferNames::CameraSparseMap, fIdx); - + ctx.transformBufferAddr = compManager->GetBufferAddr(BufferNames::TransformData, fIdx); ctx.transformSparseMapBufferAddr = compManager->GetBufferAddr(BufferNames::TransformSparseMap, fIdx); ctx.transformModelLinkBufferAddr = compManager->GetBufferAddr(BufferNames::TransformModelLinkData, fIdx); + ctx.tagSparseMapBufferAddr = compManager->GetBufferAddr(BufferNames::TagSparseMap, fIdx); + ctx.tagDataBufferAddr = compManager->GetBufferAddr(BufferNames::TagData, fIdx); + ctx.staticChunkDataBufferAddr = drawData->Chunks.chunkDataBuffer.GetAddress(fIdx); ctx.staticChunkVisibleIndexBufferAddr = drawData->Chunks.chunkVisibilityBuffer.GetAddress(fIdx); ctx.staticChunkCountBufferAddr = drawData->Chunks.chunkIndirectDispatchBuffer.GetAddress(fIdx); diff --git a/SynapseEngine/Engine/Scene/BufferNames.h b/SynapseEngine/Engine/Scene/BufferNames.h index 14a7aa7f..8970c1af 100644 --- a/SynapseEngine/Engine/Scene/BufferNames.h +++ b/SynapseEngine/Engine/Scene/BufferNames.h @@ -83,5 +83,8 @@ namespace Syn static constexpr const char* SelectionOutlineData = "SelectionOutlineData"; static constexpr const char* HierarchySparseMap = "HierarchySparseMap"; + + static constexpr const char* TagSparseMap = "TagSparseMap"; + static constexpr const char* TagData = "TagData"; }; } \ No newline at end of file diff --git a/SynapseEngine/Engine/Scene/Scene.cpp b/SynapseEngine/Engine/Scene/Scene.cpp index d91b1e67..11f03bc1 100644 --- a/SynapseEngine/Engine/Scene/Scene.cpp +++ b/SynapseEngine/Engine/Scene/Scene.cpp @@ -19,8 +19,9 @@ #include "Engine/System/Core/TransformSystem.h" #include "Engine/System/Core/HierarchySystem.h" #include "Engine/System/Core/SelectionOutlineSystem.h" -#include "Engine/System/Rendering/RenderSystem.h" +#include "Engine/System/Core/TagSystem.h" #include "Engine/System/Core/CameraSystem.h" +#include "Engine/System/Rendering/RenderSystem.h" #include "Engine/System/Rendering/ModelSystem.h" #include "Engine/System/Rendering/MaterialSystem.h" #include "Engine/System/Rendering/ModelFrustumCullingSystem.h" @@ -175,7 +176,7 @@ namespace Syn RegisterSystem(); RegisterSystem(); - + RegisterSystem(); } void Scene::InitializeComponentBuffers() @@ -230,6 +231,9 @@ namespace Syn RegisterComponentSparseMapBuffer(BufferNames::HierarchySparseMap); RegisterComponentBuffer(BufferNames::SelectionOutlineData); + RegisterComponentSparseMapBuffer(BufferNames::TagSparseMap); + RegisterComponentBuffer(BufferNames::TagData); + RegisterComponentSparseMapBuffer(BufferNames::TransformSparseMap); RegisterComponentBuffer(BufferNames::TransformData); RegisterComponentBuffer(BufferNames::TransformModelLinkData); diff --git a/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json b/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json index d7ad2117..8d5b3da8 100644 --- a/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json +++ b/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json @@ -7,18 +7,18 @@ "spawn_bistro": false, "spawn_floor": false, "spawn_pbr_sponza": false, - "spawn_monkey": false + "spawn_monkey": true }, "materials": { "use_unique_materials": false, "shared_material_count": 100 }, "entities": { - "animated_characters": 50, - "static_geometry": 500, - "physics_boxes": 50, - "physics_spheres": 50, - "physics_capsules": 50 + "animated_characters": 500, + "static_geometry": 50000, + "physics_boxes": 500, + "physics_spheres": 500, + "physics_capsules": 500 }, "lights": { "directional_count": 1, diff --git a/SynapseEngine/Engine/Shaders/Includes/Common/FrameGlobalContext.glsl b/SynapseEngine/Engine/Shaders/Includes/Common/FrameGlobalContext.glsl index 5621c225..4fe6be1c 100644 --- a/SynapseEngine/Engine/Shaders/Includes/Common/FrameGlobalContext.glsl +++ b/SynapseEngine/Engine/Shaders/Includes/Common/FrameGlobalContext.glsl @@ -16,6 +16,9 @@ struct FrameGlobalContext { uint64_t cameraBufferAddr; uint64_t cameraSparseMapBufferAddr; + uint64_t tagSparseMapBufferAddr; + uint64_t tagDataBufferAddr; + uint64_t transformBufferAddr; uint64_t transformSparseMapBufferAddr; uint64_t transformModelLinkBufferAddr; diff --git a/SynapseEngine/Engine/Shaders/Includes/Common/Tag.glsl b/SynapseEngine/Engine/Shaders/Includes/Common/Tag.glsl new file mode 100644 index 00000000..e1f7b87b --- /dev/null +++ b/SynapseEngine/Engine/Shaders/Includes/Common/Tag.glsl @@ -0,0 +1,12 @@ +#ifndef SYN_INCLUDES_COMMON_TAG_GLSL +#define SYN_INCLUDES_COMMON_TAG_GLSL + +#include "../Core.glsl" + +layout(buffer_reference, std430) readonly restrict buffer TagDataBuffer { + uint data[]; +}; + +#define GET_TAG_DATA(addr, idx) TagDataBuffer(addr).data[idx] + +#endif \ No newline at end of file diff --git a/SynapseEngine/Engine/Shaders/Passes/Culling/DirectionLight/DirectionLightShadowModelCulling.comp b/SynapseEngine/Engine/Shaders/Passes/Culling/DirectionLight/DirectionLightShadowModelCulling.comp index 48c34df2..3773ea0f 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Culling/DirectionLight/DirectionLightShadowModelCulling.comp +++ b/SynapseEngine/Engine/Shaders/Passes/Culling/DirectionLight/DirectionLightShadowModelCulling.comp @@ -16,6 +16,7 @@ #include "../../../Includes/Common/DirectionLight.glsl" #include "../../../Includes/Utils/CullingMath.glsl" #include "../../../Includes/Utils/Occlusion.glsl" +#include "../../../Includes/Common/Tag.glsl" layout(local_size_x = 32, local_size_y = 1, local_size_z = 1) in; @@ -48,8 +49,17 @@ void main() // 2. Resolve Entity and Model Data TransformModelLink link = GET_TRANSFORM_MODEL_LINK(ctx.transformModelLinkBufferAddr, transformDenseIndex); if (link.modelDenseIndex == INVALID_INDEX) return; - uint entityId = link.entityIndex; + + uint tagSparseIndex = GET_SPARSE_INDEX(ctx.tagSparseMapBufferAddr, entityId); + if (tagSparseIndex == INVALID_INDEX) return; + + uint entityFlags = GET_TAG_DATA(ctx.tagDataBufferAddr, tagSparseIndex); + bool isActive = (entityFlags & (1u << 0)) != 0; + bool castShadow = (entityFlags & (1u << 1)) != 0; + + if (!isActive || !castShadow) return; + ModelComponent modelComp = GET_MODEL_COMP(ctx.modelBufferAddr, link.modelDenseIndex); if (modelComp.modelIndex == INVALID_INDEX) return; diff --git a/SynapseEngine/Engine/Shaders/Passes/Culling/DirectionLight/DirectionLightShadowMortonModelCulling.comp b/SynapseEngine/Engine/Shaders/Passes/Culling/DirectionLight/DirectionLightShadowMortonModelCulling.comp index 3b5b8dce..81ea8e2a 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Culling/DirectionLight/DirectionLightShadowMortonModelCulling.comp +++ b/SynapseEngine/Engine/Shaders/Passes/Culling/DirectionLight/DirectionLightShadowMortonModelCulling.comp @@ -8,6 +8,7 @@ #include "../../../Includes/Common/FrameGlobalContext.glsl" #include "../../../Includes/Common/Camera.glsl" #include "../../../Includes/Common/Transform.glsl" +#include "../../../Includes/Common/Tag.glsl" #include "../../../Includes/Common/Model.glsl" #include "../../../Includes/Common/Mesh.glsl" #include "../../../Includes/Common/Material.glsl" @@ -70,8 +71,16 @@ void main() { TransformModelLink link = GET_TRANSFORM_MODEL_LINK(ctx.transformModelLinkBufferAddr, denseIndex); if (link.modelDenseIndex == INVALID_INDEX) continue; - uint entityId = link.entityIndex; + + uint tagSparseIndex = GET_SPARSE_INDEX(ctx.tagSparseMapBufferAddr, entityId); + if (tagSparseIndex == INVALID_INDEX) return; + + uint entityFlags = GET_TAG_DATA(ctx.tagDataBufferAddr, tagSparseIndex); + bool isActive = (entityFlags & (1u << 0)) != 0; + bool castShadow = (entityFlags & (1u << 1)) != 0; + + if (!isActive || !castShadow) return; TransformComponent transform = GET_TRANSFORM(ctx.transformBufferAddr, denseIndex); ModelComponent modelComp = GET_MODEL_COMP(ctx.modelBufferAddr, link.modelDenseIndex); diff --git a/SynapseEngine/Engine/Shaders/Passes/Culling/DirectionLight/DirectionLightShadowStaticModelCulling.comp b/SynapseEngine/Engine/Shaders/Passes/Culling/DirectionLight/DirectionLightShadowStaticModelCulling.comp index cf686a5f..8404a69f 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Culling/DirectionLight/DirectionLightShadowStaticModelCulling.comp +++ b/SynapseEngine/Engine/Shaders/Passes/Culling/DirectionLight/DirectionLightShadowStaticModelCulling.comp @@ -8,6 +8,7 @@ #include "../../../Includes/Common/FrameGlobalContext.glsl" #include "../../../Includes/Common/Camera.glsl" #include "../../../Includes/Common/Transform.glsl" +#include "../../../Includes/Common/Tag.glsl" #include "../../../Includes/Common/Model.glsl" #include "../../../Includes/Common/Mesh.glsl" #include "../../../Includes/Common/Material.glsl" @@ -63,10 +64,19 @@ void main() { for (uint i = gl_LocalInvocationID.x; i < chunk.entityCount; i += gl_WorkGroupSize.x) { uint denseIndex = chunk.firstEntityIndex + i; + TransformModelLink link = GET_TRANSFORM_MODEL_LINK(ctx.transformModelLinkBufferAddr, denseIndex); - if (link.modelDenseIndex == INVALID_INDEX) continue; uint entityId = link.entityIndex; + + uint tagSparseIndex = GET_SPARSE_INDEX(ctx.tagSparseMapBufferAddr, entityId); + if (tagSparseIndex == INVALID_INDEX) return; + + uint entityFlags = GET_TAG_DATA(ctx.tagDataBufferAddr, tagSparseIndex); + bool isActive = (entityFlags & (1u << 0)) != 0; + bool castShadow = (entityFlags & (1u << 1)) != 0; + + if (!isActive || !castShadow) return; TransformComponent transform = GET_TRANSFORM(ctx.transformBufferAddr, denseIndex); ModelComponent modelComp = GET_MODEL_COMP(ctx.modelBufferAddr, link.modelDenseIndex); diff --git a/SynapseEngine/Engine/Shaders/Passes/Culling/Geometry/GeometryModelCulling.comp b/SynapseEngine/Engine/Shaders/Passes/Culling/Geometry/GeometryModelCulling.comp index c67771b7..2325e131 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Culling/Geometry/GeometryModelCulling.comp +++ b/SynapseEngine/Engine/Shaders/Passes/Culling/Geometry/GeometryModelCulling.comp @@ -8,6 +8,7 @@ #include "../../../Includes/Common/FrameGlobalContext.glsl" #include "../../../Includes/Common/Camera.glsl" #include "../../../Includes/Common/Transform.glsl" +#include "../../../Includes/Common/Tag.glsl" #include "../../../Includes/Common/Model.glsl" #include "../../../Includes/Common/Mesh.glsl" #include "../../../Includes/Common/Animation.glsl" @@ -41,9 +42,15 @@ void main() TransformModelLink link = GET_TRANSFORM_MODEL_LINK(ctx.transformModelLinkBufferAddr, transformDenseIndex); if (link.modelDenseIndex == INVALID_INDEX) return; - uint entityId = link.entityIndex; + uint tagSparseIndex = GET_SPARSE_INDEX(ctx.tagSparseMapBufferAddr, entityId); + if (tagSparseIndex == INVALID_INDEX) return; + uint entityFlags = GET_TAG_DATA(ctx.tagDataBufferAddr, tagSparseIndex); + + bool isActive = (entityFlags & (1u << 0)) != 0; + if (!isActive) return; + // 2. Fetch Model component and Entity ID ModelComponent modelComp = GET_MODEL_COMP(ctx.modelBufferAddr, link.modelDenseIndex); if (modelComp.modelIndex == INVALID_INDEX) return; diff --git a/SynapseEngine/Engine/Shaders/Passes/Culling/Geometry/GeometryMortonModelCulling.comp b/SynapseEngine/Engine/Shaders/Passes/Culling/Geometry/GeometryMortonModelCulling.comp index bb938f0e..ec8a9c88 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Culling/Geometry/GeometryMortonModelCulling.comp +++ b/SynapseEngine/Engine/Shaders/Passes/Culling/Geometry/GeometryMortonModelCulling.comp @@ -8,6 +8,7 @@ #include "../../../Includes/Common/FrameGlobalContext.glsl" #include "../../../Includes/Common/Camera.glsl" #include "../../../Includes/Common/Transform.glsl" +#include "../../../Includes/Common/Tag.glsl" #include "../../../Includes/Common/Model.glsl" #include "../../../Includes/Common/Mesh.glsl" #include "../../../Includes/Common/Material.glsl" @@ -58,11 +59,17 @@ void main() { if (denseIndex == 0xFFFFFFFF) continue; - TransformModelLink link = GET_TRANSFORM_MODEL_LINK(ctx.transformModelLinkBufferAddr, denseIndex); - + TransformModelLink link = GET_TRANSFORM_MODEL_LINK(ctx.transformModelLinkBufferAddr, denseIndex); if (link.modelDenseIndex == INVALID_INDEX) continue; uint entityId = link.entityIndex; + uint tagSparseIndex = GET_SPARSE_INDEX(ctx.tagSparseMapBufferAddr, entityId); + if (tagSparseIndex == INVALID_INDEX) return; + uint entityFlags = GET_TAG_DATA(ctx.tagDataBufferAddr, tagSparseIndex); + + bool isActive = (entityFlags & (1u << 0)) != 0; + if (!isActive) return; + TransformComponent transform = GET_TRANSFORM(ctx.transformBufferAddr, denseIndex); ModelComponent modelComp = GET_MODEL_COMP(ctx.modelBufferAddr, link.modelDenseIndex); diff --git a/SynapseEngine/Engine/Shaders/Passes/Culling/Geometry/GeometryStaticModelCulling.comp b/SynapseEngine/Engine/Shaders/Passes/Culling/Geometry/GeometryStaticModelCulling.comp index 3e7757d9..58ebd8bf 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Culling/Geometry/GeometryStaticModelCulling.comp +++ b/SynapseEngine/Engine/Shaders/Passes/Culling/Geometry/GeometryStaticModelCulling.comp @@ -8,6 +8,7 @@ #include "../../../Includes/Common/FrameGlobalContext.glsl" #include "../../../Includes/Common/Camera.glsl" #include "../../../Includes/Common/Transform.glsl" +#include "../../../Includes/Common/Tag.glsl" #include "../../../Includes/Common/Model.glsl" #include "../../../Includes/Common/Mesh.glsl" #include "../../../Includes/Common/Material.glsl" @@ -55,9 +56,15 @@ void main() { // Resolve dense entity index directly from the chunk uint denseIndex = chunk.firstEntityIndex + i; TransformModelLink link = GET_TRANSFORM_MODEL_LINK(ctx.transformModelLinkBufferAddr, denseIndex); - if (link.modelDenseIndex == INVALID_INDEX) continue; uint entityId = link.entityIndex; + + uint tagSparseIndex = GET_SPARSE_INDEX(ctx.tagSparseMapBufferAddr, entityId); + if (tagSparseIndex == INVALID_INDEX) return; + uint entityFlags = GET_TAG_DATA(ctx.tagDataBufferAddr, tagSparseIndex); + + bool isActive = (entityFlags & (1u << 0)) != 0; + if (!isActive) return; TransformComponent transform = GET_TRANSFORM(ctx.transformBufferAddr, denseIndex); ModelComponent modelComp = GET_MODEL_COMP(ctx.modelBufferAddr, link.modelDenseIndex); diff --git a/SynapseEngine/Engine/Shaders/Passes/Culling/PointLight/PointLightCulling.comp b/SynapseEngine/Engine/Shaders/Passes/Culling/PointLight/PointLightCulling.comp index 40f18ac1..016db1e9 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Culling/PointLight/PointLightCulling.comp +++ b/SynapseEngine/Engine/Shaders/Passes/Culling/PointLight/PointLightCulling.comp @@ -9,6 +9,7 @@ #include "../../../Includes/Core.glsl" #include "../../../Includes/Common/FrameGlobalContext.glsl" +#include "../../../Includes/Common/Tag.glsl" #include "../../../Includes/Common/Camera.glsl" #include "../../../Includes/Common/PointLight.glsl" #include "../../../Includes/Utils/CullingMath.glsl" @@ -32,6 +33,13 @@ void main() { if (globalId >= ctx.pointLightCount) return; PointLightColliderGPU collider = GET_POINT_LIGHT_COLLIDER(ctx.pointLightColliderBufferAddr, globalId); + + uint tagSparseIndex = GET_SPARSE_INDEX(ctx.tagSparseMapBufferAddr, collider.entityIndex); + if (tagSparseIndex == INVALID_INDEX) return; + uint entityFlags = GET_TAG_DATA(ctx.tagDataBufferAddr, tagSparseIndex); + + bool isActive = (entityFlags & (1u << 0)) != 0; + if (!isActive) return; uint cameraIdx = GET_SPARSE_INDEX(ctx.cameraSparseMapBufferAddr, ctx.mainCameraEntity); CameraComponent camera = GET_CAMERA(ctx.cameraBufferAddr, cameraIdx); diff --git a/SynapseEngine/Engine/Shaders/Passes/Culling/PointLight/PointLightShadowModelCulling.comp b/SynapseEngine/Engine/Shaders/Passes/Culling/PointLight/PointLightShadowModelCulling.comp index 2c4ec2bb..69932731 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Culling/PointLight/PointLightShadowModelCulling.comp +++ b/SynapseEngine/Engine/Shaders/Passes/Culling/PointLight/PointLightShadowModelCulling.comp @@ -8,6 +8,7 @@ #include "../../../Includes/Common/FrameGlobalContext.glsl" #include "../../../Includes/Common/Camera.glsl" #include "../../../Includes/Common/Transform.glsl" +#include "../../../Includes/Common/Tag.glsl" #include "../../../Includes/Common/Model.glsl" #include "../../../Includes/Common/Mesh.glsl" #include "../../../Includes/Common/Animation.glsl" @@ -45,8 +46,17 @@ void main() // 2. Resolve Entity and Model Data TransformModelLink link = GET_TRANSFORM_MODEL_LINK(ctx.transformModelLinkBufferAddr, transformDenseIndex); if (link.modelDenseIndex == INVALID_INDEX) return; - uint entityId = link.entityIndex; + + uint tagSparseIndex = GET_SPARSE_INDEX(ctx.tagSparseMapBufferAddr, entityId); + if (tagSparseIndex == INVALID_INDEX) return; + + uint entityFlags = GET_TAG_DATA(ctx.tagDataBufferAddr, tagSparseIndex); + bool isActive = (entityFlags & (1u << 0)) != 0; + bool castShadow = (entityFlags & (1u << 1)) != 0; + + if (!isActive || !castShadow) return; + ModelComponent modelComp = GET_MODEL_COMP(ctx.modelBufferAddr, link.modelDenseIndex); if (modelComp.modelIndex == INVALID_INDEX) return; diff --git a/SynapseEngine/Engine/Shaders/Passes/Culling/PointLight/PointLightShadowMortonModelCulling.comp b/SynapseEngine/Engine/Shaders/Passes/Culling/PointLight/PointLightShadowMortonModelCulling.comp index 9e8cd2d4..fc0a091b 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Culling/PointLight/PointLightShadowMortonModelCulling.comp +++ b/SynapseEngine/Engine/Shaders/Passes/Culling/PointLight/PointLightShadowMortonModelCulling.comp @@ -8,6 +8,7 @@ #include "../../../Includes/Common/FrameGlobalContext.glsl" #include "../../../Includes/Common/Camera.glsl" #include "../../../Includes/Common/Transform.glsl" +#include "../../../Includes/Common/Tag.glsl" #include "../../../Includes/Common/Model.glsl" #include "../../../Includes/Common/Mesh.glsl" #include "../../../Includes/Common/Material.glsl" @@ -66,9 +67,17 @@ void main() { TransformModelLink link = GET_TRANSFORM_MODEL_LINK(ctx.transformModelLinkBufferAddr, denseIndex); if (link.modelDenseIndex == INVALID_INDEX) continue; - uint entityId = link.entityIndex; + uint tagSparseIndex = GET_SPARSE_INDEX(ctx.tagSparseMapBufferAddr, entityId); + if (tagSparseIndex == INVALID_INDEX) return; + + uint entityFlags = GET_TAG_DATA(ctx.tagDataBufferAddr, tagSparseIndex); + bool isActive = (entityFlags & (1u << 0)) != 0; + bool castShadow = (entityFlags & (1u << 1)) != 0; + + if (!isActive || !castShadow) return; + TransformComponent transform = GET_TRANSFORM(ctx.transformBufferAddr, denseIndex); ModelComponent modelComp = GET_MODEL_COMP(ctx.modelBufferAddr, link.modelDenseIndex); diff --git a/SynapseEngine/Engine/Shaders/Passes/Culling/PointLight/PointLightShadowStaticModelCulling.comp b/SynapseEngine/Engine/Shaders/Passes/Culling/PointLight/PointLightShadowStaticModelCulling.comp index 0f7d5a7d..0955992e 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Culling/PointLight/PointLightShadowStaticModelCulling.comp +++ b/SynapseEngine/Engine/Shaders/Passes/Culling/PointLight/PointLightShadowStaticModelCulling.comp @@ -8,6 +8,7 @@ #include "../../../Includes/Common/FrameGlobalContext.glsl" #include "../../../Includes/Common/Camera.glsl" #include "../../../Includes/Common/Transform.glsl" +#include "../../../Includes/Common/Tag.glsl" #include "../../../Includes/Common/Model.glsl" #include "../../../Includes/Common/Mesh.glsl" #include "../../../Includes/Common/Material.glsl" @@ -59,11 +60,20 @@ void main() { for (uint i = gl_LocalInvocationID.x; i < chunk.entityCount; i += gl_WorkGroupSize.x) { uint denseIndex = chunk.firstEntityIndex + i; - TransformModelLink link = GET_TRANSFORM_MODEL_LINK(ctx.transformModelLinkBufferAddr, denseIndex); - + + TransformModelLink link = GET_TRANSFORM_MODEL_LINK(ctx.transformModelLinkBufferAddr, denseIndex); if (link.modelDenseIndex == INVALID_INDEX) continue; uint entityId = link.entityIndex; + uint tagSparseIndex = GET_SPARSE_INDEX(ctx.tagSparseMapBufferAddr, entityId); + if (tagSparseIndex == INVALID_INDEX) return; + + uint entityFlags = GET_TAG_DATA(ctx.tagDataBufferAddr, tagSparseIndex); + bool isActive = (entityFlags & (1u << 0)) != 0; + bool castShadow = (entityFlags & (1u << 1)) != 0; + + if (!isActive || !castShadow) return; + TransformComponent transform = GET_TRANSFORM(ctx.transformBufferAddr, denseIndex); ModelComponent modelComp = GET_MODEL_COMP(ctx.modelBufferAddr, link.modelDenseIndex); diff --git a/SynapseEngine/Engine/Shaders/Passes/Culling/SpotLight/SpotLightCulling.comp b/SynapseEngine/Engine/Shaders/Passes/Culling/SpotLight/SpotLightCulling.comp index 467d7fcd..6ccb515a 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Culling/SpotLight/SpotLightCulling.comp +++ b/SynapseEngine/Engine/Shaders/Passes/Culling/SpotLight/SpotLightCulling.comp @@ -9,6 +9,7 @@ #include "../../../Includes/Core.glsl" #include "../../../Includes/Common/FrameGlobalContext.glsl" +#include "../../../Includes/Common/Tag.glsl" #include "../../../Includes/Common/Camera.glsl" #include "../../../Includes/Common/SpotLight.glsl" #include "../../../Includes/Utils/CullingMath.glsl" @@ -33,6 +34,13 @@ void main() { SpotLightColliderGPU collider = GET_SPOT_LIGHT_COLLIDER(ctx.spotLightColliderBufferAddr, globalId); + uint tagSparseIndex = GET_SPARSE_INDEX(ctx.tagSparseMapBufferAddr, collider.entityIndex); + if (tagSparseIndex == INVALID_INDEX) return; + uint entityFlags = GET_TAG_DATA(ctx.tagDataBufferAddr, tagSparseIndex); + + bool isActive = (entityFlags & (1u << 0)) != 0; + if (!isActive) return; + uint cameraIdx = GET_SPARSE_INDEX(ctx.cameraSparseMapBufferAddr, ctx.mainCameraEntity); CameraComponent camera = GET_CAMERA(ctx.cameraBufferAddr, cameraIdx); diff --git a/SynapseEngine/Engine/Shaders/Passes/Culling/SpotLight/SpotLightShadowModelCulling.comp b/SynapseEngine/Engine/Shaders/Passes/Culling/SpotLight/SpotLightShadowModelCulling.comp index 7bfad68c..4a44a349 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Culling/SpotLight/SpotLightShadowModelCulling.comp +++ b/SynapseEngine/Engine/Shaders/Passes/Culling/SpotLight/SpotLightShadowModelCulling.comp @@ -8,6 +8,7 @@ #include "../../../Includes/Common/FrameGlobalContext.glsl" #include "../../../Includes/Common/Camera.glsl" #include "../../../Includes/Common/Transform.glsl" +#include "../../../Includes/Common/Tag.glsl" #include "../../../Includes/Common/Model.glsl" #include "../../../Includes/Common/Mesh.glsl" #include "../../../Includes/Common/Animation.glsl" @@ -45,8 +46,17 @@ void main() // 2. Resolve Entity and Model Data TransformModelLink link = GET_TRANSFORM_MODEL_LINK(ctx.transformModelLinkBufferAddr, transformDenseIndex); if (link.modelDenseIndex == INVALID_INDEX) return; - uint entityId = link.entityIndex; + + uint tagSparseIndex = GET_SPARSE_INDEX(ctx.tagSparseMapBufferAddr, entityId); + if (tagSparseIndex == INVALID_INDEX) return; + + uint entityFlags = GET_TAG_DATA(ctx.tagDataBufferAddr, tagSparseIndex); + bool isActive = (entityFlags & (1u << 0)) != 0; + bool castShadow = (entityFlags & (1u << 1)) != 0; + + if (!isActive || !castShadow) return; + ModelComponent modelComp = GET_MODEL_COMP(ctx.modelBufferAddr, link.modelDenseIndex); if (modelComp.modelIndex == INVALID_INDEX) return; diff --git a/SynapseEngine/Engine/Shaders/Passes/Culling/SpotLight/SpotLightShadowMortonModelCulling.comp b/SynapseEngine/Engine/Shaders/Passes/Culling/SpotLight/SpotLightShadowMortonModelCulling.comp index 9337bd4f..a75b9207 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Culling/SpotLight/SpotLightShadowMortonModelCulling.comp +++ b/SynapseEngine/Engine/Shaders/Passes/Culling/SpotLight/SpotLightShadowMortonModelCulling.comp @@ -8,6 +8,7 @@ #include "../../../Includes/Common/FrameGlobalContext.glsl" #include "../../../Includes/Common/Camera.glsl" #include "../../../Includes/Common/Transform.glsl" +#include "../../../Includes/Common/Tag.glsl" #include "../../../Includes/Common/Model.glsl" #include "../../../Includes/Common/Mesh.glsl" #include "../../../Includes/Common/Material.glsl" @@ -70,6 +71,15 @@ void main() { if (link.modelDenseIndex == INVALID_INDEX) continue; uint entityId = link.entityIndex; + uint tagSparseIndex = GET_SPARSE_INDEX(ctx.tagSparseMapBufferAddr, entityId); + if (tagSparseIndex == INVALID_INDEX) return; + + uint entityFlags = GET_TAG_DATA(ctx.tagDataBufferAddr, tagSparseIndex); + bool isActive = (entityFlags & (1u << 0)) != 0; + bool castShadow = (entityFlags & (1u << 1)) != 0; + + if (!isActive || !castShadow) return; + TransformComponent transform = GET_TRANSFORM(ctx.transformBufferAddr, denseIndex); ModelComponent modelComp = GET_MODEL_COMP(ctx.modelBufferAddr, link.modelDenseIndex); diff --git a/SynapseEngine/Engine/Shaders/Passes/Culling/SpotLight/SpotLightShadowStaticModelCulling.comp b/SynapseEngine/Engine/Shaders/Passes/Culling/SpotLight/SpotLightShadowStaticModelCulling.comp index 9a49ddff..57fd9f97 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Culling/SpotLight/SpotLightShadowStaticModelCulling.comp +++ b/SynapseEngine/Engine/Shaders/Passes/Culling/SpotLight/SpotLightShadowStaticModelCulling.comp @@ -8,6 +8,7 @@ #include "../../../Includes/Common/FrameGlobalContext.glsl" #include "../../../Includes/Common/Camera.glsl" #include "../../../Includes/Common/Transform.glsl" +#include "../../../Includes/Common/Tag.glsl" #include "../../../Includes/Common/Model.glsl" #include "../../../Includes/Common/Mesh.glsl" #include "../../../Includes/Common/Material.glsl" @@ -62,9 +63,18 @@ void main() { { uint denseIndex = chunk.firstEntityIndex + i; TransformModelLink link = GET_TRANSFORM_MODEL_LINK(ctx.transformModelLinkBufferAddr, denseIndex); - if (link.modelDenseIndex == INVALID_INDEX) continue; uint entityId = link.entityIndex; + + uint tagSparseIndex = GET_SPARSE_INDEX(ctx.tagSparseMapBufferAddr, entityId); + if (tagSparseIndex == INVALID_INDEX) return; + + uint entityFlags = GET_TAG_DATA(ctx.tagDataBufferAddr, tagSparseIndex); + bool isActive = (entityFlags & (1u << 0)) != 0; + bool castShadow = (entityFlags & (1u << 1)) != 0; + + if (!isActive || !castShadow) return; + TransformComponent transform = GET_TRANSFORM(ctx.transformBufferAddr, denseIndex); ModelComponent modelComp = GET_MODEL_COMP(ctx.modelBufferAddr, link.modelDenseIndex); diff --git a/SynapseEngine/Engine/System/Core/TagSystem.cpp b/SynapseEngine/Engine/System/Core/TagSystem.cpp new file mode 100644 index 00000000..d6b248b4 --- /dev/null +++ b/SynapseEngine/Engine/System/Core/TagSystem.cpp @@ -0,0 +1,102 @@ +#include "TagSystem.h" +#include "Engine/Scene/Scene.h" +#include "Engine/Component/Core/HierarchyComponent.h" +#include "Engine/System/Core/HierarchySystem.h" + +namespace Syn +{ + std::vector TagSystem::GetReadDependencies() const + { + return { TypeInfo::ID }; + } + + std::vector TagSystem::GetWriteDependencies() const + { + return { TypeInfo::ID }; + } + + void TagSystem::UpdateComponents(Scene* scene, uint32_t frameIndex, float deltaTime, tf::Subflow& subflow) + { + auto registry = scene->GetRegistry(); + auto tagPool = registry->GetPool(); + auto hierarchyPool = registry->GetPool(); + auto hierarchyManager = scene->GetHierarchyManager(); + + if (!tagPool || !hierarchyPool || !hierarchyManager) return; + + this->EmplaceTask(subflow, SystemPhaseNames::Update, [tagPool, hierarchyPool, hierarchyManager]() { + + uint32_t maxLevel = hierarchyManager->GetMaxActiveLevel(); + + for (uint32_t level = 0; level < maxLevel; ++level) + { + auto entitiesInLevel = hierarchyManager->GetEntitiesInLevel(level); + + for (EntityID entity : entitiesInLevel) + { + if (!tagPool->Has(entity)) continue; + + auto& tag = tagPool->Get(entity); + bool parentGlobalEnabled = true; + + if (level > 0) + { + EntityID parentId = hierarchyPool->Get(entity).parent; + if (parentId != NULL_ENTITY && tagPool->Has(parentId)) + { + parentGlobalEnabled = tagPool->Get(parentId).globalEnabled; + } + } + + bool newGlobalEnabled = tag.localEnabled && parentGlobalEnabled; + + if (tag.globalEnabled != newGlobalEnabled || tagPool->IsBitSet(entity) || tagPool->IsBitSet(entity)) + { + tag.globalEnabled = newGlobalEnabled; + tagPool->SetBit(entity); + tag.version++; + } + } + } + }); + } + + void TagSystem::UploadComponents(Scene* scene, uint32_t frameIndex, tf::Subflow& subflow, bool uploadDynamic, bool uploadStatic) + { + auto registry = scene->GetRegistry(); + auto componentBufferManager = scene->GetComponentBufferManager(); + auto tagPool = registry->GetPool(); + if (!tagPool) return; + + auto tagDataBuffer = componentBufferManager->GetComponentBuffer(BufferNames::TagData, frameIndex); + if (!tagDataBuffer.buffer) return; + + auto tagDataBufferHandler = static_cast(tagDataBuffer.buffer->Map()); + bool forceUpload = this->ShouldForceUpload(); + + auto processUpload = [tagPool, tagDataBuffer, tagDataBufferHandler, forceUpload](EntityID entity) { + auto& tagComp = tagPool->Get(entity); + auto tagIndex = tagPool->GetMapping().Get(entity); + + if (forceUpload || tagDataBuffer.versions[tagIndex] != tagComp.version) + { + tagDataBuffer.versions[tagIndex] = tagComp.version; + + uint32_t flags = 0; + if (tagComp.globalEnabled) flags |= (1 << 0); + if (tagComp.castShadow) flags |= (1 << 1); + if (tagComp.receiveShadow) flags |= (1 << 2); + + tagDataBufferHandler[tagIndex] = flags; + } + }; + + ForEachStream(tagPool, subflow, SystemPhaseNames::UploadGPU, processUpload); + + if (uploadDynamic) + ForEachDynamic(tagPool, subflow, SystemPhaseNames::UploadGPU, processUpload); + + if (uploadStatic) + ForEachStatic(tagPool, subflow, SystemPhaseNames::UploadGPU, processUpload); + } +} \ No newline at end of file diff --git a/SynapseEngine/Engine/System/Core/TagSystem.h b/SynapseEngine/Engine/System/Core/TagSystem.h new file mode 100644 index 00000000..4dfb08cf --- /dev/null +++ b/SynapseEngine/Engine/System/Core/TagSystem.h @@ -0,0 +1,20 @@ +#pragma once +#include "Engine/System/ComponentSystem.h" +#include "Engine/Component/Core/TagComponent.h" + +namespace Syn +{ + class SYN_API TagSystem : public ComponentSystem + { + public: + std::string GetName() const override { return "TagSystem"; } + std::string GetGroup() const override { return SystemGroupNames::CoreSystems; } + std::vector GetWriteDependencies() const override; + std::vector GetReadDependencies() const override; + protected: + std::string GetSparseBufferName() const override { return BufferNames::TagSparseMap; } + + void UpdateComponents(Scene* scene, uint32_t frameIndex, float deltaTime, tf::Subflow& subflow) override; + void UploadComponents(Scene* scene, uint32_t frameIndex, tf::Subflow& subflow, bool uploadDynamic, bool uploadStatic) override; + }; +} \ No newline at end of file diff --git a/SynapseEngine/Engine/System/Core/TransformModelLinkSystem.cpp b/SynapseEngine/Engine/System/Core/TransformModelLinkSystem.cpp index ddb171f9..9949519c 100644 --- a/SynapseEngine/Engine/System/Core/TransformModelLinkSystem.cpp +++ b/SynapseEngine/Engine/System/Core/TransformModelLinkSystem.cpp @@ -3,6 +3,7 @@ #include "Engine/Scene/BufferNames.h" #include "Engine/System/Core/TransformSystem.h" #include "Engine/System/Rendering/ModelSystem.h" +#include "Engine/System/Core/TagSystem.h" #include namespace Syn diff --git a/SynapseEngine/Engine/System/Light/Direction/DirectionLightCullingSystem.cpp b/SynapseEngine/Engine/System/Light/Direction/DirectionLightCullingSystem.cpp index 73db86ba..b2548215 100644 --- a/SynapseEngine/Engine/System/Light/Direction/DirectionLightCullingSystem.cpp +++ b/SynapseEngine/Engine/System/Light/Direction/DirectionLightCullingSystem.cpp @@ -8,12 +8,16 @@ #include "Engine/Scene/BufferNames.h" #include +#include "Engine/Component/Core/TagComponent.h" +#include "Engine/System/Core/TagSystem.h" + namespace Syn { std::vector DirectionLightCullingSystem::GetReadDependencies() const { return { TypeInfo::ID, - TypeInfo::ID + TypeInfo::ID, + TypeInfo::ID }; } @@ -28,6 +32,7 @@ namespace Syn auto drawData = scene->GetSceneDrawData(); auto registry = scene->GetRegistry(); + auto tagPool = registry->GetPool(); auto pool = registry->GetPool(); auto shadowPool = registry->GetPool(); @@ -50,11 +55,15 @@ namespace Syn } }); - auto cullFunc = [pool, drawData, shadowPool](EntityID entity) { + auto cullFunc = [pool, drawData, shadowPool, tagPool](EntityID entity) { + if (tagPool && tagPool->Has(entity)) { + if (!tagPool->Get(entity).globalEnabled) { + return; + } + } + const auto& lightComp = pool->Get(entity); - // if (!lightComp.enabled) return; - std::atomic_ref countRef(drawData->DirectionLights.cmdTemplate.instanceCount); uint32_t slot = countRef.fetch_add(1, std::memory_order_relaxed); diff --git a/SynapseEngine/Engine/System/Light/Direction/DirectionLightShadowCullingSystem.cpp b/SynapseEngine/Engine/System/Light/Direction/DirectionLightShadowCullingSystem.cpp index ea93fa9b..4b240af7 100644 --- a/SynapseEngine/Engine/System/Light/Direction/DirectionLightShadowCullingSystem.cpp +++ b/SynapseEngine/Engine/System/Light/Direction/DirectionLightShadowCullingSystem.cpp @@ -24,6 +24,9 @@ #include "DirectionLightShadowAtlasSystem.h" #include "DirectionLightShadowSystem.h" +#include "Engine/Component/Core/TagComponent.h" +#include "Engine/System/Core/TagSystem.h" + namespace Syn { constexpr bool ENABLE_DEBUG_LOGGING = false; @@ -60,6 +63,7 @@ namespace Syn TypeInfo::ID, TypeInfo::ID, TypeInfo::ID, + TypeInfo::ID }; } @@ -94,6 +98,7 @@ namespace Syn auto animPool = registry->GetPool(); auto overridePool = registry->GetPool(); auto shadowPool = registry->GetPool(); + auto tagPool = registry->GetPool(); EntityID cameraEntity = scene->GetSceneCameraEntity(); if (!modelPool || !transformPool || !cameraPool || cameraEntity == NULL_ENTITY || !shadowPool) @@ -115,8 +120,15 @@ namespace Syn auto matTypeSnapshot = materialManager->GetRenderTypeSnapshot(); // Extract entity properties (runs exactly once per entity) - auto withEntityData = [modelPool, transformPool, animPool, overridePool, modelSnapshot, animSnapshot, drawData] + auto withEntityData = [modelPool, transformPool, animPool, overridePool, modelSnapshot, tagPool, animSnapshot, drawData] (EntityID entity, auto&& nextFunc) { + if (tagPool && tagPool->Has(entity)) { + const auto& tag = tagPool->Get(entity); + if (!tag.globalEnabled || !tag.castShadow) { + return; + } + } + if (!modelPool->Has(entity)) return; diff --git a/SynapseEngine/Engine/System/Light/Point/PointLightCullingSystem.cpp b/SynapseEngine/Engine/System/Light/Point/PointLightCullingSystem.cpp index 2da2a525..66ec8745 100644 --- a/SynapseEngine/Engine/System/Light/Point/PointLightCullingSystem.cpp +++ b/SynapseEngine/Engine/System/Light/Point/PointLightCullingSystem.cpp @@ -10,12 +10,16 @@ #include "Engine/Mesh/MeshSourceNames.h" #include +#include "Engine/Component/Core/TagComponent.h" +#include "Engine/System/Core/TagSystem.h" + namespace Syn { std::vector PointLightCullingSystem::GetReadDependencies() const { return { TypeInfo::ID, - TypeInfo::ID + TypeInfo::ID, + TypeInfo::ID, }; } @@ -33,6 +37,7 @@ namespace Syn auto pool = registry->GetPool(); auto shadowPool = registry->GetPool(); auto cameraPool = registry->GetPool(); + auto tagPool = registry->GetPool(); EntityID cameraEntity = scene->GetSceneCameraEntity(); if (!pool || !cameraPool || cameraEntity == NULL_ENTITY) return; @@ -58,7 +63,13 @@ namespace Syn glm::vec2 screenRes = glm::vec2(cameraComp.width, cameraComp.height); - auto cullFunc = [this, settings, pool, shadowPool, cameraComp, drawData, screenRes](EntityID entity) { + auto cullFunc = [this, settings, pool, shadowPool, cameraComp, drawData, screenRes, tagPool](EntityID entity) { + if (tagPool && tagPool->Has(entity)) { + if (!tagPool->Get(entity).globalEnabled) { + return; + } + } + const auto& lightComp = pool->Get(entity); bool visibility = true; diff --git a/SynapseEngine/Engine/System/Light/Point/PointLightShadowCullingSystem.cpp b/SynapseEngine/Engine/System/Light/Point/PointLightShadowCullingSystem.cpp index 21e59e7c..c4869a1b 100644 --- a/SynapseEngine/Engine/System/Light/Point/PointLightShadowCullingSystem.cpp +++ b/SynapseEngine/Engine/System/Light/Point/PointLightShadowCullingSystem.cpp @@ -26,6 +26,9 @@ #include #include +#include "Engine/Component/Core/TagComponent.h" +#include "Engine/System/Core/TagSystem.h" + namespace Syn { constexpr bool ENABLE_DEBUG_LOGGING = false; @@ -57,6 +60,7 @@ namespace Syn TypeInfo::ID, TypeInfo::ID, TypeInfo::ID, + TypeInfo::ID }; } @@ -98,6 +102,7 @@ namespace Syn auto overridePool = registry->GetPool(); auto shadowPool = registry->GetPool(); auto lightPool = registry->GetPool(); + auto tagPool = registry->GetPool(); EntityID cameraEntity = scene->GetSceneCameraEntity(); if (!modelPool || !transformPool || !cameraPool || cameraEntity == NULL_ENTITY || !shadowPool || !lightPool) return; @@ -117,8 +122,15 @@ namespace Syn auto matTypeSnapshot = materialManager->GetRenderTypeSnapshot(); // Extract Entity Data (Runs once per entity) - auto withEntityData = [modelPool, transformPool, animPool, overridePool, modelSnapshot, animSnapshot, drawData] + auto withEntityData = [modelPool, transformPool, animPool, overridePool, modelSnapshot, tagPool, animSnapshot, drawData] (EntityID entity, auto&& nextFunc) { + if (tagPool && tagPool->Has(entity)) { + const auto& tag = tagPool->Get(entity); + if (!tag.globalEnabled || !tag.castShadow) { + return; + } + } + if (!modelPool->Has(entity)) return; const auto& modelComp = modelPool->Get(entity); diff --git a/SynapseEngine/Engine/System/Light/Spot/SpotLightCullingSystem.cpp b/SynapseEngine/Engine/System/Light/Spot/SpotLightCullingSystem.cpp index 5ae563ed..4c01b2ed 100644 --- a/SynapseEngine/Engine/System/Light/Spot/SpotLightCullingSystem.cpp +++ b/SynapseEngine/Engine/System/Light/Spot/SpotLightCullingSystem.cpp @@ -8,12 +8,16 @@ #include "Engine/Collision/Tester/CollisionTester.h" #include +#include "Engine/Component/Core/TagComponent.h" +#include "Engine/System/Core/TagSystem.h" + namespace Syn { std::vector SpotLightCullingSystem::GetReadDependencies() const { return { TypeInfo::ID, - TypeInfo::ID + TypeInfo::ID, + TypeInfo::ID }; } @@ -31,6 +35,7 @@ namespace Syn auto pool = registry->GetPool(); auto shadowPool = registry->GetPool(); auto cameraPool = registry->GetPool(); + auto tagPool = registry->GetPool(); EntityID cameraEntity = scene->GetSceneCameraEntity(); if (!pool || !cameraPool || cameraEntity == NULL_ENTITY) return; @@ -56,7 +61,13 @@ namespace Syn glm::vec2 screenRes = glm::vec2(cameraComp.width, cameraComp.height); - auto cullFunc = [this, settings, pool, cameraComp, drawData, screenRes, shadowPool](EntityID entity) { + auto cullFunc = [this, settings, pool, cameraComp, drawData, screenRes, shadowPool, tagPool](EntityID entity) { + if (tagPool && tagPool->Has(entity)) { + if (!tagPool->Get(entity).globalEnabled) { + return; + } + } + const auto& lightComp = pool->Get(entity); bool visibility = true; diff --git a/SynapseEngine/Engine/System/Light/Spot/SpotLightShadowCullingSystem.cpp b/SynapseEngine/Engine/System/Light/Spot/SpotLightShadowCullingSystem.cpp index d4d3c16d..2c31fd62 100644 --- a/SynapseEngine/Engine/System/Light/Spot/SpotLightShadowCullingSystem.cpp +++ b/SynapseEngine/Engine/System/Light/Spot/SpotLightShadowCullingSystem.cpp @@ -26,6 +26,9 @@ #include #include +#include "Engine/Component/Core/TagComponent.h" +#include "Engine/System/Core/TagSystem.h" + namespace Syn { constexpr bool ENABLE_DEBUG_LOGGING = false; @@ -65,6 +68,7 @@ namespace Syn TypeInfo::ID, TypeInfo::ID, TypeInfo::ID, + TypeInfo::ID }; } @@ -106,6 +110,7 @@ namespace Syn auto overridePool = registry->GetPool(); auto shadowPool = registry->GetPool(); auto spotLightPool = registry->GetPool(); + auto tagPool = registry->GetPool(); EntityID cameraEntity = scene->GetSceneCameraEntity(); if (!modelPool || !transformPool || !cameraPool || cameraEntity == NULL_ENTITY || !shadowPool || !spotLightPool) return; @@ -125,8 +130,15 @@ namespace Syn auto matTypeSnapshot = materialManager->GetRenderTypeSnapshot(); // Extract Entity Data (Runs once per entity) - auto withEntityData = [modelPool, transformPool, animPool, overridePool, modelSnapshot, animSnapshot, drawData] + auto withEntityData = [modelPool, transformPool, animPool, overridePool, modelSnapshot, tagPool, animSnapshot, drawData] (EntityID entity, auto&& nextFunc) { + if (tagPool && tagPool->Has(entity)) { + const auto& tag = tagPool->Get(entity); + if (!tag.globalEnabled || !tag.castShadow) { + return; + } + } + if (!modelPool->Has(entity)) return; const auto& modelComp = modelPool->Get(entity); diff --git a/SynapseEngine/Engine/System/Rendering/ModelFrustumCullingSystem.cpp b/SynapseEngine/Engine/System/Rendering/ModelFrustumCullingSystem.cpp index 5ad0452a..353734aa 100644 --- a/SynapseEngine/Engine/System/Rendering/ModelFrustumCullingSystem.cpp +++ b/SynapseEngine/Engine/System/Rendering/ModelFrustumCullingSystem.cpp @@ -11,6 +11,7 @@ #include "Engine/System/Rendering/AnimationSystem.h" #include "Engine/System/Rendering/MaterialSystem.h" #include "Engine/System/Core/StaticSpatialSahSystem.h" +#include "Engine/System/Core/TagSystem.h" #include "Engine/Material/MaterialManager.h" #include "Engine/Component/Rendering/MaterialOverrideComponent.h" @@ -31,7 +32,8 @@ namespace Syn TypeInfo::ID, TypeInfo::ID, TypeInfo::ID, - TypeInfo::ID + TypeInfo::ID, + TypeInfo::ID }; } @@ -69,6 +71,7 @@ namespace Syn auto transformPool = registry->GetPool(); auto cameraPool = registry->GetPool(); auto animPool = registry->GetPool(); + auto tagPool = registry->GetPool(); EntityID cameraEntity = scene->GetSceneCameraEntity(); if (!modelPool || !transformPool || !cameraPool || cameraEntity == NULL_ENTITY) return; @@ -79,8 +82,14 @@ namespace Syn glm::vec2 screenRes = glm::vec2(cameraComp.width, cameraComp.height); - auto cullFunc = [settings, drawData, modelPool, transformPool, modelSnapshot, cameraComp, animPool, animSnapshot, matTypeSnapshot, overridePool, screenRes] + auto cullFunc = [settings, drawData, modelPool, transformPool, modelSnapshot, cameraComp, animPool, tagPool, animSnapshot, matTypeSnapshot, overridePool, screenRes] (EntityID entity, IntersectionType chunkVisibility) { + if (tagPool && tagPool->Has(entity)) { + if (!tagPool->Get(entity).globalEnabled) { + return; + } + } + const FrustumCollider& frustum = cameraComp.frustum; const auto& transformComp = transformPool->Get(entity); From dcbf6eb8ff0ff24712bf98c2e78625a2143e60ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20P=C3=A9ter?= Date: Tue, 30 Jun 2026 09:45:33 +0200 Subject: [PATCH 13/51] Implemented camera, box, sphere, capsule, convex, mesh collider mvi, and rigid body mvi --- .../Editor/EditorApi/EditorContext.cpp | 14 ++ .../Editor/EditorApi/EditorContext.h | 21 +++ .../EditorApi/Impl/BoxColliderApiImpl.cpp | 26 ++++ .../EditorApi/Impl/BoxColliderApiImpl.h | 21 +++ .../Editor/EditorApi/Impl/CameraApiImpl.cpp | 60 +++++++++ .../Editor/EditorApi/Impl/CameraApiImpl.h | 33 +++++ .../EditorApi/Impl/CapsuleColliderApiImpl.cpp | 34 +++++ .../EditorApi/Impl/CapsuleColliderApiImpl.h | 23 ++++ .../EditorApi/Impl/ConvexColliderApiImpl.cpp | 26 ++++ .../EditorApi/Impl/ConvexColliderApiImpl.h | 21 +++ .../EditorApi/Impl/MeshColliderApiImpl.cpp | 26 ++++ .../EditorApi/Impl/MeshColliderApiImpl.h | 21 +++ .../EditorApi/Impl/RigidBodyApiImpl.cpp | 50 +++++++ .../Editor/EditorApi/Impl/RigidBodyApiImpl.h | 27 ++++ .../EditorApi/Impl/SphereColliderApiImpl.cpp | 26 ++++ .../EditorApi/Impl/SphereColliderApiImpl.h | 21 +++ SynapseEngine/Editor/Manager/EditorIcons.h | 1 + .../Editor/View/Component/ComponentView.cpp | 19 +-- .../Editor/View/Component/ComponentView.h | 15 +++ .../Editor/View/Component/Core/CameraView.cpp | 61 +++++++++ .../Editor/View/Component/Core/CameraView.h | 12 ++ .../Component/Physics/BoxColliderView.cpp | 33 +++++ .../View/Component/Physics/BoxColliderView.h | 12 ++ .../Component/Physics/CapsuleColliderView.cpp | 37 +++++ .../Component/Physics/CapsuleColliderView.h | 12 ++ .../Component/Physics/ConvexColliderView.cpp | 37 +++++ .../Component/Physics/ConvexColliderView.h | 12 ++ .../Component/Physics/MeshColliderView.cpp | 37 +++++ .../View/Component/Physics/MeshColliderView.h | 12 ++ .../View/Component/Physics/RigidBodyView.cpp | 64 +++++++++ .../View/Component/Physics/RigidBodyView.h | 12 ++ .../Component/Physics/SphereColliderView.cpp | 33 +++++ .../Component/Physics/SphereColliderView.h | 12 ++ SynapseEngine/Editor/Widgets/PropertyGrid.cpp | 42 ++++-- .../Editor/Workspace/SceneWorkspace.cpp | 17 ++- .../EditorCore/Api/IBoxColliderApi.h | 18 +++ SynapseEngine/EditorCore/Api/ICameraApi.h | 29 ++++ .../EditorCore/Api/ICapsuleColliderApi.h | 20 +++ .../EditorCore/Api/IConvexColliderApi.h | 19 +++ .../EditorCore/Api/IMeshColliderApi.h | 19 +++ SynapseEngine/EditorCore/Api/IRigidBodyApi.h | 25 ++++ .../EditorCore/Api/ISphereColliderApi.h | 18 +++ .../Component/ComponentViewModel.cpp | 47 ++++++- .../ViewModels/Component/ComponentViewModel.h | 41 +++++- .../Component/Core/Camera/CameraCommands.cpp | 1 + .../Component/Core/Camera/CameraCommands.h | 15 +++ .../Component/Core/Camera/CameraIntent.cpp | 1 + .../Component/Core/Camera/CameraIntent.h | 24 ++++ .../Component/Core/Camera/CameraState.cpp | 1 + .../Component/Core/Camera/CameraState.h | 16 +++ .../Component/Core/Camera/CameraViewModel.cpp | 127 ++++++++++++++++++ .../Component/Core/Camera/CameraViewModel.h | 44 ++++++ .../BoxCollider/BoxColliderCommands.cpp | 1 + .../Physics/BoxCollider/BoxColliderCommands.h | 10 ++ .../Physics/BoxCollider/BoxColliderIntent.cpp | 1 + .../Physics/BoxCollider/BoxColliderIntent.h | 20 +++ .../Physics/BoxCollider/BoxColliderState.cpp | 1 + .../Physics/BoxCollider/BoxColliderState.h | 11 ++ .../BoxCollider/BoxColliderViewModel.cpp | 65 +++++++++ .../BoxCollider/BoxColliderViewModel.h | 32 +++++ .../CapsuleColliderCommands.cpp | 1 + .../CapsuleCollider/CapsuleColliderCommands.h | 11 ++ .../CapsuleCollider/CapsuleColliderIntent.cpp | 1 + .../CapsuleCollider/CapsuleColliderIntent.h | 26 ++++ .../CapsuleCollider/CapsuleColliderState.cpp | 1 + .../CapsuleCollider/CapsuleColliderState.h | 12 ++ .../CapsuleColliderViewModel.cpp | 78 +++++++++++ .../CapsuleColliderViewModel.h | 34 +++++ .../ConvexCollider/ConvexColliderCommands.cpp | 1 + .../ConvexCollider/ConvexColliderCommands.h | 10 ++ .../ConvexCollider/ConvexColliderIntent.cpp | 1 + .../ConvexCollider/ConvexColliderIntent.h | 20 +++ .../ConvexCollider/ConvexColliderState.cpp | 1 + .../ConvexCollider/ConvexColliderState.h | 12 ++ .../ConvexColliderViewModel.cpp | 64 +++++++++ .../ConvexCollider/ConvexColliderViewModel.h | 31 +++++ .../MeshCollider/MeshColliderCommands.cpp | 1 + .../MeshCollider/MeshColliderCommands.h | 10 ++ .../MeshCollider/MeshColliderIntent.cpp | 1 + .../Physics/MeshCollider/MeshColliderIntent.h | 20 +++ .../MeshCollider/MeshColliderState.cpp | 1 + .../Physics/MeshCollider/MeshColliderState.h | 12 ++ .../MeshCollider/MeshColliderViewModel.cpp | 64 +++++++++ .../MeshCollider/MeshColliderViewModel.h | 31 +++++ .../Physics/RigidBody/RigidBodyCommands.cpp | 1 + .../Physics/RigidBody/RigidBodyCommands.h | 10 ++ .../Physics/RigidBody/RigidBodyIntent.cpp | 1 + .../Physics/RigidBody/RigidBodyIntent.h | 37 +++++ .../Physics/RigidBody/RigidBodyState.cpp | 1 + .../Physics/RigidBody/RigidBodyState.h | 15 +++ .../Physics/RigidBody/RigidBodyViewModel.cpp | 101 ++++++++++++++ .../Physics/RigidBody/RigidBodyViewModel.h | 36 +++++ .../SphereCollider/SphereColliderCommands.cpp | 1 + .../SphereCollider/SphereColliderCommands.h | 10 ++ .../SphereCollider/SphereColliderIntent.cpp | 1 + .../SphereCollider/SphereColliderIntent.h | 20 +++ .../SphereCollider/SphereColliderState.cpp | 1 + .../SphereCollider/SphereColliderState.h | 11 ++ .../SphereColliderViewModel.cpp | 65 +++++++++ .../SphereCollider/SphereColliderViewModel.h | 32 +++++ .../DrawData/DirectionLightShadowDrawGroup.h | 4 +- 101 files changed, 2300 insertions(+), 27 deletions(-) create mode 100644 SynapseEngine/Editor/EditorApi/Impl/BoxColliderApiImpl.cpp create mode 100644 SynapseEngine/Editor/EditorApi/Impl/BoxColliderApiImpl.h create mode 100644 SynapseEngine/Editor/EditorApi/Impl/CameraApiImpl.cpp create mode 100644 SynapseEngine/Editor/EditorApi/Impl/CameraApiImpl.h create mode 100644 SynapseEngine/Editor/EditorApi/Impl/CapsuleColliderApiImpl.cpp create mode 100644 SynapseEngine/Editor/EditorApi/Impl/CapsuleColliderApiImpl.h create mode 100644 SynapseEngine/Editor/EditorApi/Impl/ConvexColliderApiImpl.cpp create mode 100644 SynapseEngine/Editor/EditorApi/Impl/ConvexColliderApiImpl.h create mode 100644 SynapseEngine/Editor/EditorApi/Impl/MeshColliderApiImpl.cpp create mode 100644 SynapseEngine/Editor/EditorApi/Impl/MeshColliderApiImpl.h create mode 100644 SynapseEngine/Editor/EditorApi/Impl/RigidBodyApiImpl.cpp create mode 100644 SynapseEngine/Editor/EditorApi/Impl/RigidBodyApiImpl.h create mode 100644 SynapseEngine/Editor/EditorApi/Impl/SphereColliderApiImpl.cpp create mode 100644 SynapseEngine/Editor/EditorApi/Impl/SphereColliderApiImpl.h create mode 100644 SynapseEngine/Editor/View/Component/Core/CameraView.cpp create mode 100644 SynapseEngine/Editor/View/Component/Core/CameraView.h create mode 100644 SynapseEngine/Editor/View/Component/Physics/BoxColliderView.cpp create mode 100644 SynapseEngine/Editor/View/Component/Physics/BoxColliderView.h create mode 100644 SynapseEngine/Editor/View/Component/Physics/CapsuleColliderView.cpp create mode 100644 SynapseEngine/Editor/View/Component/Physics/CapsuleColliderView.h create mode 100644 SynapseEngine/Editor/View/Component/Physics/ConvexColliderView.cpp create mode 100644 SynapseEngine/Editor/View/Component/Physics/ConvexColliderView.h create mode 100644 SynapseEngine/Editor/View/Component/Physics/MeshColliderView.cpp create mode 100644 SynapseEngine/Editor/View/Component/Physics/MeshColliderView.h create mode 100644 SynapseEngine/Editor/View/Component/Physics/RigidBodyView.cpp create mode 100644 SynapseEngine/Editor/View/Component/Physics/RigidBodyView.h create mode 100644 SynapseEngine/Editor/View/Component/Physics/SphereColliderView.cpp create mode 100644 SynapseEngine/Editor/View/Component/Physics/SphereColliderView.h create mode 100644 SynapseEngine/EditorCore/Api/IBoxColliderApi.h create mode 100644 SynapseEngine/EditorCore/Api/ICameraApi.h create mode 100644 SynapseEngine/EditorCore/Api/ICapsuleColliderApi.h create mode 100644 SynapseEngine/EditorCore/Api/IConvexColliderApi.h create mode 100644 SynapseEngine/EditorCore/Api/IMeshColliderApi.h create mode 100644 SynapseEngine/EditorCore/Api/IRigidBodyApi.h create mode 100644 SynapseEngine/EditorCore/Api/ISphereColliderApi.h create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraCommands.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraCommands.h create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraIntent.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraIntent.h create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraState.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraState.h create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraViewModel.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraViewModel.h create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Physics/BoxCollider/BoxColliderCommands.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Physics/BoxCollider/BoxColliderCommands.h create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Physics/BoxCollider/BoxColliderIntent.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Physics/BoxCollider/BoxColliderIntent.h create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Physics/BoxCollider/BoxColliderState.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Physics/BoxCollider/BoxColliderState.h create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Physics/BoxCollider/BoxColliderViewModel.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Physics/BoxCollider/BoxColliderViewModel.h create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Physics/CapsuleCollider/CapsuleColliderCommands.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Physics/CapsuleCollider/CapsuleColliderCommands.h create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Physics/CapsuleCollider/CapsuleColliderIntent.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Physics/CapsuleCollider/CapsuleColliderIntent.h create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Physics/CapsuleCollider/CapsuleColliderState.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Physics/CapsuleCollider/CapsuleColliderState.h create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Physics/CapsuleCollider/CapsuleColliderViewModel.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Physics/CapsuleCollider/CapsuleColliderViewModel.h create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Physics/ConvexCollider/ConvexColliderCommands.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Physics/ConvexCollider/ConvexColliderCommands.h create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Physics/ConvexCollider/ConvexColliderIntent.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Physics/ConvexCollider/ConvexColliderIntent.h create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Physics/ConvexCollider/ConvexColliderState.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Physics/ConvexCollider/ConvexColliderState.h create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Physics/ConvexCollider/ConvexColliderViewModel.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Physics/ConvexCollider/ConvexColliderViewModel.h create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Physics/MeshCollider/MeshColliderCommands.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Physics/MeshCollider/MeshColliderCommands.h create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Physics/MeshCollider/MeshColliderIntent.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Physics/MeshCollider/MeshColliderIntent.h create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Physics/MeshCollider/MeshColliderState.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Physics/MeshCollider/MeshColliderState.h create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Physics/MeshCollider/MeshColliderViewModel.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Physics/MeshCollider/MeshColliderViewModel.h create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Physics/RigidBody/RigidBodyCommands.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Physics/RigidBody/RigidBodyCommands.h create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Physics/RigidBody/RigidBodyIntent.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Physics/RigidBody/RigidBodyIntent.h create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Physics/RigidBody/RigidBodyState.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Physics/RigidBody/RigidBodyState.h create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Physics/RigidBody/RigidBodyViewModel.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Physics/RigidBody/RigidBodyViewModel.h create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Physics/SphereCollider/SphereColliderCommands.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Physics/SphereCollider/SphereColliderCommands.h create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Physics/SphereCollider/SphereColliderIntent.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Physics/SphereCollider/SphereColliderIntent.h create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Physics/SphereCollider/SphereColliderState.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Physics/SphereCollider/SphereColliderState.h create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Physics/SphereCollider/SphereColliderViewModel.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Physics/SphereCollider/SphereColliderViewModel.h diff --git a/SynapseEngine/Editor/EditorApi/EditorContext.cpp b/SynapseEngine/Editor/EditorApi/EditorContext.cpp index f46c22c7..a93e945a 100644 --- a/SynapseEngine/Editor/EditorApi/EditorContext.cpp +++ b/SynapseEngine/Editor/EditorApi/EditorContext.cpp @@ -14,6 +14,13 @@ #include "Impl/SpotLightApiImpl.h" #include "Impl/TextureApiImpl.h" #include "Impl/ModelApiImpl.h" +#include "Impl/CameraApiImpl.h" +#include "Impl/BoxColliderApiImpl.h" +#include "Impl/SphereColliderApiImpl.h" +#include "Impl/CapsuleColliderApiImpl.h" +#include "Impl/ConvexColliderApiImpl.h" +#include "Impl/MeshColliderApiImpl.h" +#include "Impl/RigidBodyApiImpl.h" namespace Syn { EditorContext::EditorContext(Engine* engine, GuiTextureManager* textureManager) { @@ -34,6 +41,13 @@ namespace Syn { _spotLightApi = std::make_unique(sm); _textureApi = std::make_unique(engine->GetImageManager(), textureManager); _modelApi = std::make_unique(engine->GetModelManager()); + _cameraApi = std::make_unique(sm); + _boxColliderApi = std::make_unique(sm); + _sphereColliderApi = std::make_unique(sm); + _capsuleColliderApi = std::make_unique(sm); + _convexColliderApi = std::make_unique(sm); + _meshColliderApi = std::make_unique(sm); + _rigidBodyApi = std::make_unique(sm); } EditorContext::~EditorContext() = default; diff --git a/SynapseEngine/Editor/EditorApi/EditorContext.h b/SynapseEngine/Editor/EditorApi/EditorContext.h index daa3ff14..90d5fc6e 100644 --- a/SynapseEngine/Editor/EditorApi/EditorContext.h +++ b/SynapseEngine/Editor/EditorApi/EditorContext.h @@ -18,6 +18,13 @@ #include "EditorCore/Api/ISpotLightApi.h" #include "EditorCore/Api/ITextureApi.h" #include "EditorCore/Api/IModelApi.h" +#include "EditorCore/Api/ICameraApi.h" +#include "EditorCore/Api/IBoxColliderApi.h" +#include "EditorCore/Api/ISphereColliderApi.h" +#include "EditorCore/Api/ICapsuleColliderApi.h" +#include "EditorCore/Api/IConvexColliderApi.h" +#include "EditorCore/Api/IMeshColliderApi.h" +#include "EditorCore/Api/IRigidBodyApi.h" namespace Syn { class EditorContext { @@ -40,6 +47,13 @@ namespace Syn { ISpotLightApi* GetSpotLightApi() const { return _spotLightApi.get(); } ITextureApi* GetTextureApi() const { return _textureApi.get(); } IModelApi* GetModelApi() const { return _modelApi.get(); } + ICameraApi* GetCameraApi() const { return _cameraApi.get(); } + IBoxColliderApi* GetBoxColliderApi() const { return _boxColliderApi.get(); } + ISphereColliderApi* GetSphereColliderApi() const { return _sphereColliderApi.get(); } + ICapsuleColliderApi* GetCapsuleColliderApi() const { return _capsuleColliderApi.get(); } + IConvexColliderApi* GetConvexColliderApi() const { return _convexColliderApi.get(); } + IMeshColliderApi* GetMeshColliderApi() const { return _meshColliderApi.get(); } + IRigidBodyApi* GetRigidBodyApi() const { return _rigidBodyApi.get(); } private: std::unique_ptr _selectionApi; std::unique_ptr _tagApi; @@ -56,5 +70,12 @@ namespace Syn { std::unique_ptr _spotLightApi; std::unique_ptr _textureApi; std::unique_ptr _modelApi; + std::unique_ptr _cameraApi; + std::unique_ptr _boxColliderApi; + std::unique_ptr _sphereColliderApi; + std::unique_ptr _capsuleColliderApi; + std::unique_ptr _convexColliderApi; + std::unique_ptr _meshColliderApi; + std::unique_ptr _rigidBodyApi; }; } \ No newline at end of file diff --git a/SynapseEngine/Editor/EditorApi/Impl/BoxColliderApiImpl.cpp b/SynapseEngine/Editor/EditorApi/Impl/BoxColliderApiImpl.cpp new file mode 100644 index 00000000..d1e98c0b --- /dev/null +++ b/SynapseEngine/Editor/EditorApi/Impl/BoxColliderApiImpl.cpp @@ -0,0 +1,26 @@ +#include "BoxColliderApiImpl.h" +#include "../EditorApiUtils.h" +#include "Engine/Component/Physics/BoxColliderComponent.h" + +namespace Syn { + + bool BoxColliderApiImpl::HasBoxCollider(EntityID entity) const { + return EditorApiUtils::HasComponent(_sceneManager, entity); + } + + glm::vec3 BoxColliderApiImpl::GetBoxColliderHalfExtents(EntityID entity) const { + return EditorApiUtils::ReadComponent(_sceneManager, entity, [](const auto& c) { return c.halfExtents; }, glm::vec3(0.5f)); + } + + glm::vec3 BoxColliderApiImpl::GetBoxColliderLocalOffset(EntityID entity) const { + return EditorApiUtils::ReadComponent(_sceneManager, entity, [](const auto& c) { return c.localOffset; }, glm::vec3(0.0f)); + } + + void BoxColliderApiImpl::SetBoxColliderHalfExtents(EntityID entity, const glm::vec3& halfExtents) { + EditorApiUtils::ModifyComponent(_sceneManager, entity, [&](auto& c, auto pool) { c.halfExtents = halfExtents; }); + } + + void BoxColliderApiImpl::SetBoxColliderLocalOffset(EntityID entity, const glm::vec3& localOffset) { + EditorApiUtils::ModifyComponent(_sceneManager, entity, [&](auto& c, auto pool) { c.localOffset = localOffset; }); + } +} \ No newline at end of file diff --git a/SynapseEngine/Editor/EditorApi/Impl/BoxColliderApiImpl.h b/SynapseEngine/Editor/EditorApi/Impl/BoxColliderApiImpl.h new file mode 100644 index 00000000..af40e0bb --- /dev/null +++ b/SynapseEngine/Editor/EditorApi/Impl/BoxColliderApiImpl.h @@ -0,0 +1,21 @@ +#pragma once +#include "EditorCore/Api/IBoxColliderApi.h" +#include "Engine/Scene/SceneManager.h" + +namespace Syn { + class BoxColliderApiImpl : public IBoxColliderApi { + public: + BoxColliderApiImpl(SceneManager* sm) : _sceneManager(sm) {} + + bool HasBoxCollider(EntityID entity) const override; + + glm::vec3 GetBoxColliderHalfExtents(EntityID entity) const override; + glm::vec3 GetBoxColliderLocalOffset(EntityID entity) const override; + + void SetBoxColliderHalfExtents(EntityID entity, const glm::vec3& halfExtents) override; + void SetBoxColliderLocalOffset(EntityID entity, const glm::vec3& localOffset) override; + + private: + SceneManager* _sceneManager; + }; +} \ No newline at end of file diff --git a/SynapseEngine/Editor/EditorApi/Impl/CameraApiImpl.cpp b/SynapseEngine/Editor/EditorApi/Impl/CameraApiImpl.cpp new file mode 100644 index 00000000..ae91c45e --- /dev/null +++ b/SynapseEngine/Editor/EditorApi/Impl/CameraApiImpl.cpp @@ -0,0 +1,60 @@ +#include "CameraApiImpl.h" +#include "../EditorApiUtils.h" +#include "Engine/Component/Core/CameraComponent.h" + +namespace Syn { + + bool CameraApiImpl::HasCamera(EntityID entity) const { + return EditorApiUtils::HasComponent(_sceneManager, entity); + } + + float CameraApiImpl::GetCameraYaw(EntityID entity) const { + return EditorApiUtils::ReadComponent(_sceneManager, entity, [](const auto& c) { return c.yaw; }, 0.0f); + } + float CameraApiImpl::GetCameraPitch(EntityID entity) const { + return EditorApiUtils::ReadComponent(_sceneManager, entity, [](const auto& c) { return c.pitch; }, 0.0f); + } + float CameraApiImpl::GetCameraNearPlane(EntityID entity) const { + return EditorApiUtils::ReadComponent(_sceneManager, entity, [](const auto& c) { return c.nearPlane; }, 0.1f); + } + float CameraApiImpl::GetCameraFarPlane(EntityID entity) const { + return EditorApiUtils::ReadComponent(_sceneManager, entity, [](const auto& c) { return c.farPlane; }, 1000.0f); + } + float CameraApiImpl::GetCameraFov(EntityID entity) const { + return EditorApiUtils::ReadComponent(_sceneManager, entity, [](const auto& c) { return c.fov; }, 45.0f); + } + float CameraApiImpl::GetCameraSpeed(EntityID entity) const { + return EditorApiUtils::ReadComponent(_sceneManager, entity, [](const auto& c) { return c.speed; }, 1.0f); + } + float CameraApiImpl::GetCameraSensitivity(EntityID entity) const { + return EditorApiUtils::ReadComponent(_sceneManager, entity, [](const auto& c) { return c.sensitivity; }, 0.1f); + } + float CameraApiImpl::GetCameraDistance(EntityID entity) const { + return EditorApiUtils::ReadComponent(_sceneManager, entity, [](const auto& c) { return c.distance; }, 10.0f); + } + + void CameraApiImpl::SetCameraYaw(EntityID entity, float yaw) { + EditorApiUtils::ModifyComponent(_sceneManager, entity, [&](auto& c, auto pool) { c.yaw = yaw; }); + } + void CameraApiImpl::SetCameraPitch(EntityID entity, float pitch) { + EditorApiUtils::ModifyComponent(_sceneManager, entity, [&](auto& c, auto pool) { c.pitch = pitch; }); + } + void CameraApiImpl::SetCameraNearPlane(EntityID entity, float nearPlane) { + EditorApiUtils::ModifyComponent(_sceneManager, entity, [&](auto& c, auto pool) { c.nearPlane = nearPlane; }); + } + void CameraApiImpl::SetCameraFarPlane(EntityID entity, float farPlane) { + EditorApiUtils::ModifyComponent(_sceneManager, entity, [&](auto& c, auto pool) { c.farPlane = farPlane; }); + } + void CameraApiImpl::SetCameraFov(EntityID entity, float fov) { + EditorApiUtils::ModifyComponent(_sceneManager, entity, [&](auto& c, auto pool) { c.fov = fov; }); + } + void CameraApiImpl::SetCameraSpeed(EntityID entity, float speed) { + EditorApiUtils::ModifyComponent(_sceneManager, entity, [&](auto& c, auto pool) { c.speed = speed; }); + } + void CameraApiImpl::SetCameraSensitivity(EntityID entity, float sensitivity) { + EditorApiUtils::ModifyComponent(_sceneManager, entity, [&](auto& c, auto pool) { c.sensitivity = sensitivity; }); + } + void CameraApiImpl::SetCameraDistance(EntityID entity, float distance) { + EditorApiUtils::ModifyComponent(_sceneManager, entity, [&](auto& c, auto pool) { c.distance = distance; }); + } +} \ No newline at end of file diff --git a/SynapseEngine/Editor/EditorApi/Impl/CameraApiImpl.h b/SynapseEngine/Editor/EditorApi/Impl/CameraApiImpl.h new file mode 100644 index 00000000..4d5f24b3 --- /dev/null +++ b/SynapseEngine/Editor/EditorApi/Impl/CameraApiImpl.h @@ -0,0 +1,33 @@ +#pragma once +#include "EditorCore/Api/ICameraApi.h" +#include "Engine/Scene/SceneManager.h" + +namespace Syn { + class CameraApiImpl : public ICameraApi { + public: + CameraApiImpl(SceneManager* sm) : _sceneManager(sm) {} + + bool HasCamera(EntityID entity) const override; + + float GetCameraYaw(EntityID entity) const override; + float GetCameraPitch(EntityID entity) const override; + float GetCameraNearPlane(EntityID entity) const override; + float GetCameraFarPlane(EntityID entity) const override; + float GetCameraFov(EntityID entity) const override; + float GetCameraSpeed(EntityID entity) const override; + float GetCameraSensitivity(EntityID entity) const override; + float GetCameraDistance(EntityID entity) const override; + + void SetCameraYaw(EntityID entity, float yaw) override; + void SetCameraPitch(EntityID entity, float pitch) override; + void SetCameraNearPlane(EntityID entity, float nearPlane) override; + void SetCameraFarPlane(EntityID entity, float farPlane) override; + void SetCameraFov(EntityID entity, float fov) override; + void SetCameraSpeed(EntityID entity, float speed) override; + void SetCameraSensitivity(EntityID entity, float sensitivity) override; + void SetCameraDistance(EntityID entity, float distance) override; + + private: + SceneManager* _sceneManager; + }; +} \ No newline at end of file diff --git a/SynapseEngine/Editor/EditorApi/Impl/CapsuleColliderApiImpl.cpp b/SynapseEngine/Editor/EditorApi/Impl/CapsuleColliderApiImpl.cpp new file mode 100644 index 00000000..ea90897d --- /dev/null +++ b/SynapseEngine/Editor/EditorApi/Impl/CapsuleColliderApiImpl.cpp @@ -0,0 +1,34 @@ +#include "CapsuleColliderApiImpl.h" +#include "../EditorApiUtils.h" +#include "Engine/Component/Physics/CapsuleColliderComponent.h" + +namespace Syn { + + bool CapsuleColliderApiImpl::HasCapsuleCollider(EntityID entity) const { + return EditorApiUtils::HasComponent(_sceneManager, entity); + } + + float CapsuleColliderApiImpl::GetCapsuleColliderRadius(EntityID entity) const { + return EditorApiUtils::ReadComponent(_sceneManager, entity, [](const auto& c) { return c.radius; }, 0.5f); + } + + float CapsuleColliderApiImpl::GetCapsuleColliderHalfHeight(EntityID entity) const { + return EditorApiUtils::ReadComponent(_sceneManager, entity, [](const auto& c) { return c.halfHeight; }, 1.0f); + } + + glm::vec3 CapsuleColliderApiImpl::GetCapsuleColliderLocalOffset(EntityID entity) const { + return EditorApiUtils::ReadComponent(_sceneManager, entity, [](const auto& c) { return c.localOffset; }, glm::vec3(0.0f)); + } + + void CapsuleColliderApiImpl::SetCapsuleColliderRadius(EntityID entity, float radius) { + EditorApiUtils::ModifyComponent(_sceneManager, entity, [&](auto& c, auto pool) { c.radius = radius; }); + } + + void CapsuleColliderApiImpl::SetCapsuleColliderHalfHeight(EntityID entity, float halfHeight) { + EditorApiUtils::ModifyComponent(_sceneManager, entity, [&](auto& c, auto pool) { c.halfHeight = halfHeight; }); + } + + void CapsuleColliderApiImpl::SetCapsuleColliderLocalOffset(EntityID entity, const glm::vec3& localOffset) { + EditorApiUtils::ModifyComponent(_sceneManager, entity, [&](auto& c, auto pool) { c.localOffset = localOffset; }); + } +} \ No newline at end of file diff --git a/SynapseEngine/Editor/EditorApi/Impl/CapsuleColliderApiImpl.h b/SynapseEngine/Editor/EditorApi/Impl/CapsuleColliderApiImpl.h new file mode 100644 index 00000000..82e3df2b --- /dev/null +++ b/SynapseEngine/Editor/EditorApi/Impl/CapsuleColliderApiImpl.h @@ -0,0 +1,23 @@ +#pragma once +#include "EditorCore/Api/ICapsuleColliderApi.h" +#include "Engine/Scene/SceneManager.h" + +namespace Syn { + class CapsuleColliderApiImpl : public ICapsuleColliderApi { + public: + CapsuleColliderApiImpl(SceneManager* sm) : _sceneManager(sm) {} + + bool HasCapsuleCollider(EntityID entity) const override; + + float GetCapsuleColliderRadius(EntityID entity) const override; + float GetCapsuleColliderHalfHeight(EntityID entity) const override; + glm::vec3 GetCapsuleColliderLocalOffset(EntityID entity) const override; + + void SetCapsuleColliderRadius(EntityID entity, float radius) override; + void SetCapsuleColliderHalfHeight(EntityID entity, float halfHeight) override; + void SetCapsuleColliderLocalOffset(EntityID entity, const glm::vec3& localOffset) override; + + private: + SceneManager* _sceneManager; + }; +} \ No newline at end of file diff --git a/SynapseEngine/Editor/EditorApi/Impl/ConvexColliderApiImpl.cpp b/SynapseEngine/Editor/EditorApi/Impl/ConvexColliderApiImpl.cpp new file mode 100644 index 00000000..083b31fa --- /dev/null +++ b/SynapseEngine/Editor/EditorApi/Impl/ConvexColliderApiImpl.cpp @@ -0,0 +1,26 @@ +#include "ConvexColliderApiImpl.h" +#include "../EditorApiUtils.h" +#include "Engine/Component/Physics/ConvexColliderComponent.h" + +namespace Syn { + + bool ConvexColliderApiImpl::HasConvexCollider(EntityID entity) const { + return EditorApiUtils::HasComponent(_sceneManager, entity); + } + + uint32_t ConvexColliderApiImpl::GetConvexColliderTargetLodLevel(EntityID entity) const { + return EditorApiUtils::ReadComponent(_sceneManager, entity, [](const auto& c) { return c.targetLodLevel; }, 0u); + } + + glm::vec3 ConvexColliderApiImpl::GetConvexColliderLocalOffset(EntityID entity) const { + return EditorApiUtils::ReadComponent(_sceneManager, entity, [](const auto& c) { return c.localOffset; }, glm::vec3(0.0f)); + } + + void ConvexColliderApiImpl::SetConvexColliderTargetLodLevel(EntityID entity, uint32_t targetLodLevel) { + EditorApiUtils::ModifyComponent(_sceneManager, entity, [&](auto& c, auto pool) { c.targetLodLevel = targetLodLevel; }); + } + + void ConvexColliderApiImpl::SetConvexColliderLocalOffset(EntityID entity, const glm::vec3& localOffset) { + EditorApiUtils::ModifyComponent(_sceneManager, entity, [&](auto& c, auto pool) { c.localOffset = localOffset; }); + } +} \ No newline at end of file diff --git a/SynapseEngine/Editor/EditorApi/Impl/ConvexColliderApiImpl.h b/SynapseEngine/Editor/EditorApi/Impl/ConvexColliderApiImpl.h new file mode 100644 index 00000000..b805e0fa --- /dev/null +++ b/SynapseEngine/Editor/EditorApi/Impl/ConvexColliderApiImpl.h @@ -0,0 +1,21 @@ +#pragma once +#include "EditorCore/Api/IConvexColliderApi.h" +#include "Engine/Scene/SceneManager.h" + +namespace Syn { + class ConvexColliderApiImpl : public IConvexColliderApi { + public: + ConvexColliderApiImpl(SceneManager* sm) : _sceneManager(sm) {} + + bool HasConvexCollider(EntityID entity) const override; + + uint32_t GetConvexColliderTargetLodLevel(EntityID entity) const override; + glm::vec3 GetConvexColliderLocalOffset(EntityID entity) const override; + + void SetConvexColliderTargetLodLevel(EntityID entity, uint32_t targetLodLevel) override; + void SetConvexColliderLocalOffset(EntityID entity, const glm::vec3& localOffset) override; + + private: + SceneManager* _sceneManager; + }; +} \ No newline at end of file diff --git a/SynapseEngine/Editor/EditorApi/Impl/MeshColliderApiImpl.cpp b/SynapseEngine/Editor/EditorApi/Impl/MeshColliderApiImpl.cpp new file mode 100644 index 00000000..685bf75a --- /dev/null +++ b/SynapseEngine/Editor/EditorApi/Impl/MeshColliderApiImpl.cpp @@ -0,0 +1,26 @@ +#include "MeshColliderApiImpl.h" +#include "../EditorApiUtils.h" +#include "Engine/Component/Physics/MeshColliderComponent.h" + +namespace Syn { + + bool MeshColliderApiImpl::HasMeshCollider(EntityID entity) const { + return EditorApiUtils::HasComponent(_sceneManager, entity); + } + + uint32_t MeshColliderApiImpl::GetMeshColliderTargetLodLevel(EntityID entity) const { + return EditorApiUtils::ReadComponent(_sceneManager, entity, [](const auto& c) { return c.targetLodLevel; }, 0u); + } + + glm::vec3 MeshColliderApiImpl::GetMeshColliderLocalOffset(EntityID entity) const { + return EditorApiUtils::ReadComponent(_sceneManager, entity, [](const auto& c) { return c.localOffset; }, glm::vec3(0.0f)); + } + + void MeshColliderApiImpl::SetMeshColliderTargetLodLevel(EntityID entity, uint32_t targetLodLevel) { + EditorApiUtils::ModifyComponent(_sceneManager, entity, [&](auto& c, auto pool) { c.targetLodLevel = targetLodLevel; }); + } + + void MeshColliderApiImpl::SetMeshColliderLocalOffset(EntityID entity, const glm::vec3& localOffset) { + EditorApiUtils::ModifyComponent(_sceneManager, entity, [&](auto& c, auto pool) { c.localOffset = localOffset; }); + } +} \ No newline at end of file diff --git a/SynapseEngine/Editor/EditorApi/Impl/MeshColliderApiImpl.h b/SynapseEngine/Editor/EditorApi/Impl/MeshColliderApiImpl.h new file mode 100644 index 00000000..57c9e736 --- /dev/null +++ b/SynapseEngine/Editor/EditorApi/Impl/MeshColliderApiImpl.h @@ -0,0 +1,21 @@ +#pragma once +#include "EditorCore/Api/IMeshColliderApi.h" +#include "Engine/Scene/SceneManager.h" + +namespace Syn { + class MeshColliderApiImpl : public IMeshColliderApi { + public: + MeshColliderApiImpl(SceneManager* sm) : _sceneManager(sm) {} + + bool HasMeshCollider(EntityID entity) const override; + + uint32_t GetMeshColliderTargetLodLevel(EntityID entity) const override; + glm::vec3 GetMeshColliderLocalOffset(EntityID entity) const override; + + void SetMeshColliderTargetLodLevel(EntityID entity, uint32_t targetLodLevel) override; + void SetMeshColliderLocalOffset(EntityID entity, const glm::vec3& localOffset) override; + + private: + SceneManager* _sceneManager; + }; +} \ No newline at end of file diff --git a/SynapseEngine/Editor/EditorApi/Impl/RigidBodyApiImpl.cpp b/SynapseEngine/Editor/EditorApi/Impl/RigidBodyApiImpl.cpp new file mode 100644 index 00000000..4b28df2c --- /dev/null +++ b/SynapseEngine/Editor/EditorApi/Impl/RigidBodyApiImpl.cpp @@ -0,0 +1,50 @@ +#include "RigidBodyApiImpl.h" +#include "../EditorApiUtils.h" +#include "Engine/Component/Physics/RigidBodyComponent.h" + +namespace Syn { + + bool RigidBodyApiImpl::HasRigidBody(EntityID entity) const { + return EditorApiUtils::HasComponent(_sceneManager, entity); + } + + PhysicsMotionType RigidBodyApiImpl::GetRigidBodyMotionType(EntityID entity) const { + return EditorApiUtils::ReadComponent(_sceneManager, entity, [](const auto& c) { return c.motionType; }, PhysicsMotionType::Dynamic); + } + + float RigidBodyApiImpl::GetRigidBodyMass(EntityID entity) const { + return EditorApiUtils::ReadComponent(_sceneManager, entity, [](const auto& c) { return c.mass; }, 1.0f); + } + + float RigidBodyApiImpl::GetRigidBodyFriction(EntityID entity) const { + return EditorApiUtils::ReadComponent(_sceneManager, entity, [](const auto& c) { return c.friction; }, 0.2f); + } + + float RigidBodyApiImpl::GetRigidBodyRestitution(EntityID entity) const { + return EditorApiUtils::ReadComponent(_sceneManager, entity, [](const auto& c) { return c.restitution; }, 0.0f); + } + + uint32_t RigidBodyApiImpl::GetRigidBodyLayer(EntityID entity) const { + return EditorApiUtils::ReadComponent(_sceneManager, entity, [](const auto& c) { return c.layer; }, 1u); + } + + void RigidBodyApiImpl::SetRigidBodyMotionType(EntityID entity, PhysicsMotionType motionType) { + EditorApiUtils::ModifyComponent(_sceneManager, entity, [&](auto& c, auto pool) { c.motionType = motionType; }); + } + + void RigidBodyApiImpl::SetRigidBodyMass(EntityID entity, float mass) { + EditorApiUtils::ModifyComponent(_sceneManager, entity, [&](auto& c, auto pool) { c.mass = mass; }); + } + + void RigidBodyApiImpl::SetRigidBodyFriction(EntityID entity, float friction) { + EditorApiUtils::ModifyComponent(_sceneManager, entity, [&](auto& c, auto pool) { c.friction = friction; }); + } + + void RigidBodyApiImpl::SetRigidBodyRestitution(EntityID entity, float restitution) { + EditorApiUtils::ModifyComponent(_sceneManager, entity, [&](auto& c, auto pool) { c.restitution = restitution; }); + } + + void RigidBodyApiImpl::SetRigidBodyLayer(EntityID entity, uint32_t layer) { + EditorApiUtils::ModifyComponent(_sceneManager, entity, [&](auto& c, auto pool) { c.layer = layer; }); + } +} \ No newline at end of file diff --git a/SynapseEngine/Editor/EditorApi/Impl/RigidBodyApiImpl.h b/SynapseEngine/Editor/EditorApi/Impl/RigidBodyApiImpl.h new file mode 100644 index 00000000..b85ffe1f --- /dev/null +++ b/SynapseEngine/Editor/EditorApi/Impl/RigidBodyApiImpl.h @@ -0,0 +1,27 @@ +#pragma once +#include "EditorCore/Api/IRigidBodyApi.h" +#include "Engine/Scene/SceneManager.h" + +namespace Syn { + class RigidBodyApiImpl : public IRigidBodyApi { + public: + RigidBodyApiImpl(SceneManager* sm) : _sceneManager(sm) {} + + bool HasRigidBody(EntityID entity) const override; + + PhysicsMotionType GetRigidBodyMotionType(EntityID entity) const override; + float GetRigidBodyMass(EntityID entity) const override; + float GetRigidBodyFriction(EntityID entity) const override; + float GetRigidBodyRestitution(EntityID entity) const override; + uint32_t GetRigidBodyLayer(EntityID entity) const override; + + void SetRigidBodyMotionType(EntityID entity, PhysicsMotionType motionType) override; + void SetRigidBodyMass(EntityID entity, float mass) override; + void SetRigidBodyFriction(EntityID entity, float friction) override; + void SetRigidBodyRestitution(EntityID entity, float restitution) override; + void SetRigidBodyLayer(EntityID entity, uint32_t layer) override; + + private: + SceneManager* _sceneManager; + }; +} \ No newline at end of file diff --git a/SynapseEngine/Editor/EditorApi/Impl/SphereColliderApiImpl.cpp b/SynapseEngine/Editor/EditorApi/Impl/SphereColliderApiImpl.cpp new file mode 100644 index 00000000..fd3676e7 --- /dev/null +++ b/SynapseEngine/Editor/EditorApi/Impl/SphereColliderApiImpl.cpp @@ -0,0 +1,26 @@ +#include "SphereColliderApiImpl.h" +#include "../EditorApiUtils.h" +#include "Engine/Component/Physics/SphereColliderComponent.h" + +namespace Syn { + + bool SphereColliderApiImpl::HasSphereCollider(EntityID entity) const { + return EditorApiUtils::HasComponent(_sceneManager, entity); + } + + float SphereColliderApiImpl::GetSphereColliderRadius(EntityID entity) const { + return EditorApiUtils::ReadComponent(_sceneManager, entity, [](const auto& c) { return c.radius; }, 0.5f); + } + + glm::vec3 SphereColliderApiImpl::GetSphereColliderLocalOffset(EntityID entity) const { + return EditorApiUtils::ReadComponent(_sceneManager, entity, [](const auto& c) { return c.localOffset; }, glm::vec3(0.0f)); + } + + void SphereColliderApiImpl::SetSphereColliderRadius(EntityID entity, float radius) { + EditorApiUtils::ModifyComponent(_sceneManager, entity, [&](auto& c, auto pool) { c.radius = radius; }); + } + + void SphereColliderApiImpl::SetSphereColliderLocalOffset(EntityID entity, const glm::vec3& localOffset) { + EditorApiUtils::ModifyComponent(_sceneManager, entity, [&](auto& c, auto pool) { c.localOffset = localOffset; }); + } +} \ No newline at end of file diff --git a/SynapseEngine/Editor/EditorApi/Impl/SphereColliderApiImpl.h b/SynapseEngine/Editor/EditorApi/Impl/SphereColliderApiImpl.h new file mode 100644 index 00000000..8ecd5add --- /dev/null +++ b/SynapseEngine/Editor/EditorApi/Impl/SphereColliderApiImpl.h @@ -0,0 +1,21 @@ +#pragma once +#include "EditorCore/Api/ISphereColliderApi.h" +#include "Engine/Scene/SceneManager.h" + +namespace Syn { + class SphereColliderApiImpl : public ISphereColliderApi { + public: + SphereColliderApiImpl(SceneManager* sm) : _sceneManager(sm) {} + + bool HasSphereCollider(EntityID entity) const override; + + float GetSphereColliderRadius(EntityID entity) const override; + glm::vec3 GetSphereColliderLocalOffset(EntityID entity) const override; + + void SetSphereColliderRadius(EntityID entity, float radius) override; + void SetSphereColliderLocalOffset(EntityID entity, const glm::vec3& localOffset) override; + + private: + SceneManager* _sceneManager; + }; +} \ No newline at end of file diff --git a/SynapseEngine/Editor/Manager/EditorIcons.h b/SynapseEngine/Editor/Manager/EditorIcons.h index 96b105b3..d39876a6 100644 --- a/SynapseEngine/Editor/Manager/EditorIcons.h +++ b/SynapseEngine/Editor/Manager/EditorIcons.h @@ -56,6 +56,7 @@ constexpr const char* ASSET_PATH = "Assets"; #define SYN_ICON_RUNNING ICON_FA_RUNNING #define SYN_ICON_SPOTLIGHT ICON_FA_BULLHORN #define SYN_ICON_FILTER ICON_FA_FILTER +#define SYN_ICON_CAMERA ICON_FA_CAMERA #define SYN_ICON_INFO_CIRCLE ICON_FA_INFO_CIRCLE #define SYN_ICON_TAG ICON_FA_TAG diff --git a/SynapseEngine/Editor/View/Component/ComponentView.cpp b/SynapseEngine/Editor/View/Component/ComponentView.cpp index 2beebf9d..40ee34b2 100644 --- a/SynapseEngine/Editor/View/Component/ComponentView.cpp +++ b/SynapseEngine/Editor/View/Component/ComponentView.cpp @@ -20,22 +20,25 @@ namespace Syn { return; } - //Tag + //Core _tagView.SetActiveEntity(state.activeEntityId); _tagView.Draw(vm.GetTagViewModel()); - - //Transform _transformView.Draw(vm.GetTransformViewModel()); + _cameraView.Draw(vm.GetCameraViewModel()); - //DirectionLight + //Lights _directionLightView.Draw(vm.GetDirectionLightViewModel()); - - //PointLight _pointLightView.Draw(vm.GetPointLightViewModel()); - - //SpotLight _spotLightView.Draw(vm.GetSpotLightViewModel()); + //Physics + _boxColliderView.Draw(vm.GetBoxColliderViewModel()); + _sphereColliderView.Draw(vm.GetSphereColliderViewModel()); + _capsuleColliderView.Draw(vm.GetCapsuleColliderViewModel()); + _convexColliderView.Draw(vm.GetConvexColliderViewModel()); + _meshColliderView.Draw(vm.GetMeshColliderViewModel()); + _rigidBodyView.Draw(vm.GetRigidBodyViewModel()); + ImGui::Spacing(); ImGui::Separator(); ImGui::Spacing(); diff --git a/SynapseEngine/Editor/View/Component/ComponentView.h b/SynapseEngine/Editor/View/Component/ComponentView.h index bff5e463..b88bce7d 100644 --- a/SynapseEngine/Editor/View/Component/ComponentView.h +++ b/SynapseEngine/Editor/View/Component/ComponentView.h @@ -6,19 +6,34 @@ #include "Core/TagView.h" #include "Core/TransformView.h" +#include "Core/CameraView.h" #include "Light/DirectionLightView.h" #include "Light/PointLightView.h" #include "Light/SpotLightView.h" +#include "Physics/BoxColliderView.h" +#include "Physics/SphereColliderView.h" +#include "Physics/CapsuleColliderView.h" +#include "Physics/ConvexColliderView.h" +#include "Physics/MeshColliderView.h" +#include "Physics/RigidBodyView.h" + namespace Syn { class ComponentView : public IView { public: void Draw(ComponentViewModel& vm) override; private: TagView _tagView; + CameraView _cameraView; TransformView _transformView; DirectionLightView _directionLightView; PointLightView _pointLightView; SpotLightView _spotLightView; + BoxColliderView _boxColliderView; + SphereColliderView _sphereColliderView; + CapsuleColliderView _capsuleColliderView; + ConvexColliderView _convexColliderView; + MeshColliderView _meshColliderView; + RigidBodyView _rigidBodyView; }; } \ No newline at end of file diff --git a/SynapseEngine/Editor/View/Component/Core/CameraView.cpp b/SynapseEngine/Editor/View/Component/Core/CameraView.cpp new file mode 100644 index 00000000..c1959799 --- /dev/null +++ b/SynapseEngine/Editor/View/Component/Core/CameraView.cpp @@ -0,0 +1,61 @@ +#include "CameraView.h" +#include "Editor/Manager/EditorIcons.h" // Esetleg lecserélheted egy kamera ikonra, ha van (pl. SYN_ICON_CAMERA) +#include "Editor/Widgets/CardWidget.h" +#include "Editor/Widgets/PropertyGrid.h" +#include + +namespace Syn { + + void CameraView::Draw(CameraViewModel& vm) { + CameraState state = vm.GetState(); + + if (!state.hasComponent) return; + + constexpr const char* CardTitle = "Camera"; + + if (Syn::UI::BeginCard(CardTitle, SYN_ICON_CAMERA, _isCardOpen)) + { + if (Syn::UI::BeginPropertyGrid("CameraGrid")) + { + if (Syn::UI::PropertyDragFloat("Yaw", state.yaw, 0.1f, -360.0f, 360.0f, "%.2f")) { + vm.Dispatch(SetCameraYawIntent{ state.yaw, !ImGui::IsItemDeactivatedAfterEdit() }); + } + + if (Syn::UI::PropertyDragFloat("Pitch", state.pitch, 0.1f, -89.0f, 89.0f, "%.2f")) { + vm.Dispatch(SetCameraPitchIntent{ state.pitch, !ImGui::IsItemDeactivatedAfterEdit() }); + } + + Syn::UI::PropertySeparator(); + + if (Syn::UI::PropertyDragFloat("FOV", state.fov, 0.1f, 1.0f, 179.0f, "%.2f")) { + vm.Dispatch(SetCameraFovIntent{ state.fov, !ImGui::IsItemDeactivatedAfterEdit() }); + } + + if (Syn::UI::PropertyDragFloat("Near Plane", state.nearPlane, 0.01f, 0.001f, 100.0f, "%.3f")) { + vm.Dispatch(SetCameraNearPlaneIntent{ state.nearPlane, !ImGui::IsItemDeactivatedAfterEdit() }); + } + + if (Syn::UI::PropertyDragFloat("Far Plane", state.farPlane, 1.0f, 1.0f, 10000.0f, "%.1f")) { + vm.Dispatch(SetCameraFarPlaneIntent{ state.farPlane, !ImGui::IsItemDeactivatedAfterEdit() }); + } + + Syn::UI::PropertySeparator(); + + if (Syn::UI::PropertyDragFloat("Speed", state.speed, 0.1f, 0.0f, 100.0f, "%.2f")) { + vm.Dispatch(SetCameraSpeedIntent{ state.speed, !ImGui::IsItemDeactivatedAfterEdit() }); + } + + if (Syn::UI::PropertyDragFloat("Sensitivity", state.sensitivity, 0.01f, 0.0f, 10.0f, "%.3f")) { + vm.Dispatch(SetCameraSensitivityIntent{ state.sensitivity, !ImGui::IsItemDeactivatedAfterEdit() }); + } + + if (Syn::UI::PropertyDragFloat("Distance", state.distance, 0.1f, 0.0f, 500.0f, "%.2f")) { + vm.Dispatch(SetCameraDistanceIntent{ state.distance, !ImGui::IsItemDeactivatedAfterEdit() }); + } + + Syn::UI::EndPropertyGrid(); + } + } + Syn::UI::EndCard(); + } +} \ No newline at end of file diff --git a/SynapseEngine/Editor/View/Component/Core/CameraView.h b/SynapseEngine/Editor/View/Component/Core/CameraView.h new file mode 100644 index 00000000..67939b50 --- /dev/null +++ b/SynapseEngine/Editor/View/Component/Core/CameraView.h @@ -0,0 +1,12 @@ +#pragma once +#include "Editor/View/IView.h" +#include "EditorCore/ViewModels/Component/Core/Camera/CameraViewModel.h" + +namespace Syn { + class CameraView : public IView { + public: + void Draw(CameraViewModel& vm) override; + private: + bool _isCardOpen = true; + }; +} \ No newline at end of file diff --git a/SynapseEngine/Editor/View/Component/Physics/BoxColliderView.cpp b/SynapseEngine/Editor/View/Component/Physics/BoxColliderView.cpp new file mode 100644 index 00000000..9839ad46 --- /dev/null +++ b/SynapseEngine/Editor/View/Component/Physics/BoxColliderView.cpp @@ -0,0 +1,33 @@ +#include "BoxColliderView.h" +#include "Editor/Manager/EditorIcons.h" +#include "Editor/Widgets/CardWidget.h" +#include "Editor/Widgets/PropertyGrid.h" +#include + +namespace Syn { + + void BoxColliderView::Draw(BoxColliderViewModel& vm) { + BoxColliderState state = vm.GetState(); + + if (!state.hasComponent) return; + + constexpr const char* CardTitle = "Box Collider"; + + if (Syn::UI::BeginCard(CardTitle, SYN_ICON_CUBE, _isCardOpen)) + { + if (Syn::UI::BeginPropertyGrid("BoxColliderGrid")) + { + if (Syn::UI::PropertyDragFloat3("Half Extents", state.halfExtents, 0.1f, 0.0f, 1000.0f, "%.2f")) { + vm.Dispatch(SetBoxColliderHalfExtentsIntent{ state.halfExtents, !ImGui::IsItemDeactivatedAfterEdit() }); + } + + if (Syn::UI::PropertyDragFloat3("Local Offset", state.localOffset, 0.1f, -1000.0f, 1000.0f, "%.2f")) { + vm.Dispatch(SetBoxColliderLocalOffsetIntent{ state.localOffset, !ImGui::IsItemDeactivatedAfterEdit() }); + } + + Syn::UI::EndPropertyGrid(); + } + } + Syn::UI::EndCard(); + } +} \ No newline at end of file diff --git a/SynapseEngine/Editor/View/Component/Physics/BoxColliderView.h b/SynapseEngine/Editor/View/Component/Physics/BoxColliderView.h new file mode 100644 index 00000000..898b0f9d --- /dev/null +++ b/SynapseEngine/Editor/View/Component/Physics/BoxColliderView.h @@ -0,0 +1,12 @@ +#pragma once +#include "Editor/View/IView.h" +#include "EditorCore/ViewModels/Component/Physics/BoxCollider/BoxColliderViewModel.h" + +namespace Syn { + class BoxColliderView : public IView { + public: + void Draw(BoxColliderViewModel& vm) override; + private: + bool _isCardOpen = true; + }; +} \ No newline at end of file diff --git a/SynapseEngine/Editor/View/Component/Physics/CapsuleColliderView.cpp b/SynapseEngine/Editor/View/Component/Physics/CapsuleColliderView.cpp new file mode 100644 index 00000000..7972332e --- /dev/null +++ b/SynapseEngine/Editor/View/Component/Physics/CapsuleColliderView.cpp @@ -0,0 +1,37 @@ +#include "CapsuleColliderView.h" +#include "Editor/Manager/EditorIcons.h" +#include "Editor/Widgets/CardWidget.h" +#include "Editor/Widgets/PropertyGrid.h" +#include + +namespace Syn { + + void CapsuleColliderView::Draw(CapsuleColliderViewModel& vm) { + CapsuleColliderState state = vm.GetState(); + + if (!state.hasComponent) return; + + constexpr const char* CardTitle = "Capsule Collider"; + + if (Syn::UI::BeginCard(CardTitle, SYN_ICON_CUBE, _isCardOpen)) + { + if (Syn::UI::BeginPropertyGrid("CapsuleColliderGrid")) + { + if (Syn::UI::PropertyDragFloat("Radius", state.radius, 0.1f, 0.0f, 1000.0f, "%.2f")) { + vm.Dispatch(SetCapsuleColliderRadiusIntent{ state.radius, !ImGui::IsItemDeactivatedAfterEdit() }); + } + + if (Syn::UI::PropertyDragFloat("Half Height", state.halfHeight, 0.1f, 0.0f, 1000.0f, "%.2f")) { + vm.Dispatch(SetCapsuleColliderHalfHeightIntent{ state.halfHeight, !ImGui::IsItemDeactivatedAfterEdit() }); + } + + if (Syn::UI::PropertyDragFloat3("Local Offset", state.localOffset, 0.1f, -1000.0f, 1000.0f, "%.2f")) { + vm.Dispatch(SetCapsuleColliderLocalOffsetIntent{ state.localOffset, !ImGui::IsItemDeactivatedAfterEdit() }); + } + + Syn::UI::EndPropertyGrid(); + } + } + Syn::UI::EndCard(); + } +} \ No newline at end of file diff --git a/SynapseEngine/Editor/View/Component/Physics/CapsuleColliderView.h b/SynapseEngine/Editor/View/Component/Physics/CapsuleColliderView.h new file mode 100644 index 00000000..d4a0a9cf --- /dev/null +++ b/SynapseEngine/Editor/View/Component/Physics/CapsuleColliderView.h @@ -0,0 +1,12 @@ +#pragma once +#include "Editor/View/IView.h" +#include "EditorCore/ViewModels/Component/Physics/CapsuleCollider/CapsuleColliderViewModel.h" + +namespace Syn { + class CapsuleColliderView : public IView { + public: + void Draw(CapsuleColliderViewModel& vm) override; + private: + bool _isCardOpen = true; + }; +} \ No newline at end of file diff --git a/SynapseEngine/Editor/View/Component/Physics/ConvexColliderView.cpp b/SynapseEngine/Editor/View/Component/Physics/ConvexColliderView.cpp new file mode 100644 index 00000000..4830f752 --- /dev/null +++ b/SynapseEngine/Editor/View/Component/Physics/ConvexColliderView.cpp @@ -0,0 +1,37 @@ +#include "ConvexColliderView.h" +#include "Editor/Manager/EditorIcons.h" +#include "Editor/Widgets/CardWidget.h" +#include "Editor/Widgets/PropertyGrid.h" +#include + +namespace Syn { + + void ConvexColliderView::Draw(ConvexColliderViewModel& vm) { + ConvexColliderState state = vm.GetState(); + + if (!state.hasComponent) return; + + constexpr const char* CardTitle = "Convex Collider"; + + if (Syn::UI::BeginCard(CardTitle, SYN_ICON_CUBE, _isCardOpen)) + { + if (Syn::UI::BeginPropertyGrid("ConvexColliderGrid")) + { + Syn::UI::BeginProperty("Target LOD"); + int lodLevel = static_cast(state.targetLodLevel); + if (ImGui::SliderInt("##TargetLOD", &lodLevel, 0, 3)) { + vm.Dispatch(SetConvexColliderTargetLodLevelIntent{ static_cast(lodLevel) }); + } + + Syn::UI::PropertySeparator(); + + if (Syn::UI::PropertyDragFloat3("Local Offset", state.localOffset, 0.1f, -1000.0f, 1000.0f, "%.2f")) { + vm.Dispatch(SetConvexColliderLocalOffsetIntent{ state.localOffset, !ImGui::IsItemDeactivatedAfterEdit() }); + } + + Syn::UI::EndPropertyGrid(); + } + } + Syn::UI::EndCard(); + } +} \ No newline at end of file diff --git a/SynapseEngine/Editor/View/Component/Physics/ConvexColliderView.h b/SynapseEngine/Editor/View/Component/Physics/ConvexColliderView.h new file mode 100644 index 00000000..fd87ffe4 --- /dev/null +++ b/SynapseEngine/Editor/View/Component/Physics/ConvexColliderView.h @@ -0,0 +1,12 @@ +#pragma once +#include "Editor/View/IView.h" +#include "EditorCore/ViewModels/Component/Physics/ConvexCollider/ConvexColliderViewModel.h" + +namespace Syn { + class ConvexColliderView : public IView { + public: + void Draw(ConvexColliderViewModel& vm) override; + private: + bool _isCardOpen = true; + }; +} \ No newline at end of file diff --git a/SynapseEngine/Editor/View/Component/Physics/MeshColliderView.cpp b/SynapseEngine/Editor/View/Component/Physics/MeshColliderView.cpp new file mode 100644 index 00000000..f4fdcf1c --- /dev/null +++ b/SynapseEngine/Editor/View/Component/Physics/MeshColliderView.cpp @@ -0,0 +1,37 @@ +#include "MeshColliderView.h" +#include "Editor/Manager/EditorIcons.h" +#include "Editor/Widgets/CardWidget.h" +#include "Editor/Widgets/PropertyGrid.h" +#include + +namespace Syn { + + void MeshColliderView::Draw(MeshColliderViewModel& vm) { + MeshColliderState state = vm.GetState(); + + if (!state.hasComponent) return; + + constexpr const char* CardTitle = "Mesh Collider"; + + if (Syn::UI::BeginCard(CardTitle, SYN_ICON_CUBE, _isCardOpen)) + { + if (Syn::UI::BeginPropertyGrid("MeshColliderGrid")) + { + Syn::UI::BeginProperty("Target LOD"); + int lodLevel = static_cast(state.targetLodLevel); + if (ImGui::SliderInt("##TargetLOD", &lodLevel, 0, 3)) { + vm.Dispatch(SetMeshColliderTargetLodLevelIntent{ static_cast(lodLevel) }); + } + + Syn::UI::PropertySeparator(); + + if (Syn::UI::PropertyDragFloat3("Local Offset", state.localOffset, 0.1f, -1000.0f, 1000.0f, "%.2f")) { + vm.Dispatch(SetMeshColliderLocalOffsetIntent{ state.localOffset, !ImGui::IsItemDeactivatedAfterEdit() }); + } + + Syn::UI::EndPropertyGrid(); + } + } + Syn::UI::EndCard(); + } +} \ No newline at end of file diff --git a/SynapseEngine/Editor/View/Component/Physics/MeshColliderView.h b/SynapseEngine/Editor/View/Component/Physics/MeshColliderView.h new file mode 100644 index 00000000..8c0f7f76 --- /dev/null +++ b/SynapseEngine/Editor/View/Component/Physics/MeshColliderView.h @@ -0,0 +1,12 @@ +#pragma once +#include "Editor/View/IView.h" +#include "EditorCore/ViewModels/Component/Physics/MeshCollider/MeshColliderViewModel.h" + +namespace Syn { + class MeshColliderView : public IView { + public: + void Draw(MeshColliderViewModel& vm) override; + private: + bool _isCardOpen = true; + }; +} \ No newline at end of file diff --git a/SynapseEngine/Editor/View/Component/Physics/RigidBodyView.cpp b/SynapseEngine/Editor/View/Component/Physics/RigidBodyView.cpp new file mode 100644 index 00000000..ebd67a41 --- /dev/null +++ b/SynapseEngine/Editor/View/Component/Physics/RigidBodyView.cpp @@ -0,0 +1,64 @@ +#include "RigidBodyView.h" +#include "Editor/Manager/EditorIcons.h" +#include "Editor/Widgets/CardWidget.h" +#include "Editor/Widgets/PropertyGrid.h" +#include + +namespace Syn { + + void RigidBodyView::Draw(RigidBodyViewModel& vm) { + RigidBodyState state = vm.GetState(); + + if (!state.hasComponent) return; + + constexpr const char* CardTitle = "Rigid Body"; + + if (Syn::UI::BeginCard(CardTitle, SYN_ICON_CUBE, _isCardOpen)) + { + if (Syn::UI::BeginPropertyGrid("RigidBodyGrid")) + { + const char* currentMotionString = "Dynamic"; + if (state.motionType == PhysicsMotionType::Static) currentMotionString = "Static"; + else if (state.motionType == PhysicsMotionType::Kinematic) currentMotionString = "Kinematic"; + + if (Syn::UI::BeginPropertyCombo("Motion Type", currentMotionString)) { + if (ImGui::Selectable("Static", state.motionType == PhysicsMotionType::Static)) { + vm.Dispatch(SetRigidBodyMotionTypeIntent{ PhysicsMotionType::Static }); + } + if (ImGui::Selectable("Kinematic", state.motionType == PhysicsMotionType::Kinematic)) { + vm.Dispatch(SetRigidBodyMotionTypeIntent{ PhysicsMotionType::Kinematic }); + } + if (ImGui::Selectable("Dynamic", state.motionType == PhysicsMotionType::Dynamic)) { + vm.Dispatch(SetRigidBodyMotionTypeIntent{ PhysicsMotionType::Dynamic }); + } + Syn::UI::EndPropertyCombo(); + } + + Syn::UI::PropertySeparator(); + + if (Syn::UI::PropertyDragFloat("Mass", state.mass, 0.1f, 0.0f, 10000.0f, "%.2f")) { + vm.Dispatch(SetRigidBodyMassIntent{ state.mass, !ImGui::IsItemDeactivatedAfterEdit() }); + } + + if (Syn::UI::PropertyDragFloat("Friction", state.friction, 0.01f, 0.0f, 1.0f, "%.2f")) { + vm.Dispatch(SetRigidBodyFrictionIntent{ state.friction, !ImGui::IsItemDeactivatedAfterEdit() }); + } + + if (Syn::UI::PropertyDragFloat("Restitution", state.restitution, 0.01f, 0.0f, 1.0f, "%.2f")) { + vm.Dispatch(SetRigidBodyRestitutionIntent{ state.restitution, !ImGui::IsItemDeactivatedAfterEdit() }); + } + + Syn::UI::PropertySeparator(); + + Syn::UI::BeginProperty("Layer"); + int layerInt = static_cast(state.layer); + if (ImGui::DragInt("##Layer", &layerInt, 1.0f, 0, 31)) { + vm.Dispatch(SetRigidBodyLayerIntent{ static_cast(layerInt) }); + } + + Syn::UI::EndPropertyGrid(); + } + } + Syn::UI::EndCard(); + } +} \ No newline at end of file diff --git a/SynapseEngine/Editor/View/Component/Physics/RigidBodyView.h b/SynapseEngine/Editor/View/Component/Physics/RigidBodyView.h new file mode 100644 index 00000000..b3381a26 --- /dev/null +++ b/SynapseEngine/Editor/View/Component/Physics/RigidBodyView.h @@ -0,0 +1,12 @@ +#pragma once +#include "Editor/View/IView.h" +#include "EditorCore/ViewModels/Component/Physics/RigidBody/RigidBodyViewModel.h" + +namespace Syn { + class RigidBodyView : public IView { + public: + void Draw(RigidBodyViewModel& vm) override; + private: + bool _isCardOpen = true; + }; +} \ No newline at end of file diff --git a/SynapseEngine/Editor/View/Component/Physics/SphereColliderView.cpp b/SynapseEngine/Editor/View/Component/Physics/SphereColliderView.cpp new file mode 100644 index 00000000..67ae833b --- /dev/null +++ b/SynapseEngine/Editor/View/Component/Physics/SphereColliderView.cpp @@ -0,0 +1,33 @@ +#include "SphereColliderView.h" +#include "Editor/Manager/EditorIcons.h" +#include "Editor/Widgets/CardWidget.h" +#include "Editor/Widgets/PropertyGrid.h" +#include + +namespace Syn { + + void SphereColliderView::Draw(SphereColliderViewModel& vm) { + SphereColliderState state = vm.GetState(); + + if (!state.hasComponent) return; + + constexpr const char* CardTitle = "Sphere Collider"; + + if (Syn::UI::BeginCard(CardTitle, SYN_ICON_CUBE, _isCardOpen)) + { + if (Syn::UI::BeginPropertyGrid("SphereColliderGrid")) + { + if (Syn::UI::PropertyDragFloat("Radius", state.radius, 0.1f, 0.0f, 1000.0f, "%.2f")) { + vm.Dispatch(SetSphereColliderRadiusIntent{ state.radius, !ImGui::IsItemDeactivatedAfterEdit() }); + } + + if (Syn::UI::PropertyDragFloat3("Local Offset", state.localOffset, 0.1f, -1000.0f, 1000.0f, "%.2f")) { + vm.Dispatch(SetSphereColliderLocalOffsetIntent{ state.localOffset, !ImGui::IsItemDeactivatedAfterEdit() }); + } + + Syn::UI::EndPropertyGrid(); + } + } + Syn::UI::EndCard(); + } +} \ No newline at end of file diff --git a/SynapseEngine/Editor/View/Component/Physics/SphereColliderView.h b/SynapseEngine/Editor/View/Component/Physics/SphereColliderView.h new file mode 100644 index 00000000..60810413 --- /dev/null +++ b/SynapseEngine/Editor/View/Component/Physics/SphereColliderView.h @@ -0,0 +1,12 @@ +#pragma once +#include "Editor/View/IView.h" +#include "EditorCore/ViewModels/Component/Physics/SphereCollider/SphereColliderViewModel.h" + +namespace Syn { + class SphereColliderView : public IView { + public: + void Draw(SphereColliderViewModel& vm) override; + private: + bool _isCardOpen = true; + }; +} \ No newline at end of file diff --git a/SynapseEngine/Editor/Widgets/PropertyGrid.cpp b/SynapseEngine/Editor/Widgets/PropertyGrid.cpp index 1032cd5c..9dd043ac 100644 --- a/SynapseEngine/Editor/Widgets/PropertyGrid.cpp +++ b/SynapseEngine/Editor/Widgets/PropertyGrid.cpp @@ -59,61 +59,83 @@ namespace Syn::UI { bool PropertyDragFloat(const char* label, float& value, float v_speed, float v_min, float v_max, const char* format, int indentLevel) { BeginProperty(label, indentLevel); std::string widgetId = std::string("##") + label; - return ImGui::DragFloat(widgetId.c_str(), &value, v_speed, v_min, v_max, format); + bool changed = ImGui::DragFloat(widgetId.c_str(), &value, v_speed, v_min, v_max, format); + return changed || ImGui::IsItemDeactivatedAfterEdit(); } bool PropertyDragFloat2(const char* label, glm::vec2& values, float v_speed, float v_min, float v_max, const char* format, int indentLevel) { BeginProperty(label, indentLevel); std::string widgetId = std::string("##") + label; - return ImGui::DragFloat2(widgetId.c_str(), &values.x, v_speed, v_min, v_max, format); + ImGui::BeginGroup(); + bool changed = ImGui::DragFloat2(widgetId.c_str(), &values.x, v_speed, v_min, v_max, format); + ImGui::EndGroup(); + return changed || ImGui::IsItemDeactivatedAfterEdit(); } bool PropertyDragFloat3(const char* label, glm::vec3& values, float v_speed, float v_min, float v_max, const char* format, int indentLevel) { BeginProperty(label, indentLevel); std::string widgetId = std::string("##") + label; - return ImGui::DragFloat3(widgetId.c_str(), &values.x, v_speed, v_min, v_max, format); + ImGui::BeginGroup(); + bool changed = ImGui::DragFloat3(widgetId.c_str(), &values.x, v_speed, v_min, v_max, format); + ImGui::EndGroup(); + return changed || ImGui::IsItemDeactivatedAfterEdit(); } bool PropertyDragFloat4(const char* label, glm::vec4& values, float v_speed, float v_min, float v_max, const char* format, int indentLevel) { BeginProperty(label, indentLevel); std::string widgetId = std::string("##") + label; - return ImGui::DragFloat4(widgetId.c_str(), &values.x, v_speed, v_min, v_max, format); + ImGui::BeginGroup(); + bool changed = ImGui::DragFloat4(widgetId.c_str(), &values.x, v_speed, v_min, v_max, format); + ImGui::EndGroup(); + return changed || ImGui::IsItemDeactivatedAfterEdit(); } bool PropertySliderFloat(const char* label, float& value, float v_min, float v_max, const char* format, int indentLevel) { BeginProperty(label, indentLevel); std::string widgetId = std::string("##") + label; - return ImGui::SliderFloat(widgetId.c_str(), &value, v_min, v_max, format); + bool changed = ImGui::SliderFloat(widgetId.c_str(), &value, v_min, v_max, format); + return changed || ImGui::IsItemDeactivatedAfterEdit(); } bool PropertySliderFloat2(const char* label, glm::vec2& values, float v_min, float v_max, const char* format, int indentLevel) { BeginProperty(label, indentLevel); std::string widgetId = std::string("##") + label; - return ImGui::SliderFloat2(widgetId.c_str(), &values.x, v_min, v_max, format); + ImGui::BeginGroup(); + bool changed = ImGui::SliderFloat2(widgetId.c_str(), &values.x, v_min, v_max, format); + ImGui::EndGroup(); + return changed || ImGui::IsItemDeactivatedAfterEdit(); } bool PropertySliderFloat3(const char* label, glm::vec3& values, float v_min, float v_max, const char* format, int indentLevel) { BeginProperty(label, indentLevel); std::string widgetId = std::string("##") + label; - return ImGui::SliderFloat3(widgetId.c_str(), &values.x, v_min, v_max, format); + ImGui::BeginGroup(); + bool changed = ImGui::SliderFloat3(widgetId.c_str(), &values.x, v_min, v_max, format); + ImGui::EndGroup(); + return changed || ImGui::IsItemDeactivatedAfterEdit(); } bool PropertySliderFloat4(const char* label, glm::vec4& values, float v_min, float v_max, const char* format, int indentLevel) { BeginProperty(label, indentLevel); std::string widgetId = std::string("##") + label; - return ImGui::SliderFloat4(widgetId.c_str(), &values.x, v_min, v_max, format); + ImGui::BeginGroup(); + bool changed = ImGui::SliderFloat4(widgetId.c_str(), &values.x, v_min, v_max, format); + ImGui::EndGroup(); + return changed || ImGui::IsItemDeactivatedAfterEdit(); } bool PropertyColor3(const char* label, glm::vec3& color, int indentLevel) { BeginProperty(label, indentLevel); std::string widgetId = std::string("##") + label; - return ImGui::ColorEdit3(widgetId.c_str(), &color.x); + bool changed = ImGui::ColorEdit3(widgetId.c_str(), &color.x); + return changed || ImGui::IsItemDeactivatedAfterEdit(); } bool PropertyColor4(const char* label, glm::vec4& color, int indentLevel) { BeginProperty(label, indentLevel); std::string widgetId = std::string("##") + label; - return ImGui::ColorEdit4(widgetId.c_str(), &color.x); + bool changed = ImGui::ColorEdit4(widgetId.c_str(), &color.x); + return changed || ImGui::IsItemDeactivatedAfterEdit(); } bool PropertyCheckbox(const char* label, bool& value, int indentLevel) { diff --git a/SynapseEngine/Editor/Workspace/SceneWorkspace.cpp b/SynapseEngine/Editor/Workspace/SceneWorkspace.cpp index ca378eaa..ee645cf6 100644 --- a/SynapseEngine/Editor/Workspace/SceneWorkspace.cpp +++ b/SynapseEngine/Editor/Workspace/SceneWorkspace.cpp @@ -33,9 +33,20 @@ namespace Syn { AddWindow( ComponentView{}, ComponentViewModel{ - _context->GetSelectionApi(), _context->GetTagApi(), _context->GetTransformApi(), - _context->GetHierarchyApi(), _context->GetDirectionLightApi(), - _context->GetPointLightApi(), _context->GetSpotLightApi() + _context->GetSelectionApi(), + _context->GetTagApi(), + _context->GetTransformApi(), + _context->GetHierarchyApi(), + _context->GetDirectionLightApi(), + _context->GetPointLightApi(), + _context->GetSpotLightApi(), + _context->GetCameraApi(), + _context->GetBoxColliderApi(), + _context->GetSphereColliderApi(), + _context->GetCapsuleColliderApi(), + _context->GetConvexColliderApi(), + _context->GetMeshColliderApi(), + _context->GetRigidBodyApi() } ); diff --git a/SynapseEngine/EditorCore/Api/IBoxColliderApi.h b/SynapseEngine/EditorCore/Api/IBoxColliderApi.h new file mode 100644 index 00000000..12669d1e --- /dev/null +++ b/SynapseEngine/EditorCore/Api/IBoxColliderApi.h @@ -0,0 +1,18 @@ +#pragma once +#include +#include "EditorCore/Types/EntityHandle.h" + +namespace Syn { + class IBoxColliderApi { + public: + virtual ~IBoxColliderApi() = default; + + virtual bool HasBoxCollider(EntityID entity) const = 0; + + virtual glm::vec3 GetBoxColliderHalfExtents(EntityID entity) const = 0; + virtual glm::vec3 GetBoxColliderLocalOffset(EntityID entity) const = 0; + + virtual void SetBoxColliderHalfExtents(EntityID entity, const glm::vec3& halfExtents) = 0; + virtual void SetBoxColliderLocalOffset(EntityID entity, const glm::vec3& localOffset) = 0; + }; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/Api/ICameraApi.h b/SynapseEngine/EditorCore/Api/ICameraApi.h new file mode 100644 index 00000000..2965f3b0 --- /dev/null +++ b/SynapseEngine/EditorCore/Api/ICameraApi.h @@ -0,0 +1,29 @@ +#pragma once +#include "EditorCore/Types/EntityHandle.h" + +namespace Syn { + class ICameraApi { + public: + virtual ~ICameraApi() = default; + + virtual bool HasCamera(EntityID entity) const = 0; + + virtual float GetCameraYaw(EntityID entity) const = 0; + virtual float GetCameraPitch(EntityID entity) const = 0; + virtual float GetCameraNearPlane(EntityID entity) const = 0; + virtual float GetCameraFarPlane(EntityID entity) const = 0; + virtual float GetCameraFov(EntityID entity) const = 0; + virtual float GetCameraSpeed(EntityID entity) const = 0; + virtual float GetCameraSensitivity(EntityID entity) const = 0; + virtual float GetCameraDistance(EntityID entity) const = 0; + + virtual void SetCameraYaw(EntityID entity, float yaw) = 0; + virtual void SetCameraPitch(EntityID entity, float pitch) = 0; + virtual void SetCameraNearPlane(EntityID entity, float nearPlane) = 0; + virtual void SetCameraFarPlane(EntityID entity, float farPlane) = 0; + virtual void SetCameraFov(EntityID entity, float fov) = 0; + virtual void SetCameraSpeed(EntityID entity, float speed) = 0; + virtual void SetCameraSensitivity(EntityID entity, float sensitivity) = 0; + virtual void SetCameraDistance(EntityID entity, float distance) = 0; + }; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/Api/ICapsuleColliderApi.h b/SynapseEngine/EditorCore/Api/ICapsuleColliderApi.h new file mode 100644 index 00000000..e4602344 --- /dev/null +++ b/SynapseEngine/EditorCore/Api/ICapsuleColliderApi.h @@ -0,0 +1,20 @@ +#pragma once +#include +#include "EditorCore/Types/EntityHandle.h" + +namespace Syn { + class ICapsuleColliderApi { + public: + virtual ~ICapsuleColliderApi() = default; + + virtual bool HasCapsuleCollider(EntityID entity) const = 0; + + virtual float GetCapsuleColliderRadius(EntityID entity) const = 0; + virtual float GetCapsuleColliderHalfHeight(EntityID entity) const = 0; + virtual glm::vec3 GetCapsuleColliderLocalOffset(EntityID entity) const = 0; + + virtual void SetCapsuleColliderRadius(EntityID entity, float radius) = 0; + virtual void SetCapsuleColliderHalfHeight(EntityID entity, float halfHeight) = 0; + virtual void SetCapsuleColliderLocalOffset(EntityID entity, const glm::vec3& localOffset) = 0; + }; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/Api/IConvexColliderApi.h b/SynapseEngine/EditorCore/Api/IConvexColliderApi.h new file mode 100644 index 00000000..38c47ba3 --- /dev/null +++ b/SynapseEngine/EditorCore/Api/IConvexColliderApi.h @@ -0,0 +1,19 @@ +#pragma once +#include +#include "EditorCore/Types/EntityHandle.h" +#include + +namespace Syn { + class IConvexColliderApi { + public: + virtual ~IConvexColliderApi() = default; + + virtual bool HasConvexCollider(EntityID entity) const = 0; + + virtual uint32_t GetConvexColliderTargetLodLevel(EntityID entity) const = 0; + virtual glm::vec3 GetConvexColliderLocalOffset(EntityID entity) const = 0; + + virtual void SetConvexColliderTargetLodLevel(EntityID entity, uint32_t targetLodLevel) = 0; + virtual void SetConvexColliderLocalOffset(EntityID entity, const glm::vec3& localOffset) = 0; + }; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/Api/IMeshColliderApi.h b/SynapseEngine/EditorCore/Api/IMeshColliderApi.h new file mode 100644 index 00000000..563271a2 --- /dev/null +++ b/SynapseEngine/EditorCore/Api/IMeshColliderApi.h @@ -0,0 +1,19 @@ +#pragma once +#include +#include "EditorCore/Types/EntityHandle.h" +#include + +namespace Syn { + class IMeshColliderApi { + public: + virtual ~IMeshColliderApi() = default; + + virtual bool HasMeshCollider(EntityID entity) const = 0; + + virtual uint32_t GetMeshColliderTargetLodLevel(EntityID entity) const = 0; + virtual glm::vec3 GetMeshColliderLocalOffset(EntityID entity) const = 0; + + virtual void SetMeshColliderTargetLodLevel(EntityID entity, uint32_t targetLodLevel) = 0; + virtual void SetMeshColliderLocalOffset(EntityID entity, const glm::vec3& localOffset) = 0; + }; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/Api/IRigidBodyApi.h b/SynapseEngine/EditorCore/Api/IRigidBodyApi.h new file mode 100644 index 00000000..abae547a --- /dev/null +++ b/SynapseEngine/EditorCore/Api/IRigidBodyApi.h @@ -0,0 +1,25 @@ +#pragma once +#include "EditorCore/Types/EntityHandle.h" +#include "Engine/Physics/PhysicsTypes.h" +#include + +namespace Syn { + class IRigidBodyApi { + public: + virtual ~IRigidBodyApi() = default; + + virtual bool HasRigidBody(EntityID entity) const = 0; + + virtual PhysicsMotionType GetRigidBodyMotionType(EntityID entity) const = 0; + virtual float GetRigidBodyMass(EntityID entity) const = 0; + virtual float GetRigidBodyFriction(EntityID entity) const = 0; + virtual float GetRigidBodyRestitution(EntityID entity) const = 0; + virtual uint32_t GetRigidBodyLayer(EntityID entity) const = 0; + + virtual void SetRigidBodyMotionType(EntityID entity, PhysicsMotionType motionType) = 0; + virtual void SetRigidBodyMass(EntityID entity, float mass) = 0; + virtual void SetRigidBodyFriction(EntityID entity, float friction) = 0; + virtual void SetRigidBodyRestitution(EntityID entity, float restitution) = 0; + virtual void SetRigidBodyLayer(EntityID entity, uint32_t layer) = 0; + }; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/Api/ISphereColliderApi.h b/SynapseEngine/EditorCore/Api/ISphereColliderApi.h new file mode 100644 index 00000000..4127cc2b --- /dev/null +++ b/SynapseEngine/EditorCore/Api/ISphereColliderApi.h @@ -0,0 +1,18 @@ +#pragma once +#include +#include "EditorCore/Types/EntityHandle.h" + +namespace Syn { + class ISphereColliderApi { + public: + virtual ~ISphereColliderApi() = default; + + virtual bool HasSphereCollider(EntityID entity) const = 0; + + virtual float GetSphereColliderRadius(EntityID entity) const = 0; + virtual glm::vec3 GetSphereColliderLocalOffset(EntityID entity) const = 0; + + virtual void SetSphereColliderRadius(EntityID entity, float radius) = 0; + virtual void SetSphereColliderLocalOffset(EntityID entity, const glm::vec3& localOffset) = 0; + }; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/ComponentViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/Component/ComponentViewModel.cpp index 5d44aa72..7c40930b 100644 --- a/SynapseEngine/EditorCore/ViewModels/Component/ComponentViewModel.cpp +++ b/SynapseEngine/EditorCore/ViewModels/Component/ComponentViewModel.cpp @@ -9,14 +9,29 @@ namespace Syn IHierarchyApi* hierarchyApi, IDirectionLightApi* directionLightApi, IPointLightApi* pointLightApi, - ISpotLightApi* spotLightApi) + ISpotLightApi* spotLightApi, + ICameraApi* cameraApi, + IBoxColliderApi* boxColliderApi, + ISphereColliderApi* sphereColliderApi, + ICapsuleColliderApi* capsuleColliderApi, + IConvexColliderApi* convexColliderApi, + IMeshColliderApi* meshColliderApi, + IRigidBodyApi* rigidBodyApi + ) : _selectionApi(selectionApi), _tagViewModel(selectionApi, tagApi), _transformViewModel(selectionApi, transformApi, hierarchyApi), _directionLightViewModel(selectionApi, directionLightApi), _pointLightViewModel(selectionApi, pointLightApi), - _spotLightViewModel(selectionApi, spotLightApi) + _spotLightViewModel(selectionApi, spotLightApi), + _cameraViewModel(selectionApi, cameraApi), + _boxColliderViewModel(selectionApi, boxColliderApi), + _sphereColliderViewModel(selectionApi, sphereColliderApi), + _capsuleColliderViewModel(selectionApi, capsuleColliderApi), + _convexColliderViewModel(selectionApi, convexColliderApi), + _meshColliderViewModel(selectionApi, meshColliderApi), + _rigidBodyViewModel(selectionApi, rigidBodyApi) {} const ComponentState& ComponentViewModel::GetState() const { @@ -37,6 +52,13 @@ namespace Syn _directionLightViewModel.SyncWithEngine(); _pointLightViewModel.SyncWithEngine(); _spotLightViewModel.SyncWithEngine(); + _cameraViewModel.SyncWithEngine(); + _boxColliderViewModel.SyncWithEngine(); + _sphereColliderViewModel.SyncWithEngine(); + _capsuleColliderViewModel.SyncWithEngine(); + _convexColliderViewModel.SyncWithEngine(); + _meshColliderViewModel.SyncWithEngine(); + _rigidBodyViewModel.SyncWithEngine(); } } @@ -59,6 +81,27 @@ namespace Syn else if constexpr (std::is_same_v) { _spotLightViewModel.Dispatch(arg); } + else if constexpr (std::is_same_v) { + _cameraViewModel.Dispatch(arg); + } + else if constexpr (std::is_same_v) { + _boxColliderViewModel.Dispatch(arg); + } + else if constexpr (std::is_same_v) { + _sphereColliderViewModel.Dispatch(arg); + } + else if constexpr (std::is_same_v) { + _capsuleColliderViewModel.Dispatch(arg); + } + else if constexpr (std::is_same_v) { + _convexColliderViewModel.Dispatch(arg); + } + else if constexpr (std::is_same_v) { + _meshColliderViewModel.Dispatch(arg); + } + else if constexpr (std::is_same_v) { + _rigidBodyViewModel.Dispatch(arg); + } }, intent); } } \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/ComponentViewModel.h b/SynapseEngine/EditorCore/ViewModels/Component/ComponentViewModel.h index 343ebddc..66cd1f36 100644 --- a/SynapseEngine/EditorCore/ViewModels/Component/ComponentViewModel.h +++ b/SynapseEngine/EditorCore/ViewModels/Component/ComponentViewModel.h @@ -8,12 +8,27 @@ #include "Light/DirectionLight/DirectionLightViewModel.h" #include "Light/PointLight/PointLightViewModel.h" #include "Light/SpotLight/SpotLightViewModel.h" +#include "Core/Camera/CameraViewModel.h" + +#include "Physics/BoxCollider/BoxColliderViewModel.h" +#include "Physics/SphereCollider/SphereColliderViewModel.h" +#include "Physics/CapsuleCollider/CapsuleColliderViewModel.h" +#include "Physics/ConvexCollider/ConvexColliderViewModel.h" +#include "Physics/MeshCollider/MeshColliderViewModel.h" +#include "Physics/RigidBody/RigidBodyViewModel.h" #include "EditorCore/Api/ISelectionApi.h" #include "EditorCore/Api/ITagApi.h" #include "EditorCore/Api/ITransformApi.h" #include "EditorCore/Api/IDirectionLightApi.h" #include "EditorCore/Api/IPointLightApi.h" +#include "EditorCore/Api/ICameraApi.h" +#include "EditorCore/Api/IBoxColliderApi.h" +#include "EditorCore/Api/ISphereColliderApi.h" +#include "EditorCore/Api/ICapsuleColliderApi.h" +#include "EditorCore/Api/IConvexColliderApi.h" +#include "EditorCore/Api/IMeshColliderApi.h" +#include "EditorCore/Api/IRigidBodyApi.h" namespace Syn { class ComponentViewModel : public IViewModel { @@ -25,7 +40,14 @@ namespace Syn { IHierarchyApi* hierarchyApi, IDirectionLightApi* directionLightApi, IPointLightApi* pointLightApi, - ISpotLightApi* spotLightApi + ISpotLightApi* spotLightApi, + ICameraApi* cameraApi, + IBoxColliderApi* boxColliderApi, + ISphereColliderApi* sphereColliderApi, + ICapsuleColliderApi* capsuleColliderApi, + IConvexColliderApi* convexColliderApi, + IMeshColliderApi* meshColliderApi, + IRigidBodyApi* rigidBodyApi ); ~ComponentViewModel() override = default; @@ -39,14 +61,29 @@ namespace Syn { DirectionLightViewModel& GetDirectionLightViewModel() { return _directionLightViewModel; } PointLightViewModel& GetPointLightViewModel() { return _pointLightViewModel; } SpotLightViewModel& GetSpotLightViewModel() { return _spotLightViewModel; } + CameraViewModel& GetCameraViewModel() { return _cameraViewModel; } + BoxColliderViewModel& GetBoxColliderViewModel() { return _boxColliderViewModel; } + SphereColliderViewModel& GetSphereColliderViewModel() { return _sphereColliderViewModel; } + CapsuleColliderViewModel& GetCapsuleColliderViewModel() { return _capsuleColliderViewModel; } + ConvexColliderViewModel& GetConvexColliderViewModel() { return _convexColliderViewModel; } + MeshColliderViewModel& GetMeshColliderViewModel() { return _meshColliderViewModel; } + RigidBodyViewModel& GetRigidBodyViewModel() { return _rigidBodyViewModel; } private: ISelectionApi* _selectionApi = nullptr; ComponentState _state; - TagViewModel _tagViewModel;; + TagViewModel _tagViewModel; + CameraViewModel _cameraViewModel; TransformViewModel _transformViewModel; DirectionLightViewModel _directionLightViewModel; PointLightViewModel _pointLightViewModel; SpotLightViewModel _spotLightViewModel; + + BoxColliderViewModel _boxColliderViewModel; + SphereColliderViewModel _sphereColliderViewModel; + CapsuleColliderViewModel _capsuleColliderViewModel; + ConvexColliderViewModel _convexColliderViewModel; + MeshColliderViewModel _meshColliderViewModel; + RigidBodyViewModel _rigidBodyViewModel; }; } \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraCommands.cpp b/SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraCommands.cpp new file mode 100644 index 00000000..9552621f --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraCommands.cpp @@ -0,0 +1 @@ +#include "CameraCommands.h" \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraCommands.h b/SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraCommands.h new file mode 100644 index 00000000..c743c64f --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraCommands.h @@ -0,0 +1,15 @@ +#pragma once +#include "EditorCore/Command/ComponentChangeCommand.h" +#include "EditorCore/Api/ICameraApi.h" + +namespace Syn +{ + using ChangeCameraYawCommand = ComponentChangeCommand; + using ChangeCameraPitchCommand = ComponentChangeCommand; + using ChangeCameraNearPlaneCommand = ComponentChangeCommand; + using ChangeCameraFarPlaneCommand = ComponentChangeCommand; + using ChangeCameraFovCommand = ComponentChangeCommand; + using ChangeCameraSpeedCommand = ComponentChangeCommand; + using ChangeCameraSensitivityCommand = ComponentChangeCommand; + using ChangeCameraDistanceCommand = ComponentChangeCommand; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraIntent.cpp b/SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraIntent.cpp new file mode 100644 index 00000000..06f0b168 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraIntent.cpp @@ -0,0 +1 @@ +#include "CameraIntent.h" \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraIntent.h b/SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraIntent.h new file mode 100644 index 00000000..43ccce40 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraIntent.h @@ -0,0 +1,24 @@ +#pragma once +#include + +namespace Syn +{ + struct SetCameraYawIntent { float yaw; bool isDragging; }; + struct SetCameraPitchIntent { float pitch; bool isDragging; }; + struct SetCameraNearPlaneIntent { float nearPlane; bool isDragging; }; + struct SetCameraFarPlaneIntent { float farPlane; bool isDragging; }; + struct SetCameraFovIntent { float fov; bool isDragging; }; + struct SetCameraSpeedIntent { float speed; bool isDragging; }; + struct SetCameraSensitivityIntent { float sensitivity; bool isDragging; }; + struct SetCameraDistanceIntent { float distance; bool isDragging; }; + + using CameraIntent = std::variant< + SetCameraYawIntent, + SetCameraPitchIntent, + SetCameraNearPlaneIntent, + SetCameraFarPlaneIntent, + SetCameraFovIntent, + SetCameraSpeedIntent, + SetCameraSensitivityIntent, + SetCameraDistanceIntent>; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraState.cpp b/SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraState.cpp new file mode 100644 index 00000000..85baba58 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraState.cpp @@ -0,0 +1 @@ +#include "CameraState.h" \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraState.h b/SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraState.h new file mode 100644 index 00000000..43c8c3d6 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraState.h @@ -0,0 +1,16 @@ +#pragma once + +namespace Syn { + struct CameraState { + bool hasComponent = false; + + float yaw; + float pitch; + float nearPlane; + float farPlane; + float fov; + float speed; + float sensitivity; + float distance; + }; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraViewModel.cpp new file mode 100644 index 00000000..dacf8211 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraViewModel.cpp @@ -0,0 +1,127 @@ +#include "CameraViewModel.h" + +namespace Syn +{ + CameraViewModel::CameraViewModel(ISelectionApi* selectionApi, ICameraApi* cameraApi) + : _selectionApi(selectionApi), _cameraApi(cameraApi) + {} + + const CameraState& CameraViewModel::GetState() const + { + return _state; + } + + void CameraViewModel::SyncWithEngine() + { + if (!_selectionApi || !_cameraApi) + return; + + EntityID activeEntity = _selectionApi->GetSelectedEntity(); + + if (activeEntity != NULL_ENTITY && _cameraApi->HasCamera(activeEntity)) + { + _state.hasComponent = true; + + if (!_yawDrag.IsDragging()) _state.yaw = _cameraApi->GetCameraYaw(activeEntity); + if (!_pitchDrag.IsDragging()) _state.pitch = _cameraApi->GetCameraPitch(activeEntity); + if (!_nearPlaneDrag.IsDragging()) _state.nearPlane = _cameraApi->GetCameraNearPlane(activeEntity); + if (!_farPlaneDrag.IsDragging()) _state.farPlane = _cameraApi->GetCameraFarPlane(activeEntity); + if (!_fovDrag.IsDragging()) _state.fov = _cameraApi->GetCameraFov(activeEntity); + if (!_speedDrag.IsDragging()) _state.speed = _cameraApi->GetCameraSpeed(activeEntity); + if (!_sensitivityDrag.IsDragging()) _state.sensitivity = _cameraApi->GetCameraSensitivity(activeEntity); + if (!_distanceDrag.IsDragging()) _state.distance = _cameraApi->GetCameraDistance(activeEntity); + } + else + { + _state.hasComponent = false; + } + } + + void CameraViewModel::Dispatch(const CameraIntent& intent) + { + std::visit([this](auto&& arg) + { + using T = std::decay_t; + + if constexpr (std::is_same_v) HandleSetYaw(arg); + else if constexpr (std::is_same_v) HandleSetPitch(arg); + else if constexpr (std::is_same_v) HandleSetNearPlane(arg); + else if constexpr (std::is_same_v) HandleSetFarPlane(arg); + else if constexpr (std::is_same_v) HandleSetFov(arg); + else if constexpr (std::is_same_v) HandleSetSpeed(arg); + else if constexpr (std::is_same_v) HandleSetSensitivity(arg); + else if constexpr (std::is_same_v) HandleSetDistance(arg); }, intent); + } + + void CameraViewModel::HandleSetYaw(const SetCameraYawIntent& intent) + { + EntityID activeEntity = _selectionApi->GetSelectedEntity(); + if (activeEntity == NULL_ENTITY) return; + _yawDrag.Handle(intent.isDragging, intent.yaw, _state.yaw, + [&](const float& v) { _cameraApi->SetCameraYaw(activeEntity, v); }, + [&](const float& s, const float& e) { return std::make_shared(_cameraApi, activeEntity, s, e); }); + } + + void CameraViewModel::HandleSetPitch(const SetCameraPitchIntent& intent) + { + EntityID activeEntity = _selectionApi->GetSelectedEntity(); + if (activeEntity == NULL_ENTITY) return; + _pitchDrag.Handle(intent.isDragging, intent.pitch, _state.pitch, + [&](const float& v) { _cameraApi->SetCameraPitch(activeEntity, v); }, + [&](const float& s, const float& e) { return std::make_shared(_cameraApi, activeEntity, s, e); }); + } + + void CameraViewModel::HandleSetNearPlane(const SetCameraNearPlaneIntent& intent) + { + EntityID activeEntity = _selectionApi->GetSelectedEntity(); + if (activeEntity == NULL_ENTITY) return; + _nearPlaneDrag.Handle(intent.isDragging, intent.nearPlane, _state.nearPlane, + [&](const float& v) { _cameraApi->SetCameraNearPlane(activeEntity, v); }, + [&](const float& s, const float& e) { return std::make_shared(_cameraApi, activeEntity, s, e); }); + } + + void CameraViewModel::HandleSetFarPlane(const SetCameraFarPlaneIntent& intent) + { + EntityID activeEntity = _selectionApi->GetSelectedEntity(); + if (activeEntity == NULL_ENTITY) return; + _farPlaneDrag.Handle(intent.isDragging, intent.farPlane, _state.farPlane, + [&](const float& v) { _cameraApi->SetCameraFarPlane(activeEntity, v); }, + [&](const float& s, const float& e) { return std::make_shared(_cameraApi, activeEntity, s, e); }); + } + + void CameraViewModel::HandleSetFov(const SetCameraFovIntent& intent) + { + EntityID activeEntity = _selectionApi->GetSelectedEntity(); + if (activeEntity == NULL_ENTITY) return; + _fovDrag.Handle(intent.isDragging, intent.fov, _state.fov, + [&](const float& v) { _cameraApi->SetCameraFov(activeEntity, v); }, + [&](const float& s, const float& e) { return std::make_shared(_cameraApi, activeEntity, s, e); }); + } + + void CameraViewModel::HandleSetSpeed(const SetCameraSpeedIntent& intent) + { + EntityID activeEntity = _selectionApi->GetSelectedEntity(); + if (activeEntity == NULL_ENTITY) return; + _speedDrag.Handle(intent.isDragging, intent.speed, _state.speed, + [&](const float& v) { _cameraApi->SetCameraSpeed(activeEntity, v); }, + [&](const float& s, const float& e) { return std::make_shared(_cameraApi, activeEntity, s, e); }); + } + + void CameraViewModel::HandleSetSensitivity(const SetCameraSensitivityIntent& intent) + { + EntityID activeEntity = _selectionApi->GetSelectedEntity(); + if (activeEntity == NULL_ENTITY) return; + _sensitivityDrag.Handle(intent.isDragging, intent.sensitivity, _state.sensitivity, + [&](const float& v) { _cameraApi->SetCameraSensitivity(activeEntity, v); }, + [&](const float& s, const float& e) { return std::make_shared(_cameraApi, activeEntity, s, e); }); + } + + void CameraViewModel::HandleSetDistance(const SetCameraDistanceIntent& intent) + { + EntityID activeEntity = _selectionApi->GetSelectedEntity(); + if (activeEntity == NULL_ENTITY) return; + _distanceDrag.Handle(intent.isDragging, intent.distance, _state.distance, + [&](const float& v) { _cameraApi->SetCameraDistance(activeEntity, v); }, + [&](const float& s, const float& e) { return std::make_shared(_cameraApi, activeEntity, s, e); }); + } +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraViewModel.h b/SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraViewModel.h new file mode 100644 index 00000000..9f89bd4c --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraViewModel.h @@ -0,0 +1,44 @@ +#pragma once +#include "EditorCore/ViewModels/IViewModel.h" +#include "EditorCore/Interaction/DragInteraction.h" +#include "CameraState.h" +#include "CameraIntent.h" +#include "CameraCommands.h" +#include "EditorCore/Api/ISelectionApi.h" +#include "EditorCore/Api/ICameraApi.h" + +namespace Syn { + class CameraViewModel : public IViewModel { + public: + CameraViewModel(ISelectionApi* selectionApi, ICameraApi* cameraApi); + ~CameraViewModel() override = default; + + const CameraState& GetState() const override; + void SyncWithEngine() override; + void Dispatch(const CameraIntent& intent) override; + + private: + void HandleSetYaw(const SetCameraYawIntent& intent); + void HandleSetPitch(const SetCameraPitchIntent& intent); + void HandleSetNearPlane(const SetCameraNearPlaneIntent& intent); + void HandleSetFarPlane(const SetCameraFarPlaneIntent& intent); + void HandleSetFov(const SetCameraFovIntent& intent); + void HandleSetSpeed(const SetCameraSpeedIntent& intent); + void HandleSetSensitivity(const SetCameraSensitivityIntent& intent); + void HandleSetDistance(const SetCameraDistanceIntent& intent); + + private: + ISelectionApi* _selectionApi = nullptr; + ICameraApi* _cameraApi = nullptr; + CameraState _state; + + DragInteraction _yawDrag; + DragInteraction _pitchDrag; + DragInteraction _nearPlaneDrag; + DragInteraction _farPlaneDrag; + DragInteraction _fovDrag; + DragInteraction _speedDrag; + DragInteraction _sensitivityDrag; + DragInteraction _distanceDrag; + }; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/BoxCollider/BoxColliderCommands.cpp b/SynapseEngine/EditorCore/ViewModels/Component/Physics/BoxCollider/BoxColliderCommands.cpp new file mode 100644 index 00000000..57ac8f2b --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Physics/BoxCollider/BoxColliderCommands.cpp @@ -0,0 +1 @@ +#include "BoxColliderCommands.h" \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/BoxCollider/BoxColliderCommands.h b/SynapseEngine/EditorCore/ViewModels/Component/Physics/BoxCollider/BoxColliderCommands.h new file mode 100644 index 00000000..fc94f30f --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Physics/BoxCollider/BoxColliderCommands.h @@ -0,0 +1,10 @@ +#pragma once +#include "EditorCore/Command/ComponentChangeCommand.h" +#include "EditorCore/Api/IBoxColliderApi.h" +#include + +namespace Syn +{ + using ChangeBoxColliderHalfExtentsCommand = ComponentChangeCommand; + using ChangeBoxColliderLocalOffsetCommand = ComponentChangeCommand; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/BoxCollider/BoxColliderIntent.cpp b/SynapseEngine/EditorCore/ViewModels/Component/Physics/BoxCollider/BoxColliderIntent.cpp new file mode 100644 index 00000000..b828172a --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Physics/BoxCollider/BoxColliderIntent.cpp @@ -0,0 +1 @@ +#include "BoxColliderIntent.h" \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/BoxCollider/BoxColliderIntent.h b/SynapseEngine/EditorCore/ViewModels/Component/Physics/BoxCollider/BoxColliderIntent.h new file mode 100644 index 00000000..7c661fab --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Physics/BoxCollider/BoxColliderIntent.h @@ -0,0 +1,20 @@ +#pragma once +#include +#include + +namespace Syn +{ + struct SetBoxColliderHalfExtentsIntent { + glm::vec3 halfExtents; + bool isDragging; + }; + + struct SetBoxColliderLocalOffsetIntent { + glm::vec3 localOffset; + bool isDragging; + }; + + using BoxColliderIntent = std::variant< + SetBoxColliderHalfExtentsIntent, + SetBoxColliderLocalOffsetIntent>; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/BoxCollider/BoxColliderState.cpp b/SynapseEngine/EditorCore/ViewModels/Component/Physics/BoxCollider/BoxColliderState.cpp new file mode 100644 index 00000000..68bfb01d --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Physics/BoxCollider/BoxColliderState.cpp @@ -0,0 +1 @@ +#include "BoxColliderState.h" \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/BoxCollider/BoxColliderState.h b/SynapseEngine/EditorCore/ViewModels/Component/Physics/BoxCollider/BoxColliderState.h new file mode 100644 index 00000000..24d96d65 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Physics/BoxCollider/BoxColliderState.h @@ -0,0 +1,11 @@ +#pragma once +#include + +namespace Syn { + struct BoxColliderState { + bool hasComponent = false; + + glm::vec3 halfExtents; + glm::vec3 localOffset; + }; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/BoxCollider/BoxColliderViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/Component/Physics/BoxCollider/BoxColliderViewModel.cpp new file mode 100644 index 00000000..8a4f6469 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Physics/BoxCollider/BoxColliderViewModel.cpp @@ -0,0 +1,65 @@ +#include "BoxColliderViewModel.h" + +namespace Syn +{ + BoxColliderViewModel::BoxColliderViewModel(ISelectionApi* selectionApi, IBoxColliderApi* colliderApi) + : _selectionApi(selectionApi), _colliderApi(colliderApi) + {} + + const BoxColliderState& BoxColliderViewModel::GetState() const + { + return _state; + } + + void BoxColliderViewModel::SyncWithEngine() + { + if (!_selectionApi || !_colliderApi) + return; + + EntityID activeEntity = _selectionApi->GetSelectedEntity(); + + if (activeEntity != NULL_ENTITY && _colliderApi->HasBoxCollider(activeEntity)) + { + _state.hasComponent = true; + + if (!_halfExtentsDrag.IsDragging()) + _state.halfExtents = _colliderApi->GetBoxColliderHalfExtents(activeEntity); + if (!_localOffsetDrag.IsDragging()) + _state.localOffset = _colliderApi->GetBoxColliderLocalOffset(activeEntity); + } + else + { + _state.hasComponent = false; + } + } + + void BoxColliderViewModel::Dispatch(const BoxColliderIntent& intent) + { + std::visit([this](auto&& arg) + { + using T = std::decay_t; + + if constexpr (std::is_same_v) HandleSetHalfExtents(arg); + else if constexpr (std::is_same_v) HandleSetLocalOffset(arg); }, intent); + } + + void BoxColliderViewModel::HandleSetHalfExtents(const SetBoxColliderHalfExtentsIntent& intent) + { + EntityID activeEntity = _selectionApi->GetSelectedEntity(); + if (activeEntity == NULL_ENTITY) return; + + _halfExtentsDrag.Handle(intent.isDragging, intent.halfExtents, _state.halfExtents, + [&](const glm::vec3& v) { _colliderApi->SetBoxColliderHalfExtents(activeEntity, v); }, + [&](const glm::vec3& s, const glm::vec3& e) { return std::make_shared(_colliderApi, activeEntity, s, e); }); + } + + void BoxColliderViewModel::HandleSetLocalOffset(const SetBoxColliderLocalOffsetIntent& intent) + { + EntityID activeEntity = _selectionApi->GetSelectedEntity(); + if (activeEntity == NULL_ENTITY) return; + + _localOffsetDrag.Handle(intent.isDragging, intent.localOffset, _state.localOffset, + [&](const glm::vec3& v) { _colliderApi->SetBoxColliderLocalOffset(activeEntity, v); }, + [&](const glm::vec3& s, const glm::vec3& e) { return std::make_shared(_colliderApi, activeEntity, s, e); }); + } +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/BoxCollider/BoxColliderViewModel.h b/SynapseEngine/EditorCore/ViewModels/Component/Physics/BoxCollider/BoxColliderViewModel.h new file mode 100644 index 00000000..50e9fa37 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Physics/BoxCollider/BoxColliderViewModel.h @@ -0,0 +1,32 @@ +#pragma once +#include "EditorCore/ViewModels/IViewModel.h" +#include "EditorCore/Interaction/DragInteraction.h" +#include "BoxColliderState.h" +#include "BoxColliderIntent.h" +#include "BoxColliderCommands.h" +#include "EditorCore/Api/ISelectionApi.h" +#include "EditorCore/Api/IBoxColliderApi.h" + +namespace Syn { + class BoxColliderViewModel : public IViewModel { + public: + BoxColliderViewModel(ISelectionApi* selectionApi, IBoxColliderApi* colliderApi); + ~BoxColliderViewModel() override = default; + + const BoxColliderState& GetState() const override; + void SyncWithEngine() override; + void Dispatch(const BoxColliderIntent& intent) override; + + private: + void HandleSetHalfExtents(const SetBoxColliderHalfExtentsIntent& intent); + void HandleSetLocalOffset(const SetBoxColliderLocalOffsetIntent& intent); + + private: + ISelectionApi* _selectionApi = nullptr; + IBoxColliderApi* _colliderApi = nullptr; + BoxColliderState _state; + + DragInteraction _halfExtentsDrag; + DragInteraction _localOffsetDrag; + }; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/CapsuleCollider/CapsuleColliderCommands.cpp b/SynapseEngine/EditorCore/ViewModels/Component/Physics/CapsuleCollider/CapsuleColliderCommands.cpp new file mode 100644 index 00000000..31842310 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Physics/CapsuleCollider/CapsuleColliderCommands.cpp @@ -0,0 +1 @@ +#include "CapsuleColliderCommands.h" \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/CapsuleCollider/CapsuleColliderCommands.h b/SynapseEngine/EditorCore/ViewModels/Component/Physics/CapsuleCollider/CapsuleColliderCommands.h new file mode 100644 index 00000000..fdaed01a --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Physics/CapsuleCollider/CapsuleColliderCommands.h @@ -0,0 +1,11 @@ +#pragma once +#include "EditorCore/Command/ComponentChangeCommand.h" +#include "EditorCore/Api/ICapsuleColliderApi.h" +#include + +namespace Syn +{ + using ChangeCapsuleColliderRadiusCommand = ComponentChangeCommand; + using ChangeCapsuleColliderHalfHeightCommand = ComponentChangeCommand; + using ChangeCapsuleColliderLocalOffsetCommand = ComponentChangeCommand; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/CapsuleCollider/CapsuleColliderIntent.cpp b/SynapseEngine/EditorCore/ViewModels/Component/Physics/CapsuleCollider/CapsuleColliderIntent.cpp new file mode 100644 index 00000000..03d0c1db --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Physics/CapsuleCollider/CapsuleColliderIntent.cpp @@ -0,0 +1 @@ +#include "CapsuleColliderIntent.h" \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/CapsuleCollider/CapsuleColliderIntent.h b/SynapseEngine/EditorCore/ViewModels/Component/Physics/CapsuleCollider/CapsuleColliderIntent.h new file mode 100644 index 00000000..e4e8efc0 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Physics/CapsuleCollider/CapsuleColliderIntent.h @@ -0,0 +1,26 @@ +#pragma once +#include +#include + +namespace Syn +{ + struct SetCapsuleColliderRadiusIntent { + float radius; + bool isDragging; + }; + + struct SetCapsuleColliderHalfHeightIntent { + float halfHeight; + bool isDragging; + }; + + struct SetCapsuleColliderLocalOffsetIntent { + glm::vec3 localOffset; + bool isDragging; + }; + + using CapsuleColliderIntent = std::variant< + SetCapsuleColliderRadiusIntent, + SetCapsuleColliderHalfHeightIntent, + SetCapsuleColliderLocalOffsetIntent>; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/CapsuleCollider/CapsuleColliderState.cpp b/SynapseEngine/EditorCore/ViewModels/Component/Physics/CapsuleCollider/CapsuleColliderState.cpp new file mode 100644 index 00000000..2f21bedc --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Physics/CapsuleCollider/CapsuleColliderState.cpp @@ -0,0 +1 @@ +#include "CapsuleColliderState.h" \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/CapsuleCollider/CapsuleColliderState.h b/SynapseEngine/EditorCore/ViewModels/Component/Physics/CapsuleCollider/CapsuleColliderState.h new file mode 100644 index 00000000..fd19b987 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Physics/CapsuleCollider/CapsuleColliderState.h @@ -0,0 +1,12 @@ +#pragma once +#include + +namespace Syn { + struct CapsuleColliderState { + bool hasComponent = false; + + float radius; + float halfHeight; + glm::vec3 localOffset; + }; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/CapsuleCollider/CapsuleColliderViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/Component/Physics/CapsuleCollider/CapsuleColliderViewModel.cpp new file mode 100644 index 00000000..d2db61a6 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Physics/CapsuleCollider/CapsuleColliderViewModel.cpp @@ -0,0 +1,78 @@ +#include "CapsuleColliderViewModel.h" + +namespace Syn +{ + CapsuleColliderViewModel::CapsuleColliderViewModel(ISelectionApi* selectionApi, ICapsuleColliderApi* colliderApi) + : _selectionApi(selectionApi), _colliderApi(colliderApi) + {} + + const CapsuleColliderState& CapsuleColliderViewModel::GetState() const + { + return _state; + } + + void CapsuleColliderViewModel::SyncWithEngine() + { + if (!_selectionApi || !_colliderApi) + return; + + EntityID activeEntity = _selectionApi->GetSelectedEntity(); + + if (activeEntity != NULL_ENTITY && _colliderApi->HasCapsuleCollider(activeEntity)) + { + _state.hasComponent = true; + + if (!_radiusDrag.IsDragging()) + _state.radius = _colliderApi->GetCapsuleColliderRadius(activeEntity); + if (!_halfHeightDrag.IsDragging()) + _state.halfHeight = _colliderApi->GetCapsuleColliderHalfHeight(activeEntity); + if (!_localOffsetDrag.IsDragging()) + _state.localOffset = _colliderApi->GetCapsuleColliderLocalOffset(activeEntity); + } + else + { + _state.hasComponent = false; + } + } + + void CapsuleColliderViewModel::Dispatch(const CapsuleColliderIntent& intent) + { + std::visit([this](auto&& arg) + { + using T = std::decay_t; + + if constexpr (std::is_same_v) HandleSetRadius(arg); + else if constexpr (std::is_same_v) HandleSetHalfHeight(arg); + else if constexpr (std::is_same_v) HandleSetLocalOffset(arg); }, intent); + } + + void CapsuleColliderViewModel::HandleSetRadius(const SetCapsuleColliderRadiusIntent& intent) + { + EntityID activeEntity = _selectionApi->GetSelectedEntity(); + if (activeEntity == NULL_ENTITY) return; + + _radiusDrag.Handle(intent.isDragging, intent.radius, _state.radius, + [&](const float& v) { _colliderApi->SetCapsuleColliderRadius(activeEntity, v); }, + [&](const float& s, const float& e) { return std::make_shared(_colliderApi, activeEntity, s, e); }); + } + + void CapsuleColliderViewModel::HandleSetHalfHeight(const SetCapsuleColliderHalfHeightIntent& intent) + { + EntityID activeEntity = _selectionApi->GetSelectedEntity(); + if (activeEntity == NULL_ENTITY) return; + + _halfHeightDrag.Handle(intent.isDragging, intent.halfHeight, _state.halfHeight, + [&](const float& v) { _colliderApi->SetCapsuleColliderHalfHeight(activeEntity, v); }, + [&](const float& s, const float& e) { return std::make_shared(_colliderApi, activeEntity, s, e); }); + } + + void CapsuleColliderViewModel::HandleSetLocalOffset(const SetCapsuleColliderLocalOffsetIntent& intent) + { + EntityID activeEntity = _selectionApi->GetSelectedEntity(); + if (activeEntity == NULL_ENTITY) return; + + _localOffsetDrag.Handle(intent.isDragging, intent.localOffset, _state.localOffset, + [&](const glm::vec3& v) { _colliderApi->SetCapsuleColliderLocalOffset(activeEntity, v); }, + [&](const glm::vec3& s, const glm::vec3& e) { return std::make_shared(_colliderApi, activeEntity, s, e); }); + } +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/CapsuleCollider/CapsuleColliderViewModel.h b/SynapseEngine/EditorCore/ViewModels/Component/Physics/CapsuleCollider/CapsuleColliderViewModel.h new file mode 100644 index 00000000..780a85dd --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Physics/CapsuleCollider/CapsuleColliderViewModel.h @@ -0,0 +1,34 @@ +#pragma once +#include "EditorCore/ViewModels/IViewModel.h" +#include "EditorCore/Interaction/DragInteraction.h" +#include "CapsuleColliderState.h" +#include "CapsuleColliderIntent.h" +#include "CapsuleColliderCommands.h" +#include "EditorCore/Api/ISelectionApi.h" +#include "EditorCore/Api/ICapsuleColliderApi.h" + +namespace Syn { + class CapsuleColliderViewModel : public IViewModel { + public: + CapsuleColliderViewModel(ISelectionApi* selectionApi, ICapsuleColliderApi* colliderApi); + ~CapsuleColliderViewModel() override = default; + + const CapsuleColliderState& GetState() const override; + void SyncWithEngine() override; + void Dispatch(const CapsuleColliderIntent& intent) override; + + private: + void HandleSetRadius(const SetCapsuleColliderRadiusIntent& intent); + void HandleSetHalfHeight(const SetCapsuleColliderHalfHeightIntent& intent); + void HandleSetLocalOffset(const SetCapsuleColliderLocalOffsetIntent& intent); + + private: + ISelectionApi* _selectionApi = nullptr; + ICapsuleColliderApi* _colliderApi = nullptr; + CapsuleColliderState _state; + + DragInteraction _radiusDrag; + DragInteraction _halfHeightDrag; + DragInteraction _localOffsetDrag; + }; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/ConvexCollider/ConvexColliderCommands.cpp b/SynapseEngine/EditorCore/ViewModels/Component/Physics/ConvexCollider/ConvexColliderCommands.cpp new file mode 100644 index 00000000..2f662026 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Physics/ConvexCollider/ConvexColliderCommands.cpp @@ -0,0 +1 @@ +#include "ConvexColliderCommands.h" \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/ConvexCollider/ConvexColliderCommands.h b/SynapseEngine/EditorCore/ViewModels/Component/Physics/ConvexCollider/ConvexColliderCommands.h new file mode 100644 index 00000000..afe95a4a --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Physics/ConvexCollider/ConvexColliderCommands.h @@ -0,0 +1,10 @@ +#pragma once +#include "EditorCore/Command/ComponentChangeCommand.h" +#include "EditorCore/Api/IConvexColliderApi.h" +#include + +namespace Syn +{ + using ChangeConvexColliderLocalOffsetCommand = ComponentChangeCommand; + using ChangeConvexColliderTargetLodCommand = ComponentChangeCommand; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/ConvexCollider/ConvexColliderIntent.cpp b/SynapseEngine/EditorCore/ViewModels/Component/Physics/ConvexCollider/ConvexColliderIntent.cpp new file mode 100644 index 00000000..627ab102 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Physics/ConvexCollider/ConvexColliderIntent.cpp @@ -0,0 +1 @@ +#include "ConvexColliderIntent.h" \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/ConvexCollider/ConvexColliderIntent.h b/SynapseEngine/EditorCore/ViewModels/Component/Physics/ConvexCollider/ConvexColliderIntent.h new file mode 100644 index 00000000..94dd25a8 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Physics/ConvexCollider/ConvexColliderIntent.h @@ -0,0 +1,20 @@ +#pragma once +#include +#include +#include + +namespace Syn +{ + struct SetConvexColliderTargetLodLevelIntent { + uint32_t targetLodLevel; + }; + + struct SetConvexColliderLocalOffsetIntent { + glm::vec3 localOffset; + bool isDragging; + }; + + using ConvexColliderIntent = std::variant< + SetConvexColliderTargetLodLevelIntent, + SetConvexColliderLocalOffsetIntent>; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/ConvexCollider/ConvexColliderState.cpp b/SynapseEngine/EditorCore/ViewModels/Component/Physics/ConvexCollider/ConvexColliderState.cpp new file mode 100644 index 00000000..5df60a64 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Physics/ConvexCollider/ConvexColliderState.cpp @@ -0,0 +1 @@ +#include "ConvexColliderState.h" \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/ConvexCollider/ConvexColliderState.h b/SynapseEngine/EditorCore/ViewModels/Component/Physics/ConvexCollider/ConvexColliderState.h new file mode 100644 index 00000000..1b7bc0b0 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Physics/ConvexCollider/ConvexColliderState.h @@ -0,0 +1,12 @@ +#pragma once +#include +#include + +namespace Syn { + struct ConvexColliderState { + bool hasComponent = false; + + uint32_t targetLodLevel; + glm::vec3 localOffset; + }; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/ConvexCollider/ConvexColliderViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/Component/Physics/ConvexCollider/ConvexColliderViewModel.cpp new file mode 100644 index 00000000..ae12c53f --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Physics/ConvexCollider/ConvexColliderViewModel.cpp @@ -0,0 +1,64 @@ +#include "ConvexColliderViewModel.h" + +namespace Syn +{ + ConvexColliderViewModel::ConvexColliderViewModel(ISelectionApi* selectionApi, IConvexColliderApi* colliderApi) + : _selectionApi(selectionApi), _colliderApi(colliderApi) + {} + + const ConvexColliderState& ConvexColliderViewModel::GetState() const + { + return _state; + } + + void ConvexColliderViewModel::SyncWithEngine() + { + if (!_selectionApi || !_colliderApi) + return; + + EntityID activeEntity = _selectionApi->GetSelectedEntity(); + + if (activeEntity != NULL_ENTITY && _colliderApi->HasConvexCollider(activeEntity)) + { + _state.hasComponent = true; + + _state.targetLodLevel = _colliderApi->GetConvexColliderTargetLodLevel(activeEntity); + + if (!_localOffsetDrag.IsDragging()) + _state.localOffset = _colliderApi->GetConvexColliderLocalOffset(activeEntity); + } + else + { + _state.hasComponent = false; + } + } + + void ConvexColliderViewModel::Dispatch(const ConvexColliderIntent& intent) + { + std::visit([this](auto&& arg) + { + using T = std::decay_t; + + if constexpr (std::is_same_v) HandleSetTargetLodLevel(arg); + else if constexpr (std::is_same_v) HandleSetLocalOffset(arg); }, intent); + } + + void ConvexColliderViewModel::HandleSetTargetLodLevel(const SetConvexColliderTargetLodLevelIntent& intent) + { + EntityID activeEntity = _selectionApi->GetSelectedEntity(); + if (activeEntity == NULL_ENTITY) return; + + _state.targetLodLevel = intent.targetLodLevel; + _colliderApi->SetConvexColliderTargetLodLevel(activeEntity, intent.targetLodLevel); + } + + void ConvexColliderViewModel::HandleSetLocalOffset(const SetConvexColliderLocalOffsetIntent& intent) + { + EntityID activeEntity = _selectionApi->GetSelectedEntity(); + if (activeEntity == NULL_ENTITY) return; + + _localOffsetDrag.Handle(intent.isDragging, intent.localOffset, _state.localOffset, + [&](const glm::vec3& v) { _colliderApi->SetConvexColliderLocalOffset(activeEntity, v); }, + [&](const glm::vec3& s, const glm::vec3& e) { return std::make_shared(_colliderApi, activeEntity, s, e); }); + } +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/ConvexCollider/ConvexColliderViewModel.h b/SynapseEngine/EditorCore/ViewModels/Component/Physics/ConvexCollider/ConvexColliderViewModel.h new file mode 100644 index 00000000..b30b2d9e --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Physics/ConvexCollider/ConvexColliderViewModel.h @@ -0,0 +1,31 @@ +#pragma once +#include "EditorCore/ViewModels/IViewModel.h" +#include "EditorCore/Interaction/DragInteraction.h" +#include "ConvexColliderState.h" +#include "ConvexColliderIntent.h" +#include "ConvexColliderCommands.h" +#include "EditorCore/Api/ISelectionApi.h" +#include "EditorCore/Api/IConvexColliderApi.h" + +namespace Syn { + class ConvexColliderViewModel : public IViewModel { + public: + ConvexColliderViewModel(ISelectionApi* selectionApi, IConvexColliderApi* colliderApi); + ~ConvexColliderViewModel() override = default; + + const ConvexColliderState& GetState() const override; + void SyncWithEngine() override; + void Dispatch(const ConvexColliderIntent& intent) override; + + private: + void HandleSetTargetLodLevel(const SetConvexColliderTargetLodLevelIntent& intent); + void HandleSetLocalOffset(const SetConvexColliderLocalOffsetIntent& intent); + + private: + ISelectionApi* _selectionApi = nullptr; + IConvexColliderApi* _colliderApi = nullptr; + ConvexColliderState _state; + + DragInteraction _localOffsetDrag; + }; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/MeshCollider/MeshColliderCommands.cpp b/SynapseEngine/EditorCore/ViewModels/Component/Physics/MeshCollider/MeshColliderCommands.cpp new file mode 100644 index 00000000..60b570ed --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Physics/MeshCollider/MeshColliderCommands.cpp @@ -0,0 +1 @@ +#include "MeshColliderCommands.h" \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/MeshCollider/MeshColliderCommands.h b/SynapseEngine/EditorCore/ViewModels/Component/Physics/MeshCollider/MeshColliderCommands.h new file mode 100644 index 00000000..91e8ba59 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Physics/MeshCollider/MeshColliderCommands.h @@ -0,0 +1,10 @@ +#pragma once +#include "EditorCore/Command/ComponentChangeCommand.h" +#include "EditorCore/Api/IMeshColliderApi.h" +#include + +namespace Syn +{ + using ChangeMeshColliderLocalOffsetCommand = ComponentChangeCommand; + using ChangeMeshColliderTargetLodCommand = ComponentChangeCommand; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/MeshCollider/MeshColliderIntent.cpp b/SynapseEngine/EditorCore/ViewModels/Component/Physics/MeshCollider/MeshColliderIntent.cpp new file mode 100644 index 00000000..efa5bff1 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Physics/MeshCollider/MeshColliderIntent.cpp @@ -0,0 +1 @@ +#include "MeshColliderIntent.h" \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/MeshCollider/MeshColliderIntent.h b/SynapseEngine/EditorCore/ViewModels/Component/Physics/MeshCollider/MeshColliderIntent.h new file mode 100644 index 00000000..9a790c24 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Physics/MeshCollider/MeshColliderIntent.h @@ -0,0 +1,20 @@ +#pragma once +#include +#include +#include + +namespace Syn +{ + struct SetMeshColliderTargetLodLevelIntent { + uint32_t targetLodLevel; + }; + + struct SetMeshColliderLocalOffsetIntent { + glm::vec3 localOffset; + bool isDragging; + }; + + using MeshColliderIntent = std::variant< + SetMeshColliderTargetLodLevelIntent, + SetMeshColliderLocalOffsetIntent>; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/MeshCollider/MeshColliderState.cpp b/SynapseEngine/EditorCore/ViewModels/Component/Physics/MeshCollider/MeshColliderState.cpp new file mode 100644 index 00000000..eee2b409 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Physics/MeshCollider/MeshColliderState.cpp @@ -0,0 +1 @@ +#include "MeshColliderState.h" \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/MeshCollider/MeshColliderState.h b/SynapseEngine/EditorCore/ViewModels/Component/Physics/MeshCollider/MeshColliderState.h new file mode 100644 index 00000000..052e432c --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Physics/MeshCollider/MeshColliderState.h @@ -0,0 +1,12 @@ +#pragma once +#include +#include + +namespace Syn { + struct MeshColliderState { + bool hasComponent = false; + + uint32_t targetLodLevel; + glm::vec3 localOffset; + }; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/MeshCollider/MeshColliderViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/Component/Physics/MeshCollider/MeshColliderViewModel.cpp new file mode 100644 index 00000000..509866c6 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Physics/MeshCollider/MeshColliderViewModel.cpp @@ -0,0 +1,64 @@ +#include "MeshColliderViewModel.h" + +namespace Syn +{ + MeshColliderViewModel::MeshColliderViewModel(ISelectionApi* selectionApi, IMeshColliderApi* colliderApi) + : _selectionApi(selectionApi), _colliderApi(colliderApi) + {} + + const MeshColliderState& MeshColliderViewModel::GetState() const + { + return _state; + } + + void MeshColliderViewModel::SyncWithEngine() + { + if (!_selectionApi || !_colliderApi) + return; + + EntityID activeEntity = _selectionApi->GetSelectedEntity(); + + if (activeEntity != NULL_ENTITY && _colliderApi->HasMeshCollider(activeEntity)) + { + _state.hasComponent = true; + + _state.targetLodLevel = _colliderApi->GetMeshColliderTargetLodLevel(activeEntity); + + if (!_localOffsetDrag.IsDragging()) + _state.localOffset = _colliderApi->GetMeshColliderLocalOffset(activeEntity); + } + else + { + _state.hasComponent = false; + } + } + + void MeshColliderViewModel::Dispatch(const MeshColliderIntent& intent) + { + std::visit([this](auto&& arg) + { + using T = std::decay_t; + + if constexpr (std::is_same_v) HandleSetTargetLodLevel(arg); + else if constexpr (std::is_same_v) HandleSetLocalOffset(arg); }, intent); + } + + void MeshColliderViewModel::HandleSetTargetLodLevel(const SetMeshColliderTargetLodLevelIntent& intent) + { + EntityID activeEntity = _selectionApi->GetSelectedEntity(); + if (activeEntity == NULL_ENTITY) return; + + _state.targetLodLevel = intent.targetLodLevel; + _colliderApi->SetMeshColliderTargetLodLevel(activeEntity, intent.targetLodLevel); + } + + void MeshColliderViewModel::HandleSetLocalOffset(const SetMeshColliderLocalOffsetIntent& intent) + { + EntityID activeEntity = _selectionApi->GetSelectedEntity(); + if (activeEntity == NULL_ENTITY) return; + + _localOffsetDrag.Handle(intent.isDragging, intent.localOffset, _state.localOffset, + [&](const glm::vec3& v) { _colliderApi->SetMeshColliderLocalOffset(activeEntity, v); }, + [&](const glm::vec3& s, const glm::vec3& e) { return std::make_shared(_colliderApi, activeEntity, s, e); }); + } +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/MeshCollider/MeshColliderViewModel.h b/SynapseEngine/EditorCore/ViewModels/Component/Physics/MeshCollider/MeshColliderViewModel.h new file mode 100644 index 00000000..20b2a7f1 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Physics/MeshCollider/MeshColliderViewModel.h @@ -0,0 +1,31 @@ +#pragma once +#include "EditorCore/ViewModels/IViewModel.h" +#include "EditorCore/Interaction/DragInteraction.h" +#include "MeshColliderState.h" +#include "MeshColliderIntent.h" +#include "MeshColliderCommands.h" +#include "EditorCore/Api/ISelectionApi.h" +#include "EditorCore/Api/IMeshColliderApi.h" + +namespace Syn { + class MeshColliderViewModel : public IViewModel { + public: + MeshColliderViewModel(ISelectionApi* selectionApi, IMeshColliderApi* colliderApi); + ~MeshColliderViewModel() override = default; + + const MeshColliderState& GetState() const override; + void SyncWithEngine() override; + void Dispatch(const MeshColliderIntent& intent) override; + + private: + void HandleSetTargetLodLevel(const SetMeshColliderTargetLodLevelIntent& intent); + void HandleSetLocalOffset(const SetMeshColliderLocalOffsetIntent& intent); + + private: + ISelectionApi* _selectionApi = nullptr; + IMeshColliderApi* _colliderApi = nullptr; + MeshColliderState _state; + + DragInteraction _localOffsetDrag; + }; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/RigidBody/RigidBodyCommands.cpp b/SynapseEngine/EditorCore/ViewModels/Component/Physics/RigidBody/RigidBodyCommands.cpp new file mode 100644 index 00000000..66760059 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Physics/RigidBody/RigidBodyCommands.cpp @@ -0,0 +1 @@ +#include "RigidBodyCommands.h" \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/RigidBody/RigidBodyCommands.h b/SynapseEngine/EditorCore/ViewModels/Component/Physics/RigidBody/RigidBodyCommands.h new file mode 100644 index 00000000..412b1854 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Physics/RigidBody/RigidBodyCommands.h @@ -0,0 +1,10 @@ +#pragma once +#include "EditorCore/Command/ComponentChangeCommand.h" +#include "EditorCore/Api/IRigidBodyApi.h" + +namespace Syn +{ + using ChangeRigidBodyMassCommand = ComponentChangeCommand; + using ChangeRigidBodyFrictionCommand = ComponentChangeCommand; + using ChangeRigidBodyRestitutionCommand = ComponentChangeCommand; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/RigidBody/RigidBodyIntent.cpp b/SynapseEngine/EditorCore/ViewModels/Component/Physics/RigidBody/RigidBodyIntent.cpp new file mode 100644 index 00000000..46c8a1a2 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Physics/RigidBody/RigidBodyIntent.cpp @@ -0,0 +1 @@ +#include "RigidBodyIntent.h" \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/RigidBody/RigidBodyIntent.h b/SynapseEngine/EditorCore/ViewModels/Component/Physics/RigidBody/RigidBodyIntent.h new file mode 100644 index 00000000..3231eba9 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Physics/RigidBody/RigidBodyIntent.h @@ -0,0 +1,37 @@ +#pragma once +#include "Engine/Physics/PhysicsTypes.h" +#include +#include + +namespace Syn +{ + struct SetRigidBodyMotionTypeIntent { + PhysicsMotionType motionType; + }; + + struct SetRigidBodyMassIntent { + float mass; + bool isDragging; + }; + + struct SetRigidBodyFrictionIntent { + float friction; + bool isDragging; + }; + + struct SetRigidBodyRestitutionIntent { + float restitution; + bool isDragging; + }; + + struct SetRigidBodyLayerIntent { + uint32_t layer; + }; + + using RigidBodyIntent = std::variant< + SetRigidBodyMotionTypeIntent, + SetRigidBodyMassIntent, + SetRigidBodyFrictionIntent, + SetRigidBodyRestitutionIntent, + SetRigidBodyLayerIntent>; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/RigidBody/RigidBodyState.cpp b/SynapseEngine/EditorCore/ViewModels/Component/Physics/RigidBody/RigidBodyState.cpp new file mode 100644 index 00000000..5abe9de2 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Physics/RigidBody/RigidBodyState.cpp @@ -0,0 +1 @@ +#include "RigidBodyState.h" \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/RigidBody/RigidBodyState.h b/SynapseEngine/EditorCore/ViewModels/Component/Physics/RigidBody/RigidBodyState.h new file mode 100644 index 00000000..ae8e04f9 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Physics/RigidBody/RigidBodyState.h @@ -0,0 +1,15 @@ +#pragma once +#include "Engine/Physics/PhysicsTypes.h" +#include + +namespace Syn { + struct RigidBodyState { + bool hasComponent = false; + + PhysicsMotionType motionType; + float mass; + float friction; + float restitution; + uint32_t layer; + }; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/RigidBody/RigidBodyViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/Component/Physics/RigidBody/RigidBodyViewModel.cpp new file mode 100644 index 00000000..9816d7fb --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Physics/RigidBody/RigidBodyViewModel.cpp @@ -0,0 +1,101 @@ +#include "RigidBodyViewModel.h" + +namespace Syn +{ + RigidBodyViewModel::RigidBodyViewModel(ISelectionApi* selectionApi, IRigidBodyApi* rigidBodyApi) + : _selectionApi(selectionApi), _rigidBodyApi(rigidBodyApi) + {} + + const RigidBodyState& RigidBodyViewModel::GetState() const + { + return _state; + } + + void RigidBodyViewModel::SyncWithEngine() + { + if (!_selectionApi || !_rigidBodyApi) + return; + + EntityID activeEntity = _selectionApi->GetSelectedEntity(); + + if (activeEntity != NULL_ENTITY && _rigidBodyApi->HasRigidBody(activeEntity)) + { + _state.hasComponent = true; + + _state.motionType = _rigidBodyApi->GetRigidBodyMotionType(activeEntity); + _state.layer = _rigidBodyApi->GetRigidBodyLayer(activeEntity); + + if (!_massDrag.IsDragging()) + _state.mass = _rigidBodyApi->GetRigidBodyMass(activeEntity); + if (!_frictionDrag.IsDragging()) + _state.friction = _rigidBodyApi->GetRigidBodyFriction(activeEntity); + if (!_restitutionDrag.IsDragging()) + _state.restitution = _rigidBodyApi->GetRigidBodyRestitution(activeEntity); + } + else + { + _state.hasComponent = false; + } + } + + void RigidBodyViewModel::Dispatch(const RigidBodyIntent& intent) + { + std::visit([this](auto&& arg) + { + using T = std::decay_t; + + if constexpr (std::is_same_v) HandleSetMotionType(arg); + else if constexpr (std::is_same_v) HandleSetMass(arg); + else if constexpr (std::is_same_v) HandleSetFriction(arg); + else if constexpr (std::is_same_v) HandleSetRestitution(arg); + else if constexpr (std::is_same_v) HandleSetLayer(arg); }, intent); + } + + void RigidBodyViewModel::HandleSetMotionType(const SetRigidBodyMotionTypeIntent& intent) + { + EntityID activeEntity = _selectionApi->GetSelectedEntity(); + if (activeEntity == NULL_ENTITY) return; + + _state.motionType = intent.motionType; + _rigidBodyApi->SetRigidBodyMotionType(activeEntity, intent.motionType); + } + + void RigidBodyViewModel::HandleSetLayer(const SetRigidBodyLayerIntent& intent) + { + EntityID activeEntity = _selectionApi->GetSelectedEntity(); + if (activeEntity == NULL_ENTITY) return; + + _state.layer = intent.layer; + _rigidBodyApi->SetRigidBodyLayer(activeEntity, intent.layer); + } + + void RigidBodyViewModel::HandleSetMass(const SetRigidBodyMassIntent& intent) + { + EntityID activeEntity = _selectionApi->GetSelectedEntity(); + if (activeEntity == NULL_ENTITY) return; + + _massDrag.Handle(intent.isDragging, intent.mass, _state.mass, + [&](const float& v) { _rigidBodyApi->SetRigidBodyMass(activeEntity, v); }, + [&](const float& s, const float& e) { return std::make_shared(_rigidBodyApi, activeEntity, s, e); }); + } + + void RigidBodyViewModel::HandleSetFriction(const SetRigidBodyFrictionIntent& intent) + { + EntityID activeEntity = _selectionApi->GetSelectedEntity(); + if (activeEntity == NULL_ENTITY) return; + + _frictionDrag.Handle(intent.isDragging, intent.friction, _state.friction, + [&](const float& v) { _rigidBodyApi->SetRigidBodyFriction(activeEntity, v); }, + [&](const float& s, const float& e) { return std::make_shared(_rigidBodyApi, activeEntity, s, e); }); + } + + void RigidBodyViewModel::HandleSetRestitution(const SetRigidBodyRestitutionIntent& intent) + { + EntityID activeEntity = _selectionApi->GetSelectedEntity(); + if (activeEntity == NULL_ENTITY) return; + + _restitutionDrag.Handle(intent.isDragging, intent.restitution, _state.restitution, + [&](const float& v) { _rigidBodyApi->SetRigidBodyRestitution(activeEntity, v); }, + [&](const float& s, const float& e) { return std::make_shared(_rigidBodyApi, activeEntity, s, e); }); + } +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/RigidBody/RigidBodyViewModel.h b/SynapseEngine/EditorCore/ViewModels/Component/Physics/RigidBody/RigidBodyViewModel.h new file mode 100644 index 00000000..529408b0 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Physics/RigidBody/RigidBodyViewModel.h @@ -0,0 +1,36 @@ +#pragma once +#include "EditorCore/ViewModels/IViewModel.h" +#include "EditorCore/Interaction/DragInteraction.h" +#include "RigidBodyState.h" +#include "RigidBodyIntent.h" +#include "RigidBodyCommands.h" +#include "EditorCore/Api/ISelectionApi.h" +#include "EditorCore/Api/IRigidBodyApi.h" + +namespace Syn { + class RigidBodyViewModel : public IViewModel { + public: + RigidBodyViewModel(ISelectionApi* selectionApi, IRigidBodyApi* rigidBodyApi); + ~RigidBodyViewModel() override = default; + + const RigidBodyState& GetState() const override; + void SyncWithEngine() override; + void Dispatch(const RigidBodyIntent& intent) override; + + private: + void HandleSetMotionType(const SetRigidBodyMotionTypeIntent& intent); + void HandleSetMass(const SetRigidBodyMassIntent& intent); + void HandleSetFriction(const SetRigidBodyFrictionIntent& intent); + void HandleSetRestitution(const SetRigidBodyRestitutionIntent& intent); + void HandleSetLayer(const SetRigidBodyLayerIntent& intent); + + private: + ISelectionApi* _selectionApi = nullptr; + IRigidBodyApi* _rigidBodyApi = nullptr; + RigidBodyState _state; + + DragInteraction _massDrag; + DragInteraction _frictionDrag; + DragInteraction _restitutionDrag; + }; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/SphereCollider/SphereColliderCommands.cpp b/SynapseEngine/EditorCore/ViewModels/Component/Physics/SphereCollider/SphereColliderCommands.cpp new file mode 100644 index 00000000..e18db9f8 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Physics/SphereCollider/SphereColliderCommands.cpp @@ -0,0 +1 @@ +#include "SphereColliderCommands.h" \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/SphereCollider/SphereColliderCommands.h b/SynapseEngine/EditorCore/ViewModels/Component/Physics/SphereCollider/SphereColliderCommands.h new file mode 100644 index 00000000..642f66df --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Physics/SphereCollider/SphereColliderCommands.h @@ -0,0 +1,10 @@ +#pragma once +#include "EditorCore/Command/ComponentChangeCommand.h" +#include "EditorCore/Api/ISphereColliderApi.h" +#include + +namespace Syn +{ + using ChangeSphereColliderRadiusCommand = ComponentChangeCommand; + using ChangeSphereColliderLocalOffsetCommand = ComponentChangeCommand; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/SphereCollider/SphereColliderIntent.cpp b/SynapseEngine/EditorCore/ViewModels/Component/Physics/SphereCollider/SphereColliderIntent.cpp new file mode 100644 index 00000000..9f0c196d --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Physics/SphereCollider/SphereColliderIntent.cpp @@ -0,0 +1 @@ +#include "SphereColliderIntent.h" \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/SphereCollider/SphereColliderIntent.h b/SynapseEngine/EditorCore/ViewModels/Component/Physics/SphereCollider/SphereColliderIntent.h new file mode 100644 index 00000000..f38509e0 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Physics/SphereCollider/SphereColliderIntent.h @@ -0,0 +1,20 @@ +#pragma once +#include +#include + +namespace Syn +{ + struct SetSphereColliderRadiusIntent { + float radius; + bool isDragging; + }; + + struct SetSphereColliderLocalOffsetIntent { + glm::vec3 localOffset; + bool isDragging; + }; + + using SphereColliderIntent = std::variant< + SetSphereColliderRadiusIntent, + SetSphereColliderLocalOffsetIntent>; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/SphereCollider/SphereColliderState.cpp b/SynapseEngine/EditorCore/ViewModels/Component/Physics/SphereCollider/SphereColliderState.cpp new file mode 100644 index 00000000..967632f7 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Physics/SphereCollider/SphereColliderState.cpp @@ -0,0 +1 @@ +#include "SphereColliderState.h" \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/SphereCollider/SphereColliderState.h b/SynapseEngine/EditorCore/ViewModels/Component/Physics/SphereCollider/SphereColliderState.h new file mode 100644 index 00000000..8b5e2593 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Physics/SphereCollider/SphereColliderState.h @@ -0,0 +1,11 @@ +#pragma once +#include + +namespace Syn { + struct SphereColliderState { + bool hasComponent = false; + + float radius; + glm::vec3 localOffset; + }; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/SphereCollider/SphereColliderViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/Component/Physics/SphereCollider/SphereColliderViewModel.cpp new file mode 100644 index 00000000..7145e681 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Physics/SphereCollider/SphereColliderViewModel.cpp @@ -0,0 +1,65 @@ +#include "SphereColliderViewModel.h" + +namespace Syn +{ + SphereColliderViewModel::SphereColliderViewModel(ISelectionApi* selectionApi, ISphereColliderApi* colliderApi) + : _selectionApi(selectionApi), _colliderApi(colliderApi) + {} + + const SphereColliderState& SphereColliderViewModel::GetState() const + { + return _state; + } + + void SphereColliderViewModel::SyncWithEngine() + { + if (!_selectionApi || !_colliderApi) + return; + + EntityID activeEntity = _selectionApi->GetSelectedEntity(); + + if (activeEntity != NULL_ENTITY && _colliderApi->HasSphereCollider(activeEntity)) + { + _state.hasComponent = true; + + if (!_radiusDrag.IsDragging()) + _state.radius = _colliderApi->GetSphereColliderRadius(activeEntity); + if (!_localOffsetDrag.IsDragging()) + _state.localOffset = _colliderApi->GetSphereColliderLocalOffset(activeEntity); + } + else + { + _state.hasComponent = false; + } + } + + void SphereColliderViewModel::Dispatch(const SphereColliderIntent& intent) + { + std::visit([this](auto&& arg) + { + using T = std::decay_t; + + if constexpr (std::is_same_v) HandleSetRadius(arg); + else if constexpr (std::is_same_v) HandleSetLocalOffset(arg); }, intent); + } + + void SphereColliderViewModel::HandleSetRadius(const SetSphereColliderRadiusIntent& intent) + { + EntityID activeEntity = _selectionApi->GetSelectedEntity(); + if (activeEntity == NULL_ENTITY) return; + + _radiusDrag.Handle(intent.isDragging, intent.radius, _state.radius, + [&](const float& v) { _colliderApi->SetSphereColliderRadius(activeEntity, v); }, + [&](const float& s, const float& e) { return std::make_shared(_colliderApi, activeEntity, s, e); }); + } + + void SphereColliderViewModel::HandleSetLocalOffset(const SetSphereColliderLocalOffsetIntent& intent) + { + EntityID activeEntity = _selectionApi->GetSelectedEntity(); + if (activeEntity == NULL_ENTITY) return; + + _localOffsetDrag.Handle(intent.isDragging, intent.localOffset, _state.localOffset, + [&](const glm::vec3& v) { _colliderApi->SetSphereColliderLocalOffset(activeEntity, v); }, + [&](const glm::vec3& s, const glm::vec3& e) { return std::make_shared(_colliderApi, activeEntity, s, e); }); + } +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/SphereCollider/SphereColliderViewModel.h b/SynapseEngine/EditorCore/ViewModels/Component/Physics/SphereCollider/SphereColliderViewModel.h new file mode 100644 index 00000000..000f489f --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Physics/SphereCollider/SphereColliderViewModel.h @@ -0,0 +1,32 @@ +#pragma once +#include "EditorCore/ViewModels/IViewModel.h" +#include "EditorCore/Interaction/DragInteraction.h" +#include "SphereColliderState.h" +#include "SphereColliderIntent.h" +#include "SphereColliderCommands.h" +#include "EditorCore/Api/ISelectionApi.h" +#include "EditorCore/Api/ISphereColliderApi.h" + +namespace Syn { + class SphereColliderViewModel : public IViewModel { + public: + SphereColliderViewModel(ISelectionApi* selectionApi, ISphereColliderApi* colliderApi); + ~SphereColliderViewModel() override = default; + + const SphereColliderState& GetState() const override; + void SyncWithEngine() override; + void Dispatch(const SphereColliderIntent& intent) override; + + private: + void HandleSetRadius(const SetSphereColliderRadiusIntent& intent); + void HandleSetLocalOffset(const SetSphereColliderLocalOffsetIntent& intent); + + private: + ISelectionApi* _selectionApi = nullptr; + ISphereColliderApi* _colliderApi = nullptr; + SphereColliderState _state; + + DragInteraction _radiusDrag; + DragInteraction _localOffsetDrag; + }; +} \ No newline at end of file diff --git a/SynapseEngine/Engine/Scene/DrawData/DirectionLightShadowDrawGroup.h b/SynapseEngine/Engine/Scene/DrawData/DirectionLightShadowDrawGroup.h index 57a7c206..6e695e75 100644 --- a/SynapseEngine/Engine/Scene/DrawData/DirectionLightShadowDrawGroup.h +++ b/SynapseEngine/Engine/Scene/DrawData/DirectionLightShadowDrawGroup.h @@ -17,8 +17,8 @@ namespace Syn constexpr uint32_t CASCADES_PER_LIGHT = 4; constexpr uint32_t SHADOW_MULTIPLIER = MAX_DIR_LIGHTS * CASCADES_PER_LIGHT; - constexpr uint32_t SHADOW_ATLAS_SIZE = 2048; - constexpr uint32_t SHADOW_MIN_BLOCK_SIZE = 1024; + constexpr uint32_t SHADOW_ATLAS_SIZE = 4096; + constexpr uint32_t SHADOW_MIN_BLOCK_SIZE = 2048; constexpr uint32_t SHADOW_GRID_SIZE = SHADOW_ATLAS_SIZE / SHADOW_MIN_BLOCK_SIZE; constexpr uint32_t SHADOW_HIZ_MIP_LEVELS = std::countr_zero(SHADOW_MIN_BLOCK_SIZE) + 1; From 2076bc036274cdce7c9f8cef94c87a3e4af83210 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20P=C3=A9ter?= Date: Thu, 2 Jul 2026 09:27:53 +0200 Subject: [PATCH 14/51] Resolved async system and manager bugs, resolved image manager image descriptor buffer bug --- .../Component/Rendering/ModelComponent.cpp | 1 + .../Component/Rendering/ModelComponent.h | 1 + SynapseEngine/Engine/Image/ImageManager.cpp | 40 +++++- SynapseEngine/Engine/Image/ImageManager.h | 11 ++ .../Engine/Manager/BaseResourceManager.h | 23 +++- .../Engine/Manager/ResourceManager.cpp | 2 +- .../Engine/Material/MaterialManager.cpp | 14 +- .../Engine/Material/MaterialManager.h | 2 - .../Converter/DefaultCpuModelExtractor.cpp | 2 +- .../Passes/Setup/GlobalFrameSetupPass.cpp | 2 + .../Engine/Render/RendererFactory.cpp | 2 +- .../Engine/Scene/DrawData/ModelDrawGroup.cpp | 2 +- SynapseEngine/Engine/Scene/Scene.cpp | 32 +++++ SynapseEngine/Engine/Scene/Scene.h | 5 + .../Source/Procedural/TestSceneSource.cpp | 2 +- .../Scene/Source/Procedural/test_config.json | 8 +- .../System/Core/StaticSpatialSahSystem.cpp | 8 +- .../DirectionLightShadowCullingSystem.cpp | 6 +- .../Point/PointLightShadowCullingSystem.cpp | 6 +- .../Spot/SpotLightShadowCullingSystem.cpp | 6 +- .../System/Physics/ConvexColliderSystem.cpp | 6 +- .../System/Physics/MeshColliderSystem.cpp | 6 +- .../Engine/System/Physics/RigidBodySystem.cpp | 7 +- .../System/Rendering/AnimationSystem.cpp | 5 +- .../System/Rendering/MaterialSystem.cpp | 94 +++++++++++--- .../Rendering/ModelFrustumCullingSystem.cpp | 21 +-- .../Engine/System/Rendering/ModelSystem.cpp | 25 +++- .../Engine/System/Rendering/RenderSystem.cpp | 35 +++-- SynapseEngine/Engine/System/SystemContext.cpp | 1 + SynapseEngine/Engine/System/SystemContext.h | 22 ++++ .../Engine/Vk/Descriptor/DescriptorBuffer.cpp | 75 +++++++++-- .../Engine/Vk/Descriptor/DescriptorBuffer.h | 19 ++- SynapseEngine/imgui.ini | 121 ++++++++++++++++++ 33 files changed, 487 insertions(+), 125 deletions(-) create mode 100644 SynapseEngine/Engine/System/SystemContext.cpp create mode 100644 SynapseEngine/Engine/System/SystemContext.h create mode 100644 SynapseEngine/imgui.ini diff --git a/SynapseEngine/Engine/Component/Rendering/ModelComponent.cpp b/SynapseEngine/Engine/Component/Rendering/ModelComponent.cpp index 05adb0d6..1cbbc97c 100644 --- a/SynapseEngine/Engine/Component/Rendering/ModelComponent.cpp +++ b/SynapseEngine/Engine/Component/Rendering/ModelComponent.cpp @@ -3,6 +3,7 @@ namespace Syn { ModelComponent::ModelComponent() : + isReady(false), castShadow(true), receiveShadow(true), hasDirectxNormals(false), diff --git a/SynapseEngine/Engine/Component/Rendering/ModelComponent.h b/SynapseEngine/Engine/Component/Rendering/ModelComponent.h index a72814d3..e3f9845c 100644 --- a/SynapseEngine/Engine/Component/Rendering/ModelComponent.h +++ b/SynapseEngine/Engine/Component/Rendering/ModelComponent.h @@ -8,6 +8,7 @@ namespace Syn struct SYN_API ModelComponent : public Component { ModelComponent(); + bool isReady; bool castShadow; bool receiveShadow; diff --git a/SynapseEngine/Engine/Image/ImageManager.cpp b/SynapseEngine/Engine/Image/ImageManager.cpp index a1bc14a8..6d1a9b2c 100644 --- a/SynapseEngine/Engine/Image/ImageManager.cpp +++ b/SynapseEngine/Engine/Image/ImageManager.cpp @@ -13,10 +13,13 @@ namespace Syn { ImageManager::ImageManager( + uint32_t framesInFlight, std::shared_ptr builder, std::unique_ptr uploader, std::unique_ptr cpuExtractor) - : _builder(builder), + : + _framesInFlight(framesInFlight), + _builder(builder), _uploader(std::move(uploader)), _cpuExtractor(std::move(cpuExtractor)) { @@ -45,6 +48,41 @@ namespace Syn { LoadDefaultImageSync(); } + void ImageManager::Update() { + BaseResourceManager::Update(); + + std::lock_guard lock(_staleMutex); + + for (auto it = _staleGpuBuffers.begin(); it != _staleGpuBuffers.end();) { + if (it->framesToLive > 0) { + it->framesToLive--; + ++it; + } + else { + it = _staleGpuBuffers.erase(it); + } + } + + for (auto it = _staleMappedBuffers.begin(); it != _staleMappedBuffers.end();) { + if (it->framesToLive > 0) { + it->framesToLive--; + ++it; + } + else { + it = _staleMappedBuffers.erase(it); + } + } + } + + void ImageManager::RecordSync(VkCommandBuffer cmd) { + if (auto staleBuffers = _bindlessBuffer->RecordSync(cmd); staleBuffers.mapped || staleBuffers.gpu) { + std::lock_guard lock(_staleMutex); + + _staleMappedBuffers.push_back({ staleBuffers.mapped, _framesInFlight }); + _staleGpuBuffers.push_back({ staleBuffers.gpu, _framesInFlight }); + } + } + void ImageManager::CreateSamplers() { { // Linear Repeat diff --git a/SynapseEngine/Engine/Image/ImageManager.h b/SynapseEngine/Engine/Image/ImageManager.h index 71f374e4..23e076e4 100644 --- a/SynapseEngine/Engine/Image/ImageManager.h +++ b/SynapseEngine/Engine/Image/ImageManager.h @@ -11,6 +11,8 @@ #include "Engine/Vk/Image/Sampler.h" #include "Engine/Vk/Descriptor/DescriptorPool.h" +#include "Engine/Manager/AddressResourceManager.h" + namespace Syn { using ImageSourceFactory = std::function()>; @@ -23,12 +25,16 @@ namespace Syn { static constexpr uint32_t BINDING_TEXTURES = 1; ImageManager( + uint32_t framesInFlight, std::shared_ptr builder, std::unique_ptr uploader, std::unique_ptr cpuExtractor); ~ImageManager(); + void Update() override; + void RecordSync(VkCommandBuffer cmd); + uint32_t LoadImageAsync(const std::string& filePath); uint32_t LoadImageFromSourceAsync(const std::string& name, ImageSourceFactory factory); @@ -51,6 +57,11 @@ namespace Syn { std::shared_ptr _builder; std::unique_ptr _uploader; std::unique_ptr _cpuExtractor; + + uint32_t _framesInFlight; + std::mutex _staleMutex; + std::vector _staleGpuBuffers; + std::vector _staleMappedBuffers; VkDescriptorSetLayout _bindlessLayout = VK_NULL_HANDLE; std::unique_ptr _bindlessBuffer; diff --git a/SynapseEngine/Engine/Manager/BaseResourceManager.h b/SynapseEngine/Engine/Manager/BaseResourceManager.h index e03535c6..fa1bfee7 100644 --- a/SynapseEngine/Engine/Manager/BaseResourceManager.h +++ b/SynapseEngine/Engine/Manager/BaseResourceManager.h @@ -52,6 +52,11 @@ namespace Syn { ResourceState state; }; + struct SnapshotResult { + std::vector snapshots; + uint32_t version; + }; + virtual ~BaseResourceManager() = default; virtual void Update(); @@ -64,8 +69,10 @@ namespace Syn { uint32_t GetResourceIndex(const std::string & name) const; std::shared_ptr GetResource(uint32_t id) const; std::shared_ptr GetResource(const std::string & name) const; - std::vector GetResourceSnapshot() const; std::vector GetResourcePaths() const; + + SnapshotResult GetSnapshotAndVersion() const; + std::vector GetResourceSnapshot() const; protected: uint32_t InternalLoadAsync(const std::string & key, std::function()> task); uint32_t InternalLoadSync(const std::string & key, std::function()> task); @@ -222,6 +229,20 @@ namespace Syn { return snapshot; } + template + typename BaseResourceManager::SnapshotResult BaseResourceManager::GetSnapshotAndVersion() const { + std::lock_guard lock(_mutex); + + std::vector snapshots; + snapshots.reserve(_entries.size()); + + for (const auto& entry : _entries) { + snapshots.push_back({ entry.resource, entry.state }); + } + + return { std::move(snapshots), _version.load(std::memory_order_acquire) }; + } + template std::vector BaseResourceManager::GetResourcePaths() const { std::lock_guard lock(_mutex); diff --git a/SynapseEngine/Engine/Manager/ResourceManager.cpp b/SynapseEngine/Engine/Manager/ResourceManager.cpp index ee0e1f2e..431f5309 100644 --- a/SynapseEngine/Engine/Manager/ResourceManager.cpp +++ b/SynapseEngine/Engine/Manager/ResourceManager.cpp @@ -73,6 +73,7 @@ namespace Syn { ServiceLocator::ProvideImageBuilder(_imageBuilder.get()); _imageManager = std::make_unique( + _framesInFlight, _imageBuilder, std::make_unique(), std::make_unique() @@ -87,7 +88,6 @@ namespace Syn { _framesInFlight, [this](const TexturePayload& payload) -> uint32_t { if (payload.IsEmbedded()) { - size_t hash = payload.embeddedData.size(); if (hash > 0) { diff --git a/SynapseEngine/Engine/Material/MaterialManager.cpp b/SynapseEngine/Engine/Material/MaterialManager.cpp index 61ee4f81..89e49c07 100644 --- a/SynapseEngine/Engine/Material/MaterialManager.cpp +++ b/SynapseEngine/Engine/Material/MaterialManager.cpp @@ -59,22 +59,10 @@ namespace Syn { auto materialGPU = GpuMaterial(*entry.resource); WriteAddress(entryIndex, materialGPU); - if (entryIndex >= _renderTypeCache.size()) { - _renderTypeCache.resize(entryIndex + 1, MaterialRenderType::Opaque1Sided); - } - - bool isTrans = entry.resource->isTransparent; - bool isDouble = entry.resource->doubleSided; - - if (isTrans && isDouble) _renderTypeCache[entryIndex] = MaterialRenderType::Transparent2Sided; - else if (isTrans && !isDouble) _renderTypeCache[entryIndex] = MaterialRenderType::Transparent1Sided; - else if (!isTrans && isDouble) _renderTypeCache[entryIndex] = MaterialRenderType::Opaque2Sided; - else _renderTypeCache[entryIndex] = MaterialRenderType::Opaque1Sided; - entry.state = ResourceState::Ready; _version.fetch_add(1, std::memory_order_release); - //Info("Material '{}' is ready", entry.path); + Info("Material '{}' is ready", entry.path); } void MaterialManager::LoadDefaultMaterialSync() diff --git a/SynapseEngine/Engine/Material/MaterialManager.h b/SynapseEngine/Engine/Material/MaterialManager.h index ab67cca1..365942e1 100644 --- a/SynapseEngine/Engine/Material/MaterialManager.h +++ b/SynapseEngine/Engine/Material/MaterialManager.h @@ -16,7 +16,6 @@ namespace Syn { uint32_t LoadMaterial(const std::string& name, const MaterialInfo& info); uint32_t LoadMaterialDirect(const std::string& name, const Material& material); - std::span GetRenderTypeSnapshot() const { return _renderTypeCache; } protected: void StartGpuUpload(EntryType& entry) override; void FinalizeResource(EntryType& entry) override; @@ -24,6 +23,5 @@ namespace Syn { void LoadDefaultMaterialSync(); private: TextureLoadCallback _textureLoadCallback; - std::vector _renderTypeCache; }; } \ No newline at end of file diff --git a/SynapseEngine/Engine/Mesh/Converter/DefaultCpuModelExtractor.cpp b/SynapseEngine/Engine/Mesh/Converter/DefaultCpuModelExtractor.cpp index 02bd6f82..111fef73 100644 --- a/SynapseEngine/Engine/Mesh/Converter/DefaultCpuModelExtractor.cpp +++ b/SynapseEngine/Engine/Mesh/Converter/DefaultCpuModelExtractor.cpp @@ -42,7 +42,7 @@ namespace Syn blueprint.meshletCmd.groupCountY = groupCountY; blueprint.meshletCmd.groupCountZ = 1; - blueprint.isMeshletPipeline = true ? MeshDrawBlueprint::PIPELINE_MESHLET : MeshDrawBlueprint::PIPELINE_TRADITIONAL; + blueprint.isMeshletPipeline = rand() % 2 == 0 ? MeshDrawBlueprint::PIPELINE_MESHLET : MeshDrawBlueprint::PIPELINE_TRADITIONAL; outCpuData.baseDrawCommands.push_back(blueprint); } diff --git a/SynapseEngine/Engine/Render/Passes/Setup/GlobalFrameSetupPass.cpp b/SynapseEngine/Engine/Render/Passes/Setup/GlobalFrameSetupPass.cpp index 32083019..98b0e2ec 100644 --- a/SynapseEngine/Engine/Render/Passes/Setup/GlobalFrameSetupPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Setup/GlobalFrameSetupPass.cpp @@ -5,6 +5,7 @@ #include "Engine/Material/MaterialManager.h" #include "Engine/Mesh/ModelManager.h" #include "Engine/Animation/AnimationManager.h" +#include "Engine/Image/ImageManager.h" #include "Engine/Render/RenderNames.h" #include "Engine/Render/ComputeGroupSize.h" #include "Engine/Scene/BufferNames.h" @@ -280,5 +281,6 @@ namespace Syn { ServiceLocator::GetAnimationManager()->RecordSync(context.cmd); ServiceLocator::GetModelManager()->RecordSync(context.cmd); ServiceLocator::GetMaterialManager()->RecordSync(context.cmd); + ServiceLocator::GetImageManager()->RecordSync(context.cmd); } } \ No newline at end of file diff --git a/SynapseEngine/Engine/Render/RendererFactory.cpp b/SynapseEngine/Engine/Render/RendererFactory.cpp index 365c47f9..e0b09139 100644 --- a/SynapseEngine/Engine/Render/RendererFactory.cpp +++ b/SynapseEngine/Engine/Render/RendererFactory.cpp @@ -297,7 +297,7 @@ namespace Syn */ pipeline->AddPass(std::make_unique()); - + //Ssao Passes pipeline->AddPass(std::make_unique()); pipeline->AddPass(std::make_unique()); diff --git a/SynapseEngine/Engine/Scene/DrawData/ModelDrawGroup.cpp b/SynapseEngine/Engine/Scene/DrawData/ModelDrawGroup.cpp index 919092ce..d0be99da 100644 --- a/SynapseEngine/Engine/Scene/DrawData/ModelDrawGroup.cpp +++ b/SynapseEngine/Engine/Scene/DrawData/ModelDrawGroup.cpp @@ -38,7 +38,7 @@ namespace Syn meshAllocBuffer.Initialize({ BufferStrategy::Hybrid, frameCount, sizeof(MeshAllocationInfo), storageUsage, 1024, 2048 }); meshAllocBuffer.UpdateCapacityAll(1); - materialIndexBuffer.Initialize({ BufferStrategy::Hybrid, frameCount, sizeof(int32_t), storageUsage, 1024, 2048 }); + materialIndexBuffer.Initialize({ BufferStrategy::Hybrid, frameCount, sizeof(int32_t), storageUsage, 4096, 8192 }); materialIndexBuffer.UpdateCapacityAll(1); drawCountBuffer.Initialize({ BufferStrategy::MappedOnly, frameCount, sizeof(uint32_t), indirectStorageUsage, 1, 1 }); diff --git a/SynapseEngine/Engine/Scene/Scene.cpp b/SynapseEngine/Engine/Scene/Scene.cpp index 11f03bc1..8da0f2ab 100644 --- a/SynapseEngine/Engine/Scene/Scene.cpp +++ b/SynapseEngine/Engine/Scene/Scene.cpp @@ -490,6 +490,38 @@ namespace Syn _currentFrameIndex = frameIndex; _currentDeltaTime = deltaTime; + auto modelSnapshot = ServiceLocator::GetModelManager()->GetSnapshotAndVersion(); + auto animSnapshot = ServiceLocator::GetAnimationManager()->GetSnapshotAndVersion(); + auto materialSnapshot = ServiceLocator::GetMaterialManager()->GetSnapshotAndVersion(); + + _systemContext.deltaTime = deltaTime; + _systemContext.frameIndex = frameIndex; + + _systemContext.modelManagerVersion = modelSnapshot.version; + _systemContext.materialManagerVersion = materialSnapshot.version; + _systemContext.animationManagerVersion = animSnapshot.version; + + _systemContext.modelSnapshots = modelSnapshot.snapshots; + _systemContext.materialSnapshots = materialSnapshot.snapshots; + _systemContext.animationSnapshots = animSnapshot.snapshots; + + _systemContext.materialRenderTypes.clear(); + _systemContext.materialRenderTypes.resize(materialSnapshot.snapshots.size()); + + std::transform(materialSnapshot.snapshots.begin(), materialSnapshot.snapshots.end(), _systemContext.materialRenderTypes.begin(), + [](const auto& snapshot) -> MaterialRenderType { + if (!snapshot.resource) + return MaterialRenderType::Opaque1Sided; + + bool isTrans = snapshot.resource->isTransparent; + bool isDouble = snapshot.resource->doubleSided; + + if (isTrans && isDouble) return MaterialRenderType::Transparent2Sided; + if (isTrans) return MaterialRenderType::Transparent1Sided; + if (isDouble) return MaterialRenderType::Opaque2Sided; + return MaterialRenderType::Opaque1Sided; + }); + auto screenWidth = ServiceLocator::GetFrameContext()->screenWidth; auto screenHeight = ServiceLocator::GetFrameContext()->screenHeight; diff --git a/SynapseEngine/Engine/Scene/Scene.h b/SynapseEngine/Engine/Scene/Scene.h index 2dc31025..d8b9a0d4 100644 --- a/SynapseEngine/Engine/Scene/Scene.h +++ b/SynapseEngine/Engine/Scene/Scene.h @@ -17,6 +17,7 @@ #include "Engine/Scene/Source/ISceneSource.h" #include "Engine/Physics/IPhysicsEngine.h" #include "HierarchyManager.h" +#include "Engine/System/SystemContext.h" namespace Syn { @@ -53,6 +54,8 @@ namespace Syn EntityID GetSelectedEntity() const { return _selectedEntity; } void SetSelectedEntity(EntityID entity) { _selectedEntity = entity; } + + const SystemContext& GetSystemContext() const { return _systemContext; } private: void InitializeSystems(); void InitializeComponentBuffers(); @@ -90,6 +93,8 @@ namespace Syn float _currentDeltaTime = 0.0f; uint32_t _currentFrameIndex = 0; + + SystemContext _systemContext; private: friend class SceneInsider; }; diff --git a/SynapseEngine/Engine/Scene/Source/Procedural/TestSceneSource.cpp b/SynapseEngine/Engine/Scene/Source/Procedural/TestSceneSource.cpp index fbecd010..bcb47898 100644 --- a/SynapseEngine/Engine/Scene/Source/Procedural/TestSceneSource.cpp +++ b/SynapseEngine/Engine/Scene/Source/Procedural/TestSceneSource.cpp @@ -226,7 +226,7 @@ namespace Syn registry.AddComponent(bistroEntity); registry.GetComponent(bistroEntity).translation = glm::vec3(0.0f, 0.0f, 0.0f); - registry.GetComponent(bistroEntity).scale = glm::vec3(0.2f, 0.2f, 0.2f); + registry.GetComponent(bistroEntity).scale = glm::vec3(2.3f, 2.3f, 2.3f); registry.GetComponent(bistroEntity).modelIndex = bistroId; registry.GetPool()->SetCategory(bistroEntity, StorageCategory::Static); diff --git a/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json b/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json index 8d5b3da8..1608f4f8 100644 --- a/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json +++ b/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json @@ -22,9 +22,9 @@ }, "lights": { "directional_count": 1, - "point_count": 32, - "point_shadow_count": 16, - "spot_count": 32, - "spot_shadow_count": 16 + "point_count": 256, + "point_shadow_count": 32, + "spot_count": 256, + "spot_shadow_count": 32 } } \ No newline at end of file diff --git a/SynapseEngine/Engine/System/Core/StaticSpatialSahSystem.cpp b/SynapseEngine/Engine/System/Core/StaticSpatialSahSystem.cpp index 136c7f01..73fb27b9 100644 --- a/SynapseEngine/Engine/System/Core/StaticSpatialSahSystem.cpp +++ b/SynapseEngine/Engine/System/Core/StaticSpatialSahSystem.cpp @@ -5,8 +5,6 @@ #include "Engine/System/Core/TransformSystem.h" #include "Engine/System/Rendering/ModelSystem.h" #include "Engine/Mesh/Utils/MeshUtils.h" -#include "Engine/Mesh/ModelManager.h" -#include "Engine/Animation/AnimationManager.h" #include #include @@ -63,10 +61,8 @@ namespace Syn return; } - auto modelManager = ServiceLocator::GetModelManager(); - auto modelSnapshot = modelManager->GetResourceSnapshot(); - auto animationManager = ServiceLocator::GetAnimationManager(); - auto animSnapshot = animationManager->GetResourceSnapshot(); + auto& modelSnapshot = scene->GetSystemContext().modelSnapshots; + auto& animSnapshot = scene->GetSystemContext().animationSnapshots; _spatialItems.clear(); _spatialItems.resize(staticEntities.size()); diff --git a/SynapseEngine/Engine/System/Light/Direction/DirectionLightShadowCullingSystem.cpp b/SynapseEngine/Engine/System/Light/Direction/DirectionLightShadowCullingSystem.cpp index 4b240af7..512a3bce 100644 --- a/SynapseEngine/Engine/System/Light/Direction/DirectionLightShadowCullingSystem.cpp +++ b/SynapseEngine/Engine/System/Light/Direction/DirectionLightShadowCullingSystem.cpp @@ -115,9 +115,9 @@ namespace Syn auto animationManager = ServiceLocator::GetAnimationManager(); auto materialManager = ServiceLocator::GetMaterialManager(); - auto modelSnapshot = modelManager->GetResourceSnapshot(); - auto animSnapshot = animationManager->GetResourceSnapshot(); - auto matTypeSnapshot = materialManager->GetRenderTypeSnapshot(); + auto& modelSnapshot = scene->GetSystemContext().modelSnapshots; + auto& animSnapshot = scene->GetSystemContext().animationSnapshots; + auto& matTypeSnapshot = scene->GetSystemContext().materialRenderTypes; // Extract entity properties (runs exactly once per entity) auto withEntityData = [modelPool, transformPool, animPool, overridePool, modelSnapshot, tagPool, animSnapshot, drawData] diff --git a/SynapseEngine/Engine/System/Light/Point/PointLightShadowCullingSystem.cpp b/SynapseEngine/Engine/System/Light/Point/PointLightShadowCullingSystem.cpp index c4869a1b..ff6fe3e9 100644 --- a/SynapseEngine/Engine/System/Light/Point/PointLightShadowCullingSystem.cpp +++ b/SynapseEngine/Engine/System/Light/Point/PointLightShadowCullingSystem.cpp @@ -117,9 +117,9 @@ namespace Syn auto animationManager = ServiceLocator::GetAnimationManager(); auto materialManager = ServiceLocator::GetMaterialManager(); - auto modelSnapshot = modelManager->GetResourceSnapshot(); - auto animSnapshot = animationManager->GetResourceSnapshot(); - auto matTypeSnapshot = materialManager->GetRenderTypeSnapshot(); + auto& modelSnapshot = scene->GetSystemContext().modelSnapshots; + auto& animSnapshot = scene->GetSystemContext().animationSnapshots; + auto& matTypeSnapshot = scene->GetSystemContext().materialRenderTypes; // Extract Entity Data (Runs once per entity) auto withEntityData = [modelPool, transformPool, animPool, overridePool, modelSnapshot, tagPool, animSnapshot, drawData] diff --git a/SynapseEngine/Engine/System/Light/Spot/SpotLightShadowCullingSystem.cpp b/SynapseEngine/Engine/System/Light/Spot/SpotLightShadowCullingSystem.cpp index 2c31fd62..3b4fdea6 100644 --- a/SynapseEngine/Engine/System/Light/Spot/SpotLightShadowCullingSystem.cpp +++ b/SynapseEngine/Engine/System/Light/Spot/SpotLightShadowCullingSystem.cpp @@ -125,9 +125,9 @@ namespace Syn auto animationManager = ServiceLocator::GetAnimationManager(); auto materialManager = ServiceLocator::GetMaterialManager(); - auto modelSnapshot = modelManager->GetResourceSnapshot(); - auto animSnapshot = animationManager->GetResourceSnapshot(); - auto matTypeSnapshot = materialManager->GetRenderTypeSnapshot(); + auto& modelSnapshot = scene->GetSystemContext().modelSnapshots; + auto& animSnapshot = scene->GetSystemContext().animationSnapshots; + auto& matTypeSnapshot = scene->GetSystemContext().materialRenderTypes; // Extract Entity Data (Runs once per entity) auto withEntityData = [modelPool, transformPool, animPool, overridePool, modelSnapshot, tagPool, animSnapshot, drawData] diff --git a/SynapseEngine/Engine/System/Physics/ConvexColliderSystem.cpp b/SynapseEngine/Engine/System/Physics/ConvexColliderSystem.cpp index 1d2e1e35..e7a785b0 100644 --- a/SynapseEngine/Engine/System/Physics/ConvexColliderSystem.cpp +++ b/SynapseEngine/Engine/System/Physics/ConvexColliderSystem.cpp @@ -4,7 +4,6 @@ #include "Engine/Component/Physics/RigidBodyComponent.h" #include "Engine/System/Physics/RigidBodySystem.h" #include "Engine/Component/Rendering/ModelComponent.h" -#include "Engine/Mesh/ModelManager.h" #include "Engine/ServiceLocator.h" #include "Engine/Physics/IPhysicsEngine.h" #include "PhysicsUtils.h" @@ -36,12 +35,11 @@ namespace Syn auto transformPool = registry->GetPool(); auto modelPool = registry->GetPool(); auto physicsEngine = scene->GetPhysicsEngine(); - auto modelManager = ServiceLocator::GetModelManager(); - if (!convexPool || !rbPool || !transformPool || !modelPool || !physicsEngine || !modelManager) + if (!convexPool || !rbPool || !transformPool || !modelPool || !physicsEngine) return; - auto modelSnapshots = modelManager->GetResourceSnapshot(); + auto& modelSnapshots = scene->GetSystemContext().modelSnapshots; ParallelForEach(convexPool, subflow, SystemPhaseNames::Update, [convexPool, rbPool, transformPool, modelPool, physicsEngine, modelSnapshots](EntityID entity) { diff --git a/SynapseEngine/Engine/System/Physics/MeshColliderSystem.cpp b/SynapseEngine/Engine/System/Physics/MeshColliderSystem.cpp index 5a518bf6..76917d87 100644 --- a/SynapseEngine/Engine/System/Physics/MeshColliderSystem.cpp +++ b/SynapseEngine/Engine/System/Physics/MeshColliderSystem.cpp @@ -5,7 +5,6 @@ #include "Engine/System/Physics/RigidBodySystem.h" #include "Engine/System/Rendering/ModelSystem.h" #include "Engine/Component/Rendering/ModelComponent.h" -#include "Engine/Mesh/ModelManager.h" #include "Engine/ServiceLocator.h" #include "Engine/Physics/IPhysicsEngine.h" #include "PhysicsUtils.h" @@ -33,11 +32,10 @@ namespace Syn auto transformPool = registry->GetPool(); auto modelPool = registry->GetPool(); auto physicsEngine = scene->GetPhysicsEngine(); - auto modelManager = ServiceLocator::GetModelManager(); - if (!meshColliderPool || !rbPool || !transformPool || !modelPool || !physicsEngine || !modelManager) return; + if (!meshColliderPool || !rbPool || !transformPool || !modelPool || !physicsEngine) return; - auto modelSnapshots = modelManager->GetResourceSnapshot(); + auto& modelSnapshots = scene->GetSystemContext().modelSnapshots; ParallelForEach(meshColliderPool, subflow, SystemPhaseNames::Update, [meshColliderPool, rbPool, transformPool, modelPool, physicsEngine, modelSnapshots](EntityID entity) { diff --git a/SynapseEngine/Engine/System/Physics/RigidBodySystem.cpp b/SynapseEngine/Engine/System/Physics/RigidBodySystem.cpp index d9b45646..4abe3fb2 100644 --- a/SynapseEngine/Engine/System/Physics/RigidBodySystem.cpp +++ b/SynapseEngine/Engine/System/Physics/RigidBodySystem.cpp @@ -19,8 +19,6 @@ #include "Engine/System/Rendering/ModelSystem.h" #include "Engine/System/Core/TransformSystem.h" -#include "Engine/Mesh/ModelManager.h" - namespace Syn { std::vector RigidBodySystem::GetReadDependencies() const @@ -55,10 +53,9 @@ namespace Syn auto modelPool = registry->GetPool(); auto physicsEngine = scene->GetPhysicsEngine(); - auto modelManager = ServiceLocator::GetModelManager(); - if (!rbPool || !physicsEngine || !modelManager) return; - auto modelSnapshots = modelManager->GetResourceSnapshot(); + if (!rbPool || !physicsEngine) return; + auto& modelSnapshots = scene->GetSystemContext().modelSnapshots; ParallelForEach(rbPool, subflow, SystemPhaseNames::Update, [rbPool, transformPool, boxPool, spherePool, capsulePool, physicsEngine, convexPool, meshPool, modelPool, modelSnapshots](EntityID entity) { auto& rb = rbPool->Get(entity); diff --git a/SynapseEngine/Engine/System/Rendering/AnimationSystem.cpp b/SynapseEngine/Engine/System/Rendering/AnimationSystem.cpp index 9f5cd439..7f22afe4 100644 --- a/SynapseEngine/Engine/System/Rendering/AnimationSystem.cpp +++ b/SynapseEngine/Engine/System/Rendering/AnimationSystem.cpp @@ -1,6 +1,5 @@ #include "AnimationSystem.h" #include "Engine/ServiceLocator.h" -#include "Engine/Animation/AnimationManager.h" namespace Syn { @@ -14,8 +13,8 @@ namespace Syn auto animPool = registry->GetPool(); if (!animPool) return; - auto animationManager = ServiceLocator::GetAnimationManager(); - auto animations = animationManager->GetResourceSnapshot(); + const auto& ctx = scene->GetSystemContext(); + auto& animations = ctx.animationSnapshots; ParallelForEachIf(animPool, subflow, SystemPhaseNames::Update, [animPool, deltaTime, animations](EntityID entity) { auto& animComponent = animPool->Get(entity); diff --git a/SynapseEngine/Engine/System/Rendering/MaterialSystem.cpp b/SynapseEngine/Engine/System/Rendering/MaterialSystem.cpp index 0866a860..d40ff4b5 100644 --- a/SynapseEngine/Engine/System/Rendering/MaterialSystem.cpp +++ b/SynapseEngine/Engine/System/Rendering/MaterialSystem.cpp @@ -4,9 +4,12 @@ #include "Engine/FrameContext.h" #include "Engine/Mesh/ModelManager.h" #include "Engine/Component/Rendering/MaterialOverrideComponent.h" +#include "Engine/Component/Core/TagComponent.h" namespace Syn { + constexpr bool ENABLE_DEBUG_LOGGING = false; + std::vector MaterialSystem::GetWriteDependencies() const { return { TypeInfo::ID }; } @@ -18,32 +21,40 @@ namespace Syn if (!pool) return; auto overridePool = registry->GetPool(); + auto tagPool = registry->GetPool(); auto drawData = scene->GetSceneDrawData(); - auto modelManager = ServiceLocator::GetModelManager(); - uint32_t currentModelManagerVersion = modelManager->GetVersion(); + uint32_t currentModelManagerVersion = scene->GetSystemContext().modelManagerVersion; //Todo: Override component changed -> Update?? - this->EmplaceTask(subflow, SystemPhaseNames::Update, [this, scene, pool, modelManager, currentModelManagerVersion, drawData, overridePool]() { + this->EmplaceTask(subflow, SystemPhaseNames::Update, [this, scene, pool, tagPool, currentModelManagerVersion, drawData, overridePool]() { bool needsRebuild = false; + bool needsUpload = false; + if (_lastModelManagerVersion != currentModelManagerVersion) { needsRebuild = true; _lastModelManagerVersion = currentModelManagerVersion; + + if constexpr (ENABLE_DEBUG_LOGGING) { + Info("[MaterialSystem UPDATE] Frame {}: ModelManager version changed to {}", scene->GetSystemContext().frameIndex, currentModelManagerVersion); + } + } if (!pool->IsStateBitSet() && !pool->IsStateBitSet() && !needsRebuild) { return; } - auto modelSnapshots = modelManager->GetResourceSnapshot(); + auto& modelSnapshots = scene->GetSystemContext().modelSnapshots; uint32_t totalExactMaterials = 0; auto countFunc = [&](EntityID entity) { auto& comp = pool->Get(entity); - if (comp.modelIndex < modelSnapshots.size() && modelSnapshots[comp.modelIndex].resource) { - totalExactMaterials += static_cast(modelSnapshots[comp.modelIndex].resource->cpuData.meshMaterialIndices.size()); + const auto& snapshot = modelSnapshots[comp.modelIndex]; + if (snapshot.state == ResourceState::Ready && snapshot.resource) { + totalExactMaterials += static_cast(snapshot.resource->cpuData.meshMaterialIndices.size()); } }; @@ -51,8 +62,10 @@ namespace Syn for (auto e : pool->GetStorage().GetDynamicEntities()) countFunc(e); for (auto e : pool->GetStorage().GetStreamEntities()) countFunc(e); - //Info("MaterialSystem: Rebuilding material indices. Total materials expected: {}", totalExactMaterials); - + if constexpr (ENABLE_DEBUG_LOGGING) { + Info("[MaterialSystem UPDATE] Frame {}: Rebuilding material indices. Total slots expected: {}", scene->GetSystemContext().frameIndex, totalExactMaterials); + } + _flatMaterialIndices.resize(totalExactMaterials); uint32_t currentOffset = 0; @@ -60,8 +73,9 @@ namespace Syn auto& comp = pool->Get(entity); if (comp.modelIndex >= modelSnapshots.size()) return; - auto model = modelSnapshots[comp.modelIndex].resource; - if (!model) return; + const auto& snapshot = modelSnapshots[comp.modelIndex]; + if (snapshot.state != ResourceState::Ready || !snapshot.resource) return; + auto model = snapshot.resource; const auto& defaultMaterials = model->cpuData.meshMaterialIndices; uint32_t materialCount = static_cast(defaultMaterials.size()); @@ -77,22 +91,41 @@ namespace Syn overrides = overrideComp.materials; } - if (comp.materialOffset != currentOffset) { + bool wasReady = comp.isReady; + comp.isReady = true; + + if (comp.materialOffset != currentOffset || !wasReady) { + if constexpr (ENABLE_DEBUG_LOGGING) { + std::string entityName = "Unknown"; + if (tagPool && tagPool->Has(entity)) entityName = tagPool->Get(entity).name; + Info("[MaterialSystem UPDATE] Frame {}: Entity {} data changed! OldOffset: {}, NewOffset: {}, WasReady: {}", scene->GetSystemContext().frameIndex, entityName, comp.materialOffset, currentOffset, wasReady); + } + comp.materialOffset = currentOffset; comp.version++; pool->SetBit(entity); pool->MarkStaticDirty(entity); + + needsUpload = true; } - //Info("MaterialSystem: Entity {} assigned offset: {}, material count: {}", (uint32_t)entity, currentOffset, materialCount); + if constexpr (ENABLE_DEBUG_LOGGING) { + std::string entityName = "Unknown"; + if (tagPool && tagPool->Has(entity)) entityName = tagPool->Get(entity).name; + Info("MaterialSystem: Entity {} assigned offset: {}, material count: {}", entityName, currentOffset, materialCount); + } for (uint32_t i = 0; i < materialCount; ++i) { - if (!overrides.empty() && overrides[i] != UINT32_MAX) { - _flatMaterialIndices[currentOffset + i] = overrides[i]; + uint32_t matIdx = defaultMaterials[i]; + if (!overrides.empty() && i < overrides.size() && overrides[i] != UINT32_MAX) { + matIdx = overrides[i]; } - else { - _flatMaterialIndices[currentOffset + i] = defaultMaterials[i]; + + if constexpr (ENABLE_DEBUG_LOGGING) { + Info(" -> Slot {}: MatID {}", currentOffset + i, matIdx); } + + _flatMaterialIndices[currentOffset + i] = matIdx; } currentOffset += materialCount; @@ -102,9 +135,16 @@ namespace Syn for (auto e : pool->GetStorage().GetDynamicEntities()) processEntity(e); for (auto e : pool->GetStorage().GetStreamEntities()) processEntity(e); - uint32_t framesInFlight = ServiceLocator::GetFrameContext()->framesInFlight; - this->SetFramesToUpload(framesInFlight); - scene->GetSceneDrawData()->RequestGlobalSync(framesInFlight); + + if (needsRebuild || needsUpload) { + uint32_t framesInFlight = ServiceLocator::GetFrameContext()->framesInFlight; + this->SetFramesToUpload(framesInFlight); + scene->GetSceneDrawData()->RequestGlobalSync(framesInFlight); + + if constexpr (ENABLE_DEBUG_LOGGING) { + Info("[MaterialSystem UPDATE] Frame {}: Requested global sync and GPU upload for {} frames.", scene->GetSystemContext().frameIndex, framesInFlight); + } + } }); } @@ -117,16 +157,30 @@ namespace Syn this->DecrementFramesToUpload(); } + if constexpr (ENABLE_DEBUG_LOGGING) { + Info("[MaterialSystem UPLOAD] Frame {}: Upload check. Force: {}, FlatIndicesSize: {}", frameIndex, force, _flatMaterialIndices.size()); + } + if (!force || _flatMaterialIndices.empty()) return; auto drawData = scene->GetSceneDrawData(); + size_t reqFromModels = drawData->Models.requiredMaterialBufferSize; + size_t actualElements = _flatMaterialIndices.size(); + size_t requiredElements = std::max(actualElements, reqFromModels); + + if constexpr (ENABLE_DEBUG_LOGGING) { + Info("[MaterialSystem UPLOAD] Frame {}: Resizing GPU buffer. Actual CPU Elements: {}, Req from Models: {}, Final GPU Elements: {}", frameIndex, actualElements, reqFromModels, requiredElements); + } - size_t requiredElements = std::max(_flatMaterialIndices.size(), (size_t)(drawData->Models.requiredMaterialBufferSize / sizeof(uint32_t))); drawData->Models.materialIndexBuffer.UpdateCapacity(frameIndex, requiredElements); size_t actualDataSize = _flatMaterialIndices.size() * sizeof(uint32_t); drawData->Models.materialIndexBuffer.Write(frameIndex, _flatMaterialIndices.data(), actualDataSize, 0); + + if constexpr (ENABLE_DEBUG_LOGGING) { + Info("[MaterialSystem UPLOAD] Frame {}: GPU buffer written successfully ({} bytes).", frameIndex, actualDataSize); + } }); } } \ No newline at end of file diff --git a/SynapseEngine/Engine/System/Rendering/ModelFrustumCullingSystem.cpp b/SynapseEngine/Engine/System/Rendering/ModelFrustumCullingSystem.cpp index 353734aa..e3cc0b54 100644 --- a/SynapseEngine/Engine/System/Rendering/ModelFrustumCullingSystem.cpp +++ b/SynapseEngine/Engine/System/Rendering/ModelFrustumCullingSystem.cpp @@ -5,15 +5,12 @@ #include "Engine/System/Rendering/ModelSystem.h" #include "Engine/System/Core/TransformSystem.h" #include "Engine/System/Rendering/RenderSystem.h" -#include "Engine/Mesh/ModelManager.h" #include "Engine/System/Core/CameraSystem.h" -#include "Engine/Animation/AnimationManager.h" #include "Engine/System/Rendering/AnimationSystem.h" #include "Engine/System/Rendering/MaterialSystem.h" #include "Engine/System/Core/StaticSpatialSahSystem.h" #include "Engine/System/Core/TagSystem.h" -#include "Engine/Material/MaterialManager.h" #include "Engine/Component/Rendering/MaterialOverrideComponent.h" #include "Engine/Mesh/Utils/MeshUtils.h" @@ -24,6 +21,8 @@ namespace Syn { + constexpr bool ENABLE_DEBUG_LOGGING = false; + std::vector ModelFrustumCullingSystem::GetReadDependencies() const { return { TypeInfo::ID, @@ -41,8 +40,7 @@ namespace Syn { auto settings = scene->GetSettings(); auto drawData = scene->GetSceneDrawData(); - auto materialManager = ServiceLocator::GetMaterialManager(); - auto matTypeSnapshot = materialManager->GetRenderTypeSnapshot(); + auto matTypeSnapshot = scene->GetSystemContext().materialRenderTypes; auto overridePool = scene->GetRegistry()->GetPool(); tf::Task initTask = this->EmplaceTask(subflow, "Update Init", [drawData]() { @@ -63,9 +61,6 @@ namespace Syn return; } - auto modelManager = ServiceLocator::GetModelManager(); - auto animationManager = ServiceLocator::GetAnimationManager(); - auto registry = scene->GetRegistry(); auto modelPool = registry->GetPool(); auto transformPool = registry->GetPool(); @@ -77,8 +72,8 @@ namespace Syn if (!modelPool || !transformPool || !cameraPool || cameraEntity == NULL_ENTITY) return; const auto& cameraComp = cameraPool->Get(cameraEntity); - auto modelSnapshot = modelManager->GetResourceSnapshot(); - auto animSnapshot = animationManager->GetResourceSnapshot(); + auto& modelSnapshot = scene->GetSystemContext().modelSnapshots; + auto& animSnapshot = scene->GetSystemContext().animationSnapshots; glm::vec2 screenRes = glm::vec2(cameraComp.width, cameraComp.height); @@ -198,6 +193,12 @@ namespace Syn MaterialRenderType matType = (matIdx < matTypeSnapshot.size()) ? matTypeSnapshot[matIdx] : MaterialRenderType::Opaque1Sided; + if constexpr (ENABLE_DEBUG_LOGGING) { + std::string name = "Unknown"; + if (tagPool && tagPool->Has(entity)) name = tagPool->Get(entity).name; + Info("[FrustumCulling] Visible in camera: Entity {} ({}) | First visible sub-mesh MatType: {}", (uint32_t)entity, name, (uint32_t)matType); + } + if (meshAlloc.activeTypes[matType]) { uint32_t slotIndex = 0; diff --git a/SynapseEngine/Engine/System/Rendering/ModelSystem.cpp b/SynapseEngine/Engine/System/Rendering/ModelSystem.cpp index d1c0c952..9c1f17d6 100644 --- a/SynapseEngine/Engine/System/Rendering/ModelSystem.cpp +++ b/SynapseEngine/Engine/System/Rendering/ModelSystem.cpp @@ -1,11 +1,13 @@ #include "ModelSystem.h" #include "MaterialSystem.h" #include "Engine/ServiceLocator.h" -#include "Engine/Mesh/ModelManager.h" #include "Engine/FrameContext.h" +#include "Engine/Component/Core/TagComponent.h" namespace Syn { + constexpr bool ENABLE_DEBUG_LOGGING = false; + std::vector ModelSystem::GetReadDependencies() const { return { TypeInfo::ID @@ -22,8 +24,7 @@ namespace Syn auto modelPool = registry->GetPool(); if (!modelPool) return; - auto modelManager = ServiceLocator::GetModelManager(); - uint32_t currentVersion = modelManager->GetVersion(); + uint32_t currentVersion = scene->GetSystemContext().modelManagerVersion; this->EmplaceTask(subflow, SystemPhaseNames::Update, [this, scene, currentVersion]() { if (_lastModelManagerVersion != currentVersion) { @@ -45,6 +46,7 @@ namespace Syn auto registry = scene->GetRegistry(); auto componentBufferManager = scene->GetComponentBufferManager(); auto modelPool= registry->GetPool(); + auto tagPool = registry->GetPool(); if (!modelPool) return; auto modelDataBuffer = componentBufferManager->GetComponentBuffer(BufferNames::ModelData, frameIndex); @@ -53,12 +55,19 @@ namespace Syn bool forceUpload = this->ShouldForceUpload(); - auto processUpload = [modelPool, modelDataBuffer, modelDataBufferHandler, forceUpload](EntityID entity) { + auto processUpload = [modelPool, tagPool, modelDataBuffer, modelDataBufferHandler, forceUpload, scene](EntityID entity) { auto& modelComponent = modelPool->Get(entity); auto modelIndex = modelPool->GetMapping().Get(entity); if (forceUpload || modelDataBuffer.versions[modelIndex] != modelComponent.version) { + if constexpr (ENABLE_DEBUG_LOGGING) { + std::string name = "Unknown"; + if (tagPool && tagPool->Has(entity)) name = tagPool->Get(entity).name; + + Info("[ModelSystem UPLOAD] Entity: {} ({}) | Material offset: {}", (uint32_t)entity, name, modelComponent.materialOffset); + } + modelDataBuffer.versions[modelIndex] = modelComponent.version; modelDataBufferHandler[modelIndex] = ModelComponentGPU(entity, modelComponent); } @@ -66,7 +75,11 @@ namespace Syn }; ForEachStream(modelPool, subflow, SystemPhaseNames::UploadGPU, processUpload); - if (uploadDynamic) ForEachDynamic(modelPool, subflow, SystemPhaseNames::UploadGPU, processUpload); - if (uploadStatic) ForEachStatic(modelPool, subflow, SystemPhaseNames::UploadGPU, processUpload); + + if (uploadDynamic) + ForEachDynamic(modelPool, subflow, SystemPhaseNames::UploadGPU, processUpload); + + if (uploadStatic) + ForEachStatic(modelPool, subflow, SystemPhaseNames::UploadGPU, processUpload); } } \ No newline at end of file diff --git a/SynapseEngine/Engine/System/Rendering/RenderSystem.cpp b/SynapseEngine/Engine/System/Rendering/RenderSystem.cpp index e5aee327..431acc65 100644 --- a/SynapseEngine/Engine/System/Rendering/RenderSystem.cpp +++ b/SynapseEngine/Engine/System/Rendering/RenderSystem.cpp @@ -2,10 +2,7 @@ #include "Engine/Scene/Scene.h" #include "Engine/Component/Rendering/ModelComponent.h" #include "Engine/ServiceLocator.h" -#include "Engine/Mesh/ModelManager.h" -#include "Engine/Material/MaterialManager.h" #include "Engine/System/Rendering/ModelSystem.h" -#include "Engine/ServiceLocator.h" #include "Engine/FrameContext.h" #include "MaterialSystem.h" #include "Engine/Component/Rendering/MaterialOverrideComponent.h" @@ -32,16 +29,13 @@ namespace Syn auto overridePool = registry->GetPool(); if (!pool) return; - auto modelManager = ServiceLocator::GetModelManager(); - auto materialManager = ServiceLocator::GetMaterialManager(); - - uint32_t totalModels = static_cast(modelManager->GetResourceCount()); - uint32_t currentModelManagerVersion = modelManager->GetVersion(); - uint32_t currentMaterialManagerVersion = materialManager->GetVersion(); + uint32_t totalModels = static_cast(scene->GetSystemContext().modelSnapshots.size()); + uint32_t currentModelManagerVersion = scene->GetSystemContext().modelManagerVersion; + uint32_t currentMaterialManagerVersion = scene->GetSystemContext().materialManagerVersion; - this->EmplaceTask(subflow, SystemPhaseNames::Update, [this, scene, pool, overridePool, modelManager, materialManager, totalModels, currentModelManagerVersion, currentMaterialManagerVersion]() { - auto modelSnapshots = modelManager->GetResourceSnapshot(); - auto matTypeSnapshot = materialManager->GetRenderTypeSnapshot(); + this->EmplaceTask(subflow, SystemPhaseNames::Update, [this, scene, pool, overridePool, totalModels, currentModelManagerVersion, currentMaterialManagerVersion]() { + auto& modelSnapshots = scene->GetSystemContext().modelSnapshots; + auto& matTypeSnapshot = scene->GetSystemContext().materialRenderTypes; if (_lastModelManagerVersion != currentModelManagerVersion) { _needsRebuild = true; @@ -77,8 +71,9 @@ namespace Syn { if(_entitiesPerModel[modelId].empty() || modelId >= modelSnapshots.size()) continue; - auto model = modelSnapshots[modelId].resource; - if (!model) continue; + const auto& snapshot = modelSnapshots[modelId]; + if (snapshot.state != ResourceState::Ready || !snapshot.resource) continue; + auto model = snapshot.resource; uint32_t meshCount = model->cpuData.globalMeshCount; if (_meshMatCapacities[modelId].size() < meshCount) { @@ -147,10 +142,8 @@ namespace Syn void RenderSystem::RebuildGlobalBuffers(Scene* scene) { - auto modelManager = ServiceLocator::GetModelManager(); auto drawData = scene->GetSceneDrawData(); - auto modelSnapshots = modelManager->GetResourceSnapshot(); - + auto& modelSnapshots = scene->GetSystemContext().modelSnapshots; drawData->Models.activeDescriptorCount = 0; @@ -165,8 +158,10 @@ namespace Syn for (uint32_t modelId = 0; modelId < _modelCapacities.size(); ++modelId) { if (_modelCapacities[modelId] == 0 || modelId >= modelSnapshots.size()) continue; - auto model = modelSnapshots[modelId].resource; - if (!model) continue; + + const auto& snapshot = modelSnapshots[modelId]; + if (snapshot.state != ResourceState::Ready || !snapshot.resource) continue; + auto model = snapshot.resource; const auto& blueprints = model->cpuData.baseDrawCommands; for (size_t i = 0; i < blueprints.size(); ++i) { @@ -320,7 +315,7 @@ namespace Syn drawData->Models.totalAllocatedInstances = globalInstanceOffset; drawData->Debug.totalMaxMeshletInstances = totalMaxMeshletInstances; - drawData->Models.requiredMaterialBufferSize = totalMaterialIndicesCapacity * sizeof(uint32_t); + drawData->Models.requiredMaterialBufferSize = totalMaterialIndicesCapacity; if (drawData->Models.instances.Size() < globalInstanceOffset) { drawData->Models.instances.Resize(globalInstanceOffset); diff --git a/SynapseEngine/Engine/System/SystemContext.cpp b/SynapseEngine/Engine/System/SystemContext.cpp new file mode 100644 index 00000000..2321db1a --- /dev/null +++ b/SynapseEngine/Engine/System/SystemContext.cpp @@ -0,0 +1 @@ +#include "SystemContext.h" \ No newline at end of file diff --git a/SynapseEngine/Engine/System/SystemContext.h b/SynapseEngine/Engine/System/SystemContext.h new file mode 100644 index 00000000..0faaff76 --- /dev/null +++ b/SynapseEngine/Engine/System/SystemContext.h @@ -0,0 +1,22 @@ +#pragma once +#include "Engine/SynApi.h" +#include "Engine/Material/MaterialManager.h" +#include "Engine/Mesh/ModelManager.h" +#include "Engine/Animation/AnimationManager.h" +#include + +namespace Syn { + struct SYN_API SystemContext { + float deltaTime; + uint32_t frameIndex; + + uint32_t modelManagerVersion; + uint32_t materialManagerVersion; + uint32_t animationManagerVersion; + + std::vector materialRenderTypes; + std::vector::ResourceSnapshot> modelSnapshots; + std::vector::ResourceSnapshot> materialSnapshots; + std::vector::ResourceSnapshot> animationSnapshots; + }; +} \ No newline at end of file diff --git a/SynapseEngine/Engine/Vk/Descriptor/DescriptorBuffer.cpp b/SynapseEngine/Engine/Vk/Descriptor/DescriptorBuffer.cpp index 8f74d3bb..1db57c61 100644 --- a/SynapseEngine/Engine/Vk/Descriptor/DescriptorBuffer.cpp +++ b/SynapseEngine/Engine/Vk/Descriptor/DescriptorBuffer.cpp @@ -3,6 +3,7 @@ #include "Engine/Vk/Context.h" #include "Engine/Vk/Core/Device.h" #include "Engine/Vk/Buffer/BufferFactory.h" +#include "Engine/Vk/Buffer/BufferUtils.h" namespace Syn::Vk { @@ -22,13 +23,32 @@ namespace Syn::Vk { VkDeviceSize layoutSizeInBytes = 0; vkGetDescriptorSetLayoutSizeEXT(device->Handle(), _layout, &layoutSizeInBytes); - - VkBufferUsageFlags usage = - VK_BUFFER_USAGE_RESOURCE_DESCRIPTOR_BUFFER_BIT_EXT | - VK_BUFFER_USAGE_SAMPLER_DESCRIPTOR_BUFFER_BIT_EXT | - VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; - - _buffer = BufferFactory::CreatePersistent(layoutSizeInBytes, usage); + _layoutSizeInBytes = layoutSizeInBytes; + + VkBufferUsageFlags gpuUsage = VK_BUFFER_USAGE_RESOURCE_DESCRIPTOR_BUFFER_BIT_EXT | + VK_BUFFER_USAGE_SAMPLER_DESCRIPTOR_BUFFER_BIT_EXT | + VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | + VK_BUFFER_USAGE_TRANSFER_DST_BIT; + + BufferConfig mappedConfig; + mappedConfig.size = _layoutSizeInBytes; + mappedConfig.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + mappedConfig.memoryUsage = VMA_MEMORY_USAGE_AUTO; + mappedConfig.allocationFlags = VMA_ALLOCATION_CREATE_MAPPED_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; + mappedConfig.useDeviceAddress = false; + + BufferConfig gpuConfig{}; + gpuConfig.size = _layoutSizeInBytes; + gpuConfig.usage = gpuUsage | VK_BUFFER_USAGE_TRANSFER_DST_BIT; + gpuConfig.memoryUsage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; + gpuConfig.allocationFlags = 0; + gpuConfig.useDeviceAddress = true; + + _mapped = std::make_shared(mappedConfig); + _gpu = std::make_shared(gpuConfig); + _gpuConfig = gpuConfig; + _mappedConfig = mappedConfig; + _isDirty = true; } void DescriptorBuffer::FillSampledImages(uint32_t binding, uint32_t count, VkImageView view, VkImageLayout layout) @@ -44,7 +64,7 @@ namespace Syn::Vk { VkDeviceSize bindingOffset; vkGetDescriptorSetLayoutBindingOffsetEXT(device->Handle(), _layout, binding, &bindingOffset); - void* mappedData = _buffer->Map(); + void* mappedData = _mapped->Map(); char* targetBaseAddress = static_cast(mappedData) + bindingOffset; std::vector descriptorPayload(_sampledImageSize); @@ -53,19 +73,25 @@ namespace Syn::Vk { for (uint32_t i = 0; i < count; ++i) { std::memcpy(targetBaseAddress + (i * _sampledImageSize), descriptorPayload.data(), _sampledImageSize); } + + _isDirty = true; } void DescriptorBuffer::WriteDescriptor(uint32_t binding, uint32_t arrayElement, size_t descriptorSize, const VkDescriptorGetInfoEXT& getInfo) { + std::lock_guard lock(_bufferMutex); + auto device = ServiceLocator::GetVkContext()->GetDevice(); VkDeviceSize bindingOffset; vkGetDescriptorSetLayoutBindingOffsetEXT(device->Handle(), _layout, binding, &bindingOffset); - void* mappedData = _buffer->Map(); + void* mappedData = _mapped->Map(); char* targetAddress = static_cast(mappedData) + bindingOffset + (arrayElement * descriptorSize); vkGetDescriptorEXT(device->Handle(), &getInfo, descriptorSize, targetAddress); + + _isDirty = true; } void DescriptorBuffer::WriteCombinedImageSampler(uint32_t binding, uint32_t arrayElement, VkImageView view, VkSampler sampler, VkImageLayout layout) @@ -132,7 +158,7 @@ namespace Syn::Vk { void DescriptorBuffer::Bind(VkCommandBuffer cmd, VkPipelineLayout pipelineLayout, uint32_t setIndex, VkPipelineBindPoint bindPoint) { VkDescriptorBufferBindingInfoEXT bindingInfo{ VK_STRUCTURE_TYPE_DESCRIPTOR_BUFFER_BINDING_INFO_EXT }; - bindingInfo.address = _buffer->GetDeviceAddress(); + bindingInfo.address = _gpu->GetDeviceAddress(); bindingInfo.usage = VK_BUFFER_USAGE_RESOURCE_DESCRIPTOR_BUFFER_BIT_EXT | VK_BUFFER_USAGE_SAMPLER_DESCRIPTOR_BUFFER_BIT_EXT; vkCmdBindDescriptorBuffersEXT(cmd, 1, &bindingInfo); @@ -142,4 +168,33 @@ namespace Syn::Vk { vkCmdSetDescriptorBufferOffsetsEXT(cmd, bindPoint, pipelineLayout, setIndex, 1, &bufferIndex, &offset); } + + StaleDescriptorBuffers DescriptorBuffer::RecordSync(VkCommandBuffer cmd) + { + std::lock_guard lock(_bufferMutex); + + if (!_isDirty) return { nullptr, nullptr }; + + auto staleGpu = _gpu; + auto staleMapped = _mapped; + + _gpu = std::make_shared(_gpuConfig); + _mapped = std::make_shared(_mappedConfig); + + void* oldData = staleMapped->Map(); + void* newData = _mapped->Map(); + std::memcpy(newData, oldData, _layoutSizeInBytes); + + BufferCopyInfo copyInfo{}; + copyInfo.srcBuffer = _mapped->Handle(); + copyInfo.dstBuffer = _gpu->Handle(); + copyInfo.size = _layoutSizeInBytes; + copyInfo.srcOffset = 0; + copyInfo.dstOffset = 0; + + BufferUtils::CopyBuffer(cmd, copyInfo); + + _isDirty = false; + return { staleMapped, staleGpu }; + } } \ No newline at end of file diff --git a/SynapseEngine/Engine/Vk/Descriptor/DescriptorBuffer.h b/SynapseEngine/Engine/Vk/Descriptor/DescriptorBuffer.h index 84f07993..854fa4f7 100644 --- a/SynapseEngine/Engine/Vk/Descriptor/DescriptorBuffer.h +++ b/SynapseEngine/Engine/Vk/Descriptor/DescriptorBuffer.h @@ -2,9 +2,15 @@ #include "../VkCommon.h" #include "Engine/Vk/Buffer/Buffer.h" #include +#include namespace Syn::Vk { + struct SYN_API StaleDescriptorBuffers { + std::shared_ptr mapped; + std::shared_ptr gpu; + }; + class SYN_API DescriptorBuffer { public: DescriptorBuffer(VkDescriptorSetLayout layout); @@ -13,7 +19,7 @@ namespace Syn::Vk { DescriptorBuffer(const DescriptorBuffer&) = delete; DescriptorBuffer& operator=(const DescriptorBuffer&) = delete; - Buffer* GetBuffer() const { return _buffer.get(); } + Buffer* GetBuffer() const { return _gpu.get(); } void FillSampledImages(uint32_t binding, uint32_t count, VkImageView view, VkImageLayout layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); void WriteCombinedImageSampler(uint32_t binding, uint32_t arrayElement, VkImageView view, VkSampler sampler, VkImageLayout layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); @@ -21,16 +27,25 @@ namespace Syn::Vk { void WriteSampler(uint32_t binding, uint32_t arrayElement, VkSampler sampler); void WriteBuffer(uint32_t binding, uint32_t arrayElement, VkBuffer buffer, VkDeviceSize offset, VkDeviceSize range, VkDescriptorType type); void Bind(VkCommandBuffer cmd, VkPipelineLayout pipelineLayout, uint32_t setIndex = 0, VkPipelineBindPoint bindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS); + StaleDescriptorBuffers RecordSync(VkCommandBuffer cmd); private: void WriteDescriptor(uint32_t binding, uint32_t arrayElement, size_t descriptorSize, const VkDescriptorGetInfoEXT& getInfo); private: - std::unique_ptr _buffer; VkDescriptorSetLayout _layout; + size_t _layoutSizeInBytes = 0; + + bool _isDirty = false; + std::shared_ptr _mapped; + std::shared_ptr _gpu; + Vk::BufferConfig _gpuConfig; + Vk::BufferConfig _mappedConfig; size_t _combinedImageSamplerSize = 0; size_t _sampledImageSize = 0; size_t _samplerSize = 0; size_t _storageBufferSize = 0; size_t _uniformBufferSize = 0; + + std::mutex _bufferMutex; }; } \ No newline at end of file diff --git a/SynapseEngine/imgui.ini b/SynapseEngine/imgui.ini new file mode 100644 index 00000000..fd00bf98 --- /dev/null +++ b/SynapseEngine/imgui.ini @@ -0,0 +1,121 @@ +[Window][HostWindow_Scene] +Pos=0,23 +Size=2304,1273 +Collapsed=0 + +[Window][Content_Scene] +Pos=405,964 +Size=1470,332 +Collapsed=0 +DockId=0x00000002,0 + +[Window][Debug##Default] +Pos=60,60 +Size=400,400 +Collapsed=0 + +[Window][ Inspector] +Pos=1877,23 +Size=427,623 +Collapsed=0 +DockId=0x00000005,0 + +[Window][ Viewport] +Pos=405,23 +Size=1470,939 +Collapsed=0 +DockId=0x00000001,0 + +[Window][ Graphics & Environment] +Pos=1877,648 +Size=427,648 +Collapsed=0 +DockId=0x00000006,0 + +[Window][ Scene Hierarchy] +Pos=0,23 +Size=403,535 +Collapsed=0 +DockId=0x00000009,0 + +[Window][ Performance Profiler] +Pos=0,560 +Size=403,736 +Collapsed=0 +DockId=0x0000000A,0 + +[Window][ Output Log] +Pos=405,964 +Size=1470,332 +Collapsed=0 +DockId=0x00000002,1 + +[Table][0x3D1A1C69,2] +RefScale=13 +Column 0 Width=147 +Column 1 Weight=1.0000 + +[Table][0x6925898D,2] +RefScale=13 +Column 0 Width=217 +Column 1 Weight=1.0000 + +[Table][0xE847EDF4,2] +RefScale=13 +Column 0 Width=140 +Column 1 Weight=1.0000 + +[Table][0x182B970D,2] +RefScale=13 +Column 0 Width=168 +Column 1 Weight=1.0000 + +[Table][0x8556BC1A,2] +RefScale=13 +Column 0 Width=175 +Column 1 Weight=1.0000 + +[Table][0x51A78E48,2] +RefScale=13 +Column 0 Weight=1.0000 +Column 1 Width=32 + +[Table][0xB6D16E5C,3] +RefScale=13 +Column 0 Weight=0.7668 +Column 1 Width=64 +Column 2 Weight=0.2332 + +[Table][0x94CE371A,2] +RefScale=13 +Column 0 Width=63 +Column 1 Weight=1.0000 + +[Table][0x5A9ED35A,2] +RefScale=13 +Column 0 Width=56 +Column 1 Weight=1.0000 + +[Table][0x78D40C46,2] +RefScale=13 +Column 0 Width=135 +Column 1 Weight=1.0000 + +[Table][0xFFF76F10,2] +RefScale=13 +Column 0 Width=77 +Column 1 Weight=1.0000 + +[Docking][Data] +DockSpace ID=0x4713F8A8 Window=0xFBC4AE05 Pos=128,95 Size=2304,1273 Split=X + DockNode ID=0x00000007 Parent=0x4713F8A8 SizeRef=403,1273 Split=Y Selected=0x02B8E2DB + DockNode ID=0x00000009 Parent=0x00000007 SizeRef=403,535 Selected=0xF995F4A5 + DockNode ID=0x0000000A Parent=0x00000007 SizeRef=403,736 Selected=0x02B8E2DB + DockNode ID=0x00000008 Parent=0x4713F8A8 SizeRef=1899,1273 Split=X + DockNode ID=0x00000003 Parent=0x00000008 SizeRef=1470,1273 Split=Y + DockNode ID=0x00000001 Parent=0x00000003 SizeRef=2304,939 CentralNode=1 Selected=0x1C1AF642 + DockNode ID=0x00000002 Parent=0x00000003 SizeRef=2304,332 Selected=0x81DECE6A + DockNode ID=0x00000004 Parent=0x00000008 SizeRef=427,1273 Split=Y Selected=0x70CE1A73 + DockNode ID=0x00000005 Parent=0x00000004 SizeRef=427,623 Selected=0x70CE1A73 + DockNode ID=0x00000006 Parent=0x00000004 SizeRef=427,648 Selected=0x57A55B3F + From 0ce54812895e0d9e24c6245ff630a66637ac70e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20P=C3=A9ter?= Date: Thu, 2 Jul 2026 11:18:36 +0200 Subject: [PATCH 15/51] Implemented model and animation component mvi architecture, deleted cast shadow logics from tag component, refactored shaders --- .../Editor/EditorApi/EditorContext.cpp | 4 ++ .../Editor/EditorApi/EditorContext.h | 6 ++ .../EditorApi/Impl/AnimationApiImpl.cpp | 49 +++++++++++++++ .../Editor/EditorApi/Impl/AnimationApiImpl.h | 21 +++++++ .../EditorApi/Impl/ModelComponentApiImpl.cpp | 53 +++++++++++++++++ .../EditorApi/Impl/ModelComponentApiImpl.h | 24 ++++++++ SynapseEngine/Editor/Manager/EditorIcons.h | 11 +++- .../Editor/View/Component/ComponentView.cpp | 4 ++ .../Editor/View/Component/ComponentView.h | 5 ++ .../Component/Physics/BoxColliderView.cpp | 2 +- .../Component/Physics/CapsuleColliderView.cpp | 2 +- .../Component/Physics/ConvexColliderView.cpp | 2 +- .../Component/Physics/MeshColliderView.cpp | 2 +- .../View/Component/Physics/RigidBodyView.cpp | 2 +- .../Component/Physics/SphereColliderView.cpp | 2 +- .../Component/Rendering/AnimationView.cpp | 49 +++++++++++++++ .../View/Component/Rendering/AnimationView.h | 12 ++++ .../Rendering/ModelComponentView.cpp | 59 +++++++++++++++++++ .../Component/Rendering/ModelComponentView.h | 12 ++++ .../Editor/Workspace/SceneWorkspace.cpp | 4 +- SynapseEngine/EditorCore/Api/IAnimationApi.h | 22 +++++++ .../EditorCore/Api/IModelComponentApi.h | 24 ++++++++ .../ViewModels/Component/ComponentIntent.h | 20 ++++++- .../Component/ComponentViewModel.cpp | 18 +++++- .../ViewModels/Component/ComponentViewModel.h | 14 ++++- .../Rendering/Animation/AnimationCommands.cpp | 1 + .../Rendering/Animation/AnimationCommands.h | 9 +++ .../Rendering/Animation/AnimationIntent.cpp | 1 + .../Rendering/Animation/AnimationIntent.h | 19 ++++++ .../Rendering/Animation/AnimationState.cpp | 1 + .../Rendering/Animation/AnimationState.h | 14 +++++ .../Animation/AnimationViewModel.cpp | 56 ++++++++++++++++++ .../Rendering/Animation/AnimationViewModel.h | 27 +++++++++ .../Model/ModelComponentCommands.cpp | 1 + .../Rendering/Model/ModelComponentCommands.h | 10 ++++ .../Rendering/Model/ModelComponentIntent.cpp | 1 + .../Rendering/Model/ModelComponentIntent.h | 24 ++++++++ .../Rendering/Model/ModelComponentState.cpp | 1 + .../Rendering/Model/ModelComponentState.h | 15 +++++ .../Model/ModelComponentViewModel.cpp | 59 +++++++++++++++++++ .../Rendering/Model/ModelComponentViewModel.h | 24 ++++++++ .../Engine/Component/Core/TagComponent.cpp | 4 +- .../Engine/Component/Core/TagComponent.h | 3 - .../Component/Rendering/ModelComponent.cpp | 2 - .../Component/Rendering/ModelComponent.h | 1 - SynapseEngine/Engine/Engine.cpp | 4 ++ SynapseEngine/Engine/Engine.h | 2 + .../Converter/DefaultCpuModelExtractor.cpp | 2 +- .../Scene/Source/Procedural/test_config.json | 8 +-- .../Rendering/ModelComponentSchema.h | 1 - .../DirectionLightShadowModelCulling.comp | 6 +- ...irectionLightShadowMortonModelCulling.comp | 6 +- ...irectionLightShadowStaticModelCulling.comp | 6 +- .../PointLightShadowModelCulling.comp | 7 +-- .../PointLightShadowMortonModelCulling.comp | 6 +- .../PointLightShadowStaticModelCulling.comp | 6 +- .../SpotLightShadowModelCulling.comp | 8 +-- .../SpotLightShadowMortonModelCulling.comp | 6 +- .../SpotLightShadowStaticModelCulling.comp | 8 +-- .../Engine/System/Core/TagSystem.cpp | 2 - .../DirectionLightShadowCullingSystem.cpp | 16 ++--- .../Point/PointLightShadowCullingSystem.cpp | 12 ++-- .../Spot/SpotLightShadowCullingSystem.cpp | 12 ++-- 63 files changed, 740 insertions(+), 74 deletions(-) create mode 100644 SynapseEngine/Editor/EditorApi/Impl/AnimationApiImpl.cpp create mode 100644 SynapseEngine/Editor/EditorApi/Impl/AnimationApiImpl.h create mode 100644 SynapseEngine/Editor/EditorApi/Impl/ModelComponentApiImpl.cpp create mode 100644 SynapseEngine/Editor/EditorApi/Impl/ModelComponentApiImpl.h create mode 100644 SynapseEngine/Editor/View/Component/Rendering/AnimationView.cpp create mode 100644 SynapseEngine/Editor/View/Component/Rendering/AnimationView.h create mode 100644 SynapseEngine/Editor/View/Component/Rendering/ModelComponentView.cpp create mode 100644 SynapseEngine/Editor/View/Component/Rendering/ModelComponentView.h create mode 100644 SynapseEngine/EditorCore/Api/IAnimationApi.h create mode 100644 SynapseEngine/EditorCore/Api/IModelComponentApi.h create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Rendering/Animation/AnimationCommands.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Rendering/Animation/AnimationCommands.h create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Rendering/Animation/AnimationIntent.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Rendering/Animation/AnimationIntent.h create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Rendering/Animation/AnimationState.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Rendering/Animation/AnimationState.h create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Rendering/Animation/AnimationViewModel.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Rendering/Animation/AnimationViewModel.h create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Rendering/Model/ModelComponentCommands.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Rendering/Model/ModelComponentCommands.h create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Rendering/Model/ModelComponentIntent.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Rendering/Model/ModelComponentIntent.h create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Rendering/Model/ModelComponentState.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Rendering/Model/ModelComponentState.h create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Rendering/Model/ModelComponentViewModel.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Rendering/Model/ModelComponentViewModel.h diff --git a/SynapseEngine/Editor/EditorApi/EditorContext.cpp b/SynapseEngine/Editor/EditorApi/EditorContext.cpp index a93e945a..d4006661 100644 --- a/SynapseEngine/Editor/EditorApi/EditorContext.cpp +++ b/SynapseEngine/Editor/EditorApi/EditorContext.cpp @@ -21,6 +21,8 @@ #include "Impl/ConvexColliderApiImpl.h" #include "Impl/MeshColliderApiImpl.h" #include "Impl/RigidBodyApiImpl.h" +#include "Impl/ModelComponentApiImpl.h" +#include "Impl/AnimationApiImpl.h" namespace Syn { EditorContext::EditorContext(Engine* engine, GuiTextureManager* textureManager) { @@ -48,6 +50,8 @@ namespace Syn { _convexColliderApi = std::make_unique(sm); _meshColliderApi = std::make_unique(sm); _rigidBodyApi = std::make_unique(sm); + _modelComponentApi = std::make_unique(sm); + _animationApi = std::make_unique(sm); } EditorContext::~EditorContext() = default; diff --git a/SynapseEngine/Editor/EditorApi/EditorContext.h b/SynapseEngine/Editor/EditorApi/EditorContext.h index 90d5fc6e..8132e7c2 100644 --- a/SynapseEngine/Editor/EditorApi/EditorContext.h +++ b/SynapseEngine/Editor/EditorApi/EditorContext.h @@ -25,6 +25,8 @@ #include "EditorCore/Api/IConvexColliderApi.h" #include "EditorCore/Api/IMeshColliderApi.h" #include "EditorCore/Api/IRigidBodyApi.h" +#include "EditorCore/Api/IModelComponentApi.h" +#include "EditorCore/Api/IAnimationApi.h" namespace Syn { class EditorContext { @@ -54,6 +56,8 @@ namespace Syn { IConvexColliderApi* GetConvexColliderApi() const { return _convexColliderApi.get(); } IMeshColliderApi* GetMeshColliderApi() const { return _meshColliderApi.get(); } IRigidBodyApi* GetRigidBodyApi() const { return _rigidBodyApi.get(); } + IModelComponentApi* GetModelComponentApi() const { return _modelComponentApi.get(); } + IAnimationApi* GetAnimationApi() const { return _animationApi.get(); } private: std::unique_ptr _selectionApi; std::unique_ptr _tagApi; @@ -77,5 +81,7 @@ namespace Syn { std::unique_ptr _convexColliderApi; std::unique_ptr _meshColliderApi; std::unique_ptr _rigidBodyApi; + std::unique_ptr _modelComponentApi; + std::unique_ptr _animationApi; }; } \ No newline at end of file diff --git a/SynapseEngine/Editor/EditorApi/Impl/AnimationApiImpl.cpp b/SynapseEngine/Editor/EditorApi/Impl/AnimationApiImpl.cpp new file mode 100644 index 00000000..a9dffc07 --- /dev/null +++ b/SynapseEngine/Editor/EditorApi/Impl/AnimationApiImpl.cpp @@ -0,0 +1,49 @@ +#include "AnimationApiImpl.h" +#include "../EditorApiUtils.h" +#include "Engine/Component/Rendering/AnimationComponent.h" +#include "Engine/ServiceLocator.h" +#include "Engine/Animation/AnimationManager.h" + +namespace Syn { + bool AnimationApiImpl::HasAnimation(EntityID entity) const { + return EditorApiUtils::HasComponent(_sceneManager, entity); + } + + float AnimationApiImpl::GetAnimationSpeed(EntityID entity) const { + return EditorApiUtils::ReadComponent(_sceneManager, entity, [](const auto& c) { return c.speed; }, 1.0f); + } + + uint32_t AnimationApiImpl::GetAnimationIndex(EntityID entity) const { + return EditorApiUtils::ReadComponent(_sceneManager, entity, [](const auto& c) { return c.animationIndex; }, UINT32_MAX); + } + + void AnimationApiImpl::SetAnimationSpeed(EntityID entity, float speed) { + EditorApiUtils::ModifyComponent(_sceneManager, entity, [&](auto& c, auto pool) { c.speed = speed; }); + } + + void AnimationApiImpl::SetAnimationIndex(EntityID entity, uint32_t index) { + EditorApiUtils::ModifyComponent(_sceneManager, entity, [&](auto& c, auto pool) { + c.animationIndex = index; + }); + } + + std::vector> AnimationApiImpl::GetAvailableAnimations() const { + std::vector> result; + + auto animManager = ServiceLocator::GetAnimationManager(); + if (!animManager) { + return result; + } + + auto paths = animManager->GetResourcePaths(); + auto snapshots = animManager->GetResourceSnapshot(); + + for (uint32_t i = 0; i < paths.size(); ++i) { + if (snapshots[i].state == ResourceState::Ready) { + result.push_back({ i, paths[i] }); + } + } + + return result; + } +} \ No newline at end of file diff --git a/SynapseEngine/Editor/EditorApi/Impl/AnimationApiImpl.h b/SynapseEngine/Editor/EditorApi/Impl/AnimationApiImpl.h new file mode 100644 index 00000000..aee179ac --- /dev/null +++ b/SynapseEngine/Editor/EditorApi/Impl/AnimationApiImpl.h @@ -0,0 +1,21 @@ +#pragma once +#include "EditorCore/Api/IAnimationApi.h" +#include "Engine/Scene/SceneManager.h" + +namespace Syn { + class AnimationApiImpl : public IAnimationApi { + public: + AnimationApiImpl(SceneManager* sm) : _sceneManager(sm) {} + + bool HasAnimation(EntityID entity) const override; + float GetAnimationSpeed(EntityID entity) const override; + uint32_t GetAnimationIndex(EntityID entity) const override; + + void SetAnimationSpeed(EntityID entity, float speed) override; + void SetAnimationIndex(EntityID entity, uint32_t index) override; + + std::vector> GetAvailableAnimations() const override; + private: + SceneManager* _sceneManager; + }; +} \ No newline at end of file diff --git a/SynapseEngine/Editor/EditorApi/Impl/ModelComponentApiImpl.cpp b/SynapseEngine/Editor/EditorApi/Impl/ModelComponentApiImpl.cpp new file mode 100644 index 00000000..93ed7e21 --- /dev/null +++ b/SynapseEngine/Editor/EditorApi/Impl/ModelComponentApiImpl.cpp @@ -0,0 +1,53 @@ +#include "ModelComponentApiImpl.h" +#include "../EditorApiUtils.h" +#include "Engine/Component/Rendering/ModelComponent.h" +#include "Engine/ServiceLocator.h" +#include "Engine/Mesh/ModelManager.h" + +namespace Syn { + bool ModelComponentApiImpl::HasModelComponent(EntityID entity) const { + return EditorApiUtils::HasComponent(_sceneManager, entity); + } + + bool ModelComponentApiImpl::GetCastShadow(EntityID entity) const { + return EditorApiUtils::ReadComponent(_sceneManager, entity, [](const auto& c) { return c.castShadow; }, true); + } + + bool ModelComponentApiImpl::GetReceiveShadow(EntityID entity) const { + return EditorApiUtils::ReadComponent(_sceneManager, entity, [](const auto& c) { return c.receiveShadow; }, true); + } + + uint32_t ModelComponentApiImpl::GetModelIndex(EntityID entity) const { + return EditorApiUtils::ReadComponent(_sceneManager, entity, [](const auto& c) { return c.modelIndex; }, UINT32_MAX); + } + + void ModelComponentApiImpl::SetCastShadow(EntityID entity, bool cast) { + EditorApiUtils::ModifyComponent(_sceneManager, entity, [&](auto& c, auto pool) { c.castShadow = cast; }); + } + + void ModelComponentApiImpl::SetReceiveShadow(EntityID entity, bool receive) { + EditorApiUtils::ModifyComponent(_sceneManager, entity, [&](auto& c, auto pool) { c.receiveShadow = receive; }); + } + + void ModelComponentApiImpl::SetModelIndex(EntityID entity, uint32_t index) { + EditorApiUtils::ModifyComponent(_sceneManager, entity, [&](auto& c, auto pool) { c.modelIndex = index; }); + } + + std::vector> ModelComponentApiImpl::GetAvailableModels() const { + std::vector> result; + + auto modelManager = ServiceLocator::GetModelManager(); + if (!modelManager) return result; + + auto paths = modelManager->GetResourcePaths(); + auto snapshots = modelManager->GetResourceSnapshot(); + + for (uint32_t i = 0; i < paths.size(); ++i) { + if (snapshots[i].state == ResourceState::Ready) { + result.push_back({ i, paths[i] }); + } + } + + return result; + } +} \ No newline at end of file diff --git a/SynapseEngine/Editor/EditorApi/Impl/ModelComponentApiImpl.h b/SynapseEngine/Editor/EditorApi/Impl/ModelComponentApiImpl.h new file mode 100644 index 00000000..6d26270c --- /dev/null +++ b/SynapseEngine/Editor/EditorApi/Impl/ModelComponentApiImpl.h @@ -0,0 +1,24 @@ +#pragma once +#include "EditorCore/Api/IModelComponentApi.h" +#include "Engine/Scene/SceneManager.h" + +namespace Syn { + class ModelComponentApiImpl : public IModelComponentApi { + public: + ModelComponentApiImpl(SceneManager* sm) : _sceneManager(sm) {} + + bool HasModelComponent(EntityID entity) const override; + + bool GetCastShadow(EntityID entity) const override; + bool GetReceiveShadow(EntityID entity) const override; + uint32_t GetModelIndex(EntityID entity) const override; + + void SetCastShadow(EntityID entity, bool cast) override; + void SetReceiveShadow(EntityID entity, bool receive) override; + void SetModelIndex(EntityID entity, uint32_t index) override; + + std::vector> GetAvailableModels() const override; + private: + SceneManager* _sceneManager; + }; +} \ No newline at end of file diff --git a/SynapseEngine/Editor/Manager/EditorIcons.h b/SynapseEngine/Editor/Manager/EditorIcons.h index d39876a6..8d1add79 100644 --- a/SynapseEngine/Editor/Manager/EditorIcons.h +++ b/SynapseEngine/Editor/Manager/EditorIcons.h @@ -70,4 +70,13 @@ constexpr const char* ASSET_PATH = "Assets"; #define SYN_WS_SCENE SYN_ICON_GLOBE " Scene" #define SYN_WS_MODEL SYN_ICON_DRAW_POLYGON " Model" #define SYN_WS_MATERIAL SYN_ICON_MAGIC " Material" -#define SYN_WS_TEXTURE SYN_ICON_IMAGE " Texture" \ No newline at end of file +#define SYN_WS_TEXTURE SYN_ICON_IMAGE " Texture" + +#define SYN_ICON_MODEL ICON_FA_SHAPES +#define SYN_ICON_ANIMATION ICON_FA_RUNNING +#define SYN_ICON_BOX_COLLIDER ICON_FA_BOX +#define SYN_ICON_CAPSULE_COLLIDER ICON_FA_CAPSULES +#define SYN_ICON_CONVEX_COLLIDER ICON_FA_GEM +#define SYN_ICON_SPHERE_COLLIDER ICON_FA_CIRCLE +#define SYN_ICON_MESH_COLLIDER ICON_FA_DRAW_POLYGON +#define SYN_ICON_RIGID_BODY ICON_FA_WEIGHT_HANGING \ No newline at end of file diff --git a/SynapseEngine/Editor/View/Component/ComponentView.cpp b/SynapseEngine/Editor/View/Component/ComponentView.cpp index 40ee34b2..ad4276c9 100644 --- a/SynapseEngine/Editor/View/Component/ComponentView.cpp +++ b/SynapseEngine/Editor/View/Component/ComponentView.cpp @@ -26,6 +26,10 @@ namespace Syn { _transformView.Draw(vm.GetTransformViewModel()); _cameraView.Draw(vm.GetCameraViewModel()); + //Rendering + _modelComponentView.Draw(vm.GetModelComponentViewModel()); + _animationView.Draw(vm.GetAnimationViewModel()); + //Lights _directionLightView.Draw(vm.GetDirectionLightViewModel()); _pointLightView.Draw(vm.GetPointLightViewModel()); diff --git a/SynapseEngine/Editor/View/Component/ComponentView.h b/SynapseEngine/Editor/View/Component/ComponentView.h index b88bce7d..df499fa4 100644 --- a/SynapseEngine/Editor/View/Component/ComponentView.h +++ b/SynapseEngine/Editor/View/Component/ComponentView.h @@ -18,6 +18,9 @@ #include "Physics/MeshColliderView.h" #include "Physics/RigidBodyView.h" +#include "Rendering/ModelComponentView.h" +#include "Rendering/AnimationView.h" + namespace Syn { class ComponentView : public IView { public: @@ -35,5 +38,7 @@ namespace Syn { ConvexColliderView _convexColliderView; MeshColliderView _meshColliderView; RigidBodyView _rigidBodyView; + ModelComponentView _modelComponentView; + AnimationView _animationView; }; } \ No newline at end of file diff --git a/SynapseEngine/Editor/View/Component/Physics/BoxColliderView.cpp b/SynapseEngine/Editor/View/Component/Physics/BoxColliderView.cpp index 9839ad46..d707458c 100644 --- a/SynapseEngine/Editor/View/Component/Physics/BoxColliderView.cpp +++ b/SynapseEngine/Editor/View/Component/Physics/BoxColliderView.cpp @@ -13,7 +13,7 @@ namespace Syn { constexpr const char* CardTitle = "Box Collider"; - if (Syn::UI::BeginCard(CardTitle, SYN_ICON_CUBE, _isCardOpen)) + if (Syn::UI::BeginCard(CardTitle, SYN_ICON_BOX_COLLIDER, _isCardOpen)) { if (Syn::UI::BeginPropertyGrid("BoxColliderGrid")) { diff --git a/SynapseEngine/Editor/View/Component/Physics/CapsuleColliderView.cpp b/SynapseEngine/Editor/View/Component/Physics/CapsuleColliderView.cpp index 7972332e..c0fc8ee8 100644 --- a/SynapseEngine/Editor/View/Component/Physics/CapsuleColliderView.cpp +++ b/SynapseEngine/Editor/View/Component/Physics/CapsuleColliderView.cpp @@ -13,7 +13,7 @@ namespace Syn { constexpr const char* CardTitle = "Capsule Collider"; - if (Syn::UI::BeginCard(CardTitle, SYN_ICON_CUBE, _isCardOpen)) + if (Syn::UI::BeginCard(CardTitle, SYN_ICON_CAPSULE_COLLIDER, _isCardOpen)) { if (Syn::UI::BeginPropertyGrid("CapsuleColliderGrid")) { diff --git a/SynapseEngine/Editor/View/Component/Physics/ConvexColliderView.cpp b/SynapseEngine/Editor/View/Component/Physics/ConvexColliderView.cpp index 4830f752..3f47a83f 100644 --- a/SynapseEngine/Editor/View/Component/Physics/ConvexColliderView.cpp +++ b/SynapseEngine/Editor/View/Component/Physics/ConvexColliderView.cpp @@ -13,7 +13,7 @@ namespace Syn { constexpr const char* CardTitle = "Convex Collider"; - if (Syn::UI::BeginCard(CardTitle, SYN_ICON_CUBE, _isCardOpen)) + if (Syn::UI::BeginCard(CardTitle, SYN_ICON_CONVEX_COLLIDER, _isCardOpen)) { if (Syn::UI::BeginPropertyGrid("ConvexColliderGrid")) { diff --git a/SynapseEngine/Editor/View/Component/Physics/MeshColliderView.cpp b/SynapseEngine/Editor/View/Component/Physics/MeshColliderView.cpp index f4fdcf1c..c7d1df02 100644 --- a/SynapseEngine/Editor/View/Component/Physics/MeshColliderView.cpp +++ b/SynapseEngine/Editor/View/Component/Physics/MeshColliderView.cpp @@ -13,7 +13,7 @@ namespace Syn { constexpr const char* CardTitle = "Mesh Collider"; - if (Syn::UI::BeginCard(CardTitle, SYN_ICON_CUBE, _isCardOpen)) + if (Syn::UI::BeginCard(CardTitle, SYN_ICON_MESH_COLLIDER, _isCardOpen)) { if (Syn::UI::BeginPropertyGrid("MeshColliderGrid")) { diff --git a/SynapseEngine/Editor/View/Component/Physics/RigidBodyView.cpp b/SynapseEngine/Editor/View/Component/Physics/RigidBodyView.cpp index ebd67a41..9cf41c50 100644 --- a/SynapseEngine/Editor/View/Component/Physics/RigidBodyView.cpp +++ b/SynapseEngine/Editor/View/Component/Physics/RigidBodyView.cpp @@ -13,7 +13,7 @@ namespace Syn { constexpr const char* CardTitle = "Rigid Body"; - if (Syn::UI::BeginCard(CardTitle, SYN_ICON_CUBE, _isCardOpen)) + if (Syn::UI::BeginCard(CardTitle, SYN_ICON_RIGID_BODY, _isCardOpen)) { if (Syn::UI::BeginPropertyGrid("RigidBodyGrid")) { diff --git a/SynapseEngine/Editor/View/Component/Physics/SphereColliderView.cpp b/SynapseEngine/Editor/View/Component/Physics/SphereColliderView.cpp index 67ae833b..c7d770ff 100644 --- a/SynapseEngine/Editor/View/Component/Physics/SphereColliderView.cpp +++ b/SynapseEngine/Editor/View/Component/Physics/SphereColliderView.cpp @@ -13,7 +13,7 @@ namespace Syn { constexpr const char* CardTitle = "Sphere Collider"; - if (Syn::UI::BeginCard(CardTitle, SYN_ICON_CUBE, _isCardOpen)) + if (Syn::UI::BeginCard(CardTitle, SYN_ICON_SPHERE_COLLIDER, _isCardOpen)) { if (Syn::UI::BeginPropertyGrid("SphereColliderGrid")) { diff --git a/SynapseEngine/Editor/View/Component/Rendering/AnimationView.cpp b/SynapseEngine/Editor/View/Component/Rendering/AnimationView.cpp new file mode 100644 index 00000000..0e7f7404 --- /dev/null +++ b/SynapseEngine/Editor/View/Component/Rendering/AnimationView.cpp @@ -0,0 +1,49 @@ +#include "AnimationView.h" +#include "Editor/Manager/EditorIcons.h" +#include "Editor/Widgets/CardWidget.h" +#include "Editor/Widgets/PropertyGrid.h" +#include + +namespace Syn { + void AnimationView::Draw(AnimationViewModel& vm) { + AnimationState state = vm.GetState(); + if (!state.hasComponent) return; + + constexpr const char* CardTitle = "Animation Component"; + if (Syn::UI::BeginCard(CardTitle, SYN_ICON_ANIMATION, _isCardOpen)) { + + if (Syn::UI::BeginPropertyGrid("AnimationGrid")) + { + if (Syn::UI::PropertyDragFloat("Speed", state.speed, 0.05f, 0.0f, 10.0f, "%.2f x")) { + vm.Dispatch(SetAnimationSpeedIntent{ state.speed, !ImGui::IsItemDeactivatedAfterEdit() }); + } + + std::string previewName = "None"; + for (const auto& anim : state.availableAnimations) { + if (anim.first == state.animationIndex) { + previewName = anim.second; + break; + } + } + + if (Syn::UI::BeginPropertyCombo("Animation", previewName.c_str())) { + for (const auto& anim : state.availableAnimations) { + bool isSelected = (state.animationIndex == anim.first); + + if (ImGui::Selectable(anim.second.c_str(), isSelected)) { + vm.Dispatch(SetAnimationIndexIntent{ anim.first }); + } + + if (isSelected) { + ImGui::SetItemDefaultFocus(); + } + } + Syn::UI::EndPropertyCombo(); + } + + Syn::UI::EndPropertyGrid(); + } + } + Syn::UI::EndCard(); + } +} \ No newline at end of file diff --git a/SynapseEngine/Editor/View/Component/Rendering/AnimationView.h b/SynapseEngine/Editor/View/Component/Rendering/AnimationView.h new file mode 100644 index 00000000..c22b488e --- /dev/null +++ b/SynapseEngine/Editor/View/Component/Rendering/AnimationView.h @@ -0,0 +1,12 @@ +#pragma once +#include "Editor/View/IView.h" +#include "EditorCore/ViewModels/Component/Rendering/Animation/AnimationViewModel.h" + +namespace Syn { + class AnimationView : public IView { + public: + void Draw(AnimationViewModel& vm) override; + private: + bool _isCardOpen = true; + }; +} \ No newline at end of file diff --git a/SynapseEngine/Editor/View/Component/Rendering/ModelComponentView.cpp b/SynapseEngine/Editor/View/Component/Rendering/ModelComponentView.cpp new file mode 100644 index 00000000..c11aba7a --- /dev/null +++ b/SynapseEngine/Editor/View/Component/Rendering/ModelComponentView.cpp @@ -0,0 +1,59 @@ +#include "ModelComponentView.h" +#include "Editor/Manager/EditorIcons.h" +#include "Editor/Widgets/CardWidget.h" +#include "Editor/Widgets/PropertyGrid.h" +#include + +namespace Syn { + void ModelComponentView::Draw(ModelComponentViewModel& vm) { + ModelComponentState state = vm.GetState(); + if (!state.hasComponent) return; + + constexpr const char* CardTitle = "Model Component"; + + if (Syn::UI::BeginCard(CardTitle, SYN_ICON_MODEL, _isCardOpen)) { + if (Syn::UI::BeginPropertyGrid("ModelGrid")) + { + std::string previewName = "None (UINT32_MAX)"; + if (state.modelIndex != UINT32_MAX) { + for (const auto& model : state.availableModels) { + if (model.first == state.modelIndex) { + previewName = model.second; + break; + } + } + } + + if (Syn::UI::BeginPropertyCombo("Model", previewName.c_str())) { + for (const auto& model : state.availableModels) { + bool isSelected = (state.modelIndex == model.first); + + if (ImGui::Selectable(model.second.c_str(), isSelected)) { + vm.Dispatch(SetModelIndexIntent{ model.first }); + } + + if (isSelected) { + ImGui::SetItemDefaultFocus(); + } + } + Syn::UI::EndPropertyCombo(); + } + + Syn::UI::PropertySeparator(); + + bool castShadow = state.castShadow; + if (Syn::UI::PropertyCheckbox("Cast Shadow", castShadow)) { + vm.Dispatch(SetModelCastShadowIntent{ castShadow }); + } + + bool receiveShadow = state.receiveShadow; + if (Syn::UI::PropertyCheckbox("Receive Shadow", receiveShadow)) { + vm.Dispatch(SetModelReceiveShadowIntent{ receiveShadow }); + } + + Syn::UI::EndPropertyGrid(); + } + } + Syn::UI::EndCard(); + } +} \ No newline at end of file diff --git a/SynapseEngine/Editor/View/Component/Rendering/ModelComponentView.h b/SynapseEngine/Editor/View/Component/Rendering/ModelComponentView.h new file mode 100644 index 00000000..9715ef06 --- /dev/null +++ b/SynapseEngine/Editor/View/Component/Rendering/ModelComponentView.h @@ -0,0 +1,12 @@ +#pragma once +#include "Editor/View/IView.h" +#include "EditorCore/ViewModels/Component/Rendering/Model/ModelComponentViewModel.h" + +namespace Syn { + class ModelComponentView : public IView { + public: + void Draw(ModelComponentViewModel& vm) override; + private: + bool _isCardOpen = true; + }; +} \ No newline at end of file diff --git a/SynapseEngine/Editor/Workspace/SceneWorkspace.cpp b/SynapseEngine/Editor/Workspace/SceneWorkspace.cpp index ee645cf6..1fa0966d 100644 --- a/SynapseEngine/Editor/Workspace/SceneWorkspace.cpp +++ b/SynapseEngine/Editor/Workspace/SceneWorkspace.cpp @@ -46,7 +46,9 @@ namespace Syn { _context->GetCapsuleColliderApi(), _context->GetConvexColliderApi(), _context->GetMeshColliderApi(), - _context->GetRigidBodyApi() + _context->GetRigidBodyApi(), + _context->GetModelComponentApi(), + _context->GetAnimationApi() } ); diff --git a/SynapseEngine/EditorCore/Api/IAnimationApi.h b/SynapseEngine/EditorCore/Api/IAnimationApi.h new file mode 100644 index 00000000..f84cde32 --- /dev/null +++ b/SynapseEngine/EditorCore/Api/IAnimationApi.h @@ -0,0 +1,22 @@ +#pragma once +#include "EditorCore/Types/EntityHandle.h" +#include +#include +#include + +namespace Syn { + class IAnimationApi { + public: + virtual ~IAnimationApi() = default; + + virtual bool HasAnimation(EntityID entity) const = 0; + + virtual float GetAnimationSpeed(EntityID entity) const = 0; + virtual uint32_t GetAnimationIndex(EntityID entity) const = 0; + + virtual void SetAnimationSpeed(EntityID entity, float speed) = 0; + virtual void SetAnimationIndex(EntityID entity, uint32_t index) = 0; + + virtual std::vector> GetAvailableAnimations() const = 0; + }; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/Api/IModelComponentApi.h b/SynapseEngine/EditorCore/Api/IModelComponentApi.h new file mode 100644 index 00000000..031a9622 --- /dev/null +++ b/SynapseEngine/EditorCore/Api/IModelComponentApi.h @@ -0,0 +1,24 @@ +#pragma once +#include "EditorCore/Types/EntityHandle.h" +#include +#include +#include + +namespace Syn { + class IModelComponentApi { + public: + virtual ~IModelComponentApi() = default; + + virtual bool HasModelComponent(EntityID entity) const = 0; + + virtual bool GetCastShadow(EntityID entity) const = 0; + virtual bool GetReceiveShadow(EntityID entity) const = 0; + virtual uint32_t GetModelIndex(EntityID entity) const = 0; + + virtual void SetCastShadow(EntityID entity, bool cast) = 0; + virtual void SetReceiveShadow(EntityID entity, bool receive) = 0; + virtual void SetModelIndex(EntityID entity, uint32_t index) = 0; + + virtual std::vector> GetAvailableModels() const = 0; + }; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/ComponentIntent.h b/SynapseEngine/EditorCore/ViewModels/Component/ComponentIntent.h index b12771e6..fe3cdd6d 100644 --- a/SynapseEngine/EditorCore/ViewModels/Component/ComponentIntent.h +++ b/SynapseEngine/EditorCore/ViewModels/Component/ComponentIntent.h @@ -1,17 +1,35 @@ #pragma once #include "Core/Tag/TagIntent.h" +#include "Core/Camera/CameraIntent.h" #include "Core/Transform/TransformIntent.h" #include "Light/DirectionLight/DirectionLightIntent.h" #include "Light/PointLight/PointLightIntent.h" #include "Light/SpotLight/SpotLightIntent.h" +#include "Rendering/Model/ModelComponentIntent.h" +#include "Rendering/Animation/AnimationIntent.h" +#include "Physics/BoxCollider/BoxColliderIntent.h" +#include "Physics/SphereCollider/SphereColliderIntent.h" +#include "Physics/CapsuleCollider/CapsuleColliderIntent.h" +#include "Physics/ConvexCollider/ConvexColliderIntent.h" +#include "Physics/MeshCollider/MeshColliderIntent.h" +#include "Physics/RigidBody/RigidBodyIntent.h" #include namespace Syn { using ComponentIntent = std::variant< TagIntent, + CameraIntent, TransformIntent, DirectionLightIntent, PointLightIntent, - SpotLightIntent + SpotLightIntent, + ModelComponentIntent, + AnimationIntent, + BoxColliderIntent, + SphereColliderIntent, + CapsuleColliderIntent, + ConvexColliderIntent, + MeshColliderIntent, + RigidBodyIntent >; } \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/ComponentViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/Component/ComponentViewModel.cpp index 7c40930b..35df052a 100644 --- a/SynapseEngine/EditorCore/ViewModels/Component/ComponentViewModel.cpp +++ b/SynapseEngine/EditorCore/ViewModels/Component/ComponentViewModel.cpp @@ -16,7 +16,9 @@ namespace Syn ICapsuleColliderApi* capsuleColliderApi, IConvexColliderApi* convexColliderApi, IMeshColliderApi* meshColliderApi, - IRigidBodyApi* rigidBodyApi + IRigidBodyApi* rigidBodyApi, + IModelComponentApi* modelComponentApi, + IAnimationApi* animationApi ) : _selectionApi(selectionApi), @@ -31,7 +33,9 @@ namespace Syn _capsuleColliderViewModel(selectionApi, capsuleColliderApi), _convexColliderViewModel(selectionApi, convexColliderApi), _meshColliderViewModel(selectionApi, meshColliderApi), - _rigidBodyViewModel(selectionApi, rigidBodyApi) + _rigidBodyViewModel(selectionApi, rigidBodyApi), + _modelComponentViewModel(selectionApi, modelComponentApi), + _animationViewModel(selectionApi, animationApi) {} const ComponentState& ComponentViewModel::GetState() const { @@ -59,6 +63,8 @@ namespace Syn _convexColliderViewModel.SyncWithEngine(); _meshColliderViewModel.SyncWithEngine(); _rigidBodyViewModel.SyncWithEngine(); + _modelComponentViewModel.SyncWithEngine(); + _animationViewModel.SyncWithEngine(); } } @@ -102,6 +108,12 @@ namespace Syn else if constexpr (std::is_same_v) { _rigidBodyViewModel.Dispatch(arg); } - }, intent); + else if constexpr (std::is_same_v) { + _modelComponentViewModel.Dispatch(arg); + } + else if constexpr (std::is_same_v) { + _animationViewModel.Dispatch(arg); + } + }, intent); } } \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/ComponentViewModel.h b/SynapseEngine/EditorCore/ViewModels/Component/ComponentViewModel.h index 66cd1f36..24994bc6 100644 --- a/SynapseEngine/EditorCore/ViewModels/Component/ComponentViewModel.h +++ b/SynapseEngine/EditorCore/ViewModels/Component/ComponentViewModel.h @@ -17,6 +17,9 @@ #include "Physics/MeshCollider/MeshColliderViewModel.h" #include "Physics/RigidBody/RigidBodyViewModel.h" +#include "Rendering/Model/ModelComponentViewModel.h" +#include "Rendering/Animation/AnimationViewModel.h" + #include "EditorCore/Api/ISelectionApi.h" #include "EditorCore/Api/ITagApi.h" #include "EditorCore/Api/ITransformApi.h" @@ -29,6 +32,8 @@ #include "EditorCore/Api/IConvexColliderApi.h" #include "EditorCore/Api/IMeshColliderApi.h" #include "EditorCore/Api/IRigidBodyApi.h" +#include "EditorCore/Api/IModelComponentApi.h" +#include "EditorCore/Api/IAnimationApi.h" namespace Syn { class ComponentViewModel : public IViewModel { @@ -47,7 +52,9 @@ namespace Syn { ICapsuleColliderApi* capsuleColliderApi, IConvexColliderApi* convexColliderApi, IMeshColliderApi* meshColliderApi, - IRigidBodyApi* rigidBodyApi + IRigidBodyApi* rigidBodyApi, + IModelComponentApi* modelComponentApi, + IAnimationApi* animationApi ); ~ComponentViewModel() override = default; @@ -68,6 +75,8 @@ namespace Syn { ConvexColliderViewModel& GetConvexColliderViewModel() { return _convexColliderViewModel; } MeshColliderViewModel& GetMeshColliderViewModel() { return _meshColliderViewModel; } RigidBodyViewModel& GetRigidBodyViewModel() { return _rigidBodyViewModel; } + ModelComponentViewModel& GetModelComponentViewModel() { return _modelComponentViewModel; } + AnimationViewModel& GetAnimationViewModel() { return _animationViewModel; } private: ISelectionApi* _selectionApi = nullptr; ComponentState _state; @@ -85,5 +94,8 @@ namespace Syn { ConvexColliderViewModel _convexColliderViewModel; MeshColliderViewModel _meshColliderViewModel; RigidBodyViewModel _rigidBodyViewModel; + + ModelComponentViewModel _modelComponentViewModel; + AnimationViewModel _animationViewModel; }; } \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Rendering/Animation/AnimationCommands.cpp b/SynapseEngine/EditorCore/ViewModels/Component/Rendering/Animation/AnimationCommands.cpp new file mode 100644 index 00000000..afe978e6 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Rendering/Animation/AnimationCommands.cpp @@ -0,0 +1 @@ +#include "AnimationCommands.h" \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Rendering/Animation/AnimationCommands.h b/SynapseEngine/EditorCore/ViewModels/Component/Rendering/Animation/AnimationCommands.h new file mode 100644 index 00000000..cc635cd2 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Rendering/Animation/AnimationCommands.h @@ -0,0 +1,9 @@ +#pragma once +#include "EditorCore/Command/ComponentChangeCommand.h" +#include "EditorCore/Api/IAnimationApi.h" + +namespace Syn +{ + using ChangeAnimationSpeedCommand = ComponentChangeCommand; + using ChangeAnimationIndexCommand = ComponentChangeCommand; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Rendering/Animation/AnimationIntent.cpp b/SynapseEngine/EditorCore/ViewModels/Component/Rendering/Animation/AnimationIntent.cpp new file mode 100644 index 00000000..f30b86d7 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Rendering/Animation/AnimationIntent.cpp @@ -0,0 +1 @@ +#include "AnimationIntent.h" \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Rendering/Animation/AnimationIntent.h b/SynapseEngine/EditorCore/ViewModels/Component/Rendering/Animation/AnimationIntent.h new file mode 100644 index 00000000..bcaaee0c --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Rendering/Animation/AnimationIntent.h @@ -0,0 +1,19 @@ +#pragma once +#include +#include + +namespace Syn +{ + struct SetAnimationSpeedIntent + { + float speed; + bool isDragging; + }; + + struct SetAnimationIndexIntent + { + uint32_t animationIndex; + }; + + using AnimationIntent = std::variant; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Rendering/Animation/AnimationState.cpp b/SynapseEngine/EditorCore/ViewModels/Component/Rendering/Animation/AnimationState.cpp new file mode 100644 index 00000000..56e2f782 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Rendering/Animation/AnimationState.cpp @@ -0,0 +1 @@ +#include "AnimationState.h" \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Rendering/Animation/AnimationState.h b/SynapseEngine/EditorCore/ViewModels/Component/Rendering/Animation/AnimationState.h new file mode 100644 index 00000000..d429c759 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Rendering/Animation/AnimationState.h @@ -0,0 +1,14 @@ +#pragma once +#include +#include +#include + +namespace Syn { + struct AnimationState { + bool hasComponent = false; + float speed; + uint32_t animationIndex; + + std::vector> availableAnimations; + }; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Rendering/Animation/AnimationViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/Component/Rendering/Animation/AnimationViewModel.cpp new file mode 100644 index 00000000..93d1cebd --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Rendering/Animation/AnimationViewModel.cpp @@ -0,0 +1,56 @@ +#include "AnimationViewModel.h" + +namespace Syn +{ + AnimationViewModel::AnimationViewModel(ISelectionApi* selectionApi, IAnimationApi* animApi) + : _selectionApi(selectionApi), _animApi(animApi) {} + + const AnimationState& AnimationViewModel::GetState() const { return _state; } + + void AnimationViewModel::SyncWithEngine() + { + if (!_selectionApi || !_animApi) + return; + + EntityID activeEntity = _selectionApi->GetSelectedEntity(); + + if (activeEntity != NULL_ENTITY && _animApi->HasAnimation(activeEntity)) + { + _state.hasComponent = true; + + if (!_speedDrag.IsDragging()) + _state.speed = _animApi->GetAnimationSpeed(activeEntity); + + _state.animationIndex = _animApi->GetAnimationIndex(activeEntity); + _state.availableAnimations = _animApi->GetAvailableAnimations(); + } + else + { + _state.hasComponent = false; + } + } + + void AnimationViewModel::Dispatch(const AnimationIntent& intent) + { + EntityID active = _selectionApi->GetSelectedEntity(); + if (active == NULL_ENTITY) + return; + + std::visit([this, active](auto&& arg) + { + using T = std::decay_t; + + if constexpr (std::is_same_v) { + _speedDrag.Handle(arg.isDragging, arg.speed, _state.speed, + [&](const float& v) { _animApi->SetAnimationSpeed(active, v); }, + [&](const float& s, const float& e) { return std::make_shared(_animApi, active, s, e); }); + } + else if constexpr (std::is_same_v) { + if (_state.animationIndex != arg.animationIndex) { + _state.animationIndex = arg.animationIndex; + _animApi->SetAnimationIndex(active, arg.animationIndex); + } + } + }, intent); + } +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Rendering/Animation/AnimationViewModel.h b/SynapseEngine/EditorCore/ViewModels/Component/Rendering/Animation/AnimationViewModel.h new file mode 100644 index 00000000..9e583e32 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Rendering/Animation/AnimationViewModel.h @@ -0,0 +1,27 @@ +#pragma once +#include "EditorCore/ViewModels/IViewModel.h" +#include "EditorCore/Interaction/DragInteraction.h" +#include "AnimationState.h" +#include "AnimationIntent.h" +#include "AnimationCommands.h" +#include "EditorCore/Api/ISelectionApi.h" +#include "EditorCore/Api/IAnimationApi.h" + +namespace Syn { + class AnimationViewModel : public IViewModel { + public: + AnimationViewModel(ISelectionApi* selectionApi, IAnimationApi* animApi); + ~AnimationViewModel() override = default; + + const AnimationState& GetState() const override; + void SyncWithEngine() override; + void Dispatch(const AnimationIntent& intent) override; + + private: + ISelectionApi* _selectionApi = nullptr; + IAnimationApi* _animApi = nullptr; + AnimationState _state; + + DragInteraction _speedDrag; + }; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Rendering/Model/ModelComponentCommands.cpp b/SynapseEngine/EditorCore/ViewModels/Component/Rendering/Model/ModelComponentCommands.cpp new file mode 100644 index 00000000..a07393a3 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Rendering/Model/ModelComponentCommands.cpp @@ -0,0 +1 @@ +#include "ModelComponentCommands.h" \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Rendering/Model/ModelComponentCommands.h b/SynapseEngine/EditorCore/ViewModels/Component/Rendering/Model/ModelComponentCommands.h new file mode 100644 index 00000000..1bcdab54 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Rendering/Model/ModelComponentCommands.h @@ -0,0 +1,10 @@ +#pragma once +#include "EditorCore/Command/ComponentChangeCommand.h" +#include "EditorCore/Api/IModelComponentApi.h" + +namespace Syn +{ + using ChangeModelCastShadowCommand = ComponentChangeCommand; + using ChangeModelReceiveShadowCommand = ComponentChangeCommand; + using ChangeModelIndexCommand = ComponentChangeCommand; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Rendering/Model/ModelComponentIntent.cpp b/SynapseEngine/EditorCore/ViewModels/Component/Rendering/Model/ModelComponentIntent.cpp new file mode 100644 index 00000000..7d69b986 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Rendering/Model/ModelComponentIntent.cpp @@ -0,0 +1 @@ +#include "ModelComponentIntent.h" \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Rendering/Model/ModelComponentIntent.h b/SynapseEngine/EditorCore/ViewModels/Component/Rendering/Model/ModelComponentIntent.h new file mode 100644 index 00000000..08781419 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Rendering/Model/ModelComponentIntent.h @@ -0,0 +1,24 @@ +#pragma once +#include +#include + +namespace Syn +{ + struct SetModelCastShadowIntent { + bool castShadow; + }; + + struct SetModelReceiveShadowIntent { + bool receiveShadow; + }; + + struct SetModelIndexIntent { + uint32_t modelIndex; + }; + + using ModelComponentIntent = std::variant< + SetModelCastShadowIntent, + SetModelReceiveShadowIntent, + SetModelIndexIntent + >; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Rendering/Model/ModelComponentState.cpp b/SynapseEngine/EditorCore/ViewModels/Component/Rendering/Model/ModelComponentState.cpp new file mode 100644 index 00000000..1f182972 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Rendering/Model/ModelComponentState.cpp @@ -0,0 +1 @@ +#include "ModelComponentState.h" \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Rendering/Model/ModelComponentState.h b/SynapseEngine/EditorCore/ViewModels/Component/Rendering/Model/ModelComponentState.h new file mode 100644 index 00000000..e1dd4397 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Rendering/Model/ModelComponentState.h @@ -0,0 +1,15 @@ +#pragma once +#include +#include +#include + +namespace Syn { + struct ModelComponentState { + bool hasComponent = false; + bool castShadow; + bool receiveShadow; + uint32_t modelIndex; + + std::vector> availableModels; + }; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Rendering/Model/ModelComponentViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/Component/Rendering/Model/ModelComponentViewModel.cpp new file mode 100644 index 00000000..5b7cc850 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Rendering/Model/ModelComponentViewModel.cpp @@ -0,0 +1,59 @@ +#include "ModelComponentViewModel.h" + +namespace Syn +{ + ModelComponentViewModel::ModelComponentViewModel(ISelectionApi* selectionApi, IModelComponentApi* modelApi) + : _selectionApi(selectionApi), _modelApi(modelApi) {} + + const ModelComponentState& ModelComponentViewModel::GetState() const { return _state; } + + void ModelComponentViewModel::SyncWithEngine() + { + if (!_selectionApi || !_modelApi) + return; + + EntityID activeEntity = _selectionApi->GetSelectedEntity(); + + if (activeEntity != NULL_ENTITY && _modelApi->HasModelComponent(activeEntity)) + { + _state.hasComponent = true; + + _state.castShadow = _modelApi->GetCastShadow(activeEntity); + _state.receiveShadow = _modelApi->GetReceiveShadow(activeEntity); + _state.modelIndex = _modelApi->GetModelIndex(activeEntity); + + _state.availableModels = _modelApi->GetAvailableModels(); + } + else + { + _state.hasComponent = false; + } + } + + void ModelComponentViewModel::Dispatch(const ModelComponentIntent& intent) + { + EntityID active = _selectionApi->GetSelectedEntity(); + if (active == NULL_ENTITY) + return; + + std::visit([this, active](auto&& arg) + { + using T = std::decay_t; + + if constexpr (std::is_same_v) { + _state.castShadow = arg.castShadow; + _modelApi->SetCastShadow(active, arg.castShadow); + } + else if constexpr (std::is_same_v) { + _state.receiveShadow = arg.receiveShadow; + _modelApi->SetReceiveShadow(active, arg.receiveShadow); + } + else if constexpr (std::is_same_v) { + if (_state.modelIndex != arg.modelIndex) { + _state.modelIndex = arg.modelIndex; + _modelApi->SetModelIndex(active, arg.modelIndex); + } + } + }, intent); + } +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Rendering/Model/ModelComponentViewModel.h b/SynapseEngine/EditorCore/ViewModels/Component/Rendering/Model/ModelComponentViewModel.h new file mode 100644 index 00000000..447ab425 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Rendering/Model/ModelComponentViewModel.h @@ -0,0 +1,24 @@ +#pragma once +#include "EditorCore/ViewModels/IViewModel.h" +#include "ModelComponentState.h" +#include "ModelComponentIntent.h" +#include "ModelComponentCommands.h" +#include "EditorCore/Api/ISelectionApi.h" +#include "EditorCore/Api/IModelComponentApi.h" + +namespace Syn { + class ModelComponentViewModel : public IViewModel { + public: + ModelComponentViewModel(ISelectionApi* selectionApi, IModelComponentApi* modelApi); + ~ModelComponentViewModel() override = default; + + const ModelComponentState& GetState() const override; + void SyncWithEngine() override; + void Dispatch(const ModelComponentIntent& intent) override; + + private: + ISelectionApi* _selectionApi = nullptr; + IModelComponentApi* _modelApi = nullptr; + ModelComponentState _state; + }; +} \ No newline at end of file diff --git a/SynapseEngine/Engine/Component/Core/TagComponent.cpp b/SynapseEngine/Engine/Component/Core/TagComponent.cpp index eafe4fff..267f3131 100644 --- a/SynapseEngine/Engine/Component/Core/TagComponent.cpp +++ b/SynapseEngine/Engine/Component/Core/TagComponent.cpp @@ -6,9 +6,7 @@ namespace Syn name("Entity"), tag("Untagged"), localEnabled(true), - globalEnabled(true), - castShadow(true), - receiveShadow(true) + globalEnabled(true) {} TagComponent::TagComponent(const std::string& name, const std::string& tag) : diff --git a/SynapseEngine/Engine/Component/Core/TagComponent.h b/SynapseEngine/Engine/Component/Core/TagComponent.h index 5a62d19f..da52130d 100644 --- a/SynapseEngine/Engine/Component/Core/TagComponent.h +++ b/SynapseEngine/Engine/Component/Core/TagComponent.h @@ -15,8 +15,5 @@ namespace Syn bool localEnabled = true; bool globalEnabled = true; - - bool castShadow = true; - bool receiveShadow = true; }; } \ No newline at end of file diff --git a/SynapseEngine/Engine/Component/Rendering/ModelComponent.cpp b/SynapseEngine/Engine/Component/Rendering/ModelComponent.cpp index 1cbbc97c..497d478a 100644 --- a/SynapseEngine/Engine/Component/Rendering/ModelComponent.cpp +++ b/SynapseEngine/Engine/Component/Rendering/ModelComponent.cpp @@ -6,7 +6,6 @@ namespace Syn isReady(false), castShadow(true), receiveShadow(true), - hasDirectxNormals(false), modelIndex(UINT32_MAX) {} @@ -18,7 +17,6 @@ namespace Syn uint32_t flags = 0; if (component.castShadow) flags |= (1 << 0); if (component.receiveShadow) flags |= (1 << 1); - if (component.hasDirectxNormals) flags |= (1 << 2); this->flags = flags; } } \ No newline at end of file diff --git a/SynapseEngine/Engine/Component/Rendering/ModelComponent.h b/SynapseEngine/Engine/Component/Rendering/ModelComponent.h index e3f9845c..076b65f5 100644 --- a/SynapseEngine/Engine/Component/Rendering/ModelComponent.h +++ b/SynapseEngine/Engine/Component/Rendering/ModelComponent.h @@ -12,7 +12,6 @@ namespace Syn bool castShadow; bool receiveShadow; - bool hasDirectxNormals; uint32_t modelIndex; uint32_t materialOffset = UINT32_MAX; }; diff --git a/SynapseEngine/Engine/Engine.cpp b/SynapseEngine/Engine/Engine.cpp index 3ca5a36b..9efe7580 100644 --- a/SynapseEngine/Engine/Engine.cpp +++ b/SynapseEngine/Engine/Engine.cpp @@ -375,4 +375,8 @@ namespace Syn ModelManager* Engine::GetModelManager() { return ServiceLocator::GetModelManager(); } + + AnimationManager* Engine::GetAnimationManager() { + return ServiceLocator::GetAnimationManager(); + } } \ No newline at end of file diff --git a/SynapseEngine/Engine/Engine.h b/SynapseEngine/Engine/Engine.h index efbf3210..18545c7d 100644 --- a/SynapseEngine/Engine/Engine.h +++ b/SynapseEngine/Engine/Engine.h @@ -28,6 +28,7 @@ namespace Syn { class MaterialManager; class ImageManager; class ModelManager; + class AnimationManager; } namespace Syn @@ -64,6 +65,7 @@ namespace Syn MaterialManager* GetMaterialManager(); ImageManager* GetImageManager(); ModelManager* GetModelManager(); + AnimationManager* GetAnimationManager(); std::shared_ptr GetMemorySink() const { return _memorySink; } private: void Init(const EngineInitParams& params); diff --git a/SynapseEngine/Engine/Mesh/Converter/DefaultCpuModelExtractor.cpp b/SynapseEngine/Engine/Mesh/Converter/DefaultCpuModelExtractor.cpp index 111fef73..02bd6f82 100644 --- a/SynapseEngine/Engine/Mesh/Converter/DefaultCpuModelExtractor.cpp +++ b/SynapseEngine/Engine/Mesh/Converter/DefaultCpuModelExtractor.cpp @@ -42,7 +42,7 @@ namespace Syn blueprint.meshletCmd.groupCountY = groupCountY; blueprint.meshletCmd.groupCountZ = 1; - blueprint.isMeshletPipeline = rand() % 2 == 0 ? MeshDrawBlueprint::PIPELINE_MESHLET : MeshDrawBlueprint::PIPELINE_TRADITIONAL; + blueprint.isMeshletPipeline = true ? MeshDrawBlueprint::PIPELINE_MESHLET : MeshDrawBlueprint::PIPELINE_TRADITIONAL; outCpuData.baseDrawCommands.push_back(blueprint); } diff --git a/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json b/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json index 1608f4f8..8d5b3da8 100644 --- a/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json +++ b/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json @@ -22,9 +22,9 @@ }, "lights": { "directional_count": 1, - "point_count": 256, - "point_shadow_count": 32, - "spot_count": 256, - "spot_shadow_count": 32 + "point_count": 32, + "point_shadow_count": 16, + "spot_count": 32, + "spot_shadow_count": 16 } } \ No newline at end of file diff --git a/SynapseEngine/Engine/Serialization/Schema/Component/Rendering/ModelComponentSchema.h b/SynapseEngine/Engine/Serialization/Schema/Component/Rendering/ModelComponentSchema.h index ac63a7d1..8035a434 100644 --- a/SynapseEngine/Engine/Serialization/Schema/Component/Rendering/ModelComponentSchema.h +++ b/SynapseEngine/Engine/Serialization/Schema/Component/Rendering/ModelComponentSchema.h @@ -17,7 +17,6 @@ namespace Syn ar.Property("castShadow", comp.castShadow); ar.Property("receiveShadow", comp.receiveShadow); - ar.Property("hasDirectxNormals", comp.hasDirectxNormals); ar.Property("modelIndex", comp.modelIndex); ar.Property("materialOffset", comp.materialOffset); } diff --git a/SynapseEngine/Engine/Shaders/Passes/Culling/DirectionLight/DirectionLightShadowModelCulling.comp b/SynapseEngine/Engine/Shaders/Passes/Culling/DirectionLight/DirectionLightShadowModelCulling.comp index 3773ea0f..348c0e4f 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Culling/DirectionLight/DirectionLightShadowModelCulling.comp +++ b/SynapseEngine/Engine/Shaders/Passes/Culling/DirectionLight/DirectionLightShadowModelCulling.comp @@ -56,12 +56,12 @@ void main() uint entityFlags = GET_TAG_DATA(ctx.tagDataBufferAddr, tagSparseIndex); bool isActive = (entityFlags & (1u << 0)) != 0; - bool castShadow = (entityFlags & (1u << 1)) != 0; - if (!isActive || !castShadow) return; + if (!isActive) return; ModelComponent modelComp = GET_MODEL_COMP(ctx.modelBufferAddr, link.modelDenseIndex); - if (modelComp.modelIndex == INVALID_INDEX) return; + bool castShadow = (modelComp.flags & (1u << 0)) != 0; + if (modelComp.modelIndex == INVALID_INDEX || !castShadow) return; // 3. Resolve Transform and Main Camera (Camera is required for LOD calculations) TransformComponent transform = GET_TRANSFORM(ctx.transformBufferAddr, transformDenseIndex); diff --git a/SynapseEngine/Engine/Shaders/Passes/Culling/DirectionLight/DirectionLightShadowMortonModelCulling.comp b/SynapseEngine/Engine/Shaders/Passes/Culling/DirectionLight/DirectionLightShadowMortonModelCulling.comp index 81ea8e2a..074d6baa 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Culling/DirectionLight/DirectionLightShadowMortonModelCulling.comp +++ b/SynapseEngine/Engine/Shaders/Passes/Culling/DirectionLight/DirectionLightShadowMortonModelCulling.comp @@ -78,13 +78,15 @@ void main() { uint entityFlags = GET_TAG_DATA(ctx.tagDataBufferAddr, tagSparseIndex); bool isActive = (entityFlags & (1u << 0)) != 0; - bool castShadow = (entityFlags & (1u << 1)) != 0; - if (!isActive || !castShadow) return; + if (!isActive) return; TransformComponent transform = GET_TRANSFORM(ctx.transformBufferAddr, denseIndex); ModelComponent modelComp = GET_MODEL_COMP(ctx.modelBufferAddr, link.modelDenseIndex); + bool castShadow = (modelComp.flags & (1u << 0)) != 0; + if (modelComp.modelIndex == INVALID_INDEX || !castShadow) return; + ModelAllocationInfo mAlloc = GET_MODEL_ALLOC(ctx.globalModelAllocationBufferAddr, modelComp.modelIndex); GpuModelAddresses addrs = GET_MODEL_ADDRESSES(ctx.modelAddressBufferAddr, modelComp.modelIndex); diff --git a/SynapseEngine/Engine/Shaders/Passes/Culling/DirectionLight/DirectionLightShadowStaticModelCulling.comp b/SynapseEngine/Engine/Shaders/Passes/Culling/DirectionLight/DirectionLightShadowStaticModelCulling.comp index 8404a69f..8468e72b 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Culling/DirectionLight/DirectionLightShadowStaticModelCulling.comp +++ b/SynapseEngine/Engine/Shaders/Passes/Culling/DirectionLight/DirectionLightShadowStaticModelCulling.comp @@ -74,13 +74,15 @@ void main() { uint entityFlags = GET_TAG_DATA(ctx.tagDataBufferAddr, tagSparseIndex); bool isActive = (entityFlags & (1u << 0)) != 0; - bool castShadow = (entityFlags & (1u << 1)) != 0; - if (!isActive || !castShadow) return; + if (!isActive) return; TransformComponent transform = GET_TRANSFORM(ctx.transformBufferAddr, denseIndex); ModelComponent modelComp = GET_MODEL_COMP(ctx.modelBufferAddr, link.modelDenseIndex); + bool castShadow = (modelComp.flags & (1u << 0)) != 0; + if (modelComp.modelIndex == INVALID_INDEX || !castShadow) return; + ModelAllocationInfo mAlloc = GET_MODEL_ALLOC(ctx.globalModelAllocationBufferAddr, modelComp.modelIndex); GpuModelAddresses addrs = GET_MODEL_ADDRESSES(ctx.modelAddressBufferAddr, modelComp.modelIndex); diff --git a/SynapseEngine/Engine/Shaders/Passes/Culling/PointLight/PointLightShadowModelCulling.comp b/SynapseEngine/Engine/Shaders/Passes/Culling/PointLight/PointLightShadowModelCulling.comp index 69932731..7a350028 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Culling/PointLight/PointLightShadowModelCulling.comp +++ b/SynapseEngine/Engine/Shaders/Passes/Culling/PointLight/PointLightShadowModelCulling.comp @@ -53,12 +53,11 @@ void main() uint entityFlags = GET_TAG_DATA(ctx.tagDataBufferAddr, tagSparseIndex); bool isActive = (entityFlags & (1u << 0)) != 0; - bool castShadow = (entityFlags & (1u << 1)) != 0; - - if (!isActive || !castShadow) return; + if (!isActive) return; ModelComponent modelComp = GET_MODEL_COMP(ctx.modelBufferAddr, link.modelDenseIndex); - if (modelComp.modelIndex == INVALID_INDEX) return; + bool castShadow = (modelComp.flags & (1u << 0)) != 0; + if (modelComp.modelIndex == INVALID_INDEX || !castShadow) return; // 3. Resolve Transform, Camera & Colliders TransformComponent transform = GET_TRANSFORM(ctx.transformBufferAddr, transformDenseIndex); diff --git a/SynapseEngine/Engine/Shaders/Passes/Culling/PointLight/PointLightShadowMortonModelCulling.comp b/SynapseEngine/Engine/Shaders/Passes/Culling/PointLight/PointLightShadowMortonModelCulling.comp index fc0a091b..068c94d2 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Culling/PointLight/PointLightShadowMortonModelCulling.comp +++ b/SynapseEngine/Engine/Shaders/Passes/Culling/PointLight/PointLightShadowMortonModelCulling.comp @@ -74,13 +74,15 @@ void main() { uint entityFlags = GET_TAG_DATA(ctx.tagDataBufferAddr, tagSparseIndex); bool isActive = (entityFlags & (1u << 0)) != 0; - bool castShadow = (entityFlags & (1u << 1)) != 0; - if (!isActive || !castShadow) return; + if (!isActive) return; TransformComponent transform = GET_TRANSFORM(ctx.transformBufferAddr, denseIndex); ModelComponent modelComp = GET_MODEL_COMP(ctx.modelBufferAddr, link.modelDenseIndex); + bool castShadow = (modelComp.flags & (1u << 0)) != 0; + if (modelComp.modelIndex == INVALID_INDEX || !castShadow) return; + ModelAllocationInfo mAlloc = GET_MODEL_ALLOC(ctx.globalModelAllocationBufferAddr, modelComp.modelIndex); GpuModelAddresses addrs = GET_MODEL_ADDRESSES(ctx.modelAddressBufferAddr, modelComp.modelIndex); diff --git a/SynapseEngine/Engine/Shaders/Passes/Culling/PointLight/PointLightShadowStaticModelCulling.comp b/SynapseEngine/Engine/Shaders/Passes/Culling/PointLight/PointLightShadowStaticModelCulling.comp index 0955992e..5272e9c6 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Culling/PointLight/PointLightShadowStaticModelCulling.comp +++ b/SynapseEngine/Engine/Shaders/Passes/Culling/PointLight/PointLightShadowStaticModelCulling.comp @@ -70,13 +70,15 @@ void main() { uint entityFlags = GET_TAG_DATA(ctx.tagDataBufferAddr, tagSparseIndex); bool isActive = (entityFlags & (1u << 0)) != 0; - bool castShadow = (entityFlags & (1u << 1)) != 0; - if (!isActive || !castShadow) return; + if (!isActive) return; TransformComponent transform = GET_TRANSFORM(ctx.transformBufferAddr, denseIndex); ModelComponent modelComp = GET_MODEL_COMP(ctx.modelBufferAddr, link.modelDenseIndex); + bool castShadow = (modelComp.flags & (1u << 0)) != 0; + if (modelComp.modelIndex == INVALID_INDEX || !castShadow) return; + ModelAllocationInfo mAlloc = GET_MODEL_ALLOC(ctx.globalModelAllocationBufferAddr, modelComp.modelIndex); GpuModelAddresses addrs = GET_MODEL_ADDRESSES(ctx.modelAddressBufferAddr, modelComp.modelIndex); diff --git a/SynapseEngine/Engine/Shaders/Passes/Culling/SpotLight/SpotLightShadowModelCulling.comp b/SynapseEngine/Engine/Shaders/Passes/Culling/SpotLight/SpotLightShadowModelCulling.comp index 4a44a349..daf3d644 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Culling/SpotLight/SpotLightShadowModelCulling.comp +++ b/SynapseEngine/Engine/Shaders/Passes/Culling/SpotLight/SpotLightShadowModelCulling.comp @@ -53,12 +53,12 @@ void main() uint entityFlags = GET_TAG_DATA(ctx.tagDataBufferAddr, tagSparseIndex); bool isActive = (entityFlags & (1u << 0)) != 0; - bool castShadow = (entityFlags & (1u << 1)) != 0; - - if (!isActive || !castShadow) return; + if (!isActive) return; ModelComponent modelComp = GET_MODEL_COMP(ctx.modelBufferAddr, link.modelDenseIndex); - if (modelComp.modelIndex == INVALID_INDEX) return; + bool castShadow = (modelComp.flags & (1u << 0)) != 0; + if (modelComp.modelIndex == INVALID_INDEX || !castShadow) return; + // 3. Resolve Transform, Camera & Colliders TransformComponent transform = GET_TRANSFORM(ctx.transformBufferAddr, transformDenseIndex); diff --git a/SynapseEngine/Engine/Shaders/Passes/Culling/SpotLight/SpotLightShadowMortonModelCulling.comp b/SynapseEngine/Engine/Shaders/Passes/Culling/SpotLight/SpotLightShadowMortonModelCulling.comp index a75b9207..4e735809 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Culling/SpotLight/SpotLightShadowMortonModelCulling.comp +++ b/SynapseEngine/Engine/Shaders/Passes/Culling/SpotLight/SpotLightShadowMortonModelCulling.comp @@ -76,13 +76,15 @@ void main() { uint entityFlags = GET_TAG_DATA(ctx.tagDataBufferAddr, tagSparseIndex); bool isActive = (entityFlags & (1u << 0)) != 0; - bool castShadow = (entityFlags & (1u << 1)) != 0; - if (!isActive || !castShadow) return; + if (!isActive) return; TransformComponent transform = GET_TRANSFORM(ctx.transformBufferAddr, denseIndex); ModelComponent modelComp = GET_MODEL_COMP(ctx.modelBufferAddr, link.modelDenseIndex); + bool castShadow = (modelComp.flags & (1u << 0)) != 0; + if (modelComp.modelIndex == INVALID_INDEX || !castShadow) return; + ModelAllocationInfo mAlloc = GET_MODEL_ALLOC(ctx.globalModelAllocationBufferAddr, modelComp.modelIndex); GpuModelAddresses addrs = GET_MODEL_ADDRESSES(ctx.modelAddressBufferAddr, modelComp.modelIndex); diff --git a/SynapseEngine/Engine/Shaders/Passes/Culling/SpotLight/SpotLightShadowStaticModelCulling.comp b/SynapseEngine/Engine/Shaders/Passes/Culling/SpotLight/SpotLightShadowStaticModelCulling.comp index 57fd9f97..e8a09347 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Culling/SpotLight/SpotLightShadowStaticModelCulling.comp +++ b/SynapseEngine/Engine/Shaders/Passes/Culling/SpotLight/SpotLightShadowStaticModelCulling.comp @@ -71,14 +71,14 @@ void main() { uint entityFlags = GET_TAG_DATA(ctx.tagDataBufferAddr, tagSparseIndex); bool isActive = (entityFlags & (1u << 0)) != 0; - bool castShadow = (entityFlags & (1u << 1)) != 0; - - if (!isActive || !castShadow) return; - + if (!isActive) return; TransformComponent transform = GET_TRANSFORM(ctx.transformBufferAddr, denseIndex); ModelComponent modelComp = GET_MODEL_COMP(ctx.modelBufferAddr, link.modelDenseIndex); + bool castShadow = (modelComp.flags & (1u << 0)) != 0; + if (modelComp.modelIndex == INVALID_INDEX || !castShadow) return; + ModelAllocationInfo mAlloc = GET_MODEL_ALLOC(ctx.globalModelAllocationBufferAddr, modelComp.modelIndex); GpuModelAddresses addrs = GET_MODEL_ADDRESSES(ctx.modelAddressBufferAddr, modelComp.modelIndex); diff --git a/SynapseEngine/Engine/System/Core/TagSystem.cpp b/SynapseEngine/Engine/System/Core/TagSystem.cpp index d6b248b4..ec3584a8 100644 --- a/SynapseEngine/Engine/System/Core/TagSystem.cpp +++ b/SynapseEngine/Engine/System/Core/TagSystem.cpp @@ -84,8 +84,6 @@ namespace Syn uint32_t flags = 0; if (tagComp.globalEnabled) flags |= (1 << 0); - if (tagComp.castShadow) flags |= (1 << 1); - if (tagComp.receiveShadow) flags |= (1 << 2); tagDataBufferHandler[tagIndex] = flags; } diff --git a/SynapseEngine/Engine/System/Light/Direction/DirectionLightShadowCullingSystem.cpp b/SynapseEngine/Engine/System/Light/Direction/DirectionLightShadowCullingSystem.cpp index 512a3bce..d11d6236 100644 --- a/SynapseEngine/Engine/System/Light/Direction/DirectionLightShadowCullingSystem.cpp +++ b/SynapseEngine/Engine/System/Light/Direction/DirectionLightShadowCullingSystem.cpp @@ -122,20 +122,22 @@ namespace Syn // Extract entity properties (runs exactly once per entity) auto withEntityData = [modelPool, transformPool, animPool, overridePool, modelSnapshot, tagPool, animSnapshot, drawData] (EntityID entity, auto&& nextFunc) { - if (tagPool && tagPool->Has(entity)) { - const auto& tag = tagPool->Get(entity); - if (!tag.globalEnabled || !tag.castShadow) { - return; - } - } - if (!modelPool->Has(entity)) return; const auto& modelComp = modelPool->Get(entity); + if (modelComp.modelIndex == NULL_INDEX || modelComp.modelIndex >= drawData->Models.modelAllocations.Size()) return; + if (tagPool && tagPool->Has(entity)) { + const auto& tag = tagPool->Get(entity); + + if (!tag.globalEnabled || !modelComp.castShadow) { + return; + } + } + const auto& snapshotEntry = modelSnapshot[modelComp.modelIndex]; if (snapshotEntry.resource == nullptr || snapshotEntry.state != ResourceState::Ready) return; diff --git a/SynapseEngine/Engine/System/Light/Point/PointLightShadowCullingSystem.cpp b/SynapseEngine/Engine/System/Light/Point/PointLightShadowCullingSystem.cpp index ff6fe3e9..6dd21af5 100644 --- a/SynapseEngine/Engine/System/Light/Point/PointLightShadowCullingSystem.cpp +++ b/SynapseEngine/Engine/System/Light/Point/PointLightShadowCullingSystem.cpp @@ -124,18 +124,18 @@ namespace Syn // Extract Entity Data (Runs once per entity) auto withEntityData = [modelPool, transformPool, animPool, overridePool, modelSnapshot, tagPool, animSnapshot, drawData] (EntityID entity, auto&& nextFunc) { + if (!modelPool->Has(entity)) return; + + const auto& modelComp = modelPool->Get(entity); + if (modelComp.modelIndex == NULL_INDEX || modelComp.modelIndex >= drawData->Models.modelAllocations.Size()) return; + if (tagPool && tagPool->Has(entity)) { const auto& tag = tagPool->Get(entity); - if (!tag.globalEnabled || !tag.castShadow) { + if (!tag.globalEnabled || !modelComp.castShadow) { return; } } - if (!modelPool->Has(entity)) return; - - const auto& modelComp = modelPool->Get(entity); - if (modelComp.modelIndex == NULL_INDEX || modelComp.modelIndex >= drawData->Models.modelAllocations.Size()) return; - const auto& snapshotEntry = modelSnapshot[modelComp.modelIndex]; if (snapshotEntry.resource == nullptr || snapshotEntry.state != ResourceState::Ready) return; diff --git a/SynapseEngine/Engine/System/Light/Spot/SpotLightShadowCullingSystem.cpp b/SynapseEngine/Engine/System/Light/Spot/SpotLightShadowCullingSystem.cpp index 3b4fdea6..94da96ea 100644 --- a/SynapseEngine/Engine/System/Light/Spot/SpotLightShadowCullingSystem.cpp +++ b/SynapseEngine/Engine/System/Light/Spot/SpotLightShadowCullingSystem.cpp @@ -132,18 +132,18 @@ namespace Syn // Extract Entity Data (Runs once per entity) auto withEntityData = [modelPool, transformPool, animPool, overridePool, modelSnapshot, tagPool, animSnapshot, drawData] (EntityID entity, auto&& nextFunc) { + if (!modelPool->Has(entity)) return; + + const auto& modelComp = modelPool->Get(entity); + if (modelComp.modelIndex == NULL_INDEX || modelComp.modelIndex >= drawData->Models.modelAllocations.Size()) return; + if (tagPool && tagPool->Has(entity)) { const auto& tag = tagPool->Get(entity); - if (!tag.globalEnabled || !tag.castShadow) { + if (!tag.globalEnabled || !modelComp.castShadow) { return; } } - if (!modelPool->Has(entity)) return; - - const auto& modelComp = modelPool->Get(entity); - if (modelComp.modelIndex == NULL_INDEX || modelComp.modelIndex >= drawData->Models.modelAllocations.Size()) return; - const auto& snapshotEntry = modelSnapshot[modelComp.modelIndex]; if (snapshotEntry.resource == nullptr || snapshotEntry.state != ResourceState::Ready) return; From b1488b0851bf30e1ff66c6d7d853de9196004290 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20P=C3=A9ter?= Date: Thu, 2 Jul 2026 12:02:51 +0200 Subject: [PATCH 16/51] Resolved model and material desync bug --- .../Component/Rendering/ModelComponent.cpp | 1 - .../Component/Rendering/ModelComponent.h | 1 - .../Scene/Source/Procedural/test_config.json | 10 +++--- .../System/Rendering/MaterialSystem.cpp | 33 ++++++++++--------- 4 files changed, 22 insertions(+), 23 deletions(-) diff --git a/SynapseEngine/Engine/Component/Rendering/ModelComponent.cpp b/SynapseEngine/Engine/Component/Rendering/ModelComponent.cpp index 497d478a..3cda4864 100644 --- a/SynapseEngine/Engine/Component/Rendering/ModelComponent.cpp +++ b/SynapseEngine/Engine/Component/Rendering/ModelComponent.cpp @@ -3,7 +3,6 @@ namespace Syn { ModelComponent::ModelComponent() : - isReady(false), castShadow(true), receiveShadow(true), modelIndex(UINT32_MAX) diff --git a/SynapseEngine/Engine/Component/Rendering/ModelComponent.h b/SynapseEngine/Engine/Component/Rendering/ModelComponent.h index 076b65f5..dd0185ff 100644 --- a/SynapseEngine/Engine/Component/Rendering/ModelComponent.h +++ b/SynapseEngine/Engine/Component/Rendering/ModelComponent.h @@ -8,7 +8,6 @@ namespace Syn struct SYN_API ModelComponent : public Component { ModelComponent(); - bool isReady; bool castShadow; bool receiveShadow; diff --git a/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json b/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json index 8d5b3da8..dfb69894 100644 --- a/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json +++ b/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json @@ -15,16 +15,16 @@ }, "entities": { "animated_characters": 500, - "static_geometry": 50000, + "static_geometry": 5000, "physics_boxes": 500, "physics_spheres": 500, "physics_capsules": 500 }, "lights": { "directional_count": 1, - "point_count": 32, - "point_shadow_count": 16, - "spot_count": 32, - "spot_shadow_count": 16 + "point_count": 128, + "point_shadow_count": 32, + "spot_count": 128, + "spot_shadow_count": 32 } } \ No newline at end of file diff --git a/SynapseEngine/Engine/System/Rendering/MaterialSystem.cpp b/SynapseEngine/Engine/System/Rendering/MaterialSystem.cpp index d40ff4b5..3b23e980 100644 --- a/SynapseEngine/Engine/System/Rendering/MaterialSystem.cpp +++ b/SynapseEngine/Engine/System/Rendering/MaterialSystem.cpp @@ -43,10 +43,16 @@ namespace Syn } - if (!pool->IsStateBitSet() && !pool->IsStateBitSet() && !needsRebuild) { + bool hasChanges = !pool->GetDirtyStatics().empty() || + pool->IsStateBitSet() || + pool->IsStateBitSet() || + needsRebuild; + + if (!hasChanges) { return; } + needsUpload = true; auto& modelSnapshots = scene->GetSystemContext().modelSnapshots; uint32_t totalExactMaterials = 0; @@ -91,23 +97,18 @@ namespace Syn overrides = overrideComp.materials; } - bool wasReady = comp.isReady; - comp.isReady = true; - - if (comp.materialOffset != currentOffset || !wasReady) { - if constexpr (ENABLE_DEBUG_LOGGING) { - std::string entityName = "Unknown"; - if (tagPool && tagPool->Has(entity)) entityName = tagPool->Get(entity).name; - Info("[MaterialSystem UPDATE] Frame {}: Entity {} data changed! OldOffset: {}, NewOffset: {}, WasReady: {}", scene->GetSystemContext().frameIndex, entityName, comp.materialOffset, currentOffset, wasReady); - } + if constexpr (ENABLE_DEBUG_LOGGING) { + std::string entityName = "Unknown"; + if (tagPool && tagPool->Has(entity)) entityName = tagPool->Get(entity).name; + Info("[MaterialSystem UPDATE] Frame {}: Entity {} data changed! OldOffset: {}, NewOffset: {}, WasReady: {}", scene->GetSystemContext().frameIndex, entityName, comp.materialOffset, currentOffset); + } - comp.materialOffset = currentOffset; - comp.version++; - pool->SetBit(entity); - pool->MarkStaticDirty(entity); + comp.materialOffset = currentOffset; + comp.version++; + pool->SetBit(entity); + pool->MarkStaticDirty(entity); - needsUpload = true; - } + needsUpload = true; if constexpr (ENABLE_DEBUG_LOGGING) { std::string entityName = "Unknown"; From ab693a7b6cf342e24f5a4a5ee97e17a33b157e67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20P=C3=A9ter?= Date: Thu, 2 Jul 2026 12:55:11 +0200 Subject: [PATCH 17/51] Implemented material override MVI, fixed material override system. Material sharing, material override works fine!! --- .../Editor/EditorApi/EditorContext.cpp | 2 + .../Editor/EditorApi/EditorContext.h | 3 + .../Impl/MaterialOverrideApiImpl.cpp | 112 ++++++++++++++++++ .../EditorApi/Impl/MaterialOverrideApiImpl.h | 24 ++++ .../Editor/View/Component/ComponentView.cpp | 3 +- .../Editor/View/Component/ComponentView.h | 2 + .../Rendering/MaterialOverrideView.cpp | 94 +++++++++++++++ .../Rendering/MaterialOverrideView.h | 12 ++ .../Editor/Workspace/SceneWorkspace.cpp | 3 +- .../EditorCore/Api/IMaterialOverrideApi.h | 25 ++++ .../ViewModels/Component/ComponentIntent.h | 4 +- .../Component/ComponentViewModel.cpp | 10 +- .../ViewModels/Component/ComponentViewModel.h | 7 +- .../MaterialOverrideIntent.cpp | 1 + .../MaterialOverride/MaterialOverrideIntent.h | 21 ++++ .../MaterialOverrideState.cpp | 1 + .../MaterialOverride/MaterialOverrideState.h | 18 +++ .../MaterialOverrideViewModel.cpp | 60 ++++++++++ .../MaterialOverrideViewModel.h | 23 ++++ SynapseEngine/Engine/Scene/Scene.cpp | 3 + .../Source/Procedural/TestSceneSource.cpp | 4 + .../Rendering/MaterialOverrideSystem.cpp | 8 ++ .../System/Rendering/MaterialOverrideSystem.h | 15 +++ .../System/Rendering/MaterialSystem.cpp | 45 ++++++- 24 files changed, 488 insertions(+), 12 deletions(-) create mode 100644 SynapseEngine/Editor/EditorApi/Impl/MaterialOverrideApiImpl.cpp create mode 100644 SynapseEngine/Editor/EditorApi/Impl/MaterialOverrideApiImpl.h create mode 100644 SynapseEngine/Editor/View/Component/Rendering/MaterialOverrideView.cpp create mode 100644 SynapseEngine/Editor/View/Component/Rendering/MaterialOverrideView.h create mode 100644 SynapseEngine/EditorCore/Api/IMaterialOverrideApi.h create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Rendering/MaterialOverride/MaterialOverrideIntent.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Rendering/MaterialOverride/MaterialOverrideIntent.h create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Rendering/MaterialOverride/MaterialOverrideState.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Rendering/MaterialOverride/MaterialOverrideState.h create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Rendering/MaterialOverride/MaterialOverrideViewModel.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/Component/Rendering/MaterialOverride/MaterialOverrideViewModel.h create mode 100644 SynapseEngine/Engine/System/Rendering/MaterialOverrideSystem.cpp create mode 100644 SynapseEngine/Engine/System/Rendering/MaterialOverrideSystem.h diff --git a/SynapseEngine/Editor/EditorApi/EditorContext.cpp b/SynapseEngine/Editor/EditorApi/EditorContext.cpp index d4006661..d5584104 100644 --- a/SynapseEngine/Editor/EditorApi/EditorContext.cpp +++ b/SynapseEngine/Editor/EditorApi/EditorContext.cpp @@ -23,6 +23,7 @@ #include "Impl/RigidBodyApiImpl.h" #include "Impl/ModelComponentApiImpl.h" #include "Impl/AnimationApiImpl.h" +#include "Impl/MaterialOverrideApiImpl.h" namespace Syn { EditorContext::EditorContext(Engine* engine, GuiTextureManager* textureManager) { @@ -52,6 +53,7 @@ namespace Syn { _rigidBodyApi = std::make_unique(sm); _modelComponentApi = std::make_unique(sm); _animationApi = std::make_unique(sm); + _materialOverrideApi = std::make_unique(sm); } EditorContext::~EditorContext() = default; diff --git a/SynapseEngine/Editor/EditorApi/EditorContext.h b/SynapseEngine/Editor/EditorApi/EditorContext.h index 8132e7c2..330d9f80 100644 --- a/SynapseEngine/Editor/EditorApi/EditorContext.h +++ b/SynapseEngine/Editor/EditorApi/EditorContext.h @@ -27,6 +27,7 @@ #include "EditorCore/Api/IRigidBodyApi.h" #include "EditorCore/Api/IModelComponentApi.h" #include "EditorCore/Api/IAnimationApi.h" +#include "EditorCore/Api/IMaterialOverrideApi.h" namespace Syn { class EditorContext { @@ -58,6 +59,7 @@ namespace Syn { IRigidBodyApi* GetRigidBodyApi() const { return _rigidBodyApi.get(); } IModelComponentApi* GetModelComponentApi() const { return _modelComponentApi.get(); } IAnimationApi* GetAnimationApi() const { return _animationApi.get(); } + IMaterialOverrideApi* GetMaterialOverrideApi() { return _materialOverrideApi.get(); } private: std::unique_ptr _selectionApi; std::unique_ptr _tagApi; @@ -83,5 +85,6 @@ namespace Syn { std::unique_ptr _rigidBodyApi; std::unique_ptr _modelComponentApi; std::unique_ptr _animationApi; + std::unique_ptr _materialOverrideApi; }; } \ No newline at end of file diff --git a/SynapseEngine/Editor/EditorApi/Impl/MaterialOverrideApiImpl.cpp b/SynapseEngine/Editor/EditorApi/Impl/MaterialOverrideApiImpl.cpp new file mode 100644 index 00000000..7dad6fa3 --- /dev/null +++ b/SynapseEngine/Editor/EditorApi/Impl/MaterialOverrideApiImpl.cpp @@ -0,0 +1,112 @@ +#include "MaterialOverrideApiImpl.h" +#include "../EditorApiUtils.h" +#include "Engine/Component/Rendering/MaterialOverrideComponent.h" +#include "Engine/Component/Rendering/ModelComponent.h" +#include "Engine/ServiceLocator.h" +#include "Engine/Mesh/ModelManager.h" +#include "Engine/Material/MaterialManager.h" +#include "Engine/Component/Core/TagComponent.h" + +namespace Syn { + bool MaterialOverrideApiImpl::HasMaterialOverride(EntityID entity) const { + return EditorApiUtils::HasComponent(_sceneManager, entity); + } + + uint32_t MaterialOverrideApiImpl::GetExpectedSlotCount(EntityID entity) const { + if (!EditorApiUtils::HasComponent(_sceneManager, entity)) return 0; + + uint32_t modelIdx = EditorApiUtils::ReadComponent(_sceneManager, entity, [](const auto& c) { return c.modelIndex; }, UINT32_MAX); + if (modelIdx == UINT32_MAX) return 0; + + auto modelManager = ServiceLocator::GetModelManager(); + if (!modelManager) return 0; + + auto snapshots = modelManager->GetResourceSnapshot(); + if (modelIdx < snapshots.size() && snapshots[modelIdx].state == ResourceState::Ready && snapshots[modelIdx].resource) { + return static_cast(snapshots[modelIdx].resource->cpuData.meshMaterialIndices.size()); + } + return 0; + } + + uint32_t MaterialOverrideApiImpl::GetMaterialAtSlot(EntityID entity, uint32_t slotIndex) const { + return EditorApiUtils::ReadComponent(_sceneManager, entity, [slotIndex](const auto& c) { + if (slotIndex < c.materials.size()) { + return c.materials[slotIndex]; + } + return UINT32_MAX; + }, UINT32_MAX); + } + + void MaterialOverrideApiImpl::SetMaterialAtSlot(EntityID entity, uint32_t slotIndex, uint32_t materialId) { + EditorApiUtils::ModifyComponent(_sceneManager, entity, [&](auto& c, auto pool) { + uint32_t expectedSize = GetExpectedSlotCount(entity); + if (c.materials.size() != expectedSize) { + c.materials.resize(expectedSize, UINT32_MAX); + } + if (slotIndex < c.materials.size()) { + c.materials[slotIndex] = materialId; + pool->template SetBit(entity); + } + }); + } + + EntityID MaterialOverrideApiImpl::GetSharedMaterialEntity(EntityID entity) const { + return EditorApiUtils::ReadComponent(_sceneManager, entity, [](const auto& c) { return c.sharedMaterialEntity; }, NULL_ENTITY); + } + + void MaterialOverrideApiImpl::SetSharedMaterialEntity(EntityID entity, EntityID sharedEntity) { + EditorApiUtils::ModifyComponent(_sceneManager, entity, [&](auto& c, auto pool) { + c.sharedMaterialEntity = sharedEntity; + pool->template SetBit(entity); + }); + } + + std::vector> MaterialOverrideApiImpl::GetAvailableMaterials() const { + std::vector> result; + + auto matManager = ServiceLocator::GetMaterialManager(); + if (!matManager) return result; + + auto paths = matManager->GetResourcePaths(); + auto snapshots = matManager->GetResourceSnapshot(); + + for (uint32_t i = 0; i < paths.size(); ++i) { + if (snapshots[i].state == ResourceState::Ready) { + result.push_back({ i, paths[i] }); + } + } + + return result; + } + + std::vector> MaterialOverrideApiImpl::GetCompatibleSharedEntities(EntityID targetEntity) const { + std::vector> result; + + auto scene = _sceneManager->GetActiveScene(); + if (!scene || !scene->GetRegistry()) return result; + + uint32_t targetSlotCount = GetExpectedSlotCount(targetEntity); + if (targetSlotCount == 0) return result; + + auto registry = scene->GetRegistry(); + + for (EntityID entity : registry->GetActiveEntities().GetDenseEntities()) { + if (entity == targetEntity) continue; + + if (registry->HasComponent(entity)) { + uint32_t currentSlotCount = GetExpectedSlotCount(entity); + + if (currentSlotCount == targetSlotCount) { + std::string entityName = "Entity " + std::to_string(entity); + if (registry->HasComponent(entity)) { + entityName = registry->GetComponent(entity).name; + } + + result.push_back({ entity, entityName + " (ID: " + std::to_string(entity) + ")" }); + } + } + } + + return result; + } +} \ No newline at end of file diff --git a/SynapseEngine/Editor/EditorApi/Impl/MaterialOverrideApiImpl.h b/SynapseEngine/Editor/EditorApi/Impl/MaterialOverrideApiImpl.h new file mode 100644 index 00000000..fa187bd2 --- /dev/null +++ b/SynapseEngine/Editor/EditorApi/Impl/MaterialOverrideApiImpl.h @@ -0,0 +1,24 @@ +#pragma once +#include "EditorCore/Api/IMaterialOverrideApi.h" +#include "Engine/Scene/SceneManager.h" + +namespace Syn { + class MaterialOverrideApiImpl : public IMaterialOverrideApi { + public: + MaterialOverrideApiImpl(SceneManager* sm) : _sceneManager(sm) {} + + bool HasMaterialOverride(EntityID entity) const override; + + uint32_t GetExpectedSlotCount(EntityID entity) const override; + uint32_t GetMaterialAtSlot(EntityID entity, uint32_t slotIndex) const override; + void SetMaterialAtSlot(EntityID entity, uint32_t slotIndex, uint32_t materialId) override; + + EntityID GetSharedMaterialEntity(EntityID entity) const override; + void SetSharedMaterialEntity(EntityID entity, EntityID sharedEntity) override; + + std::vector> GetCompatibleSharedEntities(EntityID entity) const override; + std::vector> GetAvailableMaterials() const override; + private: + SceneManager* _sceneManager; + }; +} \ No newline at end of file diff --git a/SynapseEngine/Editor/View/Component/ComponentView.cpp b/SynapseEngine/Editor/View/Component/ComponentView.cpp index ad4276c9..2ed0e349 100644 --- a/SynapseEngine/Editor/View/Component/ComponentView.cpp +++ b/SynapseEngine/Editor/View/Component/ComponentView.cpp @@ -28,7 +28,8 @@ namespace Syn { //Rendering _modelComponentView.Draw(vm.GetModelComponentViewModel()); - _animationView.Draw(vm.GetAnimationViewModel()); + _materialOverrideView.Draw(vm.GetMaterialOverrideViewModel()); + _animationView.Draw(vm.GetAnimationViewModel()); //Lights _directionLightView.Draw(vm.GetDirectionLightViewModel()); diff --git a/SynapseEngine/Editor/View/Component/ComponentView.h b/SynapseEngine/Editor/View/Component/ComponentView.h index df499fa4..b1bca46c 100644 --- a/SynapseEngine/Editor/View/Component/ComponentView.h +++ b/SynapseEngine/Editor/View/Component/ComponentView.h @@ -20,6 +20,7 @@ #include "Rendering/ModelComponentView.h" #include "Rendering/AnimationView.h" +#include "Rendering/MaterialOverrideView.h" namespace Syn { class ComponentView : public IView { @@ -40,5 +41,6 @@ namespace Syn { RigidBodyView _rigidBodyView; ModelComponentView _modelComponentView; AnimationView _animationView; + MaterialOverrideView _materialOverrideView; }; } \ No newline at end of file diff --git a/SynapseEngine/Editor/View/Component/Rendering/MaterialOverrideView.cpp b/SynapseEngine/Editor/View/Component/Rendering/MaterialOverrideView.cpp new file mode 100644 index 00000000..6355cbbf --- /dev/null +++ b/SynapseEngine/Editor/View/Component/Rendering/MaterialOverrideView.cpp @@ -0,0 +1,94 @@ +#include "MaterialOverrideView.h" +#include "Editor/Manager/EditorIcons.h" +#include "Editor/Widgets/CardWidget.h" +#include "Editor/Widgets/PropertyGrid.h" +#include +#include + +namespace Syn { + void MaterialOverrideView::Draw(MaterialOverrideViewModel& vm) { + MaterialOverrideState state = vm.GetState(); + if (!state.hasComponent) return; + + constexpr const char* CardTitle = "Material Override"; + if (Syn::UI::BeginCard(CardTitle, SYN_ICON_MAGIC, _isCardOpen)) { + if (Syn::UI::BeginPropertyGrid("MaterialOverrideGrid")) + { + std::string sharedPreview = "None"; + if (state.sharedMaterialEntity != NULL_ENTITY) { + for (const auto& ent : state.compatibleSharedEntities) { + if (ent.first == state.sharedMaterialEntity) { + sharedPreview = ent.second; + break; + } + } + } + + if (Syn::UI::BeginPropertyCombo("Shared Entity", sharedPreview.c_str())) + { + if (ImGui::Selectable("None", state.sharedMaterialEntity == NULL_ENTITY)) { + vm.Dispatch(SetSharedMaterialEntityIntent{ NULL_ENTITY }); + } + + for (const auto& ent : state.compatibleSharedEntities) { + bool isSelected = (state.sharedMaterialEntity == ent.first); + + if (ImGui::Selectable(ent.second.c_str(), isSelected)) { + vm.Dispatch(SetSharedMaterialEntityIntent{ ent.first }); + } + + if (isSelected) { + ImGui::SetItemDefaultFocus(); + } + } + Syn::UI::EndPropertyCombo(); + } + + Syn::UI::PropertySeparator(); + + if (state.expectedSlotCount == 0) { + Syn::UI::PropertyText("Info", "No valid ModelComponent found or Model has 0 meshes."); + } + else { + for (uint32_t i = 0; i < state.expectedSlotCount; ++i) { + uint32_t currentMatId = state.overrides[i]; + std::string label = "Slot " + std::to_string(i); + + std::string previewName = "-"; + if (currentMatId != UINT32_MAX) { + for (const auto& mat : state.availableMaterials) { + if (mat.first == currentMatId) { + previewName = mat.second; + break; + } + } + } + + if (Syn::UI::BeginPropertyCombo(label.c_str(), previewName.c_str())) { + bool isNoneSelected = (currentMatId == UINT32_MAX); + if (ImGui::Selectable("-", isNoneSelected)) { + vm.Dispatch(SetMaterialOverrideSlotIntent{ i, UINT32_MAX }); + } + + for (const auto& mat : state.availableMaterials) { + bool isSelected = (currentMatId == mat.first); + + if (ImGui::Selectable(mat.second.c_str(), isSelected)) { + vm.Dispatch(SetMaterialOverrideSlotIntent{ i, mat.first }); + } + + if (isSelected) { + ImGui::SetItemDefaultFocus(); + } + } + Syn::UI::EndPropertyCombo(); + } + } + } + + Syn::UI::EndPropertyGrid(); + } + } + Syn::UI::EndCard(); + } +} \ No newline at end of file diff --git a/SynapseEngine/Editor/View/Component/Rendering/MaterialOverrideView.h b/SynapseEngine/Editor/View/Component/Rendering/MaterialOverrideView.h new file mode 100644 index 00000000..75b88489 --- /dev/null +++ b/SynapseEngine/Editor/View/Component/Rendering/MaterialOverrideView.h @@ -0,0 +1,12 @@ +#pragma once +#include "Editor/View/IView.h" +#include "EditorCore/ViewModels/Component/Rendering/MaterialOverride/MaterialOverrideViewModel.h" + +namespace Syn { + class MaterialOverrideView : public IView { + public: + void Draw(MaterialOverrideViewModel& vm) override; + private: + bool _isCardOpen = true; + }; +} \ No newline at end of file diff --git a/SynapseEngine/Editor/Workspace/SceneWorkspace.cpp b/SynapseEngine/Editor/Workspace/SceneWorkspace.cpp index 1fa0966d..4be9c234 100644 --- a/SynapseEngine/Editor/Workspace/SceneWorkspace.cpp +++ b/SynapseEngine/Editor/Workspace/SceneWorkspace.cpp @@ -48,7 +48,8 @@ namespace Syn { _context->GetMeshColliderApi(), _context->GetRigidBodyApi(), _context->GetModelComponentApi(), - _context->GetAnimationApi() + _context->GetAnimationApi(), + _context->GetMaterialOverrideApi() } ); diff --git a/SynapseEngine/EditorCore/Api/IMaterialOverrideApi.h b/SynapseEngine/EditorCore/Api/IMaterialOverrideApi.h new file mode 100644 index 00000000..3a49bfe7 --- /dev/null +++ b/SynapseEngine/EditorCore/Api/IMaterialOverrideApi.h @@ -0,0 +1,25 @@ +#pragma once +#include "EditorCore/Types/EntityHandle.h" +#include +#include +#include + +namespace Syn { + class IMaterialOverrideApi { + public: + virtual ~IMaterialOverrideApi() = default; + + virtual bool HasMaterialOverride(EntityID entity) const = 0; + + virtual uint32_t GetExpectedSlotCount(EntityID entity) const = 0; + + virtual uint32_t GetMaterialAtSlot(EntityID entity, uint32_t slotIndex) const = 0; + virtual void SetMaterialAtSlot(EntityID entity, uint32_t slotIndex, uint32_t materialId) = 0; + + virtual EntityID GetSharedMaterialEntity(EntityID entity) const = 0; + virtual void SetSharedMaterialEntity(EntityID entity, EntityID sharedEntity) = 0; + + virtual std::vector> GetCompatibleSharedEntities(EntityID entity) const = 0; + virtual std::vector> GetAvailableMaterials() const = 0; + }; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/ComponentIntent.h b/SynapseEngine/EditorCore/ViewModels/Component/ComponentIntent.h index fe3cdd6d..5c76623f 100644 --- a/SynapseEngine/EditorCore/ViewModels/Component/ComponentIntent.h +++ b/SynapseEngine/EditorCore/ViewModels/Component/ComponentIntent.h @@ -7,6 +7,7 @@ #include "Light/SpotLight/SpotLightIntent.h" #include "Rendering/Model/ModelComponentIntent.h" #include "Rendering/Animation/AnimationIntent.h" +#include "Rendering/MaterialOverride/MaterialOverrideIntent.h" #include "Physics/BoxCollider/BoxColliderIntent.h" #include "Physics/SphereCollider/SphereColliderIntent.h" #include "Physics/CapsuleCollider/CapsuleColliderIntent.h" @@ -30,6 +31,7 @@ namespace Syn { CapsuleColliderIntent, ConvexColliderIntent, MeshColliderIntent, - RigidBodyIntent + RigidBodyIntent, + MaterialOverrideIntent >; } \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/ComponentViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/Component/ComponentViewModel.cpp index 35df052a..7ffca385 100644 --- a/SynapseEngine/EditorCore/ViewModels/Component/ComponentViewModel.cpp +++ b/SynapseEngine/EditorCore/ViewModels/Component/ComponentViewModel.cpp @@ -18,7 +18,8 @@ namespace Syn IMeshColliderApi* meshColliderApi, IRigidBodyApi* rigidBodyApi, IModelComponentApi* modelComponentApi, - IAnimationApi* animationApi + IAnimationApi* animationApi, + IMaterialOverrideApi* materialOverrideApi ) : _selectionApi(selectionApi), @@ -35,7 +36,8 @@ namespace Syn _meshColliderViewModel(selectionApi, meshColliderApi), _rigidBodyViewModel(selectionApi, rigidBodyApi), _modelComponentViewModel(selectionApi, modelComponentApi), - _animationViewModel(selectionApi, animationApi) + _animationViewModel(selectionApi, animationApi), + _materialOverrideViewModel(selectionApi, materialOverrideApi) {} const ComponentState& ComponentViewModel::GetState() const { @@ -65,6 +67,7 @@ namespace Syn _rigidBodyViewModel.SyncWithEngine(); _modelComponentViewModel.SyncWithEngine(); _animationViewModel.SyncWithEngine(); + _materialOverrideViewModel.SyncWithEngine(); } } @@ -114,6 +117,9 @@ namespace Syn else if constexpr (std::is_same_v) { _animationViewModel.Dispatch(arg); } + else if constexpr (std::is_same_v) { + _materialOverrideViewModel.Dispatch(arg); + } }, intent); } } \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/ComponentViewModel.h b/SynapseEngine/EditorCore/ViewModels/Component/ComponentViewModel.h index 24994bc6..09e1a37f 100644 --- a/SynapseEngine/EditorCore/ViewModels/Component/ComponentViewModel.h +++ b/SynapseEngine/EditorCore/ViewModels/Component/ComponentViewModel.h @@ -19,6 +19,7 @@ #include "Rendering/Model/ModelComponentViewModel.h" #include "Rendering/Animation/AnimationViewModel.h" +#include "Rendering/MaterialOverride/MaterialOverrideViewModel.h" #include "EditorCore/Api/ISelectionApi.h" #include "EditorCore/Api/ITagApi.h" @@ -34,6 +35,7 @@ #include "EditorCore/Api/IRigidBodyApi.h" #include "EditorCore/Api/IModelComponentApi.h" #include "EditorCore/Api/IAnimationApi.h" +#include "EditorCore/Api/IMaterialOverrideApi.h" namespace Syn { class ComponentViewModel : public IViewModel { @@ -54,7 +56,8 @@ namespace Syn { IMeshColliderApi* meshColliderApi, IRigidBodyApi* rigidBodyApi, IModelComponentApi* modelComponentApi, - IAnimationApi* animationApi + IAnimationApi* animationApi, + IMaterialOverrideApi* materialOverrideApi ); ~ComponentViewModel() override = default; @@ -77,6 +80,7 @@ namespace Syn { RigidBodyViewModel& GetRigidBodyViewModel() { return _rigidBodyViewModel; } ModelComponentViewModel& GetModelComponentViewModel() { return _modelComponentViewModel; } AnimationViewModel& GetAnimationViewModel() { return _animationViewModel; } + MaterialOverrideViewModel& GetMaterialOverrideViewModel() { return _materialOverrideViewModel; } private: ISelectionApi* _selectionApi = nullptr; ComponentState _state; @@ -97,5 +101,6 @@ namespace Syn { ModelComponentViewModel _modelComponentViewModel; AnimationViewModel _animationViewModel; + MaterialOverrideViewModel _materialOverrideViewModel; }; } \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Rendering/MaterialOverride/MaterialOverrideIntent.cpp b/SynapseEngine/EditorCore/ViewModels/Component/Rendering/MaterialOverride/MaterialOverrideIntent.cpp new file mode 100644 index 00000000..995d205e --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Rendering/MaterialOverride/MaterialOverrideIntent.cpp @@ -0,0 +1 @@ +#include "MaterialOverrideIntent.h" \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Rendering/MaterialOverride/MaterialOverrideIntent.h b/SynapseEngine/EditorCore/ViewModels/Component/Rendering/MaterialOverride/MaterialOverrideIntent.h new file mode 100644 index 00000000..a43d2b6a --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Rendering/MaterialOverride/MaterialOverrideIntent.h @@ -0,0 +1,21 @@ +#pragma once +#include +#include +#include "EditorCore/Types/EntityHandle.h" + +namespace Syn +{ + struct SetMaterialOverrideSlotIntent { + uint32_t slotIndex; + uint32_t materialId; + }; + + struct SetSharedMaterialEntityIntent { + EntityID sharedEntity; + }; + + using MaterialOverrideIntent = std::variant< + SetMaterialOverrideSlotIntent, + SetSharedMaterialEntityIntent + >; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Rendering/MaterialOverride/MaterialOverrideState.cpp b/SynapseEngine/EditorCore/ViewModels/Component/Rendering/MaterialOverride/MaterialOverrideState.cpp new file mode 100644 index 00000000..414a88e0 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Rendering/MaterialOverride/MaterialOverrideState.cpp @@ -0,0 +1 @@ +#include "MaterialOverrideState.h" \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Rendering/MaterialOverride/MaterialOverrideState.h b/SynapseEngine/EditorCore/ViewModels/Component/Rendering/MaterialOverride/MaterialOverrideState.h new file mode 100644 index 00000000..3d748cdc --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Rendering/MaterialOverride/MaterialOverrideState.h @@ -0,0 +1,18 @@ +#pragma once +#include +#include +#include +#include "EditorCore/Types/EntityHandle.h" + +namespace Syn { + struct MaterialOverrideState { + bool hasComponent = false; + + uint32_t expectedSlotCount = 0; + std::vector overrides; + EntityID sharedMaterialEntity = NULL_ENTITY; + + std::vector> availableMaterials; + std::vector> compatibleSharedEntities; + }; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Rendering/MaterialOverride/MaterialOverrideViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/Component/Rendering/MaterialOverride/MaterialOverrideViewModel.cpp new file mode 100644 index 00000000..94d535c9 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Rendering/MaterialOverride/MaterialOverrideViewModel.cpp @@ -0,0 +1,60 @@ +#include "MaterialOverrideViewModel.h" + +namespace Syn +{ + MaterialOverrideViewModel::MaterialOverrideViewModel(ISelectionApi* selectionApi, IMaterialOverrideApi* overrideApi) + : _selectionApi(selectionApi), _overrideApi(overrideApi) {} + + const MaterialOverrideState& MaterialOverrideViewModel::GetState() const { return _state; } + + void MaterialOverrideViewModel::SyncWithEngine() + { + if (!_selectionApi || !_overrideApi) + return; + + EntityID activeEntity = _selectionApi->GetSelectedEntity(); + + if (activeEntity != NULL_ENTITY && _overrideApi->HasMaterialOverride(activeEntity)) + { + _state.hasComponent = true; + + _state.expectedSlotCount = _overrideApi->GetExpectedSlotCount(activeEntity); + _state.sharedMaterialEntity = _overrideApi->GetSharedMaterialEntity(activeEntity); + _state.availableMaterials = _overrideApi->GetAvailableMaterials(); + _state.compatibleSharedEntities = _overrideApi->GetCompatibleSharedEntities(activeEntity); + + _state.overrides.clear(); + _state.overrides.reserve(_state.expectedSlotCount); + for (uint32_t i = 0; i < _state.expectedSlotCount; ++i) { + _state.overrides.push_back(_overrideApi->GetMaterialAtSlot(activeEntity, i)); + } + } + else + { + _state.hasComponent = false; + } + } + + void MaterialOverrideViewModel::Dispatch(const MaterialOverrideIntent& intent) + { + EntityID active = _selectionApi->GetSelectedEntity(); + if (active == NULL_ENTITY) + return; + + std::visit([this, active](auto&& arg) + { + using T = std::decay_t; + + if constexpr (std::is_same_v) { + if (arg.slotIndex < _state.overrides.size()) { + _state.overrides[arg.slotIndex] = arg.materialId; + _overrideApi->SetMaterialAtSlot(active, arg.slotIndex, arg.materialId); + } + } + else if constexpr (std::is_same_v) { + _state.sharedMaterialEntity = arg.sharedEntity; + _overrideApi->SetSharedMaterialEntity(active, arg.sharedEntity); + } + }, intent); + } +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Rendering/MaterialOverride/MaterialOverrideViewModel.h b/SynapseEngine/EditorCore/ViewModels/Component/Rendering/MaterialOverride/MaterialOverrideViewModel.h new file mode 100644 index 00000000..9b72bf60 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/Component/Rendering/MaterialOverride/MaterialOverrideViewModel.h @@ -0,0 +1,23 @@ +#pragma once +#include "EditorCore/ViewModels/IViewModel.h" +#include "MaterialOverrideState.h" +#include "MaterialOverrideIntent.h" +#include "EditorCore/Api/ISelectionApi.h" +#include "EditorCore/Api/IMaterialOverrideApi.h" + +namespace Syn { + class MaterialOverrideViewModel : public IViewModel { + public: + MaterialOverrideViewModel(ISelectionApi* selectionApi, IMaterialOverrideApi* overrideApi); + ~MaterialOverrideViewModel() override = default; + + const MaterialOverrideState& GetState() const override; + void SyncWithEngine() override; + void Dispatch(const MaterialOverrideIntent& intent) override; + + private: + ISelectionApi* _selectionApi = nullptr; + IMaterialOverrideApi* _overrideApi = nullptr; + MaterialOverrideState _state; + }; +} \ No newline at end of file diff --git a/SynapseEngine/Engine/Scene/Scene.cpp b/SynapseEngine/Engine/Scene/Scene.cpp index 8da0f2ab..95706f69 100644 --- a/SynapseEngine/Engine/Scene/Scene.cpp +++ b/SynapseEngine/Engine/Scene/Scene.cpp @@ -26,6 +26,7 @@ #include "Engine/System/Rendering/MaterialSystem.h" #include "Engine/System/Rendering/ModelFrustumCullingSystem.h" #include "Engine/System/Rendering/AnimationSystem.h" +#include "Engine/System/Rendering/MaterialOverrideSystem.h" #include "Engine/System/Physics/PhysicsSystem.h" #include "Engine/System/Light/Point/PointLightSystem.h" @@ -144,6 +145,7 @@ namespace Syn RegisterSystem(); RegisterSystem(); RegisterSystem(); + RegisterSystem(); RegisterSystem(); RegisterSystem(); @@ -177,6 +179,7 @@ namespace Syn RegisterSystem(); RegisterSystem(); RegisterSystem(); + } void Scene::InitializeComponentBuffers() diff --git a/SynapseEngine/Engine/Scene/Source/Procedural/TestSceneSource.cpp b/SynapseEngine/Engine/Scene/Source/Procedural/TestSceneSource.cpp index bcb47898..02c2f6ce 100644 --- a/SynapseEngine/Engine/Scene/Source/Procedural/TestSceneSource.cpp +++ b/SynapseEngine/Engine/Scene/Source/Procedural/TestSceneSource.cpp @@ -173,6 +173,7 @@ namespace Syn EntityID monkeyId = scene.CreateEntity(); registry.AddComponent(monkeyId); + registry.AddComponent(monkeyId); registry.GetComponent(monkeyId).name = "Suzanne_Monkey"; registry.GetComponent(monkeyId).tag = "Model"; registry.AddComponent(monkeyId); @@ -184,6 +185,7 @@ namespace Syn registry.GetPool()->SetCategory(monkeyId, StorageCategory::Static); registry.GetPool()->SetCategory(monkeyId, StorageCategory::Static); + registry.GetPool()->SetCategory(monkeyId, StorageCategory::Static); hm->AttachChild(rootEnvironment, monkeyId); } @@ -194,6 +196,7 @@ namespace Syn EntityID sponzaEntity = scene.CreateEntity(); registry.AddComponent(sponzaEntity); + registry.AddComponent(sponzaEntity); registry.GetComponent(sponzaEntity).name = "Classic_Sponza"; registry.GetComponent(sponzaEntity).tag = "Model"; registry.AddComponent(sponzaEntity); @@ -210,6 +213,7 @@ namespace Syn registry.GetPool()->SetCategory(sponzaEntity, StorageCategory::Static); registry.GetPool()->SetCategory(sponzaEntity, StorageCategory::Stream); registry.GetPool()->SetCategory(sponzaEntity, StorageCategory::Stream); + registry.GetPool()->SetCategory(sponzaEntity, StorageCategory::Static); hm->AttachChild(rootEnvironment, sponzaEntity); } diff --git a/SynapseEngine/Engine/System/Rendering/MaterialOverrideSystem.cpp b/SynapseEngine/Engine/System/Rendering/MaterialOverrideSystem.cpp new file mode 100644 index 00000000..409ef66e --- /dev/null +++ b/SynapseEngine/Engine/System/Rendering/MaterialOverrideSystem.cpp @@ -0,0 +1,8 @@ +#include "MaterialOverrideSystem.h" + +namespace Syn +{ + std::vector MaterialOverrideSystem::GetWriteDependencies() const { + return { TypeInfo::ID }; + } +} \ No newline at end of file diff --git a/SynapseEngine/Engine/System/Rendering/MaterialOverrideSystem.h b/SynapseEngine/Engine/System/Rendering/MaterialOverrideSystem.h new file mode 100644 index 00000000..ed23dfb9 --- /dev/null +++ b/SynapseEngine/Engine/System/Rendering/MaterialOverrideSystem.h @@ -0,0 +1,15 @@ +#pragma once +#include "Engine/System/ComponentSystem.h" +#include "Engine/Component/Rendering/MaterialOverrideComponent.h" + +namespace Syn +{ + class SYN_API MaterialOverrideSystem : public ComponentSystem + { + public: + std::string GetName() const override { return "MaterialOverrideSystem"; } + std::string GetGroup() const override { return SystemGroupNames::RenderingSystems; } + + std::vector GetWriteDependencies() const override; + }; +} \ No newline at end of file diff --git a/SynapseEngine/Engine/System/Rendering/MaterialSystem.cpp b/SynapseEngine/Engine/System/Rendering/MaterialSystem.cpp index 3b23e980..1d9f415a 100644 --- a/SynapseEngine/Engine/System/Rendering/MaterialSystem.cpp +++ b/SynapseEngine/Engine/System/Rendering/MaterialSystem.cpp @@ -5,13 +5,17 @@ #include "Engine/Mesh/ModelManager.h" #include "Engine/Component/Rendering/MaterialOverrideComponent.h" #include "Engine/Component/Core/TagComponent.h" +#include "MaterialOverrideSystem.h" namespace Syn { constexpr bool ENABLE_DEBUG_LOGGING = false; std::vector MaterialSystem::GetWriteDependencies() const { - return { TypeInfo::ID }; + return { + TypeInfo::ID, + TypeInfo::ID + }; } void MaterialSystem::OnUpdate(Scene* scene, uint32_t frameIndex, float deltaTime, tf::Subflow& subflow) @@ -26,8 +30,6 @@ namespace Syn auto drawData = scene->GetSceneDrawData(); uint32_t currentModelManagerVersion = scene->GetSystemContext().modelManagerVersion; - //Todo: Override component changed -> Update?? - this->EmplaceTask(subflow, SystemPhaseNames::Update, [this, scene, pool, tagPool, currentModelManagerVersion, drawData, overridePool]() { bool needsRebuild = false; @@ -43,10 +45,15 @@ namespace Syn } - bool hasChanges = !pool->GetDirtyStatics().empty() || + bool materialOverrideHasChanges = !overridePool->GetDirtyStatics().empty() + || overridePool->IsStateBitSet() + || overridePool->IsStateBitSet(); + + bool modelHasChanges = !pool->GetDirtyStatics().empty() || pool->IsStateBitSet() || - pool->IsStateBitSet() || - needsRebuild; + pool->IsStateBitSet(); + + bool hasChanges = materialOverrideHasChanges || modelHasChanges || needsRebuild; if (!hasChanges) { return; @@ -57,6 +64,9 @@ namespace Syn uint32_t totalExactMaterials = 0; auto countFunc = [&](EntityID entity) { + bool isShared = overridePool && overridePool->Has(entity) && overridePool->Get(entity).sharedMaterialEntity != NULL_ENTITY; + if (isShared) return; + auto& comp = pool->Get(entity); const auto& snapshot = modelSnapshots[comp.modelIndex]; if (snapshot.state == ResourceState::Ready && snapshot.resource) { @@ -76,6 +86,9 @@ namespace Syn uint32_t currentOffset = 0; auto processEntity = [&](EntityID entity) { + bool isShared = overridePool && overridePool->Has(entity) && overridePool->Get(entity).sharedMaterialEntity != NULL_ENTITY; + if (isShared) return; + auto& comp = pool->Get(entity); if (comp.modelIndex >= modelSnapshots.size()) return; @@ -136,6 +149,26 @@ namespace Syn for (auto e : pool->GetStorage().GetDynamicEntities()) processEntity(e); for (auto e : pool->GetStorage().GetStreamEntities()) processEntity(e); + auto processSharedEntity = [&](EntityID entity) { + if (!overridePool || !overridePool->Has(entity)) return; + + EntityID sharedEntity = overridePool->Get(entity).sharedMaterialEntity; + if (sharedEntity == NULL_ENTITY) return; + + if (pool->Has(sharedEntity)) { + auto& comp = pool->Get(entity); + uint32_t masterOffset = pool->Get(sharedEntity).materialOffset; + + comp.materialOffset = masterOffset; + comp.version++; + pool->SetBit(entity); + pool->MarkStaticDirty(entity); + } + }; + + for (auto e : pool->GetStorage().GetStaticEntities()) processSharedEntity(e); + for (auto e : pool->GetStorage().GetDynamicEntities()) processSharedEntity(e); + for (auto e : pool->GetStorage().GetStreamEntities()) processSharedEntity(e); if (needsRebuild || needsUpload) { uint32_t framesInFlight = ServiceLocator::GetFrameContext()->framesInFlight; From 02ebdb40357819453a6734a3d57c55519cdebcc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20P=C3=A9ter?= Date: Fri, 3 Jul 2026 10:46:44 +0200 Subject: [PATCH 18/51] Implemented skysphere rendering --- SynapseEngine/Engine/Image/ImageManager.cpp | 13 ++ SynapseEngine/Engine/Image/SamplerNames.h | 1 + .../PostProcess/SkySphere/SkySpherePass.cpp | 140 ++++++++++++++++++ .../PostProcess/SkySphere/SkySpherePass.h | 20 +++ .../Engine/Render/RendererFactory.cpp | 4 + SynapseEngine/Engine/Render/ShaderNames.h | 3 + .../Scene/Settings/EnvironmentSettings.cpp | 20 +++ .../Scene/Settings/EnvironmentSettings.h | 36 +++++ .../Scene/Settings/PostProcessSettings.cpp | 14 +- .../Engine/Scene/Settings/SceneSettings.h | 2 + .../Source/Procedural/TestSceneSource.cpp | 5 +- .../Includes/PushConstants/SkySpherePC.glsl | 14 ++ .../Includes/Utils/EnvironmentMath.glsl | 17 +++ .../PostProcess/SkySphere/SkySphere.frag | 44 ++++++ .../PostProcess/SkySphere/SkySphere.vert | 8 + SynapseEngine/imgui.ini | 55 +++++-- 16 files changed, 373 insertions(+), 23 deletions(-) create mode 100644 SynapseEngine/Engine/Render/Passes/PostProcess/SkySphere/SkySpherePass.cpp create mode 100644 SynapseEngine/Engine/Render/Passes/PostProcess/SkySphere/SkySpherePass.h create mode 100644 SynapseEngine/Engine/Scene/Settings/EnvironmentSettings.cpp create mode 100644 SynapseEngine/Engine/Scene/Settings/EnvironmentSettings.h create mode 100644 SynapseEngine/Engine/Shaders/Includes/PushConstants/SkySpherePC.glsl create mode 100644 SynapseEngine/Engine/Shaders/Includes/Utils/EnvironmentMath.glsl create mode 100644 SynapseEngine/Engine/Shaders/Passes/PostProcess/SkySphere/SkySphere.frag create mode 100644 SynapseEngine/Engine/Shaders/Passes/PostProcess/SkySphere/SkySphere.vert diff --git a/SynapseEngine/Engine/Image/ImageManager.cpp b/SynapseEngine/Engine/Image/ImageManager.cpp index 6d1a9b2c..f2ed6bfe 100644 --- a/SynapseEngine/Engine/Image/ImageManager.cpp +++ b/SynapseEngine/Engine/Image/ImageManager.cpp @@ -214,6 +214,19 @@ namespace Syn { config.compareOp = VK_COMPARE_OP_LESS; RegisterSampler(SamplerNames::ShadowSampler, config); } + + { + Vk::SamplerConfig config{}; + config.magFilter = VK_FILTER_LINEAR; + config.minFilter = VK_FILTER_LINEAR; + config.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; + config.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + config.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + config.borderColor = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK; + config.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + config.anisotropyEnable = true; + RegisterSampler(SamplerNames::SkyboxSampler, config); + } } uint32_t ImageManager::RegisterSampler(const std::string& name, const Vk::SamplerConfig& config) { diff --git a/SynapseEngine/Engine/Image/SamplerNames.h b/SynapseEngine/Engine/Image/SamplerNames.h index af02a149..bac61e8a 100644 --- a/SynapseEngine/Engine/Image/SamplerNames.h +++ b/SynapseEngine/Engine/Image/SamplerNames.h @@ -14,5 +14,6 @@ namespace Syn static constexpr const char* MaxReduction = "MaxReduction"; static constexpr const char* BloomSampler = "BloomSampler"; static constexpr const char* ShadowSampler = "ShadowSampler"; + static constexpr const char* SkyboxSampler = "SkyboxSampler"; }; } \ No newline at end of file diff --git a/SynapseEngine/Engine/Render/Passes/PostProcess/SkySphere/SkySpherePass.cpp b/SynapseEngine/Engine/Render/Passes/PostProcess/SkySphere/SkySpherePass.cpp new file mode 100644 index 00000000..4bdc7754 --- /dev/null +++ b/SynapseEngine/Engine/Render/Passes/PostProcess/SkySphere/SkySpherePass.cpp @@ -0,0 +1,140 @@ +#include "SkySpherePass.h" +#include "Engine/ServiceLocator.h" +#include "Engine/Vk/Context.h" +#include "Engine/Manager/ShaderManager.h" +#include "Engine/Vk/Image/ImageFactory.h" +#include "Engine/Scene/BufferNames.h" +#include "Engine/Vk/Image/ImageViewNames.h" +#include "Engine/Image/ImageManager.h" +#include "Engine/Render/RenderNames.h" +#include "Engine/Image/SamplerNames.h" +#include "Engine/Vk/Rendering/PushConstant.h" + +namespace Syn { + + #include "Engine/Shaders/Includes/PushConstants/SkySpherePC.glsl" + + SkySpherePass::SkySpherePass() {} + + bool SkySpherePass::ShouldExecute(const RenderContext& context) const + { + auto skyTextureId = context.scene->GetSettings()->environment.skyTextureId; + return skyTextureId != UINT32_MAX && !context.scene->GetSettings()->debug.enableDebugVisibility; + } + + void SkySpherePass::Initialize() { + auto shaderManager = ServiceLocator::GetShaderManager(); + auto imageManager = ServiceLocator::GetImageManager(); + + Vk::ShaderProgramConfig config; + config.useDescriptorBuffers = true; + config.layoutOverride = [imageManager](uint32_t setIndex) { + if (setIndex == 0) return imageManager->GetBindlessLayout(); + return VkDescriptorSetLayout{}; + }; + + _shaderProgram = shaderManager->CreateProgram("SkySphereProgram", { + ShaderNames::SkySphereVert, + ShaderNames::SkySphereFrag + }, config); + + _graphicsState = { + .raster = { + .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, + .cullMode = VK_CULL_MODE_NONE, + .frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE, + .polygonMode = VK_POLYGON_MODE_FILL, + .lineWidth = 1.0f + }, + .depth = { + .testEnable = VK_TRUE, + .writeEnable = VK_FALSE, + .compareOp = VK_COMPARE_OP_LESS_OR_EQUAL + }, + .blendStates = { + { + .enable = VK_FALSE, + .srcColorFactor = VK_BLEND_FACTOR_ONE, + .dstColorFactor = VK_BLEND_FACTOR_ZERO, + .colorBlendOp = VK_BLEND_OP_ADD, + .srcAlphaFactor = VK_BLEND_FACTOR_ONE, + .dstAlphaFactor = VK_BLEND_FACTOR_ZERO, + .alphaBlendOp = VK_BLEND_OP_ADD + } + }, + .colorAttachmentCount = 1, + .renderArea = std::nullopt + }; + } + + void SkySpherePass::PrepareFrame(const RenderContext& context) { + auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + VkExtent2D extent = { group->GetWidth(), group->GetHeight() }; + _graphicsState.renderArea = extent; + + std::vector targets = { + RenderTargetNames::Main, + }; + + for (const auto& name : targets) + { + _colorAttachments.push_back(Vk::RenderUtils::CreateAttachment({ + .imageView = group->GetImage(name)->GetView(Vk::ImageViewNames::Default), + .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + .loadOp = VK_ATTACHMENT_LOAD_OP_LOAD, + .storeOp = VK_ATTACHMENT_STORE_OP_STORE + })); + } + + _depthAttachment = Vk::RenderUtils::CreateAttachment({ + .imageView = group->GetImage(RenderTargetNames::OpaqueDepth)->GetView(Vk::ImageViewNames::Default), + .layout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL, + .loadOp = VK_ATTACHMENT_LOAD_OP_LOAD, + .storeOp = VK_ATTACHMENT_STORE_OP_STORE + }); + + _renderInfo = Vk::RenderingInfoConfig{ + .renderArea = extent, + .colorAttachments = _colorAttachments, + .depthAttachment = &_depthAttachment.value(), + .layerCount = 1 + }; + } + + void SkySpherePass::PushConstants(const RenderContext& context) { + auto scene = context.scene; + auto imageManager = ServiceLocator::GetImageManager(); + uint32_t fIdx = context.frameIndex; + + const auto& envSettings = scene->GetSettings()->environment; + + glm::vec3 rotRads = glm::radians(envSettings.skyRotation); + glm::mat4 rotMat = glm::mat4(1.0f); + rotMat = glm::rotate(rotMat, rotRads.y, glm::vec3(0.0f, 1.0f, 0.0f)); + rotMat = glm::rotate(rotMat, rotRads.x, glm::vec3(1.0f, 0.0f, 0.0f)); + rotMat = glm::rotate(rotMat, rotRads.z, glm::vec3(0.0f, 0.0f, 1.0f)); + + Vk::PushConstant pc; + pc->frameGlobalContextBufferAddr = scene->GetSceneDrawData()->frameContextBuffer.GetAddress(fIdx); + pc->skyTextureIndex = envSettings.skyTextureId; + pc->samplerIndex = imageManager->GetSamplerIndex(SamplerNames::SkyboxSampler); + pc->mappingType = envSettings.skyMode == SkyMode::EquirectangularTexture ? 0 : 1; + pc->skyIntensity = envSettings.skyIntensity; + pc->skyRotationMatrix = rotMat; + + pc.Push(context.cmd, _shaderProgram->GetLayout()); + } + + void SkySpherePass::BindDescriptors(const RenderContext& context) + { + auto imageManager = ServiceLocator::GetImageManager(); + auto bindlessBuffer = imageManager->GetBindlessBuffer(); + + bindlessBuffer->Bind(context.cmd, _shaderProgram->GetLayout(), 0, VK_PIPELINE_BIND_POINT_GRAPHICS); + } + + void SkySpherePass::Draw(const RenderContext& context) + { + vkCmdDraw(context.cmd, 3, 1, 0, 0); + } +} \ No newline at end of file diff --git a/SynapseEngine/Engine/Render/Passes/PostProcess/SkySphere/SkySpherePass.h b/SynapseEngine/Engine/Render/Passes/PostProcess/SkySphere/SkySpherePass.h new file mode 100644 index 00000000..724a56cd --- /dev/null +++ b/SynapseEngine/Engine/Render/Passes/PostProcess/SkySphere/SkySpherePass.h @@ -0,0 +1,20 @@ +#pragma once +#include "Engine/SynApi.h" +#include "Engine/Render/Passes/GraphicsPass.h" + +namespace Syn { + class SYN_API SkySpherePass : public GraphicsPass { + public: + SkySpherePass(); + + std::string GetName() const override { return "SkySpherePass"; } + std::string GetGroup() const override { return PassGroupNames::PostProcessPasses; } + void Initialize() override; + protected: + bool ShouldExecute(const RenderContext& context) const override; + void PrepareFrame(const RenderContext& context) override; + void PushConstants(const RenderContext& context) override; + void BindDescriptors(const RenderContext& context) override; + void Draw(const RenderContext& context) override; + }; +} \ No newline at end of file diff --git a/SynapseEngine/Engine/Render/RendererFactory.cpp b/SynapseEngine/Engine/Render/RendererFactory.cpp index e0b09139..cfa77441 100644 --- a/SynapseEngine/Engine/Render/RendererFactory.cpp +++ b/SynapseEngine/Engine/Render/RendererFactory.cpp @@ -12,6 +12,7 @@ #include "Engine/Render/Passes/PostProcess/Bloom/BloomDownsamplePass.h" #include "Engine/Render/Passes/PostProcess/Bloom/BloomCompositePass.h" #include "Engine/Render/Passes/PostProcess/Outline/SelectionOutlinePass.h" +#include "Engine/Render/Passes/PostProcess/SkySphere/SkySpherePass.h" #include "Engine/Render/Passes/Culling/Geometry/GeometryModelCullingPass.h" #include "Engine/Render/Passes/Culling/Geometry/GeometryStaticModelCullingPass.h" @@ -329,6 +330,9 @@ namespace Syn pipeline->AddPass(std::make_unique(MaterialRenderType::Opaque1Sided)); pipeline->AddPass(std::make_unique(MaterialRenderType::Opaque2Sided)); + // SkySphere + pipeline->AddPass(std::make_unique()); + // Wireframe Passes pipeline->AddPass(std::make_unique()); pipeline->AddPass(std::make_unique()); diff --git a/SynapseEngine/Engine/Render/ShaderNames.h b/SynapseEngine/Engine/Render/ShaderNames.h index b39eed42..583a27f7 100644 --- a/SynapseEngine/Engine/Render/ShaderNames.h +++ b/SynapseEngine/Engine/Render/ShaderNames.h @@ -16,6 +16,9 @@ namespace Syn static constexpr const char* BloomDownsample = "Engine/Shaders/Passes/PostProcess/Bloom/BloomDownsample.comp"; static constexpr const char* BloomComposite = "Engine/Shaders/Passes/PostProcess/Bloom/BloomComposite.comp"; + static constexpr const char* SkySphereVert = "Engine/Shaders/Passes/PostProcess/SkySphere/SkySphere.vert"; + static constexpr const char* SkySphereFrag = "Engine/Shaders/Passes/PostProcess/SkySphere/SkySphere.frag"; + static constexpr const char* SelectionOutlineFrag = "Engine/Shaders/Passes/PostProcess/Outline/SelectionOutline.frag"; static constexpr const char* StaticSceneAABB = "Engine/Shaders/Passes/Morton/StaticSceneAABB.comp"; diff --git a/SynapseEngine/Engine/Scene/Settings/EnvironmentSettings.cpp b/SynapseEngine/Engine/Scene/Settings/EnvironmentSettings.cpp new file mode 100644 index 00000000..57fed2f5 --- /dev/null +++ b/SynapseEngine/Engine/Scene/Settings/EnvironmentSettings.cpp @@ -0,0 +1,20 @@ +#include "EnvironmentSettings.h" + +namespace Syn +{ + EnvironmentSettings::EnvironmentSettings() + : enableSky(true) + , skyMode(SkyMode::EquirectangularTexture) + , skyTextureId(UINT32_MAX) + , skyIntensity(1.0f) + , skyTint(glm::vec3(1.0f)) + , skyRotation(glm::vec3(0.0f)) + , ambientIntensity(1.0f) + , irradianceTextureId(UINT32_MAX) + , prefilteredTextureId(UINT32_MAX) + , brdfLutTextureId(UINT32_MAX) + , enableFog(false) + , fogColor(glm::vec3(0.5f, 0.6f, 0.7f)) + , fogDensity(0.01f) + {} +} \ No newline at end of file diff --git a/SynapseEngine/Engine/Scene/Settings/EnvironmentSettings.h b/SynapseEngine/Engine/Scene/Settings/EnvironmentSettings.h new file mode 100644 index 00000000..7a603415 --- /dev/null +++ b/SynapseEngine/Engine/Scene/Settings/EnvironmentSettings.h @@ -0,0 +1,36 @@ +#pragma once +#include "Engine/SynApi.h" +#include + +namespace Syn +{ + enum class SYN_API SkyMode + { + None = 0, + EquirectangularTexture = 1, + OctahedralTexture = 2, + Procedural = 3 + }; + + struct SYN_API EnvironmentSettings + { + EnvironmentSettings(); + + bool enableSky; + SkyMode skyMode; + + uint32_t skyTextureId; + float skyIntensity; + glm::vec3 skyTint; + glm::vec3 skyRotation; + + float ambientIntensity; + uint32_t irradianceTextureId; + uint32_t prefilteredTextureId; + uint32_t brdfLutTextureId; + + bool enableFog; + glm::vec3 fogColor; + float fogDensity; + }; +} \ No newline at end of file diff --git a/SynapseEngine/Engine/Scene/Settings/PostProcessSettings.cpp b/SynapseEngine/Engine/Scene/Settings/PostProcessSettings.cpp index b22fa9a6..1ff2eb06 100644 --- a/SynapseEngine/Engine/Scene/Settings/PostProcessSettings.cpp +++ b/SynapseEngine/Engine/Scene/Settings/PostProcessSettings.cpp @@ -9,13 +9,13 @@ namespace Syn , bloomFilterRadius(0.005f) , bloomExposure(1.0f) , bloomStrength(1.0f) - , enableSsao(false) - , enableSsaoLight(false) - , aoRadius(0.930f) - , aoIntensity(100.0f) - , maxOcclusionDistance(3.0f) + , enableSsao(true) + , enableSsaoLight(true) + , aoRadius(0.95f) + , aoIntensity(5.0f) + , maxOcclusionDistance(10.0f) , depthSharpness(0.0f) - , bias(0.005f) - , sampleCount(32) + , bias(0.05f) + , sampleCount(64) {} } \ No newline at end of file diff --git a/SynapseEngine/Engine/Scene/Settings/SceneSettings.h b/SynapseEngine/Engine/Scene/Settings/SceneSettings.h index 46e742de..c47dc358 100644 --- a/SynapseEngine/Engine/Scene/Settings/SceneSettings.h +++ b/SynapseEngine/Engine/Scene/Settings/SceneSettings.h @@ -4,6 +4,7 @@ #include "LightingSettings.h" #include "PostProcessSettings.h" #include "DebugSettings.h" +#include "EnvironmentSettings.h" namespace Syn { @@ -15,5 +16,6 @@ namespace Syn LightingSettings lighting; PostProcessSettings postProcess; DebugSettings debug; + EnvironmentSettings environment; }; } \ No newline at end of file diff --git a/SynapseEngine/Engine/Scene/Source/Procedural/TestSceneSource.cpp b/SynapseEngine/Engine/Scene/Source/Procedural/TestSceneSource.cpp index 02c2f6ce..5dc02b38 100644 --- a/SynapseEngine/Engine/Scene/Source/Procedural/TestSceneSource.cpp +++ b/SynapseEngine/Engine/Scene/Source/Procedural/TestSceneSource.cpp @@ -23,6 +23,7 @@ #include "Engine/Component/Physics/ConvexColliderComponent.h" #include "Engine/Component/Physics/MeshColliderComponent.h" #include "Engine/Component/Physics/RigidBodyComponent.h" +#include "Engine/Image/ImageManager.h" #include "Engine/Logger/SynLog.h" #include "Engine/Utils/PathUtils.h" @@ -101,7 +102,9 @@ namespace Syn modelManager->GetResourceIndex(MeshSourceNames::Torus) }; - // --- ROOT CONTAINERS --- + auto skyTextureId = ServiceLocator::GetImageManager()->LoadImageSync(basePath + "Sky.png"); + scene.GetSettings()->environment.skyTextureId = skyTextureId; + EntityID rootCameras = scene.CreateEntity(); registry.AddComponent(rootCameras); registry.GetComponent(rootCameras).name = "Cameras"; diff --git a/SynapseEngine/Engine/Shaders/Includes/PushConstants/SkySpherePC.glsl b/SynapseEngine/Engine/Shaders/Includes/PushConstants/SkySpherePC.glsl new file mode 100644 index 00000000..8840b675 --- /dev/null +++ b/SynapseEngine/Engine/Shaders/Includes/PushConstants/SkySpherePC.glsl @@ -0,0 +1,14 @@ +#ifndef SYN_INCLUDES_PUSH_CONSTANTS_SKY_SPHERE_PC_GLSL +#define SYN_INCLUDES_PUSH_CONSTANTS_SKY_SPHERE_PC_GLSL + +#include "../SharedGpuTypes.glsl" + +struct SkySpherePC { + mat4 skyRotationMatrix; + uint64_t frameGlobalContextBufferAddr; + uint skyTextureIndex; + uint samplerIndex; + uint mappingType; + float skyIntensity; +}; +#endif \ No newline at end of file diff --git a/SynapseEngine/Engine/Shaders/Includes/Utils/EnvironmentMath.glsl b/SynapseEngine/Engine/Shaders/Includes/Utils/EnvironmentMath.glsl new file mode 100644 index 00000000..abd4f78f --- /dev/null +++ b/SynapseEngine/Engine/Shaders/Includes/Utils/EnvironmentMath.glsl @@ -0,0 +1,17 @@ +#ifndef SYN_INCLUDES_UTILS_ENVIRONMENT_MATH_GLSL +#define SYN_INCLUDES_UTILS_ENVIRONMENT_MATH_GLSL + +vec2 SampleEquirectangular(vec3 v) { + vec2 uv = vec2(atan(v.z, v.x), asin(v.y)); + uv *= vec2(0.15915494309, 0.31830988618); + uv += 0.5; + return uv; +} + +vec2 SampleOctahedral(vec3 v) { + v /= (abs(v.x) + abs(v.y) + abs(v.z)); + vec2 uv = v.z >= 0.0 ? v.xy : (1.0 - abs(v.yx)) * sign(v.xy); + return uv * 0.5 + 0.5; +} + +#endif \ No newline at end of file diff --git a/SynapseEngine/Engine/Shaders/Passes/PostProcess/SkySphere/SkySphere.frag b/SynapseEngine/Engine/Shaders/Passes/PostProcess/SkySphere/SkySphere.frag new file mode 100644 index 00000000..d846c64d --- /dev/null +++ b/SynapseEngine/Engine/Shaders/Passes/PostProcess/SkySphere/SkySphere.frag @@ -0,0 +1,44 @@ +#version 460 +#extension GL_GOOGLE_include_directive : require +#extension GL_EXT_nonuniform_qualifier : require +#extension GL_EXT_buffer_reference2 : require +#extension GL_EXT_shader_explicit_arithmetic_types_int64 : require + +#include "../../../Includes/Core.glsl" +#include "../../../Includes/Common/FrameGlobalContext.glsl" +#include "../../../Includes/Common/Camera.glsl" +#include "../../../Includes/Common/Texture.glsl" +#include "../../../Includes/Utils/EnvironmentMath.glsl" + +layout(location = 0) in vec2 inUV; +layout(location = 0) out vec4 outColor; + +#include "../../../Includes/PushConstants/SkySpherePC.glsl" + +layout(push_constant) uniform PushConstants { + SkySpherePC pc; +}; + +void main() { + FrameGlobalContext ctx = GET_FRAME_CONTEXT(pc.frameGlobalContextBufferAddr); + + uint cameraDenseIndex = GET_SPARSE_INDEX(ctx.cameraSparseMapBufferAddr, ctx.activeCameraEntity); + CameraComponent camera = GET_CAMERA(ctx.cameraBufferAddr, cameraDenseIndex); + + vec2 ndc = inUV * 2.0 - 1.0; + vec4 clipPos = vec4(ndc, 1.0, 1.0); + vec4 viewPos = camera.projVulkanInv * clipPos; + vec3 viewRay = (camera.viewInv * vec4(viewPos.xyz, 0.0)).xyz; + vec3 viewDir = normalize(viewRay); + + vec3 rotatedDir = (pc.skyRotationMatrix * vec4(viewDir, 0.0)).xyz; + rotatedDir = normalize(rotatedDir); + + vec2 finalUV = pc.mappingType == 0 ? SampleEquirectangular(rotatedDir) : SampleOctahedral(rotatedDir); + finalUV.y = 1.0 - finalUV.y; + + vec3 skyColor = SampleTexture2DLod(pc.skyTextureIndex, pc.samplerIndex, finalUV, 0.0).rgb; + skyColor *= pc.skyIntensity; + + outColor = vec4(skyColor, 1.0); +} \ No newline at end of file diff --git a/SynapseEngine/Engine/Shaders/Passes/PostProcess/SkySphere/SkySphere.vert b/SynapseEngine/Engine/Shaders/Passes/PostProcess/SkySphere/SkySphere.vert new file mode 100644 index 00000000..150889f6 --- /dev/null +++ b/SynapseEngine/Engine/Shaders/Passes/PostProcess/SkySphere/SkySphere.vert @@ -0,0 +1,8 @@ +#version 460 + +layout(location = 0) out vec2 outUV; + +void main() { + outUV = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2); + gl_Position = vec4(outUV * 2.0 - 1.0, 1.0, 1.0); +} \ No newline at end of file diff --git a/SynapseEngine/imgui.ini b/SynapseEngine/imgui.ini index fd00bf98..f3f98f0d 100644 --- a/SynapseEngine/imgui.ini +++ b/SynapseEngine/imgui.ini @@ -1,11 +1,11 @@ [Window][HostWindow_Scene] Pos=0,23 -Size=2304,1273 +Size=2560,1346 Collapsed=0 [Window][Content_Scene] -Pos=405,964 -Size=1470,332 +Pos=405,1037 +Size=1726,332 Collapsed=0 DockId=0x00000002,0 @@ -15,38 +15,38 @@ Size=400,400 Collapsed=0 [Window][ Inspector] -Pos=1877,23 -Size=427,623 +Pos=2133,23 +Size=427,659 Collapsed=0 DockId=0x00000005,0 [Window][ Viewport] Pos=405,23 -Size=1470,939 +Size=1726,1012 Collapsed=0 DockId=0x00000001,0 [Window][ Graphics & Environment] -Pos=1877,648 -Size=427,648 +Pos=2133,684 +Size=427,685 Collapsed=0 DockId=0x00000006,0 [Window][ Scene Hierarchy] Pos=0,23 -Size=403,535 +Size=403,566 Collapsed=0 DockId=0x00000009,0 [Window][ Performance Profiler] -Pos=0,560 -Size=403,736 +Pos=0,591 +Size=403,778 Collapsed=0 DockId=0x0000000A,0 [Window][ Output Log] -Pos=405,964 -Size=1470,332 +Pos=405,1037 +Size=1726,332 Collapsed=0 DockId=0x00000002,1 @@ -78,7 +78,7 @@ Column 1 Weight=1.0000 [Table][0x51A78E48,2] RefScale=13 Column 0 Weight=1.0000 -Column 1 Width=32 +Column 1 Width=48 [Table][0xB6D16E5C,3] RefScale=13 @@ -106,8 +106,33 @@ RefScale=13 Column 0 Width=77 Column 1 Weight=1.0000 +[Table][0xF8524FE2,2] +RefScale=13 +Column 0 Width=98 +Column 1 Weight=1.0000 + +[Table][0xA047510E,2] +RefScale=13 +Column 0 Width=91 +Column 1 Weight=1.0000 + +[Table][0x32EB459A,2] +RefScale=13 +Column 0 Width=0 +Column 1 Weight=-nan(ind) + +[Table][0x243E03DF,2] +RefScale=13 +Column 0 Width=0 +Column 1 Weight=-nan(ind) + +[Table][0xF10D0EE1,2] +RefScale=13 +Column 0 Width=63 +Column 1 Weight=1.0000 + [Docking][Data] -DockSpace ID=0x4713F8A8 Window=0xFBC4AE05 Pos=128,95 Size=2304,1273 Split=X +DockSpace ID=0x4713F8A8 Window=0xFBC4AE05 Pos=0,46 Size=2560,1346 Split=X DockNode ID=0x00000007 Parent=0x4713F8A8 SizeRef=403,1273 Split=Y Selected=0x02B8E2DB DockNode ID=0x00000009 Parent=0x00000007 SizeRef=403,535 Selected=0xF995F4A5 DockNode ID=0x0000000A Parent=0x00000007 SizeRef=403,736 Selected=0x02B8E2DB From 117db60e9b6ccecae21090171e179feac13d9767 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20P=C3=A9ter?= Date: Fri, 3 Jul 2026 11:19:44 +0200 Subject: [PATCH 19/51] Skysphere settings panel implemented --- .../Editor/EditorApi/Impl/SettingsApiImpl.cpp | 18 +++++ .../Editor/EditorApi/Impl/SettingsApiImpl.h | 1 + .../Editor/View/Settings/SettingsView.cpp | 75 +++++++++++++++++++ SynapseEngine/EditorCore/Api/ISettingsApi.h | 4 + .../ViewModels/Settings/SettingsState.h | 4 + .../ViewModels/Settings/SettingsViewModel.cpp | 1 + .../ViewModels/Settings/SettingsViewModel.h | 1 - .../PostProcess/SkySphere/SkySpherePass.cpp | 4 +- .../Includes/PushConstants/SkySpherePC.glsl | 2 + .../PostProcess/SkySphere/SkySphere.frag | 1 + 10 files changed, 109 insertions(+), 2 deletions(-) diff --git a/SynapseEngine/Editor/EditorApi/Impl/SettingsApiImpl.cpp b/SynapseEngine/Editor/EditorApi/Impl/SettingsApiImpl.cpp index 158ff68e..22d07d48 100644 --- a/SynapseEngine/Editor/EditorApi/Impl/SettingsApiImpl.cpp +++ b/SynapseEngine/Editor/EditorApi/Impl/SettingsApiImpl.cpp @@ -1,5 +1,6 @@ #include "SettingsApiImpl.h" #include "Engine/Scene/Scene.h" +#include "Engine/Image/ImageManager.h" namespace Syn { SceneSettings SettingsApiImpl::GetSceneSettings() const { @@ -13,4 +14,21 @@ namespace Syn { *(scene->GetSettings()) = settings; } } + + std::vector> SettingsApiImpl::GetAvailableSkyTextures() const { + std::vector> result; + + auto imageManager = ServiceLocator::GetImageManager(); + if (!imageManager) return result; + + auto paths = imageManager->GetResourcePaths(); + for (uint32_t i = 0; i < paths.size(); ++i) { + if (imageManager->GetEntryState(i) == ResourceState::Ready) { + std::filesystem::path p(paths[i]); + result.push_back({ i, p.filename().string() }); + } + } + + return result; + } } \ No newline at end of file diff --git a/SynapseEngine/Editor/EditorApi/Impl/SettingsApiImpl.h b/SynapseEngine/Editor/EditorApi/Impl/SettingsApiImpl.h index abb500e9..3eb8b66e 100644 --- a/SynapseEngine/Editor/EditorApi/Impl/SettingsApiImpl.h +++ b/SynapseEngine/Editor/EditorApi/Impl/SettingsApiImpl.h @@ -8,6 +8,7 @@ namespace Syn { SettingsApiImpl(SceneManager* sm) : _sceneManager(sm) {} SceneSettings GetSceneSettings() const override; void SetSceneSettings(const SceneSettings& settings) override; + std::vector> GetAvailableSkyTextures() const override; private: SceneManager* _sceneManager; }; diff --git a/SynapseEngine/Editor/View/Settings/SettingsView.cpp b/SynapseEngine/Editor/View/Settings/SettingsView.cpp index 71d5db30..1aa05131 100644 --- a/SynapseEngine/Editor/View/Settings/SettingsView.cpp +++ b/SynapseEngine/Editor/View/Settings/SettingsView.cpp @@ -3,6 +3,7 @@ #include "Engine/Render/ComputeGroupSize.h" #include "Editor/Widgets/CardWidget.h" #include "Editor/Widgets/PropertyGrid.h" +#include "Editor/Widgets/Vector3Widget.h" #include #include #include @@ -217,6 +218,80 @@ namespace Syn { } Syn::UI::EndCard(); + constexpr const char* CardEnvironmentTitle = "Environment Settings"; + if (Syn::UI::BeginCard(CardEnvironmentTitle, SYN_ICON_SUN, getCardState(CardEnvironmentTitle))) { + if (Syn::UI::BeginPropertyGrid("EnvironmentGrid")) { + drawSectionHeader("Skybox Render"); + + changed |= Syn::UI::PropertyCheckbox("Enable Sky", settings.environment.enableSky); + + if (settings.environment.enableSky) { + const char* skyModes[] = { "None", "Equirectangular", "Octahedral", "Procedural" }; + int currentMode = (int)settings.environment.skyMode; + Syn::UI::BeginProperty("Sky Mode"); + if (ImGui::Combo("##SkyMode", ¤tMode, skyModes, IM_ARRAYSIZE(skyModes))) { + settings.environment.skyMode = (SkyMode)currentMode; + changed = true; + } + + if (settings.environment.skyMode == SkyMode::EquirectangularTexture || settings.environment.skyMode == SkyMode::OctahedralTexture) { + + const auto& textures = state.availableSkyTextures; + std::vector textureNames; + int currentTextureIdx = -1; + + textureNames.push_back("None"); + if (settings.environment.skyTextureId == UINT32_MAX) currentTextureIdx = 0; + + for (int i = 0; i < textures.size(); ++i) { + textureNames.push_back(textures[i].second.c_str()); + if (textures[i].first == settings.environment.skyTextureId) { + currentTextureIdx = i + 1; + } + } + + Syn::UI::BeginProperty("Sky Texture"); + if (ImGui::Combo("##SkyTexture", ¤tTextureIdx, textureNames.data(), textureNames.size())) { + if (currentTextureIdx == 0) { + settings.environment.skyTextureId = UINT32_MAX; + } + else { + settings.environment.skyTextureId = textures[currentTextureIdx - 1].first; + } + changed = true; + } + } + + changed |= Syn::UI::PropertyDragFloat("Intensity", settings.environment.skyIntensity, 0.05f, 0.0f, 100.0f, "%.2f", 1); + + Syn::UI::BeginProperty("Tint"); + if (ImGui::ColorEdit3("##SkyTint", glm::value_ptr(settings.environment.skyTint), ImGuiColorEditFlags_NoInputs)) { + changed = true; + } + + Syn::UI::BeginProperty("Rotation"); + bool rotDeactivated = false; + if (Syn::UI::DrawVec3Control("SkyRot", settings.environment.skyRotation, 0.0f, rotDeactivated)) { + changed = true; + } + } + + Syn::UI::PropertySeparator(); + drawSectionHeader("Fog & Atmospherics"); + changed |= Syn::UI::PropertyCheckbox("Enable Fog", settings.environment.enableFog); + if (settings.environment.enableFog) { + Syn::UI::BeginProperty("Fog Color"); + if (ImGui::ColorEdit3("##FogColor", glm::value_ptr(settings.environment.fogColor), ImGuiColorEditFlags_NoInputs)) { + changed = true; + } + changed |= Syn::UI::PropertyDragFloat("Fog Density", settings.environment.fogDensity, 0.001f, 0.0f, 1.0f, "%.4f", 1); + } + + Syn::UI::EndPropertyGrid(); + } + } + Syn::UI::EndCard(); + constexpr const char* CardDebugTitle = "Debug & Visualization"; if (Syn::UI::BeginCard(CardDebugTitle, SYN_ICON_BUG, getCardState(CardDebugTitle))) { diff --git a/SynapseEngine/EditorCore/Api/ISettingsApi.h b/SynapseEngine/EditorCore/Api/ISettingsApi.h index 4d0a981b..f762e062 100644 --- a/SynapseEngine/EditorCore/Api/ISettingsApi.h +++ b/SynapseEngine/EditorCore/Api/ISettingsApi.h @@ -1,5 +1,8 @@ #pragma once #include "Engine/Scene/Settings/SceneSettings.h" +#include +#include +#include namespace Syn { class ISettingsApi { @@ -8,5 +11,6 @@ namespace Syn { virtual SceneSettings GetSceneSettings() const = 0; virtual void SetSceneSettings(const SceneSettings& settings) = 0; + virtual std::vector> GetAvailableSkyTextures() const = 0; }; } \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Settings/SettingsState.h b/SynapseEngine/EditorCore/ViewModels/Settings/SettingsState.h index a7fc8f75..78fa34e8 100644 --- a/SynapseEngine/EditorCore/ViewModels/Settings/SettingsState.h +++ b/SynapseEngine/EditorCore/ViewModels/Settings/SettingsState.h @@ -1,10 +1,14 @@ #pragma once #include "Engine/Scene/Settings/SceneSettings.h" +#include +#include +#include namespace Syn { struct SettingsState { SceneSettings sceneSettings; + std::vector> availableSkyTextures; }; } \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/Settings/SettingsViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/Settings/SettingsViewModel.cpp index 5237897a..797e0ec9 100644 --- a/SynapseEngine/EditorCore/ViewModels/Settings/SettingsViewModel.cpp +++ b/SynapseEngine/EditorCore/ViewModels/Settings/SettingsViewModel.cpp @@ -13,6 +13,7 @@ namespace Syn { void SettingsViewModel::SyncWithEngine() { if (_api) { _state.sceneSettings = _api->GetSceneSettings(); + _state.availableSkyTextures = _api->GetAvailableSkyTextures(); } } diff --git a/SynapseEngine/EditorCore/ViewModels/Settings/SettingsViewModel.h b/SynapseEngine/EditorCore/ViewModels/Settings/SettingsViewModel.h index e05b4282..c79058f6 100644 --- a/SynapseEngine/EditorCore/ViewModels/Settings/SettingsViewModel.h +++ b/SynapseEngine/EditorCore/ViewModels/Settings/SettingsViewModel.h @@ -14,7 +14,6 @@ namespace Syn { void SyncWithEngine() override; void Dispatch(const SettingsIntent& intent) override; - private: ISettingsApi* _api = nullptr; SettingsState _state; diff --git a/SynapseEngine/Engine/Render/Passes/PostProcess/SkySphere/SkySpherePass.cpp b/SynapseEngine/Engine/Render/Passes/PostProcess/SkySphere/SkySpherePass.cpp index 4bdc7754..c394fa8f 100644 --- a/SynapseEngine/Engine/Render/Passes/PostProcess/SkySphere/SkySpherePass.cpp +++ b/SynapseEngine/Engine/Render/Passes/PostProcess/SkySphere/SkySpherePass.cpp @@ -19,7 +19,8 @@ namespace Syn { bool SkySpherePass::ShouldExecute(const RenderContext& context) const { auto skyTextureId = context.scene->GetSettings()->environment.skyTextureId; - return skyTextureId != UINT32_MAX && !context.scene->GetSettings()->debug.enableDebugVisibility; + bool enabled = context.scene->GetSettings()->environment.enableSky; + return enabled && skyTextureId != UINT32_MAX && !context.scene->GetSettings()->debug.enableDebugVisibility; } void SkySpherePass::Initialize() { @@ -121,6 +122,7 @@ namespace Syn { pc->mappingType = envSettings.skyMode == SkyMode::EquirectangularTexture ? 0 : 1; pc->skyIntensity = envSettings.skyIntensity; pc->skyRotationMatrix = rotMat; + pc->skyTint = envSettings.skyTint; pc.Push(context.cmd, _shaderProgram->GetLayout()); } diff --git a/SynapseEngine/Engine/Shaders/Includes/PushConstants/SkySpherePC.glsl b/SynapseEngine/Engine/Shaders/Includes/PushConstants/SkySpherePC.glsl index 8840b675..3632c331 100644 --- a/SynapseEngine/Engine/Shaders/Includes/PushConstants/SkySpherePC.glsl +++ b/SynapseEngine/Engine/Shaders/Includes/PushConstants/SkySpherePC.glsl @@ -5,6 +5,8 @@ struct SkySpherePC { mat4 skyRotationMatrix; + vec3 skyTint; + uint padding; uint64_t frameGlobalContextBufferAddr; uint skyTextureIndex; uint samplerIndex; diff --git a/SynapseEngine/Engine/Shaders/Passes/PostProcess/SkySphere/SkySphere.frag b/SynapseEngine/Engine/Shaders/Passes/PostProcess/SkySphere/SkySphere.frag index d846c64d..c991602b 100644 --- a/SynapseEngine/Engine/Shaders/Passes/PostProcess/SkySphere/SkySphere.frag +++ b/SynapseEngine/Engine/Shaders/Passes/PostProcess/SkySphere/SkySphere.frag @@ -38,6 +38,7 @@ void main() { finalUV.y = 1.0 - finalUV.y; vec3 skyColor = SampleTexture2DLod(pc.skyTextureIndex, pc.samplerIndex, finalUV, 0.0).rgb; + skyColor *= pc.skyTint; skyColor *= pc.skyIntensity; outColor = vec4(skyColor, 1.0); From ffd047b07c7efe398fb74d51f6e8627e2911ca37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20P=C3=A9ter?= Date: Fri, 3 Jul 2026 16:02:55 +0200 Subject: [PATCH 20/51] Implemented query statistics --- SynapseEngine/Engine/Engine.cpp | 24 +++--- SynapseEngine/Engine/Engine.h | 4 + SynapseEngine/Engine/Render/IRenderPass.h | 1 + .../GBuffer/MeshletOpaqueDeferredPass.h | 1 + .../GBuffer/TraditionalOpaqueDeferredPass.h | 1 + .../Lighting/DeferredDirectionLightPass.h | 1 + .../Lighting/DeferredPointLightPass.h | 1 + .../Deferred/Lighting/DeferredSpotLightPass.h | 1 + .../DepthPrepass/MeshletOpaqueDepthPrepass.h | 1 + .../MeshletTransparentDepthPrepass.h | 1 + .../TraditionalOpaqueDepthPrepass.h | 1 + .../TraditionalTransparentDepthPrepass.h | 1 + .../Lighting/MeshletOpaqueForwardPass.h | 1 + .../Lighting/TraditionalOpaqueForwardPass.h | 1 + .../Wboit/MeshletTransparentForwardPass.h | 1 + .../Wboit/TraditionalTransparentForwardPass.h | 1 + .../DirectionLightShadowMeshletOpaquePass.h | 1 + ...irectionLightShadowTraditionalOpaquePass.h | 1 + .../PointLightShadowMeshletOpaquePass.h | 1 + .../PointLightShadowTraditionalOpaquePass.h | 1 + .../SpotLightShadowMeshletOpaquePass.h | 1 + .../SpotLightShadowTraditionalOpaquePass.h | 1 + .../Chunk/MortonChunkAabbWireframePass.h | 1 + .../Chunk/StaticChunkAabbWireframePass.h | 1 + .../Collider/BoxColliderWireframePass.h | 1 + .../Collider/CapsuleColliderWireframePass.h | 1 + .../Collider/SphereColliderWireframePass.h | 1 + .../Light/PointLightAabbWireframePass.h | 1 + .../Light/PointLightSphereWireframePass.h | 1 + .../Light/SpotLightAabbWireframePass.h | 1 + .../Light/SpotLightConeWireframePass.h | 1 + .../Light/SpotLightPyramidWireframePass.h | 1 + .../Light/SpotLightSphereWireframePass.h | 1 + .../Wireframe/Mesh/WireframeMeshAabbPass.h | 1 + .../Wireframe/Mesh/WireframeMeshSpherePass.h | 1 + .../Meshlet/WireframeMeshletAabbPass.h | 1 + .../Meshlet/WireframeMeshletConePass.h | 1 + .../Meshlet/WireframeMeshletSpherePass.h | 1 + SynapseEngine/Engine/Render/RenderManager.cpp | 2 + .../Engine/Render/RenderPipeline.cpp | 15 ++++ .../Scene/Source/Procedural/test_config.json | 8 +- SynapseEngine/Engine/ServiceLocator.cpp | 4 + SynapseEngine/Engine/ServiceLocator.h | 10 +++ .../Statistics/DefaultRenderStatCollector.cpp | 86 +++++++++++++++++++ .../Statistics/DefaultRenderStatCollector.h | 43 ++++++++++ .../Statistics/FrameStatisticsManager.cpp | 27 ++++++ .../Statistics/FrameStatisticsManager.h | 30 +++++++ .../Statistics/IRenderStatCollector.cpp | 1 + .../Engine/Statistics/IRenderStatCollector.h | 34 ++++++++ .../Vk/Query/PipelineStatisticsQueryPool.cpp | 16 ++++ .../Vk/Query/PipelineStatisticsQueryPool.h | 14 +++ SynapseEngine/imgui.ini | 33 ++++--- 52 files changed, 356 insertions(+), 31 deletions(-) create mode 100644 SynapseEngine/Engine/Statistics/DefaultRenderStatCollector.cpp create mode 100644 SynapseEngine/Engine/Statistics/DefaultRenderStatCollector.h create mode 100644 SynapseEngine/Engine/Statistics/FrameStatisticsManager.cpp create mode 100644 SynapseEngine/Engine/Statistics/FrameStatisticsManager.h create mode 100644 SynapseEngine/Engine/Statistics/IRenderStatCollector.cpp create mode 100644 SynapseEngine/Engine/Statistics/IRenderStatCollector.h create mode 100644 SynapseEngine/Engine/Vk/Query/PipelineStatisticsQueryPool.cpp create mode 100644 SynapseEngine/Engine/Vk/Query/PipelineStatisticsQueryPool.h diff --git a/SynapseEngine/Engine/Engine.cpp b/SynapseEngine/Engine/Engine.cpp index 9efe7580..55091f92 100644 --- a/SynapseEngine/Engine/Engine.cpp +++ b/SynapseEngine/Engine/Engine.cpp @@ -34,6 +34,8 @@ #include "Engine/Profiler/DefaultGpuProfiler.h" #include "Engine/Profiler/DefaultCpuProfiler.h" +#include "Engine/Statistics/DefaultRenderStatCollector.h" +#include "Engine/Statistics/FrameStatisticsManager.h" #include "Engine/Serialization/Serializer.h" #include "Engine/Serialization/Archive/DefaultArchiveRegistry.h" @@ -96,6 +98,7 @@ namespace Syn _renderManager->WaitForFrame(currentFrame); ServiceLocator::GetGpuProfiler()->ResolveFrame(currentFrame); + ServiceLocator::GetFrameStatisticsManager()->ResolveFrame(currentFrame, ServiceLocator::GetRenderStatCollector()); if (_onGuiFlushCallback) _onGuiFlushCallback(currentFrame); @@ -210,17 +213,16 @@ namespace Syn _cpuProfiler = std::make_unique(_frameContext.framesInFlight); ServiceLocator::ProvideCpuProfiler(_cpuProfiler.get()); + + _renderStatCollector = std::make_unique(_frameContext.framesInFlight); + ServiceLocator::ProvideRenderStatCollector(_renderStatCollector.get()); + + _frameStatisticsManager = std::make_unique(_frameContext.framesInFlight); + ServiceLocator::ProvideFrameStatisticsManager(_frameStatisticsManager.get()); } void Engine::InitRenderManager(const EngineInitParams& params) { - /* -#ifdef SYN_PERFORMANCE - _renderManager = std::move(RendererFactory::CreatePerformanceRenderer(_frameContext.framesInFlight)); -#else - _renderManager = std::move(RendererFactory::CreateDeferredRenderer(_frameContext.framesInFlight)); -#endif - */ _renderManager = std::move(RendererFactory::CreateDeferredRenderer(_frameContext.framesInFlight)); _renderManager->SetGuiRenderCallback(params.onRenderGuiCallback); } @@ -234,6 +236,8 @@ namespace Syn _inputManager.reset(); _gpuProfiler.reset(); _cpuProfiler.reset(); + _renderStatCollector.reset(); + _frameStatisticsManager.reset(); _resourceManager.reset(); _gpuUploader.reset(); _taskExecutor.reset(); @@ -324,12 +328,6 @@ namespace Syn return std::make_unique(frames, std::make_unique()); }); - /* - _sceneManager->RegisterScene("NatureLevel", [frames]() { - return std::make_unique(frames, std::make_unique()); - }); - */ - _sceneManager->LoadScene("TestLevel"); } diff --git a/SynapseEngine/Engine/Engine.h b/SynapseEngine/Engine/Engine.h index 18545c7d..92fe96f5 100644 --- a/SynapseEngine/Engine/Engine.h +++ b/SynapseEngine/Engine/Engine.h @@ -29,6 +29,8 @@ namespace Syn { class ImageManager; class ModelManager; class AnimationManager; + class IRenderStatCollector; + class FrameStatisticsManager; } namespace Syn @@ -97,6 +99,8 @@ namespace Syn std::unique_ptr _cpuProfiler; std::unique_ptr _serializer; std::shared_ptr _memorySink; + std::unique_ptr _renderStatCollector; + std::unique_ptr _frameStatisticsManager; std::function _onGuiFlushCallback; }; diff --git a/SynapseEngine/Engine/Render/IRenderPass.h b/SynapseEngine/Engine/Render/IRenderPass.h index f4c11352..6e32e323 100644 --- a/SynapseEngine/Engine/Render/IRenderPass.h +++ b/SynapseEngine/Engine/Render/IRenderPass.h @@ -32,6 +32,7 @@ namespace Syn virtual void Initialize() {}; virtual void Execute(const RenderContext& context) {}; virtual bool ShouldExecute(const RenderContext& context) const { return true; } + virtual bool ShouldCollectStatistics() const { return false; } virtual std::string GetName() const = 0; virtual std::string GetGroup() const { return PassGroupNames::UndefinedPasses; } }; diff --git a/SynapseEngine/Engine/Render/Passes/Shading/Deferred/GBuffer/MeshletOpaqueDeferredPass.h b/SynapseEngine/Engine/Render/Passes/Shading/Deferred/GBuffer/MeshletOpaqueDeferredPass.h index 02f335da..cc217788 100644 --- a/SynapseEngine/Engine/Render/Passes/Shading/Deferred/GBuffer/MeshletOpaqueDeferredPass.h +++ b/SynapseEngine/Engine/Render/Passes/Shading/Deferred/GBuffer/MeshletOpaqueDeferredPass.h @@ -13,6 +13,7 @@ namespace Syn std::string GetGroup() const override { return PassGroupNames::DeferredGBufferPasses; } void Initialize() override; + bool ShouldCollectStatistics() const override { return true; } protected: bool ShouldExecute(const RenderContext& context) const override; void PrepareFrame(const RenderContext& context) override; diff --git a/SynapseEngine/Engine/Render/Passes/Shading/Deferred/GBuffer/TraditionalOpaqueDeferredPass.h b/SynapseEngine/Engine/Render/Passes/Shading/Deferred/GBuffer/TraditionalOpaqueDeferredPass.h index dcfefcd5..3dcc9817 100644 --- a/SynapseEngine/Engine/Render/Passes/Shading/Deferred/GBuffer/TraditionalOpaqueDeferredPass.h +++ b/SynapseEngine/Engine/Render/Passes/Shading/Deferred/GBuffer/TraditionalOpaqueDeferredPass.h @@ -11,6 +11,7 @@ namespace Syn { std::string GetName() const override { return _passName; } std::string GetGroup() const override { return PassGroupNames::DeferredGBufferPasses; } void Initialize() override; + bool ShouldCollectStatistics() const override { return true; } protected: bool ShouldExecute(const RenderContext& context) const override; void BindDescriptors(const RenderContext& context) override; diff --git a/SynapseEngine/Engine/Render/Passes/Shading/Deferred/Lighting/DeferredDirectionLightPass.h b/SynapseEngine/Engine/Render/Passes/Shading/Deferred/Lighting/DeferredDirectionLightPass.h index e31b4e3f..b710044c 100644 --- a/SynapseEngine/Engine/Render/Passes/Shading/Deferred/Lighting/DeferredDirectionLightPass.h +++ b/SynapseEngine/Engine/Render/Passes/Shading/Deferred/Lighting/DeferredDirectionLightPass.h @@ -10,6 +10,7 @@ namespace Syn { std::string GetName() const override { return "DeferredDirectionLightPass"; } std::string GetGroup() const override { return PassGroupNames::DeferredLightingPasses; } void Initialize() override; + bool ShouldCollectStatistics() const override { return true; } protected: bool ShouldExecute(const RenderContext& context) const override; void BindDescriptors(const RenderContext& context) override; diff --git a/SynapseEngine/Engine/Render/Passes/Shading/Deferred/Lighting/DeferredPointLightPass.h b/SynapseEngine/Engine/Render/Passes/Shading/Deferred/Lighting/DeferredPointLightPass.h index 60d36250..928c4967 100644 --- a/SynapseEngine/Engine/Render/Passes/Shading/Deferred/Lighting/DeferredPointLightPass.h +++ b/SynapseEngine/Engine/Render/Passes/Shading/Deferred/Lighting/DeferredPointLightPass.h @@ -10,6 +10,7 @@ namespace Syn { std::string GetName() const override { return "DeferredPointLightPass"; } std::string GetGroup() const override { return PassGroupNames::DeferredLightingPasses; } void Initialize() override; + bool ShouldCollectStatistics() const override { return true; } protected: bool ShouldExecute(const RenderContext& context) const override; void BindDescriptors(const RenderContext& context) override; diff --git a/SynapseEngine/Engine/Render/Passes/Shading/Deferred/Lighting/DeferredSpotLightPass.h b/SynapseEngine/Engine/Render/Passes/Shading/Deferred/Lighting/DeferredSpotLightPass.h index 1ebbc09c..2d24f5f1 100644 --- a/SynapseEngine/Engine/Render/Passes/Shading/Deferred/Lighting/DeferredSpotLightPass.h +++ b/SynapseEngine/Engine/Render/Passes/Shading/Deferred/Lighting/DeferredSpotLightPass.h @@ -10,6 +10,7 @@ namespace Syn { std::string GetName() const override { return "DeferredSpotLightPass"; } std::string GetGroup() const override { return PassGroupNames::DeferredLightingPasses; } void Initialize() override; + bool ShouldCollectStatistics() const override { return true; } protected: bool ShouldExecute(const RenderContext& context) const override; void BindDescriptors(const RenderContext& context) override; diff --git a/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/DepthPrepass/MeshletOpaqueDepthPrepass.h b/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/DepthPrepass/MeshletOpaqueDepthPrepass.h index fd6acc96..d8b5c80b 100644 --- a/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/DepthPrepass/MeshletOpaqueDepthPrepass.h +++ b/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/DepthPrepass/MeshletOpaqueDepthPrepass.h @@ -12,6 +12,7 @@ namespace Syn std::string GetName() const override { return _passName; } std::string GetGroup() const override { return PassGroupNames::ForwardPlusDepthPrePasses; } void Initialize() override; + bool ShouldCollectStatistics() const override { return true; } protected: bool ShouldExecute(const RenderContext& context) const override; void PrepareFrame(const RenderContext& context) override; diff --git a/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/DepthPrepass/MeshletTransparentDepthPrepass.h b/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/DepthPrepass/MeshletTransparentDepthPrepass.h index 2a8e8aa0..ff534c25 100644 --- a/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/DepthPrepass/MeshletTransparentDepthPrepass.h +++ b/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/DepthPrepass/MeshletTransparentDepthPrepass.h @@ -11,6 +11,7 @@ namespace Syn { std::string GetName() const override { return _passName; } std::string GetGroup() const override { return PassGroupNames::ForwardPlusDepthPrePasses; } void Initialize() override; + bool ShouldCollectStatistics() const override { return true; } protected: void PrepareFrame(const RenderContext& context) override; void PushConstants(const RenderContext& context) override; diff --git a/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/DepthPrepass/TraditionalOpaqueDepthPrepass.h b/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/DepthPrepass/TraditionalOpaqueDepthPrepass.h index 5429dbcf..08e18201 100644 --- a/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/DepthPrepass/TraditionalOpaqueDepthPrepass.h +++ b/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/DepthPrepass/TraditionalOpaqueDepthPrepass.h @@ -11,6 +11,7 @@ namespace Syn { std::string GetName() const override { return _passName; } std::string GetGroup() const override { return PassGroupNames::ForwardPlusDepthPrePasses; } void Initialize() override; + bool ShouldCollectStatistics() const override { return true; } protected: bool ShouldExecute(const RenderContext& context) const override; void PrepareFrame(const RenderContext& context) override; diff --git a/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/DepthPrepass/TraditionalTransparentDepthPrepass.h b/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/DepthPrepass/TraditionalTransparentDepthPrepass.h index f67da7c7..e99e0280 100644 --- a/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/DepthPrepass/TraditionalTransparentDepthPrepass.h +++ b/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/DepthPrepass/TraditionalTransparentDepthPrepass.h @@ -11,6 +11,7 @@ namespace Syn { std::string GetName() const override { return _passName; } std::string GetGroup() const override { return PassGroupNames::ForwardPlusDepthPrePasses; } void Initialize() override; + bool ShouldCollectStatistics() const override { return true; } protected: void PrepareFrame(const RenderContext& context) override; void PushConstants(const RenderContext& context) override; diff --git a/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/Lighting/MeshletOpaqueForwardPass.h b/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/Lighting/MeshletOpaqueForwardPass.h index 3541662b..e4cda329 100644 --- a/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/Lighting/MeshletOpaqueForwardPass.h +++ b/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/Lighting/MeshletOpaqueForwardPass.h @@ -11,6 +11,7 @@ namespace Syn { std::string GetName() const override { return _passName; } std::string GetGroup() const override { return PassGroupNames::ForwardPlusLightingPasses; } void Initialize() override; + bool ShouldCollectStatistics() const override { return true; } protected: bool ShouldExecute(const RenderContext& context) const override; void PrepareFrame(const RenderContext& context) override; diff --git a/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/Lighting/TraditionalOpaqueForwardPass.h b/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/Lighting/TraditionalOpaqueForwardPass.h index 10efa190..e558ac80 100644 --- a/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/Lighting/TraditionalOpaqueForwardPass.h +++ b/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/Lighting/TraditionalOpaqueForwardPass.h @@ -11,6 +11,7 @@ namespace Syn { std::string GetName() const override { return _passName; } std::string GetGroup() const override { return PassGroupNames::ForwardPlusLightingPasses; } void Initialize() override; + bool ShouldCollectStatistics() const override { return true; } protected: bool ShouldExecute(const RenderContext& context) const override; void PrepareFrame(const RenderContext& context) override; diff --git a/SynapseEngine/Engine/Render/Passes/Shading/Wboit/MeshletTransparentForwardPass.h b/SynapseEngine/Engine/Render/Passes/Shading/Wboit/MeshletTransparentForwardPass.h index df2d4cfc..8c0fcf9a 100644 --- a/SynapseEngine/Engine/Render/Passes/Shading/Wboit/MeshletTransparentForwardPass.h +++ b/SynapseEngine/Engine/Render/Passes/Shading/Wboit/MeshletTransparentForwardPass.h @@ -11,6 +11,7 @@ namespace Syn { std::string GetName() const override { return _passName; } std::string GetGroup() const override { return PassGroupNames::WboitPasses; } void Initialize() override; + bool ShouldCollectStatistics() const override { return true; } protected: bool ShouldExecute(const RenderContext& context) const override; void PrepareFrame(const RenderContext& context) override; diff --git a/SynapseEngine/Engine/Render/Passes/Shading/Wboit/TraditionalTransparentForwardPass.h b/SynapseEngine/Engine/Render/Passes/Shading/Wboit/TraditionalTransparentForwardPass.h index 81437908..7e609818 100644 --- a/SynapseEngine/Engine/Render/Passes/Shading/Wboit/TraditionalTransparentForwardPass.h +++ b/SynapseEngine/Engine/Render/Passes/Shading/Wboit/TraditionalTransparentForwardPass.h @@ -11,6 +11,7 @@ namespace Syn { std::string GetName() const override { return _passName; } std::string GetGroup() const override { return PassGroupNames::WboitPasses; } void Initialize() override; + bool ShouldCollectStatistics() const override { return true; } protected: bool ShouldExecute(const RenderContext& context) const override; void BindDescriptors(const RenderContext& context) override; diff --git a/SynapseEngine/Engine/Render/Passes/Shadow/DirectionLight/DirectionLightShadowMeshletOpaquePass.h b/SynapseEngine/Engine/Render/Passes/Shadow/DirectionLight/DirectionLightShadowMeshletOpaquePass.h index e9942ae1..9653b6cd 100644 --- a/SynapseEngine/Engine/Render/Passes/Shadow/DirectionLight/DirectionLightShadowMeshletOpaquePass.h +++ b/SynapseEngine/Engine/Render/Passes/Shadow/DirectionLight/DirectionLightShadowMeshletOpaquePass.h @@ -12,6 +12,7 @@ namespace Syn { std::string GetGroup() const override { return PassGroupNames::ShadowPasses; } void Initialize() override; + bool ShouldCollectStatistics() const override { return true; } protected: bool ShouldExecute(const RenderContext& context) const override; void PrepareFrame(const RenderContext& context) override; diff --git a/SynapseEngine/Engine/Render/Passes/Shadow/DirectionLight/DirectionLightShadowTraditionalOpaquePass.h b/SynapseEngine/Engine/Render/Passes/Shadow/DirectionLight/DirectionLightShadowTraditionalOpaquePass.h index d2d8203e..46e93afd 100644 --- a/SynapseEngine/Engine/Render/Passes/Shadow/DirectionLight/DirectionLightShadowTraditionalOpaquePass.h +++ b/SynapseEngine/Engine/Render/Passes/Shadow/DirectionLight/DirectionLightShadowTraditionalOpaquePass.h @@ -11,6 +11,7 @@ namespace Syn { std::string GetGroup() const override { return PassGroupNames::ShadowPasses; } void Initialize() override; + bool ShouldCollectStatistics() const override { return true; } protected: bool ShouldExecute(const RenderContext& context) const override; void PrepareFrame(const RenderContext& context) override; diff --git a/SynapseEngine/Engine/Render/Passes/Shadow/PointLight/PointLightShadowMeshletOpaquePass.h b/SynapseEngine/Engine/Render/Passes/Shadow/PointLight/PointLightShadowMeshletOpaquePass.h index a46ebb32..5c2db956 100644 --- a/SynapseEngine/Engine/Render/Passes/Shadow/PointLight/PointLightShadowMeshletOpaquePass.h +++ b/SynapseEngine/Engine/Render/Passes/Shadow/PointLight/PointLightShadowMeshletOpaquePass.h @@ -12,6 +12,7 @@ namespace Syn { std::string GetGroup() const override { return PassGroupNames::ShadowPasses; } void Initialize() override; + bool ShouldCollectStatistics() const override { return true; } protected: bool ShouldExecute(const RenderContext& context) const override; void PrepareFrame(const RenderContext& context) override; diff --git a/SynapseEngine/Engine/Render/Passes/Shadow/PointLight/PointLightShadowTraditionalOpaquePass.h b/SynapseEngine/Engine/Render/Passes/Shadow/PointLight/PointLightShadowTraditionalOpaquePass.h index 4013afd8..cc66369a 100644 --- a/SynapseEngine/Engine/Render/Passes/Shadow/PointLight/PointLightShadowTraditionalOpaquePass.h +++ b/SynapseEngine/Engine/Render/Passes/Shadow/PointLight/PointLightShadowTraditionalOpaquePass.h @@ -12,6 +12,7 @@ namespace Syn { std::string GetGroup() const override { return PassGroupNames::ShadowPasses; } void Initialize() override; + bool ShouldCollectStatistics() const override { return true; } protected: bool ShouldExecute(const RenderContext& context) const override; void PrepareFrame(const RenderContext& context) override; diff --git a/SynapseEngine/Engine/Render/Passes/Shadow/SpotLight/SpotLightShadowMeshletOpaquePass.h b/SynapseEngine/Engine/Render/Passes/Shadow/SpotLight/SpotLightShadowMeshletOpaquePass.h index bac4b2e6..dd123983 100644 --- a/SynapseEngine/Engine/Render/Passes/Shadow/SpotLight/SpotLightShadowMeshletOpaquePass.h +++ b/SynapseEngine/Engine/Render/Passes/Shadow/SpotLight/SpotLightShadowMeshletOpaquePass.h @@ -12,6 +12,7 @@ namespace Syn { std::string GetGroup() const override { return PassGroupNames::ShadowPasses; } void Initialize() override; + bool ShouldCollectStatistics() const override { return true; } protected: bool ShouldExecute(const RenderContext& context) const override; void PrepareFrame(const RenderContext& context) override; diff --git a/SynapseEngine/Engine/Render/Passes/Shadow/SpotLight/SpotLightShadowTraditionalOpaquePass.h b/SynapseEngine/Engine/Render/Passes/Shadow/SpotLight/SpotLightShadowTraditionalOpaquePass.h index a0df3cda..7b385def 100644 --- a/SynapseEngine/Engine/Render/Passes/Shadow/SpotLight/SpotLightShadowTraditionalOpaquePass.h +++ b/SynapseEngine/Engine/Render/Passes/Shadow/SpotLight/SpotLightShadowTraditionalOpaquePass.h @@ -12,6 +12,7 @@ namespace Syn { std::string GetGroup() const override { return PassGroupNames::ShadowPasses; } void Initialize() override; + bool ShouldCollectStatistics() const override { return true; } protected: bool ShouldExecute(const RenderContext& context) const override; void PrepareFrame(const RenderContext& context) override; diff --git a/SynapseEngine/Engine/Render/Passes/Wireframe/Chunk/MortonChunkAabbWireframePass.h b/SynapseEngine/Engine/Render/Passes/Wireframe/Chunk/MortonChunkAabbWireframePass.h index ecc87aac..62859a2d 100644 --- a/SynapseEngine/Engine/Render/Passes/Wireframe/Chunk/MortonChunkAabbWireframePass.h +++ b/SynapseEngine/Engine/Render/Passes/Wireframe/Chunk/MortonChunkAabbWireframePass.h @@ -8,6 +8,7 @@ namespace Syn { std::string GetName() const override { return "MortonChunkAabbWireframePass"; } std::string GetGroup() const override { return PassGroupNames::WireframePasses; } void Initialize() override; + bool ShouldCollectStatistics() const override { return true; } protected: bool ShouldExecute(const RenderContext& context) const override; void PrepareFrame(const RenderContext& context) override; diff --git a/SynapseEngine/Engine/Render/Passes/Wireframe/Chunk/StaticChunkAabbWireframePass.h b/SynapseEngine/Engine/Render/Passes/Wireframe/Chunk/StaticChunkAabbWireframePass.h index 2bcf76d2..b68aec58 100644 --- a/SynapseEngine/Engine/Render/Passes/Wireframe/Chunk/StaticChunkAabbWireframePass.h +++ b/SynapseEngine/Engine/Render/Passes/Wireframe/Chunk/StaticChunkAabbWireframePass.h @@ -8,6 +8,7 @@ namespace Syn { std::string GetName() const override { return "StaticChunkAabbWireframePass"; } std::string GetGroup() const override { return PassGroupNames::WireframePasses; } void Initialize() override; + bool ShouldCollectStatistics() const override { return true; } protected: bool ShouldExecute(const RenderContext& context) const override; void PrepareFrame(const RenderContext& context) override; diff --git a/SynapseEngine/Engine/Render/Passes/Wireframe/Collider/BoxColliderWireframePass.h b/SynapseEngine/Engine/Render/Passes/Wireframe/Collider/BoxColliderWireframePass.h index f7bf3bf6..42a267c8 100644 --- a/SynapseEngine/Engine/Render/Passes/Wireframe/Collider/BoxColliderWireframePass.h +++ b/SynapseEngine/Engine/Render/Passes/Wireframe/Collider/BoxColliderWireframePass.h @@ -8,6 +8,7 @@ namespace Syn { std::string GetName() const override { return "BoxColliderWireframePass"; } std::string GetGroup() const override { return PassGroupNames::WireframePasses; } void Initialize() override; + bool ShouldCollectStatistics() const override { return true; } protected: bool ShouldExecute(const RenderContext& context) const override; void PrepareFrame(const RenderContext& context) override; diff --git a/SynapseEngine/Engine/Render/Passes/Wireframe/Collider/CapsuleColliderWireframePass.h b/SynapseEngine/Engine/Render/Passes/Wireframe/Collider/CapsuleColliderWireframePass.h index 2f5bcc5d..4b4b2caa 100644 --- a/SynapseEngine/Engine/Render/Passes/Wireframe/Collider/CapsuleColliderWireframePass.h +++ b/SynapseEngine/Engine/Render/Passes/Wireframe/Collider/CapsuleColliderWireframePass.h @@ -8,6 +8,7 @@ namespace Syn { std::string GetName() const override { return "CapsuleColliderWireframePass"; } std::string GetGroup() const override { return PassGroupNames::WireframePasses; } void Initialize() override; + bool ShouldCollectStatistics() const override { return true; } protected: bool ShouldExecute(const RenderContext& context) const override; void PrepareFrame(const RenderContext& context) override; diff --git a/SynapseEngine/Engine/Render/Passes/Wireframe/Collider/SphereColliderWireframePass.h b/SynapseEngine/Engine/Render/Passes/Wireframe/Collider/SphereColliderWireframePass.h index 39f25e96..24a95ede 100644 --- a/SynapseEngine/Engine/Render/Passes/Wireframe/Collider/SphereColliderWireframePass.h +++ b/SynapseEngine/Engine/Render/Passes/Wireframe/Collider/SphereColliderWireframePass.h @@ -8,6 +8,7 @@ namespace Syn { std::string GetName() const override { return "SphereColliderWireframePass"; } std::string GetGroup() const override { return PassGroupNames::WireframePasses; } void Initialize() override; + bool ShouldCollectStatistics() const override { return true; } protected: bool ShouldExecute(const RenderContext& context) const override; void PrepareFrame(const RenderContext& context) override; diff --git a/SynapseEngine/Engine/Render/Passes/Wireframe/Light/PointLightAabbWireframePass.h b/SynapseEngine/Engine/Render/Passes/Wireframe/Light/PointLightAabbWireframePass.h index 3d72100c..77525ebc 100644 --- a/SynapseEngine/Engine/Render/Passes/Wireframe/Light/PointLightAabbWireframePass.h +++ b/SynapseEngine/Engine/Render/Passes/Wireframe/Light/PointLightAabbWireframePass.h @@ -8,6 +8,7 @@ namespace Syn { std::string GetName() const override { return "PointLightAabbWireframePass"; } std::string GetGroup() const override { return PassGroupNames::WireframePasses; } void Initialize() override; + bool ShouldCollectStatistics() const override { return true; } protected: bool ShouldExecute(const RenderContext& context) const override; void PrepareFrame(const RenderContext& context) override; diff --git a/SynapseEngine/Engine/Render/Passes/Wireframe/Light/PointLightSphereWireframePass.h b/SynapseEngine/Engine/Render/Passes/Wireframe/Light/PointLightSphereWireframePass.h index 138acd28..f94b528c 100644 --- a/SynapseEngine/Engine/Render/Passes/Wireframe/Light/PointLightSphereWireframePass.h +++ b/SynapseEngine/Engine/Render/Passes/Wireframe/Light/PointLightSphereWireframePass.h @@ -8,6 +8,7 @@ namespace Syn { std::string GetName() const override { return "PointLightSphereWireframePass"; } std::string GetGroup() const override { return PassGroupNames::WireframePasses; } void Initialize() override; + bool ShouldCollectStatistics() const override { return true; } protected: bool ShouldExecute(const RenderContext& context) const override; void PrepareFrame(const RenderContext& context) override; diff --git a/SynapseEngine/Engine/Render/Passes/Wireframe/Light/SpotLightAabbWireframePass.h b/SynapseEngine/Engine/Render/Passes/Wireframe/Light/SpotLightAabbWireframePass.h index 7e4fcd3c..3342824d 100644 --- a/SynapseEngine/Engine/Render/Passes/Wireframe/Light/SpotLightAabbWireframePass.h +++ b/SynapseEngine/Engine/Render/Passes/Wireframe/Light/SpotLightAabbWireframePass.h @@ -8,6 +8,7 @@ namespace Syn { std::string GetName() const override { return "SpotLightAabbWireframePass"; } std::string GetGroup() const override { return PassGroupNames::WireframePasses; } void Initialize() override; + bool ShouldCollectStatistics() const override { return true; } protected: bool ShouldExecute(const RenderContext& context) const override; void PrepareFrame(const RenderContext& context) override; diff --git a/SynapseEngine/Engine/Render/Passes/Wireframe/Light/SpotLightConeWireframePass.h b/SynapseEngine/Engine/Render/Passes/Wireframe/Light/SpotLightConeWireframePass.h index 21be561b..f502d3d0 100644 --- a/SynapseEngine/Engine/Render/Passes/Wireframe/Light/SpotLightConeWireframePass.h +++ b/SynapseEngine/Engine/Render/Passes/Wireframe/Light/SpotLightConeWireframePass.h @@ -8,6 +8,7 @@ namespace Syn { std::string GetName() const override { return "SpotLightConeWireframePass"; } std::string GetGroup() const override { return PassGroupNames::WireframePasses; } void Initialize() override; + bool ShouldCollectStatistics() const override { return true; } protected: bool ShouldExecute(const RenderContext& context) const override; void PrepareFrame(const RenderContext& context) override; diff --git a/SynapseEngine/Engine/Render/Passes/Wireframe/Light/SpotLightPyramidWireframePass.h b/SynapseEngine/Engine/Render/Passes/Wireframe/Light/SpotLightPyramidWireframePass.h index 98510dc5..1f5b536e 100644 --- a/SynapseEngine/Engine/Render/Passes/Wireframe/Light/SpotLightPyramidWireframePass.h +++ b/SynapseEngine/Engine/Render/Passes/Wireframe/Light/SpotLightPyramidWireframePass.h @@ -8,6 +8,7 @@ namespace Syn { std::string GetName() const override { return "SpotLightPyramidWireframePass"; } std::string GetGroup() const override { return PassGroupNames::WireframePasses; } void Initialize() override; + bool ShouldCollectStatistics() const override { return true; } protected: bool ShouldExecute(const RenderContext& context) const override; void PrepareFrame(const RenderContext& context) override; diff --git a/SynapseEngine/Engine/Render/Passes/Wireframe/Light/SpotLightSphereWireframePass.h b/SynapseEngine/Engine/Render/Passes/Wireframe/Light/SpotLightSphereWireframePass.h index d6d7ca13..ac008624 100644 --- a/SynapseEngine/Engine/Render/Passes/Wireframe/Light/SpotLightSphereWireframePass.h +++ b/SynapseEngine/Engine/Render/Passes/Wireframe/Light/SpotLightSphereWireframePass.h @@ -8,6 +8,7 @@ namespace Syn { std::string GetName() const override { return "SpotLightSphereWireframePass"; } std::string GetGroup() const override { return PassGroupNames::WireframePasses; } void Initialize() override; + bool ShouldCollectStatistics() const override { return true; } protected: bool ShouldExecute(const RenderContext& context) const override; void PrepareFrame(const RenderContext& context) override; diff --git a/SynapseEngine/Engine/Render/Passes/Wireframe/Mesh/WireframeMeshAabbPass.h b/SynapseEngine/Engine/Render/Passes/Wireframe/Mesh/WireframeMeshAabbPass.h index 96e83f35..26b5d115 100644 --- a/SynapseEngine/Engine/Render/Passes/Wireframe/Mesh/WireframeMeshAabbPass.h +++ b/SynapseEngine/Engine/Render/Passes/Wireframe/Mesh/WireframeMeshAabbPass.h @@ -8,6 +8,7 @@ namespace Syn { std::string GetName() const override { return "WireframeMeshAabbPass"; } std::string GetGroup() const override { return PassGroupNames::WireframePasses; } void Initialize() override; + bool ShouldCollectStatistics() const override { return true; } protected: bool ShouldExecute(const RenderContext& context) const override; void PrepareFrame(const RenderContext& context) override; diff --git a/SynapseEngine/Engine/Render/Passes/Wireframe/Mesh/WireframeMeshSpherePass.h b/SynapseEngine/Engine/Render/Passes/Wireframe/Mesh/WireframeMeshSpherePass.h index 5966ae8b..b0181d08 100644 --- a/SynapseEngine/Engine/Render/Passes/Wireframe/Mesh/WireframeMeshSpherePass.h +++ b/SynapseEngine/Engine/Render/Passes/Wireframe/Mesh/WireframeMeshSpherePass.h @@ -8,6 +8,7 @@ namespace Syn { std::string GetName() const override { return "WireframeMeshSpherePass"; } std::string GetGroup() const override { return PassGroupNames::WireframePasses; } void Initialize() override; + bool ShouldCollectStatistics() const override { return true; } protected: bool ShouldExecute(const RenderContext& context) const override; void PrepareFrame(const RenderContext& context) override; diff --git a/SynapseEngine/Engine/Render/Passes/Wireframe/Meshlet/WireframeMeshletAabbPass.h b/SynapseEngine/Engine/Render/Passes/Wireframe/Meshlet/WireframeMeshletAabbPass.h index 4dd13499..2776ca8c 100644 --- a/SynapseEngine/Engine/Render/Passes/Wireframe/Meshlet/WireframeMeshletAabbPass.h +++ b/SynapseEngine/Engine/Render/Passes/Wireframe/Meshlet/WireframeMeshletAabbPass.h @@ -8,6 +8,7 @@ namespace Syn { std::string GetName() const override { return "WireframeMeshletAabbPass"; } std::string GetGroup() const override { return PassGroupNames::WireframePasses; } void Initialize() override; + bool ShouldCollectStatistics() const override { return true; } protected: bool ShouldExecute(const RenderContext& context) const override; void PrepareFrame(const RenderContext& context) override; diff --git a/SynapseEngine/Engine/Render/Passes/Wireframe/Meshlet/WireframeMeshletConePass.h b/SynapseEngine/Engine/Render/Passes/Wireframe/Meshlet/WireframeMeshletConePass.h index 8bf9565c..258209b0 100644 --- a/SynapseEngine/Engine/Render/Passes/Wireframe/Meshlet/WireframeMeshletConePass.h +++ b/SynapseEngine/Engine/Render/Passes/Wireframe/Meshlet/WireframeMeshletConePass.h @@ -8,6 +8,7 @@ namespace Syn { std::string GetName() const override { return "WireframeMeshletConePass"; } std::string GetGroup() const override { return PassGroupNames::WireframePasses; } void Initialize() override; + bool ShouldCollectStatistics() const override { return true; } protected: bool ShouldExecute(const RenderContext& context) const override; void PrepareFrame(const RenderContext& context) override; diff --git a/SynapseEngine/Engine/Render/Passes/Wireframe/Meshlet/WireframeMeshletSpherePass.h b/SynapseEngine/Engine/Render/Passes/Wireframe/Meshlet/WireframeMeshletSpherePass.h index c74066a8..dd8c84b4 100644 --- a/SynapseEngine/Engine/Render/Passes/Wireframe/Meshlet/WireframeMeshletSpherePass.h +++ b/SynapseEngine/Engine/Render/Passes/Wireframe/Meshlet/WireframeMeshletSpherePass.h @@ -8,6 +8,7 @@ namespace Syn { std::string GetName() const override { return "WireframeMeshletSpherePass"; } std::string GetGroup() const override { return PassGroupNames::WireframePasses; } void Initialize() override; + bool ShouldCollectStatistics() const override { return true; } protected: bool ShouldExecute(const RenderContext& context) const override; void PrepareFrame(const RenderContext& context) override; diff --git a/SynapseEngine/Engine/Render/RenderManager.cpp b/SynapseEngine/Engine/Render/RenderManager.cpp index 25135f9f..eb00f1c8 100644 --- a/SynapseEngine/Engine/Render/RenderManager.cpp +++ b/SynapseEngine/Engine/Render/RenderManager.cpp @@ -5,6 +5,7 @@ #include "Engine/Vk/Context.h" #include "Engine/FrameContext.h" #include "Engine/Profiler/IGpuProfiler.h" +#include "Engine/Statistics/IRenderStatCollector.h" namespace Syn { @@ -51,6 +52,7 @@ namespace Syn { return; ServiceLocator::GetGpuProfiler()->BeginFrame(cmd->Handle(), frameIndex); + ServiceLocator::GetRenderStatCollector()->BeginFrame(cmd->Handle(), frameIndex); if (_preRenderCallback) { _preRenderCallback(cmd->Handle(), frameIndex, scene); diff --git a/SynapseEngine/Engine/Render/RenderPipeline.cpp b/SynapseEngine/Engine/Render/RenderPipeline.cpp index 386a2a43..cea6b2ac 100644 --- a/SynapseEngine/Engine/Render/RenderPipeline.cpp +++ b/SynapseEngine/Engine/Render/RenderPipeline.cpp @@ -2,6 +2,7 @@ #include "Engine/ServiceLocator.h" #include "Engine/Vk/Context.h" #include "Engine/Profiler/IGpuProfiler.h" +#include "Engine/Statistics/IRenderStatCollector.h" namespace Syn { @@ -20,6 +21,7 @@ namespace Syn void RenderPipeline::Execute(const RenderContext& context) { auto profiler = ServiceLocator::GetGpuProfiler(); + auto statCollector = ServiceLocator::GetRenderStatCollector(); if (context.scene) { @@ -27,7 +29,20 @@ namespace Syn if (pass->ShouldExecute(context)) { uint32_t measureIdx = profiler->StartPass(context.cmd, context.frameIndex, pass->GetGroup(), pass->GetName()); + + bool collectStats = pass->ShouldCollectStatistics(); + uint32_t statIdx = 0; + + if (collectStats) { + statIdx = statCollector->StartPass(context.cmd, context.frameIndex, pass->GetGroup(), pass->GetName()); + } + pass->Execute(context); + + if (collectStats) { + statCollector->EndPass(context.cmd, context.frameIndex, statIdx); + } + profiler->EndPass(context.cmd, context.frameIndex, measureIdx); } } diff --git a/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json b/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json index dfb69894..2d0e3234 100644 --- a/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json +++ b/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json @@ -22,9 +22,9 @@ }, "lights": { "directional_count": 1, - "point_count": 128, - "point_shadow_count": 32, - "spot_count": 128, - "spot_shadow_count": 32 + "point_count": 1, + "point_shadow_count": 1, + "spot_count": 1, + "spot_shadow_count": 1 } } \ No newline at end of file diff --git a/SynapseEngine/Engine/ServiceLocator.cpp b/SynapseEngine/Engine/ServiceLocator.cpp index 9f2ea300..331a06fc 100644 --- a/SynapseEngine/Engine/ServiceLocator.cpp +++ b/SynapseEngine/Engine/ServiceLocator.cpp @@ -20,6 +20,8 @@ namespace Syn { ICpuProfiler* ServiceLocator::_cpuProfiler = nullptr; Serializer* ServiceLocator::_serializer = nullptr; PhysicsFactory ServiceLocator::_physicsFactory = nullptr; + IRenderStatCollector* ServiceLocator::_renderStatCollector = nullptr; + FrameStatisticsManager* ServiceLocator::_frameStatisticsManager = nullptr; void ServiceLocator::Shutdown() { @@ -41,5 +43,7 @@ namespace Syn { _gpuProfiler = nullptr; _cpuProfiler = nullptr; _serializer = nullptr; + _renderStatCollector = nullptr; + _frameStatisticsManager = nullptr; } } \ No newline at end of file diff --git a/SynapseEngine/Engine/ServiceLocator.h b/SynapseEngine/Engine/ServiceLocator.h index a530cfd6..8450e06b 100644 --- a/SynapseEngine/Engine/ServiceLocator.h +++ b/SynapseEngine/Engine/ServiceLocator.h @@ -28,6 +28,8 @@ namespace Syn { class IGpuProfiler; class ICpuProfiler; class Serializer; + class IRenderStatCollector; + class FrameStatisticsManager; using PhysicsFactory = std::function()>; } @@ -99,6 +101,12 @@ namespace Syn static void ProvidePhysicsFactory(PhysicsFactory factory) { _physicsFactory = std::move(factory); } static PhysicsFactory& GetPhysicsFactory() { return _physicsFactory; } + static void ProvideRenderStatCollector(IRenderStatCollector* collector) { _renderStatCollector = collector; } + static IRenderStatCollector* GetRenderStatCollector() { return _renderStatCollector; } + + static void ProvideFrameStatisticsManager(FrameStatisticsManager* manager) { _frameStatisticsManager = manager; } + static FrameStatisticsManager* GetFrameStatisticsManager() { return _frameStatisticsManager; } + private: static Vk::Context* _vkContext; static Vk::GpuUploader* _gpuUploader; @@ -119,5 +127,7 @@ namespace Syn static ICpuProfiler* _cpuProfiler; static Serializer* _serializer; static PhysicsFactory _physicsFactory; + static IRenderStatCollector* _renderStatCollector; + static FrameStatisticsManager* _frameStatisticsManager; }; } \ No newline at end of file diff --git a/SynapseEngine/Engine/Statistics/DefaultRenderStatCollector.cpp b/SynapseEngine/Engine/Statistics/DefaultRenderStatCollector.cpp new file mode 100644 index 00000000..2e24b95c --- /dev/null +++ b/SynapseEngine/Engine/Statistics/DefaultRenderStatCollector.cpp @@ -0,0 +1,86 @@ +#include "DefaultRenderStatCollector.h" + +namespace Syn { + + DefaultRenderStatCollector::DefaultRenderStatCollector(uint32_t framesInFlight) + : _framesInFlight(framesInFlight) + { + _pools.resize(framesInFlight); + _activeMeasurements.resize(framesInFlight); + _resolvedStats.resize(framesInFlight); + _queryCounters.resize(framesInFlight, 0); + + VkQueryPipelineStatisticFlags flags = + VK_QUERY_PIPELINE_STATISTIC_INPUT_ASSEMBLY_VERTICES_BIT | + VK_QUERY_PIPELINE_STATISTIC_INPUT_ASSEMBLY_PRIMITIVES_BIT | + VK_QUERY_PIPELINE_STATISTIC_VERTEX_SHADER_INVOCATIONS_BIT | + VK_QUERY_PIPELINE_STATISTIC_CLIPPING_INVOCATIONS_BIT | + VK_QUERY_PIPELINE_STATISTIC_CLIPPING_PRIMITIVES_BIT | + VK_QUERY_PIPELINE_STATISTIC_FRAGMENT_SHADER_INVOCATIONS_BIT; + //VK_QUERY_PIPELINE_STATISTIC_TASK_SHADER_INVOCATIONS_BIT_EXT | + //VK_QUERY_PIPELINE_STATISTIC_MESH_SHADER_INVOCATIONS_BIT_EXT; + + for (uint32_t i = 0; i < framesInFlight; ++i) { + _pools[i] = std::make_unique(MAX_QUERIES_PER_FRAME, flags); + } + } + + void DefaultRenderStatCollector::BeginFrame(VkCommandBuffer cmd, uint32_t frameIndex) { + _queryCounters[frameIndex] = 0; + _activeMeasurements[frameIndex].clear(); + _pools[frameIndex]->Reset(cmd, 0, MAX_QUERIES_PER_FRAME); + } + + uint32_t DefaultRenderStatCollector::StartPass(VkCommandBuffer cmd, uint32_t frameIndex, const std::string& groupName, const std::string& name) { + uint32_t queryIndex = _queryCounters[frameIndex]++; + + _pools[frameIndex]->BeginQuery(cmd, queryIndex); + _activeMeasurements[frameIndex].push_back({ groupName, name, queryIndex }); + + return queryIndex; + } + + void DefaultRenderStatCollector::EndPass(VkCommandBuffer cmd, uint32_t frameIndex, uint32_t queryIndex) { + _pools[frameIndex]->EndQuery(cmd, queryIndex); + } + + void DefaultRenderStatCollector::ResolveFrame(uint32_t frameIndex) { + auto& measurements = _activeMeasurements[frameIndex]; + if (measurements.empty()) return; + + uint32_t totalQueries = _queryCounters[frameIndex]; + std::vector results(totalQueries * STATS_PER_QUERY); + + if (_pools[frameIndex]->GetResults(0, totalQueries, results, false)) { + auto& stats = _resolvedStats[frameIndex]; + stats.clear(); + stats.reserve(measurements.size()); + + for (size_t i = 0; i < measurements.size(); ++i) { + const auto& m = measurements[i]; + + //uint32_t offset = m.queryIndex * STATS_PER_QUERY; + uint32_t offset = m.queryIndex * 6; + + RenderPassStats passStat; + passStat.groupName = m.groupName; + passStat.passName = m.passName; + + passStat.inputAssemblyVertices = results[offset + 0]; + passStat.inputAssemblyPrimitives = results[offset + 1]; + passStat.vertexShaderInvocations = results[offset + 2]; + passStat.clippingInvocations = results[offset + 3]; + passStat.clippingPrimitives = results[offset + 4]; + passStat.fragmentShaderInvocations = results[offset + 5]; + //passStat.taskShaderInvocations = results[offset + 6]; + //passStat.meshShaderInvocations = results[offset + 7]; + + stats.push_back(passStat); + } + } + } + + const std::vector& DefaultRenderStatCollector::GetStats(uint32_t frameIndex) const { + return _resolvedStats[frameIndex]; + } +} \ No newline at end of file diff --git a/SynapseEngine/Engine/Statistics/DefaultRenderStatCollector.h b/SynapseEngine/Engine/Statistics/DefaultRenderStatCollector.h new file mode 100644 index 00000000..f4d8e428 --- /dev/null +++ b/SynapseEngine/Engine/Statistics/DefaultRenderStatCollector.h @@ -0,0 +1,43 @@ +#pragma once +#include "Engine/SynApi.h" +#include "IRenderStatCollector.h" +#include "Engine/Vk/Query/PipelineStatisticsQueryPool.h" + +#include +#include +#include + +namespace Syn { + + struct SYN_API StatMeasurement { + std::string groupName; + std::string passName; + uint32_t queryIndex; + }; + + class SYN_API DefaultRenderStatCollector : public IRenderStatCollector { + public: + explicit DefaultRenderStatCollector(uint32_t framesInFlight); + ~DefaultRenderStatCollector() override = default; + + DefaultRenderStatCollector(const DefaultRenderStatCollector&) = delete; + DefaultRenderStatCollector& operator=(const DefaultRenderStatCollector&) = delete; + + void BeginFrame(VkCommandBuffer cmd, uint32_t frameIndex) override; + uint32_t StartPass(VkCommandBuffer cmd, uint32_t frameIndex, const std::string& groupName, const std::string& name) override; + void EndPass(VkCommandBuffer cmd, uint32_t frameIndex, uint32_t queryIndex) override; + + void ResolveFrame(uint32_t frameIndex) override; + const std::vector& GetStats(uint32_t frameIndex) const override; + private: + static constexpr uint32_t MAX_QUERIES_PER_FRAME = 256; + static constexpr uint32_t STATS_PER_QUERY = 8; + + uint32_t _framesInFlight; + std::vector _queryCounters; + std::vector> _pools; + + std::vector> _activeMeasurements; + std::vector> _resolvedStats; + }; +} \ No newline at end of file diff --git a/SynapseEngine/Engine/Statistics/FrameStatisticsManager.cpp b/SynapseEngine/Engine/Statistics/FrameStatisticsManager.cpp new file mode 100644 index 00000000..bad94788 --- /dev/null +++ b/SynapseEngine/Engine/Statistics/FrameStatisticsManager.cpp @@ -0,0 +1,27 @@ +#include "FrameStatisticsManager.h" + +namespace Syn { + + FrameStatisticsManager::FrameStatisticsManager(uint32_t framesInFlight) { + _gpuStatsPerFrame.resize(framesInFlight); + } + + void FrameStatisticsManager::UpdateCpuStats(const CpuRenderStats& newStats) { + _cpuStats = newStats; + } + + void FrameStatisticsManager::ResolveFrame(uint32_t frameIndex, IRenderStatCollector* gpuCollector) { + if (gpuCollector) { + gpuCollector->ResolveFrame(frameIndex); + _gpuStatsPerFrame[frameIndex] = gpuCollector->GetStats(frameIndex); + } + } + + const CpuRenderStats& FrameStatisticsManager::GetCpuStats() const { + return _cpuStats; + } + + const std::vector& FrameStatisticsManager::GetGpuStats(uint32_t frameIndex) const { + return _gpuStatsPerFrame[frameIndex]; + } +} \ No newline at end of file diff --git a/SynapseEngine/Engine/Statistics/FrameStatisticsManager.h b/SynapseEngine/Engine/Statistics/FrameStatisticsManager.h new file mode 100644 index 00000000..059f3206 --- /dev/null +++ b/SynapseEngine/Engine/Statistics/FrameStatisticsManager.h @@ -0,0 +1,30 @@ +#pragma once +#include "Engine/SynApi.h" +#include "Engine/Statistics/IRenderStatCollector.h" +#include + +namespace Syn { + + struct SYN_API CpuRenderStats { + uint32_t totalModels = 0; + uint32_t totalDrawDescriptors = 0; + uint32_t traditionalDrawDescriptors = 0; + uint32_t meshletDrawDescriptors = 0; + uint32_t totalAllocatedInstances = 0; + uint32_t totalMaxMeshlets = 0; + }; + + class SYN_API FrameStatisticsManager { + public: + FrameStatisticsManager(uint32_t framesInFlight); + + void UpdateCpuStats(const CpuRenderStats& newStats); + void ResolveFrame(uint32_t frameIndex, IRenderStatCollector* gpuCollector); + + const CpuRenderStats& GetCpuStats() const; + const std::vector& GetGpuStats(uint32_t frameIndex) const; + private: + CpuRenderStats _cpuStats; + std::vector> _gpuStatsPerFrame; + }; +} \ No newline at end of file diff --git a/SynapseEngine/Engine/Statistics/IRenderStatCollector.cpp b/SynapseEngine/Engine/Statistics/IRenderStatCollector.cpp new file mode 100644 index 00000000..3d9081eb --- /dev/null +++ b/SynapseEngine/Engine/Statistics/IRenderStatCollector.cpp @@ -0,0 +1 @@ +#include "IRenderStatCollector.h" \ No newline at end of file diff --git a/SynapseEngine/Engine/Statistics/IRenderStatCollector.h b/SynapseEngine/Engine/Statistics/IRenderStatCollector.h new file mode 100644 index 00000000..d8520946 --- /dev/null +++ b/SynapseEngine/Engine/Statistics/IRenderStatCollector.h @@ -0,0 +1,34 @@ +#pragma once +#include "Engine/SynApi.h" +#include +#include +#include + +namespace Syn +{ + struct SYN_API RenderPassStats { + std::string groupName; + std::string passName; + + uint64_t inputAssemblyVertices = 0; + uint64_t inputAssemblyPrimitives = 0; + uint64_t vertexShaderInvocations = 0; + uint64_t clippingInvocations = 0; + uint64_t clippingPrimitives = 0; + uint64_t fragmentShaderInvocations = 0; + uint64_t taskShaderInvocations = 0; + uint64_t meshShaderInvocations = 0; + }; + + class SYN_API IRenderStatCollector { + public: + virtual ~IRenderStatCollector() = default; + + virtual void BeginFrame(VkCommandBuffer cmd, uint32_t frameIndex) = 0; + virtual uint32_t StartPass(VkCommandBuffer cmd, uint32_t frameIndex, const std::string& groupName, const std::string& name) = 0; + virtual void EndPass(VkCommandBuffer cmd, uint32_t frameIndex, uint32_t queryIndex) = 0; + + virtual void ResolveFrame(uint32_t frameIndex) = 0; + virtual const std::vector& GetStats(uint32_t frameIndex) const = 0; + }; +} \ No newline at end of file diff --git a/SynapseEngine/Engine/Vk/Query/PipelineStatisticsQueryPool.cpp b/SynapseEngine/Engine/Vk/Query/PipelineStatisticsQueryPool.cpp new file mode 100644 index 00000000..0ff22a1a --- /dev/null +++ b/SynapseEngine/Engine/Vk/Query/PipelineStatisticsQueryPool.cpp @@ -0,0 +1,16 @@ +#include "PipelineStatisticsQueryPool.h" + +namespace Syn::Vk +{ + PipelineStatisticsQueryPool::PipelineStatisticsQueryPool(uint32_t queryCount, VkQueryPipelineStatisticFlags flags) + : QueryPool(VK_QUERY_TYPE_PIPELINE_STATISTICS, queryCount, flags) + {} + + void PipelineStatisticsQueryPool::BeginQuery(VkCommandBuffer cmd, uint32_t queryIndex) { + vkCmdBeginQuery(cmd, _handle, queryIndex, 0); + } + + void PipelineStatisticsQueryPool::EndQuery(VkCommandBuffer cmd, uint32_t queryIndex) { + vkCmdEndQuery(cmd, _handle, queryIndex); + } +} \ No newline at end of file diff --git a/SynapseEngine/Engine/Vk/Query/PipelineStatisticsQueryPool.h b/SynapseEngine/Engine/Vk/Query/PipelineStatisticsQueryPool.h new file mode 100644 index 00000000..5dd83106 --- /dev/null +++ b/SynapseEngine/Engine/Vk/Query/PipelineStatisticsQueryPool.h @@ -0,0 +1,14 @@ +#pragma once +#include "../VkCommon.h" +#include "QueryPool.h" + +namespace Syn::Vk +{ + class SYN_API PipelineStatisticsQueryPool : public QueryPool { + public: + PipelineStatisticsQueryPool(uint32_t queryCount, VkQueryPipelineStatisticFlags flags); + + void BeginQuery(VkCommandBuffer cmd, uint32_t queryIndex); + void EndQuery(VkCommandBuffer cmd, uint32_t queryIndex); + }; +} \ No newline at end of file diff --git a/SynapseEngine/imgui.ini b/SynapseEngine/imgui.ini index f3f98f0d..fea6f6dc 100644 --- a/SynapseEngine/imgui.ini +++ b/SynapseEngine/imgui.ini @@ -1,11 +1,11 @@ [Window][HostWindow_Scene] Pos=0,23 -Size=2560,1346 +Size=2304,1273 Collapsed=0 [Window][Content_Scene] -Pos=405,1037 -Size=1726,332 +Pos=405,964 +Size=1470,332 Collapsed=0 DockId=0x00000002,0 @@ -15,38 +15,38 @@ Size=400,400 Collapsed=0 [Window][ Inspector] -Pos=2133,23 -Size=427,659 +Pos=1877,23 +Size=427,623 Collapsed=0 DockId=0x00000005,0 [Window][ Viewport] Pos=405,23 -Size=1726,1012 +Size=1470,939 Collapsed=0 DockId=0x00000001,0 [Window][ Graphics & Environment] -Pos=2133,684 -Size=427,685 +Pos=1877,648 +Size=427,648 Collapsed=0 DockId=0x00000006,0 [Window][ Scene Hierarchy] Pos=0,23 -Size=403,566 +Size=403,535 Collapsed=0 DockId=0x00000009,0 [Window][ Performance Profiler] -Pos=0,591 -Size=403,778 +Pos=0,560 +Size=403,736 Collapsed=0 DockId=0x0000000A,0 [Window][ Output Log] -Pos=405,1037 -Size=1726,332 +Pos=405,964 +Size=1470,332 Collapsed=0 DockId=0x00000002,1 @@ -131,8 +131,13 @@ RefScale=13 Column 0 Width=63 Column 1 Weight=1.0000 +[Table][0x03404476,2] +RefScale=13 +Column 0 Width=0 +Column 1 Weight=-nan(ind) + [Docking][Data] -DockSpace ID=0x4713F8A8 Window=0xFBC4AE05 Pos=0,46 Size=2560,1346 Split=X +DockSpace ID=0x4713F8A8 Window=0xFBC4AE05 Pos=128,95 Size=2304,1273 Split=X DockNode ID=0x00000007 Parent=0x4713F8A8 SizeRef=403,1273 Split=Y Selected=0x02B8E2DB DockNode ID=0x00000009 Parent=0x00000007 SizeRef=403,535 Selected=0xF995F4A5 DockNode ID=0x0000000A Parent=0x00000007 SizeRef=403,736 Selected=0x02B8E2DB From 58ce7c4c9bf7b169dbb72f9090858c17cd49dffd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20P=C3=A9ter?= Date: Fri, 3 Jul 2026 19:20:54 +0200 Subject: [PATCH 21/51] Debug buffer names and texture metadata added to shader texture sampling --- SynapseEngine/Engine/Image/ImageManager.cpp | 16 +- SynapseEngine/Engine/Image/ImageManager.h | 2 +- .../Engine/Manager/ComponentBufferManager.cpp | 1 + .../Passes/Setup/GlobalFrameSetupPass.cpp | 3 + .../Engine/Scene/DrawData/ChunkDrawGroup.cpp | 20 +-- .../Engine/Scene/DrawData/DebugDrawGroup.cpp | 10 +- .../DrawData/DirectionLightDrawGroup.cpp | 4 +- .../DirectionLightShadowDrawGroup.cpp | 10 +- .../Scene/DrawData/ForwardPlusDrawGroup.cpp | 12 +- .../Engine/Scene/DrawData/ModelDrawGroup.cpp | 16 +- .../Scene/DrawData/PointLightDrawGroup.cpp | 8 +- .../DrawData/PointLightShadowDrawGroup.cpp | 32 ++-- .../Engine/Scene/DrawData/SceneDrawData.cpp | 2 +- .../Scene/DrawData/SpotLightDrawGroup.cpp | 12 +- .../DrawData/SpotLightShadowDrawGroup.cpp | 32 ++-- .../Engine/Scene/DrawData/SsaoDrawGroup.cpp | 2 +- .../Includes/Common/FrameGlobalContext.glsl | 2 + .../Shaders/Includes/Common/Texture.glsl | 15 ++ .../Shaders/Includes/Utils/MaterialMath.glsl | 47 ++++-- .../Deferred/GBuffer/OpaqueDeferred.frag | 10 +- .../ForwardPlus/DepthPrepass/PreDepth.frag | 2 +- .../ForwardPlus/Lighting/OpaqueForward.frag | 10 +- .../Shading/Wboit/TransparentForward.frag | 10 +- .../Statistics/DefaultRenderStatCollector.cpp | 9 +- .../Statistics/DefaultRenderStatCollector.h | 2 +- SynapseEngine/Engine/Utils/RenderBuffer.cpp | 9 ++ SynapseEngine/Engine/Utils/RenderBuffer.h | 1 + SynapseEngine/Engine/Vk/Buffer/Buffer.h | 2 + .../Engine/Vk/Buffer/BufferFactory.cpp | 8 + SynapseEngine/Engine/Vk/Core/Device.cpp | 14 ++ SynapseEngine/Engine/Vk/Core/Device.h | 1 + SynapseEngine/Engine/Vk/Core/Instance.cpp | 1 - SynapseEngine/Engine/imgui.ini | 140 ++++++++++++++++++ 33 files changed, 348 insertions(+), 117 deletions(-) create mode 100644 SynapseEngine/Engine/imgui.ini diff --git a/SynapseEngine/Engine/Image/ImageManager.cpp b/SynapseEngine/Engine/Image/ImageManager.cpp index f2ed6bfe..657e050a 100644 --- a/SynapseEngine/Engine/Image/ImageManager.cpp +++ b/SynapseEngine/Engine/Image/ImageManager.cpp @@ -10,14 +10,14 @@ #include "Engine/Image/Source/Procedural/DefaultImageSource.h" #include "Engine/Vk/Descriptor/DescriptorLayoutBuilder.h"; -namespace Syn { - +namespace Syn +{ ImageManager::ImageManager( uint32_t framesInFlight, std::shared_ptr builder, std::unique_ptr uploader, std::unique_ptr cpuExtractor) - : + : AddressResourceManager(framesInFlight, 1024, 256, 512), _framesInFlight(framesInFlight), _builder(builder), _uploader(std::move(uploader)), @@ -340,6 +340,16 @@ namespace Syn { ); } + uint32_t samplerIndex = GetSamplerIndex("LinearAniso"); + bool invertTangent = false; + + uint32_t textureData = (samplerIndex & 0x7FFFFFFF); + if (invertTangent) { + textureData |= (1u << 31); + } + + WriteAddress(descriptorIndex, textureData); + entry.resource->transientCpuData.reset(); entry.resource->transientGpuData.reset(); } diff --git a/SynapseEngine/Engine/Image/ImageManager.h b/SynapseEngine/Engine/Image/ImageManager.h index 23e076e4..e981d752 100644 --- a/SynapseEngine/Engine/Image/ImageManager.h +++ b/SynapseEngine/Engine/Image/ImageManager.h @@ -17,7 +17,7 @@ namespace Syn { using ImageSourceFactory = std::function()>; - class SYN_API ImageManager : public BaseResourceManager { + class SYN_API ImageManager : public AddressResourceManager { public: static constexpr uint32_t MAX_IMAGES = 2048; static constexpr uint32_t MAX_SAMPLERS = 32; diff --git a/SynapseEngine/Engine/Manager/ComponentBufferManager.cpp b/SynapseEngine/Engine/Manager/ComponentBufferManager.cpp index 10763ed2..65a3814e 100644 --- a/SynapseEngine/Engine/Manager/ComponentBufferManager.cpp +++ b/SynapseEngine/Engine/Manager/ComponentBufferManager.cpp @@ -9,6 +9,7 @@ namespace Syn void ComponentBufferManager::RegisterBuffer(const std::string& name, uint32_t elementSize, std::function sizeCallback, std::function readyCallback, ComponentMemoryType memoryType) { Vk::BufferConfig config; + config.debugName = name; config.usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; config.useDeviceAddress = true; diff --git a/SynapseEngine/Engine/Render/Passes/Setup/GlobalFrameSetupPass.cpp b/SynapseEngine/Engine/Render/Passes/Setup/GlobalFrameSetupPass.cpp index 98b0e2ec..99c6336d 100644 --- a/SynapseEngine/Engine/Render/Passes/Setup/GlobalFrameSetupPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Setup/GlobalFrameSetupPass.cpp @@ -37,9 +37,12 @@ namespace Syn { auto modelManager = ServiceLocator::GetModelManager(); auto materialManager = ServiceLocator::GetMaterialManager(); auto animationManager = ServiceLocator::GetAnimationManager(); + auto imageManager = ServiceLocator::GetImageManager(); FrameGlobalContext ctx = {}; + ctx.textureMetadataBufferAddr = imageManager->GetAddressBufferDeviceAddress(); + ctx.globalDrawCountBufferAddr = drawData->Models.drawCountBuffer.GetAddress(fIdx); ctx.globalInstanceIndexBufferAddr = drawData->Models.instanceBuffer.GetAddress(fIdx); ctx.globalIndirectCommandBufferAddr = drawData->Models.indirectBuffer.GetAddress(fIdx); diff --git a/SynapseEngine/Engine/Scene/DrawData/ChunkDrawGroup.cpp b/SynapseEngine/Engine/Scene/DrawData/ChunkDrawGroup.cpp index 3e8d6340..246ff5ac 100644 --- a/SynapseEngine/Engine/Scene/DrawData/ChunkDrawGroup.cpp +++ b/SynapseEngine/Engine/Scene/DrawData/ChunkDrawGroup.cpp @@ -22,34 +22,34 @@ namespace Syn VkBufferUsageFlags storageUsage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT; VkBufferUsageFlags indirectUsage = VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; - chunkDataBuffer.Initialize({ BufferStrategy::Hybrid, frameCount, sizeof(ChunkDataGPU), storageUsage }); + chunkDataBuffer.Initialize({ "ChunkDrawGroup_ChunkDataBuffer", BufferStrategy::Hybrid, frameCount, sizeof(ChunkDataGPU), storageUsage }); chunkDataBuffer.UpdateCapacityAll(1); - chunkVisibilityBuffer.Initialize({ BufferStrategy::Hybrid, frameCount, sizeof(uint32_t), storageUsage }); + chunkVisibilityBuffer.Initialize({ "ChunkDrawGroup_ChunkVisibilityBuffer", BufferStrategy::Hybrid, frameCount, sizeof(uint32_t), storageUsage }); chunkVisibilityBuffer.UpdateCapacityAll(1); - chunkAabbSingleCmdBuffer.Initialize({ BufferStrategy::MappedOnly, frameCount, sizeof(VkDrawIndirectCommand), indirectUsage }); + chunkAabbSingleCmdBuffer.Initialize({ "ChunkDrawGroup_ChunkAabbSingleCmdBuffer", BufferStrategy::MappedOnly, frameCount, sizeof(VkDrawIndirectCommand), indirectUsage }); chunkAabbSingleCmdBuffer.UpdateCapacityAll(1); - chunkIndirectDispatchBuffer.Initialize({ BufferStrategy::Hybrid, frameCount, sizeof(VkDispatchIndirectCommand), indirectUsage }); + chunkIndirectDispatchBuffer.Initialize({ "ChunkDrawGroup_ChunkIndirectDispatchBuffer", BufferStrategy::Hybrid, frameCount, sizeof(VkDispatchIndirectCommand), indirectUsage }); chunkIndirectDispatchBuffer.UpdateCapacityAll(1); - sceneAabbBuffer.Initialize({ BufferStrategy::GpuOnly, frameCount, sizeof(SceneAABB), storageUsage }); + sceneAabbBuffer.Initialize({ "ChunkDrawGroup_SceneAabbBuffer", BufferStrategy::GpuOnly, frameCount, sizeof(SceneAABB), storageUsage }); sceneAabbBuffer.UpdateCapacityAll(1); - mortonRadixSortTempBuffer.Initialize({ BufferStrategy::GpuOnly, frameCount, 1, storageUsage | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT, 10000, 20000 }); + mortonRadixSortTempBuffer.Initialize({ "ChunkDrawGroup_MortonRadixSortTempBuffer", BufferStrategy::GpuOnly, frameCount, 1, storageUsage | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT, 10000, 20000 }); mortonRadixSortTempBuffer.UpdateCapacityAll(1); - mortonIndirectDispatchBuffer.Initialize({ BufferStrategy::GpuOnly, frameCount, sizeof(VkDispatchIndirectCommand), indirectUsage }); + mortonIndirectDispatchBuffer.Initialize({ "ChunkDrawGroup_MortonIndirectDispatchBuffer", BufferStrategy::GpuOnly, frameCount, sizeof(VkDispatchIndirectCommand), indirectUsage }); mortonIndirectDispatchBuffer.UpdateCapacityAll(1); - mortonIndirectDrawBuffer.Initialize({ BufferStrategy::GpuOnly, frameCount, sizeof(VkDrawIndirectCommand), indirectUsage }); + mortonIndirectDrawBuffer.Initialize({ "ChunkDrawGroup_MortonIndirectDrawBuffer", BufferStrategy::GpuOnly, frameCount, sizeof(VkDrawIndirectCommand), indirectUsage }); mortonIndirectDrawBuffer.UpdateCapacityAll(1); - mortonChunkVisibleIndirectDispatchBuffer.Initialize({ BufferStrategy::GpuOnly, frameCount, sizeof(VkDispatchIndirectCommand), indirectUsage }); + mortonChunkVisibleIndirectDispatchBuffer.Initialize({ "ChunkDrawGroup_MortonChunkVisibleIndirectDispatchBuffer", BufferStrategy::GpuOnly, frameCount, sizeof(VkDispatchIndirectCommand), indirectUsage }); mortonChunkVisibleIndirectDispatchBuffer.UpdateCapacityAll(1); - mortonAabbSingleCmdBuffer.Initialize({ BufferStrategy::MappedOnly, frameCount, sizeof(VkDrawIndirectCommand), indirectUsage }); + mortonAabbSingleCmdBuffer.Initialize({ "ChunkDrawGroup_MortonAabbSingleCmdBuffer", BufferStrategy::MappedOnly, frameCount, sizeof(VkDrawIndirectCommand), indirectUsage }); mortonAabbSingleCmdBuffer.UpdateCapacityAll(1); for (uint32_t i = 0; i < frameCount; ++i) { diff --git a/SynapseEngine/Engine/Scene/DrawData/DebugDrawGroup.cpp b/SynapseEngine/Engine/Scene/DrawData/DebugDrawGroup.cpp index 4261da9e..31980195 100644 --- a/SynapseEngine/Engine/Scene/DrawData/DebugDrawGroup.cpp +++ b/SynapseEngine/Engine/Scene/DrawData/DebugDrawGroup.cpp @@ -43,19 +43,19 @@ namespace Syn VkBufferUsageFlags indirectUsage = VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT; VkBufferUsageFlags storageUsage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; - modelAabbIndirectBuffer.Initialize({ BufferStrategy::MappedOnly, frameCount, sizeof(VkDrawIndirectCommand) * 8, indirectUsage, 1024, 2048 }); + modelAabbIndirectBuffer.Initialize({ "DebugDrawGroup_ModelAabbIndirectBuffer", BufferStrategy::MappedOnly, frameCount, sizeof(VkDrawIndirectCommand) * 8, indirectUsage, 1024, 2048 }); modelAabbIndirectBuffer.UpdateCapacityAll(1); - modelSphereIndirectBuffer.Initialize({ BufferStrategy::MappedOnly, frameCount, sizeof(VkDrawIndirectCommand) * 8, indirectUsage, 1024, 2048 }); + modelSphereIndirectBuffer.Initialize({ "DebugDrawGroup_ModelSphereIndirectBuffer", BufferStrategy::MappedOnly, frameCount, sizeof(VkDrawIndirectCommand) * 8, indirectUsage, 1024, 2048 }); modelSphereIndirectBuffer.UpdateCapacityAll(1); - boxColliderIndirectBuffer.Initialize({ BufferStrategy::MappedOnly, frameCount, sizeof(VkDrawIndirectCommand), indirectUsage }); + boxColliderIndirectBuffer.Initialize({ "DebugDrawGroup_BoxColliderIndirectBuffer", BufferStrategy::MappedOnly, frameCount, sizeof(VkDrawIndirectCommand), indirectUsage }); boxColliderIndirectBuffer.UpdateCapacityAll(1); - sphereColliderIndirectBuffer.Initialize({ BufferStrategy::MappedOnly, frameCount, sizeof(VkDrawIndirectCommand), indirectUsage }); + sphereColliderIndirectBuffer.Initialize({ "DebugDrawGroup_SphereColliderIndirectBuffer", BufferStrategy::MappedOnly, frameCount, sizeof(VkDrawIndirectCommand), indirectUsage }); sphereColliderIndirectBuffer.UpdateCapacityAll(1); - capsuleColliderIndirectBuffer.Initialize({ BufferStrategy::MappedOnly, frameCount, sizeof(VkDrawIndirectCommand), indirectUsage }); + capsuleColliderIndirectBuffer.Initialize({ "DebugDrawGroup_CapsuleColliderIndirectBuffer", BufferStrategy::MappedOnly, frameCount, sizeof(VkDrawIndirectCommand), indirectUsage }); capsuleColliderIndirectBuffer.UpdateCapacityAll(1); for (uint32_t i = 0; i < frameCount; ++i) { diff --git a/SynapseEngine/Engine/Scene/DrawData/DirectionLightDrawGroup.cpp b/SynapseEngine/Engine/Scene/DrawData/DirectionLightDrawGroup.cpp index ae0783da..23c28dc5 100644 --- a/SynapseEngine/Engine/Scene/DrawData/DirectionLightDrawGroup.cpp +++ b/SynapseEngine/Engine/Scene/DrawData/DirectionLightDrawGroup.cpp @@ -17,10 +17,10 @@ namespace Syn VkBufferUsageFlags indirectUsage = VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT; - indirectBuffer.Initialize({ BufferStrategy::MappedOnly, frameCount, sizeof(VkDrawIndirectCommand), indirectUsage }); + indirectBuffer.Initialize({ "DirectionLightDrawGroup_IndirectBuffer", BufferStrategy::MappedOnly, frameCount, sizeof(VkDrawIndirectCommand), indirectUsage }); indirectBuffer.UpdateCapacityAll(1); - billboardSingleCmdBuffer.Initialize({ BufferStrategy::MappedOnly, frameCount, sizeof(VkDrawIndirectCommand), indirectUsage }); + billboardSingleCmdBuffer.Initialize({ "DirectionLightDrawGroup_BillboardSingleCmdBuffer", BufferStrategy::MappedOnly, frameCount, sizeof(VkDrawIndirectCommand), indirectUsage }); billboardSingleCmdBuffer.UpdateCapacityAll(1); for (uint32_t i = 0; i < frameCount; ++i) { diff --git a/SynapseEngine/Engine/Scene/DrawData/DirectionLightShadowDrawGroup.cpp b/SynapseEngine/Engine/Scene/DrawData/DirectionLightShadowDrawGroup.cpp index 92d15d94..e0e3ac3c 100644 --- a/SynapseEngine/Engine/Scene/DrawData/DirectionLightShadowDrawGroup.cpp +++ b/SynapseEngine/Engine/Scene/DrawData/DirectionLightShadowDrawGroup.cpp @@ -18,19 +18,19 @@ namespace Syn VkBufferUsageFlags storageUsage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT; VkBufferUsageFlags indirectStorageUsage = VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; - instanceBuffer.Initialize({ BufferStrategy::Hybrid, frameCount, sizeof(uint32_t), storageUsage, 16384 * SHADOW_MULTIPLIER, 32768 * SHADOW_MULTIPLIER }); + instanceBuffer.Initialize({ "DirectionLightShadowDrawGroup_InstanceBuffer", BufferStrategy::Hybrid, frameCount, sizeof(uint32_t), storageUsage, 16384 * SHADOW_MULTIPLIER, 32768 * SHADOW_MULTIPLIER}); instanceBuffer.UpdateCapacityAll(1); - indirectBuffer.Initialize({ BufferStrategy::Hybrid, frameCount, sizeof(VkDrawIndirectCommand) * 8, indirectStorageUsage, 1024, 2048 }); + indirectBuffer.Initialize({ "DirectionLightShadowDrawGroup_IndirectBuffer", BufferStrategy::Hybrid, frameCount, sizeof(VkDrawIndirectCommand) * 8, indirectStorageUsage, 1024, 2048 }); indirectBuffer.UpdateCapacityAll(1); - modelDispatchBuffer.Initialize({ BufferStrategy::GpuOnly, frameCount, sizeof(VkDispatchIndirectCommand), indirectStorageUsage, 1, 1 }); + modelDispatchBuffer.Initialize({ "DirectionLightShadowDrawGroup_ModelDispatchBuffer", BufferStrategy::GpuOnly, frameCount, sizeof(VkDispatchIndirectCommand), indirectStorageUsage, 1, 1 }); modelDispatchBuffer.UpdateCapacityAll(1); - staticChunkDispatchBuffer.Initialize({ BufferStrategy::GpuOnly, frameCount, sizeof(VkDispatchIndirectCommand), indirectStorageUsage, 1, 1 }); + staticChunkDispatchBuffer.Initialize({ "DirectionLightShadowDrawGroup_StaticChunkDispatchBuffer", BufferStrategy::GpuOnly, frameCount, sizeof(VkDispatchIndirectCommand), indirectStorageUsage, 1, 1 }); staticChunkDispatchBuffer.UpdateCapacityAll(1); - mortonChunkDispatchBuffer.Initialize({ BufferStrategy::GpuOnly, frameCount, sizeof(VkDispatchIndirectCommand), indirectStorageUsage, 1, 1 }); + mortonChunkDispatchBuffer.Initialize({ "DirectionLightShadowDrawGroup_MortonChunkDispatchBuffer", BufferStrategy::GpuOnly, frameCount, sizeof(VkDispatchIndirectCommand), indirectStorageUsage, 1, 1 }); mortonChunkDispatchBuffer.UpdateCapacityAll(1); Vk::ImageConfig atlasSpec{}; diff --git a/SynapseEngine/Engine/Scene/DrawData/ForwardPlusDrawGroup.cpp b/SynapseEngine/Engine/Scene/DrawData/ForwardPlusDrawGroup.cpp index bf9bbbc0..5575a47b 100644 --- a/SynapseEngine/Engine/Scene/DrawData/ForwardPlusDrawGroup.cpp +++ b/SynapseEngine/Engine/Scene/DrawData/ForwardPlusDrawGroup.cpp @@ -7,22 +7,22 @@ namespace Syn VkBufferUsageFlags storageUsage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT; VkBufferUsageFlags indirectUsage = VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT; - tileGridBuffer.Initialize({ BufferStrategy::GpuOnly, frameCount, 16, storageUsage, 3000, 6000 }); + tileGridBuffer.Initialize({ "ForwardPlusDrawGroup_TileGridBuffer", BufferStrategy::GpuOnly, frameCount, 16, storageUsage, 3000, 6000 }); tileGridBuffer.UpdateCapacityAll(1); - clusterListBuffer.Initialize({ BufferStrategy::GpuOnly, frameCount, 32, storageUsage, 48000, 96000 }); + clusterListBuffer.Initialize({ "ForwardPlusDrawGroup_ClusterListBuffer", BufferStrategy::GpuOnly, frameCount, 32, storageUsage, 48000, 96000 }); clusterListBuffer.UpdateCapacityAll(1); - clusterCountBuffer.Initialize({ BufferStrategy::GpuOnly, frameCount, sizeof(VkDispatchIndirectCommand), indirectUsage, 1, 1}); + clusterCountBuffer.Initialize({ "ForwardPlusDrawGroup_ClusterCountBuffer", BufferStrategy::GpuOnly, frameCount, sizeof(VkDispatchIndirectCommand), indirectUsage, 1, 1}); clusterCountBuffer.UpdateCapacityAll(1); - dispatchArgsBuffer.Initialize({ BufferStrategy::GpuOnly, frameCount, sizeof(ForwardPlusDispatchArgs), indirectUsage, 1, 1 }); + dispatchArgsBuffer.Initialize({ "ForwardPlusDrawGroup_DispatchArgsBuffer", BufferStrategy::GpuOnly, frameCount, sizeof(ForwardPlusDispatchArgs), indirectUsage, 1, 1 }); dispatchArgsBuffer.UpdateCapacityAll(1); - pointLightIndexBuffer.Initialize({ BufferStrategy::GpuOnly, frameCount, sizeof(uint32_t), storageUsage, 10000, 20000 }); + pointLightIndexBuffer.Initialize({ "ForwardPlusDrawGroup_PointLightIndexBuffer", BufferStrategy::GpuOnly, frameCount, sizeof(uint32_t), storageUsage, 10000, 20000 }); pointLightIndexBuffer.UpdateCapacityAll(1); - spotLightIndexBuffer.Initialize({ BufferStrategy::GpuOnly, frameCount, sizeof(uint32_t), storageUsage, 10000, 20000 }); + spotLightIndexBuffer.Initialize({ "ForwardPlusDrawGroup_SpotLightIndexBuffer", BufferStrategy::GpuOnly, frameCount, sizeof(uint32_t), storageUsage, 10000, 20000 }); spotLightIndexBuffer.UpdateCapacityAll(1); } diff --git a/SynapseEngine/Engine/Scene/DrawData/ModelDrawGroup.cpp b/SynapseEngine/Engine/Scene/DrawData/ModelDrawGroup.cpp index d0be99da..359e0b76 100644 --- a/SynapseEngine/Engine/Scene/DrawData/ModelDrawGroup.cpp +++ b/SynapseEngine/Engine/Scene/DrawData/ModelDrawGroup.cpp @@ -22,29 +22,29 @@ namespace Syn VkBufferUsageFlags storageUsage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT; VkBufferUsageFlags indirectStorageUsage = VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; - instanceBuffer.Initialize({ BufferStrategy::Hybrid, frameCount, sizeof(uint32_t), storageUsage, 16384, 32768 }); + instanceBuffer.Initialize({ "ModelDrawGroup_InstanceBuffer", BufferStrategy::Hybrid, frameCount, sizeof(uint32_t), storageUsage, 16384, 32768}); instanceBuffer.UpdateCapacityAll(1); //VkDrawIndirectCommand + VkDrawMeshTasksIndirectCommandEXT - indirectBuffer.Initialize({ BufferStrategy::Hybrid, frameCount, sizeof(VkDrawIndirectCommand) * 8, indirectStorageUsage, 1024, 2048 }); + indirectBuffer.Initialize({ "ModelDrawGroup_IndirectBuffer", BufferStrategy::Hybrid, frameCount, sizeof(VkDrawIndirectCommand) * 8, indirectStorageUsage, 1024, 2048 }); indirectBuffer.UpdateCapacityAll(1); - descriptorBuffer.Initialize({ BufferStrategy::Hybrid, frameCount, sizeof(MeshDrawDescriptor), storageUsage, 1024, 2048 }); + descriptorBuffer.Initialize({ "ModelDrawGroup_DescriptorBuffer", BufferStrategy::Hybrid, frameCount, sizeof(MeshDrawDescriptor), storageUsage, 1024, 2048 }); descriptorBuffer.UpdateCapacityAll(1); - modelAllocBuffer.Initialize({ BufferStrategy::Hybrid, frameCount, sizeof(ModelAllocationInfo), storageUsage, 1024, 2048 }); + modelAllocBuffer.Initialize({ "ModelDrawGroup_ModelAllocBuffer", BufferStrategy::Hybrid, frameCount, sizeof(ModelAllocationInfo), storageUsage, 1024, 2048 }); modelAllocBuffer.UpdateCapacityAll(1); - meshAllocBuffer.Initialize({ BufferStrategy::Hybrid, frameCount, sizeof(MeshAllocationInfo), storageUsage, 1024, 2048 }); + meshAllocBuffer.Initialize({ "ModelDrawGroup_MeshAllocBuffer", BufferStrategy::Hybrid, frameCount, sizeof(MeshAllocationInfo), storageUsage, 1024, 2048 }); meshAllocBuffer.UpdateCapacityAll(1); - materialIndexBuffer.Initialize({ BufferStrategy::Hybrid, frameCount, sizeof(int32_t), storageUsage, 4096, 8192 }); + materialIndexBuffer.Initialize({ "ModelDrawGroup_MaterialIndexBuffer", BufferStrategy::Hybrid, frameCount, sizeof(int32_t), storageUsage, 4096, 8192 }); materialIndexBuffer.UpdateCapacityAll(1); - drawCountBuffer.Initialize({ BufferStrategy::MappedOnly, frameCount, sizeof(uint32_t), indirectStorageUsage, 1, 1 }); + drawCountBuffer.Initialize({ "ModelDrawGroup_DrawCountBuffer", BufferStrategy::MappedOnly, frameCount, sizeof(uint32_t), indirectStorageUsage, 1, 1 }); drawCountBuffer.UpdateCapacityAll(MaterialRenderType::Count * 2); - computeCountBuffer.Initialize({ BufferStrategy::GpuOnly, frameCount, sizeof(VkDispatchIndirectCommand), indirectStorageUsage, 1, 1 }); + computeCountBuffer.Initialize({ "ModelDrawGroup_ComputeCountBuffer", BufferStrategy::GpuOnly, frameCount, sizeof(VkDispatchIndirectCommand), indirectStorageUsage, 1, 1 }); computeCountBuffer.UpdateCapacityAll(1); } diff --git a/SynapseEngine/Engine/Scene/DrawData/PointLightDrawGroup.cpp b/SynapseEngine/Engine/Scene/DrawData/PointLightDrawGroup.cpp index 09cbf877..13d59850 100644 --- a/SynapseEngine/Engine/Scene/DrawData/PointLightDrawGroup.cpp +++ b/SynapseEngine/Engine/Scene/DrawData/PointLightDrawGroup.cpp @@ -33,16 +33,16 @@ namespace Syn VkBufferUsageFlags usage = VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT; - indirectBuffer.Initialize({ BufferStrategy::MappedOnly, frameCount, sizeof(VkDrawIndirectCommand), usage }); + indirectBuffer.Initialize({ "PointLightDrawGroup_IndirectBuffer", BufferStrategy::MappedOnly, frameCount, sizeof(VkDrawIndirectCommand), usage }); indirectBuffer.UpdateCapacityAll(1); - sphereSingleCmdBuffer.Initialize({ BufferStrategy::MappedOnly, frameCount, sizeof(VkDrawIndirectCommand), usage }); + sphereSingleCmdBuffer.Initialize({ "PointLightDrawGroup_SphereSingleCmdBuffer", BufferStrategy::MappedOnly, frameCount, sizeof(VkDrawIndirectCommand), usage }); sphereSingleCmdBuffer.UpdateCapacityAll(1); - aabbSingleCmdBuffer.Initialize({ BufferStrategy::MappedOnly, frameCount, sizeof(VkDrawIndirectCommand), usage }); + aabbSingleCmdBuffer.Initialize({ "PointLightDrawGroup_AabbSingleCmdBuffer", BufferStrategy::MappedOnly, frameCount, sizeof(VkDrawIndirectCommand), usage }); aabbSingleCmdBuffer.UpdateCapacityAll(1); - billboardSingleCmdBuffer.Initialize({ BufferStrategy::MappedOnly, frameCount, sizeof(VkDrawIndirectCommand), usage }); + billboardSingleCmdBuffer.Initialize({ "PointLightDrawGroup_BillboardSingleCmdBuffer", BufferStrategy::MappedOnly, frameCount, sizeof(VkDrawIndirectCommand), usage }); billboardSingleCmdBuffer.UpdateCapacityAll(1); for (uint32_t i = 0; i < frameCount; ++i) { diff --git a/SynapseEngine/Engine/Scene/DrawData/PointLightShadowDrawGroup.cpp b/SynapseEngine/Engine/Scene/DrawData/PointLightShadowDrawGroup.cpp index 274d9d77..e374b2e1 100644 --- a/SynapseEngine/Engine/Scene/DrawData/PointLightShadowDrawGroup.cpp +++ b/SynapseEngine/Engine/Scene/DrawData/PointLightShadowDrawGroup.cpp @@ -13,55 +13,55 @@ namespace Syn VkBufferUsageFlags storageUsage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; VkBufferUsageFlags indirectStorageUsage = VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; - instanceBuffer.Initialize({ BufferStrategy::Hybrid, frameCount, sizeof(PointShadowInstancePayload), storageUsage, 65536, 131072 }); + instanceBuffer.Initialize({ "PointLightShadowDrawGroup_InstanceBuffer", BufferStrategy::Hybrid, frameCount, sizeof(PointShadowInstancePayload), storageUsage, 65536, 131072 }); instanceBuffer.UpdateCapacityAll(1); - unsortedInstanceBuffer.Initialize({ BufferStrategy::GpuOnly, frameCount, sizeof(PointShadowInstancePayload), storageUsage, 65536, 131072 }); + unsortedInstanceBuffer.Initialize({ "PointLightShadowDrawGroup_UnsortedInstanceBuffer", BufferStrategy::GpuOnly, frameCount, sizeof(PointShadowInstancePayload), storageUsage, 65536, 131072 }); unsortedInstanceBuffer.UpdateCapacityAll(1); - sortValuesBuffer.Initialize({ BufferStrategy::GpuOnly, frameCount, sizeof(uint32_t), storageUsage, 65536, 131072 }); + sortValuesBuffer.Initialize({ "PointLightShadowDrawGroup_SortValuesBuffer", BufferStrategy::GpuOnly, frameCount, sizeof(uint32_t), storageUsage, 65536, 131072 }); sortValuesBuffer.UpdateCapacityAll(1); - drawCallKeyBuffer.Initialize({ BufferStrategy::GpuOnly, frameCount, sizeof(uint32_t), storageUsage, 65536, 131072 }); + drawCallKeyBuffer.Initialize({ "PointLightShadowDrawGroup_DrawCallKeyBuffer", BufferStrategy::GpuOnly, frameCount, sizeof(uint32_t), storageUsage, 65536, 131072 }); drawCallKeyBuffer.UpdateCapacityAll(1); - indirectBuffer.Initialize({ BufferStrategy::Hybrid, frameCount, sizeof(VkDrawIndirectCommand) * 8, indirectStorageUsage, 1024, 2048 }); + indirectBuffer.Initialize({ "PointLightShadowDrawGroup_IndirectBuffer", BufferStrategy::Hybrid, frameCount, sizeof(VkDrawIndirectCommand) * 8, indirectStorageUsage, 1024, 2048 }); indirectBuffer.UpdateCapacityAll(1); - visibleCountDispatchBuffer.Initialize({ BufferStrategy::Hybrid, frameCount, sizeof(uint32_t), storageUsage, 1, 1 }); + visibleCountDispatchBuffer.Initialize({ "PointLightShadowDrawGroup_VisibleCountDispatchBuffer", BufferStrategy::Hybrid, frameCount, sizeof(uint32_t), storageUsage, 1, 1 }); visibleCountDispatchBuffer.UpdateCapacityAll(1); - visibleMeshCountDispatchBuffer.Initialize({ BufferStrategy::GpuOnly, frameCount, sizeof(uint32_t), storageUsage, 1, 1 }); + visibleMeshCountDispatchBuffer.Initialize({ "PointLightShadowDrawGroup_VisibleMeshCountDispatchBuffer", BufferStrategy::GpuOnly, frameCount, sizeof(uint32_t), storageUsage, 1, 1 }); visibleMeshCountDispatchBuffer.UpdateCapacityAll(1); - descriptorBuffer.Initialize({ BufferStrategy::Hybrid, frameCount, sizeof(MeshDrawDescriptor) * 8, storageUsage, 1024, 2048 }); + descriptorBuffer.Initialize({ "PointLightShadowDrawGroup_DescriptorBuffer", BufferStrategy::Hybrid, frameCount, sizeof(MeshDrawDescriptor) * 8, storageUsage, 1024, 2048 }); descriptorBuffer.UpdateCapacityAll(1); - modelCullingIndirectDispatchBuffer.Initialize({ BufferStrategy::GpuOnly, frameCount, sizeof(VkDispatchIndirectCommand), indirectStorageUsage, 1, 1 }); + modelCullingIndirectDispatchBuffer.Initialize({ "PointLightShadowDrawGroup_ModelCullingIndirectDispatchBuffer", BufferStrategy::GpuOnly, frameCount, sizeof(VkDispatchIndirectCommand), indirectStorageUsage, 1, 1 }); modelCullingIndirectDispatchBuffer.UpdateCapacityAll(1); - modelDispatchBuffer.Initialize({ BufferStrategy::GpuOnly, frameCount, sizeof(VkDispatchIndirectCommand), indirectStorageUsage, 1, 1 }); + modelDispatchBuffer.Initialize({ "PointLightShadowDrawGroup_ModelDispatchBuffer", BufferStrategy::GpuOnly, frameCount, sizeof(VkDispatchIndirectCommand), indirectStorageUsage, 1, 1 }); modelDispatchBuffer.UpdateCapacityAll(1); - finalizeDispatchBuffer.Initialize({ BufferStrategy::GpuOnly, frameCount, sizeof(VkDispatchIndirectCommand), indirectStorageUsage, 1, 1 }); + finalizeDispatchBuffer.Initialize({ "PointLightShadowDrawGroup_FinalizeDispatchBuffer", BufferStrategy::GpuOnly, frameCount, sizeof(VkDispatchIndirectCommand), indirectStorageUsage, 1, 1 }); finalizeDispatchBuffer.UpdateCapacityAll(1); - staticChunkDispatchBuffer.Initialize({ BufferStrategy::GpuOnly, frameCount, sizeof(VkDispatchIndirectCommand), indirectStorageUsage, 1, 1 }); + staticChunkDispatchBuffer.Initialize({ "PointLightShadowDrawGroup_StaticChunkDispatchBuffer", BufferStrategy::GpuOnly, frameCount, sizeof(VkDispatchIndirectCommand), indirectStorageUsage, 1, 1 }); staticChunkDispatchBuffer.UpdateCapacityAll(1); - mortonChunkDispatchBuffer.Initialize({ BufferStrategy::GpuOnly, frameCount, sizeof(VkDispatchIndirectCommand), indirectStorageUsage, 1, 1 }); + mortonChunkDispatchBuffer.Initialize({ "PointLightShadowDrawGroup_MortonChunkDispatchBuffer", BufferStrategy::GpuOnly, frameCount, sizeof(VkDispatchIndirectCommand), indirectStorageUsage, 1, 1 }); mortonChunkDispatchBuffer.UpdateCapacityAll(1); - atlasRadixSortTempBuffer.Initialize({ BufferStrategy::GpuOnly, frameCount, 1, storageUsage | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT, 10000, 20000 }); + atlasRadixSortTempBuffer.Initialize({ "PointLightShadowDrawGroup_AtlasRadixSortTempBuffer", BufferStrategy::GpuOnly, frameCount, 1, storageUsage | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT, 10000, 20000 }); atlasRadixSortTempBuffer.UpdateCapacityAll(1); - radixSortTempBuffer.Initialize({ BufferStrategy::GpuOnly, frameCount, 1, storageUsage | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT, 10000, 20000 }); + radixSortTempBuffer.Initialize({ "PointLightShadowDrawGroup_RadixSortTempBuffer", BufferStrategy::GpuOnly, frameCount, 1, storageUsage | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT, 10000, 20000 }); radixSortTempBuffer.UpdateCapacityAll(1); gridLookupData.Resize(POINT_SHADOW_GRID_SIZE * POINT_SHADOW_GRID_SIZE); std::fill(gridLookupData.Data(), gridLookupData.Data() + (POINT_SHADOW_GRID_SIZE * POINT_SHADOW_GRID_SIZE), 0xFFFFFFFF); - gridLookupBuffer.Initialize({ BufferStrategy::Hybrid, frameCount, sizeof(uint32_t) * POINT_SHADOW_GRID_SIZE * POINT_SHADOW_GRID_SIZE, storageUsage, 1, 1 }); + gridLookupBuffer.Initialize({ "PointLightShadowDrawGroup_GridLookupBuffer", BufferStrategy::Hybrid, frameCount, sizeof(uint32_t) * POINT_SHADOW_GRID_SIZE * POINT_SHADOW_GRID_SIZE, storageUsage, 1, 1 }); gridLookupBuffer.UpdateCapacityAll(1); Vk::ImageConfig atlasSpec{}; diff --git a/SynapseEngine/Engine/Scene/DrawData/SceneDrawData.cpp b/SynapseEngine/Engine/Scene/DrawData/SceneDrawData.cpp index 14ea7a90..4bf318bb 100644 --- a/SynapseEngine/Engine/Scene/DrawData/SceneDrawData.cpp +++ b/SynapseEngine/Engine/Scene/DrawData/SceneDrawData.cpp @@ -18,7 +18,7 @@ namespace Syn PointLightShadow(frameCount) { VkBufferUsageFlags contextUsage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; - frameContextBuffer.Initialize({ BufferStrategy::Hybrid, frameCount, sizeof(FrameGlobalContext), contextUsage, 1, 1}); + frameContextBuffer.Initialize({ "SceneDrawData_FrameContextBuffer", BufferStrategy::Hybrid, frameCount, sizeof(FrameGlobalContext), contextUsage, 1, 1}); frameContextBuffer.UpdateCapacityAll(1); } diff --git a/SynapseEngine/Engine/Scene/DrawData/SpotLightDrawGroup.cpp b/SynapseEngine/Engine/Scene/DrawData/SpotLightDrawGroup.cpp index 5ff270b7..0cc1c4d3 100644 --- a/SynapseEngine/Engine/Scene/DrawData/SpotLightDrawGroup.cpp +++ b/SynapseEngine/Engine/Scene/DrawData/SpotLightDrawGroup.cpp @@ -47,22 +47,22 @@ namespace Syn VkBufferUsageFlags indirectUsage = VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT; - indirectBuffer.Initialize({ BufferStrategy::MappedOnly, frameCount, sizeof(VkDrawIndirectCommand), indirectUsage }); + indirectBuffer.Initialize({ "SpotLightDrawGroup_IndirectBuffer", BufferStrategy::MappedOnly, frameCount, sizeof(VkDrawIndirectCommand), indirectUsage }); indirectBuffer.UpdateCapacityAll(1); - sphereSingleCmdBuffer.Initialize({ BufferStrategy::MappedOnly, frameCount, sizeof(VkDrawIndirectCommand), indirectUsage }); + sphereSingleCmdBuffer.Initialize({ "SpotLightDrawGroup_SphereSingleCmdBuffer", BufferStrategy::MappedOnly, frameCount, sizeof(VkDrawIndirectCommand), indirectUsage }); sphereSingleCmdBuffer.UpdateCapacityAll(1); - coneSingleCmdBuffer.Initialize({ BufferStrategy::MappedOnly, frameCount, sizeof(VkDrawIndirectCommand), indirectUsage }); + coneSingleCmdBuffer.Initialize({ "SpotLightDrawGroup_ConeSingleCmdBuffer", BufferStrategy::MappedOnly, frameCount, sizeof(VkDrawIndirectCommand), indirectUsage }); coneSingleCmdBuffer.UpdateCapacityAll(1); - pyramidSingleCmdBuffer.Initialize({ BufferStrategy::MappedOnly, frameCount, sizeof(VkDrawIndirectCommand), indirectUsage }); + pyramidSingleCmdBuffer.Initialize({ "SpotLightDrawGroup_PyramidSingleCmdBuffer", BufferStrategy::MappedOnly, frameCount, sizeof(VkDrawIndirectCommand), indirectUsage }); pyramidSingleCmdBuffer.UpdateCapacityAll(1); - aabbSingleCmdBuffer.Initialize({ BufferStrategy::MappedOnly, frameCount, sizeof(VkDrawIndirectCommand), indirectUsage }); + aabbSingleCmdBuffer.Initialize({ "SpotLightDrawGroup_AABBSingleCmdBuffer", BufferStrategy::MappedOnly, frameCount, sizeof(VkDrawIndirectCommand), indirectUsage }); aabbSingleCmdBuffer.UpdateCapacityAll(1); - billboardSingleCmdBuffer.Initialize({ BufferStrategy::MappedOnly, frameCount, sizeof(VkDrawIndirectCommand), indirectUsage }); + billboardSingleCmdBuffer.Initialize({ "SpotLightDrawGroup_BillboardSingleCmdBuffer", BufferStrategy::MappedOnly, frameCount, sizeof(VkDrawIndirectCommand), indirectUsage }); billboardSingleCmdBuffer.UpdateCapacityAll(1); for (uint32_t i = 0; i < frameCount; ++i) { diff --git a/SynapseEngine/Engine/Scene/DrawData/SpotLightShadowDrawGroup.cpp b/SynapseEngine/Engine/Scene/DrawData/SpotLightShadowDrawGroup.cpp index 7b5f0f0e..bbf6236a 100644 --- a/SynapseEngine/Engine/Scene/DrawData/SpotLightShadowDrawGroup.cpp +++ b/SynapseEngine/Engine/Scene/DrawData/SpotLightShadowDrawGroup.cpp @@ -13,55 +13,55 @@ namespace Syn VkBufferUsageFlags storageUsage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; VkBufferUsageFlags indirectStorageUsage = VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; - instanceBuffer.Initialize({ BufferStrategy::Hybrid, frameCount, sizeof(SpotShadowInstancePayload), storageUsage, 65536, 131072 }); + instanceBuffer.Initialize({ "SpotLightShadowDrawGroup_InstanceBuffer", BufferStrategy::Hybrid, frameCount, sizeof(SpotShadowInstancePayload), storageUsage, 65536, 131072 }); instanceBuffer.UpdateCapacityAll(1); - unsortedInstanceBuffer.Initialize({ BufferStrategy::GpuOnly, frameCount, sizeof(SpotShadowInstancePayload), storageUsage, 65536, 131072 }); + unsortedInstanceBuffer.Initialize({ "SpotLightShadowDrawGroup_UnsortedInstanceBuffer", BufferStrategy::GpuOnly, frameCount, sizeof(SpotShadowInstancePayload), storageUsage, 65536, 131072 }); unsortedInstanceBuffer.UpdateCapacityAll(1); - sortValuesBuffer.Initialize({ BufferStrategy::GpuOnly, frameCount, sizeof(uint32_t), storageUsage, 65536, 131072 }); + sortValuesBuffer.Initialize({ "SpotLightShadowDrawGroup_SortValuesBuffer", BufferStrategy::GpuOnly, frameCount, sizeof(uint32_t), storageUsage, 65536, 131072 }); sortValuesBuffer.UpdateCapacityAll(1); - drawCallKeyBuffer.Initialize({ BufferStrategy::GpuOnly, frameCount, sizeof(uint32_t), storageUsage, 65536, 131072 }); + drawCallKeyBuffer.Initialize({ "SpotLightShadowDrawGroup_DrawCallKeyBuffer", BufferStrategy::GpuOnly, frameCount, sizeof(uint32_t), storageUsage, 65536, 131072 }); drawCallKeyBuffer.UpdateCapacityAll(1); - indirectBuffer.Initialize({ BufferStrategy::Hybrid, frameCount, sizeof(VkDrawIndirectCommand) * 8, indirectStorageUsage, 1024, 2048 }); + indirectBuffer.Initialize({ "SpotLightShadowDrawGroup_IndirectBuffer", BufferStrategy::Hybrid, frameCount, sizeof(VkDrawIndirectCommand) * 8, indirectStorageUsage, 1024, 2048 }); indirectBuffer.UpdateCapacityAll(1); - visibleCountDispatchBuffer.Initialize({ BufferStrategy::Hybrid, frameCount, sizeof(uint32_t), storageUsage, 1, 1 }); + visibleCountDispatchBuffer.Initialize({ "SpotLightShadowDrawGroup_VisibleCountDispatchBuffer", BufferStrategy::Hybrid, frameCount, sizeof(uint32_t), storageUsage, 1, 1 }); visibleCountDispatchBuffer.UpdateCapacityAll(1); - visibleMeshCountDispatchBuffer.Initialize({ BufferStrategy::GpuOnly, frameCount, sizeof(uint32_t), storageUsage, 1, 1 }); + visibleMeshCountDispatchBuffer.Initialize({ "SpotLightShadowDrawGroup_VisibleMeshCountDispatchBuffer", BufferStrategy::GpuOnly, frameCount, sizeof(uint32_t), storageUsage, 1, 1 }); visibleMeshCountDispatchBuffer.UpdateCapacityAll(1); - descriptorBuffer.Initialize({ BufferStrategy::Hybrid, frameCount, sizeof(MeshDrawDescriptor) * 8, storageUsage, 1024, 2048 }); + descriptorBuffer.Initialize({ "SpotLightShadowDrawGroup-DescriptorBuffer", BufferStrategy::Hybrid, frameCount, sizeof(MeshDrawDescriptor) * 8, storageUsage, 1024, 2048 }); descriptorBuffer.UpdateCapacityAll(1); - modelCullingIndirectDispatchBuffer.Initialize({ BufferStrategy::GpuOnly, frameCount, sizeof(VkDispatchIndirectCommand), indirectStorageUsage, 1, 1 }); + modelCullingIndirectDispatchBuffer.Initialize({ "SpotLightShadowDrawGroup-ModelCullingIndirectDispatchBuffer", BufferStrategy::GpuOnly, frameCount, sizeof(VkDispatchIndirectCommand), indirectStorageUsage, 1, 1 }); modelCullingIndirectDispatchBuffer.UpdateCapacityAll(1); - modelDispatchBuffer.Initialize({ BufferStrategy::GpuOnly, frameCount, sizeof(VkDispatchIndirectCommand), indirectStorageUsage, 1, 1 }); + modelDispatchBuffer.Initialize({ "SpotLightShadowDrawGroup-ModelDispatchBuffer", BufferStrategy::GpuOnly, frameCount, sizeof(VkDispatchIndirectCommand), indirectStorageUsage, 1, 1 }); modelDispatchBuffer.UpdateCapacityAll(1); - finalizeDispatchBuffer.Initialize({ BufferStrategy::GpuOnly, frameCount, sizeof(VkDispatchIndirectCommand), indirectStorageUsage, 1, 1 }); + finalizeDispatchBuffer.Initialize({ "SpotLightShadowDrawGroup-FinalizeDispatchBuffer", BufferStrategy::GpuOnly, frameCount, sizeof(VkDispatchIndirectCommand), indirectStorageUsage, 1, 1 }); finalizeDispatchBuffer.UpdateCapacityAll(1); - staticChunkDispatchBuffer.Initialize({ BufferStrategy::GpuOnly, frameCount, sizeof(VkDispatchIndirectCommand), indirectStorageUsage, 1, 1 }); + staticChunkDispatchBuffer.Initialize({ "SpotLightShadowDrawGroup-StaticChunkDispatchBuffer", BufferStrategy::GpuOnly, frameCount, sizeof(VkDispatchIndirectCommand), indirectStorageUsage, 1, 1 }); staticChunkDispatchBuffer.UpdateCapacityAll(1); - mortonChunkDispatchBuffer.Initialize({ BufferStrategy::GpuOnly, frameCount, sizeof(VkDispatchIndirectCommand), indirectStorageUsage, 1, 1 }); + mortonChunkDispatchBuffer.Initialize({ "SpotLightShadowDrawGroup-MortonChunkDispatchBuffer", BufferStrategy::GpuOnly, frameCount, sizeof(VkDispatchIndirectCommand), indirectStorageUsage, 1, 1 }); mortonChunkDispatchBuffer.UpdateCapacityAll(1); - atlasRadixSortTempBuffer.Initialize({ BufferStrategy::GpuOnly, frameCount, 1, storageUsage | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT, 10000, 20000 }); + atlasRadixSortTempBuffer.Initialize({ "SpotLightShadowDrawGroup-AtlasRadixSortTempBuffer", BufferStrategy::GpuOnly, frameCount, 1, storageUsage | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT, 10000, 20000 }); atlasRadixSortTempBuffer.UpdateCapacityAll(1); - radixSortTempBuffer.Initialize({ BufferStrategy::GpuOnly, frameCount, 1, storageUsage | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT, 10000, 20000 }); + radixSortTempBuffer.Initialize({ "SpotLightShadowDrawGroup-RadixSortTempBuffer", BufferStrategy::GpuOnly, frameCount, 1, storageUsage | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT, 10000, 20000 }); radixSortTempBuffer.UpdateCapacityAll(1); gridLookupData.Resize(SPOT_SHADOW_GRID_SIZE * SPOT_SHADOW_GRID_SIZE); std::fill(gridLookupData.Data(), gridLookupData.Data() + (SPOT_SHADOW_GRID_SIZE * SPOT_SHADOW_GRID_SIZE), 0xFFFFFFFF); - gridLookupBuffer.Initialize({ BufferStrategy::Hybrid, frameCount, sizeof(uint32_t) * SPOT_SHADOW_GRID_SIZE * SPOT_SHADOW_GRID_SIZE, storageUsage, 1, 1 }); + gridLookupBuffer.Initialize({ "SpotLightShadowDrawGroup-GridLookupBuffer", BufferStrategy::Hybrid, frameCount, sizeof(uint32_t) * SPOT_SHADOW_GRID_SIZE * SPOT_SHADOW_GRID_SIZE, storageUsage, 1, 1 }); gridLookupBuffer.UpdateCapacityAll(1); Vk::ImageConfig atlasSpec{}; diff --git a/SynapseEngine/Engine/Scene/DrawData/SsaoDrawGroup.cpp b/SynapseEngine/Engine/Scene/DrawData/SsaoDrawGroup.cpp index ffd8a51c..afe01f08 100644 --- a/SynapseEngine/Engine/Scene/DrawData/SsaoDrawGroup.cpp +++ b/SynapseEngine/Engine/Scene/DrawData/SsaoDrawGroup.cpp @@ -10,7 +10,7 @@ namespace Syn SsaoDrawGroup::SsaoDrawGroup(uint32_t frameCount) { VkBufferUsageFlags bufferUsage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; - kernelBuffer.Initialize({ BufferStrategy::Hybrid, frameCount, sizeof(SsaoKernel), bufferUsage }); + kernelBuffer.Initialize({ "SsaoDrawGroup_KernelBuffer", BufferStrategy::Hybrid, frameCount, sizeof(SsaoKernel), bufferUsage }); kernelBuffer.UpdateCapacityAll(1); std::mt19937 generator; diff --git a/SynapseEngine/Engine/Shaders/Includes/Common/FrameGlobalContext.glsl b/SynapseEngine/Engine/Shaders/Includes/Common/FrameGlobalContext.glsl index 4fe6be1c..4619dea6 100644 --- a/SynapseEngine/Engine/Shaders/Includes/Common/FrameGlobalContext.glsl +++ b/SynapseEngine/Engine/Shaders/Includes/Common/FrameGlobalContext.glsl @@ -5,6 +5,8 @@ #include "../SharedGpuTypes.glsl" struct FrameGlobalContext { + uint64_t textureMetadataBufferAddr; + uint64_t globalDrawCountBufferAddr; uint64_t globalInstanceIndexBufferAddr; uint64_t globalIndirectCommandBufferAddr; diff --git a/SynapseEngine/Engine/Shaders/Includes/Common/Texture.glsl b/SynapseEngine/Engine/Shaders/Includes/Common/Texture.glsl index 04b54026..e360c3af 100644 --- a/SynapseEngine/Engine/Shaders/Includes/Common/Texture.glsl +++ b/SynapseEngine/Engine/Shaders/Includes/Common/Texture.glsl @@ -6,6 +6,21 @@ layout(set = 0, binding = 0) uniform sampler globalSamplers[]; layout(set = 0, binding = 1) uniform texture2D bindlessTextures[]; +layout(buffer_reference, std430, buffer_reference_align = 4) readonly restrict buffer TextureMetadataBuffer { + uint data[]; +}; + +#define GET_TEXTURE_METADATA(addr, texID) TextureMetadataBuffer(addr).data[texID] + +uint UnpackTextureMetadataSampler(uint meta) { + return meta & 0x7FFFFFFF; +} + +void UnpackTextureMetadata(uint meta, out uint samplerID, out bool invertTangent) { + samplerID = meta & 0x7FFFFFFF; + invertTangent = bool(meta >> 31); +} + #define SAMPLER_LINEAR_REPEAT 0 #define SAMPLER_LINEAR_CLAMP_EDGE 1 #define SAMPLER_NEAREST_REPEAT 2 diff --git a/SynapseEngine/Engine/Shaders/Includes/Utils/MaterialMath.glsl b/SynapseEngine/Engine/Shaders/Includes/Utils/MaterialMath.glsl index a61b5cf1..7486a472 100644 --- a/SynapseEngine/Engine/Shaders/Includes/Utils/MaterialMath.glsl +++ b/SynapseEngine/Engine/Shaders/Includes/Utils/MaterialMath.glsl @@ -4,22 +4,36 @@ #include "../Common/Texture.glsl" #include "../Common/Material.glsl" -vec4 EvaluateAlbedoAlpha(const Material mat, vec2 uv) { +vec4 EvaluateAlbedoAlpha(uint64_t textureMetadataBufferAddr, const Material mat, vec2 uv) { vec4 finalColor = mat.color; if (HAS_ALBEDO_TEX(mat)) { - finalColor *= SampleTexture2D(mat.albedoTexture, SAMPLER_LINEAR_ANISO, uv); + uint meta = GET_TEXTURE_METADATA(textureMetadataBufferAddr, mat.albedoTexture); + uint samplerID = UnpackTextureMetadataSampler(meta); + + finalColor *= SampleTexture2D(mat.albedoTexture, samplerID, uv); } return finalColor; } -vec3 EvaluateNormal(Material mat, vec2 uv, vec3 vertexNormal, vec4 vertexTangent) { +vec3 EvaluateNormal(uint64_t textureMetadataBufferAddr, Material mat, vec2 uv, vec3 vertexNormal, vec4 vertexTangent) { vec3 normal = normalize(vertexNormal); if (!HAS_NORMAL_TEX(mat)) { return normal; } + uint meta = GET_TEXTURE_METADATA(textureMetadataBufferAddr, mat.normalTexture); + + uint samplerID; + bool invertTangent; + UnpackTextureMetadata(meta, samplerID, invertTangent); + vec3 tangent = normalize(vertexTangent.xyz); tangent = normalize(tangent - normal * dot(normal, tangent)); + + if (invertTangent) { + tangent = -tangent; + } + vec3 bitangent = cross(normal, tangent) * vertexTangent.w; mat3 TBN = mat3(tangent, bitangent, normal); @@ -29,20 +43,27 @@ vec3 EvaluateNormal(Material mat, vec2 uv, vec3 vertexNormal, vec4 vertexTangent return normalize(TBN * tangentSpaceNormal); } -vec2 EvaluateMetallicRoughness(Material mat, vec2 uv) { +vec2 EvaluateMetallicRoughness(uint64_t textureMetadataBufferAddr, Material mat, vec2 uv) { float metalness = mat.metalness; float roughness = mat.roughness; + if (HAS_METALNESS_TEX(mat)) { - metalness *= SampleTexture2D(mat.metalnessTexture, SAMPLER_LINEAR_ANISO, uv).r; + uint meta = GET_TEXTURE_METADATA(textureMetadataBufferAddr, mat.metalnessTexture); + uint samplerID = UnpackTextureMetadataSampler(meta); + metalness *= SampleTexture2D(mat.metalnessTexture, samplerID, uv).r; } if (HAS_ROUGHNESS_TEX(mat)) { - roughness *= SampleTexture2D(mat.roughnessTexture, SAMPLER_LINEAR_ANISO, uv).r; + uint meta = GET_TEXTURE_METADATA(textureMetadataBufferAddr, mat.roughnessTexture); + uint samplerID = UnpackTextureMetadataSampler(meta); + roughness *= SampleTexture2D(mat.roughnessTexture, samplerID, uv).r; } if (HAS_METALLIC_ROUGHNESS_TEX(mat)) { - vec4 mrSample = SampleTexture2D(mat.metallicRoughnessTexture, SAMPLER_LINEAR_ANISO, uv); + uint meta = GET_TEXTURE_METADATA(textureMetadataBufferAddr, mat.metallicRoughnessTexture); + uint samplerID = UnpackTextureMetadataSampler(meta); + vec4 mrSample = SampleTexture2D(mat.metallicRoughnessTexture, samplerID, uv); roughness *= mrSample.g; metalness *= mrSample.b; } @@ -50,18 +71,22 @@ vec2 EvaluateMetallicRoughness(Material mat, vec2 uv) { return vec2(metalness, roughness); } -vec3 EvaluateEmissive(Material mat, vec2 uv) { +vec3 EvaluateEmissive(uint64_t textureMetadataBufferAddr, Material mat, vec2 uv) { vec3 emissive = mat.emissiveColor * mat.emissiveIntensity; if (HAS_EMISSIVE_TEX(mat)) { - emissive *= SampleTexture2D(mat.emissiveTexture, SAMPLER_LINEAR_ANISO, uv).rgb; + uint meta = GET_TEXTURE_METADATA(textureMetadataBufferAddr, mat.emissiveTexture); + uint samplerID = UnpackTextureMetadataSampler(meta); + emissive *= SampleTexture2D(mat.emissiveTexture, samplerID, uv).rgb; } return emissive; } -float EvaluateAO(Material mat, vec2 uv) { +float EvaluateAO(uint64_t textureMetadataBufferAddr, Material mat, vec2 uv) { float ao = mat.aoStrength; if (HAS_AO_TEX(mat)) { - ao *= SampleTexture2D(mat.ambientOcclusionTexture, SAMPLER_LINEAR_ANISO, uv).r; + uint meta = GET_TEXTURE_METADATA(textureMetadataBufferAddr, mat.ambientOcclusionTexture); + uint samplerID = UnpackTextureMetadataSampler(meta); + ao *= SampleTexture2D(mat.ambientOcclusionTexture, samplerID, uv).r; } return ao; } diff --git a/SynapseEngine/Engine/Shaders/Passes/Shading/Deferred/GBuffer/OpaqueDeferred.frag b/SynapseEngine/Engine/Shaders/Passes/Shading/Deferred/GBuffer/OpaqueDeferred.frag index 3d930dd6..ab192f3b 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Shading/Deferred/GBuffer/OpaqueDeferred.frag +++ b/SynapseEngine/Engine/Shaders/Passes/Shading/Deferred/GBuffer/OpaqueDeferred.frag @@ -42,24 +42,24 @@ void main() { vec2 finalUV = inUV * mat.uvScale; // 2. Evaluate Albedo & Alpha - vec4 albedoAlpha = EvaluateAlbedoAlpha(mat, finalUV); + vec4 albedoAlpha = EvaluateAlbedoAlpha(ctx.textureMetadataBufferAddr, mat, finalUV); if (albedoAlpha.a < ctx.alphaLimitDiscard) { discard; } // 3. Evaluate Normals & TBN - vec3 finalNormal = EvaluateNormal(mat, finalUV, inNormal, inTangent); + vec3 finalNormal = EvaluateNormal(ctx.textureMetadataBufferAddr, mat, finalUV, inNormal, inTangent); // 4. Evaluate Metalness & Roughness - vec2 metalRough = EvaluateMetallicRoughness(mat, finalUV); + vec2 metalRough = EvaluateMetallicRoughness(ctx.textureMetadataBufferAddr, mat, finalUV); float finalMetalness = metalRough.x; float finalRoughness = clamp(metalRough.y, 0.04, 1.0); // 5. Evaluate Emissive - vec3 finalEmissive = EvaluateEmissive(mat, finalUV); + vec3 finalEmissive = EvaluateEmissive(ctx.textureMetadataBufferAddr, mat, finalUV); // 6. Evaluate Ambient Occlusion - float finalAo = EvaluateAO(mat, finalUV); + float finalAo = EvaluateAO(ctx.textureMetadataBufferAddr, mat, finalUV); // 7. Write Standard Outputs outColorMetallic = vec4(albedoAlpha.rgb, finalMetalness); diff --git a/SynapseEngine/Engine/Shaders/Passes/Shading/ForwardPlus/DepthPrepass/PreDepth.frag b/SynapseEngine/Engine/Shaders/Passes/Shading/ForwardPlus/DepthPrepass/PreDepth.frag index 48d61fa2..631609d9 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Shading/ForwardPlus/DepthPrepass/PreDepth.frag +++ b/SynapseEngine/Engine/Shaders/Passes/Shading/ForwardPlus/DepthPrepass/PreDepth.frag @@ -37,7 +37,7 @@ void main() { vec2 finalUV = inUV * mat.uvScale; // 2. Evaluate Albedo & Alpha - vec4 albedoAlpha = EvaluateAlbedoAlpha(mat, finalUV); + vec4 albedoAlpha = EvaluateAlbedoAlpha(ctx.textureMetadataBufferAddr, mat, finalUV); if (albedoAlpha.a < ctx.alphaLimitDiscard) { discard; } diff --git a/SynapseEngine/Engine/Shaders/Passes/Shading/ForwardPlus/Lighting/OpaqueForward.frag b/SynapseEngine/Engine/Shaders/Passes/Shading/ForwardPlus/Lighting/OpaqueForward.frag index 141880f7..5cffc709 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Shading/ForwardPlus/Lighting/OpaqueForward.frag +++ b/SynapseEngine/Engine/Shaders/Passes/Shading/ForwardPlus/Lighting/OpaqueForward.frag @@ -49,21 +49,21 @@ void main() { vec2 finalUV = inUV * mat.uvScale; // 2. Evaluate Albedo & Alpha - vec4 albedoAlpha = EvaluateAlbedoAlpha(mat, finalUV); + vec4 albedoAlpha = EvaluateAlbedoAlpha(ctx.textureMetadataBufferAddr, mat, finalUV); if (albedoAlpha.a < ctx.alphaLimitDiscard) { discard; } // 3. Evaluate Normals & TBN - vec3 finalNormal = EvaluateNormal(mat, finalUV, inNormal, inTangent); + vec3 finalNormal = EvaluateNormal(ctx.textureMetadataBufferAddr, mat, finalUV, inNormal, inTangent); // 4. Evaluate Metalness & Roughness - vec2 metalRough = EvaluateMetallicRoughness(mat, finalUV); + vec2 metalRough = EvaluateMetallicRoughness(ctx.textureMetadataBufferAddr, mat, finalUV); float finalMetalness = metalRough.x; float finalRoughness = clamp(metalRough.y, 0.04, 1.0); // 5. Evaluate Emissive - vec3 finalEmissive = EvaluateEmissive(mat, finalUV); + vec3 finalEmissive = EvaluateEmissive(ctx.textureMetadataBufferAddr, mat, finalUV); uint cameraDenseIndex = GET_SPARSE_INDEX(ctx.cameraSparseMapBufferAddr, ctx.activeCameraEntity); @@ -79,7 +79,7 @@ void main() { ssao = texture(ssaoTexture, screenUV).r; } - float finalAo = EvaluateAO(mat, finalUV); + float finalAo = EvaluateAO(ctx.textureMetadataBufferAddr, mat, finalUV); if (ctx.enableSsao == 1) { finalAo *= ssao; } diff --git a/SynapseEngine/Engine/Shaders/Passes/Shading/Wboit/TransparentForward.frag b/SynapseEngine/Engine/Shaders/Passes/Shading/Wboit/TransparentForward.frag index 0bdf5fd5..4aadb893 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Shading/Wboit/TransparentForward.frag +++ b/SynapseEngine/Engine/Shaders/Passes/Shading/Wboit/TransparentForward.frag @@ -44,24 +44,24 @@ void main() vec2 finalUV = inUV * mat.uvScale; // 2. Evaluate Albedo & Alpha - vec4 albedoAlpha = EvaluateAlbedoAlpha(mat, finalUV); + vec4 albedoAlpha = EvaluateAlbedoAlpha(ctx.textureMetadataBufferAddr, mat, finalUV); if (albedoAlpha.a < ctx.alphaLimitDiscard) { discard; } // 3. Evaluate Normals & TBN - vec3 finalNormal = EvaluateNormal(mat, finalUV, inNormal, inTangent); + vec3 finalNormal = EvaluateNormal(ctx.textureMetadataBufferAddr, mat, finalUV, inNormal, inTangent); // 4. Evaluate Metalness & Roughness - vec2 metalRough = EvaluateMetallicRoughness(mat, finalUV); + vec2 metalRough = EvaluateMetallicRoughness(ctx.textureMetadataBufferAddr, mat, finalUV); float finalMetalness = metalRough.x; float finalRoughness = clamp(metalRough.y, 0.04, 1.0); // 5. Evaluate Emissive - vec3 finalEmissive = EvaluateEmissive(mat, finalUV); + vec3 finalEmissive = EvaluateEmissive(ctx.textureMetadataBufferAddr, mat, finalUV); // 6. Evaluate Ambient Occlusion - float finalAo = EvaluateAO(mat, finalUV); + float finalAo = EvaluateAO(ctx.textureMetadataBufferAddr, mat, finalUV); uint cameraDenseIndex = GET_SPARSE_INDEX(ctx.cameraSparseMapBufferAddr, ctx.activeCameraEntity); CameraComponent camera = GET_CAMERA(ctx.cameraBufferAddr, cameraDenseIndex); diff --git a/SynapseEngine/Engine/Statistics/DefaultRenderStatCollector.cpp b/SynapseEngine/Engine/Statistics/DefaultRenderStatCollector.cpp index 2e24b95c..e0bfb5c6 100644 --- a/SynapseEngine/Engine/Statistics/DefaultRenderStatCollector.cpp +++ b/SynapseEngine/Engine/Statistics/DefaultRenderStatCollector.cpp @@ -16,9 +16,9 @@ namespace Syn { VK_QUERY_PIPELINE_STATISTIC_VERTEX_SHADER_INVOCATIONS_BIT | VK_QUERY_PIPELINE_STATISTIC_CLIPPING_INVOCATIONS_BIT | VK_QUERY_PIPELINE_STATISTIC_CLIPPING_PRIMITIVES_BIT | - VK_QUERY_PIPELINE_STATISTIC_FRAGMENT_SHADER_INVOCATIONS_BIT; - //VK_QUERY_PIPELINE_STATISTIC_TASK_SHADER_INVOCATIONS_BIT_EXT | - //VK_QUERY_PIPELINE_STATISTIC_MESH_SHADER_INVOCATIONS_BIT_EXT; + VK_QUERY_PIPELINE_STATISTIC_FRAGMENT_SHADER_INVOCATIONS_BIT /* | + VK_QUERY_PIPELINE_STATISTIC_TASK_SHADER_INVOCATIONS_BIT_EXT | + VK_QUERY_PIPELINE_STATISTIC_MESH_SHADER_INVOCATIONS_BIT_EXT */; for (uint32_t i = 0; i < framesInFlight; ++i) { _pools[i] = std::make_unique(MAX_QUERIES_PER_FRAME, flags); @@ -65,13 +65,14 @@ namespace Syn { RenderPassStats passStat; passStat.groupName = m.groupName; passStat.passName = m.passName; - + passStat.inputAssemblyVertices = results[offset + 0]; passStat.inputAssemblyPrimitives = results[offset + 1]; passStat.vertexShaderInvocations = results[offset + 2]; passStat.clippingInvocations = results[offset + 3]; passStat.clippingPrimitives = results[offset + 4]; passStat.fragmentShaderInvocations = results[offset + 5]; + passStat.meshShaderInvocations = results[offset + 6]; //passStat.taskShaderInvocations = results[offset + 6]; //passStat.meshShaderInvocations = results[offset + 7]; diff --git a/SynapseEngine/Engine/Statistics/DefaultRenderStatCollector.h b/SynapseEngine/Engine/Statistics/DefaultRenderStatCollector.h index f4d8e428..b701b2d9 100644 --- a/SynapseEngine/Engine/Statistics/DefaultRenderStatCollector.h +++ b/SynapseEngine/Engine/Statistics/DefaultRenderStatCollector.h @@ -30,7 +30,7 @@ namespace Syn { void ResolveFrame(uint32_t frameIndex) override; const std::vector& GetStats(uint32_t frameIndex) const override; private: - static constexpr uint32_t MAX_QUERIES_PER_FRAME = 256; + static constexpr uint32_t MAX_QUERIES_PER_FRAME = 512; static constexpr uint32_t STATS_PER_QUERY = 8; uint32_t _framesInFlight; diff --git a/SynapseEngine/Engine/Utils/RenderBuffer.cpp b/SynapseEngine/Engine/Utils/RenderBuffer.cpp index 0fe50d7f..163f7272 100644 --- a/SynapseEngine/Engine/Utils/RenderBuffer.cpp +++ b/SynapseEngine/Engine/Utils/RenderBuffer.cpp @@ -1,4 +1,5 @@ #include "RenderBuffer.h" +#include namespace Syn { @@ -25,6 +26,10 @@ namespace Syn gpuConfig.useDeviceAddress = (_config.usage & VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT) != 0; for (uint32_t i = 0; i < _config.frames; ++i) { + if (!_config.debugName.empty()) { + mappedConfig.debugName = std::format("{}_Mapped_F{}", _config.debugName, i); + } + if (_config.strategy != BufferStrategy::GpuOnly) { _mapped.push_back(std::make_unique(mappedConfig, _config.elementSize, _config.upWindow, _config.downWindow)); } @@ -32,6 +37,10 @@ namespace Syn _mapped.push_back(nullptr); } + if (!_config.debugName.empty()) { + gpuConfig.debugName = std::format("{}_GPU_F{}", _config.debugName, i); + } + if (_config.strategy != BufferStrategy::MappedOnly) { _gpu.push_back(std::make_unique(gpuConfig, _config.elementSize, _config.upWindow, _config.downWindow)); } diff --git a/SynapseEngine/Engine/Utils/RenderBuffer.h b/SynapseEngine/Engine/Utils/RenderBuffer.h index bc71d3dd..7343c560 100644 --- a/SynapseEngine/Engine/Utils/RenderBuffer.h +++ b/SynapseEngine/Engine/Utils/RenderBuffer.h @@ -22,6 +22,7 @@ namespace Syn }; struct SYN_API RenderBufferConfig { + std::string debugName = ""; BufferStrategy strategy = BufferStrategy::Hybrid; uint32_t frames = 0; uint32_t elementSize = 0; diff --git a/SynapseEngine/Engine/Vk/Buffer/Buffer.h b/SynapseEngine/Engine/Vk/Buffer/Buffer.h index 75e5dc2e..52f380cf 100644 --- a/SynapseEngine/Engine/Vk/Buffer/Buffer.h +++ b/SynapseEngine/Engine/Vk/Buffer/Buffer.h @@ -1,5 +1,6 @@ #pragma once #include +#include #include "../VkCommon.h" namespace Syn::Vk { @@ -7,6 +8,7 @@ namespace Syn::Vk { class BufferFactory; struct SYN_API BufferConfig { + std::string debugName = ""; VkDeviceSize size = 0; VkBufferUsageFlags usage = 0; VmaMemoryUsage memoryUsage = VMA_MEMORY_USAGE_AUTO; diff --git a/SynapseEngine/Engine/Vk/Buffer/BufferFactory.cpp b/SynapseEngine/Engine/Vk/Buffer/BufferFactory.cpp index 1c379cf6..1d8c4104 100644 --- a/SynapseEngine/Engine/Vk/Buffer/BufferFactory.cpp +++ b/SynapseEngine/Engine/Vk/Buffer/BufferFactory.cpp @@ -43,6 +43,14 @@ namespace Syn::Vk { buffer->_persistentMappedData = allocResultInfo.pMappedData; buffer->_isMapped = true; } + + if (!buffer->GetConfig().debugName.empty()) { + device->SetDebugName( + VK_OBJECT_TYPE_BUFFER, + (uint64_t)buffer->Handle(), + buffer->GetConfig().debugName.c_str() + ); + } } std::unique_ptr BufferFactory::Create(const BufferConfig& config) { diff --git a/SynapseEngine/Engine/Vk/Core/Device.cpp b/SynapseEngine/Engine/Vk/Core/Device.cpp index 6de38a68..60e17c09 100644 --- a/SynapseEngine/Engine/Vk/Core/Device.cpp +++ b/SynapseEngine/Engine/Vk/Core/Device.cpp @@ -25,6 +25,7 @@ namespace Syn::Vk { VkPhysicalDeviceMeshShaderFeaturesEXT meshFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MESH_SHADER_FEATURES_EXT }; meshFeatures.meshShader = VK_TRUE; meshFeatures.taskShader = VK_TRUE; + meshFeatures.meshShaderQueries = VK_TRUE; VkPhysicalDeviceShaderObjectFeaturesEXT shaderObjectFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_OBJECT_FEATURES_EXT }; shaderObjectFeatures.shaderObject = VK_TRUE; @@ -203,4 +204,17 @@ namespace Syn::Vk { void Device::WaitIdle() const { vkDeviceWaitIdle(_handle); } + + void Device::SetDebugName(VkObjectType objectType, uint64_t objectHandle, const char* name) const { + if constexpr (!Syn::EnableLogging) { + return; + } + + VkDebugUtilsObjectNameInfoEXT nameInfo{ VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT }; + nameInfo.objectType = objectType; + nameInfo.objectHandle = objectHandle; + nameInfo.pObjectName = name; + + vkSetDebugUtilsObjectNameEXT(_handle, &nameInfo); + } } \ No newline at end of file diff --git a/SynapseEngine/Engine/Vk/Core/Device.h b/SynapseEngine/Engine/Vk/Core/Device.h index fdfff13f..86bef913 100644 --- a/SynapseEngine/Engine/Vk/Core/Device.h +++ b/SynapseEngine/Engine/Vk/Core/Device.h @@ -15,6 +15,7 @@ namespace Syn::Vk { ThreadSafeQueue* GetComputeQueue() const { return _computeQueue.get(); } ThreadSafeQueue* GetTransferQueue() const { return _transferQueue.get(); } void WaitIdle() const; + void SetDebugName(VkObjectType objectType, uint64_t objectHandle, const char* name) const; private: void InitVMA(VkInstance instance, const PhysicalDevice& physicalDevice); private: diff --git a/SynapseEngine/Engine/Vk/Core/Instance.cpp b/SynapseEngine/Engine/Vk/Core/Instance.cpp index 01e207e2..793a26f8 100644 --- a/SynapseEngine/Engine/Vk/Core/Instance.cpp +++ b/SynapseEngine/Engine/Vk/Core/Instance.cpp @@ -70,7 +70,6 @@ namespace Syn::Vk { if (_validationEnabled) extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); - std::vector layers; if (_validationEnabled) layers.push_back("VK_LAYER_KHRONOS_validation"); diff --git a/SynapseEngine/Engine/imgui.ini b/SynapseEngine/Engine/imgui.ini new file mode 100644 index 00000000..f87d64c5 --- /dev/null +++ b/SynapseEngine/Engine/imgui.ini @@ -0,0 +1,140 @@ +[Window][HostWindow_Scene] +Pos=0,23 +Size=2304,1273 +Collapsed=0 + +[Window][Debug##Default] +Pos=60,60 +Size=400,400 +Collapsed=0 + +[Window][ Inspector] +Pos=1883,23 +Size=421,633 +Collapsed=0 +DockId=0x00000005,0 + +[Window][ Graphics & Environment] +Pos=1883,658 +Size=421,638 +Collapsed=0 +DockId=0x00000006,0 + +[Window][ Scene Hierarchy] +Pos=0,23 +Size=470,278 +Collapsed=0 +DockId=0x00000009,0 + +[Window][ Performance Profiler] +Pos=0,303 +Size=470,993 +Collapsed=0 +DockId=0x0000000A,0 + +[Window][ Output Log] +Pos=472,787 +Size=1409,509 +Collapsed=0 +DockId=0x00000002,1 + +[Window][Content_Scene] +Pos=472,787 +Size=1409,509 +Collapsed=0 +DockId=0x00000002,0 + +[Window][ Viewport] +Pos=472,23 +Size=1409,762 +Collapsed=0 +DockId=0x00000001,0 + +[Table][0x3D1A1C69,2] +RefScale=13 +Column 0 Width=147 +Column 1 Weight=1.0000 + +[Table][0x6925898D,2] +RefScale=13 +Column 0 Width=217 +Column 1 Weight=1.0000 + +[Table][0xE847EDF4,2] +RefScale=13 +Column 0 Width=140 +Column 1 Weight=1.0000 + +[Table][0x182B970D,2] +RefScale=13 +Column 0 Width=0 +Column 1 Weight=-nan(ind) + +[Table][0x03404476,2] +RefScale=13 +Column 0 Width=0 +Column 1 Weight=-nan(ind) + +[Table][0x8556BC1A,2] +RefScale=13 +Column 0 Width=0 +Column 1 Weight=-nan(ind) + +[Table][0x51A78E48,2] +RefScale=13 +Column 0 Weight=1.0000 +Column 1 Width=32 + +[Table][0xB6D16E5C,3] +RefScale=13 + +[Table][0x05A9070B,4] +RefScale=13 +Column 0 Width=140 +Column 1 Width=60 +Column 2 Width=150 +Column 3 Weight=1.0000 + +[Table][0x94CE371A,2] +RefScale=13 +Column 0 Width=63 +Column 1 Weight=1.0000 + +[Table][0x5A9ED35A,2] +RefScale=13 +Column 0 Width=0 +Column 1 Weight=-nan(ind) + +[Table][0xF8524FE2,2] +RefScale=13 +Column 0 Width=0 +Column 1 Weight=-nan(ind) + +[Table][0xA047510E,2] +RefScale=13 +Column 0 Width=0 +Column 1 Weight=-nan(ind) + +[Table][0x32EB459A,2] +RefScale=13 +Column 0 Width=0 +Column 1 Weight=-nan(ind) + +[Table][0x243E03DF,2] +RefScale=13 +Column 0 Width=0 +Column 1 Weight=-nan(ind) + +[Docking][Data] +DockSpace ID=0x4713F8A8 Window=0xFBC4AE05 Pos=128,95 Size=2304,1273 Split=X Selected=0x1C1AF642 + DockNode ID=0x00000007 Parent=0x4713F8A8 SizeRef=470,1273 Split=Y Selected=0x02B8E2DB + DockNode ID=0x00000009 Parent=0x00000007 SizeRef=470,278 Selected=0xF995F4A5 + DockNode ID=0x0000000A Parent=0x00000007 SizeRef=470,993 Selected=0x02B8E2DB + DockNode ID=0x00000008 Parent=0x4713F8A8 SizeRef=1832,1273 Split=X + DockNode ID=0x00000003 Parent=0x00000008 SizeRef=1881,1273 Split=Y + DockNode ID=0x00000001 Parent=0x00000003 SizeRef=2304,762 CentralNode=1 Selected=0x1C1AF642 + DockNode ID=0x00000002 Parent=0x00000003 SizeRef=2304,509 Selected=0x81DECE6A + DockNode ID=0x00000004 Parent=0x00000008 SizeRef=421,1273 Split=Y Selected=0x57A55B3F + DockNode ID=0x00000005 Parent=0x00000004 SizeRef=421,633 Selected=0x70CE1A73 + DockNode ID=0x00000006 Parent=0x00000004 SizeRef=421,638 Selected=0x57A55B3F + From 9734942b529e9e5353363664782de03d76b18d0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20P=C3=A9ter?= Date: Fri, 3 Jul 2026 21:57:57 +0200 Subject: [PATCH 22/51] Gpu bug search, kinda resolved and refactored manager gpu upload --- .../Editor/EditorApi/Impl/MaterialApiImpl.cpp | 18 ++++-- .../Engine/Animation/AnimationManager.cpp | 37 ++++++----- .../Engine/Animation/AnimationManager.h | 1 + SynapseEngine/Engine/Engine.cpp | 2 +- SynapseEngine/Engine/Image/ImageManager.cpp | 41 +++++++------ SynapseEngine/Engine/Image/ImageManager.h | 1 + .../Engine/Manager/BaseResourceManager.h | 48 +++++++++++++++ .../Engine/Material/MaterialManager.cpp | 15 +++-- .../Engine/Material/MaterialManager.h | 1 + SynapseEngine/Engine/Mesh/ModelManager.cpp | 61 ++++++++++--------- SynapseEngine/Engine/Mesh/ModelManager.h | 2 + .../Engine/Render/RendererFactory.cpp | 3 +- .../Scene/Source/Procedural/test_config.json | 10 +-- .../Includes/Payload/ShadowTaskPayload.glsl | 1 + .../Shaders/Includes/Payload/TaskPayload.glsl | 1 + .../Passes/Shading/Common/Meshlet.mesh | 4 +- .../Passes/Shading/Common/Meshlet.task | 4 +- .../Passes/Shading/Common/Traditional.vert | 10 ++- .../DepthPrepass/MeshletPreDepth.mesh | 4 +- .../ForwardPlus/DepthPrepass/PreDepth.frag | 4 +- .../DepthPrepass/TraditionalPreDepth.vert | 10 ++- .../DirectionLightShadowMeshlet.task | 4 +- .../DirectionLightShadowTraditional.vert | 8 ++- .../PointLight/PointLightShadowMeshlet.task | 4 +- .../PointLightShadowTraditional.vert | 8 ++- .../SpotLight/SpotLightShadowMeshlet.task | 5 +- .../SpotLight/SpotLightShadowTraditional.vert | 8 ++- .../Statistics/DefaultRenderStatCollector.cpp | 1 - .../System/Rendering/MaterialSystem.cpp | 2 +- 29 files changed, 222 insertions(+), 96 deletions(-) diff --git a/SynapseEngine/Editor/EditorApi/Impl/MaterialApiImpl.cpp b/SynapseEngine/Editor/EditorApi/Impl/MaterialApiImpl.cpp index 9156fb3d..b2f3f121 100644 --- a/SynapseEngine/Editor/EditorApi/Impl/MaterialApiImpl.cpp +++ b/SynapseEngine/Editor/EditorApi/Impl/MaterialApiImpl.cpp @@ -19,9 +19,17 @@ namespace Syn { return result; } - uint32_t MaterialApiImpl::GetSelectedMaterial() const { return _selectedMaterial; } - void MaterialApiImpl::SetSelectedMaterial(uint32_t id) { _selectedMaterial = id; } - uint64_t MaterialApiImpl::GetVersion() const { return _materialManager ? _materialManager->GetVersion() : 0; } + uint32_t MaterialApiImpl::GetSelectedMaterial() const { + return _selectedMaterial; + } + + void MaterialApiImpl::SetSelectedMaterial(uint32_t id) { + _selectedMaterial = id; + } + + uint64_t MaterialApiImpl::GetVersion() const { + return _materialManager ? _materialManager->GetVersion() : 0; + } std::string MaterialApiImpl::GetMaterialName(uint32_t materialId) const { auto mats = GetAllMaterials(); @@ -84,11 +92,11 @@ namespace Syn { void MaterialApiImpl::UpdateMaterialData(uint32_t materialId, const Material& material) { if (!_materialManager || materialId == INVALID_MATERIAL_ID) return; - auto resource = _materialManager->GetResource(materialId); + if (resource) { *resource = material; - //_materialManager->MarkDirty(materialId) + _materialManager->MarkDirty(materialId); } } } \ No newline at end of file diff --git a/SynapseEngine/Engine/Animation/AnimationManager.cpp b/SynapseEngine/Engine/Animation/AnimationManager.cpp index aaf4063d..a0bc0fc0 100644 --- a/SynapseEngine/Engine/Animation/AnimationManager.cpp +++ b/SynapseEngine/Engine/Animation/AnimationManager.cpp @@ -79,7 +79,8 @@ namespace Syn { safeEntry.stagingBuffer.reset(); SetResourceState(entryId, ResourceState::Ready); - _version.fetch_add(1, std::memory_order_release); + MarkDirty(entryId); + Info("Animation loaded, extracted, and RAM freed: {}", safeEntry.path); }, .needsGraphics = false @@ -90,28 +91,32 @@ namespace Syn { void AnimationManager::FinalizeResource(EntryType& entry) { - uint32_t entryIndex = _pathToId.at(entry.path); - auto& gpuData = *(entry.resource->transientGpuData); auto& cpuData = entry.resource->cpuData; _cpuExtractor->Extract(gpuData, cpuData); - GpuAnimationAddresses addresses{}; - const auto& hw = entry.resource->hardwareBuffers; + entry.resource->transientGpuData.reset(); + entry.resource->transientCpuData.reset(); + } + + void AnimationManager::FlushDirtyResources() + { + ProcessDirtyReadyEntries([this](uint32_t index, const EntryType& entry) { + GpuAnimationAddresses addresses{}; + const auto& hw = entry.resource->hardwareBuffers; - addresses.vertexSkinData = hw.vertexSkinData->GetDeviceAddress(); - addresses.nodeTransforms = hw.nodeTransforms->GetDeviceAddress(); - addresses.frameGlobalColliders = hw.frameGlobalColliders->GetDeviceAddress(); - addresses.frameMeshColliders = hw.frameMeshColliders->GetDeviceAddress(); - addresses.frameMeshletColliders = hw.frameMeshletColliders->GetDeviceAddress(); - addresses.descriptor = entry.resource->cpuData.descriptor; - addresses.globalCollider = entry.resource->cpuData.globalCollider; - addresses.isReady = 1; + addresses.vertexSkinData = hw.vertexSkinData->GetDeviceAddress(); + addresses.nodeTransforms = hw.nodeTransforms->GetDeviceAddress(); + addresses.frameGlobalColliders = hw.frameGlobalColliders->GetDeviceAddress(); + addresses.frameMeshColliders = hw.frameMeshColliders->GetDeviceAddress(); + addresses.frameMeshletColliders = hw.frameMeshletColliders->GetDeviceAddress(); - WriteAddress(entryIndex, addresses); + addresses.descriptor = entry.resource->cpuData.descriptor; + addresses.globalCollider = entry.resource->cpuData.globalCollider; + addresses.isReady = 1; - entry.resource->transientGpuData.reset(); - entry.resource->transientCpuData.reset(); + WriteAddress(index, addresses); + }); } } \ No newline at end of file diff --git a/SynapseEngine/Engine/Animation/AnimationManager.h b/SynapseEngine/Engine/Animation/AnimationManager.h index 7f0cd44f..02edcbce 100644 --- a/SynapseEngine/Engine/Animation/AnimationManager.h +++ b/SynapseEngine/Engine/Animation/AnimationManager.h @@ -24,6 +24,7 @@ namespace Syn { protected: void StartGpuUpload(EntryType& entry) override; void FinalizeResource(EntryType& entry) override; + void FlushDirtyResources() override; private: std::shared_ptr _builder; std::unique_ptr _uploader; diff --git a/SynapseEngine/Engine/Engine.cpp b/SynapseEngine/Engine/Engine.cpp index 55091f92..4b706dca 100644 --- a/SynapseEngine/Engine/Engine.cpp +++ b/SynapseEngine/Engine/Engine.cpp @@ -121,7 +121,7 @@ namespace Syn _inputManager = std::make_unique(); ServiceLocator::ProvideInputManager(_inputManager.get()); - InitFrameContext(1); + InitFrameContext(2); InitLogger(); InitVulkan(params); InitTaskExecutor(); diff --git a/SynapseEngine/Engine/Image/ImageManager.cpp b/SynapseEngine/Engine/Image/ImageManager.cpp index 657e050a..9b67c5ca 100644 --- a/SynapseEngine/Engine/Image/ImageManager.cpp +++ b/SynapseEngine/Engine/Image/ImageManager.cpp @@ -317,8 +317,8 @@ namespace Syn entry.stagingBuffer.reset(); SetResourceState(entryId, ResourceState::Ready); + MarkDirty(entryId); - _version.fetch_add(1, std::memory_order_release); Info("Image '{}' is ready", entry.path); }, .needsGraphics = needsGraphics @@ -331,26 +331,31 @@ namespace Syn { _cpuExtractor->Extract(*(entry.resource->transientGpuData), entry.resource->cpuData); - uint32_t descriptorIndex = _pathToId.at(entry.path); - if (descriptorIndex != 0) { - _bindlessBuffer->WriteSampledImage( - BINDING_TEXTURES, - descriptorIndex, - entry.resource->image->GetView() - ); - } + entry.resource->transientCpuData.reset(); + entry.resource->transientGpuData.reset(); + } - uint32_t samplerIndex = GetSamplerIndex("LinearAniso"); - bool invertTangent = false; + void ImageManager::FlushDirtyResources() { + ProcessDirtyReadyEntries( + [this](uint32_t index, const EntryType& entry) { + if (!entry.resource->image) return; - uint32_t textureData = (samplerIndex & 0x7FFFFFFF); - if (invertTangent) { - textureData |= (1u << 31); - } + _bindlessBuffer->WriteSampledImage( + BINDING_TEXTURES, + index, + entry.resource->image->GetView() + ); - WriteAddress(descriptorIndex, textureData); + uint32_t samplerIndex = GetSamplerIndex("LinearAniso"); + bool invertTangent = false; - entry.resource->transientCpuData.reset(); - entry.resource->transientGpuData.reset(); + uint32_t textureData = (samplerIndex & 0x7FFFFFFF); + if (invertTangent) { + textureData |= (1u << 31); + } + + WriteAddress(index, textureData); + } + ); } } \ No newline at end of file diff --git a/SynapseEngine/Engine/Image/ImageManager.h b/SynapseEngine/Engine/Image/ImageManager.h index e981d752..3e969cb7 100644 --- a/SynapseEngine/Engine/Image/ImageManager.h +++ b/SynapseEngine/Engine/Image/ImageManager.h @@ -46,6 +46,7 @@ namespace Syn { Vk::Sampler* GetSampler(const std::string& name) const; uint32_t GetSamplerIndex(const std::string& name) const; protected: + void FlushDirtyResources() override; void StartGpuUpload(EntryType& entry) override; void FinalizeResource(EntryType& entry) override; private: diff --git a/SynapseEngine/Engine/Manager/BaseResourceManager.h b/SynapseEngine/Engine/Manager/BaseResourceManager.h index fa1bfee7..6edbec7f 100644 --- a/SynapseEngine/Engine/Manager/BaseResourceManager.h +++ b/SynapseEngine/Engine/Manager/BaseResourceManager.h @@ -62,6 +62,7 @@ namespace Syn { virtual void Update(); void WaitForResource(uint32_t id) const; void SetResourceState(uint32_t id, ResourceState newState); + void MarkDirty(uint32_t id); size_t GetResourceCount() const; ResourceState GetEntryState(uint32_t id) const; @@ -78,6 +79,11 @@ namespace Syn { uint32_t InternalLoadSync(const std::string & key, std::function()> task); uint32_t InternalLoad(const std::string & key, std::function()> task, bool isAsync); std::shared_ptr GetResource(uint32_t id, bool internalCall) const; + + virtual void FlushDirtyResources() {} + + template + void ProcessDirtyReadyEntries(Func&& processFunc); protected: void SubmitGpuRequest(const EntryType & entry, Vk::GpuUploadRequest && request); virtual void StartGpuUpload(EntryType & entry) = 0; @@ -88,8 +94,39 @@ namespace Syn { std::vector _entries; std::unordered_map _pathToId; mutable std::recursive_mutex _mutex; + + std::vector _dirtyFlags; + bool _hasDirty = false; }; + template + template + void BaseResourceManager::ProcessDirtyReadyEntries(Func&& processFunc) { + uint32_t count = static_cast(_entries.size()); + if (count == 0) return; + + for (uint32_t i = count; i-- > 0; ) { + if (_dirtyFlags[i]) { + const auto& entry = _entries[i]; + + if (entry.state != ResourceState::Ready) continue; + + processFunc(i, entry); + } + } + } + + template + void BaseResourceManager::MarkDirty(uint32_t id) { + std::lock_guard lock(_mutex); + if (id >= _dirtyFlags.size()) { + _dirtyFlags.resize(id + 1, 0); + } + + _dirtyFlags[id] = 1; + _hasDirty = true; + } + template void BaseResourceManager::Update() { std::lock_guard lock(_mutex); @@ -109,6 +146,15 @@ namespace Syn { } } } + + if (_hasDirty) { + FlushDirtyResources(); + + std::fill(_dirtyFlags.begin(), _dirtyFlags.end(), 0); + _hasDirty = false; + + _version.fetch_add(1, std::memory_order_release); + } } template @@ -173,6 +219,8 @@ namespace Syn { } } + _dirtyFlags.resize(_entries.size(), 0); + return newId; } diff --git a/SynapseEngine/Engine/Material/MaterialManager.cpp b/SynapseEngine/Engine/Material/MaterialManager.cpp index 89e49c07..9bd71233 100644 --- a/SynapseEngine/Engine/Material/MaterialManager.cpp +++ b/SynapseEngine/Engine/Material/MaterialManager.cpp @@ -54,13 +54,9 @@ namespace Syn { void MaterialManager::StartGpuUpload(EntryType& entry) { uint32_t entryIndex = _pathToId.at(entry.path); - size_t offset = entryIndex * sizeof(GpuMaterial); - - auto materialGPU = GpuMaterial(*entry.resource); - WriteAddress(entryIndex, materialGPU); entry.state = ResourceState::Ready; - _version.fetch_add(1, std::memory_order_release); + MarkDirty(entryIndex); Info("Material '{}' is ready", entry.path); } @@ -73,4 +69,13 @@ namespace Syn { void MaterialManager::FinalizeResource(EntryType& entry) { } + + void MaterialManager::FlushDirtyResources() { + ProcessDirtyReadyEntries( + [this](uint32_t index, const EntryType& entry) { + GpuMaterial gpuMat(*entry.resource); + WriteAddress(index, gpuMat); + } + ); + } } \ No newline at end of file diff --git a/SynapseEngine/Engine/Material/MaterialManager.h b/SynapseEngine/Engine/Material/MaterialManager.h index 365942e1..c6958911 100644 --- a/SynapseEngine/Engine/Material/MaterialManager.h +++ b/SynapseEngine/Engine/Material/MaterialManager.h @@ -17,6 +17,7 @@ namespace Syn { uint32_t LoadMaterial(const std::string& name, const MaterialInfo& info); uint32_t LoadMaterialDirect(const std::string& name, const Material& material); protected: + void FlushDirtyResources() override; void StartGpuUpload(EntryType& entry) override; void FinalizeResource(EntryType& entry) override; private: diff --git a/SynapseEngine/Engine/Mesh/ModelManager.cpp b/SynapseEngine/Engine/Mesh/ModelManager.cpp index 5c7ae925..be8b6ca4 100644 --- a/SynapseEngine/Engine/Mesh/ModelManager.cpp +++ b/SynapseEngine/Engine/Mesh/ModelManager.cpp @@ -142,7 +142,8 @@ namespace Syn { safeEntry.stagingBuffer.reset(); SetResourceState(entryId, ResourceState::Ready); - _version.fetch_add(1, std::memory_order_release); + MarkDirty(entryId); + Info("Model loaded, hardware buffers ready and transient RAM freed: {}", safeEntry.path); }, .needsGraphics = false @@ -153,34 +154,38 @@ namespace Syn { void ModelManager::FinalizeResource(EntryType& entry) { - uint32_t entryIndex = _pathToId.at(entry.path); - - GpuModelAddresses addresses{}; - const auto& hw = entry.resource->hardwareBuffers; - const auto& cpuData = entry.resource->cpuData; - - addresses.vertexPositions = hw.vertexPositions->GetDeviceAddress(); - addresses.vertexAttributes = hw.vertexAttributes->GetDeviceAddress(); - addresses.indices = hw.indices->GetDeviceAddress(); - addresses.meshDescriptors = hw.meshDescriptors->GetDeviceAddress(); - addresses.meshColliders = hw.meshColliders->GetDeviceAddress(); - addresses.lodDescriptors = hw.lodDescriptors->GetDeviceAddress(); - addresses.meshletVertexIndices = hw.meshletVertexIndices->GetDeviceAddress(); - addresses.meshletTriangleIndices = hw.meshletTriangleIndices->GetDeviceAddress(); - addresses.meshletDescriptors = hw.meshletDescriptors->GetDeviceAddress(); - addresses.meshletDrawDescriptors = hw.meshletDrawDescriptors->GetDeviceAddress(); - addresses.meshletColliders = hw.meshletColliders->GetDeviceAddress(); - addresses.nodeTransforms = hw.nodeTransforms->GetDeviceAddress(); - addresses.globalCollider = cpuData.globalCollider; - addresses.vertexCount = cpuData.globalVertexCount; - addresses.indexCount = cpuData.globalIndexCount; - addresses.meshCount = cpuData.globalMeshCount; - addresses.averageLodIndexCount = cpuData.globalAverageLodIndexCount; - addresses.isReady = 1; - - WriteAddress(entryIndex, addresses); - entry.resource->transientGpuData.reset(); entry.resource->transientCpuData.reset(); } + + void ModelManager::FlushDirtyResources() { + ProcessDirtyReadyEntries([this](uint32_t index, const EntryType& entry) { + + GpuModelAddresses addresses{}; + const auto& hw = entry.resource->hardwareBuffers; + const auto& cpuData = entry.resource->cpuData; + + addresses.vertexPositions = hw.vertexPositions->GetDeviceAddress(); + addresses.vertexAttributes = hw.vertexAttributes->GetDeviceAddress(); + addresses.indices = hw.indices->GetDeviceAddress(); + addresses.meshDescriptors = hw.meshDescriptors->GetDeviceAddress(); + addresses.meshColliders = hw.meshColliders->GetDeviceAddress(); + addresses.lodDescriptors = hw.lodDescriptors->GetDeviceAddress(); + addresses.meshletVertexIndices = hw.meshletVertexIndices->GetDeviceAddress(); + addresses.meshletTriangleIndices = hw.meshletTriangleIndices->GetDeviceAddress(); + addresses.meshletDescriptors = hw.meshletDescriptors->GetDeviceAddress(); + addresses.meshletDrawDescriptors = hw.meshletDrawDescriptors->GetDeviceAddress(); + addresses.meshletColliders = hw.meshletColliders->GetDeviceAddress(); + addresses.nodeTransforms = hw.nodeTransforms->GetDeviceAddress(); + + addresses.globalCollider = cpuData.globalCollider; + addresses.vertexCount = cpuData.globalVertexCount; + addresses.indexCount = cpuData.globalIndexCount; + addresses.meshCount = cpuData.globalMeshCount; + addresses.averageLodIndexCount = cpuData.globalAverageLodIndexCount; + addresses.isReady = 1; + + WriteAddress(index, addresses); + }); + } } \ No newline at end of file diff --git a/SynapseEngine/Engine/Mesh/ModelManager.h b/SynapseEngine/Engine/Mesh/ModelManager.h index 3d1efcea..61e91fa7 100644 --- a/SynapseEngine/Engine/Mesh/ModelManager.h +++ b/SynapseEngine/Engine/Mesh/ModelManager.h @@ -23,6 +23,7 @@ namespace Syn { MaterialLoadCallback materialLoadCallback = nullptr); ~ModelManager() = default; + uint32_t LoadModelAsync(const std::string& filePath); uint32_t LoadModelFromSourceAsync(const std::string& name, MeshSourceFactory factory); uint32_t LoadModelFromStaticMeshAsync(const std::string& name, StaticMeshFactory factory); @@ -31,6 +32,7 @@ namespace Syn { uint32_t LoadModelFromSourceSync(const std::string& name, MeshSourceFactory factory); uint32_t LoadModelFromStaticMeshSync(const std::string& name, StaticMeshFactory factory); protected: + void FlushDirtyResources() override; void StartGpuUpload(EntryType& entry) override; void FinalizeResource(EntryType& entry) override; private: diff --git a/SynapseEngine/Engine/Render/RendererFactory.cpp b/SynapseEngine/Engine/Render/RendererFactory.cpp index cfa77441..e3c8b002 100644 --- a/SynapseEngine/Engine/Render/RendererFactory.cpp +++ b/SynapseEngine/Engine/Render/RendererFactory.cpp @@ -266,7 +266,7 @@ namespace Syn pipeline->AddPass(std::make_unique(MaterialRenderType::Opaque2Sided)); pipeline->AddPass(std::make_unique(MaterialRenderType::Opaque1Sided)); pipeline->AddPass(std::make_unique(MaterialRenderType::Opaque2Sided)); - + //Deferred Depth Opaque Prepasses pipeline->AddPass(std::make_unique()); pipeline->AddPass(std::make_unique(MaterialRenderType::Opaque1Sided)); @@ -329,7 +329,6 @@ namespace Syn pipeline->AddPass(std::make_unique(MaterialRenderType::Opaque2Sided)); pipeline->AddPass(std::make_unique(MaterialRenderType::Opaque1Sided)); pipeline->AddPass(std::make_unique(MaterialRenderType::Opaque2Sided)); - // SkySphere pipeline->AddPass(std::make_unique()); diff --git a/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json b/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json index 2d0e3234..6469175c 100644 --- a/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json +++ b/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json @@ -11,7 +11,7 @@ }, "materials": { "use_unique_materials": false, - "shared_material_count": 100 + "shared_material_count": 150 }, "entities": { "animated_characters": 500, @@ -22,9 +22,9 @@ }, "lights": { "directional_count": 1, - "point_count": 1, - "point_shadow_count": 1, - "spot_count": 1, - "spot_shadow_count": 1 + "point_count": 64, + "point_shadow_count": 16, + "spot_count": 64, + "spot_shadow_count": 16 } } \ No newline at end of file diff --git a/SynapseEngine/Engine/Shaders/Includes/Payload/ShadowTaskPayload.glsl b/SynapseEngine/Engine/Shaders/Includes/Payload/ShadowTaskPayload.glsl index cdfb90f9..1601a03d 100644 --- a/SynapseEngine/Engine/Shaders/Includes/Payload/ShadowTaskPayload.glsl +++ b/SynapseEngine/Engine/Shaders/Includes/Payload/ShadowTaskPayload.glsl @@ -7,6 +7,7 @@ struct ShadowTaskPayload { uint transformDenseIdx; uint lightShadowDenseIdx; uint cascadeIdx; + uint modelDenseIndex; uint meshletIndices[32]; }; diff --git a/SynapseEngine/Engine/Shaders/Includes/Payload/TaskPayload.glsl b/SynapseEngine/Engine/Shaders/Includes/Payload/TaskPayload.glsl index 8425b0dc..182bdb47 100644 --- a/SynapseEngine/Engine/Shaders/Includes/Payload/TaskPayload.glsl +++ b/SynapseEngine/Engine/Shaders/Includes/Payload/TaskPayload.glsl @@ -6,6 +6,7 @@ struct TaskPayload { uint entityId; uint transformDenseIdx; uint activeCameraDenseIdx; + uint modelDenseIndex; uint meshletIndices[32]; }; diff --git a/SynapseEngine/Engine/Shaders/Passes/Shading/Common/Meshlet.mesh b/SynapseEngine/Engine/Shaders/Passes/Shading/Common/Meshlet.mesh index f52c05d3..4aca6a49 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Shading/Common/Meshlet.mesh +++ b/SynapseEngine/Engine/Shaders/Passes/Shading/Common/Meshlet.mesh @@ -103,8 +103,8 @@ void main() { // Material Resolution uint meshIndex = UNPACK_UINT16_Y(v.packedIndex); - ModelComponent modelComp = GET_MODEL_COMP(ctx.modelBufferAddr, GET_SPARSE_INDEX(ctx.modelSparseMapBufferAddr, entityId)); - uint materialId = GET_MATERIAL_INDEX(ctx.materialLookupBufferAddr, modelComp.materialOffset + meshIndex); + uint materialOffset = GET_MODEL_COMP(ctx.modelBufferAddr, payload.modelDenseIndex).materialOffset; + uint materialId = GET_MATERIAL_INDEX(ctx.materialLookupBufferAddr, materialOffset + meshIndex); // Final Transform and Output gl_MeshVerticesEXT[i].gl_Position = camera.viewProjVulkan * transform.transform * finalMat * vec4(v.position, 1.0); diff --git a/SynapseEngine/Engine/Shaders/Passes/Shading/Common/Meshlet.task b/SynapseEngine/Engine/Shaders/Passes/Shading/Common/Meshlet.task index f9a39cfb..e3c5553c 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Shading/Common/Meshlet.task +++ b/SynapseEngine/Engine/Shaders/Passes/Shading/Common/Meshlet.task @@ -43,6 +43,7 @@ void main() { uint transformDenseIndex = GET_SPARSE_INDEX(ctx.transformSparseMapBufferAddr, entityId); uint mainCameraDenseIndex = GET_SPARSE_INDEX(ctx.cameraSparseMapBufferAddr, ctx.mainCameraEntity); uint activeCameraDenseIndex = GET_SPARSE_INDEX(ctx.cameraSparseMapBufferAddr, ctx.activeCameraEntity); + uint modelDenseIndex = GET_SPARSE_INDEX(ctx.modelSparseMapBufferAddr, entityId); // 1. Initialize shared memory and payload if (threadIdx == 0) { @@ -51,6 +52,7 @@ void main() { payload.entityId = entityId; payload.transformDenseIdx = transformDenseIndex; payload.activeCameraDenseIdx = activeCameraDenseIndex; + payload.modelDenseIndex = modelDenseIndex; } barrier(); @@ -67,7 +69,7 @@ void main() { uint currentMeshletIdx = meshletGroupOffset + threadIdx; bool isVisible = (currentMeshletIdx < submeshData.meshletCount); - if (transformDenseIndex == INVALID_INDEX || mainCameraDenseIndex == INVALID_INDEX) { + if (transformDenseIndex == INVALID_INDEX || mainCameraDenseIndex == INVALID_INDEX || modelDenseIndex == INVALID_INDEX) { isVisible = false; } diff --git a/SynapseEngine/Engine/Shaders/Passes/Shading/Common/Traditional.vert b/SynapseEngine/Engine/Shaders/Passes/Shading/Common/Traditional.vert index 0b64e33c..c8bf68a7 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Shading/Common/Traditional.vert +++ b/SynapseEngine/Engine/Shaders/Passes/Shading/Common/Traditional.vert @@ -35,6 +35,15 @@ void main() { // 3. Fetch Model Component & Material Lookup uint modelDenseIndex = GET_SPARSE_INDEX(ctx.modelSparseMapBufferAddr, entityId); + uint transformDenseIndex = GET_SPARSE_INDEX(ctx.transformSparseMapBufferAddr, entityId); + + if (transformDenseIndex == INVALID_INDEX || modelDenseIndex == INVALID_INDEX) { + gl_Position = vec4(0.0, 0.0, 0.0, 0.0); + outUV = vec2(0.0); + outId = uvec3(0u); + return; + } + ModelComponent comp = GET_MODEL_COMP(ctx.modelBufferAddr, modelDenseIndex); // 4. Fetch Model Addresses & Raw Vertex Data @@ -45,7 +54,6 @@ void main() { GpuVertexAttributes attr = GET_VERTEX_ATTR(addrs.vertexAttributes, realVertexIndex); // 5. Fetch Transform and Camera - uint transformDenseIndex = GET_SPARSE_INDEX(ctx.transformSparseMapBufferAddr, entityId); TransformComponent transform = GET_TRANSFORM(ctx.transformBufferAddr, transformDenseIndex); uint cameraDenseIndex = GET_SPARSE_INDEX(ctx.cameraSparseMapBufferAddr, ctx.activeCameraEntity); diff --git a/SynapseEngine/Engine/Shaders/Passes/Shading/ForwardPlus/DepthPrepass/MeshletPreDepth.mesh b/SynapseEngine/Engine/Shaders/Passes/Shading/ForwardPlus/DepthPrepass/MeshletPreDepth.mesh index 6d266f30..1d9e3478 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Shading/ForwardPlus/DepthPrepass/MeshletPreDepth.mesh +++ b/SynapseEngine/Engine/Shaders/Passes/Shading/ForwardPlus/DepthPrepass/MeshletPreDepth.mesh @@ -100,8 +100,8 @@ void main() { // Material Resolution uint meshIndex = UNPACK_UINT16_Y(v.packedIndex); - ModelComponent modelComp = GET_MODEL_COMP(ctx.modelBufferAddr, GET_SPARSE_INDEX(ctx.modelSparseMapBufferAddr, entityId)); - uint materialId = GET_MATERIAL_INDEX(ctx.materialLookupBufferAddr, modelComp.materialOffset + meshIndex); + uint materialOffset = GET_MODEL_COMP(ctx.modelBufferAddr, payload.modelDenseIndex).materialOffset; + uint materialId = GET_MATERIAL_INDEX(ctx.materialLookupBufferAddr, materialOffset + meshIndex); // Final Transform and Output gl_MeshVerticesEXT[i].gl_Position = camera.viewProjVulkan * transform.transform * finalMat * vec4(v.position, 1.0); diff --git a/SynapseEngine/Engine/Shaders/Passes/Shading/ForwardPlus/DepthPrepass/PreDepth.frag b/SynapseEngine/Engine/Shaders/Passes/Shading/ForwardPlus/DepthPrepass/PreDepth.frag index 631609d9..32a4b913 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Shading/ForwardPlus/DepthPrepass/PreDepth.frag +++ b/SynapseEngine/Engine/Shaders/Passes/Shading/ForwardPlus/DepthPrepass/PreDepth.frag @@ -31,7 +31,8 @@ void main() { uint finalPayload = pipelineFlag == VIS_PIPELINE_MESH_SHADER ? FINALIZE_VIS_MS(partial, gl_PrimitiveID) : FINALIZE_VIS_TRADITIONAL(partial, gl_PrimitiveID); - + + /* // 1. Fetch Material Material mat = GET_MATERIAL(ctx.materialBufferAddr, materialId); vec2 finalUV = inUV * mat.uvScale; @@ -41,6 +42,7 @@ void main() { if (albedoAlpha.a < ctx.alphaLimitDiscard) { discard; } + */ outId = uvec2(packedEntity, finalPayload); } \ No newline at end of file diff --git a/SynapseEngine/Engine/Shaders/Passes/Shading/ForwardPlus/DepthPrepass/TraditionalPreDepth.vert b/SynapseEngine/Engine/Shaders/Passes/Shading/ForwardPlus/DepthPrepass/TraditionalPreDepth.vert index 53975616..fcda814a 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Shading/ForwardPlus/DepthPrepass/TraditionalPreDepth.vert +++ b/SynapseEngine/Engine/Shaders/Passes/Shading/ForwardPlus/DepthPrepass/TraditionalPreDepth.vert @@ -33,6 +33,15 @@ void main() { // 3. Fetch Model Component & Material Lookup uint modelDenseIndex = GET_SPARSE_INDEX(ctx.modelSparseMapBufferAddr, entityId); + uint transformDenseIndex = GET_SPARSE_INDEX(ctx.transformSparseMapBufferAddr, entityId); + + if (transformDenseIndex == INVALID_INDEX || modelDenseIndex == INVALID_INDEX) { + gl_Position = vec4(0.0, 0.0, 0.0, 0.0); + outUV = vec2(0.0); + outId = uvec3(0u); + return; + } + ModelComponent comp = GET_MODEL_COMP(ctx.modelBufferAddr, modelDenseIndex); // 4. Fetch Model Addresses & Raw Vertex Data @@ -43,7 +52,6 @@ void main() { GpuVertexAttributes attr = GET_VERTEX_ATTR(addrs.vertexAttributes, realVertexIndex); // 5. Fetch Transform and Camera - uint transformDenseIndex = GET_SPARSE_INDEX(ctx.transformSparseMapBufferAddr, entityId); TransformComponent transform = GET_TRANSFORM(ctx.transformBufferAddr, transformDenseIndex); uint cameraDenseIndex = GET_SPARSE_INDEX(ctx.cameraSparseMapBufferAddr, ctx.activeCameraEntity); diff --git a/SynapseEngine/Engine/Shaders/Passes/Shadow/DirectionLight/DirectionLightShadowMeshlet.task b/SynapseEngine/Engine/Shaders/Passes/Shadow/DirectionLight/DirectionLightShadowMeshlet.task index cf91845d..0dfdca01 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Shadow/DirectionLight/DirectionLightShadowMeshlet.task +++ b/SynapseEngine/Engine/Shaders/Passes/Shadow/DirectionLight/DirectionLightShadowMeshlet.task @@ -49,6 +49,7 @@ void main() { uint lightEntity = GET_DIRECTION_VISIBLE_SHADOW_LIGHT(ctx.directionLightVisibleShadowIndexBufferAddr, lightIdx); uint lightShadowDenseIdx = GET_SPARSE_INDEX(ctx.directionLightShadowSparseMapBufferAddr, lightEntity); uint lightDenseIdx = GET_SPARSE_INDEX(ctx.directionLightSparseMapBufferAddr, lightEntity); + uint modelDenseIndex = GET_SPARSE_INDEX(ctx.modelSparseMapBufferAddr, entityId); // 2. Initialize payload and shared memory if (threadIdx == 0) { @@ -58,6 +59,7 @@ void main() { payload.transformDenseIdx = transformDenseIndex; payload.lightShadowDenseIdx = lightShadowDenseIdx; payload.cascadeIdx = cascadeIdx; + payload.modelDenseIndex = modelDenseIndex; } barrier(); @@ -75,7 +77,7 @@ void main() { uint currentMeshletIdx = meshletGroupOffset + threadIdx; bool isVisible = (currentMeshletIdx < submeshData.meshletCount); - if (transformDenseIndex == INVALID_INDEX || lightShadowDenseIdx == INVALID_INDEX || lightDenseIdx == INVALID_INDEX) { + if (transformDenseIndex == INVALID_INDEX || lightShadowDenseIdx == INVALID_INDEX || lightDenseIdx == INVALID_INDEX || modelDenseIndex == INVALID_INDEX) { isVisible = false; } diff --git a/SynapseEngine/Engine/Shaders/Passes/Shadow/DirectionLight/DirectionLightShadowTraditional.vert b/SynapseEngine/Engine/Shaders/Passes/Shadow/DirectionLight/DirectionLightShadowTraditional.vert index 31576b6d..f2de60c1 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Shadow/DirectionLight/DirectionLightShadowTraditional.vert +++ b/SynapseEngine/Engine/Shaders/Passes/Shadow/DirectionLight/DirectionLightShadowTraditional.vert @@ -35,6 +35,13 @@ void main() { // 3. Fetch Model Component & Material Lookup uint modelDenseIndex = GET_SPARSE_INDEX(ctx.modelSparseMapBufferAddr, entityId); + uint transformDenseIndex = GET_SPARSE_INDEX(ctx.transformSparseMapBufferAddr, entityId); + + if (transformDenseIndex == INVALID_INDEX || modelDenseIndex == INVALID_INDEX) { + gl_Position = vec4(0.0, 0.0, 0.0, 0.0); + return; + } + ModelComponent comp = GET_MODEL_COMP(ctx.modelBufferAddr, modelDenseIndex); // 4. Fetch Model Addresses & Raw Vertex Data @@ -44,7 +51,6 @@ void main() { GpuVertexPosition v = GET_VERTEX_POS(addrs.vertexPositions, realVertexIndex); // 5. Fetch Transform and Camera - uint transformDenseIndex = GET_SPARSE_INDEX(ctx.transformSparseMapBufferAddr, entityId); TransformComponent transform = GET_TRANSFORM(ctx.transformBufferAddr, transformDenseIndex); uint cameraDenseIndex = GET_SPARSE_INDEX(ctx.cameraSparseMapBufferAddr, ctx.activeCameraEntity); diff --git a/SynapseEngine/Engine/Shaders/Passes/Shadow/PointLight/PointLightShadowMeshlet.task b/SynapseEngine/Engine/Shaders/Passes/Shadow/PointLight/PointLightShadowMeshlet.task index a8b559ab..fa671710 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Shadow/PointLight/PointLightShadowMeshlet.task +++ b/SynapseEngine/Engine/Shaders/Passes/Shadow/PointLight/PointLightShadowMeshlet.task @@ -49,6 +49,7 @@ void main() { uint lightEntity = GET_POINT_VISIBLE_SHADOW_LIGHT(ctx.pointLightVisibleShadowIndexBufferAddr, lightIdx); uint lightShadowDenseIdx = GET_SPARSE_INDEX(ctx.pointLightShadowSparseMapBufferAddr, lightEntity); uint lightDenseIdx = GET_SPARSE_INDEX(ctx.pointLightSparseMapBufferAddr, lightEntity); + uint modelDenseIndex = GET_SPARSE_INDEX(ctx.modelSparseMapBufferAddr, entityId); // 2. Initialize payload and shared memory if (threadIdx == 0) { @@ -58,6 +59,7 @@ void main() { payload.transformDenseIdx = transformDenseIndex; payload.lightShadowDenseIdx = lightShadowDenseIdx; payload.cascadeIdx = faceIndex; + payload.modelDenseIndex = modelDenseIndex; } barrier(); @@ -73,7 +75,7 @@ void main() { uint currentMeshletIdx = meshletGroupOffset + threadIdx; bool isVisible = (currentMeshletIdx < submeshData.meshletCount); - if (transformDenseIndex == INVALID_INDEX || lightShadowDenseIdx == INVALID_INDEX || lightDenseIdx == INVALID_INDEX) { + if (transformDenseIndex == INVALID_INDEX || lightShadowDenseIdx == INVALID_INDEX || lightDenseIdx == INVALID_INDEX || modelDenseIndex == INVALID_INDEX) { isVisible = false; } diff --git a/SynapseEngine/Engine/Shaders/Passes/Shadow/PointLight/PointLightShadowTraditional.vert b/SynapseEngine/Engine/Shaders/Passes/Shadow/PointLight/PointLightShadowTraditional.vert index 6b9cb191..84ffe129 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Shadow/PointLight/PointLightShadowTraditional.vert +++ b/SynapseEngine/Engine/Shaders/Passes/Shadow/PointLight/PointLightShadowTraditional.vert @@ -36,6 +36,13 @@ void main() { // 3. Fetch Model Component & Material Lookup uint modelDenseIndex = GET_SPARSE_INDEX(ctx.modelSparseMapBufferAddr, entityId); + uint transformDenseIndex = GET_SPARSE_INDEX(ctx.transformSparseMapBufferAddr, entityId); + + if (transformDenseIndex == INVALID_INDEX || modelDenseIndex == INVALID_INDEX) { + gl_Position = vec4(0.0, 0.0, 0.0, 0.0); + return; + } + ModelComponent comp = GET_MODEL_COMP(ctx.modelBufferAddr, modelDenseIndex); // 4. Fetch Model Addresses & Raw Vertex Data @@ -44,7 +51,6 @@ void main() { GpuVertexPosition v = GET_VERTEX_POS(addrs.vertexPositions, realVertexIndex); // 5. Fetch Transform - uint transformDenseIndex = GET_SPARSE_INDEX(ctx.transformSparseMapBufferAddr, entityId); TransformComponent transform = GET_TRANSFORM(ctx.transformBufferAddr, transformDenseIndex); // 6. Evaluate Static Hierarchy (Default pose) diff --git a/SynapseEngine/Engine/Shaders/Passes/Shadow/SpotLight/SpotLightShadowMeshlet.task b/SynapseEngine/Engine/Shaders/Passes/Shadow/SpotLight/SpotLightShadowMeshlet.task index b8897ddc..fb1d6c58 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Shadow/SpotLight/SpotLightShadowMeshlet.task +++ b/SynapseEngine/Engine/Shaders/Passes/Shadow/SpotLight/SpotLightShadowMeshlet.task @@ -48,15 +48,18 @@ void main() { uint lightEntity = GET_SPOT_VISIBLE_SHADOW_LIGHT(ctx.spotLightVisibleShadowIndexBufferAddr, lightIdx); uint lightShadowDenseIdx = GET_SPARSE_INDEX(ctx.spotLightShadowSparseMapBufferAddr, lightEntity); uint lightDenseIdx = GET_SPARSE_INDEX(ctx.spotLightSparseMapBufferAddr, lightEntity); + uint modelDenseIndex = GET_SPARSE_INDEX(ctx.modelSparseMapBufferAddr, entityId); // 2. Initialize payload and shared memory if (threadIdx == 0) { + survivingMeshletCount = 0; payload.drawId = gl_DrawID; payload.entityId = entityId; payload.transformDenseIdx = transformDenseIndex; payload.lightShadowDenseIdx = lightShadowDenseIdx; payload.cascadeIdx = 0; + payload.modelDenseIndex = modelDenseIndex; } barrier(); @@ -72,7 +75,7 @@ void main() { uint currentMeshletIdx = meshletGroupOffset + threadIdx; bool isVisible = (currentMeshletIdx < submeshData.meshletCount); - if (transformDenseIndex == INVALID_INDEX || lightShadowDenseIdx == INVALID_INDEX || lightDenseIdx == INVALID_INDEX) { + if (transformDenseIndex == INVALID_INDEX || lightShadowDenseIdx == INVALID_INDEX || lightDenseIdx == INVALID_INDEX || modelDenseIndex == INVALID_INDEX) { isVisible = false; } diff --git a/SynapseEngine/Engine/Shaders/Passes/Shadow/SpotLight/SpotLightShadowTraditional.vert b/SynapseEngine/Engine/Shaders/Passes/Shadow/SpotLight/SpotLightShadowTraditional.vert index 41ac59ac..817220a0 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Shadow/SpotLight/SpotLightShadowTraditional.vert +++ b/SynapseEngine/Engine/Shaders/Passes/Shadow/SpotLight/SpotLightShadowTraditional.vert @@ -34,6 +34,13 @@ void main() { // 3. Fetch Model Component & Material Lookup uint modelDenseIndex = GET_SPARSE_INDEX(ctx.modelSparseMapBufferAddr, entityId); + uint transformDenseIndex = GET_SPARSE_INDEX(ctx.transformSparseMapBufferAddr, entityId); + + if (transformDenseIndex == INVALID_INDEX || modelDenseIndex == INVALID_INDEX) { + gl_Position = vec4(0.0, 0.0, 0.0, 0.0); + return; + } + ModelComponent comp = GET_MODEL_COMP(ctx.modelBufferAddr, modelDenseIndex); // 4. Fetch Model Addresses & Raw Vertex Data @@ -43,7 +50,6 @@ void main() { GpuVertexPosition v = GET_VERTEX_POS(addrs.vertexPositions, realVertexIndex); // 5. Fetch Transform and Camera - uint transformDenseIndex = GET_SPARSE_INDEX(ctx.transformSparseMapBufferAddr, entityId); TransformComponent transform = GET_TRANSFORM(ctx.transformBufferAddr, transformDenseIndex); uint cameraDenseIndex = GET_SPARSE_INDEX(ctx.cameraSparseMapBufferAddr, ctx.activeCameraEntity); diff --git a/SynapseEngine/Engine/Statistics/DefaultRenderStatCollector.cpp b/SynapseEngine/Engine/Statistics/DefaultRenderStatCollector.cpp index e0bfb5c6..e892b170 100644 --- a/SynapseEngine/Engine/Statistics/DefaultRenderStatCollector.cpp +++ b/SynapseEngine/Engine/Statistics/DefaultRenderStatCollector.cpp @@ -72,7 +72,6 @@ namespace Syn { passStat.clippingInvocations = results[offset + 3]; passStat.clippingPrimitives = results[offset + 4]; passStat.fragmentShaderInvocations = results[offset + 5]; - passStat.meshShaderInvocations = results[offset + 6]; //passStat.taskShaderInvocations = results[offset + 6]; //passStat.meshShaderInvocations = results[offset + 7]; diff --git a/SynapseEngine/Engine/System/Rendering/MaterialSystem.cpp b/SynapseEngine/Engine/System/Rendering/MaterialSystem.cpp index 1d9f415a..56a263ce 100644 --- a/SynapseEngine/Engine/System/Rendering/MaterialSystem.cpp +++ b/SynapseEngine/Engine/System/Rendering/MaterialSystem.cpp @@ -113,7 +113,7 @@ namespace Syn if constexpr (ENABLE_DEBUG_LOGGING) { std::string entityName = "Unknown"; if (tagPool && tagPool->Has(entity)) entityName = tagPool->Get(entity).name; - Info("[MaterialSystem UPDATE] Frame {}: Entity {} data changed! OldOffset: {}, NewOffset: {}, WasReady: {}", scene->GetSystemContext().frameIndex, entityName, comp.materialOffset, currentOffset); + Info("[MaterialSystem UPDATE] Frame {}: Entity {} data changed! OldOffset: {}, NewOffset: {}", scene->GetSystemContext().frameIndex, entityName, comp.materialOffset, currentOffset); } comp.materialOffset = currentOffset; From ab36a0413eded56f871ae7094c91b55b834da8e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20P=C3=A9ter?= Date: Fri, 3 Jul 2026 22:04:49 +0200 Subject: [PATCH 23/51] Global barrier added --- .../Render/Passes/Setup/GlobalFrameSetupPass.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/SynapseEngine/Engine/Render/Passes/Setup/GlobalFrameSetupPass.cpp b/SynapseEngine/Engine/Render/Passes/Setup/GlobalFrameSetupPass.cpp index 99c6336d..72493f82 100644 --- a/SynapseEngine/Engine/Render/Passes/Setup/GlobalFrameSetupPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Setup/GlobalFrameSetupPass.cpp @@ -285,5 +285,21 @@ namespace Syn { ServiceLocator::GetModelManager()->RecordSync(context.cmd); ServiceLocator::GetMaterialManager()->RecordSync(context.cmd); ServiceLocator::GetImageManager()->RecordSync(context.cmd); + + Vk::GlobalBarrierInfo globalBarrier{}; + + globalBarrier.srcStage = VK_PIPELINE_STAGE_2_TRANSFER_BIT | VK_PIPELINE_STAGE_2_HOST_BIT | VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT; + globalBarrier.srcAccess = VK_ACCESS_2_TRANSFER_WRITE_BIT | VK_ACCESS_2_HOST_WRITE_BIT | VK_ACCESS_2_SHADER_WRITE_BIT; + + globalBarrier.dstStage = VK_PIPELINE_STAGE_2_TASK_SHADER_BIT_EXT | + VK_PIPELINE_STAGE_2_MESH_SHADER_BIT_EXT | + VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT | + VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT | + VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT | + VK_PIPELINE_STAGE_2_DRAW_INDIRECT_BIT; + + globalBarrier.dstAccess = VK_ACCESS_2_SHADER_READ_BIT | VK_ACCESS_2_INDIRECT_COMMAND_READ_BIT | VK_ACCESS_2_MEMORY_READ_BIT; + + Vk::BufferUtils::InsertGlobalBarrier(context.cmd, globalBarrier); } } \ No newline at end of file From 1c6e0fa21a09ca041b1caeab75af3324a1fd885f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20P=C3=A9ter?= Date: Sat, 4 Jul 2026 20:11:16 +0200 Subject: [PATCH 24/51] Small refactors --- .../Editor/EditorApi/Impl/RenderApiImpl.cpp | 2 +- .../Editor/View/Viewport/ViewportView.cpp | 56 +++++++++---------- .../ViewModels/Viewport/ViewportState.h | 2 +- SynapseEngine/Engine/Engine.cpp | 2 +- .../Billboard/BillboardTransitionPass.cpp | 2 +- .../Passes/Billboard/CameraBillboardPass.cpp | 2 +- .../Billboard/DirectionLightBillboardPass.cpp | 2 +- .../Billboard/PointLightBillboardPass.cpp | 2 +- .../Billboard/SpotLightBillboardPass.cpp | 2 +- .../Geometry/GeometryMeshCullingPass.cpp | 4 +- .../Geometry/GeometryModelCullingPass.cpp | 4 +- .../GeometryMortonChunkCullingPass.cpp | 2 +- .../GeometryMortonModelCullingPass.cpp | 2 +- .../GeometryStaticChunkCullingPass.cpp | 2 +- .../GeometryStaticModelCullingPass.cpp | 2 +- .../Geometry/GeometryWorkGraphCullingPass.cpp | 2 +- .../PointLight/PointLightCullingPass.cpp | 4 +- .../SpotLight/SpotLightCullingPass.cpp | 4 +- .../Geometry/GeometryHizDownsamplePass.cpp | 4 +- .../Geometry/GeometryHizLinearPreparePass.cpp | 8 +-- .../Engine/Render/Passes/Hiz/HizInitPass.cpp | 2 +- .../PostProcess/Bloom/BloomCompositePass.cpp | 6 +- .../PostProcess/Bloom/BloomDownsamplePass.cpp | 4 +- .../PostProcess/Bloom/BloomPrefilterPass.cpp | 8 +-- .../PostProcess/Bloom/BloomUpsamplePass.cpp | 4 +- .../Outline/SelectionOutlinePass.cpp | 4 +- .../PostProcess/SkySphere/SkySpherePass.cpp | 2 +- .../Render/Passes/Present/CompositePass.cpp | 4 +- .../Engine/Render/Passes/Present/GuiPass.cpp | 2 +- .../Present/PresentationTransitionPass.cpp | 2 +- .../Passes/Setup/GlobalFrameSetupPass.cpp | 2 +- .../Passes/Shading/Common/DepthCopyPass.cpp | 4 +- .../Passes/Shading/Common/OpaqueInitPass.cpp | 2 +- .../Shading/Common/TransparentInitPass.cpp | 2 +- .../GBuffer/MeshletOpaqueDeferredPass.cpp | 6 +- .../GBuffer/OpaqueDeferredTransitionPass.cpp | 2 +- .../GBuffer/TraditionalOpaqueDeferredPass.cpp | 2 +- .../Lighting/DeferredDirectionLightPass.cpp | 6 +- .../Lighting/DeferredEmissiveAoPass.cpp | 4 +- .../Lighting/DeferredLightTransitionPass.cpp | 2 +- .../Lighting/DeferredPointLightPass.cpp | 6 +- .../Lighting/DeferredSpotLightPass.cpp | 6 +- .../Clustering/ClusterPointLightCountPass.cpp | 2 +- .../Clustering/ClusterPointLightWritePass.cpp | 2 +- .../Clustering/ClusterSetupPass.cpp | 6 +- .../Clustering/ClusterSpotLightCountPass.cpp | 2 +- .../Clustering/ClusterSpotLightWritePass.cpp | 2 +- .../MeshletOpaqueDepthPrepass.cpp | 6 +- .../MeshletTransparentDepthPrepass.cpp | 6 +- .../OpaqueDepthTransitionPrepass.cpp | 2 +- .../TraditionalOpaqueDepthPrepass.cpp | 2 +- .../TraditionalTransparentDepthPrepass.cpp | 2 +- .../TransparentDepthTransitionPrepass.cpp | 2 +- .../Lighting/MeshletOpaqueForwardPass.cpp | 6 +- .../Lighting/OpaqueForwardTransitionPass.cpp | 2 +- .../Lighting/TraditionalOpaqueForwardPass.cpp | 4 +- .../Visibility/DebugVisibilityPass.cpp | 4 +- .../Wboit/MeshletTransparentForwardPass.cpp | 6 +- .../TraditionalTransparentForwardPass.cpp | 4 +- .../Wboit/TransparentCompositePass.cpp | 4 +- .../TransparentCompositeTransitionPass.cpp | 2 +- .../TransparentForwardTransitionPass.cpp | 2 +- .../Render/Passes/Ssao/SsaoBlurPass.cpp | 6 +- .../Render/Passes/Ssao/SsaoInitPass.cpp | 2 +- .../Engine/Render/Passes/Ssao/SsaoPass.cpp | 6 +- .../Chunk/MortonChunkAabbWireframePass.cpp | 2 +- .../Chunk/StaticChunkAabbWireframePass.cpp | 2 +- .../Collider/BoxColliderWireframePass.cpp | 2 +- .../Collider/CapsuleColliderWireframePass.cpp | 2 +- .../Collider/SphereColliderWireframePass.cpp | 2 +- .../Light/PointLightAabbWireframePass.cpp | 2 +- .../Light/PointLightSphereWireframePass.cpp | 2 +- .../Light/SpotLightAabbWireframePass.cpp | 2 +- .../Light/SpotLightConeWireframePass.cpp | 2 +- .../Light/SpotLightPyramidWireframePass.cpp | 2 +- .../Light/SpotLightSphereWireframePass.cpp | 2 +- .../Wireframe/Mesh/WireframeMeshAabbPass.cpp | 2 +- .../Mesh/WireframeMeshSpherePass.cpp | 2 +- .../Meshlet/WireframeMeshletAabbPass.cpp | 4 +- .../Meshlet/WireframeMeshletConePass.cpp | 4 +- .../Meshlet/WireframeMeshletSpherePass.cpp | 4 +- SynapseEngine/Engine/Render/RenderNames.h | 4 +- .../Engine/Render/RendererFactory.cpp | 34 ++++++----- SynapseEngine/Engine/Render/RendererFactory.h | 2 +- .../Scene/Source/Procedural/test_config.json | 4 +- .../ForwardPlus/DepthPrepass/PreDepth.frag | 2 - .../Statistics/DefaultRenderStatCollector.cpp | 31 ++++++---- .../Statistics/DefaultRenderStatCollector.h | 1 - .../Vk/Query/PipelineStatisticsQueryPool.cpp | 7 ++- .../Vk/Query/PipelineStatisticsQueryPool.h | 5 ++ SynapseEngine/Engine/Vk/Query/QueryPool.cpp | 6 +- SynapseEngine/Engine/Vk/Query/QueryPool.h | 1 + 92 files changed, 213 insertions(+), 196 deletions(-) diff --git a/SynapseEngine/Editor/EditorApi/Impl/RenderApiImpl.cpp b/SynapseEngine/Editor/EditorApi/Impl/RenderApiImpl.cpp index 5465597c..7ef11f6c 100644 --- a/SynapseEngine/Editor/EditorApi/Impl/RenderApiImpl.cpp +++ b/SynapseEngine/Editor/EditorApi/Impl/RenderApiImpl.cpp @@ -103,7 +103,7 @@ namespace Syn { auto frameCtx = ServiceLocator::GetFrameContext(); uint32_t currentFrame = frameCtx ? frameCtx->currentFrameIndex : 0; - auto group = rtManager->GetGroup(RenderTargetGroupNames::Deferred, currentFrame); + auto group = rtManager->GetGroup(RenderTargetGroupNames::Main, currentFrame); if (!group) return NULL_ENTITY; auto entityImage = group->GetImage(RenderTargetNames::EntityIndex); diff --git a/SynapseEngine/Editor/View/Viewport/ViewportView.cpp b/SynapseEngine/Editor/View/Viewport/ViewportView.cpp index f2bbbb98..06c6fa73 100644 --- a/SynapseEngine/Editor/View/Viewport/ViewportView.cpp +++ b/SynapseEngine/Editor/View/Viewport/ViewportView.cpp @@ -221,22 +221,22 @@ namespace Syn { return isActive; }; - RadioButton("Main", RenderTargetGroupNames::Deferred, RenderTargetNames::Main, Vk::ImageViewNames::Default); + RadioButton("Main", RenderTargetGroupNames::Main, RenderTargetNames::Main, Vk::ImageViewNames::Default); ImGui::SeparatorText("GBuffer Textures"); - RadioButton("Color", RenderTargetGroupNames::Deferred, RenderTargetNames::ColorMetallic, RenderTargetViewNames::Color); - RadioButton("Metallic", RenderTargetGroupNames::Deferred, RenderTargetNames::ColorMetallic, RenderTargetViewNames::Metallic); - RadioButton("Normal", RenderTargetGroupNames::Deferred, RenderTargetNames::NormalRoughness, RenderTargetViewNames::Normal); - RadioButton("Roughness", RenderTargetGroupNames::Deferred, RenderTargetNames::NormalRoughness, RenderTargetViewNames::Roughness); - RadioButton("Emissive", RenderTargetGroupNames::Deferred, RenderTargetNames::EmissiveAo, RenderTargetViewNames::Emissive); - RadioButton("Ambient Occlusion", RenderTargetGroupNames::Deferred, RenderTargetNames::EmissiveAo, RenderTargetViewNames::AmbientOcclusion); - RadioButton("Ssao", RenderTargetGroupNames::Deferred, RenderTargetNames::SsaoAo, Vk::ImageViewNames::Default); + RadioButton("Color", RenderTargetGroupNames::Main, RenderTargetNames::ColorMetallic, RenderTargetViewNames::Color); + RadioButton("Metallic", RenderTargetGroupNames::Main, RenderTargetNames::ColorMetallic, RenderTargetViewNames::Metallic); + RadioButton("Normal", RenderTargetGroupNames::Main, RenderTargetNames::NormalRoughness, RenderTargetViewNames::Normal); + RadioButton("Roughness", RenderTargetGroupNames::Main, RenderTargetNames::NormalRoughness, RenderTargetViewNames::Roughness); + RadioButton("Emissive", RenderTargetGroupNames::Main, RenderTargetNames::EmissiveAo, RenderTargetViewNames::Emissive); + RadioButton("Ambient Occlusion", RenderTargetGroupNames::Main, RenderTargetNames::EmissiveAo, RenderTargetViewNames::AmbientOcclusion); + RadioButton("Ssao", RenderTargetGroupNames::Main, RenderTargetNames::SsaoAo, Vk::ImageViewNames::Default); ImGui::SeparatorText("Wboit Textures"); - RadioButton("Transparent Accum", RenderTargetGroupNames::Deferred, RenderTargetNames::TransparentAccum, Vk::ImageViewNames::Default); - RadioButton("Transparent Reveal", RenderTargetGroupNames::Deferred, RenderTargetNames::TransparentReveal, Vk::ImageViewNames::Default); + RadioButton("Transparent Accum", RenderTargetGroupNames::Main, RenderTargetNames::TransparentAccum, Vk::ImageViewNames::Default); + RadioButton("Transparent Reveal", RenderTargetGroupNames::Main, RenderTargetNames::TransparentReveal, Vk::ImageViewNames::Default); ImGui::SeparatorText("Mipchain Textures"); @@ -249,13 +249,13 @@ namespace Syn { static int bloomMip = 0; bloomMip = std::min(bloomMip, maxMipIndex); std::string bloomView = std::string(Vk::ImageViewNames::Default) + Vk::ImageViewNames::Mip + std::to_string(bloomMip); - RadioButton("Bloom", RenderTargetGroupNames::Deferred, RenderTargetNames::Bloom, bloomView); + RadioButton("Bloom", RenderTargetGroupNames::Main, RenderTargetNames::Bloom, bloomView); if (state.currentTarget == RenderTargetNames::Bloom) { ImGui::Indent(); if (ImGui::SliderInt("Mip##Bloom", &bloomMip, 0, maxMipIndex)) { bloomView = std::string(Vk::ImageViewNames::Default) + Vk::ImageViewNames::Mip + std::to_string(bloomMip); - vm.Dispatch(ChangeTargetIntent{ RenderTargetGroupNames::Deferred, RenderTargetNames::Bloom, bloomView }); + vm.Dispatch(ChangeTargetIntent{ RenderTargetGroupNames::Main, RenderTargetNames::Bloom, bloomView }); } ImGui::Unindent(); } @@ -263,13 +263,13 @@ namespace Syn { static int depthMipMax = 0; depthMipMax = std::min(depthMipMax, maxMipIndex); std::string depthMaxView = std::string(RenderTargetViewNames::DepthOpaqueMax) + Vk::ImageViewNames::Mip + std::to_string(depthMipMax); - RadioButton("Depth Pyramid Max", RenderTargetGroupNames::Deferred, RenderTargetNames::DepthPyramid, depthMaxView); + RadioButton("Depth Pyramid Max", RenderTargetGroupNames::Main, RenderTargetNames::DepthPyramid, depthMaxView); if (state.currentView.contains(RenderTargetViewNames::DepthOpaqueMax)) { ImGui::Indent(); if (ImGui::SliderInt("Mip##DepthMax", &depthMipMax, 0, maxMipIndex)) { depthMaxView = std::string(RenderTargetViewNames::DepthOpaqueMax) + Vk::ImageViewNames::Mip + std::to_string(depthMipMax); - vm.Dispatch(ChangeTargetIntent{ RenderTargetGroupNames::Deferred, RenderTargetNames::DepthPyramid, depthMaxView }); + vm.Dispatch(ChangeTargetIntent{ RenderTargetGroupNames::Main, RenderTargetNames::DepthPyramid, depthMaxView }); } ImGui::Unindent(); } @@ -277,13 +277,13 @@ namespace Syn { static int depthMipMin = 0; depthMipMin = std::min(depthMipMin, maxMipIndex); std::string depthMinView = std::string(RenderTargetViewNames::DepthTransparentMin) + Vk::ImageViewNames::Mip + std::to_string(depthMipMin); - RadioButton("Depth Pyramid Min", RenderTargetGroupNames::Deferred, RenderTargetNames::DepthPyramid, depthMinView); + RadioButton("Depth Pyramid Min", RenderTargetGroupNames::Main, RenderTargetNames::DepthPyramid, depthMinView); if (state.currentView.contains(RenderTargetViewNames::DepthTransparentMin)) { ImGui::Indent(); if (ImGui::SliderInt("Mip##DepthMin", &depthMipMin, 0, maxMipIndex)) { depthMinView = std::string(RenderTargetViewNames::DepthTransparentMin) + Vk::ImageViewNames::Mip + std::to_string(depthMipMin); - vm.Dispatch(ChangeTargetIntent{ RenderTargetGroupNames::Deferred, RenderTargetNames::DepthPyramid, depthMinView }); + vm.Dispatch(ChangeTargetIntent{ RenderTargetGroupNames::Main, RenderTargetNames::DepthPyramid, depthMinView }); } ImGui::Unindent(); } @@ -295,13 +295,13 @@ namespace Syn { std::string shadowMaxBaseView = RenderTargetViewNames::DirectionLightShadowDepthPyramidMax; std::string shadowMaxView = shadowMaxBaseView + Vk::ImageViewNames::Mip + std::to_string(shadowHzbMaxMip); - RadioButton("DirLight HZB Max (R)", RenderTargetGroupNames::Deferred, RenderTargetNames::DirectionLightShadowDepthPyramid, shadowMaxView); + RadioButton("DirLight HZB Max (R)", RenderTargetGroupNames::Main, RenderTargetNames::DirectionLightShadowDepthPyramid, shadowMaxView); if (state.currentView.contains(shadowMaxBaseView)) { ImGui::Indent(); if (ImGui::SliderInt("Mip##ShadowHzbMax", &shadowHzbMaxMip, 0, SHADOW_HIZ_MIP_LEVELS - 1)) { shadowMaxView = shadowMaxBaseView + Vk::ImageViewNames::Mip + std::to_string(shadowHzbMaxMip); - vm.Dispatch(ChangeTargetIntent{ RenderTargetGroupNames::Deferred, RenderTargetNames::DirectionLightShadowDepthPyramid, shadowMaxView }); + vm.Dispatch(ChangeTargetIntent{ RenderTargetGroupNames::Main, RenderTargetNames::DirectionLightShadowDepthPyramid, shadowMaxView }); } ImGui::Unindent(); } @@ -311,13 +311,13 @@ namespace Syn { std::string shadowMinBaseView = RenderTargetViewNames::DirectionLightShadowDepthPyramidMin; std::string shadowMinView = shadowMinBaseView + Vk::ImageViewNames::Mip + std::to_string(shadowHzbMinMip); - RadioButton("DirLight HZB Min (G)", RenderTargetGroupNames::Deferred, RenderTargetNames::DirectionLightShadowDepthPyramid, shadowMinView); + RadioButton("DirLight HZB Min (G)", RenderTargetGroupNames::Main, RenderTargetNames::DirectionLightShadowDepthPyramid, shadowMinView); if (state.currentView.contains(shadowMinBaseView)) { ImGui::Indent(); if (ImGui::SliderInt("Mip##ShadowHzbMin", &shadowHzbMinMip, 0, SHADOW_HIZ_MIP_LEVELS - 1)) { shadowMinView = shadowMinBaseView + Vk::ImageViewNames::Mip + std::to_string(shadowHzbMinMip); - vm.Dispatch(ChangeTargetIntent{ RenderTargetGroupNames::Deferred, RenderTargetNames::DirectionLightShadowDepthPyramid, shadowMinView }); + vm.Dispatch(ChangeTargetIntent{ RenderTargetGroupNames::Main, RenderTargetNames::DirectionLightShadowDepthPyramid, shadowMinView }); } ImGui::Unindent(); } @@ -327,13 +327,13 @@ namespace Syn { std::string spotShadowMaxBaseView = RenderTargetViewNames::SpotLightShadowDepthPyramidMax; std::string spotShadowMaxView = spotShadowMaxBaseView + Vk::ImageViewNames::Mip + std::to_string(spotShadowHzbMaxMip); - RadioButton("SpotLight HZB Max (R)", RenderTargetGroupNames::Deferred, RenderTargetNames::SpotLightShadowDepthPyramid, spotShadowMaxView); + RadioButton("SpotLight HZB Max (R)", RenderTargetGroupNames::Main, RenderTargetNames::SpotLightShadowDepthPyramid, spotShadowMaxView); if (state.currentView.contains(spotShadowMaxBaseView)) { ImGui::Indent(); if (ImGui::SliderInt("Mip##SpotShadowHzbMax", &spotShadowHzbMaxMip, 0, SPOT_SHADOW_HIZ_MIP_LEVELS - 1)) { spotShadowMaxView = spotShadowMaxBaseView + Vk::ImageViewNames::Mip + std::to_string(spotShadowHzbMaxMip); - vm.Dispatch(ChangeTargetIntent{ RenderTargetGroupNames::Deferred, RenderTargetNames::SpotLightShadowDepthPyramid, spotShadowMaxView }); + vm.Dispatch(ChangeTargetIntent{ RenderTargetGroupNames::Main, RenderTargetNames::SpotLightShadowDepthPyramid, spotShadowMaxView }); } ImGui::Unindent(); } @@ -343,13 +343,13 @@ namespace Syn { std::string spotShadowMinBaseView = RenderTargetViewNames::SpotLightShadowDepthPyramidMin; std::string spotShadowMinView = spotShadowMinBaseView + Vk::ImageViewNames::Mip + std::to_string(spotShadowHzbMinMip); - RadioButton("SpotLight HZB Min (G)", RenderTargetGroupNames::Deferred, RenderTargetNames::SpotLightShadowDepthPyramid, spotShadowMinView); + RadioButton("SpotLight HZB Min (G)", RenderTargetGroupNames::Main, RenderTargetNames::SpotLightShadowDepthPyramid, spotShadowMinView); if (state.currentView.contains(spotShadowMinBaseView)) { ImGui::Indent(); if (ImGui::SliderInt("Mip##SpotShadowHzbMin", &spotShadowHzbMinMip, 0, SPOT_SHADOW_HIZ_MIP_LEVELS - 1)) { spotShadowMinView = spotShadowMinBaseView + Vk::ImageViewNames::Mip + std::to_string(spotShadowHzbMinMip); - vm.Dispatch(ChangeTargetIntent{ RenderTargetGroupNames::Deferred, RenderTargetNames::SpotLightShadowDepthPyramid, spotShadowMinView }); + vm.Dispatch(ChangeTargetIntent{ RenderTargetGroupNames::Main, RenderTargetNames::SpotLightShadowDepthPyramid, spotShadowMinView }); } ImGui::Unindent(); } @@ -359,12 +359,12 @@ namespace Syn { std::string pointShadowMaxBaseView = RenderTargetViewNames::PointLightShadowDepthPyramidMax; std::string pointShadowMaxView = pointShadowMaxBaseView + Vk::ImageViewNames::Mip + std::to_string(pointShadowHzbMaxMip); - RadioButton("PointLight HZB Max (R)", RenderTargetGroupNames::Deferred, RenderTargetNames::PointLightShadowDepthPyramid, pointShadowMaxView); + RadioButton("PointLight HZB Max (R)", RenderTargetGroupNames::Main, RenderTargetNames::PointLightShadowDepthPyramid, pointShadowMaxView); if (state.currentView.contains(pointShadowMaxBaseView)) { ImGui::Indent(); if (ImGui::SliderInt("Mip##PointShadowHzbMax", &pointShadowHzbMaxMip, 0, POINT_SHADOW_HIZ_MIP_LEVELS - 1)) { pointShadowMaxView = pointShadowMaxBaseView + Vk::ImageViewNames::Mip + std::to_string(pointShadowHzbMaxMip); - vm.Dispatch(ChangeTargetIntent{ RenderTargetGroupNames::Deferred, RenderTargetNames::PointLightShadowDepthPyramid, pointShadowMaxView }); + vm.Dispatch(ChangeTargetIntent{ RenderTargetGroupNames::Main, RenderTargetNames::PointLightShadowDepthPyramid, pointShadowMaxView }); } ImGui::Unindent(); } @@ -374,12 +374,12 @@ namespace Syn { std::string pointShadowMinBaseView = RenderTargetViewNames::PointLightShadowDepthPyramidMin; std::string pointShadowMinView = pointShadowMinBaseView + Vk::ImageViewNames::Mip + std::to_string(pointShadowHzbMinMip); - RadioButton("PointLight HZB Min (G)", RenderTargetGroupNames::Deferred, RenderTargetNames::PointLightShadowDepthPyramid, pointShadowMinView); + RadioButton("PointLight HZB Min (G)", RenderTargetGroupNames::Main, RenderTargetNames::PointLightShadowDepthPyramid, pointShadowMinView); if (state.currentView.contains(pointShadowMinBaseView)) { ImGui::Indent(); if (ImGui::SliderInt("Mip##PointShadowHzbMin", &pointShadowHzbMinMip, 0, POINT_SHADOW_HIZ_MIP_LEVELS - 1)) { pointShadowMinView = pointShadowMinBaseView + Vk::ImageViewNames::Mip + std::to_string(pointShadowHzbMinMip); - vm.Dispatch(ChangeTargetIntent{ RenderTargetGroupNames::Deferred, RenderTargetNames::PointLightShadowDepthPyramid, pointShadowMinView }); + vm.Dispatch(ChangeTargetIntent{ RenderTargetGroupNames::Main, RenderTargetNames::PointLightShadowDepthPyramid, pointShadowMinView }); } ImGui::Unindent(); } diff --git a/SynapseEngine/EditorCore/ViewModels/Viewport/ViewportState.h b/SynapseEngine/EditorCore/ViewModels/Viewport/ViewportState.h index 3396ef9c..088ebad3 100644 --- a/SynapseEngine/EditorCore/ViewModels/Viewport/ViewportState.h +++ b/SynapseEngine/EditorCore/ViewModels/Viewport/ViewportState.h @@ -20,7 +20,7 @@ namespace Syn { uint32_t height = 0; TextureHandle textureId = InvalidTextureHandle; - std::string currentGroup = RenderTargetGroupNames::Deferred; + std::string currentGroup = RenderTargetGroupNames::Main; std::string currentTarget = RenderTargetNames::Main; std::string currentView = Vk::ImageViewNames::Default; diff --git a/SynapseEngine/Engine/Engine.cpp b/SynapseEngine/Engine/Engine.cpp index 4b706dca..99046f8f 100644 --- a/SynapseEngine/Engine/Engine.cpp +++ b/SynapseEngine/Engine/Engine.cpp @@ -223,7 +223,7 @@ namespace Syn void Engine::InitRenderManager(const EngineInitParams& params) { - _renderManager = std::move(RendererFactory::CreateDeferredRenderer(_frameContext.framesInFlight)); + _renderManager = std::move(RendererFactory::CreateSceneRenderer(_frameContext.framesInFlight)); _renderManager->SetGuiRenderCallback(params.onRenderGuiCallback); } diff --git a/SynapseEngine/Engine/Render/Passes/Billboard/BillboardTransitionPass.cpp b/SynapseEngine/Engine/Render/Passes/Billboard/BillboardTransitionPass.cpp index 38b8c354..f7faf33c 100644 --- a/SynapseEngine/Engine/Render/Passes/Billboard/BillboardTransitionPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Billboard/BillboardTransitionPass.cpp @@ -4,7 +4,7 @@ namespace Syn { void BillboardTransitionPass::PrepareFrame(const RenderContext& context) { - auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); std::vector targets = { RenderTargetNames::Main, diff --git a/SynapseEngine/Engine/Render/Passes/Billboard/CameraBillboardPass.cpp b/SynapseEngine/Engine/Render/Passes/Billboard/CameraBillboardPass.cpp index ab4cc39b..077291cf 100644 --- a/SynapseEngine/Engine/Render/Passes/Billboard/CameraBillboardPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Billboard/CameraBillboardPass.cpp @@ -74,7 +74,7 @@ namespace Syn { } void CameraBillboardPass::PrepareFrame(const RenderContext& context) { - auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); VkExtent2D extent = { group->GetWidth(), group->GetHeight() }; _graphicsState.renderArea = extent; diff --git a/SynapseEngine/Engine/Render/Passes/Billboard/DirectionLightBillboardPass.cpp b/SynapseEngine/Engine/Render/Passes/Billboard/DirectionLightBillboardPass.cpp index 9bde82a6..bb78ee5c 100644 --- a/SynapseEngine/Engine/Render/Passes/Billboard/DirectionLightBillboardPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Billboard/DirectionLightBillboardPass.cpp @@ -73,7 +73,7 @@ namespace Syn { } void DirectionLightBillboardPass::PrepareFrame(const RenderContext& context) { - auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); VkExtent2D extent = { group->GetWidth(), group->GetHeight() }; _graphicsState.renderArea = extent; diff --git a/SynapseEngine/Engine/Render/Passes/Billboard/PointLightBillboardPass.cpp b/SynapseEngine/Engine/Render/Passes/Billboard/PointLightBillboardPass.cpp index 53838fed..65fe504b 100644 --- a/SynapseEngine/Engine/Render/Passes/Billboard/PointLightBillboardPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Billboard/PointLightBillboardPass.cpp @@ -73,7 +73,7 @@ namespace Syn { } void PointLightBillboardPass::PrepareFrame(const RenderContext& context) { - auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); VkExtent2D extent = { group->GetWidth(), group->GetHeight() }; _graphicsState.renderArea = extent; diff --git a/SynapseEngine/Engine/Render/Passes/Billboard/SpotLightBillboardPass.cpp b/SynapseEngine/Engine/Render/Passes/Billboard/SpotLightBillboardPass.cpp index 5ef8b81b..2e14a57d 100644 --- a/SynapseEngine/Engine/Render/Passes/Billboard/SpotLightBillboardPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Billboard/SpotLightBillboardPass.cpp @@ -73,7 +73,7 @@ namespace Syn { } void SpotLightBillboardPass::PrepareFrame(const RenderContext& context) { - auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); VkExtent2D extent = { group->GetWidth(), group->GetHeight() }; _graphicsState.renderArea = extent; diff --git a/SynapseEngine/Engine/Render/Passes/Culling/Geometry/GeometryMeshCullingPass.cpp b/SynapseEngine/Engine/Render/Passes/Culling/Geometry/GeometryMeshCullingPass.cpp index 84554b07..a7c97b3f 100644 --- a/SynapseEngine/Engine/Render/Passes/Culling/Geometry/GeometryMeshCullingPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Culling/Geometry/GeometryMeshCullingPass.cpp @@ -55,7 +55,7 @@ namespace Syn { auto animationManager = ServiceLocator::GetAnimationManager(); auto materialManager = ServiceLocator::GetMaterialManager(); - auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); uint32_t fIdx = context.frameIndex; @@ -69,7 +69,7 @@ namespace Syn { //Using prevous frame's depth pyramid! uint32_t prevFrameIndex = (context.frameIndex + context.framesInFlight - 1) % context.framesInFlight; - auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, prevFrameIndex); + auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, prevFrameIndex); auto depthPyramid = rtGroup->GetImage(RenderTargetNames::DepthPyramid); auto maxSampler = imageManager->GetSampler(SamplerNames::MaxReduction); diff --git a/SynapseEngine/Engine/Render/Passes/Culling/Geometry/GeometryModelCullingPass.cpp b/SynapseEngine/Engine/Render/Passes/Culling/Geometry/GeometryModelCullingPass.cpp index 85e41213..e799e49b 100644 --- a/SynapseEngine/Engine/Render/Passes/Culling/Geometry/GeometryModelCullingPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Culling/Geometry/GeometryModelCullingPass.cpp @@ -63,7 +63,7 @@ namespace Syn { auto materialManager = ServiceLocator::GetMaterialManager(); auto animationManager = ServiceLocator::GetAnimationManager(); - auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); uint32_t fIdx = context.frameIndex; @@ -77,7 +77,7 @@ namespace Syn { //Using prevous frame's depth pyramid! uint32_t prevFrameIndex = (context.frameIndex + context.framesInFlight - 1) % context.framesInFlight; - auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, prevFrameIndex); + auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, prevFrameIndex); auto depthPyramid = rtGroup->GetImage(RenderTargetNames::DepthPyramid); auto maxSampler = imageManager->GetSampler(SamplerNames::MaxReduction); diff --git a/SynapseEngine/Engine/Render/Passes/Culling/Geometry/GeometryMortonChunkCullingPass.cpp b/SynapseEngine/Engine/Render/Passes/Culling/Geometry/GeometryMortonChunkCullingPass.cpp index 0f04f186..8ed29aa1 100644 --- a/SynapseEngine/Engine/Render/Passes/Culling/Geometry/GeometryMortonChunkCullingPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Culling/Geometry/GeometryMortonChunkCullingPass.cpp @@ -48,7 +48,7 @@ namespace Syn { auto imageManager = ServiceLocator::GetImageManager(); uint32_t prevFrameIndex = (context.frameIndex + context.framesInFlight - 1) % context.framesInFlight; - auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, prevFrameIndex); + auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, prevFrameIndex); auto depthPyramid = rtGroup->GetImage(RenderTargetNames::DepthPyramid); auto maxSampler = imageManager->GetSampler(SamplerNames::MaxReduction); diff --git a/SynapseEngine/Engine/Render/Passes/Culling/Geometry/GeometryMortonModelCullingPass.cpp b/SynapseEngine/Engine/Render/Passes/Culling/Geometry/GeometryMortonModelCullingPass.cpp index 5bd7ebb3..bb50d576 100644 --- a/SynapseEngine/Engine/Render/Passes/Culling/Geometry/GeometryMortonModelCullingPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Culling/Geometry/GeometryMortonModelCullingPass.cpp @@ -48,7 +48,7 @@ namespace Syn { auto imageManager = ServiceLocator::GetImageManager(); uint32_t prevFrameIndex = (context.frameIndex + context.framesInFlight - 1) % context.framesInFlight; - auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, prevFrameIndex); + auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, prevFrameIndex); auto depthPyramid = rtGroup->GetImage(RenderTargetNames::DepthPyramid); auto maxSampler = imageManager->GetSampler(SamplerNames::MaxReduction); diff --git a/SynapseEngine/Engine/Render/Passes/Culling/Geometry/GeometryStaticChunkCullingPass.cpp b/SynapseEngine/Engine/Render/Passes/Culling/Geometry/GeometryStaticChunkCullingPass.cpp index c5e9dcb9..8815b1e6 100644 --- a/SynapseEngine/Engine/Render/Passes/Culling/Geometry/GeometryStaticChunkCullingPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Culling/Geometry/GeometryStaticChunkCullingPass.cpp @@ -54,7 +54,7 @@ namespace Syn { auto imageManager = ServiceLocator::GetImageManager(); uint32_t prevFrameIndex = (context.frameIndex + context.framesInFlight - 1) % context.framesInFlight; - auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, prevFrameIndex); + auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, prevFrameIndex); auto depthPyramid = rtGroup->GetImage(RenderTargetNames::DepthPyramid); auto maxSampler = imageManager->GetSampler(SamplerNames::MaxReduction); diff --git a/SynapseEngine/Engine/Render/Passes/Culling/Geometry/GeometryStaticModelCullingPass.cpp b/SynapseEngine/Engine/Render/Passes/Culling/Geometry/GeometryStaticModelCullingPass.cpp index 2e39baf2..eebacf5b 100644 --- a/SynapseEngine/Engine/Render/Passes/Culling/Geometry/GeometryStaticModelCullingPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Culling/Geometry/GeometryStaticModelCullingPass.cpp @@ -53,7 +53,7 @@ namespace Syn { auto imageManager = ServiceLocator::GetImageManager(); uint32_t prevFrameIndex = (context.frameIndex + context.framesInFlight - 1) % context.framesInFlight; - auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, prevFrameIndex); + auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, prevFrameIndex); auto depthPyramid = rtGroup->GetImage(RenderTargetNames::DepthPyramid); auto maxSampler = imageManager->GetSampler(SamplerNames::MaxReduction); diff --git a/SynapseEngine/Engine/Render/Passes/Culling/Geometry/GeometryWorkGraphCullingPass.cpp b/SynapseEngine/Engine/Render/Passes/Culling/Geometry/GeometryWorkGraphCullingPass.cpp index f8bb0d33..bfd858ef 100644 --- a/SynapseEngine/Engine/Render/Passes/Culling/Geometry/GeometryWorkGraphCullingPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Culling/Geometry/GeometryWorkGraphCullingPass.cpp @@ -164,7 +164,7 @@ namespace Syn { auto imageManager = ServiceLocator::GetImageManager(); uint32_t prevFrameIndex = (context.frameIndex + context.framesInFlight - 1) % context.framesInFlight; - auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, prevFrameIndex); + auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, prevFrameIndex); auto depthPyramid = rtGroup->GetImage(RenderTargetNames::DepthPyramid); auto maxSampler = imageManager->GetSampler(SamplerNames::MaxReduction); diff --git a/SynapseEngine/Engine/Render/Passes/Culling/PointLight/PointLightCullingPass.cpp b/SynapseEngine/Engine/Render/Passes/Culling/PointLight/PointLightCullingPass.cpp index 25eadd25..54552b4f 100644 --- a/SynapseEngine/Engine/Render/Passes/Culling/PointLight/PointLightCullingPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Culling/PointLight/PointLightCullingPass.cpp @@ -43,7 +43,7 @@ namespace Syn { if (_totalLightsToTest == 0) return; - auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); auto drawData = scene->GetSceneDrawData(); auto compManager = scene->GetComponentBufferManager(); uint32_t fIdx = context.frameIndex; @@ -57,7 +57,7 @@ namespace Syn { auto imageManager = ServiceLocator::GetImageManager(); uint32_t prevFrameIndex = (context.frameIndex + context.framesInFlight - 1) % context.framesInFlight; - auto prevRtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, prevFrameIndex); + auto prevRtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, prevFrameIndex); auto depthPyramid = prevRtGroup->GetImage(RenderTargetNames::DepthPyramid); auto maxSampler = imageManager->GetSampler(SamplerNames::MaxReduction); diff --git a/SynapseEngine/Engine/Render/Passes/Culling/SpotLight/SpotLightCullingPass.cpp b/SynapseEngine/Engine/Render/Passes/Culling/SpotLight/SpotLightCullingPass.cpp index 27f200f2..58781269 100644 --- a/SynapseEngine/Engine/Render/Passes/Culling/SpotLight/SpotLightCullingPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Culling/SpotLight/SpotLightCullingPass.cpp @@ -38,7 +38,7 @@ namespace Syn { if (_totalLightsToTest == 0) return; - auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); auto drawData = scene->GetSceneDrawData(); auto compManager = scene->GetComponentBufferManager(); uint32_t fIdx = context.frameIndex; @@ -52,7 +52,7 @@ namespace Syn { auto imageManager = ServiceLocator::GetImageManager(); uint32_t prevFrameIndex = (context.frameIndex + context.framesInFlight - 1) % context.framesInFlight; - auto prevRtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, prevFrameIndex); + auto prevRtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, prevFrameIndex); auto depthPyramid = prevRtGroup->GetImage(RenderTargetNames::DepthPyramid); auto maxSampler = imageManager->GetSampler(SamplerNames::MaxReduction); diff --git a/SynapseEngine/Engine/Render/Passes/Hiz/Geometry/GeometryHizDownsamplePass.cpp b/SynapseEngine/Engine/Render/Passes/Hiz/Geometry/GeometryHizDownsamplePass.cpp index 650a2e6c..f58eea90 100644 --- a/SynapseEngine/Engine/Render/Passes/Hiz/Geometry/GeometryHizDownsamplePass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Hiz/Geometry/GeometryHizDownsamplePass.cpp @@ -30,7 +30,7 @@ namespace Syn { } void GeometryHizDownsamplePass::PrepareFrame(const RenderContext& context) { - auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); auto depthPyramid = rtGroup->GetImage(RenderTargetNames::DepthPyramid); _imageTransitions.push_back({ @@ -46,7 +46,7 @@ namespace Syn { auto imageManager = ServiceLocator::GetImageManager(); auto sampler = imageManager->GetSampler(SamplerNames::NearestClampEdge); - auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); auto depthPyramid = rtGroup->GetImage(RenderTargetNames::DepthPyramid); uint32_t mipLevels = depthPyramid->GetConfig().mipLevels; diff --git a/SynapseEngine/Engine/Render/Passes/Hiz/Geometry/GeometryHizLinearPreparePass.cpp b/SynapseEngine/Engine/Render/Passes/Hiz/Geometry/GeometryHizLinearPreparePass.cpp index f8b6ba6f..cf580201 100644 --- a/SynapseEngine/Engine/Render/Passes/Hiz/Geometry/GeometryHizLinearPreparePass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Hiz/Geometry/GeometryHizLinearPreparePass.cpp @@ -33,7 +33,7 @@ namespace Syn { void GeometryHizLinearPreparePass::PrepareFrame(const RenderContext& context) { auto scene = context.scene; - auto currGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto currGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); auto depthOpaque = currGroup->GetImage(RenderTargetNames::OpaqueDepth); auto depthOpaqueTransparent = currGroup->GetImage(RenderTargetNames::TransparentDepth); @@ -71,7 +71,7 @@ namespace Syn { auto imageManager = ServiceLocator::GetImageManager(); - auto currGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto currGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); auto depthOpaque = currGroup->GetImage(RenderTargetNames::OpaqueDepth); auto depthOpaqueTransparent = currGroup->GetImage(RenderTargetNames::TransparentDepth); @@ -112,7 +112,7 @@ namespace Syn { uint32_t fIdx = context.frameIndex; auto compManager = scene->GetComponentBufferManager(); - auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, fIdx); + auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, fIdx); Vk::PushConstant pc; pc->frameGlobalContextBufferAddr = scene->GetSceneDrawData()->frameContextBuffer.GetAddress(fIdx); @@ -121,7 +121,7 @@ namespace Syn { } void GeometryHizLinearPreparePass::Dispatch(const RenderContext& context) { - auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); uint32_t width = rtGroup->GetWidth(); uint32_t height = rtGroup->GetHeight(); diff --git a/SynapseEngine/Engine/Render/Passes/Hiz/HizInitPass.cpp b/SynapseEngine/Engine/Render/Passes/Hiz/HizInitPass.cpp index f1bf985a..e6b3236b 100644 --- a/SynapseEngine/Engine/Render/Passes/Hiz/HizInitPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Hiz/HizInitPass.cpp @@ -7,7 +7,7 @@ namespace Syn { void HizInitPass::PrepareFrame(const RenderContext& context) { //Using prevous frame's depth pyramid! uint32_t prevFrameIndex = (context.frameIndex + context.framesInFlight - 1) % context.framesInFlight; - auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, prevFrameIndex); + auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, prevFrameIndex); auto drawData = context.scene->GetSceneDrawData(); { diff --git a/SynapseEngine/Engine/Render/Passes/PostProcess/Bloom/BloomCompositePass.cpp b/SynapseEngine/Engine/Render/Passes/PostProcess/Bloom/BloomCompositePass.cpp index d27218d8..739d8970 100644 --- a/SynapseEngine/Engine/Render/Passes/PostProcess/Bloom/BloomCompositePass.cpp +++ b/SynapseEngine/Engine/Render/Passes/PostProcess/Bloom/BloomCompositePass.cpp @@ -28,7 +28,7 @@ namespace Syn { } void BloomCompositePass::PrepareFrame(const RenderContext& context) { - auto rt = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto rt = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); auto main = rt->GetImage(RenderTargetNames::Main); auto bloom = rt->GetImage(RenderTargetNames::Bloom); @@ -48,7 +48,7 @@ namespace Syn { } void BloomCompositePass::BindDescriptors(const RenderContext& context) { - auto rt = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto rt = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); auto main = rt->GetImage(RenderTargetNames::Main); auto bloom = rt->GetImage(RenderTargetNames::Bloom); auto sampler = ServiceLocator::GetImageManager()->GetSampler(SamplerNames::LinearClampEdge); @@ -84,7 +84,7 @@ namespace Syn { } void BloomCompositePass::Dispatch(const RenderContext& context) { - auto rt = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto rt = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); uint32_t width = rt->GetWidth(); uint32_t height = rt->GetHeight(); diff --git a/SynapseEngine/Engine/Render/Passes/PostProcess/Bloom/BloomDownsamplePass.cpp b/SynapseEngine/Engine/Render/Passes/PostProcess/Bloom/BloomDownsamplePass.cpp index a9d3273d..b5426915 100644 --- a/SynapseEngine/Engine/Render/Passes/PostProcess/Bloom/BloomDownsamplePass.cpp +++ b/SynapseEngine/Engine/Render/Passes/PostProcess/Bloom/BloomDownsamplePass.cpp @@ -30,7 +30,7 @@ namespace Syn { } void BloomDownsamplePass::PrepareFrame(const RenderContext& context) { - auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); auto bloom = rtGroup->GetImage(RenderTargetNames::Bloom); _imageTransitions.push_back({ @@ -46,7 +46,7 @@ namespace Syn { auto imageManager = ServiceLocator::GetImageManager(); auto sampler = imageManager->GetSampler(SamplerNames::LinearClampEdge); - auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); auto bloom = rtGroup->GetImage(RenderTargetNames::Bloom); uint32_t mipLevels = bloom->GetConfig().mipLevels; diff --git a/SynapseEngine/Engine/Render/Passes/PostProcess/Bloom/BloomPrefilterPass.cpp b/SynapseEngine/Engine/Render/Passes/PostProcess/Bloom/BloomPrefilterPass.cpp index be623396..2a8f32b3 100644 --- a/SynapseEngine/Engine/Render/Passes/PostProcess/Bloom/BloomPrefilterPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/PostProcess/Bloom/BloomPrefilterPass.cpp @@ -28,7 +28,7 @@ namespace Syn { } void BloomPrefilterPass::PrepareFrame(const RenderContext& context) { - auto rt = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto rt = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); auto main = rt->GetImage(RenderTargetNames::Main); auto bloom = rt->GetImage(RenderTargetNames::Bloom); @@ -48,7 +48,7 @@ namespace Syn { } void BloomPrefilterPass::BindDescriptors(const RenderContext& context) { - auto rt = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto rt = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); auto main = rt->GetImage(RenderTargetNames::Main); auto bloom = rt->GetImage(RenderTargetNames::Bloom); auto sampler = ServiceLocator::GetImageManager()->GetSampler(SamplerNames::LinearClampEdge); @@ -81,7 +81,7 @@ namespace Syn { uint32_t fIdx = context.frameIndex; auto compManager = scene->GetComponentBufferManager(); - auto rt = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, fIdx); + auto rt = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, fIdx); uint32_t width = rt->GetWidth(); uint32_t height = rt->GetHeight(); @@ -95,7 +95,7 @@ namespace Syn { } void BloomPrefilterPass::Dispatch(const RenderContext& context) { - auto rt = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto rt = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); uint32_t width = rt->GetWidth(); uint32_t height = rt->GetHeight(); diff --git a/SynapseEngine/Engine/Render/Passes/PostProcess/Bloom/BloomUpsamplePass.cpp b/SynapseEngine/Engine/Render/Passes/PostProcess/Bloom/BloomUpsamplePass.cpp index d834cbfa..224688f2 100644 --- a/SynapseEngine/Engine/Render/Passes/PostProcess/Bloom/BloomUpsamplePass.cpp +++ b/SynapseEngine/Engine/Render/Passes/PostProcess/Bloom/BloomUpsamplePass.cpp @@ -30,7 +30,7 @@ namespace Syn { } void BloomUpsamplePass::PrepareFrame(const RenderContext& context) { - auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); auto bloom = rtGroup->GetImage(RenderTargetNames::Bloom); _imageTransitions.push_back({ @@ -46,7 +46,7 @@ namespace Syn { auto imageManager = ServiceLocator::GetImageManager(); auto sampler = imageManager->GetSampler(SamplerNames::LinearClampEdge); - auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); auto bloom = rtGroup->GetImage(RenderTargetNames::Bloom); uint32_t mipLevels = bloom->GetConfig().mipLevels; diff --git a/SynapseEngine/Engine/Render/Passes/PostProcess/Outline/SelectionOutlinePass.cpp b/SynapseEngine/Engine/Render/Passes/PostProcess/Outline/SelectionOutlinePass.cpp index 28d90fcb..d9cebb4e 100644 --- a/SynapseEngine/Engine/Render/Passes/PostProcess/Outline/SelectionOutlinePass.cpp +++ b/SynapseEngine/Engine/Render/Passes/PostProcess/Outline/SelectionOutlinePass.cpp @@ -55,7 +55,7 @@ namespace Syn void SelectionOutlinePass::PrepareFrame(const RenderContext& context) { - auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); _graphicsState.renderArea = VkExtent2D{ group->GetWidth(), group->GetHeight() }; auto mainImage = group->GetImage(RenderTargetNames::Main); @@ -119,7 +119,7 @@ namespace Syn { auto imageManager = ServiceLocator::GetImageManager(); - auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); auto entityTexture = rtGroup->GetImage(RenderTargetNames::EntityIndex); auto depthTexture = rtGroup->GetImage(RenderTargetNames::DepthPyramid); auto nearestSampler = imageManager->GetSampler(SamplerNames::NearestClampEdge); diff --git a/SynapseEngine/Engine/Render/Passes/PostProcess/SkySphere/SkySpherePass.cpp b/SynapseEngine/Engine/Render/Passes/PostProcess/SkySphere/SkySpherePass.cpp index c394fa8f..acd8a9cf 100644 --- a/SynapseEngine/Engine/Render/Passes/PostProcess/SkySphere/SkySpherePass.cpp +++ b/SynapseEngine/Engine/Render/Passes/PostProcess/SkySphere/SkySpherePass.cpp @@ -69,7 +69,7 @@ namespace Syn { } void SkySpherePass::PrepareFrame(const RenderContext& context) { - auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); VkExtent2D extent = { group->GetWidth(), group->GetHeight() }; _graphicsState.renderArea = extent; diff --git a/SynapseEngine/Engine/Render/Passes/Present/CompositePass.cpp b/SynapseEngine/Engine/Render/Passes/Present/CompositePass.cpp index 51f924c1..ec799726 100644 --- a/SynapseEngine/Engine/Render/Passes/Present/CompositePass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Present/CompositePass.cpp @@ -51,7 +51,7 @@ namespace Syn { void CompositePass::PrepareFrame(const RenderContext& context) { auto vkContext = ServiceLocator::GetVkContext(); auto swapChain = vkContext->GetSwapChain(); - auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); auto inputImage = group->GetImage(RenderTargetNames::Main); auto swapchainImage = swapChain->GetImage(context.swapchainImageIndex); @@ -92,7 +92,7 @@ namespace Syn { void CompositePass::BindDescriptors(const RenderContext& context) { auto imageManager = ServiceLocator::GetImageManager(); - auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); auto inputImage = group->GetImage(RenderTargetNames::Main); auto sampler = imageManager->GetSampler(SamplerNames::NearestClampEdge); diff --git a/SynapseEngine/Engine/Render/Passes/Present/GuiPass.cpp b/SynapseEngine/Engine/Render/Passes/Present/GuiPass.cpp index ad6d8f88..6c128bba 100644 --- a/SynapseEngine/Engine/Render/Passes/Present/GuiPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Present/GuiPass.cpp @@ -21,7 +21,7 @@ namespace Syn auto swapchainImage = swapChain->GetImage(context.swapchainImageIndex); VkExtent2D extent = { swapchainImage->GetExtent().width, swapchainImage->GetExtent().height }; - auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); std::vector debugTargets = { RenderTargetNames::Main, diff --git a/SynapseEngine/Engine/Render/Passes/Present/PresentationTransitionPass.cpp b/SynapseEngine/Engine/Render/Passes/Present/PresentationTransitionPass.cpp index 79b42b9d..9ea2d644 100644 --- a/SynapseEngine/Engine/Render/Passes/Present/PresentationTransitionPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Present/PresentationTransitionPass.cpp @@ -4,7 +4,7 @@ namespace Syn { void PresentationTransitionPass::PrepareFrame(const RenderContext& context) { - auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); std::vector gBufferTargets = { RenderTargetNames::Main, diff --git a/SynapseEngine/Engine/Render/Passes/Setup/GlobalFrameSetupPass.cpp b/SynapseEngine/Engine/Render/Passes/Setup/GlobalFrameSetupPass.cpp index 72493f82..2818d4c4 100644 --- a/SynapseEngine/Engine/Render/Passes/Setup/GlobalFrameSetupPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Setup/GlobalFrameSetupPass.cpp @@ -26,7 +26,7 @@ namespace Syn { auto drawData = scene->GetSceneDrawData(); auto settings = scene->GetSettings(); auto compManager = scene->GetComponentBufferManager(); - auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); uint32_t fIdx = context.frameIndex; uint32_t width = rtGroup->GetWidth(); diff --git a/SynapseEngine/Engine/Render/Passes/Shading/Common/DepthCopyPass.cpp b/SynapseEngine/Engine/Render/Passes/Shading/Common/DepthCopyPass.cpp index 49a32eee..73ca138c 100644 --- a/SynapseEngine/Engine/Render/Passes/Shading/Common/DepthCopyPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Shading/Common/DepthCopyPass.cpp @@ -4,7 +4,7 @@ namespace Syn { void DepthCopyPass::PrepareFrame(const RenderContext& context) { - auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); if (!group) return; auto srcDepth = group->GetImage(RenderTargetNames::OpaqueDepth); @@ -29,7 +29,7 @@ namespace Syn } void DepthCopyPass::Transfer(const RenderContext& context) { - auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); if (!group) return; auto srcDepth = group->GetImage(RenderTargetNames::OpaqueDepth); diff --git a/SynapseEngine/Engine/Render/Passes/Shading/Common/OpaqueInitPass.cpp b/SynapseEngine/Engine/Render/Passes/Shading/Common/OpaqueInitPass.cpp index bac9851e..605d2ac5 100644 --- a/SynapseEngine/Engine/Render/Passes/Shading/Common/OpaqueInitPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Shading/Common/OpaqueInitPass.cpp @@ -4,7 +4,7 @@ namespace Syn { void OpaqueInitPass::PrepareFrame(const RenderContext& context) { - auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); if (!group) return; VkExtent2D extent = { group->GetWidth(), group->GetHeight() }; diff --git a/SynapseEngine/Engine/Render/Passes/Shading/Common/TransparentInitPass.cpp b/SynapseEngine/Engine/Render/Passes/Shading/Common/TransparentInitPass.cpp index dc69a1a6..0748ff50 100644 --- a/SynapseEngine/Engine/Render/Passes/Shading/Common/TransparentInitPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Shading/Common/TransparentInitPass.cpp @@ -4,7 +4,7 @@ namespace Syn { void TransparentInitPass::PrepareFrame(const RenderContext& context) { - auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); VkExtent2D extent = { group->GetWidth(), group->GetHeight() }; _graphicsState.renderArea = extent; diff --git a/SynapseEngine/Engine/Render/Passes/Shading/Deferred/GBuffer/MeshletOpaqueDeferredPass.cpp b/SynapseEngine/Engine/Render/Passes/Shading/Deferred/GBuffer/MeshletOpaqueDeferredPass.cpp index 03277c5b..cdd9afd6 100644 --- a/SynapseEngine/Engine/Render/Passes/Shading/Deferred/GBuffer/MeshletOpaqueDeferredPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Shading/Deferred/GBuffer/MeshletOpaqueDeferredPass.cpp @@ -94,7 +94,7 @@ namespace Syn { } void MeshletOpaqueDeferredPass::PrepareFrame(const RenderContext& context) { - auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); VkExtent2D extent = { group->GetWidth(), group->GetHeight() }; _graphicsState.renderArea = extent; @@ -137,7 +137,7 @@ namespace Syn { auto drawData = scene->GetSceneDrawData(); auto componentBufferManager = scene->GetComponentBufferManager(); - auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); auto animationManager = ServiceLocator::GetAnimationManager(); @@ -157,7 +157,7 @@ namespace Syn { //Using prevous frame's depth pyramid! uint32_t prevFrameIndex = (context.frameIndex + context.framesInFlight - 1) % context.framesInFlight; - auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, prevFrameIndex); + auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, prevFrameIndex); auto depthPyramid = rtGroup->GetImage(RenderTargetNames::DepthPyramid); auto maxSampler = imageManager->GetSampler(SamplerNames::MaxReduction); diff --git a/SynapseEngine/Engine/Render/Passes/Shading/Deferred/GBuffer/OpaqueDeferredTransitionPass.cpp b/SynapseEngine/Engine/Render/Passes/Shading/Deferred/GBuffer/OpaqueDeferredTransitionPass.cpp index 319b67a1..502df2dd 100644 --- a/SynapseEngine/Engine/Render/Passes/Shading/Deferred/GBuffer/OpaqueDeferredTransitionPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Shading/Deferred/GBuffer/OpaqueDeferredTransitionPass.cpp @@ -8,7 +8,7 @@ namespace Syn { } void OpaqueDeferredTransitionPass::PrepareFrame(const RenderContext& context) { - auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); std::vector targets = { RenderTargetNames::ColorMetallic, diff --git a/SynapseEngine/Engine/Render/Passes/Shading/Deferred/GBuffer/TraditionalOpaqueDeferredPass.cpp b/SynapseEngine/Engine/Render/Passes/Shading/Deferred/GBuffer/TraditionalOpaqueDeferredPass.cpp index dc6ee974..6c357c89 100644 --- a/SynapseEngine/Engine/Render/Passes/Shading/Deferred/GBuffer/TraditionalOpaqueDeferredPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Shading/Deferred/GBuffer/TraditionalOpaqueDeferredPass.cpp @@ -87,7 +87,7 @@ namespace Syn { } void TraditionalOpaqueDeferredPass::PrepareFrame(const RenderContext& context) { - auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); VkExtent2D extent = { group->GetWidth(), group->GetHeight() }; _graphicsState.renderArea = extent; diff --git a/SynapseEngine/Engine/Render/Passes/Shading/Deferred/Lighting/DeferredDirectionLightPass.cpp b/SynapseEngine/Engine/Render/Passes/Shading/Deferred/Lighting/DeferredDirectionLightPass.cpp index 56af814f..d0977bed 100644 --- a/SynapseEngine/Engine/Render/Passes/Shading/Deferred/Lighting/DeferredDirectionLightPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Shading/Deferred/Lighting/DeferredDirectionLightPass.cpp @@ -59,7 +59,7 @@ namespace Syn { } void DeferredDirectionLightPass::PrepareFrame(const RenderContext& context) { - auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); VkExtent2D extent = { group->GetWidth(), group->GetHeight() }; _graphicsState.renderArea = extent; @@ -81,7 +81,7 @@ namespace Syn { void DeferredDirectionLightPass::PushConstants(const RenderContext& context) { auto scene = context.scene; auto bufferManager = scene->GetComponentBufferManager(); - auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); uint32_t fIdx = context.frameIndex; Vk::PushConstant pc; @@ -90,7 +90,7 @@ namespace Syn { } void DeferredDirectionLightPass::BindDescriptors(const RenderContext& context) { - auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); auto imageManager = ServiceLocator::GetImageManager(); auto sampler = imageManager->GetSampler(SamplerNames::NearestClampEdge)->Handle(); auto ssaoSampler = imageManager->GetSampler(SamplerNames::LinearClampEdge)->Handle(); diff --git a/SynapseEngine/Engine/Render/Passes/Shading/Deferred/Lighting/DeferredEmissiveAoPass.cpp b/SynapseEngine/Engine/Render/Passes/Shading/Deferred/Lighting/DeferredEmissiveAoPass.cpp index a1a2dea3..91683464 100644 --- a/SynapseEngine/Engine/Render/Passes/Shading/Deferred/Lighting/DeferredEmissiveAoPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Shading/Deferred/Lighting/DeferredEmissiveAoPass.cpp @@ -62,7 +62,7 @@ namespace Syn { } void DeferredEmissiveAoPass::PrepareFrame(const RenderContext& context) { - auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); VkExtent2D extent = { group->GetWidth(), group->GetHeight() }; _graphicsState.renderArea = extent; @@ -94,7 +94,7 @@ namespace Syn { } void DeferredEmissiveAoPass::BindDescriptors(const RenderContext& context) { - auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); auto imageManager = ServiceLocator::GetImageManager(); auto nearestSampler = imageManager->GetSampler(SamplerNames::NearestClampEdge)->Handle(); auto ssaoSampler = imageManager->GetSampler(SamplerNames::LinearClampEdge)->Handle(); diff --git a/SynapseEngine/Engine/Render/Passes/Shading/Deferred/Lighting/DeferredLightTransitionPass.cpp b/SynapseEngine/Engine/Render/Passes/Shading/Deferred/Lighting/DeferredLightTransitionPass.cpp index 9d02eb21..3c3f34d3 100644 --- a/SynapseEngine/Engine/Render/Passes/Shading/Deferred/Lighting/DeferredLightTransitionPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Shading/Deferred/Lighting/DeferredLightTransitionPass.cpp @@ -10,7 +10,7 @@ namespace Syn { } void DeferredLightTransitionPass::PrepareFrame(const RenderContext& context) { - auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); std::vector gBufferTargets = { RenderTargetNames::ColorMetallic, diff --git a/SynapseEngine/Engine/Render/Passes/Shading/Deferred/Lighting/DeferredPointLightPass.cpp b/SynapseEngine/Engine/Render/Passes/Shading/Deferred/Lighting/DeferredPointLightPass.cpp index 11d1fc70..47ef3420 100644 --- a/SynapseEngine/Engine/Render/Passes/Shading/Deferred/Lighting/DeferredPointLightPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Shading/Deferred/Lighting/DeferredPointLightPass.cpp @@ -67,7 +67,7 @@ namespace Syn { } void DeferredPointLightPass::PrepareFrame(const RenderContext& context) { - auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); VkExtent2D extent = { group->GetWidth(), group->GetHeight() }; _graphicsState.renderArea = extent; @@ -93,7 +93,7 @@ namespace Syn { auto scene = context.scene; auto bufferManager = scene->GetComponentBufferManager(); auto modelManager = ServiceLocator::GetModelManager(); - auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); uint32_t fIdx = context.frameIndex; auto cube = modelManager->GetResource(MeshSourceNames::Cube); @@ -106,7 +106,7 @@ namespace Syn { } void DeferredPointLightPass::BindDescriptors(const RenderContext& context) { - auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); auto imageManager = ServiceLocator::GetImageManager(); auto sampler = imageManager->GetSampler(SamplerNames::NearestClampEdge)->Handle(); auto ssaoSampler = imageManager->GetSampler(SamplerNames::LinearClampEdge)->Handle(); diff --git a/SynapseEngine/Engine/Render/Passes/Shading/Deferred/Lighting/DeferredSpotLightPass.cpp b/SynapseEngine/Engine/Render/Passes/Shading/Deferred/Lighting/DeferredSpotLightPass.cpp index 95d2ae8b..cf82d411 100644 --- a/SynapseEngine/Engine/Render/Passes/Shading/Deferred/Lighting/DeferredSpotLightPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Shading/Deferred/Lighting/DeferredSpotLightPass.cpp @@ -67,7 +67,7 @@ namespace Syn { } void DeferredSpotLightPass::PrepareFrame(const RenderContext& context) { - auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); VkExtent2D extent = { group->GetWidth(), group->GetHeight() }; _graphicsState.renderArea = extent; @@ -93,7 +93,7 @@ namespace Syn { auto scene = context.scene; auto bufferManager = scene->GetComponentBufferManager(); auto modelManager = ServiceLocator::GetModelManager(); - auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); uint32_t fIdx = context.frameIndex; auto pyramid = modelManager->GetResource(MeshSourceNames::ProxyPyramid); @@ -106,7 +106,7 @@ namespace Syn { } void DeferredSpotLightPass::BindDescriptors(const RenderContext& context) { - auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); auto imageManager = ServiceLocator::GetImageManager(); auto sampler = imageManager->GetSampler(SamplerNames::NearestClampEdge)->Handle(); auto ssaoSampler = imageManager->GetSampler(SamplerNames::LinearClampEdge)->Handle(); diff --git a/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/Clustering/ClusterPointLightCountPass.cpp b/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/Clustering/ClusterPointLightCountPass.cpp index 28b241d4..d448a9b5 100644 --- a/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/Clustering/ClusterPointLightCountPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/Clustering/ClusterPointLightCountPass.cpp @@ -27,7 +27,7 @@ namespace Syn { auto scene = context.scene; auto drawData = scene->GetSceneDrawData(); auto compManager = scene->GetComponentBufferManager(); - auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); uint32_t fIdx = context.frameIndex; diff --git a/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/Clustering/ClusterPointLightWritePass.cpp b/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/Clustering/ClusterPointLightWritePass.cpp index 33eb99dd..87d415a7 100644 --- a/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/Clustering/ClusterPointLightWritePass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/Clustering/ClusterPointLightWritePass.cpp @@ -28,7 +28,7 @@ namespace Syn { auto scene = context.scene; auto drawData = scene->GetSceneDrawData(); auto compManager = scene->GetComponentBufferManager(); - auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); uint32_t fIdx = context.frameIndex; diff --git a/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/Clustering/ClusterSetupPass.cpp b/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/Clustering/ClusterSetupPass.cpp index 88ba0337..3c2f1884 100644 --- a/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/Clustering/ClusterSetupPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/Clustering/ClusterSetupPass.cpp @@ -34,7 +34,7 @@ namespace Syn { auto scene = context.scene; auto drawData = scene->GetSceneDrawData(); auto compManager = scene->GetComponentBufferManager(); - auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); uint32_t fIdx = context.frameIndex; @@ -48,7 +48,7 @@ namespace Syn { void ClusterSetupPass::BindDescriptors(const RenderContext& context) { auto imageManager = ServiceLocator::GetImageManager(); - auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); //Using current frame's depth pyramid! auto depthPyramid = rtGroup->GetImage(RenderTargetNames::DepthPyramid); @@ -67,7 +67,7 @@ namespace Syn { void ClusterSetupPass::Dispatch(const RenderContext& context) { auto scene = context.scene; - auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); auto drawData = context.scene->GetSceneDrawData(); auto compManager = scene->GetComponentBufferManager(); uint32_t fIdx = context.frameIndex; diff --git a/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/Clustering/ClusterSpotLightCountPass.cpp b/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/Clustering/ClusterSpotLightCountPass.cpp index bc4f5d4f..44c7f317 100644 --- a/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/Clustering/ClusterSpotLightCountPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/Clustering/ClusterSpotLightCountPass.cpp @@ -28,7 +28,7 @@ namespace Syn auto scene = context.scene; auto drawData = scene->GetSceneDrawData(); auto compManager = scene->GetComponentBufferManager(); - auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); uint32_t fIdx = context.frameIndex; diff --git a/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/Clustering/ClusterSpotLightWritePass.cpp b/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/Clustering/ClusterSpotLightWritePass.cpp index 852f08b5..052a4dcd 100644 --- a/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/Clustering/ClusterSpotLightWritePass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/Clustering/ClusterSpotLightWritePass.cpp @@ -27,7 +27,7 @@ namespace Syn { auto scene = context.scene; auto drawData = scene->GetSceneDrawData(); auto compManager = scene->GetComponentBufferManager(); - auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); uint32_t fIdx = context.frameIndex; diff --git a/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/DepthPrepass/MeshletOpaqueDepthPrepass.cpp b/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/DepthPrepass/MeshletOpaqueDepthPrepass.cpp index fa106b5f..526fd3bc 100644 --- a/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/DepthPrepass/MeshletOpaqueDepthPrepass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/DepthPrepass/MeshletOpaqueDepthPrepass.cpp @@ -91,7 +91,7 @@ namespace Syn { } void MeshletOpaqueDepthPrepass::PrepareFrame(const RenderContext& context) { - auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); VkExtent2D extent = { group->GetWidth(), group->GetHeight() }; _graphicsState.renderArea = extent; @@ -130,7 +130,7 @@ namespace Syn { auto materialManager = ServiceLocator::GetMaterialManager(); auto drawData = scene->GetSceneDrawData(); auto componentBufferManager = scene->GetComponentBufferManager(); - auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); auto animationManager = ServiceLocator::GetAnimationManager(); uint32_t fIdx = context.frameIndex; @@ -149,7 +149,7 @@ namespace Syn { //Using prevous frame's depth pyramid! uint32_t prevFrameIndex = (context.frameIndex + context.framesInFlight - 1) % context.framesInFlight; - auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, prevFrameIndex); + auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, prevFrameIndex); auto depthPyramid = rtGroup->GetImage(RenderTargetNames::DepthPyramid); auto maxSampler = imageManager->GetSampler(SamplerNames::MaxReduction); diff --git a/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/DepthPrepass/MeshletTransparentDepthPrepass.cpp b/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/DepthPrepass/MeshletTransparentDepthPrepass.cpp index c92786c8..d078a0fd 100644 --- a/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/DepthPrepass/MeshletTransparentDepthPrepass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/DepthPrepass/MeshletTransparentDepthPrepass.cpp @@ -79,7 +79,7 @@ namespace Syn { } void MeshletTransparentDepthPrepass::PrepareFrame(const RenderContext& context) { - auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); VkExtent2D extent = { group->GetWidth(), group->GetHeight() }; _graphicsState.renderArea = extent; @@ -120,7 +120,7 @@ namespace Syn { auto materialManager = ServiceLocator::GetMaterialManager(); auto drawData = scene->GetSceneDrawData(); auto componentBufferManager = scene->GetComponentBufferManager(); - auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); auto animationManager = ServiceLocator::GetAnimationManager(); uint32_t fIdx = context.frameIndex; @@ -140,7 +140,7 @@ namespace Syn { //Using prevous frame's depth pyramid! uint32_t prevFrameIndex = (context.frameIndex + context.framesInFlight - 1) % context.framesInFlight; - auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, prevFrameIndex); + auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, prevFrameIndex); auto depthPyramid = rtGroup->GetImage(RenderTargetNames::DepthPyramid); auto maxSampler = imageManager->GetSampler(SamplerNames::MaxReduction); diff --git a/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/DepthPrepass/OpaqueDepthTransitionPrepass.cpp b/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/DepthPrepass/OpaqueDepthTransitionPrepass.cpp index 1658a622..f7aa2bb7 100644 --- a/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/DepthPrepass/OpaqueDepthTransitionPrepass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/DepthPrepass/OpaqueDepthTransitionPrepass.cpp @@ -9,7 +9,7 @@ namespace Syn { } void OpaqueDepthTransitionPrepass::PrepareFrame(const RenderContext& context) { - auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); if (auto entityImg = group->GetImage(RenderTargetNames::EntityIndex)) { if (entityImg->GetLayout() != VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL) { diff --git a/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/DepthPrepass/TraditionalOpaqueDepthPrepass.cpp b/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/DepthPrepass/TraditionalOpaqueDepthPrepass.cpp index 06f76b80..50455c14 100644 --- a/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/DepthPrepass/TraditionalOpaqueDepthPrepass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/DepthPrepass/TraditionalOpaqueDepthPrepass.cpp @@ -85,7 +85,7 @@ namespace Syn { } void TraditionalOpaqueDepthPrepass::PrepareFrame(const RenderContext& context) { - auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); VkExtent2D extent = { group->GetWidth(), group->GetHeight() }; _graphicsState.renderArea = extent; diff --git a/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/DepthPrepass/TraditionalTransparentDepthPrepass.cpp b/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/DepthPrepass/TraditionalTransparentDepthPrepass.cpp index 1acdd52b..d52a68e8 100644 --- a/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/DepthPrepass/TraditionalTransparentDepthPrepass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/DepthPrepass/TraditionalTransparentDepthPrepass.cpp @@ -73,7 +73,7 @@ namespace Syn { } void TraditionalTransparentDepthPrepass::PrepareFrame(const RenderContext& context) { - auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); VkExtent2D extent = { group->GetWidth(), group->GetHeight() }; _graphicsState.renderArea = extent; diff --git a/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/DepthPrepass/TransparentDepthTransitionPrepass.cpp b/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/DepthPrepass/TransparentDepthTransitionPrepass.cpp index ecb1fac1..2e2f7e11 100644 --- a/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/DepthPrepass/TransparentDepthTransitionPrepass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/DepthPrepass/TransparentDepthTransitionPrepass.cpp @@ -3,7 +3,7 @@ namespace Syn { void TransparentDepthTransitionPrepass::PrepareFrame(const RenderContext& context) { - auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); if (auto entityImg = group->GetImage(RenderTargetNames::EntityIndex)) { if (entityImg->GetLayout() != VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL) { diff --git a/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/Lighting/MeshletOpaqueForwardPass.cpp b/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/Lighting/MeshletOpaqueForwardPass.cpp index 40102b2e..d593ff62 100644 --- a/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/Lighting/MeshletOpaqueForwardPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/Lighting/MeshletOpaqueForwardPass.cpp @@ -92,7 +92,7 @@ namespace Syn { } void MeshletOpaqueForwardPass::PrepareFrame(const RenderContext& context) { - auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); VkExtent2D extent = { group->GetWidth(), group->GetHeight() }; _graphicsState.renderArea = extent; @@ -146,8 +146,8 @@ namespace Syn { //Using prevous frame's depth pyramid! uint fIdx = context.frameIndex; uint32_t prevFrameIndex = (context.frameIndex + context.framesInFlight - 1) % context.framesInFlight; - auto prevRtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, prevFrameIndex); - auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, fIdx); + auto prevRtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, prevFrameIndex); + auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, fIdx); auto depthPyramid = prevRtGroup->GetImage(RenderTargetNames::DepthPyramid); auto maxSampler = imageManager->GetSampler(SamplerNames::MaxReduction); diff --git a/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/Lighting/OpaqueForwardTransitionPass.cpp b/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/Lighting/OpaqueForwardTransitionPass.cpp index bb4adb9b..b66095cf 100644 --- a/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/Lighting/OpaqueForwardTransitionPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/Lighting/OpaqueForwardTransitionPass.cpp @@ -9,7 +9,7 @@ namespace Syn { } void OpaqueForwardTransitionPass::PrepareFrame(const RenderContext& context) { - auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); if (auto mainImg = group->GetImage(RenderTargetNames::Main)) { if (mainImg->GetLayout() != VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL) { diff --git a/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/Lighting/TraditionalOpaqueForwardPass.cpp b/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/Lighting/TraditionalOpaqueForwardPass.cpp index 744363a7..1c6c59bf 100644 --- a/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/Lighting/TraditionalOpaqueForwardPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/Lighting/TraditionalOpaqueForwardPass.cpp @@ -89,7 +89,7 @@ namespace Syn { } void TraditionalOpaqueForwardPass::PrepareFrame(const RenderContext& context) { - auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); VkExtent2D extent = { group->GetWidth(), group->GetHeight() }; _graphicsState.renderArea = extent; @@ -139,7 +139,7 @@ namespace Syn { auto imageManager = ServiceLocator::GetImageManager(); uint fIdx = context.frameIndex; - auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, fIdx); + auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, fIdx); auto drawData = context.scene->GetSceneDrawData(); auto ssaoTexture = rtGroup->GetImage(RenderTargetNames::SsaoAo); diff --git a/SynapseEngine/Engine/Render/Passes/Shading/Visibility/DebugVisibilityPass.cpp b/SynapseEngine/Engine/Render/Passes/Shading/Visibility/DebugVisibilityPass.cpp index 8bfee46a..17c25b0f 100644 --- a/SynapseEngine/Engine/Render/Passes/Shading/Visibility/DebugVisibilityPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Shading/Visibility/DebugVisibilityPass.cpp @@ -70,7 +70,7 @@ namespace Syn void DebugVisibilityPass::PrepareFrame(const RenderContext& context) { - auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); VkExtent2D extent = { group->GetWidth(), group->GetHeight() }; _graphicsState.renderArea = extent; @@ -122,7 +122,7 @@ namespace Syn void DebugVisibilityPass::BindDescriptors(const RenderContext& context) { auto imageManager = ServiceLocator::GetImageManager(); - auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); auto visibilityTexture = rtGroup->GetImage(RenderTargetNames::EntityIndex); auto nearestSampler = imageManager->GetSampler(SamplerNames::NearestClampEdge); diff --git a/SynapseEngine/Engine/Render/Passes/Shading/Wboit/MeshletTransparentForwardPass.cpp b/SynapseEngine/Engine/Render/Passes/Shading/Wboit/MeshletTransparentForwardPass.cpp index 0fadb4e7..0bed9ab7 100644 --- a/SynapseEngine/Engine/Render/Passes/Shading/Wboit/MeshletTransparentForwardPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Shading/Wboit/MeshletTransparentForwardPass.cpp @@ -99,7 +99,7 @@ namespace Syn { } void MeshletTransparentForwardPass::PrepareFrame(const RenderContext& context) { - auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); VkExtent2D extent = { group->GetWidth(), group->GetHeight() }; _graphicsState.renderArea = extent; @@ -140,7 +140,7 @@ namespace Syn { auto drawData = scene->GetSceneDrawData(); auto componentBufferManager = scene->GetComponentBufferManager(); - auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); auto animationManager = ServiceLocator::GetAnimationManager(); uint32_t fIdx = context.frameIndex; @@ -162,7 +162,7 @@ namespace Syn { //Using prevous frame's depth pyramid! uint fIdx = context.frameIndex; uint32_t prevFrameIndex = (context.frameIndex + context.framesInFlight - 1) % context.framesInFlight; - auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, prevFrameIndex); + auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, prevFrameIndex); auto depthPyramid = rtGroup->GetImage(RenderTargetNames::DepthPyramid); auto maxSampler = imageManager->GetSampler(SamplerNames::MaxReduction); diff --git a/SynapseEngine/Engine/Render/Passes/Shading/Wboit/TraditionalTransparentForwardPass.cpp b/SynapseEngine/Engine/Render/Passes/Shading/Wboit/TraditionalTransparentForwardPass.cpp index a9e06fc1..c4cb697a 100644 --- a/SynapseEngine/Engine/Render/Passes/Shading/Wboit/TraditionalTransparentForwardPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Shading/Wboit/TraditionalTransparentForwardPass.cpp @@ -95,7 +95,7 @@ namespace Syn { } void TraditionalTransparentForwardPass::PrepareFrame(const RenderContext& context) { - auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); VkExtent2D extent = { group->GetWidth(), group->GetHeight() }; _graphicsState.renderArea = extent; @@ -154,7 +154,7 @@ namespace Syn { auto imageManager = ServiceLocator::GetImageManager(); uint fIdx = context.frameIndex; - auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, fIdx); + auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, fIdx); auto drawData = context.scene->GetSceneDrawData(); auto dirShadowAtlas = drawData->DirectionLightShadow.shadowAtlas[fIdx].get(); diff --git a/SynapseEngine/Engine/Render/Passes/Shading/Wboit/TransparentCompositePass.cpp b/SynapseEngine/Engine/Render/Passes/Shading/Wboit/TransparentCompositePass.cpp index eb4c24af..ac428d76 100644 --- a/SynapseEngine/Engine/Render/Passes/Shading/Wboit/TransparentCompositePass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Shading/Wboit/TransparentCompositePass.cpp @@ -56,7 +56,7 @@ namespace Syn } void TransparentCompositePass::PrepareFrame(const RenderContext& context) { - auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); VkExtent2D extent = { group->GetWidth(), group->GetHeight() }; _graphicsState.renderArea = extent; @@ -76,7 +76,7 @@ namespace Syn } void TransparentCompositePass::BindDescriptors(const RenderContext& context) { - auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); auto imageManager = ServiceLocator::GetImageManager(); auto linearSampler = imageManager->GetSampler(SamplerNames::LinearClampEdge)->Handle(); diff --git a/SynapseEngine/Engine/Render/Passes/Shading/Wboit/TransparentCompositeTransitionPass.cpp b/SynapseEngine/Engine/Render/Passes/Shading/Wboit/TransparentCompositeTransitionPass.cpp index de430a69..5609c00f 100644 --- a/SynapseEngine/Engine/Render/Passes/Shading/Wboit/TransparentCompositeTransitionPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Shading/Wboit/TransparentCompositeTransitionPass.cpp @@ -9,7 +9,7 @@ namespace Syn { } void TransparentCompositeTransitionPass::PrepareFrame(const RenderContext& context) { - auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); std::vector wboitTargets = { RenderTargetNames::TransparentAccum, diff --git a/SynapseEngine/Engine/Render/Passes/Shading/Wboit/TransparentForwardTransitionPass.cpp b/SynapseEngine/Engine/Render/Passes/Shading/Wboit/TransparentForwardTransitionPass.cpp index 5b1e3747..208e4e1b 100644 --- a/SynapseEngine/Engine/Render/Passes/Shading/Wboit/TransparentForwardTransitionPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Shading/Wboit/TransparentForwardTransitionPass.cpp @@ -8,7 +8,7 @@ namespace Syn { } void TransparentForwardTransitionPass::PrepareFrame(const RenderContext& context) { - auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); std::vector targets = { RenderTargetNames::TransparentAccum, diff --git a/SynapseEngine/Engine/Render/Passes/Ssao/SsaoBlurPass.cpp b/SynapseEngine/Engine/Render/Passes/Ssao/SsaoBlurPass.cpp index 19b96788..d9f69379 100644 --- a/SynapseEngine/Engine/Render/Passes/Ssao/SsaoBlurPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Ssao/SsaoBlurPass.cpp @@ -27,7 +27,7 @@ namespace Syn { } void SsaoBlurPass::PrepareFrame(const RenderContext& context) { - auto currGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto currGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); auto ssaoAo = currGroup->GetImage(RenderTargetNames::SsaoAo); auto ssaoAoInt = currGroup->GetImage(RenderTargetNames::SsaoAoIntermediate); @@ -57,7 +57,7 @@ namespace Syn { } void SsaoBlurPass::BindDescriptors(const RenderContext& context) { - auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); auto imageManager = ServiceLocator::GetImageManager(); auto depthPyramid = rtGroup->GetImage(RenderTargetNames::DepthPyramid); @@ -84,7 +84,7 @@ namespace Syn { } void SsaoBlurPass::Dispatch(const RenderContext& context) { - auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); auto imageManager = ServiceLocator::GetImageManager(); auto ssaoAo = rtGroup->GetImage(RenderTargetNames::SsaoAo); diff --git a/SynapseEngine/Engine/Render/Passes/Ssao/SsaoInitPass.cpp b/SynapseEngine/Engine/Render/Passes/Ssao/SsaoInitPass.cpp index b90272cd..f0ad50a0 100644 --- a/SynapseEngine/Engine/Render/Passes/Ssao/SsaoInitPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Ssao/SsaoInitPass.cpp @@ -4,7 +4,7 @@ namespace Syn { void SsaoInitPass::PrepareFrame(const RenderContext& context) { - auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); if (!group) return; auto depthPyramid = group->GetImage(RenderTargetNames::DepthPyramid); diff --git a/SynapseEngine/Engine/Render/Passes/Ssao/SsaoPass.cpp b/SynapseEngine/Engine/Render/Passes/Ssao/SsaoPass.cpp index ccf8580b..e04cd219 100644 --- a/SynapseEngine/Engine/Render/Passes/Ssao/SsaoPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Ssao/SsaoPass.cpp @@ -36,7 +36,7 @@ namespace Syn { } void SsaoPass::BindDescriptors(const RenderContext& context) { - auto currGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto currGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); auto imageManager = ServiceLocator::GetImageManager(); auto noiseTexture = imageManager->GetResource(ImageNames::SsaoNoiseTexture); @@ -73,7 +73,7 @@ namespace Syn { void SsaoPass::PushConstants(const RenderContext& context) { auto scene = context.scene; uint32_t fIdx = context.frameIndex; - auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, fIdx); + auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, fIdx); auto imageManager = ServiceLocator::GetImageManager(); auto noiseTexture = imageManager->GetResource(ImageNames::SsaoNoiseTexture); @@ -91,7 +91,7 @@ namespace Syn { } void SsaoPass::Dispatch(const RenderContext& context) { - auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto rtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); uint32_t width = rtGroup->GetWidth(); uint32_t height = rtGroup->GetHeight(); diff --git a/SynapseEngine/Engine/Render/Passes/Wireframe/Chunk/MortonChunkAabbWireframePass.cpp b/SynapseEngine/Engine/Render/Passes/Wireframe/Chunk/MortonChunkAabbWireframePass.cpp index b8aa7a0f..4209766e 100644 --- a/SynapseEngine/Engine/Render/Passes/Wireframe/Chunk/MortonChunkAabbWireframePass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Wireframe/Chunk/MortonChunkAabbWireframePass.cpp @@ -53,7 +53,7 @@ namespace Syn { } void MortonChunkAabbWireframePass::PrepareFrame(const RenderContext& context) { - auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); VkExtent2D extent = { group->GetWidth(), group->GetHeight() }; _graphicsState.renderArea = extent; diff --git a/SynapseEngine/Engine/Render/Passes/Wireframe/Chunk/StaticChunkAabbWireframePass.cpp b/SynapseEngine/Engine/Render/Passes/Wireframe/Chunk/StaticChunkAabbWireframePass.cpp index 25bfebd3..becf0a34 100644 --- a/SynapseEngine/Engine/Render/Passes/Wireframe/Chunk/StaticChunkAabbWireframePass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Wireframe/Chunk/StaticChunkAabbWireframePass.cpp @@ -53,7 +53,7 @@ namespace Syn { } void StaticChunkAabbWireframePass::PrepareFrame(const RenderContext& context) { - auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); VkExtent2D extent = { group->GetWidth(), group->GetHeight() }; _graphicsState.renderArea = extent; diff --git a/SynapseEngine/Engine/Render/Passes/Wireframe/Collider/BoxColliderWireframePass.cpp b/SynapseEngine/Engine/Render/Passes/Wireframe/Collider/BoxColliderWireframePass.cpp index 779b8db3..7d821997 100644 --- a/SynapseEngine/Engine/Render/Passes/Wireframe/Collider/BoxColliderWireframePass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Wireframe/Collider/BoxColliderWireframePass.cpp @@ -53,7 +53,7 @@ namespace Syn { } void BoxColliderWireframePass::PrepareFrame(const RenderContext& context) { - auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); VkExtent2D extent = { group->GetWidth(), group->GetHeight() }; _graphicsState.renderArea = extent; diff --git a/SynapseEngine/Engine/Render/Passes/Wireframe/Collider/CapsuleColliderWireframePass.cpp b/SynapseEngine/Engine/Render/Passes/Wireframe/Collider/CapsuleColliderWireframePass.cpp index 27d72b13..af3102c1 100644 --- a/SynapseEngine/Engine/Render/Passes/Wireframe/Collider/CapsuleColliderWireframePass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Wireframe/Collider/CapsuleColliderWireframePass.cpp @@ -53,7 +53,7 @@ namespace Syn { } void CapsuleColliderWireframePass::PrepareFrame(const RenderContext& context) { - auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); VkExtent2D extent = { group->GetWidth(), group->GetHeight() }; _graphicsState.renderArea = extent; diff --git a/SynapseEngine/Engine/Render/Passes/Wireframe/Collider/SphereColliderWireframePass.cpp b/SynapseEngine/Engine/Render/Passes/Wireframe/Collider/SphereColliderWireframePass.cpp index 8c44a3bc..8d3e150c 100644 --- a/SynapseEngine/Engine/Render/Passes/Wireframe/Collider/SphereColliderWireframePass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Wireframe/Collider/SphereColliderWireframePass.cpp @@ -53,7 +53,7 @@ namespace Syn { } void SphereColliderWireframePass::PrepareFrame(const RenderContext& context) { - auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); VkExtent2D extent = { group->GetWidth(), group->GetHeight() }; _graphicsState.renderArea = extent; diff --git a/SynapseEngine/Engine/Render/Passes/Wireframe/Light/PointLightAabbWireframePass.cpp b/SynapseEngine/Engine/Render/Passes/Wireframe/Light/PointLightAabbWireframePass.cpp index cc4ee833..09f2b85a 100644 --- a/SynapseEngine/Engine/Render/Passes/Wireframe/Light/PointLightAabbWireframePass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Wireframe/Light/PointLightAabbWireframePass.cpp @@ -52,7 +52,7 @@ namespace Syn { } void PointLightAabbWireframePass::PrepareFrame(const RenderContext& context) { - auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); VkExtent2D extent = { group->GetWidth(), group->GetHeight() }; _graphicsState.renderArea = extent; diff --git a/SynapseEngine/Engine/Render/Passes/Wireframe/Light/PointLightSphereWireframePass.cpp b/SynapseEngine/Engine/Render/Passes/Wireframe/Light/PointLightSphereWireframePass.cpp index 9af681a5..dbca0488 100644 --- a/SynapseEngine/Engine/Render/Passes/Wireframe/Light/PointLightSphereWireframePass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Wireframe/Light/PointLightSphereWireframePass.cpp @@ -52,7 +52,7 @@ namespace Syn { } void PointLightSphereWireframePass::PrepareFrame(const RenderContext& context) { - auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); VkExtent2D extent = { group->GetWidth(), group->GetHeight() }; _graphicsState.renderArea = extent; diff --git a/SynapseEngine/Engine/Render/Passes/Wireframe/Light/SpotLightAabbWireframePass.cpp b/SynapseEngine/Engine/Render/Passes/Wireframe/Light/SpotLightAabbWireframePass.cpp index 152ed2c8..82d5b161 100644 --- a/SynapseEngine/Engine/Render/Passes/Wireframe/Light/SpotLightAabbWireframePass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Wireframe/Light/SpotLightAabbWireframePass.cpp @@ -52,7 +52,7 @@ namespace Syn { } void SpotLightAabbWireframePass::PrepareFrame(const RenderContext& context) { - auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); VkExtent2D extent = { group->GetWidth(), group->GetHeight() }; _graphicsState.renderArea = extent; diff --git a/SynapseEngine/Engine/Render/Passes/Wireframe/Light/SpotLightConeWireframePass.cpp b/SynapseEngine/Engine/Render/Passes/Wireframe/Light/SpotLightConeWireframePass.cpp index f08f0bc6..fb19d804 100644 --- a/SynapseEngine/Engine/Render/Passes/Wireframe/Light/SpotLightConeWireframePass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Wireframe/Light/SpotLightConeWireframePass.cpp @@ -51,7 +51,7 @@ namespace Syn { } void SpotLightConeWireframePass::PrepareFrame(const RenderContext& context) { - auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); VkExtent2D extent = { group->GetWidth(), group->GetHeight() }; _graphicsState.renderArea = extent; diff --git a/SynapseEngine/Engine/Render/Passes/Wireframe/Light/SpotLightPyramidWireframePass.cpp b/SynapseEngine/Engine/Render/Passes/Wireframe/Light/SpotLightPyramidWireframePass.cpp index 65404466..6407fb85 100644 --- a/SynapseEngine/Engine/Render/Passes/Wireframe/Light/SpotLightPyramidWireframePass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Wireframe/Light/SpotLightPyramidWireframePass.cpp @@ -51,7 +51,7 @@ namespace Syn { } void SpotLightPyramidWireframePass::PrepareFrame(const RenderContext& context) { - auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); VkExtent2D extent = { group->GetWidth(), group->GetHeight() }; _graphicsState.renderArea = extent; diff --git a/SynapseEngine/Engine/Render/Passes/Wireframe/Light/SpotLightSphereWireframePass.cpp b/SynapseEngine/Engine/Render/Passes/Wireframe/Light/SpotLightSphereWireframePass.cpp index 0032132c..53f242bd 100644 --- a/SynapseEngine/Engine/Render/Passes/Wireframe/Light/SpotLightSphereWireframePass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Wireframe/Light/SpotLightSphereWireframePass.cpp @@ -51,7 +51,7 @@ namespace Syn { } void SpotLightSphereWireframePass::PrepareFrame(const RenderContext& context) { - auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); VkExtent2D extent = { group->GetWidth(), group->GetHeight() }; _graphicsState.renderArea = extent; diff --git a/SynapseEngine/Engine/Render/Passes/Wireframe/Mesh/WireframeMeshAabbPass.cpp b/SynapseEngine/Engine/Render/Passes/Wireframe/Mesh/WireframeMeshAabbPass.cpp index cfd17d6f..89ee6024 100644 --- a/SynapseEngine/Engine/Render/Passes/Wireframe/Mesh/WireframeMeshAabbPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Wireframe/Mesh/WireframeMeshAabbPass.cpp @@ -57,7 +57,7 @@ namespace Syn { } void WireframeMeshAabbPass::PrepareFrame(const RenderContext& context) { - auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); VkExtent2D extent = { group->GetWidth(), group->GetHeight() }; _graphicsState.renderArea = extent; diff --git a/SynapseEngine/Engine/Render/Passes/Wireframe/Mesh/WireframeMeshSpherePass.cpp b/SynapseEngine/Engine/Render/Passes/Wireframe/Mesh/WireframeMeshSpherePass.cpp index 47c01b43..34ba74c0 100644 --- a/SynapseEngine/Engine/Render/Passes/Wireframe/Mesh/WireframeMeshSpherePass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Wireframe/Mesh/WireframeMeshSpherePass.cpp @@ -57,7 +57,7 @@ namespace Syn { } void WireframeMeshSpherePass::PrepareFrame(const RenderContext& context) { - auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); VkExtent2D extent = { group->GetWidth(), group->GetHeight() }; _graphicsState.renderArea = extent; diff --git a/SynapseEngine/Engine/Render/Passes/Wireframe/Meshlet/WireframeMeshletAabbPass.cpp b/SynapseEngine/Engine/Render/Passes/Wireframe/Meshlet/WireframeMeshletAabbPass.cpp index 62e5febd..0619dabc 100644 --- a/SynapseEngine/Engine/Render/Passes/Wireframe/Meshlet/WireframeMeshletAabbPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Wireframe/Meshlet/WireframeMeshletAabbPass.cpp @@ -62,7 +62,7 @@ namespace Syn { } void WireframeMeshletAabbPass::PrepareFrame(const RenderContext& context) { - auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); VkExtent2D extent = { group->GetWidth(), group->GetHeight() }; _graphicsState.renderArea = extent; @@ -116,7 +116,7 @@ namespace Syn { auto imageManager = ServiceLocator::GetImageManager(); uint32_t prevFrameIndex = (context.frameIndex + context.framesInFlight - 1) % context.framesInFlight; - auto prevRtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, prevFrameIndex); + auto prevRtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, prevFrameIndex); auto depthPyramid = prevRtGroup->GetImage(RenderTargetNames::DepthPyramid); auto maxSampler = imageManager->GetSampler(SamplerNames::MaxReduction); diff --git a/SynapseEngine/Engine/Render/Passes/Wireframe/Meshlet/WireframeMeshletConePass.cpp b/SynapseEngine/Engine/Render/Passes/Wireframe/Meshlet/WireframeMeshletConePass.cpp index f2bcf1de..1a3777e4 100644 --- a/SynapseEngine/Engine/Render/Passes/Wireframe/Meshlet/WireframeMeshletConePass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Wireframe/Meshlet/WireframeMeshletConePass.cpp @@ -61,7 +61,7 @@ namespace Syn { } void WireframeMeshletConePass::PrepareFrame(const RenderContext& context) { - auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); VkExtent2D extent = { group->GetWidth(), group->GetHeight() }; _graphicsState.renderArea = extent; @@ -115,7 +115,7 @@ namespace Syn { auto imageManager = ServiceLocator::GetImageManager(); uint32_t prevFrameIndex = (context.frameIndex + context.framesInFlight - 1) % context.framesInFlight; - auto prevRtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, prevFrameIndex); + auto prevRtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, prevFrameIndex); auto depthPyramid = prevRtGroup->GetImage(RenderTargetNames::DepthPyramid); auto maxSampler = imageManager->GetSampler(SamplerNames::MaxReduction); diff --git a/SynapseEngine/Engine/Render/Passes/Wireframe/Meshlet/WireframeMeshletSpherePass.cpp b/SynapseEngine/Engine/Render/Passes/Wireframe/Meshlet/WireframeMeshletSpherePass.cpp index cf75af1e..cb27cfec 100644 --- a/SynapseEngine/Engine/Render/Passes/Wireframe/Meshlet/WireframeMeshletSpherePass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Wireframe/Meshlet/WireframeMeshletSpherePass.cpp @@ -61,7 +61,7 @@ namespace Syn { } void WireframeMeshletSpherePass::PrepareFrame(const RenderContext& context) { - auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, context.frameIndex); + auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); VkExtent2D extent = { group->GetWidth(), group->GetHeight() }; _graphicsState.renderArea = extent; @@ -115,7 +115,7 @@ namespace Syn { auto imageManager = ServiceLocator::GetImageManager(); uint32_t prevFrameIndex = (context.frameIndex + context.framesInFlight - 1) % context.framesInFlight; - auto prevRtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Deferred, prevFrameIndex); + auto prevRtGroup = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, prevFrameIndex); auto depthPyramid = prevRtGroup->GetImage(RenderTargetNames::DepthPyramid); auto maxSampler = imageManager->GetSampler(SamplerNames::MaxReduction); diff --git a/SynapseEngine/Engine/Render/RenderNames.h b/SynapseEngine/Engine/Render/RenderNames.h index 4796829c..447ed3b9 100644 --- a/SynapseEngine/Engine/Render/RenderNames.h +++ b/SynapseEngine/Engine/Render/RenderNames.h @@ -5,12 +5,12 @@ namespace Syn { struct SYN_API RenderPipelineNames { - static constexpr const char* DeferredPipeline = "DeferredPipeline"; + static constexpr const char* ScenePipeline = "ScenePipeline"; }; struct SYN_API RenderTargetGroupNames { - static constexpr const char* Deferred = "Deferred"; + static constexpr const char* Main = "Main"; }; struct SYN_API RenderTargetNames diff --git a/SynapseEngine/Engine/Render/RendererFactory.cpp b/SynapseEngine/Engine/Render/RendererFactory.cpp index e3c8b002..7dcab40f 100644 --- a/SynapseEngine/Engine/Render/RendererFactory.cpp +++ b/SynapseEngine/Engine/Render/RendererFactory.cpp @@ -157,7 +157,6 @@ #include "Engine/Render/Passes/Shadow/PointLight/PointLightShadowTraditionalOpaquePass.h" #include "Engine/Render/Passes/Shadow/PointLight/PointLightShadowMeshletOpaquePass.h" - #include "Engine/Render/Passes/Ssao/SsaoInitPass.h" #include "Engine/Render/Passes/Ssao/SsaoPass.h" #include "Engine/Render/Passes/Ssao/SsaoBlurPass.h" @@ -169,7 +168,7 @@ namespace Syn { - std::unique_ptr RendererFactory::CreateDeferredRenderer(uint32_t framesInFlight) { + std::unique_ptr RendererFactory::CreateSceneRenderer(uint32_t framesInFlight) { auto renderManager = std::make_unique(framesInFlight); auto rtManager = renderManager->GetRenderTargetManager(); @@ -386,9 +385,9 @@ namespace Syn pipeline->AddPass(std::make_unique()); pipeline->InitializeAll(); - renderManager->RegisterPipeline(RenderPipelineNames::DeferredPipeline, std::move(pipeline)); + renderManager->RegisterPipeline(RenderPipelineNames::ScenePipeline, std::move(pipeline)); - rtManager->CreateGroup(RenderTargetGroupNames::Deferred); + rtManager->CreateGroup(RenderTargetGroupNames::Main); uint32_t initWidth = 4; uint32_t initHeight = 4; @@ -400,7 +399,7 @@ namespace Syn mainImageSpec.format = VK_FORMAT_R16G16B16A16_SFLOAT; mainImageSpec.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT; mainImageSpec.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - rtManager->AddAttachment(RenderTargetGroupNames::Deferred, RenderTargetNames::Main, mainImageSpec); + rtManager->AddAttachment(RenderTargetGroupNames::Main, RenderTargetNames::Main, mainImageSpec); Vk::ImageConfig colorImageSpec{}; colorImageSpec.width = initWidth; @@ -421,8 +420,7 @@ namespace Syn .swizzle = { VK_COMPONENT_SWIZZLE_A, VK_COMPONENT_SWIZZLE_A, VK_COMPONENT_SWIZZLE_A, VK_COMPONENT_SWIZZLE_ONE } }); - rtManager->AddAttachment(RenderTargetGroupNames::Deferred, RenderTargetNames::ColorMetallic, colorImageSpec); - + rtManager->AddAttachment(RenderTargetGroupNames::Main, RenderTargetNames::ColorMetallic, colorImageSpec); Vk::ImageConfig normalImageSpec{}; normalImageSpec.width = initWidth; @@ -444,7 +442,7 @@ namespace Syn .swizzle = { VK_COMPONENT_SWIZZLE_A, VK_COMPONENT_SWIZZLE_A, VK_COMPONENT_SWIZZLE_A, VK_COMPONENT_SWIZZLE_ONE } }); - rtManager->AddAttachment(RenderTargetGroupNames::Deferred, RenderTargetNames::NormalRoughness, normalImageSpec); + rtManager->AddAttachment(RenderTargetGroupNames::Main, RenderTargetNames::NormalRoughness, normalImageSpec); Vk::ImageConfig emissiveAoImageSpec{}; emissiveAoImageSpec.width = initWidth; @@ -466,7 +464,7 @@ namespace Syn .swizzle = { VK_COMPONENT_SWIZZLE_A, VK_COMPONENT_SWIZZLE_A, VK_COMPONENT_SWIZZLE_A, VK_COMPONENT_SWIZZLE_ONE } }); - rtManager->AddAttachment(RenderTargetGroupNames::Deferred, RenderTargetNames::EmissiveAo, emissiveAoImageSpec); + rtManager->AddAttachment(RenderTargetGroupNames::Main, RenderTargetNames::EmissiveAo, emissiveAoImageSpec); Vk::ImageConfig transparentAccumSpec{}; transparentAccumSpec.width = initWidth; @@ -475,7 +473,7 @@ namespace Syn transparentAccumSpec.format = VK_FORMAT_R16G16B16A16_SFLOAT; transparentAccumSpec.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; transparentAccumSpec.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - rtManager->AddAttachment(RenderTargetGroupNames::Deferred, RenderTargetNames::TransparentAccum, transparentAccumSpec); + rtManager->AddAttachment(RenderTargetGroupNames::Main, RenderTargetNames::TransparentAccum, transparentAccumSpec); Vk::ImageConfig transparentRevealSpec{}; transparentRevealSpec.width = initWidth; @@ -484,7 +482,7 @@ namespace Syn transparentRevealSpec.format = VK_FORMAT_R8_UNORM; transparentRevealSpec.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; transparentRevealSpec.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - rtManager->AddAttachment(RenderTargetGroupNames::Deferred, RenderTargetNames::TransparentReveal, transparentRevealSpec); + rtManager->AddAttachment(RenderTargetGroupNames::Main, RenderTargetNames::TransparentReveal, transparentRevealSpec); Vk::ImageConfig entityImageSpec{}; entityImageSpec.width = initWidth; @@ -493,7 +491,7 @@ namespace Syn entityImageSpec.format = VK_FORMAT_R32G32_UINT; entityImageSpec.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; entityImageSpec.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - rtManager->AddAttachment(RenderTargetGroupNames::Deferred, RenderTargetNames::EntityIndex, entityImageSpec); + rtManager->AddAttachment(RenderTargetGroupNames::Main, RenderTargetNames::EntityIndex, entityImageSpec); Vk::ImageConfig depthPyramidImageSpec{}; depthPyramidImageSpec.width = initWidth; @@ -521,7 +519,7 @@ namespace Syn .perMipViews = true }); - rtManager->AddAttachment(RenderTargetGroupNames::Deferred, RenderTargetNames::DepthPyramid, depthPyramidImageSpec); + rtManager->AddAttachment(RenderTargetGroupNames::Main, RenderTargetNames::DepthPyramid, depthPyramidImageSpec); Vk::ImageConfig bloomImageSpec{}; bloomImageSpec.width = initWidth; @@ -537,7 +535,7 @@ namespace Syn .perMipViews = true }); - rtManager->AddAttachment(RenderTargetGroupNames::Deferred, RenderTargetNames::Bloom, bloomImageSpec); + rtManager->AddAttachment(RenderTargetGroupNames::Main, RenderTargetNames::Bloom, bloomImageSpec); Vk::ImageConfig opaqueDepthSpec{}; opaqueDepthSpec.width = initWidth; @@ -546,7 +544,7 @@ namespace Syn opaqueDepthSpec.format = VK_FORMAT_D32_SFLOAT; opaqueDepthSpec.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; opaqueDepthSpec.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; - rtManager->AddAttachment(RenderTargetGroupNames::Deferred, RenderTargetNames::OpaqueDepth, opaqueDepthSpec); + rtManager->AddAttachment(RenderTargetGroupNames::Main, RenderTargetNames::OpaqueDepth, opaqueDepthSpec); Vk::ImageConfig transparentDepthSpec{}; transparentDepthSpec.width = initWidth; @@ -555,7 +553,7 @@ namespace Syn transparentDepthSpec.format = VK_FORMAT_D32_SFLOAT; transparentDepthSpec.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; transparentDepthSpec.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; - rtManager->AddAttachment(RenderTargetGroupNames::Deferred, RenderTargetNames::TransparentDepth, transparentDepthSpec); + rtManager->AddAttachment(RenderTargetGroupNames::Main, RenderTargetNames::TransparentDepth, transparentDepthSpec); Vk::ImageConfig volumetricAoImageSpec{}; volumetricAoImageSpec.width = initWidth; @@ -564,7 +562,7 @@ namespace Syn volumetricAoImageSpec.format = VK_FORMAT_R16_SFLOAT; volumetricAoImageSpec.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT; volumetricAoImageSpec.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - rtManager->AddAttachment(RenderTargetGroupNames::Deferred, RenderTargetNames::SsaoAo, volumetricAoImageSpec); + rtManager->AddAttachment(RenderTargetGroupNames::Main, RenderTargetNames::SsaoAo, volumetricAoImageSpec); Vk::ImageConfig volumetricAoIntermediateImageSpec{}; volumetricAoIntermediateImageSpec.width = initWidth; @@ -573,7 +571,7 @@ namespace Syn volumetricAoIntermediateImageSpec.format = VK_FORMAT_R16_SFLOAT; volumetricAoIntermediateImageSpec.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT; volumetricAoIntermediateImageSpec.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - rtManager->AddAttachment(RenderTargetGroupNames::Deferred, RenderTargetNames::SsaoAoIntermediate, volumetricAoIntermediateImageSpec); + rtManager->AddAttachment(RenderTargetGroupNames::Main, RenderTargetNames::SsaoAoIntermediate, volumetricAoIntermediateImageSpec); return renderManager; } diff --git a/SynapseEngine/Engine/Render/RendererFactory.h b/SynapseEngine/Engine/Render/RendererFactory.h index e5518324..f4557e07 100644 --- a/SynapseEngine/Engine/Render/RendererFactory.h +++ b/SynapseEngine/Engine/Render/RendererFactory.h @@ -6,7 +6,7 @@ namespace Syn { class SYN_API RendererFactory { public: - static std::unique_ptr CreateDeferredRenderer(uint32_t framesInFlight); + static std::unique_ptr CreateSceneRenderer(uint32_t framesInFlight); }; } \ No newline at end of file diff --git a/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json b/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json index 6469175c..f8c14ba4 100644 --- a/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json +++ b/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json @@ -3,8 +3,8 @@ "base_model_path": "C:/Users/User/Desktop/Models/" }, "environment": { - "spawn_sponza": true, - "spawn_bistro": false, + "spawn_sponza": false, + "spawn_bistro": true, "spawn_floor": false, "spawn_pbr_sponza": false, "spawn_monkey": true diff --git a/SynapseEngine/Engine/Shaders/Passes/Shading/ForwardPlus/DepthPrepass/PreDepth.frag b/SynapseEngine/Engine/Shaders/Passes/Shading/ForwardPlus/DepthPrepass/PreDepth.frag index 32a4b913..2319d1ae 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Shading/ForwardPlus/DepthPrepass/PreDepth.frag +++ b/SynapseEngine/Engine/Shaders/Passes/Shading/ForwardPlus/DepthPrepass/PreDepth.frag @@ -32,7 +32,6 @@ void main() { ? FINALIZE_VIS_MS(partial, gl_PrimitiveID) : FINALIZE_VIS_TRADITIONAL(partial, gl_PrimitiveID); - /* // 1. Fetch Material Material mat = GET_MATERIAL(ctx.materialBufferAddr, materialId); vec2 finalUV = inUV * mat.uvScale; @@ -42,7 +41,6 @@ void main() { if (albedoAlpha.a < ctx.alphaLimitDiscard) { discard; } - */ outId = uvec2(packedEntity, finalPayload); } \ No newline at end of file diff --git a/SynapseEngine/Engine/Statistics/DefaultRenderStatCollector.cpp b/SynapseEngine/Engine/Statistics/DefaultRenderStatCollector.cpp index e892b170..86f1cd21 100644 --- a/SynapseEngine/Engine/Statistics/DefaultRenderStatCollector.cpp +++ b/SynapseEngine/Engine/Statistics/DefaultRenderStatCollector.cpp @@ -1,4 +1,5 @@ #include "DefaultRenderStatCollector.h" +#include namespace Syn { @@ -49,7 +50,9 @@ namespace Syn { if (measurements.empty()) return; uint32_t totalQueries = _queryCounters[frameIndex]; - std::vector results(totalQueries * STATS_PER_QUERY); + auto activeFlags = _pools[frameIndex]->GetFlags(); + uint32_t activeStatCount = std::popcount(static_cast(activeFlags)); + std::vector results(totalQueries * activeStatCount); if (_pools[frameIndex]->GetResults(0, totalQueries, results, false)) { auto& stats = _resolvedStats[frameIndex]; @@ -59,21 +62,25 @@ namespace Syn { for (size_t i = 0; i < measurements.size(); ++i) { const auto& m = measurements[i]; - //uint32_t offset = m.queryIndex * STATS_PER_QUERY; - uint32_t offset = m.queryIndex * 6; - RenderPassStats passStat; passStat.groupName = m.groupName; passStat.passName = m.passName; - passStat.inputAssemblyVertices = results[offset + 0]; - passStat.inputAssemblyPrimitives = results[offset + 1]; - passStat.vertexShaderInvocations = results[offset + 2]; - passStat.clippingInvocations = results[offset + 3]; - passStat.clippingPrimitives = results[offset + 4]; - passStat.fragmentShaderInvocations = results[offset + 5]; - //passStat.taskShaderInvocations = results[offset + 6]; - //passStat.meshShaderInvocations = results[offset + 7]; + uint32_t localIndex = 0; + uint32_t offset = m.queryIndex * activeStatCount; + + auto extractStat = [&](VkQueryPipelineStatisticFlags flag) -> uint64_t { + return (activeFlags & flag) ? results[offset + localIndex++] : 0; + }; + + passStat.inputAssemblyVertices = extractStat(VK_QUERY_PIPELINE_STATISTIC_INPUT_ASSEMBLY_VERTICES_BIT); + passStat.inputAssemblyPrimitives = extractStat(VK_QUERY_PIPELINE_STATISTIC_INPUT_ASSEMBLY_PRIMITIVES_BIT); + passStat.vertexShaderInvocations = extractStat(VK_QUERY_PIPELINE_STATISTIC_VERTEX_SHADER_INVOCATIONS_BIT); + passStat.clippingInvocations = extractStat(VK_QUERY_PIPELINE_STATISTIC_CLIPPING_INVOCATIONS_BIT); + passStat.clippingPrimitives = extractStat(VK_QUERY_PIPELINE_STATISTIC_CLIPPING_PRIMITIVES_BIT); + passStat.fragmentShaderInvocations = extractStat(VK_QUERY_PIPELINE_STATISTIC_FRAGMENT_SHADER_INVOCATIONS_BIT); + passStat.taskShaderInvocations = extractStat(VK_QUERY_PIPELINE_STATISTIC_TASK_SHADER_INVOCATIONS_BIT_EXT); + passStat.meshShaderInvocations = extractStat(VK_QUERY_PIPELINE_STATISTIC_MESH_SHADER_INVOCATIONS_BIT_EXT); stats.push_back(passStat); } diff --git a/SynapseEngine/Engine/Statistics/DefaultRenderStatCollector.h b/SynapseEngine/Engine/Statistics/DefaultRenderStatCollector.h index b701b2d9..f8209582 100644 --- a/SynapseEngine/Engine/Statistics/DefaultRenderStatCollector.h +++ b/SynapseEngine/Engine/Statistics/DefaultRenderStatCollector.h @@ -31,7 +31,6 @@ namespace Syn { const std::vector& GetStats(uint32_t frameIndex) const override; private: static constexpr uint32_t MAX_QUERIES_PER_FRAME = 512; - static constexpr uint32_t STATS_PER_QUERY = 8; uint32_t _framesInFlight; std::vector _queryCounters; diff --git a/SynapseEngine/Engine/Vk/Query/PipelineStatisticsQueryPool.cpp b/SynapseEngine/Engine/Vk/Query/PipelineStatisticsQueryPool.cpp index 0ff22a1a..f1e1b467 100644 --- a/SynapseEngine/Engine/Vk/Query/PipelineStatisticsQueryPool.cpp +++ b/SynapseEngine/Engine/Vk/Query/PipelineStatisticsQueryPool.cpp @@ -1,9 +1,10 @@ #include "PipelineStatisticsQueryPool.h" +#include namespace Syn::Vk { PipelineStatisticsQueryPool::PipelineStatisticsQueryPool(uint32_t queryCount, VkQueryPipelineStatisticFlags flags) - : QueryPool(VK_QUERY_TYPE_PIPELINE_STATISTICS, queryCount, flags) + : QueryPool(VK_QUERY_TYPE_PIPELINE_STATISTICS, queryCount, flags), _flags(flags) {} void PipelineStatisticsQueryPool::BeginQuery(VkCommandBuffer cmd, uint32_t queryIndex) { @@ -13,4 +14,8 @@ namespace Syn::Vk void PipelineStatisticsQueryPool::EndQuery(VkCommandBuffer cmd, uint32_t queryIndex) { vkCmdEndQuery(cmd, _handle, queryIndex); } + + uint32_t PipelineStatisticsQueryPool::GetStride() const { + return std::popcount(static_cast(_flags)) * sizeof(uint64_t); + } } \ No newline at end of file diff --git a/SynapseEngine/Engine/Vk/Query/PipelineStatisticsQueryPool.h b/SynapseEngine/Engine/Vk/Query/PipelineStatisticsQueryPool.h index 5dd83106..5d2f1c29 100644 --- a/SynapseEngine/Engine/Vk/Query/PipelineStatisticsQueryPool.h +++ b/SynapseEngine/Engine/Vk/Query/PipelineStatisticsQueryPool.h @@ -10,5 +10,10 @@ namespace Syn::Vk void BeginQuery(VkCommandBuffer cmd, uint32_t queryIndex); void EndQuery(VkCommandBuffer cmd, uint32_t queryIndex); + VkQueryPipelineStatisticFlags GetFlags() const { return _flags; } + protected: + uint32_t GetStride() const override; + private: + VkQueryPipelineStatisticFlags _flags; }; } \ No newline at end of file diff --git a/SynapseEngine/Engine/Vk/Query/QueryPool.cpp b/SynapseEngine/Engine/Vk/Query/QueryPool.cpp index 339dbb08..a6963058 100644 --- a/SynapseEngine/Engine/Vk/Query/QueryPool.cpp +++ b/SynapseEngine/Engine/Vk/Query/QueryPool.cpp @@ -32,6 +32,10 @@ namespace Syn::Vk bool QueryPool::GetResults(uint32_t firstQuery, uint32_t count, std::vector& outResults, bool wait) { auto device = ServiceLocator::GetVkContext()->GetDevice(); + uint32_t stride = GetStride(); + uint32_t statsPerQuery = stride / sizeof(uint64_t); + uint32_t requiredElements = count * statsPerQuery; + if (outResults.size() < count) { outResults.resize(count); } @@ -49,7 +53,7 @@ namespace Syn::Vk count, outResults.size() * sizeof(uint64_t), outResults.data(), - sizeof(uint64_t), + stride, flags ); diff --git a/SynapseEngine/Engine/Vk/Query/QueryPool.h b/SynapseEngine/Engine/Vk/Query/QueryPool.h index c146e670..79f923e7 100644 --- a/SynapseEngine/Engine/Vk/Query/QueryPool.h +++ b/SynapseEngine/Engine/Vk/Query/QueryPool.h @@ -19,6 +19,7 @@ namespace Syn::Vk VkQueryType GetType() const { return _type; } protected: QueryPool(VkQueryType type, uint32_t queryCount, VkQueryPipelineStatisticFlags pipelineStats = 0); + virtual uint32_t GetStride() const { return sizeof(uint64_t); } protected: VkQueryType _type; uint32_t _queryCount = 0; From dcf89138266cb65e2c91bed364177fe102a05cba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20P=C3=A9ter?= Date: Sat, 4 Jul 2026 20:42:37 +0200 Subject: [PATCH 25/51] Resolved tangent space normal calculation problems --- SynapseEngine/Engine/Image/ImageManager.cpp | 11 +++++++++-- .../Lighting/OpaqueForwardTransitionPass.cpp | 3 +-- SynapseEngine/Engine/Render/RendererFactory.cpp | 1 + .../Scene/Source/Procedural/test_config.json | 4 ++-- .../Shaders/Includes/Utils/MaterialMath.glsl | 17 ++++++++--------- 5 files changed, 21 insertions(+), 15 deletions(-) diff --git a/SynapseEngine/Engine/Image/ImageManager.cpp b/SynapseEngine/Engine/Image/ImageManager.cpp index 9b67c5ca..b9750797 100644 --- a/SynapseEngine/Engine/Image/ImageManager.cpp +++ b/SynapseEngine/Engine/Image/ImageManager.cpp @@ -336,8 +336,15 @@ namespace Syn } void ImageManager::FlushDirtyResources() { + //Just to handle bistro test, this will be deleted!! + auto isNormalMap = [](const std::string& path) { + std::string lowerPath = path; + std::transform(lowerPath.begin(), lowerPath.end(), lowerPath.begin(), ::tolower); + return lowerPath.find("_normal") != std::string::npos; + }; + ProcessDirtyReadyEntries( - [this](uint32_t index, const EntryType& entry) { + [this, &isNormalMap](uint32_t index, const EntryType& entry) { if (!entry.resource->image) return; _bindlessBuffer->WriteSampledImage( @@ -347,7 +354,7 @@ namespace Syn ); uint32_t samplerIndex = GetSamplerIndex("LinearAniso"); - bool invertTangent = false; + bool invertTangent = isNormalMap(entry.path); uint32_t textureData = (samplerIndex & 0x7FFFFFFF); if (invertTangent) { diff --git a/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/Lighting/OpaqueForwardTransitionPass.cpp b/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/Lighting/OpaqueForwardTransitionPass.cpp index b66095cf..997f3dd9 100644 --- a/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/Lighting/OpaqueForwardTransitionPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Shading/ForwardPlus/Lighting/OpaqueForwardTransitionPass.cpp @@ -4,8 +4,7 @@ namespace Syn { bool OpaqueForwardTransitionPass::ShouldExecute(const RenderContext& context) const { - return context.scene->GetSettings()->lighting.pipelineType == PipelineType::ForwardPlus - && !context.scene->GetSettings()->debug.enableDebugVisibility;; + return !context.scene->GetSettings()->debug.enableDebugVisibility; } void OpaqueForwardTransitionPass::PrepareFrame(const RenderContext& context) { diff --git a/SynapseEngine/Engine/Render/RendererFactory.cpp b/SynapseEngine/Engine/Render/RendererFactory.cpp index 7dcab40f..3bf37a50 100644 --- a/SynapseEngine/Engine/Render/RendererFactory.cpp +++ b/SynapseEngine/Engine/Render/RendererFactory.cpp @@ -328,6 +328,7 @@ namespace Syn pipeline->AddPass(std::make_unique(MaterialRenderType::Opaque2Sided)); pipeline->AddPass(std::make_unique(MaterialRenderType::Opaque1Sided)); pipeline->AddPass(std::make_unique(MaterialRenderType::Opaque2Sided)); + // SkySphere pipeline->AddPass(std::make_unique()); diff --git a/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json b/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json index f8c14ba4..6469175c 100644 --- a/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json +++ b/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json @@ -3,8 +3,8 @@ "base_model_path": "C:/Users/User/Desktop/Models/" }, "environment": { - "spawn_sponza": false, - "spawn_bistro": true, + "spawn_sponza": true, + "spawn_bistro": false, "spawn_floor": false, "spawn_pbr_sponza": false, "spawn_monkey": true diff --git a/SynapseEngine/Engine/Shaders/Includes/Utils/MaterialMath.glsl b/SynapseEngine/Engine/Shaders/Includes/Utils/MaterialMath.glsl index 7486a472..e89ec494 100644 --- a/SynapseEngine/Engine/Shaders/Includes/Utils/MaterialMath.glsl +++ b/SynapseEngine/Engine/Shaders/Includes/Utils/MaterialMath.glsl @@ -17,6 +17,7 @@ vec4 EvaluateAlbedoAlpha(uint64_t textureMetadataBufferAddr, const Material mat, vec3 EvaluateNormal(uint64_t textureMetadataBufferAddr, Material mat, vec2 uv, vec3 vertexNormal, vec4 vertexTangent) { vec3 normal = normalize(vertexNormal); + if (!HAS_NORMAL_TEX(mat)) { return normal; } @@ -24,22 +25,20 @@ vec3 EvaluateNormal(uint64_t textureMetadataBufferAddr, Material mat, vec2 uv, v uint meta = GET_TEXTURE_METADATA(textureMetadataBufferAddr, mat.normalTexture); uint samplerID; - bool invertTangent; - UnpackTextureMetadata(meta, samplerID, invertTangent); + bool invertNormal; + UnpackTextureMetadata(meta, samplerID, invertNormal); vec3 tangent = normalize(vertexTangent.xyz); tangent = normalize(tangent - normal * dot(normal, tangent)); - if (invertTangent) { - tangent = -tangent; - } - vec3 bitangent = cross(normal, tangent) * vertexTangent.w; mat3 TBN = mat3(tangent, bitangent, normal); - vec3 normalMapSample = SampleTexture2D(mat.normalTexture, SAMPLER_LINEAR_ANISO, uv).rgb; - vec3 tangentSpaceNormal = normalMapSample * 2.0 - 1.0; - + vec3 tangentSpaceNormal; + tangentSpaceNormal.xy = SampleTexture2D(mat.normalTexture, SAMPLER_NEAREST_ANISO, uv).xy * 2.0 - 1.0; + tangentSpaceNormal.z = sqrt(max(1.0 - dot(tangentSpaceNormal.xy, tangentSpaceNormal.xy), 0.0)); + tangentSpaceNormal.y *= invertNormal ? -1.0 : 1.0; + return normalize(TBN * tangentSpaceNormal); } From e81dd26f10eadd87cfcdf0b0dac861cb1b516f6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20P=C3=A9ter?= Date: Tue, 7 Jul 2026 17:08:38 +0200 Subject: [PATCH 26/51] Implemented infinite grid post process effect --- SynapseEngine/Editor/Manager/EditorIcons.h | 6 +- .../Editor/View/Settings/SettingsView.cpp | 56 +++++++ .../MaterialHierarchyState.h | 1 + .../MaterialPropertiesState.h | 1 + .../TextureHierarchy/TextureHierarchyState.h | 1 + .../TexturePropertiesState.h | 1 + .../InfiniteGrid/InfiniteGridPass.cpp | 120 ++++++++++++++ .../InfiniteGrid/InfiniteGridPass.h | 20 +++ .../{ => PostProcess}/Ssao/SsaoBlurPass.cpp | 0 .../{ => PostProcess}/Ssao/SsaoBlurPass.h | 0 .../{ => PostProcess}/Ssao/SsaoInitPass.cpp | 0 .../{ => PostProcess}/Ssao/SsaoInitPass.h | 0 .../{ => PostProcess}/Ssao/SsaoPass.cpp | 2 +- .../Passes/{ => PostProcess}/Ssao/SsaoPass.h | 0 .../Engine/Render/RendererFactory.cpp | 10 +- SynapseEngine/Engine/Render/ShaderNames.h | 11 +- .../Engine/Scene/Settings/DebugSettings.cpp | 15 ++ .../Engine/Scene/Settings/DebugSettings.h | 17 ++ .../Schema/Scene/SceneSettingsSchema.h | 19 ++- .../PushConstants/InfiniteGridPC.glsl | 20 +++ .../InfiniteGrid/InfiniteGrid.frag | 147 ++++++++++++++++++ .../Passes/{ => PostProcess}/Ssao/DpHvo.comp | 10 +- .../{ => PostProcess}/Ssao/DpHvoBlur.comp | 8 +- .../Passes/{ => PostProcess}/Ssao/Ssao.comp | 12 +- .../{ => PostProcess}/Ssao/SsaoBlur.comp | 6 +- 25 files changed, 455 insertions(+), 28 deletions(-) create mode 100644 SynapseEngine/Engine/Render/Passes/PostProcess/InfiniteGrid/InfiniteGridPass.cpp create mode 100644 SynapseEngine/Engine/Render/Passes/PostProcess/InfiniteGrid/InfiniteGridPass.h rename SynapseEngine/Engine/Render/Passes/{ => PostProcess}/Ssao/SsaoBlurPass.cpp (100%) rename SynapseEngine/Engine/Render/Passes/{ => PostProcess}/Ssao/SsaoBlurPass.h (100%) rename SynapseEngine/Engine/Render/Passes/{ => PostProcess}/Ssao/SsaoInitPass.cpp (100%) rename SynapseEngine/Engine/Render/Passes/{ => PostProcess}/Ssao/SsaoInitPass.h (100%) rename SynapseEngine/Engine/Render/Passes/{ => PostProcess}/Ssao/SsaoPass.cpp (98%) rename SynapseEngine/Engine/Render/Passes/{ => PostProcess}/Ssao/SsaoPass.h (100%) create mode 100644 SynapseEngine/Engine/Shaders/Includes/PushConstants/InfiniteGridPC.glsl create mode 100644 SynapseEngine/Engine/Shaders/Passes/PostProcess/InfiniteGrid/InfiniteGrid.frag rename SynapseEngine/Engine/Shaders/Passes/{ => PostProcess}/Ssao/DpHvo.comp (94%) rename SynapseEngine/Engine/Shaders/Passes/{ => PostProcess}/Ssao/DpHvoBlur.comp (91%) rename SynapseEngine/Engine/Shaders/Passes/{ => PostProcess}/Ssao/Ssao.comp (92%) rename SynapseEngine/Engine/Shaders/Passes/{ => PostProcess}/Ssao/SsaoBlur.comp (87%) diff --git a/SynapseEngine/Editor/Manager/EditorIcons.h b/SynapseEngine/Editor/Manager/EditorIcons.h index 8d1add79..749aeef8 100644 --- a/SynapseEngine/Editor/Manager/EditorIcons.h +++ b/SynapseEngine/Editor/Manager/EditorIcons.h @@ -79,4 +79,8 @@ constexpr const char* ASSET_PATH = "Assets"; #define SYN_ICON_CONVEX_COLLIDER ICON_FA_GEM #define SYN_ICON_SPHERE_COLLIDER ICON_FA_CIRCLE #define SYN_ICON_MESH_COLLIDER ICON_FA_DRAW_POLYGON -#define SYN_ICON_RIGID_BODY ICON_FA_WEIGHT_HANGING \ No newline at end of file +#define SYN_ICON_RIGID_BODY ICON_FA_WEIGHT_HANGING + +#define SYN_ICON_TH ICON_FA_TH +#define SYN_ICON_TH_LARGE ICON_FA_TH_LARGE +#define SYN_ICON_BORDER_ALL ICON_FA_BORDER_ALL \ No newline at end of file diff --git a/SynapseEngine/Editor/View/Settings/SettingsView.cpp b/SynapseEngine/Editor/View/Settings/SettingsView.cpp index 1aa05131..e5b2d609 100644 --- a/SynapseEngine/Editor/View/Settings/SettingsView.cpp +++ b/SynapseEngine/Editor/View/Settings/SettingsView.cpp @@ -357,6 +357,62 @@ namespace Syn { } Syn::UI::EndCard(); + constexpr const char* CardGridTitle = "Infinite Grid & Axes"; + if (Syn::UI::BeginCard(CardGridTitle, SYN_ICON_TH, getCardState(CardGridTitle))) { + + if (Syn::UI::BeginPropertyGrid("InfiniteGridGrid")) { + + changed |= Syn::UI::PropertyCheckbox("Enable Infinite Grid", settings.debug.enableInfiniteGrid); + + if (settings.debug.enableInfiniteGrid) { + changed |= Syn::UI::PropertyDragFloat("Grid Scale", settings.debug.gridScale, 0.1f, 0.1f, 100.0f, "%.1f", 1); + changed |= Syn::UI::PropertyDragFloat("Fade Distance", settings.debug.fadeDistance, 1.0f, 10.0f, 1000.0f, "%.1f", 1); + + // Új vastagság beállítások + changed |= Syn::UI::PropertyDragFloat("Grid Line Thickness", settings.debug.gridThickness, 0.05f, 0.1f, 10.0f, "%.2f", 1); + changed |= Syn::UI::PropertyDragFloat("Axis Line Thickness", settings.debug.axisThickness, 0.05f, 0.1f, 10.0f, "%.2f", 1); + + Syn::UI::PropertySeparator(); + + drawSectionHeader("Grid Planes & Main Color"); + + Syn::UI::BeginProperty("Grid Color"); + if (ImGui::ColorEdit4("##GridColorPicker", glm::value_ptr(settings.debug.gridColor), ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreview)) { + changed = true; + } + + changed |= Syn::UI::PropertyCheckbox("Show XZ Plane (Floor)", settings.debug.gridShowXZ); + changed |= Syn::UI::PropertyCheckbox("Show XY Plane (Wall)", settings.debug.gridShowXY); + changed |= Syn::UI::PropertyCheckbox("Show YZ Plane (Wall)", settings.debug.gridShowYZ); + + Syn::UI::PropertySeparator(); + + drawSectionHeader("World Axes & Custom Colors"); + + changed |= Syn::UI::PropertyCheckbox("Show X Axis", settings.debug.gridShowAxisX); + ImGui::SameLine(); + if (ImGui::ColorEdit4("##AxisXColorPicker", glm::value_ptr(settings.debug.axisXColor), ImGuiColorEditFlags_NoInputs)) { + changed = true; + } + + changed |= Syn::UI::PropertyCheckbox("Show Y Axis", settings.debug.gridShowAxisY); + ImGui::SameLine(); + if (ImGui::ColorEdit4("##AxisYColorPicker", glm::value_ptr(settings.debug.axisYColor), ImGuiColorEditFlags_NoInputs)) { + changed = true; + } + + changed |= Syn::UI::PropertyCheckbox("Show Z Axis", settings.debug.gridShowAxisZ); + ImGui::SameLine(); + if (ImGui::ColorEdit4("##AxisZColorPicker", glm::value_ptr(settings.debug.axisZColor), ImGuiColorEditFlags_NoInputs)) { + changed = true; + } + } + + Syn::UI::EndPropertyGrid(); + } + } + Syn::UI::EndCard(); + if (changed) { vm.Dispatch(UpdateSceneSettingsIntent{ settings }); } diff --git a/SynapseEngine/EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyState.h b/SynapseEngine/EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyState.h index f43d80ec..d961486a 100644 --- a/SynapseEngine/EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyState.h +++ b/SynapseEngine/EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyState.h @@ -1,6 +1,7 @@ #pragma once #include #include +#include namespace Syn { struct MaterialNode { diff --git a/SynapseEngine/EditorCore/ViewModels/MaterialProperties/MaterialPropertiesState.h b/SynapseEngine/EditorCore/ViewModels/MaterialProperties/MaterialPropertiesState.h index 61716f5c..5a3a1c9d 100644 --- a/SynapseEngine/EditorCore/ViewModels/MaterialProperties/MaterialPropertiesState.h +++ b/SynapseEngine/EditorCore/ViewModels/MaterialProperties/MaterialPropertiesState.h @@ -1,6 +1,7 @@ #pragma once #include #include +#include #include "Engine/Material/Material.h" namespace Syn { diff --git a/SynapseEngine/EditorCore/ViewModels/TextureHierarchy/TextureHierarchyState.h b/SynapseEngine/EditorCore/ViewModels/TextureHierarchy/TextureHierarchyState.h index fc714a01..59299c89 100644 --- a/SynapseEngine/EditorCore/ViewModels/TextureHierarchy/TextureHierarchyState.h +++ b/SynapseEngine/EditorCore/ViewModels/TextureHierarchy/TextureHierarchyState.h @@ -1,6 +1,7 @@ #pragma once #include #include +#include namespace Syn { struct TextureNode { diff --git a/SynapseEngine/EditorCore/ViewModels/TextureProperties/TexturePropertiesState.h b/SynapseEngine/EditorCore/ViewModels/TextureProperties/TexturePropertiesState.h index da4931b6..e40fc250 100644 --- a/SynapseEngine/EditorCore/ViewModels/TextureProperties/TexturePropertiesState.h +++ b/SynapseEngine/EditorCore/ViewModels/TextureProperties/TexturePropertiesState.h @@ -1,6 +1,7 @@ #pragma once #include "Engine/Image/Data/Cpu/CpuTextureData.h" #include +#include namespace Syn { struct TexturePropertiesState { diff --git a/SynapseEngine/Engine/Render/Passes/PostProcess/InfiniteGrid/InfiniteGridPass.cpp b/SynapseEngine/Engine/Render/Passes/PostProcess/InfiniteGrid/InfiniteGridPass.cpp new file mode 100644 index 00000000..34f12178 --- /dev/null +++ b/SynapseEngine/Engine/Render/Passes/PostProcess/InfiniteGrid/InfiniteGridPass.cpp @@ -0,0 +1,120 @@ +#include "InfiniteGridPass.h" +#include "Engine/ServiceLocator.h" +#include "Engine/Manager/ShaderManager.h" +#include "Engine/Vk/Image/ImageViewNames.h" +#include "Engine/Render/RenderNames.h" +#include "Engine/Vk/Rendering/PushConstant.h" + +namespace Syn { + + #include "Engine/Shaders/Includes/PushConstants/InfiniteGridPC.glsl" + + bool InfiniteGridPass::ShouldExecute(const RenderContext& context) const + { + return context.scene->GetSettings()->debug.enableInfiniteGrid; + } + + void InfiniteGridPass::Initialize() { + auto shaderManager = ServiceLocator::GetShaderManager(); + + Vk::ShaderProgramConfig config; + config.useDescriptorBuffers = false; + + _shaderProgram = shaderManager->CreateProgram("InfiniteGridProgram", { + ShaderNames::FullscreenVert, + ShaderNames::InfiniteGridFrag + }, config); + + _graphicsState = { + .raster = { + .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, + .cullMode = VK_CULL_MODE_NONE, + .frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE, + .polygonMode = VK_POLYGON_MODE_FILL, + .lineWidth = 1.0f + }, + .depth = { + .testEnable = VK_TRUE, + .writeEnable = VK_FALSE, + .compareOp = VK_COMPARE_OP_LESS_OR_EQUAL + }, + .blendStates = { + { + .enable = VK_TRUE, + .srcColorFactor = VK_BLEND_FACTOR_SRC_ALPHA, + .dstColorFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, + .colorBlendOp = VK_BLEND_OP_ADD, + .srcAlphaFactor = VK_BLEND_FACTOR_ONE, + .dstAlphaFactor = VK_BLEND_FACTOR_ZERO, + .alphaBlendOp = VK_BLEND_OP_ADD + } + }, + .colorAttachmentCount = 1, + .renderArea = std::nullopt + }; + } + + void InfiniteGridPass::PrepareFrame(const RenderContext& context) { + auto group = context.renderTargetManager->GetGroup(RenderTargetGroupNames::Main, context.frameIndex); + VkExtent2D extent = { group->GetWidth(), group->GetHeight() }; + _graphicsState.renderArea = extent; + + _colorAttachments.push_back(Vk::RenderUtils::CreateAttachment({ + .imageView = group->GetImage(RenderTargetNames::Main)->GetView(Vk::ImageViewNames::Default), + .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + .loadOp = VK_ATTACHMENT_LOAD_OP_LOAD, + .storeOp = VK_ATTACHMENT_STORE_OP_STORE + })); + + _depthAttachment = Vk::RenderUtils::CreateAttachment({ + .imageView = group->GetImage(RenderTargetNames::OpaqueDepth)->GetView(Vk::ImageViewNames::Default), + .layout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL, + .loadOp = VK_ATTACHMENT_LOAD_OP_LOAD, + .storeOp = VK_ATTACHMENT_STORE_OP_STORE + }); + + _renderInfo = Vk::RenderingInfoConfig{ + .renderArea = extent, + .colorAttachments = _colorAttachments, + .depthAttachment = &_depthAttachment.value(), + .layerCount = 1 + }; + } + + void InfiniteGridPass::PushConstants(const RenderContext& context) { + auto scene = context.scene; + auto settings = scene->GetSettings(); + uint32_t fIdx = context.frameIndex; + + Vk::PushConstant pc; + pc->frameGlobalContextBufferAddr = scene->GetSceneDrawData()->frameContextBuffer.GetAddress(fIdx); + + uint32_t planeFlags = 0; + if (settings->debug.gridShowXZ) planeFlags |= (1 << 0); + if (settings->debug.gridShowXY) planeFlags |= (1 << 1); + if (settings->debug.gridShowYZ) planeFlags |= (1 << 2); + pc->planeFlags = planeFlags; + + uint32_t axisFlags = 0; + if (settings->debug.gridShowAxisX) axisFlags |= (1 << 0); + if (settings->debug.gridShowAxisY) axisFlags |= (1 << 1); + if (settings->debug.gridShowAxisZ) axisFlags |= (1 << 2); + pc->axisFlags = axisFlags; + + pc->gridScale = settings->debug.gridScale; + pc->fadeDistance = settings->debug.fadeDistance; + pc->gridThickness = settings->debug.gridThickness; + pc->axisThickness = settings->debug.axisThickness; + + pc->gridColor = settings->debug.gridColor; + pc->axisXColor = settings->debug.axisXColor; + pc->axisYColor = settings->debug.axisYColor; + pc->axisZColor = settings->debug.axisZColor; + + pc.Push(context.cmd, _shaderProgram->GetLayout()); + } + + void InfiniteGridPass::Draw(const RenderContext& context) { + vkCmdDraw(context.cmd, 3, 1, 0, 0); + } +} \ No newline at end of file diff --git a/SynapseEngine/Engine/Render/Passes/PostProcess/InfiniteGrid/InfiniteGridPass.h b/SynapseEngine/Engine/Render/Passes/PostProcess/InfiniteGrid/InfiniteGridPass.h new file mode 100644 index 00000000..6a28f6e0 --- /dev/null +++ b/SynapseEngine/Engine/Render/Passes/PostProcess/InfiniteGrid/InfiniteGridPass.h @@ -0,0 +1,20 @@ +#pragma once +#include "Engine/SynApi.h" +#include "Engine/Render/Passes/GraphicsPass.h" + +namespace Syn { + class SYN_API InfiniteGridPass : public GraphicsPass { + public: + InfiniteGridPass() = default; + + std::string GetName() const override { return "InfiniteGridPass"; } + std::string GetGroup() const override { return PassGroupNames::DebugPasses; } + void Initialize() override; + bool ShouldCollectStatistics() const override { return true; } + protected: + bool ShouldExecute(const RenderContext& context) const override; + void PrepareFrame(const RenderContext& context) override; + void PushConstants(const RenderContext& context) override; + void Draw(const RenderContext& context) override; + }; +} \ No newline at end of file diff --git a/SynapseEngine/Engine/Render/Passes/Ssao/SsaoBlurPass.cpp b/SynapseEngine/Engine/Render/Passes/PostProcess/Ssao/SsaoBlurPass.cpp similarity index 100% rename from SynapseEngine/Engine/Render/Passes/Ssao/SsaoBlurPass.cpp rename to SynapseEngine/Engine/Render/Passes/PostProcess/Ssao/SsaoBlurPass.cpp diff --git a/SynapseEngine/Engine/Render/Passes/Ssao/SsaoBlurPass.h b/SynapseEngine/Engine/Render/Passes/PostProcess/Ssao/SsaoBlurPass.h similarity index 100% rename from SynapseEngine/Engine/Render/Passes/Ssao/SsaoBlurPass.h rename to SynapseEngine/Engine/Render/Passes/PostProcess/Ssao/SsaoBlurPass.h diff --git a/SynapseEngine/Engine/Render/Passes/Ssao/SsaoInitPass.cpp b/SynapseEngine/Engine/Render/Passes/PostProcess/Ssao/SsaoInitPass.cpp similarity index 100% rename from SynapseEngine/Engine/Render/Passes/Ssao/SsaoInitPass.cpp rename to SynapseEngine/Engine/Render/Passes/PostProcess/Ssao/SsaoInitPass.cpp diff --git a/SynapseEngine/Engine/Render/Passes/Ssao/SsaoInitPass.h b/SynapseEngine/Engine/Render/Passes/PostProcess/Ssao/SsaoInitPass.h similarity index 100% rename from SynapseEngine/Engine/Render/Passes/Ssao/SsaoInitPass.h rename to SynapseEngine/Engine/Render/Passes/PostProcess/Ssao/SsaoInitPass.h diff --git a/SynapseEngine/Engine/Render/Passes/Ssao/SsaoPass.cpp b/SynapseEngine/Engine/Render/Passes/PostProcess/Ssao/SsaoPass.cpp similarity index 98% rename from SynapseEngine/Engine/Render/Passes/Ssao/SsaoPass.cpp rename to SynapseEngine/Engine/Render/Passes/PostProcess/Ssao/SsaoPass.cpp index e04cd219..e0240388 100644 --- a/SynapseEngine/Engine/Render/Passes/Ssao/SsaoPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/PostProcess/Ssao/SsaoPass.cpp @@ -16,7 +16,7 @@ namespace Syn { - #include "../../../Shaders/Includes/PushConstants/SsaoPC.glsl" + #include "Engine/Shaders/Includes/PushConstants/SsaoPC.glsl" bool SsaoPass::ShouldExecute(const RenderContext& context) const { diff --git a/SynapseEngine/Engine/Render/Passes/Ssao/SsaoPass.h b/SynapseEngine/Engine/Render/Passes/PostProcess/Ssao/SsaoPass.h similarity index 100% rename from SynapseEngine/Engine/Render/Passes/Ssao/SsaoPass.h rename to SynapseEngine/Engine/Render/Passes/PostProcess/Ssao/SsaoPass.h diff --git a/SynapseEngine/Engine/Render/RendererFactory.cpp b/SynapseEngine/Engine/Render/RendererFactory.cpp index 3bf37a50..da9de28c 100644 --- a/SynapseEngine/Engine/Render/RendererFactory.cpp +++ b/SynapseEngine/Engine/Render/RendererFactory.cpp @@ -157,9 +157,10 @@ #include "Engine/Render/Passes/Shadow/PointLight/PointLightShadowTraditionalOpaquePass.h" #include "Engine/Render/Passes/Shadow/PointLight/PointLightShadowMeshletOpaquePass.h" -#include "Engine/Render/Passes/Ssao/SsaoInitPass.h" -#include "Engine/Render/Passes/Ssao/SsaoPass.h" -#include "Engine/Render/Passes/Ssao/SsaoBlurPass.h" +#include "Engine/Render/Passes/PostProcess/Ssao/SsaoInitPass.h" +#include "Engine/Render/Passes/PostProcess/Ssao/SsaoPass.h" +#include "Engine/Render/Passes/PostProcess/Ssao/SsaoBlurPass.h" +#include "Engine/Render/Passes/PostProcess/InfiniteGrid/InfiniteGridPass.h" #include "Engine/Render/Passes/Shading/Visibility/DebugVisibilityPass.h" @@ -332,6 +333,9 @@ namespace Syn // SkySphere pipeline->AddPass(std::make_unique()); + //Grid + pipeline->AddPass(std::make_unique()); + // Wireframe Passes pipeline->AddPass(std::make_unique()); pipeline->AddPass(std::make_unique()); diff --git a/SynapseEngine/Engine/Render/ShaderNames.h b/SynapseEngine/Engine/Render/ShaderNames.h index 583a27f7..40cec398 100644 --- a/SynapseEngine/Engine/Render/ShaderNames.h +++ b/SynapseEngine/Engine/Render/ShaderNames.h @@ -71,11 +71,13 @@ namespace Syn static constexpr const char* WireframeDebugVert = "Engine/Shaders/Passes/Wireframe/WireframeDebug.vert"; static constexpr const char* DebugVisibilityFrag = "Engine/Shaders/Passes/Shading/Visibility/DebugVisibility.frag"; - static constexpr const char* DpHvoComp = "Engine/Shaders/Passes/Ssao/DpHvo.comp"; - static constexpr const char* DpHvoBlurComp = "Engine/Shaders/Passes/Ssao/DpHvoBlur.comp"; - static constexpr const char* SsaoComp = "Engine/Shaders/Passes/Ssao/Ssao.comp"; - static constexpr const char* SsaoBlurComp = "Engine/Shaders/Passes/Ssao/SsaoBlur.comp"; + static constexpr const char* DpHvoComp = "Engine/Shaders/Passes/PostProcess/Ssao/DpHvo.comp"; + static constexpr const char* DpHvoBlurComp = "Engine/Shaders/Passes/PostProcess/Ssao/DpHvoBlur.comp"; + static constexpr const char* SsaoComp = "Engine/Shaders/Passes/PostProcess/Ssao/Ssao.comp"; + static constexpr const char* SsaoBlurComp = "Engine/Shaders/Passes/PostProcess/Ssao/SsaoBlur.comp"; + static constexpr const char* InfiniteGridFrag = "Engine/Shaders/Passes/PostProcess/InfiniteGrid/InfiniteGrid.frag"; + static constexpr const char* GeometryCullingCommandResetComp = "Engine/Shaders/Passes/Culling/Geometry/GeometryCullingCommandReset.comp"; static constexpr const char* GeometryMeshCullingComp = "Engine/Shaders/Passes/Culling/Geometry/GeometryMeshCulling.comp"; static constexpr const char* GeometryModelCullingComp = "Engine/Shaders/Passes/Culling/Geometry/GeometryModelCulling.comp"; @@ -144,5 +146,6 @@ namespace Syn static constexpr const char* PointLightShadowMortonModelCullingComp = "Engine/Shaders/Passes/Culling/PointLight/PointLightShadowMortonModelCulling.comp"; static constexpr const char* PointLightShadowStaticChunkCullingComp = "Engine/Shaders/Passes/Culling/PointLight/PointLightShadowStaticChunkCulling.comp"; static constexpr const char* PointLightShadowStaticModelCullingComp = "Engine/Shaders/Passes/Culling/PointLight/PointLightShadowStaticModelCulling.comp"; + }; } \ No newline at end of file diff --git a/SynapseEngine/Engine/Scene/Settings/DebugSettings.cpp b/SynapseEngine/Engine/Scene/Settings/DebugSettings.cpp index 3ea959af..19681280 100644 --- a/SynapseEngine/Engine/Scene/Settings/DebugSettings.cpp +++ b/SynapseEngine/Engine/Scene/Settings/DebugSettings.cpp @@ -31,5 +31,20 @@ namespace Syn , outlinePrimaryColor(glm::vec4(1.0f, 0.60f, 0.0f, 1.0f)) , outlineSecondaryColor(glm::vec4(1.0f, 0.85f, 0.0f, 1.0f)) , outlineThickness(2.0f) + , enableInfiniteGrid(false) + , gridScale(1.0f) + , fadeDistance(250.0f) + , gridThickness(1.0f) + , axisThickness(2.0f) + , gridShowXZ(true) + , gridShowXY(false) + , gridShowYZ(false) + , gridShowAxisX(true) + , gridShowAxisY(true) + , gridShowAxisZ(true) + , gridColor(glm::vec4(0.5f, 0.5f, 0.5f, 1.0f)) + , axisXColor(glm::vec4(1.0f, 0.2f, 0.2f, 1.0f)) + , axisYColor(glm::vec4(0.2f, 1.0f, 0.2f, 1.0f)) + , axisZColor(glm::vec4(0.2f, 0.2f, 1.0f, 1.0f)) {} } \ No newline at end of file diff --git a/SynapseEngine/Engine/Scene/Settings/DebugSettings.h b/SynapseEngine/Engine/Scene/Settings/DebugSettings.h index be8c4ab2..bca433b2 100644 --- a/SynapseEngine/Engine/Scene/Settings/DebugSettings.h +++ b/SynapseEngine/Engine/Scene/Settings/DebugSettings.h @@ -60,5 +60,22 @@ namespace Syn glm::vec4 outlinePrimaryColor; glm::vec4 outlineSecondaryColor; float outlineThickness; + + // Infinite grid + bool enableInfiniteGrid; + float gridScale; + float fadeDistance; + float gridThickness; + float axisThickness; + bool gridShowXZ; + bool gridShowXY; + bool gridShowYZ; + bool gridShowAxisX; + bool gridShowAxisY; + bool gridShowAxisZ; + glm::vec4 gridColor; + glm::vec4 axisXColor; + glm::vec4 axisYColor; + glm::vec4 axisZColor; }; } \ No newline at end of file diff --git a/SynapseEngine/Engine/Serialization/Schema/Scene/SceneSettingsSchema.h b/SynapseEngine/Engine/Serialization/Schema/Scene/SceneSettingsSchema.h index 4f4ab568..903977e0 100644 --- a/SynapseEngine/Engine/Serialization/Schema/Scene/SceneSettingsSchema.h +++ b/SynapseEngine/Engine/Serialization/Schema/Scene/SceneSettingsSchema.h @@ -20,7 +20,7 @@ namespace Syn ScopedArchiveObject obj(ar, name); auto& settings = const_cast&>(val); - // Hardware Devices (A frameworköd automatikusan kezeli az enumokat!) + // Hardware Devices ar.Property("geometryCullingDevice", settings.geometryCullingDevice); ar.Property("spotLightCullingDevice", settings.spotLightCullingDevice); ar.Property("pointLightCullingDevice", settings.pointLightCullingDevice); @@ -167,6 +167,23 @@ namespace Syn ar.Property("outlinePrimaryColor", settings.outlinePrimaryColor); ar.Property("outlineSecondaryColor", settings.outlineSecondaryColor); ar.Property("outlineThickness", settings.outlineThickness); + + // Infinite Grid + ar.Property("enableInfiniteGrid", settings.enableInfiniteGrid); + ar.Property("gridScale", settings.gridScale); + ar.Property("fadeDistance", settings.fadeDistance); + ar.Property("gridThickness", settings.gridThickness); + ar.Property("axisThickness", settings.axisThickness); + ar.Property("gridShowXZ", settings.gridShowXZ); + ar.Property("gridShowXY", settings.gridShowXY); + ar.Property("gridShowYZ", settings.gridShowYZ); + ar.Property("gridShowAxisX", settings.gridShowAxisX); + ar.Property("gridShowAxisY", settings.gridShowAxisY); + ar.Property("gridShowAxisZ", settings.gridShowAxisZ); + ar.Property("gridColor", settings.gridColor); + ar.Property("axisXColor", settings.axisXColor); + ar.Property("axisYColor", settings.axisYColor); + ar.Property("axisZColor", settings.axisZColor); } }; diff --git a/SynapseEngine/Engine/Shaders/Includes/PushConstants/InfiniteGridPC.glsl b/SynapseEngine/Engine/Shaders/Includes/PushConstants/InfiniteGridPC.glsl new file mode 100644 index 00000000..fe21b367 --- /dev/null +++ b/SynapseEngine/Engine/Shaders/Includes/PushConstants/InfiniteGridPC.glsl @@ -0,0 +1,20 @@ +#ifndef SYN_INCLUDES_PUSH_CONSTANTS_HIZ_LINEARIZE_DEPTH_PC_GLSL +#define SYN_INCLUDES_PUSH_CONSTANTS_HIZ_LINEARIZE_DEPTH_PC_GLSL + +#include "../SharedGpuTypes.glsl" + +struct InfiniteGridPC { + uint64_t frameGlobalContextBufferAddr; + uint planeFlags; // Bit 0: XZ, Bit 1: XY, Bit 2: YZ + uint axisFlags; // Bit 0: X, Bit 1: Y, Bit 2: Z + float gridScale; + float fadeDistance; + float gridThickness; + float axisThickness; + vec4 gridColor; + vec4 axisXColor; + vec4 axisYColor; + vec4 axisZColor; +}; + +#endif \ No newline at end of file diff --git a/SynapseEngine/Engine/Shaders/Passes/PostProcess/InfiniteGrid/InfiniteGrid.frag b/SynapseEngine/Engine/Shaders/Passes/PostProcess/InfiniteGrid/InfiniteGrid.frag new file mode 100644 index 00000000..c5b47514 --- /dev/null +++ b/SynapseEngine/Engine/Shaders/Passes/PostProcess/InfiniteGrid/InfiniteGrid.frag @@ -0,0 +1,147 @@ +#version 460 +#extension GL_GOOGLE_include_directive : require +#extension GL_EXT_buffer_reference2 : require +#extension GL_EXT_shader_explicit_arithmetic_types_int64 : require + +#include "../../../Includes/Core.glsl" +#include "../../../Includes/Common/FrameGlobalContext.glsl" +#include "../../../Includes/Common/Camera.glsl" +#include "../../../Includes/PushConstants/InfiniteGridPC.glsl" + +layout(location = 0) in vec2 inUV; +layout(location = 0) out vec4 outColor; + +layout(push_constant) uniform PushConstants { + InfiniteGridPC pc; +}; + +struct IntersectionData { + float t; + vec3 worldPos; + vec4 color; + bool valid; +}; + +float ComputeGrid(vec2 coord, float scale, float thickness) { + vec2 scaledCoord = coord / scale; + vec2 derivative = fwidth(scaledCoord); + + vec2 grid = abs(fract(scaledCoord - 0.5) - 0.5) / (derivative * thickness); + float line = min(grid.x, grid.y); + return 1.0 - min(line, 1.0); +} + +void main() { + FrameGlobalContext ctx = GET_FRAME_CONTEXT(pc.frameGlobalContextBufferAddr); + uint cameraDenseIndex = GET_SPARSE_INDEX(ctx.cameraSparseMapBufferAddr, ctx.activeCameraEntity); + CameraComponent camera = GET_CAMERA(ctx.cameraBufferAddr, cameraDenseIndex); + + vec3 ndcPos = vec3(inUV * 2.0 - 1.0, 1.0); + vec3 ndcNear = vec3(ndcPos.xy, 0.000001); + + vec4 worldFar4 = camera.viewProjVulkanInv * vec4(ndcPos, 1.0); + vec4 worldNear4 = camera.viewProjVulkanInv * vec4(ndcNear, 1.0); + vec3 worldFar = worldFar4.xyz / worldFar4.w; + vec3 worldNear = worldNear4.xyz / worldNear4.w; + + vec3 rayDir = normalize(worldFar - worldNear); + vec3 rayOrigin = worldNear; + + IntersectionData hits[3]; + for(int i = 0; i < 3; ++i) hits[i].valid = false; + + if (rayDir.y != 0.0) { + float t = -rayOrigin.y / rayDir.y; + if (t > 0.0) { + hits[0].t = t; hits[0].worldPos = rayOrigin + rayDir * t; hits[0].valid = true; + vec2 coord = hits[0].worldPos.xz; vec4 c = vec4(0.0); + + if ((pc.planeFlags & 1) != 0) { + float grid1m = ComputeGrid(coord, pc.gridScale, pc.gridThickness); + float grid10m = ComputeGrid(coord, pc.gridScale * 10.0, pc.gridThickness * 1.5); + float lodFade = 1.0 - smoothstep(10.0, 50.0, distance(camera.eye.xyz, hits[0].worldPos)); + c = mix(pc.gridColor * vec4(1.0, 1.0, 1.0, 0.5) * grid1m * lodFade, pc.gridColor * grid10m, grid10m); + } + + vec2 derivative = fwidth(coord); + vec2 axisLines = abs(coord) / (derivative * pc.axisThickness); + if ((pc.axisFlags & 1) != 0) c = mix(c, pc.axisXColor, 1.0 - min(axisLines.y, 1.0)); + if ((pc.axisFlags & 4) != 0) c = mix(c, pc.axisZColor, 1.0 - min(axisLines.x, 1.0)); + + c.a *= (1.0 - smoothstep(pc.fadeDistance * 0.1, pc.fadeDistance, t)); + hits[0].color = c; + } + } + + if (rayDir.z != 0.0) { + float t = -rayOrigin.z / rayDir.z; + if (t > 0.0) { + hits[1].t = t; hits[1].worldPos = rayOrigin + rayDir * t; hits[1].valid = true; + vec2 coord = hits[1].worldPos.xy; vec4 c = vec4(0.0); + + if ((pc.planeFlags & 2) != 0) { + float grid1m = ComputeGrid(coord, pc.gridScale, pc.gridThickness); + float grid10m = ComputeGrid(coord, pc.gridScale * 10.0, pc.gridThickness * 1.5); + float lodFade = 1.0 - smoothstep(10.0, 50.0, distance(camera.eye.xyz, hits[1].worldPos)); + c = mix(pc.gridColor * vec4(1.0, 1.0, 1.0, 0.5) * grid1m * lodFade, pc.gridColor * grid10m, grid10m); + } + + vec2 derivative = fwidth(coord); + vec2 axisLines = abs(coord) / (derivative * pc.axisThickness); + if ((pc.axisFlags & 1) != 0) c = mix(c, pc.axisXColor, 1.0 - min(axisLines.y, 1.0)); + if ((pc.axisFlags & 2) != 0) c = mix(c, pc.axisYColor, 1.0 - min(axisLines.x, 1.0)); + + c.a *= (1.0 - smoothstep(pc.fadeDistance * 0.1, pc.fadeDistance, t)); + hits[1].color = c; + } + } + + if (rayDir.x != 0.0) { + float t = -rayOrigin.x / rayDir.x; + if (t > 0.0) { + hits[2].t = t; hits[2].worldPos = rayOrigin + rayDir * t; hits[2].valid = true; + vec2 coord = hits[2].worldPos.yz; vec4 c = vec4(0.0); + + if ((pc.planeFlags & 4) != 0) { + float grid1m = ComputeGrid(coord, pc.gridScale, pc.gridThickness); + float grid10m = ComputeGrid(coord, pc.gridScale * 10.0, pc.gridThickness * 1.5); + float lodFade = 1.0 - smoothstep(10.0, 50.0, distance(camera.eye.xyz, hits[2].worldPos)); + c = mix(pc.gridColor * vec4(1.0, 1.0, 1.0, 0.5) * grid1m * lodFade, pc.gridColor * grid10m, grid10m); + } + + vec2 derivative = fwidth(coord); + vec2 axisLines = abs(coord) / (derivative * pc.axisThickness); + if ((pc.axisFlags & 2) != 0) c = mix(c, pc.axisYColor, 1.0 - min(axisLines.y, 1.0)); + if ((pc.axisFlags & 4) != 0) c = mix(c, pc.axisZColor, 1.0 - min(axisLines.x, 1.0)); + + c.a *= (1.0 - smoothstep(pc.fadeDistance * 0.1, pc.fadeDistance, t)); + hits[2].color = c; + } + } + + int sortIdx[3] = {0, 1, 2}; + if ((hits[sortIdx[0]].valid ? hits[sortIdx[0]].t : -1.0) < (hits[sortIdx[1]].valid ? hits[sortIdx[1]].t : -1.0)) { int tmp = sortIdx[0]; sortIdx[0] = sortIdx[1]; sortIdx[1] = tmp; } + if ((hits[sortIdx[1]].valid ? hits[sortIdx[1]].t : -1.0) < (hits[sortIdx[2]].valid ? hits[sortIdx[2]].t : -1.0)) { int tmp = sortIdx[1]; sortIdx[1] = sortIdx[2]; sortIdx[2] = tmp; } + if ((hits[sortIdx[0]].valid ? hits[sortIdx[0]].t : -1.0) < (hits[sortIdx[1]].valid ? hits[sortIdx[1]].t : -1.0)) { int tmp = sortIdx[0]; sortIdx[0] = sortIdx[1]; sortIdx[1] = tmp; } + + vec4 accumulatedColor = vec4(0.0); + float closestDepth = 1.0; + bool hashit = false; + + for(int i = 0; i < 3; ++i) { + int idx = sortIdx[i]; + if (hits[idx].valid && hits[idx].color.a > 0.001) { + accumulatedColor.rgb = mix(accumulatedColor.rgb, hits[idx].color.rgb, hits[idx].color.a); + accumulatedColor.a = mix(accumulatedColor.a, 1.0, hits[idx].color.a); + + vec4 clipPos = camera.viewProjVulkan * vec4(hits[idx].worldPos, 1.0); + closestDepth = clipPos.z / clipPos.w; + hashit = true; + } + } + + if (!hashit || accumulatedColor.a < 0.01) discard; + + gl_FragDepth = closestDepth; + outColor = accumulatedColor; +} \ No newline at end of file diff --git a/SynapseEngine/Engine/Shaders/Passes/Ssao/DpHvo.comp b/SynapseEngine/Engine/Shaders/Passes/PostProcess/Ssao/DpHvo.comp similarity index 94% rename from SynapseEngine/Engine/Shaders/Passes/Ssao/DpHvo.comp rename to SynapseEngine/Engine/Shaders/Passes/PostProcess/Ssao/DpHvo.comp index 5ffd7e2c..4b42a2a9 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Ssao/DpHvo.comp +++ b/SynapseEngine/Engine/Shaders/Passes/PostProcess/Ssao/DpHvo.comp @@ -4,17 +4,17 @@ #extension GL_EXT_shader_explicit_arithmetic_types_int64 : require #extension GL_KHR_shader_subgroup_quad : require -#include "../../Includes/Core.glsl" -#include "../../Includes/Common/FrameGlobalContext.glsl" -#include "../../Includes/Common/Camera.glsl" -#include "../../Includes/Utils/DepthMath.glsl" +#include "../../../Includes/Core.glsl" +#include "../../../Includes/Common/FrameGlobalContext.glsl" +#include "../../../Includes/Common/Camera.glsl" +#include "../../../Includes/Utils/DepthMath.glsl" layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; layout(set = 2, binding = 0) uniform sampler2D inHizPyramid; layout(set = 2, binding = 1, r16f) uniform writeonly image2D outAoImage; -#include "../../Includes/PushConstants/DpHvoPC.glsl" +#include "../../../Includes/PushConstants/DpHvoPC.glsl" layout(push_constant) uniform PushConstants { DpHvoPC pc; diff --git a/SynapseEngine/Engine/Shaders/Passes/Ssao/DpHvoBlur.comp b/SynapseEngine/Engine/Shaders/Passes/PostProcess/Ssao/DpHvoBlur.comp similarity index 91% rename from SynapseEngine/Engine/Shaders/Passes/Ssao/DpHvoBlur.comp rename to SynapseEngine/Engine/Shaders/Passes/PostProcess/Ssao/DpHvoBlur.comp index 4ab9011b..1ef713e2 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Ssao/DpHvoBlur.comp +++ b/SynapseEngine/Engine/Shaders/Passes/PostProcess/Ssao/DpHvoBlur.comp @@ -3,9 +3,9 @@ #extension GL_EXT_buffer_reference2 : require #extension GL_EXT_shader_explicit_arithmetic_types_int64 : require -#include "../../Includes/Core.glsl" -#include "../../Includes/Common/FrameGlobalContext.glsl" -#include "../../Includes/Common/Camera.glsl" +#include "../../../Includes/Core.glsl" +#include "../../../Includes/Common/FrameGlobalContext.glsl" +#include "../../../Includes/Common/Camera.glsl" layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; @@ -13,7 +13,7 @@ layout(set = 2, binding = 0) uniform sampler2D inAoImage; layout(set = 2, binding = 1) uniform sampler2D inHizPyramid; layout(set = 2, binding = 2, r16f) uniform writeonly image2D outAoImage; -#include "../../Includes/PushConstants/DpHvoBlurPC.glsl" +#include "../../../Includes/PushConstants/DpHvoBlurPC.glsl" layout(push_constant) uniform PushConstants { DpHvoBlurPC pc; diff --git a/SynapseEngine/Engine/Shaders/Passes/Ssao/Ssao.comp b/SynapseEngine/Engine/Shaders/Passes/PostProcess/Ssao/Ssao.comp similarity index 92% rename from SynapseEngine/Engine/Shaders/Passes/Ssao/Ssao.comp rename to SynapseEngine/Engine/Shaders/Passes/PostProcess/Ssao/Ssao.comp index fe424798..06b64ea9 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Ssao/Ssao.comp +++ b/SynapseEngine/Engine/Shaders/Passes/PostProcess/Ssao/Ssao.comp @@ -4,11 +4,11 @@ #extension GL_EXT_shader_explicit_arithmetic_types_int64 : require #extension GL_KHR_shader_subgroup_quad : require -#include "../../Includes/Core.glsl" -#include "../../Includes/Common/FrameGlobalContext.glsl" -#include "../../Includes/Common/Ssao.glsl" -#include "../../Includes/Common/Camera.glsl" -#include "../../Includes/Utils/DepthMath.glsl" +#include "../../../Includes/Core.glsl" +#include "../../../Includes/Common/FrameGlobalContext.glsl" +#include "../../../Includes/Common/Ssao.glsl" +#include "../../../Includes/Common/Camera.glsl" +#include "../../../Includes/Utils/DepthMath.glsl" layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; @@ -16,7 +16,7 @@ layout(set = 2, binding = 0) uniform sampler2D inHizPyramid; layout(set = 2, binding = 1) uniform sampler2D inNoiseTexture; layout(set = 2, binding = 2, r16f) uniform writeonly image2D outAoImage; -#include "../../Includes/PushConstants/SsaoPC.glsl" +#include "../../../Includes/PushConstants/SsaoPC.glsl" layout(push_constant) uniform PushConstants { SsaoPC pc; diff --git a/SynapseEngine/Engine/Shaders/Passes/Ssao/SsaoBlur.comp b/SynapseEngine/Engine/Shaders/Passes/PostProcess/Ssao/SsaoBlur.comp similarity index 87% rename from SynapseEngine/Engine/Shaders/Passes/Ssao/SsaoBlur.comp rename to SynapseEngine/Engine/Shaders/Passes/PostProcess/Ssao/SsaoBlur.comp index 5fde550f..ac9682d9 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Ssao/SsaoBlur.comp +++ b/SynapseEngine/Engine/Shaders/Passes/PostProcess/Ssao/SsaoBlur.comp @@ -3,15 +3,15 @@ #extension GL_EXT_buffer_reference2 : require #extension GL_EXT_shader_explicit_arithmetic_types_int64 : require -#include "../../Includes/Core.glsl" -#include "../../Includes/Common/FrameGlobalContext.glsl" +#include "../../../Includes/Core.glsl" +#include "../../../Includes/Common/FrameGlobalContext.glsl" layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; layout(set = 2, binding = 0) uniform sampler2D inAoImage; layout(set = 2, binding = 1, r16f) uniform writeonly image2D outAoImage; -#include "../../Includes/PushConstants/SsaoBlurPC.glsl" +#include "../../../Includes/PushConstants/SsaoBlurPC.glsl" layout(push_constant) uniform PushConstants { SsaoBlurPC pc; From dfb4c42054cdf79ec78376afed6050d98e8dd784 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20P=C3=A9ter?= Date: Tue, 7 Jul 2026 17:50:38 +0200 Subject: [PATCH 27/51] Implemented ortho and orbit camera --- .../Editor/EditorApi/Impl/CameraApiImpl.cpp | 20 +++ .../Editor/EditorApi/Impl/CameraApiImpl.h | 8 ++ .../Editor/View/Component/Core/CameraView.cpp | 25 +++- SynapseEngine/EditorCore/Api/ICameraApi.h | 8 ++ .../Component/Core/Camera/CameraCommands.h | 3 + .../Component/Core/Camera/CameraIntent.h | 7 + .../Component/Core/Camera/CameraState.h | 4 + .../Component/Core/Camera/CameraViewModel.cpp | 42 +++++- .../Component/Core/Camera/CameraViewModel.h | 5 +- .../MaterialHierarchyIntent.h | 1 + .../MaterialPropertiesIntent.h | 1 + .../TextureHierarchy/TextureHierarchyIntent.h | 1 + .../TexturePropertiesIntent.h | 1 + .../Engine/Component/Core/CameraComponent.cpp | 3 + .../Engine/Component/Core/CameraComponent.h | 4 + .../Engine/System/Core/CameraSystem.cpp | 134 +++++++++++------- 16 files changed, 207 insertions(+), 60 deletions(-) diff --git a/SynapseEngine/Editor/EditorApi/Impl/CameraApiImpl.cpp b/SynapseEngine/Editor/EditorApi/Impl/CameraApiImpl.cpp index ae91c45e..3d0b61b6 100644 --- a/SynapseEngine/Editor/EditorApi/Impl/CameraApiImpl.cpp +++ b/SynapseEngine/Editor/EditorApi/Impl/CameraApiImpl.cpp @@ -8,6 +8,16 @@ namespace Syn { return EditorApiUtils::HasComponent(_sceneManager, entity); } + bool CameraApiImpl::GetCameraIsOrthographic(EntityID entity) const { + return EditorApiUtils::ReadComponent(_sceneManager, entity, [](const auto& c) { return c.isOrthographic; }, false); + } + float CameraApiImpl::GetCameraOrthoSize(EntityID entity) const { + return EditorApiUtils::ReadComponent(_sceneManager, entity, [](const auto& c) { return c.orthoSize; }, 10.0f); + } + bool CameraApiImpl::GetCameraUseOrbit(EntityID entity) const { + return EditorApiUtils::ReadComponent(_sceneManager, entity, [](const auto& c) { return c.useOrbit; }, false); + } + float CameraApiImpl::GetCameraYaw(EntityID entity) const { return EditorApiUtils::ReadComponent(_sceneManager, entity, [](const auto& c) { return c.yaw; }, 0.0f); } @@ -33,6 +43,16 @@ namespace Syn { return EditorApiUtils::ReadComponent(_sceneManager, entity, [](const auto& c) { return c.distance; }, 10.0f); } + void CameraApiImpl::SetCameraIsOrthographic(EntityID entity, bool isOrthographic) { + EditorApiUtils::ModifyComponent(_sceneManager, entity, [&](auto& c, auto pool) { c.isOrthographic = isOrthographic; }); + } + void CameraApiImpl::SetCameraOrthoSize(EntityID entity, float orthoSize) { + EditorApiUtils::ModifyComponent(_sceneManager, entity, [&](auto& c, auto pool) { c.orthoSize = orthoSize; }); + } + void CameraApiImpl::SetCameraUseOrbit(EntityID entity, bool useOrbit) { + EditorApiUtils::ModifyComponent(_sceneManager, entity, [&](auto& c, auto pool) { c.useOrbit = useOrbit; }); + } + void CameraApiImpl::SetCameraYaw(EntityID entity, float yaw) { EditorApiUtils::ModifyComponent(_sceneManager, entity, [&](auto& c, auto pool) { c.yaw = yaw; }); } diff --git a/SynapseEngine/Editor/EditorApi/Impl/CameraApiImpl.h b/SynapseEngine/Editor/EditorApi/Impl/CameraApiImpl.h index 4d5f24b3..674c8cb2 100644 --- a/SynapseEngine/Editor/EditorApi/Impl/CameraApiImpl.h +++ b/SynapseEngine/Editor/EditorApi/Impl/CameraApiImpl.h @@ -9,6 +9,10 @@ namespace Syn { bool HasCamera(EntityID entity) const override; + bool GetCameraIsOrthographic(EntityID entity) const override; + float GetCameraOrthoSize(EntityID entity) const override; + bool GetCameraUseOrbit(EntityID entity) const override; + float GetCameraYaw(EntityID entity) const override; float GetCameraPitch(EntityID entity) const override; float GetCameraNearPlane(EntityID entity) const override; @@ -18,6 +22,10 @@ namespace Syn { float GetCameraSensitivity(EntityID entity) const override; float GetCameraDistance(EntityID entity) const override; + void SetCameraIsOrthographic(EntityID entity, bool isOrthographic) override; + void SetCameraOrthoSize(EntityID entity, float orthoSize) override; + void SetCameraUseOrbit(EntityID entity, bool useOrbit) override; + void SetCameraYaw(EntityID entity, float yaw) override; void SetCameraPitch(EntityID entity, float pitch) override; void SetCameraNearPlane(EntityID entity, float nearPlane) override; diff --git a/SynapseEngine/Editor/View/Component/Core/CameraView.cpp b/SynapseEngine/Editor/View/Component/Core/CameraView.cpp index c1959799..d2e75dab 100644 --- a/SynapseEngine/Editor/View/Component/Core/CameraView.cpp +++ b/SynapseEngine/Editor/View/Component/Core/CameraView.cpp @@ -1,5 +1,5 @@ #include "CameraView.h" -#include "Editor/Manager/EditorIcons.h" // Esetleg lecserélheted egy kamera ikonra, ha van (pl. SYN_ICON_CAMERA) +#include "Editor/Manager/EditorIcons.h" #include "Editor/Widgets/CardWidget.h" #include "Editor/Widgets/PropertyGrid.h" #include @@ -17,6 +17,16 @@ namespace Syn { { if (Syn::UI::BeginPropertyGrid("CameraGrid")) { + if (Syn::UI::PropertyCheckbox("Orthographic", state.isOrthographic)) { + vm.Dispatch(SetCameraIsOrthographicIntent{ state.isOrthographic }); + } + + if (Syn::UI::PropertyCheckbox("Orbit Camera", state.useOrbit)) { + vm.Dispatch(SetCameraUseOrbitIntent{ state.useOrbit }); + } + + Syn::UI::PropertySeparator(); + if (Syn::UI::PropertyDragFloat("Yaw", state.yaw, 0.1f, -360.0f, 360.0f, "%.2f")) { vm.Dispatch(SetCameraYawIntent{ state.yaw, !ImGui::IsItemDeactivatedAfterEdit() }); } @@ -27,8 +37,17 @@ namespace Syn { Syn::UI::PropertySeparator(); - if (Syn::UI::PropertyDragFloat("FOV", state.fov, 0.1f, 1.0f, 179.0f, "%.2f")) { - vm.Dispatch(SetCameraFovIntent{ state.fov, !ImGui::IsItemDeactivatedAfterEdit() }); + if (state.isOrthographic) + { + if (Syn::UI::PropertyDragFloat("Ortho Size", state.orthoSize, 0.1f, 1.0f, 1000.0f, "%.2f")) { + vm.Dispatch(SetCameraOrthoSizeIntent{ state.orthoSize, !ImGui::IsItemDeactivatedAfterEdit() }); + } + } + else + { + if (Syn::UI::PropertyDragFloat("FOV", state.fov, 0.1f, 1.0f, 179.0f, "%.2f")) { + vm.Dispatch(SetCameraFovIntent{ state.fov, !ImGui::IsItemDeactivatedAfterEdit() }); + } } if (Syn::UI::PropertyDragFloat("Near Plane", state.nearPlane, 0.01f, 0.001f, 100.0f, "%.3f")) { diff --git a/SynapseEngine/EditorCore/Api/ICameraApi.h b/SynapseEngine/EditorCore/Api/ICameraApi.h index 2965f3b0..83364409 100644 --- a/SynapseEngine/EditorCore/Api/ICameraApi.h +++ b/SynapseEngine/EditorCore/Api/ICameraApi.h @@ -8,6 +8,10 @@ namespace Syn { virtual bool HasCamera(EntityID entity) const = 0; + virtual bool GetCameraIsOrthographic(EntityID entity) const = 0; + virtual float GetCameraOrthoSize(EntityID entity) const = 0; + virtual bool GetCameraUseOrbit(EntityID entity) const = 0; + virtual float GetCameraYaw(EntityID entity) const = 0; virtual float GetCameraPitch(EntityID entity) const = 0; virtual float GetCameraNearPlane(EntityID entity) const = 0; @@ -17,6 +21,10 @@ namespace Syn { virtual float GetCameraSensitivity(EntityID entity) const = 0; virtual float GetCameraDistance(EntityID entity) const = 0; + virtual void SetCameraIsOrthographic(EntityID entity, bool isOrthographic) = 0; + virtual void SetCameraOrthoSize(EntityID entity, float orthoSize) = 0; + virtual void SetCameraUseOrbit(EntityID entity, bool useOrbit) = 0; + virtual void SetCameraYaw(EntityID entity, float yaw) = 0; virtual void SetCameraPitch(EntityID entity, float pitch) = 0; virtual void SetCameraNearPlane(EntityID entity, float nearPlane) = 0; diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraCommands.h b/SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraCommands.h index c743c64f..44b99c2a 100644 --- a/SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraCommands.h +++ b/SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraCommands.h @@ -4,6 +4,9 @@ namespace Syn { + using ChangeCameraIsOrthographicCommand = ComponentChangeCommand; + using ChangeCameraOrthoSizeCommand = ComponentChangeCommand; + using ChangeCameraUseOrbitCommand = ComponentChangeCommand; using ChangeCameraYawCommand = ComponentChangeCommand; using ChangeCameraPitchCommand = ComponentChangeCommand; using ChangeCameraNearPlaneCommand = ComponentChangeCommand; diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraIntent.h b/SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraIntent.h index 43ccce40..10824c37 100644 --- a/SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraIntent.h +++ b/SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraIntent.h @@ -3,6 +3,10 @@ namespace Syn { + struct SetCameraIsOrthographicIntent { bool isOrthographic; }; + struct SetCameraOrthoSizeIntent { float orthoSize; bool isDragging; }; + struct SetCameraUseOrbitIntent { bool useOrbit; }; + struct SetCameraYawIntent { float yaw; bool isDragging; }; struct SetCameraPitchIntent { float pitch; bool isDragging; }; struct SetCameraNearPlaneIntent { float nearPlane; bool isDragging; }; @@ -13,6 +17,9 @@ namespace Syn struct SetCameraDistanceIntent { float distance; bool isDragging; }; using CameraIntent = std::variant< + SetCameraIsOrthographicIntent, + SetCameraOrthoSizeIntent, + SetCameraUseOrbitIntent, SetCameraYawIntent, SetCameraPitchIntent, SetCameraNearPlaneIntent, diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraState.h b/SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraState.h index 43c8c3d6..a7716a16 100644 --- a/SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraState.h +++ b/SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraState.h @@ -4,6 +4,10 @@ namespace Syn { struct CameraState { bool hasComponent = false; + bool isOrthographic; + float orthoSize; + bool useOrbit; + float yaw; float pitch; float nearPlane; diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraViewModel.cpp index dacf8211..5c7ca3dc 100644 --- a/SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraViewModel.cpp +++ b/SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraViewModel.cpp @@ -22,6 +22,11 @@ namespace Syn { _state.hasComponent = true; + _state.isOrthographic = _cameraApi->GetCameraIsOrthographic(activeEntity); + _state.useOrbit = _cameraApi->GetCameraUseOrbit(activeEntity); + + if (!_orthoSizeDrag.IsDragging()) _state.orthoSize = _cameraApi->GetCameraOrthoSize(activeEntity); + if (!_yawDrag.IsDragging()) _state.yaw = _cameraApi->GetCameraYaw(activeEntity); if (!_pitchDrag.IsDragging()) _state.pitch = _cameraApi->GetCameraPitch(activeEntity); if (!_nearPlaneDrag.IsDragging()) _state.nearPlane = _cameraApi->GetCameraNearPlane(activeEntity); @@ -43,14 +48,47 @@ namespace Syn { using T = std::decay_t; - if constexpr (std::is_same_v) HandleSetYaw(arg); + // Új Dispatchek + if constexpr (std::is_same_v) HandleSetIsOrthographic(arg); + else if constexpr (std::is_same_v) HandleSetOrthoSize(arg); + else if constexpr (std::is_same_v) HandleSetUseOrbit(arg); + + else if constexpr (std::is_same_v) HandleSetYaw(arg); else if constexpr (std::is_same_v) HandleSetPitch(arg); else if constexpr (std::is_same_v) HandleSetNearPlane(arg); else if constexpr (std::is_same_v) HandleSetFarPlane(arg); else if constexpr (std::is_same_v) HandleSetFov(arg); else if constexpr (std::is_same_v) HandleSetSpeed(arg); else if constexpr (std::is_same_v) HandleSetSensitivity(arg); - else if constexpr (std::is_same_v) HandleSetDistance(arg); }, intent); + else if constexpr (std::is_same_v) HandleSetDistance(arg); + }, intent); + } + + void CameraViewModel::HandleSetIsOrthographic(const SetCameraIsOrthographicIntent& intent) + { + EntityID activeEntity = _selectionApi->GetSelectedEntity(); + if (activeEntity == NULL_ENTITY) return; + + _state.isOrthographic = intent.isOrthographic; + _cameraApi->SetCameraIsOrthographic(activeEntity, intent.isOrthographic); + } + + void CameraViewModel::HandleSetUseOrbit(const SetCameraUseOrbitIntent& intent) + { + EntityID activeEntity = _selectionApi->GetSelectedEntity(); + if (activeEntity == NULL_ENTITY) return; + + _state.useOrbit = intent.useOrbit; + _cameraApi->SetCameraUseOrbit(activeEntity, intent.useOrbit); + } + + void CameraViewModel::HandleSetOrthoSize(const SetCameraOrthoSizeIntent& intent) + { + EntityID activeEntity = _selectionApi->GetSelectedEntity(); + if (activeEntity == NULL_ENTITY) return; + _orthoSizeDrag.Handle(intent.isDragging, intent.orthoSize, _state.orthoSize, + [&](const float& v) { _cameraApi->SetCameraOrthoSize(activeEntity, v); }, + [&](const float& s, const float& e) { return std::make_shared(_cameraApi, activeEntity, s, e); }); } void CameraViewModel::HandleSetYaw(const SetCameraYawIntent& intent) diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraViewModel.h b/SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraViewModel.h index 9f89bd4c..6d7b1d60 100644 --- a/SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraViewModel.h +++ b/SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraViewModel.h @@ -18,6 +18,9 @@ namespace Syn { void Dispatch(const CameraIntent& intent) override; private: + void HandleSetIsOrthographic(const SetCameraIsOrthographicIntent& intent); + void HandleSetOrthoSize(const SetCameraOrthoSizeIntent& intent); + void HandleSetUseOrbit(const SetCameraUseOrbitIntent& intent); void HandleSetYaw(const SetCameraYawIntent& intent); void HandleSetPitch(const SetCameraPitchIntent& intent); void HandleSetNearPlane(const SetCameraNearPlaneIntent& intent); @@ -26,12 +29,12 @@ namespace Syn { void HandleSetSpeed(const SetCameraSpeedIntent& intent); void HandleSetSensitivity(const SetCameraSensitivityIntent& intent); void HandleSetDistance(const SetCameraDistanceIntent& intent); - private: ISelectionApi* _selectionApi = nullptr; ICameraApi* _cameraApi = nullptr; CameraState _state; + DragInteraction _orthoSizeDrag; DragInteraction _yawDrag; DragInteraction _pitchDrag; DragInteraction _nearPlaneDrag; diff --git a/SynapseEngine/EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyIntent.h b/SynapseEngine/EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyIntent.h index 2bf23ed7..1bb0bae7 100644 --- a/SynapseEngine/EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyIntent.h +++ b/SynapseEngine/EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyIntent.h @@ -1,6 +1,7 @@ #pragma once #include #include +#include namespace Syn { struct MaterialSelectIntent { diff --git a/SynapseEngine/EditorCore/ViewModels/MaterialProperties/MaterialPropertiesIntent.h b/SynapseEngine/EditorCore/ViewModels/MaterialProperties/MaterialPropertiesIntent.h index cf1aecce..5db0df6d 100644 --- a/SynapseEngine/EditorCore/ViewModels/MaterialProperties/MaterialPropertiesIntent.h +++ b/SynapseEngine/EditorCore/ViewModels/MaterialProperties/MaterialPropertiesIntent.h @@ -1,5 +1,6 @@ #pragma once #include +#include #include "Engine/Material/Material.h" namespace Syn { diff --git a/SynapseEngine/EditorCore/ViewModels/TextureHierarchy/TextureHierarchyIntent.h b/SynapseEngine/EditorCore/ViewModels/TextureHierarchy/TextureHierarchyIntent.h index 6f248f6f..03909a82 100644 --- a/SynapseEngine/EditorCore/ViewModels/TextureHierarchy/TextureHierarchyIntent.h +++ b/SynapseEngine/EditorCore/ViewModels/TextureHierarchy/TextureHierarchyIntent.h @@ -1,6 +1,7 @@ #pragma once #include #include +#include namespace Syn { struct TextureSelectIntent { diff --git a/SynapseEngine/EditorCore/ViewModels/TextureProperties/TexturePropertiesIntent.h b/SynapseEngine/EditorCore/ViewModels/TextureProperties/TexturePropertiesIntent.h index a2be1c3c..d3d6bd02 100644 --- a/SynapseEngine/EditorCore/ViewModels/TextureProperties/TexturePropertiesIntent.h +++ b/SynapseEngine/EditorCore/ViewModels/TextureProperties/TexturePropertiesIntent.h @@ -1,5 +1,6 @@ #pragma once #include +#include namespace Syn { struct DummyTexturePropertyIntent {}; diff --git a/SynapseEngine/Engine/Component/Core/CameraComponent.cpp b/SynapseEngine/Engine/Component/Core/CameraComponent.cpp index 96596f94..a8ba9ba7 100644 --- a/SynapseEngine/Engine/Component/Core/CameraComponent.cpp +++ b/SynapseEngine/Engine/Component/Core/CameraComponent.cpp @@ -15,6 +15,9 @@ namespace Syn , position{ 0.f, 0.f, 0.f } , target{ 0.f, 0.f, -1.f } , up{ 0.f, 1.f, 0.f } + , useOrbit(false) + , isOrthographic{ false } + , orthoSize{ 100.f } { direction = glm::normalize(target - position); right = glm::normalize(glm::cross(direction, up)); diff --git a/SynapseEngine/Engine/Component/Core/CameraComponent.h b/SynapseEngine/Engine/Component/Core/CameraComponent.h index b911a22b..496d69ee 100644 --- a/SynapseEngine/Engine/Component/Core/CameraComponent.h +++ b/SynapseEngine/Engine/Component/Core/CameraComponent.h @@ -22,6 +22,10 @@ namespace Syn float sensitivity; float distance; + bool isOrthographic; + float orthoSize; + bool useOrbit; + glm::vec3 up; glm::vec3 target; glm::vec3 position; diff --git a/SynapseEngine/Engine/System/Core/CameraSystem.cpp b/SynapseEngine/Engine/System/Core/CameraSystem.cpp index c66f589a..0ef530cc 100644 --- a/SynapseEngine/Engine/System/Core/CameraSystem.cpp +++ b/SynapseEngine/Engine/System/Core/CameraSystem.cpp @@ -50,11 +50,6 @@ namespace Syn float forward = 0; float sideways = 0; - if (enableInput && inputManager->IsKeyHeld(KEY_W)) forward = 1; - if (enableInput && inputManager->IsKeyHeld(KEY_S)) forward = -1; - if (enableInput && inputManager->IsKeyHeld(KEY_D)) sideways = 1; - if (enableInput && inputManager->IsKeyHeld(KEY_A)) sideways = -1; - if (enableInput && inputManager->IsButtonHeld(BUTTON_RIGHT)) { auto deltaPos = inputManager->GetMouseDelta(); @@ -70,69 +65,100 @@ namespace Syn }; glm::vec3 worldUp = glm::vec3(0.0f, 1.0f, 0.0f); - cameraComponent.direction = glm::normalize(direction); cameraComponent.right = glm::normalize(glm::cross(cameraComponent.direction, worldUp)); cameraComponent.up = glm::normalize(glm::cross(cameraComponent.right, cameraComponent.direction)); - transformComponent.translation += (forward * cameraComponent.direction + sideways * cameraComponent.right) * cameraComponent.speed * deltaTime; + if (cameraComponent.useOrbit) + { + if (enableInput) + { + float sideways = 0; + if (inputManager->IsKeyHeld(KEY_D)) sideways = 1; + if (inputManager->IsKeyHeld(KEY_A)) sideways = -1; + + cameraComponent.target += (sideways * cameraComponent.right) * cameraComponent.speed * deltaTime; + + if (inputManager->IsKeyHeld(KEY_W)) cameraComponent.distance -= cameraComponent.speed * deltaTime; + if (inputManager->IsKeyHeld(KEY_S)) cameraComponent.distance += cameraComponent.speed * deltaTime; - cameraComponent.position = transformComponent.translation; - cameraComponent.target = cameraComponent.position + cameraComponent.direction; + cameraComponent.distance = glm::max(cameraComponent.distance, 0.1f); + } + + cameraComponent.position = cameraComponent.target - cameraComponent.direction * cameraComponent.distance; + transformComponent.translation = cameraComponent.position; + } + else + { + float forward = 0; + float sideways = 0; + + if (enableInput && inputManager->IsKeyHeld(KEY_W)) forward = 1; + if (enableInput && inputManager->IsKeyHeld(KEY_S)) forward = -1; + if (enableInput && inputManager->IsKeyHeld(KEY_D)) sideways = 1; + if (enableInput && inputManager->IsKeyHeld(KEY_A)) sideways = -1; + + transformComponent.translation += (forward * cameraComponent.direction + sideways * cameraComponent.right) * cameraComponent.speed * deltaTime; + + cameraComponent.position = transformComponent.translation; + cameraComponent.target = cameraComponent.position + cameraComponent.direction; + } cameraComponent.view = glm::lookAt(cameraComponent.position, cameraComponent.target, worldUp); cameraComponent.viewInv = glm::inverse(cameraComponent.view); - cameraComponent.proj = glm::perspective(glm::radians(cameraComponent.fov), cameraComponent.width / cameraComponent.height, cameraComponent.nearPlane, cameraComponent.farPlane); + if (cameraComponent.isOrthographic) + { + float aspectRatio = cameraComponent.width / cameraComponent.height; + float halfH = cameraComponent.orthoSize * 0.5f; + float halfW = halfH * aspectRatio; + cameraComponent.proj = glm::ortho(-halfW, halfW, -halfH, halfH, -cameraComponent.farPlane, cameraComponent.farPlane); + } + else + { + cameraComponent.proj = glm::perspective(glm::radians(cameraComponent.fov), cameraComponent.width / cameraComponent.height, cameraComponent.nearPlane, cameraComponent.farPlane); + } + cameraComponent.projInv = glm::inverse(cameraComponent.proj); cameraComponent.viewProj = cameraComponent.proj * cameraComponent.view; cameraComponent.viewProjInv = glm::inverse(cameraComponent.viewProj); - cameraComponent.frustum.Update(cameraComponent.viewProj); - - /* - float fovY = glm::radians(cameraComponent.fov); - float aspectRatio = cameraComponent.width / cameraComponent.height; - float halfV = cameraComponent.farPlane * tanf(fovY * 0.5f); - float halfH = halfV * aspectRatio; - - // Near - cameraComponent.frustum.planes[0] = FrustumCollider::CreatePlane( - cameraComponent.direction, - cameraComponent.position + cameraComponent.direction * cameraComponent.nearPlane - ); - - // Far - cameraComponent.frustum.planes[1] = FrustumCollider::CreatePlane( - -cameraComponent.direction, - cameraComponent.position + cameraComponent.direction * cameraComponent.farPlane - ); - - // Left - cameraComponent.frustum.planes[2] = FrustumCollider::CreatePlane( - -glm::cross(cameraComponent.up, glm::normalize(cameraComponent.direction * cameraComponent.farPlane - cameraComponent.right * halfH)), - cameraComponent.position - ); - - // Right - cameraComponent.frustum.planes[3] = FrustumCollider::CreatePlane( - -glm::cross(glm::normalize(cameraComponent.direction * cameraComponent.farPlane + cameraComponent.right * halfH), cameraComponent.up), - cameraComponent.position - ); - - // Top - cameraComponent.frustum.planes[4] = FrustumCollider::CreatePlane( - -glm::cross(cameraComponent.right, glm::normalize(cameraComponent.direction * cameraComponent.farPlane + cameraComponent.up * halfV)), - cameraComponent.position - ); - - // Bottom - cameraComponent.frustum.planes[5] = FrustumCollider::CreatePlane( - -glm::cross(glm::normalize(cameraComponent.direction * cameraComponent.farPlane - cameraComponent.up * halfV), cameraComponent.right), - cameraComponent.position - ); - */ + if (cameraComponent.isOrthographic) + { + float aspectRatio = cameraComponent.width / cameraComponent.height; + float halfH = cameraComponent.orthoSize * 0.5f; + float halfW = halfH * aspectRatio; + + cameraComponent.frustum.planes[0] = FrustumCollider::CreatePlane( + cameraComponent.direction, + cameraComponent.position + cameraComponent.direction * cameraComponent.nearPlane + ); + cameraComponent.frustum.planes[1] = FrustumCollider::CreatePlane( + -cameraComponent.right, + cameraComponent.position + cameraComponent.right * halfW + ); + cameraComponent.frustum.planes[2] = FrustumCollider::CreatePlane( + cameraComponent.right, + cameraComponent.position - cameraComponent.right * halfW + ); + cameraComponent.frustum.planes[3] = FrustumCollider::CreatePlane( + -cameraComponent.up, + cameraComponent.position + cameraComponent.up * halfH + ); + cameraComponent.frustum.planes[4] = FrustumCollider::CreatePlane( + cameraComponent.up, + cameraComponent.position - cameraComponent.up * halfH + ); + cameraComponent.frustum.planes[5] = FrustumCollider::CreatePlane( + -cameraComponent.direction, + cameraComponent.position + cameraComponent.direction * cameraComponent.farPlane + ); + } + else + { + cameraComponent.frustum.Update(cameraComponent.viewProj); + } cameraPool->SetBit(entity); transformPool->SetBit(entity); From ec59d2316970fcae00f16953f0e498ce09404d39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20P=C3=A9ter?= Date: Tue, 7 Jul 2026 19:53:56 +0200 Subject: [PATCH 28/51] Created material viewport --- .../Editor/EditorApi/Impl/SceneApiImpl.cpp | 7 + .../Editor/EditorApi/Impl/SceneApiImpl.h | 1 + SynapseEngine/Editor/Manager/GuiManager.cpp | 13 +- .../MaterialViewport/MaterialViewportView.cpp | 42 ++++ .../MaterialViewport/MaterialViewportView.h | 11 + SynapseEngine/Editor/Workspace/IWorkspace.h | 2 + .../Editor/Workspace/MaterialWorkspace.cpp | 15 ++ .../Editor/Workspace/MaterialWorkspace.h | 2 +- .../Editor/Workspace/SceneWorkspace.cpp | 6 + .../Editor/Workspace/SceneWorkspace.h | 2 +- SynapseEngine/EditorCore/Api/ISceneApi.h | 1 + .../MaterialViewportIntent.cpp | 1 + .../MaterialViewport/MaterialViewportIntent.h | 14 ++ .../MaterialViewportState.cpp | 1 + .../MaterialViewport/MaterialViewportState.h | 22 ++ .../MaterialViewportViewModel.cpp | 42 ++++ .../MaterialViewportViewModel.h | 25 +++ SynapseEngine/Engine/Engine.cpp | 5 + .../Scene/Settings/PostProcessSettings.cpp | 4 +- .../Procedural/MaterialPreviewSceneSource.cpp | 189 ++++++++++++++++++ .../Procedural/MaterialPreviewSceneSource.h | 13 ++ 21 files changed, 413 insertions(+), 5 deletions(-) create mode 100644 SynapseEngine/Editor/View/MaterialViewport/MaterialViewportView.cpp create mode 100644 SynapseEngine/Editor/View/MaterialViewport/MaterialViewportView.h create mode 100644 SynapseEngine/EditorCore/ViewModels/MaterialViewport/MaterialViewportIntent.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/MaterialViewport/MaterialViewportIntent.h create mode 100644 SynapseEngine/EditorCore/ViewModels/MaterialViewport/MaterialViewportState.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/MaterialViewport/MaterialViewportState.h create mode 100644 SynapseEngine/EditorCore/ViewModels/MaterialViewport/MaterialViewportViewModel.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/MaterialViewport/MaterialViewportViewModel.h create mode 100644 SynapseEngine/Engine/Scene/Source/Procedural/MaterialPreviewSceneSource.cpp create mode 100644 SynapseEngine/Engine/Scene/Source/Procedural/MaterialPreviewSceneSource.h diff --git a/SynapseEngine/Editor/EditorApi/Impl/SceneApiImpl.cpp b/SynapseEngine/Editor/EditorApi/Impl/SceneApiImpl.cpp index 74714d1c..c2510392 100644 --- a/SynapseEngine/Editor/EditorApi/Impl/SceneApiImpl.cpp +++ b/SynapseEngine/Editor/EditorApi/Impl/SceneApiImpl.cpp @@ -27,4 +27,11 @@ namespace Syn { _sceneManager->SaveActiveScene(savePath.string()); Syn::Info("SceneApiImpl: Scene saved to {}", savePath.string()); } + + void SceneApiImpl::ActivateScene(const std::string& sceneName) { + if (_sceneManager) { + _sceneManager->LoadScene(sceneName); + Syn::Info("SceneApiImpl: Activated registered scene: {}", sceneName); + } + } } \ No newline at end of file diff --git a/SynapseEngine/Editor/EditorApi/Impl/SceneApiImpl.h b/SynapseEngine/Editor/EditorApi/Impl/SceneApiImpl.h index 4b0a5c61..ee4209dc 100644 --- a/SynapseEngine/Editor/EditorApi/Impl/SceneApiImpl.h +++ b/SynapseEngine/Editor/EditorApi/Impl/SceneApiImpl.h @@ -9,6 +9,7 @@ namespace Syn { void NewScene() override; void LoadScene(const std::string& filepath = "") override; void SaveScene(const std::string& filepath = "") override; + void ActivateScene(const std::string& sceneName) override; private: SceneManager* _sceneManager; }; diff --git a/SynapseEngine/Editor/Manager/GuiManager.cpp b/SynapseEngine/Editor/Manager/GuiManager.cpp index 0206ca25..b6f2a198 100644 --- a/SynapseEngine/Editor/Manager/GuiManager.cpp +++ b/SynapseEngine/Editor/Manager/GuiManager.cpp @@ -143,7 +143,18 @@ namespace Syn { } if (ImGui::Button(label)) { - _currentWorkspace = ws; + if (_currentWorkspace != ws) + { + if (_workspaces.contains(_currentWorkspace)) { + _workspaces[_currentWorkspace]->OnDeactivate(); + } + + _currentWorkspace = ws; + + if (_workspaces.contains(_currentWorkspace)) { + _workspaces[_currentWorkspace]->OnActivate(); + } + } } ImGui::PopStyleColor(2); diff --git a/SynapseEngine/Editor/View/MaterialViewport/MaterialViewportView.cpp b/SynapseEngine/Editor/View/MaterialViewport/MaterialViewportView.cpp new file mode 100644 index 00000000..244b827a --- /dev/null +++ b/SynapseEngine/Editor/View/MaterialViewport/MaterialViewportView.cpp @@ -0,0 +1,42 @@ +#include "MaterialViewportView.h" +#include "Editor/Manager/EditorIcons.h" +#include + +namespace Syn { + + void MaterialViewportView::Draw(MaterialViewportViewModel& vm) { + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2{ 0, 0 }); + + ImGui::Begin(SYN_ICON_LAYER_GROUP " Material Viewport", nullptr); + + MaterialViewportState state = vm.GetState(); + + ImVec2 viewportPanelSize = ImGui::GetContentRegionAvail(); + uint32_t currentWidth = static_cast(viewportPanelSize.x); + uint32_t currentHeight = static_cast(viewportPanelSize.y); + + bool isResizing = (currentWidth > 0 && currentHeight > 0 && + (currentWidth != state.width || currentHeight != state.height)); + + vm.Dispatch(ResizeMaterialViewportIntent{ currentWidth, currentHeight }); + + if (viewportPanelSize.x <= 0.0f) viewportPanelSize.x = 1.0f; + if (viewportPanelSize.y <= 0.0f) viewportPanelSize.y = 1.0f; + + if (state.textureId && !isResizing) { + ImGui::GetWindowDrawList()->AddCallback(ImGui::GetPlatformIO().DrawCallback_SetSamplerNearest, nullptr); + ImGui::Image(state.textureId, viewportPanelSize); + ImGui::GetWindowDrawList()->AddCallback(ImGui::GetPlatformIO().DrawCallback_SetSamplerLinear, nullptr); + } + else { + ImGui::Dummy(viewportPanelSize); + } + + state.isHovered = ImGui::IsWindowHovered(); + state.isFocused = ImGui::IsWindowFocused(); + + ImGui::End(); + ImGui::PopStyleVar(); + } + +} \ No newline at end of file diff --git a/SynapseEngine/Editor/View/MaterialViewport/MaterialViewportView.h b/SynapseEngine/Editor/View/MaterialViewport/MaterialViewportView.h new file mode 100644 index 00000000..6e206064 --- /dev/null +++ b/SynapseEngine/Editor/View/MaterialViewport/MaterialViewportView.h @@ -0,0 +1,11 @@ +#pragma once +#include "Editor/View/IView.h" +#include "EditorCore/ViewModels/MaterialViewport/MaterialViewportViewModel.h" +#include + +namespace Syn { + class MaterialViewportView : public IView { + public: + void Draw(MaterialViewportViewModel& vm) override; + }; +} \ No newline at end of file diff --git a/SynapseEngine/Editor/Workspace/IWorkspace.h b/SynapseEngine/Editor/Workspace/IWorkspace.h index 66fdd828..c150c893 100644 --- a/SynapseEngine/Editor/Workspace/IWorkspace.h +++ b/SynapseEngine/Editor/Workspace/IWorkspace.h @@ -17,6 +17,8 @@ namespace Syn virtual ~IWorkspace() = default; virtual void Initialize() = 0; + virtual void OnActivate() {} + virtual void OnDeactivate() {} virtual void UpdateAndDraw() { for (auto& window : _windows) { diff --git a/SynapseEngine/Editor/Workspace/MaterialWorkspace.cpp b/SynapseEngine/Editor/Workspace/MaterialWorkspace.cpp index 5b4641f1..c4ce733b 100644 --- a/SynapseEngine/Editor/Workspace/MaterialWorkspace.cpp +++ b/SynapseEngine/Editor/Workspace/MaterialWorkspace.cpp @@ -13,11 +13,20 @@ #include "Editor/View/MaterialProperties/MaterialPropertiesView.h" #include "EditorCore/ViewModels/MaterialProperties/MaterialPropertiesViewModel.h" +#include "Editor/View/MaterialViewport/MaterialViewportView.h" +#include "EditorCore/ViewModels/MaterialViewport/MaterialViewportViewModel.h" + namespace Syn { MaterialWorkspace::MaterialWorkspace(EditorContext* context, IconManager* iconManager, const std::string& assetPath) : _context(context), _iconManager(iconManager), _assetPath(assetPath) {} + void MaterialWorkspace::OnActivate() { + if (_context && _context->GetSceneApi()) { + _context->GetSceneApi()->ActivateScene("MaterialPreview"); + } + } + void MaterialWorkspace::Initialize() { using ContentBrowserWin = EditorWindow; AddWindow( @@ -42,6 +51,12 @@ namespace Syn { MaterialPropertiesView{}, MaterialPropertiesViewModel{ _context->GetMaterialApi(), _context->GetTextureApi() } ); + + using MaterialViewportWin = EditorWindow; + AddWindow( + MaterialViewportView{}, + MaterialViewportViewModel{ _context->GetRenderApi() } + ); } } \ No newline at end of file diff --git a/SynapseEngine/Editor/Workspace/MaterialWorkspace.h b/SynapseEngine/Editor/Workspace/MaterialWorkspace.h index c14b251f..51bce057 100644 --- a/SynapseEngine/Editor/Workspace/MaterialWorkspace.h +++ b/SynapseEngine/Editor/Workspace/MaterialWorkspace.h @@ -12,7 +12,7 @@ namespace Syn { ~MaterialWorkspace() override = default; void Initialize() override; - + void OnActivate() override; private: EditorContext* _context; IconManager* _iconManager; diff --git a/SynapseEngine/Editor/Workspace/SceneWorkspace.cpp b/SynapseEngine/Editor/Workspace/SceneWorkspace.cpp index 4be9c234..54d9c608 100644 --- a/SynapseEngine/Editor/Workspace/SceneWorkspace.cpp +++ b/SynapseEngine/Editor/Workspace/SceneWorkspace.cpp @@ -21,6 +21,12 @@ namespace Syn { SceneWorkspace::SceneWorkspace(EditorContext* context, IconManager* iconManager, const std::string& assetPath) : _context(context), _iconManager(iconManager), _assetPath(assetPath) {} + void SceneWorkspace::OnActivate() { + if (_context && _context->GetSceneApi()) { + _context->GetSceneApi()->ActivateScene("TestLevel"); + } + } + void SceneWorkspace::Initialize() { using ContentBrowserWin = EditorWindow; diff --git a/SynapseEngine/Editor/Workspace/SceneWorkspace.h b/SynapseEngine/Editor/Workspace/SceneWorkspace.h index 797ec55a..0fc94042 100644 --- a/SynapseEngine/Editor/Workspace/SceneWorkspace.h +++ b/SynapseEngine/Editor/Workspace/SceneWorkspace.h @@ -12,7 +12,7 @@ namespace Syn { ~SceneWorkspace() override = default; void Initialize() override; - + void OnActivate() override; private: EditorContext* _context; IconManager* _iconManager; diff --git a/SynapseEngine/EditorCore/Api/ISceneApi.h b/SynapseEngine/EditorCore/Api/ISceneApi.h index 5e762ca0..70ce69cc 100644 --- a/SynapseEngine/EditorCore/Api/ISceneApi.h +++ b/SynapseEngine/EditorCore/Api/ISceneApi.h @@ -9,5 +9,6 @@ namespace Syn { virtual void NewScene() = 0; virtual void LoadScene(const std::string& filepath = "") = 0; virtual void SaveScene(const std::string& filepath = "") = 0; + virtual void ActivateScene(const std::string& sceneName) = 0; }; } \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/MaterialViewport/MaterialViewportIntent.cpp b/SynapseEngine/EditorCore/ViewModels/MaterialViewport/MaterialViewportIntent.cpp new file mode 100644 index 00000000..9d0e494f --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/MaterialViewport/MaterialViewportIntent.cpp @@ -0,0 +1 @@ +#include "MaterialViewportIntent.h" \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/MaterialViewport/MaterialViewportIntent.h b/SynapseEngine/EditorCore/ViewModels/MaterialViewport/MaterialViewportIntent.h new file mode 100644 index 00000000..a44d8fe9 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/MaterialViewport/MaterialViewportIntent.h @@ -0,0 +1,14 @@ +#pragma once +#include +#include + +namespace Syn { + struct ResizeMaterialViewportIntent { + uint32_t width; + uint32_t height; + }; + + using MaterialViewportIntent = std::variant< + ResizeMaterialViewportIntent + >; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/MaterialViewport/MaterialViewportState.cpp b/SynapseEngine/EditorCore/ViewModels/MaterialViewport/MaterialViewportState.cpp new file mode 100644 index 00000000..ac86b1e5 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/MaterialViewport/MaterialViewportState.cpp @@ -0,0 +1 @@ +#include "MaterialViewportState.h" \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/MaterialViewport/MaterialViewportState.h b/SynapseEngine/EditorCore/ViewModels/MaterialViewport/MaterialViewportState.h new file mode 100644 index 00000000..45cf8fe1 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/MaterialViewport/MaterialViewportState.h @@ -0,0 +1,22 @@ +#pragma once +#include +#include +#include "EditorCore/Types/TextureHandle.h" +#include "Engine/Vk/Image/ImageViewNames.h" +#include "Engine/Render/RenderNames.h" + +namespace Syn { + struct MaterialViewportState { + uint32_t width = 0; + uint32_t height = 0; + + TextureHandle textureId = InvalidTextureHandle; + + std::string currentGroup = RenderTargetGroupNames::Main; + std::string currentTarget = RenderTargetNames::Main; + std::string currentView = Vk::ImageViewNames::Default; + + bool isHovered = false; + bool isFocused = false; + }; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/MaterialViewport/MaterialViewportViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/MaterialViewport/MaterialViewportViewModel.cpp new file mode 100644 index 00000000..414f454c --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/MaterialViewport/MaterialViewportViewModel.cpp @@ -0,0 +1,42 @@ +#include "MaterialViewportViewModel.h" + +namespace Syn { + + MaterialViewportViewModel::MaterialViewportViewModel(IRenderApi* renderApi) + : _renderApi(renderApi) {} + + const MaterialViewportState& MaterialViewportViewModel::GetState() const { + return _state; + } + + void MaterialViewportViewModel::SyncWithEngine() { + if (!_renderApi) return; + + _state.textureId = _renderApi->GetViewportTexture( + _state.currentGroup, + _state.currentTarget, + _state.currentView + ); + } + + void MaterialViewportViewModel::Dispatch(const MaterialViewportIntent& intent) { + std::visit([this](auto&& arg) { + using T = std::decay_t; + if constexpr (std::is_same_v) { + HandleResize(arg); + } + }, intent); + } + + void MaterialViewportViewModel::HandleResize(const ResizeMaterialViewportIntent& intent) { + if (intent.width > 0 && intent.height > 0 && + (_state.width != intent.width || _state.height != intent.height)) + { + _state.width = intent.width; + _state.height = intent.height; + + _renderApi->ResizeRenderTargets(_state.width, _state.height); + } + } + +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/MaterialViewport/MaterialViewportViewModel.h b/SynapseEngine/EditorCore/ViewModels/MaterialViewport/MaterialViewportViewModel.h new file mode 100644 index 00000000..2794e01b --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/MaterialViewport/MaterialViewportViewModel.h @@ -0,0 +1,25 @@ +#pragma once +#include "EditorCore/ViewModels/IViewModel.h" +#include "MaterialViewportState.h" +#include "MaterialViewportIntent.h" +#include "EditorCore/Api/IRenderApi.h" + +namespace Syn { + class MaterialViewportViewModel : public IViewModel { + public: + explicit MaterialViewportViewModel(IRenderApi* renderApi); + ~MaterialViewportViewModel() override = default; + + const MaterialViewportState& GetState() const override; + + void SyncWithEngine() override; + void Dispatch(const MaterialViewportIntent& intent) override; + + private: + void HandleResize(const ResizeMaterialViewportIntent& intent); + + private: + IRenderApi* _renderApi = nullptr; + MaterialViewportState _state; + }; +} \ No newline at end of file diff --git a/SynapseEngine/Engine/Engine.cpp b/SynapseEngine/Engine/Engine.cpp index 99046f8f..41ebf1f1 100644 --- a/SynapseEngine/Engine/Engine.cpp +++ b/SynapseEngine/Engine/Engine.cpp @@ -26,6 +26,7 @@ #include "Engine/Logger/LogUtils.h" #include "Engine/Scene/SceneManager.h" #include "Engine/Scene/Source/Procedural/TestSceneSource.h" +#include "Engine/Scene/Source/Procedural/MaterialPreviewSceneSource.h" #include "Engine/Scene/Source/Procedural/NatureSceneSource.h" #include "Engine/Scene/Source/File/FileSceneSource.h" @@ -328,6 +329,10 @@ namespace Syn return std::make_unique(frames, std::make_unique()); }); + _sceneManager->RegisterScene("MaterialPreview", [frames]() { + return std::make_unique(frames, std::make_unique()); + }); + _sceneManager->LoadScene("TestLevel"); } diff --git a/SynapseEngine/Engine/Scene/Settings/PostProcessSettings.cpp b/SynapseEngine/Engine/Scene/Settings/PostProcessSettings.cpp index 1ff2eb06..0f80e7f4 100644 --- a/SynapseEngine/Engine/Scene/Settings/PostProcessSettings.cpp +++ b/SynapseEngine/Engine/Scene/Settings/PostProcessSettings.cpp @@ -9,8 +9,8 @@ namespace Syn , bloomFilterRadius(0.005f) , bloomExposure(1.0f) , bloomStrength(1.0f) - , enableSsao(true) - , enableSsaoLight(true) + , enableSsao(false) + , enableSsaoLight(false) , aoRadius(0.95f) , aoIntensity(5.0f) , maxOcclusionDistance(10.0f) diff --git a/SynapseEngine/Engine/Scene/Source/Procedural/MaterialPreviewSceneSource.cpp b/SynapseEngine/Engine/Scene/Source/Procedural/MaterialPreviewSceneSource.cpp new file mode 100644 index 00000000..e98faa79 --- /dev/null +++ b/SynapseEngine/Engine/Scene/Source/Procedural/MaterialPreviewSceneSource.cpp @@ -0,0 +1,189 @@ +#include "MaterialPreviewSceneSource.h" +#include "Engine/Scene/Scene.h" +#include "Engine/Scene/Insiders/SceneInsider.h" +#include "Engine/ServiceLocator.h" +#include "Engine/Component/Core/TransformComponent.h" +#include "Engine/Component/Core/CameraComponent.h" +#include "Engine/Component/Core/TagComponent.h" +#include "Engine/Component/Rendering/ModelComponent.h" +#include "Engine/Mesh/Factory/MeshFactory.h" +#include "Engine/Mesh/ModelManager.h" +#include "Engine/Component/Rendering/MaterialOverrideComponent.h" +#include "Engine/Material/MaterialManager.h" +#include "Engine/Component/Light/Point/PointLightComponent.h" +#include "Engine/Component/Light/Direction/DirectionLightComponent.h" +#include "Engine/Mesh/MeshSourceNames.h" +#include "Engine/Logger/SynLog.h" +#include "Engine/Utils/PathUtils.h" + +#include +#include +#include + +namespace Syn +{ + bool MaterialPreviewSceneSource::Populate(Scene& scene) + { + Registry& registry = SceneInsider::GetRegistry(scene, SceneInsider::GetKey()); + EntityID& sceneCam = SceneInsider::GetSceneCameraEntity(scene, SceneInsider::GetKey()); + HierarchyManager* hm = scene.GetHierarchyManager(); + + auto modelManager = ServiceLocator::GetModelManager(); + auto materialManager = ServiceLocator::GetMaterialManager(); + + Syn::Info("Populating Material Preview Scene..."); + + // Root entities + EntityID rootCameras = scene.CreateEntity(); + registry.AddComponent(rootCameras); + registry.GetComponent(rootCameras).name = "Cameras"; + registry.GetComponent(rootCameras).tag = "Root"; + registry.AddComponent(rootCameras); + registry.GetPool()->SetCategory(rootCameras, StorageCategory::Static); + + EntityID rootEnvironment = scene.CreateEntity(); + registry.AddComponent(rootEnvironment); + registry.GetComponent(rootEnvironment).name = "Preview Objects"; + registry.GetComponent(rootEnvironment).tag = "Root"; + registry.AddComponent(rootEnvironment); + registry.GetPool()->SetCategory(rootEnvironment, StorageCategory::Static); + + EntityID rootLights = scene.CreateEntity(); + registry.AddComponent(rootLights); + registry.GetComponent(rootLights).name = "Studio Lights"; + registry.GetComponent(rootLights).tag = "Root"; + registry.AddComponent(rootLights); + registry.GetPool()->SetCategory(rootLights, StorageCategory::Static); + + // Orbit camera + sceneCam = scene.CreateEntity(); + registry.AddComponent(sceneCam); + registry.GetComponent(sceneCam).name = "Preview Camera"; + registry.GetComponent(sceneCam).tag = "Camera"; + + registry.AddComponent(sceneCam); + auto& camTransform = registry.GetComponent(sceneCam); + camTransform.rotation = glm::vec3(-25.0f, 45.0f, 0.0f); + + registry.AddComponent(sceneCam); + auto& camComp = registry.GetComponent(sceneCam); + camComp.useOrbit = true; + camComp.target = glm::vec3(0.0f); + camComp.distance = 12.0f; + camComp.speed = 20.0f; + + registry.GetPool()->SetCategory(sceneCam, StorageCategory::Stream); + registry.GetPool()->SetCategory(sceneCam, StorageCategory::Stream); + hm->AttachChild(rootCameras, sceneCam); + + // Studio lighting setup + + // 1. Key light (warm, casts shadows) + EntityID keyLight = scene.CreateEntity(); + registry.AddComponent(keyLight); + registry.GetComponent(keyLight).name = "Key Light"; + + registry.AddComponent(keyLight); + registry.GetComponent(keyLight).rotation = glm::vec3(-45.0f, -45.0f, 0.0f); + + registry.AddComponent(keyLight); + auto& dirKey = registry.GetComponent(keyLight); + dirKey.color = glm::vec3(1.0f, 0.95f, 0.9f); + dirKey.strength = 3.0f; + dirKey.useShadow = true; + + registry.GetPool()->SetBit(keyLight); + hm->AttachChild(rootLights, keyLight); + + // 2. Fill light (cool, no shadows) + EntityID fillLight = scene.CreateEntity(); + registry.AddComponent(fillLight); + registry.GetComponent(fillLight).name = "Fill Light"; + + registry.AddComponent(fillLight); + registry.GetComponent(fillLight).rotation = glm::vec3(30.0f, 135.0f, 0.0f); + + registry.AddComponent(fillLight); + auto& dirFill = registry.GetComponent(fillLight); + dirFill.color = glm::vec3(0.8f, 0.9f, 1.0f); + dirFill.strength = 1.0f; + dirFill.useShadow = false; + + hm->AttachChild(rootLights, fillLight); + + // Base materials + MaterialInfo floorMatInfo{}; + floorMatInfo.color = glm::vec4(0.15f, 0.15f, 0.15f, 1.0f); + floorMatInfo.roughnessFactor = 0.9f; + uint32_t floorMatId = materialManager->LoadMaterial("Preview_FloorMat", floorMatInfo); + + // Geometries + + // 1. Floor + EntityID floor = scene.CreateEntity(); + registry.AddComponent(floor); + registry.GetComponent(floor).name = "Studio_Floor"; + + registry.AddComponent(floor); + registry.GetComponent(floor).translation = glm::vec3(0.0f, -2.0f, 0.0f); + registry.GetComponent(floor).scale = glm::vec3(25.0f, 1.0f, 25.0f); + + registry.AddComponent(floor); + registry.GetComponent(floor).modelIndex = modelManager->GetResourceIndex(MeshSourceNames::Cube); + + registry.AddComponent(floor); + registry.GetComponent(floor).materials.push_back(floorMatId); + + hm->AttachChild(rootEnvironment, floor); + + // Helper for preview objects + auto CreatePreviewObject = [&](const std::string& name, uint32_t modelIndex, glm::vec3 pos, glm::vec3 scale = glm::vec3(1.0f)) { + EntityID e = scene.CreateEntity(); + registry.AddComponent(e); + registry.GetComponent(e).name = name; + + registry.AddComponent(e); + registry.GetComponent(e).translation = pos; + registry.GetComponent(e).scale = scale; + + registry.AddComponent(e); + registry.GetComponent(e).modelIndex = modelIndex; + + registry.AddComponent(e); + + hm->AttachChild(rootEnvironment, e); + }; + + // 2. Center object: Suzanne + std::string basePath = "C:/Users/User/Desktop/Models/"; + uint32_t monkeyId = modelManager->LoadModelAsync(basePath + "Monkey/monkey.obj"); + CreatePreviewObject("Center_Monkey", monkeyId, glm::vec3(0.0f, 0.5f, 0.0f), glm::vec3(1.5f)); + + // 3. 8 primitive shapes in a circle + std::vector> previewShapes = { + {"Preview_Sphere", modelManager->GetResourceIndex(MeshSourceNames::Sphere)}, + {"Preview_Cube", modelManager->GetResourceIndex(MeshSourceNames::Cube)}, + {"Preview_Cylinder", modelManager->GetResourceIndex(MeshSourceNames::Cylinder)}, + {"Preview_Torus", modelManager->GetResourceIndex(MeshSourceNames::Torus)}, + {"Preview_Cone", modelManager->GetResourceIndex(MeshSourceNames::Cone)}, + {"Preview_Capsule", modelManager->GetResourceIndex(MeshSourceNames::Capsule)}, + {"Preview_Hemisphere", modelManager->GetResourceIndex(MeshSourceNames::Hemisphere)}, + {"Preview_IcoSphere", modelManager->GetResourceIndex(MeshSourceNames::IcoSphere)} + }; + + float radius = 8.0f; + for (size_t i = 0; i < previewShapes.size(); ++i) + { + float angle = (360.0f / previewShapes.size()) * i; + float rad = glm::radians(angle); + + float x = glm::cos(rad) * radius; + float z = glm::sin(rad) * radius; + float y = 0.5f; + + CreatePreviewObject(previewShapes[i].first, previewShapes[i].second, glm::vec3(x, y, z)); + } + + return true; + } +} \ No newline at end of file diff --git a/SynapseEngine/Engine/Scene/Source/Procedural/MaterialPreviewSceneSource.h b/SynapseEngine/Engine/Scene/Source/Procedural/MaterialPreviewSceneSource.h new file mode 100644 index 00000000..d97bce47 --- /dev/null +++ b/SynapseEngine/Engine/Scene/Source/Procedural/MaterialPreviewSceneSource.h @@ -0,0 +1,13 @@ +#pragma once +#include "Engine/SynApi.h" +#include "Engine/Scene/Source/ISceneSource.h" + +namespace Syn +{ + class SYN_API MaterialPreviewSceneSource : public ISceneSource + { + public: + MaterialPreviewSceneSource() = default; + virtual bool Populate(Scene& scene) override; + }; +} \ No newline at end of file From aec8add41f5efbc7da5337c99bcedbe96a8764c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20P=C3=A9ter?= Date: Tue, 7 Jul 2026 20:24:31 +0200 Subject: [PATCH 29/51] Material viewport material selection works fine --- .../Editor/EditorApi/EditorContext.cpp | 2 +- .../Editor/EditorApi/Impl/MaterialApiImpl.cpp | 38 +++++++++++++++++++ .../Editor/EditorApi/Impl/MaterialApiImpl.h | 8 +++- SynapseEngine/EditorCore/Api/IMaterialApi.h | 3 +- .../MaterialHierarchyViewModel.cpp | 1 + .../Procedural/MaterialPreviewSceneSource.cpp | 8 ++++ 6 files changed, 56 insertions(+), 4 deletions(-) diff --git a/SynapseEngine/Editor/EditorApi/EditorContext.cpp b/SynapseEngine/Editor/EditorApi/EditorContext.cpp index d5584104..fdb6bc4f 100644 --- a/SynapseEngine/Editor/EditorApi/EditorContext.cpp +++ b/SynapseEngine/Editor/EditorApi/EditorContext.cpp @@ -36,7 +36,7 @@ namespace Syn { _fileSystemApi = std::make_unique(); _hierarchyApi = std::make_unique(sm); _loggerApi = std::make_unique(engine); - _materialApi = std::make_unique(engine->GetMaterialManager()); + _materialApi = std::make_unique(engine->GetMaterialManager(), sm); _renderApi = std::make_unique(engine, textureManager, sm); _sceneApi = std::make_unique(sm); _settingsApi = std::make_unique(sm); diff --git a/SynapseEngine/Editor/EditorApi/Impl/MaterialApiImpl.cpp b/SynapseEngine/Editor/EditorApi/Impl/MaterialApiImpl.cpp index b2f3f121..e0b02af3 100644 --- a/SynapseEngine/Editor/EditorApi/Impl/MaterialApiImpl.cpp +++ b/SynapseEngine/Editor/EditorApi/Impl/MaterialApiImpl.cpp @@ -1,5 +1,10 @@ #include "MaterialApiImpl.h" #include "EditorCore/ViewModels/MaterialGraph/MaterialGraphState.h" +#include "Engine/Scene/Insiders/SceneInsider.h" +#include "Engine/Component/Core/TagComponent.h" +#include "Engine/Component/Rendering/MaterialOverrideComponent.h" +#include "Engine/Logger/SynLog.h" +#include "Editor/EditorApi/EditorApiUtils.h" #include namespace Syn { @@ -99,4 +104,37 @@ namespace Syn { _materialManager->MarkDirty(materialId); } } + + void MaterialApiImpl::ApplyMaterialToPreviewObjects(uint32_t materialId) { + if (!_sceneManager) return; + auto scene = _sceneManager->GetActiveScene(); + if (!scene) return; + + auto& registry = SceneInsider::GetRegistry(*scene, SceneInsider::GetKey()); + + auto tagPool = registry.GetPool(); + if (!tagPool) return; + + for (EntityID entity : tagPool->GetDenseEntities()) { + if (registry.HasComponent(entity)) { + const auto& tag = tagPool->Get(entity); + + if (tag.tag == "Preview") { + + EditorApiUtils::ModifyComponent( + _sceneManager, + entity, + [materialId](auto& matOverride, auto pool) { + matOverride.materials.clear(); + if (materialId != 0xFFFFFFFF) { + matOverride.materials.push_back(materialId); + } + } + ); + } + } + } + + Info("MaterialApiImpl: Applied material {} to preview objects.", materialId); + } } \ No newline at end of file diff --git a/SynapseEngine/Editor/EditorApi/Impl/MaterialApiImpl.h b/SynapseEngine/Editor/EditorApi/Impl/MaterialApiImpl.h index 7d1b9fee..ef60e973 100644 --- a/SynapseEngine/Editor/EditorApi/Impl/MaterialApiImpl.h +++ b/SynapseEngine/Editor/EditorApi/Impl/MaterialApiImpl.h @@ -1,14 +1,15 @@ #pragma once #include "EditorCore/Api/IMaterialApi.h" #include "Engine/Material/MaterialManager.h" +#include "Engine/Scene/SceneManager.h" namespace Syn { enum class GraphPinType; class MaterialApiImpl : public IMaterialApi { public: - MaterialApiImpl(MaterialManager* materialManager) - : _materialManager(materialManager) {} + MaterialApiImpl(MaterialManager* materialManager, SceneManager* sceneManager) + : _materialManager(materialManager), _sceneManager(sceneManager) {} std::vector GetAllMaterials() const override; uint32_t GetSelectedMaterial() const override; @@ -23,7 +24,10 @@ namespace Syn { bool GetMaterialData(uint32_t materialId, Material& outMaterial) const override; void UpdateMaterialData(uint32_t materialId, const Material& material) override; + + void ApplyMaterialToPreviewObjects(uint32_t materialId) override; private: + SceneManager* _sceneManager; MaterialManager* _materialManager; uint32_t _selectedMaterial = INVALID_MATERIAL_ID; }; diff --git a/SynapseEngine/EditorCore/Api/IMaterialApi.h b/SynapseEngine/EditorCore/Api/IMaterialApi.h index c4189b73..1c789444 100644 --- a/SynapseEngine/EditorCore/Api/IMaterialApi.h +++ b/SynapseEngine/EditorCore/Api/IMaterialApi.h @@ -5,7 +5,6 @@ #include "Engine/Material/Material.h" //Todo: Domain Material!! - namespace Syn { constexpr uint32_t INVALID_MATERIAL_ID = 0xFFFFFFFF; @@ -32,5 +31,7 @@ namespace Syn virtual bool GetMaterialData(uint32_t materialId, Material& outMaterial) const = 0; virtual void UpdateMaterialData(uint32_t materialId, const Material& material) = 0; + + virtual void ApplyMaterialToPreviewObjects(uint32_t materialId) = 0; }; } \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyViewModel.cpp index a888196c..39288189 100644 --- a/SynapseEngine/EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyViewModel.cpp +++ b/SynapseEngine/EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyViewModel.cpp @@ -31,6 +31,7 @@ namespace Syn { if constexpr (std::is_same_v) { if (_materialApi) { _materialApi->SetSelectedMaterial(arg.materialId); + _materialApi->ApplyMaterialToPreviewObjects(arg.materialId); } } else if constexpr (std::is_same_v) { diff --git a/SynapseEngine/Engine/Scene/Source/Procedural/MaterialPreviewSceneSource.cpp b/SynapseEngine/Engine/Scene/Source/Procedural/MaterialPreviewSceneSource.cpp index e98faa79..f4751bbb 100644 --- a/SynapseEngine/Engine/Scene/Source/Procedural/MaterialPreviewSceneSource.cpp +++ b/SynapseEngine/Engine/Scene/Source/Procedural/MaterialPreviewSceneSource.cpp @@ -127,12 +127,15 @@ namespace Syn registry.AddComponent(floor); registry.GetComponent(floor).translation = glm::vec3(0.0f, -2.0f, 0.0f); registry.GetComponent(floor).scale = glm::vec3(25.0f, 1.0f, 25.0f); + registry.GetPool()->SetCategory(floor, StorageCategory::Static); registry.AddComponent(floor); registry.GetComponent(floor).modelIndex = modelManager->GetResourceIndex(MeshSourceNames::Cube); + registry.GetPool()->SetCategory(floor, StorageCategory::Static); registry.AddComponent(floor); registry.GetComponent(floor).materials.push_back(floorMatId); + registry.GetPool()->SetCategory(floor, StorageCategory::Static); hm->AttachChild(rootEnvironment, floor); @@ -141,15 +144,20 @@ namespace Syn EntityID e = scene.CreateEntity(); registry.AddComponent(e); registry.GetComponent(e).name = name; + registry.GetComponent(e).tag = "Preview"; + registry.GetPool()->SetCategory(e, StorageCategory::Static); registry.AddComponent(e); registry.GetComponent(e).translation = pos; registry.GetComponent(e).scale = scale; + registry.GetPool()->SetCategory(e, StorageCategory::Static); registry.AddComponent(e); registry.GetComponent(e).modelIndex = modelIndex; + registry.GetPool()->SetCategory(e, StorageCategory::Static); registry.AddComponent(e); + registry.GetPool()->SetCategory(e, StorageCategory::Static); hm->AttachChild(rootEnvironment, e); }; From 7964b6fdf4323f14f44b9541f003d6610e23d924 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20P=C3=A9ter?= Date: Wed, 8 Jul 2026 09:44:21 +0200 Subject: [PATCH 30/51] Implemented model viewport mvi, and hdr image loader --- .../Editor/EditorApi/EditorContext.cpp | 2 +- .../Editor/EditorApi/Impl/ModelApiImpl.cpp | 57 ++++ .../Editor/EditorApi/Impl/ModelApiImpl.h | 7 +- .../Editor/EditorApi/Impl/RenderApiImpl.cpp | 26 +- .../Editor/EditorApi/Impl/RenderApiImpl.h | 1 + .../View/ModelViewport/ModelViewportView.cpp | 261 ++++++++++++++++++ .../View/ModelViewport/ModelViewportView.h | 20 ++ .../Editor/Workspace/MaterialWorkspace.cpp | 4 +- .../Editor/Workspace/ModelWorkspace.cpp | 17 +- .../Editor/Workspace/ModelWorkspace.h | 1 + .../Editor/Workspace/SceneWorkspace.cpp | 5 +- SynapseEngine/EditorCore/Api/IModelApi.h | 2 + SynapseEngine/EditorCore/Api/IRenderApi.h | 1 + .../ModelHierarchyViewModel.cpp | 7 +- .../ModelViewport/ModelViewportIntent.cpp | 1 + .../ModelViewport/ModelViewportIntent.h | 40 +++ .../ModelViewport/ModelViewportState.cpp | 1 + .../ModelViewport/ModelViewportState.h | 43 +++ .../ModelViewport/ModelViewportViewModel.cpp | 117 ++++++++ .../ModelViewport/ModelViewportViewModel.h | 36 +++ SynapseEngine/Engine/Engine.cpp | 12 +- .../Engine/Image/Loader/HdriImageLoader.cpp | 72 +++++ .../Engine/Image/Loader/HdriImageLoader.h | 19 ++ .../Engine/Manager/ResourceManager.cpp | 2 + SynapseEngine/Engine/Scene/SceneNames.h | 12 + .../Procedural/MaterialPreviewSceneSource.cpp | 34 +-- .../Procedural/ModelPreviewSceneSource.cpp | 139 ++++++++++ .../Procedural/ModelPreviewSceneSource.h | 13 + .../Source/Procedural/TestSceneSource.cpp | 2 +- .../System/Rendering/MaterialSystem.cpp | 2 + 30 files changed, 913 insertions(+), 43 deletions(-) create mode 100644 SynapseEngine/Editor/View/ModelViewport/ModelViewportView.cpp create mode 100644 SynapseEngine/Editor/View/ModelViewport/ModelViewportView.h create mode 100644 SynapseEngine/EditorCore/ViewModels/ModelViewport/ModelViewportIntent.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/ModelViewport/ModelViewportIntent.h create mode 100644 SynapseEngine/EditorCore/ViewModels/ModelViewport/ModelViewportState.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/ModelViewport/ModelViewportState.h create mode 100644 SynapseEngine/EditorCore/ViewModels/ModelViewport/ModelViewportViewModel.cpp create mode 100644 SynapseEngine/EditorCore/ViewModels/ModelViewport/ModelViewportViewModel.h create mode 100644 SynapseEngine/Engine/Image/Loader/HdriImageLoader.cpp create mode 100644 SynapseEngine/Engine/Image/Loader/HdriImageLoader.h create mode 100644 SynapseEngine/Engine/Scene/SceneNames.h create mode 100644 SynapseEngine/Engine/Scene/Source/Procedural/ModelPreviewSceneSource.cpp create mode 100644 SynapseEngine/Engine/Scene/Source/Procedural/ModelPreviewSceneSource.h diff --git a/SynapseEngine/Editor/EditorApi/EditorContext.cpp b/SynapseEngine/Editor/EditorApi/EditorContext.cpp index fdb6bc4f..3fa32cb1 100644 --- a/SynapseEngine/Editor/EditorApi/EditorContext.cpp +++ b/SynapseEngine/Editor/EditorApi/EditorContext.cpp @@ -43,7 +43,7 @@ namespace Syn { _pointLightApi = std::make_unique(sm); _spotLightApi = std::make_unique(sm); _textureApi = std::make_unique(engine->GetImageManager(), textureManager); - _modelApi = std::make_unique(engine->GetModelManager()); + _modelApi = std::make_unique(engine->GetModelManager(), sm); _cameraApi = std::make_unique(sm); _boxColliderApi = std::make_unique(sm); _sphereColliderApi = std::make_unique(sm); diff --git a/SynapseEngine/Editor/EditorApi/Impl/ModelApiImpl.cpp b/SynapseEngine/Editor/EditorApi/Impl/ModelApiImpl.cpp index 706c6148..38290e15 100644 --- a/SynapseEngine/Editor/EditorApi/Impl/ModelApiImpl.cpp +++ b/SynapseEngine/Editor/EditorApi/Impl/ModelApiImpl.cpp @@ -1,5 +1,12 @@ #include "ModelApiImpl.h" +#include "Engine/Scene/Insiders/SceneInsider.h" +#include "Engine/Component/Core/TagComponent.h" +#include "Engine/Component/Core/CameraComponent.h" +#include "Engine/Component/Rendering/ModelComponent.h" +#include "Editor/EditorApi/EditorApiUtils.h" +#include "Engine/Logger/SynLog.h" #include +#include namespace Syn { @@ -49,4 +56,54 @@ namespace Syn { auto resource = _modelManager->GetResource(modelId); return resource->cpuData.meshNodeDescriptors[nodeIndex].name; } + + void ModelApiImpl::ApplyModelToPreviewObject(uint32_t modelId) { + if (!_sceneManager) return; + auto scene = _sceneManager->GetActiveScene(); + if (!scene) return; + + auto& registry = SceneInsider::GetRegistry(*scene, SceneInsider::GetKey()); + auto tagPool = registry.GetPool(); + if (!tagPool) return; + + for (EntityID entity : tagPool->GetDenseEntities()) { + const auto& tag = tagPool->Get(entity); + if (tag.tag == "Preview" && registry.HasComponent(entity)) { + EditorApiUtils::ModifyComponent( + _sceneManager, + entity, + [modelId](auto& modelComp, auto pool) { + modelComp.modelIndex = modelId; + } + ); + } + } + + if (modelId != INVALID_MODEL_ID && _modelManager) { + auto resource = _modelManager->GetResource(modelId); + if (resource) { + glm::vec3 center = resource->cpuData.globalCollider.center; + float radius = resource->cpuData.globalCollider.radius; + + for (EntityID entity : tagPool->GetDenseEntities()) { + const auto& tag = tagPool->Get(entity); + + if (tag.tag == "Camera" && registry.HasComponent(entity)) { + + EditorApiUtils::ModifyComponent( + _sceneManager, + entity, + [center, radius](auto& camComp, auto pool) { + camComp.target = center; + camComp.distance = std::max(radius * 2.5f, 2.0f); + } + ); + break; + } + } + } + } + + Syn::Info("ModelApiImpl: Applied model {} to preview object and adjusted camera.", modelId); + } } \ No newline at end of file diff --git a/SynapseEngine/Editor/EditorApi/Impl/ModelApiImpl.h b/SynapseEngine/Editor/EditorApi/Impl/ModelApiImpl.h index 4ed87882..5f7c750b 100644 --- a/SynapseEngine/Editor/EditorApi/Impl/ModelApiImpl.h +++ b/SynapseEngine/Editor/EditorApi/Impl/ModelApiImpl.h @@ -1,12 +1,13 @@ #pragma once #include "EditorCore/Api/IModelApi.h" #include "Engine/Mesh/ModelManager.h" +#include "Engine/Scene/SceneManager.h" namespace Syn { class ModelApiImpl : public IModelApi { public: - ModelApiImpl(ModelManager* modelManager) - : _modelManager(modelManager) {} + ModelApiImpl(ModelManager* modelManager, SceneManager* sceneManager) + : _modelManager(modelManager), _sceneManager(sceneManager) {} std::vector GetAllModels() const override; uint64_t GetVersion() const override; @@ -17,8 +18,10 @@ namespace Syn { const CpuModelData* GetModelCpuData(uint32_t modelId) const override; std::string GetNodeName(uint32_t modelId, uint16_t nodeIndex) const override; + void ApplyModelToPreviewObject(uint32_t modelId) override; private: ModelManager* _modelManager; + SceneManager* _sceneManager; uint32_t _selectedModelId = INVALID_MODEL_ID; int32_t _selectedNodeIndex = INVALID_NODE_INDEX; }; diff --git a/SynapseEngine/Editor/EditorApi/Impl/RenderApiImpl.cpp b/SynapseEngine/Editor/EditorApi/Impl/RenderApiImpl.cpp index 7ef11f6c..1ab549dd 100644 --- a/SynapseEngine/Editor/EditorApi/Impl/RenderApiImpl.cpp +++ b/SynapseEngine/Editor/EditorApi/Impl/RenderApiImpl.cpp @@ -95,22 +95,23 @@ namespace Syn { return EditorApiUtils::ReadComponent(_sceneManager, cameraEntity, [](const auto& c) { return c.proj; }, glm::mat4(1.0f)); } - EntityID RenderApiImpl::ReadEntityIdAtPixel(uint32_t x, uint32_t y) { + + std::pair RenderApiImpl::ReadEntityAndMeshIdAtPixel(uint32_t x, uint32_t y) { auto renderManager = _engine->GetRenderManager(); - if (!renderManager) return NULL_ENTITY; + if (!renderManager) return { NULL_ENTITY, 0 }; auto rtManager = renderManager->GetRenderTargetManager(); auto frameCtx = ServiceLocator::GetFrameContext(); uint32_t currentFrame = frameCtx ? frameCtx->currentFrameIndex : 0; auto group = rtManager->GetGroup(RenderTargetGroupNames::Main, currentFrame); - if (!group) return NULL_ENTITY; + if (!group) return { NULL_ENTITY, 0 }; auto entityImage = group->GetImage(RenderTargetNames::EntityIndex); - if (!entityImage) return NULL_ENTITY; + if (!entityImage) return { NULL_ENTITY, 0 }; auto extent = entityImage->GetExtent(); - if (x >= extent.width || y >= extent.height) return NULL_ENTITY; + if (x >= extent.width || y >= extent.height) return { NULL_ENTITY, 0 }; Vk::BufferConfig readbackConfig{}; readbackConfig.size = sizeof(uint32_t) * 2; @@ -144,15 +145,24 @@ namespace Syn { ServiceLocator::GetGpuUploader()->UploadSync(std::move(request)); EntityID selectedEntity = NULL_ENTITY; + uint32_t selectedMesh = 0; + void* mappedData = readbackBuffer->Map(); if (mappedData) { uint32_t pixelData[2] = { 0, 0 }; std::memcpy(pixelData, mappedData, sizeof(uint32_t) * 2); readbackBuffer->Unmap(); - uint32_t packedEntity = pixelData[0]; - selectedEntity = packedEntity & ~(1u << 31); + uint32_t word0 = pixelData[0]; + selectedEntity = word0 & ~(1u << 31); + + uint32_t word1 = pixelData[1]; + selectedMesh = (word1 >> 22) & 0x3FFu; } - return selectedEntity; + return { selectedEntity, selectedMesh }; + } + + EntityID RenderApiImpl::ReadEntityIdAtPixel(uint32_t x, uint32_t y) { + return ReadEntityAndMeshIdAtPixel(x, y).first; } } \ No newline at end of file diff --git a/SynapseEngine/Editor/EditorApi/Impl/RenderApiImpl.h b/SynapseEngine/Editor/EditorApi/Impl/RenderApiImpl.h index ded10a4d..aaf13e4e 100644 --- a/SynapseEngine/Editor/EditorApi/Impl/RenderApiImpl.h +++ b/SynapseEngine/Editor/EditorApi/Impl/RenderApiImpl.h @@ -14,6 +14,7 @@ namespace Syn { void ResizeRenderTargets(uint32_t width, uint32_t height) override; TextureHandle GetViewportTexture(const std::string& groupName, const std::string& targetName, const std::string& viewName) override; EntityID ReadEntityIdAtPixel(uint32_t x, uint32_t y) override; + std::pair ReadEntityAndMeshIdAtPixel(uint32_t x, uint32_t y) override; glm::mat4 GetEditorCameraView() const override; glm::mat4 GetEditorCameraProjection() const override; private: diff --git a/SynapseEngine/Editor/View/ModelViewport/ModelViewportView.cpp b/SynapseEngine/Editor/View/ModelViewport/ModelViewportView.cpp new file mode 100644 index 00000000..4f4a90f3 --- /dev/null +++ b/SynapseEngine/Editor/View/ModelViewport/ModelViewportView.cpp @@ -0,0 +1,261 @@ +#include "ModelViewportView.h" +#include "Engine/Vk/Image/ImageUtils.h" +#include "Editor/Manager/EditorIcons.h" +#include +#include +#include +#include +#include "Engine/Scene/DrawData/SceneDrawData.h" + +namespace Syn { + + void ModelViewportView::Draw(ModelViewportViewModel& vm) { + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2{ 0, 0 }); + + ImGui::Begin(SYN_ICON_CUBES " Model Viewport", nullptr); + + ModelViewportState state = vm.GetState(); + + ImVec2 viewportPanelSize = ImGui::GetContentRegionAvail(); + uint32_t currentWidth = static_cast(viewportPanelSize.x); + uint32_t currentHeight = static_cast(viewportPanelSize.y); + + bool isResizing = (currentWidth > 0 && currentHeight > 0 && (currentWidth != state.width || currentHeight != state.height)); + + vm.Dispatch(ResizeModelViewportIntent{ currentWidth, currentHeight }); + + ImVec2 imageStartPos = ImGui::GetCursorScreenPos(); + + if (viewportPanelSize.x <= 0.0f) viewportPanelSize.x = 1.0f; + if (viewportPanelSize.y <= 0.0f) viewportPanelSize.y = 1.0f; + + if (state.textureId && !isResizing) { + ImGui::GetWindowDrawList()->AddCallback(ImGui::GetPlatformIO().DrawCallback_SetSamplerNearest, nullptr); + ImGui::Image(state.textureId, viewportPanelSize); + ImGui::GetWindowDrawList()->AddCallback(ImGui::GetPlatformIO().DrawCallback_SetSamplerLinear, nullptr); + } + else { + ImGui::Dummy(viewportPanelSize); + } + + bool isImageHovered = ImGui::IsItemHovered(); + + RenderFloatingToolbar(vm, state, imageStartPos, viewportPanelSize); + + DrawGizmo(vm, state, imageStartPos, viewportPanelSize); + + if (!ImGuizmo::IsUsing()) { + if (isImageHovered && ImGui::IsMouseClicked(ImGuiMouseButton_Left) && !ImGuizmo::IsOver() && !ImGui::IsAnyItemHovered()) { + ImVec2 mousePos = ImGui::GetMousePos(); + uint32_t x = static_cast(mousePos.x - imageStartPos.x); + uint32_t y = static_cast(mousePos.y - imageStartPos.y); + vm.Dispatch(PickMeshIntent{ x, y }); + } + } + + HandleShortcuts(vm); + + ImGui::End(); + ImGui::PopStyleVar(); + } + + void ModelViewportView::RenderFloatingToolbar(ModelViewportViewModel& vm, const ModelViewportState& state, ImVec2 startPos, ImVec2 size) { + float toolbarWidth = 40.0f; + float toolbarHeight = 110.0f; + + ImGui::SetCursorScreenPos(ImVec2(startPos.x + size.x - toolbarWidth - 8.0f, startPos.y + 8.0f)); + + ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.1f, 0.1f, 0.1f, 0.85f)); + ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 6.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(4.0f, 6.0f)); + + if (ImGui::BeginChild("##ModelFloatingToolbar", ImVec2(toolbarWidth, toolbarHeight), ImGuiChildFlags_AlwaysUseWindowPadding, ImGuiWindowFlags_NoScrollbar)) { + + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(4.0f, 6.0f)); + + if (ImGui::Button(SYN_ICON_ARROWS_ALT, ImVec2(32, 28))) ImGui::OpenPopup("GizmoPopup"); + if (ImGui::IsItemHovered()) ImGui::SetTooltip("Gizmo Settings"); + + if (ImGui::Button(SYN_ICON_LAYER_GROUP, ImVec2(32, 28))) ImGui::OpenPopup("ImagePopup"); + if (ImGui::IsItemHovered()) ImGui::SetTooltip("Render Targets & View Modes"); + + if (ImGui::Button(SYN_ICON_BUG, ImVec2(32, 28))) ImGui::OpenPopup("DebugPopup"); + if (ImGui::IsItemHovered()) ImGui::SetTooltip("Debug Visibility"); + + ImGui::PopStyleVar(); + ImGui::PopStyleColor(); + + DrawGizmoPopup(vm, state); + DrawImagePopup(vm, state); + DrawDebugPopup(vm, state); + } + ImGui::EndChild(); + + ImGui::PopStyleVar(2); + ImGui::PopStyleColor(); + } + + void ModelViewportView::DrawGizmoPopup(ModelViewportViewModel& vm, const ModelViewportState& state) { + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8.0f, 8.0f)); + if (ImGui::BeginPopup("GizmoPopup")) { + if (ImGui::BeginChild("##GizmoWindow", ImVec2(240, 210), false)) { + + ImGui::SeparatorText("Operation"); + + int mode = static_cast(state.gizmoMode); + if (ImGui::RadioButton("Local##Gizmo", &mode, ImGuizmo::LOCAL)) + vm.Dispatch(ChangeModelGizmoModeIntent{ ImGuizmo::LOCAL }); + + ImGui::SameLine(); + if (ImGui::RadioButton("World##Gizmo", &mode, ImGuizmo::WORLD)) + vm.Dispatch(ChangeModelGizmoModeIntent{ ImGuizmo::WORLD }); + + int op = static_cast(state.gizmoOperation); + if (ImGui::RadioButton("Translate##Gizmo", &op, ImGuizmo::TRANSLATE)) + vm.Dispatch(ChangeModelGizmoOperationIntent{ ImGuizmo::TRANSLATE }); + ImGui::SameLine(); + if (ImGui::RadioButton("Rotate##Gizmo", &op, ImGuizmo::ROTATE)) + vm.Dispatch(ChangeModelGizmoOperationIntent{ ImGuizmo::ROTATE }); + ImGui::SameLine(); + if (ImGui::RadioButton("Scale##Gizmo", &op, ImGuizmo::SCALE)) + vm.Dispatch(ChangeModelGizmoOperationIntent{ ImGuizmo::SCALE }); + + ImGui::SeparatorText("Snapping"); + + bool snap = state.useSnap; + if (ImGui::Checkbox("Enable##Snap", &snap)) + vm.Dispatch(ToggleModelSnapIntent{ snap }); + + glm::vec3 snapTrans = state.snapTranslate; + if (ImGui::DragFloat3("Translate##Snap", glm::value_ptr(snapTrans), 0.1f)) + vm.Dispatch(ChangeModelSnapTranslateIntent{ snapTrans }); + + float snapRot = state.snapAngle; + if (ImGui::DragFloat("Rotate##Snap", &snapRot, 1.0f)) + vm.Dispatch(ChangeModelSnapRotateIntent{ snapRot }); + + float snapScl = state.snapScale; + if (ImGui::DragFloat("Scale##Snap", &snapScl, 0.1f)) + vm.Dispatch(ChangeModelSnapScaleIntent{ snapScl }); + + } + ImGui::EndChild(); + ImGui::EndPopup(); + } + ImGui::PopStyleVar(); + } + + void ModelViewportView::DrawImagePopup(ModelViewportViewModel& vm, const ModelViewportState& state) { + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8.0f, 8.0f)); + if (ImGui::BeginPopup("ImagePopup")) { + if (ImGui::BeginChild("##ViewportImage", ImVec2(280, 200), false)) { + + auto RadioButton = [&](const char* label, const std::string& group, const std::string& target, const std::string& view) { + bool isActive = (state.currentTarget == target && state.currentView == view); + if (ImGui::RadioButton(label, isActive)) { + vm.Dispatch(ChangeModelTargetIntent{ group, target, view }); + } + return isActive; + }; + + RadioButton("Main", RenderTargetGroupNames::Main, RenderTargetNames::Main, Vk::ImageViewNames::Default); + + ImGui::SeparatorText("GBuffer Textures"); + + RadioButton("Color", RenderTargetGroupNames::Main, RenderTargetNames::ColorMetallic, RenderTargetViewNames::Color); + RadioButton("Metallic", RenderTargetGroupNames::Main, RenderTargetNames::ColorMetallic, RenderTargetViewNames::Metallic); + RadioButton("Normal", RenderTargetGroupNames::Main, RenderTargetNames::NormalRoughness, RenderTargetViewNames::Normal); + RadioButton("Roughness", RenderTargetGroupNames::Main, RenderTargetNames::NormalRoughness, RenderTargetViewNames::Roughness); + } + ImGui::EndChild(); + ImGui::EndPopup(); + } + ImGui::PopStyleVar(); + } + + void ModelViewportView::DrawDebugPopup(ModelViewportViewModel& vm, const ModelViewportState& state) { + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8.0f, 8.0f)); + if (ImGui::BeginPopup("DebugPopup")) { + if (ImGui::BeginChild("##VisualizationWindow", ImVec2(220, 280), false)) { + + ImGui::SeparatorText("Debug Visibility"); + + bool enabled = state.enableDebugVisibility; + if (ImGui::Checkbox("Enable", &enabled)) { + vm.Dispatch(ToggleModelDebugVisibilityIntent{ enabled }); + } + + ImGui::BeginDisabled(!state.enableDebugVisibility); + + ImGui::SeparatorText("Mode"); + + int mode = static_cast(state.debugVisibilityMode); + auto RadioButton = [&](const char* label, int targetMode) { + if (ImGui::RadioButton(label, &mode, targetMode)) { + vm.Dispatch(ChangeModelDebugVisibilityModeIntent{ static_cast(targetMode) }); + } + }; + + RadioButton("Entity ID", 0); + RadioButton("Pipeline Type", 1); + RadioButton("LOD Level", 2); + RadioButton("Mesh Index", 3); + RadioButton("Meshlet Index", 4); + RadioButton("Triangle Index", 5); + RadioButton("All Combined", 6); + RadioButton("Material Type", 7); + + ImGui::EndDisabled(); + + } + ImGui::EndChild(); + ImGui::EndPopup(); + } + ImGui::PopStyleVar(); + } + + void ModelViewportView::DrawGizmo(ModelViewportViewModel& vm, const ModelViewportState& state, ImVec2 startPos, ImVec2 size) { + if (state.activeEntity == NULL_ENTITY) + return; + + ImGuizmo::SetOrthographic(false); + ImGuizmo::SetDrawlist(); + ImGuizmo::SetRect(startPos.x, startPos.y, size.x, size.y); + + glm::mat4 cameraView = state.cameraView; + glm::mat4 cameraProj = state.cameraProj; + glm::mat4 transform = state.entityWorldTransform; + + float* snapValue = nullptr; + if (state.useSnap) { + switch (state.gizmoOperation) { + case ImGuizmo::TRANSLATE: snapValue = const_cast(glm::value_ptr(state.snapTranslate)); break; + case ImGuizmo::ROTATE: snapValue = const_cast(&state.snapAngle); break; + case ImGuizmo::SCALE: snapValue = const_cast(&state.snapScale); break; + } + } + + ImGuizmo::Manipulate( + glm::value_ptr(cameraView), + glm::value_ptr(cameraProj), + state.gizmoOperation, + state.gizmoMode, + glm::value_ptr(transform), + nullptr, snapValue + ); + + if (ImGuizmo::IsUsing()) { + vm.Dispatch(ApplyModelGizmoTransformIntent{ transform }); + } + } + + void ModelViewportView::HandleShortcuts(ModelViewportViewModel& vm) { + if (ImGui::IsWindowFocused() || ImGui::IsWindowHovered()) { + if (ImGui::IsKeyPressed(ImGuiKey_1)) vm.Dispatch(ChangeModelGizmoOperationIntent{ ImGuizmo::TRANSLATE }); + if (ImGui::IsKeyPressed(ImGuiKey_2)) vm.Dispatch(ChangeModelGizmoOperationIntent{ ImGuizmo::ROTATE }); + if (ImGui::IsKeyPressed(ImGuiKey_3)) vm.Dispatch(ChangeModelGizmoOperationIntent{ ImGuizmo::SCALE }); + } + } + +} \ No newline at end of file diff --git a/SynapseEngine/Editor/View/ModelViewport/ModelViewportView.h b/SynapseEngine/Editor/View/ModelViewport/ModelViewportView.h new file mode 100644 index 00000000..2937ddb4 --- /dev/null +++ b/SynapseEngine/Editor/View/ModelViewport/ModelViewportView.h @@ -0,0 +1,20 @@ +#pragma once +#include "Editor/View/IView.h" +#include "EditorCore/ViewModels/ModelViewport/ModelViewportViewModel.h" +#include + +namespace Syn { + class ModelViewportView : public IView { + public: + void Draw(ModelViewportViewModel& vm) override; + private: + void RenderFloatingToolbar(ModelViewportViewModel& vm, const ModelViewportState& state, ImVec2 startPos, ImVec2 size); + + void DrawGizmoPopup(ModelViewportViewModel& vm, const ModelViewportState& state); + void DrawImagePopup(ModelViewportViewModel& vm, const ModelViewportState& state); + void DrawDebugPopup(ModelViewportViewModel& vm, const ModelViewportState& state); + + void DrawGizmo(ModelViewportViewModel& vm, const ModelViewportState& state, ImVec2 startPos, ImVec2 size); + void HandleShortcuts(ModelViewportViewModel& vm); + }; +} \ No newline at end of file diff --git a/SynapseEngine/Editor/Workspace/MaterialWorkspace.cpp b/SynapseEngine/Editor/Workspace/MaterialWorkspace.cpp index c4ce733b..7fe9d69e 100644 --- a/SynapseEngine/Editor/Workspace/MaterialWorkspace.cpp +++ b/SynapseEngine/Editor/Workspace/MaterialWorkspace.cpp @@ -16,6 +16,8 @@ #include "Editor/View/MaterialViewport/MaterialViewportView.h" #include "EditorCore/ViewModels/MaterialViewport/MaterialViewportViewModel.h" +#include "Engine/Scene/SceneNames.h" + namespace Syn { MaterialWorkspace::MaterialWorkspace(EditorContext* context, IconManager* iconManager, const std::string& assetPath) @@ -23,7 +25,7 @@ namespace Syn { void MaterialWorkspace::OnActivate() { if (_context && _context->GetSceneApi()) { - _context->GetSceneApi()->ActivateScene("MaterialPreview"); + _context->GetSceneApi()->ActivateScene(SceneNames::MaterialPreview); } } diff --git a/SynapseEngine/Editor/Workspace/ModelWorkspace.cpp b/SynapseEngine/Editor/Workspace/ModelWorkspace.cpp index 701f5c8f..e094dde3 100644 --- a/SynapseEngine/Editor/Workspace/ModelWorkspace.cpp +++ b/SynapseEngine/Editor/Workspace/ModelWorkspace.cpp @@ -10,12 +10,22 @@ #include "Editor/View/ModelProperties/ModelPropertiesView.h" #include "EditorCore/ViewModels/ModelProperties/ModelPropertiesViewModel.h" +#include "Editor/View/ModelViewport/ModelViewportView.h" +#include "EditorCore/ViewModels/ModelViewport/ModelViewportViewModel.h" + +#include "Engine/Scene/SceneNames.h" namespace Syn { ModelWorkspace::ModelWorkspace(EditorContext* context, IconManager* iconManager, const std::string& assetPath) : _context(context), _iconManager(iconManager), _assetPath(assetPath) {} + void ModelWorkspace::OnActivate() { + if (_context && _context->GetSceneApi()) { + _context->GetSceneApi()->ActivateScene(SceneNames::ModelPreview); + } + } + void ModelWorkspace::Initialize() { using ContentBrowserWin = EditorWindow; @@ -35,6 +45,11 @@ namespace Syn { ModelPropertiesView{}, ModelPropertiesViewModel{ _context->GetModelApi() } ); - } + using ModelViewportWin = EditorWindow; + AddWindow( + ModelViewportView{}, + ModelViewportViewModel{ _context->GetRenderApi(), _context->GetSelectionApi(), _context->GetTransformApi(), _context->GetSettingsApi(), _context->GetModelApi() } + ); + } } \ No newline at end of file diff --git a/SynapseEngine/Editor/Workspace/ModelWorkspace.h b/SynapseEngine/Editor/Workspace/ModelWorkspace.h index 60722461..c3c30995 100644 --- a/SynapseEngine/Editor/Workspace/ModelWorkspace.h +++ b/SynapseEngine/Editor/Workspace/ModelWorkspace.h @@ -12,6 +12,7 @@ namespace Syn { ~ModelWorkspace() override = default; void Initialize() override; + void OnActivate() override; private: EditorContext* _context; IconManager* _iconManager; diff --git a/SynapseEngine/Editor/Workspace/SceneWorkspace.cpp b/SynapseEngine/Editor/Workspace/SceneWorkspace.cpp index 54d9c608..a7c07fd1 100644 --- a/SynapseEngine/Editor/Workspace/SceneWorkspace.cpp +++ b/SynapseEngine/Editor/Workspace/SceneWorkspace.cpp @@ -16,6 +16,9 @@ #include "Editor/View/Logger/LoggerView.h" #include "EditorCore/ViewModels/Logger/LoggerViewModel.h" +#include "Engine/Scene/SceneNames.h" + + namespace Syn { SceneWorkspace::SceneWorkspace(EditorContext* context, IconManager* iconManager, const std::string& assetPath) @@ -23,7 +26,7 @@ namespace Syn { void SceneWorkspace::OnActivate() { if (_context && _context->GetSceneApi()) { - _context->GetSceneApi()->ActivateScene("TestLevel"); + _context->GetSceneApi()->ActivateScene(SceneNames::Main); } } diff --git a/SynapseEngine/EditorCore/Api/IModelApi.h b/SynapseEngine/EditorCore/Api/IModelApi.h index 08d5529b..97be4723 100644 --- a/SynapseEngine/EditorCore/Api/IModelApi.h +++ b/SynapseEngine/EditorCore/Api/IModelApi.h @@ -27,5 +27,7 @@ namespace Syn virtual const CpuModelData* GetModelCpuData(uint32_t modelId) const = 0; virtual std::string GetNodeName(uint32_t modelId, uint16_t nodeIndex) const = 0; + + virtual void ApplyModelToPreviewObject(uint32_t modelId) = 0; }; } \ No newline at end of file diff --git a/SynapseEngine/EditorCore/Api/IRenderApi.h b/SynapseEngine/EditorCore/Api/IRenderApi.h index 5c39d682..077f4ee1 100644 --- a/SynapseEngine/EditorCore/Api/IRenderApi.h +++ b/SynapseEngine/EditorCore/Api/IRenderApi.h @@ -13,6 +13,7 @@ namespace Syn { virtual void ResizeRenderTargets(uint32_t width, uint32_t height) = 0; virtual TextureHandle GetViewportTexture(const std::string& groupName, const std::string& targetName, const std::string& viewName) = 0; virtual EntityID ReadEntityIdAtPixel(uint32_t x, uint32_t y) = 0; + virtual std::pair ReadEntityAndMeshIdAtPixel(uint32_t x, uint32_t y) = 0; virtual glm::mat4 GetEditorCameraView() const = 0; virtual glm::mat4 GetEditorCameraProjection() const = 0; diff --git a/SynapseEngine/EditorCore/ViewModels/ModelHierarchy/ModelHierarchyViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/ModelHierarchy/ModelHierarchyViewModel.cpp index 68489d12..5935dbd6 100644 --- a/SynapseEngine/EditorCore/ViewModels/ModelHierarchy/ModelHierarchyViewModel.cpp +++ b/SynapseEngine/EditorCore/ViewModels/ModelHierarchy/ModelHierarchyViewModel.cpp @@ -35,7 +35,12 @@ namespace Syn { using T = std::decay_t; if constexpr (std::is_same_v) { - if (_modelApi) _modelApi->SetSelected(arg.modelId, arg.descriptorIndex); + if (_modelApi) + { + _modelApi->SetSelected(arg.modelId, arg.descriptorIndex); + _modelApi->ApplyModelToPreviewObject(arg.modelId); + } + _state.selectedModelId = arg.modelId; _state.selectedDescriptorIndex = arg.descriptorIndex; } diff --git a/SynapseEngine/EditorCore/ViewModels/ModelViewport/ModelViewportIntent.cpp b/SynapseEngine/EditorCore/ViewModels/ModelViewport/ModelViewportIntent.cpp new file mode 100644 index 00000000..f83cb4ef --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/ModelViewport/ModelViewportIntent.cpp @@ -0,0 +1 @@ +#include "ModelViewportIntent.h" \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/ModelViewport/ModelViewportIntent.h b/SynapseEngine/EditorCore/ViewModels/ModelViewport/ModelViewportIntent.h new file mode 100644 index 00000000..5e4b8177 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/ModelViewport/ModelViewportIntent.h @@ -0,0 +1,40 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +namespace Syn { + struct ResizeModelViewportIntent { uint32_t width; uint32_t height; }; + struct ChangeModelTargetIntent { std::string currentGroup; std::string targetName; std::string viewName; }; + + struct ChangeModelGizmoOperationIntent { ImGuizmo::OPERATION op; }; + struct ChangeModelGizmoModeIntent { ImGuizmo::MODE mode; }; + struct ToggleModelSnapIntent { bool useSnap; }; + struct ChangeModelSnapTranslateIntent { glm::vec3 snap; }; + struct ChangeModelSnapRotateIntent { float angle; }; + struct ChangeModelSnapScaleIntent { float scale; }; + struct ApplyModelGizmoTransformIntent { glm::mat4 newWorldMatrix; }; + + struct PickMeshIntent { uint32_t x; uint32_t y; }; + + struct ToggleModelDebugVisibilityIntent { bool enabled; }; + struct ChangeModelDebugVisibilityModeIntent { uint32_t mode; }; + + using ModelViewportIntent = std::variant< + ResizeModelViewportIntent, + ChangeModelTargetIntent, + ChangeModelGizmoOperationIntent, + ChangeModelGizmoModeIntent, + ToggleModelSnapIntent, + ApplyModelGizmoTransformIntent, + PickMeshIntent, + ToggleModelDebugVisibilityIntent, + ChangeModelDebugVisibilityModeIntent, + ChangeModelSnapTranslateIntent, + ChangeModelSnapRotateIntent, + ChangeModelSnapScaleIntent + >; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/ModelViewport/ModelViewportState.cpp b/SynapseEngine/EditorCore/ViewModels/ModelViewport/ModelViewportState.cpp new file mode 100644 index 00000000..c135f3bf --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/ModelViewport/ModelViewportState.cpp @@ -0,0 +1 @@ +#include "ModelViewportState.h" \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/ModelViewport/ModelViewportState.h b/SynapseEngine/EditorCore/ViewModels/ModelViewport/ModelViewportState.h new file mode 100644 index 00000000..a646710b --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/ModelViewport/ModelViewportState.h @@ -0,0 +1,43 @@ +#pragma once +#include +#include +#include +#include +#include +#include "EditorCore/Types/TextureHandle.h" +#include "Engine/Vk/Image/ImageViewNames.h" +#include "Engine/Render/RenderNames.h" + +namespace Syn { + struct ModelViewportState { + uint32_t width = 0; + uint32_t height = 0; + + TextureHandle textureId = InvalidTextureHandle; + std::string currentGroup = RenderTargetGroupNames::Main; + std::string currentTarget = RenderTargetNames::Main; + std::string currentView = Vk::ImageViewNames::Default; + + bool isHovered = false; + bool isFocused = false; + + ImGuizmo::OPERATION gizmoOperation = ImGuizmo::TRANSLATE; + ImGuizmo::MODE gizmoMode = ImGuizmo::LOCAL; + bool useSnap = false; + + glm::vec3 snapTranslate{ 1.0f, 1.0f, 1.0f }; + float snapAngle = 45.0f; + float snapScale = 0.5f; + + uint32_t activeEntity = 0; + uint32_t activeModelId = 0xFFFFFFFF; + int32_t activeNodeIndex = -1; + + glm::mat4 cameraView{ 1.0f }; + glm::mat4 cameraProj{ 1.0f }; + glm::mat4 entityWorldTransform{ 1.0f }; + + bool enableDebugVisibility = false; + uint32_t debugVisibilityMode = 0; + }; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/ModelViewport/ModelViewportViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/ModelViewport/ModelViewportViewModel.cpp new file mode 100644 index 00000000..559e6622 --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/ModelViewport/ModelViewportViewModel.cpp @@ -0,0 +1,117 @@ +#include "ModelViewportViewModel.h" +#include + +namespace Syn { + + ModelViewportViewModel::ModelViewportViewModel(IRenderApi* renderApi, ISelectionApi* selectionApi, ITransformApi* transformApi, ISettingsApi* settingsApi, IModelApi* modelApi) + : _renderApi(renderApi), _selectionApi(selectionApi), _transformApi(transformApi), _settingsApi(settingsApi), _modelApi(modelApi) {} + + const ModelViewportState& ModelViewportViewModel::GetState() const { + return _state; + } + + void ModelViewportViewModel::SyncWithEngine() { + if (!_renderApi) return; + + _state.textureId = _renderApi->GetViewportTexture(_state.currentGroup, _state.currentTarget, _state.currentView); + _state.cameraView = _renderApi->GetEditorCameraView(); + _state.cameraProj = _renderApi->GetEditorCameraProjection(); + + if (_modelApi) { + auto [mId, nIdx] = _modelApi->GetSelected(); + _state.activeModelId = mId; + _state.activeNodeIndex = nIdx; + } + + _state.activeEntity = _selectionApi->GetSelectedEntity(); + if (_state.activeEntity != NULL_ENTITY) { + _state.entityWorldTransform = _transformApi->GetEntityWorldMatrix(_state.activeEntity); + } + + if (_settingsApi) { + SceneSettings settings = _settingsApi->GetSceneSettings(); + _state.enableDebugVisibility = settings.debug.enableDebugVisibility; + _state.debugVisibilityMode = static_cast(settings.debug.debugVisibilityMode); + } + } + + void ModelViewportViewModel::Dispatch(const ModelViewportIntent& intent) { + std::visit([this](auto&& arg) { + using T = std::decay_t; + if constexpr (std::is_same_v) HandleResize(arg); + else if constexpr (std::is_same_v) HandleChangeTarget(arg); + else if constexpr (std::is_same_v) _state.gizmoOperation = arg.op; + else if constexpr (std::is_same_v) _state.gizmoMode = arg.mode; + else if constexpr (std::is_same_v) _state.useSnap = arg.useSnap; + else if constexpr (std::is_same_v) HandleGizmoTransform(arg); + else if constexpr (std::is_same_v) HandlePickMesh(arg); + else if constexpr (std::is_same_v) HandleToggleDebugVisibility(arg); + else if constexpr (std::is_same_v) HandleChangeDebugVisibilityMode(arg); + else if constexpr (std::is_same_v) _state.snapTranslate = arg.snap; + else if constexpr (std::is_same_v) _state.snapAngle = arg.angle; + else if constexpr (std::is_same_v) _state.snapScale = arg.scale; + }, intent); + } + + void ModelViewportViewModel::HandlePickMesh(const PickMeshIntent& intent) { + auto [entityId, meshIndex] = _renderApi->ReadEntityAndMeshIdAtPixel(intent.x, intent.y); + + if (entityId != NULL_ENTITY) { + _selectionApi->SetSelectedEntity(entityId); + + if (_modelApi) { + auto [currentModel, currentNode] = _modelApi->GetSelected(); + if (currentModel != INVALID_MODEL_ID) { + _modelApi->SetSelected(currentModel, static_cast(meshIndex)); + } + } + } + } + + void ModelViewportViewModel::HandleResize(const ResizeModelViewportIntent& intent) { + if (intent.width > 0 && intent.height > 0 && + (_state.width != intent.width || _state.height != intent.height)) + { + _state.width = intent.width; + _state.height = intent.height; + _renderApi->ResizeRenderTargets(_state.width, _state.height); + } + } + + void ModelViewportViewModel::HandleChangeTarget(const ChangeModelTargetIntent& intent) { + _state.currentGroup = intent.currentGroup; + _state.currentTarget = intent.targetName; + _state.currentView = intent.viewName; + } + + void ModelViewportViewModel::HandleGizmoTransform(const ApplyModelGizmoTransformIntent& intent) { + if (_state.activeEntity == NULL_ENTITY) + return; + + glm::vec3 translation, rotation, scale; + ImGuizmo::DecomposeMatrixToComponents(glm::value_ptr(intent.newWorldMatrix), glm::value_ptr(translation), glm::value_ptr(rotation), glm::value_ptr(scale)); + + _transformApi->SetEntityPosition(_state.activeEntity, translation); + _transformApi->SetEntityRotation(_state.activeEntity, rotation); + _transformApi->SetEntityScale(_state.activeEntity, scale); + } + + void ModelViewportViewModel::HandleToggleDebugVisibility(const ToggleModelDebugVisibilityIntent& intent) { + _state.enableDebugVisibility = intent.enabled; + if (_settingsApi) { + SceneSettings settings = _settingsApi->GetSceneSettings(); + settings.debug.enableDebugVisibility = intent.enabled; + _settingsApi->SetSceneSettings(settings); + } + } + + void ModelViewportViewModel::HandleChangeDebugVisibilityMode(const ChangeModelDebugVisibilityModeIntent& intent) { + _state.debugVisibilityMode = intent.mode; + if (_settingsApi) { + SceneSettings settings = _settingsApi->GetSceneSettings(); + settings.debug.debugVisibilityMode = static_cast(intent.mode); + _settingsApi->SetSceneSettings(settings); + } + } + +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/ModelViewport/ModelViewportViewModel.h b/SynapseEngine/EditorCore/ViewModels/ModelViewport/ModelViewportViewModel.h new file mode 100644 index 00000000..4bc7ae1e --- /dev/null +++ b/SynapseEngine/EditorCore/ViewModels/ModelViewport/ModelViewportViewModel.h @@ -0,0 +1,36 @@ +#pragma once +#include "EditorCore/ViewModels/IViewModel.h" +#include "ModelViewportState.h" +#include "ModelViewportIntent.h" +#include "EditorCore/Api/IRenderApi.h" +#include "EditorCore/Api/ISelectionApi.h" +#include "EditorCore/Api/ITransformApi.h" +#include "EditorCore/Api/ISettingsApi.h" +#include "EditorCore/Api/IModelApi.h" + +namespace Syn { + class ModelViewportViewModel : public IViewModel { + public: + ModelViewportViewModel(IRenderApi* renderApi, ISelectionApi* selectionApi, ITransformApi* transformApi, ISettingsApi* settingsApi, IModelApi* modelApi); + ~ModelViewportViewModel() override = default; + + const ModelViewportState& GetState() const override; + + void SyncWithEngine() override; + void Dispatch(const ModelViewportIntent& intent) override; + private: + void HandlePickMesh(const PickMeshIntent& intent); + void HandleResize(const ResizeModelViewportIntent& intent); + void HandleChangeTarget(const ChangeModelTargetIntent& intent); + void HandleGizmoTransform(const ApplyModelGizmoTransformIntent& intent); + void HandleToggleDebugVisibility(const ToggleModelDebugVisibilityIntent& intent); + void HandleChangeDebugVisibilityMode(const ChangeModelDebugVisibilityModeIntent& intent); + private: + IRenderApi* _renderApi = nullptr; + ISelectionApi* _selectionApi = nullptr; + ITransformApi* _transformApi = nullptr; + ISettingsApi* _settingsApi = nullptr; + IModelApi* _modelApi = nullptr; + ModelViewportState _state; + }; +} \ No newline at end of file diff --git a/SynapseEngine/Engine/Engine.cpp b/SynapseEngine/Engine/Engine.cpp index 41ebf1f1..29d88113 100644 --- a/SynapseEngine/Engine/Engine.cpp +++ b/SynapseEngine/Engine/Engine.cpp @@ -7,6 +7,7 @@ #include "Vk/Buffer/SynVkBuffer.h" #include "Vk/Rendering/GpuUploader.h" +#include "Engine/Scene/SceneNames.h" #include "Engine/Manager/ResourceManager.h" #include "Engine/Manager/ShaderManager.h" #include "Engine/Mesh/Builder/StaticMeshBuilder.h" @@ -27,6 +28,7 @@ #include "Engine/Scene/SceneManager.h" #include "Engine/Scene/Source/Procedural/TestSceneSource.h" #include "Engine/Scene/Source/Procedural/MaterialPreviewSceneSource.h" +#include "Engine/Scene/Source/Procedural/ModelPreviewSceneSource.h" #include "Engine/Scene/Source/Procedural/NatureSceneSource.h" #include "Engine/Scene/Source/File/FileSceneSource.h" @@ -325,15 +327,19 @@ namespace Syn _sceneManager = std::make_unique(std::move(writer), std::move(loader)); ServiceLocator::ProvideSceneManager(_sceneManager.get()); - _sceneManager->RegisterScene("TestLevel", [frames]() { + _sceneManager->RegisterScene(SceneNames::Main, [frames]() { return std::make_unique(frames, std::make_unique()); }); - _sceneManager->RegisterScene("MaterialPreview", [frames]() { + _sceneManager->RegisterScene(SceneNames::MaterialPreview, [frames]() { return std::make_unique(frames, std::make_unique()); }); - _sceneManager->LoadScene("TestLevel"); + _sceneManager->RegisterScene(SceneNames::ModelPreview, [frames]() { + return std::make_unique(frames, std::make_unique()); + }); + + _sceneManager->LoadScene(SceneNames::Main); } void Engine::InitPhysicsEngine() diff --git a/SynapseEngine/Engine/Image/Loader/HdriImageLoader.cpp b/SynapseEngine/Engine/Image/Loader/HdriImageLoader.cpp new file mode 100644 index 00000000..d7138e8a --- /dev/null +++ b/SynapseEngine/Engine/Image/Loader/HdriImageLoader.cpp @@ -0,0 +1,72 @@ +#include "HdriImageLoader.h" +#include +#include +#include "Engine/Logger/SynLog.h" + +namespace Syn +{ + std::optional HdriImageLoader::LoadFile(const std::filesystem::path& path) { + int width, height, originalChannels; + + float* data = stbi_loadf(path.string().c_str(), &width, &height, &originalChannels, STBI_default); + + if (!data) { + Error("Failed to load HDR image: {} - {}", path.string(), stbi_failure_reason()); + return std::nullopt; + } + + return ProcessData(data, width, height, originalChannels); + } + + std::optional HdriImageLoader::LoadMemory(const std::vector& data) { + int width, height, originalChannels; + + float* stbiData = stbi_loadf_from_memory(data.data(), static_cast(data.size()), &width, &height, &originalChannels, STBI_default); + + if (!stbiData) { + Error("HdriImageLoader failed to load HDR image from memory - {}", stbi_failure_reason()); + return std::nullopt; + } + + return ProcessData(stbiData, width, height, originalChannels); + } + + std::optional HdriImageLoader::ProcessData(float* data, int width, int height, int originalChannels) { + + int desiredChannels = 4; + + RawImage rawImage{}; + rawImage.width = static_cast(width); + rawImage.height = static_cast(height); + rawImage.depth = 1; + rawImage.mipLevels = 1; + rawImage.isCompressed = false; + rawImage.format = VK_FORMAT_R32G32B32A32_SFLOAT; + + size_t numPixels = static_cast(width * height); + size_t imageSizeInBytes = numPixels * desiredChannels * sizeof(float); + + rawImage.pixels.resize(imageSizeInBytes); + float* destPixels = reinterpret_cast(rawImage.pixels.data()); + + if (originalChannels == 3) { + for (size_t i = 0; i < numPixels; ++i) { + destPixels[i * 4 + 0] = data[i * 3 + 0]; + destPixels[i * 4 + 1] = data[i * 3 + 1]; + destPixels[i * 4 + 2] = data[i * 3 + 2]; + destPixels[i * 4 + 3] = 1.0f; + } + } + else { + std::memcpy(destPixels, data, imageSizeInBytes); + } + + stbi_image_free(data); + return rawImage; + } + + std::vector HdriImageLoader::GetSupportedExtensions() const + { + return { ".hdr" }; + } +} \ No newline at end of file diff --git a/SynapseEngine/Engine/Image/Loader/HdriImageLoader.h b/SynapseEngine/Engine/Image/Loader/HdriImageLoader.h new file mode 100644 index 00000000..745dfb9d --- /dev/null +++ b/SynapseEngine/Engine/Image/Loader/HdriImageLoader.h @@ -0,0 +1,19 @@ +#pragma once +#include "IImageLoader.h" +#include + +namespace Syn +{ + class SYN_API HdriImageLoader : public IImageLoader + { + public: + HdriImageLoader() = default; + ~HdriImageLoader() override = default; + + std::optional LoadFile(const std::filesystem::path& path) override; + std::optional LoadMemory(const std::vector& data) override; + std::vector GetSupportedExtensions() const override; + private: + std::optional ProcessData(float* data, int width, int height, int originalChannels); + }; +} \ No newline at end of file diff --git a/SynapseEngine/Engine/Manager/ResourceManager.cpp b/SynapseEngine/Engine/Manager/ResourceManager.cpp index 431f5309..c6746f8b 100644 --- a/SynapseEngine/Engine/Manager/ResourceManager.cpp +++ b/SynapseEngine/Engine/Manager/ResourceManager.cpp @@ -23,6 +23,7 @@ #include "Engine/Image/Loader/StbImageLoader.h" #include "Engine/Image/Loader/GliImageLoader.h" #include "Engine/Image/Loader/SvgImageLoader.h" +#include "Engine/Image/Loader/HdriImageLoader.h" #include "Engine/Image/Uploader/DefaultGpuImageUploader.h" #include "Engine/Animation/Loader/AnimationLoaderRegistry.h" @@ -69,6 +70,7 @@ namespace Syn { _imageBuilder->RegisterLoader(std::make_shared(), 1); _imageBuilder->RegisterLoader(std::make_shared(), 1); _imageBuilder->RegisterLoader(std::make_shared(), 1); + _imageBuilder->RegisterLoader(std::make_shared(), 1); ServiceLocator::ProvideImageBuilder(_imageBuilder.get()); diff --git a/SynapseEngine/Engine/Scene/SceneNames.h b/SynapseEngine/Engine/Scene/SceneNames.h new file mode 100644 index 00000000..bb939f61 --- /dev/null +++ b/SynapseEngine/Engine/Scene/SceneNames.h @@ -0,0 +1,12 @@ +#pragma once +#include "Engine/SynApi.h" + +namespace Syn +{ + struct SYN_API SceneNames + { + static constexpr const char* Main = "Main"; + static constexpr const char* MaterialPreview = "MaterialPreview"; + static constexpr const char* ModelPreview = "ModelPreview"; + }; +} \ No newline at end of file diff --git a/SynapseEngine/Engine/Scene/Source/Procedural/MaterialPreviewSceneSource.cpp b/SynapseEngine/Engine/Scene/Source/Procedural/MaterialPreviewSceneSource.cpp index f4751bbb..cc87030a 100644 --- a/SynapseEngine/Engine/Scene/Source/Procedural/MaterialPreviewSceneSource.cpp +++ b/SynapseEngine/Engine/Scene/Source/Procedural/MaterialPreviewSceneSource.cpp @@ -12,6 +12,7 @@ #include "Engine/Material/MaterialManager.h" #include "Engine/Component/Light/Point/PointLightComponent.h" #include "Engine/Component/Light/Direction/DirectionLightComponent.h" +#include "Engine/Image/ImageManager.h" #include "Engine/Mesh/MeshSourceNames.h" #include "Engine/Logger/SynLog.h" #include "Engine/Utils/PathUtils.h" @@ -164,33 +165,18 @@ namespace Syn // 2. Center object: Suzanne std::string basePath = "C:/Users/User/Desktop/Models/"; + + auto skyTextureId = ServiceLocator::GetImageManager()->LoadImageSync(basePath + "MaterialPreview.hdr"); + scene.GetSettings()->environment.skyTextureId = skyTextureId; + uint32_t monkeyId = modelManager->LoadModelAsync(basePath + "Monkey/monkey.obj"); CreatePreviewObject("Center_Monkey", monkeyId, glm::vec3(0.0f, 0.5f, 0.0f), glm::vec3(1.5f)); - // 3. 8 primitive shapes in a circle - std::vector> previewShapes = { - {"Preview_Sphere", modelManager->GetResourceIndex(MeshSourceNames::Sphere)}, - {"Preview_Cube", modelManager->GetResourceIndex(MeshSourceNames::Cube)}, - {"Preview_Cylinder", modelManager->GetResourceIndex(MeshSourceNames::Cylinder)}, - {"Preview_Torus", modelManager->GetResourceIndex(MeshSourceNames::Torus)}, - {"Preview_Cone", modelManager->GetResourceIndex(MeshSourceNames::Cone)}, - {"Preview_Capsule", modelManager->GetResourceIndex(MeshSourceNames::Capsule)}, - {"Preview_Hemisphere", modelManager->GetResourceIndex(MeshSourceNames::Hemisphere)}, - {"Preview_IcoSphere", modelManager->GetResourceIndex(MeshSourceNames::IcoSphere)} - }; - - float radius = 8.0f; - for (size_t i = 0; i < previewShapes.size(); ++i) - { - float angle = (360.0f / previewShapes.size()) * i; - float rad = glm::radians(angle); - - float x = glm::cos(rad) * radius; - float z = glm::sin(rad) * radius; - float y = 0.5f; - - CreatePreviewObject(previewShapes[i].first, previewShapes[i].second, glm::vec3(x, y, z)); - } + uint32_t sphereId = modelManager->GetResourceIndex(MeshSourceNames::Sphere); + CreatePreviewObject("Preview_Sphere", sphereId, glm::vec3(-4.0f, 0.5f, 0.0f)); + + uint32_t cubeId = modelManager->GetResourceIndex(MeshSourceNames::Cube); + CreatePreviewObject("Preview_Cube", cubeId, glm::vec3(4.0f, 0.5f, 0.0f)); return true; } diff --git a/SynapseEngine/Engine/Scene/Source/Procedural/ModelPreviewSceneSource.cpp b/SynapseEngine/Engine/Scene/Source/Procedural/ModelPreviewSceneSource.cpp new file mode 100644 index 00000000..657a4e38 --- /dev/null +++ b/SynapseEngine/Engine/Scene/Source/Procedural/ModelPreviewSceneSource.cpp @@ -0,0 +1,139 @@ +#include "ModelPreviewSceneSource.h" +#include "Engine/Scene/Scene.h" +#include "Engine/Scene/Insiders/SceneInsider.h" +#include "Engine/ServiceLocator.h" +#include "Engine/Component/Core/TransformComponent.h" +#include "Engine/Component/Core/CameraComponent.h" +#include "Engine/Component/Core/TagComponent.h" +#include "Engine/Component/Rendering/ModelComponent.h" +#include "Engine/Mesh/Factory/MeshFactory.h" +#include "Engine/Mesh/ModelManager.h" +#include "Engine/Component/Rendering/MaterialOverrideComponent.h" +#include "Engine/Material/MaterialManager.h" +#include "Engine/Component/Light/Point/PointLightComponent.h" +#include "Engine/Component/Light/Direction/DirectionLightComponent.h" +#include "Engine/Image/ImageManager.h" +#include "Engine/Mesh/MeshSourceNames.h" +#include "Engine/Logger/SynLog.h" +#include "Engine/Utils/PathUtils.h" + +#include +#include + +namespace Syn +{ + bool ModelPreviewSceneSource::Populate(Scene& scene) + { + Registry& registry = SceneInsider::GetRegistry(scene, SceneInsider::GetKey()); + EntityID& sceneCam = SceneInsider::GetSceneCameraEntity(scene, SceneInsider::GetKey()); + HierarchyManager* hm = scene.GetHierarchyManager(); + + auto modelManager = ServiceLocator::GetModelManager(); + auto materialManager = ServiceLocator::GetMaterialManager(); + + Syn::Info("Populating Model Preview Scene..."); + + // Root entities + EntityID rootCameras = scene.CreateEntity(); + registry.AddComponent(rootCameras); + registry.GetComponent(rootCameras).name = "Cameras"; + registry.GetComponent(rootCameras).tag = "Root"; + registry.AddComponent(rootCameras); + registry.GetPool()->SetCategory(rootCameras, StorageCategory::Static); + + EntityID rootEnvironment = scene.CreateEntity(); + registry.AddComponent(rootEnvironment); + registry.GetComponent(rootEnvironment).name = "Preview Objects"; + registry.GetComponent(rootEnvironment).tag = "Root"; + registry.AddComponent(rootEnvironment); + registry.GetPool()->SetCategory(rootEnvironment, StorageCategory::Static); + + EntityID rootLights = scene.CreateEntity(); + registry.AddComponent(rootLights); + registry.GetComponent(rootLights).name = "Studio Lights"; + registry.GetComponent(rootLights).tag = "Root"; + registry.AddComponent(rootLights); + registry.GetPool()->SetCategory(rootLights, StorageCategory::Static); + + // Orbit camera + sceneCam = scene.CreateEntity(); + registry.AddComponent(sceneCam); + registry.GetComponent(sceneCam).name = "Preview Camera"; + registry.GetComponent(sceneCam).tag = "Camera"; + + registry.AddComponent(sceneCam); + auto& camTransform = registry.GetComponent(sceneCam); + camTransform.rotation = glm::vec3(-25.0f, 45.0f, 0.0f); + + registry.AddComponent(sceneCam); + auto& camComp = registry.GetComponent(sceneCam); + camComp.useOrbit = true; + camComp.target = glm::vec3(0.0f, 0.5f, 0.0f); + camComp.distance = 12.0f; + camComp.speed = 20.0f; + + registry.GetPool()->SetCategory(sceneCam, StorageCategory::Stream); + registry.GetPool()->SetCategory(sceneCam, StorageCategory::Stream); + hm->AttachChild(rootCameras, sceneCam); + + // 1. Key light + EntityID keyLight = scene.CreateEntity(); + registry.AddComponent(keyLight); + registry.GetComponent(keyLight).name = "Key Light"; + + registry.AddComponent(keyLight); + registry.GetComponent(keyLight).rotation = glm::vec3(-45.0f, -45.0f, 0.0f); + + registry.AddComponent(keyLight); + auto& dirKey = registry.GetComponent(keyLight); + dirKey.color = glm::vec3(1.0f, 0.95f, 0.9f); + dirKey.strength = 3.0f; + dirKey.useShadow = true; + + registry.GetPool()->SetBit(keyLight); + hm->AttachChild(rootLights, keyLight); + + // 2. Fill light + EntityID fillLight = scene.CreateEntity(); + registry.AddComponent(fillLight); + registry.GetComponent(fillLight).name = "Fill Light"; + + registry.AddComponent(fillLight); + registry.GetComponent(fillLight).rotation = glm::vec3(30.0f, 135.0f, 0.0f); + + registry.AddComponent(fillLight); + auto& dirFill = registry.GetComponent(fillLight); + dirFill.color = glm::vec3(0.8f, 0.9f, 1.0f); + dirFill.strength = 1.0f; + dirFill.useShadow = false; + + hm->AttachChild(rootLights, fillLight); + + EntityID previewModel = scene.CreateEntity(); + registry.AddComponent(previewModel); + registry.GetComponent(previewModel).name = "Preview_Model"; + registry.GetComponent(previewModel).tag = "Preview"; + registry.GetPool()->SetCategory(previewModel, StorageCategory::Static); + + registry.AddComponent(previewModel); + registry.GetComponent(previewModel).translation = glm::vec3(0.0f, 0.0f, 0.0f); + registry.GetComponent(previewModel).scale = glm::vec3(1.0f); + registry.GetPool()->SetCategory(previewModel, StorageCategory::Static); + + registry.AddComponent(previewModel); + registry.GetComponent(previewModel).modelIndex = 0xFFFFFFFF; + registry.GetPool()->SetCategory(previewModel, StorageCategory::Static); + + registry.AddComponent(previewModel); + registry.GetPool()->SetCategory(previewModel, StorageCategory::Static); + + hm->AttachChild(rootEnvironment, previewModel); + + std::string basePath = "C:/Users/User/Desktop/Models/"; + auto skyTextureId = ServiceLocator::GetImageManager()->LoadImageSync(basePath + "ModelPreview.hdr"); + scene.GetSettings()->environment.skyTextureId = skyTextureId; + scene.GetSettings()->debug.enableInfiniteGrid = true; + + return true; + } +} \ No newline at end of file diff --git a/SynapseEngine/Engine/Scene/Source/Procedural/ModelPreviewSceneSource.h b/SynapseEngine/Engine/Scene/Source/Procedural/ModelPreviewSceneSource.h new file mode 100644 index 00000000..aa1d96c1 --- /dev/null +++ b/SynapseEngine/Engine/Scene/Source/Procedural/ModelPreviewSceneSource.h @@ -0,0 +1,13 @@ +#pragma once +#include "Engine/SynApi.h" +#include "Engine/Scene/Source/ISceneSource.h" + +namespace Syn +{ + class SYN_API ModelPreviewSceneSource : public ISceneSource + { + public: + ModelPreviewSceneSource() = default; + virtual bool Populate(Scene& scene) override; + }; +} \ No newline at end of file diff --git a/SynapseEngine/Engine/Scene/Source/Procedural/TestSceneSource.cpp b/SynapseEngine/Engine/Scene/Source/Procedural/TestSceneSource.cpp index 5dc02b38..5adb1e9b 100644 --- a/SynapseEngine/Engine/Scene/Source/Procedural/TestSceneSource.cpp +++ b/SynapseEngine/Engine/Scene/Source/Procedural/TestSceneSource.cpp @@ -102,7 +102,7 @@ namespace Syn modelManager->GetResourceIndex(MeshSourceNames::Torus) }; - auto skyTextureId = ServiceLocator::GetImageManager()->LoadImageSync(basePath + "Sky.png"); + auto skyTextureId = ServiceLocator::GetImageManager()->LoadImageSync(basePath + "MainScene.hdr"); scene.GetSettings()->environment.skyTextureId = skyTextureId; EntityID rootCameras = scene.CreateEntity(); diff --git a/SynapseEngine/Engine/System/Rendering/MaterialSystem.cpp b/SynapseEngine/Engine/System/Rendering/MaterialSystem.cpp index 56a263ce..7d45c9a8 100644 --- a/SynapseEngine/Engine/System/Rendering/MaterialSystem.cpp +++ b/SynapseEngine/Engine/System/Rendering/MaterialSystem.cpp @@ -68,6 +68,8 @@ namespace Syn if (isShared) return; auto& comp = pool->Get(entity); + if (comp.modelIndex == UINT32_MAX) return; + const auto& snapshot = modelSnapshots[comp.modelIndex]; if (snapshot.state == ResourceState::Ready && snapshot.resource) { totalExactMaterials += static_cast(snapshot.resource->cpuData.meshMaterialIndices.size()); From cea58d0e3f8f6ffb4859cbe50e4dbfbd6a0cac64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20P=C3=A9ter?= Date: Wed, 8 Jul 2026 10:15:17 +0200 Subject: [PATCH 31/51] Assets download refactor --- .../Font Awesome 5 Brands-Regular-400.otf | Bin 476372 -> 0 bytes .../Fonts/Font Awesome 5 Free-Regular-400.otf | Bin 97112 -> 0 bytes .../Fonts/Font Awesome 5 Free-Solid-900.otf | Bin 591768 -> 0 bytes SynapseEngine/Assets/Editor/Icons/code.png | 3 - SynapseEngine/Assets/Editor/Icons/folder.png | 3 - SynapseEngine/Assets/Editor/Icons/mp3.png | 3 - SynapseEngine/Assets/Editor/Icons/obj.png | 3 - SynapseEngine/Assets/Editor/Icons/png.png | 3 - SynapseEngine/Assets/Editor/Icons/txt.png | 3 - SynapseEngine/Assets/Engine/CameraIcon.png | 3 - .../Assets/Engine/DirectionLightIcon.png | 3 - .../Assets/Engine/PointLightIcon.png | 3 - SynapseEngine/Assets/Engine/SpotLightIcon.png | 3 - SynapseEngine/Assets/dark_icon.svg | 6 - SynapseEngine/Assets/dark_icon_pink.svg | 6 - SynapseEngine/Assets/light_icon.svg | 6 - SynapseEngine/Assets/light_icon_pink.svg | 6 - SynapseEngine/Engine/Engine.cpp | 1 - .../Passes/Billboard/CameraBillboardPass.cpp | 2 +- .../Billboard/DirectionLightBillboardPass.cpp | 2 +- .../Billboard/PointLightBillboardPass.cpp | 2 +- .../Billboard/SpotLightBillboardPass.cpp | 2 +- .../Procedural/MaterialPreviewSceneSource.cpp | 6 +- .../Procedural/ModelPreviewSceneSource.cpp | 3 +- .../Source/Procedural/NatureSceneSource.cpp | 211 ------------------ .../Source/Procedural/NatureSceneSource.h | 13 -- .../Source/Procedural/TestSceneSource.cpp | 73 +----- .../Source/Procedural/nature_config.json | 18 -- .../Scene/Source/Procedural/test_config.json | 5 - SynapseEngine/xmake.lua | 24 ++ 30 files changed, 43 insertions(+), 373 deletions(-) delete mode 100644 SynapseEngine/Assets/Editor/Fonts/Font Awesome 5 Brands-Regular-400.otf delete mode 100644 SynapseEngine/Assets/Editor/Fonts/Font Awesome 5 Free-Regular-400.otf delete mode 100644 SynapseEngine/Assets/Editor/Fonts/Font Awesome 5 Free-Solid-900.otf delete mode 100644 SynapseEngine/Assets/Editor/Icons/code.png delete mode 100644 SynapseEngine/Assets/Editor/Icons/folder.png delete mode 100644 SynapseEngine/Assets/Editor/Icons/mp3.png delete mode 100644 SynapseEngine/Assets/Editor/Icons/obj.png delete mode 100644 SynapseEngine/Assets/Editor/Icons/png.png delete mode 100644 SynapseEngine/Assets/Editor/Icons/txt.png delete mode 100644 SynapseEngine/Assets/Engine/CameraIcon.png delete mode 100644 SynapseEngine/Assets/Engine/DirectionLightIcon.png delete mode 100644 SynapseEngine/Assets/Engine/PointLightIcon.png delete mode 100644 SynapseEngine/Assets/Engine/SpotLightIcon.png delete mode 100644 SynapseEngine/Assets/dark_icon.svg delete mode 100644 SynapseEngine/Assets/dark_icon_pink.svg delete mode 100644 SynapseEngine/Assets/light_icon.svg delete mode 100644 SynapseEngine/Assets/light_icon_pink.svg delete mode 100644 SynapseEngine/Engine/Scene/Source/Procedural/NatureSceneSource.cpp delete mode 100644 SynapseEngine/Engine/Scene/Source/Procedural/NatureSceneSource.h delete mode 100644 SynapseEngine/Engine/Scene/Source/Procedural/nature_config.json diff --git a/SynapseEngine/Assets/Editor/Fonts/Font Awesome 5 Brands-Regular-400.otf b/SynapseEngine/Assets/Editor/Fonts/Font Awesome 5 Brands-Regular-400.otf deleted file mode 100644 index 1840b935e861011c24f6cf0f2a530e91714ef661..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 476372 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ƃ{DX>lI~XUeIaEvDUp&g6%6? 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 M5Jlyu76w%_>0^@J-BQ=KMPioq6O@1sG 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 diff --git a/SynapseEngine/Assets/Editor/Fonts/Font Awesome 5 Free-Solid-900.otf b/SynapseEngine/Assets/Editor/Fonts/Font Awesome 5 Free-Solid-900.otf deleted file mode 100644 index fb8c079bb1ffc9527d1b7dc6ea4ad2fe39e9c44d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 591768 zcmeFZcYG5^*C;$YGb_oq8rZUNuLKB)4xt890z3&d^o}gamar@d6&n)>J&-Jf(0gd6 z_uh#iA%so>1PCQ0Ku9QwZEUX-L3GcoWN_a1d4Bia@4Nqe-|v!m_ss0nGpEmtdiL$x z6RFTdL{P7eojWHjy7ob5guMJLgeERLUCO>z0;WvN7Ipf>H|op zb?Mc!k8O@;Hd2&tg>+JvKK(j`U(UINVm6rG?0m8SzTJku<`&4P_ICI*C?5goG)s z=uX2AE8oQbz+b3Y%oRe00tG%pCHTM44|$r!ewe9dG0Um?kJdw|7h+FDxe_uJ@lc~1 zmkH@E!FL&qV=96sc~Z6JYq(eDJ0>&{B~|#PzUp)5QD68f^~AN})}bW$FBNj)xc;yD zk3r3YFbC~V3&PL|+7g5b)OsF-DT?O024Nn(!R-vfG8BpVARLP7;oKk`hNAG5Ago2< zBs>ULgGn?A!qrhU$qB+bD6=C7M?#rPKt&u4fiX9QzW~gk#;A7?#wZl648jDZqg_Fm zqQV{8-%s!4g4esS3{wsRuHa^8k3emScl#q z(}Qp%l=%{ML=NOc`N)N=C<|GT2e4Kf8Bra0cZP3vDW^TkLuTZLbPl{xM~5@tWzDj9 zl4=|4Bz1P!JxT5J%x*^xeEUz8l29|LNf$I=2B;4-W`j;mP^C%(NzIZvyUgaKJ`S4| zK2<3d%=zDMUk^RkM~$Q>EXRNBP2a43qh|G4iT^O~|7TNZf|@|*^`ZO5Kn)DL|GE8K zK#dFVY?VgP3QAWUdL4wCG-*`7S>vWjwf`}TT(iqnreSsd>Qn9tqK(%08NwEG7crz+-0^~$qSVRLfngnAuN~2DK?`Ell z0jS3WUp$b{7CRftcmNZquf?2{XU?eQPRapEmE?3dy*7g@$!c`iliX%clEH3D@;Dr} zY^w)KGotiBS8mh_HGuyzrEWaXZB?SIFLj>-#XTOUyH$e*nJ_CufGX=79q^$NCrRj^ zSo!C&{yX~kKSPKAuH{NP|35o6N#-!Q12Qupl|oAy*Gp*{dIO~KZV>Sbv>R5ZK5B^u zqp|n+d+qP#-utKs7x6{vqVOVJQFKvUQ9{w%MV*Rz6b&gFUSuyCS2VHcqoR+CW);mT zT2QpO=<}l0Mca#Z7hNp+w&-rry`uX?Pl`&5{w@wHRu@+*jw?6Ox3r9YP5Fa4z~q-;*vin29j8_IT-?JYZ4cC74F*}1Zd zW#5%uFZ-eFcG-_*Kb1WwdsOzM?Dw*l<+NN|9$p?@o>-n--mv_w@;2q|%R7~KDSx-T zXL;}Pe&qwphnHuRXO?G|=af6jJ>|LO`Q_uvCzMYu|G0c%`LgoW?8IUhlnFYtC%gi#qr`KajG~&oGH#17mCZpRpJ_Py|_i(F76i(i^s%5 z@uYZ4JS$!lFN@!aKZw7GPsKmP60uAaMPG%yLRF!ys9q6SQKRC`iUt*LSG-%%t)f>& zp9*6|PDOsj_=?FD(<|mwEUGwBalYbah2QT78csrb)DW21Akplgd((= zB$`bwYG2f~s29-e$Ra1u?35tQ&I6iVUbL!cZPCu6!lKJX-viD5T=b}@sHmbC6|0K1 z#WjFtlZw-d8x}Vyeyg}`aXQfKyT#p$dle5U9$7rOcv|tCAk8i>-U2kcyZB&nVetit zW^WhYD}D?#TTy~aXh~>ER7p%pa!Het7A5H=-Aj4{&5ka~muU7giDp-ntSZ@1vM)%p zCrVD2TrRowie`&T$|Rb_&qE}dee3zaAk9t!nmztMrP(Gxvn~Gv%?<^cH2}?8N}WKn zd8Ok^CzVbGnw?epS?R(c%^oa0R$2%&djV+n`_j9mKbQVe`d68*Y+l*QvVyYBWqZo@ zmmMzqqU?0pg|f>*n!Q_vW?%e+X6pmZw*Cjr_5qq51~h9bx4xp;G36fs%}y$x4m7*C zd{vNU_XlbA%YV@9o${Y0ntfVc#AvqMFNOilMg(cLwpd?m{STTQEDo1w)+LS;CyGXtqGyByJP;iHCq@zW|#3N<1T;muU97_@D~S{#C&P&1!&VbwIOq{teB3 zP%)`uszkGel{D-B$$!m%)qlZ%I;m|EPJEJhM{Cj=wHaE2cBFQMc9?dkcCdD!wx71I z_I+({Z7*$i?R(mPX*+8>YCCAtwQaR+w5_$Rw0dnlZEbBW?Hk%uZK5_#8>@}cMr*^h z)wR{MTCGN_)~d8&+E8tXR;J~(R7 z&0ftO&2G&O&3er`O@U^OW|d}vX1->wW|n5A<|EA%%_NOmI(H^^*Qxf^*8F% z>P_nDYKPjc&Q@F0W_5;ofV!W$xB6Z6+v+yz*6L>Jdg@qpj9RGQ#Daz zm0$Hz^+NTRs$BJ4RiY|Z{jPeZdZK!)`c?H%^+0uBbx-x9>aOaJ>bmNT>MK>D>bUBd z>Zoe3YNu+uYO89qYNKj_>J!z+su`;3s%fgJswt|;s!6JestKwORby3SRC%ggl~?6f zjaJ!JHq{8#AXN`lH&qu^2UUAjGgSjsJymVh8>%!_iYh@BuZmN}s3KM2s_H7ON~2P% zLRBFug-WjCRaAwQ<;pT;vGS?%iSk$FJ>?zcE#(i&8_J8y3(E7#bIP;IGs>@(Un);1 z3zf%|N0fV&dz8DB87Q<im3n18WgTU0Wi90!$`oa^GD2Bh zsa2|#DrJ}whrJAY5#|f~Gpr))*RTg+--n$GI~#U7>{QtPux(*$!d8cU9=0NEe%S1= ztgvBWy~FASJsA+S|M%bj#lZhZFd)OBY@}f1GCTMG&ApB z!T){})kXEd(y0$FPebq^8-pL&6#T*F=q=O&Z1z^DHEM&}qPI~yh#<7DTK)guFs=JP zonJ80f>No}`;1paB zH^=SqySNt~fCuB@I0KKu4(!GGcs!nhKgEmj8oV7J!e8UF_*;A(-^M@TC%71w;XjFt zgp*j3NYY4c(txxkT}U@FkeG>+j3YD1e6p5oCkM$9a*A9gH^|TA5qU<+sGNpT4GpJ} zG?6x;EodkDF6~VRPy@}O9y*Cmr*r8tx`A${+v$G#1^tFzq*v)pdY3+?MbtIlOM^u_(}X+ei6TdU&C+YckxH~LjEiM41baTmcPc|<$vaX<)8A;`M+dx znO3Hg#mEw6b!1Ist!3}XddUXJhR95^Y?)h@CmSc5D4Q;uEt@Y}AuEvWkR6a6k)4p8 zk$oq-A-gYoB>O}5mpn`!FRv$WEAJwIUp`oFl4r|D%k$*p<ZDcts+*Drg&4)T#>Hmr5LENDjbT*iiL^|io=RR#aYF7 zirb1`6h#VOh&&`bBtE20NW+l!A-zH}Lb5}=ArnJB37H$RJft9GW5~{sFG9|T+zxph z;tLH84G&ESO$)6ZS~s*oX!FpPq3?$F4ILhu73v9{5;`k%S?Jo(^`YBC_k`{bJskQ) z=+~iVLNA1V8+tSJr_jftrC}sY6&4+q8m13x9@Z}Gy|DhE`AuOtVYy)+g?$qCS=cH038? z!z=)MW(Qa}Un?(z4fHEmMLw{Q!c-ApLnVVXRZrCdtgOK*E7)FBRDx=eY8}{X`&1`Y z--0#wR8^+>8*D(GI!XPey16=C-9g<`Jq#?zeDx&t4D}rKeDzB87WIDhN%gnt8|q)x z&(wcwLN(Pi^)x-fJ{_qU1y<`suwWN!)@rtZeR~qD-=`WsSjdrJFDGjoYTJQ*-5>03 zo7STpr=6yqrCq39qur+6r#+}WuKh-PPWzqqp|+x$vRZt#`qkd5*0Ea8YNl%W)n->) zQ*BSRbJea_`>ooa)m~H&tsYrDvAVu`>+0`R?^Atv^~~zo)xFgxRG(FSQT3J8w^Tn+ z{Y3SP)$dgQt@`iP{|e{BHQ}-0HNzW*w+inN-ZOk)xG~%oo)n3i^Iz!NQ5RLF`{-v(}<1{JtBrg7$Pzw@*_TrSQ@b*Vtd5Ch@%mw zBhE!!j<^}|W5k1qrx6u8UKgs<>LPSWy2iQ=y1u$Wx=fv0H$nHYZnkc|Zn2FO_4_jqhvRuJNeG?=?zm_@cRJS+pv;dURBDQgprOhSANU+eCMY?iD>SIy2f9 zofkbJdQSAh==IT?qYp%%jJ_0oJNi*{N%Y?_)nZa&8pgDaX&2Kqrcca(nBg&&nEaS& zG4o+b`A_J1W*2J1%xo>_@Sm z#LkOd6T2aHSM0Ia)3H}#@5DZdEsiaZ^~Y)AqTuZ`aleXFn|LhoWa9b6?-K7MK1?i0^d<30iX>H1R8mq> z?W6`tjgwj=bx!J@)Gujdk~t|m$(=MlX-d+Jq*+Nzl2#=ZByCMPlyp4lRMNSmTS-49 zJxeN0M#;)#ZE|dKo#YnD9g_Pc8-O2gM(~>_;UXZ*pd2RBZPMM(kEqLiYaAu%J`J&DN9pUrEE<(ka8mBo0JPFS5kgR zxt~&$QjyA~YEmOp6H;rY)=h1Z`gUsj)b6Q$Q-`J+QnONRsm|0fspC^WPMx2+G<9R@ zfz)HEU!|T-y`1`U>f=-~^<|nYEg~&CEj6uvTI;lKY5mg-X<2Eb)5fLENE6aNOIwn* zDQ$1siL|R}57Yjv8CEl?W|Nw4*X&ZWcg>MC%{52Y98>f2ng?rsTk~%amz=Iaeks-#|6_P6Hqx+ID6-T(1fSVX1{6Y#wDc zvgOEiIRc}ugr%{vWzie0E~CvXElUuVRxSujOBsYmd!b9%3kxQ#_Dn}*DVM_yjAnJY zGYoc#wgV_)RIU%8MFPFRlPrrZ5R`}gurW?xPuN0ox)`yHG1?3{hAMqzSu;I0vmrCn z3T5-z{{fP%TG9i8!JcI^OWXw9Z?>BZE=DOXXxfnjjpqR70q1t}D^z%$m55+NahW}N zX0r#l*Xd+p#0UhQ33LGDEj{%Pr`c|V@v_Bm1!zSNm0XT&b3PESJJ^StafKdeB-3L7 z8RdahgfdP`zSqgf8L;fIOC&2j^zPAKK!F!X-I#3zGPinI&n6e3(qzi;+H4kw%WgCS z|7C(y^8`el(*cAMz^NqW0%=B&Rgzw3l?0n%0PTULuCg@2j4CS>%&A7-3&q5=X$uPPYLB zjWm0}r(5D|m@Z>*hs_P^nGLeU#U!v7xW)x-0pn%>`V3jXmM*U`8xU#E*JoI5HozDN zIyS%n1S>1$FpLBsX?8{?BkXRs14I}61t^dKURc^(vkg`l`f_B!z-%A_JZ`Ipu_&yf z%WMN#4vc69hPQZNT{74V^*JWU@>m?O_)fFiYlYt2<_suo1VLmr!v`mmBZeHPVSr@> z-2{8CnXC<{J<9CKFq>VLAQ4EM7{G``%P>AKOgOMqjJPW^0!vjj2bQX8Qd(^(LlROF zRHf4E(s_VdcQ%NSfTofV?8$KePR*me4o{%(03_9i>DcXNSAZhiIZ!MUR@|Orwq$|u zbQ(;+3mFb3v_LrPCAnb+qK3(WY+*7URuXtLA4tRo6d&MniE50%BkZ$2131JY>6`-Pj=Dn68RrrKW!Ql#UHDd?H$ZjUPfWVz@s~c2G zC6o)S{)zw$`9WbC5XAvv2#DVR41^jiEQ!t_%w#9%=>Vw*3RdBUU`8cV1e4NS%wCrR zY@IB-(*cw(K_&-~I@k-4=>mL%tOY2abt~mCf@Dx0r~vi`31DV_Y?cyJ8EhbTUb9vp z>2(IaVEk;xb5603*=|ld^a{pk^usK@hWEJG2OgKRo$a zprgG(wPavwCd&&NJku~H!)uicPw3ocavJiC-V8JQbGyvOe9)>e7>{9$)dR}QWB?8W zdX*67G=W9~#N|1FT^InAZngzJG1EvA>QYj&6(lG)#Q^LzSsfB2859!4ILKzrG=pxi zX9ele?f}XT7=e;@v@mHHnNDWQ8AL&bBPS!%%WNW#CqKh# zcNp1W60lpnpiIGjW#(m`q@8T+3$)DyV1VO5{Q`f1!Zra*LOG{_DXt7=BxhJ%9*c<) zJQ(8vy$byr0uWVCZusS92?KXqT1$hT!mf8cg11132+GZdVsGMOofHe%ZDi~>QAWoy#mCq~+ zcNW+epp##N3|~^opp0e}f?yz9YJp`!A7C*^-vgjNw`#&-H+w-`208%J27|~26etas zDF_LWjI#`otKb>gK@{d0@-qxzpt|z)nILH@VbGtzzf8r#C$P!_C75Ya2?xJbCe1lc zPd@M@xGxg*0Xynp&cM5l3}!^?vp^v`gE06rECtq}F*{!mRK{#~I~duLrqhEovVj8h zx&kf)vx-1bRwmgXUZ((?uM(2c!US@_dSTQhL2e_sdJY#*CkQH=bu1{EK)&-;S!NXl zOFMy*%%0pzi65kbpjjvh*H@n;WWGvC!WQgwu*O~a%svN=$6kz{fDAxKNtPsAZ^`}+ ziWO#tSAvxmWCl1HuVFW{XMh{P^#}?TgH3NR8exeufXYn<#?E?B$C7>nS`OHU0T%|8 zBA7X5knzC0Adw^w%go4K@6HDkB*)Es7cdY5LK$4KfOyhN7y-MQ={ghG&}!wM4V$ve@Md2;piFbXi-Tp5 zD6wk#pVV0OOTbuuT{B3PRf|ZJ3D^X=2*he}I!s_`dGY{nFb6oUYt6BaHCHB?tNJ<# zQ(!K#WDJ0ecDn73JTOnP8K2qRRuC0VP#(bqs1lQdEv_WF7{J_4>0Rk_ODF_~PReBH zwAvZSTvYak#w2;|X6^#C&a%vQrndnC2FcO~1QDbpP4{22n6~;aS(2vyFL|JuC9f&a zm}GJ@n_9BHUR&YdhP<-K^|`P6t!k-Az8D)!Fp~}7zm=4R@ZWOTAYi4zumgIuXR`_Z zGb3nfNckYo?OBpukz9h{+#$9CUjsP-2Z^P@Gq#z*2$u>;c#~3IrvwAPOU@W$OAwvl zJ+tg8#vV&aHmsEY%1dCWpwq>|K2EUwfvsTSz#t7qS)fF~24-c!bz>0;M$0S(Vh(fz zKoa@GJ4kMl24WVN#4C_rxiVn(F>e4&UEmQWm6-^5opj^@(z2vaAauZbV-Ij=n9K;m z%#sd(UdgTQj_KGqC9>MD*^Y%&dwK9?rbXpu?s*6|H$#W0~)fj1Vju2+ELP> zk`Kb>0O1%QbdY~A`7y!JB*=1CzSEkO1^%_cp6#+`WH{`g?E!;e#WK1L=tIeV1=ja~ znwETWeIR6)6-eYba-pbXo(3iZEMwM71_2AR>4V3Ip;DGZ(ifGi6(}7@F)PV#@CMip zNZgn$8@le2^ff zt=UJ9RcZkuWsWT12Ik8LEF8%@|7VUQEWuA=CL&Xv()wnwm?qO%dXJ>UfJ<{gB-$VZ zDKU8^1bhdp0j8$K3x7s1X&5NbLFo5%j_5aVW)Y}RLjIS>HAxRL-6c+%T# z9V5LZ#T$U~943bYbhl(QOCX~?@b!T@Gr`z}j=`bN$}+e?I^;N|m4YBJ*yT(LvglG( zST9usR~8PIJD~u$>!5l)`PtSyD+n?d1iL^Uv0ATyK$RQ_f`N7Q%28*`6dhpZR=X z;2S(Zlm-^(WiepMXU#B=wZgK1g$`aXSQY`l3c|V23z&j-11J*-Q9)0{2pr=9LlUk5 z7@;C)<3Oki#FyC!gldHh2zjya32VY+&0@hTy`%>LVNQ$5I(BTn31Z~ToMeb%Q3pU& z4*LVo23%_hLXX*C530fwBYH0T?F0q7qV8 zRpBgUFi~XifLNAN0V@JP=1PMF0ns9$$}I3b+1g}+dd&nk$^xo|v5~}^|JdYU!==O#}qL{;Da`s={3YK9{8pIHQp;$l&qWC~F z5TSyAD^t}{M1d7!(FHfy@-Aj?>oY+_=QF@=&43rP#3crnLLkg)w&p@;3c7*V1d|ku zeyUQI8!S_!E1$ijg$P84z=#HyAxLgg{8O5@+rdI+K>B7dCkzl8VIdbVETrfLu)iS( z+~nY{0Gn-Pq_}c=ZZD%wuz{paJ*G3Ne=`tO&~U;XqXh6 zX2URAa=}Lj@gA_{0!9Y70QM|s0Tv>tdD#FU{H4zXJH`US9~fKOjf7keh(6}RGo=MW zis>&FVSt5WmDnp7DPW+H#da9L_DWf_hIIo%K#Gx>V1z~%kpb~w$b;eA^0OTF;C`qS zr84A$=#nA`($1))45TnR>{^Hf3L3&kfEA{qV4KniiVGwfBe0;f z38q*ahY24Liy-V^p1}6tD!UA?LWN9+0+oyj?icAL>ms;o#9}s?UI>~qXNEPC1(&fv z`B*>&Kr9dfk)uFcj{@Dz+^9T=A==#82KXYyKbWNfoZ^B_O27(4k%1}f4wu0K_qxF3 zmjtlKkZEAMUsiXH6!ie22D1S01%XA$PK9m(|2Dma5hVL#>%=TYXo5wsfRSM*IWW3l zqB1t`294nkkQ4JO0zNOJWM=+@?sGHEDv>tyXEK1L2kx>V3k)(d5Td~X535z51#6UR z1sk zJZ?@F{ z#{)e2yg+ct%JeX^^Bo{b-2uZL=E(MKEw21ryA?n;Y(haq8+ZhErvTsCP?-e+SfmZY z|57{>=!UH+m;`_^AR)$KAg5T^wdxMNq#|L@fLWVhlY%%h~h?_8j-;fItY1oj{1NNk%CNQypkv-CWjh97{JZ6UrOeQzP z_hB_ys0DTiU>$Q<+44bd^4CWfRGGB z26&PgEaD5|7toktbyy56ke2}tz^e!x#DrhLup^rdJ(*Y?JBy^dK(d0^0qK+l>j;+J zE5G|ypaXngkhW$BQw82&A+xA1n6|J+Y%e)@r~xb^m}Wrt1K)vjo*&TeY*WO^)UA_+ zHJJHZ3B6LHux|uhAgMrzWtw0p^951jQ zn49oTl9R6hCSZY|m{kaZ0d%hoEU{Mrv;8gZfG_K|=161#VuG1rpqpPkAqvY}0Jn|# zm+l-l3w24{U+Xei*Z%|669TH7DD9 zk&J%Hh-P|&$uuJ)g#6c#q_m|-Kj>+gKMdZU&-T#`VB!3)U4s74m(yM6;^}YB<{e?+*4?J<9*TdVIeFT)*#x-hrFRT~Jr_FE|GN9_og=qaLUy{Hj84 z^gi6u?u+`N{%8Oihz7wm-67m`ZU*-;_X#I(Gr3vZr`&8f1U`qG%gy8F!(s4++#+r< zw}e{?$HJF$E4a_Om2fso7)4& z$M?Yj@&nvK?htpFJHj1>!{o=gFStVP1RN^=lKYA~#eEHj%fI2yaA&!5+c^_4>+V=%$0D@;i!5USI&uC1sqxbi}P_W;9|vk6vn;e{^tA`VGd(VFonbH zGAzdm90G^h!>|&o;Ap!BYjHI=;2w@6untG!C|m~MVK?@`LHt~thsWT2l!V9P5AZlR zmj5B1fG6TfcrqN&pNgm9kMMLn1AmM^!2+I%XThQU+4wU&2hYXx@O-=gFT{)B82=Ky z6feWe@e2GoUWr%X)o`$XEiS<8@Or!fZ^WDMX1oP&#oOSx{|>wp?}BULd+=Vo5AVkZ z@Ikl&a2OxKNAWRy9Djie@dq z@Kt;be-9T2Zs42v2Yd@I5ZpnVQ8K=Zf5i9TGQrRIK7N27;$QG1{44$qKZffCPw_MS zJN^R~!9{}-{2Z6Ub%Sy&;tKpH{tNr?3;YuQjr{}>j$lFvB|Kb4kP`(7A)zFUD2a-w ziH2xNHBuceDMSz*i6l{^28kvyXbD;e*B0VPJV}7d3rQrIq>xmXvGL2AKOhB`<_ z-XwKNJ)$S|;bKEW(ug!BO-NJHj5H^2krt#SX$99E+K{&7ZPJdUllG(o=}0<}cSvWr z1ksiJi@ZzTgUb-zNe|MK^dh~<`=k%)OZt)iWB^=|7(@n>A!H~SMuw9SWF#?=3}PfE zxH^$ZvWSIP$taRdY$S)+i36@vj3zGPCLZD?xg?K_A^Bu1`2a3ij3*zG31lLfL?)9d zWGb0PJ|ffM>cz+86C#kAWET09%qE|aIb<%G2NyCHkcDIsSxlCYrDPddPF9f5$x5<{ ztR`#VvPJ<}N7j=KWFy%`Hj^!6E7=AYIChYoWEa^@_K>|~AK6b1z?F_e~Yc|abLU*M9+ujDuKm^>j*$uqQ){7(KLMWmRNkmsb7l#y~G zk_z%C`HT3-3-XfuP5cyoZ-!z@D5X4=!37fq4WXfM%|uC6R1KF+w6q$n4p&Yhs1B~3 zM9~^Fn#Ryr8b{-40$f8$qRBLcrqVQ8lfFS~(b}{QeUsLu^{AfKrwwRB+K4uW%PLK2 zGuoWK1(#M@(pI!JZA07Aw`n_?PTSKCv?E+)d53nUU1(SOFSyk59_>cE(;l=Z?FAQH z-lu(NU)qoMhpR3F=^#3o4xvNoFgl!$pd;b(O9nMk6E)LJnnf+tN=MObYJ)2=cIu!` zI-0tu8?MKAX)ev9V`x4dOFy9F=y>`eoj@nTwVBCu3Y|))(U0H)%?$c6{e%j1CY?n; zrL*a0bPimqnMdc-1#}@@L>JQ~bSYf2Sx#5b&*@6Kims+>=vrDp*U|NG^=2d81lMo2 z(5-YET*BEwchX&SH{C<`(tU6x=KwuO57EQ)2t7)V(c^GMr;whYC+U~;D|(84O;5v> zoip?-Jx9;e3vhYo61_~nrQgvjaE<30{hnT@H{d$Y5A+tjP4B?9o*(Hw`V;+`-lq@f zL;4GSM1Q5f!DXK(Xexb5pV8lumi_@(e~M`deNIbh87-$Gt)PF>zu-F13;L4&P5nHA zYeASNJmq;_#>;sHToMZ9!+0gH;?=x{*Yef)>U=m{9MbWTd=y`UkLF|eSU!%A=M(ru zK8a7}Q}|RqjjzeS!Pnwz^L6+)`MP{PUeDL(8}JSJMtozqa@3S>#y98R;#=@7`Br>u zz75}&f17UySCQKD9r%uXC;lD2Gv9^p%KwXhmw%7%#&_p?z%`{_d~g1Jz7OA*@5lG& z2k-;=LHuBT2tSk`#t-L5z%?cVpTQexJ-Dw1mk!eqy9U?{!G%8fxfApmLYEO&8*%R= zZV}=hBF>L+7Q*`xzJ=J&`6P9iEtG#ODnqP-E#Lv#kBI}kmO=u5=Efp{C@ zwdZDC7+k z(inwIKq0$P$Xyhgib4mV&^ajdDGEzPVck%e8HLS6VLMRRJ*4E2su*dTqQpa})=*S? z4XPW08Wo_o9-`Loqjm#Px)pT{L!FMHcNU_qZ=rXK(EIgKzu(Zni)cs=8g>#5{|Sve zh%(Yq=22uRLbhz=h(>M=$~}X|n$g7LXv%#wqdS^01bv)^KDma3HE8BCG;-qCrcks=K%2Xx zEx(~{Hni<3+R-2Fu8nq2L3=D{Z#>$!1?^vl_TNVbIdrHOI(z^fO+rUspyTIJ;c8U) z6*_Slo$QHDW}=hR(U-N*mzU62YtbnWIyDu29fnRvqBAk*%q4X84mw+e&Y98qI_P|3 zbp9Q5p+CB)LKlBUmyV#zpQ6hv(B-Y@+n4COQ|M|xbZtGl@g=%>68&%i-MWEp-$!?T zM|Xcj_fDgqx1sxE(EUm1;Rf{ZBziOt{rUv`b{Rb-=#LGks2wUkkBa|9rE#b<4VAY* zVki=CqrcuqFGA3ZYUrgL{e78(dxZE4F6?(s^^jBBIn6g*wL4sRA{SB0MasCSXOX|X-|o$hGLpwJ~sQoLt+PT)PfjdM4N58?Mt3u9J!Dl*@Hm#C4s=y|eigR7#Tt9QJKR8b`=Sk)~cR25A&if;m*P9#Dll#EWjc>(GSjkOt zbCaHNQwDI;268hxav$I2KKYXq9&kblC-}LU54lfsxKFQgbB1zrYjX1&atpJ$MYXs^ z54gpvxFy55WiPqqBe)eNZpA5X#SL!dKyKw%+{&xms#V;YH@Je<+`7@+`j*@VIk(|D zw|NG)wKlhXIJdJUxBD$_&s^?6HSR!7?!X%Ez*+9VbM9aScknuQ=mvNANA8${`{Dzx z(7>Ij#eLbF`%255l5wXuao?nH-%Q}n-r&xcauW3D30!oqX*%bhd6!! zPS}EzB5`t0oYDrT9>r;oaLqFOh6TU*F@DpJ>s`n7n}8jT8|=o7p5SINxMd^UIsv!Y zirXB)ZMWleIc~oacN&X3N8_#q_`SDqw+Fa~6ZiZJ_gaa2zlVEI!0-3QeZIhbf5iQI z;eIaMPr&`w;eKD>em8NyUvd9Kco4#apW)3*-!CjP4Ju^coAHzy^dF;;Z<|++S7RbD7?XeH&ozF zHSwk;c=L6<^&Z|f3vX|Qw?6`F9Pj!C?>&tVPQV9`;zNn}a2!6o9v7a$CqBhr&c>%2 z;8Q*E>ACpKTlj20e9ne1DDkBPe0dZ8ZZf{o0bd=4uPO1h!}xm(z8;Bhj>A8=@GTp@ zZNYb{<2y(2-3OOfdp40U}y4)MAnfgGD*mI z67~yG{zcTkf>BN)a!ABe61ks5o+LHiCb2b1yq?4#A_;3qVoj2Go+LR)iiV{4NNOEY zvpT8WguMALQcq3l-6i$Allm7)!_K6UhBUU3#xqEh??|(B(!2&~ZXhk1l2$RKje)d# zhqR9(9j=m&BIy)MI_HwE1IW8uNVjdI+t;MWY|`^<(q{zerzQQ8NWTYU@N_cxB^h>! zjJQDzi-~y#$$CPp2g#^ABzqpQnMuwrVqZg?4an%b#NCp(JCgAs- zrv6Gk63Fxq$;YdSuz<`AA+x5F+5O39Q_0-TWL^)lU^`j#f-F5wmUkp8PLVa^NkJ$n z*hSV)CmRQm&2`D@;eB$_O};uxPJKzfzC^we$hq0%{BCmo61g~nd^?|9X-}>q za&;&9K9zibhTPadepo{8yiIasr^V!cZ}OlQ`6Z4#nnZrvO`cpJPcM>Z?MT@{ zB2FX~*NN{7^5Qe%S5VZB;ssQ`jfQNcVMD1pjH>-q^AQa{O?7=}thwN$kB82aXNT6YAkH<9XB()#sjBaSwHN}H{w%}umLJKD-X)9=#` zh<5mfb{tPTuBKfzwELU1_YV60P}+y5eZHXmF4BQXbZ{Fw_&FWEfg0YR#&4*Zqgg4` z@`8?PK}Q+rD1q7Y6Qjea zh@ul`(;XFj5{{-&RepmUXU-d(z29bG(*E*U|WenOY; zqn|gVtITwbg%(86b#>_av2`RYyqQ?{I$-n4VCG_hH^o)s~Jwwm;qgR^ItJCTAQS`RxkT^9(ED}h!!Y_N zoc?-={&tZ*xj>&<=(8+ZvX4G5p}xoT~v&|HadRJU^cgA-pP|SGVIMp7K$v z_!bTSy#T*dA{{izHJ5HZWiA@lkeP&@AioA{)q26 zo$s}cf4?E$XBOZ83_mD~AM%VJ`hp++C2wfS8~5<$Q@rIpK6^HAyU#nS@y;>)Xa(<{ z#d~k^c{TYl>-e!T{J5U{gl_!g3H($gKkWy8#ufgPhx}(9`FVN#qR#x{t^Crp{PL;% zie~&O#IL^17o6tTm+_nB{N_FUmT&lNE`Fzi-<8Ynjpq01`2!R9gLU|0JNYka@n4ST zPu=0azRjO`#-AI)U);rCKE!{!j{mNJzjlVdk;LDc&)>bt|FnjGki`G8ntxQrKj!(z zU+_=z`Da7HPDF{GUqR=j44q@Gm;^{-!c6Rz`cs24S#(=jbT?V_5LxUOvbfJ>NvmZk<7BCBSxuSj4VA36Q}$-Mte!(wf0L|nHCeNE zvS!<5&1cJ6o|Cm%E^F6K);?9%=^I(sXxV#BWZl1$^*Arvu&q@P%yf zc-i2WvLW+i!?wzX?~oaq$}%3xj7Vl0D9fBK%W}#_eI;|WkU6`{TyZk@yE1R2EVq{| zH&>RsSe8#@`G3nk@XE$v*|}c=D#OffMg3d`Zfs#;>-fBpg?R`Knfzo4Dq-xLssBh zr-NV1YUrCOSNnca`!0&_idi~eO^aAlrhd`*r!RGd{rkhG+;!q)olkw>y&vx$I45dE z)rj*g-Wu_qPm>^8XX;WNL*DQ0JX%t+{_v?&>xZT#sC|RQ78NaZ^?M4&?+**UO|1pN zB90J*?X3hsyvwV7ZgH46Qn&x`iXFl};cLUruEGdmNWR0VST>5^wqe_n?ZSRxtyeG# zwsCf=S>cQDB@5Z&1Zct6Re<^v;g`J{=!Aow%_A4>Sh9L6wDQ%+9q$T5h2dlEj@PXy z_7`|f!*ksI60KSLWM6LZ4j=CH3`oopvX+e6pcp%f-{d$n_ONhGIJ8?hr%?Os;shAC zRq%E4&4muz2tu2Eg7Cvh=(w-I8zOyLLDY&}U<6s>4zaE7^6q(&|cJ<>H{ zn2_oIz@(Tb|6|A3XA`yxn?BrTS1jAg=WH-6HwcO*13LILKK|%?_b(kgb}`WqImnhh zbjYZ!M-Ok^cI42OQ9}Ufx#E}#ozC~R@6JI%Y$yICZ1GJKge(|ruDDaw>x2W&Z9^9A zTU@YN*sC}-Xm`gBgG{{>gp9Se10N2ZV7Ce*6y1-TZvAk4_n8D=WU+4099w~~Td`+T z!SQ38T*d^!WOEIkrg%_GHzM2pes9~_0|~<3?Q2iWDV(;$Eo1=t#)w-*K_|)voomu{ z(4a0|4iCMMa7fs-X4!7VRQVfOy}Big5^@$>HYz^I<~Q5-eXv(hJow^@s7dVijc&-O zQA38>wj4dWdCQ@rTeAnlgc^w%uo{_yujvc!fUxH`v5&AvzhB_%h%n7&!Y#2U%Nr z8><^JI`{pAWU)q>?927u;jkW^wun=7KKc2M&l8Rb`Vz2GKu`t}R@Xh|S=+=qtclg?i z)y4OA_`EN;eURNo94T6LzxasEG0FasO;9u&R`~nVLxpz|_Y0dRz<8Fk@r+nDP*5bl z+sP;Qg`9s!;tPY%Cm%W?-;yv{m@w;uS+LTL#j8-EpWy4_Ya@tl z41xf4fw?<~y?h-`3cjJz+StW4k95BG#5>Tk=#{DK+=5D$7u;^yC*o>R>07-~fFJi; zEBMy#7K94UBKYEcmtaln-hgu1P)(nc-6KahK|a(19LI|{$LZ{Az3axUzp!@qS>d7Lb>mWp3A2Sc!n~QQGB;&y z^eXCzk-Dl~v95s(8i~8aF1kq@#x2SH^zf2(+l0f43nO;4O<=w56Px@Z+b*n_vM@(+ z(5E^e@V*)1UES}pQ|r9LhdDfb5^FQ^D0rsZYCAOUE8&jt#a`izuy1rh=A!&L?s>Mk zy_Yza3Oj{;+tz(0DE125#)FuErO(+k9G1Sd(0`=RO&D1)ddIjGQ`bz}F!l8K&68)E zX2OBIUV>t1ZL2UAZ+X>GKZlvIg@tx<@JH(R}Ep(%1Ci_mMiKj*i zV#Et>yHF|~f9^ZJ9a2DLLaOg;SiB+PdT1lbS1VbpHAoO^_&WO%eLar}A`ibs%KI7| z6MTtc4>4Mju?DfBI8EnMh+PK@VyZ7k@YQBCR!dA3?L(Lv@QB->HQ&N4(Jb?sea8ix zxQywDQG#d|zkmYM#9VQ;?t?A1&y9kj%doz!TOaIyIdPA$WyR91ilKYOg&A^R1WgK6 z`!Yck_tf1QR@gbgC}fWrH%cM4%y^;Ndqlo$)aEgJ1jXe;CvPQ=jO><^Ie4Ib$Juim zw;w;YF{?Y!*UAF1yRP+-{@*8T7j`UOy-G2D6F)l7J=rBF`rCG#IlFmhVPb)>cJjJh z#gff@?kdY-i=b#XxNmD9mT96_T%#MeSsGyH;r&~;KGgTp>j74%4Dy*iLD$WZHE^JP z=h-tGcN{;yA*&noItPB_vcK+T#*r?a2K8;z;>f_;2|I*cOV&u;yYd{9asR`@`W=NehgCU@P>_riQ>Kt-%h!8{8&5mRm zq!pum+7p5=MU3IqFB;yict^*EAX7K1Xut+%-_|F=BxL7}%lUAdYgvY17VL~PrXMm1 z{4DP*x8PLxy0Z}7}CF0f;cSlU59nRK*zT8XE*LRc5IVG3ZsB_y94c> z?3`c_vUA5-6w{AYY5wS5vB_&H1`P&OY_|-mq~fESG8sKh67$3rx^dgHSHcokVU^7* zmTpn_8toI0%7yha*M7QMVUkVr!4DAu(_9qa6Tj6>oi%0V6d_ZvjrkCIK2oJ;2{pp% zxt}jwx_H@&c?H4(VZq09KL!c3ZS7CG0`Im72SB>*-Y6Utc6e3~S)>@bkax_Put-=X z6s%ghQ&=yoo3u7hv1AkPT|H`1me5l$WedXv>sr_TNs0rL_{~!mPZaV5SMGSTkR>?h zIaWi{FW@(gFY4Ogl4Xn+{xwJtMjZpiQH$@t(S9^xv9M^?!r4IUlV?o+c#@!Sj9s!T zQB1xDBtBIfCAQHGwq*}DjahX#L2P31HI;YWV)-Uvr?7cBkooXEOkAjaOO634jSxia zTd-RYiMSv`XyUW*>L&2ZvlqcyN%xsLh^CRkW8cC(!c}qDHQ%s3!egi_wD*}oCM^=P z*!b$DKOQ6$`Kl%PqK^r;i*CPhOh^`^{}8J|ktJe$k*=v|g(;MbhDXD~syT$~JT`i!H*MlxS>-$21G<_N-8Un@auCidsm zVyW0y=l9<<7RvnoqIc8%NaZZ_`=6#GKMGl$?)N{g4iN0lCRP7Ji;FIh5C;rD+mH7wSJmMX47!_otiiFM_EKg&k)%5O+< zwJ^bthMrBA`BB6oOiedSz1gd!iDf~z&RjIZq-H+7$ zp>#1o{@)qI9t-mTNeG4Q9_L%7RAA|?$vJ0RMyCx?3|1M^ojEq7$Kl;>) zmP*fYJfJ3f;wOHjs0*d#mjDqcwD(2wdf zoYXm+?nf<*g)r5?ljZuqXWv;#|CzIZEeQ6rs%#Vz0s;?6CT@}c`@t3XQMdPDE&QcI zq2K>kTjckb^-A~q|EvaWmZw2d^Z^3UL}MYeKu5tK$O33xdO$K^8T9um+y4Rz_u;w+yMOJ3(r7$n|8)#1qxI14Uvpu_DxZ++_YY#ZP}1+8%z7J?`oG=VgypO^ zHe#fp&?K7$lKl?tvY8?NO9bs^Z%6-ov+#A(C5Jwdp1!Z(<@aB?0i(QK3wn?qQj$GT zYUKx1555K-EKmCII!~$=C=AsSeUzqzs>}VH7EplhESU&wAp^QZ_y|0xAsv$K7=6n- zvL|M%n^4F`E=c`fnF#&!kSqTy9r~}wdKM2r0so&N(BNOa3!#6)3iX3l{V(f7 zsj?R7(7zw2LxZf4pJNc6_zBeVy~FDI#z8gzGAQdCHxZz=kSzTi9)B~oFJq4vry*Gu z&7iA*FaHbpj6%FHUjNHkFdo^j@afMi$fTv|(0zN>Zh1ZStdJq;@haoxV;(BEn4fCchD$a@bstE#K-f8R1QcM1$;1_a5BlwiSv6h*Kc5g!x5hNw}oAc_T} z22C^~@rX5|v0;#Ch{hH~5k(A9(IeJStVA?oK?H?S7?^VJ%q{!<{`R>ufQ-)@pXC34 z-_PeY59?Wb?X_3iYnQXnzGtR@HV%}%wGV?fogc6o90EvnGS+U{1JJkyPTNHPo5V%p z%(&zoh>I?Vsd;xFb(y$>$3n4D(3)2m*qdEAGX+SF z&_^~yG);7fm`V-S6(qe0;o>a#NyosyFwX2OgZ99(k~B6c^(J={a1lF1$i zLwX@JtG-R&=OHzHqhPifsiqUuTwj^dM_5dete*&)xkFy19uMGrmvZx?9{LB%z+_78 z8wUpr3E7g88^vzOMivJeI8RITVSvcJ2JA}3suZJ4Y>dxnOYDKb+PMKk@-f=iJql;} z@1UiINq@EqVtuXj+b2A%`U}9WFkmyKKX;qc-;Y*BX{ae>Pw@(fCb16;rf{9dL~;w% zXoZ+=K&pv;8vMTC7FnmXwu`8U6a|z!ydPj=C7}Ezk9Iu`8@kS5iN}2P@eJ6L8XqP$ z%qK99!WqBG!_FG9$0VcpZ38T7q^hV^0JE85!3YEGIAMngyHA)264U9-Hx$w6NbJegP#3-3;N95) z*fX-Jc&W3jjuu6A&ax$eOzld8G&yYwy9ab^S`AiuSca=u(!(It%<*pl-sq!Jfi&}} zGATgxN{X?O2J9Y+v2FnvgWSa8Dn0^;E26OvRD9V`+yfB1O|*Rg(Ivuu0xfzsAa;dl zlROmB`Nq5kEh0m-oiGuGsxi;07h^TP#+vi_?|9m@n5sg8V$%=9UUayJb6gW5O>8az zjR}}w8f{I24Ld8WSfUrhZEo_x1^|qk;4Vg){bGV&*{kH^n#$fKHuB_fDDTbJ&LjJl zLH>!-$--$0PVq3L9$6^z1+C;5s45dk+sIap1y(G%V`yg=QJFhe*!5(Vt$!{cC+6S_ z`%#bdJC^#o6A)AOGnm4a>NR;CT}^dqxu-rk))0`uXM1{nfmWp4Mr4}du;kp(-qa7&Zh*Vi12lg^am%-0 z$>XucCeMdG@wV*FyowF6pLvA;@&G$4;B3xFW8=OCm@1|!RV+gq`v<`EAlM@7kd=N= zO_he3=J`__rj1JC7T)tJIl&wmMW7?dBS?w;=Q0Yt@X$$64_>VQ}h$dAg0#@ zNVQ{lhOzbvAC)yw?J;oKB8pj`V&+K$b`~JBTG*aGSTX>N^ANE~Sbxz*fTdRnyW9X6 z3^q$-Z02cU7l=012fKx0j?TAs7_^)Waa-x!M?To$#Epf`$Hb4+8`=GFp^(+WHW3~Z zKLz&Gb8?Zt2;|0vT4lU^*=8>jnuD0VUDz)Ju;s!^!I}cF>|s8ZJ^N>4igtZp_Sy9QAIb7<*mKx3Z( z9bNszHPdOIdCw!8(yII$sv;{4*ikZ|abIUfvEQ0kj?nxxY|&$(rnOqe=Fv3sqRXp= zph?dlHFbkQ`Hhs*^L^In^W{tJ`2jHZ;{ZP*P;(e!OqH;`e2r4&WoC+}_H#Y#mP5^d zp}uK9wNHji_h6$5mMU_JWOkEm76ZBSDMv@d?2DwFt$ z2P{k?x&YHnB;&*Mn4%JW7p{^w0?jB{AAw>rN(GyS$ykJ%_;ZpHzgC2EsE=P8fGq|q zJsm9aC0NO)V6nZ0t@6P75i^cEJWRS--bE)NlYYgHdmyICEK~HN>?(d=u&9})fL21Z z(#TU9M56)NNnnwWk?Mwo_8CakrrrrO?a5M8v@u}K{=i3QJLxYRjf~`-2F{pE;5jfA z^oN)o08_ySQWs16f=|*kGKTtSlfWSA<7a@y_75noG_db2#)6fe*mKyFdzh-&Q$FGr zi>c9IT@z!~B<_=SWiNPy<2|1^-2}$r14ZU4Qzk!5;yny$n(oWGsBD@IQ|t{7!S{m2 zyUV)biJLJnJ5*E;HI@NL5*mL+8cea8H&m-i>TRaRKSwnK6RIk zoZQK1EiZ?)_)SBS)_@iDqEW*L!#K)5>M<~YUd1VC*LTIN@P&GffTKPTzw`Q8jX%RGqW;v&e&QAjSl1gt^l0CvmT z0N5~Agt0^ zHF^enB5$g`8#YE2iA)ERxC;Zc@wOY&P&O+LwKD`xHWZ%&+ZcEW|1LPvX&T0Q1qj=M zMUhW^uxp+38$DDsjHe0!y2TNaU%?hHf-Uu#A$)_t(r)8g+SL$(S|6$PJA-~Z#iQvA zux~U&CC>uXT&qN`=MF!67uRzVl0BpFhsUBEwr*4Zc{pUhxIM+yjNbo(heh4+$ z9jy3DgU=iM#66hdGB-2Shxg+EGV_Do@>36q4*<3tPtjHx96SYJZw|oR0@u8=hx{xJ zjp zhf!rnbQA4kz#jAyoEpa^(Uh!1Sj}{wLG~rsCd@JwizMYi#VXU+r_bEtZl{9g6xwi~ z9h2^Cy>!=S5LO zDeU#bNem`OLnL3Ms^BY#+0(>+tz@*T@PQ?>AOKnkg+pkN{ewpXjIx=|K0<~DN+MZS z=95IGXalXveqvyHfco~kQPfkSY2QFGa;OJO0knvgiJ~|~H^~;x2@*5N0k=nq^+7y4 z4P3qn%=R)GpU$#!Bg_5(0U*oT%f8~@2ec)pOyVFzHZD=qzv8d0KLWI1iu^TeKqJQi z+>>R5NRaIY+L2)H@iKk}#n_|(zsOA!TL_37A3%bcg2@8CC>A7yDNo|B8oBNi_IVoJ4)(&jG4_4>5AKF!e^HlSh(I0=T_d z`GwF5uL;oB6TtZZ*0mesU#&bj!Xr=KfL!poApy=2>y@4i>1<@#AAJW}2}MW1mU&)m zQ?aahg@=%n)f;u?u$Jl>lt0o1wZkJY6$B7FUnPMR?+Ut2oXt@my$nhALztrH;h8C4 z0+EAZ+84j2`cHk5s9!*_qgs|c=h~l$R{IyKqKB&eX~KGWiFBRKHEuMOeM&w~_Ne@E zNqZZ)dbMZG{d=3rrO7eY-Z{feV;)=`GjvddrfZ*~X~mDcaE&xOuSRg={D9X(UqD+s zucdZ%)T4z_@F#I-v^4!p0r%eY+HS|!TUDts4D%h|Z&feNOfO1Z=3e%#Eccm6Hq7xy zGHwo+QCnZa*whNFaIc_}{e0#=6;ms~pjD^q-RmJo$GRdoXzDl=#oO?Cr0LAKbMQVDof|pgMBLTIWA46386Fqo;W)vhZ(f(O${zMof9Tdiq zdG$`SGfS$f^h$5r*GNc_1xuIa-y^|}Q7zV1w~(E4u1?NXjcgI~UHY~@yE+$(kt5wJ zv3xAHq36lpvC&5E=^wB?ofpt=sTXjs_$%7hlg*~^Vz4cLSFa?i$uXOJ(=ZfDJT?Ig zd7JL4^uB2fT#vp-`Np$dCb?cx2ZjE*2)N$Z*4==PIQk&3@Lq!4K0uj!$cfIPRrw;1 zAdiF=S>%K5Doj@JP9tDEbbKnHPac5U{zSBHJes?6gRssv1?tZ=Ng2FucbFP`Q(yDC z&!YB6nfk6z`Jclnzxkl!O%4FJwSd&B0Pu-X5sZpNKf1}3d#->ly}`s~63czZl}K)) zgS!?%;>Y;bRG#g@cB_wtOdh6eHY3B_SVVcedrT_+G@$;cl-+|u>9YYYwHqLq_{b30 zjs|Q1YSKNy3h(!MFKW#NvyF{Zj^%vJn63uqeQ!Zd#Umc$BiaMT$RHmu2`qX5nAybx zyOTHL^tdHQDFjTFGAue&de#{D2YLkLt&6CxnB&8^5z)5hB&ztC9AV6TE&l#klU*eK zYhcQ{ZCD<@gs9wapo-IG-==G9jrOB&oep4`MxH}!j=@x$U2R}pPE~H2Pw_P|eeQu> z=*v{>HWV(BE_|_By3P4#b4wM-eP=+dnE;VLW=Iq&2#bB|B+ZJEE*|`E(K33NkNhdZ z{wStAJd{?EYoUsh(@2B7ig^J-X9Wc|a^Y*n=2i|3DtUKq+#7VGRo&K&p-JH>2u~^- z+x74fS>98Ef%Me^QPpc@j|1RL=qz`NC#cB&c#03}17HsP_{xBa^R+OVD3!soyW)~NWJN5z5wO%a9{N1g_3EJRZv>=-)F%q!9hUd^fQWV`Z|&FT z6U<$-PqL}MnY)?x1`*=IYka1?r|E3FeuD?x*vSAo(i?d~a{qudQt~EAXcH&(Y|{i~ z)s&gEJznq9OyW7}>mvwkueCHa%qI=D1Y~`aIA3$O3_2f#8q?uCq?Bs!P&5Pnj^%W( zMkL+cPsW{D<6Cpv^*SQ|VlWcwG#2Wn`xL{MpeI4hk#y&0UOqo70ZN ztb=Od4AaNf-3jqWZ@@2^rc8$wHut0l=c5v`lMm;q2r-d6WF(NPuS7jX;Ml-%;>wS- zK`EOE(E()QzWED85*2VNK-x{@c29zCcj8Vlp{0&f)+4FxB8d1D%9M*5-e|J+XA**T zTttY?U4#(9&{EEeRE728y@cJWwWa;Dsq2!L zRrW2h_6SX8d)>kg!W0}};Qu|K;AV*CpFY?Nf#k=GV3aBo4IH3cI2(`bX3B-+kIgOi z;NJi(`<9QxZ#Sg|JCUu*qS=TN2 zBN|}Z^K)a(&}#t)_w-1l8$FnQIv=#lV&9rxyHj&vRiF{^H;B=n0?gSEqZcb6(HlH@ za3+>C&Is^eAcoX6`q&L;-h-3C8jgYGyB>Uc3*2{Wl-OJo^_f=;AmtIiM*x`%2@(@1 zDwvni8T-9-#xDqfW+h5YFAwl`060kC6F4B9RmNGHj}D(o2P))*AhVcPRGF7 z^N#dnbF;xP!i3FWS*EG)*o<}DlGC0cEZujfdKH3Z-f1IVha1TqwbmECk!~Za{r!VeBt-&_M*I`mmgD z$ocmGTjm-9a@*maGD;)i{dOqX>339p_!(HAi=b`zfYAW@hZ0$d&ifBc#^Qhul4j#V^XXylJ z4cd7Wtu#2m4iJ-vtn+EPPghu3tPowiG(c&Q*mi+f+)Du|c{2dE^CUspRk9eVTVA56 z&EDh*&Z5fBLT*-7_Go(2PCp{B_S3+SA8*|Zx`|O}SB(I?dx;wTy{8Y?x|4l^*5`5< zQY=?y=c~a!d)dJ7(Ku;n@C;(tdSFla^k;9d#s{&jQv|Gj4Or!9uqJKFx;zex-Cz>Q zv$6O;9OUWad)E2Mhbnh1;Sykd%+Lx$S`*4%L)o$=dc z8egT841IN<&mO7+{j%i|dML__YHQyoLCm-rMi18SQg4cAU$iTj1FlE~OfM~Cnr0^T z;9P>Z)JI~f2IKO@v_v|tOkyCK{oIA2=KZNISdO-o26v39*e3U++PLdFkM+|QB_CZ)&6A@I$;n)8I!oionSjHYrtIUKI*`)Iy@cUJ;Y$v>ljyl=5 zxdV<+e?*@Z^-GM1olE=Nix?5p{@4`sMu>?sZy*F}^xHr}0y?1cgP`3G_tbu8RW0x_ z{ZO#jxoR4FDv*F)KBDh|8lUcA)PcqcN!Z+iJ3%c@m|f=i><4T7N0=JtJ9!_8>s~-? z2~`mVCBC~sT;d&z_9SR{G+BN) zmY9lSAK0;ms^Z-NrHc$q7ZY$}26}nWaXpP{-NACh=`scHMtP{c<~EL~b|u4gJNA$zUffzUI9Qs00t;`Mw?7eXo+7LH zS)8?hm0SC}wSOmkwO$X8!P$C>MOY!aBe>RMp4KxaRd|X#0`uo>U~L@ze9V)7e+~Lh z|CdHC{g^c$d>Ry_23d3LR4lc-^k9+ZtdQs^U_5by5w)tNWa*lq@Knd5H2Rl9@UMon z+U7nRgQmMLGK6c`bkP0WVN!x*^2V+?3`M6L$Ji#<|;z5*)#>IpXnAlvjL2=3iY znvrZXn<-{183T5$((UUWh|)d&{2Ux*jJfrhfhNvhQ60T302{*Y-+Ma3{tfwz)+C*nDEetQr90cT*KT z-vGY#snQE-)A2?;n(Ol6XNDt9j# znT}WPQ8MxsK(eAYur5w@5 zZ$f;p#bAG12xiKddF(&fW%L|8xF`Ec`-Vr;odOt^_w=mUm7-m>$cO9b)n4+lIhuuf zkM^zU@k9%tSly3>w)OlfoS}5Y<<7ajGhx-tnZrh)<0FBVX~V!ABXELG1Ufz{sMk^4 z3x^uNz$e59`a~!x6NbMLyja=K?Ll7e;qlXqC7R|sGhK!+9LvgKt?VC=#8aSk9vuA7 zBce2a&!zc?yV5)MpKl?y?m-M%eU8U!Umjr4M@Iy1k=vS>hK|$17&#R7%u)Dbn^y(E z^hBCwX9#ri)Vm(-zd0SQe{s3jBEI8tAN+fX`EM@wFDdlNN9YCn))1fPxl6T>7HGXI z>K!nv-2#ZOqFAhxYSZyy{I^M;4j*lpj-Q~~B%p0PMOaaA9pO2Ma?=Vxrmsiz780n) z-uHq2`BG1#U?d=Y-(-5;rI=d@Tb4uC_0BibUAdRZ0@LVD$F@k;x^95fWq>dAG-or0 z)hvxo|JjVMPOf^8@z@?``ua9fG+nDub}q_Z9gIQUv^X#6>px@d$9tRnpklRpWeU$l zw)Y&LH+=5QDOXLC#t-1my(MfW!2V9yIW&u8I|kfapXh)8i2oVd!VzEVwT$?`K?_Ix zBG$ek_`YB=B9~tT_~JFdvo$`m)#?U>pURXK>2Qk%fL@&e_A>zE3nRdJ7(WYMTS-;C zzlX7BfU&*XI#%yZ_u*IZ)9!sfJ)Pf2j$yT0?MY@2Pm(0AD_vnaod}lrwMU31%7%z$ z(D*@qy25S0`yqRW4N8#Tue5AhcsF(P93Rt4=J<20BdGj<$Udp~w^j44dN&V2r%%_;3KsPnJmh z?qoS>SunS9uM*yQMu*O$>HE*K8L_V&1M5KTvdvBI9*H)ITVtz@P}N91HMNA?)v2h} zVc8hp+CRL3w3FRMp{9jMD_|>Q>x&@96`$l42+za%PKHTqe87N)(>5RDC(_f?zhb(~ zZ1w@`0l8wT({BUpVh`+0L@L>AI*G`L@56TlwCI^&*-|2AD#;G}_27U!iXAL!7b)K> zKv*Xq>@F~Sv!Rw$=7y#RM&wQ8*A!!=V9|plr+l3^6I=IkH<-12wd@i-H@8&r(w%;6 zYo5^&%zG@grhL7bA~V{1nDL4a_B00NuBVDM3p4I981!3!QFh0B0U{d#v4K8jd==!~ zF@buylQs4{)owt2D5v%;V>#`KG$+yRR2cd@Nxy8;Hg z6o$6as(m9vZ&2lSN!Kd8_4Rs4X&LI?J;=3WrEKm2DLn)Z2Pjq(rIUEp=l4obzrI%X6bJ0EY7yS`N-$okvvTgS zTmxDJZk_?eWIC6tNJG5S0$L~F3bT}Y@xBfmborYPE>n6(AwkyuqKqs&9^};D<=g^yU~K(rv!Oix~#dlyj(nFV&z0_X0Yd2q-(Rxpy&l51 zZ1#Zl!{979%O_^H(lDWy2=DLW>IXBGdB|cr5F&}8lFSC!?Tuy$-E=5{T+#N#>F7?BOFxD57!m8<|A|~hM@t9 zDS9!i%~>Jz!}U?vDYzOAI=WAN8TM@`t0hkPEXYSsgUW3QY_7NL+eq|m+LzTk9)ha7 zRAu6w4Gw)QU%plx2r~btS;vj?Fs)nyC>7+8;_54g3UXU^hyApDzVUEID(Zq>4~;69 zRxVj3S{;+us&vTbmg34{m>Ud#m|A%l@xq>J%|L9ql3X*tT;cNr#ib+NuM~S!5rp-h z2F#S29n5Ggp_(zw(U<$-EMoqvlX#d}Os@yLDbBVt)Jse~;Y?(|m<7kj4|t4?FOLXF zLCspGpsUYJwwobdDH<1lXmE^K6Q~(e^C?kcY6Wh2!e^SB1+}gbpjnfj5d^J$M^xS1 zir204uA7jK;Y870Xv7*udufTmSW^PQfoVDh&c?)UP5BE)Q^ zs% zI+`|#^8!GENSUAXq&%X6(P|=(_yC=ox1J)LK z@D=(AZ8P0&c)N|T`+&^G69WbqAiS8RW8!oL$=wEhDFbrH!v{SSoH?HM%O6D2KH6Q{ zl0`#r}DRiE^!YS>JDb>7bZZw zVm6y8``6XDF+y*sZT3Sfj<&-LPQCN}%a1}asOd}}*!(A20a^*1TN4NC@h>a8DJRZN zS3HSYQ|bc?^DPL39w-rm@=#>9lD4@;0I9XvEsBARvCnTbGUjzr=|-EoACTE#P>{Jt zYBKj=XXH##zs62o>D1ft);>^s6WvwREgskgPny7DgJ~|KfHFv+m0;q?GOYRTwXR@* z#6G=)WCat_D$Tsy!U5t%-X8@-;+Shg(a{=%WrqQzUa`y@Wkz&zMqw(C!$D}iiFJ`Z zv2$fa_}&m#`86`0g?Bp-Htv4wE|a<6x@Q$LtEs-8%DWV%&mr2ov{~XpLV$;hA?lKX z&CN%k&CL#g$$ls++QVnX{s1ho!oXy`#C!%-!GTbm$MJ{J8=Xqgc@SGmXPABiJ!=k_ zLCu7Z{As6HJI0;x*_r6c&6J!s1F-9jl!fOId&6AR7ivlq1b>tQrnF*ksIMo1f-RV2 z3id&6OuC~l1LE&V+3=a5m)*=>`}y6#(&avW#*7$bsVB@?h5A+-U5>py{R(?&Ia@la76+8iYi32t7e;O zYj2(Jq3ZYRq5jo<0wQ}UXdGah-Uu-NKJ+zzs)t6-O=~D~qZx#fcw$JyhxpS0yGyg6dCeocuIh5*u@o-xor*f~M zTi#`R5+X}l1rYV~pz4HNuc2WN-(`;_^!c|gvM2kc>d*DD^u++|DI75ceGs+dTNil} zzRPyMAByfw(0&>f zjUoD=f&Fqi*t+om_aT?hE<&ZPdB96wrtE*;U}Z-tE&XM4rRcM{LUO?^KBmW$P}iJ= zJHA1 zWhnVXB7S;c?dUl^ZrB_WQV$tYr4V8>4Q)!(JQa!Y<8d+dkmyAw&yjja6Xe=4N+|HK z$`mwt469SU>iOfDeniLit(5^*7o8gDm1B2~VX2IgMruI7n@k=Qo$6Q3*Qf{iE}e#R zP`R}#Gzm@BQ((GYRK_C}AcFNO^LCy;IIjj|P(b8|UAvpCDK})B@x`}nc z6zztc8-E7og2Qj(=vcR!Z+{`%9fNAYbL0?EtnQbrn8V}{7_8`rLc3y~eAA;FyH&)- z4OnilF!wOD?T&RxAWmO^d@)^SQ3km7jtbcBNs7i2W)K!}3PP@YdOqMKpHXfuQty2F zh)f$cCE-5!7}>s zF`kVaTFXkOo~F-_qq}T9{bEWrV{FQqmZUn#J>LC$3FU1~C`{Nkp-{79LM?^5T|!}2 zms!3`s32dZCt77C!DtmgNVZ8T&v7Q&CofA**ZU8%#iz-I9Brk?e2llvo;dczpr)6> zBBQXfX&RUz&27`o0J}5*3jn!i0L_O$%dRkq^q64?*f`5)kS>Q-w-{{WC-jf~rbtk) z;&=%t5OL*n0QCSFFbQCP^+wQ0ZcRX2!4s6T4}ldP0G4|?0CQJy3vZyQDf}r_k)!gQ zCVDFMCukz*y#VaS^U)_( zc0A9h8pddDW9B(^WO57*c6{*IhSoYyRZV7M*2&}c=LuDFzSk0ddhPQ*D@rt0)CPGU zm!#AiO_M#rXMDQr6k^i!At1LH&i5ulb@n_EE3W+K6`h)F84R&l>UK%OQYz<-xiv{(O@IY@BY` z7d`Ls96tlBJA=WldO;ES;86oyx{HtJo#1qUBE;~gS#})_Oo9d~GZ=8hy$I1Ok=8_M zGkL7}nO;8G+TVLjSZfUS-GQ(!S4r3=TJLm!z@IGGR$%LkmTLFl+E%8nXggEgiW$DWZ)~YA`@EPtqFPOqdIz3#wRpNVA4_En} zXTa+A``O6v7;%wEc%xsjHtb|fcNcY?(`nhyi-fZuh(=+m{Z{qcyvnqM@4<%VGXW#=2PVXFO*(~{b?nLVg9Y-GSl zag~9)Uj}#GAABx)yyLNevgI+mj8Ui#qP0Cd{JCQZ>7PH`Z~|O8t{KF{9;R95?0|YK zvgzMyo>;HnW@vmsxCt?tQ^a(RPw~1yBdGy_WIy$8=S`XRMrxnv>k$CE30`XhhlzC) zx@_YHHB0>oWkqKilowY9GVC0THgSnKQ-BbMZ&;&m1f->cQp#?|bmDf~{T$t^N}KDyF_V$~6}m zTxH)R9^}8U*e43Q6lzzkq^fQ{Rcp{>-|rYOmrMlIzwHsZ>(SnGqxMthkMXf-x96~= zGodnFrU3fPqS$9yFpG2;i;Ry>G6d`mvdVP5LtC%L83@hn4M=HGuJcW`wYRi01aynK zD>j2xsp~zu`9X>f@D-`MILtg8QRdQGMZy#qy7zF2%KF5loJI{lI=cM$y80#0?b)!H9Y;%-#&K`-VPCK+4* z1Ge2<`r! zn-7mP%!fT-{lO3Q|Jnuha|4o-6xjZPdeHh%|17Gria_i_wn&Gem7Oo^L>Ce zCb8FjV!qu&y+_pos(!!)_1F8se`GhGjcsz!$n*v>#6=yI)1$S-9oa+!=WeerSSndG}i|^DWIcP0-ARXG@6B-Axu6upJBk3_+Zb9 z_JL}jgBCeJSTnSUnTs-3G393c0a zuz3J`umN)l_7m+`iuN91U45_|SmPLMFg=1WwtVN2ieyPZ0-WZvVA*-24UY=U59Shg znLklTqwFNgiCd}Buh)4i86fRbupT`;tlDW!a1X=2hsX3T(6K-DHA+ZoegjvL9F6~i zD(7PS2m^LB_3RHc6b7QedzZ~Bz3|Ev&%w{@6)r+@HY%bn?%Ez zt4}=f%)vdYrj5Ik)p4eeO&1wbZGul{F2B<}J#9q+yvNU?ZCCs^hvUEL^C`u?K3?Tm zxX7=s=irW8&g>@Kv6~dHFWmyPL>hN@pHuvQ@VM978`O78G;`XGippk1v4`T>dmNYp z?WqW99aZaqXm*XjDrh0tOtX--ahzAJh2pwLe2$YjO5j!6Z8SadX8PM^PoYIqcbIcm z15#HR(xetzYG3+{#Mb_1)vE@j_foay9I)O8YCqHN3FT$kA_(!5kyoOsq7z6fv)d&; zH&Pd}A+hz!r_!qtSlh*?Ub(pO5~YA`CbiImRGSlM_NIj|N1uX6SG4UKCbST^;;N^- zgdVutST1>);_gWn9*HIU3hq~PkG=PB=E5G&_Sd-RM9^gLo&j}(-PqYA66xjZ? zAil#AK4Gp=EyZevW{3E6_v+JyY@a}UAX7i}R+G`Ga9RcgnMt_j4xd#T|-K!dEQEA?*%290CO5&j$f9`4_Hr}CfNeqqlK`6bJP z0aM-o{P%%{nnU+GPU0#x*2|n4i<`#sn z;#Uhuztm&;UG5g3b@v(a>2N^rl_4O@%7=lLYsoP2=Rpf9d{)J^fWo(xi$#-tVENP7 zS9d7XvKPT>kD|(^u9MeId-)`lMtE}#Sb7AxYik#S*6Pz9cGd0%pnO~``Q65O$Q$6U zp;g{OSD5no0kS>Geo3uHd3hk3s}PN>-*Aarb#W$?W!KFLLiwimiWaSM2(7XOhp2Db zC%~a~v`Jknt3%uCv}M4dRzm7J8bsH4G}zkjsmt!7sWWAJwa6>FFaQ?5ZV1ClyeR01 z?)YOa0tbPHInt6jVIo<=fILE=2>eQ`hPrQi*h7``pjY*#+XRoq zV5?|Xb))$z_fzrOWuUcg0jT#4`#}k{qk>k+FUS7yJf_?kR3cppwSxXfIza}A=>FV{DUmmu2GYCXpPy;KG1Mr40HyLk zSRGKu%S)n0I>_poKEu+kLtuGp#9Pb^Cnm&fmt-=2g0CkWtk36?Ja8K1fs{*+>uy^w zmr7;gx3bvEje7J6!1-sinxAy@3l!@K{xvS}beM0hqxKs-RfcHJp6SEFx8|;)bEwvL z1<2*x0WL&^lGv;`DjK{C1XDjUkk%n1(uHqIv;S0Gf48-Y)4)WQ8`wq#d_}E*7#WVj zNGEq3Mt8bS!p*fVwdDahk@p}ceF~dsJoz=nYmi^(?$GK&Ejx8RxomSuSu=3{lJcE~ zRLZ{}Md5a@*8ktWr0lNPwp_doWBC8WC1q)CdBvh09c*r-16yxfGo$pQ-&?-Yz}U&Z zu#V8fx^dmwm+Ij=jq7H5*xgNbVA~7IQ#k@-(u;!pRP$amd2 z=aU=!WQAF`!|s|iOJ55(x%gcRa76*z_zN$ru=PV($n2(-rtloP=9@b2Zw%Dc)OL3l zv~}!YO5INfV>uA)BP4Zk%6@>GC@N^b04vdRf8uzni2zq;6r#IQ-7OBW++~v zBj2RJ-@n(Lb|aJb2aLf!Th0qtBC;7Ax7x5L~fV(u$R4rH|(Zhp*# z`A77Ftv?{3TW^EfcnMhP>-fYEo2cT?BtVFsB_CFS0rEbCk;(VDI)1N)fEcD|Dknl8 z7*mS*_V$K-vweQCc~G}adx|u|371CBk=BU0Ia)SY`Ctl8z*;$yw==X^KFAT}R*Ms` z{Skq3By8$@aJiK4UBxSOgft2rF{G0E;qWOwYw{TPCygfZV*$N=Uf@A{r&{~IB`<*< z^SRHTm8L)S{P&$J3=`QFrn~qoO%{+o&oH{L2{2?|K=xNbViJ8lMk%)GIwWL&F6WX{YGA+&R zVA*fyRr}~~I?%9P`6qxsLC1vnQh@3$Zu2nkOzlb9IMGQCN?uk-tYbH!-`wqioeLHz z7q&M!VA}o0@ZdZdsc*ES=CKbj*Hr4Ur|xl>x{dUh$KRr+yDYIE`5_j57+AHYXkUO@ z82&gTJ@RaSn)JFYn8qL69;3HqZ9H{bv+PgW5Sez}g3upR=Fv<(nv%YV$-RY*_Nv>1 z%2Q9p`E0cT3;|>+0l6aq=_h?6!0%yzZmD7ZM`V7UKGw(21&E7%1BkvUz%AbApT{DQ zJSkQK7I_vBKNdMO7IeRM+V1-;Px>ewDfJZqX9y-GlG9Hm$+A0()?GjD)NL%*!2px; z&`QBlF~G%A5hPB<1Si7T;e0XJf!FL;=LwI*<`tkST#EB1 zw3vh$GSP7*e()f{9h(bu`jYPA1fS z$9rzL_zagj%)dEYZaxGtpPj8aFZ(&dqk9DO&^#Y3NspP>;U2K?ev}uCYXOB17*!pi zs%SF?=bof0+Rq^5Q_T+LJ}4i<3NaOxY}E60dx0j8PC5<{JHnTEC$C_f&+0V`p|kT z8XQY~_8&5>7xr&8-C+V6deqB5Kw*Gka{-x!z7ubN&9i(Lq*jNB9ioqh8chjcpW>_d zV6^~NcgUFZftCC4(TSp=5U<$Z&=qh0+9M18jw;K#i|)*EK5C;9ko5@LTz3Q4M13s< z5Wr9iM7Y`C@a%P$gquDAbLAY$rF{h2`4s0n*2NA!Cp~V2yeMUxU@BVzmiqwC;*-GY zr>WvwpGTy7+7510?4zgbeb`Mg<nplIZ!+nV88NodfHgVeT*A8h3T(9fAGOL&qnxNLnZSGYq@=3Dtc{Q zA^hoYd{o}$QDlB5Oaa4t9^II0p23FEHar^;wyUXc80}%#Sg`t}v&$q-JX3EF4A0cF zL-4FC2f?!(=t9yjGW6bm{allr{s9&raW;Xq4Sz*a^cp1TBef=S8d!Z}fQRM&e=qY9 zADJin)yaBHvuT=id^o^hbyEA$KFjgR3J)*R1z$z3y%LZ?I7cle(LQx6p?442_l=_U zc=ybn)>w;c)HSX1vCLsJeYfMB@3HgWc_!rEYS3W<*q#%7oa(Q$ATx;;_Rnf|S~sv; zTY+eVp&=|0Wqe>Q3lY;B`OTC(EtHznn*9=ll$(kQT~mF_I1$@fW!RPvH;Lp zb~*CQKt*z{f|QV>l4IY+FZ;5vC8D|8Vt*3z5Yb*+C*NO|8p;}CLhI-o$t3_Po3d6e z^Tsm8afpI7R6Qtp=1hr_4tF1cZ^1-jSQeQ(&|{K$aXyukiMjo`%-i4+X)PAmjG8q_ z;b-%`SsMN7uJh-6ec07rK>ps=nI=QGe z?jX>*#O;2R(Vq7XxrCN21+#b!E(Nl|?1ITwc9|F_K%up|H5Gbbu5-5_>M5J9a^_RT z$f%k>SHmmsZUOriZnlr9+?Si}C(q;y6a)C1XIG^vDzCkqEp!vzqBE~Npjf5f!L*Wf zy2TPT)35$hdd1}o+SZz|)qmIETG+QJ0PdXE0gBb>FZ1;L*`jQ3H!7&Q>raeSG%>)X zvqf>0ek9XMZq-_Sd2+>{cpz@A`QJCV3w9KRXPQpAiq%Q%adc;AXcTrEuQhvb+etJ;lekF5~*H!LcGI1EFwF4gWkFOj8GWD?lgZ5|t0rpDe z0VUI5J|N_%gC2@XUL{fXg2#ek;x$uF@RHxq{^QJ;%AqBfrVgwe)^<1BNcYDA(zgv~ zKW@9D`H;~GtXOSz^n>)zP+9cEI>S_Y54!jHcWOha?bAQyr#1^c{UIfDb|=gLXsSN~4P|a;+<84>>K1yGkHjUrelY^N zxYuSO6;}IAR~cKk02Crz_im(BD+r}hMN^4}-)zq>sS=H`k}3cLV)G3U&HOJ5w@MYZ zm0!*wb~}E|%u5Z`I)gjwR;-+Jta+)e&O7-o0yN!@KWO2X)Gz{0ssI8ze;wo-C{FE@ zuj+o7n;G47>1IYv+*QZFlAlYzL)T%-juU(J@yCZ?iQ+;gY zx9b>a;k&Se*40hR0GUzxT4SBt>D6kXU9(R4)7;Hx=2kqSTKBa0nPOon8kv6D>3p`_ za2IESe{z1AUN(@jp^1H+x2Iy8%FaT&4ecRpk|M9Kz}{QupT)71 z_@&oAu1$-fXfKyR7bD%?uEsY>w{{%nCKVla5w+%`bxQV|mMKGz9~GR%_2HSpsD+8% z*6z(6S;4$SZ<>Ynlx$qy1GPBdnfQdA& zBy| z;?uK&iEF$xE#vjBO{+RPP(w|2SnOh2*r=A}h~%$k8JX%ZM#~SPj<_Y^x$+VbmKatLqYQ<;W8)LQQui7%|Y_lF7?OX0<{(2dj zQl0)5nXT`}eG9!gmyOb~lU`J-T^I%T3~jCCyCq&?Uo`NT>(w*P0US*N#j4lwG{Ga!H4~L6KcXeY2*>h`e?>+)Msd3S=k6 zDV$Ixcdht%O2Qc`OyNN9X6&G>-gSKrHSF__YFUe2rb5QmqtNi~-i0(DxG;(c6swC4 zcQo)-^LHPqOG@&OCBw*=UeZ~lTJh0yfEYckNQAwsuGUW(Oc%LcB>oD#Qa=2V{NORr zUC30dJY}yGD+;Di<{9zJJ8p*<{J;d$T|iNAdFie{gg+CH$OLDvA{gd$nl23^Vd--B ztX_GLK9L&P>2w~8`z_Kscf(YwsURc=#{iUz(@^0|$LNCI1bt zd48Y&U~@^oq@%AZ(#H^;skR3hP1)j}G*qj7DwjM6leEKG^f<=9Z!- z_?ViNP#^8=fjt}0wq^+E%Zj9lYJ03mn&@8uiX>n0bM3!$?VmuSBI!88NSc~q5Dy;i z+IN=LpQzRB04#}+=vQhP`E)Hvrd>XX9>{hyL=06nJq67D1*+!Fh_jgk z5N9(t&^~f}K-FO$lKADqv}r$K7%%i_-MG>xh1I%wH?Y#fWI@@VJuJP~T`Ju%Be(wr z`qtE+mhSVKn~rfW_Lwirr0--jV;Pp@t)c^LZVn)Gy@Ba1f#DqS@SWp>`Ukw^<(;mz zmkkb*D=;u!B(uiB-oQXJMMr|wSGaT0S?43wL@$N%wR;_-i*+`w2c(SH(yIkL_qO;8 zu0w={u#!Kzj4F2%FRmvpC&qMT%VZ@M#q}KnY?e}+S`g5-2v9$ZK9p@~+141qzTs_; zb6-7weYFa#%P{FPudbABCC6a$xVa1zV^$d^jy`}wu@&yEVt@HzMz~L!st{J%ruV2+ z1DjlBV2~I)YcxcOMVOR?O3hBa0}NT@^J+=INy)u2n0c^Ne>}sO3!%p2fY`aTjrCHo zr&{vH!9fTnN)>^SA-f)_gXVZwvBWMxXJ)-BFY@qyqYQ~_&RyZ|&KUWgqTV)N5-_k= zP?K8i>AZU7FF7x;sZRs-`K3s3m&Vn=6m^xDVPc-@=IS>Wh_}%_C8TC}#AKNMP-pR_ z;w$JF=%uYuz}gPoUVVLScXtS#*AhBJoLa!9rtwyTNizV+*8oz;fVwcngL|7hb^a#H zU&5d4=`@>PZ~64Jj|XK6L7h9-zUL?;o$F3*xkrh3SMQ!}R1Pm!ntNz!?g>wTwNg3( zl4sn#xsAvtW`E%!QBQU5VMoV|ThH*47iP-n%{FQ{Nk!W!r;v&@ETAFyY^IOFDw_%p zwNH5=Z_s7`lyu=0?SrCknjpC#YF7V@&L8m_-T zCa`AKO#y0>XZv`m%-DPa#Kf-x9ym{sm$|xehTh(Ef-F>}P5#yk8`wH!24Z|`$6c3O=5k;A z)8h9LC3&W}KMkeB6y4`x+6Bi#y!300oAlLmxjEqkPxmmc>6JuRcHldB2= zMNEA2B*T_bRZh>=T@{J5@DUC)=kr&E)cx-F74JQa=D|Mhzn~24nZ~ncE|ICcMHOs< zU_TC8$~R~yZtrFJu(K>H=06)}8!qfy z7T`j`<7hPzpU}Mw7HgnBaxkD^6rj^g-UKO}x-VE}4wsRZC#|{MSCeJd1jaymNSPv1#|ARYMRN3<2TTSF&8fxsir}#8bT^02&g) zq|G4}462D3B<{m?@-~yp+WnWw8U?8}zg@AKsyUi4+l=2nCrhP?yM2XX)oO`Tqc`Vf zkk@K(ASXO%Y)EnKqkw#H_y6`f1*Ro4G54_h4e#)}B5CAgXE?hx8bo0=eW*9C{Oca7 zJNsRzC)U7QISY(8($x19SU}Nq6mUCw^Ah@q3`*Q6EI1t{IK(Z{x9ZfF&D!DGoR3il zgJnvo&n*TtKLhyoB4|%91$$1rmDhjgGyZyF0h|t64^(TG%@}qDsk8aYy_Syet|WTt z@95#8awY|x72=pmq3orb@ymFnYUrqV0nGd=EK@|k3h%0o-i74bZ1zh9~k zf9Chn_sQ+!l|;`2%!q+l`FJZ3%~gn2b=alSq=`iPln_u1&k02j_Zw~#!>MakvL4y) z_*MH5SE*unP$+u1-*KB1npgGilI|mOcAf8qZ*<*j-LTk7)9Yr21!tN}cBkFWR`j&} zwLMsJJ~?3Df03rW-t?L4?pOJ%Qo;2PLrDKIVAFCKSn3R{(ccO&g@XX@YhZ;hz-Wq! z1tWvuwCMnBZ`kcOV%pcx+e5*6TuklP^E~#*Og(nZO4z>e~W(W9`eyVOJV z=OgurL;Fn=T*X&Yw`riyzS@VOK^Kj4Q??kQ+Q!Szdx=CFZ^ZZvj~k#p*vFW%4v6i2 zI9N%!;a4R8M5P)0`vAD-p+4td`%`t;8FH$>yTI7@Y6u-hdbIigY^U475_1EFWF5tV z-3?Z*7JJDJ0i%Jt1lrSdJsWesdLG{LD-FG-_%{An@@E5T+j%^ceF3Q5$*}TX!$wR` zl6u)Y>3S%)P8PTZFzISjeX1^e)Lnt5qW+K?q_f2L8Ak3`|LxC$n0lM3-9#DC_LcS2 z?|UNkZ-0dW2Tn)&gKdW(0L4~ZaCIucUW*868{Ypa*#ihK%vH2u4o z&~I~K7j;1UaR@KkSJ5aTXH0?Tg>f?_D-DgxZsL|E*57(1mMFw#()D1(xvG77c5-5> zUFCoQKi1xH%gb2Ua7YWFSe@=8o1nWt8|*6ew}V-@CzQ*gpVbBf3jT1_!_?mxP_Def zOK=S!#D3wZS6vl;W^!c_1M%{PfG&Tu2m>B1@i>9y@2E)gA_xt`TL8tp=0KWVr3cn- zxOP2x%_+$!3vjz!;%ZYKXEL<`{I~hie+eBIIi}I%jbXH*(3CKn+S;st6_J~)N$jo~ zO%3%EeTMoN>NbxL7}zVonx+HlRcyM?13TNWp%_YW^Z8(G|S)@ZMY1Pdqamm)aSX$fkYhGSrp;SqqDZEH5Cn3AN1f*9XyOh1QDLh%( z*aIU>`7MBsH&gQ?%_63n$6g27c>R*9A8CnMvpD3`xrT8aa7CS0Ih@m*^~X^&kZ**e z2xm=^d?zj}Jzvu>H3zKzZ9rX(2;&jZBi^x4#0iu#kLwwCaiBAm7LD}^a78jkeveN6yMU$9boB78zJeCD0+ z+{wXx5NLkp8N5DYj2}YwR2XD_c973eW<|(Jz^Wgi+&Se{!ga`vNc~KCGL&w#OQfmS zC}{2rQ$@YOHt$YFMh1p`wa^DsRO%PP9Aj3sn>!iI>aU8Ae4Hmw_3K&YLFeWwtg`QF zb0NrDfK_(ID!_;2vE$4~m#(!uvLLZzfR9}%*1Ol_g$h-}L#SzwT>e6hH-mbNGJ_Yu zDU;1ErOD?k^SmD;Yf=8Q-7LT<78MG%OuCvXD}RzR1M?ibkd@Dn~9|T9~QGRB%sbJGOe*CXSrbFFj^y&76rf ze6?^QmsKXtnN!rSi(va{@js?2e$BMo{bRmQx!?*%6%^iM4@>P*7jQbRPf zeE2m=ZC9zGrOV4P6WD!8uRLA;*5ze<2P9O}DEG|vSl!uTjuDw{!5-#oTeH-(*oF~)dEcUs1?`c6=Xr* zpKFDd%gV^G?=n|@NQuwQK@)#Tzlce`%==#%Zsx(NmDl!$2o)dD@VddOXMHeqe))z@{iWv=9&^em z$5f?f6&>^JDM@8&4xXs}!*zD^CTM`Rt;?pEf(xf7%=5;_^i0EJ`n?(8$|St&TO(w( z#R=K5M*A?WQy0n?_!OsSr7kExwB+RcLunPw!{hWNoc8|Oehy4epM_V?N+n3=$q_(h zl~Ps!M5}zxCU#-JMdS4n+w1sR<>nxP;+oJ5Qs={eHsu;l$4XkJt+O0UKb_r|OB;sT zxuSCuo17G1O3tRgBMR?9jzJS0zxN-!ghc%!S1{%;w3s=h=BbIMu4Dq~TNdvZ!+Xh4 zaxP;W4uEJlCpqUKQ1g#(1w^aT+GEuLj&%=e^v&>brLD~Lduo-bS=1H*tO7m&qE)q; zO6r8gx+go}uLJ3!T-QqyP;MoUb<4SJnUkKF_)f}XpEn2M4!QoBG zFDv3|O*gd=U3Y={H1^4|Ky6@2oo#`d?!}KOGpmuezEd4#fJNJIZf8>6exwFq(JZ*Y zfNYf`yR!)(<`89Cm4(8cuqtI;}3_>WrZzU8hZNKqNbzw?3oP zxb!#W!&2wz&|vA1NW*vmEISXOR_j(Jw-*nXO}lR1D4`X7>liCt%vk(ROb>L2x~KFV zH-`pd^<+f;?<6wWNnIx0UV9 zHWy!my|GL2u=IITH0h(RGqx|n_SAKA6sjz9qdCdcC-rT$Obw{6TkD?s4*o!Whl~l>}I(v5c;U%eA`G=RJcLKoSfbt54%93zhCAsq9nIyN-@Q9bGH`P*1;gDqMin2;0iT+1P#w%on2@d~~ke}N* z=3B+^Y3-8v-%_mmqso3-*AIDq460?MyG;nW(i(Kv!9FzPl&Jcs3T6JfCccXzwAxt4 zHBmmyF;CC-U5-UWwuRsZzsuR8L-WYBPmoMY%_;wN$uO=d-8jX&gRg?%;H$J0&!6M~ zPbQ3zA8`t%WU$6c=?_|~5yexXM6*Zvg!v7&$EPtlF(e4D(w#}9~E#c=HH$dc0A zbeG6qSWH1@7lXbf1>fsZ@HHSD38Y>Z5~2)=aF#6qWg{;PE-YqAIhGq@4O%Du?rHKL zISBkewe@+Ic2w_bi~f^T;h5@iv1!I`tXLl9d1G9&jBLH$OnA{&i=CWf-IYucwTJxv z9w2|a7x^YM13ubJK77ke?Bf5XIqTuiLp14lLZ=>_WPosP$V1?Mdyam(z@5zm*x$V~ z>D$~%=zY2SJ)`jj|21W{9Al6APK`*K0j1lKklRb4^_RnI;KGH8)HY=MaDKuX8Q|yi zir5eoXv@0>oe{5n&q9ZbZ^dtO{xChk5_VeN4rp^)9#!ppIRII=Q1dcSCx3mQ8b9|f zLatEjDv3eX0bB>Qs2tpxqntB{mmCK8Tu1=EW+$WpScU`OtILG`;OPlJ|4|`HW~$wA zb%sKg3TqdtzdH=)PTIejHhqc0T9i6T9-ZT8?_R(U9iCq+ur>RDG=B$ z-`GX$0)+4l>Ss8+$FJvU?7%XP$W5&{5rd^0SS?-Hz;+*CF7o|KY1z(lK zgDa?`a7ceiO~k?x?Hs#c5N*ssLN1+z5NpYbkir*C2?aIw+%tCRL!p>thF4+1NZN&y zNR^sWOEg0VK(wm#&it$*Pu`%&cc8YLD3MRsRq_psj5JsNT-wyEA6IXv$~!H_ZQWJu zE&Zq|)}Xx;+F<&1tg+VB?=`H!Mj18;72n7?|!uQ5F3g$-Cf^LH&8QJcZ3YB%5@Xe0h;%_&KS>Hgtm<# z`i7f|{JHe!(z5#m#L4@RT&R@JDF+k>olIzo>~|x0fAXlJ}2jVJ;GmXzjj6*)j2cpB7Qh zpAB~2V2H}6#~c$ofQ;L`z-Ax&SbW7D<7n`Fm}cSdcarPv8|zXH$L&rEajZA>*ofc8 zdWaeTn;4bGK77XF0#J3=8U!G!6QT@o1+Xg98~W-jxUx#Q?j95;iZ` znq=Ss8S*>`O}i5uc@_+fGTcqlAPD0vm5R^?Or@MnE>0MBiWqoZyOYV&&W;w9?I;oM ziwz08y&fTH>T$AA{e5H*sbBi8nt`ZOR7)$k@91>Jsobp zqg zVdh`15zuJ67HuS2&D2EO`kla9RVxR?*Br*=2vlM?-I&4KI*zqyL&3@*OKzpSTa8a} zBWV%<3}M@(HXQn3>?l5miw=L)B&*^lP;On!s>iL21KSRvyQd_?Fru zNdD84n!hzBkpN8U=E_uxyTX0q;@>juI53%#zJ4m`SCA-bDM1>eAT9u(rE;s$2AdOTl<6S)|Y7eA%r@dK&a>`2X$$qtk88PjBBPuP;QaX zdEFWz!#6VpSIFqg=a9Z+bCMPuODMgP&?>$L_=ekwdiq1ZYFf;cdO3#lUmOObjfP+t zd#K*!eFA06`yh)pa}ZrmsNhh*#)AM`tCQP&DxiBEU>B}Cz@zcFh!yERjYntVZ2TM092F=6O6E?iC5>JoZU9*Gg!o)n9vrszW!jX=f;egH<3I4 zCPEWZfOr2HF_L}iSR?u;*)?-VL=0sI?F7*d!yNnB1qq$_sf6|&0r|^!iZcBcGZz4j z`DHl4SVVAlrxycuI5L67)=P-LjV5n>5k(50hqALA;LZ6dHD{=_Z?v$NOJ7j ze6G!h+KmU_E{4|6fY#phtgj}-<9ztbwWPRnrzbwAiir^NW;r4>ktQPaI`4}4malAH za#4l_neEjU7&GOoj#>*-G6GQ80?N^&0jQ99^e<*!7-q5K;m^~7Q0;}p04b`s1R-U} z^kG&$TyasksS568TNEmAt!D>^Q#KC_+P9*YDL&^?+li{T+!DuOfbcwMJB%V>;3z%( zO~`F}QF@lUk|SwcS*}V3>ji6yg+w=@CP&yEtuuwG1g@P@7*;TQ$Y>i9KPZW&@RL3O z?d)mokL zape$dB|$H=$Ow;GplGej$bzDdj$ zPCxN`81nkg;JThqw8I6EtWpo$slsvM?h(i1k$FV-`z+xH4=<0|2`VR=J0POiN6NKt z>uaS|Pc!pAq?MiK;5!hx+@zc+wH?Z~?*gjbEdWjmFhKODl$8sGyG@;A>b_sJBl_i+ znjgqscHq$f(JGrZfL>?gG2JPvb;{lw zTgh2=fKywubl$+O+pfD(lUed?K++V-Wg8z%B-vS5>VkhaI;)=B$+D^Ys82g)~OkPRsJe^d?WB>$H>#|A3;m%8N8?*B*4 z#H#t{w*_i>C!WJh_eu4^n{-^(PU42Mv`dD@mFk%J8_;W<((PjxtFokMd>4A%hNVL-|CwMiP$|7k-mEUMPeeTW)-}kxo2~(%1>F6!P@)?fQ6DDcmcAX&WB-@|s@_(!L_%V)lrqSA zDAsfhL2MdWV`mszfwDf;h7eXGCRnID6F1ad8&--XkbkH0QC2n283^sv9bb|jdwlsY z_PJ&q1<5~-i_1?aDV^9IAJ#CbL-yggJtyreIs45yokC9g?Npx9QA0a^ZwK)W5Uome zvzKSKB|e;MKjlQ9V#d`uLjUjyQQwwzSOOMQX04T*tS!@B}4du;xA%%0((=j zdx7t_VpA@m0u^Vg$?>UXT>ydsu}l*(`vSduJQ)g=VMR@hML(2Crkkl10IF8mQNHSk ztu8=#q_UXv-1Za*fq)OsNfNEnu{miUjALekOQmo5cTJ4(7Sxbr=i2qWTKLsE`{}yQ zB)AMNVL{l{4y=@lQxkyu@Xq_mtgBKTVDqRMuzENNgQb#L*)B_T!$^2ghG(q801GHA z0E@QbsyKX*yE%I0Jj*9a##k}r&CAiWJeAj|Dd5WL=notp5u3M!oI9LQ!t$(mK;dRP z@4Q*njaNgCC43|1bWS2S!qe>|>-3ZGCsYvHQqe1wkFya9w;9x@AP+!~fSQpNt@1u! z%Rqm-iu|2)_+FtmBugW*>IHfDD9>VvuHWZ1p?Sle)J8d27tQmF$ZPjcTqj9pYuG$_ zKv!kkZgBE4&&r7x=Z^4riEB&6k15F=q0GhOmIgj*CbTQE$R-AXdxHLewTvl9kAmHjPV_wu~h*Jlk{%7%xx&<-k zq=aKB^8xewk~(L<1e;zI5t;=9H;T|V1(d(Zk+`+%-52L|M_*|tn#$3@e|s$%xU!1} zmJnU4a=zVz==^y^zmcFbXUV9q50&5Fv00=`&tM;&OU4mxKsZYfPKRh78yKbc#Iv4} z-oNji*o#_wj&a_-%ja3%tbd&hBWA|lk9z=42jA(uAEQ%s894545YO}d%D+8>&>fPg z`E8=Q<}7*kREkhH3qGIDoOI+Sh~WxS(5sUyYg`4OaEvV?LnaOCMA z4weYJsQyR19$_J6cloCP#M0$2PoOW@Gr%|$?+Wr<5J{A5pv+eXe<~mJIbn1SMZIz{ z`6EJZh(ZP|ngwUX%7zpB#o9QVbPL>Gxgr5vNodk79D&0}5djt9X`dSXDtBXX>T1>z zsR5Mezk)n<1)-M56AzFBID^moJ^;;%V8KHQd>XA!&6O-(NRzxJDr7C5}2qAs}^Sm5X>&ZqkQ^j;BrsawWKlDdJSTTTR|PIlvafV<4Y2@NQM*Ywu@r{ds_Uz@yH)* zV(+|>Aj}Ujdkl8~mhT3bbp*KY`$(1eO$7MuP^r>95D9X7ImVU0Ml^?gA!<5I)v;<$ z0!)u2YxA98o4Z6L*$R1)I;ENyR4L8hC~07fJzgf%^jBqVIy@4CFQnJ+!KO!2UfyfY znCRz0X{jx{ho1$1R@iS4B2SbpFC#?WzhL4Ddc(x!j}wzKz~(Ma#02?WS2gF%&x&tE zF!z}l(Ice~Pso(_l(NDA)_0PWKMvN2ZcNbskb~pY=G4xoIFqY9HKKO%c89LruK_t9 zgDfcoxZXrdP6619MeYy6xdiYB2^io`gW>(r4UQIYKA~xLc%=SK1n{YzF&aG>5&8?E z=IhB?@IFKho4_gWB0}}MlF{W8s=L5ZWuHX|s-D4L1Za9NCe#PeG%ZH%XHSE}ZZ^B? z`8dID_W-Is0d%;~;ZpYlTp^Usl{0^KL(YnI?FdexDV9}nrE*s3Lh4Gqjy zwZNvOW;DTL@}l)7pU*a&r**f#Nh6TIqbBz5xuxckd%-6Eg9LSEk~eS9m{12zG(20h zOzY}SQjUh9;Ol0QEw@%74nngFUPQK`(tUhy5zU$ohuv+LuPNBc zU#ag3UW(N^1}AWY*5!T%$X_M(N2rg(ifpP5jI>9ui=J-JhXA_h8KQRi^Hn7Xr^}yJ zrMryIKaoTA`;?zlQk{PiPYb2?TtArlhO=5HYu(wR4DHjuhk?u8ft2QlBb2Hnx#fDO z^DZ!J?(sHBpXNvWLZM%H1Ne%15Ot#omF30;if1E86TaGZA_Nnyd6q@4*tH1a=Q#25 zC5bz|1&=pJ!DHv6BkDq}^xyvj!JxmI6V3#gdt5@(od-=0?bA2iAaVR(c9j!m`J8za zT4wp2*-*?B|I>V@P058fz;-?Hx#O~q_^RPG#pk}w+LqQBoUt^tSUL)pX5iY@A%|p@ z>`^eKZ=Oj8%nMcTKPNo0F*Df}2l)kRFj0P6?w5Czgv;xPA~~nV80kRllY17N-JJgc zxu}0spbu+1m7k)Y?s%{ZyT;rI=Tm~qQ%;wMOx{Bo;)S;$sVf)?7XZP4V9Q@*31sao zTRZL!u^Q_-m+U{xjaeRSh8~M}$EP>d!QX%;4T43Rg{5AWN6Z?oyAxTC;58&M%jAL? zp=`_o+SZL(n5DMEBCP^Knh`i@Q={N0G+0Y7mxv7cU5>N`7N>VRnT}BG*LjQWSozH5 zHBove2ssYz-Q_iL@heEKx7RXnn<*Q4nWS;pP@cIy9}29A$mJ?1eBpQ*HFk8<6=e0p zqs#D$5yKJby1sE=KIe=hXyBTWaV;lpCOgdlMbKe}Bd#}1&mcJL9% za90u5vp|HTsF-P*u;T!leLS40zGc5_B3w&n(oA5HLELsmjgKtHlEqHbiBWcLtnAex zS(!l@i4zXexqPv{H%N?y);dUMy2)0gha-17d}me&2lDJR-CAe$He}V(dY^m#BS>+^ z?_EYC32d528Rdv0L7n=6k`J&CKRK*&Z>Gqle^j||I&jYk^cCeNx5rGhb{3PlmDFjr z*S);&FiI}G@E*u`TnIKbTT(z-C3%@Ej2=r_vs=BSuqy)?&2RjrBjD^0pQkD!D zK2PzD3IR$!hz*!45=08C5J+1a5v-9AS)YtE%9!`9-?SziK`v9q@(QeOTCtfRQzC|e zSEyIn4A}rOp~>so%}St@7H->V4knw;Q`yVa)=B{O%V4T&j`gePMYL)(!(~=+5BwT^+(G-qzU7fy z61e}c1DWfO2CRG>u=p#LUb`Cx@bkJLLj}OsEGAUh286Q&;k5Bm_Bce1H-RnPCPEfE za#yl#gc0BWM}mZP_rkhPEmuT-*~cnm=~3}7Dx^Ew`xU(shPZ7AEgs^it}d#nf~zU7 z=RvB|yb%Pm8+OI*sO}rjizoV=Ew*r)O19q#L0mgErK119E_XDclEsm3*>P$1RD0uj z&!<36&<6>6pm+j?IzMKF=z%tiFVXC&<(zgAN`C))Gu?;?DGwxZWes3aqb&K{ZL#-P zeX825e=HeI!8d@Y{XSyz?;5elxw#%YxgLK-MAz+3$EfrIiS2tPXj_OOTOODKGTDb; z+?k^rH`!i%+3ChX)6zR>29)IuBRu&Ht*jiVc;Pe)`9YJLrE^+`l(e+)pU?#{a6c-8WR4&*7fg zpz`0AlulvFiaW%P@uv;^*wrz+E1l)S%Z9GgH0?C9ybgcN8%%}fPR19S3F(7Cm40VZ z9|i)Z6{lk5|>jGkG+ZJ=D5;^&~3%7-p}86145t0eb_Z#-1)_?>#UKt?Zus{)amS^ z^4W)_R2#6Uz)wG~lkK^ls$sp6)4o>dLq(e%StqN8ddUg~EV{k{*2vpt6m~e(w3m%< z1r}{9qm8bLn@>Kc%-a`*Q!j=3sY}u0@T*Ypo+}eZf0jP}p6O90^wdry0O*xL0vEjy zQ=uC}wD^277W|v^CI8SUang<;0rYKYnJRrb@#c$SdXjIIzwv%iJ+(~B#4O`pDxcbB zybi;+jp2$`P1b3MYh()1Bts{*eg1-2 z1<0ae#{MN!uKk{!R~a3~$JgI^0}qg`5=S0e8$-)w1KFuF5YLi|ghcVQLxhbL@ogFH z^tBP~jJt19b|l1Wq>g=CwPSD6M4!Zhc6>#25(TWVkK`dEM#oRDP<&wNYa^oh1hDSGD zK6XsTPW-CZU(`3MYA};{+D@Py*s`O0g*DMt8SSuvLp#4pn&x1+@M$$)>PTwy71P9( zNPtG9bM1pDF$GFkNfRa%SVpyFXL6h6f`{6X)vwXUZ7FftYG~HS*xm^LZ5kByDILuuuhbOoF zK1t8&=n|H#)x>|0qWTu=8z|cqnM>zyfHN6D~Q8khlUr54|l zRsMLpgxwyDPf5FW%EmR)o0w@Uj;|AvPIh|sJC&-p2$}XL*P9PsLB*1RBIgcH=<{k- zScBx&S7tfXXgWf(r(_D+W~x(_px?Cj&0^W08k4f&CovbHoL(7Z#jaFK_|B?LxH>7E zFSDv)mn)KPDe~z@!;ycwp3^C?Hn9oht`lv7`8j%rkLsaucavxghB6lO zELlzj#ZseQ*|-&bJ?k08)f`=gO0{jEo{HvEK3~StM#x8h zdW;}fW{ikT?a+(<^cVs6@S}U`AIX$`jF5rX86%>sV?^>(V?>OLsv6fHBN(G>jBxa^ zs+B^ssA}b>#t89Ly=$cWIb#G=YmE`HY(V?^@iBs2nK457aeM1Ue{zhlGwk+5lj@_% zlzohl3>r+=7$f4WV?_K@V?;zF#A@Mq_u6AbWMhPk`wPbia%9E`xNCl*HnYwcQOy*} zuQf*4K+!r*;O9R+PLw{&0ZKmCx#J}L{WTPFFl{*emds76TH*ZjN`08K%Jdvx;3BS6 zNneS552b%Auv{7EsTp#RX%ro|e93_3RleDxV$=ko%Nfj!A_MiCrM{&aZh9PqkC1Hb zuzZnwsmXJ78_+@JZVHiKD%_TmfFb#5jo0T=zB>_&NiSupQl;s0&AawQv6S2+=( z5EXf@M6o^mJZeMZ0XRu6!`p8dt2Q##q#AOS42e8+__Y0Yl{V3AOrrafDHK0V&d^7UnHAl5WAXl~mpO{hR~B9CP_*iD(ANH}KW)1tk$Mq53gy4T z2SBt+_hYq=H!iN5_`#)e*oTET($rL*&3nUhEIY{!w^p~3F9{_pk4{HHLf2iyTPd}d z61uA}7{I1C*TY^_EvO*bYqcGH%K#xSw~qKbaqUdLFq7-5M`M^N#l_5rB7R7apCJ5F z(&tYn%lzTdt+bb=vWu30epyY#j7TAtC*#-x6Av#{* zS|)DV=`dbe?A-cbD8uy{&u=-qO|>9!(cq-8H1(pM^N=2-Tr;zX~hI%RqVdV-;U6D_50Z~a*NNG_v(OMyuZB}dZVev;C3bP-) zm#+nLnqiTpGWFtZ86np(cDmP9wDEb=qOVaQ28dP;SQG6kRLzw&an)^U+D9S(@zCciXqWIJTDzF;>ih4pLXRE^W8u@N!8}}^jASd8IX>C<1bi_GFy;~Hjy)~m zS3~)*whnN|o^B*081d^p;|zn^!N!#6S5LPV6pHK#@%P^)c18lkk|E3T1om1ey{?P6xn^_Do4liWlWu& z17YER01Ob{JQ2t5}^7Bn%^7mgxQulhmR=*MW z))5_@@V#-7r2Kr7$jFhDtQjWFpA*X8esv)fNYUrUtaF1XGvne%O-vbg5Y#4o!LJGB zA%;{#wxDLtCJD;Vj)6Z;8 z>Ez{n43qh}-f=n~-zKm>u8$XC`^I`uzD;C)L z)*ME|t{J0kRqH*lfU(Uq(3NS(Z8SFg>QOh{ZE|-0AO0ft`)B9Q4-k9`dwqflTPQXT zVT8)2^T?6qQ_ir4Sw%X45_d}&?n>yIUGI}H>-?cuyw~G|=KMX*fcZmVYoHD1dQD6M zj%=~=0(w2}GJq&wxu1NLeD5U~)~Egf+q=fY@#?;WMmLfBcY6{lJ`tMwFJj#1GEy6k zBiir?{^jdGkMP2oOCrsqQnx46&ZVM|*B1YdkUx+7XMdy&&k;KQ!N~iEZg&i_gGs%4 z2_ZL@)YM%uxKeP9R}v~d6L-P;JL_XkyDcHGSz*z!5mb*TdIePNHjg2?WuJujvyn8I z;FxlMK>Gf3Nq=cT2DW{aI)(g&A0;HF2^HTH z!C6*$rJr!y5E_3jq2Xf^`LxXLhE546)ku->-PsA9M@L!VUgUDOyhF?WuYTzG-izRP_Ri`c#GH40042lVox6S1 zDu64*`p$KY0qwwYI_AdWh6DPR|E{EO_utV&V3gDC0?Xec<_#SZrUD}zYpxX#x9_Hk z|La^fNIAa+wh_C#9{a^ip3-!mndgQZa4+9{PeRYMGa$_);od*i0M(R+2qixn7%@4S z<5)AFN@#Kagk$ca0LIa=;^j8kI(Uggyt zD+jByqrIOFK)`nUf&F|WxjJb`oqI+?&)YwSW(c_ULkTsat!qComhTKaSiZAShXg~F zJ?I!xK5@*3VePs-A2E5Rsk-tpgz6(wH~PduxJkDd*uGape~sp@o3!#|8eQiYsddbbb`%-u9_4zd0eUHvPl)jvD$fB2Qm3rgJfipPEC;lA}Q` z%~zg@2@aHg9m>v=A^KisF1(oJ6}u-)&M#M<4|8LVt3Lwr!`y^7ZH}c$_!TD+?07sx z-``G3h|KKQga*O~)$|Gy}4_XpHo&cVR*aHc!D4?*y(_`{1rJT;I zz_A-mp1su~;12U+KvE)k(z%fYgtC)5-XHo=PG>rN1Wd@Y7g9?L44zh=q2zJ-=fpVP zA0g_oFUVDYhNi>ciFQ0*vTdiT*xp47MOe^gQug_9`Bfl${3%922gpYZ*JUfoChn@0 zP@-yzayozP5HJU1r}qSAQCJ%J5s-3B3)OzmOdg>;O{C`^7E#2hPQDXkm%-pVG%|tY zJGg5&p3&0vID~|S>I}NwNGacsP3U*de9h6i3O*rs@jfKwzmO0b0?4DXUGXqN9q)H6 zKxUHr`-{N!?nUT}HzQgVCHuv{Rj9>L=!k@#p%8F}Q|=)Y3)ZzIu(+y&3?nFSy))M}%?-*5Jqk|mcw9jSMo?AlH z?IF_b5O0tBTvgLfDD<$ZBg@vAULeh~bH-KRfM4gtpAKJT*_S)}f%4%x8ou3h*2jIe zQ{P2hhpP9r=R2a$A@}#ExFDRbg;2868JgUth+nb-A}Qy;JOoN9>=%BJ1S9wbeaR!k z_<1;n8Ozr{O;iT-@6I8IjOrJB1zj+5t{Gevz!xomDn%W0s3-@vKlNH*woX2%J5u_Z-bsj@VPUebQ9nW=;N-teZ zYM=HFKa_yI4vm7-x-L7ALyqi9<`a^0o8y?n>qvcQ1b)u^I5Z6ZRewTiEI#$13k}Gp zCV^9<@~K4bP?sWD`=P4hzpr0@)iJC0j7pyY|CK9MYWoCh5pwMnGGNgxI3o_(+VZyH zczb>u9?^f;*)FyDjd_r|5qVXW^&q$>dbVmjiDK1_U=KoO8MBeP5wlz9M$DG#MuL6K z(q?nBzSV=e5$c9)R!e={MnL zrK{M$7XGNS>7Ux1rp+1FaIS2)A9c9kNW(rviAC(Bn)74^BKeXHPL2*T(^XohiSlSm zmxLkcRx=&!rq8=fr(51H3Kq03IdsmIYK_)mw;K#I>%`TahmKucy!s{TuB}g^Zl{9{ z{ZdZSI#_7fu$s4_#Js_V7aM3=c8G)5Fbt8`2Bek!nFMN}iWsi6_~ReLvxYK48q{of zvw_ZrJb7&x25mr`UFU*#`}xl*fqsc0G|CL7b-f7L0Bb{S7<4uKS_`rv*#>6~(_z3a zJ^>BFAU*_K7})nAM8AZ8b=!_0{Zht{))`uUp~KknBa1tx&KRY!C^hb=^565bG`(sl z^Jd+DXPe8h3YZ+>&DChOG*&bNicyOlFl;A+@T%BScB>Uk$jaUyU&ICzyxP7h+p}9d zz(uW=RXZcdP(eTmS8HR>KDsNfh^QhxyJftKIn`jLg>ynCgl9y|$aI)9F(~nAiTB)i z7(C0@Iykm%--mBE>|=++?h;y?=xu1`S%-3XIlPCsH?M@uuZX=rhh0v1Q$RQnph8_4 zg@l6(yo82mI>oVC_-GyB)lo=AKiZCVNIPtXA?%gDU^%RlF_$0BKB#E}Ko7r;QDc=B ziK0yiRZd1GDok=x;vgj-qO#CRXS~roD7*SQ#srN~sv`ZqLmQgrjO_~TcU8{7A@Bt= zhU_37GDfhw)j+*!E4*09k(@gunK^)VQdNs2Qi%exgUp99WG%C4sA_pdu#4;3q zjKCrA1#2E^8M2z1N3grqKzJokUv*SN6!l}mkCqW>9x|7;GOKExsxcLnRSuyN&0HDm znnool7?sj6d&VJU1No;N1q(h)g&~vq0?h+cQ@zk5qyie6)Gf-InAEk@ZKct18zIgT3=eY&SqEGYo=uJ2jQ)_nn zH=o_;+bc-#CPhS~H*k=-yY&Y^MtS82ONU)UI+QR!$o5K_Yhs3C6)7U|59RTJ@PNrt zEt2ykk;T8r-cT_+iS~JwRjyvCr}~?@Dz(!L5@v`*ho1eL6{?R&gg4pSAkVA97S3?L z=4-8Z%olhOs+1NL#gk#N8>Q$Z9pjkf?5WD!j1Lq(XBVk)q;)MfWmJwYXtjy?>cUpo zs)^X6-38R-xw8D^rPQ9;T!`7PPkVU5EbW&H8CR0*){e7)_?XOl-m9!LepjeRvZubydeUl9c>LZ0o3&%`o;|~=pU_szWIPRNY5AQ#b%AcSH&Q!$A1&jtH(jdb_RU-UhbloNxfXi(^vv z_lOFL9LnxO^j9NrftCXfbiUxL9s--;gno83$}c?_ z{lq$xWW_b{8^UcuJQ1~u29qEzf@7aw-RY>I@v=|uK;{1cdGh3iEsw^8Y zRNP#JwvUQ0;fXi<@yd)n5nz2Q#H;N|`WjXU_LSQNd)k~X!zpUxL~<5;lD>aqDNM@Y92{R1N?^t4F{KKkvI zBLH2m8WSztTl#mTTlWji^XvAG_~@`Xe9JbXn5Ew$m3w{}$b*YilRGSP`uUxPzEI?0 zCg1QVp~5p`b|SBIJf+~@Dv0ov#8bSKQ6q7l#0)zOr{KvnCcp`BPm06R5i3-f5-S`J3_j~UFAvdwo!76X|btNJ2KNbOFhCt zce5#6c(;bg#)lk*{KgU5l~BPXLJfb52yG3k#_@#mF9Mf3Ccz(%1h?vvSODw!h0BRA zT&9{xX%tO8L{ihf3BJ`U#&*90S9eyDSv0MBjZp5tz@^U++m4PtaW@BUK}@J6rmJ~@ z)aK0;nhcINve2nZf_94uxk^$$I2$m3rZa#HFHR@<#hDQ@%%4hd{;MRtzf|OXW1@`- zEXGB=;}HfHc0Ft^&h+Jh1*bJnT|s+y~%J zo*%x2OHZVLXYj(%7l}jn+PcM zjmzL(Kbz3&8w=Mj@&3MEF>GInVAelrb+d;kpztkHR}BGI^jn2Kb>M~*N?%8)_|X^{ zo*+~(CK7N+Ti!7NluGMj)laeNC+}s_OQe^-3tR~b(|idP3?S5e7@_<-#dd|m&E9h0 zy2pfaVruU|QgbDL(_nCUPeEsHW)A1)k=pMTK(8H~fi}zAG^*EL!U}9(Mn*p?mLtJz z{>X%L)`=se#^|dEeQ;tTPb3P!p34^y2jB&1%mIk0<#8Yz)v@9X-1%%pt5(&s+TWm}mfW-zJ7z6fGzjw+xkg9Hp ztAp~n8o=Ba^bKd*?=L9$U;CT00$;>C@@y z2+;m%ajRGQqZvwt4wOpMen91IX$|x$&hKwW*1uNP_iEzIYH6xO+;x1yP?@u1H7hc= zt5)9O=G21^E)J{B5phG~7 z6*100j&LzBZ#BoTGL#pJ5&XQ^Kwdl&9l)$kFDSdi~ z;!lw2O9`;4bb4nd1rj%;9ANCF1jk-N(sz9z>N!Vloj)YPF12L0m(1GF7tao(pm#eL zI6jR5+_q^#@3aBoEMYY+kQg!9*lf$cA%aoLVUd4QLg;R}$firk{$-anfL!wL zqP!bqn%>Lu`1D0KO|gQ1vdkc_$q@i@=vUp*g$5|@=mz&m$br7d(Q}S7D|k2RN>Z>4 z_At}QXm02J*w1Y`10o?aHn)?AYuJ2JqGu__u!1f;%axy_U!Ok*k50|%ZfBe4RiRPM zJa|+Pa+UKqpZDVa^2e7cvTolf1Bg~NC>9e-d5_nkK;?j>;&iDLu=tz)hC{E5=gJEX25N5-gBvZ@$oWJp43+j zS4gMi3V#i`|C&(AW)USnAzVKw{emij-A{~=v5^KLEIQ3KazNt;0#8u?{-~ufKPl-% z`NiT0u%+Wn6WytE?G; znOs*z?@O@u{ov9gz~z78NH`2!@oyudOTZPhmn=8GK=9TNob0qcNrgwmi17``V5cVu z6>cx>7xX6BT~BQK#wU|Np0N|^`o{MKo=l)TXWxVyK--FPK0N)`c)Ry9%zKwd3_u6P z;EG_e$?>Fq^^K$ddXtd9jnF2alTdlRWPA3r*t-`#mu!8PMRfPe$;d0qENagtG^>|z z-z1y>(kR^T7{@+$3gA89!zTeBOmxVz{a&&6+i<3dQU4&Yuc|+3SrB=D55>AY&Jylf z4xrF?KDCUNxp$eS8;@7Q^{B5Yo@a9tOgD)fD_UYqN8jX^|HskMx2&)yrm{R8lU_+n z%W{q|wUXAk{u2gOKBq&aj(WO|a`IK`fI8&FtIL)mG^dt%HW!-nFiG=|LzrHKTyKR8 zSTqaHh`nVqpuBCks#>4Ze~gr#Fq%)8S>6JyO#g|oukEQr*ZQk_nqboyRd4Qg!p?HcDvnJ+dbxblq^R{!IlmaZ z%Y@W><^QAYf(+o5mOkZM3y}d0Lr0en(Wb%>A%|#}ZLine3-lFsKe*eLIDpIBCIMUn z$XNu)*GXGfu#uqJhxv|kgA{6t!3{P^hiIW$bbJhQ8^^6{_V6o@#2)F}CGD9}1`xH+ zYC`ipGn3}>&(z1^?z6O7Zt5u}ot5KM2Lriu(=~wfKjo^cPD`#XxR$@>q6rkM=yY7} zs0fM(3YXI%!IWKe3D>W}v0dIk(wgT*s8Oe>hs&QL!A7rG}Ha~#^cGBK3HG| zxTY9T#~uYtZXl~`PkhyX68LW-ngL|asUy_LNaynA#iU9ae-ELi-by$%mb@cC)=yH? zY7+BC`c3Dqg-9tZMMzG+39jfFQqw~soO&Xu_V8+_dt~;_w{gJIV_#kUR8H@08kG`^{a;gA1SV7u_(86v_OARJQlrbb4c5^7^+$K z5bE_BR5NDN9~_LwO?f+la2cq#9)RMlyO7CGc>~5iy$DWj2e3^&5<gq z_!L(VUtAV58GZtqA5`al{4)tmQ+*@MK8th%M62|;uh6cjHO&>zYW+zKC=DL~c-s}? zZ(tDeked?-gQmv%TGpX2MK+v4zG5Ws%dc?=m@vm z2Eg#}{-_M9h1bfTR=uJm+}bzMJHf<}sqsa@=5w5?o22GjDURwU^f`5=4)O--yPGHN zqXww@{+7`E0Z{GOhb7ER+WG;zBoLN@+Vw;zcKsbPxdZlqvCG#C&(8##_kBd@ZxH1* z3b#>$txk!Nb6Zl2_i>bXI;ll3CJdz$x!gO^_KzQa6T*W5{`uWVKCTT2X9>db^Dh$_ z_f$ncujcFrkAA*i1X(`(6fx#(6=i@$+imuCQh6%a>D3|qeqSe6r{V!S2udpu>u?#1CAxM(ZrYq zuy7XoRyd`4h5S3Mss7v6910cqoJ+{!2TqKciTxO5D9`x(XvsKPUq*4qgEVlQs9tC2 zX9;fGI2CN+eh8GB3DO?gPTBgOIKC@vcr&_@4&OJv6f@NwHzzVcey$N4fVB1{7<91s zl?l#G0ag@c7e>kp16SNX4*Xp4_HyCk{&HtvDBCCn7}`hnX4`k9P1eNKb+|;N-42XG zsjkDN5l!%%Suniy^$4|Q^nhx|1%PA5#~Glg5(-M|8}n<+&nsyC8g;Ry3?B26ZK1~ z%{mnnZXdlmj8712IM#77h5O5Omci~8 zRI{j1i8-4-8go#?b$&mVp%y_}exp-akuu2ymmdQ%epJ0sgA7?T6ILsZany<80K_sX z0ae;?5e$d4m|ov6Jl_TW6u2NY)aOKx#yS;2k(oplYZ+AR+Pq+H#ZVYUW(ucRsrV>K zR-yzA#hUOcHt<)8{EpBuaX$H%Y#(QoOLZlrJb+eCK|@mxp!^*kbEcdMZIvox2UE^W zD&>5tYZCbDWzyB{I?ekf{F;?D>`WMwKg~zCOH1xvS)Oj6YLB&a#wwg$;%8)G(|SM& zJ6u;2r$w@~22BOnD5SWW3PR;<+ZCs-qz6|hq_~PU!ZOarv=v4mHBs}F^=W4~CcV(M z3IqSOgpu1IA?ZIZ3ExrltFk$qA)=`)kh?BX-mmc^9YNZH^BEGZb~5p0)qvRw&8-2< zAtS3IM~H@KTh@NgaCF>MnHNIh%7({VaF7GV!wX~GE0R!(>pVPSE~Y~{eieLtGcD&D zY*%g1Z`!wG^Z4-XnDx|#-q7@Jdd$rzhCRv2i($-sBpuI^0XUK3eLIPKna=9E9#X-h zq1y~0dC4YWpqDW_C?kh3*)Nh>B?#t!bmIByZ}Nu6w^CXWjrtx&zid}Z z^$AuQgpc9UK+&p;v`1_2oWX;RPeq8YwcIby3d5%vx@ZTYYeDz<`%~WXfNr_oj`E7ZK3ZI#)9)BQZ?JUu%{BVbbGcuA#^>V zm#zYQrC#xaJscy$w(4FE@0Db?+c4_4ZL7mR5Svt`vn>?jbWz5Pz;~vDOMkhB?hKym5ZZJ<0EQm zc;fvNb*(^}K!Vgv3v_9?s^Vd~U-FnMD_ zuO3COER9TGjleC!EgMgv_8h|svg^p8TJ!~)a`^XaB7QN!zEU+QtBwupT^VvshPLc5 zhXnM0H{M3ug&+XFQxvBTNWCUC29u$zh75>>X;mPvHZeP?Pj#y;zpP}`?DEvmVN<3E zu*ZF;wj0X(Lbc>|=sWL4XyFHrK4_N_ZknDH>ddTnou53_MO2~J`3cr^HyYp)O_!Io zCMs%IgoR$`_f#hjvO>s1fuVd8LQxv+8zbD5qSW0ZkvVmB_si^c@_l54hiy!>qp;VZ zt-n=P{IyiRLCGC-a@uvS(cWKY5W#Xf*bt6nE>TF<41g%g@(Yag5H~1kg+|WX;OU=` z@xGo8?n03_E5vi7nj8>aso|*vI$pGlUYTlN4Ixsc*wM>}RLyz@BXihYQHPZFCXjx= zklIo1-pbJvj&2JIaQ&tyRPi2E8N;B;nIa-y>Y%%&nnQMMO z38>n46Ea7IRV(A$Dy(F1as^P{%R?bS9gS(M5E8{}ZLx8wUIq$BQthE31DsIBM$C-y zP`wxto_Xc3Y>}AQXWjyp%`l97D-`r3TUGhAimVrB+9QkU zRJRmwTk8XzKGj97-|N2OEuvm}(jv>9Nqz_YH0_+MTu7N+d%?bTXo8>3hx7L*QcoEu zEKl~6E=s>>KBmoNT~5EZ&R4+nH>e@}I$r@-6i#gDP=0wyYH)fiQ@`aH)YnxbXxpc{ z72&O;EG%Ek&N6j%MlmZY0Q9U%oj?=Wvdl4?cZK&-PV*6Hxc28eeGH`VeV5W_|K!KO z!qW7iOw`E0(VrMIyIi<7K7*1ua~z>h922!mW87h?T)A@(kTp7U=?bPmnL35?+r33d z-boSZk~pUc%EFBj38Bz6)P{2! zPqsWB0Zu7!+F`o4Ip$ZTslpHcPD-ay5p$(6gf{NwIEd0Y0Y}s?-ZI8hklufK54d)p z#mtS~1Sf_}QxqynT0T{|Jydc zy<}S^AM%DDYZ)=HDHLkhAYJKmhTD&X*nq7hOV{|#*tDJO_#u}$RNNji)brEffa1D; z@d}UCe}m(HVP;f`^QGH2(#Ojxs}sJP?EZ?@vRYRWuC&U$*`$60te+ts?<>>PYF9Y~ zL%$2gMojw!3b!j3afeJ#?qXsR*}U~I-6nAp>``dF@Jx3rrP=|+A8!g;Yw!MyvU2;DmJ3R6!q6ik~Gno@P2r zvvMtBpH-ydb{=e3?=KOil1AtXE0Ti026?PBZ8WK{_^;L)BDF)c24N%$2!y)K z5YmbI@GEp$%QW@Pl26+EdaHA$%21uo76+pKi$u&v;wyubSUi#kGaa)ZDcvWzSoC=Bg-a+eFw<0i7{<-IT@LOr)CXf;LVbASbC+-rYptS4Jrdf zc`;&Gd1~b_96k%<^tcS(GG*=zupT%ro z3{W34oL&yQN%6New(gDb{IiT!u8c#1(0}4+@nJpjgv+cFmK^Jtl?yBb7Zwz(A()2N zZUedH_e&Xgji>*QbrAnjNq)RaQu+R?*U2iGeC=NG7+i8}f=h^L!$6969f&rJQb?oW z531e{bHOcDg?6pf>?7y$8CoGJLgGB|Wg>GGfKDP46IayA*cCk=jlo~fNwnY^I*qkf zVe;&=d^Cf}LCU$iG8wR_fPbSk!T*~p0Zn>}O{3%5BV1MbPesQ(dkPcPSz3+STd3ho z_WZ%fJVzM}ShN*aHIkbGoF|_g>2*K%D|5n*)o$9kDA)Wv_YTiRF%EAJA`w5;(T$<} zm|?bksS8t0u*xz+uHL}RIh`*})}N^EUrh~W!Ka(_Gdfwea4u{ZT9n?DAE~;MUp5_t zqqWhvwi7M-b(8h?*ebPzZA076k1~g6@Ys(D8126aT{$f+D`lbIbQ9fG^%5^{M%lB#==>HnkdJ;1A~vOeye9xfqB2@u7E zrlMdOgIKT}%c!HEj)G-yWM)Jx%wR#sg5`|ZMo?@s8c`9kV?nWvs2oK{rRaz#Iuu1T z3IPN{xJhov{(k>;ZW2nE(f51b?|HsFPxjq=?X}lhd+oJ*IsB#}jo^~U9wXB&z$a}E zHc}#5dhuhMlWB&lFq)V)flDsFm^`%rzkG9Wvy6}BZM~V(D>=SsY$AD7>J7Gb(z`m9 zGn_$KB$m+&GLCU!Q5=wARp(d6iSQZ27iLJSMeaA~|p2@K*g`wR~g9 zXA89BmhYNIF-#z6lqBC}Bg*#7caLa4KBrX7N$wt%4AmMWo~_{{HwQCVrpWR?4@zgK z(QQI+hTHxvgkwk}ddUoFhjd%R)wnc)n}jJHI)id;0nQfMOKe}HI!+uqaWoC)xZYv1 zH#3jx1Exf-_0#0I)ze5ApK17=!$sC9;`FRz^y0eC##`_u zS!;hkPSTX*K;{a{&aaz!WTgH?y0WVHxO5QjiYMrOk5q^ zjITQ%XHyt3xcTrZyx%_^Lh;p)+ze?le?Cm|_5@yo&nNc`OH)Ush8NwyYlAm%a<67^ zO?J_ZiBxv-UE0JJ>?n2V=GNO}cO}~~_C=|V1AbP5B=np4Hr0-j?U?(3WXDA?00Nj7;58bgC{QxUZ%M$#aTsN~9L24lPP{O?5>fJV=0*t1o6<7e&SIBxtV36X7Vm zlSYt+r9C|mZ25PiqCiE-ljwup??Q*8A?WW$#Q32lYQ|UC@K(tpMmRh4=gOIg~c02?(O zD-PD_9p_k9iiX_uch1v7vI>tQ2nx4J*n5WU);FnT2F& zGLy7kWPH+pQ+dV4XJ)h=d<-D1x3qROG;Fj2qA;NR)D9U`THqGcV5Oc`mXX?m zVQh9*JE#~|MNCSS!+$YT4Qm_C)C3B*6p-R?s=10+#oJuDD6cT}6isNlu3Mn7uEeI6 z(alUPT~fnZE-9&BC9#DfwkavBOwu+f;{V%{;&IqVxrn{JXOmM4Xn8fe+iDh<96WB! z&?B`Hy6uD`hHbe-R~mO`l+1r$rYk*?t{(>2P79OHBoehWO=YlFR6kfBEy>lmaca?D z6GQ2TZcQ9Nr)ZpdrCwxnd?IyJ{iQ{>>6A}W`M#ZQDhBW^+rdQ>5`6M>Li$+P9SYpR z#QI%Mp5K`mTAy8XS0Xu_LEK%5`s|^A=yg|Nn-8pVbfnvk1Y_8i?+UjxoD?k3$Q64j zx+YSxi2DJ%_I92ASIA;pX|iFdZ}{>Ht~@~d|Fy_eowUgKBy!^`QiUUu<7);M9hz9g zg6-X$&hKuNwHd(*cDV?sg5!IcS_kvospD8>8MP#kz@H`X_q{T z*zOfUoNw3H#tE@2sOiXcHD;EF#+)0)_~9CH5yj>_3^4sOVH#Xy@1SWpblg;|fi5)P z9#WHU+(-(NnEWw(%$TaN(PwPLs#^$-F4H;U?g1({l{;cA=+0wfhdIe25h>8!W-*G?Do zl?X{*4OltJRj<%_jZ4u#dM=eQV4c+H5G*2&suVBn z^~C!!c?4a?Ajh<)f>8d9mUSyX|#~0mx8k@Vm zW$<2t*epbGG zEea%el(AZ+R61nCnMshy6n#aRyCweFD-PcN(rg97Cy%>2q-~1qjv(t^42z>rp}F_!Q3C&y;9L1Z# zrWTQZDlLo2*M|gdFPkA_-&ll1_p+Hhlnfb- z)1=ghLWeZAZ)emgY(Yh{MwoU6IaN%MleA!7SP`UlLh3s4=U@&ksW#!#XGyI`xI@@i zRD`h2Ml2;v*ByrfBxRay#xYba??*6na7yHEE=D0n%4KUN%honr#7g0qJ6N_Ao@%6J zeM5XgTAVx)=^Xh_Y`c)E1{6xpsVMAnfb^lPQnb>Np9VG4oS$Z!qSB|Sf~_bpElu`x zXLLxT(tx_8Rd!t7*WU9C$*s?!~(hhPofQ|>kg^Yh` zR{XV};7bgUnogdjomJQI>4=`^wE*xu5Z2xY{`wa|X!ux*mq$JyQJCqB$O5*>h`4m5 zF!^ZwAO=&-2`JK;?n37MKJ9MO9z(>tt|bS@K)E)RUxNG&XxIOimpc32H| zUKo;Oj|h!^OE7VyboIxH0Dr6Np)dyZUNcL6dTeP%F*2JviA_D(4) zGcodvN$;D=NbPnVPKwV*;@kZmh<*7`IEI@PS63FD?cMrF+nEDS49r2vy6hu|{3W`B zT^q$=>cNWNL!#3|lCKd;AGb6_5QXJ{_3pGN?d%DaaeY{+kD%uN1YtQm*0y8h;MC>_ z$XAP9;!S)=Qlh;Beq*q&7Q*^B*__j6S_|-@X)rdOPUG4HoDfJokfyU$MDprNwkZPT z_%2e&8Rr|m3y{XhZ-uVi73d+Kkj)`jI$fjT7T_}k-ZoFXXJ6CNmdtI`wpeYA#%KQ1 zCPy%D4lsRbcMHsvBae$pA{mK9m=!Y~C872`3(nzcuBO;#0I3Ca1pcYukxMyu*%M`RY{1bH)GIm2H zesxDpYy6eZJd|J^A>SAe=m6w&DRVaDVhAMZG>CJ1D1zfy)E@~zW6S8fEO@S*4p{Mw z6tR00=bS|;Sc>NL;cz09X6K4Psv;MucNZ1?Em7YwJLJkCJJf&E8)*9!1+eK%d*;75 z7cZyRF}c)9B+q`H5~w*BLnIu?38jfsfPQ+4u$HezRm)cso_}(P2o59#d8^EPk$fts zJ)aF?pu#G$XC2NnAyrLGc*q7O3_KwR69H8yUyO((kE($9r{x&|LBnd22VSu}@4kT- z2ZTv?l6Jt6k%jRc72=x#?02J}pQq{R5hQ z1WZREG^k|2^$@(F12;O+a2Dfj<4Bz-8>u%O&G0oMvj6SL)fP03aql}+a0rWLRMfI z2koUy&K=~KCJn?{cs^|2!$^J26oS?2kTQQsaNV`|67tpbC!4NM&yXa?iBm$N)%1W< z;X5E@e2y=_)Dh8_U~vIXsd}aM_e41D@a@)3iYS>Sch@$gUR9>teACC_uDArstN#FW z!$?O1H7R1`R0C}B-8idy68xkT=lf$Lu;1xJzWv7jLCdoN&eL5HwLBqU;Q3D*PVBMR zd3O!&ud1*+?R2M6BGE@24)3XUy2w{E0cyDQ))4i5IS)a;Ue)MW@XL=# zS=u3pBiye05;R9Ji)e(oG)gHIL!|f`#)?U_>+gwl3twIF{$B@$)KUCAu1!D0s(XVcClt-yYv1m6Xq~AUW0*PB8T3tihg4s?ZyCY81 z$V7oS(k1dhQvz4;9;PT7Nu%*R@HQ_)sG2^&wY~=5`lCg|Kk{d2SRjt{8?Gn0BLQD7 zW0KE<8ZQ<7owfVQqI=m7xuKK%+@B`kGuEwh-aV&RlU)0^NY?%>s(~vR4*As>slxf( zwCzVjvHI{JM)J!hMzKlz)j?zt`KfzS_ZW^P45&)WCR*}1oL|`oMdVK-WG^xmWRR|I zG6dTkMkbOqXdze4)t0v-8y6wH)+{YfOPFZ+!f%x4VRRgB?(^7(`>R@wKR*oBH`Ilg zbKBv;xB-iDm_=pgT@0&9X9QT%=VMSd-@S8Gm-9%p;$ns0%?-(S|9e53c4#<`Kko;; z5)1%%%0LiY&rc)HL;ngV@yCGhZfe9`&oHEe`6gthv_G_#K0$EF={R45=~s7kyv8{$ z)cEf6M@U-w3V=18NbgtN?9w`X{deG>v@vW}K{NC;niMso59sGB>6skK1ptS!Mr^hJ-yFU}q9YW8q`T%F~bvC~I;wZ-;^OXP_ zxjTaY;=Yn%mYS2lJVUCU?QzO(XAc4JlA{4%v7=*lcN=J~=SHzi{8=Qv1iITd3ip(J ztbmYt>7{F?fZfMC+#Y41u5Jai^xg;^oXb3hn<2@gbTgjk`WPCe8Xdjfm+VKYBKUuQ zg^+__G+KHo&}hU7NXnZR+ylQ)n;w~0&WBQU1%~1OfF$n9YWhQ0pwjJ<88QMnJK~so zNra?3XNLJ2#_I}$-BjR^2|lhcl#Ic|nr=We-(na_+4wabL&n&NfT4Lw#DM$|f{ewf zj^B-jOUD}n7-kj2OI!7NFdg4~sU|U6hVyWy+R=25L6AE9>^ewo`v#)SXZ{qT23;0m z>G~d@ugxJ*&Pcdcuf=JI9QElQd_*9`sxt%DJYOf*X=olPGq4k1T}8rNF{-;W%J-QA z!E8A2ml8LI-sO<74Nk@pAuJfrC<2i?Ac8A4%$)NDt$~LayF>xxVu?nHdnavsXaNIsw>z2Z+ubL~^|cpk`mYt0wIJakA$Qlcdu+li+mp;Hwr3 zcgdIGDHu`<1w1AVjxE7n2FE(EFEY|})5Rhcv^T0+u%5#H_ww*WdE(wzi=L;bwf`L- zowm#KY{Zugwe6?!5MANK_vmVQDBKQ=GWUpYrznmqDr`TcI?jYp?oUsk$#M(lxz7a* z!8AwX4bhUCgVhK(O49uOXFaa>VxIFEk4=q=QU_m%O>KA@nHsu6BI5*njI2;~%4U4; zO;vd(=zUzzZv*&uakQH|MKl7pQQ!O*cgX#&ox^(yQH8zmExHnC&PJT|RX9I98PM>3 z9x_fGLU7Swf~gJ>CHDd>bLxp$pAUlqF;|2u__d)xugyjxay8t_Ua5%VLt3VH@g1`Y zU+E8yTD}S2A*}*+p5Lz05a{#E09W^f#x)A4_T>HGi-=J5KZwH6J9M@{7;= zH68kE3D$HFaI>^cuu=vLycp&)p83H+yy0UpzXfJ>I4g#b<%_eGt%%etCf8B5)eRK!j z2lsIpVZ#jph_4gY>q)+TD2`v^@a-4TTx0|k+uO*McpG2!VMx>FFk+c4qQrAQmK-Z? z3UO``wGEHp-7p2G;VhiW9l00*&y5V2$VNmm--=trUStLE|?&0EHa7tVc=czLW20U>RdX?GLZx{l#$6DV$7H9>&5~ zka0TngN57SOktgOk)u^OwjA(YC*kaW5ze7qgfLOfSxRgI1XJE_vOTSZ3J5bIa0(xA-OMJ2zk1s_gqrTa;pv0{mywo(omZ$&2{EJFfSUY#(@f8Z*` zT1adCTYOy$8vcGo$#LhD$G&EK@@7XU?Wmejy9#Qyy%vMg=Osxf7Lwf#!SK`r@5dlK z(7?oKCKSbD=~XvpjuFIb=N6lf1scJt9#E{2w*@yH_p{7cY%Fq<+!k!Yx9k6Dg{8%N z0BZ3jY+6z1!>C!-(%(T#vS5t#CcCABBV?K}qqO3cf&aOxw$ix;|L2-${B~K24S(#$ ztR|G^(DoK84JdIbKDL^)YJaw>nr)@I2&9VtnGI_9rl7{&g6ejTI>n-okiGoKh~@G3 z$iP3}pa?D~RAF6kP>6fUB%%)dJQqUWnRz)SRD)^nGTCb;rtz#l$`GxHRjuU^M_ z_luB3cS)kRvLYLYJd6lGX}Wfr`i1}n>~gH5rQ8#FcYFn>=Xio@bKLH8%opG452%%Tp=vc4`rq?v+5Q@VRO_unj`4^-SycsN}LCTW!3B3Cg^+#pX z5}X)t6Mb65X5euF6$~qL=QIURCU$d}AK9 z5$JPBAQ__iXkhRzA+akvj_!Csy;26m`H8tG?4?aD=K->Vq)URs-gz~ zWj_#~k?mJ>HpgDM^LH#qHXpntKNN93j zD)^}(&VJzLj)szd7vB>LLekd~aTD#bzv45YG(hR}dVFpGu-e-k0?fu&_f$x594%?{ zfMN9XAoXZb>Zo05c-N}PlX6z6{U ze&bw3_dg7b7$>U_+{p2ZrkgFP4^j^lon~b}vgkp|{O*$=_y>Cty*47XIgOjQaA3`pAYDm%`VI>9Y=(di!xE9pE5@PV*&Z z`ZEe6V=s*^T;?G7n3E+N{fkk~71INYrQ-GFp?CM#i5(rP(>Mkux|Wk3oEnoKL>dRu zIx+@-XOe&_QIr8hK~|GmSRiQMlN^z+bPf$Gei5Ow4j}x85hM$!2aG{Ug|`JLDUk=Y zR#zYs<9h6LV?eQ`1p70J@)vr%H`kLLzxMNp{^n$)t9TCd>hl~TuKZM3uhuhb@qbeF z^!wo4Is$~{7e*jlv;%_re||~YoTgjMMtQfO>rc zzQS4}7VZbiEr;M7yeXo7_riC_-2n>c@e__2wo-lfD;dq$fjD11jJUQMk`xY!Kv;Mq zeBL_2(I3v}7Nr&>7clo*$r6CeItL`SgYmV)mvKRWBKIPgc{IVoDx9`=0&BC`C+U!?^N=s~HNG9xs#J|u1Lto_s%hKy#zE=N{tAIR{i8tbNdB)Pnwfba z9N!PHzP$-MV{taAtxc}SX_ye;dq66$zs}r=5ji}FsQStBT}^z=3`bsDN7(Ka z1nVveL9clt;$*sa%V-nb=aVrCo`5464zZPQhA6_C<~GiQ&5ci*ciTTOJ3?IOZ;kKwE8;Al)vBoi#p zt&ONY3dE7FU=TD1hW@YA!tLeLvO>uy zKXFzpKgx;*{FDNNeZ<|^*6J_;3QJkBIiK>QDO)-}{syzO^kE4{)^&uzrhjpM^bB$T zmq^{?VDW2sKEzrl2h@tI0A?Qwa_#v98?M7weL_UDFahVsUjnmr$<L(q+#BA)EHdW$fhu zo-Ksjv3PfW+jvb0;czvi>IMPKD-y3$jn`$biA={w0#;|$H|$XeYDogD{;MNM7hbK* z6m}%VrU+=OUxkRaBbd4S2l!@yp4%NK>s?|qo=`+LWwo`a^*zhj&kumU8BrLog!sOO zSZzxbj`1&;psWH%lu5)ouqKCoS~%cpFYY{||6 zIxNl&ILI*7@$6VP$iDRgnK^G5IdD7LnoFl6iOL=awBS~JS%-$GVvh(d+y^XcF|gd7 zN!Tz;zQbr1xx@DXTZ4Z7U!dP|X6NljiW{$m%C%p*ujr7;Xq<1$yn?0M*)QIosm$#X zyxUO$JAjzF>+%WCE(08@>ine2z0(2YX@khO{wKbgCmqDqfY*N*!s`tpGIqyj1E_4< z{Ozhj>^at&mI1`)91Xf}jW6%@h|;*etZbTg0lj)GDZ{IX#&2Fsq_@Xb6tpk zuNJ_ZBOGtqeBdb8H#fQq(*nBu!|ZG>(P7uitr5%!_@ zg0f)Ty(m@pTYRlha@FyqDnbS>Wx;r~C?IbOLx(Fp&(eP66-S^|gtx*0hQ5XrakB5l zzXJ*UvJt@O&e(Y`$H(J5@viRN2z(ruCoHB@@zVqf2pl_rONUbH?aoJ}X_=p^$PWjS zFbC26Oo_lFYHV0k1Qfb_{}%JU_8^uU5xPGisf~Kb8+n-y#s)ggYboR(6|iR<%^}EY z`I|hvg9e#rT!#*)^X(RWG7zOm71>fsa&B}hW+Who*rCJqpYzdr4I=p3;*h$`IZm=B z2`yO{sb>`l^fopvyCf+^GYr7PeC-C3)3ho~DO?AMpHff~X()C8m)e&Jv4*`&oyv3{ za^ZH8W4AE-N+mWYJ4Cz|35BaZjNBE$jhDDq>+)esmbRU$n!+$!uK%9~jKW)P?1mf@ zVa_|9K*70=8ywSv1R@l?j96=4Qe*cg9eHkv>~^Y$cMrV{NniVca<&qR-yM!`#iaqO zjr|yS?B6mumMJ$mO)%;o4{0x?`TBYIGXD&TO|Q$QA8P}Rb04MlxXhl!H{>E}=Hdw7 zYu~e zyuQGWyPmweejkS|*Y)j`0L{^f0okh`3yIdZ!Zy_pU*aWvb$O0{@oi_P{xXJ@1vYGY z%Pg7O6J(rCU~)Pl{HyA8fS@0g1GwF;s;rZBE*XTQ=h^(fF?}_G4J6m0lPc8Zc98`M z(e;?Fe10JFyO=8*G(g!HmNDQhwh$k+3yinss-##6frPzRud8KoU9BmW0!}Ih`Fy2# zkM@cBqrLB~_SSX#r-dzXmaAy5;#C=w7egouQuc1>Pp6V}d&^vuz4)zMbyY8(eX+P@#>LOXmVp4Kb=UdomgA$?<1^JlOprf*wl$+ ztLgZpXDfa8V9nNK74Y?we6S00a2H1fT_vvK;z1o>mR8ShNSnm=$ z>eTog{8_P?NsWSjp%Oc!O}q!F!+`lshQqQz6ka$#cj9x zoHZess?T9A3PRL#!fPS4_j(Q0blu%CW?PPG=6{G1=Jz4Ee4${!42PnH7ZHOC#2@H; zB`uYlaoo1}hr+A9jO8TVS8^E?lgm?8Q*DU*y&8sPR2BLzN&v7bYH@Tkb1QU9#50ikHgJ>$J;+MsZC@Png7Uy<=yocum33q7iR%S6 z&v4^VaaZm~^0pM~5-r#J?Iu4(?TPCx_58{zLxwU*)@WJ~QIE7*UdV(y!0KL3xft42 zOS@Q*A?-8i24N3*Um;U^lM|#p)cpNQH^t(6#dWBjnT`9oWF>=<9gMUs*Y@q$46C)4 zK`y{f&G8NIgf6uQ!|z&^u3h}KTPG{ADqWFQNl|##N;eFQCZh-xqpPa<;oGdu&69dv zVMQey8|O*HW;bDJIW#TcUex#!>D0Dpu+TbUyh-9hj{4=-^y{>B7W(r*--e>8=xfJa!ToJ#htYgDAs`3u;8eB) zSb0W7a2UbwUL&|Ja*X<`0tWY&5%I|}NV)eEe5peM?C5QLbu(~k8VIHy4e`mq>R)yE zM&Z<}vedtaZ=>>?`A|eecUAvZ72)OBT;@_B$+Mu_>M5{Ny}-&pD`d=fPxu})(s%H7 z4FLA~GDLqz4_Et_c#p9F$bifCBmKvab-lX>j5Nm+L~4+Vw4VIX3Rm;`^JlnMb9P*t9avZ02Zv{ds{!ge*=1L;aJ}y zdh3=>^?`11BV$MYl)B_>p6Gv$kCXX~o5r)$=6*Fg8hZC=rgxQ}$e1_27_!UzG73)J zh6t+}s{6V*z*Y{yse2G-#eF#I_6=|}Ao%JQIalGVNdo))_7DPph;SAW-1HBETAkv1 zrNHX7aOLZ*ecNMz%{*!^qu6r}1-I@gOeObLe12KLq)kTRUnaKW5{Du77pxkeb-AT) zgSYN0Vt2bCg7-*b_xV*Q(~GdM%rA&$2M;se<9&y9?)!JgEc^TjYUktI`4pV|;Q_O* z!VvT}5tXx!G^y$M+Lb$IgeL{~+JxXCzbpNi*yOJX7Sk5EcH{7M9)*)ZJ#yWq6T5K_ zB08U{D&)H2rFOu-^|ukudYrt;0YlysLj+qHLSijx{1=YN?{6G$itIGgv3hntLlq7T z934@vc_)ynt7b~YH36h&qdm6=hbjwex@M>TcMM`X<~BWbCf2d7VQmJB4b)6^TYKHi zhMT_LP{Jqg?kSowgMJjV(**(YLjTQ9i1J3szCbR?FA6;89~y>sKL?|9J37G9j&r?n z&=$U;Fs;Fwvm$8~U2fPZK+{zu=bWr?O#iJuBc4*h1Ze={l3H`AU@E=*=WvQCBA+}A zSn0GOD*R}>GWA>Fj0~t8MwSDmUmm^y6%WzZ?>|@EQ9Ml{t3zjxpw(cTqRw%D_mSnUExRgRCQg>AMmLX~jZ<3_Ifa74gvZJlUa`z@`SMZ?(kUfES^3dI#p zg<8EeF1u2(IGbXboRR897yL8CZ)r!gYs*dc#8lUoMV_;rw!G`abYs5t(QO#C^hOiM zIT&0`qonLJGSA!2+T?-gZf`l=L-IX5o-)g8Z<%R7&9>X(C&O4Bp=2hOqnxGS_0ue?u%=V~> z7`gVgXrfn0#0Y#-lNeb_X#jk#w#AH46EVg}k8Ow%d6zUXBTEP)z5mbQ2`sIB8C;E( zy|3bj7l++3uaD4q>^x_hRM`DO8EEv8_Sd8};MNWiL~ns^3DFtknyKqU4mcRloTf<4~}Xg{6@mNS-wo9@6@_KZ*Dk0*v(q=GzZRzEl|+3&Cx*DFe@NYu0d)jYS^&df>8P74#K7`UIfgjR`!VKxRyb*@)&&iui{&+&1XOBJ-oBt3neKz zZAqSVrcDv15-a^x)R2OTtO=ciMZ?79)%lA|KX8hX(h59;bNrpA9csC-oenagU?gZW6+9{3wHxK2laa+us%~~Q@tt01a~|W0 z*dbV&l;s}M2;!UKU;yV+8#TS+Lw%o)<^Bz4BdBsZ77;rIt5KhwjgYo=90E=pjnGyE z0aV)o#}Lvd$3qZqsEO=+gxdR)AjSCzq}FQ@jqe;p>645v@cyShMfT}($Sm#(3v(>k z1Z%nl&8`?ITi%S`4AzuZ6>n`Zn1>D~JHEa>GUTmqkv_+1m3de2!8XA0MQmb3ef#NpxN6%OhX&63spI~lPTZ+ z17b4+BGw*^uT2QvhU4(HT}1`)ZrrmGg>~rr&iku|qY9Vz2;LtfFgpB&RJ8};+o2#N zB1vJp!!IY??VCuL=-6egaj}m1h9KyT%13br=0y-5q9v2>uMKE;Yx;*YQggui<6j`&{l36S2gfWGw?lpm*_=Ef zLhBmD9`cQboxA&t(G8&vUi*(znmg1!4avGQaZ;T^1#u=G=IGn^jM)2qYJQ*z)T9HT zaMS$(^NN#gYHsco|C7(x3_8dfVIj=jI_pZ}?+vPnA$(nD8%y|iFHPU^<5LK^h zA>)@H=0GaBDEP@_YgKE18riY~95Sjww9S;bRir^IIYk*BmIhm#wUa1vhC{v&&%vo$ zf-Tq`eBba9sHs|q(+`dMx}(XUUue%``I?g>#9{u;nmxJ9yOykEJS~&@1!P>J4wd+2 zw1)o&WDmVvGAbv{Exz*0`u>T3!!+GZomC)b?15l|J`>h%fp<5)L(m-&p&BX?mq38! zJdCflKUGKf!%wF~@q*Pnr&)e_NE`Y@ zSb98Dnjhv7NNT!Sos8&`^8>q{vOXJ{`JVu(PDMGlS2H|~=635r{ zz7xsEPK40mpEyugg*bb_?EA-nwch}9SGKt&wmlX>(}_++B>8SLwq$V_R5_i@cv40-Sq*;2NGO&T|f$1Yh@2faxt*#t09#) zRWi-{25;ZNYLYMhX9%xt7l=x>{3=P)B2lS#w;xCnouz|jKd5&Y=HNI1l84j+`!pw{ z9U5vVbewsG8f5LjE_yV{M6OUspGzF0I#zn8c$Jk`v>?cF&12m z#W&PADTsJs@=&`HmXT0&e8XHKMB8ub6@pz)`=q)e>oLQLO$e(gnQ~paZQQCwsbLMF zd2DHs3YOkGrA2cq9i4iqsQxHuZCezj+hJWZ92VPJu00?iyE2oCS6v`lQeVJ#NmGOE zw*Izl`QnyY1c%!4|(dG}kuU#E#O&mf7EYh_ar&jD+546uTLVE#xH{ul^LV22OytrM68$imD8eXyJzjrS*MO?N zT=`EPLj3pBz|UUj=(WW;s}f8;qEI2&Qp9atq&dfD~3R7dO+7v%wsq(V@uVxe+9J1kPXpSRRlzC zs0h=?;dTW*r{)arUf??#sb{!9@eGC2!jsp@N7W<^gpD0ivy*%4S!o!ZDnN*)b%3T- z10>#1uAa75qS<58>&g)2hcfqP7F{K@J>zW;=i1@B+KS*FEJ-nTe4)NPqQRLI2F*Xs_9ua|Z4atTNz^bLxsgOAHn&`v1jDd7ewEZ@X8pJ3HOP7VTZgCt9e zRFzQl{HsxkbtcN%*N(aAd?6~V5t%j>Cc`}9IVKTH8?RniFl$7BkwGzjXPaE|&UZds z(_d6INk)V>02FUK5eU-FX?%NmSZ4)c2_bTX`PyZqEQRxs+-2KJo?R8lDhuvH0}?i? zoLsQyeg~v;=0><$*3l6}WEMi9RFsfph12onD#-E*`MS-{hBcLE^M?3DHC|AOA$9PHieWC%wd%i4f5J0BIgvV5wT_1da1yUuyA4q1d;Kls%YbLYc(X+ z`4>z?ypq#J#;+)j5UFlLM2nLFLh4+6S!!P!ZV92Bgj9U5y;P}Yak7H2ykB!VE-olL zmSLAPeEkSw>KaL_;&7Uj6lTnN7a3V#kT*{~^rd#a_~gi;Fk7z4|IA^X7l4qTAv*m6 zayVP%IX`l|x)?Y(*6kMx8bI3P%eqItzF;~hS=$8Y#jiOvqOEy~D02vwvSb*;U14LS zYxh*ZM=jAK9!v*#>=2BnVXfG4>VbRP_d4$CyDwd)o*<~|*P?#83jxYx_=#<d_%B&OgxehIV(4Uijj4Wz}%UBy^Q zj7z=$<7*%mJfLw*wOX1UkHhsV4MI9y6fxUdMzEIA$!5N_3qlsnehHn#2qKo80dhj^ z*pf3sL=p0e2?2A;5+ZU%D)w!34AA<7EHu3yi5tEikB5}e;MeeWd>_kjC2R5K_i|v4 z!Qt(Ey6N5#Q`eC&*swb=^DQ|UUstJaS=)dXWHHV%jqA*}mT@P~O@PvRArTAi3FQN3 zz8=%STaLi4L|V$y`%t>Ch#la_QByq&zc(M~ATf0u;(9zsYUR{&V7a$k#d=Uq z-R^4Km3#66ZD~ZgLSVFHIW^5$?oFGq9;9LW9F%DtZuBuMw-j@O_#YY;4nKrSw|bDf zfx1*4UODb^L@cQYv{-GMlNw0)A29)Y#96&0H{^R_^BQaTS`9AR+UZ3bdvIxjXs)rx z*G{Z&O9*ENdOxPp#j=ED#jL7LGJaIaiU3Xnqr37`!@oq7YGHG^<2nSWMoQgAA&ov8 z@87Nn_<-|~#BcyIHTaO!;4dMX!Jr^{X=bK&zYZx}4uhoKIQ4Fut{M)wtm5!`h|ycq10%<7HnK*_brOdj zJ*MHgDn9ONp27EbODU7kmR~eC%DHA+93ENVGaz8DmpFN%VE1+k$Tow-ApgfRP+meA zXN-3*!pfh%f^%*~IFnZxD#FvYhx6l`#E2GGa-x{N;~orT*at_Aql#hPa?ZWKMk4hb z5f=4l9K+nfJ_z*nw4BuMPzvC6?1h%Ob)NTZp{V1XyI@-7(JmP0h#==H->`(u_?Ot3 z?^PaVzsxa7S|Yv((G0=zUYh`u*Slc;zG6JyQ3prpvmwi2%?~(qt$LJm7TQRjjS?EN z0u1ubS%fRb3v>N$5oIxtsQPbkR{tIJf{wsyE_GzE_doDf+!nA9z9-SoJ)ksS>_j-~ z1LmGdqMxaX_(=SaPy@!YZy1iX(2s8K_8N!tvNV*cSRbK+wU31?biWSaHpk*CI$S#5 zcPZgtkBAWF4MeCIryip9dWVR&fYkOk#3ZI5QT@9F+juhQ{xAXHD=TT3-0sJL`Q|E8 zE<6IKVw@ufRaF3Mu5_4W8+@zp4oJBSU;P}hM2TQZKQ8|npbd`r{BfY?To_S9Y8AXq zn8l;p8FT|WY|35vO% zYv>`ejg8U4Y^NTj>stYs1+M_@cpA>mM?lFBiGUr8Z`J9_+vj_PYyvG+>;CQ4AoblL zGC>?XCJ|M0Ux+lXIOO^riR!N=kzd-An3W4dFeNQeq8g6FS5r>Htg%iJ5_zXPLfdph zkT!>dv~o64YoEvWZ4jWU%1Bd(hqFnwcPh}OgTnqHI(Y|x&fog#)!=geHlHQYY1xvk zHUiA69hj2d+HkptSvbsJ1V)yFQ^F~YheW#*L+E5u^IYMH%I@BqNHJ^~tDWxnqXWQu zwFTp3A7Jq9Bkq5FqozKiYEzhe6qUT&9URX^5lpOH*Y;x`6coWP}1llP4LSqa#zZVwCN=~_!} z$>9pelU=t1$BAbkpzClz5KHdb6-4Cb9vJh0WMlR$uW{a8Rzz~?!{u-g@XjgH&bNT_ zqyg#A<(-C4`egNK(pnJPEeMNVNsQ!td09~ODu-Lg6}`s8(SKmGKi^@w;ua@IcIj_5s$jlt5*Vx-}?S|uhV{t&4 zLphZV58D~N>coIIrLPaEqSLzK{S6JLE14Wnv0SUnP~>XDJ7|fhJT70OzSx(5hdub) z>VOb#G%zlAc0ifGuoT4KEXV1(T-DRHvgMF`Wg~|Q=CUT>%Rd5ytQ+O)E=s345HE{t z5CWxZRKf0?0r0Y`BFekA2F%>iU~Y(1)V0+@9UlqnHK4cNNXBfH%hz5XLQge$~Qi%;gu ztB!7hN{*F*v=(d8KX!eXb0ZYQs9_3@jrHPm3*f7o7}Lt9UfaE1(>Ai)A}PKfA5lM+ zy-doF%cX$2=Rm7gspq{J(ow4DTO&0IhHa!3vp^i_3MhI@;1ZA04j37?&j^HbT~MJo zG?%XUYxydc$>qy#;}WJwO?E9a-WZoI%Nc`x#-A5qEw1;k;4<-P(7P=H^M)Zo+7Zlu z)eG3dR~>7$^M9pw5J?4X1~rZUHmH>buZJP>$661W%2WG zbo?^LUKJ2amaF78X}Cx;0MBK`(H&#RG?#(A|CbHDYvipYN!*awlSvztcohwZYtP`H zIx!t7Y^u;%%(4mEf-NT^LVV)arjY`lg^_tq7KyPmi(*~oK#&$~y|YNvxUr4}43r~k zE6VY$7K|MYdTuQ@yU8%iD9~B6_)fS@KN28`SSg>gJ`>fk7EalVkU0cP>xZY|@jZ82 zH)jRCB*}tOi%aZ_^HROMAB?oLhmf-8tX%IAA*D?|SZ;=gO$zK4#OnZA#Sz*V z0sCCrJ6I*v+qZ}IB0cxkBgO@=FzL9*&mCdPoMaXsB#WbI$RrVW!tvto zI9Y{s$|ASEXu>FI3`p!cAg$X)PNp(%rTvY#)6rHKZlki zRyu3Yapm?6P|<`_#pRgOh3*b4RHO}OXQrYUwN|q=*QJwIloDwk%1(<4{}EId!)zv+ zQs(RpG%+LjzwH*DM2JPg&`2sVwFx7HvLI=at6X$THP%C(l%~v5Kf1&V>ICk%nZb%O zrqy!Swmdup^(wbtE1?tVD#h595v$13l@u)DH=B9;Bv_CpsXzHkI@GKeG;5zaE-$ZmuH zH*IyHkco}5_cuFGK1=y|M6w$Y9~~+C3S;B^$0rE*!oBp&LLSAGvW74vbIv4>epAd8 z!B!E}$^&Cr7-)||H|%Zjv`)}?dw_iS@09Rk%Z$plzBs%&&X-YdVkN%ma!5!-at8iPH%xW7^c(!aKe&1q~6qTt80asQPr3#b8cCOkY zliL9l`x-ZC^iYVN7@?66Rc_tJsbe+A)lS(K2Skh6!P{C6tOI>x%O9 z6;JQp7U^l$dweTVW+`twS~YA{7L23A$W)4KsXOkHT1$6k4MhkO4~P@s#1%@dPW(Mi zYYljMLt?!us*lqq&q$>9{391lN%$LM`j`ZKq73Q<@=-I@GuptoY5%Nghl{9XWvZ%8 zQL&k0jHTYauT&T&tjzeW0VJwqF(uGfD(NcGzk->*z{clr_+XvSIGVR2rRYf!CU8=S zPgB9v6OhS1J-`+oY8!C)Zmk9VH~6|CT+=35S@b5Ye%=Us z4LpAE)?T-$Qt7WN4zFgeQ664LCj}sO3Z-`Iy$S`B@{S8a2jcq!;7^f)7i``&9-n$xqb?f`>CQ{M7OG3Q>PIlU#Zqvu&|RqYfflNTE-G0z z(p7?lsuE9NK|Rw+u>pH4&m6muLRSzoMb_@BE>F;4XbwT@R)c60U7=NdWs5O0)mF_z zCt$9JhD_ZhUng~wD~NSFi3F_+G(Ux>|8{P4A@xB}>xmbOPPgd+X(SR4bLKB&K2#jR zv%lJt4=n8yYBWDAk4|Y!TOQi%xiKw4``o&90lcXKDMnAc(4pRKk;v~7%L*)(RfedW zL+Uq?#Wt?bQ!XxJHP!CGH#QJ&@%5;&*l0XVeG6<|5sMwRHPgdX;9K1vi-)P8Zhh%$ z@%9X}lD8V`9maS^`qj?ck-nlZgxMuHYikj=ZEi%-e^3*0^gc5{tzWKl6#usSt0ndk zvL<-*`M4xoam~m2n&2hLY^p;G$Ngd+u*(zA_;b6N5t3vQF0PJ{Zj1z3c`IvS&Fv~` zOE-slPRF;smLl~&3va`EIpqPt)8xBE*3od6xhg7mJ5Zkw3-`S;JF^GCgFjXy+)(9! z)`xLYJvf9xX51E;8xr*-s6cVr#F&_Iup~;ScP>dhREZxL;=7$-#-D;_BIi0tpDv{O z0Cwx)?>gLrw{qu@%A%xc(Ns1K(@p5R;O`s$5E6B~pyNSm_!^(j43U4~!GsrggJ|zr zf%%}6(JhXL=F3Er#%P#(7XW`Ts5Th&UGSj}Bq@Cr?`uRI2n&Acv)b{k;jrU zIdGN~Q5KUax6|l|A-X*?nR!?U^bF#a)Nj%wPJYv^BxR2XfL{fwzs}(-4smXjQr?+> zx37J+(Vr106LMAoR3@5}iwH?A6H1D&r}#fSJ7WCVIz$z+4?k-kvSLN!O<@!b!HXf) z5EsuSWC*JhsV9KS5-}1~;a81>YR7M+N+mTT;kFo<7zz1L8_0uTxt9eU4U)>wxlIzK z_bg(1eg!|3G`>idO8F}un#To}MwA{z2+<=7BacVxW{wyplseoKrX?}^lYAhJ05dl1 z+hTFd9jHuTBx!NGD7`E_)`ZzVnS`E_l5r9>R<%n2#8pqts8vTWo36fi_w0*c+;Xw( zEGm!8AxqU+aBDxo(GvX{U)vW*y!rxstz883!_=hWU{QLg*jM!uQagNW*F^OCiy~&7 zQ^E8n55ikILF8wri2p2`_&xBs{p2&pf-S%dIZoD;wh2~>tsq}Un->H64+$js5qHP_gGZWtS`ZYFR)s0W)MJW*2b<@6^+YCkq!phgFP%H_Fr6C@>D+jv6`v+AQc`lP zb5ndAB!cpN$!FZRPQ-sa&32!w_YG)7j&%&>9}|mnLuy`U2oJge5-ZODcEB8budS5z zOuZo_Gxwhe(z7qgnTB)!@p24C(49=so%52u0O$r^5s>~voMXD+47!P^q9;F-BMkfj zg5$oD@3nxBBCf|b>K$Nrb%DTN+bH5V$CBV{1l_L)j-nLZs8s=+Zuv069sLOC(-sGe z^L7d0vYPmvE(5l9dC0Vuy6o4TOvHv!VEeT!jTP2h#5c_~k7?K^biQFjY-bf9*@2AA zff0T8+~`4CNv)*03k3KSlguc^=QimOn;q1Qr_OfD(#~L}205E}qU*NB+>`&e%|y9_ zt!IaNY0qkM^`w^#Lo=SPz27MJ2*7 zEy4NpDn~sYEd%*zGN`{8=G0z{!7c4CxD(fcf=?g+q)}Qyo)-lO6w$c`RT) z_%noxKacp6vpkYRnuAjv|8IHZYV)FomnT|O`VHjo3H92qozUo5P>wsMF~XgG0{gf& zduXXBS}y0B?5XuS;`eh>%t+hDRwlz2d*>9TzSUVm?yzGaICd!fKfPu9_f0UC&?Rjwy~~*(#^lJg>I!$ur(bxW)rB4b3zy?z zERFcoAK>m&`zmQ_UsCyVB7CPh>VhVmV5X%{r*Bmqi7KR(Pvd0y)={a~7^-Hl6-UKf zQxQIJ+Gz|RHG19APJ}Y3B_$)%&AxW1OCZR%rW_~^XQuT${fNJN92eda5I$?7N!TDgri(F*vOXdre2;&EwR- zDD7iIR<=NlF5l$a$3zvY5D~UUZTrn}5_OQ~gP`oVJfg2cZArhO}jak(L|xdGL^G@#xH)3y7MXr*?8J|$WdUE&ga;^LR?NUef5z#a(~ z%j0^lb?B~7mg*jt^>L03sEa$0XvGou4t*B%gH%<2{D|1WL-8%Skl6aws-s<21MKlj z5PR;5Q@k*wHJyypCJ(9ndge6#>$VZk#BU(s=T{OVpWFSWD2{cdYelEGribg-gV>Xq zm9OXqVwdIkmVAI@e#wi3v)U8>aa_cr$YYQ=^jUoGsrE}+*Xd1ZDX#+H>$`+>T)(?g zrn2*-z`LO*ITV@!>H*>_pMU>Kw%Mq@XGK2a^gT)7>z@nL&ZcUxQsZgKyK`(pjJW4u*?E@{n~%2>!po%u#&(-2wG}g4CNiLZW0I55O^dbeTKK zw-61vQ~yY~$DK-_`*uXsbe!yWadQ6_kSOof5Z93lO3~qa5GH}&Am z?X9FLlvX35T)PTiZby8VJrjC&>nS12mG^h%tU3UE&o&B z_Sp&=Ly|9bYP_H3>mG)>?x*P%TS@9_^%@EZ9y#N3lH)b;Q9$6>Ay_)JGS4?mS^+4^lmTv{*!&~A;{3C1z^L+}2rIf3-?aw> z`NQaNS~y->i9jtJ2~PH#%G=U@(n65#h{KI});`0%=nD+s+KHWz zT;W;xlnASVLaN_XTRQmZ++T&M1b2UVA4-c+kDueE~Uxu{Ky1MVK+Y}aUS2s3O zZPZt@E@$u>>6~Q0<8yVMX#o#!M2Q(Ai|n&zng{>1wT+3a2-u+1zc?*ogRL-RP_LSJ z>>7OQIyrLwz}#pK=Qqj%x^ID9;YvgnstfMMu`%tS6NCJz6T$cy%P==ELy8i-!T&~0 z$D;b-*hUKrLSxTHhb_SEzOe?`n+?e|$gcS#Ichrm(0G6$EKSZuxE9u->TgJWs~-7p z5k+-DKuyJGFP|jGPfU%6c~g|dD_D{YCl&;qKIhWk zA9Q&UuqyDbIxZlsthtVwYH8UWBR02}!|5Dz0jGP7n!4OkcyoulG=sz_e3CK#G;G6r zoe3YbEGT-1gW0pEhTffWuOpM=-^6>)8X|vP5RvvCLICq6!t>{Y?4&lkP% z_-bzrlu6}xX=a#nCqj0FP^+g%?2~iu|`IgeG8uB0opQvC|M>S(x*Mp3jgyJa{gjxcKl&_XyB9-?_4C3Eoa%8HM2>T853p$u zfi*qe$J=rj$wV51>{hjxoWWq5ksJciYHwj+?9ziZBrTp4FeHu#{G^2I2YG zw8;a`r_1PlA~F+Hbq!gvMbhy;S-6H;1*d8GGQ@nxsf5_N1zPNk>quu8mb-TBR7e6WQ3qGRoM07yj+c^Hs2Y9ynmE)vG^;JIL z0E1h93s`gjLZ@$ZWBH`hz(4OrrpFy0ux)r6S)DNuaBPwR>d(Q<5yY-}7p(Og#4ej?G!OZ9CHU$dhK}u8;=jAoWA~^S zJ@Q8qT)ZBv+h=OAo!{}%z0H%PFF40X+@Aza0i?AL0RI5X+~kwlt%d>bFQl$quUg;l zVT15tcoK&Vh_6m1-YoSf&)-$bokJYVBti8?z|Iqhsy@tJnmsah|MM zz5rtO^#*H32SaUi3Gv(RfSBArAo^>c68+WVjQu@en_3y#*cE{CPXes@mORTo`w;lQ zZYK4aVCD*84!B?)ZDBFqd& zn7ILveI+2}dPDDUh%DS%=xqgRfyqAwlzYBm?dMgW2?~wNmThoex-iY1IBhCx^F|=x zBShOiV3mLJP(EDpT<2qetpgF$d~HB`I}R}^%fUAO%~KKIDG4L| zJ06Gkz9hIb65+Z~B6d<8}h; zTJ0fo7Qy{L5cm;Vt5X3@)FYOC3Rruq&+&vZz|ZXrZD4S~kN9aEr)4`9vzfmHChxAh zFpfv;7CjSdx6n26XTd#w$yt+1KAXh-;5&TX%xM-S+B7?f4AMXJpMIK>-1#zrJI4n) zOq*tD)rfrOUg^8!equTi=5&jXIE5`6;~4jy^-vwyw$?{kE~Vq6k9yeb2AIm!_TBue z;yr>DQ{=acGe802e35tnLfz|NjV>0~i*T72RnUv|*jFgZ32oX7O*lCS?NsH#q?-mq zuWSw~N1jo70m`NW-A=y3$Hc?%?_qjzhfJe-cp@Qa^pN zM;Vd{{%aywr_F|`)_5W=XdylNb9IU}s16Xid?CVs5`>tP0@y6_O)|yh-Zx3PDNXUe zTE>j{&dJiVY%yu=)~{f##6GtCV6ckgeSRiO?^BAwHp~Evy@yQum4|%0Z2(!N$Y-{d zf$7Sh-O*HrJ?`58rUeE=WuUpO#-t|WIuGm%0~V0n99v*UzlC4jO5Bav2w1SwC+<=} zj?_)R*H9V0SetbeELgA4W#qn$z*X4CeWZF$o_5Gsg+@PBfj;!8wcWxIk&gXi9sBPB zwQogC$mqBgEINkt1^WfE)c)QiZ1=G8>hiEBN|T_3*v_Ktf3hg4e-_Uni+%7e{R3di zr@C3X|2vL{Q4-1(PR@0rbm{Ah($|NlAdQ9}%T47syImAOxA8u_`fFE@gww80^+b6Z zX?D>B#V)$Z@oW>%swIv`OF1_7Pw{+rkS88L0LI@5CLtkPu{Cl`iR56WE*Yha`ZJl- zwn~7U=mT@|;wMLD>qpdBANK>)Ei1eCc~s*f*Diw>#p39__kwj{)U9aLNC;Q1wM6dL zOKK$4Nw?OY>RH1q4ZoX&3)xw!ls81Y(S3i-eY7imE-~erP;=FXGD}c&D$ZqJSNptv zKrp&PG?(&D%t!YbIyz(H^9%(NdxX>`nUgg4XXZR*lbe3!MS7B53wf^@uWsbh@V8YK zvMS4AZlqqr-UF7dw&s4pb(T&YZzkAA&Ft{bKK?l$+L4}Nh(8z1-3yI&2Ftu0sTDVQ z8ac|k`256owu$6r){kKNU|O{eN`m}&HIK>0Oc&tNF96$)0r26$kRPrt5) zJwbi%)2k7e+SlWrApMR>)hzg~WhA?9uy5^CrM@*jmNrSx8HR~sF!Lg`!^RM7Ml#d+ zRKWhoGZ|}rEJj^%aFt+Vm8Vk1N@ti*$3Tc&^L^iqn4B2t`%F;!T_7C zP|wk>n9P@}vZqPPH0h`iX!j|3F$5}42tH`hY3hp6k8M<%&K<357DakaRSGC1Oov_BWPz5w9|>T z(`$n>iQSb}D}b~-hsIagxDd6UhbDhK(q~CvDY8wP!Il1Gm@<7PnycMzzXmYXpP8S? zd*+8=C{R>qjrP5IwQs+oSGh`CzG6jrd9M}iDGfi6r>ifRIhXu;4*>L2gY8!v>@EM@ zh31%HvD0Brq*YJ?L@Uy7VRH|OtPLpBmVf#bq?wO%o(o4j27T)@9)04?0Ur0x839Wl z2g>7OpBo%#0a4N6q)ENkrwv|CUl6WM2GGA(qyUHK+{>%{rRe~6Jw(@iqK9kctnqH* zrOe@6x*Uf`alS6W-upeN8>PO%F)4@=4FyZ$=o@-}V@hsf!42y`KchuY#4W2jfm0mYGScp-Zzf&k*<) zV4pUM5cXui$)s{a72E+olF`%mkICd)t6X)*RwYC?vit%cqCE3#{}kI9q&WRHJ7MP`2CEWv)`C zteS858PgkV`JWuaOD}q*XteCl9(-{I4O8?9v1L~xt)PQPpV$>z?g0j2`+aP-()QC#9JKy$5qn4NYnmC(0Lrmrk;>@@QRO}{s5b=&Z zc23~fvCqN>h&gG34`=%M#3%ieaQZ~z%})f=HE&{}9Z#f{5@nJFQecc6($|-mI*6Hg7sk&l;y!FzTKPse4Z)l1o3o z_ohl=~U`T0*s}2jDZ|`<}YMYVBOO;&TJOc)fEi;jfATvwC<;42UuNadcn{=C~AL z=C-LGLnU#hyHb5kddYqe%PCd0h>L;84ut%*AV&->AJ^8 zWb6YnfhoaWlZM-D?o%;)&vptM67(j5R0_^mBTYpyJkc6{fmWq<1r7nhq6it+Gnp#P znP!us|MuA^`;yho&xiSut~irM(z-Esx1o6V5xQ`Qeg$cW-h-}Rk+ozeN|>$y~$7o$fqTC$?#{P*xtL= z4SncnPzE!pq_XFc)Bi$^n*CCc2B1f3w$>BynUOb!SnxA?u0IOSj5BHV_WNE$H@O}? z&BJZP&r75Xt^oHisU9OO>@Vxpvm+VnNafVC<9T_1Do3xFIyoUAsD;Rz)|xO+=0N0T z*B?i2(J2t48V6Q&K(S_}1h?d3FINsbeWy4#-$?wT3y50$smHFvDL%L3u9m&(Q(=U> z`Xhv80ouTJGs zd|_dX+3zu8#BB~(1b2sk9DY2C;9<8JrU{pU_09BDk+NBMb5v?5*wCZ?bS{Sl*k^G+ zpQ(Z&|IG)1b-j=}s-7>AJ6`tlBD`Gw+N}nwKll~$I#mZ4CceSYr&p78{5^=~K@!KG z@(>n(in7}HyFLTMUjr~Wxol}LnbO-ONgSx*lfZXrR2c1EDQPWlIhNytkD|B_dvpLY z-J=#e7_mtC1I8^EdL%53ULI2p>TBPE1h^WYSlU zw_{8m+yQPc_QSH-9aB+I<+Z%#j&4g*J%|NqiMN zjB@b`GUx5e+YeMjH&oZVJn!MNVr%Wzc)Cf>V9j&%nqFiui!WpSl9w+}o5Q0sC6Vlx zr#i|c`Uj)g#Wij~DGmRe~iX(X#Fcc287eT=-+1RE^r1YHDZ4P2((G24(mSuPWaK6*wo0CoqB=rDjBR*1 z30EwTPH|ONJEhz?H<&znGCb}seX@#DAUmc^H5iun4=}97oFR}*CRZLoa3sF zgzw`!pR1G_J7sHp{66&5;mF<)qABy)9$L&SrrE0Bm1aCRfFmM9BgtTO1GAkk({iXkdIld$z^JAK z*2B9qEGdW82(#J1_haRlV%Lp3Y%S~PxdgtjAg4wKp3IzN{THu(WL#;DHT zm)N6J2u+a9iLPLxoh1FOv^pB&I6wt}g%MPB$n26GMG-<#2xWMY53VJW(cmb%)%khVP~3yYBcnjzWWNEX_7b#}=fb#EGB2Qhk*a zabTllEoY6G18Vb?d4D~Jq2|VrM&htXT`sQ0%0X7;#|VI5G>M+5Bfk{bo^Vx_rwr9Y zn3l4FY9jn_ggIQ&{xz0PJ?uC+mW&^*g{bxyKGWg4f7K~6R5i6!`U=Txq>N)^OoR$O zUouW#D^BFJie`(^7h<7-8A8BZ6J$>9SNYbYlN{bTEJIt76A4g~2_9RmIN< zUi|@7u7DQ5$s;?yv_sEy{Jl^sUL%u3M}V=KOiS9JQ8b*_zoZ0iUeBj)-1lxcX$Z~z zNZ=)Lqgxtz)Q(GUTBx*=X&RkRnM|uPGI6<@l!r+#O-!nqle7!p`+DOGvM4L@X)Eo) zmOB-eWXmL9>9_k|#s}WGCM`o{4U-}!lv?`?QBeaLQ`ue*N;y~C02-4yc}R=7mrUe` zT)x0jaH()i${uLQW0qX%&H=$N7G#uw2Lq|TX>$G(^+Vodz@QBmTAb@7!jW;no#T1|*Tn+hnlEj?rnuoM#uRDT#W&~RVoWwDLkAj@Imb0KcQ+$jVfqSepl(ag zeep?VY!LQpcTV*_VAaEY_QZW}rZEhr*<3U*X)+(R0q_2l%g$ERU)Q?9lROcml@v2b zDoHPM_}qZER37A8z!UQ3WW|QJnyZIQqnWJU^uZe#R4JEhh}%`}4B3!d7sz)%h_=Wq zZ%Nu)17(r*bAz$$S*nPcilS@NP&Y-%KCT5WC}6xAni-^^1ERSI1@-C30@*Giz1s{3 z?r_UIVn20ReD7#plsInWjp!zZ@;4*nge6Q!UB|Esyq*x#Ti^ zkU8kUv2B&c5E~PR-fpth0#&l5a~}4HNTBY9`UD`(P3CPGS%&XhmWtU;NqFK2Ss@fP zoR*Z0CF3SxYB0Un<{mo?e>rH}2uI&)sme1_(k4g@W++mps`L)aVJqjtp|XXNC@a0~ zT90SwSF4!&5p6e!2m7%4=3dX!$DYDDrdw>2O`VT8JKG=J42Z|l$YO?c`hyHHQ@VRH z4$psKGuop2UYqjUeV12PqjzLMAG zb%N5VXxs~AQrq-)E&2ph-lZ)K{vT^gt|?)snzjT=JNiw-Q7!vuxa;@HaIfyO;pmxa z4X5@J8V-MB_rvwv{~H?oInk>)r~5zE#f2SR!l~6$sUy4tuiYYlZYI!DZ2~|im5Toh zP~Hq2(Z&W4MHv27iYm}LcrBHNCZBR)0l3^;5pe7Hu=Aq_^wjyOM|3?jZ>xnppzFXh zRZO*nJsM6Qe$Bd9-&|+udiijkhm1rAufBEst;*3jAiWrq1W4;JNYytxc;U?tJ9_mF$k*ucTYbX+@7jLYjj47EYsRu&$MVQ( z77X=6qQQO&C)B9xdX#WD4m*^e)gJZ!GeeJD=htEn50HoDh_*#D6aCr?X&Itzp!&kD zP%rnHT-AP~9v%ZWvT%>uUsCQTR`_G9_7_(ztq4ERFBn^@u~z9d0w5Tfj5!3!$9MVO zkr*nr=>`kfOlcjYM&zZw2tIBcZnMAq#~$D>J_o&UdkZq-fo+)aJaE^{k4lxu2Y zQZy`Ux?+-VTD6?nu{?=z@4> zS0-?Fa2=yw{SMa{ST#tnyPddXIe`0Jyg?42YcAoTx$?QeEv$s(*VjQKK}k&bwO>q{$G<~Hyp6x6rKG)=!^Ug;i>u|wvWF(-9n)Qnn7r6`t7Nc zCRmzRB)Bw1!M5%fqo!ry1Pd`#$kw&~ZMVU2n@p-BaUIPr zlSv8?3N+27g9F;j)XU|XE~;Nj{8mPCxJH-dWYnz8AtNTZ#0r;69Tr``|EycukbJak?erfI+_J}<88MaI zj%(n@<_nZ`Y(SE=lIK22AsZl>bh1nMQ+F1{u>cgCpxMn)JvtiUhXWiPR_QD&o0!z zIMTqWYQ{Mx-uQ=#8QxA*G0lEdO}KK>QZ=u#x)P|UWLZ=8I&5F8Ah*J|ywI&&UIw>v z`BQ8Bq^zkh_xnBcBz!mg7;ETONT~g}|3adsF@gIP6uK2^SIKqNG&B!E@{!8(NVnLQ zDk`mznsD~4YiiXSi%I$s*Ac0S#A;f7om_o=JB!f2(YIgaEUh1Jzpq@@Jgs-l8=UTE z#yTF8Sn%qoFNyumNqK!!pk30|O^fDm#C7lSE2RDRp0Pfg6@_0>{yHn#aaG=3S^Znx zl@&>iJ)HNOgq(`XRMzwQYiT@Ftt2XMWM1kv^9s2MU*D!)f1SFhXS1&)LEdAtZ;m@E zt48MpyDZ4eeX&fLI&ugI7R{MMz0^aWlpyb+W4z;#yu-u$y(M|wv$SnGkVovHKA=)gEmm9Z+M>c@RUe`bYk}NXm_+|J zzobgE;{rvL`V%!pmjPi~jWpa{^l$T{W_5lP;s^8NAhmgj+_wD(etha9t-rV;BdeN1vr11h~Nw+x;r46i>RZyWFa%jirhAMW<;L|PksH~ z1d+k0({~xgng=l5I|LjV>JYdG(|C(>e%6qPzC6A638uo>=M=J*9v7522g@?cH zv`^h5p|KNUC*+->o4|VfbADFrVRHN?D{>*{Ouxw*66=$9QdaDVRwvOwqVIBJbM|lNSC~L&sc7>XZjuyv5sBxxL1(NN746;8AZ%+VutS(!y7BS4609W?2#rXcC}49 zteej+%klt!X?k~n&t~3&fm>E!wjK9lw#J9zz16Fp@@-Pbht$5xCj?L*qQ*An!W2#N zXhU1ZitEfX-GEefE$Ldk@3W%u1F{-FtOURKG=g*9AbfrYqI)$V`p%zdiY8^1IG6hL z0w*CbHVpPghj@>|e>g{Q$46nmXB zQc0t?t2773q>M>_EMQ=qPO`M~4ULmS(Z=L~E&nU2%++8EegxQ8JhpW?-fWjN0b6o{ z;e=Ihf{Uh!@5g}Fv4sduKq2Kyf?NSW@??TlFZsjjLjuS1cve&TqQROyd_*;24}50bz>P3)lu z5;T(3`Btz_+rT<~U@*}vf?ZA#UuTa#rWp~uJcX2J2%5$Q$!8EuyAbO@BwKUfE3%GB zpF;YohXNvpf>m-vQpZ1-f@T63gxNzWAZg0;Xl%Nc@u}xjw^WZ+jC+=(=53PNCrg~g zT%3C+=t`al{?^yGPrGg=%~35qq@8S-ZT(`%VN-w-uXtcFV$-e^xJY@WzoKL3!#Q?7 z$;~W(cZP1lxp&Ro63?xXvxkNaByf{-Dz8+B1g+5;X7*(=F-o+(t?i(_^4(3|DR?xP;cJR~_UCm5cluBC@n zjM$>cBHiq$ji?pWV1kbqqEI~tFBdM~Rnb~GTzi_s+1|*zH`3$Br+BOi0_@H~0q!y= zDEydW@HZs8I0cJ7MwrMUqf0{K!W$!lU)iSw=O%J>azcQdq8nrHzEXb<4J0_1Dy>KR zS7L)}%C$!h4J2rwxY6Nj@+Q@26k0)L{w-59py-Vq{`B~+6;wA?YWi9`(tSizreOceV6R?%Le>gzXN1GZ?W>UmT;GFPrt5P43sfdP2O?#KG#?z}W}Tt^Q9 z$Nh@p9aVJb?qFb;h~$Nl=OP7?#H3Zn854kDVI)t}JI*;JfZN{ZLul&1G=0`b(KO^g zCZBh!wGhHx?AlcHazpjn&%3x_J%?Xn@1m6G;VBz^hfA+j6-MB%_t`4=9et0ZIk9)+ zJ!!#v-_7DnU7F7Q4`e-qbLfkD-=l62CqhVcC$@Apka)w9@I;x9*o{wd zlF;dtoA-TItWT^X3e7yiCxb72$}$Qta0Fu_Wwh_)80fT5`{6dQnKaG1v>Z|UW^TZv z(A0Q-fNHAd4O%eU;7(kxVFiv8)l@F^=3SBF zsy!-u{o}|s!vab*LEuQ7&aNM0Q2n*9Jd8M{S0MYhy3T|wrp0V8OvdR#uni{?VW0Hr z6-R_k#8{$EagEveu7Urmh)xyfkr%LRtt8}}N1gKj5mn*W5JYh#!# zWxF(P9}tlw>;Q)W;@JSll3nd70#lef@B({Qe_uZ;^&@q=_1S7@2Qk_Qu}v%7Cf*lM z(~M24eptY?Jl}(U7LjRf0C|&r#I(5w^oG|_$D|AnkZ>R%X(q&+S5<*c4-wZ>7veI; z7>{I&$U=5C%|AAwtqoTxc!}!T$5najd}7v#r!`os)?VRKG)OtfM_5V@0?ftGllA+{ zyd7jl3MP?t3x50Y3y;|BFAO7JQ2=*~M~NP#n$B5{yvRC5SNIgQ4DR&RL1rv<4rbiE zCg3+TD%%Z%0kNN8-u^axW#iG^{wkaNLPuu*_!{X>{VX8OuV;Za64UAqfbB~?e!QO+ zgb`rSemx7Z*_}+tV)lCzTE%G|)_D67i0>bdj&|EHj;%93)-wYLX}eE6==DZw<#T;# zATP9%Gg>{CE1ER2US9XgK}nu&xC}okQ0LcT9-^x5GnoFB05vC|dd@d0{e|U$O-k;B zo7l*mP@CN1GhotX^PwO(H?IrWa;bi2xJKOC8EWms)m^e!(ZXje2ZS&2>)B6h*2Fxj zHTwrc?wQj(5^qLaj3LMF=w!%v!_%O1pI64QLy6j<2EF5HKx73m+un9tc%v@RFthAc zpDn@jY5>8ak?Nv8Lg78{zCC`GJi5^^m+=pX+j$&?$tdy2XMk$w`j3$_y&D;u^nZiK zJ|aH#21CObM^4Ex19qv8pEiflq;&Uzty%<_DHj>d zOTpG}l8jA7MA@=~5xQ+P)YyEm&BZGB%4a=3$oi1VZF~7N-hUC^nJh3eAb7n}#=bMa z`19nNav1_mY8ME+gaW}dK?8FS85%WHb0<!MU_6*$rs!}o+-2*Tm1t$rQR8ke%7ynHA@asnPZO<-`pc<**U~#&jH(X ziAT!y8SCmJG?dwN2~h1E1GXPTdzwVq zlRaSUN(k{2yaXU9p}iG_7;1HtkFHf@lJXZ<)*nFcxB{shwe{$0$lsPthBG@KNt=F> z==G(7n_EHvID#*MO<*%)ND%)6Qj(f`U{^@jL#BXUzYzi92FC%6FeYma;LtCa zv}t`;dmHrGWZc?M^yBK$!yZaBB8a8`DjfMT(taoJO(V4c^E;U@9~rsu_j8Z7)f8rW@Ir4-?~dH_7TjH^nL7*}CQjH~#ExyyPneKReiALT5!7^ks@mdg7*O@=&f z(rR+f*n|}sr%3ySp;|nm8O1UuMGEAv??Fp3#F`;Qq_p(lPRa8#V^56B>oT%5qqJ2W z#PD4sw6o`BelO=V4*Q$I_aQ{x?g%jh@NdNbKenDLFJ9%{DTZ*6Tug)j27ZO}ri0 zn@?Ob_|$VOm*sMI{DR{KOqPmK-W})FW;U4#JA(9@|c8w>K^sxccY(4`)6i4I2_%K(OKbkO3Cyn5v+_Um-ihO`&t#4rK;i25z z01pIcLs17~KGN}STgAyh%r5ogQ(>#7G@QxA$?YN~^L1o7yu45 zkAZm^#J0b9YDZ%OS{{@45D>)Smb?uA{M-Dz^LQubrmIQu^iu|jne%;`ZW57v1(T`6 z)nJ>*fPb#y9z>$8_|~IlqZx=y>j+mYLS`9HVi&t`Ip~>XBv^YRcHq9)IUdfk_XHC~ z*I!Rn*G1K>j#{&BU_g7=dmWrs*zXNh%_*7z6{*^mRRg~K#Me2!U+(l6`EK{=#SRCo zdkj#v2#u1iBw{Di3>$wez>~U>nkgWj&^6#zbaXW6DU@R|Gg6OnhLPv|3oC$lHJIIAReY*x&~+U2>OmSUeaIkpDq;Nze{YOH(H z-3Ct$IqcEsyNYHF!W^~ib#%AUg5cfzzQS4VEs(wGyIlneH9{z#Kiaj6tfTK3= z=&*zqwWp_uPw*qv-5U|0C#s=3p@`O@M`Z=qZUUXwj^9fpLJ7xDu|xjEZIl|RqV8^| z0W`bNL8+C0`gqMHH9-4j+AQ6BI#W1An;K$w8(=Pt+bwNiFY?@2?#FVc-=Cm?C4F~^ zt46^vZ-f?>aNuh1y}hE?GwLwzpgehia6^H@gkKs^sT@571dAf@*QX?V`uzi50&SwM zK6R3e+aGy03dY+!iD2w(59hcs+v98I-JQ#cDR#D)iUYN%)9>PP{Ro{702^ z7v9eYDIbjzZrvhWF<_|S9jRRhmbyiaaI5Mgr0%}B5Y^0n@6o2nCN+&}QKn~Xwg!L&+-+CI&L`rH&O zY|EeXn8xHHynHaAdKtm;ro`7ks3xgH5OSKNQhssRq&{T<=9_XW%pc!RoC7W>V7!_L zGl;P@N(On9Yta;BbUBmnhcNw#U=fpYF}X+X(M-`6 zzv#IKuofGP3MFptnbR?Pd;{({T3Vk{S@(_^)w$+rOdgv5Y6FMZ6tbMdi zKl19?Ad95-&xqX4E?lh7r2ZwYsG4X{Zs5-WvzQk>a@pzMiydSoyJ#{^!R0F;uYt|! zrEo%9v8FwCFImcuB*T@BpBG_4o2VFiNG${ei}Yhk8*+1Bfj^|o4dIcFAxn1?p zc#g9Dx1n5T0UF=uQ6o)mrNJd#aHiXdXnY1KcZ?x2<3pcP-GR&)Q*ypgWJ)*4O~zV+JRCsTZ`Tli(Nr&@om8a= z1vn`R7Dcq}EV|1plukZ{;ny9G%^DF~miL3s-1g*84Y3p=RwLK&H=GW?Dwz1-b`8Dm z^lPLix$J%VwJ`j+HL1fNNHubb+*ftq&C@n*zbAZl=boTMy-UERmyp$TVKc)BM`I-H z;=U8L@LL!=0N7wgYS+VpaKjxWq`OGbiy{*;V((63VAFQCDR>VC2VAh0W>*nwm%SGh zmhk9hq-T%3$yu?%ttRVhR*STBQeW@SgF3I#6z!8o`PPhC;H!5XXINV3Z|KD1U@7Vx zw!Y^R@oXTBpjd4DdxE8L4-dY=Q-nNt(3PNL765X0(6n6G3_BFLjw_TKR}Yk%;)l|R zFktTn2|=atAR*jks`J3oKLLy0?>g)E^hF1g^McP4 zJgd!b^De&FqZwA|$G45O3INj)RBe5Tmm7M#GQ`-|S3`XMqTdipUI`Us30osJ`0rw; z{Hm8qP@zHI=u+=;II5?hMD!IygMT%k|2+;YgRvgFo;u$nqxBv6&;=v(uX+aLx5^Y< z>N|mNA*LaLl+|wiv(Hx<3NWFqZkYF=7d3FP0gTa8*L#4I&@9RL5TD0{rl}m@oC1iK zy(B#w5KCTRnV6fAszxj+8WKwmPV68)l0Y@5u_Dv}Z$`%;bQ+Xs$Y}x+w_57)g)K8^ zKbi11%47!~h3)=YpaJ6fi^7z==oyxfmjWX^E}D037N2d$>3uSFUw_bdAoOL21aQH6 zXpT8dR-3k#RumZ?8&0mD>SxzH`uq^Mpn&OK6Jh#lL~uYf7g0xZ$wFq56~%g{-pnP0 zqZl5NK6bafqCK#tduPYJB4=aLyONc;I3sa4{fM(WG#HdgcH?3c(Wwc&@T=NuU+h`d z7z3=BshT$?!14zHQqu^g>eoQ}q4Wt!*Yz;GgymMZ7}v%j)!9oW+c*~a+)i^JAmW6F z`O6NHU6gHj-ziG`BN+mf2doF#*-<_5b}v```VV z{Uua-%M}4D_k|vNVPJNnFb7<)9-3nglhvlJr4{iNtPc_Me3cw-)hVEra=VR& zFilqDTPc`XV%Vms@~D*I#I9G_vu8kCejT)w@n992j>Lw6l|LFJg7M2dVSxB$i3pSW zIG7?#BPLol(jQT+P7A=sLu)aZ*s6tKo%D?67q{3JAFB1mI(_zpeGz6Eija(Xp${^X( z=_Q+VduKR4IXJ+zlLsM1ONS=u0YLI-pYDhgF~FozhGy`%;$<&;f&uGA+p%|Vhd6(n zPg{&XfvDL00Pq-0@%O>1))a{+q7Aq`!^)EfZjB@s^?6=mOSkh)2W}7(?uB zf*Icv;aDmMfi)W<;bZW)q!GX1+u?=3mQ#sxKz9uj7Y>Dq<}RIvU?Vi^fV+h0ZlgLD z(UgpK^X&H?d%DZLyjXUmD|U*V;8Drd0XoJ4yrwN8qU*sJv9Oa#H6@t1(&J}ZbcqjY zNgv$|SA20ma=CJ4skHudE9l#FcPyKn=CP1qw~h93&Sc2??15=A9AN(Ffn^b4jss-f z82~1dNN+vzAuBWJb2EPzW-vk3^n-IV?K@PWfC`gj%fE` zm}WSwt*r1c=tV?i{sX>r<<#tXkD~l>6XEwx3CN?QJ86D-hVX&kQd|6}fy+z|!2acd zEfbS*U&~JJ1tDn;tPT()D(OTc*d8f-PZ`!3O4?Gn6f$W8g(+1{2Jn$_C|;~ zHh^}nq0%n_%eopY^GFXl9}{e<9x?K@PsAnsB(mnEjZnWm)yKoCr^A&hU$6crSlSpL zjHYbTJkh{x6*63`tr{lUbVJ=dfkL*Toahzj8%~pq)6B{|FV{Wx6THiA{Tpo0ABD`E z`wWwowlfjhte9}~e}T2jH-Ns#+kB5AK8JhN=>c9ejgiP){G5k}*0WR?=NM)^XLG`z za`i0ExwZLv7Uyhrxhm$kWf;LEze4WOBYnzxBv@plz${Jo?8-$bZ>t`mQf%xB4=+|P zTm6v1YWm2djr0O-TSbbwFNrBK40Ppw9#vnBm)I&?-^Tt7h@T0l;BkihEpM%c~)&hR!Zc?~9aY zUwM9~QUoo%2)SRR@zE6eSaa#wkiN(aFlkDGPl;E-mwEXd8nFlj)QFj+;)ed z&@X#vJ<*`B_buo_Be9)HT?U%cC;%K`(4$zTOz$k1EYGx$K~3%)fGIvHN`dY2r{O;% zEx`6WZ}H$io2)ArLQK6CEc!Xgt-U4?yjDV@Q0;XBTa|uxH_)mQ)m`c3r0?BWOplV@ z>jlVbVt6m`_yA3hoKH+uiI1k{u`oT@Bf=_3Xu@x<_i;*h_sRcnq)&bTEd6YyZ+8{w z8EGo1`N}x`!Q_$2o&B(kuDs+D{|u^oU?kQdkWHlN$A^JBOYt#7&{Xte#|mJZ8#Eh8 zLI>_V1DXT&Yv0;KU+_2~y`= zSGb?SHu2U_wWLkqcU;RTAy;Hfhf(UZrSskRNKvc@Toq+%*2q(7$KP?E6s` zKn`N1k4e1^ARSGg*3eEG4Q8^TU3C)aK2z&B=~--J7i17#$U-$Du#HKZU?|tyKI|I3 zsulxcO~hGsm=7okR4)2h8=D2N#em9oButqm+96u>{Axp&dkf&F>j}nB z@e+JyELdexfVRb=&6b_hzJR-*J%Lv{ovH0h?S0}hA@0j!mb>PVB-vH zd;EJ3-OEX_^rf z`XoS;a}6c95v=KAux)RcFv6~U2`tA8=zy;TOMcwLUse*VI>na&Xc_pLX_^F<_9&oP zTY^bj0Xfr!y#dHN*(CC)&H>ALA1T#md%_U4?R6iPlfbt2^NGm8RCe{927gq4vbGvf zZVBo@lHGbD+?lIMta6#gDpF(Wk+(Sm#5}6tP@g{JF9eg`0BiQ9PsjHN!Nx-autmbI zK-89O#{z8!)`7*N&EYF{X_n8F%IOwY_;WVkaK2%I>vaa(;^OldImw80IEzaYwBBuR$ zfwt2w+F&Fgz(M}5qf7^M4WJek!=bb3Sl8!h%nyLfXFO(JTadi%$bi@b0_M15)KRv$ z)dMz}PJC`xiMtl|=4(By_#(8zd^lG*iMRPYygXDn`p-%1QX0SAw4OaS- zN4@CNp?1^#C}+3HZ%XMQP3GgUZ*F5K_{GpxtOYB0R%!0@Ikn0(9P}!WqPz`pU1y?` z@L*f|F40*8`AjQV`cNO&UJ0nW5t?&eBGA5aZ~)Kip85bV9f>wg&Jhn0rtQUGTR$eY z-B_@l*FtO4mSEa6pJZF1xoRr2V6Hm1tn_e@CIQJ**=o_Y{68j{xQAfM*#W;a=m|}h0pz_2jm`n3Bl`y`m`5XBMdBSQ@%Fn2 zmaB9-#(_DHsy^GN*k_Tr(O;nL7>9&L)5LPDM<4Jrn9T&s+Uyfi5U>TFV#4m;6r2mK z=e+>eFZMo`jLpGC9!tXA0Y|q0v~M2JfZZsbss_dBMTDJ9RvZwQD|jf|W+rHh@oF8- zKM4R|2iRa4d2blw@dHS=GSJr(=7UA#2bSv}`~V*YYgqyDWd%fS#6=KjSL4QsmZ6ry zPY4sX9+Q1HylPRdt8=X^tXH)>V@?7znjQ!f3oX>ex>g%@j%o}@@xulZ_Ty?Wx*oF2 z&}x&iZ}%`y4>grl|3*&1$(Vw#41uP;g|5*n<(i4F5XsSAiv|K}9I?}-<>_M30SFQh zMHmAYoMN0n^@|cCR-I^|#fnP+u5&X$&&}{0m#9;aUk!;Fno*+j+onCJtD?DrA z3L?GOb1_4%N5rxexr7r(}uy8 z-p9iVqh2Q2`C4zX+xvhvI*Itr?EyOvGkD{FJR?%~Z+s}eSW#f(`n99on55X-l4^6KjxNQ}Znx*;bsPE_;pyw&X zUonjzpa`KTdkZ0+RU!#NorDmuW}L@B$k_kX2)TQVaY8~(NVTgb=C?KZ3HB;EenPbh3o1EUb+cgk@TV8v8ggz0##xORMF=mQQ2u)?Mi;+l*^OttE7|33qo zcl4tU+zv`?D1X5tTgw&P`Vb82SDHbsq+lZxF^%WGjva+qoIA`Ak?EJppa| zJ3iyrag%&TuA%byDv5mNCLfV@gil6a5VdU%{Mi?Ql}Uyoz)S+${t`6DVn%_L9gRqn z^e3W!ddf~8E>uOy1LFtoFKh~Ivs9C_O)kL5|b#gMFTGoPER5Buu~G3*=x&1Fcm5u(ib2y{PQ%%vV554Op@&x84gi}5=<>v=dXnGDBMKl-e8Z70L_lRP>ch^#70k(9(3Z{-QCUl2+S-Wp$rC*Mt|K5?Ou6i~ zmBdtpJMkSr#cp>F0o&S$gc;8gEbo9!)1|%7YsesmWdnX54w%)( zs3blh769S`4%_J^ut*<+>~;V<53uPxG|h~Fm3>8ioqe#6CV}@7YO^c~?w7YB5MD+y-ZY`_kewzm{}d@ojhXOexaeb9?O+LtS3&N$HO zG!JY&lB=so)KnL#OyjSOswWP?yZ~r?yH9I5!XxM7;ZJH>j73fM@lZ1lAzoKhOh%u8 ze&9ZW=?9~uiG9xyHEy_a3a!@>rko1$+<75PS<3(%)q}H1ThK<~X@zAliQQF@$%%M1 zHMh)=`=S*@AN^-!SY>IFrAP0BLHDOB%pczL`T5bz&{tQ29dUfX=%wbD(H@X|4IooP zv&mc@I4EW4?@Xq8>DX7EIMLk>m>jSt=#E5f++vV^j3J}_@IKS-F}PAPz`A^CsJB)D z2Sm;aNW>Bx%m3@nN7TkeHDKNONLtrboXLjC`}w49eJg2NPXue(#ZQ3N6ZNCVWH2P! zUp<3S=yL68XU9%`m8Snn&L|QkB>LU?14$ zd!H4SuAuhag@Qlbg_!M!1lIm?KT#W~1iLPik(-~;?@aZgx}pCD*DFTC8yyflKxdl| zGsm$h;PX?x!`0`m^#n*Bg1Io%(Y6#5QC5s1W&jbtlH9ltQkCJ-?rwJY<*7DPJ=z5< z3=Tx(AWu8L0>ZS7oIvw2t{J5GQ+lYr($c;b2vQy}6) z_VBw|1erQ7pBr?^m|?94I}WNmYu^zJPgX@#dUql!svd;nRS6&(%!Xt{aGBN%({h0& zaH2RhI#osARP0gT*U0|2_3-#~&T9AC{>p)fTP=nKGPSdf&L5Zp(blMb+R7-&!VXnL zTkn+s&^$@!|7{fpsV zFS8s3E64NCgKz(Vz(D5v^w)YXcZXOZ# z6=8D?MLi$v=jQ<{M+Btk3fMja@Stky^(8)%uLhJILh#pC9v#rF1f_wE905&Q*=-l7 zBFbLJg4NOK+N#`_O(y%%**<%@;|$)|sD+2Pd8#S163Nxmh%%baRo|dIs?QYMd7+~I zW58Y$mH}2h8nC{tMskC7iZ6-*L?qpenVnS7p) z+a>F1Ig>OQg64)M=}}=6%Oss5XKT~~v`Ke4L-Id}N_pR-zoF)g`B}l2$-}*7)#^tC zZ)pdu-S~iF9pZC(MnzrvBFxQ}K-)eoKQ*b^>=_)KX|Y#_d^ZBC!?g5wFeL@O>O){{UmdDZ0I$|!s_n( zIY2Oc*bTC~YU1K-CAV#EnL(eVBSh}4`tS@A&A%|Vh_T6?L)z^KQc;|W~7GybBZGmM+f8`o7IKGKHXF! z2Nq-hA5;nRg*o7Y_0Sx1n5;H!Ev+cFk@9|_o|KQ0A-Ro&E}I*$vZJ1_g|H6A9*!hz zKd~IL)-?ci#Eg3$z9W^A2LxaP%`^0rVr@cljfifHf|G#_sP5Aw3%HJ!lhk2KV>Fyd zn!7is@l?N9!>p9Gu2zQX*kzI|75ONt7piU7$P6lmd#Oja-d1v+wj{5Vx~P8?BxN`X zN)V%9Q5UM&jfRPV5LH~t+T?B|6|HCn8z}qWDI=nb#?@c<@dH{=VOE(I#6daRZ;n}^L`L{bkZ;mhc?Z0pVrK# zW^|vHDv{~*9aFs%uZnW8iBf2$W3p98X`CNHs!J+4pk z+SuEtqkbBu?OP3l6=}7nAMgPAG1F(x6@U#2YUL6UB>fg>#l_*RSM*+Y+iDEjON6-P$T!ex=l=XUK@SNXP?X`VV?cDz%)h2N%`c*G)b=C;3)>7ST zc#{3UD|^+xDf{&QQDqO>H)R+9KdtPNta^+d`JAU5yJk%)73iP0HmjsWU=G1Z?j+D5 zOs(C`Wp@kN>iV+DtOdJWPYxqO7Da>?o+H9`yY*Z+Vg}+%OO)j#K>z*#^4%4iGlw7z zb|U_~SlD-&BmVeX&wn!9Wb}-1$Ac|m;khys==UD z=Ni(NmXfOFR~|pFO2D#v5wl_(nA<4X_^BjqKG?uM6`r!gz#=z!nRh=)+6}FI`l*zF z6wHuKlSxGW{C7jzL!f<-Eh%*3cJ@6!6Xd7WW zuU`uP>lX)!^iD{~gpj(V*O0Yp-K45GE|cUn{5+gAhoR8qpu?&Uh|NA5zO~OIy(M*S zH$NIs#Ko=6KGn{+$P**>DFMHs*OP_I(==D*NDq^LSrF=G3QjPYGvb3YqC=PRC^}!l z4cHCfzy?f>r@`K6lzVYa5XE@H0s|`vtGh8l(kbr`nyg+P8Y|s5{{T~Oe}>{ zqQ7%maZ=Jf%8x-3rddhPkT92~UYHO|ZTt`-h9#%%w-{>gK_B0FE1=na>Jh!l&B}h9 z1@)H-1K0ue$5}xUQ#H@j_{#1bVfcZ1`p4b-)*xSKX}8^A?hXe@v!wxIo&j6l(Mtf)!ZQQQe$eNx?axa{ zn!Snm|9l2}#w%c}#z0F`nvFYsjG-)JV~B0B)F+D;OO;W}VML`4^Ta8pWFO-_CV!uu ztZsbMX;537M$#Q0K+OM%U{Y5=!7xLuHVHTdH{GMPoGWmQPw;(cNgDx2yb4He3plg` zS}f{JhW5*^$=t5#3Sq|DKo0Q`gC&a*s#>eloFi@gMvk|Ek$pZ28YQ`rYv zMyCJ^Tx(E%Cb8u)*(>Qoi1G7aO#Q^id#b^bw+DdF0R;;Q?%&ujvwI)V`9rakdrY=x zBcZwzv;jR4@agdZXP6DKQ|A$zd@!+---VWZ8rX;JiTLP273D3rck?|<85~(4YRT)c z7o-xuhC-R-9R{_JCSmHQ1e2Vbe*~8DnuoH|J3Qc!(HUaq<3U(Hll~c)%50K*08Bq0 z=8J;RHYQn5AeuiNz`?C{k7(XKswY{Gn?wA#ZvdnI;OWJbiOGiD_-74E*ykbS6ji{o zzF?_mgY8@qfNg;#<#nGP!D1FKi33O@;>WE%7PG_o8XYNSf$@ z-AZgyIj_5^%`7^Y>gM~c}_UArer@bR^wMS428Bf-@^mvHokA^jC zh6FohyGJ3$&o#hLoBO!^V!)R-5;gxGz%MTlw8mo|UIDgqGuW1!4A_kU*bsmUWn=Ql z)GZ!vagMOF$soB9?Bs|?`T42y=i_r<#AV~`GODeE#YyKpU6)P~MTWU|LHlzT&ZpYjM_>?ZF$ z7rNf9zjP->p2<5m_K%Orzdrlk+K_#FhK0X}61Z2v-5VXO)$$QMS^bU#-9;*xSb>AS z=W`zLyPF4lJm*mwsmfcIb!2MY|M*{D&s(2$Y3XjjU7;Nia9Y$|?fY46xTSAi8N;B- z0z#ryr>+IdRE_MMXIw<){Y(YddKwz%1|h3<0dQK?jJ8h{LGL`Bj}-HJJ>Q?ghOSsU z!eIK%Gq<`NB+>~8(|U;v$vTq{m9S%^!CQJr8LLmFhk8V$M{*C7dE*#la_o}P zJapPMqa9*JC#i#Gih==s&=&!gZ=cdNsc7~qsbk-vsIKUyqx`}2b5*dH>%hN@>tNRV z6J~XO=c+&Sve3#Ddd!VXCn$DU{jLH{s#H*=y4^lk(Tc!8s(!o1nK35iqORt}SwV@M z^T6(*yIe}uf8!`=AYLgO)~WMzYTiZQrqsWTU87UNLsk8?O$dn;d3@Le?9zOnCV|!= zhF^?NlrtejYVPhn_a|}*4dFaZjWV=`Ya6u8%vVN**03A3d!KM;r@SP(GHMZ5D=~?tL_@?HYC$xxQ8yyh8KPbdDP`7Cuhcm%ymWc$=De^Q3?EK+U8Cy@UG-~27fw~b$&m3~ z&1-MeIS;$C{KU0M>|)!bf3lT5n7c&ywH+f+s?PP3e18C&a^w<%Dy(cjlRWp_`n<9D zggsaSk8}I$?o;CB3bTKI8-~mGeT>*cna`!3p7NQ&tLbQ=!(^#DZ>+X!0!m}Pcimcd zWxPfO>m$G`Crsp(CEzQ(N|wnTOL&-?$n3S=rz~4H;yx@9NmJZF7grj6pWJ>YOuNlt zZ?VCR6MvZ-Fw3bX%Ce!&&om4WGFj=CBKB(4Xud>n_uZNs#jJBaMmr|qJC3=ceb1UY zVG&D}&Yh^xIRxvDG5j6TPL^FO7OSANv)dfAuxcW78UZUj!bbx_O--KIzf%ba-@UvgJ;P=xpZ>lP)`RBs8Pn8Pn5MNXi}Bd+(MU7 zy>dX1ce}OyG}_zhdFBr17I%#uo%xw^Cb4@(GXw;SqC=T)HBm9kU8IC=;{*nmR^3U2 zn^hp!%$}2{-HQX|SYt=~Y|QbaXa)&+Q3^j@6yQiJ0Sg}oeD^RWw#!aIyAIPRjxAdO z+LQ$jySB6Ue=+wa@KqOA|Nq?V1PF=*4Pvee)haHvp=t%|x7MXftX8RAu+>%@wb<4L zwbt!x-D)Fh6(3Et?mlX%)f%NJE?@U5!B)i;6cva<1lht(xFPd@f6m-{19*9!@;uM4 zufM*S%y*VEXU?2CbLPyMxioetH1LaWag;h|IR=;FPlR7KNXC`gpFnn3{Rj~1-T}fg z8Wb$*z8N`4l{g4i{~pod45Hg{8L`bu2zLH?0<*D)a>q3B>DnS>#z!%iG`>pU8whT@ z!V&u#!QH3wIc*BT!zB@~AMCEdesf+-_|A4ztht}84}S!lVIL&0#n%%^zY`_9-X!o( z5!ju!?C;d8x7l|npzqmjlMpz!FR`u~Dwc2KNc%CdQQf4yeQ<=U)EItqUGqL^OPgpaC6SE&< z;rE*%ymq?W5<$hgmL@)1#b^2(#6Ef~!VrA_!HM@jJXl-pBJcPFwgqK)ILDUowAeDX z0PIHmjT{1abC=7&{Eain8v9+KE_^QnrPCA8K4*7@CqONC^rr;1can8_yQ3S&6IA{v z#-#f#Vim6t>x#{M^}<6;3%}-9RQ1`t0ob!AnZ5Tq67M8fayh}Yail#ljNs)Z5qjhG zr1_-;vpY%itBJk!a~j*Mf5N`{37XjEAQ)G(DLaCT^qp$4CB+a~DeTuZEg?`@ih;RB`RQ6&keSxJ`3R=#PM=P=0{Zf~AQ;wqI6 z>&!Js;Ot>4h0m1*<-fcGNvzfe34?b@&b`sKeeCWjx`*7D2S;`Q#Ke^Y;(>DC);<4{ zXz=w=o;IfS_t0)E`rizeVO?kib$=gXeFPYaY26PA{2fq)vCIcbvkaxxXAd|L&iZ__ z^&5Gnl^-LHk(0&t#v6HhwWjOR%r|OLSwu~gQD{DhHNvkJq4}=Lj@3_^!nm9myM0*UKK|lcihqG?D)!~ryZAUta44) z=%LB9X*^suzA`y(+R^pG>GQ*}u^%ZHN(D@<8|si6U8iS-^i2f*VCILs6v*J5_Xjp* zP%}{W|6))wHs%}W2^I%8DJ@1X`H=@+0H?LLC-uE#hw6+5|E8HHUfcYVx(%yJOGhQwo4P^aTPb z1!N>aQDDMGL3!|>^}3)?`1y4SnB^8pM7b#+L7Vx%SR|uji{y1eh{eJ``vzzX+lE3? zw4CFmQO4)lrWiXazmE}1Y^eIB0wnbPsz!~A z^~ck<&{gZNK4l62q=mA!!R-*iL@ItfD@J;&ku_RJwDIvD+4RB!Dh>ovpqEh&8rFC5 z^sEyxEjU%}}N2(=1f^zi1*mTfbUT1kyTbg;$n~ zA-PUk!Q5LvO21p@_PUr6B#;jB08tBa*9jkF@WQ3T9WQ1y-aZfFU#{{5|8@EAs{Fm@ zMa|i=?5gH+CMO)}Q+wFtG#^3&IHg`JyF|=2*oN-ecA{hJfSykUg%53Cx#TdS1aRZ4 zlu{shDDEYIuThCW9|qBPRi-psn$xSS5NefXb6(E^%J)>Bpw{Xeuf>Ks_x)zW-QR(S#=iTE?G#!+49>ANahngJdAeTaY~*n)E& zeb6~pox;(afz}+W|-ZU zNH&G?Ez$lQ2FOokfVeF%r!|6epAuM^h5ZNQxis_$;xrOIBO$s5cKavj{ZF0|85dKc zcd_=eD>W>WU(`72=QI2=_Em1cC(0QDltFAET_;o{o z>7SiX{#bhka!KUilLT-dnqP{(JpstSct#Wg7nhTGVV076PgZcHhDDX!VEM(saj{jh zM6DXjmnA-R=IprkQ#>a7p$EDlmnSs5?8gy^-jX1c{t`hdeJh}~EA}RO#aGn3ZlOa! zfps1tekP`8LvN?lBJ=vF5T+{Xs<#7{)3*Xj{q!}WKYcaHmscw%othAw5dpt#AyEt7 zdouQZhDIYZ#jzFVO%N89F24QqD*0d0nMMEaG*k5>f&+d^TiYK*tbskcYcP+C=UyhN zz~4WHoFkui_8aYEIkXw%xSO!6OcAc)wiuQnaC!jA9;81CJ}JDHWO@2b``*_EIp(NY zFtjs*JnRC))>*qJX$tiI=s^)1gyoJg>L-w^1$@w#DbF`3k(et*QI*PXBLh5F_pY;@ zrgmPowgWyMq)DK}6XLNQ5&509Vi4mdM8s9u8s43s%|-)AZ&}H zB|BOXk_T)>TWAkyK|%WA=L}YmRqVhR2PQCffV7~n`nJRpg)OB5zZkXw<($3=9Ry!{ z@bN3P1+Udq*Y=Gq8U4{u6ZvI2HsXc|-&n@IpE4a>CQ3?AJP-gA8~7agB{IkryA6** zMOP;|1e^eupShQ9hd1E}oPu)Y@J%>yPbzh4hE+dCY{g4voAArD8tN+3;+rWIHjakf zpe>y>!kCR@_612i<#}RLg!dHi%pqpjHE2Xt$MF+Q0gkIv-vpN*jD(r|nuHd7mNK?6 z3EwMTQflXciD5?+%yi2(V5T<&+2ArVg4K!CHuwsJ*lv6y^1(#rpDB_Si5KkoB$WZpqMZt@CvfZDPSgI0Z!OKKvB%OK}mB)6mzx5gL$yc8g|PT zIcr2i37yoXMZOD}-Y&la4s-1*c{tqKzF-VdmEyimpThCG`tI%za7_#rIwKPO-}E;?zf27Fh3 zL{Y6TVy{h=fqOt2TWV~5F}c3J3?%>aGLW+&@N)+h`ff&J-uH@;lldH4SGgUBr5ib-S-xQMj zEQMLd^dOycY0lBE?u><_Ab#}6G!36!`js(5 z0lXUp+eut-wo}0`2OL#-j>YZ(HHO(TtmjEyIz02UnngCD^uod6eJ4%m2hdwkivzeJB};M9TC` z=_-=PB3dE2K9lRIn2psTT${2YG?XK$aL87IfY1_AX&Qp|(Kpu_nPf#zWzkRWy#goz z4y{zyT*ttlRGpcgW#Id;EYP!l96VT{X93GJSbBBSx=ib4Bp%RCM}(z;=mhLF`x>t3j*NKN<|Gu_aU_tLFMz#`e}Bcvoo_ZBWbRPOR8MQinyd^ zM+0o(Z=lNtKas|ry`n#nwu%Wb>;lF^Kb^MD&zB7;4*%KA?AolX1B-8GUii)_p1Gmrx%duA(#wKwQP0;E)f?azLC)3JCRA-VD5ze{tZ1y5#5bLp6Um?`{~- zwy3piFEi@r!2KLuA#l}C6I<~#Kc9jw#?RYdTHRnvW#OF6cYYl}93Q2zke0_L#3v`v z0MYI{PbXN`Me~Cy5{CGLQb4BHk~sF9$O4IqBjQ}Z14;Bg|E`kCcg2*i6l<%Lo*$xr z*ffYfpl^GL=-JEpR^UG=FDLl=C(4Ta!&mwS4+v5MyoX|qxl zx*dowkEwrk41%@4OsZyNXt>xhAeZ&s|2{4ZxYd~CUJYrisT`ZS=B zkg8?@pY;;+9bznI()ay2>g|Xv$D=xwf5daQsh_c+i_-S3Nt;QE(ryp06+4r$Fe%TKz z3lhGZU({$z$k5{eE??a0W=2!%JFOA#h;ng>zt_PivTKKDE#%rI`xoX0EqkSOCp(Bo zTC{q*-D5Y@yDJ#F-ly3;T}hpmghEJ(E2+idXJ#9p`kHMM9@d(JPY@^wlJ60n1l_N- zCTP)>==f7ec&DE-_cxivDSNU{eRVRW_nI1kW6d`xISA3`J1dB7)djGQG&7b>MpcrT z3tvd;Y{0QdkqVi(Iu92kuDP26E4Og)$JH@oc4|`f>{06XE&y9QoAgpgdhflFDaB&N zqq>vqT{KY<1Er%Ml=q#Z5@sUpyPDC&aEzhciQQDZ+KW8>E+pFdJTNw6gQ>^fNjs0v z>OB*-*tu?{zByh*xv`2Or@DmESbZ^3%3zGtHcR#V>Nvonmp4`rwHEyHnUS?i@!7-- zmm#YY;k#dVEPVfLy=ET(zCE6b>XUU*LDf<8n(mVh4bTRDkmTr6{a2fS&vqv{dfMa< z0pv|*=F&taVxA58Ab|_3yTR1PV)^657$0NRdyG~0Vvoh3`o}f!ux0aJbmLJNagr8S zC9eN6fSnyG-Lce!OuG`Du|+qZZkb9Cr=>)4MXeIm>p4|8Sikwpv4!|DEymimonS{v zAJ-JOT1cX>n`av6+i>_8NnsUU|73TnNFhE|atP!&G^et89)>!hbwFbh`pWR`~#I=eTmmQIP8fD_}iWITTfUQBYy<@34K>%KES zp89XD2Ze>ae=2ghnbLW#SLi!$2Q}pFW0%8suS4O>*9}x(^+R2VeYrm?n_lNY>T|{c{ zIT5k96`K(=*{ePzy29=&rhbu79w4sdbYi_z5@Ih>-K9+HOI;QRAUvB$7C@-)eiacM zF8uy}HHVW31uzdCvEJy@E`KRGbj>BC?42 zY*WmnQHHrLmQa`)an+q8v|LE!bP7^G4QQ8QItRv$iQVrf$d#t}E$#uKFOQ)sGBEni zIv|*nrv09gsYn7XcH+6hp9@gyVdB^yOVs>Yid67vKD;i*w@AV9riP!=&Ul4i=j&3@ zen=$)FTAQo+(~101-V2Z%|({u>s*gIIT@dzU#eGsSzUy=(ZG6Xbm`_y&8USFuIXtj zRdONX(=Ver|FJ%ow8v~TXwLWE0`S!4u`9pCc9j>wdn&sv~Ey=4PNzYC7!)Ckp)E5IOHA$`sKSJAFQYm&E_+@l{OH+-9e zfj%4)R>{ty>Z-+5UVRD>ZCzce6}JCirJYJI+=Hno%MZ7>LC2G{Vwz)7You&-qEQ`U zEBtpCYuUT0He#MV7vsM6PJ;VI9P{key;S7v6WXuEFJd{^jk_W zDv4Q*UQ1YrG(E?@9|3&dQ`N+c`%pW+-N8G1Q1RGfiKX7uNE|WHUW*zsl>#46C%IZ= zSau_&Ry>5s-lz2HR_YW~tn7GgGu9jxF{oP6yI&-hq94Bd*YFG>4f^iCQB`E5q@yo) zb7(nUn+V))VP=Rf-vT~uUHsJS8DFIdce>d1thQt|D;dM;`nOTauFFzNxK zKZz3luhj6@3c))Ryb4FN`5cMpzp^@eKoI>0Y*h54CT^1Gms(j^64nv@)A&Vlsny3V z1BVL-fszG$N(G;(Ue!d7DfByHTfuDpjWg+U`OU;u|CqGu2NMQB3r7DDC~jT+WVyvc zjw_eERcY7nmLx!MI}4Vex;@Smy+_;b;nsZPE*36eP(QAWa*x=MyibyySC&2z!!W<* zvv)qhT7C8+{(0nzPyZ#QU9DkQjW*yH*d6*c@5ihKvU;!C$&r69LHGDrD)-Xyq%LRq zay7)<=89QRb0qlX)9?n%K3w}~^$eEDN702=KTqa2Pu6^VW@?gcWWo)65J0Q;r+Sre zUHYM3WZRGV=>`$s=DnUgzyu{Fv4cq@gw6Car22eRqcnuOnWLAZX1rek$T&rafk_@s)E%fy?LpR_Z&D zT1Ad7!s}MF5FABxb5Q3K=~21jediJ3*rJFB4Mi=luc#Udx57V-{F0#%p#C-PI?XAB%sn6g8)Mw7z^MJ4n z|DZlCFs}#;3TdH;(gZ4<88Q)0{<~bt{2~d%P6Z!JEOB+^wC2-b;*Z68_%2aGLXoRH z@NcN{s0zN&8s!2$P1P#00SZ6mq-M3p>DvHFg+C|{z_F0By=qMA^Y?Yx2GV*j&bsuy zOgLm^IxvOBJX8qaW&f7?Odz$OfheV{5JeSLRZ2afroW*zLNWh%u#c_}zObap@!d0oa{TC67K?b;m}1=&QjT`c_n(yc1G{;NOD2 zcV~$9uZ#AEtO8>kQq>v01LH&*fTWLc$j>v56;-KYLYaST?`PMhN<$QU#iYPK(kk6PB=`=Lx^TVseaV`g zo2V|W&@TmsUoLsXUE~<(+9>(?hXJIfPIMX2mfX#{+7R6V?6%}qsii;|1s48UiQQqX z(KjLO6PQRtN_Z_$%B!CZnke>y4(ft3$sj`Xx_mqGt}O@7(8dHz2;3I*IOwc+)1Hh{ zm+wLDZu0XwR`Z0wRx4aWy`NI*;$V6rhk5R_4G}0Pb_XLU8geC&{)Ds0h2-HPOz?>) zu~^w1e7arKlwmy~H%Q|6ohY87tY_GgE?YR*O2B}ZGlH*dbdxBEhiTGV)gLZ!eAq6xG zu)pAPry6=C(YJ{YY}0HT*hFk3KK9RRG~T70f6Z9%7tqKZ)&e+T0WM$^2035}zE4=& zxz~;jAKMLqBj`LMP#5viDEW=F`2|{X`v_kcVEv>YM zND_vlgz+pP(@Pp;;B%R1HkVw#KrsBiH0dsoLM$@n2xg?~P;nz?FCYSmjo5+gZ3)A` zV1P&8=(qz#`Dz<8wI1*=jKB>H!IMTAdSe2H6D)H07-rfO3)}C*O}R%@`T4U(41+T} zYL`^cz$Y5d@i#J;)Lw-4R^&MOT-GTiZ}8+1?6oYRhMyB7Z(bo-a|Xd1-$+(&Q%^<2 zj&Z~mu!y)+x5R4I>=@mJ0Mv_#g_vG_;MN(#+c92zpK=am^G0kAnMsgU&2IQyo*Ozmk`GnIH-OZ0 z5K+Jm3khbrTaAjE|{)i)VDkGD+F7K^@qGHUZ_|`w0vH~ zFrGwC)$`(l@S7Q~g!(>9meUJ(>?=j3eSRlXmfXS2T%EMh0TC;cjGB9_^YzEms+^ED%4x^lps$t1+q z=Lx+|T6oNip{}1e5r}8+G}tAk`kPhdK!=0n`NWo0 zQ)tAQf^gG=fW2v|CPMFIvU(@;-Rp??VZ_$XbRe*HW~9)^jMhRcL@afBMEz9{5)F@G z_D;4`YWb}l`CjdpjJEJAB)EkHCC&lj1I-;iv1`b;;U7pnV}i(Csc zU`mV~kBR1A*A#qCtxb^y(@(nv0slBQ+7F}JpK+I*A8Nn?{uC?UxN)3`u;l;;SIOth znUym+F71jeXQsCwXHaT(3dw6i37^{U9V$H45nHLukeLBaZ@dE-M0J!B*wlpx0Ux7x z&m1?dawcw2`imeMkZ_1HbLO~;%n(okCrj^5sNI{uAj--P8E1bD*IYEkHE_00|*JXaXBB0@X2c2+qBB0@&8dMA7 z%L{>J$2_Z@FfxP9bJ)fu2xfNL;4+vi}ZUT78pjVWVg0GH{XQ zYz9+6$r~(N$LEW0kS@26BFonC#}{ADCKO-JS|`{lv-JCno$rAZ%l}Ml`Jco`OTVYE z=YEmyVzPQ>!Uk@+Bi0kcx`zyjc!IDiEAilgLIVyUw=sRm#Wb>;QKq-rgxr8maNN5b+mf$8u1?ag=n ze@2u{O(V5pZVb%39o7FzPQ@MsrCSN+P_a<|Au%MGHC3Ts8_QFD$!L1fxi`UQPdY@C zcF0|6=;L3IS$6*S>4jhTY{Z7+J-(N8IaI;-K;3`vfPv|y2{*(MR4WzXMcw7+a%8gK z+z7L2Cr8~w;t#iD?K@?V)}rp?ViuQM^8ue}VWTzMuRTQ2{*#8*P6T9X1f|wK21HjQ zQ89l?{F~o^Pxj9d!M(~n$^ooYEq9+__^e5Q;3(|ZM8YTQpwc8SXol_XYa206;s3Y?+1{o)g)(#`BjQpV4p?OtZ=ASs9Q)&Xa1R;UAvTr ztxv{VsV?Sh78z?+%_2)5D`+vj_$K=E8+F|hU zWs#%AeDL2H8^GV7T>1HNUgh0DRhr6_Euy&PkaOF>v2O5>0lLK_#5ipuZp-If%$ zz&;iD_I!r_klYX`%`*DZtXe2cwRGkHPQsURDDuts+38lkgXEu zp34Ea_Dq5`w-9uRvYBraTSd41iV*}|zlxa_CnWTx$DV3gn*vYwT?(zO2AsQ&{7%H&YKOy+YF=aXwo2OW(Vm8~CeMb~nu~STsu8yG6_lWg> zB&OBr5oyAn!y}~AG30jL#b*}Agyz3+=W5FSHGyE;DcFeu**bw+a~gE=YkvtSCz-kS z_W($3q1ZcOktP88U8b`9?|vynuy6Oe-2_xASF!_v`va9qP5~Z2vl0`QUq!98yOUb> zF)_95O5TqoqPxmCRn%3xA`@@I1TA4j=EIncl*i~&7jL8vyR3~(@aF6RGbc(6{ezDA zUNPUR8h};f<3sUO7-mi_e6)p3xBJ2B+V0b%s+m)pGY4o3}VEBNf2<36A15w^Ev+WHccrnD`_n$4y8 zJoXN8{hcFnU^ejYnt{SFP}5?4|E|nkkXz~2YN$i2^<8(N9G$T;S80RHjbNGumi6FY zYkK9KQda1b^&kX%rZt#KwyuWW(O+It!FhjUEsy2l(y_&RvXLr1BR2_I9@H3;rG-uC zUl%PDBdzSqvPh;bZe^JZhfjkWBGzjLKm0=4k~Rjatevz^>Yz`Bs9~O?Ts2_BBbvE( z$+esHeY_bMJs@~|-ocb4n!U{|b#6;a_MFH$xZH%o$&bO;yDOnhYaL;Lv}^}nx7Am! zT^AXpHoV!3k5|h#%h82Lw#c7NbK%vzcd2ORD`AYl=~Q3tHOto9>f4U;vT>KF{1yT- z3GnWC1w&Qx#@2{RJGMHEtTJ@^bJY>tGQ4jl-`}iY5f%N3vcskJRn2o{os`#ZE-05c zvu`=zLLGY+f-B#&TuSdqjU?|tRZ$Bh8wFc|8xB;(`pP58C?ATdyt)QJ3&4_u94HD= zRNrbU2Q>g()I|A3LYQZd`rwC}efZdSfKMr$oBH6B1=0o|Ea`9oz5p6o)X4hy2Y7w> zhwlyXj|%v&1Al$|^YrP%hf1I8uc#t|Td9DhRsk`UXdkdP3=3$2uyL-&Lit6?YXk#S z707{FZ=55>IX`YNQw!x4FpSPJa#*hq-ylK7b)TYK7rs!*bIbNyT9;GbA8MU-{m3yVDd+*0q?p#uxflX8;4WC8~&^*14k`MXZCy+m|2BB`5s zVGf?=x+71XdhPf489v z(Lbx^$4ov$-+j33Xgb#tAIBS!f@=Za({#?w2d7JXzHxjA(;}2hee~iF1{S{*!8^IDdn%0$PTwX<_SuVz<+|s z;y>?RQIcGL+!)Qo-CLs<6*`n$O{XTd93tTjqI5g>9?Nz|04#W2IP<1>?f^<@&R!h1 zU<9DUc~wZuSJP?LvFXhAJ{8$v3WuL}fuX#gphA2R3aC0yM@ZDEiQiIm_?Bq%UVA)E zWrb@+QzT!^S>*Mo081Zb?Oo89)S;tk-(M?^*+;>Erl=f~VoF$*D&HP%%|TL4V6$fv z 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
Ix0- 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

05{w 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

RpSHamfi<2T$-{1bA*%dPs^x2934|{JP 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

8Xfq2ulLV9^Z$qC6Rj( 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�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 fnNVjQ@sU5ymmG>9xKq!5=o!Ypz4#a-#?}7; - - - - - diff --git a/SynapseEngine/Assets/dark_icon_pink.svg b/SynapseEngine/Assets/dark_icon_pink.svg deleted file mode 100644 index 30a0b22a..00000000 --- a/SynapseEngine/Assets/dark_icon_pink.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/SynapseEngine/Assets/light_icon.svg b/SynapseEngine/Assets/light_icon.svg deleted file mode 100644 index 0083ecf3..00000000 --- a/SynapseEngine/Assets/light_icon.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/SynapseEngine/Assets/light_icon_pink.svg b/SynapseEngine/Assets/light_icon_pink.svg deleted file mode 100644 index f37c0eca..00000000 --- a/SynapseEngine/Assets/light_icon_pink.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/SynapseEngine/Engine/Engine.cpp b/SynapseEngine/Engine/Engine.cpp index 29d88113..10e80529 100644 --- a/SynapseEngine/Engine/Engine.cpp +++ b/SynapseEngine/Engine/Engine.cpp @@ -29,7 +29,6 @@ #include "Engine/Scene/Source/Procedural/TestSceneSource.h" #include "Engine/Scene/Source/Procedural/MaterialPreviewSceneSource.h" #include "Engine/Scene/Source/Procedural/ModelPreviewSceneSource.h" -#include "Engine/Scene/Source/Procedural/NatureSceneSource.h" #include "Engine/Scene/Source/File/FileSceneSource.h" #include "Engine/Render/RendererFactory.h" diff --git a/SynapseEngine/Engine/Render/Passes/Billboard/CameraBillboardPass.cpp b/SynapseEngine/Engine/Render/Passes/Billboard/CameraBillboardPass.cpp index 077291cf..2c3f24c7 100644 --- a/SynapseEngine/Engine/Render/Passes/Billboard/CameraBillboardPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Billboard/CameraBillboardPass.cpp @@ -70,7 +70,7 @@ namespace Syn { .colorAttachmentCount = 2 }; - _iconTexture = ServiceLocator::GetImageManager()->LoadImageSync(PathUtils::GetAbsolutePathString("Assets/Engine/CameraIcon.png")); + _iconTexture = ServiceLocator::GetImageManager()->LoadImageSync(PathUtils::GetAbsolutePathString("Assets/Engine/Icons/CameraIcon.png")); } void CameraBillboardPass::PrepareFrame(const RenderContext& context) { diff --git a/SynapseEngine/Engine/Render/Passes/Billboard/DirectionLightBillboardPass.cpp b/SynapseEngine/Engine/Render/Passes/Billboard/DirectionLightBillboardPass.cpp index bb78ee5c..f9a34c5a 100644 --- a/SynapseEngine/Engine/Render/Passes/Billboard/DirectionLightBillboardPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Billboard/DirectionLightBillboardPass.cpp @@ -69,7 +69,7 @@ namespace Syn { .colorAttachmentCount = 2 }; - _iconTexture = ServiceLocator::GetImageManager()->LoadImageSync(PathUtils::GetAbsolutePathString("Assets/Engine/DirectionLightIcon.png")); + _iconTexture = ServiceLocator::GetImageManager()->LoadImageSync(PathUtils::GetAbsolutePathString("Assets/Engine/Icons/DirectionLightIcon.png")); } void DirectionLightBillboardPass::PrepareFrame(const RenderContext& context) { diff --git a/SynapseEngine/Engine/Render/Passes/Billboard/PointLightBillboardPass.cpp b/SynapseEngine/Engine/Render/Passes/Billboard/PointLightBillboardPass.cpp index 65fe504b..648c7f7b 100644 --- a/SynapseEngine/Engine/Render/Passes/Billboard/PointLightBillboardPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Billboard/PointLightBillboardPass.cpp @@ -69,7 +69,7 @@ namespace Syn { .colorAttachmentCount = 2, }; - _iconTexture = ServiceLocator::GetImageManager()->LoadImageSync(PathUtils::GetAbsolutePathString("Assets/Engine/PointLightIcon.png")); + _iconTexture = ServiceLocator::GetImageManager()->LoadImageSync(PathUtils::GetAbsolutePathString("Assets/Engine/Icons/PointLightIcon.png")); } void PointLightBillboardPass::PrepareFrame(const RenderContext& context) { diff --git a/SynapseEngine/Engine/Render/Passes/Billboard/SpotLightBillboardPass.cpp b/SynapseEngine/Engine/Render/Passes/Billboard/SpotLightBillboardPass.cpp index 2e14a57d..ceb5bafc 100644 --- a/SynapseEngine/Engine/Render/Passes/Billboard/SpotLightBillboardPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Billboard/SpotLightBillboardPass.cpp @@ -69,7 +69,7 @@ namespace Syn { .colorAttachmentCount = 2 }; - _iconTexture = ServiceLocator::GetImageManager()->LoadImageSync(PathUtils::GetAbsolutePathString("Assets/Engine/SpotLightIcon.png")); + _iconTexture = ServiceLocator::GetImageManager()->LoadImageSync(PathUtils::GetAbsolutePathString("Assets/Engine/Icons/SpotLightIcon.png")); } void SpotLightBillboardPass::PrepareFrame(const RenderContext& context) { diff --git a/SynapseEngine/Engine/Scene/Source/Procedural/MaterialPreviewSceneSource.cpp b/SynapseEngine/Engine/Scene/Source/Procedural/MaterialPreviewSceneSource.cpp index cc87030a..f15c88d2 100644 --- a/SynapseEngine/Engine/Scene/Source/Procedural/MaterialPreviewSceneSource.cpp +++ b/SynapseEngine/Engine/Scene/Source/Procedural/MaterialPreviewSceneSource.cpp @@ -164,12 +164,10 @@ namespace Syn }; // 2. Center object: Suzanne - std::string basePath = "C:/Users/User/Desktop/Models/"; - - auto skyTextureId = ServiceLocator::GetImageManager()->LoadImageSync(basePath + "MaterialPreview.hdr"); + auto skyTextureId = ServiceLocator::GetImageManager()->LoadImageSync(PathUtils::GetAbsolutePathString("Assets/Engine/Environment/MaterialPreview.hdr")); scene.GetSettings()->environment.skyTextureId = skyTextureId; - uint32_t monkeyId = modelManager->LoadModelAsync(basePath + "Monkey/monkey.obj"); + uint32_t monkeyId = modelManager->LoadModelAsync(PathUtils::GetAbsolutePathString("Assets/Engine/Models/Monkey/Untitled.obj")); CreatePreviewObject("Center_Monkey", monkeyId, glm::vec3(0.0f, 0.5f, 0.0f), glm::vec3(1.5f)); uint32_t sphereId = modelManager->GetResourceIndex(MeshSourceNames::Sphere); diff --git a/SynapseEngine/Engine/Scene/Source/Procedural/ModelPreviewSceneSource.cpp b/SynapseEngine/Engine/Scene/Source/Procedural/ModelPreviewSceneSource.cpp index 657a4e38..6b025ecf 100644 --- a/SynapseEngine/Engine/Scene/Source/Procedural/ModelPreviewSceneSource.cpp +++ b/SynapseEngine/Engine/Scene/Source/Procedural/ModelPreviewSceneSource.cpp @@ -129,8 +129,7 @@ namespace Syn hm->AttachChild(rootEnvironment, previewModel); - std::string basePath = "C:/Users/User/Desktop/Models/"; - auto skyTextureId = ServiceLocator::GetImageManager()->LoadImageSync(basePath + "ModelPreview.hdr"); + auto skyTextureId = ServiceLocator::GetImageManager()->LoadImageSync(PathUtils::GetAbsolutePathString("Assets/Engine/Environment/ModelPreview.hdr")); scene.GetSettings()->environment.skyTextureId = skyTextureId; scene.GetSettings()->debug.enableInfiniteGrid = true; diff --git a/SynapseEngine/Engine/Scene/Source/Procedural/NatureSceneSource.cpp b/SynapseEngine/Engine/Scene/Source/Procedural/NatureSceneSource.cpp deleted file mode 100644 index a6f7f69e..00000000 --- a/SynapseEngine/Engine/Scene/Source/Procedural/NatureSceneSource.cpp +++ /dev/null @@ -1,211 +0,0 @@ -#include "NatureSceneSource.h" -#include "Engine/Scene/Scene.h" -#include "Engine/Scene/Insiders/SceneInsider.h" -#include "Engine/ServiceLocator.h" -#include "Engine/Component/Core/TransformComponent.h" -#include "Engine/Component/Core/CameraComponent.h" -#include "Engine/Component/Rendering/ModelComponent.h" -#include "Engine/Mesh/Factory/MeshFactory.h" -#include "Engine/Mesh/ModelManager.h" -#include "Engine/Manager/ShaderManager.h" -#include "Engine/Mesh/MeshSourceNames.h" -#include "Engine/Component/Rendering/MaterialOverrideComponent.h" -#include "Engine/Material/MaterialManager.h" -#include "Engine/Animation/AnimationManager.h" -#include "Engine/Component/Rendering/AnimationComponent.h" -#include "Engine/Component/Light/Point/PointLightComponent.h" -#include "Engine/Component/Light/Spot/SpotLightComponent.h" -#include "Engine/Component/Light/Direction/DirectionLightComponent.h" -#include "Engine/Component/Physics/BoxColliderComponent.h" -#include "Engine/Component/Physics/SphereColliderComponent.h" -#include "Engine/Component/Physics/CapsuleColliderComponent.h" -#include "Engine/Component/Physics/RigidBodyComponent.h" -#include "Engine/Logger/SynLog.h" -#include "Engine/Utils/PathUtils.h" - -#include -#include -#include - -using json = nlohmann::json; - -namespace Syn -{ - bool NatureSceneSource::Populate(Scene& scene) - { - Registry& registry = SceneInsider::GetRegistry(scene, SceneInsider::GetKey()); - EntityID& sceneCam = SceneInsider::GetSceneCameraEntity(scene, SceneInsider::GetKey()); - EntityID& debugCam = SceneInsider::GetDebugCameraEntity(scene, SceneInsider::GetKey()); - - auto modelManager = ServiceLocator::GetModelManager(); - auto animationManager = ServiceLocator::GetAnimationManager(); - auto materialManager = ServiceLocator::GetMaterialManager(); - - json config; - std::string path = PathUtils::GetAbsolutePathString("Engine/Scene/Source/Procedural/nature_config.json"); - std::ifstream configFile(path); - - if (configFile.is_open()) - { - try { - configFile >> config; - Syn::Info("Nature Scene configuration loaded successfully!"); - } - catch (const json::parse_error& e) { - Syn::Error("JSON parsing error: {}. Using default settings.", e.what()); - } - } - else - { - Syn::Warning("nature_config.json not found, using default settings."); - } - - std::string basePath = config.value("/paths/base_model_path"_json_pointer, "C:/Users/User/Desktop/Models/"); - bool spawnFloor = config.value("/environment/spawn_floor"_json_pointer, true); - int staticGeoCount = config.value("/entities/static_geometry"_json_pointer, 100); - - int dirLightCount = config.value("/lights/directional_count"_json_pointer, 1); - int pointLightCount = config.value("/lights/point_count"_json_pointer, 10); - int pointShadowCount = config.value("/lights/point_shadow_count"_json_pointer, 5); - int spotLightCount = config.value("/lights/spot_count"_json_pointer, 10); - int spotShadowCount = config.value("/lights/spot_shadow_count"_json_pointer, 5); - - std::vector treeIds = { - modelManager->LoadModelAsync(basePath + "Nature/Tree/CommonTree_3.fbx"), - modelManager->LoadModelAsync(basePath + "Nature/Tree-aVOxaHRPWe/CommonTree_2.fbx"), - modelManager->LoadModelAsync(basePath + "Nature/Tree-qZtx0AHhcy/CommonTree_1.fbx"), - modelManager->LoadModelAsync(basePath + "Nature/Tree-t9KbsfYdXz/CommonTree_5.fbx"), - modelManager->LoadModelAsync(basePath + "Nature/Tree-YWjGDJ9F7g/CommonTree_4.fbx") - }; - - // Cameras (Main & Debug) - { - sceneCam = scene.CreateEntity(); - registry.AddComponent(sceneCam); - registry.AddComponent(sceneCam); - registry.GetPool()->SetCategory(sceneCam, StorageCategory::Stream); - registry.GetPool()->SetCategory(sceneCam, StorageCategory::Stream); - - debugCam = scene.CreateEntity(); - registry.AddComponent(debugCam); - registry.AddComponent(debugCam); - registry.GetPool()->SetCategory(debugCam, StorageCategory::Stream); - registry.GetPool()->SetCategory(debugCam, StorageCategory::Stream); - } - - if (spawnFloor) - { - EntityID floorEntity = scene.CreateEntity(); - registry.AddComponent(floorEntity); - registry.AddComponent(floorEntity); - registry.AddComponent(floorEntity); - registry.AddComponent(floorEntity); - registry.AddComponent(floorEntity); - - auto& floorTransform = registry.GetComponent(floorEntity); - floorTransform.translation = glm::vec3(0.0f, -1.0f, 0.0f); - floorTransform.scale = glm::vec3(500.0f, 1.0f, 500.0f); - - auto& floorModel = registry.GetComponent(floorEntity); - floorModel.modelIndex = modelManager->GetResourceIndex(MeshSourceNames::Cube); - - auto& floorRb = registry.GetComponent(floorEntity); - floorRb.motionType = PhysicsMotionType::Static; - - registry.GetPool()->SetCategory(floorEntity, StorageCategory::Static); - registry.GetPool()->SetCategory(floorEntity, StorageCategory::Static); - registry.GetPool()->SetCategory(floorEntity, StorageCategory::Static); - registry.GetPool()->SetCategory(floorEntity, StorageCategory::Static); - - MaterialInfo floorMatInfo{}; - floorMatInfo.color = glm::vec4(0.2f, 0.8f, 0.2f, 1.0f); - uint32_t floorMatId = materialManager->LoadMaterial("FloorMat", floorMatInfo); - registry.GetComponent(floorEntity).materials.push_back(floorMatId); - } - - // Static Geometry - for (int i = 0; i < staticGeoCount; i++) { - EntityID e = scene.CreateEntity(); - registry.AddComponent(e); - registry.AddComponent(e); - registry.AddComponent(e); - - auto& transform = registry.GetComponent(e); - transform.translation = glm::vec3((rand() % 1000) - 500.0f, 0, (rand() % 1000) - 500.0f); - transform.rotation = glm::vec3(0, rand() % 360, 0); - transform.scale = glm::vec3(0.01f); - - registry.GetComponent(e).modelIndex = treeIds[rand() % treeIds.size()]; - - registry.GetPool()->SetCategory(e, StorageCategory::Static); - registry.GetPool()->SetCategory(e, StorageCategory::Static); - } - - // Lights: Directional - for (int i = 0; i < dirLightCount; ++i) { - EntityID e = scene.CreateEntity(); - registry.AddComponent(e); - registry.AddComponent(e); - - registry.GetComponent(e).rotation = glm::vec3(-45.0f, 45.0f, 0.0f); - auto& light = registry.GetComponent(e); - light.color = glm::vec3(1.0f, 0.95f, 0.85f) * 0.55f; - light.strength = 5.0f; - light.useShadow = true; - - registry.GetPool()->SetCategory(e, StorageCategory::Stream); - registry.GetPool()->SetCategory(e, StorageCategory::Stream); - registry.GetPool()->SetBit(e); - registry.GetPool()->SetBit(e); - } - - // Lights: Point - for (int i = 0; i < pointLightCount; i++) { - EntityID e = scene.CreateEntity(); - registry.AddComponent(e); - registry.AddComponent(e); - - auto& transform = registry.GetComponent(e); - transform.translation = glm::vec3((rand() % 1000) - 500.0f, (rand() % 10) + 5.0f, (rand() % 1000) - 500.0f); - - auto& light = registry.GetComponent(e); - light.position = transform.translation; - light.color = glm::vec3(static_cast(rand()) / RAND_MAX, static_cast(rand()) / RAND_MAX, static_cast(rand()) / RAND_MAX); - light.radius = 2.0f + (rand() % 10); - light.strength = 5.0f + (rand() % 25); - light.useShadow = (i < pointShadowCount); - - registry.GetPool()->SetCategory(e, StorageCategory::Stream); - registry.GetPool()->SetCategory(e, StorageCategory::Stream); - registry.GetPool()->SetBit(e); - registry.GetPool()->SetBit(e); - } - - // Lights: Spot - for (int i = 0; i < spotLightCount; i++) { - EntityID e = scene.CreateEntity(); - registry.AddComponent(e); - registry.AddComponent(e); - - auto& transform = registry.GetComponent(e); - transform.translation = glm::vec3((rand() % 1000) - 500.0f, (rand() % 10) + 5.0f, (rand() % 1000) - 500.0f); - transform.rotation = glm::vec3(-45.0f - (rand() % 45), (float)(rand() % 360), 0.0f); - - auto& light = registry.GetComponent(e); - light.position = transform.translation; - light.color = glm::vec3(static_cast(rand()) / RAND_MAX, static_cast(rand()) / RAND_MAX, static_cast(rand()) / RAND_MAX); - light.range = 30.0f + (rand() % 30); - light.innerAngle = 15.0f + (rand() % 10); - light.outerAngle = light.innerAngle + 10.0f + (rand() % 15); - light.strength = 5.0f + (rand() % 25); - light.useShadow = (i < spotShadowCount); - - registry.GetPool()->SetCategory(e, StorageCategory::Stream); - registry.GetPool()->SetCategory(e, StorageCategory::Stream); - registry.GetPool()->SetBit(e); - registry.GetPool()->SetBit(e); - } - - return true; - } -} \ No newline at end of file diff --git a/SynapseEngine/Engine/Scene/Source/Procedural/NatureSceneSource.h b/SynapseEngine/Engine/Scene/Source/Procedural/NatureSceneSource.h deleted file mode 100644 index e958b37e..00000000 --- a/SynapseEngine/Engine/Scene/Source/Procedural/NatureSceneSource.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once -#include "Engine/SynApi.h" -#include "Engine/Scene/Source/ISceneSource.h" - -namespace Syn -{ - class SYN_API NatureSceneSource : public ISceneSource - { - public: - NatureSceneSource() = default; - virtual bool Populate(Scene& scene) override; - }; -} \ No newline at end of file diff --git a/SynapseEngine/Engine/Scene/Source/Procedural/TestSceneSource.cpp b/SynapseEngine/Engine/Scene/Source/Procedural/TestSceneSource.cpp index 5adb1e9b..d095ea00 100644 --- a/SynapseEngine/Engine/Scene/Source/Procedural/TestSceneSource.cpp +++ b/SynapseEngine/Engine/Scene/Source/Procedural/TestSceneSource.cpp @@ -66,7 +66,9 @@ namespace Syn Syn::Warning("scene_config.json not found, using default settings."); } - std::string basePath = config.value("/paths/base_model_path"_json_pointer, "C:/Users/User/Desktop/Models/"); + const std::string modelPath = "Assets/Engine/Models/"; + const std::string envPath = "Assets/Engine/Environment/"; + bool spawnSponza = config.value("/environment/spawn_sponza"_json_pointer, true); bool spawnBistro = config.value("/environment/spawn_bistro"_json_pointer, false); bool spawnFloor = config.value("/environment/spawn_floor"_json_pointer, true); @@ -102,7 +104,7 @@ namespace Syn modelManager->GetResourceIndex(MeshSourceNames::Torus) }; - auto skyTextureId = ServiceLocator::GetImageManager()->LoadImageSync(basePath + "MainScene.hdr"); + auto skyTextureId = ServiceLocator::GetImageManager()->LoadImageSync(PathUtils::GetAbsolutePathString(envPath + "MainScene.hdr")); scene.GetSettings()->environment.skyTextureId = skyTextureId; EntityID rootCameras = scene.CreateEntity(); @@ -172,7 +174,7 @@ namespace Syn if (spawnMonkey) { - uint32_t monkeyModelIndex = modelManager->LoadModelAsync(basePath + "Monkey/monkey.obj"); + uint32_t monkeyModelIndex = modelManager->LoadModelAsync(PathUtils::GetAbsolutePathString(modelPath + "Monkey/Untitled.obj")); EntityID monkeyId = scene.CreateEntity(); registry.AddComponent(monkeyId); @@ -195,7 +197,7 @@ namespace Syn if (spawnSponza) { - uint32_t sponzaId = modelManager->LoadModelAsync(basePath + "Sponza/sponza.obj"); + uint32_t sponzaId = modelManager->LoadModelAsync(PathUtils::GetAbsolutePathString(modelPath + "Sponza/sponza.obj")); EntityID sponzaEntity = scene.CreateEntity(); registry.AddComponent(sponzaEntity); @@ -221,27 +223,6 @@ namespace Syn hm->AttachChild(rootEnvironment, sponzaEntity); } - if (spawnBistro) - { - uint32_t bistroId = modelManager->LoadModelAsync(basePath + "Bistro/BistroExterior.fbx"); - - EntityID bistroEntity = scene.CreateEntity(); - registry.AddComponent(bistroEntity); - registry.GetComponent(bistroEntity).name = "Amazon_Bistro"; - registry.GetComponent(bistroEntity).tag = "Model"; - registry.AddComponent(bistroEntity); - registry.AddComponent(bistroEntity); - - registry.GetComponent(bistroEntity).translation = glm::vec3(0.0f, 0.0f, 0.0f); - registry.GetComponent(bistroEntity).scale = glm::vec3(2.3f, 2.3f, 2.3f); - registry.GetComponent(bistroEntity).modelIndex = bistroId; - - registry.GetPool()->SetCategory(bistroEntity, StorageCategory::Static); - registry.GetPool()->SetCategory(bistroEntity, StorageCategory::Static); - - hm->AttachChild(rootEnvironment, bistroEntity); - } - if (spawnFloor) { EntityID floorEntity = scene.CreateEntity(); @@ -277,46 +258,16 @@ namespace Syn hm->AttachChild(rootEnvironment, floorEntity); } - if (spawnPbrSponza) - { - uint32_t sponzaPbr = modelManager->LoadModelAsync(basePath + "Sponza_Pbr/NewSponza_Main_Yup_003.fbx"); - uint32_t sponzaPbrCurtains = modelManager->LoadModelAsync(basePath + "Sponza_Pbr_Curtains/NewSponza_Curtains_FBX_YUp.fbx"); - uint32_t sponzaPbrFlowers = modelManager->LoadModelAsync(basePath + "Sponza_Pbr_Flowers/NewSponza_IvyGrowth_FBX_YUp.fbx"); - uint32_t sponzaPbrTree = modelManager->LoadModelAsync(basePath + "Sponza_Pbr_Tree/NewSponza_CypressTree_FBX_YUp.fbx"); - - std::array sponzaModels = { sponzaPbr, sponzaPbrCurtains, sponzaPbrFlowers, sponzaPbrTree }; - std::array sponzaNames = { "PBR_Sponza_Main", "PBR_Sponza_Curtains", "PBR_Sponza_Flowers", "PBR_Sponza_Tree" }; - - for (size_t i = 0; i < sponzaModels.size(); i++) - { - EntityID entity = scene.CreateEntity(); - registry.AddComponent(entity); - registry.GetComponent(entity).name = sponzaNames[i]; - registry.GetComponent(entity).tag = "Model"; - registry.AddComponent(entity); - registry.AddComponent(entity); - - registry.GetComponent(entity).translation = glm::vec3(0.0f, 0.0f, 0.0f); - registry.GetComponent(entity).scale = glm::vec3(0.2f, 0.2f, 0.2f); - registry.GetComponent(entity).modelIndex = sponzaModels[i]; - - registry.GetPool()->SetCategory(entity, StorageCategory::Static); - registry.GetPool()->SetCategory(entity, StorageCategory::Static); - - hm->AttachChild(rootEnvironment, entity); - } - } - if (charCount > 0) { - uint32_t mutantId = modelManager->LoadModelAsync(basePath + "Monster/Mutant/Mutant.dae"); + uint32_t mutantId = modelManager->LoadModelAsync(PathUtils::GetAbsolutePathString(modelPath + "Monster/Mutant/Mutant.dae")); std::vector animationIds; - animationIds.push_back(animationManager->LoadAnimationAsync(basePath + "Monster/Breakdance 1990/Breakdance 1990.dae", mutantId)); - animationIds.push_back(animationManager->LoadAnimationAsync(basePath + "Monster/Breakdance Ending 1/Breakdance Ending 1.dae", mutantId)); - animationIds.push_back(animationManager->LoadAnimationAsync(basePath + "Monster/Dancing/Dancing.dae", mutantId)); - animationIds.push_back(animationManager->LoadAnimationAsync(basePath + "Monster/Hip Hop Dancing/Hip Hop Dancing.dae", mutantId)); - animationIds.push_back(animationManager->LoadAnimationAsync(basePath + "Monster/Hip Hop Dancing_2/Hip Hop Dancing.dae", mutantId)); + animationIds.push_back(animationManager->LoadAnimationAsync(PathUtils::GetAbsolutePathString(modelPath + "Monster/Breakdance 1990/Breakdance 1990.dae"), mutantId)); + animationIds.push_back(animationManager->LoadAnimationAsync(PathUtils::GetAbsolutePathString(modelPath + "Monster/Breakdance Ending 1/Breakdance Ending 1.dae"), mutantId)); + animationIds.push_back(animationManager->LoadAnimationAsync(PathUtils::GetAbsolutePathString(modelPath + "Monster/Dancing/Dancing.dae"), mutantId)); + animationIds.push_back(animationManager->LoadAnimationAsync(PathUtils::GetAbsolutePathString(modelPath + "Monster/Hip Hop Dancing/Hip Hop Dancing.dae"), mutantId)); + animationIds.push_back(animationManager->LoadAnimationAsync(PathUtils::GetAbsolutePathString(modelPath + "Monster/Hip Hop Dancing_2/Hip Hop Dancing.dae"), mutantId)); // Animated Characters for (int i = 0; i < charCount; i++) diff --git a/SynapseEngine/Engine/Scene/Source/Procedural/nature_config.json b/SynapseEngine/Engine/Scene/Source/Procedural/nature_config.json deleted file mode 100644 index 8c2138cf..00000000 --- a/SynapseEngine/Engine/Scene/Source/Procedural/nature_config.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "paths": { - "base_model_path": "C:/Users/User/Desktop/Models/" - }, - "environment": { - "spawn_floor": true - }, - "entities": { - "static_geometry": 100000, - }, - "lights": { - "directional_count": 1, - "point_count": 256, - "point_shadow_count": 0, - "spot_count": 256, - "spot_shadow_count": 0 - } -} \ No newline at end of file diff --git a/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json b/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json index 6469175c..f372cda9 100644 --- a/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json +++ b/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json @@ -1,12 +1,7 @@ { - "paths": { - "base_model_path": "C:/Users/User/Desktop/Models/" - }, "environment": { "spawn_sponza": true, - "spawn_bistro": false, "spawn_floor": false, - "spawn_pbr_sponza": false, "spawn_monkey": true }, "materials": { diff --git a/SynapseEngine/xmake.lua b/SynapseEngine/xmake.lua index e2736594..e6bf2d41 100644 --- a/SynapseEngine/xmake.lua +++ b/SynapseEngine/xmake.lua @@ -172,6 +172,30 @@ target("Editor") add_deps("Engine", "EditorCore") set_rundir("$(projectdir)") + on_load(function (target) + local asset_dir = path.join(os.projectdir(), "Assets") + + if not os.isdir(asset_dir) then + import("net.http") + import("utils.archive.extract") + + local zip_path = path.join(os.projectdir(), "Assets.zip") + local url = "https://github.com/TamasPetii/SynapseEngine/releases/latest/download/Assets.zip" + + http.download(url, zip_path) + + print(" Download complete! Extracting to: " .. os.projectdir()) + + extract(zip_path, os.projectdir()) + + os.rm(zip_path) + + print("=================================================") + print(" Assets successfully downloaded and installed!") + print("=================================================") + end + end) + target("UnitTests") set_kind("binary") add_files("UnitTests/**.cpp") From fa3fee917f03acaa871620434b2091dab1a662ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20P=C3=A9ter?= Date: Wed, 8 Jul 2026 15:01:11 +0200 Subject: [PATCH 32/51] Transform hierarchical fully parallel update work queue graph implemented --- .gitignore | 1 + .../Editor/View/Settings/SettingsView.cpp | 1 + .../ViewModels/Viewport/ViewportState.h | 2 +- SynapseEngine/Engine/Registry/BitFlag.h | 7 +- .../PostProcess/SkySphere/SkySpherePass.cpp | 1 + .../DirectionLightShadowMeshletOpaquePass.cpp | 1 + .../PointLightShadowMeshletOpaquePass.cpp | 1 + .../SpotLightShadowMeshletOpaquePass.cpp | 1 + .../Scene/Hierarchy/HierarchyLevelData.cpp | 1 + .../Scene/Hierarchy/HierarchyLevelData.h | 13 +++ .../{ => Hierarchy}/HierarchyManager.cpp | 0 .../Scene/{ => Hierarchy}/HierarchyManager.h | 39 ++++++-- .../Scene/Hierarchy/HierarchyWorkQueue.cpp | 21 +++++ .../Scene/Hierarchy/HierarchyWorkQueue.h | 66 +++++++++++++ SynapseEngine/Engine/Scene/Scene.cpp | 3 + SynapseEngine/Engine/Scene/Scene.h | 2 +- .../Engine/Scene/Settings/CullingSettings.cpp | 2 +- .../Scene/Settings/EnvironmentSettings.cpp | 1 + .../Scene/Settings/EnvironmentSettings.h | 1 + .../Includes/PushConstants/SkySpherePC.glsl | 1 + .../Shaders/Includes/Utils/CullingMath.glsl | 8 +- .../PointLightShadowMeshCulling.comp | 5 - .../PointLightShadowModelCulling.comp | 5 - .../PointLightShadowMortonChunkCulling.comp | 6 -- .../PointLightShadowMortonModelCulling.comp | 6 -- .../PointLightShadowStaticChunkCulling.comp | 6 -- .../PointLightShadowStaticModelCulling.comp | 5 - .../SpotLight/SpotLightShadowMeshCulling.comp | 8 -- .../SpotLightShadowModelCulling.comp | 8 -- .../SpotLightShadowMortonChunkCulling.comp | 8 -- .../SpotLightShadowMortonModelCulling.comp | 8 -- .../SpotLightShadowStaticChunkCulling.comp | 8 -- .../SpotLightShadowStaticModelCulling.comp | 8 -- .../PostProcess/SkySphere/SkySphere.frag | 1 + .../DirectionLightShadowMeshlet.task | 15 +-- .../PointLight/PointLightShadowMeshlet.task | 10 +- .../SpotLight/SpotLightShadowMeshlet.task | 2 +- SynapseEngine/Engine/System/ComponentSystem.h | 4 +- .../System/Core/TransformSetupSystem.cpp | 94 +++++++++++++++++++ .../Engine/System/Core/TransformSetupSystem.h | 18 ++++ .../Engine/System/Core/TransformSystem.cpp | 75 ++++++++++++--- .../Engine/System/Core/TransformSystem.h | 1 + SynapseEngine/UnitTests/TestHierarchy.cpp | 2 +- 43 files changed, 339 insertions(+), 137 deletions(-) create mode 100644 SynapseEngine/Engine/Scene/Hierarchy/HierarchyLevelData.cpp create mode 100644 SynapseEngine/Engine/Scene/Hierarchy/HierarchyLevelData.h rename SynapseEngine/Engine/Scene/{ => Hierarchy}/HierarchyManager.cpp (100%) rename SynapseEngine/Engine/Scene/{ => Hierarchy}/HierarchyManager.h (58%) create mode 100644 SynapseEngine/Engine/Scene/Hierarchy/HierarchyWorkQueue.cpp create mode 100644 SynapseEngine/Engine/Scene/Hierarchy/HierarchyWorkQueue.h create mode 100644 SynapseEngine/Engine/System/Core/TransformSetupSystem.cpp create mode 100644 SynapseEngine/Engine/System/Core/TransformSetupSystem.h diff --git a/.gitignore b/.gitignore index eac150c9..ff33e148 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,7 @@ install_manifest.txt # ========================================== # Binaries, Intermediates, and Compiled Objects # ========================================== +Assets/ Binaries/ Intermediates/ [Dd]ebug/ diff --git a/SynapseEngine/Editor/View/Settings/SettingsView.cpp b/SynapseEngine/Editor/View/Settings/SettingsView.cpp index e5b2d609..f006c738 100644 --- a/SynapseEngine/Editor/View/Settings/SettingsView.cpp +++ b/SynapseEngine/Editor/View/Settings/SettingsView.cpp @@ -263,6 +263,7 @@ namespace Syn { } changed |= Syn::UI::PropertyDragFloat("Intensity", settings.environment.skyIntensity, 0.05f, 0.0f, 100.0f, "%.2f", 1); + changed |= Syn::UI::PropertyDragFloat("Exposure", settings.environment.skyExposureEV, 0.05f, -100.0f, 100.0f, "%.2f", 1); Syn::UI::BeginProperty("Tint"); if (ImGui::ColorEdit3("##SkyTint", glm::value_ptr(settings.environment.skyTint), ImGuiColorEditFlags_NoInputs)) { diff --git a/SynapseEngine/EditorCore/ViewModels/Viewport/ViewportState.h b/SynapseEngine/EditorCore/ViewModels/Viewport/ViewportState.h index 088ebad3..1a5ba680 100644 --- a/SynapseEngine/EditorCore/ViewModels/Viewport/ViewportState.h +++ b/SynapseEngine/EditorCore/ViewModels/Viewport/ViewportState.h @@ -28,7 +28,7 @@ namespace Syn { bool isFocused = false; ImGuizmo::OPERATION gizmoOperation = ImGuizmo::TRANSLATE; - ImGuizmo::MODE gizmoMode = ImGuizmo::LOCAL; + ImGuizmo::MODE gizmoMode = ImGuizmo::WORLD; bool useSnap = false; glm::vec3 snapTranslate{ 1.0f, 1.0f, 1.0f }; diff --git a/SynapseEngine/Engine/Registry/BitFlag.h b/SynapseEngine/Engine/Registry/BitFlag.h index f2a8219a..db6d03aa 100644 --- a/SynapseEngine/Engine/Registry/BitFlag.h +++ b/SynapseEngine/Engine/Registry/BitFlag.h @@ -8,7 +8,8 @@ namespace Syn { constexpr uint32_t INDEX_CHANGED_BIT = 3; constexpr uint32_t DIRTY_STATIC_BIT = 4; constexpr uint32_t FORCE_STATIC_GPU_UPLOAD = 5; - constexpr uint32_t CUSTOM_CHANGED_BIT1 = 6; - constexpr uint32_t CUSTOM_CHANGED_BIT2 = 7; - constexpr uint32_t CUSTOM_CHANGED_BIT3 = 8; + constexpr uint32_t QUEUED_BIT = 6; + constexpr uint32_t CUSTOM_CHANGED_BIT1 = 7; + constexpr uint32_t CUSTOM_CHANGED_BIT2 = 8; + constexpr uint32_t CUSTOM_CHANGED_BIT3 = 9; } diff --git a/SynapseEngine/Engine/Render/Passes/PostProcess/SkySphere/SkySpherePass.cpp b/SynapseEngine/Engine/Render/Passes/PostProcess/SkySphere/SkySpherePass.cpp index acd8a9cf..e172094b 100644 --- a/SynapseEngine/Engine/Render/Passes/PostProcess/SkySphere/SkySpherePass.cpp +++ b/SynapseEngine/Engine/Render/Passes/PostProcess/SkySphere/SkySpherePass.cpp @@ -123,6 +123,7 @@ namespace Syn { pc->skyIntensity = envSettings.skyIntensity; pc->skyRotationMatrix = rotMat; pc->skyTint = envSettings.skyTint; + pc->skyExposureEV = envSettings.skyExposureEV; pc.Push(context.cmd, _shaderProgram->GetLayout()); } diff --git a/SynapseEngine/Engine/Render/Passes/Shadow/DirectionLight/DirectionLightShadowMeshletOpaquePass.cpp b/SynapseEngine/Engine/Render/Passes/Shadow/DirectionLight/DirectionLightShadowMeshletOpaquePass.cpp index c75fc8b9..9f97d2b1 100644 --- a/SynapseEngine/Engine/Render/Passes/Shadow/DirectionLight/DirectionLightShadowMeshletOpaquePass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Shadow/DirectionLight/DirectionLightShadowMeshletOpaquePass.cpp @@ -103,6 +103,7 @@ namespace Syn { pc->frameGlobalContextBufferAddr = scene->GetSceneDrawData()->frameContextBuffer.GetAddress(fIdx); pc->baseDescriptorOffset = drawData->Models.activeTraditionalCount + drawData->Models.meshletCmdOffsets[_renderType]; pc->materialRenderType = static_cast(_renderType); + pc->disableConeCulling = _renderType == MaterialRenderType::Opaque2Sided ? 1 : 0; pc.Push(context.cmd, _shaderProgram->GetLayout()); } diff --git a/SynapseEngine/Engine/Render/Passes/Shadow/PointLight/PointLightShadowMeshletOpaquePass.cpp b/SynapseEngine/Engine/Render/Passes/Shadow/PointLight/PointLightShadowMeshletOpaquePass.cpp index 73703f1a..a23a1b4b 100644 --- a/SynapseEngine/Engine/Render/Passes/Shadow/PointLight/PointLightShadowMeshletOpaquePass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Shadow/PointLight/PointLightShadowMeshletOpaquePass.cpp @@ -103,6 +103,7 @@ namespace Syn { pc->frameGlobalContextBufferAddr = scene->GetSceneDrawData()->frameContextBuffer.GetAddress(fIdx); pc->baseDescriptorOffset = drawData->Models.activeTraditionalCount + drawData->Models.meshletCmdOffsets[_renderType]; pc->materialRenderType = static_cast(_renderType); + pc->disableConeCulling = _renderType == MaterialRenderType::Opaque2Sided ? 1 : 0; pc.Push(context.cmd, _shaderProgram->GetLayout()); } diff --git a/SynapseEngine/Engine/Render/Passes/Shadow/SpotLight/SpotLightShadowMeshletOpaquePass.cpp b/SynapseEngine/Engine/Render/Passes/Shadow/SpotLight/SpotLightShadowMeshletOpaquePass.cpp index 78962374..ed4e3e7c 100644 --- a/SynapseEngine/Engine/Render/Passes/Shadow/SpotLight/SpotLightShadowMeshletOpaquePass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Shadow/SpotLight/SpotLightShadowMeshletOpaquePass.cpp @@ -103,6 +103,7 @@ namespace Syn { pc->frameGlobalContextBufferAddr = scene->GetSceneDrawData()->frameContextBuffer.GetAddress(fIdx); pc->baseDescriptorOffset = drawData->Models.activeTraditionalCount + drawData->Models.meshletCmdOffsets[_renderType]; pc->materialRenderType = static_cast(_renderType); + pc->disableConeCulling = _renderType == MaterialRenderType::Opaque2Sided ? 1 : 0; pc.Push(context.cmd, _shaderProgram->GetLayout()); } diff --git a/SynapseEngine/Engine/Scene/Hierarchy/HierarchyLevelData.cpp b/SynapseEngine/Engine/Scene/Hierarchy/HierarchyLevelData.cpp new file mode 100644 index 00000000..3943cb7c --- /dev/null +++ b/SynapseEngine/Engine/Scene/Hierarchy/HierarchyLevelData.cpp @@ -0,0 +1 @@ +#include "HierarchyLevelData.h" \ No newline at end of file diff --git a/SynapseEngine/Engine/Scene/Hierarchy/HierarchyLevelData.h b/SynapseEngine/Engine/Scene/Hierarchy/HierarchyLevelData.h new file mode 100644 index 00000000..ff13cf92 --- /dev/null +++ b/SynapseEngine/Engine/Scene/Hierarchy/HierarchyLevelData.h @@ -0,0 +1,13 @@ +#pragma once +#include "Engine/SynApi.h" +#include + +namespace Syn +{ + struct SYN_API HierarchyLevelData + { + uint32_t startIndex = 0; + uint32_t capacity = 0; + uint32_t activeCount = 0; + }; +} \ No newline at end of file diff --git a/SynapseEngine/Engine/Scene/HierarchyManager.cpp b/SynapseEngine/Engine/Scene/Hierarchy/HierarchyManager.cpp similarity index 100% rename from SynapseEngine/Engine/Scene/HierarchyManager.cpp rename to SynapseEngine/Engine/Scene/Hierarchy/HierarchyManager.cpp diff --git a/SynapseEngine/Engine/Scene/HierarchyManager.h b/SynapseEngine/Engine/Scene/Hierarchy/HierarchyManager.h similarity index 58% rename from SynapseEngine/Engine/Scene/HierarchyManager.h rename to SynapseEngine/Engine/Scene/Hierarchy/HierarchyManager.h index 288819bd..f62e9806 100644 --- a/SynapseEngine/Engine/Scene/HierarchyManager.h +++ b/SynapseEngine/Engine/Scene/Hierarchy/HierarchyManager.h @@ -2,23 +2,24 @@ #include "Engine/SynApi.h" #include "Engine/Registry/Registry.h" #include "Engine/Component/Core/HierarchyComponent.h" +#include "Engine/Registry/Type/TypeInfo.h" +#include "HierarchyWorkQueue.h" +#include "HierarchyLevelData.h" #include #include +#include +#include namespace Syn { - struct SYN_API HierarchyLevelData - { - uint32_t startIndex = 0; - uint32_t capacity = 0; - uint32_t activeCount = 0; - }; - class SYN_API HierarchyManager { public: HierarchyManager(Registry* registry); + HierarchyManager(const HierarchyManager&) = delete; + HierarchyManager& operator=(const HierarchyManager&) = delete; + void AttachChild(EntityID parent, EntityID child); void DetachChild(EntityID child); bool CanAttach(EntityID parent, EntityID child) const; @@ -30,16 +31,38 @@ namespace Syn uint32_t GetMaxActiveLevel() const { return static_cast(_levels.size()); } const std::span GetLevels() const { return std::span(_levels.data(), _levels.size()); } uint64_t GetVersion() const { return _version; } + + template + HierarchyWorkQueue* EnsureWorkQueue(); private: void InsertIntoLevel(EntityID entity, uint32_t level); void RemoveFromLevel(EntityID entity); void RebuildTopologicalArray(); void UpdateSubtreeLevels(EntityID root, int32_t levelDelta); - private: Registry* _registry; std::vector _topologicalArray; std::vector _levels; uint64_t _version = 0; + + std::unordered_map> _workQueues; }; + + template + HierarchyWorkQueue* HierarchyManager::EnsureWorkQueue() + { + const TypeID id = TypeInfo::ID; + auto it = _workQueues.find(id); + + [[unlikely]] + if (it == _workQueues.end()) + { + auto queue = std::make_unique(); + auto* rawPtr = queue.get(); + _workQueues[id] = std::move(queue); + return rawPtr; + } + + return it->second.get(); + } } \ No newline at end of file diff --git a/SynapseEngine/Engine/Scene/Hierarchy/HierarchyWorkQueue.cpp b/SynapseEngine/Engine/Scene/Hierarchy/HierarchyWorkQueue.cpp new file mode 100644 index 00000000..3295c02c --- /dev/null +++ b/SynapseEngine/Engine/Scene/Hierarchy/HierarchyWorkQueue.cpp @@ -0,0 +1,21 @@ +#include "HierarchyWorkQueue.h" + +namespace Syn +{ + void HierarchyWorkQueue::Initialize(std::span levels) + { + uint32_t maxLevel = static_cast(levels.size()); + + if (_queues.size() < maxLevel) { + _queues.resize(maxLevel); + } + + for (uint32_t i = 0; i < maxLevel; ++i) { + if (_queues[i].entities.size() < levels[i].capacity) { + _queues[i].entities.resize(levels[i].capacity); + } + + _queues[i].count.store(0, std::memory_order_relaxed); + } + } +} \ No newline at end of file diff --git a/SynapseEngine/Engine/Scene/Hierarchy/HierarchyWorkQueue.h b/SynapseEngine/Engine/Scene/Hierarchy/HierarchyWorkQueue.h new file mode 100644 index 00000000..d4688f7b --- /dev/null +++ b/SynapseEngine/Engine/Scene/Hierarchy/HierarchyWorkQueue.h @@ -0,0 +1,66 @@ +#pragma once +#include "Engine/SynApi.h" +#include "Engine/SynMacro.h" +#include "Engine/Registry/Entity.h" +#include "HierarchyLevelData.h" +#include +#include +#include + +namespace Syn +{ + struct alignas(64) SYN_API LevelQueue + { + std::vector entities; + std::atomic count{ 0 }; + + LevelQueue() = default; + + LevelQueue(LevelQueue&& other) noexcept + : entities(std::move(other.entities)) + { + count.store(other.count.load(std::memory_order_relaxed), std::memory_order_relaxed); + } + + LevelQueue& operator=(LevelQueue&& other) noexcept + { + if (this != &other) { + entities = std::move(other.entities); + count.store(other.count.load(std::memory_order_relaxed), std::memory_order_relaxed); + } + return *this; + } + + LevelQueue(const LevelQueue&) = delete; + LevelQueue& operator=(const LevelQueue&) = delete; + }; + + class SYN_API HierarchyWorkQueue + { + public: + HierarchyWorkQueue() = default; + + HierarchyWorkQueue(const HierarchyWorkQueue&) = delete; + HierarchyWorkQueue& operator=(const HierarchyWorkQueue&) = delete; + + HierarchyWorkQueue(HierarchyWorkQueue&&) = default; + HierarchyWorkQueue& operator=(HierarchyWorkQueue&&) = default; + + void Initialize(std::span levels); + + SYN_INLINE void Push(uint32_t level, EntityID entity) + { + size_t idx = _queues[level].count.fetch_add(1, std::memory_order_relaxed); + _queues[level].entities[idx] = entity; + } + + SYN_INLINE std::span GetQueue(uint32_t level) const + { + size_t activeCount = _queues[level].count.load(std::memory_order_relaxed); + return std::span(_queues[level].entities.data(), activeCount); + } + + private: + std::vector _queues; + }; +} \ No newline at end of file diff --git a/SynapseEngine/Engine/Scene/Scene.cpp b/SynapseEngine/Engine/Scene/Scene.cpp index 95706f69..b022bb21 100644 --- a/SynapseEngine/Engine/Scene/Scene.cpp +++ b/SynapseEngine/Engine/Scene/Scene.cpp @@ -17,6 +17,7 @@ #include "Engine/Component/Rendering/MaterialOverrideComponent.h" #include "Engine/System/Core/TransformSystem.h" +#include "Engine/System/Core/TransformSetupSystem.h" #include "Engine/System/Core/HierarchySystem.h" #include "Engine/System/Core/SelectionOutlineSystem.h" #include "Engine/System/Core/TagSystem.h" @@ -136,7 +137,9 @@ namespace Syn void Scene::InitializeSystems() { + RegisterSystem(); RegisterSystem(); + RegisterSystem(); RegisterSystem(); RegisterSystem(); diff --git a/SynapseEngine/Engine/Scene/Scene.h b/SynapseEngine/Engine/Scene/Scene.h index d8b9a0d4..df04330b 100644 --- a/SynapseEngine/Engine/Scene/Scene.h +++ b/SynapseEngine/Engine/Scene/Scene.h @@ -16,7 +16,7 @@ #include "DrawData/SceneDrawData.h" #include "Engine/Scene/Source/ISceneSource.h" #include "Engine/Physics/IPhysicsEngine.h" -#include "HierarchyManager.h" +#include "Hierarchy/HierarchyManager.h" #include "Engine/System/SystemContext.h" namespace Syn diff --git a/SynapseEngine/Engine/Scene/Settings/CullingSettings.cpp b/SynapseEngine/Engine/Scene/Settings/CullingSettings.cpp index a793b9a0..5b89c876 100644 --- a/SynapseEngine/Engine/Scene/Settings/CullingSettings.cpp +++ b/SynapseEngine/Engine/Scene/Settings/CullingSettings.cpp @@ -14,7 +14,7 @@ namespace Syn , spotLightShadowSpatialAcceleration(SpatialAccelerationType::None) , pointLightShadowSpatialAcceleration(SpatialAccelerationType::None) , enableHiz(true) - , enableMeshletConeCulling(false) + , enableMeshletConeCulling(true) , enableFrustumCulling(true) , enableChunkFrustumCulling(true) , enableModelFrustumCulling(true) diff --git a/SynapseEngine/Engine/Scene/Settings/EnvironmentSettings.cpp b/SynapseEngine/Engine/Scene/Settings/EnvironmentSettings.cpp index 57fed2f5..aea51014 100644 --- a/SynapseEngine/Engine/Scene/Settings/EnvironmentSettings.cpp +++ b/SynapseEngine/Engine/Scene/Settings/EnvironmentSettings.cpp @@ -7,6 +7,7 @@ namespace Syn , skyMode(SkyMode::EquirectangularTexture) , skyTextureId(UINT32_MAX) , skyIntensity(1.0f) + , skyExposureEV(0.0f) , skyTint(glm::vec3(1.0f)) , skyRotation(glm::vec3(0.0f)) , ambientIntensity(1.0f) diff --git a/SynapseEngine/Engine/Scene/Settings/EnvironmentSettings.h b/SynapseEngine/Engine/Scene/Settings/EnvironmentSettings.h index 7a603415..069b4061 100644 --- a/SynapseEngine/Engine/Scene/Settings/EnvironmentSettings.h +++ b/SynapseEngine/Engine/Scene/Settings/EnvironmentSettings.h @@ -21,6 +21,7 @@ namespace Syn uint32_t skyTextureId; float skyIntensity; + float skyExposureEV; glm::vec3 skyTint; glm::vec3 skyRotation; diff --git a/SynapseEngine/Engine/Shaders/Includes/PushConstants/SkySpherePC.glsl b/SynapseEngine/Engine/Shaders/Includes/PushConstants/SkySpherePC.glsl index 3632c331..6e7fdbed 100644 --- a/SynapseEngine/Engine/Shaders/Includes/PushConstants/SkySpherePC.glsl +++ b/SynapseEngine/Engine/Shaders/Includes/PushConstants/SkySpherePC.glsl @@ -12,5 +12,6 @@ struct SkySpherePC { uint samplerIndex; uint mappingType; float skyIntensity; + float skyExposureEV; }; #endif \ No newline at end of file diff --git a/SynapseEngine/Engine/Shaders/Includes/Utils/CullingMath.glsl b/SynapseEngine/Engine/Shaders/Includes/Utils/CullingMath.glsl index 4050370c..1a31e4fd 100644 --- a/SynapseEngine/Engine/Shaders/Includes/Utils/CullingMath.glsl +++ b/SynapseEngine/Engine/Shaders/Includes/Utils/CullingMath.glsl @@ -16,14 +16,14 @@ bool TestConeCulling(vec3 apex, vec3 axis, float cutoff, vec3 cameraEye) { return dot(view, axis) >= cutoff; } -bool TestConeCulling(GpuMeshletCollider collider, vec3 cameraEye) { - return TestConeCulling(collider.apex, collider.axis, collider.cutoff, cameraEye); -} - bool TestConeCulling(vec3 axis, float cutoff, vec3 lightDir) { return dot(lightDir, axis) >= cutoff; } +bool TestConeCulling(GpuMeshletCollider collider, vec3 cameraEye) { + return TestConeCulling(collider.apex, collider.axis, collider.cutoff, cameraEye); +} + bool TestConeCullingLight(GpuMeshletCollider collider, vec3 lightDir) { return TestConeCulling(collider.axis, collider.cutoff, lightDir); } diff --git a/SynapseEngine/Engine/Shaders/Passes/Culling/PointLight/PointLightShadowMeshCulling.comp b/SynapseEngine/Engine/Shaders/Passes/Culling/PointLight/PointLightShadowMeshCulling.comp index fd8e4f47..3514cefb 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Culling/PointLight/PointLightShadowMeshCulling.comp +++ b/SynapseEngine/Engine/Shaders/Passes/Culling/PointLight/PointLightShadowMeshCulling.comp @@ -120,11 +120,6 @@ void main() { float mainCamScreenSize = CalculateSphereScreenSizePerspective(worldCollider.center, worldCollider.radius, camera.view, camera.proj, cameraNear, screenRes); if (mainCamScreenSize < 1.0) continue; - if (ctx.enableChunkOcclusionCulling == 1) { - float shadowScreenSizePixels = 0.0; - // Todo: - } - uint rawLod = (mainCamScreenSize > 512.0) ? 0 : (mainCamScreenSize > 256.0) ? 1 : (mainCamScreenSize > 128.0) ? 2 : 3; uint lod = min(rawLod + ctx.pointLightShadowLodBias, 3u); diff --git a/SynapseEngine/Engine/Shaders/Passes/Culling/PointLight/PointLightShadowModelCulling.comp b/SynapseEngine/Engine/Shaders/Passes/Culling/PointLight/PointLightShadowModelCulling.comp index 7a350028..0d5737c9 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Culling/PointLight/PointLightShadowModelCulling.comp +++ b/SynapseEngine/Engine/Shaders/Passes/Culling/PointLight/PointLightShadowModelCulling.comp @@ -103,11 +103,6 @@ void main() if (mainCamScreenSize < 1.0) return; - if (ctx.enableChunkOcclusionCulling == 1) { - float shadowScreenSizePixels = 0.0; - // Todo: - } - uint entityData = (entityId & 0x7FFFFFFF); entityData = SET_BIT_TO(entityData, 31, visibility == INTERSECTION_INSIDE); diff --git a/SynapseEngine/Engine/Shaders/Passes/Culling/PointLight/PointLightShadowMortonChunkCulling.comp b/SynapseEngine/Engine/Shaders/Passes/Culling/PointLight/PointLightShadowMortonChunkCulling.comp index 343e7437..574e5cf1 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Culling/PointLight/PointLightShadowMortonChunkCulling.comp +++ b/SynapseEngine/Engine/Shaders/Passes/Culling/PointLight/PointLightShadowMortonChunkCulling.comp @@ -60,12 +60,6 @@ void main() { if (visibility == INTERSECTION_OUTSIDE) return; } - // 5. Occlusion Culling (Deferred for Point Lights / Atlas evaluation needed) - if (ctx.enableChunkOcclusionCulling == 1) { - float shadowScreenSizePixels = 0.0; - //Todo - } - // 6. Subgroup-optimized writes to the visible Morton chunk list uint needsWrite = 1; uint subgroupWriteCount = subgroupAdd(needsWrite); diff --git a/SynapseEngine/Engine/Shaders/Passes/Culling/PointLight/PointLightShadowMortonModelCulling.comp b/SynapseEngine/Engine/Shaders/Passes/Culling/PointLight/PointLightShadowMortonModelCulling.comp index 068c94d2..8be2c2c6 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Culling/PointLight/PointLightShadowMortonModelCulling.comp +++ b/SynapseEngine/Engine/Shaders/Passes/Culling/PointLight/PointLightShadowMortonModelCulling.comp @@ -117,12 +117,6 @@ void main() { float mainCamScreenSize = CalculateSphereScreenSizePerspective(worldCollider.center, worldCollider.radius, camera.view, camera.proj, cameraNear, screenRes); if (mainCamScreenSize < 1.0) continue; - // 7. Occlusion Culling using Spot Light HZB - if (ctx.enableModelOcclusionCulling == 1) { - float shadowScreenSizePixels = 0.0; - //Todo - } - // 7. Encode Payload for sort/draw phase uint entityData = (entityId & 0x7FFFFFFF); entityData = SET_BIT_TO(entityData, 31, visibility == INTERSECTION_INSIDE); diff --git a/SynapseEngine/Engine/Shaders/Passes/Culling/PointLight/PointLightShadowStaticChunkCulling.comp b/SynapseEngine/Engine/Shaders/Passes/Culling/PointLight/PointLightShadowStaticChunkCulling.comp index dcb1afe6..2ad053e4 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Culling/PointLight/PointLightShadowStaticChunkCulling.comp +++ b/SynapseEngine/Engine/Shaders/Passes/Culling/PointLight/PointLightShadowStaticChunkCulling.comp @@ -56,12 +56,6 @@ void main() { if (visibility == INTERSECTION_OUTSIDE) return; } - // 5. Occlusion Culling (Deferred for Point Lights / Atlas evaluation needed) - if (ctx.enableChunkOcclusionCulling == 1) { - float shadowScreenSizePixels = 0.0; - // Todo - } - // 6. Subgroup-optimized writes to the visible chunk list uint needsWrite = 1; uint subgroupWriteCount = subgroupAdd(needsWrite); diff --git a/SynapseEngine/Engine/Shaders/Passes/Culling/PointLight/PointLightShadowStaticModelCulling.comp b/SynapseEngine/Engine/Shaders/Passes/Culling/PointLight/PointLightShadowStaticModelCulling.comp index 5272e9c6..bcdd9f49 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Culling/PointLight/PointLightShadowStaticModelCulling.comp +++ b/SynapseEngine/Engine/Shaders/Passes/Culling/PointLight/PointLightShadowStaticModelCulling.comp @@ -113,11 +113,6 @@ void main() { float mainCamScreenSize = CalculateSphereScreenSizePerspective(worldCollider.center, worldCollider.radius, camera.view, camera.proj, cameraNear, screenRes); if (mainCamScreenSize < 1.0) continue; - if (ctx.enableModelOcclusionCulling == 1) { - float shadowScreenSizePixels = 0.0; - //Todo - } - // 7. Encode Payload for sort/draw phase uint entityData = (entityId & 0x7FFFFFFF); entityData = SET_BIT_TO(entityData, 31, visibility == INTERSECTION_INSIDE); diff --git a/SynapseEngine/Engine/Shaders/Passes/Culling/SpotLight/SpotLightShadowMeshCulling.comp b/SynapseEngine/Engine/Shaders/Passes/Culling/SpotLight/SpotLightShadowMeshCulling.comp index 84eeb8b3..38ca1856 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Culling/SpotLight/SpotLightShadowMeshCulling.comp +++ b/SynapseEngine/Engine/Shaders/Passes/Culling/SpotLight/SpotLightShadowMeshCulling.comp @@ -119,14 +119,6 @@ void main() { float mainCamScreenSize = CalculateSphereScreenSizePerspective(worldCollider.center, worldCollider.radius, camera.view, camera.proj, cameraNear, screenRes); if (mainCamScreenSize < 1.0) continue; - // 10. Precise Occlusion Culling (HZB) - if (ctx.enableMeshOcclusionCulling == 1) { - float shadowScreenSizePixels = 0.0; - if (false && IsSphereOccludedAtlasPerspective(worldCollider.center, worldCollider.radius, shadowComp.view, shadowComp.proj, shadowComp.planes.x, shadowComp.planes.y, shadowComp.atlasRect, shadowDepthPyramid, float(ctx.spotLightShadowAtlasSize), ctx.spotLightShadowHizMipLevels, shadowScreenSizePixels)) { - continue; - } - } - uint rawLod = (mainCamScreenSize > 512.0) ? 0 : (mainCamScreenSize > 256.0) ? 1 : (mainCamScreenSize > 128.0) ? 2 : 3; uint lod = min(rawLod + ctx.spotLightShadowLodBias, 3u); diff --git a/SynapseEngine/Engine/Shaders/Passes/Culling/SpotLight/SpotLightShadowModelCulling.comp b/SynapseEngine/Engine/Shaders/Passes/Culling/SpotLight/SpotLightShadowModelCulling.comp index daf3d644..e3138536 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Culling/SpotLight/SpotLightShadowModelCulling.comp +++ b/SynapseEngine/Engine/Shaders/Passes/Culling/SpotLight/SpotLightShadowModelCulling.comp @@ -107,14 +107,6 @@ void main() if (mainCamScreenSize < 1.0) return; - // 7. Occlusion Culling (HZB) - if (ctx.enableModelOcclusionCulling == 1) { - float shadowScreenSizePixels = 0.0; - if (false && IsSphereOccludedAtlasPerspective(worldCollider.center, worldCollider.radius, shadowComp.view, shadowComp.proj, shadowComp.planes.x, shadowComp.planes.y, shadowComp.atlasRect, shadowDepthPyramid, float(ctx.spotLightShadowAtlasSize), ctx.spotLightShadowHizMipLevels, shadowScreenSizePixels)) { - return; - } - } - uint entityData = (entityId & 0x7FFFFFFF); entityData = SET_BIT_TO(entityData, 31, visibility == INTERSECTION_INSIDE); diff --git a/SynapseEngine/Engine/Shaders/Passes/Culling/SpotLight/SpotLightShadowMortonChunkCulling.comp b/SynapseEngine/Engine/Shaders/Passes/Culling/SpotLight/SpotLightShadowMortonChunkCulling.comp index 5dcb0036..4cccfe8b 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Culling/SpotLight/SpotLightShadowMortonChunkCulling.comp +++ b/SynapseEngine/Engine/Shaders/Passes/Culling/SpotLight/SpotLightShadowMortonChunkCulling.comp @@ -60,14 +60,6 @@ void main() { if (visibility == INTERSECTION_OUTSIDE) return; } - // 5. Occlusion Culling using Spot Light HZB - if (ctx.enableChunkOcclusionCulling == 1) { - float shadowScreenSizePixels = 0.0; - if (false && IsSphereOccludedAtlasPerspective(sphereCenter, sphereRadius, shadowComp.view, shadowComp.proj, shadowComp.planes.x, shadowComp.planes.y, shadowComp.atlasRect, shadowDepthPyramid, float(ctx.spotLightShadowAtlasSize), ctx.spotLightShadowHizMipLevels, shadowScreenSizePixels)) { - return; - } - } - // 6. Subgroup-optimized writes to the visible Morton chunk list uint needsWrite = 1; uint subgroupWriteCount = subgroupAdd(needsWrite); diff --git a/SynapseEngine/Engine/Shaders/Passes/Culling/SpotLight/SpotLightShadowMortonModelCulling.comp b/SynapseEngine/Engine/Shaders/Passes/Culling/SpotLight/SpotLightShadowMortonModelCulling.comp index 4e735809..8a7b1303 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Culling/SpotLight/SpotLightShadowMortonModelCulling.comp +++ b/SynapseEngine/Engine/Shaders/Passes/Culling/SpotLight/SpotLightShadowMortonModelCulling.comp @@ -119,14 +119,6 @@ void main() { float mainCamScreenSize = CalculateSphereScreenSizePerspective(worldCollider.center, worldCollider.radius, camera.view, camera.proj, cameraNear, screenRes); if (mainCamScreenSize < 1.0) continue; - // 7. Occlusion Culling using Spot Light HZB - if (ctx.enableModelOcclusionCulling == 1) { - float shadowScreenSizePixels = 0.0; - if (false && IsSphereOccludedAtlasPerspective(worldCollider.center, worldCollider.radius, shadowComp.view, shadowComp.proj, shadowComp.planes.x, shadowComp.planes.y, shadowComp.atlasRect, shadowDepthPyramid, float(ctx.spotLightShadowAtlasSize), ctx.spotLightShadowHizMipLevels, shadowScreenSizePixels)) { - continue; - } - } - // 8. Encode Payload for sort/draw phase uint entityData = (entityId & 0x7FFFFFFF); entityData = SET_BIT_TO(entityData, 31, visibility == INTERSECTION_INSIDE); diff --git a/SynapseEngine/Engine/Shaders/Passes/Culling/SpotLight/SpotLightShadowStaticChunkCulling.comp b/SynapseEngine/Engine/Shaders/Passes/Culling/SpotLight/SpotLightShadowStaticChunkCulling.comp index c6134dbe..35605d86 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Culling/SpotLight/SpotLightShadowStaticChunkCulling.comp +++ b/SynapseEngine/Engine/Shaders/Passes/Culling/SpotLight/SpotLightShadowStaticChunkCulling.comp @@ -56,14 +56,6 @@ void main() { if (visibility == INTERSECTION_OUTSIDE) return; } - // 5. Occlusion Culling using Spot Light HZB - if (ctx.enableChunkOcclusionCulling == 1) { - float shadowScreenSizePixels = 0.0; - if (false && IsSphereOccludedAtlasPerspective(sphereCenter, sphereRadius, shadowComp.view, shadowComp.proj, shadowComp.planes.x, shadowComp.planes.y, shadowComp.atlasRect, shadowDepthPyramid, float(ctx.spotLightShadowAtlasSize), ctx.spotLightShadowHizMipLevels, shadowScreenSizePixels)) { - return; - } - } - // 6. Subgroup-optimized writes to the visible chunk list uint needsWrite = 1; uint subgroupWriteCount = subgroupAdd(needsWrite); diff --git a/SynapseEngine/Engine/Shaders/Passes/Culling/SpotLight/SpotLightShadowStaticModelCulling.comp b/SynapseEngine/Engine/Shaders/Passes/Culling/SpotLight/SpotLightShadowStaticModelCulling.comp index e8a09347..d16e615b 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Culling/SpotLight/SpotLightShadowStaticModelCulling.comp +++ b/SynapseEngine/Engine/Shaders/Passes/Culling/SpotLight/SpotLightShadowStaticModelCulling.comp @@ -112,14 +112,6 @@ void main() { float mainCamScreenSize = CalculateSphereScreenSizePerspective(worldCollider.center, worldCollider.radius, camera.view, camera.proj, cameraNear, screenRes); if (mainCamScreenSize < 1.0) continue; - // 7. Occlusion Culling using Spot Light HZB - if (ctx.enableModelOcclusionCulling == 1) { - float shadowScreenSizePixels = 0.0; - if (false && IsSphereOccludedAtlasPerspective(worldCollider.center, worldCollider.radius, shadowComp.view, shadowComp.proj, shadowComp.planes.x, shadowComp.planes.y, shadowComp.atlasRect, shadowDepthPyramid, float(ctx.spotLightShadowAtlasSize), ctx.spotLightShadowHizMipLevels, shadowScreenSizePixels)) { - continue; - } - } - // 8. Encode Payload for sort/draw phase uint entityData = (entityId & 0x7FFFFFFF); entityData = SET_BIT_TO(entityData, 31, visibility == INTERSECTION_INSIDE); diff --git a/SynapseEngine/Engine/Shaders/Passes/PostProcess/SkySphere/SkySphere.frag b/SynapseEngine/Engine/Shaders/Passes/PostProcess/SkySphere/SkySphere.frag index c991602b..4eb37b30 100644 --- a/SynapseEngine/Engine/Shaders/Passes/PostProcess/SkySphere/SkySphere.frag +++ b/SynapseEngine/Engine/Shaders/Passes/PostProcess/SkySphere/SkySphere.frag @@ -40,6 +40,7 @@ void main() { vec3 skyColor = SampleTexture2DLod(pc.skyTextureIndex, pc.samplerIndex, finalUV, 0.0).rgb; skyColor *= pc.skyTint; skyColor *= pc.skyIntensity; + skyColor *= exp2(pc.skyExposureEV); outColor = vec4(skyColor, 1.0); } \ No newline at end of file diff --git a/SynapseEngine/Engine/Shaders/Passes/Shadow/DirectionLight/DirectionLightShadowMeshlet.task b/SynapseEngine/Engine/Shaders/Passes/Shadow/DirectionLight/DirectionLightShadowMeshlet.task index 0dfdca01..0b634460 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Shadow/DirectionLight/DirectionLightShadowMeshlet.task +++ b/SynapseEngine/Engine/Shaders/Passes/Shadow/DirectionLight/DirectionLightShadowMeshlet.task @@ -107,7 +107,7 @@ void main() { // 8. Backface Cone Culling for Directional Light if (pc.disableConeCulling == 0 && ctx.enableMeshletConeCulling == 1) { - if (TestConeCulling(worldCollider, lightComp.direction)) { + if (TestConeCullingLight(worldCollider, lightComp.direction)) { isVisible = false; } } @@ -119,19 +119,6 @@ void main() { } } - // 10. Occlusion Culling using Cascade Depth Pyramid (HZB) - float screenSizePixels = 0.0; - if (isVisible && ctx.enableMeshletOcclusionCulling == 1) { - mat4 shadowViewProj = GET_DIRECTION_LIGHT_SHADOW(ctx.directionLightShadowDataBufferAddr, lightShadowDenseIdx).cascadeViewProjsVulkan[cascadeIdx]; - vec4 shadowAtlasRect = GET_DIRECTION_LIGHT_SHADOW(ctx.directionLightShadowDataBufferAddr, lightShadowDenseIdx).cascadeAtlasRects[cascadeIdx]; - bool enableDepthOcclusion = (ctx.enableMeshletOcclusionCulling == 1); - - float shadowScreenSizePixels = 0.0; - if (false && IsSphereOccludedAtlasOrtho(worldCollider.center, worldCollider.radius, shadowViewProj, shadowAtlasRect, shadowDepthPyramid, float(ctx.directionLightShadowAtlasSize), ctx.directionLightShadowHizMipLevels, shadowScreenSizePixels)) { - isVisible = false; - } - } - // 11. Emit surviving meshlets if (isVisible) { uint slot = atomicAdd(survivingMeshletCount, 1); diff --git a/SynapseEngine/Engine/Shaders/Passes/Shadow/PointLight/PointLightShadowMeshlet.task b/SynapseEngine/Engine/Shaders/Passes/Shadow/PointLight/PointLightShadowMeshlet.task index fa671710..3e933904 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Shadow/PointLight/PointLightShadowMeshlet.task +++ b/SynapseEngine/Engine/Shaders/Passes/Shadow/PointLight/PointLightShadowMeshlet.task @@ -104,7 +104,9 @@ void main() { // 6. Backface Cone Culling if (pc.disableConeCulling == 0 && ctx.enableMeshletConeCulling == 1) { - //Todo: + if (TestConeCulling(worldCollider, pointCollider.center)) { + isVisible = false; + } } // 7. Point Sphere vs Meshlet Sphere Culling @@ -112,12 +114,6 @@ void main() { //Todo: } - - // 8. Occlusion Culling using Depth Pyramid (HZB) - if (isVisible && ctx.enableMeshletOcclusionCulling == 1) { - //Todo: - } - // 9. Emit surviving meshlets if (isVisible) { uint slot = atomicAdd(survivingMeshletCount, 1); diff --git a/SynapseEngine/Engine/Shaders/Passes/Shadow/SpotLight/SpotLightShadowMeshlet.task b/SynapseEngine/Engine/Shaders/Passes/Shadow/SpotLight/SpotLightShadowMeshlet.task index fb1d6c58..69907664 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Shadow/SpotLight/SpotLightShadowMeshlet.task +++ b/SynapseEngine/Engine/Shaders/Passes/Shadow/SpotLight/SpotLightShadowMeshlet.task @@ -104,7 +104,7 @@ void main() { // 6. Backface Cone Culling if (pc.disableConeCulling == 0 && ctx.enableMeshletConeCulling == 1) { - if (TestConeCulling(worldCollider, spotCollider.worldPos)) { + if (TestConeCullingLight(worldCollider, spotCollider.worldPos)) { isVisible = false; } } diff --git a/SynapseEngine/Engine/System/ComponentSystem.h b/SynapseEngine/Engine/System/ComponentSystem.h index db7d4393..0641e938 100644 --- a/SynapseEngine/Engine/System/ComponentSystem.h +++ b/SynapseEngine/Engine/System/ComponentSystem.h @@ -122,12 +122,13 @@ namespace Syn bool hasUpdate = pool->template IsStateBitSet(); bool hasIndex = pool->template IsStateBitSet(); bool hasStaticUpload = pool->template IsStateBitSet(); + bool queued = pool->template IsStateBitSet(); bool hasCustom1 = pool->template IsStateBitSet(); bool hasCustom2 = pool->template IsStateBitSet(); bool hasCustom3 = pool->template IsStateBitSet(); bool hasDirtyStatics = !pool->GetStorage().GetDirtyStatics().empty(); - if (!hasChanged && !hasUpdate && !hasIndex && !hasCustom1 && !hasCustom2 && !hasCustom3 && !hasDirtyStatics && !hasStaticUpload) return; + if (!hasChanged && !hasUpdate && !hasIndex && !hasCustom1 && !hasCustom2 && !hasCustom3 && !hasDirtyStatics && !hasStaticUpload && !queued) return; //Info("{} -> OnFinish: Cleaning up frame. (Changed: {}, Update: {}, Index: {}, DirtyStatics: {})", GetName(), hasChanged, hasUpdate, hasIndex, hasDirtyStatics); @@ -136,6 +137,7 @@ namespace Syn if (pool->template IsBitSet(entity)) pool->template ResetBit(entity); if (pool->template IsBitSet(entity)) pool->template ResetBit(entity); + if (pool->template IsBitSet(entity)) pool->template ResetBit(entity); if (pool->template IsBitSet(entity)) pool->template ResetBit(entity); if (pool->template IsBitSet(entity)) pool->template ResetBit(entity); if (pool->template IsBitSet(entity)) pool->template ResetBit(entity); diff --git a/SynapseEngine/Engine/System/Core/TransformSetupSystem.cpp b/SynapseEngine/Engine/System/Core/TransformSetupSystem.cpp new file mode 100644 index 00000000..a9d33c32 --- /dev/null +++ b/SynapseEngine/Engine/System/Core/TransformSetupSystem.cpp @@ -0,0 +1,94 @@ +#include "TransformSetupSystem.h" +#include "Engine/Scene/Scene.h" + +namespace Syn +{ + std::vector TransformSetupSystem::GetWriteDependencies() const + { + return { TypeInfo::ID }; + } + + void TransformSetupSystem::UpdateComponents(Scene* scene, uint32_t frameIndex, float deltaTime, tf::Subflow& subflow) + { + auto registry = scene->GetRegistry(); + auto transformPool = registry->GetPool(); + auto hierarchyPool = registry->GetPool(); + auto hierarchyManager = scene->GetHierarchyManager(); + + if (!transformPool || !hierarchyPool || !hierarchyManager) return; + + auto* workQueue = hierarchyManager->EnsureWorkQueue(); + + auto initTask = this->EmplaceTask(subflow, "InitTransformQueue", [workQueue, hierarchyManager]() { + workQueue->Initialize(hierarchyManager->GetLevels()); + }); + + auto seedTasks = ParallelForEachIf(transformPool, subflow, "SeedTransformQueue", [transformPool, hierarchyPool, workQueue](EntityID entity) { + + uint32_t level = 0; + if (hierarchyPool->Has(entity)) { + level = hierarchyPool->Get(entity).depthLevel; + } + + workQueue->Push(level, entity); + transformPool->SetBit(entity); + + }); + + for (auto& st : seedTasks) { + initTask.precede(st); + } + + uint32_t maxLevel = hierarchyManager->GetMaxActiveLevel(); + std::vector propagateTasks; + + for (uint32_t level = 0; level < maxLevel; ++level) + { + tf::Task propTask = this->EmplaceTask(subflow, "PropagateDirty_" + std::to_string(level), [=](tf::Subflow& nested_subflow) { + auto currentQueue = workQueue->GetQueue(level); + + if (currentQueue.empty()) return; + + nested_subflow.for_each(currentQueue.begin(), currentQueue.end(), [=](EntityID entity) { + + if (!hierarchyPool->Has(entity)) return; + + EntityID child = hierarchyPool->Get(entity).firstChild; + + while (child != NULL_ENTITY) + { + if (transformPool->Has(child)) + { + if (!transformPool->IsBitSet(child)) + { + transformPool->SetBit(child); + + uint32_t childLevel = hierarchyPool->Get(child).depthLevel; + workQueue->Push(childLevel, child); + + if (transformPool->IsStatic(child)) + transformPool->MarkStaticDirty(child); + else if (transformPool->IsDynamic(child)) + transformPool->SetBit(child); + } + } + + child = hierarchyPool->Get(child).nextSibling; + } + }); + }); + + if (!propagateTasks.empty()) { + propagateTasks.back().precede(propTask); + } + else { + for (auto& st : seedTasks) { + st.precede(propTask); + } + initTask.precede(propTask); + } + + propagateTasks.push_back(propTask); + } + } +} \ No newline at end of file diff --git a/SynapseEngine/Engine/System/Core/TransformSetupSystem.h b/SynapseEngine/Engine/System/Core/TransformSetupSystem.h new file mode 100644 index 00000000..bc0593af --- /dev/null +++ b/SynapseEngine/Engine/System/Core/TransformSetupSystem.h @@ -0,0 +1,18 @@ +#pragma once +#include "Engine/System/ComponentSystem.h" +#include "Engine/Component/Core/TransformComponent.h" +#include "Engine/Component/Core/HierarchyComponent.h" + +namespace Syn +{ + class SYN_API TransformSetupSystem : public ComponentSystem + { + public: + std::string GetName() const override { return "TransformSetupSystem"; } + std::string GetGroup() const override { return SystemGroupNames::CoreSystems; } + + std::vector GetWriteDependencies() const override; + protected: + void UpdateComponents(Scene* scene, uint32_t frameIndex, float deltaTime, tf::Subflow& subflow) override; + }; +} \ No newline at end of file diff --git a/SynapseEngine/Engine/System/Core/TransformSystem.cpp b/SynapseEngine/Engine/System/Core/TransformSystem.cpp index 78352748..cccc74a8 100644 --- a/SynapseEngine/Engine/System/Core/TransformSystem.cpp +++ b/SynapseEngine/Engine/System/Core/TransformSystem.cpp @@ -6,9 +6,15 @@ #include #include "Engine/Component/Rendering/ModelComponent.h" #include "Engine/System/Rendering/ModelSystem.h" +#include "TransformSetupSystem.h" namespace Syn { + std::vector TransformSystem::GetReadDependencies() const + { + return { TypeInfo::ID }; + } + std::vector TransformSystem::GetWriteDependencies() const { return { TypeInfo::ID }; @@ -18,24 +24,65 @@ namespace Syn { auto registry = scene->GetRegistry(); auto transformPool = registry->GetPool(); - if (!transformPool) return; + auto hierarchyPool = registry->GetPool(); + auto hierarchyManager = scene->GetHierarchyManager(); - ParallelForEachIf(transformPool, subflow, SystemPhaseNames::Update, [transformPool](EntityID entity) { - auto& transformComponent = transformPool->Get(entity); + if (!transformPool || !hierarchyPool || !hierarchyManager) return; + + auto* workQueue = hierarchyManager->EnsureWorkQueue(); + uint32_t maxLevel = hierarchyManager->GetMaxActiveLevel(); + std::vector levelTasks; + + for (uint32_t level = 0; level < maxLevel; ++level) + { + tf::Task levelTask = this->EmplaceTask(subflow, "TransformMath_" + std::to_string(level), [=](tf::Subflow& nested_subflow) { + + auto currentQueue = workQueue->GetQueue(level); - transformComponent.transform = glm::mat4(1.0f); - transformComponent.transform = glm::translate(transformComponent.transform, transformComponent.translation); - transformComponent.transform = glm::rotate(transformComponent.transform, glm::radians(transformComponent.rotation.z), glm::vec3(0, 0, 1)); - transformComponent.transform = glm::rotate(transformComponent.transform, glm::radians(transformComponent.rotation.y), glm::vec3(0, 1, 0)); - transformComponent.transform = glm::rotate(transformComponent.transform, glm::radians(transformComponent.rotation.x), glm::vec3(1, 0, 0)); - transformComponent.transform = glm::scale(transformComponent.transform, transformComponent.scale); - transformComponent.transformIT = glm::transpose(glm::inverse(transformComponent.transform)); + if (currentQueue.empty()) return; - if (transformPool->IsDynamic(entity)) - transformPool->SetBit(entity); + nested_subflow.for_each(currentQueue.begin(), currentQueue.end(), [=](EntityID entity) { - transformComponent.version++; - }); + auto& transformComponent = transformPool->Get(entity); + + glm::mat4 localMat = glm::translate(glm::mat4(1.0f), transformComponent.translation); + localMat = glm::rotate(localMat, glm::radians(transformComponent.rotation.z), glm::vec3(0, 0, 1)); + localMat = glm::rotate(localMat, glm::radians(transformComponent.rotation.y), glm::vec3(0, 1, 0)); + localMat = glm::rotate(localMat, glm::radians(transformComponent.rotation.x), glm::vec3(1, 0, 0)); + localMat = glm::scale(localMat, transformComponent.scale); + + if (hierarchyPool->Has(entity)) + { + EntityID parent = hierarchyPool->Get(entity).parent; + + if (parent != NULL_ENTITY && transformPool->Has(parent)) { + auto& parentTransform = transformPool->Get(parent); + transformComponent.transform = parentTransform.transform * localMat; + } + else { + transformComponent.transform = localMat; + } + } + else + { + transformComponent.transform = localMat; + } + + transformComponent.transformIT = glm::transpose(glm::inverse(transformComponent.transform)); + + if (transformPool->IsDynamic(entity)) { + transformPool->SetBit(entity); + } + + transformComponent.version++; + }); + }); + + if (!levelTasks.empty()) { + levelTasks.back().precede(levelTask); + } + levelTasks.push_back(levelTask); + } } void TransformSystem::UploadComponents(Scene* scene, uint32_t frameIndex, tf::Subflow& subflow, bool uploadDynamic, bool uploadStatic) diff --git a/SynapseEngine/Engine/System/Core/TransformSystem.h b/SynapseEngine/Engine/System/Core/TransformSystem.h index 54d44792..59cff60e 100644 --- a/SynapseEngine/Engine/System/Core/TransformSystem.h +++ b/SynapseEngine/Engine/System/Core/TransformSystem.h @@ -10,6 +10,7 @@ namespace Syn std::string GetName() const override { return "TransformSystem"; } std::string GetGroup() const override { return SystemGroupNames::CoreSystems; } + std::vector GetReadDependencies() const override; std::vector GetWriteDependencies() const override; protected: std::string GetSparseBufferName() const override { return BufferNames::TransformSparseMap; } diff --git a/SynapseEngine/UnitTests/TestHierarchy.cpp b/SynapseEngine/UnitTests/TestHierarchy.cpp index c3ac8e94..6c5cd9ca 100644 --- a/SynapseEngine/UnitTests/TestHierarchy.cpp +++ b/SynapseEngine/UnitTests/TestHierarchy.cpp @@ -1,6 +1,6 @@ #include #include "Engine/Registry/Registry.h" -#include "Engine/Scene/HierarchyManager.h" +#include "Engine/Scene/Hierarchy/HierarchyManager.h" #include "Engine/Component/Core/HierarchyComponent.h" using namespace Syn; From a09d84c466bbe3d3aa81e93ea750b837ef448cf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20P=C3=A9ter?= Date: Wed, 8 Jul 2026 15:22:57 +0200 Subject: [PATCH 33/51] Tag hierarchical fully parallel update work queue graph implemented --- SynapseEngine/Engine/Scene/Scene.cpp | 8 +- .../Source/Procedural/TestSceneSource.cpp | 26 +++++ .../Scene/Source/Procedural/test_config.json | 2 +- .../Engine/System/Core/TagSetupSystem.cpp | 94 +++++++++++++++++++ .../Engine/System/Core/TagSetupSystem.h | 18 ++++ .../Engine/System/Core/TagSystem.cpp | 53 ++++++----- 6 files changed, 177 insertions(+), 24 deletions(-) create mode 100644 SynapseEngine/Engine/System/Core/TagSetupSystem.cpp create mode 100644 SynapseEngine/Engine/System/Core/TagSetupSystem.h diff --git a/SynapseEngine/Engine/Scene/Scene.cpp b/SynapseEngine/Engine/Scene/Scene.cpp index b022bb21..4a05ab64 100644 --- a/SynapseEngine/Engine/Scene/Scene.cpp +++ b/SynapseEngine/Engine/Scene/Scene.cpp @@ -21,6 +21,7 @@ #include "Engine/System/Core/HierarchySystem.h" #include "Engine/System/Core/SelectionOutlineSystem.h" #include "Engine/System/Core/TagSystem.h" +#include "Engine/System/Core/TagSetupSystem.h" #include "Engine/System/Core/CameraSystem.h" #include "Engine/System/Rendering/RenderSystem.h" #include "Engine/System/Rendering/ModelSystem.h" @@ -139,8 +140,11 @@ namespace Syn { RegisterSystem(); RegisterSystem(); - RegisterSystem(); + + RegisterSystem(); + RegisterSystem(); + RegisterSystem(); RegisterSystem(); RegisterSystem(); @@ -181,7 +185,7 @@ namespace Syn RegisterSystem(); RegisterSystem(); - RegisterSystem(); + } diff --git a/SynapseEngine/Engine/Scene/Source/Procedural/TestSceneSource.cpp b/SynapseEngine/Engine/Scene/Source/Procedural/TestSceneSource.cpp index d095ea00..36737501 100644 --- a/SynapseEngine/Engine/Scene/Source/Procedural/TestSceneSource.cpp +++ b/SynapseEngine/Engine/Scene/Source/Procedural/TestSceneSource.cpp @@ -113,6 +113,7 @@ namespace Syn registry.GetComponent(rootCameras).tag = "Root"; registry.AddComponent(rootCameras); registry.GetPool()->SetCategory(rootCameras, StorageCategory::Static); + registry.GetPool()->SetCategory(rootCameras, StorageCategory::Static); EntityID rootEnvironment = scene.CreateEntity(); registry.AddComponent(rootEnvironment); @@ -120,6 +121,7 @@ namespace Syn registry.GetComponent(rootEnvironment).tag = "Root"; registry.AddComponent(rootEnvironment); registry.GetPool()->SetCategory(rootEnvironment, StorageCategory::Static); + registry.GetPool()->SetCategory(rootEnvironment, StorageCategory::Static); EntityID rootCharacters = scene.CreateEntity(); registry.AddComponent(rootCharacters); @@ -127,6 +129,7 @@ namespace Syn registry.GetComponent(rootCharacters).tag = "Root"; registry.AddComponent(rootCharacters); registry.GetPool()->SetCategory(rootCharacters, StorageCategory::Static); + registry.GetPool()->SetCategory(rootCharacters, StorageCategory::Static); EntityID rootStaticGeo = scene.CreateEntity(); registry.AddComponent(rootStaticGeo); @@ -134,6 +137,7 @@ namespace Syn registry.GetComponent(rootStaticGeo).tag = "Root"; registry.AddComponent(rootStaticGeo); registry.GetPool()->SetCategory(rootStaticGeo, StorageCategory::Static); + registry.GetPool()->SetCategory(rootStaticGeo, StorageCategory::Static); EntityID rootPhysics = scene.CreateEntity(); registry.AddComponent(rootPhysics); @@ -141,6 +145,7 @@ namespace Syn registry.GetComponent(rootPhysics).tag = "Root"; registry.AddComponent(rootPhysics); registry.GetPool()->SetCategory(rootPhysics, StorageCategory::Static); + registry.GetPool()->SetCategory(rootPhysics, StorageCategory::Static); EntityID rootLights = scene.CreateEntity(); registry.AddComponent(rootLights); @@ -148,6 +153,7 @@ namespace Syn registry.GetComponent(rootLights).tag = "Root"; registry.AddComponent(rootLights); registry.GetPool()->SetCategory(rootLights, StorageCategory::Static); + registry.GetPool()->SetCategory(rootLights, StorageCategory::Static); // Cameras (Main & Debug) { @@ -159,6 +165,7 @@ namespace Syn registry.AddComponent(sceneCam); registry.GetPool()->SetCategory(sceneCam, StorageCategory::Stream); registry.GetPool()->SetCategory(sceneCam, StorageCategory::Stream); + registry.GetPool()->SetCategory(sceneCam, StorageCategory::Static); hm->AttachChild(rootCameras, sceneCam); debugCam = scene.CreateEntity(); @@ -169,6 +176,7 @@ namespace Syn registry.AddComponent(debugCam); registry.GetPool()->SetCategory(debugCam, StorageCategory::Stream); registry.GetPool()->SetCategory(debugCam, StorageCategory::Stream); + registry.GetPool()->SetCategory(debugCam, StorageCategory::Static); hm->AttachChild(rootCameras, debugCam); } @@ -191,6 +199,7 @@ namespace Syn registry.GetPool()->SetCategory(monkeyId, StorageCategory::Static); registry.GetPool()->SetCategory(monkeyId, StorageCategory::Static); registry.GetPool()->SetCategory(monkeyId, StorageCategory::Static); + registry.GetPool()->SetCategory(monkeyId, StorageCategory::Static); hm->AttachChild(rootEnvironment, monkeyId); } @@ -219,6 +228,7 @@ namespace Syn registry.GetPool()->SetCategory(sponzaEntity, StorageCategory::Stream); registry.GetPool()->SetCategory(sponzaEntity, StorageCategory::Stream); registry.GetPool()->SetCategory(sponzaEntity, StorageCategory::Static); + registry.GetPool()->SetCategory(sponzaEntity, StorageCategory::Static); hm->AttachChild(rootEnvironment, sponzaEntity); } @@ -249,6 +259,8 @@ namespace Syn registry.GetPool()->SetCategory(floorEntity, StorageCategory::Static); registry.GetPool()->SetCategory(floorEntity, StorageCategory::Static); registry.GetPool()->SetCategory(floorEntity, StorageCategory::Static); + registry.GetPool()->SetCategory(floorEntity, StorageCategory::Static); + registry.GetPool()->SetCategory(floorEntity, StorageCategory::Static); MaterialInfo floorMatInfo{}; floorMatInfo.color = glm::vec4(0.2f, 0.2f, 0.2f, 1.0f); @@ -279,6 +291,7 @@ namespace Syn registry.AddComponent(characterEntity); registry.AddComponent(characterEntity); registry.AddComponent(characterEntity); + registry.AddComponent(characterEntity); registry.GetComponent(characterEntity).translation = glm::vec3((rand() % 400) - 200.0f, 0.0f, (rand() % 400) - 200.0f); registry.GetComponent(characterEntity).scale = glm::vec3(5.f); @@ -288,6 +301,8 @@ namespace Syn animComp.animationIndex = animationIds[rand() % animationIds.size()]; animComp.speed = 0.5f + (static_cast(rand()) / static_cast(RAND_MAX)) * 1.5f; + registry.GetPool()->SetCategory(characterEntity, StorageCategory::Static); + registry.GetPool()->SetCategory(characterEntity, StorageCategory::Static); registry.GetPool()->SetCategory(characterEntity, StorageCategory::Static); registry.GetPool()->SetCategory(characterEntity, StorageCategory::Static); registry.GetPool()->SetCategory(characterEntity, StorageCategory::Stream); @@ -350,6 +365,8 @@ namespace Syn registry.GetComponent(e).modelIndex = geoIds[rand() % geoIds.size()]; + registry.GetPool()->SetCategory(e, StorageCategory::Static); + registry.GetPool()->SetCategory(e, StorageCategory::Static); registry.GetPool()->SetCategory(e, StorageCategory::Static); registry.GetPool()->SetCategory(e, StorageCategory::Static); @@ -376,6 +393,8 @@ namespace Syn registry.GetComponent(e).modelIndex = cubeMeshId; registry.GetComponent(e).motionType = PhysicsMotionType::Dynamic; + registry.GetPool()->SetCategory(e, StorageCategory::Static); + registry.GetPool()->SetCategory(e, StorageCategory::Static); registry.GetPool()->SetCategory(e, StorageCategory::Stream); registry.GetPool()->SetCategory(e, StorageCategory::Static); registry.GetPool()->SetCategory(e, StorageCategory::Static); @@ -400,6 +419,8 @@ namespace Syn registry.GetComponent(e).modelIndex = sphereMeshId; registry.GetComponent(e).motionType = PhysicsMotionType::Dynamic; + registry.GetPool()->SetCategory(e, StorageCategory::Static); + registry.GetPool()->SetCategory(e, StorageCategory::Static); registry.GetPool()->SetCategory(e, StorageCategory::Stream); registry.GetPool()->SetCategory(e, StorageCategory::Static); registry.GetPool()->SetCategory(e, StorageCategory::Static); @@ -426,6 +447,8 @@ namespace Syn registry.GetComponent(e).modelIndex = capsuleMeshId; registry.GetComponent(e).motionType = PhysicsMotionType::Dynamic; + registry.GetPool()->SetCategory(e, StorageCategory::Static); + registry.GetPool()->SetCategory(e, StorageCategory::Static); registry.GetPool()->SetCategory(e, StorageCategory::Stream); registry.GetPool()->SetCategory(e, StorageCategory::Static); registry.GetPool()->SetCategory(e, StorageCategory::Static); @@ -452,6 +475,7 @@ namespace Syn registry.GetPool()->SetCategory(e, StorageCategory::Stream); registry.GetPool()->SetCategory(e, StorageCategory::Stream); + registry.GetPool()->SetCategory(e, StorageCategory::Static); registry.GetPool()->SetBit(e); registry.GetPool()->SetBit(e); @@ -478,6 +502,7 @@ namespace Syn registry.GetPool()->SetCategory(e, StorageCategory::Stream); registry.GetPool()->SetCategory(e, StorageCategory::Stream); + registry.GetPool()->SetCategory(e, StorageCategory::Static); registry.GetPool()->SetBit(e); registry.GetPool()->SetBit(e); @@ -507,6 +532,7 @@ namespace Syn registry.GetPool()->SetCategory(e, StorageCategory::Stream); registry.GetPool()->SetCategory(e, StorageCategory::Stream); + registry.GetPool()->SetCategory(e, StorageCategory::Static); registry.GetPool()->SetBit(e); registry.GetPool()->SetBit(e); diff --git a/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json b/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json index f372cda9..eb1a9bc9 100644 --- a/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json +++ b/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json @@ -10,7 +10,7 @@ }, "entities": { "animated_characters": 500, - "static_geometry": 5000, + "static_geometry": 50000, "physics_boxes": 500, "physics_spheres": 500, "physics_capsules": 500 diff --git a/SynapseEngine/Engine/System/Core/TagSetupSystem.cpp b/SynapseEngine/Engine/System/Core/TagSetupSystem.cpp new file mode 100644 index 00000000..ccf4f88b --- /dev/null +++ b/SynapseEngine/Engine/System/Core/TagSetupSystem.cpp @@ -0,0 +1,94 @@ +#include "TagSetupSystem.h" +#include "Engine/Scene/Scene.h" + +namespace Syn +{ + std::vector TagSetupSystem::GetWriteDependencies() const + { + return { TypeInfo::ID }; + } + + void TagSetupSystem::UpdateComponents(Scene* scene, uint32_t frameIndex, float deltaTime, tf::Subflow& subflow) + { + auto registry = scene->GetRegistry(); + auto tagPool = registry->GetPool(); + auto hierarchyPool = registry->GetPool(); + auto hierarchyManager = scene->GetHierarchyManager(); + + if (!tagPool || !hierarchyPool || !hierarchyManager) return; + + auto* workQueue = hierarchyManager->EnsureWorkQueue(); + + auto initTask = this->EmplaceTask(subflow, "InitTagQueue", [workQueue, hierarchyManager]() { + workQueue->Initialize(hierarchyManager->GetLevels()); + }); + + auto seedTasks = ParallelForEachIf(tagPool, subflow, "SeedTagQueue", [tagPool, hierarchyPool, workQueue](EntityID entity) { + + uint32_t level = 0; + if (hierarchyPool->Has(entity)) { + level = hierarchyPool->Get(entity).depthLevel; + } + + workQueue->Push(level, entity); + tagPool->SetBit(entity); + + }); + + for (auto& st : seedTasks) { + initTask.precede(st); + } + + uint32_t maxLevel = hierarchyManager->GetMaxActiveLevel(); + std::vector propagateTasks; + + for (uint32_t level = 0; level < maxLevel; ++level) + { + tf::Task propTask = this->EmplaceTask(subflow, "PropagateTagDirty_" + std::to_string(level), [=](tf::Subflow& nested_subflow) { + auto currentQueue = workQueue->GetQueue(level); + + if (currentQueue.empty()) return; + + nested_subflow.for_each(currentQueue.begin(), currentQueue.end(), [=](EntityID entity) { + + if (!hierarchyPool->Has(entity)) return; + + EntityID child = hierarchyPool->Get(entity).firstChild; + + while (child != NULL_ENTITY) + { + if (tagPool->Has(child)) + { + if (!tagPool->IsBitSet(child)) + { + tagPool->SetBit(child); + + uint32_t childLevel = hierarchyPool->Get(child).depthLevel; + workQueue->Push(childLevel, child); + + if (tagPool->IsStatic(child)) + tagPool->MarkStaticDirty(child); + else if (tagPool->IsDynamic(child)) + tagPool->SetBit(child); + } + } + + child = hierarchyPool->Get(child).nextSibling; + } + }); + }); + + if (!propagateTasks.empty()) { + propagateTasks.back().precede(propTask); + } + else { + for (auto& st : seedTasks) { + st.precede(propTask); + } + initTask.precede(propTask); + } + + propagateTasks.push_back(propTask); + } + } +} \ No newline at end of file diff --git a/SynapseEngine/Engine/System/Core/TagSetupSystem.h b/SynapseEngine/Engine/System/Core/TagSetupSystem.h new file mode 100644 index 00000000..c3e6f3b1 --- /dev/null +++ b/SynapseEngine/Engine/System/Core/TagSetupSystem.h @@ -0,0 +1,18 @@ +#pragma once +#include "Engine/System/ComponentSystem.h" +#include "Engine/Component/Core/TagComponent.h" +#include "Engine/Component/Core/HierarchyComponent.h" + +namespace Syn +{ + class SYN_API TagSetupSystem : public ComponentSystem + { + public: + std::string GetName() const override { return "TagSetupSystem"; } + std::string GetGroup() const override { return SystemGroupNames::CoreSystems; } + + std::vector GetWriteDependencies() const override; + protected: + void UpdateComponents(Scene* scene, uint32_t frameIndex, float deltaTime, tf::Subflow& subflow) override; + }; +} \ No newline at end of file diff --git a/SynapseEngine/Engine/System/Core/TagSystem.cpp b/SynapseEngine/Engine/System/Core/TagSystem.cpp index ec3584a8..48e46895 100644 --- a/SynapseEngine/Engine/System/Core/TagSystem.cpp +++ b/SynapseEngine/Engine/System/Core/TagSystem.cpp @@ -1,13 +1,13 @@ #include "TagSystem.h" #include "Engine/Scene/Scene.h" #include "Engine/Component/Core/HierarchyComponent.h" -#include "Engine/System/Core/HierarchySystem.h" +#include "TagSetupSystem.h" namespace Syn { std::vector TagSystem::GetReadDependencies() const { - return { TypeInfo::ID }; + return { TypeInfo::ID }; } std::vector TagSystem::GetWriteDependencies() const @@ -24,41 +24,52 @@ namespace Syn if (!tagPool || !hierarchyPool || !hierarchyManager) return; - this->EmplaceTask(subflow, SystemPhaseNames::Update, [tagPool, hierarchyPool, hierarchyManager]() { + auto* workQueue = hierarchyManager->EnsureWorkQueue(); + uint32_t maxLevel = hierarchyManager->GetMaxActiveLevel(); + std::vector levelTasks; - uint32_t maxLevel = hierarchyManager->GetMaxActiveLevel(); + for (uint32_t level = 0; level < maxLevel; ++level) + { + tf::Task levelTask = this->EmplaceTask(subflow, "TagEnable_" + std::to_string(level), [=](tf::Subflow& nested_subflow) { - for (uint32_t level = 0; level < maxLevel; ++level) - { - auto entitiesInLevel = hierarchyManager->GetEntitiesInLevel(level); + auto currentQueue = workQueue->GetQueue(level); + + if (currentQueue.empty()) return; - for (EntityID entity : entitiesInLevel) - { - if (!tagPool->Has(entity)) continue; + nested_subflow.for_each(currentQueue.begin(), currentQueue.end(), [=](EntityID entity) { auto& tag = tagPool->Get(entity); - bool parentGlobalEnabled = true; - if (level > 0) + bool parentGlobalEnabled = true; + if (hierarchyPool->Has(entity)) { - EntityID parentId = hierarchyPool->Get(entity).parent; - if (parentId != NULL_ENTITY && tagPool->Has(parentId)) - { - parentGlobalEnabled = tagPool->Get(parentId).globalEnabled; + EntityID parent = hierarchyPool->Get(entity).parent; + + if (parent != NULL_ENTITY && tagPool->Has(parent)) { + parentGlobalEnabled = tagPool->Get(parent).globalEnabled; } } bool newGlobalEnabled = tag.localEnabled && parentGlobalEnabled; - - if (tag.globalEnabled != newGlobalEnabled || tagPool->IsBitSet(entity) || tagPool->IsBitSet(entity)) + if (tag.globalEnabled != newGlobalEnabled) { tag.globalEnabled = newGlobalEnabled; - tagPool->SetBit(entity); + + if (tagPool->IsDynamic(entity)) { + tagPool->SetBit(entity); + } + tag.version++; } - } + }); + }); + + if (!levelTasks.empty()) { + levelTasks.back().precede(levelTask); } - }); + + levelTasks.push_back(levelTask); + } } void TagSystem::UploadComponents(Scene* scene, uint32_t frameIndex, tf::Subflow& subflow, bool uploadDynamic, bool uploadStatic) From a69fe70b50d580eaa70dc41e4a45ed6d2de8f3f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20P=C3=A9ter?= Date: Wed, 8 Jul 2026 16:21:41 +0200 Subject: [PATCH 34/51] Material override combo box disabled bc of performance issues --- .../Rendering/MaterialOverrideView.cpp | 22 +- .../MaterialOverrideViewModel.cpp | 2 +- .../Shaders/Includes/Utils/MaterialMath.glsl | 2 +- SynapseEngine/imgui.ini | 259 ++++++++++++++---- 4 files changed, 228 insertions(+), 57 deletions(-) diff --git a/SynapseEngine/Editor/View/Component/Rendering/MaterialOverrideView.cpp b/SynapseEngine/Editor/View/Component/Rendering/MaterialOverrideView.cpp index 6355cbbf..919f6c31 100644 --- a/SynapseEngine/Editor/View/Component/Rendering/MaterialOverrideView.cpp +++ b/SynapseEngine/Editor/View/Component/Rendering/MaterialOverrideView.cpp @@ -30,17 +30,25 @@ namespace Syn { vm.Dispatch(SetSharedMaterialEntityIntent{ NULL_ENTITY }); } - for (const auto& ent : state.compatibleSharedEntities) { - bool isSelected = (state.sharedMaterialEntity == ent.first); + ImGuiListClipper clipper; + clipper.Begin(static_cast(state.compatibleSharedEntities.size())); - if (ImGui::Selectable(ent.second.c_str(), isSelected)) { - vm.Dispatch(SetSharedMaterialEntityIntent{ ent.first }); - } + while (clipper.Step()) { + for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; ++i) { + const auto& ent = state.compatibleSharedEntities[i]; + bool isSelected = (state.sharedMaterialEntity == ent.first); - if (isSelected) { - ImGui::SetItemDefaultFocus(); + ImGui::PushID(ent.first); + if (ImGui::Selectable(ent.second.c_str(), isSelected)) { + vm.Dispatch(SetSharedMaterialEntityIntent{ ent.first }); + } + if (isSelected) { + ImGui::SetItemDefaultFocus(); + } + ImGui::PopID(); } } + Syn::UI::EndPropertyCombo(); } diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Rendering/MaterialOverride/MaterialOverrideViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/Component/Rendering/MaterialOverride/MaterialOverrideViewModel.cpp index 94d535c9..07e7c3a6 100644 --- a/SynapseEngine/EditorCore/ViewModels/Component/Rendering/MaterialOverride/MaterialOverrideViewModel.cpp +++ b/SynapseEngine/EditorCore/ViewModels/Component/Rendering/MaterialOverride/MaterialOverrideViewModel.cpp @@ -21,7 +21,7 @@ namespace Syn _state.expectedSlotCount = _overrideApi->GetExpectedSlotCount(activeEntity); _state.sharedMaterialEntity = _overrideApi->GetSharedMaterialEntity(activeEntity); _state.availableMaterials = _overrideApi->GetAvailableMaterials(); - _state.compatibleSharedEntities = _overrideApi->GetCompatibleSharedEntities(activeEntity); + //_state.compatibleSharedEntities = _overrideApi->GetCompatibleSharedEntities(activeEntity); _state.overrides.clear(); _state.overrides.reserve(_state.expectedSlotCount); diff --git a/SynapseEngine/Engine/Shaders/Includes/Utils/MaterialMath.glsl b/SynapseEngine/Engine/Shaders/Includes/Utils/MaterialMath.glsl index e89ec494..df6a1096 100644 --- a/SynapseEngine/Engine/Shaders/Includes/Utils/MaterialMath.glsl +++ b/SynapseEngine/Engine/Shaders/Includes/Utils/MaterialMath.glsl @@ -35,7 +35,7 @@ vec3 EvaluateNormal(uint64_t textureMetadataBufferAddr, Material mat, vec2 uv, v mat3 TBN = mat3(tangent, bitangent, normal); vec3 tangentSpaceNormal; - tangentSpaceNormal.xy = SampleTexture2D(mat.normalTexture, SAMPLER_NEAREST_ANISO, uv).xy * 2.0 - 1.0; + tangentSpaceNormal.xy = SampleTexture2D(mat.normalTexture, SAMPLER_LINEAR_REPEAT, uv).xy * 2.0 - 1.0; tangentSpaceNormal.z = sqrt(max(1.0 - dot(tangentSpaceNormal.xy, tangentSpaceNormal.xy), 0.0)); tangentSpaceNormal.y *= invertNormal ? -1.0 : 1.0; diff --git a/SynapseEngine/imgui.ini b/SynapseEngine/imgui.ini index fea6f6dc..225fbc69 100644 --- a/SynapseEngine/imgui.ini +++ b/SynapseEngine/imgui.ini @@ -3,52 +3,141 @@ Pos=0,23 Size=2304,1273 Collapsed=0 -[Window][Content_Scene] -Pos=405,964 -Size=1470,332 +[Window][ Inspector] +Pos=1814,23 +Size=490,630 Collapsed=0 -DockId=0x00000002,0 +DockId=0x00000007,0 + +[Window][ Viewport] +Pos=464,23 +Size=1348,842 +Collapsed=0 +DockId=0x00000003,0 [Window][Debug##Default] Pos=60,60 Size=400,400 Collapsed=0 -[Window][ Inspector] -Pos=1877,23 -Size=427,623 -Collapsed=0 -DockId=0x00000005,0 - -[Window][ Viewport] -Pos=405,23 -Size=1470,939 +[Window][Content_Scene] +Pos=464,867 +Size=1348,429 Collapsed=0 -DockId=0x00000001,0 +DockId=0x00000004,0 [Window][ Graphics & Environment] -Pos=1877,648 -Size=427,648 +Pos=1814,655 +Size=490,641 Collapsed=0 -DockId=0x00000006,0 +DockId=0x00000008,0 [Window][ Scene Hierarchy] Pos=0,23 -Size=403,535 +Size=462,658 Collapsed=0 DockId=0x00000009,0 [Window][ Performance Profiler] -Pos=0,560 -Size=403,736 +Pos=0,683 +Size=462,613 Collapsed=0 DockId=0x0000000A,0 [Window][ Output Log] -Pos=405,964 -Size=1470,332 +Pos=464,867 +Size=1348,429 +Collapsed=0 +DockId=0x00000004,1 + +[Window][HostWindow_Material] +Pos=0,23 +Size=2304,1273 Collapsed=0 -DockId=0x00000002,1 + +[Window][Content_Material] +Pos=494,865 +Size=1810,431 +Collapsed=0 +DockId=0x0000000E,0 + +[Window][ Materials] +Pos=0,23 +Size=492,655 +Collapsed=0 +DockId=0x0000000B,0 + +[Window][ Material Graph] +Pos=1616,23 +Size=688,840 +Collapsed=0 +DockId=0x00000012,0 + +[Window][ Material Viewport] +Pos=494,23 +Size=1120,840 +Collapsed=0 +DockId=0x00000011,0 + +[Window][ Material Properties] +Pos=0,680 +Size=492,616 +Collapsed=0 +DockId=0x0000000C,0 + +[Window][HostWindow_Model] +Pos=0,23 +Size=2304,1273 +Collapsed=0 + +[Window][Content_Model] +Pos=612,923 +Size=1293,373 +Collapsed=0 +DockId=0x00000014,0 + +[Window][ Model Hierarchy] +Pos=0,23 +Size=610,1273 +Collapsed=0 +DockId=0x00000017,0 + +[Window][ Model Properties##ModelPropsWindow] +Pos=1907,23 +Size=397,1273 +Collapsed=0 +DockId=0x00000016,0 + +[Window][HostWindow_Texture] +Pos=0,23 +Size=2304,1273 +Collapsed=0 + +[Window][Content_Texture] +Pos=60,60 +Size=276,222 +Collapsed=0 + +[Window][ Textures] +Pos=60,60 +Size=32,67 +Collapsed=0 + +[Window][ Texture Properties] +Pos=60,60 +Size=32,67 +Collapsed=0 + +[Window][ Texture Graph] +Pos=60,60 +Size=32,32 +Collapsed=0 + +[Window][ Model Viewport] +Pos=612,23 +Size=1293,898 +Collapsed=0 +DockId=0x00000013,0 [Table][0x3D1A1C69,2] RefScale=13 @@ -70,11 +159,21 @@ RefScale=13 Column 0 Width=168 Column 1 Weight=1.0000 +[Table][0x03404476,2] +RefScale=13 +Column 0 Width=126 +Column 1 Weight=1.0000 + [Table][0x8556BC1A,2] RefScale=13 Column 0 Width=175 Column 1 Weight=1.0000 +[Table][0xCC86EC87,2] +RefScale=13 +Column 0 Width=140 +Column 1 Weight=1.0000 + [Table][0x51A78E48,2] RefScale=13 Column 0 Weight=1.0000 @@ -82,9 +181,16 @@ Column 1 Width=48 [Table][0xB6D16E5C,3] RefScale=13 -Column 0 Weight=0.7668 -Column 1 Width=64 -Column 2 Weight=0.2332 +Column 0 Weight=0.5346 +Column 1 Width=58 +Column 2 Weight=0.4654 + +[Table][0x05A9070B,4] +RefScale=13 +Column 0 Width=140 +Column 1 Width=60 +Column 2 Width=150 +Column 3 Weight=1.0000 [Table][0x94CE371A,2] RefScale=13 @@ -96,16 +202,6 @@ RefScale=13 Column 0 Width=56 Column 1 Weight=1.0000 -[Table][0x78D40C46,2] -RefScale=13 -Column 0 Width=135 -Column 1 Weight=1.0000 - -[Table][0xFFF76F10,2] -RefScale=13 -Column 0 Width=77 -Column 1 Weight=1.0000 - [Table][0xF8524FE2,2] RefScale=13 Column 0 Width=98 @@ -126,26 +222,93 @@ RefScale=13 Column 0 Width=0 Column 1 Weight=-nan(ind) +[Table][0x9543A694,1] +Column 0 Weight=1.0000 + +[Table][0x8A428527,2] +RefScale=13 +Column 0 Width=126 +Column 1 Weight=1.0000 + +[Table][0xFFF52B67,2] +RefScale=13 +Column 0 Width=119 +Column 1 Weight=1.0000 + +[Table][0x748E37A9,2] +RefScale=13 +Column 0 Weight=1.0000 +Column 1 Width=45 + [Table][0xF10D0EE1,2] RefScale=13 Column 0 Width=63 Column 1 Weight=1.0000 -[Table][0x03404476,2] +[Table][0x22DE21CA,1] +Column 0 Weight=1.0000 + +[Table][0xD16AD86C,2] +RefScale=13 +Column 0 Width=63 +Column 1 Weight=1.0000 + +[Table][0xEA33DFC0,2] +RefScale=13 +Column 0 Width=70 +Column 1 Weight=1.0000 + +[Table][0xFFF76F10,2] +RefScale=13 +Column 0 Width=84 +Column 1 Weight=1.0000 + +[Table][0x5806762E,2] +RefScale=13 +Column 0 Width=84 +Column 1 Weight=1.0000 + +[Table][0x132E0EDB,2] RefScale=13 Column 0 Width=0 Column 1 Weight=-nan(ind) +[Table][0x78D40C46,2] +RefScale=13 +Column 0 Width=135 +Column 1 Weight=1.0000 + +[Table][0x6EB26338,2] +RefScale=13 +Column 0 Width=84 +Column 1 Weight=1.0000 + [Docking][Data] -DockSpace ID=0x4713F8A8 Window=0xFBC4AE05 Pos=128,95 Size=2304,1273 Split=X - DockNode ID=0x00000007 Parent=0x4713F8A8 SizeRef=403,1273 Split=Y Selected=0x02B8E2DB - DockNode ID=0x00000009 Parent=0x00000007 SizeRef=403,535 Selected=0xF995F4A5 - DockNode ID=0x0000000A Parent=0x00000007 SizeRef=403,736 Selected=0x02B8E2DB - DockNode ID=0x00000008 Parent=0x4713F8A8 SizeRef=1899,1273 Split=X - DockNode ID=0x00000003 Parent=0x00000008 SizeRef=1470,1273 Split=Y - DockNode ID=0x00000001 Parent=0x00000003 SizeRef=2304,939 CentralNode=1 Selected=0x1C1AF642 - DockNode ID=0x00000002 Parent=0x00000003 SizeRef=2304,332 Selected=0x81DECE6A - DockNode ID=0x00000004 Parent=0x00000008 SizeRef=427,1273 Split=Y Selected=0x70CE1A73 - DockNode ID=0x00000005 Parent=0x00000004 SizeRef=427,623 Selected=0x70CE1A73 - DockNode ID=0x00000006 Parent=0x00000004 SizeRef=427,648 Selected=0x57A55B3F +DockSpace ID=0x1B7E3B2F Pos=128,95 Size=2304,1273 Split=X + DockNode ID=0x0000000F Parent=0x1B7E3B2F SizeRef=492,1273 Split=Y Selected=0xF8059156 + DockNode ID=0x0000000B Parent=0x0000000F SizeRef=542,655 Selected=0xF8059156 + DockNode ID=0x0000000C Parent=0x0000000F SizeRef=542,616 Selected=0x59113670 + DockNode ID=0x00000010 Parent=0x1B7E3B2F SizeRef=1810,1273 Split=Y + DockNode ID=0x0000000D Parent=0x00000010 SizeRef=1922,840 Split=X Selected=0x6F3619D2 + DockNode ID=0x00000011 Parent=0x0000000D SizeRef=1120,840 CentralNode=1 Selected=0x6F3619D2 + DockNode ID=0x00000012 Parent=0x0000000D SizeRef=688,840 Selected=0x814C861D + DockNode ID=0x0000000E Parent=0x00000010 SizeRef=1922,431 Selected=0x4B58EA58 +DockSpace ID=0x4713F8A8 Window=0xFBC4AE05 Pos=128,95 Size=2304,1273 Split=X Selected=0x1C1AF642 + DockNode ID=0x00000005 Parent=0x4713F8A8 SizeRef=462,1273 Split=Y Selected=0x02B8E2DB + DockNode ID=0x00000009 Parent=0x00000005 SizeRef=442,658 Selected=0xF995F4A5 + DockNode ID=0x0000000A Parent=0x00000005 SizeRef=442,613 Selected=0x02B8E2DB + DockNode ID=0x00000006 Parent=0x4713F8A8 SizeRef=1840,1273 Split=X + DockNode ID=0x00000001 Parent=0x00000006 SizeRef=1348,1273 Split=Y Selected=0x1C1AF642 + DockNode ID=0x00000003 Parent=0x00000001 SizeRef=1901,842 CentralNode=1 Selected=0x1C1AF642 + DockNode ID=0x00000004 Parent=0x00000001 SizeRef=1901,429 Selected=0x81DECE6A + DockNode ID=0x00000002 Parent=0x00000006 SizeRef=490,1273 Split=Y Selected=0x70CE1A73 + DockNode ID=0x00000007 Parent=0x00000002 SizeRef=401,630 Selected=0x70CE1A73 + DockNode ID=0x00000008 Parent=0x00000002 SizeRef=401,641 Selected=0x57A55B3F +DockSpace ID=0x64578384 Pos=128,95 Size=2304,1273 Split=X + DockNode ID=0x00000017 Parent=0x64578384 SizeRef=610,1273 Selected=0x7B451746 + DockNode ID=0x00000018 Parent=0x64578384 SizeRef=1692,1273 Split=X + DockNode ID=0x00000015 Parent=0x00000018 SizeRef=1293,1273 Split=Y + DockNode ID=0x00000013 Parent=0x00000015 SizeRef=2304,898 CentralNode=1 Selected=0x8D543EF8 + DockNode ID=0x00000014 Parent=0x00000015 SizeRef=2304,373 Selected=0xB3A6D868 + DockNode ID=0x00000016 Parent=0x00000018 SizeRef=397,1273 Selected=0x54E69AFF From ac8d361a9aae36adbb20145c136c061ebdd9134a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20P=C3=A9ter?= Date: Wed, 8 Jul 2026 16:50:56 +0200 Subject: [PATCH 35/51] Resolved tag issues --- SynapseEngine/Engine/System/Core/TagSystem.cpp | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/SynapseEngine/Engine/System/Core/TagSystem.cpp b/SynapseEngine/Engine/System/Core/TagSystem.cpp index 48e46895..cd094062 100644 --- a/SynapseEngine/Engine/System/Core/TagSystem.cpp +++ b/SynapseEngine/Engine/System/Core/TagSystem.cpp @@ -50,17 +50,13 @@ namespace Syn } } - bool newGlobalEnabled = tag.localEnabled && parentGlobalEnabled; - if (tag.globalEnabled != newGlobalEnabled) - { - tag.globalEnabled = newGlobalEnabled; - - if (tagPool->IsDynamic(entity)) { - tagPool->SetBit(entity); - } + tag.globalEnabled = tag.localEnabled && parentGlobalEnabled; - tag.version++; + if (tagPool->IsDynamic(entity)) { + tagPool->SetBit(entity); } + + tag.version++; }); }); From 09615fc16b6576a9b2b57cffc951950936c8e9a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20P=C3=A9ter?= Date: Wed, 8 Jul 2026 17:04:16 +0200 Subject: [PATCH 36/51] Direction light instance buffer issues resolved --- .../Scene/Source/Procedural/test_config.json | 2 +- .../DirectionLightShadowCullingSystem.cpp | 14 +++++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json b/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json index eb1a9bc9..e2462fb4 100644 --- a/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json +++ b/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json @@ -10,7 +10,7 @@ }, "entities": { "animated_characters": 500, - "static_geometry": 50000, + "static_geometry": 500000, "physics_boxes": 500, "physics_spheres": 500, "physics_capsules": 500 diff --git a/SynapseEngine/Engine/System/Light/Direction/DirectionLightShadowCullingSystem.cpp b/SynapseEngine/Engine/System/Light/Direction/DirectionLightShadowCullingSystem.cpp index d11d6236..008832e7 100644 --- a/SynapseEngine/Engine/System/Light/Direction/DirectionLightShadowCullingSystem.cpp +++ b/SynapseEngine/Engine/System/Light/Direction/DirectionLightShadowCullingSystem.cpp @@ -440,7 +440,7 @@ namespace Syn auto& mainGroup = drawData->Models; auto& shadowGroup = drawData->DirectionLightShadow; - if (settings->culling.directionLightShadowCullingDevice == CPU) + if (settings->culling.directionLightShadowCullingDevice == CPU ) { // Sync CPU counters to indirect commands for (uint32_t i = 0; i < mainGroup.activeTraditionalCount; ++i) { @@ -451,10 +451,14 @@ namespace Syn shadowGroup.meshletCmds[i].groupCountX = shadowGroup.paddedMeshletCounts[i * 16]; } - // Upload instances - size_t instanceSize = mainGroup.totalAllocatedInstances * SHADOW_MULTIPLIER * sizeof(uint32_t); - if (instanceSize > 0) { - shadowGroup.instanceBuffer.Write(frameIndex, shadowGroup.instances.Data(), instanceSize, 0); + uint32_t activeShadowLightCount = drawData->DirectionLightShadow.visibleLightCount; + if (activeShadowLightCount != 0) + { + // Upload instances + size_t instanceSize = mainGroup.totalAllocatedInstances * SHADOW_MULTIPLIER * sizeof(uint32_t); + if (instanceSize > 0) { + shadowGroup.instanceBuffer.Write(frameIndex, shadowGroup.instances.Data(), instanceSize, 0); + } } } From 481496448422894b5971e93736c9edcec3ac255b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20P=C3=A9ter?= Date: Thu, 9 Jul 2026 11:30:23 +0200 Subject: [PATCH 37/51] Material sampler refactor --- .../Editor/EditorApi/Impl/TextureApiImpl.cpp | 17 +++ .../Editor/EditorApi/Impl/TextureApiImpl.h | 1 + .../MaterialPropertiesView.cpp | 123 ++++++++++-------- .../MaterialPropertiesView.h | 5 +- .../ModelHierarchy/ModelHierarchyView.cpp | 23 +--- .../ModelProperties/ModelPropertiesView.cpp | 6 +- .../TexturePropertiesView.cpp | 5 +- SynapseEngine/EditorCore/Api/ITextureApi.h | 8 ++ .../MaterialPropertiesState.h | 22 +++- .../MaterialPropertiesViewModel.cpp | 27 ++++ SynapseEngine/Engine/Image/ImageManager.h | 2 + SynapseEngine/Engine/Material/Material.cpp | 30 +++-- SynapseEngine/Engine/Material/Material.h | 15 +++ .../Scene/Source/Procedural/test_config.json | 2 +- .../Shaders/Includes/Common/Material.glsl | 7 +- .../Shaders/Includes/Utils/MaterialMath.glsl | 70 ++++++---- SynapseEngine/synapse_stats.txt | 10 +- 17 files changed, 252 insertions(+), 121 deletions(-) diff --git a/SynapseEngine/Editor/EditorApi/Impl/TextureApiImpl.cpp b/SynapseEngine/Editor/EditorApi/Impl/TextureApiImpl.cpp index 5c8e793e..cd87f29f 100644 --- a/SynapseEngine/Editor/EditorApi/Impl/TextureApiImpl.cpp +++ b/SynapseEngine/Editor/EditorApi/Impl/TextureApiImpl.cpp @@ -26,6 +26,23 @@ namespace Syn { return result; } + std::vector TextureApiImpl::GetAllSamplers() const { + if (!_imageManager) return {}; + + std::vector result; + const auto& samplers = _imageManager->GetAvailableSamplers(); + + for (const auto& [name, id] : samplers) { + result.push_back({ id, name }); + } + + std::sort(result.begin(), result.end(), [](const SamplerItemData& a, const SamplerItemData& b) { + return a.id < b.id; + }); + + return result; + } + uint32_t TextureApiImpl::GetSelectedTexture() const { return _selectedTexture; } diff --git a/SynapseEngine/Editor/EditorApi/Impl/TextureApiImpl.h b/SynapseEngine/Editor/EditorApi/Impl/TextureApiImpl.h index 604984a9..fb880583 100644 --- a/SynapseEngine/Editor/EditorApi/Impl/TextureApiImpl.h +++ b/SynapseEngine/Editor/EditorApi/Impl/TextureApiImpl.h @@ -10,6 +10,7 @@ namespace Syn { : _imageManager(imageManager), _guiTextureManager(guiTextureManager) {} std::vector GetAllTextures() const override; + std::vector GetAllSamplers() const override; uint32_t GetSelectedTexture() const override; void SetSelectedTexture(uint32_t id) override; diff --git a/SynapseEngine/Editor/View/MaterialProperties/MaterialPropertiesView.cpp b/SynapseEngine/Editor/View/MaterialProperties/MaterialPropertiesView.cpp index e3fd161f..33338a93 100644 --- a/SynapseEngine/Editor/View/MaterialProperties/MaterialPropertiesView.cpp +++ b/SynapseEngine/Editor/View/MaterialProperties/MaterialPropertiesView.cpp @@ -9,7 +9,7 @@ namespace Syn { const auto& state = vm.GetState(); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8, 8)); - ImGuiWindowFlags windowFlags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse; + ImGuiWindowFlags windowFlags = ImGuiWindowFlags_None; if (ImGui::Begin(SYN_ICON_INFO_CIRCLE " Material Properties", nullptr, windowFlags)) { @@ -32,51 +32,26 @@ namespace Syn { constexpr const char* BasicPropsCard = "MatBasicPropsCard"; if (Syn::UI::BeginCard(BasicPropsCard, SYN_ICON_SLIDERS_H, getCardState(BasicPropsCard))) { if (Syn::UI::BeginPropertyGrid("MatPropsGrid")) { - - if (Syn::UI::PropertyColor4("Color", editedMat.color)) { - isModified = true; - } - - if (Syn::UI::PropertyColor3("Emissive Color", editedMat.emissiveColor)) { - isModified = true; - } - - if (Syn::UI::PropertyDragFloat("Emissive Intensity", editedMat.emissiveIntensity, 0.1f, 0.0f, 1000.0f, "%.2f")) { - isModified = true; - } - - if (Syn::UI::PropertyDragFloat2("UV Scale", editedMat.uvScale, 0.05f, 0.0f, 0.0f, "%.2f")) { - isModified = true; - } + if (Syn::UI::PropertyColor4("Color", editedMat.color)) isModified = true; + if (Syn::UI::PropertyColor3("Emissive Color", editedMat.emissiveColor)) isModified = true; + if (Syn::UI::PropertyDragFloat("Emissive Intensity", editedMat.emissiveIntensity, 0.1f, 0.0f, 1000.0f, "%.2f")) isModified = true; + if (Syn::UI::PropertyDragFloat2("UV Scale", editedMat.uvScale, 0.05f, 0.0f, 0.0f, "%.2f")) isModified = true; Syn::UI::PropertySeparator(); - if (Syn::UI::PropertyDragFloat("Metalness", editedMat.metalness, 0.01f, 0.0f, 1.0f, "%.2f")) { - isModified = true; - } - - if (Syn::UI::PropertyDragFloat("Roughness", editedMat.roughness, 0.01f, 0.0f, 1.0f, "%.2f")) { - isModified = true; - } - - if (Syn::UI::PropertyDragFloat("AO Strength", editedMat.aoStrength, 0.01f, 0.0f, 1.0f, "%.2f")) { - isModified = true; - } + if (Syn::UI::PropertyDragFloat("Metalness", editedMat.metalness, 0.01f, 0.0f, 1.0f, "%.2f")) isModified = true; + if (Syn::UI::PropertyDragFloat("Roughness", editedMat.roughness, 0.01f, 0.0f, 1.0f, "%.2f")) isModified = true; + if (Syn::UI::PropertyDragFloat("AO Strength", editedMat.aoStrength, 0.01f, 0.0f, 1.0f, "%.2f")) isModified = true; Syn::UI::PropertySeparator(); - if (Syn::UI::PropertyCheckbox("Double Sided", editedMat.doubleSided)) { - isModified = true; - } - - if (Syn::UI::PropertyCheckbox("Transparent", editedMat.isTransparent)) { - isModified = true; - } + if (Syn::UI::PropertyCheckbox("Double Sided", editedMat.doubleSided)) isModified = true; + if (Syn::UI::PropertyCheckbox("Transparent", editedMat.isTransparent)) isModified = true; Syn::UI::EndPropertyGrid(); } - Syn::UI::EndCard(); } + Syn::UI::EndCard(); ImGui::Spacing(); @@ -84,18 +59,38 @@ namespace Syn { if (Syn::UI::BeginCard(TexturesCard, SYN_ICON_IMAGE, getCardState(TexturesCard))) { if (Syn::UI::BeginPropertyGrid("MatTexGrid")) { - DrawTextureSlot("Albedo", editedMat.albedoTexture, state.albedoName, state.availableTextures, isModified); - DrawTextureSlot("Normal", editedMat.normalTexture, state.normalName, state.availableTextures, isModified); - DrawTextureSlot("Metalness", editedMat.metalnessTexture, state.metalnessName, state.availableTextures, isModified); - DrawTextureSlot("Roughness", editedMat.roughnessTexture, state.roughnessName, state.availableTextures, isModified); - DrawTextureSlot("MetallicRoughness", editedMat.metallicRoughnessTexture, state.metallicRoughnessName, state.availableTextures, isModified); - DrawTextureSlot("Emissive", editedMat.emissiveTexture, state.emissiveName, state.availableTextures, isModified); - DrawTextureSlot("Ambient Occlusion", editedMat.ambientOcclusionTexture, state.aoName, state.availableTextures, isModified); + DrawTextureSlot("Albedo", + editedMat.albedoTexture, state.albedoName, state.availableTextures, + editedMat.albedoSampler, state.albedoSamplerName, state.availableSamplers, isModified); + + DrawTextureSlot("Normal", + editedMat.normalTexture, state.normalName, state.availableTextures, + editedMat.normalSampler, state.normalSamplerName, state.availableSamplers, isModified); + + DrawTextureSlot("Metalness", + editedMat.metalnessTexture, state.metalnessName, state.availableTextures, + editedMat.metalnessSampler, state.metalnessSamplerName, state.availableSamplers, isModified); + + DrawTextureSlot("Roughness", + editedMat.roughnessTexture, state.roughnessName, state.availableTextures, + editedMat.roughnessSampler, state.roughnessSamplerName, state.availableSamplers, isModified); + + DrawTextureSlot("MetallicRoughness", + editedMat.metallicRoughnessTexture, state.metallicRoughnessName, state.availableTextures, + editedMat.metallicRoughnessSampler, state.metallicRoughnessSamplerName, state.availableSamplers, isModified); + + DrawTextureSlot("Emissive", + editedMat.emissiveTexture, state.emissiveName, state.availableTextures, + editedMat.emissiveSampler, state.emissiveSamplerName, state.availableSamplers, isModified); + + DrawTextureSlot("AO", + editedMat.ambientOcclusionTexture, state.aoName, state.availableTextures, + editedMat.ambientOcclusionSampler, state.aoSamplerName, state.availableSamplers, isModified); Syn::UI::EndPropertyGrid(); } - Syn::UI::EndCard(); } + Syn::UI::EndCard(); if (isModified) { vm.Dispatch(UpdateMaterialPropertyIntent{ editedMat }); @@ -106,18 +101,21 @@ namespace Syn { ImGui::PopStyleVar(); } - void MaterialPropertiesView::DrawTextureSlot(const char* label, uint32_t& currentTexId, const std::string& currentName, const std::vector& options, bool& changed) { - - if (Syn::UI::BeginPropertyCombo(label, currentName.c_str())) { - + void MaterialPropertiesView::DrawTextureSlot( + const char* label, + uint32_t& currentTexId, const std::string& currentTexName, const std::vector& texOptions, + uint32_t& currentSampId, const std::string& currentSampName, const std::vector& sampOptions, + bool& changed) + { + std::string texLabel = std::string(label) + " Tex"; + if (Syn::UI::BeginPropertyCombo(texLabel.c_str(), currentTexName.c_str())) { if (ImGui::Selectable("None", currentTexId == 0xFFFFFFFF)) { if (currentTexId != 0xFFFFFFFF) { currentTexId = 0xFFFFFFFF; changed = true; } } - - for (const auto& opt : options) { + for (const auto& opt : texOptions) { bool isSelected = (currentTexId == opt.id); if (ImGui::Selectable(opt.name.c_str(), isSelected)) { if (currentTexId != opt.id) { @@ -125,13 +123,34 @@ namespace Syn { changed = true; } } + if (isSelected) ImGui::SetItemDefaultFocus(); + } + Syn::UI::EndPropertyCombo(); + } + + std::string sampLabel = std::string(label) + " Sampler"; + if (Syn::UI::BeginPropertyCombo(sampLabel.c_str(), currentSampName.c_str())) { - if (isSelected) { - ImGui::SetItemDefaultFocus(); + if (ImGui::Selectable("Default", currentSampId == 0xFFFFFFFF)) { + if (currentSampId != 0xFFFFFFFF) { + currentSampId = 0xFFFFFFFF; + changed = true; } } + for (const auto& opt : sampOptions) { + bool isSelected = (currentSampId == opt.id); + if (ImGui::Selectable(opt.name.c_str(), isSelected)) { + if (currentSampId != opt.id) { + currentSampId = opt.id; + changed = true; + } + } + if (isSelected) ImGui::SetItemDefaultFocus(); + } Syn::UI::EndPropertyCombo(); } + + Syn::UI::PropertySeparator(); } } \ No newline at end of file diff --git a/SynapseEngine/Editor/View/MaterialProperties/MaterialPropertiesView.h b/SynapseEngine/Editor/View/MaterialProperties/MaterialPropertiesView.h index 966ae678..a63e8612 100644 --- a/SynapseEngine/Editor/View/MaterialProperties/MaterialPropertiesView.h +++ b/SynapseEngine/Editor/View/MaterialProperties/MaterialPropertiesView.h @@ -9,7 +9,10 @@ namespace Syn { public: void Draw(MaterialPropertiesViewModel& vm) override; private: - void DrawTextureSlot(const char* label, uint32_t& currentTexId, const std::string& currentName, const std::vector& options, bool& changed); + void DrawTextureSlot(const char* label, + uint32_t& currentTexId, const std::string& currentTexName, const std::vector& texOptions, + uint32_t& currentSampId, const std::string& currentSampName, const std::vector& sampOptions, + bool& changed); private: std::unordered_map _cardStates; }; diff --git a/SynapseEngine/Editor/View/ModelHierarchy/ModelHierarchyView.cpp b/SynapseEngine/Editor/View/ModelHierarchy/ModelHierarchyView.cpp index 6b095113..f83ac96d 100644 --- a/SynapseEngine/Editor/View/ModelHierarchy/ModelHierarchyView.cpp +++ b/SynapseEngine/Editor/View/ModelHierarchy/ModelHierarchyView.cpp @@ -3,7 +3,7 @@ #include "Editor/Widgets/CardWidget.h" #include -namespace Syn +namespace Syn { void ModelHierarchyView::Draw(ModelHierarchyViewModel& vm) { ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8, 8)); @@ -36,10 +36,9 @@ namespace Syn ImGui::BeginChild("ModelHierarchyTableContainer", ImVec2(0, tableHeight), ImGuiChildFlags_Borders, ImGuiWindowFlags_NoScrollbar); - if (ImGui::BeginTable("ModelTable", 2, ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_ScrollY | ImGuiTableFlags_Resizable)) { + if (ImGui::BeginTable("ModelTable", 1, ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_ScrollY | ImGuiTableFlags_Resizable)) { ImGui::TableSetupScrollFreeze(0, 1); ImGui::TableSetupColumn("Hierarchy", ImGuiTableColumnFlags_WidthStretch); - ImGui::TableSetupColumn("Tris", ImGuiTableColumnFlags_WidthFixed, 45.0f); ImGui::TableNextRow(ImGuiTableRowFlags_Headers); @@ -51,17 +50,9 @@ namespace Syn ImGui::TableHeader("##ColHierarchy"); ImGui::SetCursorPos(ImVec2(startPos.x + (cellWidth - textWidth) * 0.5f, startPos.y + 3.0f)); + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); ImGui::Text("Hierarchy"); - - ImGui::TableSetColumnIndex(1); - cellWidth = ImGui::GetColumnWidth(); - textWidth = ImGui::CalcTextSize("Tris").x; - startPos = ImGui::GetCursorPos(); - - ImGui::TableHeader("##ColTris"); - - ImGui::SetCursorPos(ImVec2(startPos.x + (cellWidth - textWidth) * 0.5f, startPos.y + 3.0f)); - ImGui::Text("Tris"); + ImGui::PopStyleColor(); ImGuiListClipper clipper; clipper.Begin(static_cast(state.flatNodes.size())); @@ -155,11 +146,5 @@ namespace Syn if (ImGui::IsItemToggledOpen()) { vm.Dispatch(ModelHierarchyToggleExpandIntent{ node.modelId, node.descriptorIndex, !node.isExpanded }); } - - ImGui::TableNextColumn(); - - if (node.triangleCount > 0) { - ImGui::TextDisabled("%u", node.triangleCount); - } } } \ No newline at end of file diff --git a/SynapseEngine/Editor/View/ModelProperties/ModelPropertiesView.cpp b/SynapseEngine/Editor/View/ModelProperties/ModelPropertiesView.cpp index 5c4bf8f3..2229cb92 100644 --- a/SynapseEngine/Editor/View/ModelProperties/ModelPropertiesView.cpp +++ b/SynapseEngine/Editor/View/ModelProperties/ModelPropertiesView.cpp @@ -34,9 +34,9 @@ namespace Syn { DrawPropertyRow("Indices", std::to_string(state.globalIndexCount)); DrawPropertyRow("Triangles", std::to_string(state.globalIndexCount / 3)); Syn::UI::EndPropertyGrid(); - } - Syn::UI::EndCard(); + } } + Syn::UI::EndCard(); if (state.isNodeSelected) { ImGui::Spacing(); @@ -50,8 +50,8 @@ namespace Syn { DrawPropertyRow("Meshlets", std::to_string(state.nodeMeshletCount)); Syn::UI::EndPropertyGrid(); } - Syn::UI::EndCard(); } + Syn::UI::EndCard(); } } diff --git a/SynapseEngine/Editor/View/TextureProperties/TexturePropertiesView.cpp b/SynapseEngine/Editor/View/TextureProperties/TexturePropertiesView.cpp index 333f2a60..8f2cf709 100644 --- a/SynapseEngine/Editor/View/TextureProperties/TexturePropertiesView.cpp +++ b/SynapseEngine/Editor/View/TextureProperties/TexturePropertiesView.cpp @@ -10,7 +10,7 @@ namespace Syn { const TexturePropertiesState& state = vm.GetState(); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8, 8)); - ImGuiWindowFlags windowFlags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse; + ImGuiWindowFlags windowFlags = ImGuiWindowFlags_None; if (ImGui::Begin(SYN_ICON_INFO_CIRCLE " Texture Properties", nullptr, windowFlags)) { @@ -43,9 +43,8 @@ namespace Syn { Syn::UI::EndPropertyGrid(); } } - - Syn::UI::EndCard(); } + Syn::UI::EndCard(); } ImGui::End(); diff --git a/SynapseEngine/EditorCore/Api/ITextureApi.h b/SynapseEngine/EditorCore/Api/ITextureApi.h index 9e3ac64a..72af4f8f 100644 --- a/SynapseEngine/EditorCore/Api/ITextureApi.h +++ b/SynapseEngine/EditorCore/Api/ITextureApi.h @@ -6,6 +6,7 @@ namespace Syn { constexpr uint32_t INVALID_TEXTURE_ID = 0xFFFFFFFF; + constexpr uint32_t INVALID_SAMPLER_ID = 0xFFFFFFFF; struct TextureItemData { uint32_t id; @@ -13,11 +14,18 @@ namespace Syn { std::string path; }; + struct SamplerItemData { + uint32_t id; + std::string name; + }; + class ITextureApi { public: virtual ~ITextureApi() = default; virtual std::vector GetAllTextures() const = 0; + virtual std::vector GetAllSamplers() const = 0; + virtual uint32_t GetSelectedTexture() const = 0; virtual void SetSelectedTexture(uint32_t id) = 0; virtual bool GetTextureData(uint32_t id, CpuTextureData& outData) const = 0; diff --git a/SynapseEngine/EditorCore/ViewModels/MaterialProperties/MaterialPropertiesState.h b/SynapseEngine/EditorCore/ViewModels/MaterialProperties/MaterialPropertiesState.h index 5a3a1c9d..6282bc4d 100644 --- a/SynapseEngine/EditorCore/ViewModels/MaterialProperties/MaterialPropertiesState.h +++ b/SynapseEngine/EditorCore/ViewModels/MaterialProperties/MaterialPropertiesState.h @@ -4,12 +4,18 @@ #include #include "Engine/Material/Material.h" -namespace Syn { +namespace Syn +{ struct TextureOption { uint32_t id; std::string name; }; + struct SamplerOption { + uint32_t id; + std::string name; + }; + struct MaterialPropertiesState { bool hasSelection = false; uint32_t selectedMaterialId = 0xFFFFFFFF; @@ -17,13 +23,27 @@ namespace Syn { Material materialData; std::vector availableTextures; + std::vector availableSamplers; std::string albedoName = "None"; + std::string albedoSamplerName = "Default"; + std::string normalName = "None"; + std::string normalSamplerName = "Default"; + std::string metalnessName = "None"; + std::string metalnessSamplerName = "Default"; + std::string roughnessName = "None"; + std::string roughnessSamplerName = "Default"; + std::string metallicRoughnessName = "None"; + std::string metallicRoughnessSamplerName = "Default"; + std::string emissiveName = "None"; + std::string emissiveSamplerName = "Default"; + std::string aoName = "None"; + std::string aoSamplerName = "Default"; }; } \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/MaterialProperties/MaterialPropertiesViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/MaterialProperties/MaterialPropertiesViewModel.cpp index 742730b3..7dfbf902 100644 --- a/SynapseEngine/EditorCore/ViewModels/MaterialProperties/MaterialPropertiesViewModel.cpp +++ b/SynapseEngine/EditorCore/ViewModels/MaterialProperties/MaterialPropertiesViewModel.cpp @@ -28,6 +28,12 @@ namespace Syn { _state.availableTextures.push_back({ t.id, t.name }); } + _state.availableSamplers.clear(); + auto samps = _textureApi ? _textureApi->GetAllSamplers() : std::vector(); + for (const auto& s : samps) { + _state.availableSamplers.push_back({ s.id, s.name }); + } + auto getTexName = [&](uint32_t id) -> std::string { if (id == 0xFFFFFFFF) return "None"; for (const auto& t : texs) { @@ -36,13 +42,34 @@ namespace Syn { return "Unknown"; }; + auto getSamplerName = [&](uint32_t id) -> std::string { + if (id == 0xFFFFFFFF || id == 0xFF) return "Default"; + for (const auto& s : samps) { + if (s.id == id) return s.name; + } + return "Unknown"; + }; + _state.albedoName = getTexName(matData.albedoTexture); + _state.albedoSamplerName = getSamplerName(matData.albedoSampler); + _state.normalName = getTexName(matData.normalTexture); + _state.normalSamplerName = getSamplerName(matData.normalSampler); + _state.metalnessName = getTexName(matData.metalnessTexture); + _state.metalnessSamplerName = getSamplerName(matData.metalnessSampler); + _state.roughnessName = getTexName(matData.roughnessTexture); + _state.roughnessSamplerName = getSamplerName(matData.roughnessSampler); + _state.metallicRoughnessName = getTexName(matData.metallicRoughnessTexture); + _state.metallicRoughnessSamplerName = getSamplerName(matData.metallicRoughnessSampler); + _state.emissiveName = getTexName(matData.emissiveTexture); + _state.emissiveSamplerName = getSamplerName(matData.emissiveSampler); + _state.aoName = getTexName(matData.ambientOcclusionTexture); + _state.aoSamplerName = getSamplerName(matData.ambientOcclusionSampler); } else { _state.hasSelection = false; diff --git a/SynapseEngine/Engine/Image/ImageManager.h b/SynapseEngine/Engine/Image/ImageManager.h index 3e969cb7..4b399305 100644 --- a/SynapseEngine/Engine/Image/ImageManager.h +++ b/SynapseEngine/Engine/Image/ImageManager.h @@ -45,6 +45,8 @@ namespace Syn { VkDescriptorSetLayout GetBindlessLayout() const { return _bindlessLayout; } Vk::Sampler* GetSampler(const std::string& name) const; uint32_t GetSamplerIndex(const std::string& name) const; + + const std::unordered_map& GetAvailableSamplers() const { return _samplerNameToIndex; } protected: void FlushDirtyResources() override; void StartGpuUpload(EntryType& entry) override; diff --git a/SynapseEngine/Engine/Material/Material.cpp b/SynapseEngine/Engine/Material/Material.cpp index 4c56a53f..7ee56024 100644 --- a/SynapseEngine/Engine/Material/Material.cpp +++ b/SynapseEngine/Engine/Material/Material.cpp @@ -1,4 +1,5 @@ #include "Material.h" +#include "Engine/SynMacro.h" #include namespace Syn { @@ -24,6 +25,20 @@ namespace Syn { { } + SYN_INLINE uint32_t PackTextureAndSampler(uint32_t textureIdx, uint32_t samplerIdx) { + if (textureIdx == UINT32_MAX) + return UINT32_MAX; + + uint32_t tex = textureIdx & 0x00FFFFFF; + uint32_t samp = INVALID_SAMPLER_INDEX; + + if (samplerIdx != UINT32_MAX) { + samp = samplerIdx & 0xFF; + } + + return (samp << 24) | tex; + } + GpuMaterial::GpuMaterial(const Material& material) : color(material.color) , emissiveColor(material.emissiveColor) @@ -32,13 +47,13 @@ namespace Syn { , metalness(material.metalness) , roughness(material.roughness) , aoStrength(material.aoStrength) - , albedoTexture(material.albedoTexture == UINT32_MAX ? UINT32_MAX : material.albedoTexture) - , normalTexture(material.normalTexture == UINT32_MAX ? UINT32_MAX : material.normalTexture) - , metalnessTexture(material.metalnessTexture == UINT32_MAX ? UINT32_MAX : material.metalnessTexture) - , roughnessTexture(material.roughnessTexture == UINT32_MAX ? UINT32_MAX : material.roughnessTexture) - , metallicRoughnessTexture(material.metallicRoughnessTexture == UINT32_MAX ? UINT32_MAX : material.metallicRoughnessTexture) - , emissiveTexture(material.emissiveTexture == UINT32_MAX ? UINT32_MAX : material.emissiveTexture) - , ambientOcclusionTexture(material.ambientOcclusionTexture == UINT32_MAX ? UINT32_MAX : material.ambientOcclusionTexture) + , albedoTexture(PackTextureAndSampler(material.albedoTexture, material.albedoSampler)) + , normalTexture(PackTextureAndSampler(material.normalTexture, material.normalSampler)) + , metalnessTexture(PackTextureAndSampler(material.metalnessTexture, material.metalnessSampler)) + , roughnessTexture(PackTextureAndSampler(material.roughnessTexture, material.roughnessSampler)) + , metallicRoughnessTexture(PackTextureAndSampler(material.metallicRoughnessTexture, material.metallicRoughnessSampler)) + , emissiveTexture(PackTextureAndSampler(material.emissiveTexture, material.emissiveSampler)) + , ambientOcclusionTexture(PackTextureAndSampler(material.ambientOcclusionTexture, material.ambientOcclusionSampler)) , padding0(0) , padding1(0) , padding2(0) @@ -50,5 +65,4 @@ namespace Syn { this->packedFlags = flags; } - } \ No newline at end of file diff --git a/SynapseEngine/Engine/Material/Material.h b/SynapseEngine/Engine/Material/Material.h index 2269ec3a..16b94fa8 100644 --- a/SynapseEngine/Engine/Material/Material.h +++ b/SynapseEngine/Engine/Material/Material.h @@ -6,6 +6,8 @@ namespace Syn { + constexpr uint32_t INVALID_SAMPLER_INDEX = 0xFF; + struct SYN_API Material { glm::vec4 color = glm::vec4(1.0f); glm::vec3 emissiveColor = glm::vec3(0.0f); @@ -18,12 +20,25 @@ namespace Syn bool isTransparent = false; uint32_t albedoTexture = UINT32_MAX; + uint32_t albedoSampler = UINT32_MAX; + uint32_t normalTexture = UINT32_MAX; + uint32_t normalSampler = UINT32_MAX; + uint32_t metalnessTexture = UINT32_MAX; + uint32_t metalnessSampler = UINT32_MAX; + uint32_t roughnessTexture = UINT32_MAX; + uint32_t roughnessSampler = UINT32_MAX; + uint32_t metallicRoughnessTexture = UINT32_MAX; + uint32_t metallicRoughnessSampler = UINT32_MAX; + uint32_t emissiveTexture = UINT32_MAX; + uint32_t emissiveSampler = UINT32_MAX; + uint32_t ambientOcclusionTexture = UINT32_MAX; + uint32_t ambientOcclusionSampler = UINT32_MAX; }; struct SYN_API GpuMaterial { diff --git a/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json b/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json index e2462fb4..c9328fd1 100644 --- a/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json +++ b/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json @@ -10,7 +10,7 @@ }, "entities": { "animated_characters": 500, - "static_geometry": 500000, + "static_geometry": 25000, "physics_boxes": 500, "physics_spheres": 500, "physics_capsules": 500 diff --git a/SynapseEngine/Engine/Shaders/Includes/Common/Material.glsl b/SynapseEngine/Engine/Shaders/Includes/Common/Material.glsl index 567c8333..9d650a7b 100644 --- a/SynapseEngine/Engine/Shaders/Includes/Common/Material.glsl +++ b/SynapseEngine/Engine/Shaders/Includes/Common/Material.glsl @@ -3,6 +3,8 @@ #include "../Core.glsl" +#define INVALID_SAMPLER_INDEX 0xFF + struct Material { vec4 color; vec3 emissiveColor; @@ -12,7 +14,7 @@ struct Material { float roughness; float aoStrength; uint packedFlags; - uint albedoTexture; + uint albedoTexture; //(8 bit sampler | 24 bit texture) uint normalTexture; uint metalnessTexture; uint roughnessTexture; @@ -32,6 +34,9 @@ layout(buffer_reference, std430) readonly restrict buffer MaterialLookupBuffer { #define HAS_VALID_TEXTURE(texIdx) ((texIdx) != INVALID_INDEX) +#define UNPACK_TEXTURE_ID(packedVal) ((packedVal) & 0x00FFFFFF) +#define UNPACK_SAMPLER_ID(packedVal) ((packedVal) >> 24) + #define IS_DOUBLE_SIDED(mat) HAS_FLAG((mat).packedFlags, 0) #define IS_TRANSPARENT(mat) HAS_FLAG((mat).packedFlags, 1) diff --git a/SynapseEngine/Engine/Shaders/Includes/Utils/MaterialMath.glsl b/SynapseEngine/Engine/Shaders/Includes/Utils/MaterialMath.glsl index df6a1096..3cbe2a79 100644 --- a/SynapseEngine/Engine/Shaders/Includes/Utils/MaterialMath.glsl +++ b/SynapseEngine/Engine/Shaders/Includes/Utils/MaterialMath.glsl @@ -4,29 +4,44 @@ #include "../Common/Texture.glsl" #include "../Common/Material.glsl" +uint ResolveSampler(uint64_t textureMetadataBufferAddr, uint packedTexData, uint texID) { + uint sampID = UNPACK_SAMPLER_ID(packedTexData); + if (sampID == INVALID_SAMPLER_INDEX) { + uint meta = GET_TEXTURE_METADATA(textureMetadataBufferAddr, texID); + return UnpackTextureMetadataSampler(meta); + } + return sampID; +} + vec4 EvaluateAlbedoAlpha(uint64_t textureMetadataBufferAddr, const Material mat, vec2 uv) { vec4 finalColor = mat.color; + if (HAS_ALBEDO_TEX(mat)) { - uint meta = GET_TEXTURE_METADATA(textureMetadataBufferAddr, mat.albedoTexture); - uint samplerID = UnpackTextureMetadataSampler(meta); - - finalColor *= SampleTexture2D(mat.albedoTexture, samplerID, uv); + uint texID = UNPACK_TEXTURE_ID(mat.albedoTexture); + uint sampID = ResolveSampler(textureMetadataBufferAddr, mat.albedoTexture, texID); + finalColor *= SampleTexture2D(texID, sampID, uv); } return finalColor; } vec3 EvaluateNormal(uint64_t textureMetadataBufferAddr, Material mat, vec2 uv, vec3 vertexNormal, vec4 vertexTangent) { vec3 normal = normalize(vertexNormal); - + if (!HAS_NORMAL_TEX(mat)) { return normal; } - uint meta = GET_TEXTURE_METADATA(textureMetadataBufferAddr, mat.normalTexture); - - uint samplerID; + uint texID = UNPACK_TEXTURE_ID(mat.normalTexture); + uint meta = GET_TEXTURE_METADATA(textureMetadataBufferAddr, texID); + + uint defaultSamplerID; bool invertNormal; - UnpackTextureMetadata(meta, samplerID, invertNormal); + UnpackTextureMetadata(meta, defaultSamplerID, invertNormal); + + uint sampID = UNPACK_SAMPLER_ID(mat.normalTexture); + if (sampID == INVALID_SAMPLER_INDEX) { + sampID = defaultSamplerID; + } vec3 tangent = normalize(vertexTangent.xyz); tangent = normalize(tangent - normal * dot(normal, tangent)); @@ -35,7 +50,7 @@ vec3 EvaluateNormal(uint64_t textureMetadataBufferAddr, Material mat, vec2 uv, v mat3 TBN = mat3(tangent, bitangent, normal); vec3 tangentSpaceNormal; - tangentSpaceNormal.xy = SampleTexture2D(mat.normalTexture, SAMPLER_LINEAR_REPEAT, uv).xy * 2.0 - 1.0; + tangentSpaceNormal.xy = SampleTexture2D(texID, sampID, uv).xy * 2.0 - 1.0; tangentSpaceNormal.z = sqrt(max(1.0 - dot(tangentSpaceNormal.xy, tangentSpaceNormal.xy), 0.0)); tangentSpaceNormal.y *= invertNormal ? -1.0 : 1.0; @@ -45,24 +60,23 @@ vec3 EvaluateNormal(uint64_t textureMetadataBufferAddr, Material mat, vec2 uv, v vec2 EvaluateMetallicRoughness(uint64_t textureMetadataBufferAddr, Material mat, vec2 uv) { float metalness = mat.metalness; float roughness = mat.roughness; - - + if (HAS_METALNESS_TEX(mat)) { - uint meta = GET_TEXTURE_METADATA(textureMetadataBufferAddr, mat.metalnessTexture); - uint samplerID = UnpackTextureMetadataSampler(meta); - metalness *= SampleTexture2D(mat.metalnessTexture, samplerID, uv).r; + uint texID = UNPACK_TEXTURE_ID(mat.metalnessTexture); + uint sampID = ResolveSampler(textureMetadataBufferAddr, mat.metalnessTexture, texID); + metalness *= SampleTexture2D(texID, sampID, uv).r; } if (HAS_ROUGHNESS_TEX(mat)) { - uint meta = GET_TEXTURE_METADATA(textureMetadataBufferAddr, mat.roughnessTexture); - uint samplerID = UnpackTextureMetadataSampler(meta); - roughness *= SampleTexture2D(mat.roughnessTexture, samplerID, uv).r; + uint texID = UNPACK_TEXTURE_ID(mat.roughnessTexture); + uint sampID = ResolveSampler(textureMetadataBufferAddr, mat.roughnessTexture, texID); + roughness *= SampleTexture2D(texID, sampID, uv).r; } if (HAS_METALLIC_ROUGHNESS_TEX(mat)) { - uint meta = GET_TEXTURE_METADATA(textureMetadataBufferAddr, mat.metallicRoughnessTexture); - uint samplerID = UnpackTextureMetadataSampler(meta); - vec4 mrSample = SampleTexture2D(mat.metallicRoughnessTexture, samplerID, uv); + uint texID = UNPACK_TEXTURE_ID(mat.metallicRoughnessTexture); + uint sampID = ResolveSampler(textureMetadataBufferAddr, mat.metallicRoughnessTexture, texID); + vec4 mrSample = SampleTexture2D(texID, sampID, uv); roughness *= mrSample.g; metalness *= mrSample.b; } @@ -72,20 +86,22 @@ vec2 EvaluateMetallicRoughness(uint64_t textureMetadataBufferAddr, Material mat, vec3 EvaluateEmissive(uint64_t textureMetadataBufferAddr, Material mat, vec2 uv) { vec3 emissive = mat.emissiveColor * mat.emissiveIntensity; + if (HAS_EMISSIVE_TEX(mat)) { - uint meta = GET_TEXTURE_METADATA(textureMetadataBufferAddr, mat.emissiveTexture); - uint samplerID = UnpackTextureMetadataSampler(meta); - emissive *= SampleTexture2D(mat.emissiveTexture, samplerID, uv).rgb; + uint texID = UNPACK_TEXTURE_ID(mat.emissiveTexture); + uint sampID = ResolveSampler(textureMetadataBufferAddr, mat.emissiveTexture, texID); + emissive *= SampleTexture2D(texID, sampID, uv).rgb; } return emissive; } float EvaluateAO(uint64_t textureMetadataBufferAddr, Material mat, vec2 uv) { float ao = mat.aoStrength; + if (HAS_AO_TEX(mat)) { - uint meta = GET_TEXTURE_METADATA(textureMetadataBufferAddr, mat.ambientOcclusionTexture); - uint samplerID = UnpackTextureMetadataSampler(meta); - ao *= SampleTexture2D(mat.ambientOcclusionTexture, samplerID, uv).r; + uint texID = UNPACK_TEXTURE_ID(mat.ambientOcclusionTexture); + uint sampID = ResolveSampler(textureMetadataBufferAddr, mat.ambientOcclusionTexture, texID); + ao *= SampleTexture2D(texID, sampID, uv).r; } return ao; } diff --git a/SynapseEngine/synapse_stats.txt b/SynapseEngine/synapse_stats.txt index 6d164063..f94b50d7 100644 --- a/SynapseEngine/synapse_stats.txt +++ b/SynapseEngine/synapse_stats.txt @@ -1,10 +1,10 @@ -github.com/AlDanial/cloc v 2.08 T=7.97 s (187.4 files/s, 9899.4 lines/s) +github.com/AlDanial/cloc v 2.08 T=11.91 s (147.5 files/s, 7631.2 lines/s) ------------------------------------------------------------------------------- Language files blank comment code ------------------------------------------------------------------------------- -C++ 617 8277 702 36598 -C/C++ Header 706 3132 96 18645 -GLSL 170 2532 615 8277 +C++ 737 9727 717 42787 +C/C++ Header 840 3610 100 21477 +GLSL 180 2754 625 9120 ------------------------------------------------------------------------------- -SUM: 1493 13941 1413 63520 +SUM: 1757 16091 1442 73384 ------------------------------------------------------------------------------- From e92b7aee9b3f5c4330decf9eb2ff371033c3a76a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20P=C3=A9ter?= Date: Thu, 9 Jul 2026 16:03:30 +0200 Subject: [PATCH 38/51] Implemented material preview manager and preview texture rendering --- .../Editor/EditorApi/Impl/RenderApiImpl.cpp | 10 ++ .../Editor/View/Viewport/ViewportView.cpp | 2 + .../Engine/Manager/PreviewManager.cpp | 137 ++++++++++++++++++ SynapseEngine/Engine/Manager/PreviewManager.h | 55 +++++++ .../Engine/Manager/ResourceManager.cpp | 19 +++ .../Engine/Manager/ResourceManager.h | 4 + .../Engine/Material/MaterialManager.cpp | 20 ++- .../Engine/Material/MaterialManager.h | 10 +- SynapseEngine/Engine/Mesh/ModelManager.cpp | 13 +- SynapseEngine/Engine/Mesh/ModelManager.h | 10 +- SynapseEngine/Engine/Render/PassGroupNames.h | 1 + .../Passes/Preview/MaterialPreviewPass.cpp | 135 +++++++++++++++++ .../Passes/Preview/MaterialPreviewPass.h | 23 +++ .../Preview/PreviewPostTransitionPass.cpp | 18 +++ .../Preview/PreviewPostTransitionPass.h | 13 ++ .../Preview/PreviewPreTransitionPass.cpp | 18 +++ .../Passes/Preview/PreviewPreTransitionPass.h | 13 ++ SynapseEngine/Engine/Render/RenderNames.h | 2 + .../Engine/Render/RendererFactory.cpp | 9 ++ SynapseEngine/Engine/Render/ShaderNames.h | 2 + .../Schema/Material/MaterialSchema.h | 8 + SynapseEngine/Engine/ServiceLocator.cpp | 2 + SynapseEngine/Engine/ServiceLocator.h | 4 + .../Shaders/Includes/Common/Material.glsl | 13 +- .../PushConstants/MaterialPreviewPC.glsl | 11 ++ .../Passes/Preview/MaterialPreview.frag | 91 ++++++++++++ .../Passes/Preview/MaterialPreview.vert | 8 + .../Passes/Shading/Common/Meshlet.mesh | 2 +- .../Passes/Shading/Common/Traditional.vert | 2 +- 29 files changed, 639 insertions(+), 16 deletions(-) create mode 100644 SynapseEngine/Engine/Manager/PreviewManager.cpp create mode 100644 SynapseEngine/Engine/Manager/PreviewManager.h create mode 100644 SynapseEngine/Engine/Render/Passes/Preview/MaterialPreviewPass.cpp create mode 100644 SynapseEngine/Engine/Render/Passes/Preview/MaterialPreviewPass.h create mode 100644 SynapseEngine/Engine/Render/Passes/Preview/PreviewPostTransitionPass.cpp create mode 100644 SynapseEngine/Engine/Render/Passes/Preview/PreviewPostTransitionPass.h create mode 100644 SynapseEngine/Engine/Render/Passes/Preview/PreviewPreTransitionPass.cpp create mode 100644 SynapseEngine/Engine/Render/Passes/Preview/PreviewPreTransitionPass.h create mode 100644 SynapseEngine/Engine/Shaders/Includes/PushConstants/MaterialPreviewPC.glsl create mode 100644 SynapseEngine/Engine/Shaders/Passes/Preview/MaterialPreview.frag create mode 100644 SynapseEngine/Engine/Shaders/Passes/Preview/MaterialPreview.vert diff --git a/SynapseEngine/Editor/EditorApi/Impl/RenderApiImpl.cpp b/SynapseEngine/Editor/EditorApi/Impl/RenderApiImpl.cpp index 1ab549dd..7a34b57c 100644 --- a/SynapseEngine/Editor/EditorApi/Impl/RenderApiImpl.cpp +++ b/SynapseEngine/Editor/EditorApi/Impl/RenderApiImpl.cpp @@ -7,6 +7,7 @@ #include "Engine/Component/Core/CameraComponent.h" #include "Engine/Vk/Image/ImageUtils.h" #include "Engine/Vk/Rendering/GpuUploader.h" +#include "Engine/Manager/PreviewManager.h" #include namespace Syn { @@ -46,6 +47,15 @@ namespace Syn { ); _viewportTextures[cacheKey] = handle; } + else if (targetName == RenderTargetNames::PreviewAtlas) { + auto imageView = ServiceLocator::GetPreviewManager()->GetAtlasImage()->GetView(viewName); + auto sampler = ServiceLocator::GetImageManager()->GetSampler(SamplerNames::LinearClampEdge); + TextureHandle handle = _textureManager->RegisterTexture( + imageView, + sampler->Handle() + ); + _viewportTextures[cacheKey] = handle; + } else { auto rtManager = renderManager->GetRenderTargetManager(); auto group = rtManager->GetGroup(groupName, currentFrame); diff --git a/SynapseEngine/Editor/View/Viewport/ViewportView.cpp b/SynapseEngine/Editor/View/Viewport/ViewportView.cpp index 06c6fa73..6121870b 100644 --- a/SynapseEngine/Editor/View/Viewport/ViewportView.cpp +++ b/SynapseEngine/Editor/View/Viewport/ViewportView.cpp @@ -383,6 +383,8 @@ namespace Syn { } ImGui::Unindent(); } + + RadioButton("PreviewAtlas", RenderTargetGroupNames::Main, RenderTargetNames::PreviewAtlas, Vk::ImageViewNames::Default); } ImGui::EndChild(); ImGui::EndPopup(); diff --git a/SynapseEngine/Engine/Manager/PreviewManager.cpp b/SynapseEngine/Engine/Manager/PreviewManager.cpp new file mode 100644 index 00000000..ca38eedd --- /dev/null +++ b/SynapseEngine/Engine/Manager/PreviewManager.cpp @@ -0,0 +1,137 @@ +#include "PreviewManager.h" +#include "Engine/Logger/SynLog.h" +#include "Engine/Vk/Image/ImageViewNames.h" + +namespace Syn { + + PreviewManager::PreviewManager(uint32_t initialResolution, uint32_t tileSize) + : _resolution(initialResolution), _tileSize(tileSize), _tilesPerRow(0) + { + CreateOrResizeAtlas(_resolution); + } + + uint64_t PreviewManager::GetUniqueId(PreviewResourceType type, uint32_t resourceId) const { + return (static_cast(type) << 32) | resourceId; + } + + void PreviewManager::CreateOrResizeAtlas(uint32_t newResolution) { + uint32_t oldTotalTiles = _tilesPerRow * _tilesPerRow; + + _resolution = newResolution; + _tilesPerRow = _resolution / _tileSize; + uint32_t newTotalTiles = _tilesPerRow * _tilesPerRow; + + Vk::ImageConfig config{}; + config.width = _resolution; + config.height = _resolution; + config.type = VK_IMAGE_TYPE_2D; + config.format = VK_FORMAT_R16G16B16A16_SFLOAT; + config.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; + config.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + config.AddView(Vk::ImageViewNames::Default, { .viewType = VK_IMAGE_VIEW_TYPE_2D }); + + _atlasImage = std::make_unique(config); + + for (uint32_t i = oldTotalTiles; i < newTotalTiles; ++i) { + _freeTiles.push(i); + } + + for (const auto& [id, tileIndex] : _resourceToTile) { + PreviewResourceType type = static_cast(id >> 32); + uint32_t resourceId = static_cast(id & 0xFFFFFFFF); + _dirtyResources[type].insert(resourceId); + } + + Info("PreviewManager: Atlas resized to {}x{}", _resolution, _resolution); + } + + bool PreviewManager::AllocateTile(PreviewResourceType type, uint32_t resourceId) { + std::lock_guard lock(_mutex); + uint64_t id = GetUniqueId(type, resourceId); + + if (_resourceToTile.find(id) != _resourceToTile.end()) return true; + + if (_freeTiles.empty()) { + CreateOrResizeAtlas(_resolution * 2); + } + + uint32_t tileIndex = _freeTiles.front(); + _freeTiles.pop(); + + _resourceToTile[id] = tileIndex; + return true; + } + + void PreviewManager::FreeTile(PreviewResourceType type, uint32_t resourceId) { + std::lock_guard lock(_mutex); + uint64_t id = GetUniqueId(type, resourceId); + auto it = _resourceToTile.find(id); + + if (it != _resourceToTile.end()) { + _freeTiles.push(it->second); + _resourceToTile.erase(it); + _dirtyResources[type].erase(resourceId); + } + } + + bool PreviewManager::HasTile(PreviewResourceType type, uint32_t resourceId) const { + std::lock_guard lock(_mutex); + return _resourceToTile.find(GetUniqueId(type, resourceId)) != _resourceToTile.end(); + } + + void PreviewManager::MarkDirty(PreviewResourceType type, uint32_t resourceId) { + std::lock_guard lock(_mutex); + _dirtyResources[type].insert(resourceId); + } + + std::vector PreviewManager::ConsumeDirtyResources(PreviewResourceType type) { + std::lock_guard lock(_mutex); + auto& dirtySet = _dirtyResources[type]; + + std::vector result(dirtySet.begin(), dirtySet.end()); + dirtySet.clear(); + + return result; + } + + void PreviewManager::GetViewportAndScissor(PreviewResourceType type, uint32_t resourceId, VkViewport& outViewport, VkRect2D& outScissor) const { + std::lock_guard lock(_mutex); + auto it = _resourceToTile.find(GetUniqueId(type, resourceId)); + if (it == _resourceToTile.end()) return; + + uint32_t tileIndex = it->second; + uint32_t col = tileIndex % _tilesPerRow; + uint32_t row = tileIndex / _tilesPerRow; + + float x = static_cast(col * _tileSize); + float y = static_cast(row * _tileSize); + + outViewport.x = x; + outViewport.y = y; + outViewport.width = static_cast(_tileSize); + outViewport.height = static_cast(_tileSize); + outViewport.minDepth = 0.0f; + outViewport.maxDepth = 1.0f; + + outScissor.offset = { static_cast(x), static_cast(y) }; + outScissor.extent = { _tileSize, _tileSize }; + } + + void PreviewManager::GetNormalizedUVs(PreviewResourceType type, uint32_t resourceId, glm::vec2& outUv0, glm::vec2& outUv1) const { + std::lock_guard lock(_mutex); + auto it = _resourceToTile.find(GetUniqueId(type, resourceId)); + if (it == _resourceToTile.end()) { + outUv0 = glm::vec2(0.0f); outUv1 = glm::vec2(1.0f); + return; + } + + uint32_t tileIndex = it->second; + uint32_t col = tileIndex % _tilesPerRow; + uint32_t row = tileIndex / _tilesPerRow; + + float normalizedTileSize = 1.0f / static_cast(_tilesPerRow); + + outUv0 = glm::vec2(col * normalizedTileSize, row * normalizedTileSize); + outUv1 = glm::vec2((col + 1) * normalizedTileSize, (row + 1) * normalizedTileSize); + } +} \ No newline at end of file diff --git a/SynapseEngine/Engine/Manager/PreviewManager.h b/SynapseEngine/Engine/Manager/PreviewManager.h new file mode 100644 index 00000000..c7a5ca7c --- /dev/null +++ b/SynapseEngine/Engine/Manager/PreviewManager.h @@ -0,0 +1,55 @@ +#pragma once +#include "Engine/SynApi.h" +#include "Engine/Vk/Image/Image.h" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Syn { + + enum class PreviewResourceType : uint32_t { + Material = 0, + Model = 1, + Image = 2 + }; + + class SYN_API PreviewManager { + public: + using AtlasResizedCallback = std::function; + + PreviewManager(uint32_t initialResolution = 4096, uint32_t tileSize = 256); + virtual ~PreviewManager() = default; + + bool AllocateTile(PreviewResourceType type, uint32_t resourceId); + void FreeTile(PreviewResourceType type, uint32_t resourceId); + bool HasTile(PreviewResourceType type, uint32_t resourceId) const; + + void MarkDirty(PreviewResourceType type, uint32_t resourceId); + std::vector ConsumeDirtyResources(PreviewResourceType type); + + void GetViewportAndScissor(PreviewResourceType type, uint32_t resourceId, VkViewport& outViewport, VkRect2D& outScissor) const; + void GetNormalizedUVs(PreviewResourceType type, uint32_t resourceId, glm::vec2& outUv0, glm::vec2& outUv1) const; + + Vk::Image* GetAtlasImage() const { return _atlasImage.get(); } + private: + void CreateOrResizeAtlas(uint32_t newResolution); + uint64_t GetUniqueId(PreviewResourceType type, uint32_t resourceId) const; + protected: + mutable std::mutex _mutex; + + uint32_t _resolution; + uint32_t _tileSize; + uint32_t _tilesPerRow; + + std::unique_ptr _atlasImage; + + std::queue _freeTiles; + std::unordered_map _resourceToTile; + std::unordered_map> _dirtyResources; + }; +} \ No newline at end of file diff --git a/SynapseEngine/Engine/Manager/ResourceManager.cpp b/SynapseEngine/Engine/Manager/ResourceManager.cpp index c6746f8b..95a2a411 100644 --- a/SynapseEngine/Engine/Manager/ResourceManager.cpp +++ b/SynapseEngine/Engine/Manager/ResourceManager.cpp @@ -46,12 +46,18 @@ namespace Syn { ResourceManager::ResourceManager(uint32_t framesInFlight) : _framesInFlight(framesInFlight) { InitShaderManager(); + InitPreviewManager(); InitImageManager(); InitMaterialManager(); InitModelManager(); InitAnimationManager(); } + void ResourceManager::InitPreviewManager() { + _previewManager = std::make_unique(1024, 64); + ServiceLocator::ProvidePreviewManager(_previewManager.get()); + } + void ResourceManager::InitShaderManager() { _shaderManager = std::make_unique(); @@ -108,6 +114,12 @@ namespace Syn { else { return _imageManager->LoadImageAsync(payload.path); } + }, + [this](uint32_t id) { + if (_previewManager) _previewManager->AllocateTile(PreviewResourceType::Material, id); + }, + [this](uint32_t id) { + if (_previewManager) _previewManager->MarkDirty(PreviewResourceType::Material, id); } ); @@ -146,6 +158,12 @@ namespace Syn { std::make_unique(), [this](const std::string& name, const MaterialInfo& info) -> uint32_t { return _materialManager->LoadMaterial(name, info); + }, + [this](uint32_t id) { + if (_previewManager) _previewManager->AllocateTile(PreviewResourceType::Model, id); + }, + [this](uint32_t id) { + if (_previewManager) _previewManager->MarkDirty(PreviewResourceType::Model, id); } ); @@ -203,5 +221,6 @@ namespace Syn { ServiceLocator::ProvideImageBuilder(nullptr); ServiceLocator::ProvideImageManager(nullptr); ServiceLocator::ProvideMaterialManager(nullptr); + } } \ No newline at end of file diff --git a/SynapseEngine/Engine/Manager/ResourceManager.h b/SynapseEngine/Engine/Manager/ResourceManager.h index 0caec36c..2efbad9b 100644 --- a/SynapseEngine/Engine/Manager/ResourceManager.h +++ b/SynapseEngine/Engine/Manager/ResourceManager.h @@ -6,6 +6,7 @@ #include "Engine/Material/MaterialManager.h" #include "Engine/Animation/Builder/AnimationBuilder.h" #include "Engine/Animation/AnimationManager.h" +#include "PreviewManager.h" #include namespace Syn { @@ -23,6 +24,7 @@ namespace Syn { void InitImageManager(); void InitMaterialManager(); void InitAnimationManager(); + void InitPreviewManager(); private: std::unique_ptr _shaderManager; std::unique_ptr _materialManager; @@ -36,6 +38,8 @@ namespace Syn { std::shared_ptr _animationBuilder; std::unique_ptr _animationManager; + std::unique_ptr _previewManager; + uint32_t _framesInFlight; }; } \ No newline at end of file diff --git a/SynapseEngine/Engine/Material/MaterialManager.cpp b/SynapseEngine/Engine/Material/MaterialManager.cpp index 9bd71233..6e8a206d 100644 --- a/SynapseEngine/Engine/Material/MaterialManager.cpp +++ b/SynapseEngine/Engine/Material/MaterialManager.cpp @@ -5,9 +5,15 @@ namespace Syn { - MaterialManager::MaterialManager(uint32_t framesInFlight, TextureLoadCallback textureLoadCallback) + MaterialManager::MaterialManager(uint32_t framesInFlight, + TextureLoadCallback textureLoadCallback, + PreviewAllocateCallback previewAllocateCallback, + PreviewMarkDirtyCallback previewMarkDirtyCallback + ) : AddressResourceManager(framesInFlight, 1024, 1024, 2048) , _textureLoadCallback(std::move(textureLoadCallback)) + , _previewAllocateCallback(std::move(previewAllocateCallback)) + , _previewMarkDirtyCallback(std::move(previewMarkDirtyCallback)) { Material emptyMat; WriteAddress(0, GpuMaterial(emptyMat)); @@ -55,6 +61,8 @@ namespace Syn { void MaterialManager::StartGpuUpload(EntryType& entry) { uint32_t entryIndex = _pathToId.at(entry.path); + FinalizeResource(entry); + entry.state = ResourceState::Ready; MarkDirty(entryIndex); @@ -68,13 +76,19 @@ namespace Syn { } void MaterialManager::FinalizeResource(EntryType& entry) { + uint32_t index = _pathToId.at(entry.path); + if (_previewAllocateCallback) _previewAllocateCallback(index); + if (_previewMarkDirtyCallback) _previewMarkDirtyCallback(index); } void MaterialManager::FlushDirtyResources() { ProcessDirtyReadyEntries( [this](uint32_t index, const EntryType& entry) { - GpuMaterial gpuMat(*entry.resource); - WriteAddress(index, gpuMat); + WriteAddress(index, GpuMaterial(*entry.resource)); + + if (_previewMarkDirtyCallback) { + _previewMarkDirtyCallback(index); + } } ); } diff --git a/SynapseEngine/Engine/Material/MaterialManager.h b/SynapseEngine/Engine/Material/MaterialManager.h index c6958911..209dfd67 100644 --- a/SynapseEngine/Engine/Material/MaterialManager.h +++ b/SynapseEngine/Engine/Material/MaterialManager.h @@ -8,10 +8,16 @@ namespace Syn { using TextureLoadCallback = std::function; + using PreviewAllocateCallback = std::function; + using PreviewMarkDirtyCallback = std::function; class SYN_API MaterialManager : public AddressResourceManager { public: - MaterialManager(uint32_t framesInFlight, TextureLoadCallback textureLoadCallback); + MaterialManager(uint32_t framesInFlight, + TextureLoadCallback textureLoadCallback, + PreviewAllocateCallback previewAllocateCallback = nullptr, + PreviewMarkDirtyCallback previewMarkDirtyCallback = nullptr + ); ~MaterialManager() = default; uint32_t LoadMaterial(const std::string& name, const MaterialInfo& info); @@ -24,5 +30,7 @@ namespace Syn { void LoadDefaultMaterialSync(); private: TextureLoadCallback _textureLoadCallback; + PreviewAllocateCallback _previewAllocateCallback; + PreviewMarkDirtyCallback _previewMarkDirtyCallback; }; } \ No newline at end of file diff --git a/SynapseEngine/Engine/Mesh/ModelManager.cpp b/SynapseEngine/Engine/Mesh/ModelManager.cpp index be8b6ca4..defded92 100644 --- a/SynapseEngine/Engine/Mesh/ModelManager.cpp +++ b/SynapseEngine/Engine/Mesh/ModelManager.cpp @@ -15,11 +15,16 @@ namespace Syn { ModelManager::ModelManager(uint32_t framesInFlight, std::shared_ptr builder, std::unique_ptr uploader, - MaterialLoadCallback materialLoadCallback) + MaterialLoadCallback materialLoadCallback, + PreviewAllocateCallback previewAllocateCallback, + PreviewMarkDirtyCallback previewMarkDirtyCallback + ) : AddressResourceManager(framesInFlight, 1024, 256, 512), _builder(builder), _uploader(std::move(uploader)), - _materialLoadCallback(std::move(materialLoadCallback)) + _materialLoadCallback(std::move(materialLoadCallback)), + _previewAllocateCallback(std::move(previewAllocateCallback)), + _previewMarkDirtyCallback(std::move(previewMarkDirtyCallback)) { } @@ -154,6 +159,8 @@ namespace Syn { void ModelManager::FinalizeResource(EntryType& entry) { + uint32_t index = _pathToId.at(entry.path); + if (_previewAllocateCallback) _previewAllocateCallback(index); entry.resource->transientGpuData.reset(); entry.resource->transientCpuData.reset(); } @@ -186,6 +193,8 @@ namespace Syn { addresses.isReady = 1; WriteAddress(index, addresses); + + if (_previewMarkDirtyCallback) _previewMarkDirtyCallback(index); }); } } \ No newline at end of file diff --git a/SynapseEngine/Engine/Mesh/ModelManager.h b/SynapseEngine/Engine/Mesh/ModelManager.h index 61e91fa7..ac9eed1c 100644 --- a/SynapseEngine/Engine/Mesh/ModelManager.h +++ b/SynapseEngine/Engine/Mesh/ModelManager.h @@ -14,13 +14,19 @@ namespace Syn { using MaterialLoadCallback = std::function; using MeshSourceFactory = std::function()>; using StaticMeshFactory = std::function()>; + using PreviewAllocateCallback = std::function; + using PreviewMarkDirtyCallback = std::function; class SYN_API ModelManager : public AddressResourceManager { public: ModelManager(uint32_t framesInFlight, std::shared_ptr builder, std::unique_ptr uploader, - MaterialLoadCallback materialLoadCallback = nullptr); + MaterialLoadCallback materialLoadCallback = nullptr, + PreviewAllocateCallback previewAllocateCallback = nullptr, + PreviewMarkDirtyCallback previewMarkDirtyCallback = nullptr + ); + ~ModelManager() = default; @@ -37,6 +43,8 @@ namespace Syn { void FinalizeResource(EntryType& entry) override; private: MaterialLoadCallback _materialLoadCallback; + PreviewAllocateCallback _previewAllocateCallback; + PreviewMarkDirtyCallback _previewMarkDirtyCallback; std::shared_ptr _builder; std::unique_ptr _uploader; }; diff --git a/SynapseEngine/Engine/Render/PassGroupNames.h b/SynapseEngine/Engine/Render/PassGroupNames.h index fc4a7d09..f6a8a726 100644 --- a/SynapseEngine/Engine/Render/PassGroupNames.h +++ b/SynapseEngine/Engine/Render/PassGroupNames.h @@ -29,5 +29,6 @@ namespace Syn static constexpr const char* SsaoPasses = "SsaoPasses"; static constexpr const char* ShadowPasses = "ShadowPasses"; static constexpr const char* PostProcessPasses = "PostProcessPasses"; + static constexpr const char* UtilityPasses = "UtilityPasses"; }; } \ No newline at end of file diff --git a/SynapseEngine/Engine/Render/Passes/Preview/MaterialPreviewPass.cpp b/SynapseEngine/Engine/Render/Passes/Preview/MaterialPreviewPass.cpp new file mode 100644 index 00000000..d7773844 --- /dev/null +++ b/SynapseEngine/Engine/Render/Passes/Preview/MaterialPreviewPass.cpp @@ -0,0 +1,135 @@ +#include "MaterialPreviewPass.h" +#include "Engine/ServiceLocator.h" +#include "Engine/Manager/ShaderManager.h" +#include "Engine/Image/ImageManager.h" +#include "Engine/Scene/Scene.h" +#include "Engine/Mesh/ModelManager.h" +#include "Engine/Mesh/Source/MeshSources.h" +#include "Engine/Vk/Image/ImageViewNames.h" +#include "Engine/Manager/PreviewManager.h" +#include "Engine/Vk/Rendering/PushConstant.h" +#include "Engine/Mesh/MeshSourceNames.h" +#include +#include + +namespace Syn { + + #include "Engine/Shaders/Includes/PushConstants/MaterialPreviewPC.glsl" + + void MaterialPreviewPass::Initialize() { + auto shaderManager = ServiceLocator::GetShaderManager(); + auto imageManager = ServiceLocator::GetImageManager(); + + Vk::ShaderProgramConfig config; + config.useDescriptorBuffers = true; + config.layoutOverride = [imageManager](uint32_t setIndex) { + if (setIndex == 0) return imageManager->GetBindlessLayout(); + return VkDescriptorSetLayout{}; + }; + + _shaderProgram = shaderManager->CreateProgram("MaterialPreviewProgram", { + ShaderNames::MaterialPreviewVert, + ShaderNames::MaterialPreviewFrag + }, config); + + _graphicsState = { + .raster = { + .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, + .cullMode = VK_CULL_MODE_NONE, + .frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE, + .polygonMode = VK_POLYGON_MODE_FILL, + .lineWidth = 1.0f + }, + .depth = { + .testEnable = VK_FALSE, + .writeEnable = VK_FALSE, + .compareOp = VK_COMPARE_OP_ALWAYS + }, + .blendStates = { + { + .enable = VK_FALSE, + .srcColorFactor = VK_BLEND_FACTOR_SRC_ALPHA, + .dstColorFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, + .colorBlendOp = VK_BLEND_OP_ADD, + .srcAlphaFactor = VK_BLEND_FACTOR_ONE, + .dstAlphaFactor = VK_BLEND_FACTOR_ZERO, + .alphaBlendOp = VK_BLEND_OP_ADD + } + }, + .colorAttachmentCount = 1, + .renderArea = std::nullopt + }; + } + + void MaterialPreviewPass::PrepareFrame(const RenderContext& context) { + _dirtyMaterials.clear(); + _renderInfo.reset(); + + auto pm = ServiceLocator::GetPreviewManager(); + auto modelManager = ServiceLocator::GetModelManager(); + + _dirtyMaterials = pm->ConsumeDirtyResources(PreviewResourceType::Material); + if (_dirtyMaterials.empty()) return; + + auto atlas = pm->GetAtlasImage(); + VkExtent2D extent = { atlas->GetExtent().width, atlas->GetExtent().height }; + _graphicsState.renderArea = std::nullopt; + + _colorAttachments.push_back(Vk::RenderUtils::CreateAttachment({ + .imageView = atlas->GetView(Vk::ImageViewNames::Default), + .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + .loadOp = VK_ATTACHMENT_LOAD_OP_LOAD, + .storeOp = VK_ATTACHMENT_STORE_OP_STORE + })); + + _renderInfo = Vk::RenderingInfoConfig{ + .renderArea = extent, + .colorAttachments = _colorAttachments, + .layerCount = 1 + }; + } + + void MaterialPreviewPass::BindDescriptors(const RenderContext& context) { + if (_dirtyMaterials.empty()) return; + + auto imageManager = ServiceLocator::GetImageManager(); + auto bindlessBuffer = imageManager->GetBindlessBuffer(); + bindlessBuffer->Bind(context.cmd, _shaderProgram->GetLayout(), 0, VK_PIPELINE_BIND_POINT_GRAPHICS); + } + + void MaterialPreviewPass::Draw(const RenderContext& context) { + if (_dirtyMaterials.empty() || !_renderInfo.has_value()) return; + + auto pm = ServiceLocator::GetPreviewManager(); + auto modelManager = ServiceLocator::GetModelManager(); + + Vk::PushConstant pc; + pc->frameGlobalContextBufferAddr = context.scene->GetSceneDrawData()->frameContextBuffer.GetAddress(context.frameIndex); + + for (uint32_t matId : _dirtyMaterials) { + VkViewport viewport{}; + VkRect2D scissor{}; + pm->GetViewportAndScissor(PreviewResourceType::Material, matId, viewport, scissor); + + vkCmdSetViewportWithCount(context.cmd, 1, &viewport); + vkCmdSetScissorWithCount(context.cmd, 1, &scissor); + + VkClearAttachment clearAttachment{}; + clearAttachment.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + clearAttachment.colorAttachment = 0; + clearAttachment.clearValue.color = { {0.15f, 0.15f, 0.15f, 1.0f} }; + + VkClearRect clearRect{}; + clearRect.rect = scissor; + clearRect.baseArrayLayer = 0; + clearRect.layerCount = 1; + + vkCmdClearAttachments(context.cmd, 1, &clearAttachment, 1, &clearRect); + + pc->materialId = matId; + pc.Push(context.cmd, _shaderProgram->GetLayout()); + + vkCmdDraw(context.cmd, 3, 1, 0, 0); + } + } +} \ No newline at end of file diff --git a/SynapseEngine/Engine/Render/Passes/Preview/MaterialPreviewPass.h b/SynapseEngine/Engine/Render/Passes/Preview/MaterialPreviewPass.h new file mode 100644 index 00000000..cfc0c76b --- /dev/null +++ b/SynapseEngine/Engine/Render/Passes/Preview/MaterialPreviewPass.h @@ -0,0 +1,23 @@ +#pragma once +#include "Engine/SynApi.h" +#include "Engine/Render/Passes/GraphicsPass.h" +#include + +namespace Syn { + class SYN_API MaterialPreviewPass : public GraphicsPass { + public: + MaterialPreviewPass() = default; + + std::string GetName() const override { return "MaterialPreviewPass"; } + std::string GetGroup() const override { return PassGroupNames::UtilityPasses; } + + void Initialize() override; + bool ShouldCollectStatistics() const override { return false; } + protected: + void PrepareFrame(const RenderContext& context) override; + void BindDescriptors(const RenderContext& context) override; + void Draw(const RenderContext& context) override; + private: + std::vector _dirtyMaterials; + }; +} \ No newline at end of file diff --git a/SynapseEngine/Engine/Render/Passes/Preview/PreviewPostTransitionPass.cpp b/SynapseEngine/Engine/Render/Passes/Preview/PreviewPostTransitionPass.cpp new file mode 100644 index 00000000..d8f5c8db --- /dev/null +++ b/SynapseEngine/Engine/Render/Passes/Preview/PreviewPostTransitionPass.cpp @@ -0,0 +1,18 @@ +#include "PreviewPostTransitionPass.h" +#include "Engine/ServiceLocator.h" +#include "Engine/Manager/PreviewManager.h" + +namespace Syn { + + void PreviewPostTransitionPass::PrepareFrame(const RenderContext& context) { + auto pm = ServiceLocator::GetPreviewManager(); + + _imageTransitions.push_back({ + .image = pm->GetAtlasImage(), + .newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + .dstStage = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT, + .dstAccess = VK_ACCESS_2_SHADER_READ_BIT, + .discardContent = false + }); + } +} \ No newline at end of file diff --git a/SynapseEngine/Engine/Render/Passes/Preview/PreviewPostTransitionPass.h b/SynapseEngine/Engine/Render/Passes/Preview/PreviewPostTransitionPass.h new file mode 100644 index 00000000..8acaefa4 --- /dev/null +++ b/SynapseEngine/Engine/Render/Passes/Preview/PreviewPostTransitionPass.h @@ -0,0 +1,13 @@ +#pragma once +#include "Engine/SynApi.h" +#include "Engine/Render/Passes/TransitionPass.h" + +namespace Syn { + class SYN_API PreviewPostTransitionPass : public TransitionPass { + public: + std::string GetName() const override { return "PreviewPostTransitionPass"; } + std::string GetGroup() const override { return PassGroupNames::UtilityPasses; } + protected: + void PrepareFrame(const RenderContext& context) override; + }; +} \ No newline at end of file diff --git a/SynapseEngine/Engine/Render/Passes/Preview/PreviewPreTransitionPass.cpp b/SynapseEngine/Engine/Render/Passes/Preview/PreviewPreTransitionPass.cpp new file mode 100644 index 00000000..fc2f6c1b --- /dev/null +++ b/SynapseEngine/Engine/Render/Passes/Preview/PreviewPreTransitionPass.cpp @@ -0,0 +1,18 @@ +#include "PreviewPreTransitionPass.h" +#include "Engine/ServiceLocator.h" +#include "Engine/Manager/PreviewManager.h" + +namespace Syn { + + void PreviewPreTransitionPass::PrepareFrame(const RenderContext& context) { + auto pm = ServiceLocator::GetPreviewManager(); + + _imageTransitions.push_back({ + .image = pm->GetAtlasImage(), + .newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + .dstStage = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, + .dstAccess = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT, + .discardContent = false + }); + } +} \ No newline at end of file diff --git a/SynapseEngine/Engine/Render/Passes/Preview/PreviewPreTransitionPass.h b/SynapseEngine/Engine/Render/Passes/Preview/PreviewPreTransitionPass.h new file mode 100644 index 00000000..552a70c9 --- /dev/null +++ b/SynapseEngine/Engine/Render/Passes/Preview/PreviewPreTransitionPass.h @@ -0,0 +1,13 @@ +#pragma once +#include "Engine/SynApi.h" +#include "Engine/Render/Passes/TransitionPass.h" + +namespace Syn { + class SYN_API PreviewPreTransitionPass : public TransitionPass { + public: + std::string GetName() const override { return "PreviewPreTransitionPass"; } + std::string GetGroup() const override { return PassGroupNames::UtilityPasses; } + protected: + void PrepareFrame(const RenderContext& context) override; + }; +} \ No newline at end of file diff --git a/SynapseEngine/Engine/Render/RenderNames.h b/SynapseEngine/Engine/Render/RenderNames.h index 447ed3b9..616576e3 100644 --- a/SynapseEngine/Engine/Render/RenderNames.h +++ b/SynapseEngine/Engine/Render/RenderNames.h @@ -38,6 +38,8 @@ namespace Syn static constexpr const char* PointLightShadowAtlas = "PointLightShadowAtlas"; static constexpr const char* PointLightShadowDepthPyramid = "PointLightShadowDepthPyramid"; + + static constexpr const char* PreviewAtlas = "PreviewAtlas"; }; struct SYN_API RenderTargetViewNames diff --git a/SynapseEngine/Engine/Render/RendererFactory.cpp b/SynapseEngine/Engine/Render/RendererFactory.cpp index da9de28c..604affac 100644 --- a/SynapseEngine/Engine/Render/RendererFactory.cpp +++ b/SynapseEngine/Engine/Render/RendererFactory.cpp @@ -14,6 +14,10 @@ #include "Engine/Render/Passes/PostProcess/Outline/SelectionOutlinePass.h" #include "Engine/Render/Passes/PostProcess/SkySphere/SkySpherePass.h" +#include "Engine/Render/Passes/Preview/PreviewPreTransitionPass.h" +#include "Engine/Render/Passes/Preview/PreviewPostTransitionPass.h" +#include "Engine/Render/Passes/Preview/MaterialPreviewPass.h" + #include "Engine/Render/Passes/Culling/Geometry/GeometryModelCullingPass.h" #include "Engine/Render/Passes/Culling/Geometry/GeometryStaticModelCullingPass.h" #include "Engine/Render/Passes/Culling/Geometry/GeometryStaticChunkCullingPass.h" @@ -385,6 +389,11 @@ namespace Syn //Debug Visibility Pass pipeline->AddPass(std::make_unique()); + //Preview Passes + pipeline->AddPass(std::make_unique()); + pipeline->AddPass(std::make_unique()); + pipeline->AddPass(std::make_unique()); + //Gui and Present Passes pipeline->AddPass(std::make_unique()); pipeline->AddPass(std::make_unique()); diff --git a/SynapseEngine/Engine/Render/ShaderNames.h b/SynapseEngine/Engine/Render/ShaderNames.h index 40cec398..536cdc7f 100644 --- a/SynapseEngine/Engine/Render/ShaderNames.h +++ b/SynapseEngine/Engine/Render/ShaderNames.h @@ -147,5 +147,7 @@ namespace Syn static constexpr const char* PointLightShadowStaticChunkCullingComp = "Engine/Shaders/Passes/Culling/PointLight/PointLightShadowStaticChunkCulling.comp"; static constexpr const char* PointLightShadowStaticModelCullingComp = "Engine/Shaders/Passes/Culling/PointLight/PointLightShadowStaticModelCulling.comp"; + static constexpr const char* MaterialPreviewVert = "Engine/Shaders/Passes/Preview/MaterialPreview.vert"; + static constexpr const char* MaterialPreviewFrag = "Engine/Shaders/Passes/Preview/MaterialPreview.frag"; }; } \ No newline at end of file diff --git a/SynapseEngine/Engine/Serialization/Schema/Material/MaterialSchema.h b/SynapseEngine/Engine/Serialization/Schema/Material/MaterialSchema.h index 77a06a13..a917f54b 100644 --- a/SynapseEngine/Engine/Serialization/Schema/Material/MaterialSchema.h +++ b/SynapseEngine/Engine/Serialization/Schema/Material/MaterialSchema.h @@ -32,6 +32,14 @@ namespace Syn ar.Property("metallicRoughnessTexture", mat.metallicRoughnessTexture); ar.Property("emissiveTexture", mat.emissiveTexture); ar.Property("ambientOcclusionTexture", mat.ambientOcclusionTexture); + + ar.Property("albedoSampler", mat.albedoSampler); + ar.Property("normalSampler", mat.normalSampler); + ar.Property("metalnessSampler", mat.metalnessSampler); + ar.Property("roughnessSampler", mat.roughnessSampler); + ar.Property("metallicRoughnessSampler", mat.metallicRoughnessSampler); + ar.Property("emissiveSampler", mat.emissiveSampler); + ar.Property("ambientOcclusionSampler", mat.ambientOcclusionSampler); } }; } \ No newline at end of file diff --git a/SynapseEngine/Engine/ServiceLocator.cpp b/SynapseEngine/Engine/ServiceLocator.cpp index 331a06fc..0f07b0d5 100644 --- a/SynapseEngine/Engine/ServiceLocator.cpp +++ b/SynapseEngine/Engine/ServiceLocator.cpp @@ -22,6 +22,7 @@ namespace Syn { PhysicsFactory ServiceLocator::_physicsFactory = nullptr; IRenderStatCollector* ServiceLocator::_renderStatCollector = nullptr; FrameStatisticsManager* ServiceLocator::_frameStatisticsManager = nullptr; + PreviewManager* ServiceLocator::_previewManager = nullptr; void ServiceLocator::Shutdown() { @@ -45,5 +46,6 @@ namespace Syn { _serializer = nullptr; _renderStatCollector = nullptr; _frameStatisticsManager = nullptr; + _previewManager = nullptr; } } \ No newline at end of file diff --git a/SynapseEngine/Engine/ServiceLocator.h b/SynapseEngine/Engine/ServiceLocator.h index 8450e06b..97d573d6 100644 --- a/SynapseEngine/Engine/ServiceLocator.h +++ b/SynapseEngine/Engine/ServiceLocator.h @@ -30,6 +30,7 @@ namespace Syn { class Serializer; class IRenderStatCollector; class FrameStatisticsManager; + class PreviewManager; using PhysicsFactory = std::function()>; } @@ -107,6 +108,8 @@ namespace Syn static void ProvideFrameStatisticsManager(FrameStatisticsManager* manager) { _frameStatisticsManager = manager; } static FrameStatisticsManager* GetFrameStatisticsManager() { return _frameStatisticsManager; } + static void ProvidePreviewManager(PreviewManager* manager) { _previewManager = manager; } + static PreviewManager* GetPreviewManager() { return _previewManager; } private: static Vk::Context* _vkContext; static Vk::GpuUploader* _gpuUploader; @@ -129,5 +132,6 @@ namespace Syn static PhysicsFactory _physicsFactory; static IRenderStatCollector* _renderStatCollector; static FrameStatisticsManager* _frameStatisticsManager; + static PreviewManager* _previewManager; }; } \ No newline at end of file diff --git a/SynapseEngine/Engine/Shaders/Includes/Common/Material.glsl b/SynapseEngine/Engine/Shaders/Includes/Common/Material.glsl index 9d650a7b..98892944 100644 --- a/SynapseEngine/Engine/Shaders/Includes/Common/Material.glsl +++ b/SynapseEngine/Engine/Shaders/Includes/Common/Material.glsl @@ -3,18 +3,16 @@ #include "../Core.glsl" -#define INVALID_SAMPLER_INDEX 0xFF - struct Material { vec4 color; vec3 emissiveColor; float emissiveIntensity; vec2 uvScale; float metalness; - float roughness; - float aoStrength; + float roughness; + float aoStrength; uint packedFlags; - uint albedoTexture; //(8 bit sampler | 24 bit texture) + uint albedoTexture; uint normalTexture; uint metalnessTexture; uint roughnessTexture; @@ -29,6 +27,8 @@ struct Material { layout(buffer_reference, std430) readonly restrict buffer MaterialBuffer { Material data[]; }; layout(buffer_reference, std430) readonly restrict buffer MaterialLookupBuffer { uint data[]; }; +#define INVALID_SAMPLER_INDEX 0xFF + #define GET_MATERIAL(addr, idx) MaterialBuffer(addr).data[idx] #define GET_MATERIAL_INDEX(addr, idx) MaterialLookupBuffer(addr).data[idx] @@ -48,5 +48,4 @@ layout(buffer_reference, std430) readonly restrict buffer MaterialLookupBuffer { #define HAS_EMISSIVE_TEX(mat) HAS_VALID_TEXTURE((mat).emissiveTexture) #define HAS_AO_TEX(mat) HAS_VALID_TEXTURE((mat).ambientOcclusionTexture) -#endif - +#endif \ No newline at end of file diff --git a/SynapseEngine/Engine/Shaders/Includes/PushConstants/MaterialPreviewPC.glsl b/SynapseEngine/Engine/Shaders/Includes/PushConstants/MaterialPreviewPC.glsl new file mode 100644 index 00000000..8f980c18 --- /dev/null +++ b/SynapseEngine/Engine/Shaders/Includes/PushConstants/MaterialPreviewPC.glsl @@ -0,0 +1,11 @@ +#ifndef SYN_INCLUDES_PC_MATERIAL_PREVIEW_GLSL +#define SYN_INCLUDES_PC_MATERIAL_PREVIEW_GLSL + +#include "../SharedGpuTypes.glsl" + +struct MaterialPreviewPC { + uint64_t frameGlobalContextBufferAddr; + uint materialId; +}; + +#endif \ No newline at end of file diff --git a/SynapseEngine/Engine/Shaders/Passes/Preview/MaterialPreview.frag b/SynapseEngine/Engine/Shaders/Passes/Preview/MaterialPreview.frag new file mode 100644 index 00000000..ee62e339 --- /dev/null +++ b/SynapseEngine/Engine/Shaders/Passes/Preview/MaterialPreview.frag @@ -0,0 +1,91 @@ +#version 460 +#extension GL_GOOGLE_include_directive : require +#extension GL_EXT_nonuniform_qualifier : require +#extension GL_EXT_buffer_reference2 : require +#extension GL_EXT_shader_explicit_arithmetic_types_int64 : require + +#include "../../Includes/Core.glsl" +#include "../../Includes/Common/FrameGlobalContext.glsl" +#include "../../Includes/Common/Material.glsl" +#include "../../Includes/Common/Texture.glsl" +#include "../../Includes/Utils/MaterialMath.glsl" +#include "../../Includes/Utils/PbrMath.glsl" +#include "../../Includes/Utils/LightMath.glsl" + +layout(location = 0) in vec2 inUV; +layout(location = 0) out vec4 outColor; + +#include "../../Includes/PushConstants/MaterialPreviewPC.glsl" + +layout(push_constant) uniform PushConstants { + MaterialPreviewPC pc; +}; + +void main() { + vec2 checkerUV = inUV * 10.0; + float checker = mod(floor(checkerUV.x) + floor(checkerUV.y), 2.0); + vec3 bgColor = mix(vec3(0.15), vec3(0.25), checker); + + vec2 p = inUV * 2.0 - 1.0; + float r2 = dot(p, p); + + if (r2 > 1.0) { + outColor = vec4(bgColor, 1.0); + return; + } + + float z = sqrt(1.0 - r2); + vec3 localNormal = vec3(p.x, -p.y, z); + + vec2 sphereUV = vec2( + atan(localNormal.x, localNormal.z) / (2.0 * PI) + 0.5, + asin(localNormal.y) / PI + 0.5 + ); + + vec3 t = cross(vec3(0.0, 1.0, 0.0), localNormal); + vec3 tangent = (dot(t, t) < 1e-6) ? vec3(1.0, 0.0, 0.0) : normalize(t); + vec4 inTangent = vec4(tangent, 1.0); + + float radius = 1.0; + vec3 inWorldPos = localNormal * radius; + vec3 viewDir = vec3(0.0, 0.0, 1.0); + + FrameGlobalContext ctx = GET_FRAME_CONTEXT(pc.frameGlobalContextBufferAddr); + Material mat = GET_MATERIAL(ctx.materialBufferAddr, pc.materialId); + + vec4 albedoAlpha = EvaluateAlbedoAlpha(ctx.textureMetadataBufferAddr, mat, sphereUV); + vec3 finalNormal = EvaluateNormal(ctx.textureMetadataBufferAddr, mat, sphereUV, localNormal, inTangent); + vec2 metalRough = EvaluateMetallicRoughness(ctx.textureMetadataBufferAddr, mat, sphereUV); + vec3 emissive = EvaluateEmissive(ctx.textureMetadataBufferAddr, mat, sphereUV); + float ao = EvaluateAO(ctx.textureMetadataBufferAddr, mat, sphereUV); + + float finalMetalness = clamp(metalRough.x, 0.0, 1.0); + float finalRoughness = clamp(metalRough.y, 0.04, 1.0); + + vec3 totalRadiance = vec3(0.0); + + // Key Light + vec3 keyLightDir = normalize(vec3(1.0, 1.0, 1.0)); + vec3 keyLightColor = vec3(1.0, 1.0, 1.0); + float keyLightStrength = 2.5; + totalRadiance += ShadePhysicallyBased( + albedoAlpha.rgb, finalNormal, viewDir, keyLightDir, + finalRoughness, finalMetalness, keyLightColor, 1.0, keyLightStrength + ); + + // Fill Light + vec3 fillLightDir = normalize(vec3(-1.0, 0.2, -0.5)); + vec3 fillLightColor = vec3(0.5, 0.6, 0.8); + float fillLightStrength = 1.0; + totalRadiance += ShadePhysicallyBased( + albedoAlpha.rgb, finalNormal, viewDir, fillLightDir, + finalRoughness, finalMetalness, fillLightColor, 1.0, fillLightStrength + ); + + // Ambient & Bloom + totalRadiance += SimulateAmbientLight(albedoAlpha.rgb, ao, ctx.ambientStrength); + totalRadiance += SimulateBloom(emissive, 1.0, ctx.emissiveStrength); + + vec3 finalColor = mix(bgColor, totalRadiance, albedoAlpha.a); + outColor = vec4(finalColor, 1.0); +} \ No newline at end of file diff --git a/SynapseEngine/Engine/Shaders/Passes/Preview/MaterialPreview.vert b/SynapseEngine/Engine/Shaders/Passes/Preview/MaterialPreview.vert new file mode 100644 index 00000000..631b4afc --- /dev/null +++ b/SynapseEngine/Engine/Shaders/Passes/Preview/MaterialPreview.vert @@ -0,0 +1,8 @@ +#version 460 + +layout(location = 0) out vec2 outUV; + +void main() { + outUV = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2); + gl_Position = vec4(outUV * 2.0f - 1.0f, 0.0f, 1.0f); +} \ No newline at end of file diff --git a/SynapseEngine/Engine/Shaders/Passes/Shading/Common/Meshlet.mesh b/SynapseEngine/Engine/Shaders/Passes/Shading/Common/Meshlet.mesh index 4aca6a49..0674f605 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Shading/Common/Meshlet.mesh +++ b/SynapseEngine/Engine/Shaders/Passes/Shading/Common/Meshlet.mesh @@ -109,7 +109,7 @@ void main() { // Final Transform and Output gl_MeshVerticesEXT[i].gl_Position = camera.viewProjVulkan * transform.transform * finalMat * vec4(v.position, 1.0); outNormal[i] = (transform.transformIT * finalMatIT * vec4(attr.normal, 0.0)).xyz; - outTangent[i] = vec4((transform.transform * finalMat * vec4(attr.tangent, 0.0)).xyz, 1.0); //Todo + outTangent[i] = vec4((transform.transform * finalMat * vec4(attr.tangent, 0.0)).xyz, 1.0); outUV[i] = vec2(attr.uv_x, 1.0 - attr.uv_y); uint packedEntity = PACK_VISIBILITY_ENTITY(entityId, VIS_PIPELINE_MESH_SHADER); diff --git a/SynapseEngine/Engine/Shaders/Passes/Shading/Common/Traditional.vert b/SynapseEngine/Engine/Shaders/Passes/Shading/Common/Traditional.vert index c8bf68a7..2e7b3834 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Shading/Common/Traditional.vert +++ b/SynapseEngine/Engine/Shaders/Passes/Shading/Common/Traditional.vert @@ -116,7 +116,7 @@ void main() { gl_Position = camera.viewProjVulkan * transform.transform * finalModelMat * vec4(v.position, 1.0); outNormal = (transform.transformIT * finalModelMatIT * vec4(attr.normal, 0.0)).xyz; - outTangent = vec4((transform.transform * finalModelMat * vec4(attr.tangent, 0.0)).xyz, 1.0); // Todo: Invert Normal from model! + outTangent = vec4((transform.transform * finalModelMat * vec4(attr.tangent, 0.0)).xyz, 1.0); outUV = vec2(attr.uv_x, 1.0 - attr.uv_y); uint packedEntity = PACK_VISIBILITY_ENTITY(entityId, VIS_PIPELINE_TRADITIONAL); From 67e493fccba148a57ff77c6deaf7613f8a895c0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20P=C3=A9ter?= Date: Thu, 9 Jul 2026 16:23:17 +0200 Subject: [PATCH 39/51] Implemented new item card widget and item card container --- .../ContentBrowser/ContentBrowserView.cpp | 42 ++-- .../Widgets/ItemCardContainerWidget.cpp | 46 +++++ .../Editor/Widgets/ItemCardContainerWidget.h | 11 + .../Editor/Widgets/ItemCardWidget.cpp | 191 ++++++++++++++++++ SynapseEngine/Editor/Widgets/ItemCardWidget.h | 28 +++ SynapseEngine/imgui.ini | 2 +- 6 files changed, 306 insertions(+), 14 deletions(-) create mode 100644 SynapseEngine/Editor/Widgets/ItemCardContainerWidget.cpp create mode 100644 SynapseEngine/Editor/Widgets/ItemCardContainerWidget.h create mode 100644 SynapseEngine/Editor/Widgets/ItemCardWidget.cpp create mode 100644 SynapseEngine/Editor/Widgets/ItemCardWidget.h diff --git a/SynapseEngine/Editor/View/ContentBrowser/ContentBrowserView.cpp b/SynapseEngine/Editor/View/ContentBrowser/ContentBrowserView.cpp index 4025e5c5..21058b5a 100644 --- a/SynapseEngine/Editor/View/ContentBrowser/ContentBrowserView.cpp +++ b/SynapseEngine/Editor/View/ContentBrowser/ContentBrowserView.cpp @@ -1,6 +1,8 @@ #include "ContentBrowserView.h" #include "Editor/Manager/EditorIcons.h" #include "Editor/Widgets/CardWidget.h" +#include "Editor/Widgets/ItemCardWidget.h" +#include "Editor/Widgets/ItemCardContainerWidget.h" #include #include #include @@ -223,19 +225,33 @@ namespace Syn { } void ContentBrowserView::RenderContentArea(ContentBrowserViewModel& vm, const ContentBrowserState& state) { - float panelWidth = ImGui::GetContentRegionAvail().x; - float padding = 16.0f; - float cellSize = state.thumbnailSize + padding; - int columnCount = std::max(1, static_cast(panelWidth / cellSize)); - - ImGui::Columns(columnCount, "FileSystemGrid", false); - - for (const auto& entry : state.currentEntries) { - RenderFileCard(vm, state, entry); - ImGui::NextColumn(); - } - - ImGui::Columns(1); + const auto entries = state.currentEntries; + Syn::UI::ItemCardContainer("ContentGrid", (int)entries.size(), state.thumbnailSize, + [&](int index) { + const FileEntry& entry = entries[index]; + + Syn::UI::ItemCardDesc desc; + desc.id = entry.path.c_str(); + desc.title = entry.name.c_str(); + desc.texture = GetIconForEntry(entry); + desc.selected = (state.selectedPath == entry.path); + + desc.events.onClick = [&vm, &entry] { + vm.Dispatch(SelectEntryIntent{ entry.path }); + }; + + desc.events.onDoubleClick = [&vm, &entry] { + if (entry.isDirectory) vm.Dispatch(ChangeDirectoryIntent{ entry.path }); + }; + + desc.events.onDragDropSource = [this, &entry] { + std::string payloadType = GetPayloadType(entry.extension); + ImGui::SetDragDropPayload(payloadType.c_str(), entry.path.c_str(), entry.path.size() + 1); + ImGui::TextUnformatted(entry.name.c_str()); + }; + + Syn::UI::ItemCard(desc, state.thumbnailSize); + }); } void ContentBrowserView::RenderFileCard(ContentBrowserViewModel& vm, const ContentBrowserState& state, const FileEntry& entry) { diff --git a/SynapseEngine/Editor/Widgets/ItemCardContainerWidget.cpp b/SynapseEngine/Editor/Widgets/ItemCardContainerWidget.cpp new file mode 100644 index 00000000..7b41fe69 --- /dev/null +++ b/SynapseEngine/Editor/Widgets/ItemCardContainerWidget.cpp @@ -0,0 +1,46 @@ +#include "ItemCardContainerWidget.h" +#include "ItemCardWidget.h" +#include +#include + +namespace Syn::UI { + + void ItemCardContainer(const char* strId, + int itemCount, + float thumbnailSize, + const std::function& drawItem, + float spacing) + { + if (itemCount <= 0 || !drawItem) return; + + ImGui::PushID(strId); + + const float cardW = ItemCardWidth(thumbnailSize); + const float cardH = ItemCardHeight(thumbnailSize); + const float avail = ImGui::GetContentRegionAvail().x; + + int columns = static_cast((avail + spacing) / (cardW + spacing)); + columns = std::max(1, columns); + + const int rows = (itemCount + columns - 1) / columns; + + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(spacing, spacing)); + + ImGuiListClipper clipper; + clipper.Begin(rows, cardH + spacing); + while (clipper.Step()) { + for (int row = clipper.DisplayStart; row < clipper.DisplayEnd; ++row) { + for (int col = 0; col < columns; ++col) { + const int index = row * columns + col; + if (index >= itemCount) break; + if (col > 0) ImGui::SameLine(); + drawItem(index); + } + } + } + clipper.End(); + + ImGui::PopStyleVar(); + ImGui::PopID(); + } +} \ No newline at end of file diff --git a/SynapseEngine/Editor/Widgets/ItemCardContainerWidget.h b/SynapseEngine/Editor/Widgets/ItemCardContainerWidget.h new file mode 100644 index 00000000..5422358e --- /dev/null +++ b/SynapseEngine/Editor/Widgets/ItemCardContainerWidget.h @@ -0,0 +1,11 @@ +#pragma once +#include + +namespace Syn::UI +{ + void ItemCardContainer(const char* strId, + int itemCount, + float thumbnailSize, + const std::function& drawItem, + float spacing = 12.0f); +} \ No newline at end of file diff --git a/SynapseEngine/Editor/Widgets/ItemCardWidget.cpp b/SynapseEngine/Editor/Widgets/ItemCardWidget.cpp new file mode 100644 index 00000000..59d6233e --- /dev/null +++ b/SynapseEngine/Editor/Widgets/ItemCardWidget.cpp @@ -0,0 +1,191 @@ +#include "ItemCardWidget.h" +#include +#include + +namespace Syn::UI { + + namespace { + constexpr float kPadding = 6.0f; + constexpr float kRounding = 6.0f; + constexpr float kImageRounding = 4.0f; + constexpr float kTextGap = 4.0f; + constexpr int kMaxTitleLines = 2; + + constexpr ImU32 kColBg = IM_COL32(36, 36, 40, 255); + constexpr ImU32 kColBgHovered = IM_COL32(50, 50, 56, 255); + constexpr ImU32 kColBgActive = IM_COL32(58, 62, 70, 255); + constexpr ImU32 kColBgSelected = IM_COL32(38, 56, 80, 255); + constexpr ImU32 kColAccent = IM_COL32(66, 150, 250, 255); + constexpr ImU32 kColBorder = IM_COL32(255, 255, 255, 18); + constexpr ImU32 kColText = IM_COL32(230, 230, 230, 255); + constexpr ImU32 kColPlaceholder = IM_COL32(20, 20, 22, 255); + + const char* Utf8Next(const char* s, const char* end) { + if (s >= end) return end; + unsigned char c = static_cast(*s); + int len = 1; + if (c >= 0xF0) len = 4; + else if (c >= 0xE0) len = 3; + else if (c >= 0xC0) len = 2; + const char* n = s + len; + return n > end ? end : n; + } + + const char* ComputeLineEnd(const char* lineStart, const char* textEnd, + float maxWidth, const char** outNextStart) { + const char* s = lineStart; + const char* lastSpace = nullptr; + while (s < textEnd) { + const char* next = Utf8Next(s, textEnd); + if (ImGui::CalcTextSize(lineStart, next).x > maxWidth && s > lineStart) { + if (lastSpace && lastSpace > lineStart) { + *outNextStart = lastSpace + 1; + return lastSpace; + } + *outNextStart = s; + return s; + } + if (*s == ' ') lastSpace = s; + s = next; + } + *outNextStart = textEnd; + return textEnd; + } + } + + float ItemCardWidth(float thumbnailSize) { + return thumbnailSize + kPadding * 2.0f; + } + + float ItemCardHeight(float thumbnailSize) { + return kPadding + thumbnailSize + kTextGap + ImGui::GetTextLineHeight() * kMaxTitleLines + kPadding; + } + + bool ItemCard(const ItemCardDesc& desc, float thumbnailSize) { + const char* idStr = desc.id ? desc.id : desc.title; + ImGui::PushID(idStr); + + const ImVec2 size(ItemCardWidth(thumbnailSize), ItemCardHeight(thumbnailSize)); + const ImVec2 min = ImGui::GetCursorScreenPos(); + const ImVec2 max(min.x + size.x, min.y + size.y); + + bool clicked = ImGui::InvisibleButton("##ItemCard", size); + const bool hovered = ImGui::IsItemHovered(); + const bool active = ImGui::IsItemActive(); + const bool doubleClicked = hovered && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left); + + if (desc.events.onDragDropSource) { + if (ImGui::BeginDragDropSource()) { + desc.events.onDragDropSource(); + ImGui::EndDragDropSource(); + } + } + + ImDrawList* dl = ImGui::GetWindowDrawList(); + + { + const float yOffset = 2.0f; + const int layers = 3; + const int alpha = hovered ? 20 : 13; + for (int i = 0; i < layers; ++i) { + const float expand = 1.5f * static_cast(i + 1); + dl->AddRectFilled( + ImVec2(min.x - expand, min.y - expand + yOffset), + ImVec2(max.x + expand, max.y + expand + yOffset), + IM_COL32(0, 0, 0, alpha), kRounding + expand); + } + } + + // Background + ImU32 bgCol = kColBg; + if (desc.selected) bgCol = kColBgSelected; + if (hovered) bgCol = desc.selected ? kColBgSelected : kColBgHovered; + if (active) bgCol = kColBgActive; + dl->AddRectFilled(min, max, bgCol, kRounding); + + // Border + if (desc.selected) { + dl->AddRect(min, max, kColAccent, kRounding, 0, 2.0f); + } + else { + dl->AddRect(min, max, kColBorder, kRounding, 0, 1.0f); + } + + // Thumbnail + const ImVec2 imgMin(min.x + kPadding, min.y + kPadding); + const ImVec2 imgMax(imgMin.x + thumbnailSize, imgMin.y + thumbnailSize); + if (desc.texture) { + dl->AddImageRounded(desc.texture, imgMin, imgMax, + desc.uv0, desc.uv1, + ImGui::ColorConvertFloat4ToU32(desc.imageTint), + kImageRounding); + } + else { + dl->AddRectFilled(imgMin, imgMax, kColPlaceholder, kImageRounding); + } + + const char* text = desc.title; + const char* textEnd = text + std::strlen(text); + const float maxTextWidth = thumbnailSize; + const float lineHeight = ImGui::GetTextLineHeight(); + float textY = imgMax.y + kTextGap; + + bool truncated = false; + const char* lineStart = text; + + for (int line = 0; line < kMaxTitleLines && lineStart < textEnd; ++line) { + const bool lastLine = (line == kMaxTitleLines - 1); + + if (!lastLine) { + const char* nextStart = textEnd; + const char* lineEnd = ComputeLineEnd(lineStart, textEnd, + maxTextWidth, &nextStart); + const float w = ImGui::CalcTextSize(lineStart, lineEnd).x; + const float x = min.x + kPadding + + std::max(0.0f, (maxTextWidth - w) * 0.5f); + dl->AddText(ImVec2(x, textY), kColText, lineStart, lineEnd); + lineStart = nextStart; + } + else { + float w = ImGui::CalcTextSize(lineStart, textEnd).x; + const char* lineEnd = textEnd; + + if (w > maxTextWidth) { + truncated = true; + const float ellipsisW = ImGui::CalcTextSize("...").x; + const char* s = lineStart; + lineEnd = lineStart; + while (s < textEnd) { + const char* next = Utf8Next(s, textEnd); + if (ImGui::CalcTextSize(lineStart, next).x + ellipsisW + > maxTextWidth) break; + lineEnd = next; + s = next; + } + w = ImGui::CalcTextSize(lineStart, lineEnd).x + ellipsisW; + } + + const float x = min.x + kPadding + + std::max(0.0f, (maxTextWidth - w) * 0.5f); + dl->AddText(ImVec2(x, textY), kColText, lineStart, lineEnd); + if (truncated) { + const float usedW = ImGui::CalcTextSize(lineStart, lineEnd).x; + dl->AddText(ImVec2(x + usedW, textY), kColText, "..."); + } + lineStart = textEnd; + } + textY += lineHeight; + } + if (lineStart < textEnd) truncated = true; + + if (truncated && hovered && !active) { + ImGui::SetTooltip("%s", desc.title); + } + + if (doubleClicked && desc.events.onDoubleClick) desc.events.onDoubleClick(); + else if (clicked && desc.events.onClick) desc.events.onClick(); + + ImGui::PopID(); + return clicked; + } +} \ No newline at end of file diff --git a/SynapseEngine/Editor/Widgets/ItemCardWidget.h b/SynapseEngine/Editor/Widgets/ItemCardWidget.h new file mode 100644 index 00000000..3d8c8997 --- /dev/null +++ b/SynapseEngine/Editor/Widgets/ItemCardWidget.h @@ -0,0 +1,28 @@ +#pragma once +#include +#include + +namespace Syn::UI { + struct ItemCardEvents { + std::function onClick; + std::function onDoubleClick; + std::function onDragDropSource; + }; + + struct ItemCardDesc { + const char* id = nullptr; + const char* title = ""; + + ImTextureID texture = 0; + ImVec2 uv0 = ImVec2(0.0f, 0.0f); + ImVec2 uv1 = ImVec2(1.0f, 1.0f); + ImVec4 imageTint = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); + + bool selected = false; + ItemCardEvents events; + }; + + bool ItemCard(const ItemCardDesc& desc, float thumbnailSize); + float ItemCardWidth(float thumbnailSize); + float ItemCardHeight(float thumbnailSize); +} \ No newline at end of file diff --git a/SynapseEngine/imgui.ini b/SynapseEngine/imgui.ini index 225fbc69..d2d629f6 100644 --- a/SynapseEngine/imgui.ini +++ b/SynapseEngine/imgui.ini @@ -300,7 +300,7 @@ DockSpace ID=0x4713F8A8 Window=0xFBC4AE05 Pos=128,95 Size=2304,1273 Split= DockNode ID=0x00000006 Parent=0x4713F8A8 SizeRef=1840,1273 Split=X DockNode ID=0x00000001 Parent=0x00000006 SizeRef=1348,1273 Split=Y Selected=0x1C1AF642 DockNode ID=0x00000003 Parent=0x00000001 SizeRef=1901,842 CentralNode=1 Selected=0x1C1AF642 - DockNode ID=0x00000004 Parent=0x00000001 SizeRef=1901,429 Selected=0x81DECE6A + DockNode ID=0x00000004 Parent=0x00000001 SizeRef=1901,429 Selected=0xEF69BCF5 DockNode ID=0x00000002 Parent=0x00000006 SizeRef=490,1273 Split=Y Selected=0x70CE1A73 DockNode ID=0x00000007 Parent=0x00000002 SizeRef=401,630 Selected=0x70CE1A73 DockNode ID=0x00000008 Parent=0x00000002 SizeRef=401,641 Selected=0x57A55B3F From ce735be9e042e0f13c306bb956172f3a44ac3d8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20P=C3=A9ter?= Date: Thu, 9 Jul 2026 16:51:14 +0200 Subject: [PATCH 40/51] Texture hierarchy reworked, using modern item card abstraction --- .../Editor/EditorApi/Impl/TextureApiImpl.cpp | 3 +- .../Editor/EditorApi/Impl/TextureApiImpl.h | 2 +- .../ContentBrowser/ContentBrowserView.cpp | 86 ----------------- .../View/ContentBrowser/ContentBrowserView.h | 1 - .../TextureHierarchy/TextureHierarchyView.cpp | 92 ++++++------------- SynapseEngine/EditorCore/Api/ITextureApi.h | 3 +- .../TextureHierarchy/TextureHierarchyState.h | 4 + .../TextureHierarchyViewModel.cpp | 2 + 8 files changed, 41 insertions(+), 152 deletions(-) diff --git a/SynapseEngine/Editor/EditorApi/Impl/TextureApiImpl.cpp b/SynapseEngine/Editor/EditorApi/Impl/TextureApiImpl.cpp index cd87f29f..f7636d84 100644 --- a/SynapseEngine/Editor/EditorApi/Impl/TextureApiImpl.cpp +++ b/SynapseEngine/Editor/EditorApi/Impl/TextureApiImpl.cpp @@ -4,7 +4,7 @@ namespace Syn { - std::vector TextureApiImpl::GetAllTextures() const { + std::vector TextureApiImpl::GetAllTextures() { if (!_imageManager) return {}; std::vector result; @@ -18,6 +18,7 @@ namespace Syn { std::filesystem::path p(paths[i]); data.name = p.filename().string(); + data.handle = GetTextureHandle(i); result.push_back(data); } diff --git a/SynapseEngine/Editor/EditorApi/Impl/TextureApiImpl.h b/SynapseEngine/Editor/EditorApi/Impl/TextureApiImpl.h index fb880583..f591187f 100644 --- a/SynapseEngine/Editor/EditorApi/Impl/TextureApiImpl.h +++ b/SynapseEngine/Editor/EditorApi/Impl/TextureApiImpl.h @@ -9,7 +9,7 @@ namespace Syn { TextureApiImpl(ImageManager* imageManager, GuiTextureManager* guiTextureManager) : _imageManager(imageManager), _guiTextureManager(guiTextureManager) {} - std::vector GetAllTextures() const override; + std::vector GetAllTextures() override; std::vector GetAllSamplers() const override; uint32_t GetSelectedTexture() const override; diff --git a/SynapseEngine/Editor/View/ContentBrowser/ContentBrowserView.cpp b/SynapseEngine/Editor/View/ContentBrowser/ContentBrowserView.cpp index 21058b5a..97f26fb9 100644 --- a/SynapseEngine/Editor/View/ContentBrowser/ContentBrowserView.cpp +++ b/SynapseEngine/Editor/View/ContentBrowser/ContentBrowserView.cpp @@ -254,92 +254,6 @@ namespace Syn { }); } - void ContentBrowserView::RenderFileCard(ContentBrowserViewModel& vm, const ContentBrowserState& state, const FileEntry& entry) { - ImGui::PushID(entry.path.c_str()); - - bool isSelected = (state.selectedPath == entry.path); - ImVec4 baseCol = isSelected ? ImVec4(0.26f, 0.59f, 0.98f, 0.4f) : ImVec4(0, 0, 0, 0); - ImVec4 hoverCol = isSelected ? ImVec4(0.26f, 0.59f, 0.98f, 0.5f) : ImVec4(1.0f, 1.0f, 1.0f, 0.05f); - - ImGui::PushStyleColor(ImGuiCol_Button, baseCol); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, hoverCol); - ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.26f, 0.59f, 0.98f, 0.7f)); - ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 4.0f); - - float cardSize = state.thumbnailSize; - float textHeight = ImGui::GetTextLineHeightWithSpacing() * 2; - ImVec2 totalSize = ImVec2(cardSize, cardSize + textHeight); - - if (ImGui::Button("##CardButton", totalSize)) { - vm.Dispatch(SelectEntryIntent{ entry.path }); - } - - if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { - if (entry.isDirectory) { - vm.Dispatch(ChangeDirectoryIntent{ entry.path }); - } - } - - if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_SourceAllowNullID)) { - std::string payloadType = GetPayloadType(entry.extension); - ImGui::SetDragDropPayload(payloadType.c_str(), entry.path.c_str(), entry.path.size() + 1); - ImGui::TextUnformatted(entry.name.c_str()); - ImGui::EndDragDropSource(); - } - - ImVec2 itemMin = ImGui::GetItemRectMin(); - ImGui::SetNextItemAllowOverlap(); - - ImTextureID iconID = GetIconForEntry(entry); - if (iconID) { - ImGui::SetCursorScreenPos(itemMin); - ImGui::Image(iconID, ImVec2(cardSize, cardSize)); - } - - float currentY = itemMin.y + cardSize + 2.0f; - float lineHeight = ImGui::GetTextLineHeight(); - - const char* text = entry.name.c_str(); - const char* textEnd = text + entry.name.length(); - const char* lineStart = text; - - float maxTextWidth = cardSize - 4.0f; - - int lineCount = 0; - while (lineStart < textEnd && lineCount < 2) { - const char* lineEnd = lineStart; - - while (lineEnd < textEnd) { - float w = ImGui::CalcTextSize(lineStart, lineEnd + 1).x; - if (w > maxTextWidth) { - break; - } - lineEnd++; - } - - if (lineEnd == lineStart) lineEnd++; - - float lineWidth = ImGui::CalcTextSize(lineStart, lineEnd).x; - float textIndent = std::max(0.0f, (cardSize - lineWidth) * 0.5f); - - ImGui::SetCursorScreenPos(ImVec2(itemMin.x + textIndent, currentY)); - ImGui::TextUnformatted(lineStart, lineEnd); - - currentY += lineHeight; - lineStart = lineEnd; - - if (lineStart < textEnd && *lineStart == ' ') { - lineStart++; - } - - lineCount++; - } - - ImGui::PopStyleVar(); - ImGui::PopStyleColor(3); - ImGui::PopID(); - } - ImTextureID ContentBrowserView::GetIconForEntry(const FileEntry& entry) const { if (!_iconManager) return 0; diff --git a/SynapseEngine/Editor/View/ContentBrowser/ContentBrowserView.h b/SynapseEngine/Editor/View/ContentBrowser/ContentBrowserView.h index 288788b0..17879d14 100644 --- a/SynapseEngine/Editor/View/ContentBrowser/ContentBrowserView.h +++ b/SynapseEngine/Editor/View/ContentBrowser/ContentBrowserView.h @@ -19,7 +19,6 @@ namespace Syn { void RenderFolderTree(ContentBrowserViewModel& vm, const ContentBrowserState& state); void RenderContentArea(ContentBrowserViewModel& vm, const ContentBrowserState& state); - void RenderFileCard(ContentBrowserViewModel& vm, const ContentBrowserState& state, const FileEntry& entry); ImTextureID GetIconForEntry(const FileEntry& entry) const; std::string GetPayloadType(const std::string& extension) const; diff --git a/SynapseEngine/Editor/View/TextureHierarchy/TextureHierarchyView.cpp b/SynapseEngine/Editor/View/TextureHierarchy/TextureHierarchyView.cpp index 0b7b0239..21f55406 100644 --- a/SynapseEngine/Editor/View/TextureHierarchy/TextureHierarchyView.cpp +++ b/SynapseEngine/Editor/View/TextureHierarchy/TextureHierarchyView.cpp @@ -1,12 +1,14 @@ #include "TextureHierarchyView.h" #include "Editor/Manager/EditorIcons.h" #include "Editor/Widgets/CardWidget.h" +#include "Editor/Widgets/ItemCardWidget.h" +#include "Editor/Widgets/ItemCardContainerWidget.h" #include namespace Syn { void TextureHierarchyView::Draw(TextureHierarchyViewModel& vm) { ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8, 8)); - ImGuiWindowFlags windowFlags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse; + ImGuiWindowFlags windowFlags = ImGuiWindowFlags_None; if (ImGui::Begin(SYN_ICON_IMAGE " Textures", nullptr, windowFlags)) { @@ -25,47 +27,33 @@ namespace Syn { const auto& state = vm.GetState(); - ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.08f, 0.08f, 0.08f, 0.6f)); - ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 8.0f); - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); - - float currentY = ImGui::GetCursorScreenPos().y; - float tableHeight = mainContentBottomY - currentY - 12.0f; - if (tableHeight < 100.0f) tableHeight = 100.0f; - - ImGui::BeginChild("TextureTableContainer", ImVec2(0, tableHeight), ImGuiChildFlags_Borders, ImGuiWindowFlags_NoScrollbar); - - if (ImGui::BeginTable("TextureTable", 1, ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_ScrollY | ImGuiTableFlags_Resizable)) { - ImGui::TableSetupScrollFreeze(0, 1); - ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch); - - ImGui::TableNextRow(ImGuiTableRowFlags_Headers); - ImGui::TableSetColumnIndex(0); - - float cellWidth = ImGui::GetColumnWidth(); - float textWidth = ImGui::CalcTextSize("Name").x; - ImVec2 startPos = ImGui::GetCursorPos(); - - ImGui::TableHeader(""); - ImGui::SetCursorPos(ImVec2(startPos.x + (cellWidth - textWidth) * 0.5f, startPos.y + 3.0f)); - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); - ImGui::Text("Name"); - ImGui::PopStyleColor(); - - ImGuiListClipper clipper; - clipper.Begin(static_cast(state.filteredNodes.size())); - - while (clipper.Step()) { - for (int row = clipper.DisplayStart; row < clipper.DisplayEnd; ++row) { - RenderTextureRow(vm, state.filteredNodes[row]); - } - } - - ImGui::EndTable(); - } - ImGui::EndChild(); - ImGui::PopStyleVar(2); - ImGui::PopStyleColor(); + const auto entries = state.filteredNodes; + const float thumbnailSize = 100; + Syn::UI::ItemCardContainer("ContentGrid", (int)entries.size(), thumbnailSize, + [&](int index) { + const auto& entry = entries[index]; + + Syn::UI::ItemCardDesc desc; + desc.id = entry.path.c_str(); + desc.title = entry.name.c_str(); + desc.texture = entry.handle; + desc.selected = (state.selectedTexture == entry.id); + + desc.events.onClick = [&vm, &entry] { + vm.Dispatch(TextureSelectIntent{ entry.id }); + }; + + desc.events.onDoubleClick = [&vm, &entry] { + //Todo? + }; + + desc.events.onDragDropSource = [this, &entry] { + //Todo? + }; + + Syn::UI::ItemCard(desc, thumbnailSize); + }); + } Syn::UI::EndCard(); @@ -110,24 +98,4 @@ namespace Syn { ImGui::PopStyleVar(); ImGui::Spacing(); } - - void TextureHierarchyView::RenderTextureRow(TextureHierarchyViewModel& vm, const TextureNode& node) { - ImGui::PushID(node.id); - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - - ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen | ImGuiTreeNodeFlags_SpanFullWidth | ImGuiTreeNodeFlags_FramePadding; - if (vm.GetState().selectedTexture == node.id) { - flags |= ImGuiTreeNodeFlags_Selected; - } - - std::string label = node.icon + " " + node.name; - ImGui::TreeNodeEx((void*)(intptr_t)node.id, flags, "%s", label.c_str()); - - if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { - vm.Dispatch(TextureSelectIntent{ node.id }); - } - - ImGui::PopID(); - } } \ No newline at end of file diff --git a/SynapseEngine/EditorCore/Api/ITextureApi.h b/SynapseEngine/EditorCore/Api/ITextureApi.h index 72af4f8f..c0fab599 100644 --- a/SynapseEngine/EditorCore/Api/ITextureApi.h +++ b/SynapseEngine/EditorCore/Api/ITextureApi.h @@ -12,6 +12,7 @@ namespace Syn { uint32_t id; std::string name; std::string path; + TextureHandle handle; }; struct SamplerItemData { @@ -23,7 +24,7 @@ namespace Syn { public: virtual ~ITextureApi() = default; - virtual std::vector GetAllTextures() const = 0; + virtual std::vector GetAllTextures() = 0; virtual std::vector GetAllSamplers() const = 0; virtual uint32_t GetSelectedTexture() const = 0; diff --git a/SynapseEngine/EditorCore/ViewModels/TextureHierarchy/TextureHierarchyState.h b/SynapseEngine/EditorCore/ViewModels/TextureHierarchy/TextureHierarchyState.h index 59299c89..26610592 100644 --- a/SynapseEngine/EditorCore/ViewModels/TextureHierarchy/TextureHierarchyState.h +++ b/SynapseEngine/EditorCore/ViewModels/TextureHierarchy/TextureHierarchyState.h @@ -3,11 +3,15 @@ #include #include +#include "EditorCore/Types/TextureHandle.h" + namespace Syn { struct TextureNode { uint32_t id; std::string name; + std::string path; std::string icon; + TextureHandle handle = InvalidTextureHandle; }; struct TextureHierarchyState { diff --git a/SynapseEngine/EditorCore/ViewModels/TextureHierarchy/TextureHierarchyViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/TextureHierarchy/TextureHierarchyViewModel.cpp index f6e72931..0dfa348d 100644 --- a/SynapseEngine/EditorCore/ViewModels/TextureHierarchy/TextureHierarchyViewModel.cpp +++ b/SynapseEngine/EditorCore/ViewModels/TextureHierarchy/TextureHierarchyViewModel.cpp @@ -62,6 +62,8 @@ namespace Syn { TextureNode node; node.id = tex.id; node.name = tex.name; + node.path = tex.path; + node.handle = tex.handle; node.icon = SYN_ICON_IMAGE; _state.filteredNodes.push_back(node); } From ee03277727b04f5074acb153f7f392994d5dc811 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20P=C3=A9ter?= Date: Thu, 9 Jul 2026 17:57:27 +0200 Subject: [PATCH 41/51] Material hierarchy refactored using preview material textures and new itemcard widgets --- .../Editor/EditorApi/EditorContext.cpp | 54 +++++------ .../Editor/EditorApi/EditorContext.h | 72 +++++---------- .../Editor/EditorApi/Impl/PreviewApiImpl.cpp | 72 +++++++++++++++ .../Editor/EditorApi/Impl/PreviewApiImpl.h | 27 ++++++ SynapseEngine/Editor/Synapse.cpp | 2 +- .../MaterialHierarchyView.cpp | 90 +++++++------------ .../MaterialHierarchy/MaterialHierarchyView.h | 1 - .../Editor/Workspace/MaterialWorkspace.cpp | 14 +-- .../Editor/Workspace/ModelWorkspace.cpp | 18 ++-- .../Editor/Workspace/SceneWorkspace.cpp | 64 +++++++------ .../Editor/Workspace/TextureWorkspace.cpp | 10 +-- SynapseEngine/EditorCore/Api/IAnimationApi.h | 3 +- SynapseEngine/EditorCore/Api/IApi.h | 9 ++ .../EditorCore/Api/IBoxColliderApi.h | 3 +- SynapseEngine/EditorCore/Api/ICameraApi.h | 3 +- .../EditorCore/Api/ICapsuleColliderApi.h | 3 +- .../EditorCore/Api/IConvexColliderApi.h | 3 +- .../EditorCore/Api/IDirectionLightApi.h | 3 +- SynapseEngine/EditorCore/Api/IFileDialogApi.h | 3 +- SynapseEngine/EditorCore/Api/IFileSystemApi.h | 3 +- SynapseEngine/EditorCore/Api/IHierarchyApi.h | 3 +- SynapseEngine/EditorCore/Api/ILoggerApi.h | 3 +- SynapseEngine/EditorCore/Api/IMaterialApi.h | 3 +- .../EditorCore/Api/IMaterialOverrideApi.h | 3 +- .../EditorCore/Api/IMeshColliderApi.h | 3 +- SynapseEngine/EditorCore/Api/IModelApi.h | 3 +- .../EditorCore/Api/IModelComponentApi.h | 3 +- SynapseEngine/EditorCore/Api/IPointLightApi.h | 3 +- SynapseEngine/EditorCore/Api/IPreviewApi.h | 28 ++++++ SynapseEngine/EditorCore/Api/IRenderApi.h | 3 +- SynapseEngine/EditorCore/Api/IRigidBodyApi.h | 3 +- SynapseEngine/EditorCore/Api/ISceneApi.h | 3 +- SynapseEngine/EditorCore/Api/ISelectionApi.h | 3 +- SynapseEngine/EditorCore/Api/ISettingsApi.h | 3 +- .../EditorCore/Api/ISphereColliderApi.h | 3 +- SynapseEngine/EditorCore/Api/ISpotLightApi.h | 3 +- SynapseEngine/EditorCore/Api/ITagApi.h | 3 +- SynapseEngine/EditorCore/Api/ITextureApi.h | 3 +- SynapseEngine/EditorCore/Api/ITransformApi.h | 3 +- .../ContentBrowser/ContentBrowserState.h | 2 +- .../MaterialHierarchyState.h | 7 ++ .../MaterialHierarchyViewModel.cpp | 28 +++++- .../MaterialHierarchyViewModel.h | 5 +- SynapseEngine/Engine/Engine.cpp | 5 ++ SynapseEngine/Engine/Engine.h | 2 + .../Engine/Manager/PreviewManager.cpp | 16 ++++ SynapseEngine/Engine/Manager/PreviewManager.h | 2 + 47 files changed, 391 insertions(+), 215 deletions(-) create mode 100644 SynapseEngine/Editor/EditorApi/Impl/PreviewApiImpl.cpp create mode 100644 SynapseEngine/Editor/EditorApi/Impl/PreviewApiImpl.h create mode 100644 SynapseEngine/EditorCore/Api/IApi.h create mode 100644 SynapseEngine/EditorCore/Api/IPreviewApi.h diff --git a/SynapseEngine/Editor/EditorApi/EditorContext.cpp b/SynapseEngine/Editor/EditorApi/EditorContext.cpp index 3fa32cb1..2550462a 100644 --- a/SynapseEngine/Editor/EditorApi/EditorContext.cpp +++ b/SynapseEngine/Editor/EditorApi/EditorContext.cpp @@ -24,37 +24,37 @@ #include "Impl/ModelComponentApiImpl.h" #include "Impl/AnimationApiImpl.h" #include "Impl/MaterialOverrideApiImpl.h" +#include "Impl/PreviewApiImpl.h" namespace Syn { EditorContext::EditorContext(Engine* engine, GuiTextureManager* textureManager) { SceneManager* sm = engine->GetSceneManager(); - _selectionApi = std::make_unique(sm); - _tagApi = std::make_unique(sm); - _transformApi = std::make_unique(sm); - _directionLightApi = std::make_unique(sm); - _fileSystemApi = std::make_unique(); - _hierarchyApi = std::make_unique(sm); - _loggerApi = std::make_unique(engine); - _materialApi = std::make_unique(engine->GetMaterialManager(), sm); - _renderApi = std::make_unique(engine, textureManager, sm); - _sceneApi = std::make_unique(sm); - _settingsApi = std::make_unique(sm); - _pointLightApi = std::make_unique(sm); - _spotLightApi = std::make_unique(sm); - _textureApi = std::make_unique(engine->GetImageManager(), textureManager); - _modelApi = std::make_unique(engine->GetModelManager(), sm); - _cameraApi = std::make_unique(sm); - _boxColliderApi = std::make_unique(sm); - _sphereColliderApi = std::make_unique(sm); - _capsuleColliderApi = std::make_unique(sm); - _convexColliderApi = std::make_unique(sm); - _meshColliderApi = std::make_unique(sm); - _rigidBodyApi = std::make_unique(sm); - _modelComponentApi = std::make_unique(sm); - _animationApi = std::make_unique(sm); - _materialOverrideApi = std::make_unique(sm); + RegisterApi(sm); + RegisterApi(sm); + RegisterApi(sm); + RegisterApi(sm); + RegisterApi(); + RegisterApi(sm); + RegisterApi(engine); + RegisterApi(engine->GetMaterialManager(), sm); + RegisterApi(engine, textureManager, sm); + RegisterApi(sm); + RegisterApi(sm); + RegisterApi(sm); + RegisterApi(sm); + RegisterApi(engine->GetImageManager(), textureManager); + RegisterApi(engine->GetModelManager(), sm); + RegisterApi(sm); + RegisterApi(sm); + RegisterApi(sm); + RegisterApi(sm); + RegisterApi(sm); + RegisterApi(sm); + RegisterApi(sm); + RegisterApi(sm); + RegisterApi(sm); + RegisterApi(sm); + RegisterApi(engine->GetPreviewManager(), textureManager, engine->GetImageManager()); } - - EditorContext::~EditorContext() = default; } \ No newline at end of file diff --git a/SynapseEngine/Editor/EditorApi/EditorContext.h b/SynapseEngine/Editor/EditorApi/EditorContext.h index 330d9f80..e4d79ac0 100644 --- a/SynapseEngine/Editor/EditorApi/EditorContext.h +++ b/SynapseEngine/Editor/EditorApi/EditorContext.h @@ -1,8 +1,11 @@ #pragma once #include +#include +#include #include "Engine/Engine.h" #include "Editor/Manager/GuiTextureManager.h" +#include "EditorCore/Api/IApi.h" #include "EditorCore/Api/ISelectionApi.h" #include "EditorCore/Api/ITagApi.h" #include "EditorCore/Api/ITransformApi.h" @@ -28,63 +31,30 @@ #include "EditorCore/Api/IModelComponentApi.h" #include "EditorCore/Api/IAnimationApi.h" #include "EditorCore/Api/IMaterialOverrideApi.h" +#include "EditorCore/Api/IPreviewApi.h" namespace Syn { class EditorContext { public: EditorContext(Engine* engine, GuiTextureManager* textureManager); - ~EditorContext(); - ISelectionApi* GetSelectionApi() const { return _selectionApi.get(); } - ITagApi* GetTagApi() const { return _tagApi.get(); } - ITransformApi* GetTransformApi() const { return _transformApi.get(); } - IDirectionLightApi* GetDirectionLightApi() const { return _directionLightApi.get(); } - IFileSystemApi* GetFileSystemApi() const { return _fileSystemApi.get(); } - IHierarchyApi* GetHierarchyApi() const { return _hierarchyApi.get(); } - ILoggerApi* GetLoggerApi() const { return _loggerApi.get(); } - IMaterialApi* GetMaterialApi() const { return _materialApi.get(); } - IRenderApi* GetRenderApi() const { return _renderApi.get(); } - ISceneApi* GetSceneApi() const { return _sceneApi.get(); } - ISettingsApi* GetSettingsApi() const { return _settingsApi.get(); } - IPointLightApi* GetPointLightApi() const { return _pointLightApi.get(); } - ISpotLightApi* GetSpotLightApi() const { return _spotLightApi.get(); } - ITextureApi* GetTextureApi() const { return _textureApi.get(); } - IModelApi* GetModelApi() const { return _modelApi.get(); } - ICameraApi* GetCameraApi() const { return _cameraApi.get(); } - IBoxColliderApi* GetBoxColliderApi() const { return _boxColliderApi.get(); } - ISphereColliderApi* GetSphereColliderApi() const { return _sphereColliderApi.get(); } - ICapsuleColliderApi* GetCapsuleColliderApi() const { return _capsuleColliderApi.get(); } - IConvexColliderApi* GetConvexColliderApi() const { return _convexColliderApi.get(); } - IMeshColliderApi* GetMeshColliderApi() const { return _meshColliderApi.get(); } - IRigidBodyApi* GetRigidBodyApi() const { return _rigidBodyApi.get(); } - IModelComponentApi* GetModelComponentApi() const { return _modelComponentApi.get(); } - IAnimationApi* GetAnimationApi() const { return _animationApi.get(); } - IMaterialOverrideApi* GetMaterialOverrideApi() { return _materialOverrideApi.get(); } + template + T* GetApi() const { + auto it = _apis.find(std::type_index(typeid(T))); + + if (it != _apis.end()) { + return static_cast(it->second.get()); + } + + return nullptr; + } + private: - std::unique_ptr _selectionApi; - std::unique_ptr _tagApi; - std::unique_ptr _transformApi; - std::unique_ptr _fileSystemApi; - std::unique_ptr _hierarchyApi; - std::unique_ptr _loggerApi; - std::unique_ptr _materialApi; - std::unique_ptr _renderApi; - std::unique_ptr _sceneApi; - std::unique_ptr _settingsApi; - std::unique_ptr _directionLightApi; - std::unique_ptr _pointLightApi; - std::unique_ptr _spotLightApi; - std::unique_ptr _textureApi; - std::unique_ptr _modelApi; - std::unique_ptr _cameraApi; - std::unique_ptr _boxColliderApi; - std::unique_ptr _sphereColliderApi; - std::unique_ptr _capsuleColliderApi; - std::unique_ptr _convexColliderApi; - std::unique_ptr _meshColliderApi; - std::unique_ptr _rigidBodyApi; - std::unique_ptr _modelComponentApi; - std::unique_ptr _animationApi; - std::unique_ptr _materialOverrideApi; + template + void RegisterApi(Args&&... args) { + _apis[std::type_index(typeid(Interface))] = std::make_unique(std::forward(args)...); + } + + std::unordered_map> _apis; }; } \ No newline at end of file diff --git a/SynapseEngine/Editor/EditorApi/Impl/PreviewApiImpl.cpp b/SynapseEngine/Editor/EditorApi/Impl/PreviewApiImpl.cpp new file mode 100644 index 00000000..0f3b58ba --- /dev/null +++ b/SynapseEngine/Editor/EditorApi/Impl/PreviewApiImpl.cpp @@ -0,0 +1,72 @@ +#include "PreviewApiImpl.h" +#include "Engine/Image/SamplerNames.h" + +namespace Syn { + + PreviewApiImpl::PreviewApiImpl(PreviewManager* previewManager, GuiTextureManager* guiTextureManager, ImageManager* imageManager) + : _previewManager(previewManager), _guiTextureManager(guiTextureManager), _imageManager(imageManager) + {} + + TextureHandle PreviewApiImpl::GetAtlasHandle() { + if (!_previewManager || !_guiTextureManager || !_imageManager) + return InvalidTextureHandle; + + auto* atlasImage = _previewManager->GetAtlasImage(); + if (!atlasImage) + return InvalidTextureHandle; + + uint32_t currentRes = _previewManager->GetResolution(); + + if (_lastAtlasResolution != currentRes) { + auto sampler = _imageManager->GetSampler(SamplerNames::LinearClampEdge); + _atlasTextureHandle = _guiTextureManager->RegisterTexture( + atlasImage->GetView(), + sampler->Handle() + ); + + _lastAtlasResolution = currentRes; + } + + return _guiTextureManager->GetImGuiTextureID(_atlasTextureHandle); + } + + bool PreviewApiImpl::GetPreviewUVs(PreviewResourceType type, uint32_t resourceId, glm::vec2& outUv0, glm::vec2& outUv1) const { + if (!_previewManager) return false; + + if (_previewManager->HasTile(type, resourceId)) { + _previewManager->GetNormalizedUVs(type, resourceId, outUv0, outUv1); + return true; + } + + outUv0 = glm::vec2(0.0f); + outUv1 = glm::vec2(1.0f); + return false; + } + + bool PreviewApiImpl::HasPreview(PreviewResourceType type, uint32_t resourceId) const { + if (!_previewManager) return false; + return _previewManager->HasTile(type, resourceId); + } + + void PreviewApiImpl::RequestPreview(PreviewResourceType type, uint32_t resourceId) { + if (!_previewManager) return; + _previewManager->AllocateTile(type, resourceId); + _previewManager->MarkDirty(type, resourceId); + } + + std::vector PreviewApiImpl::GetAllPreviews(PreviewResourceType type) const { + if (!_previewManager) return {}; + + std::vector result; + auto activeIds = _previewManager->GetActiveResources(type); + result.reserve(activeIds.size()); + + for (uint32_t id : activeIds) { + glm::vec2 uv0, uv1; + _previewManager->GetNormalizedUVs(type, id, uv0, uv1); + result.push_back({ id, uv0, uv1 }); + } + + return result; + } +} \ No newline at end of file diff --git a/SynapseEngine/Editor/EditorApi/Impl/PreviewApiImpl.h b/SynapseEngine/Editor/EditorApi/Impl/PreviewApiImpl.h new file mode 100644 index 00000000..e632c8de --- /dev/null +++ b/SynapseEngine/Editor/EditorApi/Impl/PreviewApiImpl.h @@ -0,0 +1,27 @@ +#pragma once +#include "EditorCore/Api/IPreviewApi.h" +#include "Engine/Manager/PreviewManager.h" +#include "Editor/Manager/GuiTextureManager.h" +#include "Engine/Image/ImageManager.h" + +namespace Syn { + + class PreviewApiImpl : public IPreviewApi { + public: + PreviewApiImpl(PreviewManager* previewManager, GuiTextureManager* guiTextureManager, ImageManager* imageManager); + ~PreviewApiImpl() override = default; + + TextureHandle GetAtlasHandle() override; + bool GetPreviewUVs(PreviewResourceType type, uint32_t resourceId, glm::vec2& outUv0, glm::vec2& outUv1) const override; + bool HasPreview(PreviewResourceType type, uint32_t resourceId) const override; + void RequestPreview(PreviewResourceType type, uint32_t resourceId) override; + std::vector GetAllPreviews(PreviewResourceType type) const override; + private: + PreviewManager* _previewManager; + GuiTextureManager* _guiTextureManager; + ImageManager* _imageManager; + + uint32_t _lastAtlasResolution = 0; + TextureHandle _atlasTextureHandle; + }; +} \ No newline at end of file diff --git a/SynapseEngine/Editor/Synapse.cpp b/SynapseEngine/Editor/Synapse.cpp index 912bec20..3a1d5b19 100644 --- a/SynapseEngine/Editor/Synapse.cpp +++ b/SynapseEngine/Editor/Synapse.cpp @@ -114,7 +114,7 @@ void Synapse::OnInit() { using MainMenuWin = Syn::EditorWindow; _guiManager->AddGlobalWindow( Syn::MainMenuView{}, - Syn::MainMenuViewModel{ _editorContext->GetSceneApi(), _guiManager->GetFileDialog() } + Syn::MainMenuViewModel{ _editorContext->GetApi(), _guiManager->GetFileDialog() } ); _guiManager->AddWorkspace(Syn::EditorWorkspace::Scene, std::make_unique( diff --git a/SynapseEngine/Editor/View/MaterialHierarchy/MaterialHierarchyView.cpp b/SynapseEngine/Editor/View/MaterialHierarchy/MaterialHierarchyView.cpp index 275e41d1..3933d63a 100644 --- a/SynapseEngine/Editor/View/MaterialHierarchy/MaterialHierarchyView.cpp +++ b/SynapseEngine/Editor/View/MaterialHierarchy/MaterialHierarchyView.cpp @@ -1,12 +1,14 @@ #include "MaterialHierarchyView.h" #include "Editor/Manager/EditorIcons.h" #include "Editor/Widgets/CardWidget.h" +#include "Editor/Widgets/ItemCardWidget.h" +#include "Editor/Widgets/ItemCardContainerWidget.h" #include namespace Syn { void MaterialHierarchyView::Draw(MaterialHierarchyViewModel& vm) { ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8, 8)); - ImGuiWindowFlags windowFlags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse; + ImGuiWindowFlags windowFlags = ImGuiWindowFlags_None; if (ImGui::Begin(SYN_ICON_BRUSH " Materials", nullptr, windowFlags)) { @@ -24,48 +26,40 @@ namespace Syn { RenderTopBar(vm); const auto& state = vm.GetState(); + const auto entries = state.filteredNodes; + const float thumbnailSize = 100.0f; + + Syn::UI::ItemCardContainer("MaterialGrid", (int)entries.size(), thumbnailSize, + [&](int index) { + const auto& entry = entries[index]; + + Syn::UI::ItemCardDesc desc; + desc.id = entry.path.c_str(); + desc.title = entry.name.c_str(); + + if (entry.hasPreview && state.atlasHandle) { + desc.texture = state.atlasHandle; + desc.uv0 = ImVec2(entry.uv0.x, entry.uv0.y); + desc.uv1 = ImVec2(entry.uv1.x, entry.uv1.y); + } + else { + desc.texture = InvalidTextureHandle; + } - ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.08f, 0.08f, 0.08f, 0.6f)); - ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 8.0f); - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); - - float currentY = ImGui::GetCursorScreenPos().y; - float tableHeight = mainContentBottomY - currentY - 12.0f; - if (tableHeight < 100.0f) tableHeight = 100.0f; - - ImGui::BeginChild("MaterialTableContainer", ImVec2(0, tableHeight), ImGuiChildFlags_Borders, ImGuiWindowFlags_NoScrollbar); - - if (ImGui::BeginTable("MaterialTable", 1, ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_ScrollY | ImGuiTableFlags_Resizable)) { - ImGui::TableSetupScrollFreeze(0, 1); - ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch); - - ImGui::TableNextRow(ImGuiTableRowFlags_Headers); - ImGui::TableSetColumnIndex(0); - - float cellWidth = ImGui::GetColumnWidth(); - float textWidth = ImGui::CalcTextSize("Name").x; - ImVec2 startPos = ImGui::GetCursorPos(); + desc.selected = (state.selectedMaterial == entry.id); - ImGui::TableHeader(""); - ImGui::SetCursorPos(ImVec2(startPos.x + (cellWidth - textWidth) * 0.5f, startPos.y + 3.0f)); - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); - ImGui::Text("Name"); - ImGui::PopStyleColor(); + desc.events.onClick = [&vm, &entry] { + vm.Dispatch(MaterialSelectIntent{ entry.id }); + }; - ImGuiListClipper clipper; - clipper.Begin(static_cast(state.filteredNodes.size())); + desc.events.onDoubleClick = [&vm, &entry] { + }; - while (clipper.Step()) { - for (int row = clipper.DisplayStart; row < clipper.DisplayEnd; ++row) { - RenderMaterialRow(vm, state.filteredNodes[row]); - } - } + desc.events.onDragDropSource = [&entry] { + }; - ImGui::EndTable(); - } - ImGui::EndChild(); - ImGui::PopStyleVar(2); - ImGui::PopStyleColor(); + Syn::UI::ItemCard(desc, thumbnailSize); + }); } Syn::UI::EndCard(); @@ -110,24 +104,4 @@ namespace Syn { ImGui::PopStyleVar(); ImGui::Spacing(); } - - void MaterialHierarchyView::RenderMaterialRow(MaterialHierarchyViewModel& vm, const MaterialNode& node) { - ImGui::PushID(node.id); - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - - ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen | ImGuiTreeNodeFlags_SpanFullWidth | ImGuiTreeNodeFlags_FramePadding; - if (vm.GetState().selectedMaterial == node.id) { - flags |= ImGuiTreeNodeFlags_Selected; - } - - std::string label = node.icon + " " + node.name; - ImGui::TreeNodeEx((void*)(intptr_t)node.id, flags, "%s", label.c_str()); - - if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { - vm.Dispatch(MaterialSelectIntent{ node.id }); - } - - ImGui::PopID(); - } } \ No newline at end of file diff --git a/SynapseEngine/Editor/View/MaterialHierarchy/MaterialHierarchyView.h b/SynapseEngine/Editor/View/MaterialHierarchy/MaterialHierarchyView.h index 6b63ab46..322678c0 100644 --- a/SynapseEngine/Editor/View/MaterialHierarchy/MaterialHierarchyView.h +++ b/SynapseEngine/Editor/View/MaterialHierarchy/MaterialHierarchyView.h @@ -10,7 +10,6 @@ namespace Syn { void Draw(MaterialHierarchyViewModel& vm) override; private: void RenderTopBar(MaterialHierarchyViewModel& vm); - void RenderMaterialRow(MaterialHierarchyViewModel& vm, const MaterialNode& node); private: std::unordered_map _cardStates; }; diff --git a/SynapseEngine/Editor/Workspace/MaterialWorkspace.cpp b/SynapseEngine/Editor/Workspace/MaterialWorkspace.cpp index 7fe9d69e..b8e30e3f 100644 --- a/SynapseEngine/Editor/Workspace/MaterialWorkspace.cpp +++ b/SynapseEngine/Editor/Workspace/MaterialWorkspace.cpp @@ -24,8 +24,8 @@ namespace Syn { : _context(context), _iconManager(iconManager), _assetPath(assetPath) {} void MaterialWorkspace::OnActivate() { - if (_context && _context->GetSceneApi()) { - _context->GetSceneApi()->ActivateScene(SceneNames::MaterialPreview); + if (_context && _context->GetApi()) { + _context->GetApi()->ActivateScene(SceneNames::MaterialPreview); } } @@ -33,31 +33,31 @@ namespace Syn { using ContentBrowserWin = EditorWindow; AddWindow( ContentBrowserView{ _iconManager, SYN_ICON_FOLDER_OPEN " Content Browser###Content_Material" }, - ContentBrowserViewModel{ _context->GetFileSystemApi(), _assetPath } + ContentBrowserViewModel{ _context->GetApi(), _assetPath } ); using MaterialHierarchyWin = EditorWindow; AddWindow( MaterialHierarchyView{}, - MaterialHierarchyViewModel{ _context->GetMaterialApi() } + MaterialHierarchyViewModel{ _context->GetApi(), _context->GetApi() } ); using MaterialGraphWin = EditorWindow; AddWindow( MaterialGraphView{}, - MaterialGraphViewModel{ _context->GetMaterialApi(), _context->GetTextureApi() } + MaterialGraphViewModel{ _context->GetApi(), _context->GetApi() } ); using MaterialPropertiesWin = EditorWindow; AddWindow( MaterialPropertiesView{}, - MaterialPropertiesViewModel{ _context->GetMaterialApi(), _context->GetTextureApi() } + MaterialPropertiesViewModel{ _context->GetApi(), _context->GetApi() } ); using MaterialViewportWin = EditorWindow; AddWindow( MaterialViewportView{}, - MaterialViewportViewModel{ _context->GetRenderApi() } + MaterialViewportViewModel{ _context->GetApi() } ); } diff --git a/SynapseEngine/Editor/Workspace/ModelWorkspace.cpp b/SynapseEngine/Editor/Workspace/ModelWorkspace.cpp index e094dde3..f7781e96 100644 --- a/SynapseEngine/Editor/Workspace/ModelWorkspace.cpp +++ b/SynapseEngine/Editor/Workspace/ModelWorkspace.cpp @@ -21,8 +21,8 @@ namespace Syn { : _context(context), _iconManager(iconManager), _assetPath(assetPath) {} void ModelWorkspace::OnActivate() { - if (_context && _context->GetSceneApi()) { - _context->GetSceneApi()->ActivateScene(SceneNames::ModelPreview); + if (_context && _context->GetApi()) { + _context->GetApi()->ActivateScene(SceneNames::ModelPreview); } } @@ -31,25 +31,31 @@ namespace Syn { using ContentBrowserWin = EditorWindow; AddWindow( ContentBrowserView{ _iconManager, SYN_ICON_FOLDER_OPEN " Content Browser###Content_Model" }, - ContentBrowserViewModel{ _context->GetFileSystemApi(), _assetPath } + ContentBrowserViewModel{ _context->GetApi(), _assetPath } ); using ModelHierarchyWin = EditorWindow; AddWindow( ModelHierarchyView{}, - ModelHierarchyViewModel{ _context->GetModelApi() } + ModelHierarchyViewModel{ _context->GetApi() } ); using ModelPropertiesWin = EditorWindow; AddWindow( ModelPropertiesView{}, - ModelPropertiesViewModel{ _context->GetModelApi() } + ModelPropertiesViewModel{ _context->GetApi() } ); using ModelViewportWin = EditorWindow; AddWindow( ModelViewportView{}, - ModelViewportViewModel{ _context->GetRenderApi(), _context->GetSelectionApi(), _context->GetTransformApi(), _context->GetSettingsApi(), _context->GetModelApi() } + ModelViewportViewModel{ + _context->GetApi(), + _context->GetApi(), + _context->GetApi(), + _context->GetApi(), + _context->GetApi() + } ); } } \ No newline at end of file diff --git a/SynapseEngine/Editor/Workspace/SceneWorkspace.cpp b/SynapseEngine/Editor/Workspace/SceneWorkspace.cpp index a7c07fd1..8cfed37a 100644 --- a/SynapseEngine/Editor/Workspace/SceneWorkspace.cpp +++ b/SynapseEngine/Editor/Workspace/SceneWorkspace.cpp @@ -18,47 +18,46 @@ #include "Engine/Scene/SceneNames.h" - namespace Syn { SceneWorkspace::SceneWorkspace(EditorContext* context, IconManager* iconManager, const std::string& assetPath) : _context(context), _iconManager(iconManager), _assetPath(assetPath) {} void SceneWorkspace::OnActivate() { - if (_context && _context->GetSceneApi()) { - _context->GetSceneApi()->ActivateScene(SceneNames::Main); + if (_context && _context->GetApi()) { + _context->GetApi()->ActivateScene(SceneNames::Main); } } - void SceneWorkspace::Initialize() + void SceneWorkspace::Initialize() { using ContentBrowserWin = EditorWindow; AddWindow( ContentBrowserView{ _iconManager, SYN_ICON_FOLDER_OPEN " Content Browser###Content_Scene" }, - ContentBrowserViewModel{ _context->GetFileSystemApi(), _assetPath } + ContentBrowserViewModel{ _context->GetApi(), _assetPath } ); using ComponentWin = EditorWindow; AddWindow( ComponentView{}, ComponentViewModel{ - _context->GetSelectionApi(), - _context->GetTagApi(), - _context->GetTransformApi(), - _context->GetHierarchyApi(), - _context->GetDirectionLightApi(), - _context->GetPointLightApi(), - _context->GetSpotLightApi(), - _context->GetCameraApi(), - _context->GetBoxColliderApi(), - _context->GetSphereColliderApi(), - _context->GetCapsuleColliderApi(), - _context->GetConvexColliderApi(), - _context->GetMeshColliderApi(), - _context->GetRigidBodyApi(), - _context->GetModelComponentApi(), - _context->GetAnimationApi(), - _context->GetMaterialOverrideApi() + _context->GetApi(), + _context->GetApi(), + _context->GetApi(), + _context->GetApi(), + _context->GetApi(), + _context->GetApi(), + _context->GetApi(), + _context->GetApi(), + _context->GetApi(), + _context->GetApi(), + _context->GetApi(), + _context->GetApi(), + _context->GetApi(), + _context->GetApi(), + _context->GetApi(), + _context->GetApi(), + _context->GetApi() } ); @@ -66,34 +65,41 @@ namespace Syn { AddWindow( ViewportView{}, ViewportViewModel{ - _context->GetRenderApi(), _context->GetSelectionApi(), _context->GetTransformApi(), - _context->GetSettingsApi(), _context->GetHierarchyApi() + _context->GetApi(), + _context->GetApi(), + _context->GetApi(), + _context->GetApi(), + _context->GetApi() } ); using SettingsWin = EditorWindow; AddWindow( SettingsView{}, - SettingsViewModel{ _context->GetSettingsApi() } + SettingsViewModel{ _context->GetApi() } ); using HierarchyWin = EditorWindow; AddWindow( HierarchyView{}, - HierarchyViewModel{ _context->GetHierarchyApi(), _context->GetSelectionApi(), _context->GetTagApi() } + HierarchyViewModel{ + _context->GetApi(), + _context->GetApi(), + _context->GetApi() + } ); using BenchmarkWin = EditorWindow; AddWindow( - BenchmarkView{}, + BenchmarkView{}, BenchmarkViewModel{} ); using LoggerWin = EditorWindow; AddWindow( LoggerView{}, - LoggerViewModel{ - _context->GetLoggerApi() + LoggerViewModel{ + _context->GetApi() } ); } diff --git a/SynapseEngine/Editor/Workspace/TextureWorkspace.cpp b/SynapseEngine/Editor/Workspace/TextureWorkspace.cpp index fe5d0465..f33693e3 100644 --- a/SynapseEngine/Editor/Workspace/TextureWorkspace.cpp +++ b/SynapseEngine/Editor/Workspace/TextureWorkspace.cpp @@ -18,30 +18,30 @@ namespace Syn { TextureWorkspace::TextureWorkspace(EditorContext* context, IconManager* iconManager, const std::string& assetPath) : _context(context), _iconManager(iconManager), _assetPath(assetPath) {} - void TextureWorkspace::Initialize() + void TextureWorkspace::Initialize() { using ContentBrowserWin = EditorWindow; AddWindow( ContentBrowserView{ _iconManager, SYN_ICON_FOLDER_OPEN " Content Browser###Content_Texture" }, - ContentBrowserViewModel{ _context->GetFileSystemApi(), _assetPath } + ContentBrowserViewModel{ _context->GetApi(), _assetPath } ); using TextureHierarchyWin = EditorWindow; AddWindow( TextureHierarchyView{}, - TextureHierarchyViewModel{ _context->GetTextureApi() } + TextureHierarchyViewModel{ _context->GetApi() } ); using TexturePropertiesWin = EditorWindow; AddWindow( TexturePropertiesView{}, - TexturePropertiesViewModel{ _context->GetTextureApi() } + TexturePropertiesViewModel{ _context->GetApi() } ); using TextureGraphWin = EditorWindow; AddWindow( TextureGraphView{}, - TextureGraphViewModel{ _context->GetTextureApi() } + TextureGraphViewModel{ _context->GetApi() } ); } } \ No newline at end of file diff --git a/SynapseEngine/EditorCore/Api/IAnimationApi.h b/SynapseEngine/EditorCore/Api/IAnimationApi.h index f84cde32..ddc1c81e 100644 --- a/SynapseEngine/EditorCore/Api/IAnimationApi.h +++ b/SynapseEngine/EditorCore/Api/IAnimationApi.h @@ -1,11 +1,12 @@ #pragma once +#include "IApi.h" #include "EditorCore/Types/EntityHandle.h" #include #include #include namespace Syn { - class IAnimationApi { + class IAnimationApi : public IApi { public: virtual ~IAnimationApi() = default; diff --git a/SynapseEngine/EditorCore/Api/IApi.h b/SynapseEngine/EditorCore/Api/IApi.h new file mode 100644 index 00000000..bde147cf --- /dev/null +++ b/SynapseEngine/EditorCore/Api/IApi.h @@ -0,0 +1,9 @@ +#pragma once + +namespace Syn +{ + class IApi { + public: + virtual ~IApi() = default; + }; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/Api/IBoxColliderApi.h b/SynapseEngine/EditorCore/Api/IBoxColliderApi.h index 12669d1e..98531478 100644 --- a/SynapseEngine/EditorCore/Api/IBoxColliderApi.h +++ b/SynapseEngine/EditorCore/Api/IBoxColliderApi.h @@ -1,9 +1,10 @@ #pragma once #include +#include "IApi.h" #include "EditorCore/Types/EntityHandle.h" namespace Syn { - class IBoxColliderApi { + class IBoxColliderApi : public IApi { public: virtual ~IBoxColliderApi() = default; diff --git a/SynapseEngine/EditorCore/Api/ICameraApi.h b/SynapseEngine/EditorCore/Api/ICameraApi.h index 83364409..2b638f10 100644 --- a/SynapseEngine/EditorCore/Api/ICameraApi.h +++ b/SynapseEngine/EditorCore/Api/ICameraApi.h @@ -1,8 +1,9 @@ #pragma once +#include "IApi.h" #include "EditorCore/Types/EntityHandle.h" namespace Syn { - class ICameraApi { + class ICameraApi : public IApi { public: virtual ~ICameraApi() = default; diff --git a/SynapseEngine/EditorCore/Api/ICapsuleColliderApi.h b/SynapseEngine/EditorCore/Api/ICapsuleColliderApi.h index e4602344..bf2f43b0 100644 --- a/SynapseEngine/EditorCore/Api/ICapsuleColliderApi.h +++ b/SynapseEngine/EditorCore/Api/ICapsuleColliderApi.h @@ -1,9 +1,10 @@ #pragma once +#include "IApi.h" #include #include "EditorCore/Types/EntityHandle.h" namespace Syn { - class ICapsuleColliderApi { + class ICapsuleColliderApi : public IApi { public: virtual ~ICapsuleColliderApi() = default; diff --git a/SynapseEngine/EditorCore/Api/IConvexColliderApi.h b/SynapseEngine/EditorCore/Api/IConvexColliderApi.h index 38c47ba3..310d0658 100644 --- a/SynapseEngine/EditorCore/Api/IConvexColliderApi.h +++ b/SynapseEngine/EditorCore/Api/IConvexColliderApi.h @@ -1,10 +1,11 @@ #pragma once +#include "IApi.h" #include #include "EditorCore/Types/EntityHandle.h" #include namespace Syn { - class IConvexColliderApi { + class IConvexColliderApi : public IApi { public: virtual ~IConvexColliderApi() = default; diff --git a/SynapseEngine/EditorCore/Api/IDirectionLightApi.h b/SynapseEngine/EditorCore/Api/IDirectionLightApi.h index b472318e..a5250fe7 100644 --- a/SynapseEngine/EditorCore/Api/IDirectionLightApi.h +++ b/SynapseEngine/EditorCore/Api/IDirectionLightApi.h @@ -1,9 +1,10 @@ #pragma once +#include "IApi.h" #include #include "EditorCore/Types/EntityHandle.h" namespace Syn { - class IDirectionLightApi { + class IDirectionLightApi : public IApi { public: virtual ~IDirectionLightApi() = default; diff --git a/SynapseEngine/EditorCore/Api/IFileDialogApi.h b/SynapseEngine/EditorCore/Api/IFileDialogApi.h index e03b067f..8735a5c2 100644 --- a/SynapseEngine/EditorCore/Api/IFileDialogApi.h +++ b/SynapseEngine/EditorCore/Api/IFileDialogApi.h @@ -1,4 +1,5 @@ #pragma once +#include "IApi.h" #include #include @@ -10,7 +11,7 @@ namespace Syn { std::string DefaultPath; }; - class IFileDialogApi { + class IFileDialogApi : public IApi { public: virtual ~IFileDialogApi() = default; diff --git a/SynapseEngine/EditorCore/Api/IFileSystemApi.h b/SynapseEngine/EditorCore/Api/IFileSystemApi.h index 00857997..fd406961 100644 --- a/SynapseEngine/EditorCore/Api/IFileSystemApi.h +++ b/SynapseEngine/EditorCore/Api/IFileSystemApi.h @@ -1,11 +1,12 @@ #pragma once +#include "IApi.h" #include #include #include "EditorCore/Types/FileEntry.h" namespace Syn { - class IFileSystemApi { + class IFileSystemApi : public IApi { public: virtual ~IFileSystemApi() = default; diff --git a/SynapseEngine/EditorCore/Api/IHierarchyApi.h b/SynapseEngine/EditorCore/Api/IHierarchyApi.h index bbac6c69..1b670e2e 100644 --- a/SynapseEngine/EditorCore/Api/IHierarchyApi.h +++ b/SynapseEngine/EditorCore/Api/IHierarchyApi.h @@ -1,10 +1,11 @@ #pragma once +#include "IApi.h" #include #include #include "EditorCore/Types/EntityHandle.h" namespace Syn { - class IHierarchyApi { + class IHierarchyApi : public IApi { public: virtual ~IHierarchyApi() = default; diff --git a/SynapseEngine/EditorCore/Api/ILoggerApi.h b/SynapseEngine/EditorCore/Api/ILoggerApi.h index 33811858..79cd4950 100644 --- a/SynapseEngine/EditorCore/Api/ILoggerApi.h +++ b/SynapseEngine/EditorCore/Api/ILoggerApi.h @@ -1,9 +1,10 @@ #pragma once +#include "IApi.h" #include "Engine/Logger/LogMessage.h" #include namespace Syn { - class ILoggerApi { + class ILoggerApi : public IApi { public: virtual ~ILoggerApi() = default; virtual const std::vector& GetLogs() const = 0; diff --git a/SynapseEngine/EditorCore/Api/IMaterialApi.h b/SynapseEngine/EditorCore/Api/IMaterialApi.h index 1c789444..10212a91 100644 --- a/SynapseEngine/EditorCore/Api/IMaterialApi.h +++ b/SynapseEngine/EditorCore/Api/IMaterialApi.h @@ -1,4 +1,5 @@ #pragma once +#include "IApi.h" #include #include #include @@ -15,7 +16,7 @@ namespace Syn std::string path; }; - class IMaterialApi { + class IMaterialApi : public IApi { public: virtual ~IMaterialApi() = default; diff --git a/SynapseEngine/EditorCore/Api/IMaterialOverrideApi.h b/SynapseEngine/EditorCore/Api/IMaterialOverrideApi.h index 3a49bfe7..7bc3ac1e 100644 --- a/SynapseEngine/EditorCore/Api/IMaterialOverrideApi.h +++ b/SynapseEngine/EditorCore/Api/IMaterialOverrideApi.h @@ -1,11 +1,12 @@ #pragma once +#include "IApi.h" #include "EditorCore/Types/EntityHandle.h" #include #include #include namespace Syn { - class IMaterialOverrideApi { + class IMaterialOverrideApi : public IApi { public: virtual ~IMaterialOverrideApi() = default; diff --git a/SynapseEngine/EditorCore/Api/IMeshColliderApi.h b/SynapseEngine/EditorCore/Api/IMeshColliderApi.h index 563271a2..3c2568db 100644 --- a/SynapseEngine/EditorCore/Api/IMeshColliderApi.h +++ b/SynapseEngine/EditorCore/Api/IMeshColliderApi.h @@ -1,10 +1,11 @@ #pragma once #include +#include "IApi.h" #include "EditorCore/Types/EntityHandle.h" #include namespace Syn { - class IMeshColliderApi { + class IMeshColliderApi : public IApi { public: virtual ~IMeshColliderApi() = default; diff --git a/SynapseEngine/EditorCore/Api/IModelApi.h b/SynapseEngine/EditorCore/Api/IModelApi.h index 97be4723..4a7415a1 100644 --- a/SynapseEngine/EditorCore/Api/IModelApi.h +++ b/SynapseEngine/EditorCore/Api/IModelApi.h @@ -2,6 +2,7 @@ #include #include #include +#include "IApi.h" #include "Engine/Mesh/Data/Cpu/CpuModelData.h" namespace Syn @@ -15,7 +16,7 @@ namespace Syn std::string path; }; - class IModelApi { + class IModelApi : public IApi { public: virtual ~IModelApi() = default; diff --git a/SynapseEngine/EditorCore/Api/IModelComponentApi.h b/SynapseEngine/EditorCore/Api/IModelComponentApi.h index 031a9622..d1f1266a 100644 --- a/SynapseEngine/EditorCore/Api/IModelComponentApi.h +++ b/SynapseEngine/EditorCore/Api/IModelComponentApi.h @@ -1,11 +1,12 @@ #pragma once +#include "IApi.h" #include "EditorCore/Types/EntityHandle.h" #include #include #include namespace Syn { - class IModelComponentApi { + class IModelComponentApi : public IApi { public: virtual ~IModelComponentApi() = default; diff --git a/SynapseEngine/EditorCore/Api/IPointLightApi.h b/SynapseEngine/EditorCore/Api/IPointLightApi.h index 6fff0362..1a1e43c0 100644 --- a/SynapseEngine/EditorCore/Api/IPointLightApi.h +++ b/SynapseEngine/EditorCore/Api/IPointLightApi.h @@ -1,9 +1,10 @@ #pragma once #include +#include "IApi.h" #include "EditorCore/Types/EntityHandle.h" namespace Syn { - class IPointLightApi { + class IPointLightApi : public IApi { public: virtual ~IPointLightApi() = default; diff --git a/SynapseEngine/EditorCore/Api/IPreviewApi.h b/SynapseEngine/EditorCore/Api/IPreviewApi.h new file mode 100644 index 00000000..da87bc8c --- /dev/null +++ b/SynapseEngine/EditorCore/Api/IPreviewApi.h @@ -0,0 +1,28 @@ +#pragma once +#include +#include +#include + +#include "IApi.h" +#include "EditorCore/Types/TextureHandle.h" +#include "Engine/Manager/PreviewManager.h" + +namespace Syn { + + struct PreviewItemData { + uint32_t resourceId; + glm::vec2 uv0; + glm::vec2 uv1; + }; + + class IPreviewApi : public IApi { + public: + virtual ~IPreviewApi() = default; + + virtual TextureHandle GetAtlasHandle() = 0; + virtual bool GetPreviewUVs(PreviewResourceType type, uint32_t resourceId, glm::vec2& outUv0, glm::vec2& outUv1) const = 0; + virtual bool HasPreview(PreviewResourceType type, uint32_t resourceId) const = 0; + virtual void RequestPreview(PreviewResourceType type, uint32_t resourceId) = 0; + virtual std::vector GetAllPreviews(PreviewResourceType type) const = 0; + }; +} \ No newline at end of file diff --git a/SynapseEngine/EditorCore/Api/IRenderApi.h b/SynapseEngine/EditorCore/Api/IRenderApi.h index 077f4ee1..776e2e24 100644 --- a/SynapseEngine/EditorCore/Api/IRenderApi.h +++ b/SynapseEngine/EditorCore/Api/IRenderApi.h @@ -4,9 +4,10 @@ #include #include "EditorCore/Types/TextureHandle.h" #include "EditorCore/Types/EntityHandle.h" +#include "IApi.h" namespace Syn { - class IRenderApi { + class IRenderApi : public IApi { public: virtual ~IRenderApi() = default; diff --git a/SynapseEngine/EditorCore/Api/IRigidBodyApi.h b/SynapseEngine/EditorCore/Api/IRigidBodyApi.h index abae547a..0dbb9e8b 100644 --- a/SynapseEngine/EditorCore/Api/IRigidBodyApi.h +++ b/SynapseEngine/EditorCore/Api/IRigidBodyApi.h @@ -1,10 +1,11 @@ #pragma once #include "EditorCore/Types/EntityHandle.h" #include "Engine/Physics/PhysicsTypes.h" +#include "IApi.h" #include namespace Syn { - class IRigidBodyApi { + class IRigidBodyApi : public IApi { public: virtual ~IRigidBodyApi() = default; diff --git a/SynapseEngine/EditorCore/Api/ISceneApi.h b/SynapseEngine/EditorCore/Api/ISceneApi.h index 70ce69cc..072e6e21 100644 --- a/SynapseEngine/EditorCore/Api/ISceneApi.h +++ b/SynapseEngine/EditorCore/Api/ISceneApi.h @@ -1,8 +1,9 @@ #pragma once +#include "IApi.h" #include namespace Syn { - class ISceneApi { + class ISceneApi : public IApi { public: virtual ~ISceneApi() = default; diff --git a/SynapseEngine/EditorCore/Api/ISelectionApi.h b/SynapseEngine/EditorCore/Api/ISelectionApi.h index f4e8937e..ce5f9011 100644 --- a/SynapseEngine/EditorCore/Api/ISelectionApi.h +++ b/SynapseEngine/EditorCore/Api/ISelectionApi.h @@ -1,8 +1,9 @@ #pragma once +#include "IApi.h" #include "EditorCore/Types/EntityHandle.h" namespace Syn { - class ISelectionApi { + class ISelectionApi : public IApi { public: virtual ~ISelectionApi() = default; virtual EntityID GetSelectedEntity() const = 0; diff --git a/SynapseEngine/EditorCore/Api/ISettingsApi.h b/SynapseEngine/EditorCore/Api/ISettingsApi.h index f762e062..72d25a28 100644 --- a/SynapseEngine/EditorCore/Api/ISettingsApi.h +++ b/SynapseEngine/EditorCore/Api/ISettingsApi.h @@ -1,11 +1,12 @@ #pragma once +#include "IApi.h" #include "Engine/Scene/Settings/SceneSettings.h" #include #include #include namespace Syn { - class ISettingsApi { + class ISettingsApi : public IApi { public: virtual ~ISettingsApi() = default; diff --git a/SynapseEngine/EditorCore/Api/ISphereColliderApi.h b/SynapseEngine/EditorCore/Api/ISphereColliderApi.h index 4127cc2b..8e6deaca 100644 --- a/SynapseEngine/EditorCore/Api/ISphereColliderApi.h +++ b/SynapseEngine/EditorCore/Api/ISphereColliderApi.h @@ -1,9 +1,10 @@ #pragma once #include +#include "IApi.h" #include "EditorCore/Types/EntityHandle.h" namespace Syn { - class ISphereColliderApi { + class ISphereColliderApi : public IApi { public: virtual ~ISphereColliderApi() = default; diff --git a/SynapseEngine/EditorCore/Api/ISpotLightApi.h b/SynapseEngine/EditorCore/Api/ISpotLightApi.h index f23b3f07..bd554c9f 100644 --- a/SynapseEngine/EditorCore/Api/ISpotLightApi.h +++ b/SynapseEngine/EditorCore/Api/ISpotLightApi.h @@ -1,9 +1,10 @@ #pragma once #include +#include "IApi.h" #include "EditorCore/Types/EntityHandle.h" namespace Syn { - class ISpotLightApi { + class ISpotLightApi : public IApi { public: virtual ~ISpotLightApi() = default; diff --git a/SynapseEngine/EditorCore/Api/ITagApi.h b/SynapseEngine/EditorCore/Api/ITagApi.h index 368374ae..b8f12499 100644 --- a/SynapseEngine/EditorCore/Api/ITagApi.h +++ b/SynapseEngine/EditorCore/Api/ITagApi.h @@ -1,9 +1,10 @@ #pragma once #include +#include "IApi.h" #include "EditorCore/Types/EntityHandle.h" namespace Syn { - class ITagApi { + class ITagApi : public IApi { public: virtual ~ITagApi() = default; diff --git a/SynapseEngine/EditorCore/Api/ITextureApi.h b/SynapseEngine/EditorCore/Api/ITextureApi.h index c0fab599..303001ee 100644 --- a/SynapseEngine/EditorCore/Api/ITextureApi.h +++ b/SynapseEngine/EditorCore/Api/ITextureApi.h @@ -1,6 +1,7 @@ #pragma once #include #include +#include "IApi.h" #include "Engine/Image/Data/Cpu/CpuTextureData.h" #include "EditorCore/Types/TextureHandle.h" @@ -20,7 +21,7 @@ namespace Syn { std::string name; }; - class ITextureApi { + class ITextureApi : public IApi { public: virtual ~ITextureApi() = default; diff --git a/SynapseEngine/EditorCore/Api/ITransformApi.h b/SynapseEngine/EditorCore/Api/ITransformApi.h index b6545f23..b3f4ce53 100644 --- a/SynapseEngine/EditorCore/Api/ITransformApi.h +++ b/SynapseEngine/EditorCore/Api/ITransformApi.h @@ -1,9 +1,10 @@ #pragma once #include +#include "IApi.h" #include "EditorCore/Types/EntityHandle.h" namespace Syn { - class ITransformApi { + class ITransformApi : public IApi { public: virtual ~ITransformApi() = default; diff --git a/SynapseEngine/EditorCore/ViewModels/ContentBrowser/ContentBrowserState.h b/SynapseEngine/EditorCore/ViewModels/ContentBrowser/ContentBrowserState.h index 6c2b196b..1d7b58a1 100644 --- a/SynapseEngine/EditorCore/ViewModels/ContentBrowser/ContentBrowserState.h +++ b/SynapseEngine/EditorCore/ViewModels/ContentBrowser/ContentBrowserState.h @@ -9,7 +9,7 @@ namespace Syn { std::string selectedPath; std::vector currentEntries; - float thumbnailSize = 54.0f; + float thumbnailSize = 100.0f; bool isLoading = false; }; } \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyState.h b/SynapseEngine/EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyState.h index d961486a..ace94a28 100644 --- a/SynapseEngine/EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyState.h +++ b/SynapseEngine/EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyState.h @@ -2,17 +2,24 @@ #include #include #include +#include +#include "EditorCore/Types/TextureHandle.h" namespace Syn { struct MaterialNode { uint32_t id; std::string name; + std::string path; std::string icon; + glm::vec2 uv0{ 0.0f, 0.0f }; + glm::vec2 uv1{ 1.0f, 1.0f }; + bool hasPreview = false; }; struct MaterialHierarchyState { std::vector filteredNodes; uint32_t selectedMaterial = 0xFFFFFFFF; std::string searchQuery = ""; + TextureHandle atlasHandle = InvalidTextureHandle; }; } \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyViewModel.cpp index 39288189..b4122099 100644 --- a/SynapseEngine/EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyViewModel.cpp +++ b/SynapseEngine/EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyViewModel.cpp @@ -3,8 +3,8 @@ #include namespace Syn { - MaterialHierarchyViewModel::MaterialHierarchyViewModel(IMaterialApi* materialApi) - : _materialApi(materialApi) + MaterialHierarchyViewModel::MaterialHierarchyViewModel(IMaterialApi* materialApi, IPreviewApi* previewApi) + : _materialApi(materialApi), _previewApi(previewApi) {} void MaterialHierarchyViewModel::SyncWithEngine() { @@ -22,6 +22,18 @@ namespace Syn { if (_state.selectedMaterial != currentSelection) { _state.selectedMaterial = currentSelection; } + + if (_previewApi) { + _state.atlasHandle = _previewApi->GetAtlasHandle(); + + for (auto& node : _state.filteredNodes) { + if (!node.hasPreview) { + if (_previewApi->GetPreviewUVs(PreviewResourceType::Material, node.id, node.uv0, node.uv1)) { + node.hasPreview = true; + } + } + } + } } void MaterialHierarchyViewModel::Dispatch(const MaterialHierarchyIntent& intent) { @@ -63,7 +75,19 @@ namespace Syn { MaterialNode node; node.id = mat.id; node.name = mat.name; + node.path = mat.path; node.icon = SYN_ICON_BRUSH; + + if (_previewApi) { + if (_previewApi->GetPreviewUVs(PreviewResourceType::Material, mat.id, node.uv0, node.uv1)) { + node.hasPreview = true; + } + else { + node.hasPreview = false; + _previewApi->RequestPreview(PreviewResourceType::Material, mat.id); + } + } + _state.filteredNodes.push_back(node); } } diff --git a/SynapseEngine/EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyViewModel.h b/SynapseEngine/EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyViewModel.h index 0e53fe72..205f2cca 100644 --- a/SynapseEngine/EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyViewModel.h +++ b/SynapseEngine/EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyViewModel.h @@ -2,12 +2,13 @@ #include "EditorCore/ViewModels/IViewModel.h" #include "MaterialHierarchyState.h" #include "MaterialHierarchyIntent.h" +#include "EditorCore/Api/IPreviewApi.h" #include "EditorCore/Api/IMaterialApi.h" namespace Syn { class MaterialHierarchyViewModel : public IViewModel { public: - MaterialHierarchyViewModel(IMaterialApi* materialApi); + MaterialHierarchyViewModel(IMaterialApi* materialApi, IPreviewApi* previewApi); ~MaterialHierarchyViewModel() override = default; const MaterialHierarchyState& GetState() const override { return _state; } @@ -18,6 +19,8 @@ namespace Syn { void RebuildList(); private: IMaterialApi* _materialApi = nullptr; + IPreviewApi* _previewApi = nullptr; + MaterialHierarchyState _state; bool _isDirty = true; diff --git a/SynapseEngine/Engine/Engine.cpp b/SynapseEngine/Engine/Engine.cpp index 10e80529..70937ec2 100644 --- a/SynapseEngine/Engine/Engine.cpp +++ b/SynapseEngine/Engine/Engine.cpp @@ -387,4 +387,9 @@ namespace Syn AnimationManager* Engine::GetAnimationManager() { return ServiceLocator::GetAnimationManager(); } + + PreviewManager* Engine::GetPreviewManager() + { + return ServiceLocator::GetPreviewManager(); + } } \ No newline at end of file diff --git a/SynapseEngine/Engine/Engine.h b/SynapseEngine/Engine/Engine.h index 92fe96f5..f91e88ae 100644 --- a/SynapseEngine/Engine/Engine.h +++ b/SynapseEngine/Engine/Engine.h @@ -31,6 +31,7 @@ namespace Syn { class AnimationManager; class IRenderStatCollector; class FrameStatisticsManager; + class PreviewManager; } namespace Syn @@ -68,6 +69,7 @@ namespace Syn ImageManager* GetImageManager(); ModelManager* GetModelManager(); AnimationManager* GetAnimationManager(); + PreviewManager* GetPreviewManager(); std::shared_ptr GetMemorySink() const { return _memorySink; } private: void Init(const EngineInitParams& params); diff --git a/SynapseEngine/Engine/Manager/PreviewManager.cpp b/SynapseEngine/Engine/Manager/PreviewManager.cpp index ca38eedd..9e900ed7 100644 --- a/SynapseEngine/Engine/Manager/PreviewManager.cpp +++ b/SynapseEngine/Engine/Manager/PreviewManager.cpp @@ -134,4 +134,20 @@ namespace Syn { outUv0 = glm::vec2(col * normalizedTileSize, row * normalizedTileSize); outUv1 = glm::vec2((col + 1) * normalizedTileSize, (row + 1) * normalizedTileSize); } + + std::vector PreviewManager::GetActiveResources(PreviewResourceType type) const { + std::lock_guard lock(_mutex); + std::vector result; + + uint64_t typePrefix = static_cast(type) << 32; + uint64_t typeMask = 0xFFFFFFFF00000000; + + for (const auto& [id, tileIndex] : _resourceToTile) { + if ((id & typeMask) == typePrefix) { + result.push_back(static_cast(id & 0xFFFFFFFF)); + } + } + + return result; + } } \ No newline at end of file diff --git a/SynapseEngine/Engine/Manager/PreviewManager.h b/SynapseEngine/Engine/Manager/PreviewManager.h index c7a5ca7c..74c53da1 100644 --- a/SynapseEngine/Engine/Manager/PreviewManager.h +++ b/SynapseEngine/Engine/Manager/PreviewManager.h @@ -36,6 +36,8 @@ namespace Syn { void GetNormalizedUVs(PreviewResourceType type, uint32_t resourceId, glm::vec2& outUv0, glm::vec2& outUv1) const; Vk::Image* GetAtlasImage() const { return _atlasImage.get(); } + uint32_t GetResolution() const { return _resolution; } + std::vector GetActiveResources(PreviewResourceType type) const; private: void CreateOrResizeAtlas(uint32_t newResolution); uint64_t GetUniqueId(PreviewResourceType type, uint32_t resourceId) const; From fad5ba6d8daa1c0271a6a2ef1785b641003fdb4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20P=C3=A9ter?= Date: Thu, 9 Jul 2026 19:14:18 +0200 Subject: [PATCH 42/51] Implemented bloom simulation on preview material textures. --- SynapseEngine/Engine/Engine.cpp | 1 + .../Engine/Manager/PreviewManager.cpp | 40 +- SynapseEngine/Engine/Manager/PreviewManager.h | 11 +- .../Engine/Manager/ResourceManager.cpp | 2 +- .../Preview/MaterialPreviewBloomPass.cpp | 358 ++++++++++++++++++ .../Passes/Preview/MaterialPreviewBloomPass.h | 28 ++ .../Passes/Preview/MaterialPreviewPass.cpp | 2 +- .../Preview/PreviewPostTransitionPass.cpp | 22 +- .../Engine/Render/RendererFactory.cpp | 2 + .../Passes/Preview/MaterialPreview.frag | 2 +- 10 files changed, 456 insertions(+), 12 deletions(-) create mode 100644 SynapseEngine/Engine/Render/Passes/Preview/MaterialPreviewBloomPass.cpp create mode 100644 SynapseEngine/Engine/Render/Passes/Preview/MaterialPreviewBloomPass.h diff --git a/SynapseEngine/Engine/Engine.cpp b/SynapseEngine/Engine/Engine.cpp index 70937ec2..ac97eb82 100644 --- a/SynapseEngine/Engine/Engine.cpp +++ b/SynapseEngine/Engine/Engine.cpp @@ -112,6 +112,7 @@ namespace Syn _sceneManager->Finish(); ServiceLocator::GetCpuProfiler()->ResolveFrame(currentFrame); + ServiceLocator::GetPreviewManager()->ClearAllDirtyResources(); AdvanceFrameIndex(); } diff --git a/SynapseEngine/Engine/Manager/PreviewManager.cpp b/SynapseEngine/Engine/Manager/PreviewManager.cpp index 9e900ed7..0e5fed6e 100644 --- a/SynapseEngine/Engine/Manager/PreviewManager.cpp +++ b/SynapseEngine/Engine/Manager/PreviewManager.cpp @@ -8,6 +8,30 @@ namespace Syn { : _resolution(initialResolution), _tileSize(tileSize), _tilesPerRow(0) { CreateOrResizeAtlas(_resolution); + + Vk::ImageConfig colorConfig{}; + colorConfig.width = _tileSize; + colorConfig.height = _tileSize; + colorConfig.type = VK_IMAGE_TYPE_2D; + colorConfig.format = VK_FORMAT_R16G16B16A16_SFLOAT; + colorConfig.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; + colorConfig.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + colorConfig.AddView(Vk::ImageViewNames::Default, { .viewType = VK_IMAGE_VIEW_TYPE_2D }); + _scratchColorImage = std::make_unique(colorConfig); + + Vk::ImageConfig bloomConfig{}; + bloomConfig.width = _tileSize; + bloomConfig.height = _tileSize; + bloomConfig.type = VK_IMAGE_TYPE_2D; + bloomConfig.format = VK_FORMAT_R16G16B16A16_SFLOAT; + bloomConfig.usage = VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; + bloomConfig.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + bloomConfig.generateMipMaps = true; + bloomConfig.AddView(Vk::ImageViewNames::Default, { + .viewType = VK_IMAGE_VIEW_TYPE_2D, + .perMipViews = true + }); + _scratchBloomImage = std::make_unique(bloomConfig); } uint64_t PreviewManager::GetUniqueId(PreviewResourceType type, uint32_t resourceId) const { @@ -84,14 +108,22 @@ namespace Syn { _dirtyResources[type].insert(resourceId); } - std::vector PreviewManager::ConsumeDirtyResources(PreviewResourceType type) { + std::vector PreviewManager::GetDirtyResources(PreviewResourceType type) { std::lock_guard lock(_mutex); auto& dirtySet = _dirtyResources[type]; + return std::vector(dirtySet.begin(), dirtySet.end()); + } - std::vector result(dirtySet.begin(), dirtySet.end()); - dirtySet.clear(); + void PreviewManager::ClearDirtyResources(PreviewResourceType type) { + std::lock_guard lock(_mutex); + _dirtyResources[type].clear(); + } - return result; + void PreviewManager::ClearAllDirtyResources() { + std::lock_guard lock(_mutex); + for (auto& [type, set] : _dirtyResources) { + set.clear(); + } } void PreviewManager::GetViewportAndScissor(PreviewResourceType type, uint32_t resourceId, VkViewport& outViewport, VkRect2D& outScissor) const { diff --git a/SynapseEngine/Engine/Manager/PreviewManager.h b/SynapseEngine/Engine/Manager/PreviewManager.h index 74c53da1..8b6f8087 100644 --- a/SynapseEngine/Engine/Manager/PreviewManager.h +++ b/SynapseEngine/Engine/Manager/PreviewManager.h @@ -22,7 +22,7 @@ namespace Syn { public: using AtlasResizedCallback = std::function; - PreviewManager(uint32_t initialResolution = 4096, uint32_t tileSize = 256); + PreviewManager(uint32_t initialResolution = 1024, uint32_t tileSize = 64); virtual ~PreviewManager() = default; bool AllocateTile(PreviewResourceType type, uint32_t resourceId); @@ -30,12 +30,17 @@ namespace Syn { bool HasTile(PreviewResourceType type, uint32_t resourceId) const; void MarkDirty(PreviewResourceType type, uint32_t resourceId); - std::vector ConsumeDirtyResources(PreviewResourceType type); + std::vector GetDirtyResources(PreviewResourceType type); + void ClearDirtyResources(PreviewResourceType type); + void ClearAllDirtyResources(); void GetViewportAndScissor(PreviewResourceType type, uint32_t resourceId, VkViewport& outViewport, VkRect2D& outScissor) const; void GetNormalizedUVs(PreviewResourceType type, uint32_t resourceId, glm::vec2& outUv0, glm::vec2& outUv1) const; Vk::Image* GetAtlasImage() const { return _atlasImage.get(); } + Vk::Image* GetScratchColorImage() const { return _scratchColorImage.get(); } + Vk::Image* GetScratchBloomImage() const { return _scratchBloomImage.get(); } + uint32_t GetResolution() const { return _resolution; } std::vector GetActiveResources(PreviewResourceType type) const; private: @@ -49,6 +54,8 @@ namespace Syn { uint32_t _tilesPerRow; std::unique_ptr _atlasImage; + std::unique_ptr _scratchColorImage; + std::unique_ptr _scratchBloomImage; std::queue _freeTiles; std::unordered_map _resourceToTile; diff --git a/SynapseEngine/Engine/Manager/ResourceManager.cpp b/SynapseEngine/Engine/Manager/ResourceManager.cpp index 95a2a411..18c889e7 100644 --- a/SynapseEngine/Engine/Manager/ResourceManager.cpp +++ b/SynapseEngine/Engine/Manager/ResourceManager.cpp @@ -54,7 +54,7 @@ namespace Syn { } void ResourceManager::InitPreviewManager() { - _previewManager = std::make_unique(1024, 64); + _previewManager = std::make_unique(2048, 128); ServiceLocator::ProvidePreviewManager(_previewManager.get()); } diff --git a/SynapseEngine/Engine/Render/Passes/Preview/MaterialPreviewBloomPass.cpp b/SynapseEngine/Engine/Render/Passes/Preview/MaterialPreviewBloomPass.cpp new file mode 100644 index 00000000..d53e548a --- /dev/null +++ b/SynapseEngine/Engine/Render/Passes/Preview/MaterialPreviewBloomPass.cpp @@ -0,0 +1,358 @@ +#include "MaterialPreviewBloomPass.h" +#include "Engine/ServiceLocator.h" +#include "Engine/Manager/ShaderManager.h" +#include "Engine/Manager/PreviewManager.h" +#include "Engine/Image/ImageManager.h" +#include "Engine/Scene/Scene.h" +#include "Engine/Vk/Image/ImageUtils.h" +#include "Engine/Render/ComputeGroupSize.h" +#include "Engine/Vk/Rendering/PushConstant.h" +#include "Engine/Image/SamplerNames.h" +#include "Engine/Vk/Image/ImageViewNames.h" + +#include "Engine/Shaders/Includes/PushConstants/BloomPrefilterPC.glsl" +#include "Engine/Shaders/Includes/PushConstants/BloomDownSamplePC.glsl" +#include "Engine/Shaders/Includes/PushConstants/BloomUpSamplePC.glsl" +#include "Engine/Shaders/Includes/PushConstants/BloomCompositePC.glsl" + +namespace Syn { + + bool MaterialPreviewBloomPass::ShouldExecute(const RenderContext& context) const { + return context.scene->GetSettings()->postProcess.enableBloom + && !context.scene->GetSettings()->debug.enableDebugVisibility; + } + + void MaterialPreviewBloomPass::Initialize() { + auto sm = ServiceLocator::GetShaderManager(); + _prefilterProgram = sm->CreateProgram("PrevBloomPrefilter", { ShaderNames::BloomPrefilter }); + _downsampleProgram = sm->CreateProgram("PrevBloomDown", { ShaderNames::BloomDownsample }); + _upsampleProgram = sm->CreateProgram("PrevBloomUp", { ShaderNames::BloomUpsample }); + _compositeProgram = sm->CreateProgram("PrevBloomComp", { ShaderNames::BloomComposite }); + } + + void MaterialPreviewBloomPass::Execute(const RenderContext& context) { + auto pm = ServiceLocator::GetPreviewManager(); + + std::vector dirtyMaterials = pm->GetDirtyResources(PreviewResourceType::Material); + if (dirtyMaterials.empty()) return; + + auto atlas = pm->GetAtlasImage(); + auto scratchColor = pm->GetScratchColorImage(); + auto scratchBloom = pm->GetScratchBloomImage(); + + for (uint32_t matId : dirtyMaterials) { + + VkViewport vp; VkRect2D scissor; + pm->GetViewportAndScissor(PreviewResourceType::Material, matId, vp, scissor); + + // Copy Atlas Region to Scratch Color Texture + Vk::ImageBarrierInfo atlasToSrc{}; + atlasToSrc.image = atlas->Handle(); + atlasToSrc.srcStage = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + atlasToSrc.srcAccess = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT; + atlasToSrc.dstStage = VK_PIPELINE_STAGE_2_TRANSFER_BIT; + atlasToSrc.dstAccess = VK_ACCESS_2_TRANSFER_READ_BIT; + atlasToSrc.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + atlasToSrc.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + atlasToSrc.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + atlasToSrc.baseMipLevel = 0; + atlasToSrc.levelCount = 1; + atlasToSrc.baseArrayLayer = 0; + atlasToSrc.layerCount = 1; + Vk::ImageUtils::InsertBarrier(context.cmd, atlasToSrc); + + Vk::ImageBarrierInfo scratchToDst{}; + scratchToDst.image = scratchColor->Handle(); + scratchToDst.srcStage = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT; + scratchToDst.srcAccess = VK_ACCESS_2_SHADER_READ_BIT | VK_ACCESS_2_SHADER_WRITE_BIT; + scratchToDst.dstStage = VK_PIPELINE_STAGE_2_TRANSFER_BIT; + scratchToDst.dstAccess = VK_ACCESS_2_TRANSFER_WRITE_BIT; + scratchToDst.oldLayout = VK_IMAGE_LAYOUT_GENERAL; + scratchToDst.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + scratchToDst.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + scratchToDst.baseMipLevel = 0; + scratchToDst.levelCount = 1; + scratchToDst.baseArrayLayer = 0; + scratchToDst.layerCount = 1; + Vk::ImageUtils::InsertBarrier(context.cmd, scratchToDst); + + VkImageCopy copyRegion{}; + copyRegion.srcSubresource = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1 }; + copyRegion.srcOffset = { scissor.offset.x, scissor.offset.y, 0 }; + copyRegion.dstSubresource = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1 }; + copyRegion.dstOffset = { 0, 0, 0 }; + copyRegion.extent = { (uint32_t)scissor.extent.width, (uint32_t)scissor.extent.height, 1 }; + vkCmdCopyImage(context.cmd, atlas->Handle(), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, scratchColor->Handle(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©Region); + + // Bloom Prefilter + Vk::ImageBarrierInfo scratchToRead{}; + scratchToRead.image = scratchColor->Handle(); + scratchToRead.srcStage = VK_PIPELINE_STAGE_2_TRANSFER_BIT; + scratchToRead.srcAccess = VK_ACCESS_2_TRANSFER_WRITE_BIT; + scratchToRead.dstStage = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT; + scratchToRead.dstAccess = VK_ACCESS_2_SHADER_READ_BIT; + scratchToRead.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + scratchToRead.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + scratchToRead.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + scratchToRead.baseMipLevel = 0; + scratchToRead.levelCount = 1; + scratchToRead.baseArrayLayer = 0; + scratchToRead.layerCount = 1; + Vk::ImageUtils::InsertBarrier(context.cmd, scratchToRead); + + Vk::ImageBarrierInfo bloomSync{}; + bloomSync.image = scratchBloom->Handle(); + bloomSync.srcStage = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT; + bloomSync.srcAccess = VK_ACCESS_2_SHADER_READ_BIT | VK_ACCESS_2_SHADER_WRITE_BIT; + bloomSync.dstStage = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT; + bloomSync.dstAccess = VK_ACCESS_2_SHADER_WRITE_BIT; + bloomSync.oldLayout = VK_IMAGE_LAYOUT_GENERAL; + bloomSync.newLayout = VK_IMAGE_LAYOUT_GENERAL; + bloomSync.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + bloomSync.baseMipLevel = 0; + bloomSync.levelCount = 1; + bloomSync.baseArrayLayer = 0; + bloomSync.layerCount = 1; + Vk::ImageUtils::InsertBarrier(context.cmd, bloomSync); + + DispatchPrefilter(context, scratchColor, scratchBloom); + + // Bloom down and up sample simulation + DispatchDownsample(context, scratchBloom); + DispatchUpsample(context, scratchBloom); + + // Bloom scratch composite pass + Vk::ImageBarrierInfo scratchToGen{}; + scratchToGen.image = scratchColor->Handle(); + scratchToGen.srcStage = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT; + scratchToGen.srcAccess = VK_ACCESS_2_SHADER_READ_BIT; + scratchToGen.dstStage = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT; + scratchToGen.dstAccess = VK_ACCESS_2_SHADER_WRITE_BIT; + scratchToGen.oldLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + scratchToGen.newLayout = VK_IMAGE_LAYOUT_GENERAL; + scratchToGen.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + scratchToGen.baseMipLevel = 0; + scratchToGen.levelCount = 1; + scratchToGen.baseArrayLayer = 0; + scratchToGen.layerCount = 1; + Vk::ImageUtils::InsertBarrier(context.cmd, scratchToGen); + + DispatchComposite(context, scratchColor, scratchBloom); + + // Copy Scratch to Atlas Region + Vk::ImageBarrierInfo scratchToSrc{}; + scratchToSrc.image = scratchColor->Handle(); + scratchToSrc.srcStage = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT; + scratchToSrc.srcAccess = VK_ACCESS_2_SHADER_WRITE_BIT; + scratchToSrc.dstStage = VK_PIPELINE_STAGE_2_TRANSFER_BIT; + scratchToSrc.dstAccess = VK_ACCESS_2_TRANSFER_READ_BIT; + scratchToSrc.oldLayout = VK_IMAGE_LAYOUT_GENERAL; + scratchToSrc.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + scratchToSrc.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + scratchToSrc.baseMipLevel = 0; + scratchToSrc.levelCount = 1; + scratchToSrc.baseArrayLayer = 0; + scratchToSrc.layerCount = 1; + Vk::ImageUtils::InsertBarrier(context.cmd, scratchToSrc); + + Vk::ImageBarrierInfo atlasToDst{}; + atlasToDst.image = atlas->Handle(); + atlasToDst.srcStage = VK_PIPELINE_STAGE_2_TRANSFER_BIT; + atlasToDst.srcAccess = VK_ACCESS_2_TRANSFER_READ_BIT; + atlasToDst.dstStage = VK_PIPELINE_STAGE_2_TRANSFER_BIT; + atlasToDst.dstAccess = VK_ACCESS_2_TRANSFER_WRITE_BIT; + atlasToDst.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + atlasToDst.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + atlasToDst.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + atlasToDst.baseMipLevel = 0; + atlasToDst.levelCount = 1; + atlasToDst.baseArrayLayer = 0; + atlasToDst.layerCount = 1; + Vk::ImageUtils::InsertBarrier(context.cmd, atlasToDst); + + VkImageCopy copyBackRegion{}; + copyBackRegion.srcSubresource = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1 }; + copyBackRegion.srcOffset = { 0, 0, 0 }; + copyBackRegion.dstSubresource = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1 }; + copyBackRegion.dstOffset = { scissor.offset.x, scissor.offset.y, 0 }; + copyBackRegion.extent = { (uint32_t)scissor.extent.width, (uint32_t)scissor.extent.height, 1 }; + vkCmdCopyImage(context.cmd, scratchColor->Handle(), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, atlas->Handle(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©BackRegion); + + // Post transition to prepare for rendering or iteration + Vk::ImageBarrierInfo atlasToColor{}; + atlasToColor.image = atlas->Handle(); + atlasToColor.srcStage = VK_PIPELINE_STAGE_2_TRANSFER_BIT; + atlasToColor.srcAccess = VK_ACCESS_2_TRANSFER_WRITE_BIT; + atlasToColor.dstStage = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + atlasToColor.dstAccess = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT; + atlasToColor.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + atlasToColor.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + atlasToColor.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + atlasToColor.baseMipLevel = 0; + atlasToColor.levelCount = 1; + atlasToColor.baseArrayLayer = 0; + atlasToColor.layerCount = 1; + Vk::ImageUtils::InsertBarrier(context.cmd, atlasToColor); + + // Scratch Color vissza Generalra a következő iterációnak + Vk::ImageBarrierInfo scratchBackToGen{}; + scratchBackToGen.image = scratchColor->Handle(); + scratchBackToGen.srcStage = VK_PIPELINE_STAGE_2_TRANSFER_BIT; + scratchBackToGen.srcAccess = VK_ACCESS_2_TRANSFER_READ_BIT; + scratchBackToGen.dstStage = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT; + scratchBackToGen.dstAccess = VK_ACCESS_2_SHADER_READ_BIT | VK_ACCESS_2_SHADER_WRITE_BIT; + scratchBackToGen.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + scratchBackToGen.newLayout = VK_IMAGE_LAYOUT_GENERAL; + scratchBackToGen.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + scratchBackToGen.baseMipLevel = 0; + scratchBackToGen.levelCount = 1; + scratchBackToGen.baseArrayLayer = 0; + scratchBackToGen.layerCount = 1; + Vk::ImageUtils::InsertBarrier(context.cmd, scratchBackToGen); + } + + pm->ClearDirtyResources(PreviewResourceType::Material); + } + + void MaterialPreviewBloomPass::DispatchPrefilter(const RenderContext& context, Vk::Image* colorImage, Vk::Image* bloomImage) { + _prefilterProgram->Bind(context.cmd); + + auto sampler = ServiceLocator::GetImageManager()->GetSampler(SamplerNames::LinearClampEdge); + + Vk::PushDescriptorWriter writer; + writer.AddCombinedImageSampler(0, colorImage->GetView(Vk::ImageViewNames::Default), sampler->Handle(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + writer.AddStorageImage(1, bloomImage->GetView(std::string(Vk::ImageViewNames::Default) + std::string(Vk::ImageViewNames::Mip) + "0"), VK_IMAGE_LAYOUT_GENERAL); + writer.Push(context.cmd, _prefilterProgram->GetLayout(), 2, VK_PIPELINE_BIND_POINT_COMPUTE); + + Vk::PushConstant pc; + pc->knee = context.scene->GetSettings()->postProcess.bloomKnee; + pc->threshold = context.scene->GetSettings()->postProcess.bloomThreshold; + pc->texelSize = 1.0f / glm::vec2(colorImage->GetExtent().width, colorImage->GetExtent().height); + pc.Push(context.cmd, _prefilterProgram->GetLayout()); + + uint32_t gx = ComputeGroupSize::CalculateDispatchCount(colorImage->GetExtent().width, ComputeGroupSize::Image8D); + uint32_t gy = ComputeGroupSize::CalculateDispatchCount(colorImage->GetExtent().height, ComputeGroupSize::Image8D); + vkCmdDispatch(context.cmd, gx, gy, 1); + } + + void MaterialPreviewBloomPass::DispatchDownsample(const RenderContext& context, Vk::Image* bloomImage) { + _downsampleProgram->Bind(context.cmd); + + auto sampler = ServiceLocator::GetImageManager()->GetSampler(SamplerNames::LinearClampEdge); + Vk::PushDescriptorWriter pushWriter; + glm::vec2 currentInSize = glm::vec2(bloomImage->GetExtent().width, bloomImage->GetExtent().height); + + for (uint32_t i = 1; i < bloomImage->GetConfig().mipLevels; ++i) { + glm::vec2 currentOutSize = glm::vec2(std::max(1.0f, std::floor(currentInSize.x / 2.0f)), std::max(1.0f, std::floor(currentInSize.y / 2.0f))); + std::string parentMip = std::string(Vk::ImageViewNames::Default) + std::string(Vk::ImageViewNames::Mip) + std::to_string(i - 1); + std::string currentMip = std::string(Vk::ImageViewNames::Default) + std::string(Vk::ImageViewNames::Mip) + std::to_string(i); + + Vk::ImageBarrierInfo barrier{}; + barrier.image = bloomImage->Handle(); + barrier.srcStage = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT; + barrier.srcAccess = VK_ACCESS_2_SHADER_READ_BIT | VK_ACCESS_2_SHADER_WRITE_BIT; + barrier.dstStage = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT; + barrier.dstAccess = VK_ACCESS_2_SHADER_WRITE_BIT; + barrier.oldLayout = VK_IMAGE_LAYOUT_GENERAL; + barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL; + barrier.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + barrier.baseMipLevel = i; + barrier.levelCount = 1; + barrier.baseArrayLayer = 0; + barrier.layerCount = 1; + Vk::ImageUtils::InsertBarrier(context.cmd, barrier); + + pushWriter.AddCombinedImageSampler(0, bloomImage->GetView(parentMip), sampler->Handle(), VK_IMAGE_LAYOUT_GENERAL); + pushWriter.AddStorageImage(1, bloomImage->GetView(currentMip), VK_IMAGE_LAYOUT_GENERAL); + pushWriter.Push(context.cmd, _downsampleProgram->GetLayout(), 2, VK_PIPELINE_BIND_POINT_COMPUTE); + + Vk::PushConstant pc; + pc->texelSize = 1.0f / currentInSize; + pc.Push(context.cmd, _downsampleProgram->GetLayout()); + + uint32_t groupCountX = ComputeGroupSize::CalculateDispatchCount((uint32_t)currentOutSize.x, ComputeGroupSize::Image8D); + uint32_t groupCountY = ComputeGroupSize::CalculateDispatchCount((uint32_t)currentOutSize.y, ComputeGroupSize::Image8D); + vkCmdDispatch(context.cmd, groupCountX, groupCountY, 1); + + Vk::ImageBarrierInfo readBarrier{}; + readBarrier.image = bloomImage->Handle(); + readBarrier.srcStage = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT; + readBarrier.srcAccess = VK_ACCESS_2_SHADER_WRITE_BIT; + readBarrier.dstStage = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT; + readBarrier.dstAccess = VK_ACCESS_2_SHADER_READ_BIT; + readBarrier.oldLayout = VK_IMAGE_LAYOUT_GENERAL; + readBarrier.newLayout = VK_IMAGE_LAYOUT_GENERAL; + readBarrier.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + readBarrier.baseMipLevel = i; + readBarrier.levelCount = 1; + readBarrier.baseArrayLayer = 0; + readBarrier.layerCount = 1; + Vk::ImageUtils::InsertBarrier(context.cmd, readBarrier); + + currentInSize = currentOutSize; + } + } + + void MaterialPreviewBloomPass::DispatchUpsample(const RenderContext& context, Vk::Image* bloomImage) { + _upsampleProgram->Bind(context.cmd); + + auto sampler = ServiceLocator::GetImageManager()->GetSampler(SamplerNames::LinearClampEdge); + Vk::PushDescriptorWriter pushWriter; + glm::vec2 baseSize = glm::vec2(bloomImage->GetExtent().width, bloomImage->GetExtent().height); + + for (int32_t i = bloomImage->GetConfig().mipLevels - 1; i > 0; --i) { + glm::vec2 sourceSize = glm::vec2(std::max(1.0f, (float)std::floor(baseSize.x / std::pow(2.0f, i))), std::max(1.0f, (float)std::floor(baseSize.y / std::pow(2.0f, i)))); + glm::vec2 targetSize = glm::vec2(std::max(1.0f, (float)std::floor(baseSize.x / std::pow(2.0f, i - 1))), std::max(1.0f, (float)std::floor(baseSize.y / std::pow(2.0f, i - 1)))); + + std::string sourceMip = std::string(Vk::ImageViewNames::Default) + std::string(Vk::ImageViewNames::Mip) + std::to_string(i); + std::string targetMip = std::string(Vk::ImageViewNames::Default) + std::string(Vk::ImageViewNames::Mip) + std::to_string(i - 1); + + pushWriter.AddCombinedImageSampler(0, bloomImage->GetView(sourceMip), sampler->Handle(), VK_IMAGE_LAYOUT_GENERAL); + pushWriter.AddStorageImage(1, bloomImage->GetView(targetMip), VK_IMAGE_LAYOUT_GENERAL); + pushWriter.Push(context.cmd, _upsampleProgram->GetLayout(), 2, VK_PIPELINE_BIND_POINT_COMPUTE); + + Vk::PushConstant pc; + pc->texelSize = 1.0f / sourceSize; + pc->filterRadius = context.scene->GetSettings()->postProcess.bloomFilterRadius; + pc.Push(context.cmd, _upsampleProgram->GetLayout()); + + uint32_t groupCountX = ComputeGroupSize::CalculateDispatchCount((uint32_t)targetSize.x, ComputeGroupSize::Image8D); + uint32_t groupCountY = ComputeGroupSize::CalculateDispatchCount((uint32_t)targetSize.y, ComputeGroupSize::Image8D); + vkCmdDispatch(context.cmd, groupCountX, groupCountY, 1); + + Vk::ImageBarrierInfo barrier{}; + barrier.image = bloomImage->Handle(); + barrier.srcStage = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT; + barrier.srcAccess = VK_ACCESS_2_SHADER_WRITE_BIT; + barrier.dstStage = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT; + barrier.dstAccess = VK_ACCESS_2_SHADER_READ_BIT; + barrier.oldLayout = VK_IMAGE_LAYOUT_GENERAL; + barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL; + barrier.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + barrier.baseMipLevel = i - 1; + barrier.levelCount = 1; + barrier.baseArrayLayer = 0; + barrier.layerCount = 1; + Vk::ImageUtils::InsertBarrier(context.cmd, barrier); + } + } + + void MaterialPreviewBloomPass::DispatchComposite(const RenderContext& context, Vk::Image* colorImage, Vk::Image* bloomImage) { + _compositeProgram->Bind(context.cmd); + + auto sampler = ServiceLocator::GetImageManager()->GetSampler(SamplerNames::LinearClampEdge); + Vk::PushDescriptorWriter writer; + writer.AddCombinedImageSampler(0, bloomImage->GetView(std::string(Vk::ImageViewNames::Default) + std::string(Vk::ImageViewNames::Mip) + "0"), sampler->Handle(), VK_IMAGE_LAYOUT_GENERAL); + writer.AddStorageImage(1, colorImage->GetView(Vk::ImageViewNames::Default), VK_IMAGE_LAYOUT_GENERAL); + writer.Push(context.cmd, _compositeProgram->GetLayout(), 2, VK_PIPELINE_BIND_POINT_COMPUTE); + + Vk::PushConstant pc; + pc->exposure = context.scene->GetSettings()->postProcess.bloomExposure; + pc->bloomStrength = context.scene->GetSettings()->postProcess.bloomStrength; + pc.Push(context.cmd, _compositeProgram->GetLayout()); + + uint32_t gx = ComputeGroupSize::CalculateDispatchCount(colorImage->GetExtent().width, ComputeGroupSize::Image8D); + uint32_t gy = ComputeGroupSize::CalculateDispatchCount(colorImage->GetExtent().height, ComputeGroupSize::Image8D); + vkCmdDispatch(context.cmd, gx, gy, 1); + } +} \ No newline at end of file diff --git a/SynapseEngine/Engine/Render/Passes/Preview/MaterialPreviewBloomPass.h b/SynapseEngine/Engine/Render/Passes/Preview/MaterialPreviewBloomPass.h new file mode 100644 index 00000000..8562bccd --- /dev/null +++ b/SynapseEngine/Engine/Render/Passes/Preview/MaterialPreviewBloomPass.h @@ -0,0 +1,28 @@ +#pragma once +#include "Engine/Render/IRenderPass.h" +#include "Engine/Vk/Descriptor/PushDescriptorWriter.h" +#include "Engine/Vk/Shader/ShaderProgram.h" +#include +#include + +namespace Syn { + class MaterialPreviewBloomPass : public IRenderPass { + public: + void Initialize() override; + void Execute(const RenderContext& context) override; + bool ShouldExecute(const RenderContext& context) const override; + + std::string GetName() const override { return "MaterialPreviewBloomPass"; } + std::string GetGroup() const override { return PassGroupNames::UtilityPasses; } + private: + void DispatchPrefilter(const RenderContext& context, Vk::Image* colorImage, Vk::Image* bloomImage); + void DispatchDownsample(const RenderContext& context, Vk::Image* bloomImage); + void DispatchUpsample(const RenderContext& context, Vk::Image* bloomImage); + void DispatchComposite(const RenderContext& context, Vk::Image* colorImage, Vk::Image* bloomImage); + + Vk::ShaderProgram* _prefilterProgram; + Vk::ShaderProgram* _downsampleProgram; + Vk::ShaderProgram* _upsampleProgram; + Vk::ShaderProgram* _compositeProgram; + }; +} \ No newline at end of file diff --git a/SynapseEngine/Engine/Render/Passes/Preview/MaterialPreviewPass.cpp b/SynapseEngine/Engine/Render/Passes/Preview/MaterialPreviewPass.cpp index d7773844..d64d5f30 100644 --- a/SynapseEngine/Engine/Render/Passes/Preview/MaterialPreviewPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Preview/MaterialPreviewPass.cpp @@ -68,7 +68,7 @@ namespace Syn { auto pm = ServiceLocator::GetPreviewManager(); auto modelManager = ServiceLocator::GetModelManager(); - _dirtyMaterials = pm->ConsumeDirtyResources(PreviewResourceType::Material); + _dirtyMaterials = pm->GetDirtyResources(PreviewResourceType::Material); if (_dirtyMaterials.empty()) return; auto atlas = pm->GetAtlasImage(); diff --git a/SynapseEngine/Engine/Render/Passes/Preview/PreviewPostTransitionPass.cpp b/SynapseEngine/Engine/Render/Passes/Preview/PreviewPostTransitionPass.cpp index d8f5c8db..e98db864 100644 --- a/SynapseEngine/Engine/Render/Passes/Preview/PreviewPostTransitionPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Preview/PreviewPostTransitionPass.cpp @@ -9,9 +9,25 @@ namespace Syn { _imageTransitions.push_back({ .image = pm->GetAtlasImage(), - .newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, - .dstStage = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT, - .dstAccess = VK_ACCESS_2_SHADER_READ_BIT, + .newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + .dstStage = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, + .dstAccess = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT, + .discardContent = false + }); + + _imageTransitions.push_back({ + .image = pm->GetScratchColorImage(), + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .dstStage = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, + .dstAccess = VK_ACCESS_2_SHADER_WRITE_BIT | VK_ACCESS_2_SHADER_READ_BIT, + .discardContent = false + }); + + _imageTransitions.push_back({ + .image = pm->GetScratchBloomImage(), + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .dstStage = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, + .dstAccess = VK_ACCESS_2_SHADER_WRITE_BIT | VK_ACCESS_2_SHADER_READ_BIT, .discardContent = false }); } diff --git a/SynapseEngine/Engine/Render/RendererFactory.cpp b/SynapseEngine/Engine/Render/RendererFactory.cpp index 604affac..99a58dce 100644 --- a/SynapseEngine/Engine/Render/RendererFactory.cpp +++ b/SynapseEngine/Engine/Render/RendererFactory.cpp @@ -17,6 +17,7 @@ #include "Engine/Render/Passes/Preview/PreviewPreTransitionPass.h" #include "Engine/Render/Passes/Preview/PreviewPostTransitionPass.h" #include "Engine/Render/Passes/Preview/MaterialPreviewPass.h" +#include "Engine/Render/Passes/Preview/MaterialPreviewBloomPass.h" #include "Engine/Render/Passes/Culling/Geometry/GeometryModelCullingPass.h" #include "Engine/Render/Passes/Culling/Geometry/GeometryStaticModelCullingPass.h" @@ -392,6 +393,7 @@ namespace Syn //Preview Passes pipeline->AddPass(std::make_unique()); pipeline->AddPass(std::make_unique()); + pipeline->AddPass(std::make_unique()); pipeline->AddPass(std::make_unique()); //Gui and Present Passes diff --git a/SynapseEngine/Engine/Shaders/Passes/Preview/MaterialPreview.frag b/SynapseEngine/Engine/Shaders/Passes/Preview/MaterialPreview.frag index ee62e339..82331794 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Preview/MaterialPreview.frag +++ b/SynapseEngine/Engine/Shaders/Passes/Preview/MaterialPreview.frag @@ -29,7 +29,7 @@ void main() { vec2 p = inUV * 2.0 - 1.0; float r2 = dot(p, p); - if (r2 > 1.0) { + if (r2 > 0.85) { outColor = vec4(bgColor, 1.0); return; } From b9227f1fa39cd5454d44d914f7aa7f4161972ca4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20P=C3=A9ter?= Date: Fri, 10 Jul 2026 12:50:30 +0200 Subject: [PATCH 43/51] Model hierarchy refactored to use new item card --- .../Editor/View/Hierarchy/HierarchyView.cpp | 2 +- .../MaterialHierarchyView.cpp | 12 +- .../ModelHierarchy/ModelHierarchyView.cpp | 144 +++++++++++++----- .../TextureHierarchy/TextureHierarchyView.cpp | 11 +- .../Editor/Workspace/ModelWorkspace.cpp | 2 +- .../ModelHierarchy/ModelHierarchyState.h | 19 ++- .../ModelHierarchyViewModel.cpp | 88 +++++++++-- .../ModelHierarchy/ModelHierarchyViewModel.h | 5 +- .../Passes/Preview/MaterialPreview.frag | 4 +- 9 files changed, 222 insertions(+), 65 deletions(-) diff --git a/SynapseEngine/Editor/View/Hierarchy/HierarchyView.cpp b/SynapseEngine/Editor/View/Hierarchy/HierarchyView.cpp index 8d7d59aa..1eba5107 100644 --- a/SynapseEngine/Editor/View/Hierarchy/HierarchyView.cpp +++ b/SynapseEngine/Editor/View/Hierarchy/HierarchyView.cpp @@ -34,7 +34,7 @@ namespace Syn { ImGui::BeginChild("HierarchyTableContainer", ImVec2(0, tableHeight), ImGuiChildFlags_Borders, ImGuiWindowFlags_NoScrollbar); - if (ImGui::BeginTable("HierarchyTable", 2, ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_ScrollY | ImGuiTableFlags_Resizable)) { + if (ImGui::BeginTable("HierarchyTable", 2, ImGuiTableFlags_ScrollY | ImGuiTableFlags_Resizable)) { ImGui::TableSetupScrollFreeze(0, 1); ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch); diff --git a/SynapseEngine/Editor/View/MaterialHierarchy/MaterialHierarchyView.cpp b/SynapseEngine/Editor/View/MaterialHierarchy/MaterialHierarchyView.cpp index 3933d63a..ce63b4a0 100644 --- a/SynapseEngine/Editor/View/MaterialHierarchy/MaterialHierarchyView.cpp +++ b/SynapseEngine/Editor/View/MaterialHierarchy/MaterialHierarchyView.cpp @@ -8,7 +8,7 @@ namespace Syn { void MaterialHierarchyView::Draw(MaterialHierarchyViewModel& vm) { ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8, 8)); - ImGuiWindowFlags windowFlags = ImGuiWindowFlags_None; + ImGuiWindowFlags windowFlags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse; if (ImGui::Begin(SYN_ICON_BRUSH " Materials", nullptr, windowFlags)) { @@ -25,10 +25,16 @@ namespace Syn { RenderTopBar(vm); + float currentY = ImGui::GetCursorScreenPos().y; + float gridHeight = mainContentBottomY - currentY - 12.0f; + if (gridHeight < 150.0f) gridHeight = 150.0f; + + ImGui::BeginChild("MaterialGridContainer", ImVec2(0, gridHeight), false, ImGuiWindowFlags_NoScrollbar); + const auto& state = vm.GetState(); const auto entries = state.filteredNodes; const float thumbnailSize = 100.0f; - + Syn::UI::ItemCardContainer("MaterialGrid", (int)entries.size(), thumbnailSize, [&](int index) { const auto& entry = entries[index]; @@ -60,6 +66,8 @@ namespace Syn { Syn::UI::ItemCard(desc, thumbnailSize); }); + + ImGui::EndChild(); } Syn::UI::EndCard(); diff --git a/SynapseEngine/Editor/View/ModelHierarchy/ModelHierarchyView.cpp b/SynapseEngine/Editor/View/ModelHierarchy/ModelHierarchyView.cpp index f83ac96d..cf3a594b 100644 --- a/SynapseEngine/Editor/View/ModelHierarchy/ModelHierarchyView.cpp +++ b/SynapseEngine/Editor/View/ModelHierarchy/ModelHierarchyView.cpp @@ -1,6 +1,8 @@ #include "ModelHierarchyView.h" #include "Editor/Manager/EditorIcons.h" #include "Editor/Widgets/CardWidget.h" +#include "Editor/Widgets/ItemCardWidget.h" +#include "Editor/Widgets/ItemCardContainerWidget.h" #include namespace Syn @@ -9,73 +11,133 @@ namespace Syn ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8, 8)); ImGuiWindowFlags windowFlags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse; - if (ImGui::Begin(SYN_ICON_CUBE " Model Hierarchy", nullptr, windowFlags)) { + const auto& state = vm.GetState(); + + auto getCardState = [this](const char* name) -> bool& { + std::string key(name); + if (_cardStates.find(key) == _cardStates.end()) _cardStates[key] = true; + return _cardStates[key]; + }; - auto getCardState = [this](const char* name) -> bool& { - std::string key(name); - if (_cardStates.find(key) == _cardStates.end()) _cardStates[key] = true; - return _cardStates[key]; - }; + if (ImGui::Begin(SYN_ICON_CUBE " Models", nullptr, windowFlags)) { float mainContentBottomY = ImGui::GetWindowPos().y + ImGui::GetWindowContentRegionMax().y; constexpr const char* CardModelTitle = "ModelListCard"; if (Syn::UI::BeginCard(CardModelTitle, SYN_ICON_CUBE, getCardState(CardModelTitle))) { - RenderTopBar(vm); - const auto& state = vm.GetState(); - - ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.08f, 0.08f, 0.08f, 0.6f)); - ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 8.0f); - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); - float currentY = ImGui::GetCursorScreenPos().y; - float tableHeight = mainContentBottomY - currentY - 12.0f; - if (tableHeight < 100.0f) tableHeight = 100.0f; + float gridHeight = mainContentBottomY - currentY - 12.0f; + if (gridHeight < 150.0f) gridHeight = 150.0f; - ImGui::BeginChild("ModelHierarchyTableContainer", ImVec2(0, tableHeight), ImGuiChildFlags_Borders, ImGuiWindowFlags_NoScrollbar); + ImGui::BeginChild("ModelGridContainer", ImVec2(0, gridHeight), false, ImGuiWindowFlags_NoScrollbar); - if (ImGui::BeginTable("ModelTable", 1, ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_ScrollY | ImGuiTableFlags_Resizable)) { - ImGui::TableSetupScrollFreeze(0, 1); - ImGui::TableSetupColumn("Hierarchy", ImGuiTableColumnFlags_WidthStretch); + const auto& entries = state.filteredModels; + const float thumbnailSize = 100.0f; - ImGui::TableNextRow(ImGuiTableRowFlags_Headers); + Syn::UI::ItemCardContainer("ModelGrid", (int)entries.size(), thumbnailSize, + [&](int index) { + const auto& entry = entries[index]; - ImGui::TableSetColumnIndex(0); - float cellWidth = ImGui::GetColumnWidth(); - float textWidth = ImGui::CalcTextSize("Hierarchy").x; - ImVec2 startPos = ImGui::GetCursorPos(); + Syn::UI::ItemCardDesc desc; + std::string idStr = std::to_string(entry.id); + desc.id = idStr.c_str(); + desc.title = entry.name.c_str(); - ImGui::TableHeader("##ColHierarchy"); + if (entry.hasPreview && state.atlasHandle) { + desc.texture = state.atlasHandle; + desc.uv0 = ImVec2(entry.uv0.x, entry.uv0.y); + desc.uv1 = ImVec2(entry.uv1.x, entry.uv1.y); + } + else { + desc.texture = InvalidTextureHandle; + } - ImGui::SetCursorPos(ImVec2(startPos.x + (cellWidth - textWidth) * 0.5f, startPos.y + 3.0f)); - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); - ImGui::Text("Hierarchy"); - ImGui::PopStyleColor(); + desc.selected = (state.selectedModelId == entry.id); - ImGuiListClipper clipper; - clipper.Begin(static_cast(state.flatNodes.size())); + desc.events.onClick = [&vm, &entry] { + vm.Dispatch(ModelHierarchySelectIntent{ entry.id, -1 }); + }; - while (clipper.Step()) { - for (int row = clipper.DisplayStart; row < clipper.DisplayEnd; ++row) { - RenderNodeRow(vm, state.flatNodes[row]); - } - } + desc.events.onDoubleClick = [&vm, &entry] {}; + desc.events.onDragDropSource = [&entry] {}; + + Syn::UI::ItemCard(desc, thumbnailSize); + }); - ImGui::EndTable(); - } ImGui::EndChild(); - ImGui::PopStyleVar(2); - ImGui::PopStyleColor(); } Syn::UI::EndCard(); if (ImGui::IsMouseDown(ImGuiMouseButton_Left) && ImGui::IsWindowHovered()) { - vm.Dispatch(ModelHierarchySelectIntent{ INVALID_MODEL_ID, -1 }); + vm.Dispatch(ModelHierarchySelectIntent{ 0xFFFFFFFF, -1 }); } } ImGui::End(); + + + if (ImGui::Begin(SYN_ICON_LIST " Internal Hierarchy", nullptr, windowFlags)) { + + float mainContentBottomY = ImGui::GetWindowPos().y + ImGui::GetWindowContentRegionMax().y; + constexpr const char* CardHierarchyTitle = "Internal Hierarchy Card"; + + if (Syn::UI::BeginCard(CardHierarchyTitle, SYN_ICON_LIST, getCardState(CardHierarchyTitle))) { + + if (state.selectedModelId != 0xFFFFFFFF) { + ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.08f, 0.08f, 0.08f, 0.6f)); + ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 8.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); + + float currentY = ImGui::GetCursorScreenPos().y; + float tableHeight = mainContentBottomY - currentY - 12.0f; + if (tableHeight < 100.0f) tableHeight = 100.0f; + + ImGui::BeginChild("ModelHierarchyTableContainer", ImVec2(0, tableHeight), ImGuiChildFlags_Borders, ImGuiWindowFlags_NoScrollbar); + + if (ImGui::BeginTable("ModelTable", 1, ImGuiTableFlags_ScrollY | ImGuiTableFlags_Resizable)) { + ImGui::TableSetupScrollFreeze(0, 1); + ImGui::TableSetupColumn("Hierarchy", ImGuiTableColumnFlags_WidthStretch); + + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); + ImGui::TableSetColumnIndex(0); + + float cellWidth = ImGui::GetColumnWidth(); + float textWidth = ImGui::CalcTextSize("Hierarchy").x; + ImVec2 startPos = ImGui::GetCursorPos(); + + ImGui::TableHeader("##ColHierarchy"); + ImGui::SetCursorPos(ImVec2(startPos.x + (cellWidth - textWidth) * 0.5f, startPos.y + 3.0f)); + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); + ImGui::Text("Hierarchy"); + ImGui::PopStyleColor(); + + ImGuiListClipper clipper; + clipper.Begin(static_cast(state.flatNodes.size())); + + while (clipper.Step()) { + for (int row = clipper.DisplayStart; row < clipper.DisplayEnd; ++row) { + RenderNodeRow(vm, state.flatNodes[row]); + } + } + + ImGui::EndTable(); + } + ImGui::EndChild(); + ImGui::PopStyleVar(2); + ImGui::PopStyleColor(); + } + else { + ImGui::Spacing(); + ImGui::Indent(8.0f); + ImGui::TextDisabled("Select a model to view its internal hierarchy."); + ImGui::Unindent(8.0f); + } + } + Syn::UI::EndCard(); + } + ImGui::End(); + ImGui::PopStyleVar(); } diff --git a/SynapseEngine/Editor/View/TextureHierarchy/TextureHierarchyView.cpp b/SynapseEngine/Editor/View/TextureHierarchy/TextureHierarchyView.cpp index 21f55406..d4486016 100644 --- a/SynapseEngine/Editor/View/TextureHierarchy/TextureHierarchyView.cpp +++ b/SynapseEngine/Editor/View/TextureHierarchy/TextureHierarchyView.cpp @@ -8,7 +8,7 @@ namespace Syn { void TextureHierarchyView::Draw(TextureHierarchyViewModel& vm) { ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8, 8)); - ImGuiWindowFlags windowFlags = ImGuiWindowFlags_None; + ImGuiWindowFlags windowFlags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse; if (ImGui::Begin(SYN_ICON_IMAGE " Textures", nullptr, windowFlags)) { @@ -25,6 +25,12 @@ namespace Syn { RenderTopBar(vm); + float currentY = ImGui::GetCursorScreenPos().y; + float gridHeight = mainContentBottomY - currentY - 12.0f; + if (gridHeight < 150.0f) gridHeight = 150.0f; + + ImGui::BeginChild("TextureGridContainer", ImVec2(0, gridHeight), false, ImGuiWindowFlags_NoScrollbar); + const auto& state = vm.GetState(); const auto entries = state.filteredNodes; @@ -53,7 +59,8 @@ namespace Syn { Syn::UI::ItemCard(desc, thumbnailSize); }); - + + ImGui::EndChild(); } Syn::UI::EndCard(); diff --git a/SynapseEngine/Editor/Workspace/ModelWorkspace.cpp b/SynapseEngine/Editor/Workspace/ModelWorkspace.cpp index f7781e96..7d5d9d7f 100644 --- a/SynapseEngine/Editor/Workspace/ModelWorkspace.cpp +++ b/SynapseEngine/Editor/Workspace/ModelWorkspace.cpp @@ -37,7 +37,7 @@ namespace Syn { using ModelHierarchyWin = EditorWindow; AddWindow( ModelHierarchyView{}, - ModelHierarchyViewModel{ _context->GetApi() } + ModelHierarchyViewModel{ _context->GetApi(), _context->GetApi() } ); using ModelPropertiesWin = EditorWindow; diff --git a/SynapseEngine/EditorCore/ViewModels/ModelHierarchy/ModelHierarchyState.h b/SynapseEngine/EditorCore/ViewModels/ModelHierarchy/ModelHierarchyState.h index 8c1c8034..e4a8c827 100644 --- a/SynapseEngine/EditorCore/ViewModels/ModelHierarchy/ModelHierarchyState.h +++ b/SynapseEngine/EditorCore/ViewModels/ModelHierarchy/ModelHierarchyState.h @@ -2,8 +2,21 @@ #include #include #include +#include +#include "EditorCore/Types/TextureHandle.h" + +namespace Syn +{ + struct ModelNode { + uint32_t id; + std::string name; + std::string path; + std::string icon; + glm::vec2 uv0{ 0.0f, 0.0f }; + glm::vec2 uv1{ 1.0f, 1.0f }; + bool hasPreview = false; + }; -namespace Syn { struct ModelHierarchyNode { uint32_t modelId = 0xFFFFFFFF; int32_t descriptorIndex = -1; @@ -20,9 +33,13 @@ namespace Syn { }; struct ModelHierarchyState { + std::vector filteredModels; std::vector flatNodes; + uint32_t selectedModelId = 0xFFFFFFFF; int32_t selectedDescriptorIndex = -1; std::string searchQuery = ""; + + TextureHandle atlasHandle = InvalidTextureHandle; }; } \ No newline at end of file diff --git a/SynapseEngine/EditorCore/ViewModels/ModelHierarchy/ModelHierarchyViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/ModelHierarchy/ModelHierarchyViewModel.cpp index 5935dbd6..6558a37e 100644 --- a/SynapseEngine/EditorCore/ViewModels/ModelHierarchy/ModelHierarchyViewModel.cpp +++ b/SynapseEngine/EditorCore/ViewModels/ModelHierarchy/ModelHierarchyViewModel.cpp @@ -4,8 +4,8 @@ #include namespace Syn { - ModelHierarchyViewModel::ModelHierarchyViewModel(IModelApi* modelApi) - : _modelApi(modelApi) + ModelHierarchyViewModel::ModelHierarchyViewModel(IModelApi* modelApi, IPreviewApi* previewApi) + : _modelApi(modelApi), _previewApi(previewApi) {} uint64_t ModelHierarchyViewModel::GetNodeKey(uint32_t modelId, int32_t descriptorIndex) const { @@ -19,6 +19,7 @@ namespace Syn { auto selection = _modelApi->GetSelected(); if (currentVersion != _lastEngineVersion || _isDirty) { + RebuildModelList(); RebuildFlatList(); _lastEngineVersion = currentVersion; _isDirty = false; @@ -28,6 +29,18 @@ namespace Syn { _state.selectedModelId = selection.first; _state.selectedDescriptorIndex = selection.second; } + + if (_previewApi) { + _state.atlasHandle = _previewApi->GetAtlasHandle(); + + for (auto& node : _state.filteredModels) { + if (!node.hasPreview) { + if (_previewApi->GetPreviewUVs(PreviewResourceType::Model, node.id, node.uv0, node.uv1)) { + node.hasPreview = true; + } + } + } + } } void ModelHierarchyViewModel::Dispatch(const ModelHierarchyIntent& intent) { @@ -35,14 +48,19 @@ namespace Syn { using T = std::decay_t; if constexpr (std::is_same_v) { - if (_modelApi) - { + if (_modelApi) { _modelApi->SetSelected(arg.modelId, arg.descriptorIndex); _modelApi->ApplyModelToPreviewObject(arg.modelId); } - _state.selectedModelId = arg.modelId; - _state.selectedDescriptorIndex = arg.descriptorIndex; + if (_state.selectedModelId != arg.modelId) { + _state.selectedModelId = arg.modelId; + _state.selectedDescriptorIndex = arg.descriptorIndex; + _isDirty = true; + } + else { + _state.selectedDescriptorIndex = arg.descriptorIndex; + } } else if constexpr (std::is_same_v) { uint64_t key = GetNodeKey(arg.modelId, arg.descriptorIndex); @@ -62,25 +80,65 @@ namespace Syn { }, intent); } - void ModelHierarchyViewModel::RebuildFlatList() { + void ModelHierarchyViewModel::RebuildModelList() { if (!_modelApi) return; - _state.flatNodes.clear(); + _state.filteredModels.clear(); auto allModels = _modelApi->GetAllModels(); - for (const auto& mod : allModels) { - if (const CpuModelData* cpuData = _modelApi->GetModelCpuData(mod.id)) { + std::string searchLower = _state.searchQuery; + std::transform(searchLower.begin(), searchLower.end(), searchLower.begin(), ::tolower); - std::unordered_map> childMap; - for (uint32_t i = 0; i < cpuData->meshNodeDescriptors.size(); ++i) { - childMap[cpuData->meshNodeDescriptors[i].parentNodeIndex].push_back(i); + for (const auto& mod : allModels) { + std::string nameLower = mod.name; + std::transform(nameLower.begin(), nameLower.end(), nameLower.begin(), ::tolower); + + if (searchLower.empty() || nameLower.find(searchLower) != std::string::npos) { + ModelNode node; + node.id = mod.id; + node.name = mod.name; + node.path = mod.path; + node.icon = SYN_ICON_CUBE; + + if (_previewApi) { + if (_previewApi->GetPreviewUVs(PreviewResourceType::Model, mod.id, node.uv0, node.uv1)) { + node.hasPreview = true; + } + else { + node.hasPreview = false; + _previewApi->RequestPreview(PreviewResourceType::Model, mod.id); + } } - TraverseAndFlatten(mod.id, -1, 0xFFFF, 0, *cpuData, childMap, mod.name); + _state.filteredModels.push_back(node); } } } + void ModelHierarchyViewModel::RebuildFlatList() { + _state.flatNodes.clear(); + if (!_modelApi || _state.selectedModelId == 0xFFFFFFFF) return; + + if (const CpuModelData* cpuData = _modelApi->GetModelCpuData(_state.selectedModelId)) { + + std::unordered_map> childMap; + for (uint32_t i = 0; i < cpuData->meshNodeDescriptors.size(); ++i) { + childMap[cpuData->meshNodeDescriptors[i].parentNodeIndex].push_back(i); + } + + std::string modelName = "Selected Model"; + auto allModels = _modelApi->GetAllModels(); + for (const auto& mod : allModels) { + if (mod.id == _state.selectedModelId) { + modelName = mod.name; + break; + } + } + + TraverseAndFlatten(_state.selectedModelId, -1, 0xFFFF, 0, *cpuData, childMap, modelName); + } + } + bool ModelHierarchyViewModel::TraverseAndFlatten(uint32_t modelId, int32_t descriptorIndex, uint16_t currentTransformIndex, int depth, const CpuModelData& cpuData, const std::unordered_map>& childMap, const std::string& nodeName) { bool matchesSearch = _state.searchQuery.empty() || @@ -123,7 +181,7 @@ namespace Syn { const auto& childDesc = cpuData.meshNodeDescriptors[childDescIdx]; std::string childName = childDesc.name; - if (childName.empty()) + if (childName.empty()) childName = "Node_" + std::to_string(childDescIdx); if (TraverseAndFlatten(modelId, childDescIdx, childDesc.nodeIndex, depth + 1, cpuData, childMap, childName)) { diff --git a/SynapseEngine/EditorCore/ViewModels/ModelHierarchy/ModelHierarchyViewModel.h b/SynapseEngine/EditorCore/ViewModels/ModelHierarchy/ModelHierarchyViewModel.h index 5e4cd572..87ccb272 100644 --- a/SynapseEngine/EditorCore/ViewModels/ModelHierarchy/ModelHierarchyViewModel.h +++ b/SynapseEngine/EditorCore/ViewModels/ModelHierarchy/ModelHierarchyViewModel.h @@ -3,13 +3,14 @@ #include "ModelHierarchyState.h" #include "ModelHierarchyIntent.h" #include "EditorCore/Api/IModelApi.h" +#include "EditorCore/Api/IPreviewApi.h" #include #include namespace Syn { class ModelHierarchyViewModel : public IViewModel { public: - ModelHierarchyViewModel(IModelApi* modelApi); + ModelHierarchyViewModel(IModelApi* modelApi, IPreviewApi* previewApi); ~ModelHierarchyViewModel() override = default; const ModelHierarchyState& GetState() const override { return _state; } @@ -17,11 +18,13 @@ namespace Syn { void Dispatch(const ModelHierarchyIntent& intent) override; private: + void RebuildModelList(); void RebuildFlatList(); bool TraverseAndFlatten(uint32_t modelId, int32_t descriptorIndex, uint16_t currentTransformIndex, int depth, const CpuModelData& cpuData, const std::unordered_map>& childMap, const std::string& nodeName); uint64_t GetNodeKey(uint32_t modelId, int32_t descriptorIndex) const; private: IModelApi* _modelApi = nullptr; + IPreviewApi* _previewApi = nullptr; ModelHierarchyState _state; bool _isDirty = true; diff --git a/SynapseEngine/Engine/Shaders/Passes/Preview/MaterialPreview.frag b/SynapseEngine/Engine/Shaders/Passes/Preview/MaterialPreview.frag index 82331794..1ab62cba 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Preview/MaterialPreview.frag +++ b/SynapseEngine/Engine/Shaders/Passes/Preview/MaterialPreview.frag @@ -86,6 +86,8 @@ void main() { totalRadiance += SimulateAmbientLight(albedoAlpha.rgb, ao, ctx.ambientStrength); totalRadiance += SimulateBloom(emissive, 1.0, ctx.emissiveStrength); - vec3 finalColor = mix(bgColor, totalRadiance, albedoAlpha.a); + + + vec3 finalColor = mix(bgColor, totalRadiance, IS_TRANSPARENT(mat) ? albedoAlpha.a : 1.0); outColor = vec4(finalColor, 1.0); } \ No newline at end of file From 265fbef0e25e48486b6180a229e95c54c12c56d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20P=C3=A9ter?= Date: Fri, 10 Jul 2026 13:17:59 +0200 Subject: [PATCH 44/51] Resolved material-image async loading preview dirty state tracking issues --- SynapseEngine/Engine/Image/ImageManager.cpp | 10 +++- SynapseEngine/Engine/Image/ImageManager.h | 6 ++- .../Engine/Manager/ResourceManager.cpp | 8 ++- .../Engine/Material/MaterialManager.cpp | 49 ++++++++++++++++++- .../Engine/Material/MaterialManager.h | 5 ++ 5 files changed, 73 insertions(+), 5 deletions(-) diff --git a/SynapseEngine/Engine/Image/ImageManager.cpp b/SynapseEngine/Engine/Image/ImageManager.cpp index b9750797..e494220b 100644 --- a/SynapseEngine/Engine/Image/ImageManager.cpp +++ b/SynapseEngine/Engine/Image/ImageManager.cpp @@ -16,12 +16,14 @@ namespace Syn uint32_t framesInFlight, std::shared_ptr builder, std::unique_ptr uploader, - std::unique_ptr cpuExtractor) + std::unique_ptr cpuExtractor, + ImageReadyCallback imageReadyCallback) : AddressResourceManager(framesInFlight, 1024, 256, 512), _framesInFlight(framesInFlight), _builder(builder), _uploader(std::move(uploader)), - _cpuExtractor(std::move(cpuExtractor)) + _cpuExtractor(std::move(cpuExtractor)), + _imageReadyCallback(std::move(imageReadyCallback)) { InitializeBindlessSetup(); } @@ -362,6 +364,10 @@ namespace Syn } WriteAddress(index, textureData); + + if (_imageReadyCallback) { + _imageReadyCallback(index); + } } ); } diff --git a/SynapseEngine/Engine/Image/ImageManager.h b/SynapseEngine/Engine/Image/ImageManager.h index 4b399305..db068e9c 100644 --- a/SynapseEngine/Engine/Image/ImageManager.h +++ b/SynapseEngine/Engine/Image/ImageManager.h @@ -16,6 +16,7 @@ namespace Syn { using ImageSourceFactory = std::function()>; + using ImageReadyCallback = std::function; class SYN_API ImageManager : public AddressResourceManager { public: @@ -28,7 +29,8 @@ namespace Syn { uint32_t framesInFlight, std::shared_ptr builder, std::unique_ptr uploader, - std::unique_ptr cpuExtractor); + std::unique_ptr cpuExtractor, + ImageReadyCallback imageReadyCallback = nullptr); ~ImageManager(); @@ -57,6 +59,8 @@ namespace Syn { void LoadDefaultImageSync(); uint32_t RegisterSampler(const std::string& name, const Vk::SamplerConfig& config); private: + ImageReadyCallback _imageReadyCallback; + std::shared_ptr _builder; std::unique_ptr _uploader; std::unique_ptr _cpuExtractor; diff --git a/SynapseEngine/Engine/Manager/ResourceManager.cpp b/SynapseEngine/Engine/Manager/ResourceManager.cpp index 18c889e7..a14ffb65 100644 --- a/SynapseEngine/Engine/Manager/ResourceManager.cpp +++ b/SynapseEngine/Engine/Manager/ResourceManager.cpp @@ -84,7 +84,13 @@ namespace Syn { _framesInFlight, _imageBuilder, std::make_unique(), - std::make_unique() + std::make_unique(), + [](uint32_t imageId) { + auto matManager = ServiceLocator::GetMaterialManager(); + if (matManager) { + matManager->NotifyImageReady(imageId); + } + } ); ServiceLocator::ProvideImageManager(_imageManager.get()); diff --git a/SynapseEngine/Engine/Material/MaterialManager.cpp b/SynapseEngine/Engine/Material/MaterialManager.cpp index 6e8a206d..2a081fa8 100644 --- a/SynapseEngine/Engine/Material/MaterialManager.cpp +++ b/SynapseEngine/Engine/Material/MaterialManager.cpp @@ -81,7 +81,26 @@ namespace Syn { if (_previewMarkDirtyCallback) _previewMarkDirtyCallback(index); } - void MaterialManager::FlushDirtyResources() { + void MaterialManager::FlushDirtyResources() + { + std::vector imagesToProcess; + + { + std::lock_guard lock(_pendingImageMutex); + imagesToProcess = _pendingImages; + _pendingImages.clear(); + } + + for (uint32_t imgId : imagesToProcess) { + auto affectedMaterials = GetMaterialsUsingTexture(imgId); + + for (uint32_t matId : affectedMaterials) { + if (_previewMarkDirtyCallback) { + _previewMarkDirtyCallback(matId); + } + } + } + ProcessDirtyReadyEntries( [this](uint32_t index, const EntryType& entry) { WriteAddress(index, GpuMaterial(*entry.resource)); @@ -92,4 +111,32 @@ namespace Syn { } ); } + + std::vector MaterialManager::GetMaterialsUsingTexture(uint32_t textureId) const { + std::lock_guard lock(_mutex); + std::vector result; + + for (uint32_t i = 0; i < _entries.size(); ++i) { + if (_entries[i].state == ResourceState::Ready && _entries[i].resource) { + const auto& mat = *_entries[i].resource; + + if (mat.albedoTexture == textureId || + mat.normalTexture == textureId || + mat.metalnessTexture == textureId || + mat.roughnessTexture == textureId || + mat.metallicRoughnessTexture == textureId || + mat.emissiveTexture == textureId || + mat.ambientOcclusionTexture == textureId) + { + result.push_back(i); + } + } + } + return result; + } + + void MaterialManager::NotifyImageReady(uint32_t imageId) { + std::lock_guard lock(_pendingImageMutex); + _pendingImages.push_back(imageId); + } } \ No newline at end of file diff --git a/SynapseEngine/Engine/Material/MaterialManager.h b/SynapseEngine/Engine/Material/MaterialManager.h index 209dfd67..ae2bdf5a 100644 --- a/SynapseEngine/Engine/Material/MaterialManager.h +++ b/SynapseEngine/Engine/Material/MaterialManager.h @@ -22,6 +22,8 @@ namespace Syn { uint32_t LoadMaterial(const std::string& name, const MaterialInfo& info); uint32_t LoadMaterialDirect(const std::string& name, const Material& material); + std::vector GetMaterialsUsingTexture(uint32_t textureId) const; + void NotifyImageReady(uint32_t imageId); protected: void FlushDirtyResources() override; void StartGpuUpload(EntryType& entry) override; @@ -32,5 +34,8 @@ namespace Syn { TextureLoadCallback _textureLoadCallback; PreviewAllocateCallback _previewAllocateCallback; PreviewMarkDirtyCallback _previewMarkDirtyCallback; + + std::mutex _pendingImageMutex; + std::vector _pendingImages; }; } \ No newline at end of file From d09bafce0871f807e7388bd1c74c33b7d0bef853 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20P=C3=A9ter?= Date: Fri, 10 Jul 2026 16:41:24 +0200 Subject: [PATCH 45/51] Model preview texture rendering --- SynapseEngine/Engine/Image/ImageManager.cpp | 8 +- SynapseEngine/Engine/Image/ImageManager.h | 6 +- .../Engine/Manager/PreviewManager.cpp | 11 ++ SynapseEngine/Engine/Manager/PreviewManager.h | 2 + .../Engine/Manager/ResourceManager.cpp | 6 + .../Engine/Material/MaterialManager.cpp | 15 +- .../Engine/Material/MaterialManager.h | 9 +- .../Engine/Mesh/Data/Gpu/GpuBatchedModel.h | 1 + .../Engine/Mesh/Data/Gpu/GpuModelBuffers.h | 4 +- SynapseEngine/Engine/Mesh/ModelManager.cpp | 50 ++++- SynapseEngine/Engine/Mesh/ModelManager.h | 8 + .../Mesh/Uploader/DefaultGpuModelUploader.cpp | 2 + .../Passes/Preview/ModelPreviewPass.cpp | 174 ++++++++++++++++++ .../Render/Passes/Preview/ModelPreviewPass.h | 24 +++ .../Preview/PreviewPostTransitionPass.cpp | 14 +- .../Preview/PreviewPreTransitionPass.cpp | 24 +++ .../Engine/Render/RendererFactory.cpp | 2 + SynapseEngine/Engine/Render/ShaderNames.h | 3 + .../Engine/Shaders/Includes/Common/Mesh.glsl | 7 +- .../PushConstants/ModelPreviewPC.glsl | 13 ++ .../Passes/Preview/MaterialPreview.frag | 5 + .../Shaders/Passes/Preview/ModelPreview.frag | 74 ++++++++ .../Shaders/Passes/Preview/ModelPreview.vert | 45 +++++ .../ForwardPlus/DepthPrepass/PreDepth.frag | 1 + 24 files changed, 485 insertions(+), 23 deletions(-) create mode 100644 SynapseEngine/Engine/Render/Passes/Preview/ModelPreviewPass.cpp create mode 100644 SynapseEngine/Engine/Render/Passes/Preview/ModelPreviewPass.h create mode 100644 SynapseEngine/Engine/Shaders/Includes/PushConstants/ModelPreviewPC.glsl create mode 100644 SynapseEngine/Engine/Shaders/Passes/Preview/ModelPreview.frag create mode 100644 SynapseEngine/Engine/Shaders/Passes/Preview/ModelPreview.vert diff --git a/SynapseEngine/Engine/Image/ImageManager.cpp b/SynapseEngine/Engine/Image/ImageManager.cpp index e494220b..da1ed6b9 100644 --- a/SynapseEngine/Engine/Image/ImageManager.cpp +++ b/SynapseEngine/Engine/Image/ImageManager.cpp @@ -17,13 +17,13 @@ namespace Syn std::shared_ptr builder, std::unique_ptr uploader, std::unique_ptr cpuExtractor, - ImageReadyCallback imageReadyCallback) + ImageReadyOrChangedCallback imageReadyCallback) : AddressResourceManager(framesInFlight, 1024, 256, 512), _framesInFlight(framesInFlight), _builder(builder), _uploader(std::move(uploader)), _cpuExtractor(std::move(cpuExtractor)), - _imageReadyCallback(std::move(imageReadyCallback)) + _imageReadyOrChangedCallback(std::move(imageReadyCallback)) { InitializeBindlessSetup(); } @@ -365,8 +365,8 @@ namespace Syn WriteAddress(index, textureData); - if (_imageReadyCallback) { - _imageReadyCallback(index); + if (_imageReadyOrChangedCallback) { + _imageReadyOrChangedCallback(index); } } ); diff --git a/SynapseEngine/Engine/Image/ImageManager.h b/SynapseEngine/Engine/Image/ImageManager.h index db068e9c..e5dab8d3 100644 --- a/SynapseEngine/Engine/Image/ImageManager.h +++ b/SynapseEngine/Engine/Image/ImageManager.h @@ -16,7 +16,7 @@ namespace Syn { using ImageSourceFactory = std::function()>; - using ImageReadyCallback = std::function; + using ImageReadyOrChangedCallback = std::function; class SYN_API ImageManager : public AddressResourceManager { public: @@ -30,7 +30,7 @@ namespace Syn { std::shared_ptr builder, std::unique_ptr uploader, std::unique_ptr cpuExtractor, - ImageReadyCallback imageReadyCallback = nullptr); + ImageReadyOrChangedCallback imageReadyCallback = nullptr); ~ImageManager(); @@ -59,7 +59,7 @@ namespace Syn { void LoadDefaultImageSync(); uint32_t RegisterSampler(const std::string& name, const Vk::SamplerConfig& config); private: - ImageReadyCallback _imageReadyCallback; + ImageReadyOrChangedCallback _imageReadyOrChangedCallback; std::shared_ptr _builder; std::unique_ptr _uploader; diff --git a/SynapseEngine/Engine/Manager/PreviewManager.cpp b/SynapseEngine/Engine/Manager/PreviewManager.cpp index 0e5fed6e..43b98b74 100644 --- a/SynapseEngine/Engine/Manager/PreviewManager.cpp +++ b/SynapseEngine/Engine/Manager/PreviewManager.cpp @@ -56,6 +56,17 @@ namespace Syn { _atlasImage = std::make_unique(config); + Vk::ImageConfig depthConfig{}; + depthConfig.width = _resolution; + depthConfig.height = _resolution; + depthConfig.type = VK_IMAGE_TYPE_2D; + depthConfig.format = VK_FORMAT_D32_SFLOAT; + depthConfig.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; + depthConfig.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; + depthConfig.AddView(Vk::ImageViewNames::Default, { .viewType = VK_IMAGE_VIEW_TYPE_2D }); + + _atlasDepthImage = std::make_unique(depthConfig); + for (uint32_t i = oldTotalTiles; i < newTotalTiles; ++i) { _freeTiles.push(i); } diff --git a/SynapseEngine/Engine/Manager/PreviewManager.h b/SynapseEngine/Engine/Manager/PreviewManager.h index 8b6f8087..06ed0b0d 100644 --- a/SynapseEngine/Engine/Manager/PreviewManager.h +++ b/SynapseEngine/Engine/Manager/PreviewManager.h @@ -38,6 +38,7 @@ namespace Syn { void GetNormalizedUVs(PreviewResourceType type, uint32_t resourceId, glm::vec2& outUv0, glm::vec2& outUv1) const; Vk::Image* GetAtlasImage() const { return _atlasImage.get(); } + Vk::Image* GetAtlasDepthImage() const { return _atlasDepthImage.get(); } Vk::Image* GetScratchColorImage() const { return _scratchColorImage.get(); } Vk::Image* GetScratchBloomImage() const { return _scratchBloomImage.get(); } @@ -54,6 +55,7 @@ namespace Syn { uint32_t _tilesPerRow; std::unique_ptr _atlasImage; + std::unique_ptr _atlasDepthImage; std::unique_ptr _scratchColorImage; std::unique_ptr _scratchBloomImage; diff --git a/SynapseEngine/Engine/Manager/ResourceManager.cpp b/SynapseEngine/Engine/Manager/ResourceManager.cpp index a14ffb65..ba05b547 100644 --- a/SynapseEngine/Engine/Manager/ResourceManager.cpp +++ b/SynapseEngine/Engine/Manager/ResourceManager.cpp @@ -126,6 +126,12 @@ namespace Syn { }, [this](uint32_t id) { if (_previewManager) _previewManager->MarkDirty(PreviewResourceType::Material, id); + }, + [](uint32_t materialId) { + auto modelManager = ServiceLocator::GetModelManager(); + if (modelManager) { + modelManager->NotifyMaterialReady(materialId); + } } ); diff --git a/SynapseEngine/Engine/Material/MaterialManager.cpp b/SynapseEngine/Engine/Material/MaterialManager.cpp index 2a081fa8..c6663426 100644 --- a/SynapseEngine/Engine/Material/MaterialManager.cpp +++ b/SynapseEngine/Engine/Material/MaterialManager.cpp @@ -8,12 +8,14 @@ namespace Syn { MaterialManager::MaterialManager(uint32_t framesInFlight, TextureLoadCallback textureLoadCallback, PreviewAllocateCallback previewAllocateCallback, - PreviewMarkDirtyCallback previewMarkDirtyCallback + PreviewMarkDirtyCallback previewMarkDirtyCallback, + MaterialReadyOrChangedCallback materialReadyOrChangedCallback ) : AddressResourceManager(framesInFlight, 1024, 1024, 2048) , _textureLoadCallback(std::move(textureLoadCallback)) , _previewAllocateCallback(std::move(previewAllocateCallback)) , _previewMarkDirtyCallback(std::move(previewMarkDirtyCallback)) + , _materialReadyOrChangedCallback(std::move(materialReadyOrChangedCallback)) { Material emptyMat; WriteAddress(0, GpuMaterial(emptyMat)); @@ -79,11 +81,12 @@ namespace Syn { uint32_t index = _pathToId.at(entry.path); if (_previewAllocateCallback) _previewAllocateCallback(index); if (_previewMarkDirtyCallback) _previewMarkDirtyCallback(index); + if (_materialReadyOrChangedCallback) _materialReadyOrChangedCallback(index); } void MaterialManager::FlushDirtyResources() { - std::vector imagesToProcess; + std::unordered_set imagesToProcess; { std::lock_guard lock(_pendingImageMutex); @@ -105,9 +108,11 @@ namespace Syn { [this](uint32_t index, const EntryType& entry) { WriteAddress(index, GpuMaterial(*entry.resource)); - if (_previewMarkDirtyCallback) { + if (_previewMarkDirtyCallback) _previewMarkDirtyCallback(index); - } + + if (_materialReadyOrChangedCallback) + _materialReadyOrChangedCallback(index); } ); } @@ -137,6 +142,6 @@ namespace Syn { void MaterialManager::NotifyImageReady(uint32_t imageId) { std::lock_guard lock(_pendingImageMutex); - _pendingImages.push_back(imageId); + _pendingImages.insert(imageId); } } \ No newline at end of file diff --git a/SynapseEngine/Engine/Material/MaterialManager.h b/SynapseEngine/Engine/Material/MaterialManager.h index ae2bdf5a..6f4fde64 100644 --- a/SynapseEngine/Engine/Material/MaterialManager.h +++ b/SynapseEngine/Engine/Material/MaterialManager.h @@ -6,17 +6,21 @@ #include "Engine/Mesh/Data/Common/MaterialInfo.h" #include "MaterialRenderType.h" +#include + namespace Syn { using TextureLoadCallback = std::function; using PreviewAllocateCallback = std::function; using PreviewMarkDirtyCallback = std::function; + using MaterialReadyOrChangedCallback = std::function; class SYN_API MaterialManager : public AddressResourceManager { public: MaterialManager(uint32_t framesInFlight, TextureLoadCallback textureLoadCallback, PreviewAllocateCallback previewAllocateCallback = nullptr, - PreviewMarkDirtyCallback previewMarkDirtyCallback = nullptr + PreviewMarkDirtyCallback previewMarkDirtyCallback = nullptr, + MaterialReadyOrChangedCallback = nullptr ); ~MaterialManager() = default; @@ -34,8 +38,9 @@ namespace Syn { TextureLoadCallback _textureLoadCallback; PreviewAllocateCallback _previewAllocateCallback; PreviewMarkDirtyCallback _previewMarkDirtyCallback; + MaterialReadyOrChangedCallback _materialReadyOrChangedCallback; std::mutex _pendingImageMutex; - std::vector _pendingImages; + std::unordered_set _pendingImages; }; } \ No newline at end of file diff --git a/SynapseEngine/Engine/Mesh/Data/Gpu/GpuBatchedModel.h b/SynapseEngine/Engine/Mesh/Data/Gpu/GpuBatchedModel.h index 55cb1e8a..e57924b4 100644 --- a/SynapseEngine/Engine/Mesh/Data/Gpu/GpuBatchedModel.h +++ b/SynapseEngine/Engine/Mesh/Data/Gpu/GpuBatchedModel.h @@ -14,6 +14,7 @@ namespace Syn GpuVertexData vertexData; GpuIndexedDrawData indexedData; GpuMeshletDrawData meshletData; + std::vector meshMaterialIndices; std::vector materials; std::vector nodeTransforms; GpuMeshCollider globalCollider; diff --git a/SynapseEngine/Engine/Mesh/Data/Gpu/GpuModelBuffers.h b/SynapseEngine/Engine/Mesh/Data/Gpu/GpuModelBuffers.h index d6d26c93..c487f1a6 100644 --- a/SynapseEngine/Engine/Mesh/Data/Gpu/GpuModelBuffers.h +++ b/SynapseEngine/Engine/Mesh/Data/Gpu/GpuModelBuffers.h @@ -12,6 +12,7 @@ namespace Syn std::unique_ptr vertexPositions; std::unique_ptr vertexAttributes; std::unique_ptr indices; + std::unique_ptr meshMaterialIndices; // Traditional Pipeline Buffers std::unique_ptr meshDescriptors; @@ -34,6 +35,7 @@ namespace Syn VkDeviceAddress vertexPositions; VkDeviceAddress vertexAttributes; VkDeviceAddress indices; + VkDeviceAddress meshMaterialIndices; VkDeviceAddress meshDescriptors; VkDeviceAddress meshColliders; VkDeviceAddress lodDescriptors; @@ -50,8 +52,6 @@ namespace Syn uint32_t averageLodIndexCount; uint32_t meshCount; uint32_t padding0; - uint32_t padding1; - uint32_t padding2; GpuMeshCollider globalCollider; }; diff --git a/SynapseEngine/Engine/Mesh/ModelManager.cpp b/SynapseEngine/Engine/Mesh/ModelManager.cpp index defded92..bcbf936c 100644 --- a/SynapseEngine/Engine/Mesh/ModelManager.cpp +++ b/SynapseEngine/Engine/Mesh/ModelManager.cpp @@ -123,6 +123,7 @@ namespace Syn { uint32_t globalMatId = loadedMaterialIds[localMatIndex]; entry.resource->cpuData.meshMaterialIndices.push_back(globalMatId); + transientGpu.meshMaterialIndices.push_back(globalMatId); } } @@ -165,7 +166,26 @@ namespace Syn { entry.resource->transientCpuData.reset(); } - void ModelManager::FlushDirtyResources() { + void ModelManager::FlushDirtyResources() + { + std::unordered_set materialsToProcess; + + { + std::lock_guard lock(_pendingMaterialMutex); + materialsToProcess = _pendingMaterials; + _pendingMaterials.clear(); + } + + for (uint32_t materialId : materialsToProcess) { + auto affectedModels = GetModelsUsingMaterials(materialId); + + for (uint32_t modelId : affectedModels) { + if (_previewMarkDirtyCallback) { + _previewMarkDirtyCallback(modelId); + } + } + } + ProcessDirtyReadyEntries([this](uint32_t index, const EntryType& entry) { GpuModelAddresses addresses{}; @@ -175,6 +195,7 @@ namespace Syn { addresses.vertexPositions = hw.vertexPositions->GetDeviceAddress(); addresses.vertexAttributes = hw.vertexAttributes->GetDeviceAddress(); addresses.indices = hw.indices->GetDeviceAddress(); + addresses.meshMaterialIndices = hw.meshMaterialIndices->GetDeviceAddress(); addresses.meshDescriptors = hw.meshDescriptors->GetDeviceAddress(); addresses.meshColliders = hw.meshColliders->GetDeviceAddress(); addresses.lodDescriptors = hw.lodDescriptors->GetDeviceAddress(); @@ -197,4 +218,31 @@ namespace Syn { if (_previewMarkDirtyCallback) _previewMarkDirtyCallback(index); }); } + + std::vector ModelManager::GetModelsUsingMaterials(uint32_t materialId) const { + std::lock_guard lock(_mutex); + std::vector result; + + for (uint32_t i = 0; i < _entries.size(); ++i) { + if (_entries[i].state == ResourceState::Ready && _entries[i].resource) { + const auto& model = *_entries[i].resource; + + for (auto meshMaterialIndex : model.cpuData.meshMaterialIndices) + { + if (meshMaterialIndex == materialId) + { + result.push_back(i); + break; + } + } + } + } + + return result; + } + + void ModelManager::NotifyMaterialReady(uint32_t materialId) { + std::lock_guard lock(_pendingMaterialMutex); + _pendingMaterials.insert(materialId); + } } \ No newline at end of file diff --git a/SynapseEngine/Engine/Mesh/ModelManager.h b/SynapseEngine/Engine/Mesh/ModelManager.h index ac9eed1c..4efe154b 100644 --- a/SynapseEngine/Engine/Mesh/ModelManager.h +++ b/SynapseEngine/Engine/Mesh/ModelManager.h @@ -9,6 +9,8 @@ #include "Engine/Vk/Command/CommandPool.h" #include "Engine/Utils/WindowedBuffer.h" +#include + namespace Syn { using MaterialLoadCallback = std::function; @@ -37,6 +39,9 @@ namespace Syn { uint32_t LoadModelSync(const std::string& filePath); uint32_t LoadModelFromSourceSync(const std::string& name, MeshSourceFactory factory); uint32_t LoadModelFromStaticMeshSync(const std::string& name, StaticMeshFactory factory); + + std::vector GetModelsUsingMaterials(uint32_t materialId) const; + void NotifyMaterialReady(uint32_t materialId); protected: void FlushDirtyResources() override; void StartGpuUpload(EntryType& entry) override; @@ -47,5 +52,8 @@ namespace Syn { PreviewMarkDirtyCallback _previewMarkDirtyCallback; std::shared_ptr _builder; std::unique_ptr _uploader; + + std::mutex _pendingMaterialMutex; + std::unordered_set _pendingMaterials; }; } \ No newline at end of file diff --git a/SynapseEngine/Engine/Mesh/Uploader/DefaultGpuModelUploader.cpp b/SynapseEngine/Engine/Mesh/Uploader/DefaultGpuModelUploader.cpp index 6320e4e2..508fd3f4 100644 --- a/SynapseEngine/Engine/Mesh/Uploader/DefaultGpuModelUploader.cpp +++ b/SynapseEngine/Engine/Mesh/Uploader/DefaultGpuModelUploader.cpp @@ -12,6 +12,7 @@ namespace Syn totalStagingSize += getAlignedSize(data.vertexData.vertexPositions.size() * sizeof(GpuVertexPosition)); totalStagingSize += getAlignedSize(data.vertexData.vertexAttributes.size() * sizeof(GpuVertexAttributes)); totalStagingSize += getAlignedSize(data.indexedData.indices.size() * sizeof(uint32_t)); + totalStagingSize += getAlignedSize(data.meshMaterialIndices.size() * sizeof(uint32_t)); totalStagingSize += getAlignedSize(data.indexedData.meshDescriptors.size() * sizeof(GpuMeshDescriptor)); totalStagingSize += getAlignedSize(data.indexedData.meshColliders.size() * sizeof(GpuMeshCollider)); totalStagingSize += getAlignedSize(data.indexedData.lodDescriptors.size() * sizeof(GpuMeshLodDescriptor)); @@ -64,6 +65,7 @@ namespace Syn uploadVector(data.vertexData.vertexPositions, vertexFlags, result.hardwareBuffers.vertexPositions); uploadVector(data.vertexData.vertexAttributes, vertexFlags, result.hardwareBuffers.vertexAttributes); uploadVector(data.indexedData.indices, indexFlags, result.hardwareBuffers.indices); + uploadVector(data.meshMaterialIndices, ssboFlags, result.hardwareBuffers.meshMaterialIndices); // Traditional Pipeline uploadVector(data.indexedData.meshDescriptors, ssboFlags, result.hardwareBuffers.meshDescriptors); diff --git a/SynapseEngine/Engine/Render/Passes/Preview/ModelPreviewPass.cpp b/SynapseEngine/Engine/Render/Passes/Preview/ModelPreviewPass.cpp new file mode 100644 index 00000000..b7388cf9 --- /dev/null +++ b/SynapseEngine/Engine/Render/Passes/Preview/ModelPreviewPass.cpp @@ -0,0 +1,174 @@ +#include "ModelPreviewPass.h" +#include "Engine/ServiceLocator.h" +#include "Engine/Manager/ShaderManager.h" +#include "Engine/Image/ImageManager.h" +#include "Engine/Scene/Scene.h" +#include "Engine/Mesh/ModelManager.h" +#include "Engine/Vk/Image/ImageViewNames.h" +#include "Engine/Manager/PreviewManager.h" +#include "Engine/Vk/Rendering/PushConstant.h" + +#include +#include + +namespace Syn { + +#include "Engine/Shaders/Includes/PushConstants/ModelPreviewPC.glsl" + + void ModelPreviewPass::Initialize() { + auto shaderManager = ServiceLocator::GetShaderManager(); + auto imageManager = ServiceLocator::GetImageManager(); + + Vk::ShaderProgramConfig config; + config.useDescriptorBuffers = true; + config.layoutOverride = [imageManager](uint32_t setIndex) { + if (setIndex == 0) return imageManager->GetBindlessLayout(); + return VkDescriptorSetLayout{}; + }; + + _shaderProgram = shaderManager->CreateProgram("ModelPreviewProgram", { + ShaderNames::ModelPreviewVert, + ShaderNames::ModelPreviewFrag, + }, config); + + _graphicsState = { + .raster = { + .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, + .cullMode = VK_CULL_MODE_BACK_BIT, + .frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE, + .polygonMode = VK_POLYGON_MODE_FILL, + .lineWidth = 1.0f + }, + .depth = { + .testEnable = VK_TRUE, + .writeEnable = VK_TRUE, + .compareOp = VK_COMPARE_OP_LESS + }, + .blendStates = { + { + .enable = VK_FALSE, + .srcColorFactor = VK_BLEND_FACTOR_SRC_ALPHA, + .dstColorFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, + .colorBlendOp = VK_BLEND_OP_ADD, + .srcAlphaFactor = VK_BLEND_FACTOR_ONE, + .dstAlphaFactor = VK_BLEND_FACTOR_ZERO, + .alphaBlendOp = VK_BLEND_OP_ADD + } + }, + .colorAttachmentCount = 1, + .renderArea = std::nullopt + }; + } + + void ModelPreviewPass::PrepareFrame(const RenderContext& context) { + _dirtyModels.clear(); + _renderInfo.reset(); + _colorAttachments.clear(); + + auto pm = ServiceLocator::GetPreviewManager(); + + _dirtyModels = pm->GetDirtyResources(PreviewResourceType::Model); + if (_dirtyModels.empty()) return; + + auto atlas = pm->GetAtlasImage(); + auto atlasDepth = pm->GetAtlasDepthImage(); + VkExtent2D extent = { atlas->GetExtent().width, atlas->GetExtent().height }; + _graphicsState.renderArea = std::nullopt; + + _colorAttachments.push_back(Vk::RenderUtils::CreateAttachment({ + .imageView = atlas->GetView(Vk::ImageViewNames::Default), + .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + .loadOp = VK_ATTACHMENT_LOAD_OP_LOAD, + .storeOp = VK_ATTACHMENT_STORE_OP_STORE + })); + + _depthAttachment = Vk::RenderUtils::CreateAttachment({ + .imageView = atlasDepth->GetView(Vk::ImageViewNames::Default), + .layout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL, + .clearValue = VkClearValue{.depthStencil = {1.0f, 0}}, + .loadOp = VK_ATTACHMENT_LOAD_OP_LOAD, + .storeOp = VK_ATTACHMENT_STORE_OP_STORE, + }); + + _renderInfo = Vk::RenderingInfoConfig{ + .renderArea = extent, + .colorAttachments = _colorAttachments, + .depthAttachment = &_depthAttachment.value(), + .layerCount = 1 + }; + } + + void ModelPreviewPass::BindDescriptors(const RenderContext& context) { + if (_dirtyModels.empty()) return; + + auto imageManager = ServiceLocator::GetImageManager(); + auto bindlessBuffer = imageManager->GetBindlessBuffer(); + bindlessBuffer->Bind(context.cmd, _shaderProgram->GetLayout(), 0, VK_PIPELINE_BIND_POINT_GRAPHICS); + } + + void ModelPreviewPass::Draw(const RenderContext& context) { + if (_dirtyModels.empty() || !_renderInfo.has_value()) return; + + auto pm = ServiceLocator::GetPreviewManager(); + auto modelManager = ServiceLocator::GetModelManager(); + + Vk::PushConstant pc; + pc->frameGlobalContextBufferAddr = context.scene->GetSceneDrawData()->frameContextBuffer.GetAddress(context.frameIndex); + + auto modelSnapshot = modelManager->GetResourceSnapshot(); + + for (uint32_t modelId : _dirtyModels) + { + auto resource = modelSnapshot[modelId].resource; + if (resource == nullptr) continue; + + VkViewport viewport{}; + VkRect2D scissor{}; + pm->GetViewportAndScissor(PreviewResourceType::Model, modelId, viewport, scissor); + + vkCmdSetViewportWithCount(context.cmd, 1, &viewport); + vkCmdSetScissorWithCount(context.cmd, 1, &scissor); + + VkClearAttachment clearAttachments[2] = {}; + + clearAttachments[0].aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + clearAttachments[0].colorAttachment = 0; + clearAttachments[0].clearValue.color = { {0.15f, 0.15f, 0.15f, 1.0f} }; + + clearAttachments[1].aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; + clearAttachments[1].clearValue.depthStencil = { 1.0f, 0 }; + + VkClearRect clearRect{}; + clearRect.rect = scissor; + clearRect.baseArrayLayer = 0; + clearRect.layerCount = 1; + vkCmdClearAttachments(context.cmd, 2, clearAttachments, 1, &clearRect); + + const auto& cpuData = resource->cpuData; + glm::vec3 center = cpuData.globalCollider.center; + float radius = cpuData.globalCollider.radius > 0.0f ? cpuData.globalCollider.radius : 1.0f; + + glm::mat4 view = glm::lookAt(glm::vec3(0.0f, 0.0f, radius * 2.5f), glm::vec3(0.0f), glm::vec3(0.0f, 1.0f, 0.0f)); + glm::mat4 proj = glm::perspective(glm::radians(45.0f), 1.0f, 0.1f, 1000.0f); + proj[1][1] *= -1.0f; + + glm::mat4 model = glm::translate(glm::mat4(1.0f), -center); + + pc->modelId = modelId; + pc->mvp = proj * view * model; + + const auto& drawCommands = cpuData.baseDrawCommands; + uint32_t meshCount = cpuData.globalMeshCount; + + for (uint32_t meshIdx = 0; meshIdx < meshCount; ++meshIdx) { + pc->meshIndex = meshIdx; + pc.Push(context.cmd, _shaderProgram->GetLayout()); + + uint32_t lod0CommandIndex = meshIdx * 4; + const auto& cmd = drawCommands[lod0CommandIndex]; + + vkCmdDraw(context.cmd, cmd.traditionalCmd.vertexCount, 1, cmd.traditionalCmd.firstVertex, cmd.traditionalCmd.firstInstance); + } + } + } +} \ No newline at end of file diff --git a/SynapseEngine/Engine/Render/Passes/Preview/ModelPreviewPass.h b/SynapseEngine/Engine/Render/Passes/Preview/ModelPreviewPass.h new file mode 100644 index 00000000..1d8d2da1 --- /dev/null +++ b/SynapseEngine/Engine/Render/Passes/Preview/ModelPreviewPass.h @@ -0,0 +1,24 @@ +#pragma once +#include "Engine/SynApi.h" +#include "Engine/Render/Passes/GraphicsPass.h" +#include + +namespace Syn +{ + class SYN_API ModelPreviewPass : public GraphicsPass { + public: + ModelPreviewPass() = default; + + std::string GetName() const override { return "ModelPreviewPass"; } + std::string GetGroup() const override { return PassGroupNames::UtilityPasses; } + + void Initialize() override; + bool ShouldCollectStatistics() const override { return false; } + protected: + void PrepareFrame(const RenderContext& context) override; + void BindDescriptors(const RenderContext& context) override; + void Draw(const RenderContext& context) override; + private: + std::vector _dirtyModels; + }; +} \ No newline at end of file diff --git a/SynapseEngine/Engine/Render/Passes/Preview/PreviewPostTransitionPass.cpp b/SynapseEngine/Engine/Render/Passes/Preview/PreviewPostTransitionPass.cpp index e98db864..0b71c770 100644 --- a/SynapseEngine/Engine/Render/Passes/Preview/PreviewPostTransitionPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Preview/PreviewPostTransitionPass.cpp @@ -9,9 +9,9 @@ namespace Syn { _imageTransitions.push_back({ .image = pm->GetAtlasImage(), - .newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, - .dstStage = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, - .dstAccess = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT, + .newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + .dstStage = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT, + .dstAccess = VK_ACCESS_2_SHADER_READ_BIT, .discardContent = false }); @@ -30,5 +30,13 @@ namespace Syn { .dstAccess = VK_ACCESS_2_SHADER_WRITE_BIT | VK_ACCESS_2_SHADER_READ_BIT, .discardContent = false }); + + _imageTransitions.push_back({ + .image = pm->GetAtlasDepthImage(), + .newLayout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL, + .dstStage = VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT, + .dstAccess = VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_READ_BIT, + .discardContent = false + }); } } \ No newline at end of file diff --git a/SynapseEngine/Engine/Render/Passes/Preview/PreviewPreTransitionPass.cpp b/SynapseEngine/Engine/Render/Passes/Preview/PreviewPreTransitionPass.cpp index fc2f6c1b..ecf3f3d1 100644 --- a/SynapseEngine/Engine/Render/Passes/Preview/PreviewPreTransitionPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Preview/PreviewPreTransitionPass.cpp @@ -14,5 +14,29 @@ namespace Syn { .dstAccess = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT, .discardContent = false }); + + _imageTransitions.push_back({ + .image = pm->GetScratchColorImage(), + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .dstStage = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, + .dstAccess = VK_ACCESS_2_SHADER_WRITE_BIT | VK_ACCESS_2_SHADER_READ_BIT, + .discardContent = false + }); + + _imageTransitions.push_back({ + .image = pm->GetScratchBloomImage(), + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .dstStage = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, + .dstAccess = VK_ACCESS_2_SHADER_WRITE_BIT | VK_ACCESS_2_SHADER_READ_BIT, + .discardContent = false + }); + + _imageTransitions.push_back({ + .image = pm->GetAtlasDepthImage(), + .newLayout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL, + .dstStage = VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT, + .dstAccess = VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_READ_BIT, + .discardContent = false + }); } } \ No newline at end of file diff --git a/SynapseEngine/Engine/Render/RendererFactory.cpp b/SynapseEngine/Engine/Render/RendererFactory.cpp index 99a58dce..4384773c 100644 --- a/SynapseEngine/Engine/Render/RendererFactory.cpp +++ b/SynapseEngine/Engine/Render/RendererFactory.cpp @@ -16,6 +16,7 @@ #include "Engine/Render/Passes/Preview/PreviewPreTransitionPass.h" #include "Engine/Render/Passes/Preview/PreviewPostTransitionPass.h" +#include "Engine/Render/Passes/Preview/ModelPreviewPass.h" #include "Engine/Render/Passes/Preview/MaterialPreviewPass.h" #include "Engine/Render/Passes/Preview/MaterialPreviewBloomPass.h" @@ -393,6 +394,7 @@ namespace Syn //Preview Passes pipeline->AddPass(std::make_unique()); pipeline->AddPass(std::make_unique()); + pipeline->AddPass(std::make_unique()); pipeline->AddPass(std::make_unique()); pipeline->AddPass(std::make_unique()); diff --git a/SynapseEngine/Engine/Render/ShaderNames.h b/SynapseEngine/Engine/Render/ShaderNames.h index 536cdc7f..203a49be 100644 --- a/SynapseEngine/Engine/Render/ShaderNames.h +++ b/SynapseEngine/Engine/Render/ShaderNames.h @@ -149,5 +149,8 @@ namespace Syn static constexpr const char* MaterialPreviewVert = "Engine/Shaders/Passes/Preview/MaterialPreview.vert"; static constexpr const char* MaterialPreviewFrag = "Engine/Shaders/Passes/Preview/MaterialPreview.frag"; + + static constexpr const char* ModelPreviewVert = "Engine/Shaders/Passes/Preview/ModelPreview.vert"; + static constexpr const char* ModelPreviewFrag = "Engine/Shaders/Passes/Preview/ModelPreview.frag"; }; } \ No newline at end of file diff --git a/SynapseEngine/Engine/Shaders/Includes/Common/Mesh.glsl b/SynapseEngine/Engine/Shaders/Includes/Common/Mesh.glsl index 506545f9..63da4d9d 100644 --- a/SynapseEngine/Engine/Shaders/Includes/Common/Mesh.glsl +++ b/SynapseEngine/Engine/Shaders/Includes/Common/Mesh.glsl @@ -41,6 +41,7 @@ struct GpuModelAddresses { uint64_t vertexPositions; uint64_t vertexAttributes; uint64_t indices; + uint64_t meshMaterialIndices; uint64_t meshDescriptors; uint64_t meshColliders; uint64_t lodDescriptors; @@ -56,9 +57,7 @@ struct GpuModelAddresses { uint indexCount; uint averageLodIndexCount; uint meshCount; - uint padding0; - uint padding1; - uint padding2; + uint padding; GpuMeshCollider globalCollider; }; @@ -105,10 +104,12 @@ layout(buffer_reference, std430) readonly restrict buffer MeshletColliderBuffer layout(buffer_reference, std430) readonly restrict buffer VertexIndicesBuffer { uint data[]; }; layout(buffer_reference, std430) readonly restrict buffer TriangleIndicesBuffer { uint8_t data[]; }; layout(buffer_reference, std430) readonly restrict buffer DebugMeshletInstanceBuffer { DebugMeshletInstance data[]; }; +layout(buffer_reference, std430) readonly restrict buffer MaterialIndices { uint data[]; }; #define GET_VERTEX_POS(addr, idx) PositionBuffer(addr).data[idx] #define GET_VERTEX_ATTR(addr, idx) AttributeBuffer(addr).data[idx] #define GET_INDEX(addr, idx) IndexBuffer(addr).data[idx] +#define GET_DEFUALT_MATERIAL_INDEX(addr, idx) MaterialIndices(addr).data[idx] #define GET_MODEL_ADDRESSES(addr, idx) ModelAddressBuffer(addr).data[idx] #define GET_DRAW_DESCRIPTOR(addr, idx) DescriptorBuffer(addr).data[idx] #define GET_MESHLET_DRAW_DESC(addr, idx) MeshletDrawDescBuffer(addr).data[idx] diff --git a/SynapseEngine/Engine/Shaders/Includes/PushConstants/ModelPreviewPC.glsl b/SynapseEngine/Engine/Shaders/Includes/PushConstants/ModelPreviewPC.glsl new file mode 100644 index 00000000..ba7f9a98 --- /dev/null +++ b/SynapseEngine/Engine/Shaders/Includes/PushConstants/ModelPreviewPC.glsl @@ -0,0 +1,13 @@ +#ifndef SYN_INCLUDES_PC_MODEL_PREVIEW_GLSL +#define SYN_INCLUDES_PC_MODEL_PREVIEW_GLSL + +#include "../SharedGpuTypes.glsl" + +struct ModelPreviewPC { + uint64_t frameGlobalContextBufferAddr; + uint modelId; + uint meshIndex; + mat4 mvp; +}; + +#endif \ No newline at end of file diff --git a/SynapseEngine/Engine/Shaders/Passes/Preview/MaterialPreview.frag b/SynapseEngine/Engine/Shaders/Passes/Preview/MaterialPreview.frag index 1ab62cba..f8758ba7 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Preview/MaterialPreview.frag +++ b/SynapseEngine/Engine/Shaders/Passes/Preview/MaterialPreview.frag @@ -54,6 +54,11 @@ void main() { Material mat = GET_MATERIAL(ctx.materialBufferAddr, pc.materialId); vec4 albedoAlpha = EvaluateAlbedoAlpha(ctx.textureMetadataBufferAddr, mat, sphereUV); + + if (albedoAlpha.a < ctx.alphaLimitDiscard) { + discard; + } + vec3 finalNormal = EvaluateNormal(ctx.textureMetadataBufferAddr, mat, sphereUV, localNormal, inTangent); vec2 metalRough = EvaluateMetallicRoughness(ctx.textureMetadataBufferAddr, mat, sphereUV); vec3 emissive = EvaluateEmissive(ctx.textureMetadataBufferAddr, mat, sphereUV); diff --git a/SynapseEngine/Engine/Shaders/Passes/Preview/ModelPreview.frag b/SynapseEngine/Engine/Shaders/Passes/Preview/ModelPreview.frag new file mode 100644 index 00000000..3d40428a --- /dev/null +++ b/SynapseEngine/Engine/Shaders/Passes/Preview/ModelPreview.frag @@ -0,0 +1,74 @@ +#version 460 +#extension GL_GOOGLE_include_directive : require +#extension GL_EXT_nonuniform_qualifier : require +#extension GL_EXT_buffer_reference2 : require +#extension GL_EXT_shader_explicit_arithmetic_types_int64 : require + +#include "../../Includes/Core.glsl" +#include "../../Includes/Common/FrameGlobalContext.glsl" +#include "../../Includes/Common/Material.glsl" +#include "../../Includes/Common/Texture.glsl" +#include "../../Includes/Utils/MaterialMath.glsl" +#include "../../Includes/Utils/PbrMath.glsl" +#include "../../Includes/Utils/LightMath.glsl" + +layout(location = 0) in vec3 inNormal; +layout(location = 1) in vec4 inTangent; +layout(location = 2) in vec2 inUV; +layout(location = 3) in flat uint inMaterialId; + +layout(location = 0) out vec4 outColor; + +#include "../../Includes/PushConstants/ModelPreviewPC.glsl" + +layout(push_constant) uniform PushConstants { + ModelPreviewPC pc; +}; + +void main() { + FrameGlobalContext ctx = GET_FRAME_CONTEXT(pc.frameGlobalContextBufferAddr); + Material mat = GET_MATERIAL(ctx.materialBufferAddr, inMaterialId); + + vec4 albedoAlpha = EvaluateAlbedoAlpha(ctx.textureMetadataBufferAddr, mat, inUV); + if (albedoAlpha.a < ctx.alphaLimitDiscard) { + discard; + } + + vec3 finalNormal = EvaluateNormal(ctx.textureMetadataBufferAddr, mat, inUV, inNormal, inTangent); + vec2 metalRough = EvaluateMetallicRoughness(ctx.textureMetadataBufferAddr, mat, inUV); + vec3 emissive = EvaluateEmissive(ctx.textureMetadataBufferAddr, mat, inUV); + float ao = EvaluateAO(ctx.textureMetadataBufferAddr, mat, inUV); + + float finalMetalness = clamp(metalRough.x, 0.0, 1.0); + float finalRoughness = clamp(metalRough.y, 0.04, 1.0); + + vec3 viewDir = vec3(0.0, 0.0, 1.0); + vec3 totalRadiance = vec3(0.0); + + // Key Light + vec3 keyLightDir = normalize(vec3(1.0, 1.0, 1.0)); + vec3 keyLightColor = vec3(1.0, 1.0, 1.0); + float keyLightStrength = 2.5; + totalRadiance += ShadePhysicallyBased( + albedoAlpha.rgb, finalNormal, viewDir, keyLightDir, + finalRoughness, finalMetalness, keyLightColor, 1.0, keyLightStrength + ); + + // Fill Light + vec3 fillLightDir = normalize(vec3(-1.0, 0.2, -0.5)); + vec3 fillLightColor = vec3(0.5, 0.6, 0.8); + float fillLightStrength = 1.0; + totalRadiance += ShadePhysicallyBased( + albedoAlpha.rgb, finalNormal, viewDir, fillLightDir, + finalRoughness, finalMetalness, fillLightColor, 1.0, fillLightStrength + ); + + // Ambient és Bloom + totalRadiance += SimulateAmbientLight(albedoAlpha.rgb, ao, ctx.ambientStrength); + totalRadiance += SimulateBloom(emissive, 1.0, ctx.emissiveStrength); + + vec3 bgColor = vec3(0.15); + vec3 finalColor = mix(bgColor, totalRadiance, IS_TRANSPARENT(mat) ? albedoAlpha.a : 1.0); + + outColor = vec4(finalColor, 1.0); +} \ No newline at end of file diff --git a/SynapseEngine/Engine/Shaders/Passes/Preview/ModelPreview.vert b/SynapseEngine/Engine/Shaders/Passes/Preview/ModelPreview.vert new file mode 100644 index 00000000..886a922a --- /dev/null +++ b/SynapseEngine/Engine/Shaders/Passes/Preview/ModelPreview.vert @@ -0,0 +1,45 @@ +#version 460 +#extension GL_GOOGLE_include_directive : require +#extension GL_EXT_nonuniform_qualifier : require +#extension GL_EXT_buffer_reference2 : require +#extension GL_EXT_shader_explicit_arithmetic_types_int64 : require + +#include "../../Includes/Core.glsl" +#include "../../Includes/Common/FrameGlobalContext.glsl" +#include "../../Includes/Common/Mesh.glsl" +#include "../../Includes/Common/Model.glsl" +#include "../../Includes/Common/Transform.glsl" + +layout(location = 0) out vec3 outNormal; +layout(location = 1) out vec4 outTangent; +layout(location = 2) out vec2 outUV; +layout(location = 3) out flat uint outMaterialId; + +#include "../../Includes/PushConstants/ModelPreviewPC.glsl" + +layout(push_constant) uniform PushConstants { + ModelPreviewPC pc; +}; + +void main() { + FrameGlobalContext ctx = GET_FRAME_CONTEXT(pc.frameGlobalContextBufferAddr); + GpuModelAddresses addrs = GET_MODEL_ADDRESSES(ctx.modelAddressBufferAddr, pc.modelId); + + uint realVertexIndex = GET_INDEX(addrs.indices, gl_VertexIndex); + GpuVertexPosition v = GET_VERTEX_POS(addrs.vertexPositions, realVertexIndex); + GpuVertexAttributes attr = GET_VERTEX_ATTR(addrs.vertexAttributes, realVertexIndex); + + uint nodeIndex = UNPACK_UINT16_X(v.packedIndex); + uint meshIndex = UNPACK_UINT16_Y(v.packedIndex); + + GpuNodeTransform staticNodeTransform = GET_NODE_TRANSFORM(addrs.nodeTransforms, nodeIndex); + mat4 finalModelMat = staticNodeTransform.globalTransform; + mat4 finalModelMatIT = staticNodeTransform.globalTransformIT; + + gl_Position = pc.mvp * finalModelMat * vec4(v.position, 1.0); + + outNormal = (finalModelMatIT * vec4(attr.normal, 0.0)).xyz; + outTangent = vec4((finalModelMat * vec4(attr.tangent, 0.0)).xyz, 1.0); + outUV = vec2(attr.uv_x, 1.0 - attr.uv_y); + outMaterialId = GET_DEFUALT_MATERIAL_INDEX(addrs.meshMaterialIndices, pc.meshIndex); +} \ No newline at end of file diff --git a/SynapseEngine/Engine/Shaders/Passes/Shading/ForwardPlus/DepthPrepass/PreDepth.frag b/SynapseEngine/Engine/Shaders/Passes/Shading/ForwardPlus/DepthPrepass/PreDepth.frag index 2319d1ae..4d9ad8b1 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Shading/ForwardPlus/DepthPrepass/PreDepth.frag +++ b/SynapseEngine/Engine/Shaders/Passes/Shading/ForwardPlus/DepthPrepass/PreDepth.frag @@ -38,6 +38,7 @@ void main() { // 2. Evaluate Albedo & Alpha vec4 albedoAlpha = EvaluateAlbedoAlpha(ctx.textureMetadataBufferAddr, mat, finalUV); + if (albedoAlpha.a < ctx.alphaLimitDiscard) { discard; } From 37422f98765fd9976ba621237a88a5c91e526eab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20P=C3=A9ter?= Date: Fri, 10 Jul 2026 17:57:48 +0200 Subject: [PATCH 46/51] Resolved default sync loading preview texture rendering issues --- SynapseEngine/Engine/Engine.cpp | 8 +++- .../Engine/Manager/BaseResourceManager.h | 2 + .../Engine/Manager/PreviewManager.cpp | 16 ++++++++ SynapseEngine/Engine/Manager/PreviewManager.h | 2 + .../Engine/Material/MaterialManager.cpp | 40 ++++++++++--------- .../Engine/Material/MaterialManager.h | 1 + SynapseEngine/Engine/Mesh/ModelManager.cpp | 36 ++++++++--------- SynapseEngine/Engine/Mesh/ModelManager.h | 2 +- 8 files changed, 69 insertions(+), 38 deletions(-) diff --git a/SynapseEngine/Engine/Engine.cpp b/SynapseEngine/Engine/Engine.cpp index ac97eb82..6b9bcb3c 100644 --- a/SynapseEngine/Engine/Engine.cpp +++ b/SynapseEngine/Engine/Engine.cpp @@ -79,10 +79,16 @@ namespace Syn ServiceLocator::GetCpuProfiler()->BeginFrame(currentFrame); + //Updates ServiceLocator::GetAnimationManager()->Update(); ServiceLocator::GetModelManager()->Update(); ServiceLocator::GetMaterialManager()->Update(); ServiceLocator::GetImageManager()->Update(); + + //Notifications + ServiceLocator::GetMaterialManager()->ProcessPendingNotifications(); + ServiceLocator::GetModelManager()->ProcessPendingNotifications(); + ServiceLocator::GetGpuUploader()->ProcessUploads(); _sceneManager->Update(_frameContext.deltaTime, currentFrame); @@ -124,7 +130,7 @@ namespace Syn _inputManager = std::make_unique(); ServiceLocator::ProvideInputManager(_inputManager.get()); - InitFrameContext(2); + InitFrameContext(1); InitLogger(); InitVulkan(params); InitTaskExecutor(); diff --git a/SynapseEngine/Engine/Manager/BaseResourceManager.h b/SynapseEngine/Engine/Manager/BaseResourceManager.h index 6edbec7f..74da2200 100644 --- a/SynapseEngine/Engine/Manager/BaseResourceManager.h +++ b/SynapseEngine/Engine/Manager/BaseResourceManager.h @@ -60,6 +60,8 @@ namespace Syn { virtual ~BaseResourceManager() = default; virtual void Update(); + virtual void ProcessPendingNotifications() {} + void WaitForResource(uint32_t id) const; void SetResourceState(uint32_t id, ResourceState newState); void MarkDirty(uint32_t id); diff --git a/SynapseEngine/Engine/Manager/PreviewManager.cpp b/SynapseEngine/Engine/Manager/PreviewManager.cpp index 43b98b74..8a06ba57 100644 --- a/SynapseEngine/Engine/Manager/PreviewManager.cpp +++ b/SynapseEngine/Engine/Manager/PreviewManager.cpp @@ -135,6 +135,14 @@ namespace Syn { for (auto& [type, set] : _dirtyResources) { set.clear(); } + + if (_warmupFramesRemaining > 0) { + --_warmupFramesRemaining; + for (const auto& [id, tile] : _resourceToTile) { + PreviewResourceType type = static_cast(id >> 32); + _dirtyResources[type].insert(static_cast(id & 0xFFFFFFFF)); + } + } } void PreviewManager::GetViewportAndScissor(PreviewResourceType type, uint32_t resourceId, VkViewport& outViewport, VkRect2D& outScissor) const { @@ -193,4 +201,12 @@ namespace Syn { return result; } + + void PreviewManager::MarkAllActiveDirty() { + std::lock_guard lock(_mutex); + for (const auto& [id, tile] : _resourceToTile) { + PreviewResourceType type = static_cast(id >> 32); + _dirtyResources[type].insert(static_cast(id & 0xFFFFFFFF)); + } + } } \ No newline at end of file diff --git a/SynapseEngine/Engine/Manager/PreviewManager.h b/SynapseEngine/Engine/Manager/PreviewManager.h index 06ed0b0d..a238d3dd 100644 --- a/SynapseEngine/Engine/Manager/PreviewManager.h +++ b/SynapseEngine/Engine/Manager/PreviewManager.h @@ -29,6 +29,7 @@ namespace Syn { void FreeTile(PreviewResourceType type, uint32_t resourceId); bool HasTile(PreviewResourceType type, uint32_t resourceId) const; + void MarkAllActiveDirty(); void MarkDirty(PreviewResourceType type, uint32_t resourceId); std::vector GetDirtyResources(PreviewResourceType type); void ClearDirtyResources(PreviewResourceType type); @@ -53,6 +54,7 @@ namespace Syn { uint32_t _resolution; uint32_t _tileSize; uint32_t _tilesPerRow; + uint32_t _warmupFramesRemaining = 1; std::unique_ptr _atlasImage; std::unique_ptr _atlasDepthImage; diff --git a/SynapseEngine/Engine/Material/MaterialManager.cpp b/SynapseEngine/Engine/Material/MaterialManager.cpp index c6663426..b7f41215 100644 --- a/SynapseEngine/Engine/Material/MaterialManager.cpp +++ b/SynapseEngine/Engine/Material/MaterialManager.cpp @@ -86,24 +86,6 @@ namespace Syn { void MaterialManager::FlushDirtyResources() { - std::unordered_set imagesToProcess; - - { - std::lock_guard lock(_pendingImageMutex); - imagesToProcess = _pendingImages; - _pendingImages.clear(); - } - - for (uint32_t imgId : imagesToProcess) { - auto affectedMaterials = GetMaterialsUsingTexture(imgId); - - for (uint32_t matId : affectedMaterials) { - if (_previewMarkDirtyCallback) { - _previewMarkDirtyCallback(matId); - } - } - } - ProcessDirtyReadyEntries( [this](uint32_t index, const EntryType& entry) { WriteAddress(index, GpuMaterial(*entry.resource)); @@ -144,4 +126,26 @@ namespace Syn { std::lock_guard lock(_pendingImageMutex); _pendingImages.insert(imageId); } + + void MaterialManager::ProcessPendingNotifications() { + std::unordered_set imagesToProcess; + + { + std::lock_guard lock(_pendingImageMutex); + imagesToProcess.swap(_pendingImages); + } + + if (imagesToProcess.empty()) return; + + for (uint32_t imgId : imagesToProcess) { + for (uint32_t matId : GetMaterialsUsingTexture(imgId)) + { + if (_previewMarkDirtyCallback) + _previewMarkDirtyCallback(matId); + + if (_materialReadyOrChangedCallback) + _materialReadyOrChangedCallback(matId); + } + } + } } \ No newline at end of file diff --git a/SynapseEngine/Engine/Material/MaterialManager.h b/SynapseEngine/Engine/Material/MaterialManager.h index 6f4fde64..7992bf0a 100644 --- a/SynapseEngine/Engine/Material/MaterialManager.h +++ b/SynapseEngine/Engine/Material/MaterialManager.h @@ -28,6 +28,7 @@ namespace Syn { uint32_t LoadMaterialDirect(const std::string& name, const Material& material); std::vector GetMaterialsUsingTexture(uint32_t textureId) const; void NotifyImageReady(uint32_t imageId); + void ProcessPendingNotifications() override; protected: void FlushDirtyResources() override; void StartGpuUpload(EntryType& entry) override; diff --git a/SynapseEngine/Engine/Mesh/ModelManager.cpp b/SynapseEngine/Engine/Mesh/ModelManager.cpp index bcbf936c..5248e632 100644 --- a/SynapseEngine/Engine/Mesh/ModelManager.cpp +++ b/SynapseEngine/Engine/Mesh/ModelManager.cpp @@ -168,24 +168,6 @@ namespace Syn { void ModelManager::FlushDirtyResources() { - std::unordered_set materialsToProcess; - - { - std::lock_guard lock(_pendingMaterialMutex); - materialsToProcess = _pendingMaterials; - _pendingMaterials.clear(); - } - - for (uint32_t materialId : materialsToProcess) { - auto affectedModels = GetModelsUsingMaterials(materialId); - - for (uint32_t modelId : affectedModels) { - if (_previewMarkDirtyCallback) { - _previewMarkDirtyCallback(modelId); - } - } - } - ProcessDirtyReadyEntries([this](uint32_t index, const EntryType& entry) { GpuModelAddresses addresses{}; @@ -245,4 +227,22 @@ namespace Syn { std::lock_guard lock(_pendingMaterialMutex); _pendingMaterials.insert(materialId); } + + void ModelManager::ProcessPendingNotifications() { + std::unordered_set materialsToProcess; + + { + std::lock_guard lock(_pendingMaterialMutex); + materialsToProcess.swap(_pendingMaterials); + } + + if (materialsToProcess.empty()) return; + + for (uint32_t materialId : materialsToProcess) { + for (uint32_t modelId : GetModelsUsingMaterials(materialId)) { + if (_previewMarkDirtyCallback) + _previewMarkDirtyCallback(modelId); + } + } + } } \ No newline at end of file diff --git a/SynapseEngine/Engine/Mesh/ModelManager.h b/SynapseEngine/Engine/Mesh/ModelManager.h index 4efe154b..3d465bf9 100644 --- a/SynapseEngine/Engine/Mesh/ModelManager.h +++ b/SynapseEngine/Engine/Mesh/ModelManager.h @@ -31,7 +31,6 @@ namespace Syn { ~ModelManager() = default; - uint32_t LoadModelAsync(const std::string& filePath); uint32_t LoadModelFromSourceAsync(const std::string& name, MeshSourceFactory factory); uint32_t LoadModelFromStaticMeshAsync(const std::string& name, StaticMeshFactory factory); @@ -42,6 +41,7 @@ namespace Syn { std::vector GetModelsUsingMaterials(uint32_t materialId) const; void NotifyMaterialReady(uint32_t materialId); + void ProcessPendingNotifications() override; protected: void FlushDirtyResources() override; void StartGpuUpload(EntryType& entry) override; From 57fc377eee38187b53800e60c91a63e01456e97e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20P=C3=A9ter?= Date: Fri, 10 Jul 2026 19:59:18 +0200 Subject: [PATCH 47/51] Resolved large model preview viewport display bug --- .../Editor/EditorApi/Impl/ModelApiImpl.cpp | 28 +++++++++++++++---- SynapseEngine/Engine/Engine.cpp | 2 +- .../Passes/Preview/ModelPreviewPass.cpp | 18 +++++++++--- SynapseEngine/synapse_stats.txt | 10 +++---- 4 files changed, 42 insertions(+), 16 deletions(-) diff --git a/SynapseEngine/Editor/EditorApi/Impl/ModelApiImpl.cpp b/SynapseEngine/Editor/EditorApi/Impl/ModelApiImpl.cpp index 38290e15..103af020 100644 --- a/SynapseEngine/Editor/EditorApi/Impl/ModelApiImpl.cpp +++ b/SynapseEngine/Editor/EditorApi/Impl/ModelApiImpl.cpp @@ -1,6 +1,7 @@ #include "ModelApiImpl.h" #include "Engine/Scene/Insiders/SceneInsider.h" #include "Engine/Component/Core/TagComponent.h" +#include "Engine/Component/Core/TransformComponent.h" #include "Engine/Component/Core/CameraComponent.h" #include "Engine/Component/Rendering/ModelComponent.h" #include "Editor/EditorApi/EditorApiUtils.h" @@ -83,22 +84,37 @@ namespace Syn { auto resource = _modelManager->GetResource(modelId); if (resource) { glm::vec3 center = resource->cpuData.globalCollider.center; - float radius = resource->cpuData.globalCollider.radius; + float radius = resource->cpuData.globalCollider.radius * 1.05f; + + float targetSize = radius; + + if (targetSize > 100) + targetSize = 100.0f; + + float scaleFactor = targetSize / (radius * 2.0f); for (EntityID entity : tagPool->GetDenseEntities()) { const auto& tag = tagPool->Get(entity); - if (tag.tag == "Camera" && registry.HasComponent(entity)) { + if (tag.tag == "Preview" && registry.HasComponent(entity)) { + EditorApiUtils::ModifyComponent( + _sceneManager, + entity, + [center, scaleFactor](auto& transformComp, auto pool) { + transformComp.scale = glm::vec3(scaleFactor); + } + ); + } + if (tag.tag == "Camera" && registry.HasComponent(entity)) { EditorApiUtils::ModifyComponent( _sceneManager, entity, - [center, radius](auto& camComp, auto pool) { - camComp.target = center; - camComp.distance = std::max(radius * 2.5f, 2.0f); + [targetSize](auto& camComp, auto pool) { + camComp.target = glm::vec3(0.0f); + camComp.distance = targetSize * 1.25f; } ); - break; } } } diff --git a/SynapseEngine/Engine/Engine.cpp b/SynapseEngine/Engine/Engine.cpp index 6b9bcb3c..4f81ff15 100644 --- a/SynapseEngine/Engine/Engine.cpp +++ b/SynapseEngine/Engine/Engine.cpp @@ -130,7 +130,7 @@ namespace Syn _inputManager = std::make_unique(); ServiceLocator::ProvideInputManager(_inputManager.get()); - InitFrameContext(1); + InitFrameContext(2); InitLogger(); InitVulkan(params); InitTaskExecutor(); diff --git a/SynapseEngine/Engine/Render/Passes/Preview/ModelPreviewPass.cpp b/SynapseEngine/Engine/Render/Passes/Preview/ModelPreviewPass.cpp index b7388cf9..d3da2d00 100644 --- a/SynapseEngine/Engine/Render/Passes/Preview/ModelPreviewPass.cpp +++ b/SynapseEngine/Engine/Render/Passes/Preview/ModelPreviewPass.cpp @@ -146,13 +146,23 @@ namespace Syn { const auto& cpuData = resource->cpuData; glm::vec3 center = cpuData.globalCollider.center; - float radius = cpuData.globalCollider.radius > 0.0f ? cpuData.globalCollider.radius : 1.0f; + float radius = cpuData.globalCollider.radius > 0.0f ? cpuData.globalCollider.radius * 1.05f : 1.0f; - glm::mat4 view = glm::lookAt(glm::vec3(0.0f, 0.0f, radius * 2.5f), glm::vec3(0.0f), glm::vec3(0.0f, 1.0f, 0.0f)); - glm::mat4 proj = glm::perspective(glm::radians(45.0f), 1.0f, 0.1f, 1000.0f); + float targetSize = 10.0f; + float scaleFactor = targetSize / (radius * 2.0f); + float cameraDistance = targetSize * 1.25f; + + glm::vec3 camDir = glm::normalize(glm::vec3(1.0f, 0.75f, 1.0f)); + glm::vec3 camPos = camDir * cameraDistance; + glm::mat4 view = glm::lookAt(camPos, glm::vec3(0.0f), glm::vec3(0.0f, 1.0f, 0.0f)); + + float nearPlane = targetSize * 0.05f; + float farPlane = targetSize * 5.0f; + glm::mat4 proj = glm::perspective(glm::radians(45.0f), 1.0f, nearPlane, farPlane); proj[1][1] *= -1.0f; - glm::mat4 model = glm::translate(glm::mat4(1.0f), -center); + glm::mat4 model = glm::scale(glm::mat4(1.0f), glm::vec3(scaleFactor)); + model = glm::translate(model, -center); pc->modelId = modelId; pc->mvp = proj * view * model; diff --git a/SynapseEngine/synapse_stats.txt b/SynapseEngine/synapse_stats.txt index f94b50d7..f11d4d47 100644 --- a/SynapseEngine/synapse_stats.txt +++ b/SynapseEngine/synapse_stats.txt @@ -1,10 +1,10 @@ -github.com/AlDanial/cloc v 2.08 T=11.91 s (147.5 files/s, 7631.2 lines/s) +github.com/AlDanial/cloc v 2.08 T=31.49 s (56.6 files/s, 2959.7 lines/s) ------------------------------------------------------------------------------- Language files blank comment code ------------------------------------------------------------------------------- -C++ 737 9727 717 42787 -C/C++ Header 840 3610 100 21477 -GLSL 180 2754 625 9120 +C++ 746 9977 732 44116 +C/C++ Header 851 3686 100 21819 +GLSL 186 2815 631 9324 ------------------------------------------------------------------------------- -SUM: 1757 16091 1442 73384 +SUM: 1783 16478 1463 75259 ------------------------------------------------------------------------------- From 70c240092c96ba3765453c22acc44b8ccbf56b27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20P=C3=A9ter?= Date: Sun, 12 Jul 2026 14:21:54 +0200 Subject: [PATCH 48/51] Found and fixed the long-standing hidden GPU memory garbage read bug. Implemented entity delete, create, and copy functionality. --- .../Editor/EditorApi/EditorApiUtils.h | 38 +++ .../Editor/EditorApi/EditorContext.cpp | 2 +- .../EditorApi/Impl/HierarchyApiImpl.cpp | 296 +++++++++++++++++- .../Editor/EditorApi/Impl/HierarchyApiImpl.h | 10 +- SynapseEngine/Editor/Manager/EditorIcons.h | 1 + .../Editor/View/Hierarchy/HierarchyView.cpp | 74 ++++- .../Editor/View/Logger/LoggerView.cpp | 111 +++---- SynapseEngine/EditorCore/Api/IHierarchyApi.h | 34 +- .../ViewModels/Hierarchy/HierarchyIntent.h | 18 +- .../Hierarchy/HierarchyViewModel.cpp | 18 +- SynapseEngine/Engine/Component/Components.h | 2 + SynapseEngine/Engine/Registry/Pool/Pool.h | 7 + SynapseEngine/Engine/Registry/Registry.h | 1 + .../Scene/Source/Procedural/test_config.json | 12 +- .../DepthPrepass/TraditionalPreDepth.vert | 7 - 15 files changed, 538 insertions(+), 93 deletions(-) diff --git a/SynapseEngine/Editor/EditorApi/EditorApiUtils.h b/SynapseEngine/Editor/EditorApi/EditorApiUtils.h index 0b7ef5c3..877f2472 100644 --- a/SynapseEngine/Editor/EditorApi/EditorApiUtils.h +++ b/SynapseEngine/Editor/EditorApi/EditorApiUtils.h @@ -2,8 +2,16 @@ #include "Engine/Scene/SceneManager.h" #include "Engine/Scene/Scene.h" #include "EditorCore/Types/EntityHandle.h" +#include +#include namespace Syn { + + template + concept HasStorageCategory = requires(T pool, EntityID entity) { + pool->SetCategory(entity, pool->GetCategory(entity)); + }; + class EditorApiUtils { public: template @@ -44,5 +52,35 @@ namespace Syn { auto registry = scene->GetRegistry(); return registry && registry->HasComponent(entity); } + + + template + static void CloneEntityComponents(SceneManager* sm, EntityID src, EntityID dst) { + auto scene = sm->GetActiveScene(); + if (!scene) return; + + auto registry = scene->GetRegistry(); + if (!registry) return; + + ( + [&]() { + auto pool = registry->GetPool(); + if (pool && pool->Has(src)) { + + if constexpr (std::is_empty_v) { + pool->Add(dst); + } + else { + registry->AddComponent(dst); + registry->GetComponent(dst) = registry->GetComponent(src); + } + + if constexpr (HasStorageCategory) { + pool->SetCategory(dst, pool->GetCategory(src)); + } + } + }(), ... + ); + } }; } \ No newline at end of file diff --git a/SynapseEngine/Editor/EditorApi/EditorContext.cpp b/SynapseEngine/Editor/EditorApi/EditorContext.cpp index 2550462a..aa3d519c 100644 --- a/SynapseEngine/Editor/EditorApi/EditorContext.cpp +++ b/SynapseEngine/Editor/EditorApi/EditorContext.cpp @@ -35,7 +35,7 @@ namespace Syn { RegisterApi(sm); RegisterApi(sm); RegisterApi(); - RegisterApi(sm); + RegisterApi(sm, engine->GetModelManager()); RegisterApi(engine); RegisterApi(engine->GetMaterialManager(), sm); RegisterApi(engine, textureManager, sm); diff --git a/SynapseEngine/Editor/EditorApi/Impl/HierarchyApiImpl.cpp b/SynapseEngine/Editor/EditorApi/Impl/HierarchyApiImpl.cpp index d30898f5..abf47a5c 100644 --- a/SynapseEngine/Editor/EditorApi/Impl/HierarchyApiImpl.cpp +++ b/SynapseEngine/Editor/EditorApi/Impl/HierarchyApiImpl.cpp @@ -9,6 +9,8 @@ #include "Engine/Component/Light/Point/PointLightComponent.h" #include "Engine/Component/Light/Spot/SpotLightComponent.h" #include "Engine/Component/Rendering/AnimationComponent.h" +#include "Engine/Component/Components.h" +#include "Engine/Mesh/MeshSourceNames.h" namespace Syn { uint64_t HierarchyApiImpl::GetVersion() const { @@ -60,7 +62,7 @@ namespace Syn { else scene->GetHierarchyManager()->AttachChild(parent, child); } - EntityID HierarchyApiImpl::CreateEntity(const std::string& name, EntityID parent) { + EntityID HierarchyApiImpl::CreateEntity(EntityTemplate templateType, EntityID parent) { auto scene = _sceneManager->GetActiveScene(); if (!scene || !scene->GetRegistry()) return NULL_ENTITY; @@ -68,10 +70,224 @@ namespace Syn { auto registry = scene->GetRegistry(); registry->AddComponent(newEntity); - registry->GetComponent(newEntity).name = name; registry->AddComponent(newEntity); - if (parent != NULL_ENTITY) SetParent(newEntity, parent); + registry->GetPool()->SetCategory(newEntity, StorageCategory::Static); + registry->GetPool()->SetCategory(newEntity, StorageCategory::Static); + + std::string name = "Entity"; + + switch (templateType) { + case EntityTemplate::Empty: + name = "Empty Entity"; + break; + + case EntityTemplate::Camera: + name = "Camera"; + registry->AddComponent(newEntity); + registry->GetPool()->SetCategory(newEntity, StorageCategory::Stream); + registry->GetPool()->SetCategory(newEntity, StorageCategory::Stream); + break; + + case EntityTemplate::DirectionalLight: + name = "Directional Light"; + registry->AddComponent(newEntity); + registry->GetPool()->SetCategory(newEntity, StorageCategory::Stream); + registry->GetPool()->SetCategory(newEntity, StorageCategory::Stream); + registry->GetPool()->SetBit(newEntity); + registry->GetPool()->SetBit(newEntity); + break; + case EntityTemplate::PointLight: + name = "Point Light"; + registry->AddComponent(newEntity); + registry->GetPool()->SetCategory(newEntity, StorageCategory::Stream); + registry->GetPool()->SetCategory(newEntity, StorageCategory::Stream); + registry->GetPool()->SetBit(newEntity); + registry->GetPool()->SetBit(newEntity); + break; + case EntityTemplate::SpotLight: + name = "Spot Light"; + registry->AddComponent(newEntity); + registry->GetPool()->SetCategory(newEntity, StorageCategory::Stream); + registry->GetPool()->SetCategory(newEntity, StorageCategory::Stream); + registry->GetPool()->SetBit(newEntity); + registry->GetPool()->SetBit(newEntity); + break; + + case EntityTemplate::BoxCollider: + name = "Box Collider"; + registry->AddComponent(newEntity); + registry->AddComponent(newEntity); + registry->AddComponent(newEntity); + registry->AddComponent(newEntity); + registry->GetComponent(newEntity).modelIndex = _modelManager->GetResourceIndex(MeshSourceNames::Cube); + + registry->GetPool()->SetCategory(newEntity, StorageCategory::Stream); + registry->GetPool()->SetCategory(newEntity, StorageCategory::Static); + registry->GetPool()->SetCategory(newEntity, StorageCategory::Static); + registry->GetPool()->SetCategory(newEntity, StorageCategory::Static); + registry->GetPool()->SetCategory(newEntity, StorageCategory::Static); + break; + case EntityTemplate::SphereCollider: + name = "Sphere Collider"; + registry->AddComponent(newEntity); + registry->AddComponent(newEntity); + registry->AddComponent(newEntity); + registry->AddComponent(newEntity); + registry->GetComponent(newEntity).modelIndex = _modelManager->GetResourceIndex(MeshSourceNames::Sphere); + + registry->GetPool()->SetCategory(newEntity, StorageCategory::Stream); + registry->GetPool()->SetCategory(newEntity, StorageCategory::Static); + registry->GetPool()->SetCategory(newEntity, StorageCategory::Static); + registry->GetPool()->SetCategory(newEntity, StorageCategory::Static); + registry->GetPool()->SetCategory(newEntity, StorageCategory::Static); + break; + case EntityTemplate::CapsuleCollider: + name = "Capsule Collider"; + registry->AddComponent(newEntity); + registry->AddComponent(newEntity); + registry->AddComponent(newEntity); + registry->AddComponent(newEntity); + registry->GetComponent(newEntity).modelIndex = _modelManager->GetResourceIndex(MeshSourceNames::Capsule); + + registry->GetPool()->SetCategory(newEntity, StorageCategory::Stream); + registry->GetPool()->SetCategory(newEntity, StorageCategory::Static); + registry->GetPool()->SetCategory(newEntity, StorageCategory::Static); + registry->GetPool()->SetCategory(newEntity, StorageCategory::Static); + registry->GetPool()->SetCategory(newEntity, StorageCategory::Static); + break; + case EntityTemplate::ConvexCollider: + name = "Convex Collider"; + registry->AddComponent(newEntity); + registry->AddComponent(newEntity); + registry->AddComponent(newEntity); + registry->AddComponent(newEntity); + + registry->GetPool()->SetCategory(newEntity, StorageCategory::Stream); + registry->GetPool()->SetCategory(newEntity, StorageCategory::Static); + registry->GetPool()->SetCategory(newEntity, StorageCategory::Static); + registry->GetPool()->SetCategory(newEntity, StorageCategory::Static); + registry->GetPool()->SetCategory(newEntity, StorageCategory::Static); + break; + case EntityTemplate::MeshCollider: + name = "Mesh Collider"; + registry->AddComponent(newEntity); + registry->AddComponent(newEntity); + registry->AddComponent(newEntity); + registry->AddComponent(newEntity); + + registry->GetPool()->SetCategory(newEntity, StorageCategory::Stream); + registry->GetPool()->SetCategory(newEntity, StorageCategory::Static); + registry->GetPool()->SetCategory(newEntity, StorageCategory::Stream); + registry->GetPool()->SetCategory(newEntity, StorageCategory::Static); + registry->GetPool()->SetCategory(newEntity, StorageCategory::Static); + break; + + case EntityTemplate::ShapeCube: + name = "Cube"; + registry->AddComponent(newEntity); + registry->AddComponent(newEntity); + registry->GetComponent(newEntity).modelIndex = _modelManager->GetResourceIndex(MeshSourceNames::Cube); + registry->GetPool()->SetCategory(newEntity, StorageCategory::Static); + registry->GetPool()->SetCategory(newEntity, StorageCategory::Static); + break; + case EntityTemplate::ShapeSphere: + name = "Sphere"; + registry->AddComponent(newEntity); + registry->AddComponent(newEntity); + registry->GetComponent(newEntity).modelIndex = _modelManager->GetResourceIndex(MeshSourceNames::Sphere); + registry->GetPool()->SetCategory(newEntity, StorageCategory::Static); + registry->GetPool()->SetCategory(newEntity, StorageCategory::Static); + break; + case EntityTemplate::ShapeQuad: + name = "Quad"; + registry->AddComponent(newEntity); + registry->AddComponent(newEntity); + registry->GetComponent(newEntity).modelIndex = _modelManager->GetResourceIndex(MeshSourceNames::Quad); + registry->GetPool()->SetCategory(newEntity, StorageCategory::Static); + registry->GetPool()->SetCategory(newEntity, StorageCategory::Static); + break; + case EntityTemplate::ShapeCylinder: + name = "Cylinder"; + registry->AddComponent(newEntity); + registry->AddComponent(newEntity); + registry->GetComponent(newEntity).modelIndex = _modelManager->GetResourceIndex(MeshSourceNames::Cylinder); + registry->GetPool()->SetCategory(newEntity, StorageCategory::Static); + registry->GetPool()->SetCategory(newEntity, StorageCategory::Static); + break; + case EntityTemplate::ShapeCone: + name = "Cone"; + registry->AddComponent(newEntity); + registry->AddComponent(newEntity); + registry->GetComponent(newEntity).modelIndex = _modelManager->GetResourceIndex(MeshSourceNames::Cone); + registry->GetPool()->SetCategory(newEntity, StorageCategory::Static); + registry->GetPool()->SetCategory(newEntity, StorageCategory::Static); + break; + case EntityTemplate::ShapeCapsule: + name = "Capsule"; + registry->AddComponent(newEntity); + registry->AddComponent(newEntity); + registry->GetComponent(newEntity).modelIndex = _modelManager->GetResourceIndex(MeshSourceNames::Capsule); + registry->GetPool()->SetCategory(newEntity, StorageCategory::Static); + registry->GetPool()->SetCategory(newEntity, StorageCategory::Static); + break; + case EntityTemplate::ShapeHemisphere: + name = "Hemisphere"; + registry->AddComponent(newEntity); + registry->AddComponent(newEntity); + registry->GetComponent(newEntity).modelIndex = _modelManager->GetResourceIndex(MeshSourceNames::Hemisphere); + registry->GetPool()->SetCategory(newEntity, StorageCategory::Static); + registry->GetPool()->SetCategory(newEntity, StorageCategory::Static); + break; + case EntityTemplate::ShapePyramid: + name = "Pyramid"; + registry->AddComponent(newEntity); + registry->AddComponent(newEntity); + registry->GetComponent(newEntity).modelIndex = _modelManager->GetResourceIndex(MeshSourceNames::Pyramid); + registry->GetPool()->SetCategory(newEntity, StorageCategory::Static); + registry->GetPool()->SetCategory(newEntity, StorageCategory::Static); + break; + case EntityTemplate::ShapeGrid: + name = "Grid"; + registry->AddComponent(newEntity); + registry->AddComponent(newEntity); + registry->GetComponent(newEntity).modelIndex = _modelManager->GetResourceIndex(MeshSourceNames::Grid); + registry->GetPool()->SetCategory(newEntity, StorageCategory::Static); + registry->GetPool()->SetCategory(newEntity, StorageCategory::Static); + break; + case EntityTemplate::ShapeTorus: + name = "Torus"; + registry->AddComponent(newEntity); + registry->AddComponent(newEntity); + registry->GetComponent(newEntity).modelIndex = _modelManager->GetResourceIndex(MeshSourceNames::Torus); + registry->GetPool()->SetCategory(newEntity, StorageCategory::Static); + registry->GetPool()->SetCategory(newEntity, StorageCategory::Static); + break; + + case EntityTemplate::Model: + name = "Model"; + registry->AddComponent(newEntity); + registry->AddComponent(newEntity); + registry->GetPool()->SetCategory(newEntity, StorageCategory::Static); + registry->GetPool()->SetCategory(newEntity, StorageCategory::Static); + break; + case EntityTemplate::Animation: + name = "Animation"; + registry->AddComponent(newEntity); + registry->AddComponent(newEntity); + registry->AddComponent(newEntity); + + registry->GetPool()->SetCategory(newEntity, StorageCategory::Stream); + registry->GetPool()->SetCategory(newEntity, StorageCategory::Static); + registry->GetPool()->SetCategory(newEntity, StorageCategory::Static); + break; + } + + registry->GetComponent(newEntity).name = name; + + if (parent != NULL_ENTITY) { + SetParent(newEntity, parent); + } return newEntity; } @@ -83,9 +299,83 @@ namespace Syn { if (scene->GetSelectedEntity() == entity) { scene->SetSelectedEntity(NULL_ENTITY); } + scene->DestroyEntity(entity); } + void HierarchyApiImpl::DestroyEntityRecursive(EntityID entity) { + auto children = GetChildren(entity); + for (EntityID child : children) { + DestroyEntityRecursive(child); + } + DestroyEntity(entity); + } + + void HierarchyApiImpl::DestroyEntityKeepChildren(EntityID entity) { + EntityID parent = GetParent(entity); + auto children = GetChildren(entity); + + for (EntityID child : children) { + SetParent(child, parent); + } + + DestroyEntity(entity); + } + + EntityID HierarchyApiImpl::CopyEntity(EntityID entity, EntityID parent) { + auto scene = _sceneManager->GetActiveScene(); + if (!scene || !scene->GetRegistry()) return NULL_ENTITY; + + EntityID newEntity = scene->CreateEntity(); + auto registry = scene->GetRegistry(); + + EditorApiUtils::CloneEntityComponents< + TagComponent, + TransformComponent, + CameraComponent, + ModelComponent, + AnimationComponent, + MaterialOverrideComponent, + DirectionLightComponent, + PointLightComponent, + SpotLightComponent, + DirectionLightShadowComponent, + PointLightShadowComponent, + SpotLightShadowComponent, + BoxColliderComponent, + SphereColliderComponent, + CapsuleColliderComponent, + ConvexColliderComponent, + MeshColliderComponent, + RigidBodyComponent + >(_sceneManager, entity, newEntity); + + if (registry->HasComponent(newEntity)) { + registry->GetComponent(newEntity).name += " (Copy)"; + } + + if (registry->HasComponent(newEntity)) { + registry->GetPool()->SetBit(newEntity); + } + + if (parent != NULL_ENTITY) { + SetParent(newEntity, parent); + } + + return newEntity; + } + + EntityID HierarchyApiImpl::FullCopyEntity(EntityID entity, EntityID parent) { + EntityID newEntity = CopyEntity(entity, parent); + + auto children = GetChildren(entity); + for (EntityID child : children) { + FullCopyEntity(child, newEntity); + } + + return newEntity; + } + EntityID HierarchyApiImpl::GetParent(EntityID entity) const { auto scene = _sceneManager->GetActiveScene(); if (!scene || !scene->GetRegistry()) return NULL_ENTITY; diff --git a/SynapseEngine/Editor/EditorApi/Impl/HierarchyApiImpl.h b/SynapseEngine/Editor/EditorApi/Impl/HierarchyApiImpl.h index 16ce7776..6bb9cd1e 100644 --- a/SynapseEngine/Editor/EditorApi/Impl/HierarchyApiImpl.h +++ b/SynapseEngine/Editor/EditorApi/Impl/HierarchyApiImpl.h @@ -2,10 +2,11 @@ #include "EditorCore/Api/IHierarchyApi.h" #include "Engine/Scene/SceneManager.h" + namespace Syn { class HierarchyApiImpl : public IHierarchyApi { public: - HierarchyApiImpl(SceneManager* sm) : _sceneManager(sm) {} + HierarchyApiImpl(SceneManager* sm, ModelManager* modelManager) : _sceneManager(sm), _modelManager(modelManager) {} uint64_t GetVersion() const override; std::vector GetRootEntities() const override; @@ -14,9 +15,14 @@ namespace Syn { bool HasChildren(EntityID entity) const override; EntityID GetParent(EntityID entity) const override; void SetParent(EntityID child, EntityID parent) override; - EntityID CreateEntity(const std::string& name, EntityID parent = NULL_ENTITY) override; + EntityID CreateEntity(EntityTemplate templateType, EntityID parent = NULL_ENTITY) override; + void DestroyEntityRecursive(EntityID entity) override; + void DestroyEntityKeepChildren(EntityID entity) override; + EntityID CopyEntity(EntityID entity, EntityID parent = NULL_ENTITY) override; + EntityID FullCopyEntity(EntityID entity, EntityID parent = NULL_ENTITY) override; void DestroyEntity(EntityID entity) override; private: SceneManager* _sceneManager; + ModelManager* _modelManager; }; } \ No newline at end of file diff --git a/SynapseEngine/Editor/Manager/EditorIcons.h b/SynapseEngine/Editor/Manager/EditorIcons.h index 749aeef8..b409c6a1 100644 --- a/SynapseEngine/Editor/Manager/EditorIcons.h +++ b/SynapseEngine/Editor/Manager/EditorIcons.h @@ -28,6 +28,7 @@ constexpr const char* ASSET_PATH = "Assets"; #define SYN_ICON_CUBES ICON_FA_CUBES #define SYN_ICON_VIDEO ICON_FA_VIDEO #define SYN_ICON_TRASH ICON_FA_TRASH +#define SYN_ICON_COPY ICON_FA_COPY #define SYN_ICON_EXPAND_ALL ICON_FA_ANGLE_DOUBLE_DOWN #define SYN_ICON_COLLAPSE_ALL ICON_FA_ANGLE_DOUBLE_UP diff --git a/SynapseEngine/Editor/View/Hierarchy/HierarchyView.cpp b/SynapseEngine/Editor/View/Hierarchy/HierarchyView.cpp index 1eba5107..63c81d72 100644 --- a/SynapseEngine/Editor/View/Hierarchy/HierarchyView.cpp +++ b/SynapseEngine/Editor/View/Hierarchy/HierarchyView.cpp @@ -234,21 +234,85 @@ namespace Syn { } void HierarchyView::RenderContextMenu(HierarchyViewModel& vm, EntityID contextEntity) { + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(8.0f, 8.0f)); + + auto DrawStyledSeparator = []() { + ImGui::PushStyleColor(ImGuiCol_Separator, ImVec4(0.35f, 0.35f, 0.35f, 1.0f)); + ImGui::Separator(); + ImGui::PopStyleColor(); + }; + if (ImGui::MenuItem(SYN_ICON_CUBE " Empty Entity")) { - vm.Dispatch(HierarchyCreateEntityIntent{ "Empty Entity", contextEntity }); + vm.Dispatch(HierarchyCreateEntityIntent{ EntityTemplate::Empty, contextEntity }); } - ImGui::Separator(); - if (ImGui::MenuItem(SYN_ICON_VIDEO " Camera")) { - vm.Dispatch(HierarchyCreateEntityIntent{ "Camera", contextEntity }); + vm.Dispatch(HierarchyCreateEntityIntent{ EntityTemplate::Camera, contextEntity }); + } + + DrawStyledSeparator(); + + if (ImGui::BeginMenu(SYN_ICON_LIGHTBULB " Lights")) { + if (ImGui::MenuItem("Directional Light")) vm.Dispatch(HierarchyCreateEntityIntent{ EntityTemplate::DirectionalLight, contextEntity }); + if (ImGui::MenuItem("Point Light")) vm.Dispatch(HierarchyCreateEntityIntent{ EntityTemplate::PointLight, contextEntity }); + if (ImGui::MenuItem("Spot Light")) vm.Dispatch(HierarchyCreateEntityIntent{ EntityTemplate::SpotLight, contextEntity }); + ImGui::EndMenu(); + } + + if (ImGui::BeginMenu(SYN_ICON_CUBE " Shape")) { + if (ImGui::MenuItem("Cube")) vm.Dispatch(HierarchyCreateEntityIntent{ EntityTemplate::ShapeCube, contextEntity }); + if (ImGui::MenuItem("Sphere")) vm.Dispatch(HierarchyCreateEntityIntent{ EntityTemplate::ShapeSphere, contextEntity }); + if (ImGui::MenuItem("Quad")) vm.Dispatch(HierarchyCreateEntityIntent{ EntityTemplate::ShapeQuad, contextEntity }); + if (ImGui::MenuItem("Cylinder")) vm.Dispatch(HierarchyCreateEntityIntent{ EntityTemplate::ShapeCylinder, contextEntity }); + if (ImGui::MenuItem("Cone")) vm.Dispatch(HierarchyCreateEntityIntent{ EntityTemplate::ShapeCone, contextEntity }); + if (ImGui::MenuItem("Capsule")) vm.Dispatch(HierarchyCreateEntityIntent{ EntityTemplate::ShapeCapsule, contextEntity }); + if (ImGui::MenuItem("Hemisphere")) vm.Dispatch(HierarchyCreateEntityIntent{ EntityTemplate::ShapeHemisphere, contextEntity }); + if (ImGui::MenuItem("Pyramid")) vm.Dispatch(HierarchyCreateEntityIntent{ EntityTemplate::ShapePyramid, contextEntity }); + if (ImGui::MenuItem("Grid")) vm.Dispatch(HierarchyCreateEntityIntent{ EntityTemplate::ShapeGrid, contextEntity }); + if (ImGui::MenuItem("Torus")) vm.Dispatch(HierarchyCreateEntityIntent{ EntityTemplate::ShapeTorus, contextEntity }); + ImGui::EndMenu(); + } + + if (ImGui::BeginMenu(SYN_ICON_CUBE " Collider")) { + if (ImGui::MenuItem("Box Collider")) vm.Dispatch(HierarchyCreateEntityIntent{ EntityTemplate::BoxCollider, contextEntity }); + if (ImGui::MenuItem("Sphere Collider")) vm.Dispatch(HierarchyCreateEntityIntent{ EntityTemplate::SphereCollider, contextEntity }); + if (ImGui::MenuItem("Capsule Collider")) vm.Dispatch(HierarchyCreateEntityIntent{ EntityTemplate::CapsuleCollider, contextEntity }); + if (ImGui::MenuItem("Convex Collider")) vm.Dispatch(HierarchyCreateEntityIntent{ EntityTemplate::ConvexCollider, contextEntity }); + if (ImGui::MenuItem("Mesh Collider")) vm.Dispatch(HierarchyCreateEntityIntent{ EntityTemplate::MeshCollider, contextEntity }); + ImGui::EndMenu(); + } + + DrawStyledSeparator(); + + if (ImGui::MenuItem(SYN_ICON_CUBE " Model")) { + vm.Dispatch(HierarchyCreateEntityIntent{ EntityTemplate::Model, contextEntity }); + } + + if (ImGui::MenuItem(SYN_ICON_RUNNING " Animation")) { + vm.Dispatch(HierarchyCreateEntityIntent{ EntityTemplate::Animation, contextEntity }); } if (contextEntity != NULL_ENTITY) { - ImGui::Separator(); + DrawStyledSeparator(); + + if (ImGui::MenuItem(SYN_ICON_COPY " Copy")) { + vm.Dispatch(HierarchyCopyEntityIntent{ contextEntity }); + } + if (ImGui::MenuItem(SYN_ICON_COPY " Full Copy")) { + vm.Dispatch(HierarchyFullCopyEntityIntent{ contextEntity }); + } + + DrawStyledSeparator(); + if (ImGui::MenuItem(SYN_ICON_TRASH " Delete")) { vm.Dispatch(HierarchyDestroyEntityIntent{ contextEntity }); } + + if (ImGui::MenuItem(SYN_ICON_TRASH " Delete (Keep Children)")) { + vm.Dispatch(HierarchyDestroyKeepChildrenIntent{ contextEntity }); + } } + + ImGui::PopStyleVar(); } } \ No newline at end of file diff --git a/SynapseEngine/Editor/View/Logger/LoggerView.cpp b/SynapseEngine/Editor/View/Logger/LoggerView.cpp index 754c7af1..88f5a8af 100644 --- a/SynapseEngine/Editor/View/Logger/LoggerView.cpp +++ b/SynapseEngine/Editor/View/Logger/LoggerView.cpp @@ -4,6 +4,7 @@ #include "Engine/Logger/LogUtils.h" #include #include +#include namespace Syn { @@ -47,46 +48,31 @@ namespace Syn { void LoggerView::RenderTopBar(LoggerViewModel& vm, const LoggerState& state) { - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.8f, 0.8f, 0.8f, 1.0f)); - if (ImGui::Button(SYN_ICON_FILTER " Filters")) { - ImGui::OpenPopup("LogLevelFilterPopup"); - } - ImGui::PopStyleColor(2); - - if (ImGui::BeginPopup("LogLevelFilterPopup")) { - ImGui::TextDisabled("Log Levels"); - ImGui::Separator(); - - bool showInfo = state.filters.showInfo; - ImGui::PushStyleColor(ImGuiCol_Text, GetColorForLevel(LogLevel::Info)); - if (ImGui::Checkbox("Info", &showInfo)) vm.Dispatch(LoggerToggleLevelIntent{ LogLevel::Info, showInfo }); - ImGui::PopStyleColor(); - - bool showWarn = state.filters.showWarning; - ImGui::PushStyleColor(ImGuiCol_Text, GetColorForLevel(LogLevel::Warning)); - if (ImGui::Checkbox("Warning", &showWarn)) vm.Dispatch(LoggerToggleLevelIntent{ LogLevel::Warning, showWarn }); - ImGui::PopStyleColor(); - - bool showError = state.filters.showError; - ImGui::PushStyleColor(ImGuiCol_Text, GetColorForLevel(LogLevel::Error)); - if (ImGui::Checkbox("Error", &showError)) vm.Dispatch(LoggerToggleLevelIntent{ LogLevel::Error, showError }); - ImGui::PopStyleColor(); + auto DrawFilterToggle = [&](const char* label, LogLevel level, bool& currentVal) { + ImGui::PushStyleColor(ImGuiCol_Text, currentVal ? GetColorForLevel(level) : ImVec4(0.4f, 0.4f, 0.4f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1, 1, 1, 0.1f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1, 1, 1, 0.2f)); + + if (ImGui::Button(label)) { + bool newVal = !currentVal; + vm.Dispatch(LoggerToggleLevelIntent{ level, newVal }); + } - bool showCrit = state.filters.showCritical; - ImGui::PushStyleColor(ImGuiCol_Text, GetColorForLevel(LogLevel::Critical)); - if (ImGui::Checkbox("Critical", &showCrit)) vm.Dispatch(LoggerToggleLevelIntent{ LogLevel::Critical, showCrit }); - ImGui::PopStyleColor(); + ImGui::PopStyleColor(4); + }; - ImGui::EndPopup(); - } + bool showInfo = state.filters.showInfo; + bool showWarn = state.filters.showWarning; + bool showError = state.filters.showError; + bool showCrit = state.filters.showCritical; - ImGui::SameLine(); + DrawFilterToggle("Info", LogLevel::Info, showInfo); ImGui::SameLine(); + DrawFilterToggle("Warn", LogLevel::Warning, showWarn); ImGui::SameLine(); + DrawFilterToggle("Error", LogLevel::Error, showError); ImGui::SameLine(); + DrawFilterToggle("Crit", LogLevel::Critical, showCrit); - float autoScrollWidth = ImGui::CalcTextSize("Auto-Scroll").x + 35.0f; - float clearBtnWidth = ImGui::CalcTextSize(SYN_ICON_TRASH " Clear").x + ImGui::GetStyle().FramePadding.x * 2.0f; - float spacing = ImGui::GetStyle().ItemSpacing.x; - float rightItemsTotalWidth = autoScrollWidth + clearBtnWidth + spacing * 3.0f; + ImGui::SameLine(0, 15.0f); ImGui::AlignTextToFramePadding(); ImGui::TextDisabled(SYN_ICON_SEARCH); @@ -96,6 +82,8 @@ namespace Syn { strncpy(searchBuffer, state.filters.searchQuery.c_str(), sizeof(searchBuffer)); searchBuffer[sizeof(searchBuffer) - 1] = '\0'; + float rightItemsTotalWidth = ImGui::CalcTextSize("Auto-Scroll").x + ImGui::CalcTextSize(SYN_ICON_TRASH " Clear").x + 70.0f; + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - rightItemsTotalWidth); if (ImGui::InputTextWithHint("##LogSearch", "Filter logs by message or file...", searchBuffer, IM_ARRAYSIZE(searchBuffer))) { vm.Dispatch(LoggerSetSearchQueryIntent{ std::string(searchBuffer) }); @@ -113,11 +101,9 @@ namespace Syn { void LoggerView::RenderLogTable(const LoggerState& state, float tableHeight) { ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(4, 4)); ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 8.0f); - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); - ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.08f, 0.08f, 0.08f, 0.6f)); - ImGui::BeginChild("LogTableContainer", ImVec2(0, tableHeight), ImGuiChildFlags_Borders, ImGuiWindowFlags_NoScrollbar); + ImGui::BeginChild("LogTableContainer", ImVec2(0, tableHeight), false, ImGuiWindowFlags_NoScrollbar); if (state.filteredLogs.empty()) { ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 8.0f); @@ -125,34 +111,22 @@ namespace Syn { ImGui::TextDisabled("No logs match the current filters."); } else { - ImGuiTableFlags flags = ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY | ImGuiTableFlags_RowBg; + ImGuiTableFlags flags = ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY | ImGuiTableFlags_ScrollX | ImGuiTableFlags_PadOuterX; if (ImGui::BeginTable("LogTable", 4, flags)) { ImGui::TableSetupScrollFreeze(0, 1); - ImGui::TableSetupColumn("Time", ImGuiTableColumnFlags_WidthFixed, 140.0f); - ImGui::TableSetupColumn("Level", ImGuiTableColumnFlags_WidthFixed, 60.0f); - ImGui::TableSetupColumn("Source", ImGuiTableColumnFlags_WidthFixed, 150.0f); + ImGui::TableSetupColumn("Time", ImGuiTableColumnFlags_WidthFixed, 100.0f); + ImGui::TableSetupColumn("Level", ImGuiTableColumnFlags_WidthFixed, 50.0f); + ImGui::TableSetupColumn("Source", ImGuiTableColumnFlags_WidthFixed, 130.0f); ImGui::TableSetupColumn("Message", ImGuiTableColumnFlags_WidthStretch); ImGui::TableNextRow(ImGuiTableRowFlags_Headers); for (int column = 0; column < 4; column++) { ImGui::TableSetColumnIndex(column); const char* columnName = ImGui::TableGetColumnName(column); - - ImGui::PushID(column); - - float cellWidth = ImGui::GetColumnWidth(); - float textWidth = ImGui::CalcTextSize(columnName).x; - ImVec2 startPos = ImGui::GetCursorPos(); - - ImGui::TableHeader(""); - - ImGui::SetCursorPos(ImVec2(startPos.x + (cellWidth - textWidth) * 0.5f, startPos.y + 3.0f)); - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); - ImGui::Text("%s", columnName); + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.6f, 0.6f, 0.6f, 1.0f)); + ImGui::TableHeader(columnName); ImGui::PopStyleColor(); - - ImGui::PopID(); } ImGuiListClipper clipper; @@ -176,7 +150,17 @@ namespace Syn { ImGui::Text("%s:%d", filenameStr.c_str(), log.line); ImGui::TableNextColumn(); - ImGui::TextWrapped("%.*s", static_cast(log.message.length()), log.message.data()); + + std::string singleLineMsg = std::string(log.message.data(), log.message.length()); + std::replace(singleLineMsg.begin(), singleLineMsg.end(), '\n', ' '); + + ImGui::TextUnformatted(singleLineMsg.c_str()); + + if (ImGui::IsItemHovered() && log.message.find('\n') != std::string::npos) { + ImGui::BeginTooltip(); + ImGui::TextUnformatted(log.message.data()); + ImGui::EndTooltip(); + } ImGui::PopStyleColor(); } @@ -191,17 +175,16 @@ namespace Syn { } ImGui::EndChild(); - ImGui::PopStyleColor(); - ImGui::PopStyleVar(3); + ImGui::PopStyleVar(2); } ImVec4 LoggerView::GetColorForLevel(LogLevel level) const { switch (level) { - case LogLevel::Info: return ImVec4(1.0f, 1.0f, 1.0f, 1.0f); - case LogLevel::Warning: return ImVec4(0.8f, 0.4f, 1.0f, 1.0f); - case LogLevel::Error: return ImVec4(1.0f, 0.2f, 0.2f, 1.0f); - case LogLevel::Critical: return ImVec4(1.0f, 0.6f, 0.0f, 1.0f); + case LogLevel::Info: return ImVec4(0.9f, 0.9f, 0.9f, 1.0f); + case LogLevel::Warning: return ImVec4(0.9f, 0.7f, 0.0f, 1.0f); + case LogLevel::Error: return ImVec4(1.0f, 0.3f, 0.3f, 1.0f); + case LogLevel::Critical: return ImVec4(1.0f, 0.1f, 0.1f, 1.0f); default: return ImGui::GetStyleColorVec4(ImGuiCol_Text); } } diff --git a/SynapseEngine/EditorCore/Api/IHierarchyApi.h b/SynapseEngine/EditorCore/Api/IHierarchyApi.h index 1b670e2e..ee188652 100644 --- a/SynapseEngine/EditorCore/Api/IHierarchyApi.h +++ b/SynapseEngine/EditorCore/Api/IHierarchyApi.h @@ -4,7 +4,33 @@ #include #include "EditorCore/Types/EntityHandle.h" -namespace Syn { +namespace Syn +{ + enum class EntityTemplate { + Empty, + Camera, + DirectionalLight, + PointLight, + SpotLight, + BoxCollider, + SphereCollider, + CapsuleCollider, + ConvexCollider, + MeshCollider, + ShapeCube, + ShapeSphere, + ShapeQuad, + ShapeCylinder, + ShapeCone, + ShapeCapsule, + ShapeHemisphere, + ShapePyramid, + ShapeGrid, + ShapeTorus, + Model, + Animation + }; + class IHierarchyApi : public IApi { public: virtual ~IHierarchyApi() = default; @@ -18,7 +44,11 @@ namespace Syn { virtual EntityID GetParent(EntityID entity) const = 0; virtual void SetParent(EntityID child, EntityID parent) = 0; - virtual EntityID CreateEntity(const std::string& name, EntityID parent = NULL_ENTITY) = 0; + virtual EntityID CreateEntity(EntityTemplate templateType, EntityID parent = NULL_ENTITY) = 0; + virtual void DestroyEntityRecursive(EntityID entity) = 0; + virtual void DestroyEntityKeepChildren(EntityID entity) = 0; + virtual EntityID CopyEntity(EntityID entity, EntityID parent = NULL_ENTITY) = 0; + virtual EntityID FullCopyEntity(EntityID entity, EntityID parent = NULL_ENTITY) = 0; virtual void DestroyEntity(EntityID entity) = 0; virtual uint64_t GetVersion() const = 0; diff --git a/SynapseEngine/EditorCore/ViewModels/Hierarchy/HierarchyIntent.h b/SynapseEngine/EditorCore/ViewModels/Hierarchy/HierarchyIntent.h index 28be95ac..bea9211f 100644 --- a/SynapseEngine/EditorCore/ViewModels/Hierarchy/HierarchyIntent.h +++ b/SynapseEngine/EditorCore/ViewModels/Hierarchy/HierarchyIntent.h @@ -3,6 +3,7 @@ #include #include #include "EditorCore/Types/EntityHandle.h" +#include "EditorCore/Api/IHierarchyApi.h" namespace Syn { @@ -26,14 +27,26 @@ namespace Syn }; struct HierarchyCreateEntityIntent { - std::string name; + EntityTemplate type; EntityID parent; }; + struct HierarchyCopyEntityIntent { + EntityID entity; + }; + + struct HierarchyFullCopyEntityIntent { + EntityID entity; + }; + struct HierarchyDestroyEntityIntent { EntityID entity; }; + struct HierarchyDestroyKeepChildrenIntent { + EntityID entity; + }; + struct HierarchyRefreshHierarchyIntent { }; @@ -53,7 +66,10 @@ namespace Syn HierarchyToggleVisibilityIntent, HierarchyReparentEntityIntent, HierarchyCreateEntityIntent, + HierarchyCopyEntityIntent, + HierarchyFullCopyEntityIntent, HierarchyDestroyEntityIntent, + HierarchyDestroyKeepChildrenIntent, HierarchyRefreshHierarchyIntent, HierarchySetSearchQueryIntent, HierarchyExpandAllIntent, diff --git a/SynapseEngine/EditorCore/ViewModels/Hierarchy/HierarchyViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/Hierarchy/HierarchyViewModel.cpp index 48a198b6..ecdb88d3 100644 --- a/SynapseEngine/EditorCore/ViewModels/Hierarchy/HierarchyViewModel.cpp +++ b/SynapseEngine/EditorCore/ViewModels/Hierarchy/HierarchyViewModel.cpp @@ -50,14 +50,28 @@ namespace Syn { _hierarchyApi->SetParent(arg.child, arg.newParent); } else if constexpr (std::is_same_v) { - EntityID newEnt = _hierarchyApi->CreateEntity(arg.name, arg.parent); + EntityID newEnt = _hierarchyApi->CreateEntity(arg.type, arg.parent); if (arg.parent != NULL_ENTITY) { _expandedNodes.insert(arg.parent); } _selectionApi->SetSelectedEntity(newEnt); } + else if constexpr (std::is_same_v) { + EntityID newEnt = _hierarchyApi->CopyEntity(arg.entity, _hierarchyApi->GetParent(arg.entity)); + _selectionApi->SetSelectedEntity(newEnt); + } + else if constexpr (std::is_same_v) { + EntityID newEnt = _hierarchyApi->FullCopyEntity(arg.entity, _hierarchyApi->GetParent(arg.entity)); + _selectionApi->SetSelectedEntity(newEnt); + } else if constexpr (std::is_same_v) { - _hierarchyApi->DestroyEntity(arg.entity); + _hierarchyApi->DestroyEntityRecursive(arg.entity); + if (_state.selectedEntity == arg.entity) { + _selectionApi->SetSelectedEntity(NULL_ENTITY); + } + } + else if constexpr (std::is_same_v) { + _hierarchyApi->DestroyEntityKeepChildren(arg.entity); if (_state.selectedEntity == arg.entity) { _selectionApi->SetSelectedEntity(NULL_ENTITY); } diff --git a/SynapseEngine/Engine/Component/Components.h b/SynapseEngine/Engine/Component/Components.h index 39822504..2b764475 100644 --- a/SynapseEngine/Engine/Component/Components.h +++ b/SynapseEngine/Engine/Component/Components.h @@ -11,6 +11,8 @@ #include "Physics/BoxColliderComponent.h" #include "Physics/SphereColliderComponent.h" #include "Physics/CapsuleColliderComponent.h" +#include "Physics/ConvexColliderComponent.h" +#include "Physics/MeshColliderComponent.h" #include "Physics/RigidBodyComponent.h" #include "Rendering/ModelComponent.h" #include "Rendering/MaterialOverrideComponent.h" diff --git a/SynapseEngine/Engine/Registry/Pool/Pool.h b/SynapseEngine/Engine/Registry/Pool/Pool.h index bdb3f3b0..7fffde37 100644 --- a/SynapseEngine/Engine/Registry/Pool/Pool.h +++ b/SynapseEngine/Engine/Registry/Pool/Pool.h @@ -252,6 +252,13 @@ namespace Syn requires StorageConstraint&& MappingConstraint SYN_INLINE void Pool::EnsureEntityMapping(EntityID entity) { + size_t oldSize = _mapping.GetSparseIndices().size(); + _mapping.EnsureEntityMapping(entity); + + if (_mapping.GetSparseIndices().size() > oldSize) + { + IncrementMappingVersion(); + } } } \ No newline at end of file diff --git a/SynapseEngine/Engine/Registry/Registry.h b/SynapseEngine/Engine/Registry/Registry.h index 1758577d..c728599c 100644 --- a/SynapseEngine/Engine/Registry/Registry.h +++ b/SynapseEngine/Engine/Registry/Registry.h @@ -118,6 +118,7 @@ namespace Syn { auto* wrapper = new WrapperType(); _pools.Add(id, wrapper); + _pools.EnsureEntityMapping(_entityCounter); return wrapper; } diff --git a/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json b/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json index c9328fd1..e8d51261 100644 --- a/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json +++ b/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json @@ -6,10 +6,10 @@ }, "materials": { "use_unique_materials": false, - "shared_material_count": 150 + "shared_material_count": 100 }, "entities": { - "animated_characters": 500, + "animated_characters": 1000, "static_geometry": 25000, "physics_boxes": 500, "physics_spheres": 500, @@ -17,9 +17,9 @@ }, "lights": { "directional_count": 1, - "point_count": 64, - "point_shadow_count": 16, - "spot_count": 64, - "spot_shadow_count": 16 + "point_count": 128, + "point_shadow_count": 32, + "spot_count": 128, + "spot_shadow_count": 32 } } \ No newline at end of file diff --git a/SynapseEngine/Engine/Shaders/Passes/Shading/ForwardPlus/DepthPrepass/TraditionalPreDepth.vert b/SynapseEngine/Engine/Shaders/Passes/Shading/ForwardPlus/DepthPrepass/TraditionalPreDepth.vert index fcda814a..7a881114 100644 --- a/SynapseEngine/Engine/Shaders/Passes/Shading/ForwardPlus/DepthPrepass/TraditionalPreDepth.vert +++ b/SynapseEngine/Engine/Shaders/Passes/Shading/ForwardPlus/DepthPrepass/TraditionalPreDepth.vert @@ -35,13 +35,6 @@ void main() { uint modelDenseIndex = GET_SPARSE_INDEX(ctx.modelSparseMapBufferAddr, entityId); uint transformDenseIndex = GET_SPARSE_INDEX(ctx.transformSparseMapBufferAddr, entityId); - if (transformDenseIndex == INVALID_INDEX || modelDenseIndex == INVALID_INDEX) { - gl_Position = vec4(0.0, 0.0, 0.0, 0.0); - outUV = vec2(0.0); - outId = uvec3(0u); - return; - } - ModelComponent comp = GET_MODEL_COMP(ctx.modelBufferAddr, modelDenseIndex); // 4. Fetch Model Addresses & Raw Vertex Data From 6be1ddac4a00856bdcecb2b1381ff17a0e101b4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20P=C3=A9ter?= Date: Mon, 13 Jul 2026 11:49:21 +0200 Subject: [PATCH 49/51] Refactored UI view files --- .../Editor/EditorApi/Impl/MaterialApiImpl.cpp | 2 +- SynapseEngine/Editor/Manager/GuiManager.h | 2 +- SynapseEngine/Editor/Synapse.cpp | 14 +-- .../ContentBrowser/ContentBrowserView.cpp | 0 .../ContentBrowser/ContentBrowserView.h | 4 +- .../Common}/MainMenu/MainMenuView.cpp | 0 .../Common}/MainMenu/MainMenuView.h | 4 +- .../Editor/{View => Workspace}/IGuiWindow.cpp | 0 .../Editor/{View => Workspace}/IGuiWindow.h | 0 .../Editor/{View => Workspace}/IView.cpp | 0 .../Editor/{View => Workspace}/IView.h | 0 SynapseEngine/Editor/Workspace/IWorkspace.h | 2 +- .../MaterialWorkspace.cpp | 20 ++-- .../MaterialWorkspace.h | 2 +- .../View/MaterialGraph/MaterialGraphView.cpp | 0 .../View/MaterialGraph/MaterialGraphView.h | 4 +- .../MaterialHierarchyView.cpp | 0 .../MaterialHierarchy/MaterialHierarchyView.h | 4 +- .../MaterialPropertiesView.cpp | 0 .../MaterialPropertiesView.h | 4 +- .../MaterialViewport/MaterialViewportView.cpp | 0 .../MaterialViewport/MaterialViewportView.h | 4 +- .../{ => ModelWorkspace}/ModelWorkspace.cpp | 16 ++-- .../{ => ModelWorkspace}/ModelWorkspace.h | 2 +- .../ModelHierarchy/ModelHierarchyView.cpp | 0 .../View/ModelHierarchy/ModelHierarchyView.h | 4 +- .../ModelProperties/ModelPropertiesView.cpp | 0 .../ModelProperties/ModelPropertiesView.h | 4 +- .../View/ModelViewport/ModelViewportView.cpp | 0 .../View/ModelViewport/ModelViewportView.h | 4 +- .../{ => SceneWorkspace}/SceneWorkspace.cpp | 34 ++++--- .../{ => SceneWorkspace}/SceneWorkspace.h | 2 +- .../View/Benchmark/BenchmarkView.cpp | 2 - .../View/Benchmark/BenchmarkView.h | 4 +- .../View/Component/ComponentView.cpp | 0 .../View/Component/ComponentView.h | 4 +- .../View/Component/Core/CameraView.cpp | 0 .../View/Component/Core/CameraView.h | 4 +- .../View/Component/Core/TagView.cpp | 0 .../View/Component/Core/TagView.h | 4 +- .../View/Component/Core/TransformView.cpp | 0 .../View/Component/Core/TransformView.h | 4 +- .../Component/Light/DirectionLightView.cpp | 0 .../View/Component/Light/DirectionLightView.h | 4 +- .../View/Component/Light/PointLightView.cpp | 0 .../View/Component/Light/PointLightView.h | 4 +- .../View/Component/Light/SpotLightView.cpp | 0 .../View/Component/Light/SpotLightView.h | 4 +- .../Component/Physics/BoxColliderView.cpp | 0 .../View/Component/Physics/BoxColliderView.h | 4 +- .../Component/Physics/CapsuleColliderView.cpp | 0 .../Component/Physics/CapsuleColliderView.h | 4 +- .../Component/Physics/ConvexColliderView.cpp | 0 .../Component/Physics/ConvexColliderView.h | 4 +- .../Component/Physics/MeshColliderView.cpp | 0 .../View/Component/Physics/MeshColliderView.h | 4 +- .../View/Component/Physics/RigidBodyView.cpp | 0 .../View/Component/Physics/RigidBodyView.h | 4 +- .../Component/Physics/SphereColliderView.cpp | 0 .../Component/Physics/SphereColliderView.h | 4 +- .../Component/Rendering/AnimationView.cpp | 0 .../View/Component/Rendering/AnimationView.h | 4 +- .../Rendering/MaterialOverrideView.cpp | 0 .../Rendering/MaterialOverrideView.h | 4 +- .../Rendering/ModelComponentView.cpp | 0 .../Component/Rendering/ModelComponentView.h | 4 +- .../View/Hierarchy/HierarchyView.cpp | 0 .../View/Hierarchy/HierarchyView.h | 4 +- .../View/Logger/LoggerView.cpp | 0 .../SceneWorkspace}/View/Logger/LoggerView.h | 4 +- .../View/Settings/SettingsView.cpp | 0 .../View/Settings/SettingsView.h | 4 +- .../View/Viewport/ViewportView.cpp | 0 .../View/Viewport/ViewportView.h | 4 +- .../TextureWorkspace.cpp | 16 ++-- .../{ => TextureWorkspace}/TextureWorkspace.h | 2 +- .../View/TextureGraph/TextureGraphView.cpp | 0 .../View/TextureGraph/TextureGraphView.h | 4 +- .../TextureHierarchy/TextureHierarchyView.cpp | 0 .../TextureHierarchy/TextureHierarchyView.h | 4 +- .../TexturePropertiesView.cpp | 0 .../TextureProperties/TexturePropertiesView.h | 4 +- .../ContentBrowser/ContentBrowserIntent.cpp | 0 .../ContentBrowser/ContentBrowserIntent.h | 0 .../ContentBrowser/ContentBrowserState.cpp | 0 .../ContentBrowser/ContentBrowserState.h | 0 .../ContentBrowserViewModel.cpp | 0 .../ContentBrowser/ContentBrowserViewModel.h | 0 .../{ => Common}/MainMenu/MainMenuIntent.cpp | 0 .../{ => Common}/MainMenu/MainMenuIntent.h | 0 .../{ => Common}/MainMenu/MainMenuState.cpp | 0 .../{ => Common}/MainMenu/MainMenuState.h | 0 .../MainMenu/MainMenuViewModel.cpp | 0 .../{ => Common}/MainMenu/MainMenuViewModel.h | 0 .../MaterialGraph/MaterialGraphIntent.cpp | 0 .../MaterialGraph/MaterialGraphIntent.h | 0 .../MaterialGraph/MaterialGraphState.cpp | 0 .../MaterialGraph/MaterialGraphState.h | 0 .../MaterialGraph/MaterialGraphViewModel.cpp | 0 .../MaterialGraph/MaterialGraphViewModel.h | 0 .../MaterialHierarchyIntent.cpp | 0 .../MaterialHierarchyIntent.h | 0 .../MaterialHierarchyState.cpp | 0 .../MaterialHierarchyState.h | 0 .../MaterialHierarchyViewModel.cpp | 0 .../MaterialHierarchyViewModel.h | 0 .../MaterialPropertiesIntent.cpp | 0 .../MaterialPropertiesIntent.h | 0 .../MaterialPropertiesState.cpp | 0 .../MaterialPropertiesState.h | 0 .../MaterialPropertiesViewModel.cpp | 0 .../MaterialPropertiesViewModel.h | 0 .../MaterialViewportIntent.cpp | 0 .../MaterialViewport/MaterialViewportIntent.h | 0 .../MaterialViewportState.cpp | 0 .../MaterialViewport/MaterialViewportState.h | 0 .../MaterialViewportViewModel.cpp | 0 .../MaterialViewportViewModel.h | 0 .../ModelHierarchy/ModelHierarchyIntent.cpp | 0 .../ModelHierarchy/ModelHierarchyIntent.h | 0 .../ModelHierarchy/ModelHierarchyState.cpp | 0 .../ModelHierarchy/ModelHierarchyState.h | 0 .../ModelHierarchyViewModel.cpp | 0 .../ModelHierarchy/ModelHierarchyViewModel.h | 0 .../ModelProperties/ModelPropertiesIntent.cpp | 0 .../ModelProperties/ModelPropertiesIntent.h | 0 .../ModelProperties/ModelPropertiesState.cpp | 0 .../ModelProperties/ModelPropertiesState.h | 0 .../ModelPropertiesViewModel.cpp | 0 .../ModelPropertiesViewModel.h | 0 .../ModelViewport/ModelViewportIntent.cpp | 0 .../ModelViewport/ModelViewportIntent.h | 0 .../ModelViewport/ModelViewportState.cpp | 0 .../ModelViewport/ModelViewportState.h | 0 .../ModelViewport/ModelViewportViewModel.cpp | 0 .../ModelViewport/ModelViewportViewModel.h | 0 .../Benchmark/BenchmarkIntent.cpp | 0 .../Benchmark/BenchmarkIntent.h | 0 .../Benchmark/BenchmarkState.cpp | 0 .../Benchmark/BenchmarkState.h | 0 .../Benchmark/BenchmarkViewModel.cpp | 0 .../Benchmark/BenchmarkViewModel.h | 0 .../Component/ComponentIntent.cpp | 0 .../Component/ComponentIntent.h | 0 .../Component/ComponentState.cpp | 0 .../Component/ComponentState.h | 0 .../Component/ComponentViewModel.cpp | 0 .../Component/ComponentViewModel.h | 0 .../Component/Core/Camera/CameraCommands.cpp | 0 .../Component/Core/Camera/CameraCommands.h | 0 .../Component/Core/Camera/CameraIntent.cpp | 0 .../Component/Core/Camera/CameraIntent.h | 0 .../Component/Core/Camera/CameraState.cpp | 0 .../Component/Core/Camera/CameraState.h | 0 .../Component/Core/Camera/CameraViewModel.cpp | 0 .../Component/Core/Camera/CameraViewModel.h | 0 .../Component/Core/Tag/TagIntent.cpp | 0 .../Component/Core/Tag/TagIntent.h | 0 .../Component/Core/Tag/TagState.cpp | 0 .../Component/Core/Tag/TagState.h | 0 .../Component/Core/Tag/TagViewModel.cpp | 0 .../Component/Core/Tag/TagViewModel.h | 0 .../Core/Transform/TransformCommands.cpp | 0 .../Core/Transform/TransformCommands.h | 0 .../Core/Transform/TransformIntent.cpp | 0 .../Core/Transform/TransformIntent.h | 0 .../Core/Transform/TransformState.cpp | 0 .../Component/Core/Transform/TransformState.h | 0 .../Core/Transform/TransformViewModel.cpp | 0 .../Core/Transform/TransformViewModel.h | 0 .../DirectionLight/DirectionLightCommands.cpp | 0 .../DirectionLight/DirectionLightCommands.h | 0 .../DirectionLight/DirectionLightIntent.cpp | 0 .../DirectionLight/DirectionLightIntent.h | 0 .../DirectionLight/DirectionLightState.cpp | 0 .../DirectionLight/DirectionLightState.h | 0 .../DirectionLightViewModel.cpp | 0 .../DirectionLight/DirectionLightViewModel.h | 0 .../Light/PointLight/PointLightCommands.cpp | 0 .../Light/PointLight/PointLightCommands.h | 0 .../Light/PointLight/PointLightIntent.cpp | 0 .../Light/PointLight/PointLightIntent.h | 0 .../Light/PointLight/PointLightState.cpp | 0 .../Light/PointLight/PointLightState.h | 0 .../Light/PointLight/PointLightViewModel.cpp | 0 .../Light/PointLight/PointLightViewModel.h | 0 .../Light/SpotLight/SpotLightCommands.cpp | 0 .../Light/SpotLight/SpotLightCommands.h | 0 .../Light/SpotLight/SpotLightIntent.cpp | 0 .../Light/SpotLight/SpotLightIntent.h | 0 .../Light/SpotLight/SpotLightState.cpp | 0 .../Light/SpotLight/SpotLightState.h | 0 .../Light/SpotLight/SpotLightViewModel.cpp | 0 .../Light/SpotLight/SpotLightViewModel.h | 0 .../BoxCollider/BoxColliderCommands.cpp | 0 .../Physics/BoxCollider/BoxColliderCommands.h | 0 .../Physics/BoxCollider/BoxColliderIntent.cpp | 0 .../Physics/BoxCollider/BoxColliderIntent.h | 0 .../Physics/BoxCollider/BoxColliderState.cpp | 0 .../Physics/BoxCollider/BoxColliderState.h | 0 .../BoxCollider/BoxColliderViewModel.cpp | 0 .../BoxCollider/BoxColliderViewModel.h | 0 .../CapsuleColliderCommands.cpp | 0 .../CapsuleCollider/CapsuleColliderCommands.h | 0 .../CapsuleCollider/CapsuleColliderIntent.cpp | 0 .../CapsuleCollider/CapsuleColliderIntent.h | 0 .../CapsuleCollider/CapsuleColliderState.cpp | 0 .../CapsuleCollider/CapsuleColliderState.h | 0 .../CapsuleColliderViewModel.cpp | 0 .../CapsuleColliderViewModel.h | 0 .../ConvexCollider/ConvexColliderCommands.cpp | 0 .../ConvexCollider/ConvexColliderCommands.h | 0 .../ConvexCollider/ConvexColliderIntent.cpp | 0 .../ConvexCollider/ConvexColliderIntent.h | 0 .../ConvexCollider/ConvexColliderState.cpp | 0 .../ConvexCollider/ConvexColliderState.h | 0 .../ConvexColliderViewModel.cpp | 0 .../ConvexCollider/ConvexColliderViewModel.h | 0 .../MeshCollider/MeshColliderCommands.cpp | 0 .../MeshCollider/MeshColliderCommands.h | 0 .../MeshCollider/MeshColliderIntent.cpp | 0 .../Physics/MeshCollider/MeshColliderIntent.h | 0 .../MeshCollider/MeshColliderState.cpp | 0 .../Physics/MeshCollider/MeshColliderState.h | 0 .../MeshCollider/MeshColliderViewModel.cpp | 0 .../MeshCollider/MeshColliderViewModel.h | 0 .../Physics/RigidBody/RigidBodyCommands.cpp | 0 .../Physics/RigidBody/RigidBodyCommands.h | 0 .../Physics/RigidBody/RigidBodyIntent.cpp | 0 .../Physics/RigidBody/RigidBodyIntent.h | 0 .../Physics/RigidBody/RigidBodyState.cpp | 0 .../Physics/RigidBody/RigidBodyState.h | 0 .../Physics/RigidBody/RigidBodyViewModel.cpp | 0 .../Physics/RigidBody/RigidBodyViewModel.h | 0 .../SphereCollider/SphereColliderCommands.cpp | 0 .../SphereCollider/SphereColliderCommands.h | 0 .../SphereCollider/SphereColliderIntent.cpp | 0 .../SphereCollider/SphereColliderIntent.h | 0 .../SphereCollider/SphereColliderState.cpp | 0 .../SphereCollider/SphereColliderState.h | 0 .../SphereColliderViewModel.cpp | 0 .../SphereCollider/SphereColliderViewModel.h | 0 .../Rendering/Animation/AnimationCommands.cpp | 0 .../Rendering/Animation/AnimationCommands.h | 0 .../Rendering/Animation/AnimationIntent.cpp | 0 .../Rendering/Animation/AnimationIntent.h | 0 .../Rendering/Animation/AnimationState.cpp | 0 .../Rendering/Animation/AnimationState.h | 0 .../Animation/AnimationViewModel.cpp | 0 .../Rendering/Animation/AnimationViewModel.h | 0 .../MaterialOverrideIntent.cpp | 0 .../MaterialOverride/MaterialOverrideIntent.h | 0 .../MaterialOverrideState.cpp | 0 .../MaterialOverride/MaterialOverrideState.h | 0 .../MaterialOverrideViewModel.cpp | 0 .../MaterialOverrideViewModel.h | 0 .../Model/ModelComponentCommands.cpp | 0 .../Rendering/Model/ModelComponentCommands.h | 0 .../Rendering/Model/ModelComponentIntent.cpp | 0 .../Rendering/Model/ModelComponentIntent.h | 0 .../Rendering/Model/ModelComponentState.cpp | 0 .../Rendering/Model/ModelComponentState.h | 0 .../Model/ModelComponentViewModel.cpp | 0 .../Rendering/Model/ModelComponentViewModel.h | 0 .../Hierarchy/HierarchyIntent.cpp | 0 .../Hierarchy/HierarchyIntent.h | 0 .../Hierarchy/HierarchyState.cpp | 0 .../Hierarchy/HierarchyState.h | 0 .../Hierarchy/HierarchyViewModel.cpp | 0 .../Hierarchy/HierarchyViewModel.h | 0 .../Logger/LoggerIntent.cpp | 0 .../Logger/LoggerIntent.h | 0 .../Logger/LoggerState.cpp | 0 .../{ => SceneWorkspace}/Logger/LoggerState.h | 0 .../Logger/LoggerViewModel.cpp | 0 .../Logger/LoggerViewModel.h | 0 .../Settings/SettingsIntent.cpp | 0 .../Settings/SettingsIntent.h | 0 .../Settings/SettingsState.cpp | 0 .../Settings/SettingsState.h | 0 .../Settings/SettingsViewModel.cpp | 0 .../Settings/SettingsViewModel.h | 0 .../Viewport/ViewportIntent.cpp | 0 .../Viewport/ViewportIntent.h | 0 .../Viewport/ViewportState.cpp | 0 .../Viewport/ViewportState.h | 0 .../Viewport/ViewportViewModel.cpp | 0 .../Viewport/ViewportViewModel.h | 0 .../TextureGraph/TextureGraphIntent.cpp | 0 .../TextureGraph/TextureGraphIntent.h | 0 .../TextureGraph/TextureGraphState.cpp | 0 .../TextureGraph/TextureGraphState.h | 0 .../TextureGraph/TextureGraphViewModel.cpp | 0 .../TextureGraph/TextureGraphViewModel.h | 0 .../TextureHierarchyIntent.cpp | 0 .../TextureHierarchy/TextureHierarchyIntent.h | 0 .../TextureHierarchyState.cpp | 0 .../TextureHierarchy/TextureHierarchyState.h | 0 .../TextureHierarchyViewModel.cpp | 0 .../TextureHierarchyViewModel.h | 0 .../TexturePropertiesIntent.cpp | 0 .../TexturePropertiesIntent.h | 0 .../TexturePropertiesState.cpp | 0 .../TexturePropertiesState.h | 0 .../TexturePropertiesViewModel.cpp | 0 .../TexturePropertiesViewModel.h | 0 SynapseEngine/Engine/Engine.cpp | 2 +- .../Pool/Storage/Core/SegmentedStorageImpl.h | 10 +- .../Engine/Scene/Settings/CullingSettings.cpp | 4 +- .../Scene/Source/Procedural/test_config.json | 8 +- SynapseEngine/Synapse_MaterialGraph.json | 2 +- SynapseEngine/imgui.ini | 92 +++++++++++-------- 312 files changed, 194 insertions(+), 172 deletions(-) rename SynapseEngine/Editor/{View => Workspace/Common}/ContentBrowser/ContentBrowserView.cpp (100%) rename SynapseEngine/Editor/{View => Workspace/Common}/ContentBrowser/ContentBrowserView.h (91%) rename SynapseEngine/Editor/{View => Workspace/Common}/MainMenu/MainMenuView.cpp (100%) rename SynapseEngine/Editor/{View => Workspace/Common}/MainMenu/MainMenuView.h (63%) rename SynapseEngine/Editor/{View => Workspace}/IGuiWindow.cpp (100%) rename SynapseEngine/Editor/{View => Workspace}/IGuiWindow.h (100%) rename SynapseEngine/Editor/{View => Workspace}/IView.cpp (100%) rename SynapseEngine/Editor/{View => Workspace}/IView.h (100%) rename SynapseEngine/Editor/Workspace/{ => MaterialWorkspace}/MaterialWorkspace.cpp (73%) rename SynapseEngine/Editor/Workspace/{ => MaterialWorkspace}/MaterialWorkspace.h (93%) rename SynapseEngine/Editor/{ => Workspace/MaterialWorkspace}/View/MaterialGraph/MaterialGraphView.cpp (100%) rename SynapseEngine/Editor/{ => Workspace/MaterialWorkspace}/View/MaterialGraph/MaterialGraphView.h (87%) rename SynapseEngine/Editor/{ => Workspace/MaterialWorkspace}/View/MaterialHierarchy/MaterialHierarchyView.cpp (100%) rename SynapseEngine/Editor/{ => Workspace/MaterialWorkspace}/View/MaterialHierarchy/MaterialHierarchyView.h (73%) rename SynapseEngine/Editor/{ => Workspace/MaterialWorkspace}/View/MaterialProperties/MaterialPropertiesView.cpp (100%) rename SynapseEngine/Editor/{ => Workspace/MaterialWorkspace}/View/MaterialProperties/MaterialPropertiesView.h (82%) rename SynapseEngine/Editor/{ => Workspace/MaterialWorkspace}/View/MaterialViewport/MaterialViewportView.cpp (100%) rename SynapseEngine/Editor/{ => Workspace/MaterialWorkspace}/View/MaterialViewport/MaterialViewportView.h (60%) rename SynapseEngine/Editor/Workspace/{ => ModelWorkspace}/ModelWorkspace.cpp (76%) rename SynapseEngine/Editor/Workspace/{ => ModelWorkspace}/ModelWorkspace.h (92%) rename SynapseEngine/Editor/{ => Workspace/ModelWorkspace}/View/ModelHierarchy/ModelHierarchyView.cpp (100%) rename SynapseEngine/Editor/{ => Workspace/ModelWorkspace}/View/ModelHierarchy/ModelHierarchyView.h (78%) rename SynapseEngine/Editor/{ => Workspace/ModelWorkspace}/View/ModelProperties/ModelPropertiesView.cpp (100%) rename SynapseEngine/Editor/{ => Workspace/ModelWorkspace}/View/ModelProperties/ModelPropertiesView.h (75%) rename SynapseEngine/Editor/{ => Workspace/ModelWorkspace}/View/ModelViewport/ModelViewportView.cpp (100%) rename SynapseEngine/Editor/{ => Workspace/ModelWorkspace}/View/ModelViewport/ModelViewportView.h (86%) rename SynapseEngine/Editor/Workspace/{ => SceneWorkspace}/SceneWorkspace.cpp (79%) rename SynapseEngine/Editor/Workspace/{ => SceneWorkspace}/SceneWorkspace.h (92%) rename SynapseEngine/Editor/{ => Workspace/SceneWorkspace}/View/Benchmark/BenchmarkView.cpp (99%) rename SynapseEngine/Editor/{ => Workspace/SceneWorkspace}/View/Benchmark/BenchmarkView.h (88%) rename SynapseEngine/Editor/{ => Workspace/SceneWorkspace}/View/Component/ComponentView.cpp (100%) rename SynapseEngine/Editor/{ => Workspace/SceneWorkspace}/View/Component/ComponentView.h (92%) rename SynapseEngine/Editor/{ => Workspace/SceneWorkspace}/View/Component/Core/CameraView.cpp (100%) rename SynapseEngine/Editor/{ => Workspace/SceneWorkspace}/View/Component/Core/CameraView.h (61%) rename SynapseEngine/Editor/{ => Workspace/SceneWorkspace}/View/Component/Core/TagView.cpp (100%) rename SynapseEngine/Editor/{ => Workspace/SceneWorkspace}/View/Component/Core/TagView.h (71%) rename SynapseEngine/Editor/{ => Workspace/SceneWorkspace}/View/Component/Core/TransformView.cpp (100%) rename SynapseEngine/Editor/{ => Workspace/SceneWorkspace}/View/Component/Core/TransformView.h (61%) rename SynapseEngine/Editor/{ => Workspace/SceneWorkspace}/View/Component/Light/DirectionLightView.cpp (100%) rename SynapseEngine/Editor/{ => Workspace/SceneWorkspace}/View/Component/Light/DirectionLightView.h (61%) rename SynapseEngine/Editor/{ => Workspace/SceneWorkspace}/View/Component/Light/PointLightView.cpp (100%) rename SynapseEngine/Editor/{ => Workspace/SceneWorkspace}/View/Component/Light/PointLightView.h (61%) rename SynapseEngine/Editor/{ => Workspace/SceneWorkspace}/View/Component/Light/SpotLightView.cpp (100%) rename SynapseEngine/Editor/{ => Workspace/SceneWorkspace}/View/Component/Light/SpotLightView.h (61%) rename SynapseEngine/Editor/{ => Workspace/SceneWorkspace}/View/Component/Physics/BoxColliderView.cpp (100%) rename SynapseEngine/Editor/{ => Workspace/SceneWorkspace}/View/Component/Physics/BoxColliderView.h (61%) rename SynapseEngine/Editor/{ => Workspace/SceneWorkspace}/View/Component/Physics/CapsuleColliderView.cpp (100%) rename SynapseEngine/Editor/{ => Workspace/SceneWorkspace}/View/Component/Physics/CapsuleColliderView.h (61%) rename SynapseEngine/Editor/{ => Workspace/SceneWorkspace}/View/Component/Physics/ConvexColliderView.cpp (100%) rename SynapseEngine/Editor/{ => Workspace/SceneWorkspace}/View/Component/Physics/ConvexColliderView.h (61%) rename SynapseEngine/Editor/{ => Workspace/SceneWorkspace}/View/Component/Physics/MeshColliderView.cpp (100%) rename SynapseEngine/Editor/{ => Workspace/SceneWorkspace}/View/Component/Physics/MeshColliderView.h (61%) rename SynapseEngine/Editor/{ => Workspace/SceneWorkspace}/View/Component/Physics/RigidBodyView.cpp (100%) rename SynapseEngine/Editor/{ => Workspace/SceneWorkspace}/View/Component/Physics/RigidBodyView.h (61%) rename SynapseEngine/Editor/{ => Workspace/SceneWorkspace}/View/Component/Physics/SphereColliderView.cpp (100%) rename SynapseEngine/Editor/{ => Workspace/SceneWorkspace}/View/Component/Physics/SphereColliderView.h (61%) rename SynapseEngine/Editor/{ => Workspace/SceneWorkspace}/View/Component/Rendering/AnimationView.cpp (100%) rename SynapseEngine/Editor/{ => Workspace/SceneWorkspace}/View/Component/Rendering/AnimationView.h (60%) rename SynapseEngine/Editor/{ => Workspace/SceneWorkspace}/View/Component/Rendering/MaterialOverrideView.cpp (100%) rename SynapseEngine/Editor/{ => Workspace/SceneWorkspace}/View/Component/Rendering/MaterialOverrideView.h (60%) rename SynapseEngine/Editor/{ => Workspace/SceneWorkspace}/View/Component/Rendering/ModelComponentView.cpp (100%) rename SynapseEngine/Editor/{ => Workspace/SceneWorkspace}/View/Component/Rendering/ModelComponentView.h (62%) rename SynapseEngine/Editor/{ => Workspace/SceneWorkspace}/View/Hierarchy/HierarchyView.cpp (100%) rename SynapseEngine/Editor/{ => Workspace/SceneWorkspace}/View/Hierarchy/HierarchyView.h (83%) rename SynapseEngine/Editor/{ => Workspace/SceneWorkspace}/View/Logger/LoggerView.cpp (100%) rename SynapseEngine/Editor/{ => Workspace/SceneWorkspace}/View/Logger/LoggerView.h (82%) rename SynapseEngine/Editor/{ => Workspace/SceneWorkspace}/View/Settings/SettingsView.cpp (100%) rename SynapseEngine/Editor/{ => Workspace/SceneWorkspace}/View/Settings/SettingsView.h (70%) rename SynapseEngine/Editor/{ => Workspace/SceneWorkspace}/View/Viewport/ViewportView.cpp (100%) rename SynapseEngine/Editor/{ => Workspace/SceneWorkspace}/View/Viewport/ViewportView.h (88%) rename SynapseEngine/Editor/Workspace/{ => TextureWorkspace}/TextureWorkspace.cpp (70%) rename SynapseEngine/Editor/Workspace/{ => TextureWorkspace}/TextureWorkspace.h (92%) rename SynapseEngine/Editor/{ => Workspace/TextureWorkspace}/View/TextureGraph/TextureGraphView.cpp (100%) rename SynapseEngine/Editor/{ => Workspace/TextureWorkspace}/View/TextureGraph/TextureGraphView.h (84%) rename SynapseEngine/Editor/{ => Workspace/TextureWorkspace}/View/TextureHierarchy/TextureHierarchyView.cpp (100%) rename SynapseEngine/Editor/{ => Workspace/TextureWorkspace}/View/TextureHierarchy/TextureHierarchyView.h (77%) rename SynapseEngine/Editor/{ => Workspace/TextureWorkspace}/View/TextureProperties/TexturePropertiesView.cpp (100%) rename SynapseEngine/Editor/{ => Workspace/TextureWorkspace}/View/TextureProperties/TexturePropertiesView.h (74%) rename SynapseEngine/EditorCore/ViewModels/{ => Common}/ContentBrowser/ContentBrowserIntent.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => Common}/ContentBrowser/ContentBrowserIntent.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => Common}/ContentBrowser/ContentBrowserState.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => Common}/ContentBrowser/ContentBrowserState.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => Common}/ContentBrowser/ContentBrowserViewModel.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => Common}/ContentBrowser/ContentBrowserViewModel.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => Common}/MainMenu/MainMenuIntent.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => Common}/MainMenu/MainMenuIntent.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => Common}/MainMenu/MainMenuState.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => Common}/MainMenu/MainMenuState.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => Common}/MainMenu/MainMenuViewModel.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => Common}/MainMenu/MainMenuViewModel.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => MaterialWorkspace}/MaterialGraph/MaterialGraphIntent.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => MaterialWorkspace}/MaterialGraph/MaterialGraphIntent.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => MaterialWorkspace}/MaterialGraph/MaterialGraphState.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => MaterialWorkspace}/MaterialGraph/MaterialGraphState.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => MaterialWorkspace}/MaterialGraph/MaterialGraphViewModel.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => MaterialWorkspace}/MaterialGraph/MaterialGraphViewModel.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => MaterialWorkspace}/MaterialHierarchy/MaterialHierarchyIntent.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => MaterialWorkspace}/MaterialHierarchy/MaterialHierarchyIntent.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => MaterialWorkspace}/MaterialHierarchy/MaterialHierarchyState.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => MaterialWorkspace}/MaterialHierarchy/MaterialHierarchyState.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => MaterialWorkspace}/MaterialHierarchy/MaterialHierarchyViewModel.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => MaterialWorkspace}/MaterialHierarchy/MaterialHierarchyViewModel.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => MaterialWorkspace}/MaterialProperties/MaterialPropertiesIntent.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => MaterialWorkspace}/MaterialProperties/MaterialPropertiesIntent.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => MaterialWorkspace}/MaterialProperties/MaterialPropertiesState.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => MaterialWorkspace}/MaterialProperties/MaterialPropertiesState.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => MaterialWorkspace}/MaterialProperties/MaterialPropertiesViewModel.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => MaterialWorkspace}/MaterialProperties/MaterialPropertiesViewModel.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => MaterialWorkspace}/MaterialViewport/MaterialViewportIntent.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => MaterialWorkspace}/MaterialViewport/MaterialViewportIntent.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => MaterialWorkspace}/MaterialViewport/MaterialViewportState.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => MaterialWorkspace}/MaterialViewport/MaterialViewportState.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => MaterialWorkspace}/MaterialViewport/MaterialViewportViewModel.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => MaterialWorkspace}/MaterialViewport/MaterialViewportViewModel.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => ModelWorkspace}/ModelHierarchy/ModelHierarchyIntent.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => ModelWorkspace}/ModelHierarchy/ModelHierarchyIntent.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => ModelWorkspace}/ModelHierarchy/ModelHierarchyState.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => ModelWorkspace}/ModelHierarchy/ModelHierarchyState.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => ModelWorkspace}/ModelHierarchy/ModelHierarchyViewModel.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => ModelWorkspace}/ModelHierarchy/ModelHierarchyViewModel.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => ModelWorkspace}/ModelProperties/ModelPropertiesIntent.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => ModelWorkspace}/ModelProperties/ModelPropertiesIntent.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => ModelWorkspace}/ModelProperties/ModelPropertiesState.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => ModelWorkspace}/ModelProperties/ModelPropertiesState.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => ModelWorkspace}/ModelProperties/ModelPropertiesViewModel.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => ModelWorkspace}/ModelProperties/ModelPropertiesViewModel.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => ModelWorkspace}/ModelViewport/ModelViewportIntent.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => ModelWorkspace}/ModelViewport/ModelViewportIntent.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => ModelWorkspace}/ModelViewport/ModelViewportState.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => ModelWorkspace}/ModelViewport/ModelViewportState.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => ModelWorkspace}/ModelViewport/ModelViewportViewModel.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => ModelWorkspace}/ModelViewport/ModelViewportViewModel.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Benchmark/BenchmarkIntent.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Benchmark/BenchmarkIntent.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Benchmark/BenchmarkState.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Benchmark/BenchmarkState.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Benchmark/BenchmarkViewModel.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Benchmark/BenchmarkViewModel.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/ComponentIntent.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/ComponentIntent.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/ComponentState.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/ComponentState.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/ComponentViewModel.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/ComponentViewModel.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Core/Camera/CameraCommands.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Core/Camera/CameraCommands.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Core/Camera/CameraIntent.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Core/Camera/CameraIntent.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Core/Camera/CameraState.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Core/Camera/CameraState.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Core/Camera/CameraViewModel.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Core/Camera/CameraViewModel.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Core/Tag/TagIntent.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Core/Tag/TagIntent.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Core/Tag/TagState.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Core/Tag/TagState.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Core/Tag/TagViewModel.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Core/Tag/TagViewModel.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Core/Transform/TransformCommands.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Core/Transform/TransformCommands.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Core/Transform/TransformIntent.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Core/Transform/TransformIntent.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Core/Transform/TransformState.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Core/Transform/TransformState.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Core/Transform/TransformViewModel.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Core/Transform/TransformViewModel.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Light/DirectionLight/DirectionLightCommands.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Light/DirectionLight/DirectionLightCommands.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Light/DirectionLight/DirectionLightIntent.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Light/DirectionLight/DirectionLightIntent.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Light/DirectionLight/DirectionLightState.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Light/DirectionLight/DirectionLightState.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Light/DirectionLight/DirectionLightViewModel.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Light/DirectionLight/DirectionLightViewModel.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Light/PointLight/PointLightCommands.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Light/PointLight/PointLightCommands.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Light/PointLight/PointLightIntent.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Light/PointLight/PointLightIntent.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Light/PointLight/PointLightState.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Light/PointLight/PointLightState.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Light/PointLight/PointLightViewModel.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Light/PointLight/PointLightViewModel.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Light/SpotLight/SpotLightCommands.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Light/SpotLight/SpotLightCommands.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Light/SpotLight/SpotLightIntent.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Light/SpotLight/SpotLightIntent.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Light/SpotLight/SpotLightState.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Light/SpotLight/SpotLightState.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Light/SpotLight/SpotLightViewModel.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Light/SpotLight/SpotLightViewModel.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Physics/BoxCollider/BoxColliderCommands.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Physics/BoxCollider/BoxColliderCommands.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Physics/BoxCollider/BoxColliderIntent.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Physics/BoxCollider/BoxColliderIntent.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Physics/BoxCollider/BoxColliderState.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Physics/BoxCollider/BoxColliderState.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Physics/BoxCollider/BoxColliderViewModel.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Physics/BoxCollider/BoxColliderViewModel.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Physics/CapsuleCollider/CapsuleColliderCommands.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Physics/CapsuleCollider/CapsuleColliderCommands.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Physics/CapsuleCollider/CapsuleColliderIntent.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Physics/CapsuleCollider/CapsuleColliderIntent.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Physics/CapsuleCollider/CapsuleColliderState.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Physics/CapsuleCollider/CapsuleColliderState.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Physics/CapsuleCollider/CapsuleColliderViewModel.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Physics/CapsuleCollider/CapsuleColliderViewModel.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Physics/ConvexCollider/ConvexColliderCommands.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Physics/ConvexCollider/ConvexColliderCommands.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Physics/ConvexCollider/ConvexColliderIntent.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Physics/ConvexCollider/ConvexColliderIntent.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Physics/ConvexCollider/ConvexColliderState.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Physics/ConvexCollider/ConvexColliderState.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Physics/ConvexCollider/ConvexColliderViewModel.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Physics/ConvexCollider/ConvexColliderViewModel.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Physics/MeshCollider/MeshColliderCommands.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Physics/MeshCollider/MeshColliderCommands.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Physics/MeshCollider/MeshColliderIntent.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Physics/MeshCollider/MeshColliderIntent.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Physics/MeshCollider/MeshColliderState.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Physics/MeshCollider/MeshColliderState.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Physics/MeshCollider/MeshColliderViewModel.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Physics/MeshCollider/MeshColliderViewModel.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Physics/RigidBody/RigidBodyCommands.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Physics/RigidBody/RigidBodyCommands.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Physics/RigidBody/RigidBodyIntent.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Physics/RigidBody/RigidBodyIntent.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Physics/RigidBody/RigidBodyState.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Physics/RigidBody/RigidBodyState.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Physics/RigidBody/RigidBodyViewModel.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Physics/RigidBody/RigidBodyViewModel.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Physics/SphereCollider/SphereColliderCommands.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Physics/SphereCollider/SphereColliderCommands.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Physics/SphereCollider/SphereColliderIntent.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Physics/SphereCollider/SphereColliderIntent.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Physics/SphereCollider/SphereColliderState.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Physics/SphereCollider/SphereColliderState.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Physics/SphereCollider/SphereColliderViewModel.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Physics/SphereCollider/SphereColliderViewModel.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Rendering/Animation/AnimationCommands.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Rendering/Animation/AnimationCommands.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Rendering/Animation/AnimationIntent.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Rendering/Animation/AnimationIntent.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Rendering/Animation/AnimationState.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Rendering/Animation/AnimationState.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Rendering/Animation/AnimationViewModel.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Rendering/Animation/AnimationViewModel.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Rendering/MaterialOverride/MaterialOverrideIntent.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Rendering/MaterialOverride/MaterialOverrideIntent.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Rendering/MaterialOverride/MaterialOverrideState.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Rendering/MaterialOverride/MaterialOverrideState.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Rendering/MaterialOverride/MaterialOverrideViewModel.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Rendering/MaterialOverride/MaterialOverrideViewModel.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Rendering/Model/ModelComponentCommands.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Rendering/Model/ModelComponentCommands.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Rendering/Model/ModelComponentIntent.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Rendering/Model/ModelComponentIntent.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Rendering/Model/ModelComponentState.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Rendering/Model/ModelComponentState.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Rendering/Model/ModelComponentViewModel.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Component/Rendering/Model/ModelComponentViewModel.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Hierarchy/HierarchyIntent.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Hierarchy/HierarchyIntent.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Hierarchy/HierarchyState.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Hierarchy/HierarchyState.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Hierarchy/HierarchyViewModel.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Hierarchy/HierarchyViewModel.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Logger/LoggerIntent.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Logger/LoggerIntent.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Logger/LoggerState.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Logger/LoggerState.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Logger/LoggerViewModel.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Logger/LoggerViewModel.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Settings/SettingsIntent.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Settings/SettingsIntent.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Settings/SettingsState.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Settings/SettingsState.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Settings/SettingsViewModel.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Settings/SettingsViewModel.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Viewport/ViewportIntent.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Viewport/ViewportIntent.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Viewport/ViewportState.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Viewport/ViewportState.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Viewport/ViewportViewModel.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => SceneWorkspace}/Viewport/ViewportViewModel.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => TextureWorkspace}/TextureGraph/TextureGraphIntent.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => TextureWorkspace}/TextureGraph/TextureGraphIntent.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => TextureWorkspace}/TextureGraph/TextureGraphState.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => TextureWorkspace}/TextureGraph/TextureGraphState.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => TextureWorkspace}/TextureGraph/TextureGraphViewModel.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => TextureWorkspace}/TextureGraph/TextureGraphViewModel.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => TextureWorkspace}/TextureHierarchy/TextureHierarchyIntent.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => TextureWorkspace}/TextureHierarchy/TextureHierarchyIntent.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => TextureWorkspace}/TextureHierarchy/TextureHierarchyState.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => TextureWorkspace}/TextureHierarchy/TextureHierarchyState.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => TextureWorkspace}/TextureHierarchy/TextureHierarchyViewModel.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => TextureWorkspace}/TextureHierarchy/TextureHierarchyViewModel.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => TextureWorkspace}/TextureProperties/TexturePropertiesIntent.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => TextureWorkspace}/TextureProperties/TexturePropertiesIntent.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => TextureWorkspace}/TextureProperties/TexturePropertiesState.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => TextureWorkspace}/TextureProperties/TexturePropertiesState.h (100%) rename SynapseEngine/EditorCore/ViewModels/{ => TextureWorkspace}/TextureProperties/TexturePropertiesViewModel.cpp (100%) rename SynapseEngine/EditorCore/ViewModels/{ => TextureWorkspace}/TextureProperties/TexturePropertiesViewModel.h (100%) diff --git a/SynapseEngine/Editor/EditorApi/Impl/MaterialApiImpl.cpp b/SynapseEngine/Editor/EditorApi/Impl/MaterialApiImpl.cpp index e0b02af3..03ddf867 100644 --- a/SynapseEngine/Editor/EditorApi/Impl/MaterialApiImpl.cpp +++ b/SynapseEngine/Editor/EditorApi/Impl/MaterialApiImpl.cpp @@ -1,5 +1,5 @@ #include "MaterialApiImpl.h" -#include "EditorCore/ViewModels/MaterialGraph/MaterialGraphState.h" +#include "EditorCore/ViewModels/MaterialWorkspace/MaterialGraph/MaterialGraphState.h" #include "Engine/Scene/Insiders/SceneInsider.h" #include "Engine/Component/Core/TagComponent.h" #include "Engine/Component/Rendering/MaterialOverrideComponent.h" diff --git a/SynapseEngine/Editor/Manager/GuiManager.h b/SynapseEngine/Editor/Manager/GuiManager.h index 88e8319d..d0dc2c26 100644 --- a/SynapseEngine/Editor/Manager/GuiManager.h +++ b/SynapseEngine/Editor/Manager/GuiManager.h @@ -4,7 +4,7 @@ #include #include "GuiTextureManager.h" #include "EditorCore/Api/IFileDialogApi.h" -#include "Editor/View/IGuiWindow.h" +#include "Editor/Workspace/IGuiWindow.h" #include "Editor/Workspace/IWorkspace.h" #include diff --git a/SynapseEngine/Editor/Synapse.cpp b/SynapseEngine/Editor/Synapse.cpp index 3a1d5b19..13b569e8 100644 --- a/SynapseEngine/Editor/Synapse.cpp +++ b/SynapseEngine/Editor/Synapse.cpp @@ -4,18 +4,18 @@ #include #include -#include "Editor/Workspace/SceneWorkspace.h" -#include "Editor/Workspace/ModelWorkspace.h" -#include "Editor/Workspace/MaterialWorkspace.h" -#include "Editor/Workspace/TextureWorkspace.h" +#include "Editor/Workspace/SceneWorkspace/SceneWorkspace.h" +#include "Editor/Workspace/ModelWorkspace/ModelWorkspace.h" +#include "Editor/Workspace/MaterialWorkspace/MaterialWorkspace.h" +#include "Editor/Workspace/TextureWorkspace/TextureWorkspace.h" -#include "Editor/View/MainMenu/MainMenuView.h" -#include "EditorCore/ViewModels/MainMenu/MainMenuViewModel.h" +#include "Editor/Workspace/Common/MainMenu/MainMenuView.h" +#include "EditorCore/ViewModels/Common/MainMenu/MainMenuViewModel.h" #include "Manager/GuiTextureManager.h" #include "Manager/EditorIcons.h" #include "Engine/Utils/PathUtils.h" -#include "Editor/View/IGuiWindow.h" +#include "Editor/Workspace/IGuiWindow.h" #include "Engine/Image/Loader/SvgImageLoader.h" #include "Engine/Logger/SynLog.h" diff --git a/SynapseEngine/Editor/View/ContentBrowser/ContentBrowserView.cpp b/SynapseEngine/Editor/Workspace/Common/ContentBrowser/ContentBrowserView.cpp similarity index 100% rename from SynapseEngine/Editor/View/ContentBrowser/ContentBrowserView.cpp rename to SynapseEngine/Editor/Workspace/Common/ContentBrowser/ContentBrowserView.cpp diff --git a/SynapseEngine/Editor/View/ContentBrowser/ContentBrowserView.h b/SynapseEngine/Editor/Workspace/Common/ContentBrowser/ContentBrowserView.h similarity index 91% rename from SynapseEngine/Editor/View/ContentBrowser/ContentBrowserView.h rename to SynapseEngine/Editor/Workspace/Common/ContentBrowser/ContentBrowserView.h index 17879d14..ed94eb0f 100644 --- a/SynapseEngine/Editor/View/ContentBrowser/ContentBrowserView.h +++ b/SynapseEngine/Editor/Workspace/Common/ContentBrowser/ContentBrowserView.h @@ -1,6 +1,6 @@ #pragma once -#include "Editor/View/IView.h" -#include "EditorCore/ViewModels/ContentBrowser/ContentBrowserViewModel.h" +#include "Editor/Workspace/IView.h" +#include "EditorCore/ViewModels/Common/ContentBrowser/ContentBrowserViewModel.h" #include "Editor/Manager/IIconManager.h" #include #include diff --git a/SynapseEngine/Editor/View/MainMenu/MainMenuView.cpp b/SynapseEngine/Editor/Workspace/Common/MainMenu/MainMenuView.cpp similarity index 100% rename from SynapseEngine/Editor/View/MainMenu/MainMenuView.cpp rename to SynapseEngine/Editor/Workspace/Common/MainMenu/MainMenuView.cpp diff --git a/SynapseEngine/Editor/View/MainMenu/MainMenuView.h b/SynapseEngine/Editor/Workspace/Common/MainMenu/MainMenuView.h similarity index 63% rename from SynapseEngine/Editor/View/MainMenu/MainMenuView.h rename to SynapseEngine/Editor/Workspace/Common/MainMenu/MainMenuView.h index 0b293cce..2bd26ffc 100644 --- a/SynapseEngine/Editor/View/MainMenu/MainMenuView.h +++ b/SynapseEngine/Editor/Workspace/Common/MainMenu/MainMenuView.h @@ -1,6 +1,6 @@ #pragma once -#include "../IView.h" -#include "EditorCore/ViewModels/MainMenu/MainMenuViewModel.h" +#include "Editor/Workspace/IView.h" +#include "EditorCore/ViewModels/Common/MainMenu/MainMenuViewModel.h" #include namespace Syn { diff --git a/SynapseEngine/Editor/View/IGuiWindow.cpp b/SynapseEngine/Editor/Workspace/IGuiWindow.cpp similarity index 100% rename from SynapseEngine/Editor/View/IGuiWindow.cpp rename to SynapseEngine/Editor/Workspace/IGuiWindow.cpp diff --git a/SynapseEngine/Editor/View/IGuiWindow.h b/SynapseEngine/Editor/Workspace/IGuiWindow.h similarity index 100% rename from SynapseEngine/Editor/View/IGuiWindow.h rename to SynapseEngine/Editor/Workspace/IGuiWindow.h diff --git a/SynapseEngine/Editor/View/IView.cpp b/SynapseEngine/Editor/Workspace/IView.cpp similarity index 100% rename from SynapseEngine/Editor/View/IView.cpp rename to SynapseEngine/Editor/Workspace/IView.cpp diff --git a/SynapseEngine/Editor/View/IView.h b/SynapseEngine/Editor/Workspace/IView.h similarity index 100% rename from SynapseEngine/Editor/View/IView.h rename to SynapseEngine/Editor/Workspace/IView.h diff --git a/SynapseEngine/Editor/Workspace/IWorkspace.h b/SynapseEngine/Editor/Workspace/IWorkspace.h index c150c893..d59b3788 100644 --- a/SynapseEngine/Editor/Workspace/IWorkspace.h +++ b/SynapseEngine/Editor/Workspace/IWorkspace.h @@ -1,7 +1,7 @@ #pragma once #include #include -#include "Editor/View/IGuiWindow.h" +#include "Editor/Workspace/IGuiWindow.h" namespace Syn { diff --git a/SynapseEngine/Editor/Workspace/MaterialWorkspace.cpp b/SynapseEngine/Editor/Workspace/MaterialWorkspace/MaterialWorkspace.cpp similarity index 73% rename from SynapseEngine/Editor/Workspace/MaterialWorkspace.cpp rename to SynapseEngine/Editor/Workspace/MaterialWorkspace/MaterialWorkspace.cpp index b8e30e3f..4d577f7b 100644 --- a/SynapseEngine/Editor/Workspace/MaterialWorkspace.cpp +++ b/SynapseEngine/Editor/Workspace/MaterialWorkspace/MaterialWorkspace.cpp @@ -1,20 +1,20 @@ #include "MaterialWorkspace.h" #include "Editor/Manager/EditorIcons.h" -#include "Editor/View/ContentBrowser/ContentBrowserView.h" -#include "EditorCore/ViewModels/ContentBrowser/ContentBrowserViewModel.h" +#include "Editor/Workspace/Common/ContentBrowser/ContentBrowserView.h" +#include "EditorCore/ViewModels/Common/ContentBrowser/ContentBrowserViewModel.h" -#include "Editor/View/MaterialGraph/MaterialGraphView.h" -#include "EditorCore/ViewModels/MaterialGraph/MaterialGraphViewModel.h" +#include "View/MaterialGraph/MaterialGraphView.h" +#include "EditorCore/ViewModels/MaterialWorkspace/MaterialGraph/MaterialGraphViewModel.h" -#include "Editor/View/MaterialHierarchy/MaterialHierarchyView.h" -#include "EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyViewModel.h" +#include "View/MaterialHierarchy/MaterialHierarchyView.h" +#include "EditorCore/ViewModels/MaterialWorkspace/MaterialHierarchy/MaterialHierarchyViewModel.h" -#include "Editor/View/MaterialProperties/MaterialPropertiesView.h" -#include "EditorCore/ViewModels/MaterialProperties/MaterialPropertiesViewModel.h" +#include "View/MaterialProperties/MaterialPropertiesView.h" +#include "EditorCore/ViewModels/MaterialWorkspace/MaterialProperties/MaterialPropertiesViewModel.h" -#include "Editor/View/MaterialViewport/MaterialViewportView.h" -#include "EditorCore/ViewModels/MaterialViewport/MaterialViewportViewModel.h" +#include "View/MaterialViewport/MaterialViewportView.h" +#include "EditorCore/ViewModels/MaterialWorkspace/MaterialViewport/MaterialViewportViewModel.h" #include "Engine/Scene/SceneNames.h" diff --git a/SynapseEngine/Editor/Workspace/MaterialWorkspace.h b/SynapseEngine/Editor/Workspace/MaterialWorkspace/MaterialWorkspace.h similarity index 93% rename from SynapseEngine/Editor/Workspace/MaterialWorkspace.h rename to SynapseEngine/Editor/Workspace/MaterialWorkspace/MaterialWorkspace.h index 51bce057..c177ef07 100644 --- a/SynapseEngine/Editor/Workspace/MaterialWorkspace.h +++ b/SynapseEngine/Editor/Workspace/MaterialWorkspace/MaterialWorkspace.h @@ -1,5 +1,5 @@ #pragma once -#include "IWorkspace.h" +#include "Editor/Workspace/IWorkspace.h" #include "Editor/EditorApi/EditorContext.h" #include "Editor/Manager/IconManager.h" #include diff --git a/SynapseEngine/Editor/View/MaterialGraph/MaterialGraphView.cpp b/SynapseEngine/Editor/Workspace/MaterialWorkspace/View/MaterialGraph/MaterialGraphView.cpp similarity index 100% rename from SynapseEngine/Editor/View/MaterialGraph/MaterialGraphView.cpp rename to SynapseEngine/Editor/Workspace/MaterialWorkspace/View/MaterialGraph/MaterialGraphView.cpp diff --git a/SynapseEngine/Editor/View/MaterialGraph/MaterialGraphView.h b/SynapseEngine/Editor/Workspace/MaterialWorkspace/View/MaterialGraph/MaterialGraphView.h similarity index 87% rename from SynapseEngine/Editor/View/MaterialGraph/MaterialGraphView.h rename to SynapseEngine/Editor/Workspace/MaterialWorkspace/View/MaterialGraph/MaterialGraphView.h index 8188a378..8102f202 100644 --- a/SynapseEngine/Editor/View/MaterialGraph/MaterialGraphView.h +++ b/SynapseEngine/Editor/Workspace/MaterialWorkspace/View/MaterialGraph/MaterialGraphView.h @@ -1,6 +1,6 @@ #pragma once -#include "Editor/View/IView.h" -#include "EditorCore/ViewModels/MaterialGraph/MaterialGraphViewModel.h" +#include "Editor/Workspace/IView.h" +#include "EditorCore/ViewModels/MaterialWorkspace/MaterialGraph/MaterialGraphViewModel.h" namespace ax { namespace NodeEditor { diff --git a/SynapseEngine/Editor/View/MaterialHierarchy/MaterialHierarchyView.cpp b/SynapseEngine/Editor/Workspace/MaterialWorkspace/View/MaterialHierarchy/MaterialHierarchyView.cpp similarity index 100% rename from SynapseEngine/Editor/View/MaterialHierarchy/MaterialHierarchyView.cpp rename to SynapseEngine/Editor/Workspace/MaterialWorkspace/View/MaterialHierarchy/MaterialHierarchyView.cpp diff --git a/SynapseEngine/Editor/View/MaterialHierarchy/MaterialHierarchyView.h b/SynapseEngine/Editor/Workspace/MaterialWorkspace/View/MaterialHierarchy/MaterialHierarchyView.h similarity index 73% rename from SynapseEngine/Editor/View/MaterialHierarchy/MaterialHierarchyView.h rename to SynapseEngine/Editor/Workspace/MaterialWorkspace/View/MaterialHierarchy/MaterialHierarchyView.h index 322678c0..6d2eaccb 100644 --- a/SynapseEngine/Editor/View/MaterialHierarchy/MaterialHierarchyView.h +++ b/SynapseEngine/Editor/Workspace/MaterialWorkspace/View/MaterialHierarchy/MaterialHierarchyView.h @@ -1,6 +1,6 @@ #pragma once -#include "Editor/View/IView.h" -#include "EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyViewModel.h" +#include "Editor/Workspace/IView.h" +#include "EditorCore/ViewModels/MaterialWorkspace/MaterialHierarchy/MaterialHierarchyViewModel.h" #include #include diff --git a/SynapseEngine/Editor/View/MaterialProperties/MaterialPropertiesView.cpp b/SynapseEngine/Editor/Workspace/MaterialWorkspace/View/MaterialProperties/MaterialPropertiesView.cpp similarity index 100% rename from SynapseEngine/Editor/View/MaterialProperties/MaterialPropertiesView.cpp rename to SynapseEngine/Editor/Workspace/MaterialWorkspace/View/MaterialProperties/MaterialPropertiesView.cpp diff --git a/SynapseEngine/Editor/View/MaterialProperties/MaterialPropertiesView.h b/SynapseEngine/Editor/Workspace/MaterialWorkspace/View/MaterialProperties/MaterialPropertiesView.h similarity index 82% rename from SynapseEngine/Editor/View/MaterialProperties/MaterialPropertiesView.h rename to SynapseEngine/Editor/Workspace/MaterialWorkspace/View/MaterialProperties/MaterialPropertiesView.h index a63e8612..aa238cfc 100644 --- a/SynapseEngine/Editor/View/MaterialProperties/MaterialPropertiesView.h +++ b/SynapseEngine/Editor/Workspace/MaterialWorkspace/View/MaterialProperties/MaterialPropertiesView.h @@ -1,6 +1,6 @@ #pragma once -#include "Editor/View/IView.h" -#include "EditorCore/ViewModels/MaterialProperties/MaterialPropertiesViewModel.h" +#include "Editor/Workspace/IView.h" +#include "EditorCore/ViewModels/MaterialWorkspace/MaterialProperties/MaterialPropertiesViewModel.h" #include #include diff --git a/SynapseEngine/Editor/View/MaterialViewport/MaterialViewportView.cpp b/SynapseEngine/Editor/Workspace/MaterialWorkspace/View/MaterialViewport/MaterialViewportView.cpp similarity index 100% rename from SynapseEngine/Editor/View/MaterialViewport/MaterialViewportView.cpp rename to SynapseEngine/Editor/Workspace/MaterialWorkspace/View/MaterialViewport/MaterialViewportView.cpp diff --git a/SynapseEngine/Editor/View/MaterialViewport/MaterialViewportView.h b/SynapseEngine/Editor/Workspace/MaterialWorkspace/View/MaterialViewport/MaterialViewportView.h similarity index 60% rename from SynapseEngine/Editor/View/MaterialViewport/MaterialViewportView.h rename to SynapseEngine/Editor/Workspace/MaterialWorkspace/View/MaterialViewport/MaterialViewportView.h index 6e206064..39138a46 100644 --- a/SynapseEngine/Editor/View/MaterialViewport/MaterialViewportView.h +++ b/SynapseEngine/Editor/Workspace/MaterialWorkspace/View/MaterialViewport/MaterialViewportView.h @@ -1,6 +1,6 @@ #pragma once -#include "Editor/View/IView.h" -#include "EditorCore/ViewModels/MaterialViewport/MaterialViewportViewModel.h" +#include "Editor/Workspace/IView.h" +#include "EditorCore/ViewModels/MaterialWorkspace/MaterialViewport/MaterialViewportViewModel.h" #include namespace Syn { diff --git a/SynapseEngine/Editor/Workspace/ModelWorkspace.cpp b/SynapseEngine/Editor/Workspace/ModelWorkspace/ModelWorkspace.cpp similarity index 76% rename from SynapseEngine/Editor/Workspace/ModelWorkspace.cpp rename to SynapseEngine/Editor/Workspace/ModelWorkspace/ModelWorkspace.cpp index 7d5d9d7f..56926149 100644 --- a/SynapseEngine/Editor/Workspace/ModelWorkspace.cpp +++ b/SynapseEngine/Editor/Workspace/ModelWorkspace/ModelWorkspace.cpp @@ -1,17 +1,17 @@ #include "ModelWorkspace.h" #include "Editor/Manager/EditorIcons.h" -#include "Editor/View/ContentBrowser/ContentBrowserView.h" -#include "EditorCore/ViewModels/ContentBrowser/ContentBrowserViewModel.h" +#include "Editor/Workspace/Common/ContentBrowser/ContentBrowserView.h" +#include "EditorCore/ViewModels/Common/ContentBrowser/ContentBrowserViewModel.h" -#include "Editor/View/ModelHierarchy/ModelHierarchyView.h" -#include "EditorCore/ViewModels/ModelHierarchy/ModelHierarchyViewModel.h" +#include "View/ModelHierarchy/ModelHierarchyView.h" +#include "EditorCore/ViewModels/ModelWorkspace/ModelHierarchy/ModelHierarchyViewModel.h" -#include "Editor/View/ModelProperties/ModelPropertiesView.h" -#include "EditorCore/ViewModels/ModelProperties/ModelPropertiesViewModel.h" +#include "View/ModelProperties/ModelPropertiesView.h" +#include "EditorCore/ViewModels/ModelWorkspace/ModelProperties/ModelPropertiesViewModel.h" -#include "Editor/View/ModelViewport/ModelViewportView.h" -#include "EditorCore/ViewModels/ModelViewport/ModelViewportViewModel.h" +#include "View/ModelViewport/ModelViewportView.h" +#include "EditorCore/ViewModels/ModelWorkspace/ModelViewport/ModelViewportViewModel.h" #include "Engine/Scene/SceneNames.h" diff --git a/SynapseEngine/Editor/Workspace/ModelWorkspace.h b/SynapseEngine/Editor/Workspace/ModelWorkspace/ModelWorkspace.h similarity index 92% rename from SynapseEngine/Editor/Workspace/ModelWorkspace.h rename to SynapseEngine/Editor/Workspace/ModelWorkspace/ModelWorkspace.h index c3c30995..5fd3216d 100644 --- a/SynapseEngine/Editor/Workspace/ModelWorkspace.h +++ b/SynapseEngine/Editor/Workspace/ModelWorkspace/ModelWorkspace.h @@ -1,5 +1,5 @@ #pragma once -#include "IWorkspace.h" +#include "Editor/Workspace/IWorkspace.h" #include "Editor/EditorApi/EditorContext.h" #include "Editor/Manager/IconManager.h" #include diff --git a/SynapseEngine/Editor/View/ModelHierarchy/ModelHierarchyView.cpp b/SynapseEngine/Editor/Workspace/ModelWorkspace/View/ModelHierarchy/ModelHierarchyView.cpp similarity index 100% rename from SynapseEngine/Editor/View/ModelHierarchy/ModelHierarchyView.cpp rename to SynapseEngine/Editor/Workspace/ModelWorkspace/View/ModelHierarchy/ModelHierarchyView.cpp diff --git a/SynapseEngine/Editor/View/ModelHierarchy/ModelHierarchyView.h b/SynapseEngine/Editor/Workspace/ModelWorkspace/View/ModelHierarchy/ModelHierarchyView.h similarity index 78% rename from SynapseEngine/Editor/View/ModelHierarchy/ModelHierarchyView.h rename to SynapseEngine/Editor/Workspace/ModelWorkspace/View/ModelHierarchy/ModelHierarchyView.h index b315935c..f736a2f9 100644 --- a/SynapseEngine/Editor/View/ModelHierarchy/ModelHierarchyView.h +++ b/SynapseEngine/Editor/Workspace/ModelWorkspace/View/ModelHierarchy/ModelHierarchyView.h @@ -1,6 +1,6 @@ #pragma once -#include "Editor/View/IView.h" -#include "EditorCore/ViewModels/ModelHierarchy/ModelHierarchyViewModel.h" +#include "Editor/Workspace/IView.h" +#include "EditorCore/ViewModels/ModelWorkspace/ModelHierarchy/ModelHierarchyViewModel.h" #include #include diff --git a/SynapseEngine/Editor/View/ModelProperties/ModelPropertiesView.cpp b/SynapseEngine/Editor/Workspace/ModelWorkspace/View/ModelProperties/ModelPropertiesView.cpp similarity index 100% rename from SynapseEngine/Editor/View/ModelProperties/ModelPropertiesView.cpp rename to SynapseEngine/Editor/Workspace/ModelWorkspace/View/ModelProperties/ModelPropertiesView.cpp diff --git a/SynapseEngine/Editor/View/ModelProperties/ModelPropertiesView.h b/SynapseEngine/Editor/Workspace/ModelWorkspace/View/ModelProperties/ModelPropertiesView.h similarity index 75% rename from SynapseEngine/Editor/View/ModelProperties/ModelPropertiesView.h rename to SynapseEngine/Editor/Workspace/ModelWorkspace/View/ModelProperties/ModelPropertiesView.h index 51cee299..c4218e85 100644 --- a/SynapseEngine/Editor/View/ModelProperties/ModelPropertiesView.h +++ b/SynapseEngine/Editor/Workspace/ModelWorkspace/View/ModelProperties/ModelPropertiesView.h @@ -1,6 +1,6 @@ #pragma once -#include "Editor/View/IView.h" -#include "EditorCore/ViewModels/ModelProperties/ModelPropertiesViewModel.h" +#include "Editor/Workspace/IView.h" +#include "EditorCore/ViewModels/ModelWorkspace/ModelProperties/ModelPropertiesViewModel.h" #include #include diff --git a/SynapseEngine/Editor/View/ModelViewport/ModelViewportView.cpp b/SynapseEngine/Editor/Workspace/ModelWorkspace/View/ModelViewport/ModelViewportView.cpp similarity index 100% rename from SynapseEngine/Editor/View/ModelViewport/ModelViewportView.cpp rename to SynapseEngine/Editor/Workspace/ModelWorkspace/View/ModelViewport/ModelViewportView.cpp diff --git a/SynapseEngine/Editor/View/ModelViewport/ModelViewportView.h b/SynapseEngine/Editor/Workspace/ModelWorkspace/View/ModelViewport/ModelViewportView.h similarity index 86% rename from SynapseEngine/Editor/View/ModelViewport/ModelViewportView.h rename to SynapseEngine/Editor/Workspace/ModelWorkspace/View/ModelViewport/ModelViewportView.h index 2937ddb4..c48e8608 100644 --- a/SynapseEngine/Editor/View/ModelViewport/ModelViewportView.h +++ b/SynapseEngine/Editor/Workspace/ModelWorkspace/View/ModelViewport/ModelViewportView.h @@ -1,6 +1,6 @@ #pragma once -#include "Editor/View/IView.h" -#include "EditorCore/ViewModels/ModelViewport/ModelViewportViewModel.h" +#include "Editor/Workspace/IView.h" +#include "EditorCore/ViewModels/ModelWorkspace/ModelViewport/ModelViewportViewModel.h" #include namespace Syn { diff --git a/SynapseEngine/Editor/Workspace/SceneWorkspace.cpp b/SynapseEngine/Editor/Workspace/SceneWorkspace/SceneWorkspace.cpp similarity index 79% rename from SynapseEngine/Editor/Workspace/SceneWorkspace.cpp rename to SynapseEngine/Editor/Workspace/SceneWorkspace/SceneWorkspace.cpp index 8cfed37a..d25b2949 100644 --- a/SynapseEngine/Editor/Workspace/SceneWorkspace.cpp +++ b/SynapseEngine/Editor/Workspace/SceneWorkspace/SceneWorkspace.cpp @@ -1,20 +1,26 @@ #include "SceneWorkspace.h" #include "Editor/Manager/EditorIcons.h" -#include "Editor/View/ContentBrowser/ContentBrowserView.h" -#include "EditorCore/ViewModels/ContentBrowser/ContentBrowserViewModel.h" -#include "Editor/View/Component/ComponentView.h" -#include "EditorCore/ViewModels/Component/ComponentViewModel.h" -#include "Editor/View/Viewport/ViewportView.h" -#include "EditorCore/ViewModels/Viewport/ViewportViewModel.h" -#include "Editor/View/Settings/SettingsView.h" -#include "EditorCore/ViewModels/Settings/SettingsViewModel.h" -#include "Editor/View/Hierarchy/HierarchyView.h" -#include "EditorCore/ViewModels/Hierarchy/HierarchyViewModel.h" -#include "Editor/View/Benchmark/BenchmarkView.h" -#include "EditorCore/ViewModels/Benchmark/BenchmarkViewModel.h" -#include "Editor/View/Logger/LoggerView.h" -#include "EditorCore/ViewModels/Logger/LoggerViewModel.h" +#include "Editor/Workspace/Common/ContentBrowser/ContentBrowserView.h" +#include "EditorCore/ViewModels/Common/ContentBrowser/ContentBrowserViewModel.h" + +#include "View/Component/ComponentView.h" +#include "EditorCore/ViewModels/SceneWorkspace/Component/ComponentViewModel.h" + +#include "View/Viewport/ViewportView.h" +#include "EditorCore/ViewModels/SceneWorkspace/Viewport/ViewportViewModel.h" + +#include "View/Settings/SettingsView.h" +#include "EditorCore/ViewModels/SceneWorkspace/Settings/SettingsViewModel.h" + +#include "View/Hierarchy/HierarchyView.h" +#include "EditorCore/ViewModels/SceneWorkspace/Hierarchy/HierarchyViewModel.h" + +#include "View/Benchmark/BenchmarkView.h" +#include "EditorCore/ViewModels/SceneWorkspace/Benchmark/BenchmarkViewModel.h" + +#include "View/Logger/LoggerView.h" +#include "EditorCore/ViewModels/SceneWorkspace/Logger/LoggerViewModel.h" #include "Engine/Scene/SceneNames.h" diff --git a/SynapseEngine/Editor/Workspace/SceneWorkspace.h b/SynapseEngine/Editor/Workspace/SceneWorkspace/SceneWorkspace.h similarity index 92% rename from SynapseEngine/Editor/Workspace/SceneWorkspace.h rename to SynapseEngine/Editor/Workspace/SceneWorkspace/SceneWorkspace.h index 0fc94042..787f285d 100644 --- a/SynapseEngine/Editor/Workspace/SceneWorkspace.h +++ b/SynapseEngine/Editor/Workspace/SceneWorkspace/SceneWorkspace.h @@ -1,5 +1,5 @@ #pragma once -#include "IWorkspace.h" +#include "Editor/Workspace/IWorkspace.h" #include "Editor/EditorApi/EditorContext.h" #include "Editor/Manager/IconManager.h" #include diff --git a/SynapseEngine/Editor/View/Benchmark/BenchmarkView.cpp b/SynapseEngine/Editor/Workspace/SceneWorkspace/View/Benchmark/BenchmarkView.cpp similarity index 99% rename from SynapseEngine/Editor/View/Benchmark/BenchmarkView.cpp rename to SynapseEngine/Editor/Workspace/SceneWorkspace/View/Benchmark/BenchmarkView.cpp index 2d6e8064..c2ab46c3 100644 --- a/SynapseEngine/Editor/View/Benchmark/BenchmarkView.cpp +++ b/SynapseEngine/Editor/Workspace/SceneWorkspace/View/Benchmark/BenchmarkView.cpp @@ -6,8 +6,6 @@ #include #include -#include "EditorCore/ViewModels/Benchmark/BenchmarkViewModel.h" - namespace Syn { void BenchmarkView::Draw(BenchmarkViewModel& vm) { diff --git a/SynapseEngine/Editor/View/Benchmark/BenchmarkView.h b/SynapseEngine/Editor/Workspace/SceneWorkspace/View/Benchmark/BenchmarkView.h similarity index 88% rename from SynapseEngine/Editor/View/Benchmark/BenchmarkView.h rename to SynapseEngine/Editor/Workspace/SceneWorkspace/View/Benchmark/BenchmarkView.h index b67a0598..d7a4be44 100644 --- a/SynapseEngine/Editor/View/Benchmark/BenchmarkView.h +++ b/SynapseEngine/Editor/Workspace/SceneWorkspace/View/Benchmark/BenchmarkView.h @@ -1,6 +1,6 @@ #pragma once -#include "Editor/View/IView.h" -#include "EditorCore/ViewModels/Benchmark/BenchmarkViewModel.h" +#include "Editor/Workspace/IView.h" +#include "EditorCore/ViewModels/SceneWorkspace/Benchmark/BenchmarkViewModel.h" #include #include diff --git a/SynapseEngine/Editor/View/Component/ComponentView.cpp b/SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/ComponentView.cpp similarity index 100% rename from SynapseEngine/Editor/View/Component/ComponentView.cpp rename to SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/ComponentView.cpp diff --git a/SynapseEngine/Editor/View/Component/ComponentView.h b/SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/ComponentView.h similarity index 92% rename from SynapseEngine/Editor/View/Component/ComponentView.h rename to SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/ComponentView.h index b1bca46c..68d2f5e5 100644 --- a/SynapseEngine/Editor/View/Component/ComponentView.h +++ b/SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/ComponentView.h @@ -1,6 +1,6 @@ #pragma once -#include "Editor/View/IView.h" -#include "EditorCore/ViewModels/Component/ComponentViewModel.h" +#include "Editor/Workspace/IView.h" +#include "EditorCore/ViewModels/SceneWorkspace/Component/ComponentViewModel.h" #include #include diff --git a/SynapseEngine/Editor/View/Component/Core/CameraView.cpp b/SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Core/CameraView.cpp similarity index 100% rename from SynapseEngine/Editor/View/Component/Core/CameraView.cpp rename to SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Core/CameraView.cpp diff --git a/SynapseEngine/Editor/View/Component/Core/CameraView.h b/SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Core/CameraView.h similarity index 61% rename from SynapseEngine/Editor/View/Component/Core/CameraView.h rename to SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Core/CameraView.h index 67939b50..9e57e0bf 100644 --- a/SynapseEngine/Editor/View/Component/Core/CameraView.h +++ b/SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Core/CameraView.h @@ -1,6 +1,6 @@ #pragma once -#include "Editor/View/IView.h" -#include "EditorCore/ViewModels/Component/Core/Camera/CameraViewModel.h" +#include "Editor/Workspace/IView.h" +#include "EditorCore/ViewModels/SceneWorkspace/Component/Core/Camera/CameraViewModel.h" namespace Syn { class CameraView : public IView { diff --git a/SynapseEngine/Editor/View/Component/Core/TagView.cpp b/SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Core/TagView.cpp similarity index 100% rename from SynapseEngine/Editor/View/Component/Core/TagView.cpp rename to SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Core/TagView.cpp diff --git a/SynapseEngine/Editor/View/Component/Core/TagView.h b/SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Core/TagView.h similarity index 71% rename from SynapseEngine/Editor/View/Component/Core/TagView.h rename to SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Core/TagView.h index 622b5755..ee8e63aa 100644 --- a/SynapseEngine/Editor/View/Component/Core/TagView.h +++ b/SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Core/TagView.h @@ -1,6 +1,6 @@ #pragma once -#include "Editor/View/IView.h" -#include "EditorCore/ViewModels/Component/Core/Tag/TagViewModel.h" +#include "Editor/Workspace/IView.h" +#include "EditorCore/ViewModels/SceneWorkspace/Component/Core/Tag/TagViewModel.h" namespace Syn { class TagView : public IView { diff --git a/SynapseEngine/Editor/View/Component/Core/TransformView.cpp b/SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Core/TransformView.cpp similarity index 100% rename from SynapseEngine/Editor/View/Component/Core/TransformView.cpp rename to SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Core/TransformView.cpp diff --git a/SynapseEngine/Editor/View/Component/Core/TransformView.h b/SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Core/TransformView.h similarity index 61% rename from SynapseEngine/Editor/View/Component/Core/TransformView.h rename to SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Core/TransformView.h index 4855288a..831e23e6 100644 --- a/SynapseEngine/Editor/View/Component/Core/TransformView.h +++ b/SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Core/TransformView.h @@ -1,6 +1,6 @@ #pragma once -#include "Editor/View/IView.h" -#include "EditorCore/ViewModels/Component/Core/Transform/TransformViewModel.h" +#include "Editor/Workspace/IView.h" +#include "EditorCore/ViewModels/SceneWorkspace/Component/Core/Transform/TransformViewModel.h" namespace Syn { class TransformView : public IView { diff --git a/SynapseEngine/Editor/View/Component/Light/DirectionLightView.cpp b/SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Light/DirectionLightView.cpp similarity index 100% rename from SynapseEngine/Editor/View/Component/Light/DirectionLightView.cpp rename to SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Light/DirectionLightView.cpp diff --git a/SynapseEngine/Editor/View/Component/Light/DirectionLightView.h b/SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Light/DirectionLightView.h similarity index 61% rename from SynapseEngine/Editor/View/Component/Light/DirectionLightView.h rename to SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Light/DirectionLightView.h index 73ccb025..77135da7 100644 --- a/SynapseEngine/Editor/View/Component/Light/DirectionLightView.h +++ b/SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Light/DirectionLightView.h @@ -1,6 +1,6 @@ #pragma once -#include "Editor/View/IView.h" -#include "EditorCore/ViewModels/Component/Light/DirectionLight/DirectionLightViewModel.h" +#include "Editor/Workspace/IView.h" +#include "EditorCore/ViewModels/SceneWorkspace/Component/Light/DirectionLight/DirectionLightViewModel.h" namespace Syn { class DirectionLightView : public IView { diff --git a/SynapseEngine/Editor/View/Component/Light/PointLightView.cpp b/SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Light/PointLightView.cpp similarity index 100% rename from SynapseEngine/Editor/View/Component/Light/PointLightView.cpp rename to SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Light/PointLightView.cpp diff --git a/SynapseEngine/Editor/View/Component/Light/PointLightView.h b/SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Light/PointLightView.h similarity index 61% rename from SynapseEngine/Editor/View/Component/Light/PointLightView.h rename to SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Light/PointLightView.h index 7d02c096..268f7d20 100644 --- a/SynapseEngine/Editor/View/Component/Light/PointLightView.h +++ b/SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Light/PointLightView.h @@ -1,6 +1,6 @@ #pragma once -#include "Editor/View/IView.h" -#include "EditorCore/ViewModels/Component/Light/PointLight/PointLightViewModel.h" +#include "Editor/Workspace/IView.h" +#include "EditorCore/ViewModels/SceneWorkspace/Component/Light/PointLight/PointLightViewModel.h" namespace Syn { class PointLightView : public IView { diff --git a/SynapseEngine/Editor/View/Component/Light/SpotLightView.cpp b/SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Light/SpotLightView.cpp similarity index 100% rename from SynapseEngine/Editor/View/Component/Light/SpotLightView.cpp rename to SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Light/SpotLightView.cpp diff --git a/SynapseEngine/Editor/View/Component/Light/SpotLightView.h b/SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Light/SpotLightView.h similarity index 61% rename from SynapseEngine/Editor/View/Component/Light/SpotLightView.h rename to SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Light/SpotLightView.h index 744c4a67..4ef82466 100644 --- a/SynapseEngine/Editor/View/Component/Light/SpotLightView.h +++ b/SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Light/SpotLightView.h @@ -1,6 +1,6 @@ #pragma once -#include "Editor/View/IView.h" -#include "EditorCore/ViewModels/Component/Light/SpotLight/SpotLightViewModel.h" +#include "Editor/Workspace/IView.h" +#include "EditorCore/ViewModels/SceneWorkspace/Component/Light/SpotLight/SpotLightViewModel.h" namespace Syn { class SpotLightView : public IView { diff --git a/SynapseEngine/Editor/View/Component/Physics/BoxColliderView.cpp b/SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Physics/BoxColliderView.cpp similarity index 100% rename from SynapseEngine/Editor/View/Component/Physics/BoxColliderView.cpp rename to SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Physics/BoxColliderView.cpp diff --git a/SynapseEngine/Editor/View/Component/Physics/BoxColliderView.h b/SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Physics/BoxColliderView.h similarity index 61% rename from SynapseEngine/Editor/View/Component/Physics/BoxColliderView.h rename to SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Physics/BoxColliderView.h index 898b0f9d..28f17113 100644 --- a/SynapseEngine/Editor/View/Component/Physics/BoxColliderView.h +++ b/SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Physics/BoxColliderView.h @@ -1,6 +1,6 @@ #pragma once -#include "Editor/View/IView.h" -#include "EditorCore/ViewModels/Component/Physics/BoxCollider/BoxColliderViewModel.h" +#include "Editor/Workspace/IView.h" +#include "EditorCore/ViewModels/SceneWorkspace/Component/Physics/BoxCollider/BoxColliderViewModel.h" namespace Syn { class BoxColliderView : public IView { diff --git a/SynapseEngine/Editor/View/Component/Physics/CapsuleColliderView.cpp b/SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Physics/CapsuleColliderView.cpp similarity index 100% rename from SynapseEngine/Editor/View/Component/Physics/CapsuleColliderView.cpp rename to SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Physics/CapsuleColliderView.cpp diff --git a/SynapseEngine/Editor/View/Component/Physics/CapsuleColliderView.h b/SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Physics/CapsuleColliderView.h similarity index 61% rename from SynapseEngine/Editor/View/Component/Physics/CapsuleColliderView.h rename to SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Physics/CapsuleColliderView.h index d4a0a9cf..5b5745b7 100644 --- a/SynapseEngine/Editor/View/Component/Physics/CapsuleColliderView.h +++ b/SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Physics/CapsuleColliderView.h @@ -1,6 +1,6 @@ #pragma once -#include "Editor/View/IView.h" -#include "EditorCore/ViewModels/Component/Physics/CapsuleCollider/CapsuleColliderViewModel.h" +#include "Editor/Workspace/IView.h" +#include "EditorCore/ViewModels/SceneWorkspace/Component/Physics/CapsuleCollider/CapsuleColliderViewModel.h" namespace Syn { class CapsuleColliderView : public IView { diff --git a/SynapseEngine/Editor/View/Component/Physics/ConvexColliderView.cpp b/SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Physics/ConvexColliderView.cpp similarity index 100% rename from SynapseEngine/Editor/View/Component/Physics/ConvexColliderView.cpp rename to SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Physics/ConvexColliderView.cpp diff --git a/SynapseEngine/Editor/View/Component/Physics/ConvexColliderView.h b/SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Physics/ConvexColliderView.h similarity index 61% rename from SynapseEngine/Editor/View/Component/Physics/ConvexColliderView.h rename to SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Physics/ConvexColliderView.h index fd87ffe4..cd78ae71 100644 --- a/SynapseEngine/Editor/View/Component/Physics/ConvexColliderView.h +++ b/SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Physics/ConvexColliderView.h @@ -1,6 +1,6 @@ #pragma once -#include "Editor/View/IView.h" -#include "EditorCore/ViewModels/Component/Physics/ConvexCollider/ConvexColliderViewModel.h" +#include "Editor/Workspace/IView.h" +#include "EditorCore/ViewModels/SceneWorkspace/Component/Physics/ConvexCollider/ConvexColliderViewModel.h" namespace Syn { class ConvexColliderView : public IView { diff --git a/SynapseEngine/Editor/View/Component/Physics/MeshColliderView.cpp b/SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Physics/MeshColliderView.cpp similarity index 100% rename from SynapseEngine/Editor/View/Component/Physics/MeshColliderView.cpp rename to SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Physics/MeshColliderView.cpp diff --git a/SynapseEngine/Editor/View/Component/Physics/MeshColliderView.h b/SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Physics/MeshColliderView.h similarity index 61% rename from SynapseEngine/Editor/View/Component/Physics/MeshColliderView.h rename to SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Physics/MeshColliderView.h index 8c0f7f76..2c37376f 100644 --- a/SynapseEngine/Editor/View/Component/Physics/MeshColliderView.h +++ b/SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Physics/MeshColliderView.h @@ -1,6 +1,6 @@ #pragma once -#include "Editor/View/IView.h" -#include "EditorCore/ViewModels/Component/Physics/MeshCollider/MeshColliderViewModel.h" +#include "Editor/Workspace/IView.h" +#include "EditorCore/ViewModels/SceneWorkspace/Component/Physics/MeshCollider/MeshColliderViewModel.h" namespace Syn { class MeshColliderView : public IView { diff --git a/SynapseEngine/Editor/View/Component/Physics/RigidBodyView.cpp b/SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Physics/RigidBodyView.cpp similarity index 100% rename from SynapseEngine/Editor/View/Component/Physics/RigidBodyView.cpp rename to SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Physics/RigidBodyView.cpp diff --git a/SynapseEngine/Editor/View/Component/Physics/RigidBodyView.h b/SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Physics/RigidBodyView.h similarity index 61% rename from SynapseEngine/Editor/View/Component/Physics/RigidBodyView.h rename to SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Physics/RigidBodyView.h index b3381a26..aa7b64f1 100644 --- a/SynapseEngine/Editor/View/Component/Physics/RigidBodyView.h +++ b/SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Physics/RigidBodyView.h @@ -1,6 +1,6 @@ #pragma once -#include "Editor/View/IView.h" -#include "EditorCore/ViewModels/Component/Physics/RigidBody/RigidBodyViewModel.h" +#include "Editor/Workspace/IView.h" +#include "EditorCore/ViewModels/SceneWorkspace/Component/Physics/RigidBody/RigidBodyViewModel.h" namespace Syn { class RigidBodyView : public IView { diff --git a/SynapseEngine/Editor/View/Component/Physics/SphereColliderView.cpp b/SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Physics/SphereColliderView.cpp similarity index 100% rename from SynapseEngine/Editor/View/Component/Physics/SphereColliderView.cpp rename to SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Physics/SphereColliderView.cpp diff --git a/SynapseEngine/Editor/View/Component/Physics/SphereColliderView.h b/SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Physics/SphereColliderView.h similarity index 61% rename from SynapseEngine/Editor/View/Component/Physics/SphereColliderView.h rename to SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Physics/SphereColliderView.h index 60810413..4277a81b 100644 --- a/SynapseEngine/Editor/View/Component/Physics/SphereColliderView.h +++ b/SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Physics/SphereColliderView.h @@ -1,6 +1,6 @@ #pragma once -#include "Editor/View/IView.h" -#include "EditorCore/ViewModels/Component/Physics/SphereCollider/SphereColliderViewModel.h" +#include "Editor/Workspace/IView.h" +#include "EditorCore/ViewModels/SceneWorkspace/Component/Physics/SphereCollider/SphereColliderViewModel.h" namespace Syn { class SphereColliderView : public IView { diff --git a/SynapseEngine/Editor/View/Component/Rendering/AnimationView.cpp b/SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Rendering/AnimationView.cpp similarity index 100% rename from SynapseEngine/Editor/View/Component/Rendering/AnimationView.cpp rename to SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Rendering/AnimationView.cpp diff --git a/SynapseEngine/Editor/View/Component/Rendering/AnimationView.h b/SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Rendering/AnimationView.h similarity index 60% rename from SynapseEngine/Editor/View/Component/Rendering/AnimationView.h rename to SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Rendering/AnimationView.h index c22b488e..9c865624 100644 --- a/SynapseEngine/Editor/View/Component/Rendering/AnimationView.h +++ b/SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Rendering/AnimationView.h @@ -1,6 +1,6 @@ #pragma once -#include "Editor/View/IView.h" -#include "EditorCore/ViewModels/Component/Rendering/Animation/AnimationViewModel.h" +#include "Editor/Workspace/IView.h" +#include "EditorCore/ViewModels/SceneWorkspace/Component/Rendering/Animation/AnimationViewModel.h" namespace Syn { class AnimationView : public IView { diff --git a/SynapseEngine/Editor/View/Component/Rendering/MaterialOverrideView.cpp b/SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Rendering/MaterialOverrideView.cpp similarity index 100% rename from SynapseEngine/Editor/View/Component/Rendering/MaterialOverrideView.cpp rename to SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Rendering/MaterialOverrideView.cpp diff --git a/SynapseEngine/Editor/View/Component/Rendering/MaterialOverrideView.h b/SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Rendering/MaterialOverrideView.h similarity index 60% rename from SynapseEngine/Editor/View/Component/Rendering/MaterialOverrideView.h rename to SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Rendering/MaterialOverrideView.h index 75b88489..c7d0da19 100644 --- a/SynapseEngine/Editor/View/Component/Rendering/MaterialOverrideView.h +++ b/SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Rendering/MaterialOverrideView.h @@ -1,6 +1,6 @@ #pragma once -#include "Editor/View/IView.h" -#include "EditorCore/ViewModels/Component/Rendering/MaterialOverride/MaterialOverrideViewModel.h" +#include "Editor/Workspace/IView.h" +#include "EditorCore/ViewModels/SceneWorkspace/Component/Rendering/MaterialOverride/MaterialOverrideViewModel.h" namespace Syn { class MaterialOverrideView : public IView { diff --git a/SynapseEngine/Editor/View/Component/Rendering/ModelComponentView.cpp b/SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Rendering/ModelComponentView.cpp similarity index 100% rename from SynapseEngine/Editor/View/Component/Rendering/ModelComponentView.cpp rename to SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Rendering/ModelComponentView.cpp diff --git a/SynapseEngine/Editor/View/Component/Rendering/ModelComponentView.h b/SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Rendering/ModelComponentView.h similarity index 62% rename from SynapseEngine/Editor/View/Component/Rendering/ModelComponentView.h rename to SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Rendering/ModelComponentView.h index 9715ef06..5ba2b6e0 100644 --- a/SynapseEngine/Editor/View/Component/Rendering/ModelComponentView.h +++ b/SynapseEngine/Editor/Workspace/SceneWorkspace/View/Component/Rendering/ModelComponentView.h @@ -1,6 +1,6 @@ #pragma once -#include "Editor/View/IView.h" -#include "EditorCore/ViewModels/Component/Rendering/Model/ModelComponentViewModel.h" +#include "Editor/Workspace/IView.h" +#include "EditorCore/ViewModels/SceneWorkspace/Component/Rendering/Model/ModelComponentViewModel.h" namespace Syn { class ModelComponentView : public IView { diff --git a/SynapseEngine/Editor/View/Hierarchy/HierarchyView.cpp b/SynapseEngine/Editor/Workspace/SceneWorkspace/View/Hierarchy/HierarchyView.cpp similarity index 100% rename from SynapseEngine/Editor/View/Hierarchy/HierarchyView.cpp rename to SynapseEngine/Editor/Workspace/SceneWorkspace/View/Hierarchy/HierarchyView.cpp diff --git a/SynapseEngine/Editor/View/Hierarchy/HierarchyView.h b/SynapseEngine/Editor/Workspace/SceneWorkspace/View/Hierarchy/HierarchyView.h similarity index 83% rename from SynapseEngine/Editor/View/Hierarchy/HierarchyView.h rename to SynapseEngine/Editor/Workspace/SceneWorkspace/View/Hierarchy/HierarchyView.h index ccdc4158..eb38ad29 100644 --- a/SynapseEngine/Editor/View/Hierarchy/HierarchyView.h +++ b/SynapseEngine/Editor/Workspace/SceneWorkspace/View/Hierarchy/HierarchyView.h @@ -1,6 +1,6 @@ #pragma once -#include "Editor/View/IView.h" -#include "EditorCore/ViewModels/Hierarchy/HierarchyViewModel.h" +#include "Editor/Workspace/IView.h" +#include "EditorCore/ViewModels/SceneWorkspace/Hierarchy/HierarchyViewModel.h" #include #include diff --git a/SynapseEngine/Editor/View/Logger/LoggerView.cpp b/SynapseEngine/Editor/Workspace/SceneWorkspace/View/Logger/LoggerView.cpp similarity index 100% rename from SynapseEngine/Editor/View/Logger/LoggerView.cpp rename to SynapseEngine/Editor/Workspace/SceneWorkspace/View/Logger/LoggerView.cpp diff --git a/SynapseEngine/Editor/View/Logger/LoggerView.h b/SynapseEngine/Editor/Workspace/SceneWorkspace/View/Logger/LoggerView.h similarity index 82% rename from SynapseEngine/Editor/View/Logger/LoggerView.h rename to SynapseEngine/Editor/Workspace/SceneWorkspace/View/Logger/LoggerView.h index f148731b..4e55e869 100644 --- a/SynapseEngine/Editor/View/Logger/LoggerView.h +++ b/SynapseEngine/Editor/Workspace/SceneWorkspace/View/Logger/LoggerView.h @@ -1,6 +1,6 @@ #pragma once -#include "Editor/View/IView.h" -#include "EditorCore/ViewModels/Logger/LoggerViewModel.h" +#include "Editor/Workspace/IView.h" +#include "EditorCore/ViewModels/SceneWorkspace/Logger/LoggerViewModel.h" #include #include #include diff --git a/SynapseEngine/Editor/View/Settings/SettingsView.cpp b/SynapseEngine/Editor/Workspace/SceneWorkspace/View/Settings/SettingsView.cpp similarity index 100% rename from SynapseEngine/Editor/View/Settings/SettingsView.cpp rename to SynapseEngine/Editor/Workspace/SceneWorkspace/View/Settings/SettingsView.cpp diff --git a/SynapseEngine/Editor/View/Settings/SettingsView.h b/SynapseEngine/Editor/Workspace/SceneWorkspace/View/Settings/SettingsView.h similarity index 70% rename from SynapseEngine/Editor/View/Settings/SettingsView.h rename to SynapseEngine/Editor/Workspace/SceneWorkspace/View/Settings/SettingsView.h index 29a3327e..9c1d2bb4 100644 --- a/SynapseEngine/Editor/View/Settings/SettingsView.h +++ b/SynapseEngine/Editor/Workspace/SceneWorkspace/View/Settings/SettingsView.h @@ -1,6 +1,6 @@ #pragma once -#include "Editor/View/IView.h" -#include "EditorCore/ViewModels/Settings/SettingsViewModel.h" +#include "Editor/Workspace/IView.h" +#include "EditorCore/ViewModels/SceneWorkspace/Settings/SettingsViewModel.h" #include #include diff --git a/SynapseEngine/Editor/View/Viewport/ViewportView.cpp b/SynapseEngine/Editor/Workspace/SceneWorkspace/View/Viewport/ViewportView.cpp similarity index 100% rename from SynapseEngine/Editor/View/Viewport/ViewportView.cpp rename to SynapseEngine/Editor/Workspace/SceneWorkspace/View/Viewport/ViewportView.cpp diff --git a/SynapseEngine/Editor/View/Viewport/ViewportView.h b/SynapseEngine/Editor/Workspace/SceneWorkspace/View/Viewport/ViewportView.h similarity index 88% rename from SynapseEngine/Editor/View/Viewport/ViewportView.h rename to SynapseEngine/Editor/Workspace/SceneWorkspace/View/Viewport/ViewportView.h index 1c86aca1..0b0efcf3 100644 --- a/SynapseEngine/Editor/View/Viewport/ViewportView.h +++ b/SynapseEngine/Editor/Workspace/SceneWorkspace/View/Viewport/ViewportView.h @@ -1,7 +1,7 @@ #pragma once -#include "../IView.h" -#include "EditorCore/ViewModels/Viewport/ViewportViewModel.h" +#include "Editor/Workspace/IView.h" +#include "EditorCore/ViewModels/SceneWorkspace/Viewport/ViewportViewModel.h" #include namespace Syn { diff --git a/SynapseEngine/Editor/Workspace/TextureWorkspace.cpp b/SynapseEngine/Editor/Workspace/TextureWorkspace/TextureWorkspace.cpp similarity index 70% rename from SynapseEngine/Editor/Workspace/TextureWorkspace.cpp rename to SynapseEngine/Editor/Workspace/TextureWorkspace/TextureWorkspace.cpp index f33693e3..c8b60a97 100644 --- a/SynapseEngine/Editor/Workspace/TextureWorkspace.cpp +++ b/SynapseEngine/Editor/Workspace/TextureWorkspace/TextureWorkspace.cpp @@ -1,17 +1,17 @@ #include "TextureWorkspace.h" #include "Editor/Manager/EditorIcons.h" -#include "Editor/View/ContentBrowser/ContentBrowserView.h" -#include "EditorCore/ViewModels/ContentBrowser/ContentBrowserViewModel.h" +#include "Editor/Workspace/Common/ContentBrowser/ContentBrowserView.h" +#include "EditorCore/ViewModels/Common/ContentBrowser/ContentBrowserViewModel.h" -#include "Editor/View/TextureHierarchy/TextureHierarchyView.h" -#include "EditorCore/ViewModels/TextureHierarchy/TextureHierarchyViewModel.h" +#include "View/TextureHierarchy/TextureHierarchyView.h" +#include "EditorCore/ViewModels/TextureWorkspace/TextureHierarchy/TextureHierarchyViewModel.h" -#include "Editor/View/TextureProperties/TexturePropertiesView.h" -#include "EditorCore/ViewModels/TextureProperties/TexturePropertiesViewModel.h" +#include "View/TextureProperties/TexturePropertiesView.h" +#include "EditorCore/ViewModels/TextureWorkspace/TextureProperties/TexturePropertiesViewModel.h" -#include "Editor/View/TextureGraph/TextureGraphView.h" -#include "EditorCore/ViewModels/TextureGraph/TextureGraphViewModel.h" +#include "View/TextureGraph/TextureGraphView.h" +#include "EditorCore/ViewModels/TextureWorkspace/TextureGraph/TextureGraphViewModel.h" namespace Syn { diff --git a/SynapseEngine/Editor/Workspace/TextureWorkspace.h b/SynapseEngine/Editor/Workspace/TextureWorkspace/TextureWorkspace.h similarity index 92% rename from SynapseEngine/Editor/Workspace/TextureWorkspace.h rename to SynapseEngine/Editor/Workspace/TextureWorkspace/TextureWorkspace.h index 391b21ea..3bb82497 100644 --- a/SynapseEngine/Editor/Workspace/TextureWorkspace.h +++ b/SynapseEngine/Editor/Workspace/TextureWorkspace/TextureWorkspace.h @@ -1,5 +1,5 @@ #pragma once -#include "IWorkspace.h" +#include "Editor/Workspace/IWorkspace.h" #include "Editor/EditorApi/EditorContext.h" #include "Editor/Manager/IconManager.h" #include diff --git a/SynapseEngine/Editor/View/TextureGraph/TextureGraphView.cpp b/SynapseEngine/Editor/Workspace/TextureWorkspace/View/TextureGraph/TextureGraphView.cpp similarity index 100% rename from SynapseEngine/Editor/View/TextureGraph/TextureGraphView.cpp rename to SynapseEngine/Editor/Workspace/TextureWorkspace/View/TextureGraph/TextureGraphView.cpp diff --git a/SynapseEngine/Editor/View/TextureGraph/TextureGraphView.h b/SynapseEngine/Editor/Workspace/TextureWorkspace/View/TextureGraph/TextureGraphView.h similarity index 84% rename from SynapseEngine/Editor/View/TextureGraph/TextureGraphView.h rename to SynapseEngine/Editor/Workspace/TextureWorkspace/View/TextureGraph/TextureGraphView.h index 5b3a7826..f6e05771 100644 --- a/SynapseEngine/Editor/View/TextureGraph/TextureGraphView.h +++ b/SynapseEngine/Editor/Workspace/TextureWorkspace/View/TextureGraph/TextureGraphView.h @@ -1,6 +1,6 @@ #pragma once -#include "Editor/View/IView.h" -#include "EditorCore/ViewModels/TextureGraph/TextureGraphViewModel.h" +#include "Editor/Workspace/IView.h" +#include "EditorCore/ViewModels/TextureWorkspace/TextureGraph/TextureGraphViewModel.h" namespace ax { namespace NodeEditor { diff --git a/SynapseEngine/Editor/View/TextureHierarchy/TextureHierarchyView.cpp b/SynapseEngine/Editor/Workspace/TextureWorkspace/View/TextureHierarchy/TextureHierarchyView.cpp similarity index 100% rename from SynapseEngine/Editor/View/TextureHierarchy/TextureHierarchyView.cpp rename to SynapseEngine/Editor/Workspace/TextureWorkspace/View/TextureHierarchy/TextureHierarchyView.cpp diff --git a/SynapseEngine/Editor/View/TextureHierarchy/TextureHierarchyView.h b/SynapseEngine/Editor/Workspace/TextureWorkspace/View/TextureHierarchy/TextureHierarchyView.h similarity index 77% rename from SynapseEngine/Editor/View/TextureHierarchy/TextureHierarchyView.h rename to SynapseEngine/Editor/Workspace/TextureWorkspace/View/TextureHierarchy/TextureHierarchyView.h index ecbf4bcc..fd6792b3 100644 --- a/SynapseEngine/Editor/View/TextureHierarchy/TextureHierarchyView.h +++ b/SynapseEngine/Editor/Workspace/TextureWorkspace/View/TextureHierarchy/TextureHierarchyView.h @@ -1,6 +1,6 @@ #pragma once -#include "Editor/View/IView.h" -#include "EditorCore/ViewModels/TextureHierarchy/TextureHierarchyViewModel.h" +#include "Editor/Workspace/IView.h" +#include "EditorCore/ViewModels/TextureWorkspace/TextureHierarchy/TextureHierarchyViewModel.h" #include #include diff --git a/SynapseEngine/Editor/View/TextureProperties/TexturePropertiesView.cpp b/SynapseEngine/Editor/Workspace/TextureWorkspace/View/TextureProperties/TexturePropertiesView.cpp similarity index 100% rename from SynapseEngine/Editor/View/TextureProperties/TexturePropertiesView.cpp rename to SynapseEngine/Editor/Workspace/TextureWorkspace/View/TextureProperties/TexturePropertiesView.cpp diff --git a/SynapseEngine/Editor/View/TextureProperties/TexturePropertiesView.h b/SynapseEngine/Editor/Workspace/TextureWorkspace/View/TextureProperties/TexturePropertiesView.h similarity index 74% rename from SynapseEngine/Editor/View/TextureProperties/TexturePropertiesView.h rename to SynapseEngine/Editor/Workspace/TextureWorkspace/View/TextureProperties/TexturePropertiesView.h index d68b45af..45e9fd50 100644 --- a/SynapseEngine/Editor/View/TextureProperties/TexturePropertiesView.h +++ b/SynapseEngine/Editor/Workspace/TextureWorkspace/View/TextureProperties/TexturePropertiesView.h @@ -1,6 +1,6 @@ #pragma once -#include "Editor/View/IView.h" -#include "EditorCore/ViewModels/TextureProperties/TexturePropertiesViewModel.h" +#include "Editor/Workspace/IView.h" +#include "EditorCore/ViewModels/TextureWorkspace/TextureProperties/TexturePropertiesViewModel.h" #include #include diff --git a/SynapseEngine/EditorCore/ViewModels/ContentBrowser/ContentBrowserIntent.cpp b/SynapseEngine/EditorCore/ViewModels/Common/ContentBrowser/ContentBrowserIntent.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/ContentBrowser/ContentBrowserIntent.cpp rename to SynapseEngine/EditorCore/ViewModels/Common/ContentBrowser/ContentBrowserIntent.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/ContentBrowser/ContentBrowserIntent.h b/SynapseEngine/EditorCore/ViewModels/Common/ContentBrowser/ContentBrowserIntent.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/ContentBrowser/ContentBrowserIntent.h rename to SynapseEngine/EditorCore/ViewModels/Common/ContentBrowser/ContentBrowserIntent.h diff --git a/SynapseEngine/EditorCore/ViewModels/ContentBrowser/ContentBrowserState.cpp b/SynapseEngine/EditorCore/ViewModels/Common/ContentBrowser/ContentBrowserState.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/ContentBrowser/ContentBrowserState.cpp rename to SynapseEngine/EditorCore/ViewModels/Common/ContentBrowser/ContentBrowserState.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/ContentBrowser/ContentBrowserState.h b/SynapseEngine/EditorCore/ViewModels/Common/ContentBrowser/ContentBrowserState.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/ContentBrowser/ContentBrowserState.h rename to SynapseEngine/EditorCore/ViewModels/Common/ContentBrowser/ContentBrowserState.h diff --git a/SynapseEngine/EditorCore/ViewModels/ContentBrowser/ContentBrowserViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/Common/ContentBrowser/ContentBrowserViewModel.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/ContentBrowser/ContentBrowserViewModel.cpp rename to SynapseEngine/EditorCore/ViewModels/Common/ContentBrowser/ContentBrowserViewModel.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/ContentBrowser/ContentBrowserViewModel.h b/SynapseEngine/EditorCore/ViewModels/Common/ContentBrowser/ContentBrowserViewModel.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/ContentBrowser/ContentBrowserViewModel.h rename to SynapseEngine/EditorCore/ViewModels/Common/ContentBrowser/ContentBrowserViewModel.h diff --git a/SynapseEngine/EditorCore/ViewModels/MainMenu/MainMenuIntent.cpp b/SynapseEngine/EditorCore/ViewModels/Common/MainMenu/MainMenuIntent.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/MainMenu/MainMenuIntent.cpp rename to SynapseEngine/EditorCore/ViewModels/Common/MainMenu/MainMenuIntent.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/MainMenu/MainMenuIntent.h b/SynapseEngine/EditorCore/ViewModels/Common/MainMenu/MainMenuIntent.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/MainMenu/MainMenuIntent.h rename to SynapseEngine/EditorCore/ViewModels/Common/MainMenu/MainMenuIntent.h diff --git a/SynapseEngine/EditorCore/ViewModels/MainMenu/MainMenuState.cpp b/SynapseEngine/EditorCore/ViewModels/Common/MainMenu/MainMenuState.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/MainMenu/MainMenuState.cpp rename to SynapseEngine/EditorCore/ViewModels/Common/MainMenu/MainMenuState.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/MainMenu/MainMenuState.h b/SynapseEngine/EditorCore/ViewModels/Common/MainMenu/MainMenuState.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/MainMenu/MainMenuState.h rename to SynapseEngine/EditorCore/ViewModels/Common/MainMenu/MainMenuState.h diff --git a/SynapseEngine/EditorCore/ViewModels/MainMenu/MainMenuViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/Common/MainMenu/MainMenuViewModel.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/MainMenu/MainMenuViewModel.cpp rename to SynapseEngine/EditorCore/ViewModels/Common/MainMenu/MainMenuViewModel.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/MainMenu/MainMenuViewModel.h b/SynapseEngine/EditorCore/ViewModels/Common/MainMenu/MainMenuViewModel.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/MainMenu/MainMenuViewModel.h rename to SynapseEngine/EditorCore/ViewModels/Common/MainMenu/MainMenuViewModel.h diff --git a/SynapseEngine/EditorCore/ViewModels/MaterialGraph/MaterialGraphIntent.cpp b/SynapseEngine/EditorCore/ViewModels/MaterialWorkspace/MaterialGraph/MaterialGraphIntent.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/MaterialGraph/MaterialGraphIntent.cpp rename to SynapseEngine/EditorCore/ViewModels/MaterialWorkspace/MaterialGraph/MaterialGraphIntent.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/MaterialGraph/MaterialGraphIntent.h b/SynapseEngine/EditorCore/ViewModels/MaterialWorkspace/MaterialGraph/MaterialGraphIntent.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/MaterialGraph/MaterialGraphIntent.h rename to SynapseEngine/EditorCore/ViewModels/MaterialWorkspace/MaterialGraph/MaterialGraphIntent.h diff --git a/SynapseEngine/EditorCore/ViewModels/MaterialGraph/MaterialGraphState.cpp b/SynapseEngine/EditorCore/ViewModels/MaterialWorkspace/MaterialGraph/MaterialGraphState.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/MaterialGraph/MaterialGraphState.cpp rename to SynapseEngine/EditorCore/ViewModels/MaterialWorkspace/MaterialGraph/MaterialGraphState.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/MaterialGraph/MaterialGraphState.h b/SynapseEngine/EditorCore/ViewModels/MaterialWorkspace/MaterialGraph/MaterialGraphState.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/MaterialGraph/MaterialGraphState.h rename to SynapseEngine/EditorCore/ViewModels/MaterialWorkspace/MaterialGraph/MaterialGraphState.h diff --git a/SynapseEngine/EditorCore/ViewModels/MaterialGraph/MaterialGraphViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/MaterialWorkspace/MaterialGraph/MaterialGraphViewModel.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/MaterialGraph/MaterialGraphViewModel.cpp rename to SynapseEngine/EditorCore/ViewModels/MaterialWorkspace/MaterialGraph/MaterialGraphViewModel.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/MaterialGraph/MaterialGraphViewModel.h b/SynapseEngine/EditorCore/ViewModels/MaterialWorkspace/MaterialGraph/MaterialGraphViewModel.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/MaterialGraph/MaterialGraphViewModel.h rename to SynapseEngine/EditorCore/ViewModels/MaterialWorkspace/MaterialGraph/MaterialGraphViewModel.h diff --git a/SynapseEngine/EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyIntent.cpp b/SynapseEngine/EditorCore/ViewModels/MaterialWorkspace/MaterialHierarchy/MaterialHierarchyIntent.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyIntent.cpp rename to SynapseEngine/EditorCore/ViewModels/MaterialWorkspace/MaterialHierarchy/MaterialHierarchyIntent.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyIntent.h b/SynapseEngine/EditorCore/ViewModels/MaterialWorkspace/MaterialHierarchy/MaterialHierarchyIntent.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyIntent.h rename to SynapseEngine/EditorCore/ViewModels/MaterialWorkspace/MaterialHierarchy/MaterialHierarchyIntent.h diff --git a/SynapseEngine/EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyState.cpp b/SynapseEngine/EditorCore/ViewModels/MaterialWorkspace/MaterialHierarchy/MaterialHierarchyState.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyState.cpp rename to SynapseEngine/EditorCore/ViewModels/MaterialWorkspace/MaterialHierarchy/MaterialHierarchyState.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyState.h b/SynapseEngine/EditorCore/ViewModels/MaterialWorkspace/MaterialHierarchy/MaterialHierarchyState.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyState.h rename to SynapseEngine/EditorCore/ViewModels/MaterialWorkspace/MaterialHierarchy/MaterialHierarchyState.h diff --git a/SynapseEngine/EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/MaterialWorkspace/MaterialHierarchy/MaterialHierarchyViewModel.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyViewModel.cpp rename to SynapseEngine/EditorCore/ViewModels/MaterialWorkspace/MaterialHierarchy/MaterialHierarchyViewModel.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyViewModel.h b/SynapseEngine/EditorCore/ViewModels/MaterialWorkspace/MaterialHierarchy/MaterialHierarchyViewModel.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/MaterialHierarchy/MaterialHierarchyViewModel.h rename to SynapseEngine/EditorCore/ViewModels/MaterialWorkspace/MaterialHierarchy/MaterialHierarchyViewModel.h diff --git a/SynapseEngine/EditorCore/ViewModels/MaterialProperties/MaterialPropertiesIntent.cpp b/SynapseEngine/EditorCore/ViewModels/MaterialWorkspace/MaterialProperties/MaterialPropertiesIntent.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/MaterialProperties/MaterialPropertiesIntent.cpp rename to SynapseEngine/EditorCore/ViewModels/MaterialWorkspace/MaterialProperties/MaterialPropertiesIntent.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/MaterialProperties/MaterialPropertiesIntent.h b/SynapseEngine/EditorCore/ViewModels/MaterialWorkspace/MaterialProperties/MaterialPropertiesIntent.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/MaterialProperties/MaterialPropertiesIntent.h rename to SynapseEngine/EditorCore/ViewModels/MaterialWorkspace/MaterialProperties/MaterialPropertiesIntent.h diff --git a/SynapseEngine/EditorCore/ViewModels/MaterialProperties/MaterialPropertiesState.cpp b/SynapseEngine/EditorCore/ViewModels/MaterialWorkspace/MaterialProperties/MaterialPropertiesState.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/MaterialProperties/MaterialPropertiesState.cpp rename to SynapseEngine/EditorCore/ViewModels/MaterialWorkspace/MaterialProperties/MaterialPropertiesState.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/MaterialProperties/MaterialPropertiesState.h b/SynapseEngine/EditorCore/ViewModels/MaterialWorkspace/MaterialProperties/MaterialPropertiesState.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/MaterialProperties/MaterialPropertiesState.h rename to SynapseEngine/EditorCore/ViewModels/MaterialWorkspace/MaterialProperties/MaterialPropertiesState.h diff --git a/SynapseEngine/EditorCore/ViewModels/MaterialProperties/MaterialPropertiesViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/MaterialWorkspace/MaterialProperties/MaterialPropertiesViewModel.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/MaterialProperties/MaterialPropertiesViewModel.cpp rename to SynapseEngine/EditorCore/ViewModels/MaterialWorkspace/MaterialProperties/MaterialPropertiesViewModel.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/MaterialProperties/MaterialPropertiesViewModel.h b/SynapseEngine/EditorCore/ViewModels/MaterialWorkspace/MaterialProperties/MaterialPropertiesViewModel.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/MaterialProperties/MaterialPropertiesViewModel.h rename to SynapseEngine/EditorCore/ViewModels/MaterialWorkspace/MaterialProperties/MaterialPropertiesViewModel.h diff --git a/SynapseEngine/EditorCore/ViewModels/MaterialViewport/MaterialViewportIntent.cpp b/SynapseEngine/EditorCore/ViewModels/MaterialWorkspace/MaterialViewport/MaterialViewportIntent.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/MaterialViewport/MaterialViewportIntent.cpp rename to SynapseEngine/EditorCore/ViewModels/MaterialWorkspace/MaterialViewport/MaterialViewportIntent.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/MaterialViewport/MaterialViewportIntent.h b/SynapseEngine/EditorCore/ViewModels/MaterialWorkspace/MaterialViewport/MaterialViewportIntent.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/MaterialViewport/MaterialViewportIntent.h rename to SynapseEngine/EditorCore/ViewModels/MaterialWorkspace/MaterialViewport/MaterialViewportIntent.h diff --git a/SynapseEngine/EditorCore/ViewModels/MaterialViewport/MaterialViewportState.cpp b/SynapseEngine/EditorCore/ViewModels/MaterialWorkspace/MaterialViewport/MaterialViewportState.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/MaterialViewport/MaterialViewportState.cpp rename to SynapseEngine/EditorCore/ViewModels/MaterialWorkspace/MaterialViewport/MaterialViewportState.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/MaterialViewport/MaterialViewportState.h b/SynapseEngine/EditorCore/ViewModels/MaterialWorkspace/MaterialViewport/MaterialViewportState.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/MaterialViewport/MaterialViewportState.h rename to SynapseEngine/EditorCore/ViewModels/MaterialWorkspace/MaterialViewport/MaterialViewportState.h diff --git a/SynapseEngine/EditorCore/ViewModels/MaterialViewport/MaterialViewportViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/MaterialWorkspace/MaterialViewport/MaterialViewportViewModel.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/MaterialViewport/MaterialViewportViewModel.cpp rename to SynapseEngine/EditorCore/ViewModels/MaterialWorkspace/MaterialViewport/MaterialViewportViewModel.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/MaterialViewport/MaterialViewportViewModel.h b/SynapseEngine/EditorCore/ViewModels/MaterialWorkspace/MaterialViewport/MaterialViewportViewModel.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/MaterialViewport/MaterialViewportViewModel.h rename to SynapseEngine/EditorCore/ViewModels/MaterialWorkspace/MaterialViewport/MaterialViewportViewModel.h diff --git a/SynapseEngine/EditorCore/ViewModels/ModelHierarchy/ModelHierarchyIntent.cpp b/SynapseEngine/EditorCore/ViewModels/ModelWorkspace/ModelHierarchy/ModelHierarchyIntent.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/ModelHierarchy/ModelHierarchyIntent.cpp rename to SynapseEngine/EditorCore/ViewModels/ModelWorkspace/ModelHierarchy/ModelHierarchyIntent.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/ModelHierarchy/ModelHierarchyIntent.h b/SynapseEngine/EditorCore/ViewModels/ModelWorkspace/ModelHierarchy/ModelHierarchyIntent.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/ModelHierarchy/ModelHierarchyIntent.h rename to SynapseEngine/EditorCore/ViewModels/ModelWorkspace/ModelHierarchy/ModelHierarchyIntent.h diff --git a/SynapseEngine/EditorCore/ViewModels/ModelHierarchy/ModelHierarchyState.cpp b/SynapseEngine/EditorCore/ViewModels/ModelWorkspace/ModelHierarchy/ModelHierarchyState.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/ModelHierarchy/ModelHierarchyState.cpp rename to SynapseEngine/EditorCore/ViewModels/ModelWorkspace/ModelHierarchy/ModelHierarchyState.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/ModelHierarchy/ModelHierarchyState.h b/SynapseEngine/EditorCore/ViewModels/ModelWorkspace/ModelHierarchy/ModelHierarchyState.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/ModelHierarchy/ModelHierarchyState.h rename to SynapseEngine/EditorCore/ViewModels/ModelWorkspace/ModelHierarchy/ModelHierarchyState.h diff --git a/SynapseEngine/EditorCore/ViewModels/ModelHierarchy/ModelHierarchyViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/ModelWorkspace/ModelHierarchy/ModelHierarchyViewModel.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/ModelHierarchy/ModelHierarchyViewModel.cpp rename to SynapseEngine/EditorCore/ViewModels/ModelWorkspace/ModelHierarchy/ModelHierarchyViewModel.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/ModelHierarchy/ModelHierarchyViewModel.h b/SynapseEngine/EditorCore/ViewModels/ModelWorkspace/ModelHierarchy/ModelHierarchyViewModel.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/ModelHierarchy/ModelHierarchyViewModel.h rename to SynapseEngine/EditorCore/ViewModels/ModelWorkspace/ModelHierarchy/ModelHierarchyViewModel.h diff --git a/SynapseEngine/EditorCore/ViewModels/ModelProperties/ModelPropertiesIntent.cpp b/SynapseEngine/EditorCore/ViewModels/ModelWorkspace/ModelProperties/ModelPropertiesIntent.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/ModelProperties/ModelPropertiesIntent.cpp rename to SynapseEngine/EditorCore/ViewModels/ModelWorkspace/ModelProperties/ModelPropertiesIntent.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/ModelProperties/ModelPropertiesIntent.h b/SynapseEngine/EditorCore/ViewModels/ModelWorkspace/ModelProperties/ModelPropertiesIntent.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/ModelProperties/ModelPropertiesIntent.h rename to SynapseEngine/EditorCore/ViewModels/ModelWorkspace/ModelProperties/ModelPropertiesIntent.h diff --git a/SynapseEngine/EditorCore/ViewModels/ModelProperties/ModelPropertiesState.cpp b/SynapseEngine/EditorCore/ViewModels/ModelWorkspace/ModelProperties/ModelPropertiesState.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/ModelProperties/ModelPropertiesState.cpp rename to SynapseEngine/EditorCore/ViewModels/ModelWorkspace/ModelProperties/ModelPropertiesState.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/ModelProperties/ModelPropertiesState.h b/SynapseEngine/EditorCore/ViewModels/ModelWorkspace/ModelProperties/ModelPropertiesState.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/ModelProperties/ModelPropertiesState.h rename to SynapseEngine/EditorCore/ViewModels/ModelWorkspace/ModelProperties/ModelPropertiesState.h diff --git a/SynapseEngine/EditorCore/ViewModels/ModelProperties/ModelPropertiesViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/ModelWorkspace/ModelProperties/ModelPropertiesViewModel.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/ModelProperties/ModelPropertiesViewModel.cpp rename to SynapseEngine/EditorCore/ViewModels/ModelWorkspace/ModelProperties/ModelPropertiesViewModel.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/ModelProperties/ModelPropertiesViewModel.h b/SynapseEngine/EditorCore/ViewModels/ModelWorkspace/ModelProperties/ModelPropertiesViewModel.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/ModelProperties/ModelPropertiesViewModel.h rename to SynapseEngine/EditorCore/ViewModels/ModelWorkspace/ModelProperties/ModelPropertiesViewModel.h diff --git a/SynapseEngine/EditorCore/ViewModels/ModelViewport/ModelViewportIntent.cpp b/SynapseEngine/EditorCore/ViewModels/ModelWorkspace/ModelViewport/ModelViewportIntent.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/ModelViewport/ModelViewportIntent.cpp rename to SynapseEngine/EditorCore/ViewModels/ModelWorkspace/ModelViewport/ModelViewportIntent.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/ModelViewport/ModelViewportIntent.h b/SynapseEngine/EditorCore/ViewModels/ModelWorkspace/ModelViewport/ModelViewportIntent.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/ModelViewport/ModelViewportIntent.h rename to SynapseEngine/EditorCore/ViewModels/ModelWorkspace/ModelViewport/ModelViewportIntent.h diff --git a/SynapseEngine/EditorCore/ViewModels/ModelViewport/ModelViewportState.cpp b/SynapseEngine/EditorCore/ViewModels/ModelWorkspace/ModelViewport/ModelViewportState.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/ModelViewport/ModelViewportState.cpp rename to SynapseEngine/EditorCore/ViewModels/ModelWorkspace/ModelViewport/ModelViewportState.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/ModelViewport/ModelViewportState.h b/SynapseEngine/EditorCore/ViewModels/ModelWorkspace/ModelViewport/ModelViewportState.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/ModelViewport/ModelViewportState.h rename to SynapseEngine/EditorCore/ViewModels/ModelWorkspace/ModelViewport/ModelViewportState.h diff --git a/SynapseEngine/EditorCore/ViewModels/ModelViewport/ModelViewportViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/ModelWorkspace/ModelViewport/ModelViewportViewModel.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/ModelViewport/ModelViewportViewModel.cpp rename to SynapseEngine/EditorCore/ViewModels/ModelWorkspace/ModelViewport/ModelViewportViewModel.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/ModelViewport/ModelViewportViewModel.h b/SynapseEngine/EditorCore/ViewModels/ModelWorkspace/ModelViewport/ModelViewportViewModel.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/ModelViewport/ModelViewportViewModel.h rename to SynapseEngine/EditorCore/ViewModels/ModelWorkspace/ModelViewport/ModelViewportViewModel.h diff --git a/SynapseEngine/EditorCore/ViewModels/Benchmark/BenchmarkIntent.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Benchmark/BenchmarkIntent.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Benchmark/BenchmarkIntent.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Benchmark/BenchmarkIntent.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Benchmark/BenchmarkIntent.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Benchmark/BenchmarkIntent.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Benchmark/BenchmarkIntent.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Benchmark/BenchmarkIntent.h diff --git a/SynapseEngine/EditorCore/ViewModels/Benchmark/BenchmarkState.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Benchmark/BenchmarkState.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Benchmark/BenchmarkState.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Benchmark/BenchmarkState.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Benchmark/BenchmarkState.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Benchmark/BenchmarkState.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Benchmark/BenchmarkState.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Benchmark/BenchmarkState.h diff --git a/SynapseEngine/EditorCore/ViewModels/Benchmark/BenchmarkViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Benchmark/BenchmarkViewModel.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Benchmark/BenchmarkViewModel.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Benchmark/BenchmarkViewModel.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Benchmark/BenchmarkViewModel.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Benchmark/BenchmarkViewModel.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Benchmark/BenchmarkViewModel.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Benchmark/BenchmarkViewModel.h diff --git a/SynapseEngine/EditorCore/ViewModels/Component/ComponentIntent.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/ComponentIntent.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/ComponentIntent.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/ComponentIntent.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Component/ComponentIntent.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/ComponentIntent.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/ComponentIntent.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/ComponentIntent.h diff --git a/SynapseEngine/EditorCore/ViewModels/Component/ComponentState.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/ComponentState.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/ComponentState.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/ComponentState.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Component/ComponentState.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/ComponentState.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/ComponentState.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/ComponentState.h diff --git a/SynapseEngine/EditorCore/ViewModels/Component/ComponentViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/ComponentViewModel.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/ComponentViewModel.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/ComponentViewModel.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Component/ComponentViewModel.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/ComponentViewModel.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/ComponentViewModel.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/ComponentViewModel.h diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraCommands.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Core/Camera/CameraCommands.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraCommands.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Core/Camera/CameraCommands.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraCommands.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Core/Camera/CameraCommands.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraCommands.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Core/Camera/CameraCommands.h diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraIntent.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Core/Camera/CameraIntent.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraIntent.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Core/Camera/CameraIntent.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraIntent.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Core/Camera/CameraIntent.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraIntent.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Core/Camera/CameraIntent.h diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraState.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Core/Camera/CameraState.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraState.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Core/Camera/CameraState.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraState.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Core/Camera/CameraState.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraState.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Core/Camera/CameraState.h diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Core/Camera/CameraViewModel.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraViewModel.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Core/Camera/CameraViewModel.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraViewModel.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Core/Camera/CameraViewModel.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Core/Camera/CameraViewModel.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Core/Camera/CameraViewModel.h diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Core/Tag/TagIntent.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Core/Tag/TagIntent.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Core/Tag/TagIntent.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Core/Tag/TagIntent.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Core/Tag/TagIntent.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Core/Tag/TagIntent.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Core/Tag/TagIntent.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Core/Tag/TagIntent.h diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Core/Tag/TagState.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Core/Tag/TagState.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Core/Tag/TagState.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Core/Tag/TagState.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Core/Tag/TagState.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Core/Tag/TagState.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Core/Tag/TagState.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Core/Tag/TagState.h diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Core/Tag/TagViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Core/Tag/TagViewModel.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Core/Tag/TagViewModel.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Core/Tag/TagViewModel.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Core/Tag/TagViewModel.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Core/Tag/TagViewModel.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Core/Tag/TagViewModel.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Core/Tag/TagViewModel.h diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Core/Transform/TransformCommands.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Core/Transform/TransformCommands.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Core/Transform/TransformCommands.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Core/Transform/TransformCommands.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Core/Transform/TransformCommands.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Core/Transform/TransformCommands.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Core/Transform/TransformCommands.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Core/Transform/TransformCommands.h diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Core/Transform/TransformIntent.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Core/Transform/TransformIntent.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Core/Transform/TransformIntent.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Core/Transform/TransformIntent.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Core/Transform/TransformIntent.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Core/Transform/TransformIntent.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Core/Transform/TransformIntent.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Core/Transform/TransformIntent.h diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Core/Transform/TransformState.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Core/Transform/TransformState.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Core/Transform/TransformState.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Core/Transform/TransformState.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Core/Transform/TransformState.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Core/Transform/TransformState.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Core/Transform/TransformState.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Core/Transform/TransformState.h diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Core/Transform/TransformViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Core/Transform/TransformViewModel.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Core/Transform/TransformViewModel.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Core/Transform/TransformViewModel.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Core/Transform/TransformViewModel.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Core/Transform/TransformViewModel.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Core/Transform/TransformViewModel.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Core/Transform/TransformViewModel.h diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Light/DirectionLight/DirectionLightCommands.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Light/DirectionLight/DirectionLightCommands.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Light/DirectionLight/DirectionLightCommands.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Light/DirectionLight/DirectionLightCommands.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Light/DirectionLight/DirectionLightCommands.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Light/DirectionLight/DirectionLightCommands.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Light/DirectionLight/DirectionLightCommands.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Light/DirectionLight/DirectionLightCommands.h diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Light/DirectionLight/DirectionLightIntent.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Light/DirectionLight/DirectionLightIntent.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Light/DirectionLight/DirectionLightIntent.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Light/DirectionLight/DirectionLightIntent.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Light/DirectionLight/DirectionLightIntent.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Light/DirectionLight/DirectionLightIntent.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Light/DirectionLight/DirectionLightIntent.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Light/DirectionLight/DirectionLightIntent.h diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Light/DirectionLight/DirectionLightState.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Light/DirectionLight/DirectionLightState.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Light/DirectionLight/DirectionLightState.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Light/DirectionLight/DirectionLightState.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Light/DirectionLight/DirectionLightState.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Light/DirectionLight/DirectionLightState.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Light/DirectionLight/DirectionLightState.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Light/DirectionLight/DirectionLightState.h diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Light/DirectionLight/DirectionLightViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Light/DirectionLight/DirectionLightViewModel.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Light/DirectionLight/DirectionLightViewModel.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Light/DirectionLight/DirectionLightViewModel.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Light/DirectionLight/DirectionLightViewModel.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Light/DirectionLight/DirectionLightViewModel.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Light/DirectionLight/DirectionLightViewModel.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Light/DirectionLight/DirectionLightViewModel.h diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Light/PointLight/PointLightCommands.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Light/PointLight/PointLightCommands.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Light/PointLight/PointLightCommands.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Light/PointLight/PointLightCommands.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Light/PointLight/PointLightCommands.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Light/PointLight/PointLightCommands.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Light/PointLight/PointLightCommands.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Light/PointLight/PointLightCommands.h diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Light/PointLight/PointLightIntent.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Light/PointLight/PointLightIntent.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Light/PointLight/PointLightIntent.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Light/PointLight/PointLightIntent.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Light/PointLight/PointLightIntent.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Light/PointLight/PointLightIntent.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Light/PointLight/PointLightIntent.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Light/PointLight/PointLightIntent.h diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Light/PointLight/PointLightState.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Light/PointLight/PointLightState.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Light/PointLight/PointLightState.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Light/PointLight/PointLightState.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Light/PointLight/PointLightState.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Light/PointLight/PointLightState.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Light/PointLight/PointLightState.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Light/PointLight/PointLightState.h diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Light/PointLight/PointLightViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Light/PointLight/PointLightViewModel.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Light/PointLight/PointLightViewModel.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Light/PointLight/PointLightViewModel.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Light/PointLight/PointLightViewModel.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Light/PointLight/PointLightViewModel.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Light/PointLight/PointLightViewModel.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Light/PointLight/PointLightViewModel.h diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Light/SpotLight/SpotLightCommands.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Light/SpotLight/SpotLightCommands.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Light/SpotLight/SpotLightCommands.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Light/SpotLight/SpotLightCommands.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Light/SpotLight/SpotLightCommands.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Light/SpotLight/SpotLightCommands.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Light/SpotLight/SpotLightCommands.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Light/SpotLight/SpotLightCommands.h diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Light/SpotLight/SpotLightIntent.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Light/SpotLight/SpotLightIntent.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Light/SpotLight/SpotLightIntent.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Light/SpotLight/SpotLightIntent.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Light/SpotLight/SpotLightIntent.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Light/SpotLight/SpotLightIntent.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Light/SpotLight/SpotLightIntent.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Light/SpotLight/SpotLightIntent.h diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Light/SpotLight/SpotLightState.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Light/SpotLight/SpotLightState.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Light/SpotLight/SpotLightState.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Light/SpotLight/SpotLightState.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Light/SpotLight/SpotLightState.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Light/SpotLight/SpotLightState.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Light/SpotLight/SpotLightState.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Light/SpotLight/SpotLightState.h diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Light/SpotLight/SpotLightViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Light/SpotLight/SpotLightViewModel.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Light/SpotLight/SpotLightViewModel.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Light/SpotLight/SpotLightViewModel.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Light/SpotLight/SpotLightViewModel.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Light/SpotLight/SpotLightViewModel.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Light/SpotLight/SpotLightViewModel.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Light/SpotLight/SpotLightViewModel.h diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/BoxCollider/BoxColliderCommands.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/BoxCollider/BoxColliderCommands.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Physics/BoxCollider/BoxColliderCommands.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/BoxCollider/BoxColliderCommands.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/BoxCollider/BoxColliderCommands.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/BoxCollider/BoxColliderCommands.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Physics/BoxCollider/BoxColliderCommands.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/BoxCollider/BoxColliderCommands.h diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/BoxCollider/BoxColliderIntent.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/BoxCollider/BoxColliderIntent.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Physics/BoxCollider/BoxColliderIntent.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/BoxCollider/BoxColliderIntent.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/BoxCollider/BoxColliderIntent.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/BoxCollider/BoxColliderIntent.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Physics/BoxCollider/BoxColliderIntent.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/BoxCollider/BoxColliderIntent.h diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/BoxCollider/BoxColliderState.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/BoxCollider/BoxColliderState.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Physics/BoxCollider/BoxColliderState.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/BoxCollider/BoxColliderState.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/BoxCollider/BoxColliderState.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/BoxCollider/BoxColliderState.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Physics/BoxCollider/BoxColliderState.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/BoxCollider/BoxColliderState.h diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/BoxCollider/BoxColliderViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/BoxCollider/BoxColliderViewModel.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Physics/BoxCollider/BoxColliderViewModel.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/BoxCollider/BoxColliderViewModel.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/BoxCollider/BoxColliderViewModel.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/BoxCollider/BoxColliderViewModel.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Physics/BoxCollider/BoxColliderViewModel.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/BoxCollider/BoxColliderViewModel.h diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/CapsuleCollider/CapsuleColliderCommands.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/CapsuleCollider/CapsuleColliderCommands.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Physics/CapsuleCollider/CapsuleColliderCommands.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/CapsuleCollider/CapsuleColliderCommands.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/CapsuleCollider/CapsuleColliderCommands.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/CapsuleCollider/CapsuleColliderCommands.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Physics/CapsuleCollider/CapsuleColliderCommands.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/CapsuleCollider/CapsuleColliderCommands.h diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/CapsuleCollider/CapsuleColliderIntent.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/CapsuleCollider/CapsuleColliderIntent.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Physics/CapsuleCollider/CapsuleColliderIntent.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/CapsuleCollider/CapsuleColliderIntent.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/CapsuleCollider/CapsuleColliderIntent.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/CapsuleCollider/CapsuleColliderIntent.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Physics/CapsuleCollider/CapsuleColliderIntent.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/CapsuleCollider/CapsuleColliderIntent.h diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/CapsuleCollider/CapsuleColliderState.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/CapsuleCollider/CapsuleColliderState.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Physics/CapsuleCollider/CapsuleColliderState.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/CapsuleCollider/CapsuleColliderState.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/CapsuleCollider/CapsuleColliderState.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/CapsuleCollider/CapsuleColliderState.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Physics/CapsuleCollider/CapsuleColliderState.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/CapsuleCollider/CapsuleColliderState.h diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/CapsuleCollider/CapsuleColliderViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/CapsuleCollider/CapsuleColliderViewModel.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Physics/CapsuleCollider/CapsuleColliderViewModel.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/CapsuleCollider/CapsuleColliderViewModel.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/CapsuleCollider/CapsuleColliderViewModel.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/CapsuleCollider/CapsuleColliderViewModel.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Physics/CapsuleCollider/CapsuleColliderViewModel.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/CapsuleCollider/CapsuleColliderViewModel.h diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/ConvexCollider/ConvexColliderCommands.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/ConvexCollider/ConvexColliderCommands.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Physics/ConvexCollider/ConvexColliderCommands.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/ConvexCollider/ConvexColliderCommands.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/ConvexCollider/ConvexColliderCommands.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/ConvexCollider/ConvexColliderCommands.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Physics/ConvexCollider/ConvexColliderCommands.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/ConvexCollider/ConvexColliderCommands.h diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/ConvexCollider/ConvexColliderIntent.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/ConvexCollider/ConvexColliderIntent.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Physics/ConvexCollider/ConvexColliderIntent.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/ConvexCollider/ConvexColliderIntent.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/ConvexCollider/ConvexColliderIntent.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/ConvexCollider/ConvexColliderIntent.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Physics/ConvexCollider/ConvexColliderIntent.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/ConvexCollider/ConvexColliderIntent.h diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/ConvexCollider/ConvexColliderState.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/ConvexCollider/ConvexColliderState.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Physics/ConvexCollider/ConvexColliderState.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/ConvexCollider/ConvexColliderState.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/ConvexCollider/ConvexColliderState.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/ConvexCollider/ConvexColliderState.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Physics/ConvexCollider/ConvexColliderState.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/ConvexCollider/ConvexColliderState.h diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/ConvexCollider/ConvexColliderViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/ConvexCollider/ConvexColliderViewModel.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Physics/ConvexCollider/ConvexColliderViewModel.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/ConvexCollider/ConvexColliderViewModel.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/ConvexCollider/ConvexColliderViewModel.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/ConvexCollider/ConvexColliderViewModel.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Physics/ConvexCollider/ConvexColliderViewModel.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/ConvexCollider/ConvexColliderViewModel.h diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/MeshCollider/MeshColliderCommands.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/MeshCollider/MeshColliderCommands.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Physics/MeshCollider/MeshColliderCommands.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/MeshCollider/MeshColliderCommands.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/MeshCollider/MeshColliderCommands.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/MeshCollider/MeshColliderCommands.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Physics/MeshCollider/MeshColliderCommands.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/MeshCollider/MeshColliderCommands.h diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/MeshCollider/MeshColliderIntent.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/MeshCollider/MeshColliderIntent.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Physics/MeshCollider/MeshColliderIntent.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/MeshCollider/MeshColliderIntent.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/MeshCollider/MeshColliderIntent.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/MeshCollider/MeshColliderIntent.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Physics/MeshCollider/MeshColliderIntent.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/MeshCollider/MeshColliderIntent.h diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/MeshCollider/MeshColliderState.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/MeshCollider/MeshColliderState.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Physics/MeshCollider/MeshColliderState.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/MeshCollider/MeshColliderState.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/MeshCollider/MeshColliderState.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/MeshCollider/MeshColliderState.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Physics/MeshCollider/MeshColliderState.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/MeshCollider/MeshColliderState.h diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/MeshCollider/MeshColliderViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/MeshCollider/MeshColliderViewModel.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Physics/MeshCollider/MeshColliderViewModel.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/MeshCollider/MeshColliderViewModel.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/MeshCollider/MeshColliderViewModel.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/MeshCollider/MeshColliderViewModel.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Physics/MeshCollider/MeshColliderViewModel.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/MeshCollider/MeshColliderViewModel.h diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/RigidBody/RigidBodyCommands.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/RigidBody/RigidBodyCommands.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Physics/RigidBody/RigidBodyCommands.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/RigidBody/RigidBodyCommands.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/RigidBody/RigidBodyCommands.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/RigidBody/RigidBodyCommands.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Physics/RigidBody/RigidBodyCommands.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/RigidBody/RigidBodyCommands.h diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/RigidBody/RigidBodyIntent.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/RigidBody/RigidBodyIntent.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Physics/RigidBody/RigidBodyIntent.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/RigidBody/RigidBodyIntent.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/RigidBody/RigidBodyIntent.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/RigidBody/RigidBodyIntent.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Physics/RigidBody/RigidBodyIntent.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/RigidBody/RigidBodyIntent.h diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/RigidBody/RigidBodyState.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/RigidBody/RigidBodyState.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Physics/RigidBody/RigidBodyState.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/RigidBody/RigidBodyState.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/RigidBody/RigidBodyState.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/RigidBody/RigidBodyState.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Physics/RigidBody/RigidBodyState.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/RigidBody/RigidBodyState.h diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/RigidBody/RigidBodyViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/RigidBody/RigidBodyViewModel.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Physics/RigidBody/RigidBodyViewModel.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/RigidBody/RigidBodyViewModel.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/RigidBody/RigidBodyViewModel.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/RigidBody/RigidBodyViewModel.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Physics/RigidBody/RigidBodyViewModel.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/RigidBody/RigidBodyViewModel.h diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/SphereCollider/SphereColliderCommands.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/SphereCollider/SphereColliderCommands.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Physics/SphereCollider/SphereColliderCommands.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/SphereCollider/SphereColliderCommands.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/SphereCollider/SphereColliderCommands.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/SphereCollider/SphereColliderCommands.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Physics/SphereCollider/SphereColliderCommands.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/SphereCollider/SphereColliderCommands.h diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/SphereCollider/SphereColliderIntent.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/SphereCollider/SphereColliderIntent.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Physics/SphereCollider/SphereColliderIntent.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/SphereCollider/SphereColliderIntent.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/SphereCollider/SphereColliderIntent.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/SphereCollider/SphereColliderIntent.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Physics/SphereCollider/SphereColliderIntent.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/SphereCollider/SphereColliderIntent.h diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/SphereCollider/SphereColliderState.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/SphereCollider/SphereColliderState.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Physics/SphereCollider/SphereColliderState.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/SphereCollider/SphereColliderState.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/SphereCollider/SphereColliderState.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/SphereCollider/SphereColliderState.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Physics/SphereCollider/SphereColliderState.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/SphereCollider/SphereColliderState.h diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/SphereCollider/SphereColliderViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/SphereCollider/SphereColliderViewModel.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Physics/SphereCollider/SphereColliderViewModel.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/SphereCollider/SphereColliderViewModel.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Physics/SphereCollider/SphereColliderViewModel.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/SphereCollider/SphereColliderViewModel.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Physics/SphereCollider/SphereColliderViewModel.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Physics/SphereCollider/SphereColliderViewModel.h diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Rendering/Animation/AnimationCommands.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Rendering/Animation/AnimationCommands.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Rendering/Animation/AnimationCommands.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Rendering/Animation/AnimationCommands.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Rendering/Animation/AnimationCommands.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Rendering/Animation/AnimationCommands.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Rendering/Animation/AnimationCommands.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Rendering/Animation/AnimationCommands.h diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Rendering/Animation/AnimationIntent.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Rendering/Animation/AnimationIntent.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Rendering/Animation/AnimationIntent.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Rendering/Animation/AnimationIntent.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Rendering/Animation/AnimationIntent.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Rendering/Animation/AnimationIntent.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Rendering/Animation/AnimationIntent.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Rendering/Animation/AnimationIntent.h diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Rendering/Animation/AnimationState.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Rendering/Animation/AnimationState.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Rendering/Animation/AnimationState.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Rendering/Animation/AnimationState.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Rendering/Animation/AnimationState.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Rendering/Animation/AnimationState.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Rendering/Animation/AnimationState.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Rendering/Animation/AnimationState.h diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Rendering/Animation/AnimationViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Rendering/Animation/AnimationViewModel.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Rendering/Animation/AnimationViewModel.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Rendering/Animation/AnimationViewModel.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Rendering/Animation/AnimationViewModel.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Rendering/Animation/AnimationViewModel.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Rendering/Animation/AnimationViewModel.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Rendering/Animation/AnimationViewModel.h diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Rendering/MaterialOverride/MaterialOverrideIntent.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Rendering/MaterialOverride/MaterialOverrideIntent.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Rendering/MaterialOverride/MaterialOverrideIntent.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Rendering/MaterialOverride/MaterialOverrideIntent.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Rendering/MaterialOverride/MaterialOverrideIntent.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Rendering/MaterialOverride/MaterialOverrideIntent.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Rendering/MaterialOverride/MaterialOverrideIntent.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Rendering/MaterialOverride/MaterialOverrideIntent.h diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Rendering/MaterialOverride/MaterialOverrideState.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Rendering/MaterialOverride/MaterialOverrideState.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Rendering/MaterialOverride/MaterialOverrideState.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Rendering/MaterialOverride/MaterialOverrideState.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Rendering/MaterialOverride/MaterialOverrideState.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Rendering/MaterialOverride/MaterialOverrideState.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Rendering/MaterialOverride/MaterialOverrideState.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Rendering/MaterialOverride/MaterialOverrideState.h diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Rendering/MaterialOverride/MaterialOverrideViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Rendering/MaterialOverride/MaterialOverrideViewModel.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Rendering/MaterialOverride/MaterialOverrideViewModel.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Rendering/MaterialOverride/MaterialOverrideViewModel.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Rendering/MaterialOverride/MaterialOverrideViewModel.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Rendering/MaterialOverride/MaterialOverrideViewModel.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Rendering/MaterialOverride/MaterialOverrideViewModel.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Rendering/MaterialOverride/MaterialOverrideViewModel.h diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Rendering/Model/ModelComponentCommands.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Rendering/Model/ModelComponentCommands.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Rendering/Model/ModelComponentCommands.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Rendering/Model/ModelComponentCommands.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Rendering/Model/ModelComponentCommands.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Rendering/Model/ModelComponentCommands.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Rendering/Model/ModelComponentCommands.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Rendering/Model/ModelComponentCommands.h diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Rendering/Model/ModelComponentIntent.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Rendering/Model/ModelComponentIntent.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Rendering/Model/ModelComponentIntent.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Rendering/Model/ModelComponentIntent.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Rendering/Model/ModelComponentIntent.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Rendering/Model/ModelComponentIntent.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Rendering/Model/ModelComponentIntent.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Rendering/Model/ModelComponentIntent.h diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Rendering/Model/ModelComponentState.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Rendering/Model/ModelComponentState.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Rendering/Model/ModelComponentState.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Rendering/Model/ModelComponentState.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Rendering/Model/ModelComponentState.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Rendering/Model/ModelComponentState.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Rendering/Model/ModelComponentState.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Rendering/Model/ModelComponentState.h diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Rendering/Model/ModelComponentViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Rendering/Model/ModelComponentViewModel.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Rendering/Model/ModelComponentViewModel.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Rendering/Model/ModelComponentViewModel.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Component/Rendering/Model/ModelComponentViewModel.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Rendering/Model/ModelComponentViewModel.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Component/Rendering/Model/ModelComponentViewModel.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Component/Rendering/Model/ModelComponentViewModel.h diff --git a/SynapseEngine/EditorCore/ViewModels/Hierarchy/HierarchyIntent.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Hierarchy/HierarchyIntent.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Hierarchy/HierarchyIntent.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Hierarchy/HierarchyIntent.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Hierarchy/HierarchyIntent.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Hierarchy/HierarchyIntent.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Hierarchy/HierarchyIntent.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Hierarchy/HierarchyIntent.h diff --git a/SynapseEngine/EditorCore/ViewModels/Hierarchy/HierarchyState.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Hierarchy/HierarchyState.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Hierarchy/HierarchyState.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Hierarchy/HierarchyState.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Hierarchy/HierarchyState.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Hierarchy/HierarchyState.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Hierarchy/HierarchyState.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Hierarchy/HierarchyState.h diff --git a/SynapseEngine/EditorCore/ViewModels/Hierarchy/HierarchyViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Hierarchy/HierarchyViewModel.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Hierarchy/HierarchyViewModel.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Hierarchy/HierarchyViewModel.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Hierarchy/HierarchyViewModel.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Hierarchy/HierarchyViewModel.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Hierarchy/HierarchyViewModel.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Hierarchy/HierarchyViewModel.h diff --git a/SynapseEngine/EditorCore/ViewModels/Logger/LoggerIntent.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Logger/LoggerIntent.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Logger/LoggerIntent.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Logger/LoggerIntent.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Logger/LoggerIntent.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Logger/LoggerIntent.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Logger/LoggerIntent.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Logger/LoggerIntent.h diff --git a/SynapseEngine/EditorCore/ViewModels/Logger/LoggerState.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Logger/LoggerState.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Logger/LoggerState.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Logger/LoggerState.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Logger/LoggerState.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Logger/LoggerState.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Logger/LoggerState.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Logger/LoggerState.h diff --git a/SynapseEngine/EditorCore/ViewModels/Logger/LoggerViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Logger/LoggerViewModel.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Logger/LoggerViewModel.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Logger/LoggerViewModel.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Logger/LoggerViewModel.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Logger/LoggerViewModel.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Logger/LoggerViewModel.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Logger/LoggerViewModel.h diff --git a/SynapseEngine/EditorCore/ViewModels/Settings/SettingsIntent.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Settings/SettingsIntent.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Settings/SettingsIntent.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Settings/SettingsIntent.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Settings/SettingsIntent.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Settings/SettingsIntent.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Settings/SettingsIntent.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Settings/SettingsIntent.h diff --git a/SynapseEngine/EditorCore/ViewModels/Settings/SettingsState.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Settings/SettingsState.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Settings/SettingsState.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Settings/SettingsState.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Settings/SettingsState.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Settings/SettingsState.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Settings/SettingsState.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Settings/SettingsState.h diff --git a/SynapseEngine/EditorCore/ViewModels/Settings/SettingsViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Settings/SettingsViewModel.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Settings/SettingsViewModel.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Settings/SettingsViewModel.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Settings/SettingsViewModel.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Settings/SettingsViewModel.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Settings/SettingsViewModel.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Settings/SettingsViewModel.h diff --git a/SynapseEngine/EditorCore/ViewModels/Viewport/ViewportIntent.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Viewport/ViewportIntent.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Viewport/ViewportIntent.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Viewport/ViewportIntent.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Viewport/ViewportIntent.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Viewport/ViewportIntent.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Viewport/ViewportIntent.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Viewport/ViewportIntent.h diff --git a/SynapseEngine/EditorCore/ViewModels/Viewport/ViewportState.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Viewport/ViewportState.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Viewport/ViewportState.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Viewport/ViewportState.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Viewport/ViewportState.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Viewport/ViewportState.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Viewport/ViewportState.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Viewport/ViewportState.h diff --git a/SynapseEngine/EditorCore/ViewModels/Viewport/ViewportViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Viewport/ViewportViewModel.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Viewport/ViewportViewModel.cpp rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Viewport/ViewportViewModel.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/Viewport/ViewportViewModel.h b/SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Viewport/ViewportViewModel.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/Viewport/ViewportViewModel.h rename to SynapseEngine/EditorCore/ViewModels/SceneWorkspace/Viewport/ViewportViewModel.h diff --git a/SynapseEngine/EditorCore/ViewModels/TextureGraph/TextureGraphIntent.cpp b/SynapseEngine/EditorCore/ViewModels/TextureWorkspace/TextureGraph/TextureGraphIntent.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/TextureGraph/TextureGraphIntent.cpp rename to SynapseEngine/EditorCore/ViewModels/TextureWorkspace/TextureGraph/TextureGraphIntent.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/TextureGraph/TextureGraphIntent.h b/SynapseEngine/EditorCore/ViewModels/TextureWorkspace/TextureGraph/TextureGraphIntent.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/TextureGraph/TextureGraphIntent.h rename to SynapseEngine/EditorCore/ViewModels/TextureWorkspace/TextureGraph/TextureGraphIntent.h diff --git a/SynapseEngine/EditorCore/ViewModels/TextureGraph/TextureGraphState.cpp b/SynapseEngine/EditorCore/ViewModels/TextureWorkspace/TextureGraph/TextureGraphState.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/TextureGraph/TextureGraphState.cpp rename to SynapseEngine/EditorCore/ViewModels/TextureWorkspace/TextureGraph/TextureGraphState.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/TextureGraph/TextureGraphState.h b/SynapseEngine/EditorCore/ViewModels/TextureWorkspace/TextureGraph/TextureGraphState.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/TextureGraph/TextureGraphState.h rename to SynapseEngine/EditorCore/ViewModels/TextureWorkspace/TextureGraph/TextureGraphState.h diff --git a/SynapseEngine/EditorCore/ViewModels/TextureGraph/TextureGraphViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/TextureWorkspace/TextureGraph/TextureGraphViewModel.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/TextureGraph/TextureGraphViewModel.cpp rename to SynapseEngine/EditorCore/ViewModels/TextureWorkspace/TextureGraph/TextureGraphViewModel.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/TextureGraph/TextureGraphViewModel.h b/SynapseEngine/EditorCore/ViewModels/TextureWorkspace/TextureGraph/TextureGraphViewModel.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/TextureGraph/TextureGraphViewModel.h rename to SynapseEngine/EditorCore/ViewModels/TextureWorkspace/TextureGraph/TextureGraphViewModel.h diff --git a/SynapseEngine/EditorCore/ViewModels/TextureHierarchy/TextureHierarchyIntent.cpp b/SynapseEngine/EditorCore/ViewModels/TextureWorkspace/TextureHierarchy/TextureHierarchyIntent.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/TextureHierarchy/TextureHierarchyIntent.cpp rename to SynapseEngine/EditorCore/ViewModels/TextureWorkspace/TextureHierarchy/TextureHierarchyIntent.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/TextureHierarchy/TextureHierarchyIntent.h b/SynapseEngine/EditorCore/ViewModels/TextureWorkspace/TextureHierarchy/TextureHierarchyIntent.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/TextureHierarchy/TextureHierarchyIntent.h rename to SynapseEngine/EditorCore/ViewModels/TextureWorkspace/TextureHierarchy/TextureHierarchyIntent.h diff --git a/SynapseEngine/EditorCore/ViewModels/TextureHierarchy/TextureHierarchyState.cpp b/SynapseEngine/EditorCore/ViewModels/TextureWorkspace/TextureHierarchy/TextureHierarchyState.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/TextureHierarchy/TextureHierarchyState.cpp rename to SynapseEngine/EditorCore/ViewModels/TextureWorkspace/TextureHierarchy/TextureHierarchyState.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/TextureHierarchy/TextureHierarchyState.h b/SynapseEngine/EditorCore/ViewModels/TextureWorkspace/TextureHierarchy/TextureHierarchyState.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/TextureHierarchy/TextureHierarchyState.h rename to SynapseEngine/EditorCore/ViewModels/TextureWorkspace/TextureHierarchy/TextureHierarchyState.h diff --git a/SynapseEngine/EditorCore/ViewModels/TextureHierarchy/TextureHierarchyViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/TextureWorkspace/TextureHierarchy/TextureHierarchyViewModel.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/TextureHierarchy/TextureHierarchyViewModel.cpp rename to SynapseEngine/EditorCore/ViewModels/TextureWorkspace/TextureHierarchy/TextureHierarchyViewModel.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/TextureHierarchy/TextureHierarchyViewModel.h b/SynapseEngine/EditorCore/ViewModels/TextureWorkspace/TextureHierarchy/TextureHierarchyViewModel.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/TextureHierarchy/TextureHierarchyViewModel.h rename to SynapseEngine/EditorCore/ViewModels/TextureWorkspace/TextureHierarchy/TextureHierarchyViewModel.h diff --git a/SynapseEngine/EditorCore/ViewModels/TextureProperties/TexturePropertiesIntent.cpp b/SynapseEngine/EditorCore/ViewModels/TextureWorkspace/TextureProperties/TexturePropertiesIntent.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/TextureProperties/TexturePropertiesIntent.cpp rename to SynapseEngine/EditorCore/ViewModels/TextureWorkspace/TextureProperties/TexturePropertiesIntent.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/TextureProperties/TexturePropertiesIntent.h b/SynapseEngine/EditorCore/ViewModels/TextureWorkspace/TextureProperties/TexturePropertiesIntent.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/TextureProperties/TexturePropertiesIntent.h rename to SynapseEngine/EditorCore/ViewModels/TextureWorkspace/TextureProperties/TexturePropertiesIntent.h diff --git a/SynapseEngine/EditorCore/ViewModels/TextureProperties/TexturePropertiesState.cpp b/SynapseEngine/EditorCore/ViewModels/TextureWorkspace/TextureProperties/TexturePropertiesState.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/TextureProperties/TexturePropertiesState.cpp rename to SynapseEngine/EditorCore/ViewModels/TextureWorkspace/TextureProperties/TexturePropertiesState.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/TextureProperties/TexturePropertiesState.h b/SynapseEngine/EditorCore/ViewModels/TextureWorkspace/TextureProperties/TexturePropertiesState.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/TextureProperties/TexturePropertiesState.h rename to SynapseEngine/EditorCore/ViewModels/TextureWorkspace/TextureProperties/TexturePropertiesState.h diff --git a/SynapseEngine/EditorCore/ViewModels/TextureProperties/TexturePropertiesViewModel.cpp b/SynapseEngine/EditorCore/ViewModels/TextureWorkspace/TextureProperties/TexturePropertiesViewModel.cpp similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/TextureProperties/TexturePropertiesViewModel.cpp rename to SynapseEngine/EditorCore/ViewModels/TextureWorkspace/TextureProperties/TexturePropertiesViewModel.cpp diff --git a/SynapseEngine/EditorCore/ViewModels/TextureProperties/TexturePropertiesViewModel.h b/SynapseEngine/EditorCore/ViewModels/TextureWorkspace/TextureProperties/TexturePropertiesViewModel.h similarity index 100% rename from SynapseEngine/EditorCore/ViewModels/TextureProperties/TexturePropertiesViewModel.h rename to SynapseEngine/EditorCore/ViewModels/TextureWorkspace/TextureProperties/TexturePropertiesViewModel.h diff --git a/SynapseEngine/Engine/Engine.cpp b/SynapseEngine/Engine/Engine.cpp index 4f81ff15..6b9bcb3c 100644 --- a/SynapseEngine/Engine/Engine.cpp +++ b/SynapseEngine/Engine/Engine.cpp @@ -130,7 +130,7 @@ namespace Syn _inputManager = std::make_unique(); ServiceLocator::ProvideInputManager(_inputManager.get()); - InitFrameContext(2); + InitFrameContext(1); InitLogger(); InitVulkan(params); InitTaskExecutor(); diff --git a/SynapseEngine/Engine/Registry/Pool/Storage/Core/SegmentedStorageImpl.h b/SynapseEngine/Engine/Registry/Pool/Storage/Core/SegmentedStorageImpl.h index 2fe26004..91235a51 100644 --- a/SynapseEngine/Engine/Registry/Pool/Storage/Core/SegmentedStorageImpl.h +++ b/SynapseEngine/Engine/Registry/Pool/Storage/Core/SegmentedStorageImpl.h @@ -200,10 +200,12 @@ namespace Syn { const DenseIndex swapTarget = static_cast(_staticEnd - 1); - Base::FlagIndexChanged(swapTarget); - Base::SwapBackend(index, swapTarget, onSwap); - - MarkStaticDirty(index); + if (index != swapTarget) + { + Base::FlagIndexChanged(swapTarget); + Base::SwapBackend(index, swapTarget, onSwap); + MarkStaticDirty(index); + } index = swapTarget; _staticEnd--; diff --git a/SynapseEngine/Engine/Scene/Settings/CullingSettings.cpp b/SynapseEngine/Engine/Scene/Settings/CullingSettings.cpp index 5b89c876..cb42d1cb 100644 --- a/SynapseEngine/Engine/Scene/Settings/CullingSettings.cpp +++ b/SynapseEngine/Engine/Scene/Settings/CullingSettings.cpp @@ -3,10 +3,10 @@ namespace Syn { CullingSettings::CullingSettings() - : geometryCullingDevice(CullingDeviceType::CPU) + : geometryCullingDevice(CullingDeviceType::GPU) , spotLightCullingDevice(CullingDeviceType::CPU) , pointLightCullingDevice(CullingDeviceType::CPU) - , directionLightShadowCullingDevice(CullingDeviceType::CPU) + , directionLightShadowCullingDevice(CullingDeviceType::GPU) , spotLightShadowCullingDevice(CullingDeviceType::CPU) , pointLightShadowCullingDevice(CullingDeviceType::CPU) , geometrySpatialAcceleration(SpatialAccelerationType::None) diff --git a/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json b/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json index e8d51261..945625f3 100644 --- a/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json +++ b/SynapseEngine/Engine/Scene/Source/Procedural/test_config.json @@ -9,17 +9,17 @@ "shared_material_count": 100 }, "entities": { - "animated_characters": 1000, - "static_geometry": 25000, + "animated_characters": 500, + "static_geometry": 50000, "physics_boxes": 500, "physics_spheres": 500, "physics_capsules": 500 }, "lights": { "directional_count": 1, - "point_count": 128, + "point_count": 64, "point_shadow_count": 32, - "spot_count": 128, + "spot_count": 64, "spot_shadow_count": 32 } } \ No newline at end of file diff --git a/SynapseEngine/Synapse_MaterialGraph.json b/SynapseEngine/Synapse_MaterialGraph.json index a72cb7f2..853f7d31 100644 --- a/SynapseEngine/Synapse_MaterialGraph.json +++ b/SynapseEngine/Synapse_MaterialGraph.json @@ -1 +1 @@ -{"nodes":{"node:1":{"location":{"x":0,"y":0}},"node:10":{"location":{"x":0,"y":0}},"node:12":{"location":{"x":0,"y":0}},"node:14":{"location":{"x":0,"y":0}},"node:16":{"location":{"x":0,"y":0}},"node:18":{"location":{"x":0,"y":0}},"node:20":{"location":{"x":0,"y":0}},"node:22":{"location":{"x":0,"y":0}},"node:24":{"location":{"x":0,"y":0}},"node:26":{"location":{"x":0,"y":0}},"node:28":{"location":{"x":0,"y":0}},"node:8":{"location":{"x":0,"y":0}}},"selection":null,"view":{"scroll":{"x":-731.410400390625,"y":-323.9666748046875},"visible_rect":{"max":{"x":1048.39306640625,"y":468.022216796875},"min":{"x":-487.60693359375,"y":-215.977783203125}},"zoom":1.5}} \ No newline at end of file +{"nodes":{"node:1":{"location":{"x":0,"y":0}},"node:4":{"location":{"x":0,"y":0}}},"selection":null,"view":{"scroll":{"x":-119.161598205566406,"y":0},"visible_rect":{"max":{"x":870.3157958984375,"y":1250},"min":{"x":-182.3157958984375,"y":0}},"zoom":0.653599977493286133}} \ No newline at end of file diff --git a/SynapseEngine/imgui.ini b/SynapseEngine/imgui.ini index d2d629f6..b3eaf243 100644 --- a/SynapseEngine/imgui.ini +++ b/SynapseEngine/imgui.ini @@ -4,14 +4,14 @@ Size=2304,1273 Collapsed=0 [Window][ Inspector] -Pos=1814,23 -Size=490,630 +Pos=1811,23 +Size=493,630 Collapsed=0 DockId=0x00000007,0 [Window][ Viewport] Pos=464,23 -Size=1348,842 +Size=1345,842 Collapsed=0 DockId=0x00000003,0 @@ -22,13 +22,13 @@ Collapsed=0 [Window][Content_Scene] Pos=464,867 -Size=1348,429 +Size=1345,429 Collapsed=0 DockId=0x00000004,0 [Window][ Graphics & Environment] -Pos=1814,655 -Size=490,641 +Pos=1811,655 +Size=493,641 Collapsed=0 DockId=0x00000008,0 @@ -46,7 +46,7 @@ DockId=0x0000000A,0 [Window][ Output Log] Pos=464,867 -Size=1348,429 +Size=1345,429 Collapsed=0 DockId=0x00000004,1 @@ -91,8 +91,8 @@ Size=2304,1273 Collapsed=0 [Window][Content_Model] -Pos=612,923 -Size=1293,373 +Pos=409,923 +Size=1496,373 Collapsed=0 DockId=0x00000014,0 @@ -134,11 +134,23 @@ Size=32,32 Collapsed=0 [Window][ Model Viewport] -Pos=612,23 -Size=1293,898 +Pos=409,23 +Size=1496,898 Collapsed=0 DockId=0x00000013,0 +[Window][ Models] +Pos=0,23 +Size=407,735 +Collapsed=0 +DockId=0x0000001B,0 + +[Window][ Internal Hierarchy] +Pos=0,760 +Size=407,536 +Collapsed=0 +DockId=0x0000001C,0 + [Table][0x3D1A1C69,2] RefScale=13 Column 0 Width=147 @@ -284,31 +296,35 @@ Column 0 Width=84 Column 1 Weight=1.0000 [Docking][Data] -DockSpace ID=0x1B7E3B2F Pos=128,95 Size=2304,1273 Split=X - DockNode ID=0x0000000F Parent=0x1B7E3B2F SizeRef=492,1273 Split=Y Selected=0xF8059156 - DockNode ID=0x0000000B Parent=0x0000000F SizeRef=542,655 Selected=0xF8059156 - DockNode ID=0x0000000C Parent=0x0000000F SizeRef=542,616 Selected=0x59113670 - DockNode ID=0x00000010 Parent=0x1B7E3B2F SizeRef=1810,1273 Split=Y - DockNode ID=0x0000000D Parent=0x00000010 SizeRef=1922,840 Split=X Selected=0x6F3619D2 - DockNode ID=0x00000011 Parent=0x0000000D SizeRef=1120,840 CentralNode=1 Selected=0x6F3619D2 - DockNode ID=0x00000012 Parent=0x0000000D SizeRef=688,840 Selected=0x814C861D - DockNode ID=0x0000000E Parent=0x00000010 SizeRef=1922,431 Selected=0x4B58EA58 -DockSpace ID=0x4713F8A8 Window=0xFBC4AE05 Pos=128,95 Size=2304,1273 Split=X Selected=0x1C1AF642 - DockNode ID=0x00000005 Parent=0x4713F8A8 SizeRef=462,1273 Split=Y Selected=0x02B8E2DB - DockNode ID=0x00000009 Parent=0x00000005 SizeRef=442,658 Selected=0xF995F4A5 - DockNode ID=0x0000000A Parent=0x00000005 SizeRef=442,613 Selected=0x02B8E2DB - DockNode ID=0x00000006 Parent=0x4713F8A8 SizeRef=1840,1273 Split=X - DockNode ID=0x00000001 Parent=0x00000006 SizeRef=1348,1273 Split=Y Selected=0x1C1AF642 - DockNode ID=0x00000003 Parent=0x00000001 SizeRef=1901,842 CentralNode=1 Selected=0x1C1AF642 - DockNode ID=0x00000004 Parent=0x00000001 SizeRef=1901,429 Selected=0xEF69BCF5 - DockNode ID=0x00000002 Parent=0x00000006 SizeRef=490,1273 Split=Y Selected=0x70CE1A73 - DockNode ID=0x00000007 Parent=0x00000002 SizeRef=401,630 Selected=0x70CE1A73 - DockNode ID=0x00000008 Parent=0x00000002 SizeRef=401,641 Selected=0x57A55B3F -DockSpace ID=0x64578384 Pos=128,95 Size=2304,1273 Split=X - DockNode ID=0x00000017 Parent=0x64578384 SizeRef=610,1273 Selected=0x7B451746 - DockNode ID=0x00000018 Parent=0x64578384 SizeRef=1692,1273 Split=X - DockNode ID=0x00000015 Parent=0x00000018 SizeRef=1293,1273 Split=Y - DockNode ID=0x00000013 Parent=0x00000015 SizeRef=2304,898 CentralNode=1 Selected=0x8D543EF8 - DockNode ID=0x00000014 Parent=0x00000015 SizeRef=2304,373 Selected=0xB3A6D868 - DockNode ID=0x00000016 Parent=0x00000018 SizeRef=397,1273 Selected=0x54E69AFF +DockSpace ID=0x1B7E3B2F Window=0xDCD75C92 Pos=128,95 Size=2304,1273 Split=X + DockNode ID=0x0000000F Parent=0x1B7E3B2F SizeRef=492,1273 Split=Y Selected=0xF8059156 + DockNode ID=0x0000000B Parent=0x0000000F SizeRef=542,655 Selected=0xF8059156 + DockNode ID=0x0000000C Parent=0x0000000F SizeRef=542,616 Selected=0x59113670 + DockNode ID=0x00000010 Parent=0x1B7E3B2F SizeRef=1810,1273 Split=Y + DockNode ID=0x0000000D Parent=0x00000010 SizeRef=1922,840 Split=X Selected=0x6F3619D2 + DockNode ID=0x00000011 Parent=0x0000000D SizeRef=1120,840 CentralNode=1 Selected=0x6F3619D2 + DockNode ID=0x00000012 Parent=0x0000000D SizeRef=688,840 Selected=0x814C861D + DockNode ID=0x0000000E Parent=0x00000010 SizeRef=1922,431 Selected=0x4B58EA58 +DockSpace ID=0x4713F8A8 Window=0xFBC4AE05 Pos=128,95 Size=2304,1273 Split=X Selected=0x1C1AF642 + DockNode ID=0x00000005 Parent=0x4713F8A8 SizeRef=462,1273 Split=Y Selected=0x02B8E2DB + DockNode ID=0x00000009 Parent=0x00000005 SizeRef=442,658 Selected=0xF995F4A5 + DockNode ID=0x0000000A Parent=0x00000005 SizeRef=442,613 Selected=0x02B8E2DB + DockNode ID=0x00000006 Parent=0x4713F8A8 SizeRef=1840,1273 Split=X + DockNode ID=0x00000001 Parent=0x00000006 SizeRef=1345,1273 Split=Y Selected=0x1C1AF642 + DockNode ID=0x00000003 Parent=0x00000001 SizeRef=1901,842 CentralNode=1 Selected=0x1C1AF642 + DockNode ID=0x00000004 Parent=0x00000001 SizeRef=1901,429 Selected=0x81DECE6A + DockNode ID=0x00000002 Parent=0x00000006 SizeRef=493,1273 Split=Y Selected=0x70CE1A73 + DockNode ID=0x00000007 Parent=0x00000002 SizeRef=401,630 Selected=0x70CE1A73 + DockNode ID=0x00000008 Parent=0x00000002 SizeRef=401,641 Selected=0x57A55B3F +DockSpace ID=0x64578384 Window=0xD880D529 Pos=128,95 Size=2304,1273 Split=X + DockNode ID=0x00000019 Parent=0x64578384 SizeRef=407,1273 Split=Y Selected=0x8F684317 + DockNode ID=0x0000001B Parent=0x00000019 SizeRef=407,735 Selected=0x8F684317 + DockNode ID=0x0000001C Parent=0x00000019 SizeRef=407,536 Selected=0x92BE6F94 + DockNode ID=0x0000001A Parent=0x64578384 SizeRef=1895,1273 Split=X + DockNode ID=0x00000017 Parent=0x0000001A SizeRef=610,1273 Selected=0x7B451746 + DockNode ID=0x00000018 Parent=0x0000001A SizeRef=1692,1273 Split=X + DockNode ID=0x00000015 Parent=0x00000018 SizeRef=1496,1273 Split=Y + DockNode ID=0x00000013 Parent=0x00000015 SizeRef=2304,898 CentralNode=1 Selected=0x8D543EF8 + DockNode ID=0x00000014 Parent=0x00000015 SizeRef=2304,373 Selected=0xB3A6D868 + DockNode ID=0x00000016 Parent=0x00000018 SizeRef=397,1273 Selected=0x54E69AFF From bb58d5cbaeee835aadebea2438ce609dd7c9a06c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20P=C3=A9ter?= Date: Mon, 13 Jul 2026 11:54:38 +0200 Subject: [PATCH 50/51] Updated Readme --- .gitignore | 2 +- Docs/Images/MaterialWorkspace.png | 3 +++ Docs/Images/ModelWorkspace.png | 3 +++ Docs/Images/SceneWorkspace.png | 3 +++ Docs/Images/TextureWorkspace.png | 3 +++ README.md | 13 +++++++++++-- 6 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 Docs/Images/MaterialWorkspace.png create mode 100644 Docs/Images/ModelWorkspace.png create mode 100644 Docs/Images/SceneWorkspace.png create mode 100644 Docs/Images/TextureWorkspace.png diff --git a/.gitignore b/.gitignore index ff33e148..a03c63d0 100644 --- a/.gitignore +++ b/.gitignore @@ -27,7 +27,7 @@ install_manifest.txt *.dae *.jpg -*.png +#*.png *.obj *.fbx *.mtl diff --git a/Docs/Images/MaterialWorkspace.png b/Docs/Images/MaterialWorkspace.png new file mode 100644 index 00000000..f96033b0 --- /dev/null +++ b/Docs/Images/MaterialWorkspace.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:954a4ebd0e2e0f38002ec71a8e33fac6d7c3cac22c6972b74af7c169b3933514 +size 428719 diff --git a/Docs/Images/ModelWorkspace.png b/Docs/Images/ModelWorkspace.png new file mode 100644 index 00000000..2be62db0 --- /dev/null +++ b/Docs/Images/ModelWorkspace.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9fb953f423539f5c7b0f05e7e0b82a0d2d5f6822e6f60d9c9ddeee21afef3f51 +size 858287 diff --git a/Docs/Images/SceneWorkspace.png b/Docs/Images/SceneWorkspace.png new file mode 100644 index 00000000..b37184de --- /dev/null +++ b/Docs/Images/SceneWorkspace.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5e20e689ac6eee3505472f5f6ff2b0a0b86d7f639fdaabfd3c4dde1d8f13291d +size 1512680 diff --git a/Docs/Images/TextureWorkspace.png b/Docs/Images/TextureWorkspace.png new file mode 100644 index 00000000..acee0d64 --- /dev/null +++ b/Docs/Images/TextureWorkspace.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ecc8fda13dfd5cdf4fe88e8aff33e469fc15592b3bb37da5449687d7d52faa09 +size 1501260 diff --git a/README.md b/README.md index 17ae3804..a3d8b13f 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,17 @@ Synapse Engine is a research-oriented real-time rendering engine focusing on eli The system integrates a segmented data-oriented ECS with a hierarchical GPU culling architecture, modern mesh shader support, and an advanced virtualized shadow map system for directional, spot, and point lights (supporting both CPU and GPU-driven paths). The engine now utilizes an MVI architecture for its tooling and features a robust cross-platform build system using `xmake` for Windows and Linux. -![Synapse Engine Editor UI](Docs/Images/EditorUI.png) -*Synapse Engine running with the integrated ImGui editor, showcasing the MVI architecture.* +![Synapse Engine Scene Workspace](Docs/Images/SceneWorkspace.png) +*Synapse Engine running with the integrated ImGui editor (Scene Workspace), showcasing the MVI architecture.* + +![Synapse Engine Model Workspace](Docs/Images/ModelWorkspace.png) +*Model Workspace* + +![Synapse Engine Material Workspace](Docs/Images/MaterialWorkspace.png) +*Material Workspace* + +![Synapse Engine Texture Workspace](Docs/Images/TextureWorkspace.png) +*Texture Workspace* ## Core Concepts From 9ad1f6534946237979d4f962277d0dce150a1652 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20P=C3=A9ter?= Date: Mon, 13 Jul 2026 11:54:50 +0200 Subject: [PATCH 51/51] Updated gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index a03c63d0..ff33e148 100644 --- a/.gitignore +++ b/.gitignore @@ -27,7 +27,7 @@ install_manifest.txt *.dae *.jpg -#*.png +*.png *.obj *.fbx *.mtl