diff --git a/editor/src/Actions/EditorActions.h b/editor/src/Actions/EditorActions.h index d4e0b50f..42cfa5e8 100644 --- a/editor/src/Actions/EditorActions.h +++ b/editor/src/Actions/EditorActions.h @@ -28,6 +28,10 @@ inline ActionBinding MakeSaveProjectAction(bool enabled = true) { return MakeAction("Save Project", nullptr, false, enabled); } +inline ActionBinding MakeProjectGraphicsSettingsAction(bool enabled = true) { + return MakeAction("Project Graphics Settings...", nullptr, false, enabled); +} + inline ActionBinding MakeRebuildScriptsAction(bool enabled = true) { return MakeAction("Rebuild Script Assemblies", nullptr, false, enabled); } diff --git a/editor/src/Actions/MainMenuActionRouter.h b/editor/src/Actions/MainMenuActionRouter.h index 0f013715..ae6143a1 100644 --- a/editor/src/Actions/MainMenuActionRouter.h +++ b/editor/src/Actions/MainMenuActionRouter.h @@ -126,6 +126,11 @@ inline void RequestAboutPopup(UI::DeferredPopupState& aboutPopup) { aboutPopup.RequestOpen(); } +inline void RequestProjectGraphicsSettingsPopup( + UI::DeferredPopupState& projectGraphicsSettingsPopup) { + projectGraphicsSettingsPopup.RequestOpen(); +} + inline void HandleMainMenuShortcuts(IEditorContext& context, const ShortcutContext& shortcutContext) { const bool canEditDocuments = IsDocumentEditingAllowed(context); const bool canPause = context.GetRuntimeMode() == EditorRuntimeMode::Play || context.GetRuntimeMode() == EditorRuntimeMode::Paused; @@ -162,6 +167,18 @@ inline void DrawFileMenuActions(IEditorContext& context) { DrawMenuAction(MakeExitAction(), [&]() { RequestEditorExit(context); }); } +inline void DrawEditMenuActions( + IEditorContext& context, + UI::DeferredPopupState& projectGraphicsSettingsPopup) { + DrawEditActions(context); + DrawMenuSeparator(); + DrawMenuAction( + MakeProjectGraphicsSettingsAction(!context.GetProjectPath().empty()), + [&]() { + RequestProjectGraphicsSettingsPopup(projectGraphicsSettingsPopup); + }); +} + inline void DrawRunMenuActions(IEditorContext& context) { const bool canPause = context.GetRuntimeMode() == EditorRuntimeMode::Play || context.GetRuntimeMode() == EditorRuntimeMode::Paused; const bool canStep = context.GetRuntimeMode() == EditorRuntimeMode::Paused; @@ -207,7 +224,10 @@ inline void HandleMenuBarShortcuts(IEditorContext& context) { HandleMainMenuShortcuts(context, GlobalShortcutContext()); } -inline void DrawMainMenuBar(IEditorContext& context, UI::DeferredPopupState& aboutPopup) { +inline void DrawMainMenuBar( + IEditorContext& context, + UI::DeferredPopupState& aboutPopup, + UI::DeferredPopupState& projectGraphicsSettingsPopup) { if (!ImGui::BeginMainMenuBar()) { return; } @@ -216,7 +236,7 @@ inline void DrawMainMenuBar(IEditorContext& context, UI::DeferredPopupState& abo DrawFileMenuActions(context); }); UI::DrawMenuScope("Edit", [&]() { - DrawEditActions(context); + DrawEditMenuActions(context, projectGraphicsSettingsPopup); }); UI::DrawMenuScope("Assets", [&]() { DrawAssetsMenuActions(context); @@ -236,8 +256,14 @@ inline void DrawMainMenuBar(IEditorContext& context, UI::DeferredPopupState& abo ImGui::EndMainMenuBar(); } -inline void DrawMainMenuOverlays(IEditorContext* context, UI::DeferredPopupState& aboutPopup) { +inline void DrawMainMenuOverlays( + IEditorContext* context, + UI::DeferredPopupState& aboutPopup, + UI::DeferredPopupState& projectGraphicsSettingsPopup) { UI::DrawEditorAboutDialog(context, aboutPopup); + UI::DrawProjectGraphicsSettingsDialog( + context, + projectGraphicsSettingsPopup); } } // namespace Actions diff --git a/editor/src/Application.cpp b/editor/src/Application.cpp index adeb0a41..9e5db9fe 100644 --- a/editor/src/Application.cpp +++ b/editor/src/Application.cpp @@ -9,6 +9,8 @@ #include "Core/EventBus.h" #include "Scripting/EditorScriptAssemblyBuilder.h" #include "UI/BuiltInIcons.h" +#include "Utils/ProjectFileUtils.h" +#include "Utils/ProjectGraphicsSettings.h" #include "Platform/Win32Utf8.h" #include "Platform/WindowsProcessDiagnostics.h" #include @@ -63,6 +65,7 @@ void Application::InitializeScriptingRuntime(const std::string& projectPath) { ". Script class discovery is disabled until the managed assemblies are built."; logger.Warning(Debug::LogCategory::Scripting, m_scriptRuntimeStatus.statusMessage.c_str()); ::XCEngine::Scripting::ScriptEngine::Get().SetRuntime(nullptr); + ProjectGraphicsSettings::ApplyCurrentSelection(projectPath); return; } @@ -72,6 +75,7 @@ void Application::InitializeScriptingRuntime(const std::string& projectPath) { "Failed to initialize editor script runtime: " + runtime->GetLastError(); logger.Warning(Debug::LogCategory::Scripting, m_scriptRuntimeStatus.statusMessage.c_str()); ::XCEngine::Scripting::ScriptEngine::Get().SetRuntime(nullptr); + ProjectGraphicsSettings::ApplyCurrentSelection(projectPath); return; } @@ -79,11 +83,13 @@ void Application::InitializeScriptingRuntime(const std::string& projectPath) { m_scriptRuntimeStatus.runtimeLoaded = true; m_scriptRuntime = std::move(runtime); logger.Info(Debug::LogCategory::Scripting, "Editor script runtime initialized."); + ProjectGraphicsSettings::ApplyCurrentSelection(projectPath); #else (void)projectPath; m_scriptRuntimeStatus.backendEnabled = false; m_scriptRuntimeStatus.statusMessage = "This editor build does not include Mono scripting support."; ::XCEngine::Scripting::ScriptEngine::Get().SetRuntime(nullptr); + ProjectGraphicsSettings::ApplyCurrentSelection(projectPath); #endif } @@ -255,7 +261,7 @@ void Application::ShutdownEditorContext() { } void Application::RenderEditorFrame() { - static constexpr float kClearColor[4] = { 0.22f, 0.22f, 0.22f, 1.0f }; + static constexpr float kClearColor[4] = { 0.05f, 0.05f, 0.05f, 1.0f }; if (!m_windowRenderer.BeginFrame()) { return; } diff --git a/editor/src/Commands/ProjectCommands.h b/editor/src/Commands/ProjectCommands.h index af81f987..d2f5ee11 100644 --- a/editor/src/Commands/ProjectCommands.h +++ b/editor/src/Commands/ProjectCommands.h @@ -11,12 +11,14 @@ #include "SceneCommands.h" #include "Utils/FileDialogUtils.h" #include "Utils/ProjectFileUtils.h" +#include "Utils/ProjectGraphicsSettings.h" #include "Utils/UndoUtils.h" #include #include #include #include +#include #include #include @@ -445,6 +447,8 @@ inline bool EnsureProjectStructure(const std::string& projectPath) { std::error_code ec; fs::create_directories(fs::path(projectPath) / "Assets" / "Scenes", ec); ec.clear(); + fs::create_directories(fs::path(projectPath) / "ProjectSettings", ec); + ec.clear(); fs::create_directories(fs::path(projectPath) / ".xceditor", ec); return true; } @@ -474,6 +478,56 @@ inline bool SaveProjectDescriptor(IEditorContext& context) { BuildProjectDescriptor(context)); } +inline bool SaveProjectGraphicsSettings(IEditorContext& context) { + return ProjectGraphicsSettings::SaveCurrentSelection( + context.GetProjectPath()); +} + +inline bool SetProjectRenderPipelineAssetSelection( + IEditorContext& context, + const ::XCEngine::Scripting::ScriptClassDescriptor* descriptor) { + const std::string& projectPath = context.GetProjectPath(); + if (projectPath.empty()) { + return false; + } + + auto& state = ::XCEngine::Rendering::GetGraphicsSettingsState(); + const auto previousDescriptor = state.GetRenderPipelineAssetDescriptor(); + + const bool hasCurrentSelection = previousDescriptor.IsValid(); + const bool selectingSameDescriptor = + descriptor != nullptr && + previousDescriptor.assemblyName == descriptor->assemblyName && + previousDescriptor.namespaceName == descriptor->namespaceName && + previousDescriptor.className == descriptor->className; + if ((descriptor == nullptr && !hasCurrentSelection) || + selectingSameDescriptor) { + return false; + } + + if (descriptor == nullptr) { + state.ClearRenderPipelineAssetDescriptor(); + } else { + ::XCEngine::Rendering::Pipelines::ManagedRenderPipelineAssetDescriptor + nativeDescriptor = {}; + nativeDescriptor.assemblyName = descriptor->assemblyName; + nativeDescriptor.namespaceName = descriptor->namespaceName; + nativeDescriptor.className = descriptor->className; + state.SetRenderPipelineAssetDescriptor(nativeDescriptor); + } + + if (SaveProjectGraphicsSettings(context)) { + return true; + } + + if (previousDescriptor.IsValid()) { + state.SetRenderPipelineAssetDescriptor(previousDescriptor); + } else { + state.ClearRenderPipelineAssetDescriptor(); + } + return false; +} + inline bool SaveProject(IEditorContext& context) { if (!IsProjectDocumentEditingAllowed(context)) { return false; @@ -489,7 +543,9 @@ inline bool SaveProject(IEditorContext& context) { context.GetProjectManager().RefreshCurrentFolder(); Application::Get().SaveProjectState(); - return SaveProjectDescriptor(context); + const bool descriptorSaved = SaveProjectDescriptor(context); + const bool graphicsSettingsSaved = SaveProjectGraphicsSettings(context); + return descriptorSaved && graphicsSettingsSaved; } inline bool CanRebuildScriptAssemblies(const IEditorContext& context) { @@ -671,6 +727,7 @@ inline bool SwitchProject(IEditorContext& context, const std::string& projectPat if (loaded) { SaveProjectDescriptor(context); + SaveProjectGraphicsSettings(context); } return loaded; diff --git a/editor/src/ComponentEditors/LightComponentEditor.h b/editor/src/ComponentEditors/LightComponentEditor.h index 9f545226..f5d0bcc5 100644 --- a/editor/src/ComponentEditors/LightComponentEditor.h +++ b/editor/src/ComponentEditors/LightComponentEditor.h @@ -93,6 +93,65 @@ public: light->SetCastsShadows(castsShadows); }); + if (light->GetLightType() == ::XCEngine::Components::LightType::Directional && + light->GetCastsShadows()) { + bool overrideShadowSettings = light->GetOverridesDirectionalShadowSettings(); + changed |= UI::ApplyPropertyChange( + UI::DrawPropertyBool("Override Shadow Params", overrideShadowSettings), + undoManager, + kUndoLabel, + [&]() { + light->SetOverridesDirectionalShadowSettings(overrideShadowSettings); + }); + + if (overrideShadowSettings) { + float receiverDepthBias = light->GetDirectionalShadowReceiverDepthBias(); + changed |= UI::ApplyPropertyChange( + UI::DrawPropertyFloat("Receiver Depth Bias", receiverDepthBias, 0.0001f, 0.0f, 0.0f, "%.4f"), + undoManager, + kUndoLabel, + [&]() { + light->SetDirectionalShadowReceiverDepthBias(receiverDepthBias); + }); + + float normalBiasScale = light->GetDirectionalShadowNormalBiasScale(); + changed |= UI::ApplyPropertyChange( + UI::DrawPropertyFloat("Normal Bias Scale", normalBiasScale, 0.05f, 0.0f, 0.0f, "%.2f"), + undoManager, + kUndoLabel, + [&]() { + light->SetDirectionalShadowNormalBiasScale(normalBiasScale); + }); + + float shadowStrength = light->GetDirectionalShadowStrength(); + changed |= UI::ApplyPropertyChange( + UI::DrawPropertySliderFloat("Shadow Strength", shadowStrength, 0.0f, 1.0f, "%.2f"), + undoManager, + kUndoLabel, + [&]() { + light->SetDirectionalShadowStrength(shadowStrength); + }); + + float depthBiasFactor = light->GetDirectionalShadowDepthBiasFactor(); + changed |= UI::ApplyPropertyChange( + UI::DrawPropertyFloat("Depth Bias Factor", depthBiasFactor, 0.1f, 0.0f, 0.0f, "%.2f"), + undoManager, + kUndoLabel, + [&]() { + light->SetDirectionalShadowDepthBiasFactor(depthBiasFactor); + }); + + int depthBiasUnits = light->GetDirectionalShadowDepthBiasUnits(); + changed |= UI::ApplyPropertyChange( + UI::DrawPropertyInt("Depth Bias Units", depthBiasUnits, 1, 0), + undoManager, + kUndoLabel, + [&]() { + light->SetDirectionalShadowDepthBiasUnits(depthBiasUnits); + }); + } + } + return changed; } diff --git a/editor/src/UI/BaseTheme.h b/editor/src/UI/BaseTheme.h index 2a28d062..87347049 100644 --- a/editor/src/UI/BaseTheme.h +++ b/editor/src/UI/BaseTheme.h @@ -10,33 +10,33 @@ namespace Editor { namespace UI { inline void ApplyBaseThemeColors(ImVec4* colors) { - colors[ImGuiCol_Text] = ImVec4(0.88f, 0.88f, 0.88f, 1.00f); - colors[ImGuiCol_TextDisabled] = ImVec4(0.58f, 0.58f, 0.58f, 1.00f); - colors[ImGuiCol_WindowBg] = ImVec4(0.22f, 0.22f, 0.22f, 1.00f); - colors[ImGuiCol_ChildBg] = ImVec4(0.22f, 0.22f, 0.22f, 1.00f); - colors[ImGuiCol_PopupBg] = ImVec4(0.17f, 0.17f, 0.17f, 0.98f); - colors[ImGuiCol_Border] = ImVec4(0.14f, 0.14f, 0.14f, 1.00f); + colors[ImGuiCol_Text] = ImVec4(0.92f, 0.92f, 0.92f, 1.00f); + colors[ImGuiCol_TextDisabled] = ImVec4(0.46f, 0.46f, 0.46f, 1.00f); + colors[ImGuiCol_WindowBg] = ImVec4(0.10f, 0.10f, 0.10f, 1.00f); + colors[ImGuiCol_ChildBg] = ImVec4(0.10f, 0.10f, 0.10f, 1.00f); + colors[ImGuiCol_PopupBg] = ImVec4(0.10f, 0.10f, 0.10f, 0.98f); + colors[ImGuiCol_Border] = ImVec4(0.15f, 0.15f, 0.15f, 1.00f); colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); - colors[ImGuiCol_FrameBg] = ImVec4(0.18f, 0.18f, 0.18f, 1.00f); - colors[ImGuiCol_FrameBgHovered] = ImVec4(0.21f, 0.21f, 0.21f, 1.00f); - colors[ImGuiCol_FrameBgActive] = ImVec4(0.24f, 0.24f, 0.24f, 1.00f); - colors[ImGuiCol_TitleBg] = ImVec4(0.18f, 0.18f, 0.18f, 1.00f); - colors[ImGuiCol_TitleBgActive] = ImVec4(0.20f, 0.20f, 0.20f, 1.00f); - colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.18f, 0.18f, 0.18f, 1.00f); - colors[ImGuiCol_MenuBarBg] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); - colors[ImGuiCol_ScrollbarBg] = ImVec4(0.16f, 0.16f, 0.16f, 1.00f); - colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.30f, 0.30f, 0.30f, 1.00f); - colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.36f, 0.36f, 0.36f, 1.00f); - colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.41f, 0.41f, 0.41f, 1.00f); - colors[ImGuiCol_CheckMark] = ImVec4(0.72f, 0.72f, 0.72f, 1.00f); - colors[ImGuiCol_SliderGrab] = ImVec4(0.44f, 0.44f, 0.44f, 1.00f); - colors[ImGuiCol_SliderGrabActive] = ImVec4(0.54f, 0.54f, 0.54f, 1.00f); - colors[ImGuiCol_Button] = ImVec4(0.24f, 0.24f, 0.24f, 1.00f); - colors[ImGuiCol_ButtonHovered] = ImVec4(0.28f, 0.28f, 0.28f, 1.00f); - colors[ImGuiCol_ButtonActive] = ImVec4(0.31f, 0.31f, 0.31f, 1.00f); - colors[ImGuiCol_Header] = ImVec4(0.24f, 0.24f, 0.24f, 1.00f); - colors[ImGuiCol_HeaderHovered] = ImVec4(0.27f, 0.27f, 0.27f, 1.00f); - colors[ImGuiCol_HeaderActive] = ImVec4(0.30f, 0.30f, 0.30f, 1.00f); + colors[ImGuiCol_FrameBg] = ImVec4(0.11f, 0.11f, 0.11f, 1.00f); + colors[ImGuiCol_FrameBgHovered] = ImVec4(0.14f, 0.14f, 0.14f, 1.00f); + colors[ImGuiCol_FrameBgActive] = ImVec4(0.17f, 0.17f, 0.17f, 1.00f); + colors[ImGuiCol_TitleBg] = ImVec4(0.10f, 0.10f, 0.10f, 1.00f); + colors[ImGuiCol_TitleBgActive] = ImVec4(0.11f, 0.11f, 0.11f, 1.00f); + colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.10f, 0.10f, 0.10f, 1.00f); + colors[ImGuiCol_MenuBarBg] = ImVec4(0.10f, 0.10f, 0.10f, 1.00f); + colors[ImGuiCol_ScrollbarBg] = ImVec4(0.08f, 0.08f, 0.08f, 1.00f); + colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.10f, 0.10f, 0.10f, 1.00f); + colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.12f, 0.12f, 0.12f, 1.00f); + colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.14f, 0.14f, 0.14f, 1.00f); + colors[ImGuiCol_CheckMark] = ImVec4(0.84f, 0.84f, 0.84f, 1.00f); + colors[ImGuiCol_SliderGrab] = ImVec4(0.24f, 0.24f, 0.24f, 1.00f); + colors[ImGuiCol_SliderGrabActive] = ImVec4(0.30f, 0.30f, 0.30f, 1.00f); + colors[ImGuiCol_Button] = ImVec4(0.11f, 0.11f, 0.11f, 1.00f); + colors[ImGuiCol_ButtonHovered] = ImVec4(0.13f, 0.13f, 0.13f, 1.00f); + colors[ImGuiCol_ButtonActive] = ImVec4(0.15f, 0.15f, 0.15f, 1.00f); + colors[ImGuiCol_Header] = ImVec4(0.11f, 0.11f, 0.11f, 1.00f); + colors[ImGuiCol_HeaderHovered] = ImVec4(0.14f, 0.14f, 0.14f, 1.00f); + colors[ImGuiCol_HeaderActive] = ImVec4(0.17f, 0.17f, 0.17f, 1.00f); ApplySplitterThemeColors(colors); colors[ImGuiCol_Tab] = DockTabColor(); colors[ImGuiCol_TabHovered] = DockTabHoveredColor(); @@ -45,23 +45,23 @@ inline void ApplyBaseThemeColors(ImVec4* colors) { colors[ImGuiCol_TabDimmed] = DockTabDimmedColor(); colors[ImGuiCol_TabDimmedSelected] = DockTabDimmedSelectedColor(); colors[ImGuiCol_TabDimmedSelectedOverline] = DockTabDimmedSelectedOverlineColor(); - colors[ImGuiCol_DockingPreview] = ImVec4(0.58f, 0.58f, 0.58f, 0.22f); - colors[ImGuiCol_DockingEmptyBg] = ImVec4(0.18f, 0.18f, 0.18f, 1.00f); + colors[ImGuiCol_DockingPreview] = ImVec4(0.92f, 0.92f, 0.92f, 0.14f); + colors[ImGuiCol_DockingEmptyBg] = ImVec4(0.10f, 0.10f, 0.10f, 1.00f); colors[ImGuiCol_PlotLines] = ImVec4(0.61f, 0.61f, 0.61f, 1.00f); colors[ImGuiCol_PlotLinesHovered] = ImVec4(0.76f, 0.76f, 0.76f, 1.00f); colors[ImGuiCol_PlotHistogram] = ImVec4(0.56f, 0.56f, 0.56f, 1.00f); colors[ImGuiCol_PlotHistogramHovered] = ImVec4(0.66f, 0.66f, 0.66f, 1.00f); - colors[ImGuiCol_TableHeaderBg] = ImVec4(0.21f, 0.21f, 0.21f, 1.00f); - colors[ImGuiCol_TableBorderStrong] = ImVec4(0.14f, 0.14f, 0.14f, 1.00f); - colors[ImGuiCol_TableBorderLight] = ImVec4(0.18f, 0.18f, 0.18f, 1.00f); + colors[ImGuiCol_TableHeaderBg] = ImVec4(0.11f, 0.11f, 0.11f, 1.00f); + colors[ImGuiCol_TableBorderStrong] = ImVec4(0.15f, 0.15f, 0.15f, 1.00f); + colors[ImGuiCol_TableBorderLight] = ImVec4(0.11f, 0.11f, 0.11f, 1.00f); colors[ImGuiCol_TableRowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); - colors[ImGuiCol_TableRowBgAlt] = ImVec4(1.00f, 1.00f, 1.00f, 0.03f); - colors[ImGuiCol_TextSelectedBg] = ImVec4(0.48f, 0.48f, 0.48f, 0.28f); - colors[ImGuiCol_DragDropTarget] = ImVec4(0.62f, 0.62f, 0.62f, 0.72f); - colors[ImGuiCol_NavHighlight] = ImVec4(0.62f, 0.62f, 0.62f, 0.52f); + colors[ImGuiCol_TableRowBgAlt] = ImVec4(1.00f, 1.00f, 1.00f, 0.02f); + colors[ImGuiCol_TextSelectedBg] = ImVec4(0.92f, 0.92f, 0.92f, 0.16f); + colors[ImGuiCol_DragDropTarget] = ImVec4(0.88f, 0.88f, 0.88f, 0.50f); + colors[ImGuiCol_NavHighlight] = ImVec4(0.72f, 0.72f, 0.72f, 0.34f); colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f); - colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f); - colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.35f); + colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.45f); + colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.62f); } inline void ApplyBaseThemeMetrics(ImGuiStyle& style) { diff --git a/editor/src/UI/ColorPicker.h b/editor/src/UI/ColorPicker.h index 2567c908..a6ad1a02 100644 --- a/editor/src/UI/ColorPicker.h +++ b/editor/src/UI/ColorPicker.h @@ -130,23 +130,23 @@ inline ImVec2 ColorPickerPopupSize(bool includeAlpha) { } inline ImVec4 ColorPickerHeaderColor() { - return ImVec4(0.43f, 0.24f, 0.05f, 1.0f); + return ToolbarBackgroundColor(); } inline ImVec4 ColorPickerCloseButtonColor() { - return ImVec4(0.76f, 0.35f, 0.34f, 1.0f); + return ImVec4(0.20f, 0.10f, 0.10f, 1.0f); } inline ImVec4 ColorPickerCloseButtonHoveredColor() { - return ImVec4(0.82f, 0.40f, 0.39f, 1.0f); + return ImVec4(0.28f, 0.14f, 0.14f, 1.0f); } inline ImVec4 ColorPickerBodyColor() { - return ImVec4(0.24f, 0.24f, 0.24f, 1.0f); + return PopupBackgroundColor(); } inline ImVec4 ColorPickerBorderColor() { - return ImVec4(0.15f, 0.15f, 0.15f, 1.0f); + return PopupBorderColor(); } inline float ColorPickerPopupRounding() { @@ -154,19 +154,19 @@ inline float ColorPickerPopupRounding() { } inline ImVec4 ColorPickerPreviewBorderColor() { - return ImVec4(0.12f, 0.12f, 0.12f, 1.0f); + return PopupBorderColor(); } inline ImVec4 ColorPickerPreviewBaseColor() { - return ImVec4(0.19f, 0.19f, 0.19f, 1.0f); + return ImVec4(0.12f, 0.12f, 0.12f, 1.0f); } inline ImVec4 CheckerDarkColor() { - return ImVec4(0.55f, 0.55f, 0.55f, 1.0f); + return ImVec4(0.14f, 0.14f, 0.14f, 1.0f); } inline ImVec4 CheckerLightColor() { - return ImVec4(0.84f, 0.84f, 0.84f, 1.0f); + return ImVec4(0.24f, 0.24f, 0.24f, 1.0f); } inline ImU32 HueColorU32(float hue, float alpha = 1.0f) { diff --git a/editor/src/UI/ProjectGraphicsSettingsDialog.h b/editor/src/UI/ProjectGraphicsSettingsDialog.h new file mode 100644 index 00000000..a064ebd7 --- /dev/null +++ b/editor/src/UI/ProjectGraphicsSettingsDialog.h @@ -0,0 +1,187 @@ +#pragma once + +#include "PopupState.h" +#include "Widgets.h" + +#include "Application.h" +#include "Commands/ProjectCommands.h" +#include "ComponentEditors/ScriptComponentEditorUtils.h" +#include "Core/IEditorContext.h" +#include "Utils/ProjectFileUtils.h" + +#include +#include + +#include +#include +#include + +namespace XCEngine { +namespace Editor { +namespace UI { + +namespace Detail { + +inline std::string BuildRenderPipelineSelectionLabel( + const ::XCEngine::Rendering::Pipelines::ManagedRenderPipelineAssetDescriptor& + descriptor, + const std::vector<::XCEngine::Scripting::ScriptClassDescriptor>& + availableClasses, + bool hasLoadedClasses) { + if (!descriptor.IsValid()) { + return "Builtin Forward (Native Default)"; + } + + const ::XCEngine::Scripting::ScriptClassDescriptor scriptDescriptor{ + descriptor.assemblyName, + descriptor.namespaceName, + descriptor.className}; + const std::string label = + ScriptComponentEditorUtils::BuildScriptClassDisplayName( + scriptDescriptor); + if (!hasLoadedClasses) { + return label; + } + + return std::find( + availableClasses.begin(), + availableClasses.end(), + scriptDescriptor) != availableClasses.end() + ? label + : label + " (Missing)"; +} + +} // namespace Detail + +inline void DrawProjectGraphicsSettingsDialog( + IEditorContext* context, + DeferredPopupState& popupState) { + popupState.ConsumeOpenRequest("Project Graphics Settings"); + + if (!BeginModalPopup("Project Graphics Settings")) { + return; + } + + if (context == nullptr) { + DrawHintText("No editor context is available."); + if (ImGui::Button("Close", DialogActionButtonSize())) { + ImGui::CloseCurrentPopup(); + } + EndPopup(); + return; + } + + const std::string& projectPath = context->GetProjectPath(); + const std::string settingsFilePath = + ProjectFileUtils::GetGraphicsSettingsFilePath(projectPath).string(); + std::vector<::XCEngine::Scripting::ScriptClassDescriptor> + pipelineAssetClasses; + const bool hasLoadedClasses = + ::XCEngine::Scripting::ScriptEngine::Get() + .TryGetAvailableRenderPipelineAssetClasses( + pipelineAssetClasses); + const auto currentDescriptor = + ::XCEngine::Rendering::GetGraphicsSettingsState() + .GetRenderPipelineAssetDescriptor(); + const std::string currentLabel = + Detail::BuildRenderPipelineSelectionLabel( + currentDescriptor, + pipelineAssetClasses, + hasLoadedClasses); + const ::XCEngine::Scripting::ScriptClassDescriptor + currentScriptDescriptor{ + currentDescriptor.assemblyName, + currentDescriptor.namespaceName, + currentDescriptor.className}; + + ImGui::TextUnformatted("Project Graphics Settings"); + ImGui::Separator(); + ImGui::Text("Project: %s", projectPath.c_str()); + ImGui::Text("Settings File: %s", settingsFilePath.c_str()); + ImGui::Spacing(); + + ImGui::TextUnformatted("Render Pipeline"); + ImGui::SetNextItemWidth(420.0f); + if (ImGui::BeginCombo("##ProjectRenderPipelineAsset", currentLabel.c_str())) { + if (ImGui::Selectable( + "Builtin Forward (Native Default)", + !currentDescriptor.IsValid())) { + Commands::SetProjectRenderPipelineAssetSelection( + *context, + nullptr); + } + + if (!pipelineAssetClasses.empty()) { + ImGui::Separator(); + } + + for (const ::XCEngine::Scripting::ScriptClassDescriptor& descriptor : + pipelineAssetClasses) { + const bool selected = descriptor == currentScriptDescriptor; + const std::string label = + ScriptComponentEditorUtils::BuildScriptClassDisplayName( + descriptor); + if (ImGui::Selectable(label.c_str(), selected)) { + Commands::SetProjectRenderPipelineAssetSelection( + *context, + &descriptor); + } + if (selected) { + ImGui::SetItemDefaultFocus(); + } + } + + if (!hasLoadedClasses) { + ImGui::Separator(); + ImGui::TextDisabled( + "No ScriptableRenderPipelineAsset classes are currently loaded."); + } + + ImGui::EndCombo(); + } + + DrawHintText( + "Changes apply immediately and are written to ProjectSettings/GraphicsSettings.asset."); + + if (!hasLoadedClasses) { + const EditorScriptRuntimeStatus& runtimeStatus = + Application::Get().GetScriptRuntimeStatus(); + const std::string hintText = + ScriptComponentEditorUtils::BuildScriptRuntimeUnavailableHint( + runtimeStatus); + DrawHintText(hintText.c_str()); + + if (ScriptComponentEditorUtils::CanRebuildScriptAssemblies( + runtimeStatus)) { + if (InspectorActionButton( + "Rebuild Scripts", + ImVec2(120.0f, 0.0f))) { + Application::Get().RebuildScriptingAssemblies(); + } + } + + if (ScriptComponentEditorUtils::CanReloadScriptRuntime( + runtimeStatus)) { + if (ScriptComponentEditorUtils::CanRebuildScriptAssemblies( + runtimeStatus)) { + ImGui::SameLine(); + } + if (InspectorActionButton( + "Reload Scripts", + ImVec2(120.0f, 0.0f))) { + Application::Get().ReloadScriptingRuntime(); + } + } + } + + ImGui::Spacing(); + if (ImGui::Button("Close", DialogActionButtonSize())) { + ImGui::CloseCurrentPopup(); + } + + EndPopup(); +} + +} // namespace UI +} // namespace Editor +} // namespace XCEngine diff --git a/editor/src/UI/ReferencePicker.h b/editor/src/UI/ReferencePicker.h index 3c9bece9..ac38976d 100644 --- a/editor/src/UI/ReferencePicker.h +++ b/editor/src/UI/ReferencePicker.h @@ -91,15 +91,15 @@ inline ImVec4 ReferencePickerSurfaceColor() { } inline ImVec4 ReferencePickerBorderColor() { - return ImVec4(0.32f, 0.32f, 0.32f, 1.0f); + return PopupBorderColor(); } inline ImVec4 ReferencePickerTextColor() { - return ImVec4(0.84f, 0.84f, 0.84f, 1.0f); + return PopupTextColor(); } inline ImVec4 ReferencePickerTextDisabledColor() { - return ImVec4(0.60f, 0.60f, 0.60f, 1.0f); + return PopupTextDisabledColor(); } inline ImVec4 ReferencePickerItemColor() { diff --git a/editor/src/UI/StyleTokens.h b/editor/src/UI/StyleTokens.h index 8f3df884..fdd4b26c 100644 --- a/editor/src/UI/StyleTokens.h +++ b/editor/src/UI/StyleTokens.h @@ -29,31 +29,31 @@ inline float DockHostTabBarOverlineSize() { } inline ImVec4 DockTabColor() { - return ImVec4(0.17f, 0.17f, 0.17f, 1.00f); + return ImVec4(0.095f, 0.095f, 0.095f, 1.00f); } inline ImVec4 DockTabHoveredColor() { - return ImVec4(0.21f, 0.21f, 0.21f, 1.00f); + return ImVec4(0.11f, 0.11f, 0.11f, 1.00f); } inline ImVec4 DockTabSelectedColor() { - return ImVec4(0.24f, 0.24f, 0.24f, 1.00f); + return ImVec4(0.12f, 0.12f, 0.12f, 1.00f); } inline ImVec4 DockTabSelectedOverlineColor() { - return ImVec4(0.62f, 0.62f, 0.62f, 0.70f); + return ImVec4(0.68f, 0.68f, 0.68f, 0.38f); } inline ImVec4 DockTabDimmedColor() { - return ImVec4(0.16f, 0.16f, 0.16f, 1.00f); + return ImVec4(0.09f, 0.09f, 0.09f, 1.00f); } inline ImVec4 DockTabDimmedSelectedColor() { - return ImVec4(0.20f, 0.20f, 0.20f, 1.00f); + return ImVec4(0.10f, 0.10f, 0.10f, 1.00f); } inline ImVec4 DockTabDimmedSelectedOverlineColor() { - return ImVec4(0.44f, 0.44f, 0.44f, 0.55f); + return ImVec4(0.40f, 0.40f, 0.40f, 0.30f); } inline ImVec2 PanelWindowPadding() { @@ -61,7 +61,7 @@ inline ImVec2 PanelWindowPadding() { } inline ImVec4 HierarchyInspectorPanelBackgroundColor() { - return ImVec4(0.235f, 0.235f, 0.235f, 1.0f); + return ImVec4(0.10f, 0.10f, 0.10f, 1.0f); } inline ImVec2 ToolbarPadding() { @@ -165,23 +165,23 @@ inline float LinearSliderHorizontalPadding() { } inline ImVec4 LinearSliderTrackColor() { - return ImVec4(0.30f, 0.30f, 0.30f, 1.0f); + return ImVec4(0.13f, 0.13f, 0.13f, 1.0f); } inline ImVec4 LinearSliderFillColor() { - return ImVec4(0.66f, 0.66f, 0.66f, 1.0f); + return ImVec4(0.24f, 0.24f, 0.24f, 1.0f); } inline ImVec4 LinearSliderGrabColor() { - return ImVec4(0.78f, 0.78f, 0.78f, 1.0f); + return ImVec4(0.30f, 0.30f, 0.30f, 1.0f); } inline ImVec4 LinearSliderGrabHoveredColor() { - return ImVec4(0.88f, 0.88f, 0.88f, 1.0f); + return ImVec4(0.36f, 0.36f, 0.36f, 1.0f); } inline ImVec4 LinearSliderGrabActiveColor() { - return ImVec4(0.95f, 0.95f, 0.95f, 1.0f); + return ImVec4(0.42f, 0.42f, 0.42f, 1.0f); } inline ImVec2 InspectorPanelContentPadding() { @@ -217,15 +217,15 @@ inline float PanelSplitterVisibleThickness() { } inline ImVec4 PanelSplitterIdleColor() { - return ImVec4(0.14f, 0.14f, 0.14f, 1.0f); + return ImVec4(0.10f, 0.10f, 0.10f, 1.0f); } inline ImVec4 PanelSplitterHoveredColor() { - return PanelSplitterIdleColor(); + return ImVec4(0.11f, 0.11f, 0.11f, 1.0f); } inline ImVec4 PanelSplitterActiveColor() { - return PanelSplitterIdleColor(); + return ImVec4(0.13f, 0.13f, 0.13f, 1.0f); } inline ImVec2 ProjectNavigationPanePadding() { @@ -273,15 +273,15 @@ inline float DisclosureArrowScale() { } inline ImVec4 DisclosureArrowColor() { - return ImVec4(0.42f, 0.42f, 0.42f, 1.0f); + return ImVec4(0.44f, 0.44f, 0.44f, 1.0f); } inline ImVec4 NavigationTreePrefixColor(bool selected = false, bool hovered = false) { if (selected) { - return ImVec4(0.86f, 0.86f, 0.86f, 1.0f); + return ImVec4(0.90f, 0.90f, 0.90f, 1.0f); } if (hovered) { - return ImVec4(0.76f, 0.76f, 0.76f, 1.0f); + return ImVec4(0.78f, 0.78f, 0.78f, 1.0f); } return ImVec4(0.62f, 0.62f, 0.62f, 1.0f); } @@ -315,31 +315,31 @@ inline TreeViewStyle ProjectFolderTreeStyle() { } inline ImVec4 ProjectPanelBackgroundColor() { - return DockTabSelectedColor(); + return ImVec4(0.095f, 0.095f, 0.095f, 1.0f); } inline ImVec4 ProjectPanelToolbarBackgroundColor() { - return ProjectPanelBackgroundColor(); + return ImVec4(0.095f, 0.095f, 0.095f, 1.0f); } inline ImVec4 ProjectNavigationPaneBackgroundColor() { - return ProjectPanelBackgroundColor(); + return ImVec4(0.095f, 0.095f, 0.095f, 1.0f); } inline ImVec4 ProjectBrowserSurfaceColor() { - return ImVec4(0.20f, 0.20f, 0.20f, 1.0f); + return ImVec4(0.085f, 0.085f, 0.085f, 1.0f); } inline ImVec4 ProjectBrowserHeaderBackgroundColor() { - return ProjectPanelBackgroundColor(); + return ImVec4(0.095f, 0.095f, 0.095f, 1.0f); } inline ImVec4 ProjectBrowserPaneBackgroundColor() { - return ProjectBrowserSurfaceColor(); + return ImVec4(0.085f, 0.085f, 0.085f, 1.0f); } inline ImVec4 ToolbarBackgroundColor() { - return ImVec4(0.19f, 0.19f, 0.19f, 1.0f); + return ImVec4(0.095f, 0.095f, 0.095f, 1.0f); } inline ImVec2 SearchFieldFramePadding() { @@ -363,7 +363,7 @@ inline float InlineRenameFieldRounding() { } inline ImVec4 InlineRenameFieldBackgroundColor() { - return ImVec4(0.25f, 0.25f, 0.25f, 1.0f); + return ImVec4(0.10f, 0.10f, 0.10f, 1.0f); } inline ImVec4 InlineRenameFieldHoveredColor() { @@ -387,11 +387,11 @@ inline float PanelDividerThickness() { } inline ImVec4 EmptyStateSubtitleColor() { - return ImVec4(0.55f, 0.55f, 0.55f, 1.0f); + return ImVec4(0.60f, 0.60f, 0.60f, 1.0f); } inline ImVec4 HintTextColor() { - return ImVec4(0.53f, 0.53f, 0.53f, 1.0f); + return ImVec4(0.62f, 0.62f, 0.62f, 1.0f); } inline ImVec2 BreadcrumbSegmentPadding() { @@ -404,12 +404,12 @@ inline float BreadcrumbSegmentSpacing() { inline ImVec4 BreadcrumbSegmentTextColor(bool current = false, bool hovered = false) { if (hovered) { - return ImVec4(0.90f, 0.90f, 0.90f, 1.0f); + return ImVec4(0.88f, 0.88f, 0.88f, 1.0f); } if (current) { - return ImVec4(0.84f, 0.84f, 0.84f, 1.0f); + return ImVec4(0.80f, 0.80f, 0.80f, 1.0f); } - return ImVec4(0.72f, 0.72f, 0.72f, 1.0f); + return ImVec4(0.68f, 0.68f, 0.68f, 1.0f); } inline ImVec4 BreadcrumbSeparatorColor() { @@ -445,27 +445,27 @@ inline ImVec2 ComboPopupItemSpacing() { } inline ImVec4 MenuSurfaceColor() { - return ImVec4(0.98f, 0.98f, 0.98f, 1.0f); + return ImVec4(0.10f, 0.10f, 0.10f, 1.0f); } inline ImVec4 MenuSurfaceHoverColor() { - return ImVec4(0.97f, 0.97f, 0.97f, 1.0f); + return ImVec4(0.14f, 0.14f, 0.14f, 1.0f); } inline ImVec4 MenuSurfaceActiveColor() { - return ImVec4(0.955f, 0.955f, 0.955f, 1.0f); + return ImVec4(0.17f, 0.17f, 0.17f, 1.0f); } inline ImVec4 MenuBorderColor() { - return ImVec4(0.84f, 0.84f, 0.84f, 1.0f); + return ImVec4(0.15f, 0.15f, 0.15f, 1.0f); } inline ImVec4 MenuTextColor() { - return ImVec4(0.16f, 0.16f, 0.16f, 1.0f); + return ImVec4(0.88f, 0.88f, 0.88f, 1.0f); } inline ImVec4 MenuTextDisabledColor() { - return ImVec4(0.47f, 0.47f, 0.47f, 1.0f); + return ImVec4(0.48f, 0.48f, 0.48f, 1.0f); } inline float ComboPopupRounding() { @@ -625,23 +625,23 @@ inline float AssetTileRounding() { } inline ImVec4 AssetTileIdleFillColor() { - return ImVec4(1.0f, 1.0f, 1.0f, 0.02f); + return ImVec4(1.0f, 1.0f, 1.0f, 0.03f); } inline ImVec4 AssetTileIdleBorderColor() { - return ImVec4(1.0f, 1.0f, 1.0f, 0.05f); + return ImVec4(1.0f, 1.0f, 1.0f, 0.06f); } inline ImVec4 AssetTileHoverFillColor() { - return ImVec4(1.0f, 1.0f, 1.0f, 0.04f); + return ImVec4(1.0f, 1.0f, 1.0f, 0.05f); } inline ImVec4 AssetTileSelectedFillColor() { - return ImVec4(0.61f, 0.61f, 0.61f, 0.12f); + return ImVec4(0.86f, 0.86f, 0.86f, 0.08f); } inline ImVec4 AssetTileSelectedBorderColor() { - return ImVec4(0.74f, 0.74f, 0.74f, 0.43f); + return ImVec4(0.86f, 0.86f, 0.86f, 0.24f); } inline ImVec4 AssetTileDraggedOverlayColor() { @@ -685,55 +685,55 @@ inline ImVec4 AssetTileTextColor(bool selected = false) { } inline ImVec4 BuiltInFolderIconTabColor() { - return ImVec4(0.77f, 0.77f, 0.77f, 1.0f); + return ImVec4(0.63f, 0.63f, 0.63f, 1.0f); } inline ImVec4 BuiltInFolderIconTopColor() { - return ImVec4(0.82f, 0.82f, 0.82f, 1.0f); + return ImVec4(0.70f, 0.70f, 0.70f, 1.0f); } inline ImVec4 BuiltInFolderIconBodyColor() { - return ImVec4(0.72f, 0.72f, 0.72f, 1.0f); + return ImVec4(0.56f, 0.56f, 0.56f, 1.0f); } inline ImVec4 AssetFileIconFillColor() { - return ImVec4(0.46f, 0.46f, 0.46f, 1.0f); + return ImVec4(0.36f, 0.36f, 0.36f, 1.0f); } inline ImVec4 AssetFileIconLineColor() { - return ImVec4(0.74f, 0.74f, 0.74f, 0.90f); + return ImVec4(0.82f, 0.82f, 0.82f, 0.72f); } inline ImVec4 AssetFileFoldColor() { - return ImVec4(0.92f, 0.92f, 0.92f, 0.16f); + return ImVec4(0.95f, 0.95f, 0.95f, 0.12f); } inline ImVec4 ConsoleRowHoverFillColor() { - return ImVec4(1.0f, 1.0f, 1.0f, 0.03f); + return ImVec4(1.0f, 1.0f, 1.0f, 0.05f); } inline ImVec4 ConsoleRowSelectedFillColor() { - return ImVec4(0.26f, 0.33f, 0.43f, 0.95f); + return ImVec4(0.18f, 0.21f, 0.25f, 0.96f); } inline ImVec4 ConsoleListBackgroundColor() { - return ImVec4(0.205f, 0.205f, 0.205f, 1.0f); + return ImVec4(0.10f, 0.10f, 0.10f, 1.0f); } inline ImVec4 ConsoleDetailsBackgroundColor() { - return ImVec4(0.185f, 0.185f, 0.185f, 1.0f); + return ImVec4(0.10f, 0.10f, 0.10f, 1.0f); } inline ImVec4 ConsoleDetailsHeaderBackgroundColor() { - return ImVec4(0.19f, 0.19f, 0.19f, 1.0f); + return ImVec4(0.11f, 0.11f, 0.11f, 1.0f); } inline ImVec4 ConsoleSecondaryTextColor() { - return ImVec4(0.57f, 0.57f, 0.57f, 1.0f); + return ImVec4(0.62f, 0.62f, 0.62f, 1.0f); } inline ImVec4 ConsoleCountBadgeBackgroundColor() { - return ImVec4(0.29f, 0.29f, 0.29f, 1.0f); + return ImVec4(0.12f, 0.12f, 0.12f, 1.0f); } inline ImVec4 ConsoleCountBadgeTextColor() { @@ -785,11 +785,11 @@ inline float MenuBarStatusRightPadding() { } inline ImVec4 MenuBarStatusIdleColor() { - return ImVec4(0.62f, 0.62f, 0.62f, 1.0f); + return ImVec4(0.64f, 0.64f, 0.64f, 1.0f); } inline ImVec4 MenuBarStatusDirtyColor() { - return ImVec4(0.82f, 0.82f, 0.82f, 1.0f); + return ImVec4(0.90f, 0.90f, 0.90f, 1.0f); } inline ImVec2 VectorAxisInputSpacing() { @@ -805,23 +805,23 @@ inline float VectorAxisResetButtonExtraWidth() { } inline ImVec4 VectorAxisButtonColor() { - return ImVec4(0.28f, 0.28f, 0.28f, 1.0f); + return ImVec4(0.10f, 0.10f, 0.10f, 1.0f); } inline ImVec4 VectorAxisButtonHoveredColor() { - return ImVec4(0.34f, 0.34f, 0.34f, 1.0f); + return ImVec4(0.12f, 0.12f, 0.12f, 1.0f); } inline ImVec4 ToolbarButtonColor(bool active) { - return active ? ImVec4(0.33f, 0.33f, 0.33f, 1.0f) : ImVec4(0.24f, 0.24f, 0.24f, 1.0f); + return active ? ImVec4(0.11f, 0.11f, 0.11f, 1.0f) : ImVec4(0.095f, 0.095f, 0.095f, 1.0f); } inline ImVec4 ToolbarButtonHoveredColor(bool active) { - return active ? ImVec4(0.38f, 0.38f, 0.38f, 1.0f) : ImVec4(0.30f, 0.30f, 0.30f, 1.0f); + return active ? ImVec4(0.12f, 0.12f, 0.12f, 1.0f) : ImVec4(0.11f, 0.11f, 0.11f, 1.0f); } inline ImVec4 ToolbarButtonActiveColor() { - return ImVec4(0.42f, 0.42f, 0.42f, 1.0f); + return ImVec4(0.13f, 0.13f, 0.13f, 1.0f); } } diff --git a/editor/src/UI/UI.h b/editor/src/UI/UI.h index c9b334be..b4f9bd58 100644 --- a/editor/src/UI/UI.h +++ b/editor/src/UI/UI.h @@ -13,6 +13,7 @@ #include "MenuCommand.h" #include "PanelChrome.h" #include "PopupState.h" +#include "ProjectGraphicsSettingsDialog.h" #include "PropertyLayout.h" #include "PropertyGrid.h" #include "ReferencePicker.h" diff --git a/editor/src/Utils/ProjectFileUtils.h b/editor/src/Utils/ProjectFileUtils.h index 4ca7a269..1b2d04e0 100644 --- a/editor/src/Utils/ProjectFileUtils.h +++ b/editor/src/Utils/ProjectFileUtils.h @@ -16,10 +16,29 @@ struct ProjectDescriptor { std::string startupScene; }; +struct GraphicsSettingsDescriptor { + std::string renderPipelineAssetAssembly; + std::string renderPipelineAssetNamespace; + std::string renderPipelineAssetClass; + + bool HasRenderPipelineAsset() const { + return !renderPipelineAssetAssembly.empty() && + !renderPipelineAssetClass.empty(); + } +}; + inline fs::path GetProjectFilePath(const std::string& projectRoot) { return fs::path(projectRoot) / "Project.xcproject"; } +inline fs::path GetProjectSettingsDirectory(const std::string& projectRoot) { + return fs::path(projectRoot) / "ProjectSettings"; +} + +inline fs::path GetGraphicsSettingsFilePath(const std::string& projectRoot) { + return GetProjectSettingsDirectory(projectRoot) / "GraphicsSettings.asset"; +} + inline std::string GetProjectName(const std::string& projectRoot) { const fs::path rootPath(projectRoot); const fs::path folderName = rootPath.filename().empty() ? rootPath.parent_path().filename() : rootPath.filename(); @@ -79,6 +98,59 @@ inline std::optional LoadProjectDescriptor(const std::string& return descriptor; } +inline bool SaveProjectGraphicsSettings( + const std::string& projectRoot, + const GraphicsSettingsDescriptor& descriptor) { + std::error_code ec; + fs::create_directories(GetProjectSettingsDirectory(projectRoot), ec); + + std::ofstream output( + GetGraphicsSettingsFilePath(projectRoot), + std::ios::out | std::ios::trunc); + if (!output.is_open()) { + return false; + } + + output << "version=1\n"; + output << "render_pipeline_asset_assembly=" + << descriptor.renderPipelineAssetAssembly << "\n"; + output << "render_pipeline_asset_namespace=" + << descriptor.renderPipelineAssetNamespace << "\n"; + output << "render_pipeline_asset_class=" + << descriptor.renderPipelineAssetClass << "\n"; + return output.good(); +} + +inline std::optional LoadProjectGraphicsSettings( + const std::string& projectRoot) { + std::ifstream input(GetGraphicsSettingsFilePath(projectRoot)); + if (!input.is_open()) { + return std::nullopt; + } + + GraphicsSettingsDescriptor descriptor; + + std::string line; + while (std::getline(input, line)) { + const size_t equalsPos = line.find('='); + if (equalsPos == std::string::npos) { + continue; + } + + const std::string key = Trim(line.substr(0, equalsPos)); + const std::string value = Trim(line.substr(equalsPos + 1)); + if (key == "render_pipeline_asset_assembly") { + descriptor.renderPipelineAssetAssembly = value; + } else if (key == "render_pipeline_asset_namespace") { + descriptor.renderPipelineAssetNamespace = value; + } else if (key == "render_pipeline_asset_class") { + descriptor.renderPipelineAssetClass = value; + } + } + + return descriptor; +} + inline std::string MakeProjectRelativePath(const std::string& projectRoot, const std::string& fullPath) { if (projectRoot.empty() || fullPath.empty()) { return {}; diff --git a/editor/src/Utils/ProjectGraphicsSettings.h b/editor/src/Utils/ProjectGraphicsSettings.h new file mode 100644 index 00000000..43f62bc0 --- /dev/null +++ b/editor/src/Utils/ProjectGraphicsSettings.h @@ -0,0 +1,78 @@ +#pragma once + +#include "Utils/ProjectFileUtils.h" + +#include + +namespace XCEngine { +namespace Editor { +namespace ProjectGraphicsSettings { + +inline ProjectFileUtils::GraphicsSettingsDescriptor BuildDescriptor( + const ::XCEngine::Rendering::GraphicsSettingsState& state) { + ProjectFileUtils::GraphicsSettingsDescriptor descriptor; + const auto nativeDescriptor = state.GetRenderPipelineAssetDescriptor(); + if (nativeDescriptor.IsValid()) { + descriptor.renderPipelineAssetAssembly = nativeDescriptor.assemblyName; + descriptor.renderPipelineAssetNamespace = + nativeDescriptor.namespaceName; + descriptor.renderPipelineAssetClass = nativeDescriptor.className; + } + + return descriptor; +} + +inline bool SaveSelection( + const std::string& projectRoot, + const ::XCEngine::Rendering::GraphicsSettingsState& state) { + return ProjectFileUtils::SaveProjectGraphicsSettings( + projectRoot, + BuildDescriptor(state)); +} + +inline void ApplySelection( + const std::string& projectRoot, + ::XCEngine::Rendering::GraphicsSettingsState& state) { + state.ClearRenderPipelineAssetDescriptor(); + + if (projectRoot.empty()) { + return; + } + + const auto savedDescriptor = + ProjectFileUtils::LoadProjectGraphicsSettings(projectRoot); + if (!savedDescriptor || !savedDescriptor->HasRenderPipelineAsset()) { + return; + } + + ::XCEngine::Rendering::Pipelines::ManagedRenderPipelineAssetDescriptor + nativeDescriptor = {}; + nativeDescriptor.assemblyName = + savedDescriptor->renderPipelineAssetAssembly; + nativeDescriptor.namespaceName = + savedDescriptor->renderPipelineAssetNamespace; + nativeDescriptor.className = savedDescriptor->renderPipelineAssetClass; + if (nativeDescriptor.IsValid()) { + state.SetRenderPipelineAssetDescriptor(nativeDescriptor); + } +} + +inline ProjectFileUtils::GraphicsSettingsDescriptor BuildCurrentDescriptor() { + return BuildDescriptor(::XCEngine::Rendering::GetGraphicsSettingsState()); +} + +inline bool SaveCurrentSelection(const std::string& projectRoot) { + return SaveSelection( + projectRoot, + ::XCEngine::Rendering::GetGraphicsSettingsState()); +} + +inline void ApplyCurrentSelection(const std::string& projectRoot) { + ApplySelection( + projectRoot, + ::XCEngine::Rendering::GetGraphicsSettingsState()); +} + +} // namespace ProjectGraphicsSettings +} // namespace Editor +} // namespace XCEngine diff --git a/editor/src/Viewport/SceneViewportChrome.cpp b/editor/src/Viewport/SceneViewportChrome.cpp index 55ce61d2..84071cc9 100644 --- a/editor/src/Viewport/SceneViewportChrome.cpp +++ b/editor/src/Viewport/SceneViewportChrome.cpp @@ -106,7 +106,7 @@ void RenderSceneViewportTopBar( true, ImVec2(UI::ToolbarPadding().x, kSceneToolbarPaddingY), ImVec2(0.0f, UI::ToolbarItemSpacing().y), - ImVec4(0.23f, 0.23f, 0.23f, 1.0f)); + UI::ToolbarBackgroundColor()); if (!toolbar.IsOpen()) { return; } diff --git a/editor/src/Viewport/SceneViewportMoveGizmo.cpp b/editor/src/Viewport/SceneViewportMoveGizmo.cpp index 446f9e68..dd98611f 100644 --- a/editor/src/Viewport/SceneViewportMoveGizmo.cpp +++ b/editor/src/Viewport/SceneViewportMoveGizmo.cpp @@ -13,6 +13,8 @@ namespace { constexpr float kMoveGizmoHandleLengthPixels = 100.0f; constexpr float kMoveGizmoHoverThresholdPixels = 10.0f; +constexpr float kMoveGizmoPlaneInsetPixels = 2.0f; +constexpr float kMoveGizmoPlaneSizePixels = 24.0f; Math::Vector3 GetBaseAxisVector(SceneViewportGizmoAxis axis) { switch (axis) { @@ -489,8 +491,9 @@ void SceneViewportMoveGizmo::BuildDrawData(const SceneViewportMoveGizmoContext& } const float axisLengthWorld = worldUnitsPerPixel * kMoveGizmoHandleLengthPixels; - const float planeInsetWorld = axisLengthWorld * 0.02f; - const float planeExtentWorld = axisLengthWorld * 0.36f; + const float planeInsetWorld = worldUnitsPerPixel * kMoveGizmoPlaneInsetPixels; + const float planeExtentWorld = + worldUnitsPerPixel * (kMoveGizmoPlaneInsetPixels + kMoveGizmoPlaneSizePixels); const SceneViewportGizmoAxis axes[] = { SceneViewportGizmoAxis::X, diff --git a/editor/src/panels/ConsolePanel.cpp b/editor/src/panels/ConsolePanel.cpp index 63efd8a3..11196665 100644 --- a/editor/src/panels/ConsolePanel.cpp +++ b/editor/src/panels/ConsolePanel.cpp @@ -64,7 +64,6 @@ constexpr float kConsoleSearchWidth = 220.0f; constexpr float kConsoleRowHeight = 20.0f; constexpr float kConsoleDetailsHeaderHeight = 24.0f; constexpr float kConsoleToolbarItemSpacing = 4.0f; -constexpr ImVec4 kConsoleToolbarBackgroundColor = ImVec4(0.23f, 0.23f, 0.23f, 1.0f); float ResolveConsoleDetailsHeaderHeight() { const ImGuiStyle& style = ImGui::GetStyle(); @@ -1059,7 +1058,7 @@ void ConsolePanel::Render() { true, ImVec2(6.0f, kConsoleToolbarRowPaddingY), ImVec2(kConsoleToolbarItemSpacing, 0.0f), - kConsoleToolbarBackgroundColor); + UI::ToolbarBackgroundColor()); ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(0.0f, 0.0f)); if (toolbar.IsOpen() && ImGui::BeginTable( diff --git a/editor/src/panels/GameViewPanel.cpp b/editor/src/panels/GameViewPanel.cpp index cbcd1114..6afb0dfe 100644 --- a/editor/src/panels/GameViewPanel.cpp +++ b/editor/src/panels/GameViewPanel.cpp @@ -7,6 +7,9 @@ #include +#include +#include + #include namespace XCEngine { @@ -159,10 +162,89 @@ void PublishGameViewInputFrame(IEditorContext* context, const GameViewInputFrame context->GetEventBus().Publish(event); } +void DrawGameViewPerformanceOverlay( + const ViewportPanelContentResult& content, + float displayFps, + float displayFrameTimeMs) { + if (!content.hasViewportArea || !content.frame.hasTexture) { + return; + } + + ImDrawList* drawList = ImGui::GetWindowDrawList(); + if (drawList == nullptr) { + return; + } + + char fpsText[32] = {}; + char frameTimeText[32] = {}; + char resolutionText[32] = {}; + std::snprintf(fpsText, sizeof(fpsText), "FPS %.1f", displayFps); + std::snprintf(frameTimeText, sizeof(frameTimeText), "%.2f ms", displayFrameTimeMs); + const ::XCEngine::UI::UISize displaySize = + content.frame.renderSize.width > 0.0f && content.frame.renderSize.height > 0.0f + ? content.frame.renderSize + : content.frame.requestedSize; + std::snprintf( + resolutionText, + sizeof(resolutionText), + "%d x %d", + static_cast(displaySize.width + 0.5f), + static_cast(displaySize.height + 0.5f)); + + const ImVec2 padding(10.0f, 8.0f); + const float lineSpacing = 4.0f; + const ImVec2 fpsTextSize = ImGui::CalcTextSize(fpsText); + const ImVec2 frameTimeTextSize = ImGui::CalcTextSize(frameTimeText); + const ImVec2 resolutionTextSize = ImGui::CalcTextSize(resolutionText); + const float panelWidth = + (std::max)( + (std::max)(fpsTextSize.x, frameTimeTextSize.x), + resolutionTextSize.x) + padding.x * 2.0f; + const float panelHeight = + fpsTextSize.y + frameTimeTextSize.y + resolutionTextSize.y + + lineSpacing * 2.0f + padding.y * 2.0f; + + const ImVec2 panelMin(content.itemMin.x + 12.0f, content.itemMin.y + 12.0f); + const ImVec2 panelMax(panelMin.x + panelWidth, panelMin.y + panelHeight); + drawList->AddRectFilled(panelMin, panelMax, IM_COL32(18, 20, 24, 196), 6.0f); + drawList->AddText( + ImVec2(panelMin.x + padding.x, panelMin.y + padding.y), + IM_COL32(245, 245, 245, 255), + fpsText); + drawList->AddText( + ImVec2(panelMin.x + padding.x, panelMin.y + padding.y + fpsTextSize.y + lineSpacing), + IM_COL32(196, 201, 209, 255), + frameTimeText); + drawList->AddText( + ImVec2( + panelMin.x + padding.x, + panelMin.y + padding.y + fpsTextSize.y + frameTimeTextSize.y + lineSpacing * 2.0f), + IM_COL32(156, 163, 175, 255), + resolutionText); +} + } // namespace GameViewPanel::GameViewPanel() : Panel("Game") {} +void GameViewPanel::OnUpdate(float dt) { + if (dt <= 0.0f) { + return; + } + + constexpr float kSmoothingFactor = 0.12f; + if (m_smoothedDeltaTimeSeconds <= 0.0f) { + m_smoothedDeltaTimeSeconds = dt; + } else { + m_smoothedDeltaTimeSeconds += (dt - m_smoothedDeltaTimeSeconds) * kSmoothingFactor; + } + + m_displayFrameTimeMs = m_smoothedDeltaTimeSeconds * 1000.0f; + m_displayFps = m_smoothedDeltaTimeSeconds > 0.0f + ? 1.0f / m_smoothedDeltaTimeSeconds + : 0.0f; +} + void GameViewPanel::Render() { ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); UI::PanelWindowScope panel(m_name.c_str()); @@ -173,6 +255,7 @@ void GameViewPanel::Render() { } const ViewportPanelContentResult content = RenderViewportPanelContent(*m_context, EditorViewportKind::Game); + DrawGameViewPerformanceOverlay(content, m_displayFps, m_displayFrameTimeMs); PublishGameViewInputFrame(m_context, BuildGameViewInputFrame(content)); Actions::ObserveInactiveActionRoute(*m_context); } diff --git a/editor/src/panels/GameViewPanel.h b/editor/src/panels/GameViewPanel.h index 1024ee7d..282e7b2a 100644 --- a/editor/src/panels/GameViewPanel.h +++ b/editor/src/panels/GameViewPanel.h @@ -8,7 +8,13 @@ namespace Editor { class GameViewPanel : public Panel { public: GameViewPanel(); + void OnUpdate(float dt) override; void Render() override; + +private: + float m_smoothedDeltaTimeSeconds = 0.0f; + float m_displayFps = 0.0f; + float m_displayFrameTimeMs = 0.0f; }; } diff --git a/editor/src/panels/MenuBar.cpp b/editor/src/panels/MenuBar.cpp index 76ce97e9..422bc0eb 100644 --- a/editor/src/panels/MenuBar.cpp +++ b/editor/src/panels/MenuBar.cpp @@ -22,10 +22,6 @@ constexpr float kRunToolbarButtonExtent = 24.0f; constexpr float kRunToolbarButtonSpacing = 8.0f; constexpr float kRunToolbarIconInset = 3.0f; constexpr ImVec2 kMainMenuFramePadding(6.0f, 2.0f); -constexpr ImVec4 kMainMenuTextColor(0.08f, 0.08f, 0.08f, 1.0f); -constexpr ImVec4 kMainMenuItemHoveredColor(0.88f, 0.88f, 0.88f, 1.0f); -constexpr ImVec4 kMainMenuItemActiveColor(0.82f, 0.82f, 0.82f, 1.0f); -constexpr ImVec4 kRunToolbarBackgroundColor(0.1f, 0.1f, 0.1f, 1.0f); std::string BuildRunToolbarIconPath(const char* fileName) { const std::filesystem::path exeDir( @@ -113,11 +109,14 @@ void MenuBar::RenderChrome() { Actions::HandleMenuBarShortcuts(*m_context); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, kMainMenuFramePadding); - ImGui::PushStyleColor(ImGuiCol_Text, kMainMenuTextColor); - ImGui::PushStyleColor(ImGuiCol_Header, kMainMenuItemActiveColor); - ImGui::PushStyleColor(ImGuiCol_HeaderHovered, kMainMenuItemHoveredColor); - ImGui::PushStyleColor(ImGuiCol_HeaderActive, kMainMenuItemActiveColor); - Actions::DrawMainMenuBar(*m_context, m_aboutPopup); + ImGui::PushStyleColor(ImGuiCol_Text, UI::MenuTextColor()); + ImGui::PushStyleColor(ImGuiCol_Header, UI::MenuSurfaceActiveColor()); + ImGui::PushStyleColor(ImGuiCol_HeaderHovered, UI::MenuSurfaceHoverColor()); + ImGui::PushStyleColor(ImGuiCol_HeaderActive, UI::MenuSurfaceActiveColor()); + Actions::DrawMainMenuBar( + *m_context, + m_aboutPopup, + m_projectGraphicsSettingsPopup); ImGui::PopStyleColor(4); ImGui::PopStyleVar(); RenderRunToolbar(); @@ -128,7 +127,10 @@ void MenuBar::RenderOverlays() { return; } - Actions::DrawMainMenuOverlays(m_context, m_aboutPopup); + Actions::DrawMainMenuOverlays( + m_context, + m_aboutPopup, + m_projectGraphicsSettingsPopup); } void MenuBar::RenderRunToolbar() { @@ -150,7 +152,7 @@ void MenuBar::RenderRunToolbar() { ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(12.0f, 1.0f)); ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); - ImGui::PushStyleColor(ImGuiCol_WindowBg, kRunToolbarBackgroundColor); + ImGui::PushStyleColor(ImGuiCol_WindowBg, UI::ToolbarBackgroundColor()); const bool open = ImGui::BeginViewportSideBar("##MainRunToolbar", viewport, ImGuiDir_Up, kRunToolbarHeight, kWindowFlags); ImGui::PopStyleColor(); diff --git a/editor/src/panels/MenuBar.h b/editor/src/panels/MenuBar.h index 6b81310c..de221661 100644 --- a/editor/src/panels/MenuBar.h +++ b/editor/src/panels/MenuBar.h @@ -17,6 +17,7 @@ private: void RenderRunToolbar(); UI::DeferredPopupState m_aboutPopup; + UI::DeferredPopupState m_projectGraphicsSettingsPopup; }; } diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index 05fc2c73..aa58156c 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -552,6 +552,7 @@ add_library(XCEngine STATIC ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Passes/BuiltinFinalColorPass.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Features/BuiltinGaussianSplatPass.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Features/BuiltinVolumetricPass.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/GraphicsSettingsState.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Pipelines/NativeSceneRecorder.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.h @@ -595,6 +596,7 @@ add_library(XCEngine STATIC ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Features/BuiltinGaussianSplatPass.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Features/BuiltinVolumetricPass.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Features/Internal/BuiltinGaussianSplatPassResources.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/GraphicsSettingsState.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/RenderPipeline.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/RenderPipelineAsset.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/RenderSurface.cpp diff --git a/engine/include/XCEngine/Components/LightComponent.h b/engine/include/XCEngine/Components/LightComponent.h index 3d4f3aba..68088d71 100644 --- a/engine/include/XCEngine/Components/LightComponent.h +++ b/engine/include/XCEngine/Components/LightComponent.h @@ -2,6 +2,7 @@ #include #include +#include namespace XCEngine { namespace Components { @@ -34,6 +35,34 @@ public: bool GetCastsShadows() const { return m_castsShadows; } void SetCastsShadows(bool value) { m_castsShadows = value; } + bool GetOverridesDirectionalShadowSettings() const { return m_overrideDirectionalShadowSettings; } + void SetOverridesDirectionalShadowSettings(bool value) { m_overrideDirectionalShadowSettings = value; } + + const Rendering::DirectionalShadowSamplingSettings& GetDirectionalShadowSamplingSettings() const { + return m_directionalShadowSampling; + } + void SetDirectionalShadowSamplingSettings(const Rendering::DirectionalShadowSamplingSettings& value); + + const Rendering::DirectionalShadowCasterBiasSettings& GetDirectionalShadowCasterBiasSettings() const { + return m_directionalShadowCasterBias; + } + void SetDirectionalShadowCasterBiasSettings(const Rendering::DirectionalShadowCasterBiasSettings& value); + + float GetDirectionalShadowReceiverDepthBias() const { return m_directionalShadowSampling.receiverDepthBias; } + void SetDirectionalShadowReceiverDepthBias(float value); + + float GetDirectionalShadowNormalBiasScale() const { return m_directionalShadowSampling.normalBiasScale; } + void SetDirectionalShadowNormalBiasScale(float value); + + float GetDirectionalShadowStrength() const { return m_directionalShadowSampling.shadowStrength; } + void SetDirectionalShadowStrength(float value); + + float GetDirectionalShadowDepthBiasFactor() const { return m_directionalShadowCasterBias.depthBiasFactor; } + void SetDirectionalShadowDepthBiasFactor(float value); + + int GetDirectionalShadowDepthBiasUnits() const { return m_directionalShadowCasterBias.depthBiasUnits; } + void SetDirectionalShadowDepthBiasUnits(int value); + void Serialize(std::ostream& os) const override; void Deserialize(std::istream& is) override; @@ -44,6 +73,9 @@ private: float m_range = 10.0f; float m_spotAngle = 30.0f; bool m_castsShadows = false; + bool m_overrideDirectionalShadowSettings = false; + Rendering::DirectionalShadowSamplingSettings m_directionalShadowSampling = {}; + Rendering::DirectionalShadowCasterBiasSettings m_directionalShadowCasterBias = {}; }; } // namespace Components diff --git a/engine/include/XCEngine/Rendering/GraphicsSettingsState.h b/engine/include/XCEngine/Rendering/GraphicsSettingsState.h new file mode 100644 index 00000000..6cda9dc6 --- /dev/null +++ b/engine/include/XCEngine/Rendering/GraphicsSettingsState.h @@ -0,0 +1,50 @@ +#pragma once + +#include + +namespace XCEngine { +namespace Rendering { + +class GraphicsSettingsState final { +public: + GraphicsSettingsState() = default; + + void SetManagedRenderPipelineBridge( + std::shared_ptr bridge); + void ClearManagedRenderPipelineBridge(); + std::shared_ptr + GetManagedRenderPipelineBridge() const { + return m_managedRenderPipelineBridge; + } + + size_t GetManagedRenderPipelineBridgeGeneration() const { + return m_managedRenderPipelineBridgeGeneration; + } + + size_t GetEnvironmentGeneration() const { + return m_environmentGeneration; + } + + void SetRenderPipelineAssetDescriptor( + const Pipelines::ManagedRenderPipelineAssetDescriptor& descriptor); + void ClearRenderPipelineAssetDescriptor(); + Pipelines::ManagedRenderPipelineAssetDescriptor + GetRenderPipelineAssetDescriptor() const { + return m_renderPipelineAssetDescriptor; + } + +private: + void BumpEnvironmentGeneration(); + + Pipelines::ManagedRenderPipelineAssetDescriptor + m_renderPipelineAssetDescriptor = {}; + std::shared_ptr + m_managedRenderPipelineBridge = nullptr; + size_t m_managedRenderPipelineBridgeGeneration = 1u; + size_t m_environmentGeneration = 1u; +}; + +GraphicsSettingsState& GetGraphicsSettingsState(); + +} // namespace Rendering +} // namespace XCEngine diff --git a/engine/src/Components/LightComponent.cpp b/engine/src/Components/LightComponent.cpp index e537b555..feb7e2f7 100644 --- a/engine/src/Components/LightComponent.cpp +++ b/engine/src/Components/LightComponent.cpp @@ -1,11 +1,20 @@ #include "Components/LightComponent.h" #include +#include #include namespace XCEngine { namespace Components { +namespace { + +float SanitizeNonNegativeFinite(float value, float fallback) { + return std::isfinite(value) && value >= 0.0f ? value : fallback; +} + +} // namespace + void LightComponent::SetIntensity(float value) { m_intensity = std::max(0.0f, value); } @@ -18,6 +27,51 @@ void LightComponent::SetSpotAngle(float value) { m_spotAngle = std::clamp(value, 1.0f, 179.0f); } +void LightComponent::SetDirectionalShadowSamplingSettings( + const Rendering::DirectionalShadowSamplingSettings& value) { + SetDirectionalShadowReceiverDepthBias(value.receiverDepthBias); + SetDirectionalShadowNormalBiasScale(value.normalBiasScale); + SetDirectionalShadowStrength(value.shadowStrength); +} + +void LightComponent::SetDirectionalShadowCasterBiasSettings( + const Rendering::DirectionalShadowCasterBiasSettings& value) { + SetDirectionalShadowDepthBiasFactor(value.depthBiasFactor); + SetDirectionalShadowDepthBiasUnits(value.depthBiasUnits); +} + +void LightComponent::SetDirectionalShadowReceiverDepthBias(float value) { + const Rendering::DirectionalShadowSamplingSettings defaults = {}; + m_directionalShadowSampling.receiverDepthBias = + SanitizeNonNegativeFinite(value, defaults.receiverDepthBias); +} + +void LightComponent::SetDirectionalShadowNormalBiasScale(float value) { + const Rendering::DirectionalShadowSamplingSettings defaults = {}; + m_directionalShadowSampling.normalBiasScale = + SanitizeNonNegativeFinite(value, defaults.normalBiasScale); +} + +void LightComponent::SetDirectionalShadowStrength(float value) { + if (!std::isfinite(value)) { + const Rendering::DirectionalShadowSamplingSettings defaults = {}; + m_directionalShadowSampling.shadowStrength = defaults.shadowStrength; + return; + } + + m_directionalShadowSampling.shadowStrength = std::clamp(value, 0.0f, 1.0f); +} + +void LightComponent::SetDirectionalShadowDepthBiasFactor(float value) { + const Rendering::DirectionalShadowCasterBiasSettings defaults = {}; + m_directionalShadowCasterBias.depthBiasFactor = + SanitizeNonNegativeFinite(value, defaults.depthBiasFactor); +} + +void LightComponent::SetDirectionalShadowDepthBiasUnits(int value) { + m_directionalShadowCasterBias.depthBiasUnits = std::max(0, value); +} + void LightComponent::Serialize(std::ostream& os) const { os << "type=" << static_cast(m_lightType) << ";"; os << "color=" << m_color.r << "," << m_color.g << "," << m_color.b << "," << m_color.a << ";"; @@ -25,6 +79,12 @@ void LightComponent::Serialize(std::ostream& os) const { os << "range=" << m_range << ";"; os << "spotAngle=" << m_spotAngle << ";"; os << "shadows=" << (m_castsShadows ? 1 : 0) << ";"; + os << "overrideDirectionalShadowSettings=" << (m_overrideDirectionalShadowSettings ? 1 : 0) << ";"; + os << "directionalShadowReceiverDepthBias=" << m_directionalShadowSampling.receiverDepthBias << ";"; + os << "directionalShadowNormalBiasScale=" << m_directionalShadowSampling.normalBiasScale << ";"; + os << "directionalShadowStrength=" << m_directionalShadowSampling.shadowStrength << ";"; + os << "directionalShadowDepthBiasFactor=" << m_directionalShadowCasterBias.depthBiasFactor << ";"; + os << "directionalShadowDepthBiasUnits=" << m_directionalShadowCasterBias.depthBiasUnits << ";"; } void LightComponent::Deserialize(std::istream& is) { @@ -56,6 +116,18 @@ void LightComponent::Deserialize(std::istream& is) { SetSpotAngle(std::stof(value)); } else if (key == "shadows") { m_castsShadows = (std::stoi(value) != 0); + } else if (key == "overrideDirectionalShadowSettings") { + m_overrideDirectionalShadowSettings = (std::stoi(value) != 0); + } else if (key == "directionalShadowReceiverDepthBias") { + SetDirectionalShadowReceiverDepthBias(std::stof(value)); + } else if (key == "directionalShadowNormalBiasScale") { + SetDirectionalShadowNormalBiasScale(std::stof(value)); + } else if (key == "directionalShadowStrength") { + SetDirectionalShadowStrength(std::stof(value)); + } else if (key == "directionalShadowDepthBiasFactor") { + SetDirectionalShadowDepthBiasFactor(std::stof(value)); + } else if (key == "directionalShadowDepthBiasUnits") { + SetDirectionalShadowDepthBiasUnits(std::stoi(value)); } } } diff --git a/engine/src/Rendering/GraphicsSettingsState.cpp b/engine/src/Rendering/GraphicsSettingsState.cpp new file mode 100644 index 00000000..0efb13b4 --- /dev/null +++ b/engine/src/Rendering/GraphicsSettingsState.cpp @@ -0,0 +1,42 @@ +#include "Rendering/GraphicsSettingsState.h" + +#include + +namespace XCEngine { +namespace Rendering { + +GraphicsSettingsState& GetGraphicsSettingsState() { + static GraphicsSettingsState s_state; + return s_state; +} + +void GraphicsSettingsState::SetManagedRenderPipelineBridge( + std::shared_ptr bridge) { + m_managedRenderPipelineBridge = std::move(bridge); + ++m_managedRenderPipelineBridgeGeneration; + BumpEnvironmentGeneration(); +} + +void GraphicsSettingsState::ClearManagedRenderPipelineBridge() { + m_managedRenderPipelineBridge.reset(); + ++m_managedRenderPipelineBridgeGeneration; + BumpEnvironmentGeneration(); +} + +void GraphicsSettingsState::SetRenderPipelineAssetDescriptor( + const Pipelines::ManagedRenderPipelineAssetDescriptor& descriptor) { + m_renderPipelineAssetDescriptor = descriptor; + BumpEnvironmentGeneration(); +} + +void GraphicsSettingsState::ClearRenderPipelineAssetDescriptor() { + m_renderPipelineAssetDescriptor = {}; + BumpEnvironmentGeneration(); +} + +void GraphicsSettingsState::BumpEnvironmentGeneration() { + ++m_environmentGeneration; +} + +} // namespace Rendering +} // namespace XCEngine diff --git a/tests/Components/test_camera_light_component.cpp b/tests/Components/test_camera_light_component.cpp index e4eadcd6..02a458d0 100644 --- a/tests/Components/test_camera_light_component.cpp +++ b/tests/Components/test_camera_light_component.cpp @@ -156,7 +156,7 @@ TEST(CameraComponent_Test, DeserializeLegacyColorScalePostProcessFields) { ASSERT_EQ(camera.GetPostProcessPasses().size(), 2u); EXPECT_EQ( camera.GetPostProcessPasses()[0].type, - XCEngine::Rendering::FullscreenPassType::ColorScale); + XCEngine::Rendering::FullscreenPassType::ColorScale); EXPECT_TRUE(camera.IsColorScalePostProcessEnabled()); EXPECT_FLOAT_EQ(camera.GetColorScalePostProcessPasses()[0].x, 0.55f); EXPECT_FLOAT_EQ(camera.GetColorScalePostProcessPasses()[1].y, 0.95f); @@ -193,6 +193,12 @@ TEST(LightComponent_Test, DefaultValues) { EXPECT_FLOAT_EQ(light.GetRange(), 10.0f); EXPECT_FLOAT_EQ(light.GetSpotAngle(), 30.0f); EXPECT_FALSE(light.GetCastsShadows()); + EXPECT_FALSE(light.GetOverridesDirectionalShadowSettings()); + EXPECT_FLOAT_EQ(light.GetDirectionalShadowReceiverDepthBias(), 0.0010f); + EXPECT_FLOAT_EQ(light.GetDirectionalShadowNormalBiasScale(), 2.0f); + EXPECT_FLOAT_EQ(light.GetDirectionalShadowStrength(), 0.85f); + EXPECT_FLOAT_EQ(light.GetDirectionalShadowDepthBiasFactor(), 2.5f); + EXPECT_EQ(light.GetDirectionalShadowDepthBiasUnits(), 4); } TEST(LightComponent_Test, SetterClamping) { @@ -205,6 +211,44 @@ TEST(LightComponent_Test, SetterClamping) { EXPECT_FLOAT_EQ(light.GetIntensity(), 0.0f); EXPECT_FLOAT_EQ(light.GetRange(), 0.001f); EXPECT_FLOAT_EQ(light.GetSpotAngle(), 179.0f); + + light.SetDirectionalShadowReceiverDepthBias(-1.0f); + light.SetDirectionalShadowNormalBiasScale(-2.0f); + light.SetDirectionalShadowStrength(5.0f); + light.SetDirectionalShadowDepthBiasFactor(-3.0f); + light.SetDirectionalShadowDepthBiasUnits(-4); + + EXPECT_FLOAT_EQ(light.GetDirectionalShadowReceiverDepthBias(), 0.0010f); + EXPECT_FLOAT_EQ(light.GetDirectionalShadowNormalBiasScale(), 2.0f); + EXPECT_FLOAT_EQ(light.GetDirectionalShadowStrength(), 1.0f); + EXPECT_FLOAT_EQ(light.GetDirectionalShadowDepthBiasFactor(), 2.5f); + EXPECT_EQ(light.GetDirectionalShadowDepthBiasUnits(), 0); +} + +TEST(LightComponent_Test, SerializeRoundTripPreservesDirectionalShadowOverrides) { + LightComponent source; + source.SetLightType(LightType::Directional); + source.SetCastsShadows(true); + source.SetOverridesDirectionalShadowSettings(true); + source.SetDirectionalShadowReceiverDepthBias(0.0025f); + source.SetDirectionalShadowNormalBiasScale(1.75f); + source.SetDirectionalShadowStrength(0.72f); + source.SetDirectionalShadowDepthBiasFactor(3.5f); + source.SetDirectionalShadowDepthBiasUnits(6); + + std::stringstream stream; + source.Serialize(stream); + + LightComponent target; + target.Deserialize(stream); + + EXPECT_TRUE(target.GetCastsShadows()); + EXPECT_TRUE(target.GetOverridesDirectionalShadowSettings()); + EXPECT_FLOAT_EQ(target.GetDirectionalShadowReceiverDepthBias(), 0.0025f); + EXPECT_FLOAT_EQ(target.GetDirectionalShadowNormalBiasScale(), 1.75f); + EXPECT_FLOAT_EQ(target.GetDirectionalShadowStrength(), 0.72f); + EXPECT_FLOAT_EQ(target.GetDirectionalShadowDepthBiasFactor(), 3.5f); + EXPECT_EQ(target.GetDirectionalShadowDepthBiasUnits(), 6); } } // namespace diff --git a/tests/Rendering/unit/CMakeLists.txt b/tests/Rendering/unit/CMakeLists.txt index fcd0f13c..3d37d57f 100644 --- a/tests/Rendering/unit/CMakeLists.txt +++ b/tests/Rendering/unit/CMakeLists.txt @@ -16,6 +16,7 @@ set(RENDERING_UNIT_TEST_SOURCES test_render_scene_utility.cpp test_render_scene_extractor.cpp test_render_resource_cache.cpp + test_graphics_settings_state.cpp test_render_graph.cpp test_render_graph_recording_context.cpp test_fullscreen_pass_factory.cpp diff --git a/tests/Rendering/unit/test_graphics_settings_state.cpp b/tests/Rendering/unit/test_graphics_settings_state.cpp new file mode 100644 index 00000000..be6c9a4b --- /dev/null +++ b/tests/Rendering/unit/test_graphics_settings_state.cpp @@ -0,0 +1,92 @@ +#include + +#include + +#include +#include + +using namespace XCEngine::Rendering; + +namespace { + +class DummyManagedRenderPipelineBridge final + : public Pipelines::ManagedRenderPipelineBridge { +}; + +class GraphicsSettingsStateScope final { +public: + GraphicsSettingsStateScope() + : m_savedDescriptor( + GetGraphicsSettingsState().GetRenderPipelineAssetDescriptor()) + , m_savedBridge( + GetGraphicsSettingsState().GetManagedRenderPipelineBridge()) { + } + + ~GraphicsSettingsStateScope() { + GraphicsSettingsState& state = GetGraphicsSettingsState(); + if (m_savedBridge != nullptr) { + state.SetManagedRenderPipelineBridge(m_savedBridge); + } else { + state.ClearManagedRenderPipelineBridge(); + } + + if (m_savedDescriptor.IsValid() || + m_savedDescriptor.managedAssetHandle != 0u) { + state.SetRenderPipelineAssetDescriptor(m_savedDescriptor); + } else { + state.ClearRenderPipelineAssetDescriptor(); + } + } + +private: + Pipelines::ManagedRenderPipelineAssetDescriptor m_savedDescriptor = {}; + std::shared_ptr m_savedBridge = + nullptr; +}; + +} // namespace + +TEST(GraphicsSettingsState_Test, StoresRenderPipelineAssetDescriptorAndBumpsEnvironmentGeneration) { + GraphicsSettingsStateScope scope; + GraphicsSettingsState& state = GetGraphicsSettingsState(); + state.ClearRenderPipelineAssetDescriptor(); + + const size_t generationBefore = state.GetEnvironmentGeneration(); + const Pipelines::ManagedRenderPipelineAssetDescriptor descriptor = { + "XCEngine.GameScripts", + "Gameplay", + "ManagedForwardRenderPipelineProbeAsset", + 42u + }; + + state.SetRenderPipelineAssetDescriptor(descriptor); + + const Pipelines::ManagedRenderPipelineAssetDescriptor resolved = + state.GetRenderPipelineAssetDescriptor(); + EXPECT_EQ(resolved.assemblyName, descriptor.assemblyName); + EXPECT_EQ(resolved.namespaceName, descriptor.namespaceName); + EXPECT_EQ(resolved.className, descriptor.className); + EXPECT_EQ(resolved.managedAssetHandle, descriptor.managedAssetHandle); + EXPECT_GT(state.GetEnvironmentGeneration(), generationBefore); +} + +TEST(GraphicsSettingsState_Test, StoresManagedBridgeAndBumpsBridgeAndEnvironmentGenerations) { + GraphicsSettingsStateScope scope; + GraphicsSettingsState& state = GetGraphicsSettingsState(); + state.ClearManagedRenderPipelineBridge(); + + const size_t environmentGenerationBefore = + state.GetEnvironmentGeneration(); + const size_t bridgeGenerationBefore = + state.GetManagedRenderPipelineBridgeGeneration(); + const auto bridge = + std::make_shared(); + + state.SetManagedRenderPipelineBridge(bridge); + + EXPECT_EQ(state.GetManagedRenderPipelineBridge(), bridge); + EXPECT_GT(state.GetManagedRenderPipelineBridgeGeneration(), + bridgeGenerationBefore); + EXPECT_GT(state.GetEnvironmentGeneration(), + environmentGenerationBefore); +} diff --git a/tests/editor/CMakeLists.txt b/tests/editor/CMakeLists.txt index 4e86a808..ec004a80 100644 --- a/tests/editor/CMakeLists.txt +++ b/tests/editor/CMakeLists.txt @@ -50,6 +50,8 @@ set(EDITOR_TEST_SOURCES test_component_editor_registry.cpp test_editor_script_assembly_builder.cpp test_editor_script_assembly_builder_utils.cpp + test_project_graphics_settings.cpp + test_project_file_utils.cpp test_material_inspector_material_state_io.cpp ${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui.cpp ${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui_draw.cpp diff --git a/tests/editor/test_project_file_utils.cpp b/tests/editor/test_project_file_utils.cpp new file mode 100644 index 00000000..4fe37543 --- /dev/null +++ b/tests/editor/test_project_file_utils.cpp @@ -0,0 +1,65 @@ +#include + +#include "Utils/ProjectFileUtils.h" + +#include +#include + +namespace XCEngine::Editor::ProjectFileUtils { +namespace { + +class ProjectFileUtilsTest : public ::testing::Test { +protected: + void SetUp() override { + const auto stamp = std::chrono::steady_clock::now() + .time_since_epoch() + .count(); + m_projectRoot = + std::filesystem::temp_directory_path() / + ("xc_project_file_utils_" + std::to_string(stamp)); + std::filesystem::create_directories(m_projectRoot); + } + + void TearDown() override { + std::error_code ec; + std::filesystem::remove_all(m_projectRoot, ec); + } + + std::filesystem::path m_projectRoot; +}; + +TEST_F(ProjectFileUtilsTest, SavesAndLoadsGraphicsSettingsRoundTrip) { + GraphicsSettingsDescriptor savedDescriptor; + savedDescriptor.renderPipelineAssetAssembly = "GameScripts"; + savedDescriptor.renderPipelineAssetNamespace = "Gameplay"; + savedDescriptor.renderPipelineAssetClass = + "ManagedForwardRenderPipelineProbeAsset"; + + ASSERT_TRUE(SaveProjectGraphicsSettings( + m_projectRoot.string(), + savedDescriptor)); + EXPECT_TRUE(std::filesystem::exists( + GetGraphicsSettingsFilePath(m_projectRoot.string()))); + + const auto loadedDescriptor = + LoadProjectGraphicsSettings(m_projectRoot.string()); + ASSERT_TRUE(loadedDescriptor.has_value()); + EXPECT_EQ( + loadedDescriptor->renderPipelineAssetAssembly, + savedDescriptor.renderPipelineAssetAssembly); + EXPECT_EQ( + loadedDescriptor->renderPipelineAssetNamespace, + savedDescriptor.renderPipelineAssetNamespace); + EXPECT_EQ( + loadedDescriptor->renderPipelineAssetClass, + savedDescriptor.renderPipelineAssetClass); + EXPECT_TRUE(loadedDescriptor->HasRenderPipelineAsset()); +} + +TEST_F(ProjectFileUtilsTest, MissingGraphicsSettingsFileReturnsNullopt) { + EXPECT_FALSE( + LoadProjectGraphicsSettings(m_projectRoot.string()).has_value()); +} + +} // namespace +} // namespace XCEngine::Editor::ProjectFileUtils diff --git a/tests/editor/test_project_graphics_settings.cpp b/tests/editor/test_project_graphics_settings.cpp new file mode 100644 index 00000000..8ed5b7e6 --- /dev/null +++ b/tests/editor/test_project_graphics_settings.cpp @@ -0,0 +1,113 @@ +#include + +#include "Utils/ProjectFileUtils.h" +#include "Utils/ProjectGraphicsSettings.h" + +#include +#include + +namespace XCEngine::Editor::ProjectGraphicsSettings { +namespace { + +class ProjectGraphicsSettingsTest : public ::testing::Test { +protected: + void SetUp() override { + const auto stamp = std::chrono::steady_clock::now() + .time_since_epoch() + .count(); + m_projectRoot = + std::filesystem::temp_directory_path() / + ("xc_project_graphics_settings_" + std::to_string(stamp)); + std::filesystem::create_directories(m_projectRoot); + } + + void TearDown() override { + std::error_code ec; + std::filesystem::remove_all(m_projectRoot, ec); + } + + std::filesystem::path m_projectRoot; + ::XCEngine::Rendering::GraphicsSettingsState m_state; +}; + +TEST_F(ProjectGraphicsSettingsTest, BuildsDescriptorFromNativeSelection) { + ::XCEngine::Rendering::Pipelines::ManagedRenderPipelineAssetDescriptor + nativeDescriptor = {}; + nativeDescriptor.assemblyName = "GameScripts"; + nativeDescriptor.namespaceName = "Gameplay"; + nativeDescriptor.className = "ManagedForwardRenderPipelineProbeAsset"; + nativeDescriptor.managedAssetHandle = 77u; + m_state.SetRenderPipelineAssetDescriptor(nativeDescriptor); + + const auto descriptor = BuildDescriptor(m_state); + EXPECT_EQ(descriptor.renderPipelineAssetAssembly, "GameScripts"); + EXPECT_EQ(descriptor.renderPipelineAssetNamespace, "Gameplay"); + EXPECT_EQ( + descriptor.renderPipelineAssetClass, + "ManagedForwardRenderPipelineProbeAsset"); +} + +TEST_F(ProjectGraphicsSettingsTest, SavesNativeSelectionIntoProjectSettingsFile) { + ::XCEngine::Rendering::Pipelines::ManagedRenderPipelineAssetDescriptor + nativeDescriptor = {}; + nativeDescriptor.assemblyName = "GameScripts"; + nativeDescriptor.namespaceName = "Gameplay"; + nativeDescriptor.className = "ManagedForwardRenderPipelineProbeAsset"; + nativeDescriptor.managedAssetHandle = 88u; + m_state.SetRenderPipelineAssetDescriptor(nativeDescriptor); + + ASSERT_TRUE(SaveSelection(m_projectRoot.string(), m_state)); + + const auto loaded = + ProjectFileUtils::LoadProjectGraphicsSettings(m_projectRoot.string()); + ASSERT_TRUE(loaded.has_value()); + EXPECT_EQ(loaded->renderPipelineAssetAssembly, "GameScripts"); + EXPECT_EQ(loaded->renderPipelineAssetNamespace, "Gameplay"); + EXPECT_EQ( + loaded->renderPipelineAssetClass, + "ManagedForwardRenderPipelineProbeAsset"); +} + +TEST_F( + ProjectGraphicsSettingsTest, + AppliesProjectSettingsSelectionIntoNativeState) { + ProjectFileUtils::GraphicsSettingsDescriptor savedDescriptor; + savedDescriptor.renderPipelineAssetAssembly = "GameScripts"; + savedDescriptor.renderPipelineAssetNamespace = "Gameplay"; + savedDescriptor.renderPipelineAssetClass = + "ManagedForwardRenderPipelineProbeAsset"; + ASSERT_TRUE(ProjectFileUtils::SaveProjectGraphicsSettings( + m_projectRoot.string(), + savedDescriptor)); + + ApplySelection(m_projectRoot.string(), m_state); + + const auto nativeDescriptor = m_state.GetRenderPipelineAssetDescriptor(); + EXPECT_EQ(nativeDescriptor.assemblyName, "GameScripts"); + EXPECT_EQ(nativeDescriptor.namespaceName, "Gameplay"); + EXPECT_EQ( + nativeDescriptor.className, + "ManagedForwardRenderPipelineProbeAsset"); + EXPECT_EQ(nativeDescriptor.managedAssetHandle, 0u); +} + +TEST_F( + ProjectGraphicsSettingsTest, + ClearsNativeSelectionWhenProjectSettingsFileIsMissing) { + ::XCEngine::Rendering::Pipelines::ManagedRenderPipelineAssetDescriptor + nativeDescriptor = {}; + nativeDescriptor.assemblyName = "GameScripts"; + nativeDescriptor.className = "ManagedForwardRenderPipelineProbeAsset"; + nativeDescriptor.managedAssetHandle = 99u; + m_state.SetRenderPipelineAssetDescriptor(nativeDescriptor); + + ApplySelection(m_projectRoot.string(), m_state); + + EXPECT_FALSE(m_state.GetRenderPipelineAssetDescriptor().IsValid()); + EXPECT_EQ( + m_state.GetRenderPipelineAssetDescriptor().managedAssetHandle, + 0u); +} + +} // namespace +} // namespace XCEngine::Editor::ProjectGraphicsSettings