diff --git a/editor/README.md b/editor/README.md
index 3977c85d..e7d4792b 100644
--- a/editor/README.md
+++ b/editor/README.md
@@ -1,8 +1,10 @@
# XCEditor
-`editor/` 是 XCEngine 当前随仓库维护的桌面编辑器模块。它不是一套独立渲染器,而是 `D3D12` 宿主应用,用来承接引擎 `Rendering + RHI + Scene + Scripting` 主链。
+`editor/` 是 XCEngine 当前随仓库维护的桌面编辑器模块。它不是第二套独立渲染器,而是 `D3D12` 宿主应用,用来承接引擎 `Rendering + RHI + Scene + Scripting` 主链。
-当前 editor 已经具备:
+如果你想了解整个工作区,先看仓库根目录的 [README.md](../README.md);如果你要直接改 editor 或相关引擎接线,再看 [AGENT.md](../AGENT.md)。
+
+## 当前能力
- Scene / Game viewport 离屏渲染接入
- object-id picking 与选中描边
@@ -10,20 +12,22 @@
- 项目根目录解析与 `Project.xcproject` 加载
- `Assets + .meta + Library` 风格项目目录接入
- `ScriptComponent` 的脚本类与字段编辑入口
+- 项目脚本程序集重建与运行时重载入口
## 当前定位
-如果你想理解当前 editor,先把它当成三层:
+理解当前 editor,推荐把它拆成四层:
1. `Win32 + D3D12` 宿主窗口与 ImGui backend
-2. `ViewportHostService` 对引擎渲染链路的接线
+2. `ViewportHostService` 以及 Scene View helper 对引擎渲染链路的接线
3. `panels/`、`Managers/`、`ComponentEditors/` 这些编辑器业务层
+4. `Scripting/` 对项目脚本程序集和运行时状态的桥接
-当前不应再把 editor 视为旧式“UI sample”。它已经是引擎工作区的正式入口之一。
+当前不要再把 editor 当成“旧式 UI sample”。它已经是引擎工作区的正式入口之一。
## 构建
-推荐直接在仓库根目录构建,而不是单独进入 `editor/` 目录。
+推荐始终在仓库根目录构建,而不是单独进入 `editor/` 目录。
### 前置要求
@@ -32,19 +36,13 @@
- CMake 3.15+
- Vulkan SDK
-如果需要启用 Mono 脚本运行时,还需要:
+如果要启用 Mono 脚本运行时,还需要:
- .NET SDK
-- `参考/Fermion/Fermion/external/mono` 下的 Mono 依赖
+- `参考/Fermion/Fermion/external/mono` 下可用的 Mono 依赖
### 配置
-```bash
-cmake -S .. -B ..\build -A x64
-```
-
-更常见的做法是直接在仓库根目录运行:
-
```bash
cmake -S . -B build -A x64
```
@@ -85,48 +83,99 @@ cmake --build build --config Debug --target XCEditor
cmake --build build --config Debug --target xcengine_project_managed_assemblies
```
-该 target 会把程序集放到:
+成功重建后,项目脚本程序集目录通常应包含:
- `project/Library/ScriptAssemblies/XCEngine.ScriptCore.dll`
- `project/Library/ScriptAssemblies/GameScripts.dll`
- `project/Library/ScriptAssemblies/mscorlib.dll`
+当前工作树如果暂时缺少 `XCEngine.ScriptCore.dll`,通常说明项目脚本程序集还没有按最新状态完成重建,而不是 editor 只需要两个程序集。
+
## 当前目录结构
```text
editor/
-├── CMakeLists.txt
-├── README.md
-├── resources/
-│ └── Icons/
-├── src/
-│ ├── Actions/ # 编辑器动作路由
-│ ├── Commands/ # 命令与实体操作
-│ ├── ComponentEditors/ # Inspector 组件编辑器
-│ ├── Core/ # 应用生命周期、日志、项目根解析、撤销等
-│ ├── Layers/ # EditorLayer 等高层组装
-│ ├── Layout/
-│ ├── Managers/ # SceneManager / ProjectManager
-│ ├── panels/ # Hierarchy / Scene / Game / Inspector / Project / Console
-│ ├── Platform/ # Win32 host、D3D12 backend 辅助
-│ ├── UI/ # ImGui bridge 与通用 widget
-│ ├── Utils/
-│ ├── Viewport/
-│ │ ├── Passes/ # editor viewport overlay pass
-│ │ ├── SceneViewportOverlayBuilder.*
-│ │ ├── SceneViewportPicker.*
-│ │ ├── SceneViewportMoveGizmo.*
-│ │ ├── SceneViewportRotateGizmo.*
-│ │ ├── SceneViewportScaleGizmo.*
-│ │ ├── ViewportHostRenderFlowUtils.h
-│ │ └── ViewportHostService.h
-│ ├── Application.cpp
-│ ├── Application.h
-│ ├── EditorApp.rc
-│ ├── Theme.cpp
-│ ├── Theme.h
-│ └── main.cpp
-└── bin/
+|- CMakeLists.txt
+|- README.md
+|- bin/ # 输出目录,产物名仍为 XCEngine.exe
+|- resources/
+| `- Icons/
+`- src/
+ |- Actions/ # 编辑器动作路由
+ |- Commands/ # 命令与实体操作
+ |- ComponentEditors/ # Inspector 组件编辑器
+ |- Core/ # 生命周期、日志、项目根解析、撤销、play session
+ |- Layers/ # EditorLayer 等高层组装
+ |- Layout/
+ |- Managers/ # SceneManager / ProjectManager / SelectionManager
+ |- panels/
+ | |- ConsolePanel.cpp
+ | |- ConsolePanel.h
+ | |- GameViewPanel.cpp
+ | |- GameViewPanel.h
+ | |- HierarchyPanel.cpp
+ | |- HierarchyPanel.h
+ | |- InspectorPanel.cpp
+ | |- InspectorPanel.h
+ | |- MenuBar.cpp
+ | |- MenuBar.h
+ | |- Panel.cpp
+ | |- Panel.h
+ | |- PanelCollection.h
+ | |- ProjectPanel.cpp
+ | |- ProjectPanel.h
+ | |- SceneViewPanel.cpp
+ | |- SceneViewPanel.h
+ | `- ViewportPanelContent.h
+ |- Platform/ # Win32 host、D3D12 backend 辅助
+ |- Scripting/ # 项目脚本程序集构建与运行时状态
+ |- UI/ # ImGui bridge 与通用 widget
+ |- Utils/
+ |- Viewport/
+ | |- Passes/
+ | | |- SceneViewportEditorOverlayPass.*
+ | | |- SceneViewportGridPass.*
+ | | `- SceneViewportSelectionOutlinePass.*
+ | |- IViewportHostService.h
+ | |- SceneViewportCameraController.h
+ | |- SceneViewportChrome.*
+ | |- SceneViewportEditorModes.h
+ | |- SceneViewportEditorOverlayData.h
+ | |- SceneViewportHudOverlay.*
+ | |- SceneViewportInteractionActions.*
+ | |- SceneViewportInteractionFrame.h
+ | |- SceneViewportInteractionResolver.*
+ | |- SceneViewportMath.h
+ | |- SceneViewportMoveGizmo.*
+ | |- SceneViewportNavigation.h
+ | |- SceneViewportOrientationGizmo.*
+ | |- SceneViewportOverlayBuilder.*
+ | |- SceneViewportOverlayFrameCache.*
+ | |- SceneViewportOverlayHandleBuilder.h
+ | |- SceneViewportOverlayHitTester.h
+ | |- SceneViewportOverlayProviders.*
+ | |- SceneViewportOverlaySpriteResources.*
+ | |- SceneViewportPassSpecs.h
+ | |- SceneViewportPicker.*
+ | |- SceneViewportRenderPlan.h
+ | |- SceneViewportResourcePaths.h
+ | |- SceneViewportRotateGizmo.*
+ | |- SceneViewportScaleGizmo.*
+ | |- SceneViewportShaderPaths.h # 兼容 include,真实 owner 已转到 ResourcePaths
+ | |- SceneViewportTransformGizmoCoordinator.*
+ | |- SceneViewportTransformGizmoFrameBuilder.h
+ | |- ViewportHostRenderFlowUtils.h
+ | |- ViewportHostRenderTargets.h
+ | |- ViewportHostService.h
+ | |- ViewportHostSurfaceUtils.h
+ | `- ViewportObjectIdPicker.h
+ |- Application.cpp
+ |- Application.h
+ |- EditorApp.rc
+ |- EditorResources.h
+ |- Theme.cpp
+ |- Theme.h
+ `- main.cpp
```
## 关键模块
@@ -140,7 +189,7 @@ editor/
- editor 初始化与关闭
- resource root 设置
-- scripting runtime 初始化
+- scripting runtime 初始化与重载
- ImGui backend 初始化
- `ViewportHostService` 接线
@@ -148,25 +197,35 @@ editor/
- `src/Core/ProjectRootResolver.h`
- `src/Utils/ProjectFileUtils.h`
+- `src/Managers/ProjectManager.*`
负责:
- 自动识别仓库内 `project/`
- 解析 `--project`
- 读写 `Project.xcproject`
+- 当前 Project 面板目录与资源操作
### Viewport
- `src/Viewport/ViewportHostService.h`
-- `src/Viewport/ViewportHostRenderFlowUtils.h`
+- `src/Viewport/SceneViewportChrome.h`
+- `src/Viewport/SceneViewportInteractionFrame.h`
+- `src/Viewport/SceneViewportNavigation.h`
+- `src/Viewport/SceneViewportTransformGizmoCoordinator.h`
+- `src/Viewport/SceneViewportRenderPlan.h`
- `src/Viewport/SceneViewportOverlayBuilder.*`
+- `src/Viewport/SceneViewportOverlaySpriteResources.*`
+- `src/Viewport/SceneViewportResourcePaths.h`
- `src/Viewport/Passes/SceneViewportEditorOverlayPass.*`
负责:
-- 组装 scene/game viewport 渲染请求
+- 组装 Scene / Game viewport 渲染请求
- 把 editor overlay 接入 `CameraRenderRequest::overlayPasses`
-- object-id picking、outline、overlay pass 等 editor 视口能力
+- 处理 Scene View 的工具切换、hover / click 解析与导航输入
+- 维护 gizmo overlay state、handle hit-test、HUD overlay、sprite 资源缓存与 presentation 刷新
+- 提供 object-id picking、outline、grid 等 editor 视口能力
### Panels
@@ -181,12 +240,29 @@ editor/
### Component Editors
-`ComponentEditors/` 当前不仅负责基础组件,也已经包含 `ScriptComponent` 的 Inspector 编辑入口。
+`ComponentEditors/` 不只负责基础组件,也已经包含:
+
+- `MeshFilterComponentEditor`
+- `MeshRendererComponentEditor`
+- `ScriptComponentEditor`
+- `AssetReferenceEditorUtils`
+
+### Scripting
+
+- `src/Scripting/EditorScriptAssemblyBuilder.*`
+- `src/Scripting/EditorScriptAssemblyBuilderUtils.h`
+- `src/Scripting/EditorScriptRuntimeStatus.h`
+
+负责:
+
+- 从项目脚本资产构建程序集
+- 汇报脚本运行时状态
+- 为 Inspector / 菜单动作提供“能否重建脚本程序集”的判断依据
## 开发约束
- editor 是宿主,不是第二套 renderer。
-- 新的世界空间 overlay / gizmo,不应继续堆到 ImGui world draw 路径。
+- 新的世界空间 overlay / gizmo,不应继续堆到旧的 ImGui world draw 路径。
- viewport 相关问题优先检查 `engine/Rendering`、`RenderSurface` 与 `ViewportHostService` 的接线,而不是直接在 panel 里复制渲染逻辑。
- 与项目资源、脚本程序集、`.meta`、`Library` 相关的问题,不要假设 editor 仍处于“无工程状态”的旧结构。
@@ -197,7 +273,7 @@ cmake --build build --config Debug --target editor_tests
cmake --build build --config Debug --target rendering_phase_regression
```
-如果改动影响脚本类发现或 Inspector 脚本字段编辑,再补:
+如果改动影响脚本类发现、脚本程序集重建或 Inspector 脚本字段编辑,再补:
```bash
cmake --build build --config Debug --target xcengine_project_managed_assemblies
diff --git a/editor/resources/xcui_demo/xcui_demo_theme.xctheme b/editor/resources/xcui_demo/xcui_demo_theme.xctheme
new file mode 100644
index 00000000..c4c1b784
--- /dev/null
+++ b/editor/resources/xcui_demo/xcui_demo_theme.xctheme
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/editor/resources/xcui_demo/xcui_demo_view.xcui b/editor/resources/xcui_demo/xcui_demo_view.xcui
new file mode 100644
index 00000000..b151dbf9
--- /dev/null
+++ b/editor/resources/xcui_demo/xcui_demo_view.xcui
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/editor/src/Actions/EditorActions.h b/editor/src/Actions/EditorActions.h
index a3310fd7..01f19f10 100644
--- a/editor/src/Actions/EditorActions.h
+++ b/editor/src/Actions/EditorActions.h
@@ -32,8 +32,16 @@ inline ActionBinding MakeRebuildScriptsAction(bool enabled = true) {
return MakeAction("Rebuild Script Assemblies", nullptr, false, enabled);
}
-inline ActionBinding MakeMigrateSceneAssetReferencesAction(bool enabled = true) {
- return MakeAction("Migrate Scene AssetRefs", nullptr, false, enabled);
+inline ActionBinding MakeReimportSelectedAssetAction(bool enabled = true) {
+ return MakeAction("Reimport Selected Asset", nullptr, false, enabled);
+}
+
+inline ActionBinding MakeReimportAllAssetsAction(bool enabled = true) {
+ return MakeAction("Reimport All Assets", nullptr, false, enabled);
+}
+
+inline ActionBinding MakeClearLibraryAction(bool enabled = true) {
+ return MakeAction("Clear Library", nullptr, false, enabled);
}
inline ActionBinding MakeNewSceneAction(bool enabled = true) {
diff --git a/editor/src/Actions/MainMenuActionRouter.h b/editor/src/Actions/MainMenuActionRouter.h
index 3070528b..0f013715 100644
--- a/editor/src/Actions/MainMenuActionRouter.h
+++ b/editor/src/Actions/MainMenuActionRouter.h
@@ -38,8 +38,16 @@ inline void ExecuteRebuildScriptAssemblies(IEditorContext& context) {
Commands::RebuildScriptAssemblies(context);
}
-inline void ExecuteMigrateSceneAssetReferences(IEditorContext& context) {
- Commands::MigrateSceneAssetReferences(context);
+inline void ExecuteReimportSelectedAsset(IEditorContext& context) {
+ Commands::ReimportSelectedAsset(context);
+}
+
+inline void ExecuteReimportAllAssets(IEditorContext& context) {
+ Commands::ReimportAllAssets(context);
+}
+
+inline void ExecuteClearLibrary(IEditorContext& context) {
+ Commands::ClearLibrary(context);
}
inline void ExecuteOpenScene(IEditorContext& context) {
@@ -151,10 +159,6 @@ inline void DrawFileMenuActions(IEditorContext& context) {
DrawMenuAction(MakeSaveSceneAction(canEditDocuments), [&]() { ExecuteSaveScene(context); });
DrawMenuAction(MakeSaveSceneAsAction(canEditDocuments), [&]() { ExecuteSaveSceneAs(context); });
DrawMenuSeparator();
- DrawMenuAction(
- MakeMigrateSceneAssetReferencesAction(Commands::CanMigrateSceneAssetReferences(context)),
- [&]() { ExecuteMigrateSceneAssetReferences(context); });
- DrawMenuSeparator();
DrawMenuAction(MakeExitAction(), [&]() { RequestEditorExit(context); });
}
@@ -178,6 +182,19 @@ inline void DrawScriptsMenuActions(IEditorContext& context) {
[&]() { ExecuteRebuildScriptAssemblies(context); });
}
+inline void DrawAssetsMenuActions(IEditorContext& context) {
+ DrawMenuAction(
+ MakeReimportSelectedAssetAction(Commands::CanReimportSelectedAsset(context)),
+ [&]() { ExecuteReimportSelectedAsset(context); });
+ DrawMenuAction(
+ MakeReimportAllAssetsAction(Commands::CanReimportAllAssets(context)),
+ [&]() { ExecuteReimportAllAssets(context); });
+ DrawMenuSeparator();
+ DrawMenuAction(
+ MakeClearLibraryAction(Commands::CanClearLibrary(context)),
+ [&]() { ExecuteClearLibrary(context); });
+}
+
inline void DrawViewMenuActions(IEditorContext& context) {
DrawMenuAction(MakeResetLayoutAction(), [&]() { RequestDockLayoutReset(context); });
}
@@ -201,6 +218,9 @@ inline void DrawMainMenuBar(IEditorContext& context, UI::DeferredPopupState& abo
UI::DrawMenuScope("Edit", [&]() {
DrawEditActions(context);
});
+ UI::DrawMenuScope("Assets", [&]() {
+ DrawAssetsMenuActions(context);
+ });
UI::DrawMenuScope("Run", [&]() {
DrawRunMenuActions(context);
});
diff --git a/editor/src/Application.cpp b/editor/src/Application.cpp
index 5cf8644f..9985c9dc 100644
--- a/editor/src/Application.cpp
+++ b/editor/src/Application.cpp
@@ -145,6 +145,40 @@ bool Application::RebuildScriptingAssemblies() {
#endif
}
+bool Application::CanReimportProjectAsset(const std::string& assetPath) const {
+ if (assetPath.empty()) {
+ return false;
+ }
+
+ return ::XCEngine::Resources::ResourceManager::Get().CanReimportProjectAsset(assetPath.c_str());
+}
+
+bool Application::ReimportProjectAsset(const std::string& assetPath) {
+ if (assetPath.empty()) {
+ return false;
+ }
+
+ auto& resourceManager = ::XCEngine::Resources::ResourceManager::Get();
+ resourceManager.Initialize();
+ return resourceManager.ReimportProjectAsset(assetPath.c_str());
+}
+
+bool Application::ReimportAllProjectAssets() {
+ auto& resourceManager = ::XCEngine::Resources::ResourceManager::Get();
+ resourceManager.Initialize();
+ return resourceManager.RebuildProjectAssetCache();
+}
+
+bool Application::ClearProjectLibrary() {
+ auto& resourceManager = ::XCEngine::Resources::ResourceManager::Get();
+ resourceManager.Initialize();
+ return resourceManager.ClearProjectLibraryCache();
+}
+
+std::string Application::GetProjectLibraryRoot() const {
+ return ::XCEngine::Resources::ResourceManager::Get().GetProjectLibraryRoot().CStr();
+}
+
bool Application::InitializeWindowRenderer(HWND hwnd) {
RECT clientRect = {};
if (!GetClientRect(hwnd, &clientRect)) {
@@ -227,13 +261,13 @@ void Application::RenderEditorFrame() {
m_imguiBackend.BeginFrame();
m_viewportHostService.BeginFrame();
- m_layerStack.onImGuiRender();
+ m_layerStack.onUIRender();
UpdateWindowTitle();
ImGui::Render();
m_windowRenderer.Render(
m_imguiBackend,
kClearColor,
- [this](const Rendering::RenderContext& renderContext) {
+ [this](const Rendering::RenderContext& renderContext, const Rendering::RenderSurface&) {
if (m_editorContext) {
m_viewportHostService.RenderRequestedViewports(*m_editorContext, renderContext);
}
diff --git a/editor/src/Application.h b/editor/src/Application.h
index 4490ea92..869fb028 100644
--- a/editor/src/Application.h
+++ b/editor/src/Application.h
@@ -7,6 +7,7 @@
#include "Viewport/ViewportHostService.h"
#include
+#include
#include
#include
#include
@@ -42,8 +43,14 @@ public:
bool SwitchProject(const std::string& projectPath);
bool ReloadScriptingRuntime();
bool RebuildScriptingAssemblies();
+ bool CanReimportProjectAsset(const std::string& assetPath) const;
+ bool ReimportProjectAsset(const std::string& assetPath);
+ bool ReimportAllProjectAssets();
+ bool ClearProjectLibrary();
+ std::string GetProjectLibraryRoot() const;
void SaveProjectState();
Rendering::RenderContext GetMainRenderContext() const { return m_windowRenderer.GetRenderContext(); }
+ const Rendering::RenderSurface* GetMainRenderSurface() const { return m_windowRenderer.GetCurrentRenderSurface(); }
RHI::RHIDevice* GetMainRHIDevice() const { return m_windowRenderer.GetRHIDevice(); }
RHI::RHISwapChain* GetMainSwapChain() const { return m_windowRenderer.GetSwapChain(); }
IViewportHostService& GetViewportHostService() { return m_viewportHostService; }
diff --git a/editor/src/Commands/ProjectCommands.h b/editor/src/Commands/ProjectCommands.h
index 094f2c1c..29021681 100644
--- a/editor/src/Commands/ProjectCommands.h
+++ b/editor/src/Commands/ProjectCommands.h
@@ -12,6 +12,8 @@
#include "Utils/FileDialogUtils.h"
#include "Utils/ProjectFileUtils.h"
+#include
+
#include
#include
#include
@@ -331,16 +333,79 @@ inline bool RebuildScriptAssemblies(IEditorContext& context) {
return rebuilt;
}
-inline bool CanMigrateSceneAssetReferences(const IEditorContext& context) {
+inline bool CanManageProjectAssetCache(const IEditorContext& context) {
return IsProjectDocumentEditingAllowed(context) && !context.GetProjectPath().empty();
}
-inline IProjectManager::SceneAssetReferenceMigrationReport MigrateSceneAssetReferences(IEditorContext& context) {
- if (!CanMigrateSceneAssetReferences(context)) {
- return {};
+inline Resources::ResourceManager& GetProjectResourceManager(IEditorContext& context) {
+ auto& resourceManager = Resources::ResourceManager::Get();
+ resourceManager.Initialize();
+
+ if (!context.GetProjectPath().empty() &&
+ std::string(resourceManager.GetResourceRoot().CStr()) != context.GetProjectPath()) {
+ resourceManager.SetResourceRoot(context.GetProjectPath().c_str());
}
- return context.GetProjectManager().MigrateSceneAssetReferences();
+ return resourceManager;
+}
+
+inline bool CanReimportSelectedAsset(IEditorContext& context) {
+ if (!CanManageProjectAssetCache(context)) {
+ return false;
+ }
+
+ const AssetItemPtr selectedItem = context.GetProjectManager().GetSelectedItem();
+ return selectedItem != nullptr &&
+ !selectedItem->isFolder &&
+ GetProjectResourceManager(context).CanReimportProjectAsset(selectedItem->fullPath.c_str());
+}
+
+inline bool ReimportSelectedAsset(IEditorContext& context) {
+ if (!CanReimportSelectedAsset(context)) {
+ return false;
+ }
+
+ const AssetItemPtr selectedItem = context.GetProjectManager().GetSelectedItem();
+ const bool reimported = GetProjectResourceManager(context).ReimportProjectAsset(selectedItem->fullPath.c_str());
+ if (reimported) {
+ context.GetProjectManager().RefreshCurrentFolder();
+ }
+
+ return reimported;
+}
+
+inline bool CanReimportAllAssets(const IEditorContext& context) {
+ return CanManageProjectAssetCache(context);
+}
+
+inline bool ReimportAllAssets(IEditorContext& context) {
+ if (!CanReimportAllAssets(context)) {
+ return false;
+ }
+
+ const bool reimported = GetProjectResourceManager(context).RebuildProjectAssetCache();
+ if (reimported) {
+ context.GetProjectManager().RefreshCurrentFolder();
+ }
+
+ return reimported;
+}
+
+inline bool CanClearLibrary(const IEditorContext& context) {
+ return CanManageProjectAssetCache(context);
+}
+
+inline bool ClearLibrary(IEditorContext& context) {
+ if (!CanClearLibrary(context)) {
+ return false;
+ }
+
+ const bool cleared = GetProjectResourceManager(context).ClearProjectLibraryCache();
+ if (cleared) {
+ context.GetProjectManager().RefreshCurrentFolder();
+ }
+
+ return cleared;
}
inline bool SwitchProject(IEditorContext& context, const std::string& projectPath) {
diff --git a/editor/src/Core/EditorConsoleSink.cpp b/editor/src/Core/EditorConsoleSink.cpp
index 11140be5..41fa9b40 100644
--- a/editor/src/Core/EditorConsoleSink.cpp
+++ b/editor/src/Core/EditorConsoleSink.cpp
@@ -6,8 +6,7 @@ namespace Debug {
EditorConsoleSink* EditorConsoleSink::s_instance = nullptr;
EditorConsoleSink* EditorConsoleSink::GetInstance() {
- static EditorConsoleSink fallbackInstance;
- return s_instance ? s_instance : &fallbackInstance;
+ return s_instance;
}
EditorConsoleSink::EditorConsoleSink() {
diff --git a/editor/src/Core/IProjectManager.h b/editor/src/Core/IProjectManager.h
index b4b63e7c..41ee24e0 100644
--- a/editor/src/Core/IProjectManager.h
+++ b/editor/src/Core/IProjectManager.h
@@ -11,13 +11,6 @@ namespace Editor {
class IProjectManager {
public:
- struct SceneAssetReferenceMigrationReport {
- size_t scannedSceneCount = 0;
- size_t migratedSceneCount = 0;
- size_t unchangedSceneCount = 0;
- size_t failedSceneCount = 0;
- };
-
virtual ~IProjectManager() = default;
virtual const std::vector& GetCurrentItems() const = 0;
@@ -47,7 +40,6 @@ public:
virtual bool DeleteItem(const std::string& fullPath) = 0;
virtual bool MoveItem(const std::string& sourceFullPath, const std::string& destFolderFullPath) = 0;
virtual bool RenameItem(const std::string& sourceFullPath, const std::string& newName) = 0;
- virtual SceneAssetReferenceMigrationReport MigrateSceneAssetReferences() = 0;
virtual const std::string& GetProjectPath() const = 0;
};
diff --git a/editor/src/Core/ISceneManager.h b/editor/src/Core/ISceneManager.h
index f51cb374..db24bfe3 100644
--- a/editor/src/Core/ISceneManager.h
+++ b/editor/src/Core/ISceneManager.h
@@ -14,6 +14,30 @@ class Scene;
namespace Editor {
+struct SceneLoadProgressSnapshot {
+ std::uint64_t revision = 0;
+ bool inProgress = false;
+ bool success = false;
+ bool firstFramePresented = false;
+ bool streamingCompleted = false;
+ bool interactive = false;
+ bool startupScene = false;
+ std::string operation;
+ std::string scenePath;
+ std::string message;
+ std::uint64_t startedAtMs = 0;
+ std::uint64_t structureReadyAtMs = 0;
+ std::uint64_t firstFrameAtMs = 0;
+ std::uint64_t streamingCompletedAtMs = 0;
+ std::uint64_t interactiveAtMs = 0;
+ std::uint32_t currentPendingAsyncLoads = 0;
+ std::uint32_t peakPendingAsyncLoads = 0;
+
+ bool HasValue() const {
+ return revision != 0;
+ }
+};
+
class ISceneManager {
public:
virtual ~ISceneManager() = default;
@@ -42,6 +66,8 @@ public:
virtual const std::string& GetCurrentSceneName() const = 0;
virtual ::XCEngine::Components::Scene* GetScene() = 0;
virtual const ::XCEngine::Components::Scene* GetScene() const = 0;
+ virtual SceneLoadProgressSnapshot GetSceneLoadProgress() const = 0;
+ virtual void NotifySceneViewportFramePresented(std::uint32_t pendingAsyncLoads) = 0;
virtual SceneSnapshot CaptureSceneSnapshot() const = 0;
virtual bool RestoreSceneSnapshot(const SceneSnapshot& snapshot) = 0;
virtual void CreateDemoScene() = 0;
diff --git a/editor/src/Layers/EditorLayer.cpp b/editor/src/Layers/EditorLayer.cpp
index fd87563a..3cc17ff3 100644
--- a/editor/src/Layers/EditorLayer.cpp
+++ b/editor/src/Layers/EditorLayer.cpp
@@ -32,7 +32,7 @@ void EditorLayer::onEvent(void* event) {
m_workspace.DispatchEvent(event);
}
-void EditorLayer::onImGuiRender() {
+void EditorLayer::onUIRender() {
m_workspace.Render();
}
diff --git a/editor/src/Layers/EditorLayer.h b/editor/src/Layers/EditorLayer.h
index 89a21376..270715c7 100644
--- a/editor/src/Layers/EditorLayer.h
+++ b/editor/src/Layers/EditorLayer.h
@@ -20,7 +20,7 @@ public:
void onDetach() override;
void onUpdate(float dt) override;
void onEvent(void* event) override;
- void onImGuiRender() override;
+ void onUIRender() override;
void SetContext(std::shared_ptr context);
diff --git a/editor/src/Layout/DockLayoutController.h b/editor/src/Layout/DockLayoutController.h
index 3ab64eb2..8c32d985 100644
--- a/editor/src/Layout/DockLayoutController.h
+++ b/editor/src/Layout/DockLayoutController.h
@@ -6,7 +6,6 @@
#include "UI/DockHostStyle.h"
#include "UI/DockTabBarChrome.h"
-#include
#include
#include
@@ -70,9 +69,7 @@ public:
}
if (m_layoutDirty) {
- if (m_forceRebuild || !HasSavedDockLayout()) {
- BuildDefaultLayout(dockspaceId, viewport->WorkSize);
- }
+ BuildDefaultLayout(dockspaceId, viewport->WorkSize);
m_layoutDirty = false;
m_forceRebuild = false;
}
@@ -82,31 +79,6 @@ public:
}
private:
- static bool HasSavedDockLayout() {
- if (ImGui::GetCurrentContext() == nullptr) {
- return false;
- }
-
- const char* iniFilename = ImGui::GetIO().IniFilename;
- if (iniFilename == nullptr || iniFilename[0] == '\0') {
- return false;
- }
-
- std::ifstream input(iniFilename, std::ios::in);
- if (!input.is_open()) {
- return false;
- }
-
- std::string line;
- while (std::getline(input, line)) {
- if (line == "[Docking][Data]") {
- return true;
- }
- }
-
- return false;
- }
-
void BuildDefaultLayout(ImGuiID dockspaceId, const ImVec2& dockspaceSize) {
ImGui::DockBuilderRemoveNode(dockspaceId);
ImGui::DockBuilderAddNode(dockspaceId, m_dockspaceFlags | ImGuiDockNodeFlags_DockSpace);
diff --git a/editor/src/Managers/ProjectManager.cpp b/editor/src/Managers/ProjectManager.cpp
index 22d2da75..208a0188 100644
--- a/editor/src/Managers/ProjectManager.cpp
+++ b/editor/src/Managers/ProjectManager.cpp
@@ -1,14 +1,9 @@
#include "ProjectManager.h"
-#include
-#include
-#include
#include
#include
#include
#include
-#include
-#include
#include
#include
#include
@@ -217,27 +212,6 @@ bool CanPreviewImageAssetExtension(std::wstring_view extension) {
});
}
-bool IsSceneAssetFile(const fs::path& path) {
- if (!fs::is_regular_file(path)) {
- return false;
- }
-
- std::wstring extension = path.extension().wstring();
- std::transform(extension.begin(), extension.end(), extension.begin(), ::towlower);
- return extension == L".xc";
-}
-
-std::string ReadFileText(const fs::path& path) {
- std::ifstream input(path, std::ios::binary);
- if (!input.is_open()) {
- return {};
- }
-
- std::ostringstream stream;
- stream << input.rdbuf();
- return stream.str();
-}
-
} // namespace
const std::vector& ProjectManager::GetCurrentItems() const {
@@ -522,89 +496,6 @@ bool ProjectManager::RenameItem(const std::string& sourceFullPath, const std::st
}
}
-IProjectManager::SceneAssetReferenceMigrationReport ProjectManager::MigrateSceneAssetReferences() {
- IProjectManager::SceneAssetReferenceMigrationReport report;
- if (m_projectPath.empty()) {
- return report;
- }
-
- const fs::path assetsPath = fs::path(Utf8PathToWstring(m_projectPath)) / L"Assets";
- if (!fs::exists(assetsPath) || !fs::is_directory(assetsPath)) {
- return report;
- }
-
- auto& logger = ::XCEngine::Debug::Logger::Get();
- ::XCEngine::Resources::ResourceManager& resourceManager = ::XCEngine::Resources::ResourceManager::Get();
- resourceManager.Initialize();
-
- const std::string previousRoot = resourceManager.GetResourceRoot().CStr();
- const bool restoreResourceRoot = previousRoot != m_projectPath;
- if (restoreResourceRoot) {
- resourceManager.SetResourceRoot(m_projectPath.c_str());
- }
-
- try {
- for (const fs::directory_entry& entry : fs::recursive_directory_iterator(assetsPath)) {
- if (!IsSceneAssetFile(entry.path())) {
- continue;
- }
-
- ++report.scannedSceneCount;
- const fs::path scenePath = entry.path();
- const std::string scenePathUtf8 = WstringPathToUtf8(scenePath.wstring());
-
- try {
- const std::string before = ReadFileText(scenePath);
- ::XCEngine::Components::Scene scene;
- {
- ::XCEngine::Resources::ResourceManager::ScopedDeferredSceneLoad deferredLoadScope(resourceManager);
- scene.Load(scenePathUtf8);
- }
-
- scene.Save(scenePathUtf8);
- const std::string after = ReadFileText(scenePath);
- if (after != before) {
- ++report.migratedSceneCount;
- } else {
- ++report.unchangedSceneCount;
- }
- } catch (const std::exception& exception) {
- ++report.failedSceneCount;
- logger.Error(
- ::XCEngine::Debug::LogCategory::FileSystem,
- ("Failed to migrate scene asset references: " + scenePathUtf8 + " - " + exception.what()).c_str());
- } catch (...) {
- ++report.failedSceneCount;
- logger.Error(
- ::XCEngine::Debug::LogCategory::FileSystem,
- ("Failed to migrate scene asset references: " + scenePathUtf8 + " - unknown error").c_str());
- }
- }
- } catch (const std::exception& exception) {
- logger.Error(
- ::XCEngine::Debug::LogCategory::FileSystem,
- ("Scene asset reference migration aborted: " + std::string(exception.what())).c_str());
- } catch (...) {
- logger.Error(
- ::XCEngine::Debug::LogCategory::FileSystem,
- "Scene asset reference migration aborted: unknown error");
- }
-
- if (restoreResourceRoot) {
- resourceManager.SetResourceRoot(previousRoot.c_str());
- }
-
- logger.Info(
- ::XCEngine::Debug::LogCategory::FileSystem,
- ("Scene asset reference migration finished. scanned=" + std::to_string(report.scannedSceneCount) +
- " migrated=" + std::to_string(report.migratedSceneCount) +
- " unchanged=" + std::to_string(report.unchangedSceneCount) +
- " failed=" + std::to_string(report.failedSceneCount)).c_str());
-
- RefreshCurrentFolder();
- return report;
-}
-
AssetItemPtr ProjectManager::FindCurrentItemByPath(const std::string& fullPath) const {
const int index = FindCurrentItemIndex(fullPath);
if (index < 0) {
diff --git a/editor/src/Managers/ProjectManager.h b/editor/src/Managers/ProjectManager.h
index 505013e1..1602cd08 100644
--- a/editor/src/Managers/ProjectManager.h
+++ b/editor/src/Managers/ProjectManager.h
@@ -38,7 +38,6 @@ public:
bool DeleteItem(const std::string& fullPath) override;
bool MoveItem(const std::string& sourceFullPath, const std::string& destFolderFullPath) override;
bool RenameItem(const std::string& sourceFullPath, const std::string& newName) override;
- SceneAssetReferenceMigrationReport MigrateSceneAssetReferences() override;
const std::string& GetProjectPath() const override { return m_projectPath; }
diff --git a/editor/src/Managers/SceneManager.cpp b/editor/src/Managers/SceneManager.cpp
index 0b9d3e84..b7bfb661 100644
--- a/editor/src/Managers/SceneManager.cpp
+++ b/editor/src/Managers/SceneManager.cpp
@@ -7,6 +7,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -27,6 +28,48 @@ std::pair SerializeComponent(const ::XCEngine::Compone
SceneManager::SceneManager(EventBus* eventBus)
: m_eventBus(eventBus) {}
+std::uint64_t SceneManager::GetCurrentSteadyTimeMs() {
+ return static_cast(
+ std::chrono::duration_cast(
+ std::chrono::steady_clock::now().time_since_epoch()).count());
+}
+
+void SceneManager::BeginSceneLoadProgress(
+ const std::string& operation,
+ const std::string& scenePath,
+ bool startupScene,
+ const std::string& message) {
+ m_sceneLoadProgress = {};
+ m_sceneLoadProgress.revision = m_nextSceneLoadProgressRevision++;
+ m_sceneLoadProgress.inProgress = true;
+ m_sceneLoadProgress.success = false;
+ m_sceneLoadProgress.startupScene = startupScene;
+ m_sceneLoadProgress.operation = operation;
+ m_sceneLoadProgress.scenePath = scenePath;
+ m_sceneLoadProgress.message = message;
+ m_sceneLoadProgress.startedAtMs = GetCurrentSteadyTimeMs();
+}
+
+void SceneManager::MarkSceneStructureReady(const std::string& message) {
+ if (!m_sceneLoadProgress.HasValue()) {
+ return;
+ }
+
+ m_sceneLoadProgress.success = true;
+ m_sceneLoadProgress.inProgress = true;
+ m_sceneLoadProgress.message = message;
+ m_sceneLoadProgress.structureReadyAtMs = GetCurrentSteadyTimeMs();
+ m_sceneLoadProgress.currentPendingAsyncLoads =
+ ::XCEngine::Resources::ResourceManager::Get().GetAsyncPendingCount();
+ m_sceneLoadProgress.peakPendingAsyncLoads = (std::max)(
+ m_sceneLoadProgress.peakPendingAsyncLoads,
+ m_sceneLoadProgress.currentPendingAsyncLoads);
+}
+
+void SceneManager::ClearSceneLoadProgress() {
+ m_sceneLoadProgress = {};
+}
+
void SceneManager::SetSceneDirty(bool dirty) {
m_isSceneDirty = dirty;
}
@@ -188,6 +231,7 @@ bool SceneManager::RestoreSceneSnapshot(const SceneSnapshot& snapshot) {
m_currentScenePath.clear();
m_currentSceneName = "Untitled Scene";
SetSceneDirty(snapshot.dirty);
+ ClearSceneLoadProgress();
OnSceneChanged.Invoke();
if (m_eventBus) {
@@ -196,6 +240,11 @@ bool SceneManager::RestoreSceneSnapshot(const SceneSnapshot& snapshot) {
return true;
}
+ BeginSceneLoadProgress(
+ "Restore Scene Snapshot",
+ snapshot.scenePath,
+ false,
+ "Restoring scene snapshot structure...");
auto scene = std::make_unique<::XCEngine::Components::Scene>();
{
::XCEngine::Resources::ResourceManager::ScopedDeferredSceneLoad deferredLoadScope;
@@ -208,6 +257,7 @@ bool SceneManager::RestoreSceneSnapshot(const SceneSnapshot& snapshot) {
m_currentScenePath = snapshot.scenePath;
m_currentSceneName = m_scene ? m_scene->GetName() : "Untitled Scene";
SetSceneDirty(snapshot.dirty);
+ MarkSceneStructureReady("Scene snapshot restored. Waiting for first viewport frame...");
OnSceneChanged.Invoke();
if (m_eventBus) {
@@ -238,6 +288,7 @@ void SceneManager::NewScene(const std::string& name) {
m_currentScenePath.clear();
m_currentSceneName = name;
SetSceneDirty(true);
+ ClearSceneLoadProgress();
OnSceneChanged.Invoke();
if (m_eventBus) {
@@ -252,6 +303,11 @@ bool SceneManager::LoadScene(const std::string& filePath) {
return false;
}
+ BeginSceneLoadProgress(
+ "Load Scene",
+ filePath,
+ false,
+ "Restoring scene structure...");
auto scene = std::make_unique<::XCEngine::Components::Scene>();
{
::XCEngine::Resources::ResourceManager::ScopedDeferredSceneLoad deferredLoadScope;
@@ -264,6 +320,7 @@ bool SceneManager::LoadScene(const std::string& filePath) {
m_currentScenePath = filePath;
m_currentSceneName = m_scene ? m_scene->GetName() : "Untitled Scene";
SetSceneDirty(false);
+ MarkSceneStructureReady("Scene structure restored. Waiting for first viewport frame...");
OnSceneChanged.Invoke();
if (m_eventBus) {
@@ -310,11 +367,57 @@ bool SceneManager::SaveSceneAs(const std::string& filePath) {
bool SceneManager::LoadStartupScene(const std::string& projectPath) {
const std::string defaultScenePath = ResolveDefaultScenePath(projectPath);
if (IsSceneFileUsable(defaultScenePath) && LoadScene(defaultScenePath)) {
+ if (m_sceneLoadProgress.HasValue()) {
+ m_sceneLoadProgress.operation = "Load Startup Scene";
+ m_sceneLoadProgress.startupScene = true;
+ }
return true;
}
CreateDemoScene();
- return SaveSceneAs(defaultScenePath);
+ const bool saved = SaveSceneAs(defaultScenePath);
+ if (saved) {
+ BeginSceneLoadProgress(
+ "Load Startup Scene",
+ defaultScenePath,
+ true,
+ "Created default startup scene.");
+ MarkSceneStructureReady("Created default startup scene. Waiting for first viewport frame...");
+ }
+ return saved;
+}
+
+void SceneManager::NotifySceneViewportFramePresented(std::uint32_t pendingAsyncLoads) {
+ if (!m_sceneLoadProgress.HasValue() ||
+ !m_sceneLoadProgress.success ||
+ m_sceneLoadProgress.streamingCompleted) {
+ return;
+ }
+
+ const std::uint64_t nowMs = GetCurrentSteadyTimeMs();
+ m_sceneLoadProgress.currentPendingAsyncLoads = pendingAsyncLoads;
+ m_sceneLoadProgress.peakPendingAsyncLoads = (std::max)(
+ m_sceneLoadProgress.peakPendingAsyncLoads,
+ pendingAsyncLoads);
+
+ if (!m_sceneLoadProgress.firstFramePresented) {
+ m_sceneLoadProgress.firstFramePresented = true;
+ m_sceneLoadProgress.firstFrameAtMs = nowMs;
+ m_sceneLoadProgress.interactive = true;
+ m_sceneLoadProgress.interactiveAtMs = nowMs;
+ }
+
+ if (pendingAsyncLoads == 0) {
+ m_sceneLoadProgress.streamingCompleted = true;
+ m_sceneLoadProgress.streamingCompletedAtMs = nowMs;
+ m_sceneLoadProgress.inProgress = false;
+ m_sceneLoadProgress.message = "Scene ready.";
+ return;
+ }
+
+ m_sceneLoadProgress.inProgress = true;
+ m_sceneLoadProgress.message =
+ "Runtime streaming scene assets... (" + std::to_string(pendingAsyncLoads) + ")";
}
void SceneManager::RenameEntity(::XCEngine::Components::GameObject::ID id, const std::string& newName) {
@@ -365,6 +468,7 @@ void SceneManager::CreateDemoScene() {
m_currentScenePath.clear();
m_currentSceneName = m_scene->GetName();
SetSceneDirty(true);
+ ClearSceneLoadProgress();
::XCEngine::Components::GameObject* camera = CreateEntity("Main Camera", nullptr);
camera->AddComponent<::XCEngine::Components::CameraComponent>();
diff --git a/editor/src/Managers/SceneManager.h b/editor/src/Managers/SceneManager.h
index 4b1fa359..5ceacb11 100644
--- a/editor/src/Managers/SceneManager.h
+++ b/editor/src/Managers/SceneManager.h
@@ -67,6 +67,8 @@ public:
const std::string& GetCurrentSceneName() const override { return m_currentSceneName; }
::XCEngine::Components::Scene* GetScene() override { return m_scene.get(); }
const ::XCEngine::Components::Scene* GetScene() const override { return m_scene.get(); }
+ SceneLoadProgressSnapshot GetSceneLoadProgress() const override { return m_sceneLoadProgress; }
+ void NotifySceneViewportFramePresented(std::uint32_t pendingAsyncLoads) override;
SceneSnapshot CaptureSceneSnapshot() const override;
bool RestoreSceneSnapshot(const SceneSnapshot& snapshot) override;
void CreateDemoScene() override;
@@ -92,6 +94,14 @@ private:
::XCEngine::Components::GameObject::ID PasteEntityRecursive(const ClipboardData& data, ::XCEngine::Components::GameObject::ID parent);
void SetSceneDirty(bool dirty);
void SyncRootEntities();
+ static std::uint64_t GetCurrentSteadyTimeMs();
+ void BeginSceneLoadProgress(
+ const std::string& operation,
+ const std::string& scenePath,
+ bool startupScene,
+ const std::string& message);
+ void MarkSceneStructureReady(const std::string& message);
+ void ClearSceneLoadProgress();
static std::string GetScenesDirectory(const std::string& projectPath);
static std::string ResolveDefaultScenePath(const std::string& projectPath);
static bool IsSceneFileUsable(const std::string& filePath);
@@ -104,6 +114,8 @@ private:
std::string m_currentSceneName = "Untitled Scene";
bool m_isSceneDirty = false;
bool m_sceneDocumentDirtyTrackingEnabled = true;
+ SceneLoadProgressSnapshot m_sceneLoadProgress;
+ std::uint64_t m_nextSceneLoadProgressRevision = 1;
};
}
diff --git a/editor/src/Platform/D3D12WindowRenderer.h b/editor/src/Platform/D3D12WindowRenderer.h
index 04176cfa..d6af3e3f 100644
--- a/editor/src/Platform/D3D12WindowRenderer.h
+++ b/editor/src/Platform/D3D12WindowRenderer.h
@@ -3,6 +3,7 @@
#include "UI/ImGuiBackendBridge.h"
#include
+#include
#include
#include
#include
@@ -28,6 +29,8 @@ class D3D12WindowRenderer {
public:
static constexpr UINT kSrvDescriptorCount = 64;
static constexpr uint32_t kSwapChainBufferCount = 3;
+ using RenderCallback =
+ std::function;
bool Initialize(HWND hwnd, int width, int height) {
Shutdown();
@@ -47,6 +50,10 @@ public:
}
RHI::RHIDeviceDesc deviceDesc = {};
+#ifdef _DEBUG
+ deviceDesc.enableDebugLayer = true;
+ deviceDesc.enableGPUValidation = true;
+#endif
if (!m_device->Initialize(deviceDesc)) {
Shutdown();
return false;
@@ -175,7 +182,7 @@ public:
void Render(
UI::ImGuiBackendBridge& imguiBackend,
const float clearColor[4],
- const std::function& beforeUiRender = {}) {
+ const RenderCallback& beforeUiRender = {}) {
auto* d3d12Queue = GetD3D12CommandQueue();
auto* d3d12CommandList = GetD3D12CommandList();
if (m_swapChain == nullptr ||
@@ -185,16 +192,15 @@ public:
return;
}
- if (beforeUiRender) {
- beforeUiRender(GetRenderContext());
- }
-
const uint32_t backBufferIndex = m_swapChain->GetCurrentBackBufferIndex();
- if (backBufferIndex >= m_backBufferViews.size() || m_backBufferViews[backBufferIndex] == nullptr) {
+ if (backBufferIndex >= m_backBufferViews.size() ||
+ backBufferIndex >= m_backBufferSurfaces.size() ||
+ m_backBufferViews[backBufferIndex] == nullptr) {
return;
}
RHI::RHIResourceView* renderTargetView = m_backBufferViews[backBufferIndex];
+ Rendering::RenderSurface& renderSurface = m_backBufferSurfaces[backBufferIndex];
d3d12CommandList->TransitionBarrier(
renderTargetView,
RHI::ResourceStates::Present,
@@ -202,6 +208,16 @@ public:
d3d12CommandList->SetRenderTargets(1, &renderTargetView, nullptr);
d3d12CommandList->ClearRenderTarget(renderTargetView, clearColor);
+ // Host-owned swapchain surfaces are handed to the callback already bound and ready for rendering.
+ // The callback must leave the color attachment in RenderTarget state so ImGui can render after it.
+ if (beforeUiRender) {
+ beforeUiRender(GetRenderContext(), renderSurface);
+
+ // Viewport rendering can bind offscreen render targets on the shared command list.
+ // Rebind the swapchain backbuffer so the subsequent ImGui draw data lands on the main window.
+ d3d12CommandList->SetRenderTargets(1, &renderTargetView, nullptr);
+ }
+
ID3D12DescriptorHeap* descriptorHeaps[] = { m_srvHeap->GetDescriptorHeap() };
d3d12CommandList->SetDescriptorHeaps(1, descriptorHeaps);
imguiBackend.RenderDrawData(d3d12CommandList->GetCommandList());
@@ -247,6 +263,19 @@ public:
return m_swapChain;
}
+ const Rendering::RenderSurface* GetCurrentRenderSurface() const {
+ if (m_swapChain == nullptr) {
+ return nullptr;
+ }
+
+ const uint32_t backBufferIndex = m_swapChain->GetCurrentBackBufferIndex();
+ if (backBufferIndex >= m_backBufferSurfaces.size()) {
+ return nullptr;
+ }
+
+ return &m_backBufferSurfaces[backBufferIndex];
+ }
+
Rendering::RenderContext GetRenderContext() const {
Rendering::RenderContext context = {};
context.device = m_device;
@@ -287,6 +316,7 @@ private:
}
}
m_backBufferViews.clear();
+ m_backBufferSurfaces.clear();
}
bool RecreateBackBufferViews() {
@@ -296,6 +326,7 @@ private:
}
m_backBufferViews.resize(kSwapChainBufferCount, nullptr);
+ m_backBufferSurfaces.resize(kSwapChainBufferCount);
RHI::ResourceViewDesc viewDesc = {};
viewDesc.format = static_cast(RHI::Format::R8G8B8A8_UNorm);
@@ -309,6 +340,15 @@ private:
ReleaseBackBufferViews();
return false;
}
+
+ Rendering::RenderSurface& surface = m_backBufferSurfaces[backBufferIndex];
+ surface = Rendering::RenderSurface(
+ static_cast(m_width),
+ static_cast(m_height));
+ surface.SetColorAttachment(m_backBufferViews[backBufferIndex]);
+ surface.SetAutoTransitionEnabled(false);
+ surface.SetColorStateBefore(RHI::ResourceStates::RenderTarget);
+ surface.SetColorStateAfter(RHI::ResourceStates::RenderTarget);
}
return true;
@@ -325,6 +365,7 @@ private:
RHI::RHIDescriptorPool* m_srvPool = nullptr;
RHI::D3D12DescriptorHeap* m_srvHeap = nullptr;
std::vector m_backBufferViews;
+ std::vector m_backBufferSurfaces;
UINT m_srvDescriptorSize = 0;
};
diff --git a/editor/src/Viewport/ViewportHostService.h b/editor/src/Viewport/ViewportHostService.h
index 7878e67b..bb0252d3 100644
--- a/editor/src/Viewport/ViewportHostService.h
+++ b/editor/src/Viewport/ViewportHostService.h
@@ -29,6 +29,7 @@
#include
#include
+#include
#include
#include
#include
@@ -60,6 +61,52 @@ Math::Vector3 GetSceneViewportOrientationAxisVector(SceneViewportOrientationAxis
}
}
+std::uint64_t GetCurrentSceneLoadStatusTimeMs() {
+ return static_cast(
+ std::chrono::duration_cast(
+ std::chrono::steady_clock::now().time_since_epoch()).count());
+}
+
+std::uint64_t ResolveSceneLoadStatusElapsedMs(const SceneLoadProgressSnapshot& status) {
+ if (!status.HasValue() || status.startedAtMs == 0) {
+ return 0;
+ }
+
+ if (status.inProgress) {
+ const std::uint64_t nowMs = GetCurrentSceneLoadStatusTimeMs();
+ return nowMs >= status.startedAtMs ? nowMs - status.startedAtMs : 0;
+ }
+
+ if (status.streamingCompletedAtMs >= status.startedAtMs && status.streamingCompletedAtMs != 0) {
+ return status.streamingCompletedAtMs - status.startedAtMs;
+ }
+
+ if (status.firstFrameAtMs >= status.startedAtMs && status.firstFrameAtMs != 0) {
+ return status.firstFrameAtMs - status.startedAtMs;
+ }
+
+ if (status.structureReadyAtMs >= status.startedAtMs && status.structureReadyAtMs != 0) {
+ return status.structureReadyAtMs - status.startedAtMs;
+ }
+
+ return 0;
+}
+
+std::string BuildViewportSceneLoadStatusText(const SceneLoadProgressSnapshot& status) {
+ if (!status.HasValue() || !status.inProgress || status.message.empty()) {
+ return {};
+ }
+
+ std::string text = status.message;
+ const std::uint64_t elapsedMs = ResolveSceneLoadStatusElapsedMs(status);
+ if (elapsedMs > 0) {
+ text += " (";
+ text += std::to_string(elapsedMs);
+ text += " ms)";
+ }
+ return text;
+}
+
} // namespace
class ViewportHostService : public IViewportHostService {
@@ -568,8 +615,10 @@ private:
MarkSceneViewportRenderSuccess(entry.renderTargets, requests[0]);
const Core::uint32 pendingAsyncLoads = Resources::ResourceManager::Get().GetAsyncPendingCount();
- if (pendingAsyncLoads > 0) {
- entry.statusText = "Loading scene assets... (" + std::to_string(pendingAsyncLoads) + ")";
+ context.GetSceneManager().NotifySceneViewportFramePresented(pendingAsyncLoads);
+ if (entry.statusText.empty()) {
+ entry.statusText = BuildViewportSceneLoadStatusText(
+ context.GetSceneManager().GetSceneLoadProgress());
}
return true;
}
diff --git a/editor/src/XCUIBackend/XCUIDemoRuntime.h b/editor/src/XCUIBackend/XCUIDemoRuntime.h
new file mode 100644
index 00000000..fb05ecde
--- /dev/null
+++ b/editor/src/XCUIBackend/XCUIDemoRuntime.h
@@ -0,0 +1,72 @@
+#pragma once
+
+#include
+
+#include
+#include
+
+namespace XCEngine {
+namespace UI {
+struct UIRect;
+struct UIPoint;
+} // namespace UI
+namespace Editor {
+namespace XCUIBackend {
+
+struct XCUIDemoInputState {
+ UI::UIRect canvasRect = {};
+ UI::UIPoint pointerPosition = {};
+ bool pointerInside = false;
+ bool pointerPressed = false;
+ bool pointerReleased = false;
+ bool pointerDown = false;
+ bool windowFocused = false;
+ bool shortcutPressed = false;
+};
+
+struct XCUIDemoFrameStats {
+ bool documentsReady = false;
+ std::string statusMessage = {};
+ std::size_t dependencyCount = 0;
+ std::size_t elementCount = 0;
+ std::size_t dirtyRootCount = 0;
+ std::size_t drawListCount = 0;
+ std::size_t commandCount = 0;
+ std::uint64_t treeGeneration = 0;
+ bool accentEnabled = false;
+ std::string lastCommandId = {};
+ std::string focusedElementId = {};
+ std::string hoveredElementId = {};
+};
+
+struct XCUIDemoFrameResult {
+ UI::UIDrawData drawData = {};
+ XCUIDemoFrameStats stats = {};
+};
+
+class XCUIDemoRuntime {
+public:
+ XCUIDemoRuntime();
+ ~XCUIDemoRuntime();
+
+ XCUIDemoRuntime(XCUIDemoRuntime&& other) noexcept;
+ XCUIDemoRuntime& operator=(XCUIDemoRuntime&& other) noexcept;
+
+ XCUIDemoRuntime(const XCUIDemoRuntime&) = delete;
+ XCUIDemoRuntime& operator=(const XCUIDemoRuntime&) = delete;
+
+ bool ReloadDocuments();
+
+ const XCUIDemoFrameResult& Update(const XCUIDemoInputState& input);
+ const XCUIDemoFrameResult& GetFrameResult() const;
+
+ bool TryGetElementRect(const std::string& elementId, UI::UIRect& outRect) const;
+
+private:
+ struct RuntimeState;
+ std::unique_ptr m_state;
+};
+
+} // namespace XCUIBackend
+} // namespace Editor
+} // namespace XCEngine
diff --git a/editor/src/panels/InspectorPanel.cpp b/editor/src/panels/InspectorPanel.cpp
index 2fcf9a79..bbeb2841 100644
--- a/editor/src/panels/InspectorPanel.cpp
+++ b/editor/src/panels/InspectorPanel.cpp
@@ -430,16 +430,11 @@ const char* SerializeBlendFactor(::XCEngine::Resources::MaterialBlendFactor fact
}
}
-void ApplyMaterialStateToResource(
+void ApplyResolvedMaterialStateToResource(
const InspectorPanel::MaterialAssetState& state,
+ const ::XCEngine::Resources::ResourceHandle<::XCEngine::Resources::Shader>& shader,
::XCEngine::Resources::Material& material) {
- const std::string shaderPath = TrimCopy(BufferToString(state.shaderPath));
- if (shaderPath.empty()) {
- material.SetShader(::XCEngine::Resources::ResourceHandle<::XCEngine::Resources::Shader>());
- } else {
- material.SetShader(::XCEngine::Resources::ResourceManager::Get().Load<::XCEngine::Resources::Shader>(shaderPath.c_str()));
- }
-
+ material.SetShader(shader);
material.SetShaderPass(TrimCopy(BufferToString(state.shaderPass)).c_str());
material.SetRenderQueue(state.renderQueue);
material.SetRenderState(state.renderState);
@@ -454,6 +449,50 @@ void ApplyMaterialStateToResource(
material.RecalculateMemorySize();
}
+bool TryGetLoadedShaderHandle(
+ const std::string& shaderPath,
+ ::XCEngine::Resources::ResourceHandle<::XCEngine::Resources::Shader>& outShader) {
+ outShader.Reset();
+ if (shaderPath.empty()) {
+ return false;
+ }
+
+ ::XCEngine::Resources::IResource* cachedResource =
+ ::XCEngine::Resources::ResourceManager::Get().Find(shaderPath.c_str());
+ if (cachedResource == nullptr ||
+ cachedResource->GetType() != ::XCEngine::Resources::ResourceType::Shader ||
+ !cachedResource->IsValid()) {
+ return false;
+ }
+
+ outShader = ::XCEngine::Resources::ResourceHandle<::XCEngine::Resources::Shader>(
+ static_cast<::XCEngine::Resources::Shader*>(cachedResource));
+ return outShader.IsValid();
+}
+
+std::string BuildMaterialLoadFailureMessage(const ::XCEngine::Resources::LoadResult& result) {
+ if (!result.errorMessage.Empty()) {
+ return std::string("Material file is invalid or unavailable: ") + result.errorMessage.CStr();
+ }
+
+ return "Material file is empty or invalid. Showing default values until you save it.";
+}
+
+std::string BuildShaderLoadFailureMessage(
+ const std::string& shaderPath,
+ const ::XCEngine::Resources::LoadResult& result) {
+ std::string message = "Failed to load shader for live material update";
+ if (!shaderPath.empty()) {
+ message += ": ";
+ message += shaderPath;
+ }
+ if (!result.errorMessage.Empty()) {
+ message += " - ";
+ message += result.errorMessage.CStr();
+ }
+ return message;
+}
+
std::string BuildMaterialAssetFileText(const InspectorPanel::MaterialAssetState& state) {
std::vector rootEntries;
@@ -596,16 +635,130 @@ void InspectorPanel::SetSubjectMode(SubjectMode mode) {
}
void InspectorPanel::ClearMaterialAsset() {
+ InvalidateMaterialAssetAsyncState();
m_selectedMaterial.Reset();
m_materialAssetState.Reset();
}
+void InspectorPanel::InvalidateMaterialAssetAsyncState() {
+ ++m_materialAssetLoadRevision;
+ ++m_materialShaderLoadRevision;
+ m_materialShaderLoadInFlight = false;
+ m_pendingMaterialShaderPath.clear();
+}
+
+void InspectorPanel::PopulateMaterialAssetStateFromResource(::XCEngine::Resources::Material& material) {
+ m_materialAssetState.shaderPath.fill('\0');
+ m_materialAssetState.shaderPass.fill('\0');
+ m_materialAssetState.renderQueue = material.GetRenderQueue();
+ m_materialAssetState.renderState = material.GetRenderState();
+ m_materialAssetState.tags.clear();
+
+ if (::XCEngine::Resources::Shader* shader = material.GetShader()) {
+ CopyToCharBuffer(std::string(shader->GetPath().CStr()), m_materialAssetState.shaderPath);
+ }
+ CopyToCharBuffer(std::string(material.GetShaderPass().CStr()), m_materialAssetState.shaderPass);
+
+ m_materialAssetState.tags.reserve(material.GetTagCount());
+ for (Core::uint32 tagIndex = 0; tagIndex < material.GetTagCount(); ++tagIndex) {
+ MaterialTagEditRow row;
+ CopyToCharBuffer(std::string(material.GetTagName(tagIndex).CStr()), row.name);
+ CopyToCharBuffer(std::string(material.GetTagValue(tagIndex).CStr()), row.value);
+ m_materialAssetState.tags.push_back(row);
+ }
+
+ m_materialAssetState.loaded = true;
+ m_materialAssetState.dirty = false;
+}
+
+void InspectorPanel::ApplyMaterialAssetStateToSelectedMaterial() {
+ if (!m_selectedMaterial.IsValid() || m_selectedMaterial.Get() == nullptr) {
+ return;
+ }
+
+ ::XCEngine::Resources::Material& material = *m_selectedMaterial.Get();
+ const std::string shaderPath = TrimCopy(BufferToString(m_materialAssetState.shaderPath));
+ if (shaderPath.empty()) {
+ ++m_materialShaderLoadRevision;
+ m_materialShaderLoadInFlight = false;
+ m_pendingMaterialShaderPath.clear();
+ ApplyResolvedMaterialStateToResource(
+ m_materialAssetState,
+ ::XCEngine::Resources::ResourceHandle<::XCEngine::Resources::Shader>(),
+ material);
+ m_materialAssetState.errorMessage.clear();
+ return;
+ }
+
+ ::XCEngine::Resources::ResourceHandle<::XCEngine::Resources::Shader> shaderHandle;
+ if (TryGetLoadedShaderHandle(shaderPath, shaderHandle)) {
+ ++m_materialShaderLoadRevision;
+ m_materialShaderLoadInFlight = false;
+ m_pendingMaterialShaderPath.clear();
+ ApplyResolvedMaterialStateToResource(m_materialAssetState, shaderHandle, material);
+ m_materialAssetState.errorMessage.clear();
+ return;
+ }
+
+ if (m_materialShaderLoadInFlight && m_pendingMaterialShaderPath == shaderPath) {
+ m_materialAssetState.errorMessage =
+ std::string("Loading shader for live material update: ") + shaderPath;
+ return;
+ }
+
+ const std::uint64_t shaderLoadRevision = ++m_materialShaderLoadRevision;
+ m_materialShaderLoadInFlight = true;
+ m_pendingMaterialShaderPath = shaderPath;
+ m_materialAssetState.errorMessage =
+ std::string("Loading shader for live material update: ") + shaderPath;
+
+ ::XCEngine::Resources::ResourceManager::Get().LoadAsync(
+ shaderPath.c_str(),
+ ::XCEngine::Resources::ResourceType::Shader,
+ [this, shaderLoadRevision, requestedShaderPath = shaderPath](::XCEngine::Resources::LoadResult result) {
+ if (shaderLoadRevision != m_materialShaderLoadRevision ||
+ m_subjectMode != SubjectMode::MaterialAsset ||
+ TrimCopy(BufferToString(m_materialAssetState.shaderPath)) != requestedShaderPath) {
+ return;
+ }
+
+ m_materialShaderLoadInFlight = false;
+ m_pendingMaterialShaderPath.clear();
+
+ if (!m_selectedMaterial.IsValid() || m_selectedMaterial.Get() == nullptr) {
+ m_materialAssetState.errorMessage.clear();
+ return;
+ }
+
+ if (!result || result.resource == nullptr) {
+ m_materialAssetState.errorMessage =
+ BuildShaderLoadFailureMessage(requestedShaderPath, result);
+ return;
+ }
+
+ ::XCEngine::Resources::ResourceHandle<::XCEngine::Resources::Shader> loadedShader(
+ static_cast<::XCEngine::Resources::Shader*>(result.resource));
+ if (!loadedShader.IsValid()) {
+ m_materialAssetState.errorMessage =
+ BuildShaderLoadFailureMessage(requestedShaderPath, result);
+ return;
+ }
+
+ ApplyResolvedMaterialStateToResource(
+ m_materialAssetState,
+ loadedShader,
+ *m_selectedMaterial.Get());
+ m_materialAssetState.errorMessage.clear();
+ });
+}
+
void InspectorPanel::ReloadMaterialAsset() {
if (!m_selectedAssetItem || !m_context) {
ClearMaterialAsset();
return;
}
+ InvalidateMaterialAssetAsyncState();
m_selectedMaterial.Reset();
m_materialAssetState.Reset();
m_materialAssetState.assetFullPath = m_selectedAssetItem->fullPath;
@@ -619,38 +772,41 @@ void InspectorPanel::ReloadMaterialAsset() {
return;
}
- m_selectedMaterial = ::XCEngine::Resources::ResourceManager::Get().Load<::XCEngine::Resources::Material>(
- m_materialAssetState.assetPath.c_str());
- if (!m_selectedMaterial.IsValid()) {
- m_materialAssetState.errorMessage = "Material file is empty or invalid. Showing default values until you save it.";
- m_materialAssetState.loaded = true;
- m_materialAssetState.dirty = false;
- return;
- }
+ const std::uint64_t loadRevision = m_materialAssetLoadRevision;
+ const std::string requestedAssetPath = m_materialAssetState.assetPath;
+ m_materialAssetState.errorMessage = "Loading material asset from Library...";
- ::XCEngine::Resources::Material* material = m_selectedMaterial.Get();
- if (material == nullptr) {
- m_materialAssetState.errorMessage = "Material resource is unavailable. Showing default values until you save it.";
- m_materialAssetState.loaded = true;
- m_materialAssetState.dirty = false;
- return;
- }
+ ::XCEngine::Resources::ResourceManager::Get().LoadAsync(
+ m_materialAssetState.assetPath.c_str(),
+ ::XCEngine::Resources::ResourceType::Material,
+ [this, loadRevision, requestedAssetPath](::XCEngine::Resources::LoadResult result) {
+ if (loadRevision != m_materialAssetLoadRevision ||
+ m_subjectMode != SubjectMode::MaterialAsset ||
+ m_materialAssetState.assetPath != requestedAssetPath) {
+ return;
+ }
- if (::XCEngine::Resources::Shader* shader = material->GetShader()) {
- CopyToCharBuffer(std::string(shader->GetPath().CStr()), m_materialAssetState.shaderPath);
- }
- CopyToCharBuffer(std::string(material->GetShaderPass().CStr()), m_materialAssetState.shaderPass);
- m_materialAssetState.renderQueue = material->GetRenderQueue();
- m_materialAssetState.renderState = material->GetRenderState();
- m_materialAssetState.tags.reserve(material->GetTagCount());
- for (Core::uint32 tagIndex = 0; tagIndex < material->GetTagCount(); ++tagIndex) {
- MaterialTagEditRow row;
- CopyToCharBuffer(std::string(material->GetTagName(tagIndex).CStr()), row.name);
- CopyToCharBuffer(std::string(material->GetTagValue(tagIndex).CStr()), row.value);
- m_materialAssetState.tags.push_back(row);
- }
- m_materialAssetState.loaded = true;
- m_materialAssetState.dirty = false;
+ if (!result || result.resource == nullptr) {
+ m_selectedMaterial.Reset();
+ m_materialAssetState.loaded = true;
+ m_materialAssetState.dirty = false;
+ m_materialAssetState.errorMessage = BuildMaterialLoadFailureMessage(result);
+ return;
+ }
+
+ m_selectedMaterial = ::XCEngine::Resources::ResourceHandle<::XCEngine::Resources::Material>(
+ static_cast<::XCEngine::Resources::Material*>(result.resource));
+ if (!m_selectedMaterial.IsValid() || m_selectedMaterial.Get() == nullptr) {
+ m_selectedMaterial.Reset();
+ m_materialAssetState.loaded = true;
+ m_materialAssetState.dirty = false;
+ m_materialAssetState.errorMessage = BuildMaterialLoadFailureMessage(result);
+ return;
+ }
+
+ m_materialAssetState.errorMessage.clear();
+ PopulateMaterialAssetStateFromResource(*m_selectedMaterial.Get());
+ });
}
void InspectorPanel::InspectMaterialAsset(const AssetItemPtr& item) {
@@ -811,16 +967,9 @@ bool InspectorPanel::SaveMaterialAsset() {
return false;
}
- if (!m_selectedMaterial.IsValid()) {
- m_selectedMaterial = ::XCEngine::Resources::ResourceManager::Get().Load<::XCEngine::Resources::Material>(
- m_materialAssetState.assetPath.c_str());
- }
- if (m_selectedMaterial.IsValid()) {
- ApplyMaterialStateToResource(m_materialAssetState, *m_selectedMaterial.Get());
- }
-
m_materialAssetState.errorMessage.clear();
m_materialAssetState.dirty = false;
+ ApplyMaterialAssetStateToSelectedMaterial();
return true;
} catch (...) {
m_materialAssetState.errorMessage = "Saving material asset failed.";
diff --git a/editor/src/panels/InspectorPanel.h b/editor/src/panels/InspectorPanel.h
index 3ba6dc1d..85822d3b 100644
--- a/editor/src/panels/InspectorPanel.h
+++ b/editor/src/panels/InspectorPanel.h
@@ -76,6 +76,9 @@ private:
void SetSubjectMode(SubjectMode mode);
void InspectMaterialAsset(const AssetItemPtr& item);
void ClearMaterialAsset();
+ void InvalidateMaterialAssetAsyncState();
+ void PopulateMaterialAssetStateFromResource(::XCEngine::Resources::Material& material);
+ void ApplyMaterialAssetStateToSelectedMaterial();
void RenderMaterialAsset();
void RenderUnsupportedAsset();
bool SaveMaterialAsset();
@@ -92,6 +95,10 @@ private:
AssetItemPtr m_selectedAssetItem;
::XCEngine::Resources::ResourceHandle<::XCEngine::Resources::Material> m_selectedMaterial;
MaterialAssetState m_materialAssetState;
+ std::uint64_t m_materialAssetLoadRevision = 0;
+ std::uint64_t m_materialShaderLoadRevision = 0;
+ bool m_materialShaderLoadInFlight = false;
+ std::string m_pendingMaterialShaderPath;
UI::DeferredPopupState m_addComponentPopup;
std::function m_deferredContextAction;
};
diff --git a/editor/src/panels/ProjectPanel.cpp b/editor/src/panels/ProjectPanel.cpp
index 10f4678e..0655f168 100644
--- a/editor/src/panels/ProjectPanel.cpp
+++ b/editor/src/panels/ProjectPanel.cpp
@@ -4,6 +4,7 @@
#include "ProjectPanel.h"
#include "Core/IEditorContext.h"
#include "Core/IProjectManager.h"
+#include "Core/ISceneManager.h"
#include "Core/AssetItem.h"
#include "Platform/Win32Utf8.h"
#include "Utils/ProjectFileUtils.h"
@@ -60,6 +61,51 @@ ImVec4 ResolveImportStatusColor(const XCEngine::Resources::AssetImportService::I
: XCEngine::Editor::UI::ConsoleErrorColor();
}
+std::uint64_t ResolveSceneLoadElapsedMs(const XCEngine::Editor::SceneLoadProgressSnapshot& status) {
+ if (!status.HasValue() || status.startedAtMs == 0) {
+ return 0;
+ }
+
+ if (status.inProgress) {
+ const auto nowMs = static_cast(
+ std::chrono::duration_cast(
+ std::chrono::steady_clock::now().time_since_epoch()).count());
+ return nowMs >= status.startedAtMs ? nowMs - status.startedAtMs : 0;
+ }
+
+ if (status.streamingCompletedAtMs >= status.startedAtMs && status.streamingCompletedAtMs != 0) {
+ return status.streamingCompletedAtMs - status.startedAtMs;
+ }
+
+ if (status.firstFrameAtMs >= status.startedAtMs && status.firstFrameAtMs != 0) {
+ return status.firstFrameAtMs - status.startedAtMs;
+ }
+
+ if (status.structureReadyAtMs >= status.startedAtMs && status.structureReadyAtMs != 0) {
+ return status.structureReadyAtMs - status.startedAtMs;
+ }
+
+ return 0;
+}
+
+std::uint64_t ResolveSceneLoadMarkerDeltaMs(std::uint64_t startMs, std::uint64_t endMs) {
+ return endMs >= startMs && startMs != 0 ? endMs - startMs : 0;
+}
+
+ImVec4 ResolveSceneLoadStatusColor(const XCEngine::Editor::SceneLoadProgressSnapshot& status) {
+ if (!status.HasValue()) {
+ return XCEngine::Editor::UI::ConsoleSecondaryTextColor();
+ }
+
+ if (status.inProgress) {
+ return XCEngine::Editor::UI::ConsoleWarningColor();
+ }
+
+ return status.success
+ ? XCEngine::Editor::UI::ConsoleSecondaryTextColor()
+ : XCEngine::Editor::UI::ConsoleErrorColor();
+}
+
std::string BuildImportStatusText(const XCEngine::Resources::AssetImportService::ImportStatusSnapshot& status) {
if (!status.HasValue()) {
return "Library: ready";
@@ -76,6 +122,32 @@ std::string BuildImportStatusText(const XCEngine::Resources::AssetImportService:
return text;
}
+std::string BuildSceneLoadStatusText(const XCEngine::Editor::SceneLoadProgressSnapshot& status) {
+ if (!status.HasValue()) {
+ return "Scene: ready";
+ }
+
+ std::string text = "Scene: ";
+ if (!status.success) {
+ text += status.message.empty() ? "failed" : status.message;
+ return text;
+ }
+
+ if (status.inProgress) {
+ text += status.message.empty() ? "loading..." : status.message;
+ } else {
+ text += "ready";
+ }
+
+ const std::uint64_t elapsedMs = ResolveSceneLoadElapsedMs(status);
+ if (elapsedMs > 0) {
+ text += " (";
+ text += std::to_string(elapsedMs);
+ text += " ms)";
+ }
+ return text;
+}
+
std::string BuildImportStatusTooltipText(const XCEngine::Resources::AssetImportService::ImportStatusSnapshot& status) {
if (!status.HasValue()) {
return "No import operations have been recorded in this session.";
@@ -107,6 +179,68 @@ std::string BuildImportStatusTooltipText(const XCEngine::Resources::AssetImportS
return tooltip;
}
+std::string BuildSceneLoadStatusTooltipText(const XCEngine::Editor::SceneLoadProgressSnapshot& status) {
+ if (!status.HasValue()) {
+ return "No scene load operations have been recorded in this session.";
+ }
+
+ std::string tooltip;
+ tooltip += "Operation: ";
+ tooltip += status.operation.empty() ? "Scene Load" : status.operation;
+ if (!status.scenePath.empty()) {
+ tooltip += "\nScene: ";
+ tooltip += status.scenePath;
+ }
+ tooltip += "\nState: ";
+ tooltip += status.inProgress ? "Running" : (status.success ? "Succeeded" : "Failed");
+
+ const std::uint64_t structureMs =
+ ResolveSceneLoadMarkerDeltaMs(status.startedAtMs, status.structureReadyAtMs);
+ if (structureMs > 0) {
+ tooltip += "\nStructure restore: ";
+ tooltip += std::to_string(structureMs);
+ tooltip += " ms";
+ }
+
+ const std::uint64_t firstFrameMs =
+ ResolveSceneLoadMarkerDeltaMs(status.startedAtMs, status.firstFrameAtMs);
+ if (firstFrameMs > 0) {
+ tooltip += "\nFirst viewport frame: ";
+ tooltip += std::to_string(firstFrameMs);
+ tooltip += " ms";
+ }
+
+ const std::uint64_t interactiveMs =
+ ResolveSceneLoadMarkerDeltaMs(status.startedAtMs, status.interactiveAtMs);
+ if (interactiveMs > 0) {
+ tooltip += "\nFirst interactive frame: ";
+ tooltip += std::to_string(interactiveMs);
+ tooltip += " ms";
+ }
+
+ const std::uint64_t streamingMs =
+ ResolveSceneLoadMarkerDeltaMs(status.startedAtMs, status.streamingCompletedAtMs);
+ if (streamingMs > 0) {
+ tooltip += "\nRuntime streaming complete: ";
+ tooltip += std::to_string(streamingMs);
+ tooltip += " ms";
+ }
+
+ if (status.peakPendingAsyncLoads > 0) {
+ tooltip += "\nPeak async loads: ";
+ tooltip += std::to_string(status.peakPendingAsyncLoads);
+ }
+ if (status.inProgress && status.currentPendingAsyncLoads > 0) {
+ tooltip += "\nPending async loads: ";
+ tooltip += std::to_string(status.currentPendingAsyncLoads);
+ }
+ if (!status.message.empty()) {
+ tooltip += "\nStatus: ";
+ tooltip += status.message;
+ }
+ return tooltip;
+}
+
template
void QueueDeferredAction(std::function& pendingAction, Fn&& fn) {
if (!pendingAction) {
@@ -457,8 +591,12 @@ void ProjectPanel::Render() {
void ProjectPanel::RenderToolbar() {
const auto importStatus = ::XCEngine::Resources::ResourceManager::Get().GetProjectAssetImportStatus();
+ const SceneLoadProgressSnapshot sceneLoadStatus =
+ m_context != nullptr ? m_context->GetSceneManager().GetSceneLoadProgress() : SceneLoadProgressSnapshot{};
const std::string importStatusText = BuildImportStatusText(importStatus);
const std::string importStatusTooltip = BuildImportStatusTooltipText(importStatus);
+ const std::string sceneLoadStatusText = BuildSceneLoadStatusText(sceneLoadStatus);
+ const std::string sceneLoadStatusTooltip = BuildSceneLoadStatusTooltipText(sceneLoadStatus);
UI::PanelToolbarScope toolbar(
"ProjectToolbar",
@@ -473,8 +611,9 @@ void ProjectPanel::RenderToolbar() {
}
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(0.0f, 0.0f));
- if (ImGui::BeginTable("##ProjectToolbarLayout", 2, ImGuiTableFlags_NoSavedSettings | ImGuiTableFlags_SizingStretchProp)) {
+ if (ImGui::BeginTable("##ProjectToolbarLayout", 3, ImGuiTableFlags_NoSavedSettings | ImGuiTableFlags_SizingStretchProp)) {
ImGui::TableSetupColumn("##ImportStatus", ImGuiTableColumnFlags_WidthStretch);
+ ImGui::TableSetupColumn("##SceneLoadStatus", ImGuiTableColumnFlags_WidthStretch);
ImGui::TableSetupColumn("##Search", ImGuiTableColumnFlags_WidthFixed, 220.0f);
ImGui::TableNextRow();
@@ -492,6 +631,19 @@ void ProjectPanel::RenderToolbar() {
ImGui::EndTooltip();
}
+ ImGui::TableNextColumn();
+ ImGui::AlignTextToFramePadding();
+ ImGui::PushStyleColor(ImGuiCol_Text, ResolveSceneLoadStatusColor(sceneLoadStatus));
+ ImGui::TextUnformatted(sceneLoadStatusText.c_str());
+ ImGui::PopStyleColor();
+ if (ImGui::IsItemHovered() && !sceneLoadStatusTooltip.empty()) {
+ ImGui::BeginTooltip();
+ ImGui::PushTextWrapPos(ImGui::GetFontSize() * 36.0f);
+ ImGui::TextUnformatted(sceneLoadStatusTooltip.c_str());
+ ImGui::PopTextWrapPos();
+ ImGui::EndTooltip();
+ }
+
ImGui::TableNextColumn();
UI::ToolbarSearchField("##Search", "Search assets", m_searchBuffer, sizeof(m_searchBuffer));
diff --git a/editor/src/panels/ViewportPanelContent.h b/editor/src/panels/ViewportPanelContent.h
index a5606c7b..24ea337f 100644
--- a/editor/src/panels/ViewportPanelContent.h
+++ b/editor/src/panels/ViewportPanelContent.h
@@ -83,7 +83,9 @@ inline void RenderViewportInteractionSurface(
result.clickedMiddle = ImGui::IsItemClicked(ImGuiMouseButton_Middle);
}
-inline ViewportPanelContentResult RenderViewportPanelContent(IEditorContext& context, EditorViewportKind kind) {
+inline ViewportPanelContentResult RenderViewportPanelContent(
+ IEditorContext& context,
+ EditorViewportKind kind) {
ViewportPanelContentResult result = {};
UI::CollapsePanelSectionSpacing();
result.availableSize = ImGui::GetContentRegionAvail();
diff --git a/engine/include/XCEngine/Core/Layer.h b/engine/include/XCEngine/Core/Layer.h
index 8473bdde..31ed0746 100644
--- a/engine/include/XCEngine/Core/Layer.h
+++ b/engine/include/XCEngine/Core/Layer.h
@@ -14,7 +14,7 @@ public:
virtual void onDetach() {}
virtual void onUpdate(float dt) {}
virtual void onEvent(void* event) {}
- virtual void onImGuiRender() {}
+ virtual void onUIRender() {}
const std::string& getName() const { return m_name; }
diff --git a/engine/include/XCEngine/Core/LayerStack.h b/engine/include/XCEngine/Core/LayerStack.h
index f331cfff..535f547e 100644
--- a/engine/include/XCEngine/Core/LayerStack.h
+++ b/engine/include/XCEngine/Core/LayerStack.h
@@ -20,7 +20,7 @@ public:
void onUpdate(float dt);
void onEvent(void* event);
- void onImGuiRender();
+ void onUIRender();
void onAttach();
void onDetach();
@@ -78,9 +78,9 @@ inline void LayerStack::onEvent(void* event) {
}
}
-inline void LayerStack::onImGuiRender() {
+inline void LayerStack::onUIRender() {
for (auto& layer : m_layers) {
- layer->onImGuiRender();
+ layer->onUIRender();
}
}