diff --git a/docs/plan/NewEditor_FrameLifecycleRuntimeRefactorPlan_2026-04-22.md b/docs/plan/NewEditor_FrameLifecycleRuntimeRefactorPlan_2026-04-22.md deleted file mode 100644 index aa0ea29b..00000000 --- a/docs/plan/NewEditor_FrameLifecycleRuntimeRefactorPlan_2026-04-22.md +++ /dev/null @@ -1,251 +0,0 @@ -# NewEditor Frame/Lifecycle Runtime Refactor Plan - -Date: 2026-04-22 - -## 1. Objective - -This plan targets the highest-priority remaining architecture debt in `new_editor`: - -1. frame execution still has multiple live owners -2. window destruction still has multiple live owners -3. `EditorShellRuntime` is still a per-frame god-object - -The goal is not to split files mechanically. -The goal is to collapse ownership so that: - -1. only one module can drive a frame -2. only one module can finalize window death -3. `EditorShellRuntime` becomes a thin facade over explicit per-frame stages - -## 2. Confirmed Root Issues - -### 2.1 Frame scheduling is not single-owner - -`RenderFrame()` is currently called from: - -- main host render loop -- `WM_PAINT` -- `WM_SIZE` -- `WM_DPICHANGED` -- `WM_EXITSIZEMOVE` -- borderless chrome transition logic - -This means rendering is still driven by both: - -1. the normal frame loop -2. synchronous Win32 message-side side effects - -As long as this remains true, frame timing, transfer request ordering, and present timing -will keep depending on which path happened to trigger first. - -### 2.2 Window lifetime is not single-owner - -`Shutdown()`, `DestroyWindow(...)`, `MarkDestroyed()`, and host erase currently happen from -multiple places in `WindowManager`, host runtime, and workspace coordination. - -This means there is still no single authoritative answer to: - -1. who requests close -2. who observes native window destruction -3. who shuts runtime down -4. who removes the managed window from the host container - -This is the same root class of problem that previously caused sub-window close regressions. - -### 2.3 EditorShellRuntime still owns too many per-frame responsibilities - -`EditorShellRuntime::Update(...)` still performs, in one place: - -1. shell definition build -2. workspace/session sync -3. dock host interaction update -4. viewport request/update -5. hosted panel input routing -6. hosted panel update -7. status/trace/command-focus synchronization - -That makes the runtime difficult to reason about and causes unrelated per-frame concerns to -stay tightly coupled. - -## 3. Target End State - -After this refactor: - -1. only one frame driver can execute `BeginFrame -> UpdateAndAppend -> Present` -2. Win32 message dispatch only records frame requests / dirty reasons, never renders directly -3. borderless chrome logic can request a new frame, but cannot render directly -4. only one lifecycle coordinator can finalize managed window destruction -5. `WorkspaceCoordinator` can request a window-set mutation, but cannot directly destroy windows -6. `EditorShellRuntime` becomes a thin composition facade over smaller stage modules - -## 4. New Modules - -### 4.1 Frame ownership - -Add: - -- `new_editor/app/Platform/Win32/EditorWindowFrameDriver.h` -- `new_editor/app/Platform/Win32/EditorWindowFrameDriver.cpp` -- `new_editor/app/Platform/Win32/EditorWindowFrameRequest.h` - -Responsibilities: - -- own the only live path that executes a frame -- consume frame request reasons / dirty state -- normalize when transfer requests are collected and applied - -### 4.2 Lifecycle ownership - -Add: - -- `new_editor/app/Platform/Win32/WindowManager/EditorWindowLifecycleCoordinator.h` -- `new_editor/app/Platform/Win32/WindowManager/EditorWindowLifecycleCoordinator.cpp` - -Responsibilities: - -- own close request state -- own runtime shutdown sequencing -- own native destroy finalization and managed erase -- own primary-window cascade behavior - -### 4.3 Shell runtime stage split - -Add: - -- `new_editor/app/Composition/EditorShellSessionCoordinator.h` -- `new_editor/app/Composition/EditorShellSessionCoordinator.cpp` -- `new_editor/app/Composition/EditorShellInteractionEngine.h` -- `new_editor/app/Composition/EditorShellInteractionEngine.cpp` -- `new_editor/app/Composition/EditorShellHostedPanelCoordinator.h` -- `new_editor/app/Composition/EditorShellHostedPanelCoordinator.cpp` -- `new_editor/app/Composition/EditorShellDrawComposer.h` -- `new_editor/app/Composition/EditorShellDrawComposer.cpp` - -Responsibilities: - -- `SessionCoordinator`: context/workspace/session/command-focus/status synchronization -- `InteractionEngine`: shell definition, dock host interaction, shell frame/state update -- `HostedPanelCoordinator`: hosted panel input filtering, panel update, panel capture ownership -- `DrawComposer`: shell draw packets, panel draw packets, viewport frame application to draw data - -## 5. Refactor Strategy - -### Phase A. Collapse frame execution to one driver - -Refactor current flow so that: - -1. `EditorWindowHostRuntime` requests frame execution through `EditorWindowFrameDriver` -2. `EditorWindowMessageDispatcher` no longer calls `RenderFrame(...)` directly -3. `EditorWindowChromeController` no longer calls `RenderFrame(...)` directly -4. `EditorWindow` no longer exposes uncontrolled render entrypoints to unrelated callers - -Message-side behavior must become: - -- mark dirty -- enqueue reason -- invalidate host window when needed - -But never: - -- begin a frame -- build draw data -- present directly - -### Phase B. Collapse lifecycle to one owner - -Refactor current flow so that: - -1. `WM_CLOSE` only means `RequestClose` -2. `WM_DESTROY` only reports native death -3. `EditorWindowLifecycleCoordinator` becomes the only place that can: - - call `Shutdown()` - - finalize managed destruction - - erase the managed window from host storage - - cascade close from the primary window -4. `WorkspaceCoordinator` stops duplicating host destroy logic - -### Phase C. Split EditorShellRuntime by stage, not by helper dumping - -Do not split by random utility extraction. -Split by stage ownership: - -1. session sync stage -2. shell interaction stage -3. hosted panel stage -4. draw composition stage - -`EditorShellRuntime` should remain as a thin orchestrating facade that wires these stages -together in a stable order. - -### Phase D. Normalize panel capture ownership - -As part of hosted panel coordination: - -1. stop hardcoding panel capture checks directly in the runtime facade where possible -2. centralize panel capture ownership through one hosted-panel coordination boundary -3. make it explicit which hosted panels participate in pointer-stream ownership decisions - -## 6. File-Level Impact - -### 6.1 Existing files to shrink / re-scope - -- `new_editor/app/Platform/Win32/EditorWindow.cpp` -- `new_editor/app/Platform/Win32/EditorWindow.h` -- `new_editor/app/Platform/Win32/EditorWindowChromeController.cpp` -- `new_editor/app/Platform/Win32/WindowManager/EditorWindowHostRuntime.cpp` -- `new_editor/app/Platform/Win32/WindowManager/EditorWindowHostRuntime.h` -- `new_editor/app/Platform/Win32/WindowManager/EditorWindowMessageDispatcher.cpp` -- `new_editor/app/Platform/Win32/WindowManager/EditorWindowWorkspaceCoordinator.cpp` -- `new_editor/app/Composition/EditorShellRuntime.cpp` -- `new_editor/app/Composition/EditorShellRuntime.h` - -### 6.2 Build graph - -Update: - -- `new_editor/CMakeLists.txt` - -to register the new frame/lifecycle/runtime stage units. - -## 7. Validation - -### 7.1 Structural validation - -After completion: - -1. direct `RenderFrame(...)` call sites outside the frame driver must be gone -2. duplicate `Shutdown()/DestroyWindow()/MarkDestroyed()/erase` chains must be gone -3. `WorkspaceCoordinator` must no longer perform raw window destruction -4. `EditorShellRuntime` facade must delegate to explicit stage modules - -### 7.2 Runtime validation - -Must validate: - -1. editor starts normally -2. main window continues rendering normally -3. resize / maximize / drag-restore no longer perform direct message-side rendering -4. detached panel windows and tool windows still open/close correctly -5. closing child windows does not close the main window -6. closing the primary window still cascades correctly -7. frame transfer requests still behave correctly during docking / detach workflows - -## 8. Execution Order - -1. introduce frame request model and frame driver -2. remove message-side and chrome-side direct rendering -3. introduce lifecycle coordinator -4. remove duplicate destroy/shutdown ownership from host runtime and workspace coordinator -5. split `EditorShellRuntime` into session/interaction/panel/draw stages -6. rebuild and smoke-test -7. perform structural grep validation - -## 9. Success Criteria - -This refactor is complete only if all of the following are true: - -1. `new_editor` frame execution has a single live owner -2. `new_editor` managed window destruction has a single live owner -3. `EditorShellRuntime` is no longer a monolithic per-frame god-object -4. resize / dpi / chrome transitions no longer trigger direct rendering from message handlers -5. build and smoke validation both pass diff --git a/docs/plan/NewEditor_LiveResizeJitterRootCleanupPlan_2026-04-22.md b/docs/plan/NewEditor_LiveResizeJitterRootCleanupPlan_2026-04-22.md deleted file mode 100644 index 126f349e..00000000 --- a/docs/plan/NewEditor_LiveResizeJitterRootCleanupPlan_2026-04-22.md +++ /dev/null @@ -1,129 +0,0 @@ -# NewEditor Live Resize Jitter Root Cleanup Plan - -Date: 2026-04-23 -Status: Planned - -## 1. Objective - -彻底清理 `new_editor` 自定义顶栏窗口在实时拖拽 resize 时的抖动问题,并且继续保持当前已经恢复的“内容不变形”契约。 - -这次修复只接受下面这个结果: - -1. 右侧和下侧,尤其右下角,拖拽时不再出现明显抖动 -2. `Scene` 内容继续实时跟随,不允许用冻结、延迟、复用旧帧拉伸来掩盖问题 -3. 自定义顶栏和 borderless resize 机制继续保留 -4. 代码结构保持主线化,不能靠零散补丁维持 - -## 2. Confirmed Root Cause - -当前问题的根源不是 `Scene` 视口本身慢,也不是自定义顶栏设计有问题。 - -已经确认的根因是窗口层的 resize / present 时序错误: - -1. `EditorWindowChromeController::HandleResizePointerMove(...)` 在 borderless live resize 中先把目标尺寸写进 predicted client size -2. `EditorWindow::ResolveRenderClientPixelSize(...)` 会优先使用这个 predicted size -3. 于是这一步立即帧会按“未来尺寸”完成整窗布局、viewport 请求和 UI 合成 -4. 但是此时 HWND/client rect 还没有通过 `SetWindowPos(...)` 真正确认到这个尺寸 -5. 这帧随后直接进入可见 swapchain,再由 DXGI `DXGI_SCALING_STRETCH` 填到仍处于旧几何的窗口表面 - -因此抖动的本质不是“重绘慢”,而是: - -1. 可见 swapchain 被拿来展示了一帧与当前 HWND 几何不一致的 future-size 内容 -2. DXGI stretch 误差在归一化坐标接近 `1.0` 的位置最大,所以右侧和下侧最明显 -3. `Scene` 只是把这段错误状态维持得更久,因此把问题放大了 - -直接调整 `Scene` 视口 splitter 之所以平滑,是因为那条路径没有“future-size frame 先进入旧 HWND 可见 swapchain”这个错误。 - -## 3. Architectural Decision - -根修复必须把两个尺寸彻底分开: - -1. `predicted size` - 用于 borderless resize 期间的下一步布局推演和离屏整窗合成 -2. `committed visible size` - 只对应已经被 Win32 / DXGI 真正确认的 HWND client size 和可见 swapchain backbuffer - -最终原则: - -1. 预测尺寸可以驱动布局和离屏合成 -2. 可见 swapchain 只能 present committed size 的内容 -3. HWND 几何提交前,future-size frame 不能再直接进入可见 swapchain - -## 4. Red Lines - -本次修复不能做下面这些事: - -1. 不能取消自定义顶栏 -2. 不能把 resize 责任下沉到 UI Editor 层 -3. 不能冻结 `Scene` -4. 不能复用旧纹理做拉伸/延迟展示来冒充实时 resize -5. 不能在各处消息处理里堆条件分支补丁 -6. 不能破坏 tab 拖拽拆窗、utility window、正常 `WM_SIZE`、viewport splitter resize - -## 5. Target End State - -修完之后,窗口层行为必须变成下面这样: - -1. pointer move 到来时,先计算 predicted rect -2. predicted rect 只驱动离屏整窗 composition target 的大小和内容 -3. `SetWindowPos(...)` 提交 HWND 几何 -4. 真正收到 committed client size 后,再 resize 可见 swapchain -5. 可见 swapchain 只展示 committed size backbuffer -6. 如果 predicted size 和 committed size 一致,则直接把最近一次预测合成结果拷到 committed backbuffer,再 present -7. 如果 committed size 变化继续推进,则按新的 predicted size 重新离屏合成,但仍不提前污染可见 swapchain - -## 6. Implementation Plan - -### Phase A. Separate predicted composition from visible swapchain state - -1. 在 `D3D12WindowSwapChainPresenter` / `D3D12WindowRenderer` 引入窗口级离屏 composition target -2. 为它建立独立的 color texture、RTV、SRV 和 `RenderSurface` -3. 把“当前可见 swapchain 尺寸”和“最近预测 composition 尺寸”分成两套状态 - -### Phase B. Route immediate resize frames into the offscreen composition target - -1. 给窗口 render loop 增加 present target policy -2. 正常 steady-state 帧仍然走 committed swapchain backbuffer -3. borderless resize pointer-move immediate frame 改为绘制到 predicted composition target -4. 这一步仍然执行真实 `Scene` 渲染和整窗 UI 合成,不做冻结 - -### Phase C. Commit HWND geometry first, then publish only committed-size content - -1. 清理 `HandleResizePointerMove(...)` 的顺序,让未来尺寸帧不再直接 present 到旧 swapchain -2. `SetWindowPos(...)` 提交后,由实际 committed client size 驱动 swapchain resize -3. 只有 committed size backbuffer 才允许进入 `Present()` -4. 如果最近的 predicted composition 与 committed size 匹配,则把该离屏结果拷贝到 committed backbuffer 后 present - -### Phase D. Narrow WM_SIZE / prediction acknowledgement semantics - -1. 预测尺寸状态不再表达“这帧已经进入可见 swapchain” -2. 改为表达“这份 predicted composition 已经生成,等待 committed publish” -3. `WM_SIZE` 只消费 committed size,并决定是否复用最近一次 composition 结果 -4. 保留已经正确收窄的 frame retirement / lifecycle 安全逻辑 - -### Phase E. Clean up right/bottom asymmetry at the actual source - -1. 彻底移除“future-size frame displayed in old HWND”这件事 -2. 让 DXGI stretch 不再承担预测阶段的显示职责 -3. 右侧、下侧、右下角的额外抖动应当因此一起消失 - -## 7. Validation Requirements - -完成前必须验证: - -1. `Scene` 激活时拖右侧、下侧、右下角,不再出现现在这种严重抖动 -2. `Scene` 内容继续实时响应 resize,而不是冻结或延后 -3. `Game`、空视口、普通面板 resize 行为不回退 -4. 直接拖 `Scene` 视口 splitter 仍然保持原来的平滑度 -5. tab 拖出独立窗口、utility window、maximize/restore、DPI change 不回退 -6. 不再出现 resize 期间 future-size frame 被直接 present 到旧窗口的问题 - -## 8. Completion Criteria - -只有同时满足下面几点,这个问题才算真正收拾干净: - -1. 不变形契约保留 -2. `Scene` 保持实时 resize -3. 右/下/右下 resize 抖动被从源头消除 -4. 可见 swapchain 与 HWND committed 几何重新建立一一对应 -5. 实现落在窗口平台层和渲染主线上,而不是 UI 层补丁 diff --git a/docs/plan/NewEditor_PortBoundaryRefactorPlan_2026-04-23.md b/docs/plan/NewEditor_PortBoundaryRefactorPlan_2026-04-23.md deleted file mode 100644 index c9f124ae..00000000 --- a/docs/plan/NewEditor_PortBoundaryRefactorPlan_2026-04-23.md +++ /dev/null @@ -1,176 +0,0 @@ -# NewEditor Port Boundary Refactor Plan - -Date: 2026-04-23 -Status: In Progress - -## 1. Objective - -Refactor `new_editor` so that external boundaries are modeled explicitly instead of being mixed into a single `app/Ports` bucket. - -This plan targets three concrete issues: - -1. `SystemInteractionPort` is a platform/system boundary. -2. `TexturePort` and `ViewportRenderPort` are rendering host boundaries. -3. `ShaderResourceDescriptorAllocatorPort` is not a real application boundary because it leaks `d3d12.h` types directly into the interface. - -The goal is to make the directory structure match the actual architecture, reduce "add another port" cargo cult growth, and make later editor features cheaper to extend. - -## 2. Current Problems - -### 2.1 Mixed abstraction levels - -`app/Ports` currently mixes: - -1. operating-system integration -2. rendering-host integration -3. backend-internal D3D12 helpers - -These are not the same architectural seam and should not share one bucket. - -### 2.2 The word "Port" is being used too broadly - -The current structure encourages a bad rule: - -"If something crosses a layer, add a port." - -That is not the intended use of ports. A port should exist only for a real boundary contract, not for every data handoff or helper collaboration. - -### 2.3 Fake abstraction in D3D12 descriptor allocation - -`ShaderResourceDescriptorAllocatorPort` exposes: - -1. `D3D12_CPU_DESCRIPTOR_HANDLE` -2. `D3D12_GPU_DESCRIPTOR_HANDLE` -3. `UINT` -4. direct D3D12 header dependency - -That makes it a D3D12-specific helper wearing an interface costume. It should be concrete backend code unless there is a proven need for a cross-backend contract. - -## 3. Target End State - -After the refactor: - -1. system-facing services live in a system-oriented location and namespace -2. rendering host contracts live under rendering host boundaries -3. D3D12-only helpers stay D3D12-local unless a backend-agnostic contract is justified -4. new features do not add interfaces by default; they only add one when a real host boundary appears - -## 4. Boundary Rules - -Use a boundary interface only when all of the following are true: - -1. the caller depends on an external capability -2. dependency inversion is useful at the application boundary -3. the contract can be expressed without leaking backend-only details -4. there is a realistic need for substitution, testing, or multiple implementations - -Do not add a boundary interface for: - -1. feature-to-feature communication -2. panel-to-runtime state transfer -3. helpers inside one rendering backend -4. temporary include avoidance - -## 5. Execution Phases - -### Phase A. Split the `app/Ports` bucket by responsibility - -Create explicit homes for: - -1. system interaction service -2. rendering host contracts - -Then move call sites to the new locations and names. - -### Phase B. Remove the fake D3D12 port - -Delete `ShaderResourceDescriptorAllocatorPort` and make the descriptor allocator a concrete D3D12 helper again. - -### Phase C. Clean up forwarding and naming - -Replace `Ports::...` references with names that explain the real role: - -1. `System::SystemInteractionService` -2. `Rendering::Host::UiTextureHost` -3. `Rendering::Host::ViewportRenderHost` - -### Phase D. Validate and harden - -1. ensure no remaining includes depend on `app/Ports` -2. ensure the app still builds -3. document the rule for future extension - -## 6. File Plan - -### New files - -1. `new_editor/app/System/SystemInteractionService.h` -2. `new_editor/app/Rendering/Host/HostFwd.h` -3. `new_editor/app/Rendering/Host/UiTextureHost.h` -4. `new_editor/app/Rendering/Host/ViewportRenderHost.h` - -### Files expected to be updated - -1. `new_editor/app/Bootstrap/Application.h` -2. `new_editor/app/Bootstrap/Application.cpp` -3. `new_editor/app/Composition/EditorContext.h` -4. `new_editor/app/Composition/EditorContext.cpp` -5. `new_editor/app/Composition/EditorShellRuntime.h` -6. `new_editor/app/Composition/EditorShellRuntime.cpp` -7. `new_editor/app/Features/Project/ProjectPanel.h` -8. `new_editor/app/Features/Project/ProjectPanel.cpp` -9. `new_editor/app/Features/Scene/SceneViewportFeature.h` -10. `new_editor/app/Features/Scene/SceneViewportFeature.cpp` -11. `new_editor/app/Features/Scene/SceneViewportController.h` -12. `new_editor/app/Features/Scene/SceneViewportController.cpp` -13. `new_editor/app/Features/Scene/SceneViewportToolOverlay.h` -14. `new_editor/app/Features/Scene/SceneViewportToolOverlay.cpp` -15. `new_editor/app/Rendering/Assets/BuiltInIcons.h` -16. `new_editor/app/Rendering/Assets/BuiltInIcons.cpp` -17. `new_editor/app/Rendering/D3D12/D3D12UiTextureHost.h` -18. `new_editor/app/Rendering/D3D12/D3D12WindowRenderer.h` -19. `new_editor/app/Rendering/D3D12/D3D12ShaderResourceDescriptorAllocator.h` -20. `new_editor/app/Rendering/Viewport/ViewportHostService.h` -21. `new_editor/app/Rendering/Viewport/ViewportHostService.cpp` -22. `new_editor/app/Rendering/Viewport/ViewportRenderTargets.h` -23. `new_editor/app/Rendering/Viewport/ViewportRenderTargets.cpp` -24. `new_editor/app/Support/EmbeddedPngLoader.h` -25. `new_editor/app/Support/EmbeddedPngLoader.cpp` -26. `new_editor/app/Platform/Win32/System/Win32SystemInteractionHost.h` -27. `new_editor/app/Platform/Win32/Content/EditorWindowContentController.h` - -### Files expected to be retired - -1. `new_editor/app/Ports/SystemInteractionPort.h` -2. `new_editor/app/Ports/TexturePort.h` -3. `new_editor/app/Ports/ViewportRenderPort.h` -4. `new_editor/app/Ports/PortFwd.h` -5. `new_editor/app/Ports/ShaderResourceDescriptorAllocatorPort.h` - -## 7. First Execution Slice - -Execute the smallest high-value slice first: - -1. introduce the new system and rendering host interface headers -2. switch call sites away from `Ports::` -3. remove `ShaderResourceDescriptorAllocatorPort` -4. leave behavior unchanged - -This keeps risk lower while immediately fixing the worst architectural confusion. - -## 8. Validation - -Minimum validation for this refactor: - -1. `rg "Ports::|Ports/" new_editor/app` returns no production references -2. `ShaderResourceDescriptorAllocatorPort` no longer exists -3. `XCUIEditorApp` compiles, or any build failure is isolated and reported precisely - -## 9. Completion Criteria - -This refactor is complete only when: - -1. `app/Ports` is no longer the active boundary bucket -2. system and rendering host boundaries are visibly separated -3. D3D12-only helpers are no longer presented as generic app ports -4. future contributors can answer "where should a new boundary go?" without guessing diff --git a/docs/plan/NewEditor_PropertyGridInputRoutingRefactorPlan_2026-04-23.md b/docs/plan/NewEditor_PropertyGridInputRoutingRefactorPlan_2026-04-23.md deleted file mode 100644 index b46bd704..00000000 --- a/docs/plan/NewEditor_PropertyGridInputRoutingRefactorPlan_2026-04-23.md +++ /dev/null @@ -1,460 +0,0 @@ -# NewEditor PropertyGrid Input Routing Refactor Plan - -Date: 2026-04-23 -Status: Planned - -## 1. Objective - -彻底解决 `new_editor` Inspector PropertyGrid 中由共享输入状态污染导致的交互异常,重点包括但不限于: - -1. `Color` 字段关闭颜色选择器后首击失效、需要双击、甚至双击也无反应。 -2. 不同字段类型之间切换时,前一个字段的焦点、按下态、编辑态污染后一个字段。 -3. `PropertyGrid` 内部同样表现为“看起来都是按钮/控件”,但行为不一致,`Enum`、`Color`、`Bool`、`Asset`、`Number/Text/Vector` 各走各的隐式输入规则。 - -这次不是做症状修补,而是把 `PropertyGrid` 的输入架构改成单目标路由和明确所有权模型,从根上消掉这类首击被吃、状态串扰、字段间抢状态的问题。 - -## 2. Confirmed Root Cause - -当前问题不是颜色选择器窗口生命周期本身导致的,而是 `PropertyGrid` 输入分发架构有设计缺陷。 - -已确认的根因如下: - -1. `UpdateUIEditorPropertyGridInteraction(...)` 会把同一个事件依次送进多套字段处理逻辑: - - `ProcessNumberFieldEvent(...)` - - `ProcessTextFieldEvent(...)` - - `ProcessColorFieldEvent(...)` - - `ProcessVector*FieldEvent(...)` - - `ProcessAssetFieldEvent(...)` - 见 `new_editor/src/Fields/UIEditorPropertyGridInteraction.cpp` -2. `Number/Text/Vector` 的共享处理器内部不是只处理“当前命中的那个字段”,而是遍历所有可见字段,并把 `hadFocus`、`focused` 也视为本次事件的有效证据。 -3. 共享编辑会话 `editableFieldSession` 会被拷入每个字段的局部交互状态,再被某个字段处理器写回全局,导致一次事件期间多个字段都可能改写共享状态。 -4. `pressedFieldId` 是 `PropertyGrid` 级别的共享按下态,但多个字段处理器会在处理中途清掉它。 -5. `Color` 字段打开颜色选择器依赖“两阶段命中”: - - `PointerButtonDown` 时 `pressedFieldId = 当前 color 字段` - - `PointerButtonUp` 时再次确认 `pressedFieldId` 仍然是该字段 - 所以它对共享按下态污染最敏感。 -6. `Enum` 下拉菜单则不同,它在 `PropertyGrid` 的通用 `PointerButtonUp` 路径里直接根据当前命中结果 `OpenPopup(...)`,不依赖 `Color` 那套专门的 armed/确认链路,所以表面上表现更稳定。 - -结论是:当前 bug 的根不是 `ColorPicker`,而是 `PropertyGrid` 自身没有明确的输入 owner,导致事件广播、共享状态回写、字段局部状态和全局状态互相污染。 - -## 3. Problem Statement - -当前架构同时踩中了下面几个反模式: - -1. 广播式分发:一个事件被多套字段逻辑重复消费。 -2. 共享可变状态:`pressedFieldId`、`editableFieldSession`、`propertyGridState.focused` 由多个处理器写入。 -3. 旧焦点兜底:把 `hadFocus`、`focused` 这种陈旧状态当作本次点击的直接交互证据。 -4. 路由和行为耦合:`PropertyGrid` 既负责决定事件给谁,又让字段处理器反过来改全局路由状态。 -5. 字段类别缺少统一抽象:`Color` 和 `Enum` 都像“动作型控件”,但输入语义却不统一。 - -如果继续在现状上加特判,只会形成新的状态分叉,后面还会在 `Asset`、`Bool`、`Vector`、键盘导航、失焦恢复上继续炸。 - -## 4. Target End State - -重构完成后,`PropertyGrid` 必须满足下面这些结构性约束: - -1. 每个输入事件在进入字段处理前,先由 `PropertyGrid` 路由层解析出唯一目标。 -2. 任意时刻只有一个字段拥有“按下态”。 -3. 任意时刻只有一个字段拥有“活动编辑态”。 -4. 任意时刻只有一个字段拥有“弹出层所有权”。 -5. 字段处理器不再直接清理或重置 `PropertyGrid` 的共享路由状态。 -6. `Selection`、`Focus`、`Armed`、`ActiveEdit`、`PopupOwner` 是不同概念,不能再混用。 -7. `Color`、`Enum`、`Bool`、`Asset` 这类动作型字段遵循统一输入契约。 -8. `Number`、`Text`、`Vector2/3/4` 这类内联编辑字段遵循统一输入契约。 -9. 关闭颜色选择器、关闭枚举下拉、提交数值编辑、切换字段后,下一次首击必须稳定命中目标控件。 - -## 5. Architectural Decision - -### 5.1 引入单目标路由层 - -`PropertyGrid` 必须先做一次统一 hit test 和 owner 解析,再决定这次事件只派发给谁。不能继续让每类字段都自己遍历所有字段再“看看自己是不是该处理”。 - -路由层需要先得到: - -1. `hoveredFieldId` -2. `hoveredPart` -3. `armedFieldId` -4. `capturedFieldId` -5. `activeEditFieldId` -6. `popupFieldId` - -然后按事件类型走固定路由规则: - -1. `PointerMove` 只更新 hover,以及发给 `capturedFieldId` 或当前 hover 字段。 -2. `PointerButtonDown` 只发给当前命中字段,并决定是否设置 `armedFieldId` / `capturedFieldId`。 -3. `PointerButtonUp` 只发给 `armedFieldId` 或 `capturedFieldId`,而不是再次广播给所有字段。 -4. `KeyDown` / `TextInput` 只发给 `activeEditFieldId` 或 `popupFieldId`。 -5. `FocusLost` 由路由层统一处理清理顺序,而不是让每个字段各自猜测。 - -### 5.2 引入明确的输入所有权状态 - -现有状态过于粗糙,后续需要拆成下面几个正交状态: - -1. `gridFocused` -2. `hoveredFieldId` -3. `hoveredPart` -4. `armedFieldId` -5. `capturedFieldId` -6. `activeEditFieldId` -7. `popupFieldId` -8. `popupHighlightedIndex` - -原则: - -1. `selectionModel` 只表示“谁被选中”。 -2. `gridFocused` 只表示“PropertyGrid 是否拥有键盘焦点”。 -3. `armedFieldId` 只表示“谁接收对应的抬起事件”。 -4. `activeEditFieldId` 只表示“谁接收文本编辑和编辑提交”。 -5. `popupFieldId` 只表示“谁拥有弹出层”。 - -任何字段逻辑都不应再拿 `selected == true` 或 `focused == true` 推断“这次点击就是我的”。 - -### 5.3 统一字段交互类别 - -后续要把字段分成两类,而不是继续按“字段类型代码文件”来决定输入语义。 - -#### A. Action Control - -适用字段: - -1. `Color` -2. `Enum` -3. `Bool` -4. `Asset` - -统一规则: - -1. 点击命中当前控件时由路由层设置 `armedFieldId`。 -2. 抬起时若仍满足激活条件,则执行动作。 -3. 动作可能是: - - 打开/关闭 popup - - 请求外部 picker - - toggle bool - - activate asset picker -4. 动作执行前由路由层统一决定是否提交其他活动编辑。 - -#### B. Inline Editor - -适用字段: - -1. `Number` -2. `Text` -3. `Vector2` -4. `Vector3` -5. `Vector4` - -统一规则: - -1. 点击命中值域时由路由层激活编辑或拖拽。 -2. 所有文本编辑态都只通过 `activeEditFieldId` 进入。 -3. 提交、取消、切换字段时由路由层统一协调。 -4. 不再通过 `hadFocus` 来“补判定”当前事件属于哪个字段。 - -## 6. State Refactor Plan - -### 6.1 `UIEditorPropertyGridState` - -需要把当前定义从“渲染态和交互 owner 混在一起”改成“路由态 + 视觉态”分离。 - -建议修改方向: - -1. 保留: - - `hoveredSectionId` - - `hoveredFieldId` - - `hoveredHitTarget` - - `popupHighlightedIndex` -2. 替换: - - `focused` -> `gridFocused` - - `pressedFieldId` -> `armedFieldId` -3. 新增: - - `capturedFieldId` - - `activeEditFieldId` - - 视情况新增 `activeFieldPart` -4. 明确 `popupFieldId` 是 owner,而不是顺手存一份状态。 - -### 6.2 `UIEditorPropertyGridInteractionState` - -当前 `editableFieldSession` 仍然是核心污染源之一。需要改成: - -1. 全局状态只保留 owner id 和必要的共享编辑资源。 -2. 局部字段会话只在 owner 命中时构造和更新。 -3. 非 owner 字段不再拿到一份复制出来的共享编辑会话。 - -也就是说,后续不应继续存在“给所有字段都 BuildInteractionState,然后再把共享 session 混进去”的流程。 - -## 7. Routing Refactor Plan - -### Phase A. 建立统一命中解析和 owner 解析 - -目标: - -1. 在 `UpdateUIEditorPropertyGridInteraction(...)` 入口统一解析当前命中的 section / field / field part。 -2. 明确当前事件是发给: - - hover target - - armed target - - captured target - - popup owner - - active editor - -实施内容: - -1. 抽出 `ResolveCurrentFieldTarget(...)`。 -2. 抽出 `ResolveEventOwner(...)`。 -3. 明确 `PointerButtonDown` 和 `PointerButtonUp` 的路由优先级。 - -完成标准: - -1. 一个事件最多只进入一个字段 owner 的处理逻辑。 -2. 非 owner 字段不会因为 `hadFocus` 或视觉状态变化而被视为“参与了这次交互”。 - -### Phase B. 把 Action Control 从共享广播链路中剥离 - -目标: - -1. 让 `Color`、`Enum`、`Bool`、`Asset` 都走统一的 action-control owner 路由。 -2. 消除 `Color` 与 `Enum` 当前“看起来都像按钮,但输入模型完全不同”的问题。 - -实施内容: - -1. 新建统一的 action-control dispatch 层。 -2. `Color` 不再依赖“先被别的字段处理器放过、再轮到我”的顺序偶然性。 -3. `Enum` 的 popup 打开逻辑保留语义,但改为经由统一 owner 路由触发。 -4. `Bool`、`Asset` 也并入相同的 click activation 契约。 - -完成标准: - -1. `Color` 和 `Enum` 首击都依赖同一套 armed/release 判定语义。 -2. `Action Control` 的 popup / picker / toggle 行为不再受其他字段旧焦点状态影响。 - -### Phase C. 把 Inline Editor 改成 owner-exclusive 编辑模型 - -目标: - -1. `Number/Text/Vector` 只处理当前活动编辑字段。 -2. 去掉 `hadFocus`、`focused` 参与当前事件归属判定。 - -实施内容: - -1. 删掉共享模板处理中“旧焦点也算有意义事件”的判定。 -2. `CommitActiveEdit(...)` 改为只在 owner 切换或路由层明确要求时触发。 -3. `StoreInteractionState(...)` 不再承担全局 owner 切换职责。 -4. 拖拽、文本输入、提交/取消都改成围绕 `activeEditFieldId` 运行。 - -完成标准: - -1. 点 `Color` 时不会因为前一个 `Number`/`Vector` 字段曾经 focused 而被抢走首击。 -2. 各内联编辑字段只在自己是 active owner 时接收编辑事件。 - -### Phase D. 统一 focus lost / popup close / picker return 的清理顺序 - -目标: - -1. 让 `FocusLost`、utility window 关闭、popup 关闭后的首击行为稳定。 - -实施内容: - -1. 路由层统一清理: - - `armedFieldId` - - `capturedFieldId` - - `activeEditFieldId` - - popup owner -2. 明确“关闭外部 picker”不会自动把旧字段恢复成 armed 状态。 -3. 明确“返回 Inspector 后的第一下点击”必须重新从命中解析开始。 - -完成标准: - -1. 关闭 `ColorPicker` 后直接单击 color swatch 即可再次打开。 -2. 不需要先额外点一下面板来恢复交互。 - -## 8. File-Level Execution Plan - -### 8.1 必改文件 - -1. `new_editor/include/XCEditor/Fields/UIEditorPropertyGrid.h` -2. `new_editor/include/XCEditor/Fields/UIEditorPropertyGridInteraction.h` -3. `new_editor/src/Fields/PropertyGridInteractionInternal.h` -4. `new_editor/src/Fields/UIEditorPropertyGridInteraction.cpp` -5. `new_editor/src/Fields/UIEditorPropertyGrid.cpp` - -### 8.2 可能需要同步调整的字段交互文件 - -1. `new_editor/src/Fields/UIEditorColorFieldInteraction.cpp` -2. `new_editor/src/Fields/UIEditorNumberFieldInteraction.cpp` -3. `new_editor/src/Fields/UIEditorTextFieldInteraction.cpp` -4. `new_editor/src/Fields/UIEditorVectorFieldInteractionShared.h` -5. `new_editor/src/Fields/UIEditorAssetFieldInteraction.cpp` - -### 8.3 原则上不该成为主战场的文件 - -1. `new_editor/app/Features/Inspector/InspectorPanel.cpp` -2. `new_editor/app/Platform/Win32/Windowing/EditorUtilityWindowCoordinator.cpp` -3. `new_editor/app/Platform/Win32/Chrome/EditorWindowChromeController.cpp` - -这些文件最多做接口适配,不应该再承担修 PropertyGrid 首击丢失问题的核心逻辑。 - -## 9. Detailed Execution Steps - -### Step 1. 先改状态模型,不碰行为 - -1. 在头文件里引入新的 owner 状态字段。 -2. 保持现有逻辑先能编译,通过适配层把旧字段名映射到新字段名。 -3. 明确哪些状态属于路由层,哪些属于视觉层。 - -交付物: - -1. 新的状态结构。 -2. 路由状态注释和使用约束。 - -### Step 2. 抽出统一命中和 owner 决策 - -1. 先把当前 scattered hit test 收拢到路由入口。 -2. 让 `PointerDown` / `PointerUp` 都走同一套 owner 解析。 -3. 暂时保留旧字段处理器,但只让 owner 字段收到事件。 - -交付物: - -1. `ResolveCurrentFieldTarget(...)` -2. `ResolveEventOwner(...)` - -### Step 3. 重写 Action Control 路由 - -1. 让 `Color` 和 `Enum` 共用 action-control 激活契约。 -2. 再纳入 `Bool` 和 `Asset`。 -3. 把“是否需要提交已有编辑”提升到路由层统一决定。 - -交付物: - -1. `Action Control` 输入约束。 -2. `Color` / `Enum` / `Bool` / `Asset` 的统一 owner 生命周期。 - -### Step 4. 重写 Inline Editor 路由 - -1. 删除基于 `hadFocus` 的事件归属判定。 -2. 只在 owner 切换时提交旧编辑。 -3. 把 vector 组件拖拽和文本输入纳入统一 owner-exclusive 逻辑。 - -交付物: - -1. 无 `hadFocus` 兜底判定的 `Inline Editor` 路由。 -2. 统一的 commit / cancel / drag 生命周期。 - -### Step 5. 统一外部 popup / picker 返回后的恢复行为 - -1. 把 `FocusLost` 和 popup close 的清理顺序固定下来。 -2. 做 color picker 返回、enum popup 切换、asset picker 返回等场景的首击验证。 - -交付物: - -1. 稳定的失焦恢复行为。 -2. 不依赖“双击补救”的交互语义。 - -### Step 6. 清理旧状态分支和垃圾代码 - -1. 删除只为旧架构兜底的 `hadFocus`、共享 pressed reset、补丁式焦点同步逻辑。 -2. 清理无主状态变量和死分支。 -3. 补充必要注释,说明 owner 规则。 - -交付物: - -1. 干净的主线逻辑。 -2. 可维护的输入架构。 - -## 10. Red Lines - -这次重构不能做下面这些事: - -1. 不能通过在 `InspectorPanel` 外层加 reset 或“返回时强制清状态”来掩盖问题。 -2. 不能依赖日志、延迟、双击判定、窗口 reopen、焦点强抢来修症状。 -3. 不能在 `Color` 专门加一堆例外分支,而不处理共享输入路由本身。 -4. 不能回退成“所有字段都当普通按钮”而破坏现有数值拖拽、文本编辑、键盘导航。 -5. 不能继续把 `selected`、`focused`、`pressed`、`editing` 当成同一种状态使用。 - -## 11. Validation Matrix - -### 11.1 Color - -1. 点击 `Color` 首次打开 picker。 -2. 关闭 picker 后直接再次点击同一 `Color` 首次打开。 -3. 先编辑 `Number`,再点 `Color`,首击打开。 -4. 先拖 `Vector`,再点 `Color`,首击打开。 -5. 切换不同组件中的 `Color` 字段,首击打开。 - -### 11.2 Enum - -1. 点击 `Enum` 首次打开下拉。 -2. `Enum` 打开后切到 `Color`,首击打开颜色选择器。 -3. `Color` 返回后切回 `Enum`,首击打开下拉。 - -### 11.3 Inline Editor - -1. `Number` 点击进入编辑、提交、切换字段正常。 -2. `Text` 点击进入编辑、提交、取消正常。 -3. `Vector2/3/4` 组件拖拽和文本编辑不回退。 - -### 11.4 Focus / Popup / External Window - -1. Inspector 失焦再获焦后,首击目标字段正常。 -2. `ColorPicker` 关闭后,首击 `Color` 正常。 -3. `Enum` popup 关闭后,首击任意 action-control 正常。 - -## 12. Risks and Mitigations - -### Risk A. 影响面大 - -原因: - -1. `PropertyGrid` 是多个字段类型的共同入口。 - -应对: - -1. 按 `Action Control` 和 `Inline Editor` 分阶段切换。 -2. 每阶段都以行为矩阵回归验证。 - -### Risk B. 向后兼容旧视觉状态 - -原因: - -1. `UIEditorPropertyGrid.cpp` 目前从共享状态推导很多视觉态。 - -应对: - -1. 先把 visual state 和 route state 分离。 -2. 保证绘制代码只消费稳定 owner 状态,不直接猜测交互过程。 - -### Risk C. Vector/Drag 回归 - -原因: - -1. `Vector` 拖拽目前和共享 session 深度耦合。 - -应对: - -1. 把拖拽先当作 `captured owner` 行为处理。 -2. 不在 action-control 重构阶段动 vector 细节。 - -## 13. Completion Criteria - -只有同时满足下面几条,这次重构才算真正完成: - -1. `Color` 首击打开问题在所有已知场景下消失。 -2. `Enum`、`Bool`、`Asset`、`Color` 共享统一 action-control 输入模型。 -3. `Number/Text/Vector` 不再依赖 `hadFocus` / `focused` 判定当前事件归属。 -4. `pressedFieldId` 这类共享按下态不再被多个字段处理器在一次事件里交叉清理。 -5. `PropertyGrid` 的输入所有权模型能够被清晰描述为: - - 路由层决定 owner - - 字段层只处理 owner 事件 - - 路由层统一提交/取消/关闭 popup/切换 owner -6. 修复后不需要保留任何针对 `Color` 首击失效的临时补丁。 - -## 14. Recommended Implementation Order - -实际开工时建议严格按下面顺序推进: - -1. 改 `UIEditorPropertyGrid.h` -2. 改 `UIEditorPropertyGridInteraction.h` -3. 在 `UIEditorPropertyGridInteraction.cpp` 建立统一路由入口 -4. 先重构 `Color + Enum + Bool + Asset` -5. 再重构 `Number + Text + Vector` -6. 最后清理旧模板分发和无用状态 - -如果顺序反过来,尤其是先在 `Color` 上局部修补,后面还会再次引入状态污染。 diff --git a/docs/plan/NewEditor_SystemAudit_2026-04-22.md b/docs/plan/NewEditor_SystemAudit_2026-04-22.md deleted file mode 100644 index 50d89e2e..00000000 --- a/docs/plan/NewEditor_SystemAudit_2026-04-22.md +++ /dev/null @@ -1,146 +0,0 @@ -# NewEditor System Audit - -Date: 2026-04-22 - -## 1. Scope - -本次审计目标不是继续“猜一个点然后修一个点”,而是重新检查 `new_editor` -当前还残留哪些: - -1. 旧代码或旧语义残留 -2. 冗余控制路径 -3. 临时性/补丁式结构 -4. 边界没有收口干净的模块职责 - -审计重点覆盖: - -- `app/Platform/Win32` -- `app/Rendering/D3D12` -- `app/Rendering/Viewport` -- `app/Composition` -- `WindowManager` - -## 2. Confirmed Closed Items - -这轮审计先确认了已经真正收掉的历史链路,避免把已经解决的问题重复列为风险: - -1. `new_editor` 中已无活跃的 `D2D / Direct2D / D3D11On12 / NativeRenderer / WindowInterop` - 主窗口链路。 -2. `D3D12UiRenderer` 已不再保留活跃的 `legacy/indexed` 双路径 UI geometry 提交。 -3. 启动截图策略已经不再由旧的渲染层壳子控制,`Rendering/Native` 活 include 路径已清空。 - -这说明上一轮 D3D12 / 截图职责收口本身是有效的。 - -## 3. Primary Findings - -### 3.1 High: frame execution still has multiple live owners - -`RenderFrame()` 现在不是单点驱动,而是同时被多条路径直接调用: - -- [EditorWindow.cpp:612](D:\Xuanchi\Main\XCEngine\new_editor\app\Platform\Win32\EditorWindow.cpp#L612) -- [EditorWindow.cpp:654](D:\Xuanchi\Main\XCEngine\new_editor\app\Platform\Win32\EditorWindow.cpp#L654) -- [EditorWindowChromeController.cpp:431](D:\Xuanchi\Main\XCEngine\new_editor\app\Platform\Win32\EditorWindowChromeController.cpp#L431) -- [EditorWindowChromeController.cpp:912](D:\Xuanchi\Main\XCEngine\new_editor\app\Platform\Win32\EditorWindowChromeController.cpp#L912) -- [EditorWindowHostRuntime.cpp:237](D:\Xuanchi\Main\XCEngine\new_editor\app\Platform\Win32\WindowManager\EditorWindowHostRuntime.cpp#L237) -- [EditorWindowMessageDispatcher.cpp:62](D:\Xuanchi\Main\XCEngine\new_editor\app\Platform\Win32\WindowManager\EditorWindowMessageDispatcher.cpp#L62) -- [EditorWindowMessageDispatcher.cpp:402](D:\Xuanchi\Main\XCEngine\new_editor\app\Platform\Win32\WindowManager\EditorWindowMessageDispatcher.cpp#L402) -- [EditorWindowMessageDispatcher.cpp:411](D:\Xuanchi\Main\XCEngine\new_editor\app\Platform\Win32\WindowManager\EditorWindowMessageDispatcher.cpp#L411) -- [EditorWindowMessageDispatcher.cpp:419](D:\Xuanchi\Main\XCEngine\new_editor\app\Platform\Win32\WindowManager\EditorWindowMessageDispatcher.cpp#L419) -- [EditorWindowMessageDispatcher.cpp:439](D:\Xuanchi\Main\XCEngine\new_editor\app\Platform\Win32\WindowManager\EditorWindowMessageDispatcher.cpp#L439) - -根本问题不是“调用次数多”,而是“帧驱动权不唯一”: - -1. 主循环在主动渲染。 -2. `WM_PAINT` 在同步渲染。 -3. `WM_SIZE / WM_DPICHANGED / WM_EXITSIZEMOVE` 在消息里同步渲染。 -4. `EditorWindowChromeController` 在窗口过渡操作里直接同步渲染。 - -这会导致: - -1. 帧生成/Present 时机不再只有一个真实来源。 -2. `EditorWindowFrameTransferRequests` 的处理顺序在不同入口下不一致。 -3. resize / maximize / drag-restore 时,Chrome 层直接拥有渲染副作用。 -4. 输入、布局、Present、窗口消息之间很难建立稳定时序。 - -这不是局部实现细节,而是当前 `new_editor` 最明显的未收口架构问题。 - -### 3.2 High: window destruction lifecycle still has multiple owners - -窗口销毁收尾现在仍然散落在多处: - -- [EditorWindowHostRuntime.cpp:172](D:\Xuanchi\Main\XCEngine\new_editor\app\Platform\Win32\WindowManager\EditorWindowHostRuntime.cpp#L172) -- [EditorWindowHostRuntime.cpp:194](D:\Xuanchi\Main\XCEngine\new_editor\app\Platform\Win32\WindowManager\EditorWindowHostRuntime.cpp#L194) -- [EditorWindowHostRuntime.cpp:262](D:\Xuanchi\Main\XCEngine\new_editor\app\Platform\Win32\WindowManager\EditorWindowHostRuntime.cpp#L262) -- [EditorWindowMessageDispatcher.cpp:428](D:\Xuanchi\Main\XCEngine\new_editor\app\Platform\Win32\WindowManager\EditorWindowMessageDispatcher.cpp#L428) -- [EditorWindowMessageDispatcher.cpp:452](D:\Xuanchi\Main\XCEngine\new_editor\app\Platform\Win32\WindowManager\EditorWindowMessageDispatcher.cpp#L452) -- [EditorWindowWorkspaceCoordinator.cpp:221](D:\Xuanchi\Main\XCEngine\new_editor\app\Platform\Win32\WindowManager\EditorWindowWorkspaceCoordinator.cpp#L221) -- [EditorWindowWorkspaceCoordinator.cpp:267](D:\Xuanchi\Main\XCEngine\new_editor\app\Platform\Win32\WindowManager\EditorWindowWorkspaceCoordinator.cpp#L267) - -当前重复动作包括: - -1. `window.Shutdown()` -2. `DestroyWindow(hwnd)` -3. `window.MarkDestroyed()` -4. 从 `m_windows` 中 erase - -根本问题是:native window death、runtime shutdown、workspace removal、host container erase -没有一个唯一所有者。 - -这会导致: - -1. 生命周期语义只能靠“现在碰巧没炸”维持,而不是靠明确边界维持。 -2. 工具窗口、弹出窗口、独立面板窗口的关闭行为仍然容易反复回归。 -3. `WorkspaceCoordinator` 直接复制 host runtime 的 destroy 逻辑,说明边界没有收口。 - -这条问题和之前的“关子窗口把主窗口带走”属于同一类根因:生命周期控制权分裂。 - -### 3.3 Medium: EditorShellRuntime is still a monolithic per-frame god-object - -[EditorShellRuntime.cpp:549](D:\Xuanchi\Main\XCEngine\new_editor\app\Composition\EditorShellRuntime.cpp#L549) -开始的 `EditorShellRuntime::Update(...)` 仍然在一个单元里同时承担: - -1. shell definition 构建 -2. workspace/session 同步 -3. dock host interaction 更新 -4. viewport request / frame 应用 -5. hosted panel input 分发 -6. hierarchy / project / color picker / inspector / console 更新 -7. status / command focus / trace 同步 - -同一个 update 内还存在多次会话同步与定义重建: - -- [EditorShellRuntime.cpp:580](D:\Xuanchi\Main\XCEngine\new_editor\app\Composition\EditorShellRuntime.cpp#L580) -- [EditorShellRuntime.cpp:582](D:\Xuanchi\Main\XCEngine\new_editor\app\Composition\EditorShellRuntime.cpp#L582) -- [EditorShellRuntime.cpp:605](D:\Xuanchi\Main\XCEngine\new_editor\app\Composition\EditorShellRuntime.cpp#L605) -- [EditorShellRuntime.cpp:607](D:\Xuanchi\Main\XCEngine\new_editor\app\Composition\EditorShellRuntime.cpp#L607) -- [EditorShellRuntime.cpp:629](D:\Xuanchi\Main\XCEngine\new_editor\app\Composition\EditorShellRuntime.cpp#L629) -- [EditorShellRuntime.cpp:672](D:\Xuanchi\Main\XCEngine\new_editor\app\Composition\EditorShellRuntime.cpp#L672) - -这不是“旧链路残留”,但它是当前最明显的未拆分核心耦合点。它会导致: - -1. 任意一个 panel / dock / viewport 变更都容易影响整帧更新流程。 -2. 输入问题、状态问题、布局问题、面板问题很难局部定位。 -3. 后续继续做性能/交互优化时,改动面会被这个 god-object 放大。 - -## 4. Non-Findings - -以下命中已确认不是这轮要处理的“旧代码/临时方案”问题: - -1. 普通 `fallback` 参数或默认值。 -2. `DirectWrite` 在 `D3D12UiTextSystem` 中的文本栅格化使用。 -3. 视口 pass 中的 `fallbackState`,它表示材质渲染状态默认值,不是旧主窗口链路。 - -## 5. Audit Conclusion - -截至本次审计,`new_editor` 不是“还有一堆 D2D 遗留没删干净”,那条主线已经基本收掉了。 - -当前剩余最关键的两个系统级问题是: - -1. 帧驱动权没有单点收口。 -2. 窗口销毁权没有单点收口。 - -如果后续继续做系统级收口,优先级应该是: - -1. 先把 frame scheduling / present / transfer request 统一到一个唯一驱动源。 -2. 再把 window close / destroy / shutdown / erase 统一到一个唯一生命周期所有者。 -3. 最后再拆 `EditorShellRuntime` 这个 per-frame god-object。 diff --git a/docs/plan/NewEditor_TreeResidualDuplicationClosurePlan_2026-04-22.md b/docs/plan/NewEditor_TreeResidualDuplicationClosurePlan_2026-04-22.md deleted file mode 100644 index 1f73a7f3..00000000 --- a/docs/plan/NewEditor_TreeResidualDuplicationClosurePlan_2026-04-22.md +++ /dev/null @@ -1,133 +0,0 @@ -# NewEditor Tree Residual Duplication Closure Plan - -Date: `2026-04-22` -Status: `In Progress` - -## Goal - -- Close the true remaining duplication between `HierarchyPanel` and the project left tree after the `TreePanelHost` / `UIEditorTreePanelBehavior` cleanup. -- Keep the solution rooted in existing modules instead of creating new `Behavior` / `Host` / `Support` files. -- Reduce code only where the duplication is structurally real, not where product semantics differ. - -## Why A New Plan Is Needed - -- The previous plan completed the removal of the tree glue layer. -- That did not automatically mean the hierarchy tree and project tree were free of all true duplication. -- The remaining work is smaller in scope, but it is real and should be tracked separately instead of being hidden under the completed root plan. - -## Confirmed Residual Duplication - -### 1. Hosted panel command focus claiming is still duplicated - -Current duplicated shape: - -- `HierarchyPanel::ClaimCommandFocus(...)` -- `ProjectPanel::ClaimCommandFocus(...)` -- `InspectorPanel::ClaimCommandFocus(...)` - -This is app-layer hosted panel behavior, not tree-business behavior. - -### 2. Tree rename host skeleton is still partially duplicated - -Current duplicated shape: - -- `ClearRenameState(...)` -- `QueueRenameSession(...)` -- `TryStartQueuedRenameSession(...)` -- `UpdateRenameSession(...)` - -Notes: - -- `HierarchyPanel` has a single tree surface. -- `ProjectPanel` has both tree and grid rename surfaces. -- The commit side-effects must stay local, but the session host skeleton still contains real duplication. - -### 3. Tree append/update skeleton must be re-audited carefully - -- Some call order is shared: tree background, tree foreground, inline rename overlay, drop preview. -- But `ProjectPanel` also owns splitter, breadcrumb, grid, context menu, asset drag/drop, and dual-surface rename. -- This area must not be “abstracted for symmetry” unless the extracted part is truly stable and reduces code clearly. - -## Explicit Non-Goals - -- Do not merge panel refresh loops. -- Do not merge project tree and asset grid logic. -- Do not move project or scene runtime semantics into `XCEditor`. -- Do not create any new `Behavior`, `Host`, `Helper`, `Support`, or similar glue files. -- Do not refactor for visual symmetry when code ownership would become less clear. - -## Execution Plan - -### Phase A. Hosted Panel Command Focus Dedup - -Status: `Completed` - -Target: - -- Consolidate hosted-panel command focus claiming into an existing app-level module. - -Rules: - -- No new file. -- Prefer extending an existing app-level focus module. -- Update `HierarchyPanel`, `ProjectPanel`, and `InspectorPanel` together if they share the same pattern. - -Validation: - -- Build `XCUIEditorApp` -- Smoke test `XCUIEditor.exe` - -Completed result: - -- Consolidated hosted panel command focus claiming into `EditorCommandFocusService.h` -- Removed duplicated `ClaimCommandFocus(...)` implementations from `HierarchyPanel`, `ProjectPanel`, and `InspectorPanel` -- Verified by build and smoke test - -### Phase B. Tree Rename Host Skeleton Dedup - -Status: `Pending` - -Target: - -- Reduce duplicated tree rename session host code while preserving local business commit behavior. - -Rules: - -- No new glue layer. -- Keep tree-generic session mechanics in existing tree/editor modules. -- Keep `ProjectPanel` grid rename semantics local. -- Keep `HierarchyPanel` scene rename commit local. - -Validation: - -- Build `XCUIEditorApp` -- Smoke test `XCUIEditor.exe` -- Confirm hierarchy rename and project tree/grid rename still start and shut down correctly - -### Phase C. Final Re-Audit Of Tree Skeleton Duplication - -Status: `Pending` - -Target: - -- Re-check whether any remaining shared tree call sequence should still be extracted. - -Rules: - -- Only extract if it removes clear structural duplication. -- If the remaining overlap is just normal orchestration around different business surfaces, record it as intentional and stop. - -Validation: - -- `rg` verification of targeted duplicate blocks -- Build `XCUIEditorApp` -- Smoke test `XCUIEditor.exe` - -## Acceptance - -- Previous root plan is archived. -- Remaining true duplication is tracked separately. -- Hosted panel command focus duplicate is removed from the duplicated panels. -- Tree rename host skeleton duplication is reduced without breaking project grid/tree split semantics. -- No new glue-layer file is introduced. -- `XCUIEditorApp` builds and smoke tests pass after each completed phase. diff --git a/docs/plan/NewEditor_Win32WindowArchitectureRefactorPlan_2026-04-23.md b/docs/plan/NewEditor_Win32WindowArchitectureRefactorPlan_2026-04-23.md deleted file mode 100644 index 1bc9a090..00000000 --- a/docs/plan/NewEditor_Win32WindowArchitectureRefactorPlan_2026-04-23.md +++ /dev/null @@ -1,1887 +0,0 @@ -# New Editor Win32 窗口架构重构总计划 - -## 1. 文档目的 - -本计划用于指导 `new_editor/app/Platform/Win32` 相关窗口系统的彻底重构。 - -这不是一次“把大类再拆小一点”的整理,而是一次底层架构重建。核心目标是把当前体系改造成: - -**状态机 + 单向事件流 + 平台投影** - -本计划覆盖: - -- `new_editor/app/Platform/Win32/Windowing` -- `new_editor/app/Platform/Win32/Runtime` -- `new_editor/app/Platform/Win32/Chrome` -- `new_editor/app/Platform/Win32/Content` -- `new_editor/app/Platform/Win32/System` -- `new_editor/app/Composition` 中与窗口、workspace、utility window、全局上下文相关的部分 - ---- - -## 2. 当前系统的根因 - -### 2.1 根因结论 - -当前最根本的问题只有一个: - -**`EditorWindow` 没有形成真正的状态边界,窗口相关状态被多个类共同持有、共同修改、相互穿透。** - -这带来的直接后果是: - -1. 单窗口状态没有唯一所有者。 -2. 跨窗口状态没有唯一所有者。 -3. `WndProc` 路径承担了过多业务编排职责。 -4. 稳态渲染与即时渲染双轨并存,且存在重入。 -5. transfer request、binding 探测、兼容状态机大量出现,本质上都在给“边界失效”打补丁。 - -### 2.2 当前系统的派生问题 - -#### 2.2.1 `EditorWindow` 变成了共享可变状态包 - -它同时挂着: - -- lifecycle -- HWND / title / dpi / size -- input controller -- chrome controller -- runtime controller -- frame orchestrator -- queued transfer requests - -但这些状态不由它自己独占修改,而是被 `MessageDispatcher`、`ChromeController`、`HostRuntime`、`LifecycleCoordinator`、`WorkspaceCoordinator` 等对象直接穿透读写。 - -#### 2.2.2 Win32 消息分发承担了应用层职责 - -当前 `WM_*` 消息路径中,除了输入翻译,还直接做了: - -- 生命周期推进 -- resize / dpi 响应 -- 立即帧渲染 -- global tab drag 编排 -- workspace 事务提交 -- utility window 打开 -- chrome 命令执行 - -这说明当前平台层已经不是适配层,而是业务主链的一部分。 - -#### 2.2.3 渲染链路是双轨的 - -现状存在两条渲染主链: - -- 稳态帧:主循环统一 `RenderAllWindows` -- 即时帧:消息回调里直接 render / present - -这会造成: - -1. 消息回调重入渲染。 -2. transfer request 需要额外缓冲。 -3. resize / dpi / paint 顺序变得脆弱。 - -#### 2.2.4 `WorkspaceCoordinator` 已经偷偷变成状态源 - -当前 `EditorWindowWorkspaceCoordinator` 不只是协调器,而是同时承担: - -- window set 构造 -- workspace 同步 -- 快照回滚 -- 新窗口创建 -- 窗口关闭编排 -- global tab drag 会话 -- 标题刷新 - -但它并没有被正式定义为“权威状态源”,所以职责和边界都不稳定。 - -#### 2.2.5 全局 `EditorContext` 混入了窗口局部能力 - -当前每个窗口 runtime 都会把自己的局部能力挂到全局 context 上,例如: - -- text measurer -- utility window request 消费 -- capture 状态文本依赖 - -这让全局上下文和局部窗口能力耦在了一起。 - -### 2.3 代码层证据 - -下面这些不是推测,而是当前代码直接暴露出来的结构事实: - -1. `new_editor/app/Platform/Win32/Windowing/EditorWindowState.h` - 中所谓 `EditorWindowState` 实际只保存了 `HWND`、`windowId`、`title`、`primary`、`lifecycle`。输入、chrome、resize、dpi、capture、frame、content 相关真实状态都不在这个“状态对象”里,这说明系统从命名层面就在伪装一个并不存在的统一状态边界。 -2. `new_editor/app/Platform/Win32/Windowing/EditorWindow.h` - 通过 `friend class` 把 `EditorWindowChromeController`、`EditorWindowFrameDriver`、`EditorWindowHostRuntime`、`EditorWindowMessageDispatcher`、`EditorWindowLifecycleCoordinator`、`EditorWindowWorkspaceCoordinator` 全部放进 `EditorWindow` 内部。这意味着窗口对象没有封装,只是一个公开状态袋。 -3. `new_editor/app/Platform/Win32/Windowing/EditorWindowMessageDispatcher.cpp` - 在 `WM_DPICHANGED`、`WM_EXITSIZEMOVE`、`WM_SIZE`、`WM_PAINT` 中直接触发 `RenderAndHandleWindowFrame` 或 `OnPaintMessage`。也就是说 Win32 消息回调直接参与渲染、事务完成和跨模块编排。 -4. `new_editor/app/Platform/Win32/Windowing/EditorWindowHostRuntime.cpp` - 中的 `RenderAllWindows` 不只是“遍历窗口”,它同时承担帧驱动、presentation 刷新、transfer request 聚合和后续分发。平台宿主已经兼任应用层调度器。 -5. `new_editor/app/Platform/Win32/Windowing/EditorWindowWorkspaceCoordinator.cpp` - 通过 `BuildLiveWindowWorkspaceSet(...)` 从 live windows 反推当前窗口集合,再基于这个集合做同步、提交和回滚。这说明跨窗口 topology 并没有显式权威状态,只能从现场对象拼出来。 -6. `new_editor/app/Platform/Win32/Content/EditorWindowContentController.h` - 暴露 `TryGetWorkspaceBinding`、`TryGetDockHostBinding`、`TryGetInputFeedbackBinding`、`TryGetTitleBarBinding`。应用层必须去“探测内容层支持哪些能力”,再临时决定走哪条逻辑分支,本质上是拿 capability probing 代替正式协议。 -7. `new_editor/app/Platform/Win32/Runtime/EditorWindowRuntimeController.h` - 同时持有 D3D12 renderer、texture host、text system、window render loop、screenshot controller、content controller、frame timing、dpi scale。render host、content host、diagnostic host 被揉成了一个类。 -8. `new_editor/app/Platform/Win32/Content/EditorWorkspaceWindowContentController.cpp` - 里 workspace 内容控制器一边对外暴露 binding,一边直接驱动 shell runtime 和 frame orchestrator。说明“内容输出”不是一个结构化结果,而是一堆散落接口和副作用。 - -因此,当前问题不是“某几个类太大”,而是根本没有形成: - -- 单窗口状态所有权 -- 跨窗口拓扑状态所有权 -- 事件到 effect 的正式管线 -- 平台层与应用层的稳定边界 - ---- - -## 3. 重构目标 - -### 3.1 总目标 - -建立以下新的权责模型: - -1. **单窗口状态** - 只能由 `WindowSession` 持有。 -2. **跨窗口拓扑状态** - 只能由 `WindowTopologyService` 持有。 -3. **平台对象** - 由 Win32 / D3D12 backend 持有,但只作为投影结果,不作为业务状态源。 -4. **内容层** - 不再通过 binding 探测链暴露能力,而是统一返回 `ContentOutput`。 -5. **帧调度** - 统一由 `FrameScheduler` 管理,消息回调只登记帧请求,不直接渲染。 - -### 3.2 完成后必须满足的硬约束 - -1. 新窗口架构中禁止 `friend` 穿透状态。 -2. domain / application 头文件中禁止出现 `HWND`、`WPARAM`、`LPARAM`、`RECT`、`POINT`。 -3. `WndProc` 路径中禁止直接 render / present。 -4. `EditorWindowFrameTransferRequests` 必须删除。 -5. `TryGetWorkspaceBinding` / `TryGetDockHostBinding` / `TryGetInputFeedbackBinding` / `TryGetTitleBarBinding` 必须退出主干架构。 -6. 跨窗口状态必须显式保存在 `WindowTopologyState` 中,禁止从 live windows 反推。 -7. `EditorContext` 中禁止挂载窗口私有能力对象。 - ---- - -## 4. 目标架构 - -## 4.1 分层模型 - -```text -Domain - 状态、事件、命令、意图、effect 定义 - -Application - WindowSession / WindowTopologyService / FrameScheduler / Projector - -Content - WorkspaceContentHost / UtilityContentHost - -Platform - Win32 事件适配、native window backend、D3D12 render host -``` - -## 4.2 状态所有权 - -### 单窗口状态:`WindowState` - -由 `WindowSession` 独占持有,建议至少包含: - -- identity - - `windowId` - - `windowKind` -- lifecycle - - `PendingCreate` - - `NativeAttached` - - `Initializing` - - `Running` - - `Closing` - - `Destroyed` -- presentation - - `title` - - `dpi` - - `dpiScale` - - `focused` - - `visible` - - `minimized` -- size - - `clientPixelWidth` - - `clientPixelHeight` - - `predictedClientPixelWidth` - - `predictedClientPixelHeight` -- input - - `pointerCaptureOwner` - - `trackingMouseLeave` - - `modifierSnapshot` - - `pendingEvents` - - `pendingDoubleClickMask` -- chrome - - `hoveredChromeTarget` - - `pressedChromeTarget` - - `hoveredResizeEdge` - - `activeResizeEdge` - - `maximized` - - `restoreRect` - - `dragRestoreState` -- frame - - `needsFrame` - - `framePriority` - - `frameSerial` - - `resizeEpoch` -- content - - `cursorKind` - - `captureDemand` - - `titleBarMode` - - `contentStatus` - -### 跨窗口状态:`WindowTopologyState` - -由 `WindowTopologyService` 独占持有,至少包含: - -- `primaryWindowId` -- `activeWindowId` -- `orderedWindowIds` -- `workspaceWindowIds` -- `utilityWindowIds` -- `windowProjections` -- `workspaceProjectionSnapshots` -- `workspaceSessionSnapshots` -- `globalTabDragState` -- `pendingUtilityRequests` -- `pendingDetachTransactions` -- `pendingDropTransactions` - -说明: - -- `WindowTopologyState` 不要求吞掉整个 workspace runtime 对象,但必须显式持有“窗口到 workspace 的权威投影真相”。 -- 至少要能按 `windowId` 解释:window kind / role、workspace projection snapshot、workspace session snapshot、utility window kind、primary / active 标记、正在进行的 detach / drop / reuse 事务。 -- 禁止再通过 live window、content controller、workspace controller 临时拼装当前业务 window set。 - -### 平台状态 - -平台状态由 platform backend 持有,但不作为业务真状态: - -- `HWND` -- Win32 class registration -- OS capture / cursor -- D3D12 device / swapchain / renderer - ---- - -## 5. 核心抽象 - -## 5.1 Domain 抽象 - -建议新增目录: - -```text -new_editor/app/Windowing/Domain/ -``` - -建议新增类型: - -- `WindowId.h` -- `WindowState.h` -- `WindowTopologyState.h` -- `WindowEvent.h` -- `WindowIntent.h` -- `WindowCommand.h` -- `WindowEffect.h` -- `WindowCursorKind.h` -- `WindowCaptureOwner.h` -- `FramePriority.h` - -### `WindowEvent` - -平台层只负责把 `WM_*` 转成这些事件: - -- 生命周期 - - `NativeAttached` - - `NativeDestroyed` - - `CloseRequested` -- 输入 - - `PointerMoved` - - `PointerLeft` - - `PointerButtonDown` - - `PointerButtonUp` - - `PointerWheel` - - `KeyDown` - - `KeyUp` - - `Character` - - `FocusGained` - - `FocusLost` - - `CaptureChanged` -- 尺寸 - - `ClientResized` - - `InteractiveResizeStarted` - - `InteractiveResizeEnded` - - `DpiChanged` -- 帧 - - `FrameTick` - - `PaintRequested` - - `FramePresented` -- 内容 - - `ContentOutputReady` - -### `WindowIntent` - -内容层不再返回 transfer request,而是返回正式意图: - -- `StartGlobalTabDrag` -- `DetachPanel` -- `OpenUtilityWindow` -- `RequestCursor` -- `RequestCapture` -- `ReleaseCapture` -- `RequestImmediateFrame` -- `RequestInvalidate` -- `ProposeWorkspaceMutation` - -### `WindowEffect` - -应用层最终向平台层发 effect: - -- `CreateNativeWindow` -- `DestroyNativeWindow` -- `ShowWindow` -- `FocusWindow` -- `MoveWindow` -- `ResizeWindow` -- `SetWindowTitle` -- `SetCursor` -- `AcquireCapture` -- `ReleaseCapture` -- `TrackMouseLeave` -- `ScheduleFrame` -- `ApplyRenderResize` - ---- - -## 6. 目标模块设计 - -## 6.1 `WindowSession` - -职责: - -- 持有 `WindowState` -- 接收 `WindowEvent` -- 演进单窗口状态 -- 触发内容更新 -- 生成窗口级命令和 effect - -禁止: - -- 直接调用 Win32 API -- 直接调用 D3D12 present -- 直接修改其它窗口状态 -- 直接创建 utility window - -## 6.2 `WindowTopologyService` - -职责: - -- 持有 `WindowTopologyState` -- 处理跨窗口事务 -- 统一管理: - - primary / active window - - panel detach - - cross-window drop - - global tab drag - - utility window 去重与重用 - -它必须替代: - -- `EditorWindowWorkspaceCoordinator` -- `EditorUtilityWindowCoordinator` - -## 6.3 `FrameScheduler` - -职责: - -- 统一登记所有窗口帧请求 -- 合并稳态帧与优先帧 -- 对 resize / dpi / paint 恢复设定显式优先级 - -关键规则: - -1. 消息回调只登记帧请求。 -2. 所有 render / present 集中在调度点执行。 -3. 不允许出现新的“隐式即时帧”路径。 - -## 6.4 `IWindowContentHost` - -内容层改成统一宿主接口,而不是多 binding 探测。 - -输入:`WindowContentInput` - -- workspace bounds -- input events -- frame context -- capture status text -- topology snapshot -- window role - -输出:`WindowContentOutput` - -- draw data -- cursor kind -- capture demand -- title bar mode -- workspace mutation intent -- utility window intent -- focus hints -- status text - -## 6.5 `WorkspaceContentHost` - -替代: - -- `EditorWorkspaceWindowContentController` - -职责: - -- 持有 `EditorShellRuntime` -- 更新 shell -- 输出统一 `WindowContentOutput` - -## 6.6 `UtilityContentHost` - -替代: - -- `EditorUtilityWindowContentController` - -职责: - -- 承载 utility panel -- 输出聚焦态、绘制结果、最小尺寸和内容状态 - -## 6.7 `Win32WindowProcAdapter` - -替代: - -- `EditorWindowMessageDispatcher` - -职责: - -- `WM_* -> WindowEvent` -- 不做业务决策 -- 不持有业务状态 -- 不 render / present - -## 6.8 `Win32WindowBackend` - -职责: - -- `CreateWindowExW` -- `DestroyWindow` -- `SetWindowPos` -- `SetWindowTextW` -- `ShowWindow` -- `SetForegroundWindow` -- `TrackMouseEvent` -- `SetCapture / ReleaseCapture` - -原则: - -- 只执行 effect -- 不知道 workspace -- 不知道 topology -- 不知道 content host - -## 6.9 `D3D12WindowRenderHost` - -替代旧 `EditorWindowRuntimeController` 中的 D3D12 部分。 - -职责: - -- 初始化窗口 renderer -- resize -- begin frame -- present -- viewport surface presentation capability - -它是渲染宿主,不是业务控制器。 - -## 6.10 Chrome 子层 - -当前 stateful `EditorWindowChromeController` 需要拆为: - -- `ChromeGeometry` - - 纯几何、纯 hit test -- `ChromeReducer` - - hover / press / resize / drag restore 状态机 -- `ChromeEffectsBuilder` - - 根据状态变化生成 effect - -纯 helper 如 `BorderlessWindowChrome.*`、`BorderlessWindowFrame.*` 可以保留或收缩。 - -## 6.11 关键接口草案 - -下面给的是目标结构草案,不要求首阶段一次到位,但最终主干必须收敛到类似形态。 - -### `WindowSession` - -```cpp -class WindowSession final { -public: - explicit WindowSession(WindowId id, WindowKind kind); - - const WindowState& Snapshot() const; - - WindowSessionResult ApplyEvent(const WindowEvent& event); - WindowSessionResult ApplyContentOutput(const WindowContentOutput& output); - WindowSessionResult ApplyTopologyFeedback(const WindowTopologyFeedback& feedback); - -private: - WindowState m_state = {}; -}; -``` - -约束: - -- `WindowSession` 是单窗口真状态唯一可写者。 -- 外部只能拿 snapshot,不能直接改字段。 -- chrome、input、lifecycle、frame 都通过 reducer 更新,不允许 controller 私改。 - -### `WindowTopologyService` - -```cpp -class WindowTopologyService final { -public: - const WindowTopologyState& Snapshot() const; - - TopologyResult ApplyIntent( - WindowId sourceWindowId, - const WindowIntent& intent, - const WindowTopologySnapshot& snapshot); - - void OnWindowClosed(WindowId windowId); - void OnWindowCreated(WindowId windowId, WindowRole role); - -private: - WindowTopologyState m_state = {}; -}; -``` - -约束: - -- 所有跨窗口事务统一进这里。 -- 不允许在别处直接增删 `workspaceWindowIds` / `utilityWindowIds`。 -- global tab drag 只能由它维护完整会话状态。 - -### `FrameScheduler` - -```cpp -class FrameScheduler final { -public: - void RequestFrame(WindowId windowId, FramePriority priority, FrameReason reason); - bool HasPendingFrame() const; - FrameBatch DequeueReadyBatch(const WindowSessionStore& sessions); - void NotifyFramePresented(WindowId windowId, std::uint64_t frameSerial); -}; -``` - -约束: - -- 只有 `FrameScheduler` 可以决定某一帧何时真正执行。 -- `WM_PAINT`、`WM_SIZE`、`WM_DPICHANGED` 只产生请求,不直接 render。 -- steady frame / immediate frame 不再是两条链,而是一个调度器内的不同优先级。 - -### `FrameScheduler` 帧事务模型 - -单帧事务顺序必须固定,不允许实现时自由发挥。标准顺序定义为: - -1. drain window events -2. merge frame requests -3. build `WindowContentInput` -4. update content hosts -5. submit topology intents / effect intents -6. finalize render plan -7. execute render subpasses / viewport work -8. present -9. publish `FramePresented` / frame feedback -10. validate paint / clear frame debt / decide requeue - -必须同时定义以下语义: - -- `WM_PAINT` 只产生 `FrameReason::PaintValidation` -- `WM_SIZE` 只产生 `FrameReason::Resize` -- `WM_DPICHANGED` 只产生 `FrameReason::DpiChange` -- steady tick 只产生 `FrameReason::SteadyTick` -- global tab drag / capture 恢复只产生显式 interaction continuation reason - -优先级规则必须显式写死: - -- `DpiChange` = `Critical` -- `Resize` = `High` -- `PaintValidation` = `High` -- `InteractionContinuation` = `High` -- `SteadyTick` = `Normal` - -旧语义替代关系必须明确: - -- `DriveFrame` -> steady tick frame request -- `DriveImmediateFrame` -> high/critical priority frame request -- `RequestSkipNextSteadyStateFrame` -> scheduler suppression rule -- queued completed immediate frame -> scheduler-owned post-present topology transaction queue - -### `IWindowContentHost` - -```cpp -class IWindowContentHost { -public: - virtual ~IWindowContentHost() = default; - virtual void Initialize(const WindowContentHostInitContext&) = 0; - virtual WindowContentOutput Update(const WindowContentInput&) = 0; - virtual void RenderSubpasses(const WindowRenderSubpassContext&) = 0; - virtual void Shutdown() = 0; -}; -``` - -约束: - -- 内容层统一返回 `WindowContentOutput`。 -- 应用层不再 `TryGet*Binding`。 -- 内容层如果需要表达 cursor、capture、detach、utility window、title bar mode,都在 output 中显式返回。 - -### `Win32WindowProcAdapter` - -```cpp -class Win32WindowProcAdapter final { -public: - std::optional Translate( - HWND hwnd, - UINT message, - WPARAM wParam, - LPARAM lParam) const; -}; -``` - -约束: - -- 只做消息翻译,不做业务分支。 -- 不调用 workspace coordinator。 -- 不调用 utility coordinator。 -- 不调用 render / present。 - ---- - -## 7. 旧模块到新模块的映射 - -| 现有模块 | 目标归宿 | 最终命运 | -| --- | --- | --- | -| `EditorWindow` | `WindowSession` + 轻量 native facade | 大幅收缩或删除 | -| `EditorWindowManager` | `WindowSessionStore` + `WindowTopologyService` + `FrameScheduler` | 删除 | -| `EditorWindowHostRuntime` | `WindowSessionStore` + `Win32WindowBackend` | 删除 | -| `EditorWindowMessageDispatcher` | `Win32WindowProcAdapter` | 删除 | -| `EditorWindowLifecycleCoordinator` | `WindowSession` + topology service | 删除 | -| `EditorWindowWorkspaceCoordinator` | `WindowTopologyService` | 删除 | -| `EditorUtilityWindowCoordinator` | `WindowTopologyService` | 删除 | -| `EditorWindowFrameDriver` | `FrameScheduler` | 删除 | -| `EditorWindowRuntimeController` | `D3D12WindowRenderHost` + content host bridge | 拆分后删除旧形态 | -| `EditorWorkspaceWindowContentController` | `WorkspaceContentHost` | 替换 | -| `EditorUtilityWindowContentController` | `UtilityContentHost` | 替换 | -| `EditorWindowTransferRequests` | `WindowIntent / WindowCommand / WindowEffect` | 删除 | -| `TryGet*Binding` 链 | `WindowContentOutput` | 删除 | - -## 7.1 最终顶层编排者 - -最终必须引入新的顶层运行时,建议命名: - -- `WindowApplicationRuntime` - -它的职责只能是: - -- 持有 `WindowSessionStore` -- 持有 `WindowTopologyService` -- 持有 `FrameScheduler` -- 持有 content host factory -- 持有 `Win32WindowBackend` -- 持有 `Win32WindowProjector` -- 持有 `D3D12WindowRenderHost` registry -- 驱动主循环调度 - -它明确禁止做的事: - -- 不新增业务可写状态真相 -- 不直接执行 Win32 API -- 不直接做 workspace 事务 -- 不把 topology / session / frame 规则塞回 `Application` - -`Application` 最终只保留: - -- 进程级初始化 -- crash / log / DPI bootstrap -- 创建 `WindowApplicationRuntime` -- 调用 runtime run / shutdown - ---- - -## 8. 目录重组建议 - -建议新增目录: - -```text -new_editor/app/Windowing/Domain/ -new_editor/app/Windowing/Application/ -new_editor/app/Windowing/Content/ -new_editor/app/Platform/Win32/Adapter/ -new_editor/app/Platform/Win32/Backend/ -new_editor/app/Platform/Win32/Projection/ -new_editor/app/Platform/Win32/Rendering/ -``` - -旧目录的处理策略: - -- `Platform/Win32/Windowing` - 先收缩为迁移桥,最终清空或仅保留少量 native glue -- `Platform/Win32/Runtime` - D3D12 相关迁出后删除旧 driver / controller -- `Platform/Win32/Content` - 迁入 `Windowing/Content` -- `Platform/Win32/Chrome` - 仅保留纯 helper,删除 stateful controller - -## 8.1 依赖与边界规则 - -重构过程中必须同时执行“依赖收口”,否则类名换了,旧问题会原样复活。 - -### 允许的依赖方向 - -```text -Domain <- Application <- Content - <- Platform Projection -Platform Backend 只被 Application/Projection 调用 -Rendering Host 只被 FrameScheduler/Render Pipeline 调用 -``` - -### 明确禁止的依赖 - -1. `Windowing/Domain` 禁止包含 `windows.h`、D3D12、swapchain、renderer 头文件。 -2. `Windowing/Application` 禁止直接调用 `CreateWindowExW`、`SetWindowPos`、`SetCapture`、`Present`。 -3. `Windowing/Content` 禁止感知 `HWND`、`RECT`、`POINT`、`WPARAM`、`LPARAM`。 -4. `Platform/Win32/Adapter` 禁止包含 workspace 事务逻辑。 -5. `Platform/Win32/Backend` 禁止读取 workspace/session/content 模型。 -6. `WindowTopologyService` 之外禁止直接维护 global tab drag 会话。 -7. `FrameScheduler` 之外禁止新增任何“临时立即帧”入口。 -8. `EditorContext` 之外的全局单例禁止继续扩散;新增全局能力必须先证明不是 per-window。 - -### include 审核规则 - -审核员在代码审查时必须额外检查: - -- 新建 domain 头文件是否仍然引用 Win32 类型。 -- 新建 content host 是否仍然暴露 binding 探测接口。 -- 新建 platform 代码是否偷偷包含 `UIEditorWorkspaceController`。 -- 任何新类是否再次通过 `friend` 穿透 `WindowSession`。 - -## 8.2 迁移期间的反腐层规则 - -迁移不是一口气删光旧代码,而是分期替换。但桥接层必须受限,否则过渡层会永久化。 - -### 允许存在的临时桥 - -1. `EditorWindowManager` - 只允许暂时作为启动入口和 legacy facade。 -2. `EditorWindow` - 过渡期允许保留,但只能逐步退化为: - - `windowId` - - native handle facade - - 指向 `WindowSession` / backend 的轻量桥 -3. `EditorWindowRuntimeController` - 只允许短期作为 `content host + render host` 的组合桥,后续必须拆解。 -4. `EditorWindowMessageDispatcher` - 只允许短期 forward 到 `Win32WindowProcAdapter`,不能继续长逻辑。 - -所有临时桥都必须同时满足以下硬约束: - -1. 只允许做机械转发或数据形状适配。 -2. 不允许持有业务可写状态。 -3. 不允许做条件分支决策。 -4. 不允许生成新的业务语义。 -5. 不允许吞掉 effect、intent、错误或反馈。 -6. 必须在计划中绑定删除阶段和删除条件。 -7. 做不到以上约束的桥,不允许“先留着”,必须在当前阶段直接拆掉。 - -### 迁移期明确禁止 - -1. 禁止新增任何新的 `friend class`。 -2. 禁止给 `EditorWindowFrameTransferRequests` 再加字段。 -3. 禁止新增新的 `TryGet*Binding`。 -4. 禁止在 `WM_*` 路径里新增 render / present / workspace 事务代码。 -5. 禁止在旧 coordinator 中继续沉积新业务。 -6. 禁止从 live windows 再推导新的“临时全局状态”。 - -### 判断一个桥是否可以删除的标准 - -同时满足以下条件时,桥必须进入删除阶段: - -1. 新结构已经有等价状态对象。 -2. 新结构已经有等价命令或 effect。 -3. 旧桥只剩转发,不再做决策。 -4. 已有回归覆盖该路径。 - -## 9.0 所有阶段通用门禁 - -下面这些门禁不是某一阶段独有,而是从 Phase 1 开始到 Phase 8 都必须成立: - -1. 本阶段新增桥接层只能是机械转发 adapter,不能业务化。 -2. 任何单窗口状态在同一时刻只能有一个可写源。 -3. 任何跨窗口状态在同一时刻只能有一个可写源。 -4. 新增类型不得把 Win32 类型重新包装后带回 domain / application。 -5. 没有对应测试门禁和回归命令的结构,不允许宣称阶段完成。 - ---- - -## 9. 分阶段执行方案 - -## Phase 0:基线冻结与护栏建立 - -### 目标 - -在动架构前冻结当前行为基线。 - -### 工作项 - -1. 记录旧系统关键链路: - - 建窗 - - 关闭 - - steady frame - - immediate frame - - resize - - dpi change - - utility window open / reuse - - panel detach - - global tab drag -2. 补最小回归清单。 -3. 建立 trace / log 基线。 -4. 标记本计划的阶段门禁。 - -### 退出条件 - -- 关键行为已文档化。 -- 有最小回归检查项。 -- 已定义测试 target 规划、fake 对象职责和阶段门禁命令。 -- 评论区已登记 Phase 0 启动记录。 - -## Phase 1:新协议落地 - -### 目标 - -先引入新的 domain 抽象,不替换旧主链。 - -### 工作项 - -1. 新增 `WindowState`、`WindowTopologyState`、`WindowEvent`、`WindowIntent`、`WindowEffect`。 -2. 新增 `WindowContentInput`、`WindowContentOutput`。 -3. 新增纯 reducer 雏形: - - lifecycle - - input - - chrome -4. 保持旧主链不动。 - -### 退出条件 - -- 新类型不依赖 Win32 类型。 -- 新类型足以表达旧主链中的关键意图。 -- 已把 `window_session_tests`、`window_topology_tests`、`frame_scheduler_tests`、`win32_message_translator_tests` - 纳入 CMake 目标规划与目录规划。 - -## Phase 2:单窗口状态迁入 `WindowSession` - -### 目标 - -解决最底层的状态 ownership 问题。 - -### 工作项 - -1. 创建 `WindowSession`。 -2. 把单窗口状态迁入 `WindowSession`。 -3. `EditorWindow` 退化为兼容外壳。 -4. 开始消灭外部对 `EditorWindow` 内部状态的直接访问。 - -### 退出条件 - -- 单窗口真状态只在 `WindowSession` 或其 reducer 中可写。 -- 旧 `EditorWindow` / `EditorWindowInputController` / `EditorWindowChromeController` - 不能再持有独立业务状态真相。 -- 不允许存在新旧双写路径。 -- `EditorWindow` 不再是共享可变状态包。 - -## Phase 3:消息路径改造 - -### 目标 - -把 `WndProc` 路径变成纯事件适配。 - -### 工作项 - -1. 新建 `Win32WindowProcAdapter`。 -2. 所有 `WM_*` 转成 `WindowEvent`。 -3. 移出消息路径中的: - - render / present - - workspace 事务 - - utility window 打开 - - chrome 直接 effect - -### 退出条件 - -- `WndProc` 中不再直接调用渲染入口。 - -## Phase 4:统一帧调度 - -### 目标 - -消灭稳态帧与即时帧的双轨结构。 - -### 工作项 - -1. 建立 `FrameScheduler`。 -2. 消息路径只登记帧请求。 -3. 稳态帧和优先帧统一调度。 -4. 定义完整 frame lifecycle、`FrameReason` 和 `FramePriority`。 -5. 删除 `DriveImmediateFrame` 思维模式。 - -### 退出条件 - -- 没有消息路径直接 render / present。 -- `FrameScheduler` 生命周期顺序、reason 映射、priority 映射、失败/跳帧规则已落成文档并对应旧语义。 -- 不再需要 queued completed immediate frame 补偿逻辑。 - -## Phase 5:内容层协议替换 - -### 目标 - -删除 binding 探测链。 - -### 工作项 - -1. 建立 `IWindowContentHost`。 -2. 实现 `WorkspaceContentHost`。 -3. 实现 `UtilityContentHost`。 -4. 让内容层统一返回 `WindowContentOutput`。 -5. 删除 `TryGet*Binding` 链。 - -### 退出条件 - -- 应用层不再感知内容层内部的 binding 形状。 - -## Phase 6:拓扑服务落地 - -### 目标 - -建立显式 `WindowTopologyState`。 - -### 工作项 - -1. 建立 `WindowTopologyService`。 -2. 迁移: - - primary / active window - - panel detach - - cross-window drop - - global tab drag - - utility window 管理 -3. 停止从 live windows 反推业务 window set。 - -### 退出条件 - -- 系统里只有一个跨窗口拓扑状态源。 -- `WindowTopologyState` 已显式保存 window projection / workspace projection / session snapshot / 事务快照。 -- `BuildLiveWindowWorkspaceSet(...)` 不再承担业务真相恢复职责。 - -## Phase 7:平台投影与后端收口 - -### 目标 - -把 Win32 / D3D12 执行逻辑从业务决策中彻底剥离。 - -### 工作项 - -1. 新建 `WindowApplicationRuntime`。 -1. 新建 `Win32WindowBackend`。 -2. 新建 `Win32WindowProjector`。 -3. 新建 `D3D12WindowRenderHost`。 -4. 把 `Application` 收缩为 bootstrap,主循环调度迁给 `WindowApplicationRuntime`。 -5. 所有 Win32 effect 都通过 backend 执行。 - -### 退出条件 - -- `EditorWindowManager` 删除后的顶层调用链已经明确落到 `WindowApplicationRuntime`。 -- 应用层不再直接调用 Win32 API。 -- 会话层不再直接持有 D3D12 业务控制职责。 - -## Phase 8:删除旧结构与最终清理 - -### 目标 - -删掉所有旧桥、旧接口、旧控制器。 - -### 工作项 - -1. 删除旧 coordinator / driver / dispatcher / transfer request。 -2. 删除旧 binding 探测链。 -3. 收缩 `EditorContext`。 -4. 清理 include 方向和目录结构。 - -### 退出条件 - -- 本计划第 3 节的硬约束全部满足。 - -## 9.1 文件级实施矩阵 - -这一节是执行时的真正落地清单。阶段说明解决“做什么”,这一节解决“先动哪些文件、怎么桥接、删哪些旧件”。 - -### Phase 0 文件级动作 - -目标:冻结现状,不改行为。 - -重点读取与建档文件: - -- `new_editor/app/Platform/Win32/Windowing/EditorWindow.h/.cpp` -- `new_editor/app/Platform/Win32/Windowing/EditorWindowMessageDispatcher.cpp` -- `new_editor/app/Platform/Win32/Windowing/EditorWindowHostRuntime.cpp` -- `new_editor/app/Platform/Win32/Windowing/EditorWindowWorkspaceCoordinator.cpp` -- `new_editor/app/Platform/Win32/Windowing/EditorUtilityWindowCoordinator.cpp` -- `new_editor/app/Platform/Win32/Runtime/EditorWindowFrameDriver.cpp` -- `new_editor/app/Platform/Win32/Runtime/EditorWindowRuntimeController.h/.cpp` -- `new_editor/app/Platform/Win32/Chrome/EditorWindowChromeController.h/.cpp` - -必须沉淀的基线: - -- 消息到渲染路径时序图 -- steady frame 时序图 -- immediate frame 时序图 -- global tab drag 时序图 -- detach panel -> 新窗口创建时序图 -- utility window open / reuse 时序图 - -审核检查点: - -- 文档是否覆盖所有 `WM_*` 关键路径 -- 是否识别了所有 direct Win32 API 调用点 -- 是否列出当前 transfer request 生产者和消费者 - -### Phase 1 文件级动作 - -目标:先落地新协议,不改主行为。 - -新增文件建议: - -- `new_editor/app/Windowing/Domain/WindowId.h` -- `new_editor/app/Windowing/Domain/WindowKind.h` -- `new_editor/app/Windowing/Domain/WindowState.h` -- `new_editor/app/Windowing/Domain/WindowTopologyState.h` -- `new_editor/app/Windowing/Domain/WindowEvent.h` -- `new_editor/app/Windowing/Domain/WindowIntent.h` -- `new_editor/app/Windowing/Domain/WindowCommand.h` -- `new_editor/app/Windowing/Domain/WindowEffect.h` -- `new_editor/app/Windowing/Domain/WindowContentInput.h` -- `new_editor/app/Windowing/Domain/WindowContentOutput.h` -- `new_editor/app/Windowing/Domain/FramePriority.h` - -新增 reducer 建议: - -- `new_editor/app/Windowing/Application/Reducers/WindowLifecycleReducer.*` -- `new_editor/app/Windowing/Application/Reducers/WindowInputReducer.*` -- `new_editor/app/Windowing/Application/Reducers/WindowChromeReducer.*` - -桥接策略: - -- 旧 `EditorWindow` 继续跑,但开始把内部可见状态映射到新的 `WindowState` 草案。 -- 新 domain 类型只作为纯类型定义,不接管现网行为。 - -本阶段不能做的事: - -- 不能让新 domain 类型依赖 `windows.h` -- 不能在新协议里出现 `HWND` -- 不能为了兼容旧代码把 Win32 类型偷偷包一层再塞回 domain - -### Phase 2 文件级动作 - -目标:把单窗口真状态收回到 `WindowSession`。 - -新增文件建议: - -- `new_editor/app/Windowing/Application/WindowSession.h` -- `new_editor/app/Windowing/Application/WindowSession.cpp` -- `new_editor/app/Windowing/Application/WindowSessionStore.h` -- `new_editor/app/Windowing/Application/WindowSessionStore.cpp` -- `new_editor/app/Windowing/Application/WindowSessionResult.h` - -优先迁移状态来源: - -1. `EditorWindowState.h` -2. `EditorWindowInputController` -3. `EditorWindowChromeController` -4. `EditorWindow` 中与 resize / dpi / queued immediate frame 相关的状态 - -具体做法: - -- 先把当前 scattered state 建成 `WindowState` 快照字段,不立即删旧控制器。 -- 让 `EditorWindow` 只通过 `WindowSession` 读写窗口状态。 -- 外部模块改成读取 `WindowSession::Snapshot()`,不再读 `EditorWindow` 私有成员。 -- 旧 controller 如果暂时还存在,只允许退化为无状态 helper,不允许继续做独立状态持有者。 - -必须优先清掉的穿透点: - -- `EditorWindow.h` 中的 `friend class` 链 -- `EditorWindowMessageDispatcher` 对 `m_inputController` / `m_chromeController` / `m_runtime` 的直接访问 -- `EditorWindowFrameDriver` 对 `window.m_chromeController` 的直接访问 - -阶段验收: - -- 所有单窗口状态写入都只经由 `WindowSession` 或 reducer -- `EditorWindow` 不再是唯一可写状态袋 -- 不存在旧 controller 与 `WindowSession` 双写 -- `friend class` 不再承担状态穿透写入入口 - -### Phase 3 文件级动作 - -目标:把消息路径收口成纯适配层。 - -新增文件建议: - -- `new_editor/app/Platform/Win32/Adapter/Win32WindowProcAdapter.h` -- `new_editor/app/Platform/Win32/Adapter/Win32WindowProcAdapter.cpp` -- `new_editor/app/Platform/Win32/Adapter/Win32MessageTranslator.*` - -旧文件处理: - -- `EditorWindowMessageDispatcher.cpp` 第一阶段变薄,只保留: - - `HWND -> WindowId` 查找 - - 调用 `Win32WindowProcAdapter` - - 把事件送给 `WindowSession` -- 后续删除 `EditorWindowMessageDispatcher.*` - -需要迁出的逻辑: - -- `WM_SIZE` 内直接 immediate frame -- `WM_DPICHANGED` 内直接 render -- `WM_PAINT` 内直接 `OnPaintMessage` -- `WM_CLOSE` 中对生命周期的复杂编排 -- `WM_MOUSE*` 中直接 global tab drag / chrome action 决策 - -阶段验收: - -- `WndProc` 中只剩翻译和转发 -- 业务层收到的是 `WindowEvent`,不是 `WM_*` - -### Phase 4 文件级动作 - -目标:建立统一帧调度器。 - -新增文件建议: - -- `new_editor/app/Windowing/Application/FrameScheduler.h` -- `new_editor/app/Windowing/Application/FrameScheduler.cpp` -- `new_editor/app/Windowing/Application/FrameRequestQueue.h` -- `new_editor/app/Windowing/Application/FrameRequestQueue.cpp` - -旧文件收缩: - -- `Runtime/EditorWindowFrameDriver.*` 先转成 scheduler 内部 helper,再删除 -- `EditorWindowHostRuntime::RenderAllWindows(...)` 改为委托 `FrameScheduler` - -关键迁移动作: - -- 把 `DriveFrame` / `DriveImmediateFrame` 收束成统一请求模型 -- 把 `RequestSkipNextSteadyStateFrame` 这种补丁语义转为显式调度规则 -- 把 `HasQueuedCompletedImmediateFrame` / `ConsumeQueuedCompletedImmediateFrameTransferRequests` - 转换为 scheduler 完成回调或拓扑事务队列 -- 把 frame transaction 顺序固定到: - event drain -> request merge -> content update -> topology submit -> render -> present -> feedback -> paint validation -- 为 `WM_PAINT` / `WM_SIZE` / `WM_DPICHANGED` 建立明确的 `FrameReason` / `FramePriority` 映射 - -阶段验收: - -- steady frame / immediate frame 共享同一套调度实体 -- 新代码里不存在“为了某个消息直接 present 一帧”的入口 - -### Phase 5 文件级动作 - -目标:把内容层从 binding 探测链改成正式输出协议。 - -新增文件建议: - -- `new_editor/app/Windowing/Content/IWindowContentHost.h` -- `new_editor/app/Windowing/Content/WorkspaceContentHost.h` -- `new_editor/app/Windowing/Content/WorkspaceContentHost.cpp` -- `new_editor/app/Windowing/Content/UtilityContentHost.h` -- `new_editor/app/Windowing/Content/UtilityContentHost.cpp` - -重点改造旧文件: - -- `Platform/Win32/Content/EditorWindowContentController.h` -- `Platform/Win32/Content/EditorWorkspaceWindowContentController.*` -- `Platform/Win32/Content/EditorUtilityWindowContentController.*` - -替换策略: - -1. 先让旧 content controller 内部构造 `WindowContentOutput` -2. 再让应用层只消费 output,不再 `TryGet*Binding` -3. 等应用层不再 probe binding 后,删除 binding 接口 - -必须退出主干的接口: - -- `TryGetWorkspaceBinding` -- `TryGetDockHostBinding` -- `TryGetInputFeedbackBinding` -- `TryGetTitleBarBinding` -- `EditorWindowFrameTransferRequests` - -阶段验收: - -- 内容层对外只暴露一个宿主接口 -- cursor / capture / title / utility / detach 等信号都能通过 output 表达 - -### Phase 6 文件级动作 - -目标:建立显式拓扑状态源。 - -新增文件建议: - -- `new_editor/app/Windowing/Application/WindowTopologyService.h` -- `new_editor/app/Windowing/Application/WindowTopologyService.cpp` -- `new_editor/app/Windowing/Application/WindowTopologyResult.h` -- `new_editor/app/Windowing/Application/WindowTopologySnapshot.h` - -优先迁移旧文件职责: - -- `Windowing/EditorWindowWorkspaceCoordinator.*` -- `Windowing/EditorUtilityWindowCoordinator.*` - -必须先抽离的事务: - -- active / primary window 管理 -- global tab drag 会话 -- panel detach -> 新窗口创建 -- cross-window drop -- utility window open / reuse / focus - -核心要求: - -- `BuildLiveWindowWorkspaceSet(...)` 只能作为短期兼容代码,最终删除 -- topology state 必须显式保存,而不是运行时临时拼装 -- topology state 必须持有按 `windowId` 建立的权威 workspace projection / session snapshot - -阶段验收: - -- 任何跨窗口行为都能从 `WindowTopologyState` 解释 -- 不再需要从 live windows 反推出唯一真相 - -### Phase 7 文件级动作 - -目标:把平台执行层和业务决策彻底分开。 - -新增文件建议: - -- `new_editor/app/Windowing/Application/WindowApplicationRuntime.h` -- `new_editor/app/Windowing/Application/WindowApplicationRuntime.cpp` -- `new_editor/app/Platform/Win32/Backend/Win32WindowBackend.h` -- `new_editor/app/Platform/Win32/Backend/Win32WindowBackend.cpp` -- `new_editor/app/Platform/Win32/Projection/Win32WindowProjector.h` -- `new_editor/app/Platform/Win32/Projection/Win32WindowProjector.cpp` -- `new_editor/app/Platform/Win32/Rendering/D3D12WindowRenderHost.h` -- `new_editor/app/Platform/Win32/Rendering/D3D12WindowRenderHost.cpp` - -迁移来源: - -- `EditorWindowHostRuntime.cpp` 中的 Win32 建窗、ShowWindow、UpdateWindow -- `EditorWindowWorkspaceCoordinator.cpp` 中的 `SetWindowTextW` -- `EditorWindowChromeController.cpp` 中的窗口移动/缩放/最大化相关 Win32 调用 -- `EditorWindowRuntimeController.*` 中的渲染宿主职责 - -投影规则: - -- `WindowApplicationRuntime` 只做依赖组装和主循环调度 -- `WindowSession` / `WindowTopologyService` 只产生命令和 effect -- `Win32WindowProjector` 把状态变化映射成 effect 列表 -- `Win32WindowBackend` 执行 effect -- `D3D12WindowRenderHost` 只执行渲染宿主职责 - -阶段验收: - -- 应用层不直接包含 Win32 行为代码 -- render host 不直接承担内容决策 - -### Phase 8 文件级动作 - -目标:删掉旧骨架,只保留新主干。 - -计划删除的旧文件范围: - -- `new_editor/app/Platform/Win32/Windowing/EditorWindowManager.*` -- `new_editor/app/Platform/Win32/Windowing/EditorWindowHostRuntime.*` -- `new_editor/app/Platform/Win32/Windowing/EditorWindowMessageDispatcher.*` -- `new_editor/app/Platform/Win32/Windowing/EditorWindowLifecycleCoordinator.*` -- `new_editor/app/Platform/Win32/Windowing/EditorWindowWorkspaceCoordinator.*` -- `new_editor/app/Platform/Win32/Windowing/EditorUtilityWindowCoordinator.*` -- `new_editor/app/Platform/Win32/Runtime/EditorWindowFrameDriver.*` -- `new_editor/app/Platform/Win32/Windowing/EditorWindowTransferRequests.h` - -高风险清理项: - -- `EditorContext` 中所有 per-window capability -- 旧 chrome controller 中残留的状态机 -- 所有 `TryGet*Binding` 调用点 -- 所有 `friend class` 穿透 - -最终收口标准: - -- 旧桥只剩零或极薄 facade -- include 方向符合第 8.1 节规则 -- 代码库中查不到 transfer request 和 binding probe 主干调用 - ---- - -## 10. `EditorContext` 重构要求 - -当前 `EditorContext` 必须被收缩,只保留全局状态和全局服务。 - -### 可以继续保留在全局中的内容 - -- shell asset -- selection service -- command focus service -- project runtime -- scene runtime -- system interaction host -- session store - -### 必须移出的内容 - -- per-window text measurer -- per-window render host -- per-window capture / presentation capability -- 任何依赖“当前窗口实例”的局部能力 - -建议拆分为: - -- `EditorAppContext` -- `EditorSessionStore` -- per-window capability context - ---- - -## 11. 测试策略 - -## 11.1 测试 target 与目录规划 - -必须落成以下 target 规划: - -- `window_session_tests` -- `window_topology_tests` -- `frame_scheduler_tests` -- `win32_message_translator_tests` -- `new_editor_windowing_tests` 作为聚合 target - -建议目录: - -- `new_editor/tests/windowing/window_session/` -- `new_editor/tests/windowing/window_topology/` -- `new_editor/tests/windowing/frame_scheduler/` -- `new_editor/tests/platform/win32_message_translator/` - -## 11.2 fake 组件要求 - -为保证这些测试不是空壳,必须同时定义: - -- `FakeWindowBackend` - - 记录 effect 执行顺序,不实际调用 Win32 -- `FakeRenderHost` - - 记录 resize / render / present / feedback 顺序 -- `FakeContentHost` - - 可配置输出 `WindowContentOutput` -- `FakeTopologyObserver` 或 `FakeTopologySink` - - 校验 topology transaction 提交顺序 -- `FakeFrameClock` - - 控制 steady tick / priority frame 时序 - -## 11.3 阶段门禁命令 - -计划执行时必须收口到明确命令,不能只写“补测试”。最低门禁如下: - -- 编译门禁: - - `cmake --build build --config Debug --target XCUIEditorApp` -- 单元测试门禁: - - `cmake --build build --config Debug --target new_editor_windowing_tests` - - `ctest --test-dir build --config Debug --output-on-failure -R "window_session|window_topology|frame_scheduler|win32_message_translator"` -- 启动级冒烟: - - 启动 `build/new_editor/Debug/XCUIEditor.exe`,确认能稳定启动并进入主循环 - -阶段要求: - -- Phase 0 结束前必须把这些命令和 target 写进计划。 -- Phase 1 结束前必须把 target scaffold 和 fake 组件接口纳入 CMake 与目录结构。 -- Phase 2 及之后每个阶段结束时,未通过本阶段门禁不得进入下一阶段。 - -### 单元测试 - -必须补: - -- `WindowSession` 生命周期测试 -- 输入 reducer 测试 -- chrome reducer 测试 -- topology service 拖拽事务测试 -- frame scheduler 优先级测试 - -### 平台适配测试 - -- `Win32WindowProcAdapter` -- `Win32WindowProjector` -- `Win32WindowBackend` effect 执行桩 - -### 集成测试 - -至少覆盖: - -1. 建窗 / 关窗 -2. 主窗口关闭触发全局回收 -3. resize / dpi change 后的帧调度 -4. panel detach -> 新窗口创建 -5. global tab drag -> cross-window drop -6. utility window open / reuse / focus - -### 高频回归项 - -每阶段后都要回归: - -- capture 是否正确 -- chrome hover / press / resize 是否正常 -- resize 时是否抖动或错帧 -- utility window 是否重复创建 -- 跨窗口拖拽是否残留 preview -- DPI 切换后 cursor / title / size 是否一致 - ---- - -## 12. 风险与控制 - -### 风险 A:新旧状态双写 - -控制: - -- 每阶段明确唯一可写状态源。 -- 审核员重点检查是否存在双写。 - -### 风险 B:移除即时帧后交互变钝 - -控制: - -- `FrameScheduler` 必须支持优先帧。 -- resize / paint 恢复链必须做延迟对比。 - -### 风险 C:拓扑迁移时语义漂移 - -控制: - -- 先冻结旧行为。 -- 每个事务都要有明确测试和日志。 - -### 风险 D:`EditorContext` 收缩影响外围模块 - -控制: - -- 先加新接口,再迁移调用点。 -- 兼容桥必须有明确删除阶段。 - ---- - -## 13. 完成定义 - -当且仅当以下条件同时满足时,重构完成: - -1. 新窗口体系不存在 `friend` 穿透状态。 -2. domain / application 中没有 Win32 类型泄漏。 -3. `WndProc` 路径中不存在直接 render / present。 -4. `EditorWindowFrameTransferRequests` 已删除。 -5. `TryGet*Binding` 探测链已删除。 -6. `WindowTopologyService` 是唯一跨窗口状态源。 -7. `EditorContext` 不再挂载窗口局部能力。 -8. 旧 coordinator / dispatcher / driver 已退出主干。 -9. blocker / major 级审核评论全部关闭。 - ---- - -## 14. 审核评论规则 - -这部分直接作为本计划的附录使用,规则保持简单。 - -### 规则 1:评论必须带阶段和严重度 - -严重度只允许: - -- `BLOCKER` -- `MAJOR` -- `MINOR` -- `QUESTION` - -### 规则 2:评论必须有证据 - -证据至少包含一项: - -- 文件路径 -- 类名 / 函数名 -- 可复现行为 -- 测试或日志 - -没有证据的评论不进入正式台账。 - -### 规则 3:一条评论只打一个核心问题 - -不要把多个无关问题塞进同一条评论。 - -### 规则 4:`BLOCKER` 直接卡阶段 - -有未关闭的 `BLOCKER` 时,不允许进入下一阶段。 - -### 规则 5:`MAJOR` 必须在当前阶段关闭 - -`MAJOR` 不一定立即停工,但当前阶段结束前必须处理完。 - -### 规则 6:执行者必须逐条回复 - -回复至少包含: - -- 处理动作 -- 证据 -- 当前剩余风险 - -### 规则 7:评论只能追加,不删除历史 - -即使评论被驳回,也要保留记录。 - -### 规则 8:计划变更必须先记评论区 - -如果实施过程中要改阶段目标、改边界、改顺序,必须先在评论区登记“计划变更请求”。 - ---- - -## 15. 评论区 - -### 15.1 使用说明 - -以下评论区用于登记: - -- 阶段开始 -- 阶段结束 -- 审核员评论 -- 执行者回复 -- 审核结论 -- 计划变更请求 - -### 15.2 状态总览 - -| Phase | Status | Start Date | End Date | Executor Summary | Reviewer Summary | -| --- | --- | --- | --- | --- | --- | -| Phase 0 | Not Started | | | | | -| Phase 1 | Not Started | | | | | -| Phase 2 | Not Started | | | | | -| Phase 3 | Not Started | | | | | -| Phase 4 | Not Started | | | | | -| Phase 5 | Not Started | | | | | -| Phase 6 | Not Started | | | | | -| Phase 7 | Not Started | | | | | -| Phase 8 | Not Started | | | | | - -### 15.3 评论模板 - -```md -Comment ID: -Phase: -Severity: -Status: Open - -Summary: - -Evidence: -- - -Impact: - -Required Action: - -Close Condition: - -Reviewer: -Date: -``` - -### 15.4 回复模板 - -```md -Response To: -Status: Answered -Executor: -Date: - -Action Taken: -- - -Evidence: -- - -Residual Risk: - -Requested Status: -``` - -### 15.5 计划变更请求模板 - -```md -Plan Change Request ID: -Affected Phase: -Date: -Executor: - -Reason: - -Requested Change: - -Impact: -- - -Rollback Condition: -``` - ---- - -## 16. 阶段评论记录区 - -## Phase 0 - -### Start - -- Date: -- Executor: -- Goal: -- Exit Criteria: - -### Comments -_No open comments. 2026-04-23 的 REVIEW-P0-001 ~ REVIEW-P0-006 已吸收到计划正文。_ - -### End - -- Date: -- Executor: -- Requested Review Decision: - -### Reviewer Decision - -- Decision: -- Reviewer: -- Notes: - ---- - -## Phase 1 - -### Start - -- Date: -- Executor: -- Goal: -- Exit Criteria: - -### Comments - -_No comments yet._ - -### End - -- Date: -- Executor: -- Requested Review Decision: - -### Reviewer Decision - -- Decision: -- Reviewer: -- Notes: - ---- - -## Phase 2 - -### Start - -- Date: -- Executor: -- Goal: -- Exit Criteria: - -### Comments - -_No comments yet._ - -### End - -- Date: -- Executor: -- Requested Review Decision: - -### Reviewer Decision - -- Decision: -- Reviewer: -- Notes: - ---- - -## Phase 3 - -### Start - -- Date: -- Executor: -- Goal: -- Exit Criteria: - -### Comments - -_No comments yet._ - -### End - -- Date: -- Executor: -- Requested Review Decision: - -### Reviewer Decision - -- Decision: -- Reviewer: -- Notes: - ---- - -## Phase 4 - -### Start - -- Date: -- Executor: -- Goal: -- Exit Criteria: - -### Comments - -_No comments yet._ - -### End - -- Date: -- Executor: -- Requested Review Decision: - -### Reviewer Decision - -- Decision: -- Reviewer: -- Notes: - ---- - -## Phase 5 - -### Start - -- Date: -- Executor: -- Goal: -- Exit Criteria: - -### Comments - -_No comments yet._ - -### End - -- Date: -- Executor: -- Requested Review Decision: - -### Reviewer Decision - -- Decision: -- Reviewer: -- Notes: - ---- - -## Phase 6 - -### Start - -- Date: -- Executor: -- Goal: -- Exit Criteria: - -### Comments - -_No comments yet._ - -### End - -- Date: -- Executor: -- Requested Review Decision: - -### Reviewer Decision - -- Decision: -- Reviewer: -- Notes: - ---- - -## Phase 7 - -### Start - -- Date: -- Executor: -- Goal: -- Exit Criteria: - -### Comments - -_No comments yet._ - -### End - -- Date: -- Executor: -- Requested Review Decision: - -### Reviewer Decision - -- Decision: -- Reviewer: -- Notes: - ---- - -## Phase 8 - -### Start - -- Date: -- Executor: -- Goal: -- Exit Criteria: - -### Comments - -_No comments yet._ - -### End - -- Date: -- Executor: -- Requested Review Decision: - -### Reviewer Decision - -- Decision: -- Reviewer: -- Notes: diff --git a/docs/plan/PhysX物理模块架构与分阶段实施计划_2026-04-15.md b/docs/plan/PhysX物理模块架构与分阶段实施计划_2026-04-15.md deleted file mode 100644 index aad5ba67..00000000 --- a/docs/plan/PhysX物理模块架构与分阶段实施计划_2026-04-15.md +++ /dev/null @@ -1,846 +0,0 @@ -# PhysX 物理模块架构与分阶段实施计划 -日期: `2026-04-15` - -## 1. 文档定位 - -这份文档是当前 `XCEngine` 物理模块主线的正式计划。 - -本计划只针对一条路线: - -- 只集成 `PhysX` -- 只做 `3D` 物理 -- 只做一个后端 -- 不做 `Jolt` -- 不做类似 `RHI` 的双后端抽象 - -这份文档的目标不是讨论“物理引擎选型”,而是把当前工程里怎样把 `PhysX` 正式接进运行时、编辑器、序列化、测试链路,拆成可执行的阶段计划。 - -## 2. 当前工程事实 - -### 2.1 运行时已经具备固定步入口 - -当前运行时已经有较成熟的固定步循环: - -- `engine/src/Scene/RuntimeLoop.cpp` -- `engine/src/Scene/SceneRuntime.cpp` - -当前 `SceneRuntime::FixedUpdate()` 的顺序是: - -1. `ScriptEngine::OnFixedUpdate()` -2. `Scene::FixedUpdate()` - -这意味着物理模块的首个正式接入点是明确的:物理 step 应该插在当前 fixed-step 主链上,而不是另起一个临时循环或旁路更新器。 - -### 2.2 Play 模式已经有场景克隆与恢复链路 - -当前编辑器 Play 模式不是直接改编辑态场景,而是通过运行时场景快照/恢复工作: - -- `editor/src/Core/PlaySessionController.cpp` -- `editor/src/Managers/SceneManager.cpp` - -这意味着物理世界不应该成为 `Scene` 的持久化运行时负担,而应该是运行时侧临时构建、停止播放后销毁的对象。 - -### 2.3 组件生命周期目前不够支撑正式物理注册链 - -当前 `GameObject::AddComponent()` 只负责创建组件并塞入 `m_components`,不会自动补齐运行时注册链: - -- `engine/include/XCEngine/Components/GameObject.h` - -当前 `GameObject` 已经有: - -- `AddComponent()` -- `RemoveComponent()` -- `RemoveComponent(Component*)` - -但当前 `Scene` 只有: - -- `OnGameObjectCreated()` -- `OnGameObjectDestroyed()` - -没有: - -- `OnComponentAdded()` -- `OnComponentRemoved()` - -这会直接影响物理模块,因为 `Rigidbody` / `Collider` 属于组件级对象,不是 `GameObject` 级对象。 - -### 2.4 反序列化后没有现成的物理补注册链 - -当前 `Scene::DeserializeFromString()` 会重建 `GameObject` 和组件,并调用组件 `Deserialize()`,但没有正式的物理补注册入口: - -- `engine/src/Scene/Scene.cpp` - -这意味着物理模块首版不能只依赖组件 `Awake()` 完成注册,必须在运行时启动时做一次全场景扫描建表。 - -### 2.5 组件工厂和 Inspector 还没有物理组件入口 - -当前内建组件注册点是: - -- `engine/src/Components/ComponentFactoryRegistry.cpp` -- `editor/src/ComponentEditors/ComponentEditorRegistry.cpp` - -这两处目前都还没有物理相关组件: - -- `Rigidbody` -- `Collider` -- `BoxCollider` -- `SphereCollider` -- `CapsuleCollider` - -因此物理模块不能只写运行时,还必须同步补齐工厂、编辑器和序列化入口。 - -### 2.6 任务系统当前不适合直接承接物理并发 - -当前线程基础模块存在明确缺口: - -- `engine/src/Threading/TaskSystem.cpp` 中 `TaskSystem::Wait()` 为空实现 -- `engine/include/XCEngine/Threading/TaskSystem.h` 中 `ParallelFor()` 只提交任务,不等待完成 -- `engine/src/Threading/TaskGroup.cpp` / `engine/include/XCEngine/Threading/TaskGroup.h` 的 `m_pendingCount` 只看到了增加路径,没有完整的完成回收闭环 - -这意味着当前工程的任务系统暂时不能作为物理 step 的可信并发底座。 - -结论很明确: - -- 线程问题是真问题 -- 但它不是 `PhysX v1` 的主阻塞项 -- `PhysX v1` 首版必须按单线程/同步 step 路线设计 -- 不能在 `v1` 阶段把 `XCEngine::Threading::TaskSystem` 接到物理关键路径里 - -### 2.7 构建系统当前还没有物理第三方接入位 - -当前 `engine/CMakeLists.txt` 已经是显式 vendored third-party 风格: - -- `GLAD` -- `stb` -- `assimp` -- `kissfft` - -因此 `PhysX` 最顺的接法不是额外做一套复杂包管理,而是沿用当前工程习惯,以 `engine/third_party/physx/` 的形式落地。 - -## 3. 总体目标 - -这次物理模块建设的总体目标是: - -1. 在当前引擎里建立正式的 `PhysX` 物理子系统,而不是 demo 级接入。 -2. 对外提供 Unity 风格的 `Rigidbody + Collider + FixedUpdate + Query + Collision/Trigger` 体验。 -3. 物理模块必须打通运行时、编辑器、序列化、Play 模式、测试链路。 -4. 首版优先保证正确性、稳定性和可维护性,不追求并行优化和高级特性广度。 - -## 4. 核心架构决策 - -### 4.1 只做 `PhysX`,不预留双后端实现 - -当前计划里不做: - -- `IPhysicsBackend` -- `PhysXBackend + JoltBackend` -- `类似 RHI 的多后端切换` - -当前只做: - -- 引擎自己的物理概念层 -- 一套 `PhysX` 实现 - -这里的“引擎自己的物理概念层”不是为了多后端,而是为了不把 `Px*` 类型泄露到公共头文件和脚本/编辑器接口中。 - -### 4.2 只做 `3D` 物理 - -当前路线不包含: - -- `2D` -- `Box2D` - -后续如果要做 `2D`,必须作为独立项目线,而不是在这条 `PhysX 3D` 计划里夹带实现。 - -### 4.3 首版单线程 - -当前物理模块首版固定为: - -- 主线程创建物理世界 -- 主线程同步 step -- 主线程收集和派发物理事件 - -不做: - -- 物理内部 job system 接桥 -- 自研任务系统并行 broadphase / query -- 并行回调派发 - -### 4.4 `SceneRuntime` 持有 `PhysicsWorld` - -当前建议所有权如下: - -- `Scene` 只持有可序列化的场景和组件数据 -- `SceneRuntime` 持有运行时 `PhysicsWorld` -- `PhysicsWorld` 持有 `PhysX Foundation / Physics / Scene / Material / Dispatcher` 等原生对象 - -这样可以和当前 Play 模式克隆/恢复模型自然对齐。 - -### 4.5 运行时物理对象与组件解耦 - -当前建议把物理对象分成两层: - -1. 可序列化层 - 组件数据,例如: - - `RigidbodyComponent` - - `ColliderComponent` - - `BoxColliderComponent` - - `SphereColliderComponent` - - `CapsuleColliderComponent` - -2. 运行时绑定层 - 运行时 `PhysicsWorld` 内维护: - - `GameObject ID -> PhysX actor/shape binding` - - `PhysX actor/shape -> GameObject ID` - - 变更脏标记 - - 最近一次同步的 transform 快照 - -这意味着组件不直接拥有 `PxRigidActor*` 或 `PxShape*` 的公开生命周期。 - -### 4.6 `GameObject.layer` 直接作为 v1 过滤入口 - -`v1` 不做项目级碰撞矩阵资产系统。 - -`v1` 先直接复用现有: - -- `GameObject.layer` - -后续再扩展成: - -- layer collision matrix -- query mask -- trigger special-case mask - -### 4.7 物理事件必须先入队,再主线程派发 - -不能在 `PhysX` 回调里直接调脚本或原生用户逻辑。 - -正确路线是: - -1. `PhysX` 原始 callback 收集 contact/trigger 信息 -2. 写入引擎自己的事件缓冲 -3. 在 step 完成后,回到主线程统一翻译并派发 - -### 4.8 先做运行时启动全量扫描,再补组件级事件 - -由于当前反序列化与组件生命周期链还不完整,首版必须: - -1. 运行时启动时扫描整个 scene -2. 为所有 `Rigidbody/Collider` 建立运行时绑定 - -然后再补: - -- 组件新增 -- 组件删除 -- 运行时临时创建/销毁对象 - -### 4.9 不把 `Px*` 类型泄露到公共 API - -公共头文件中禁止暴露: - -- `PxRigidActor` -- `PxShape` -- `PxScene` -- `PxMaterial` -- 其他 `Px*` SDK 类型 - -`PhysX` 相关包含和具体实现必须收口在: - -- `engine/src/Physics/PhysX/` - -## 5. 首版范围与边界 - -### 5.1 首版必须完成 - -`v1` 必须完成以下能力: - -- `RigidbodyComponent` -- `ColliderComponent` 抽象基类 -- `BoxColliderComponent` -- `SphereColliderComponent` -- `CapsuleColliderComponent` -- `Static / Dynamic / Kinematic` 三类刚体模式 -- 基础重力与力学 step -- `Raycast` -- 至少一种基础 `Overlap` -- `Collision / Trigger` 事件缓冲与主线程派发 -- Inspector 编辑 -- 场景序列化 / 反序列化 -- Play 模式进入/退出时的物理世界创建与销毁 -- 基础自动化测试 - -### 5.2 首版明确限制 - -为了保证第一条线能闭环,`v1` 主动限制为: - -- 每个 `GameObject` 只支持一个 `RigidbodyComponent` -- 每个 `GameObject` 首版只支持一个 `ColliderComponent` -- 不做 child-collider compound 组装 -- 不做 `MeshCollider` -- 不做 joint -- 不做 character controller -- 不做 vehicle -- 不做 `PhysicsMaterial` 资产系统 -- 不做复杂 runtime cook -- 不做多线程 step - -这不是说后续永远不支持,而是明确防止 `v1` 范围失控。 - -### 5.3 首版的简单语义 - -首版建议采用以下简单语义: - -- `Collider` 但没有 `Rigidbody`:创建 static actor -- `Rigidbody + Collider`:创建 dynamic actor 或 kinematic actor -- `Rigidbody` 没有 `Collider`:允许存在,但不创建可碰撞 shape,并输出警告日志 -- 运行时移动 static collider:先定义为“允许但会触发重建或警告”,不在 `v1` 追求完美优化 - -## 6. 建议的模块结构 - -### 6.1 公共头文件 - -建议新增: - -- `engine/include/XCEngine/Physics/PhysicsTypes.h` -- `engine/include/XCEngine/Physics/RaycastHit.h` -- `engine/include/XCEngine/Physics/PhysicsQuery.h` -- `engine/include/XCEngine/Physics/PhysicsEvents.h` -- `engine/include/XCEngine/Physics/PhysicsWorld.h` - -### 6.2 PhysX 实现目录 - -建议新增: - -- `engine/src/Physics/PhysicsWorld.cpp` -- `engine/src/Physics/PhysicsQuery.cpp` -- `engine/src/Physics/PhysicsEvents.cpp` -- `engine/src/Physics/PhysX/PhysXCommon.h` -- `engine/src/Physics/PhysX/PhysXWorld.cpp` -- `engine/src/Physics/PhysX/PhysXShapeBuilder.cpp` -- `engine/src/Physics/PhysX/PhysXEventListener.cpp` -- `engine/src/Physics/PhysX/PhysXQueryAdapter.cpp` - -### 6.3 组件层 - -建议新增: - -- `engine/include/XCEngine/Components/RigidbodyComponent.h` -- `engine/include/XCEngine/Components/ColliderComponent.h` -- `engine/include/XCEngine/Components/BoxColliderComponent.h` -- `engine/include/XCEngine/Components/SphereColliderComponent.h` -- `engine/include/XCEngine/Components/CapsuleColliderComponent.h` -- 对应 `engine/src/Components/*.cpp` - -### 6.4 编辑器层 - -建议新增: - -- `editor/src/ComponentEditors/RigidbodyComponentEditor.cpp` -- `editor/src/ComponentEditors/BoxColliderComponentEditor.cpp` -- `editor/src/ComponentEditors/SphereColliderComponentEditor.cpp` -- `editor/src/ComponentEditors/CapsuleColliderComponentEditor.cpp` - -### 6.5 测试层 - -建议新增: - -- `tests/Physics/CMakeLists.txt` -- `tests/Physics/test_physics_world.cpp` -- `tests/Physics/test_rigidbody_component.cpp` -- `tests/Physics/test_collider_component.cpp` -- `tests/Physics/test_physics_queries.cpp` -- `tests/Physics/test_physics_events.cpp` - -必要时补充: - -- `tests/Scene/` -- `tests/Editor/` -- `tests/Scripting/` - -## 7. 运行时数据模型与职责分工 - -### 7.1 `PhysicsWorld` 的职责 - -`PhysicsWorld` 负责: - -- 初始化和销毁 `PhysX` -- 创建和销毁 runtime `PxScene` -- 维护 `GameObject` 与物理 actor/shape 的双向绑定 -- 同步 transform -- 执行 step -- 执行 query -- 缓存和派发事件 - -`PhysicsWorld` 不负责: - -- 组件序列化格式定义 -- Inspector UI -- 编辑器 undo/redo - -### 7.2 `RigidbodyComponent` 的职责 - -`RigidbodyComponent` 负责声明可序列化的刚体属性,例如: - -- body type -- mass -- use gravity -- linear drag -- angular drag -- constraints -- is kinematic -- is trigger 不应该放在刚体里 - -### 7.3 `ColliderComponent` 的职责 - -`ColliderComponent` 负责声明可序列化的碰撞体属性,例如: - -- trigger -- center -- size / radius / height -- friction / restitution 的简化参数 -- 运行时 dirty 标记 - -### 7.4 transform 同步方向 - -`v1` 建议采用明确的单向职责: - -- dynamic rigidbody:`PhysX -> Transform` -- kinematic rigidbody:`Transform -> PhysX` -- static collider:初始化时 `Transform -> PhysX`,运行时若变更则标记重建 - -### 7.5 transform 变更检测策略 - -当前 `TransformComponent` 只有内部 `m_dirty`,它主要服务矩阵缓存,不是对外事件总线。 - -因此 `v1` 不建议先补全局 transform 事件。 - -`v1` 采用更稳妥的做法: - -- 物理绑定缓存最近一次同步的 world transform -- 每个 fixed-step 前,对受物理影响的对象做快照比对 -- 只有发现变更时才同步到 `PhysX` - -这样可以避免为了物理模块先重构整个 transform 通知系统。 - -## 8. 关键接入链路 - -### 8.1 Runtime fixed-step 接入 - -当前建议在 `SceneRuntime::FixedUpdate()` 中接入物理 step,目标顺序为: - -1. `ScriptEngine::OnFixedUpdate(fixedDeltaTime)` -2. `Scene::FixedUpdate(fixedDeltaTime)` -3. `PhysicsWorld::Step(fixedDeltaTime)` -4. 写回 dynamic rigidbody transform -5. 派发 `Collision / Trigger` 事件 - -这条顺序与 Unity 经典 `FixedUpdate -> Physics Simulation` 的高层心智基本一致。 - -### 8.2 Runtime start/stop 接入 - -当前建议: - -- `SceneRuntime::Start()` 或等价 runtime 启动点创建 `PhysicsWorld` -- 启动时扫描 scene 全量注册物理组件 -- `SceneRuntime::Stop()` 销毁 `PhysicsWorld` - -### 8.3 运行时对象新增/销毁接入 - -利用当前已经存在的: - -- `Scene::OnGameObjectCreated()` -- `Scene::OnGameObjectDestroyed()` - -处理: - -- 运行时新建带物理组件的对象 -- 运行时销毁对象时的物理解绑 - -### 8.4 组件增删接入 - -这里是首个必须补的基础设施点。 - -建议新增: - -- `Scene::OnComponentAdded()` -- `Scene::OnComponentRemoved()` - -并让: - -- `GameObject::AddComponent()` -- `GameObject::RemoveComponent()` - -在有 `Scene` 挂接时通知 scene 事件。 - -只有这样,运行时临时添加 `Rigidbody` 或 `Collider` 才能被物理系统正式接收。 - -### 8.5 序列化接入 - -当前做法建议是: - -- 物理组件完全走现有组件序列化体系 -- `PhysicsWorld` 本身不序列化 -- 进入 play 时从场景数据重建 runtime 物理世界 - -### 8.6 Inspector 接入 - -Inspector 需要完成: - -- 组件创建入口 -- 字段编辑 -- dirty scene 标记 -- 运行时模式下对物理对象的即时重建或同步 - -### 8.7 Scripting 接入 - -脚本接入分两步: - -1. `v1a` - 先保证脚本 `FixedUpdate` 修改 transform 可以影响 kinematic rigidbody。 - -2. `v1b` - 再暴露 managed 侧基础 API,例如: - - `Physics.Raycast` - - `Rigidbody.AddForce` - - `OnCollisionEnter/Exit` - - `OnTriggerEnter/Exit` - -脚本桥接不应阻塞 native 物理核心落地。 - -## 9. 必须先处理的基础设施问题 - -### 9.1 必须优先解决:组件级注册链 - -这是物理模块首个真实 blocker。 - -没有组件级事件,就没有正式的 runtime add/remove 注册链。 - -临时在 `ComponentFactoryRegistry` 或编辑器命令里补逻辑都不对,因为: - -- 场景反序列化会绕过它 -- 脚本运行时添加组件也会绕过它 -- 后续测试会出现路径不一致 - -### 9.2 不必阻塞首版:任务系统修复 - -线程模块存在问题,但它不是 `PhysX v1` 的阻塞项。 - -本轮物理计划的原则是: - -- 承认任务系统当前不可靠 -- 不把它接进 `PhysX v1` -- 单独列为后续基础设施整修任务 - -### 9.3 需要明确策略:transform 通知缺失 - -当前没有统一 transform 变更事件。 - -本轮不建议为了物理模块先重构全局 transform 事件系统,而是采用缓存比对方案。 - -### 9.4 需要明确策略:运行时静态体变更 - -Unity 里移动 static collider 本来也不是理想路径。 - -`v1` 建议明确定义: - -- 若 static collider transform 发生变更,物理系统执行重建或输出警告 -- 不在首版追求最优成本 - -## 10. 分阶段执行计划 - -### Phase 0:基础设施收口与边界建立 - -目标: - -- 把物理模块需要的“最低限度基础链”补齐 - -任务: - -1. 在 `Scene` 增加组件级事件 -2. 让 `GameObject::AddComponent()` / `RemoveComponent()` 通知 `Scene` -3. 新建 `Physics` 目录、CMake 条目、测试目录骨架 -4. 明确 `PhysX-only` 宏和 build 配置 -5. 定义物理模块公共类型和命名规范 - -完成标志: - -- 可以稳定收到组件增删事件 -- 物理模块可以作为正式 engine 模块参与编译 -- 没有把 `Px*` 暴露到公共 API - -### Phase 1:PhysX 启动与 `PhysicsWorld` 最小闭环 - -目标: - -- 在运行时建立一套最小可工作的 `PhysicsWorld` - -任务: - -1. 接入 `PhysX` 第三方目录和 CMake -2. 初始化 `foundation / physics / scene / material` -3. 建立 `PhysicsWorld` 生命周期 -4. 在 `SceneRuntime` 的 start/stop 路径中创建/销毁世界 -5. 在 fixed-step 中插入 `PhysicsWorld::Step()` - -完成标志: - -- 空场景可正常创建和销毁物理世界 -- fixed-step 中可执行空 step 且不破坏现有 runtime loop -- Play 模式进出不泄漏物理资源 - -### Phase 2:刚体与碰撞体组件接入 - -目标: - -- 把最基础的 `Rigidbody + Collider` 跑通 - -任务: - -1. 新增 `RigidbodyComponent` -2. 新增 `ColliderComponent` 基类 -3. 新增 `Box / Sphere / Capsule` 三种碰撞体 -4. 在 `ComponentFactoryRegistry` 注册组件 -5. 在组件序列化中支持物理字段 -6. 实现 `Collider without Rigidbody => static actor` -7. 实现 `Rigidbody + Collider => dynamic/kinematic actor` - -完成标志: - -- 编辑器可添加这些组件 -- 场景保存/加载后组件数据完整恢复 -- 运行时能生成对应 `PhysX` actor/shape - -### Phase 3:运行时注册链与 transform 同步 - -目标: - -- 解决“启动时有物理对象”和“运行时新增/删除物理对象”两条链 - -任务: - -1. 运行时启动时全量扫描 scene -2. 对 `OnGameObjectCreated/Destroyed` 建立注册/解绑逻辑 -3. 对 `OnComponentAdded/Removed` 建立注册/解绑逻辑 -4. 建立 `GameObject ID <-> actor/shape` 双向绑定 -5. 实现 pre-step 与 post-step transform 同步 -6. 对 static collider 运行时变更采用重建或警告策略 - -完成标志: - -- 运行时创建/删除物体可以正确影响物理世界 -- kinematic transform 修改能正确驱动物理 -- dynamic rigidbody 的仿真结果能稳定写回 transform - -### Phase 4:查询、过滤与事件 - -目标: - -- 把 gameplay 最核心的 physics query 和事件做出来 - -任务: - -1. 实现 `Raycast` -2. 实现至少一种 `Overlap` -3. 复用 `GameObject.layer` 做碰撞/查询过滤 -4. 接入 `PhysX` contact / trigger callback -5. 建立引擎自己的事件缓冲 -6. 在 step 后统一派发 `Collision / Trigger` 事件 - -完成标志: - -- 可通过 API 执行稳定 raycast -- layer 过滤能生效 -- trigger 与 collision 事件能按主线程顺序派发 - -### Phase 5:编辑器与 Play 模式正式化 - -目标: - -- 让物理模块成为编辑器里的正式一等公民 - -任务: - -1. 新增物理组件 Inspector -2. 支持组件属性编辑和 scene dirty 标记 -3. Play 模式下 runtime scene 正确构建 physics world -4. Stop Play 后编辑态 scene 不被 runtime 结果污染 -5. 补充最小物理调试可视化方案 - -完成标志: - -- 在 Inspector 中可稳定编辑物理组件 -- Play / Stop 后 scene 状态恢复正确 -- 物理模块不再依赖临时 debug 路径才能观测 - -### Phase 6:脚本桥接与 API 收口 - -目标: - -- 把物理能力正式接到 managed 层 - -任务: - -1. 暴露 `Physics.Raycast` -2. 暴露 `Rigidbody` 基础方法 -3. 暴露碰撞/触发回调 -4. 对脚本侧文档和示例进行最小补齐 - -完成标志: - -- C# 脚本可执行基础 query -- C# 脚本可感知基础碰撞/触发事件 -- 脚本层没有直接依赖 `Px*` - -### Phase 7:稳定性、限制收口与二期预留 - -目标: - -- 在不扩大范围的前提下,把首版边界收紧 - -任务: - -1. 完成日志、断言、错误提示收口 -2. 明确静态体移动、缺 collider 刚体、非法参数等限制 -3. 评估二期项目: - - 多 collider - - compound shape - - mesh collider - - joints - - physics material asset - - 任务系统修复后再考虑的并行 step - -完成标志: - -- `v1` 边界稳定、行为可解释、测试可重复 -- 二期需求不再反向污染 `v1` 核心结构 - -## 11. 构建与第三方接入策略 - -当前建议沿用已有 third-party 风格: - -- `engine/third_party/physx/include` -- `engine/third_party/physx/lib` -- `engine/third_party/physx/bin` - -建议首版只支持: - -- Windows -- MSVC -- x64 - -构建侧任务包括: - -1. `engine/CMakeLists.txt` 增加 include path -2. 增加 PhysX 相关 link libraries -3. editor/tests 复制 PhysX 运行时 DLL -4. Debug / Release 配置分离 - -当前不建议首版同时做: - -- 多平台打包脚本 -- 自动下载 SDK -- 复杂 find-package 兼容层 - -## 12. 测试计划 - -### 12.1 单元测试 - -建议覆盖: - -- 组件工厂可创建物理组件 -- 物理组件序列化/反序列化 -- `PhysicsWorld` 创建/销毁 -- raycast 命中 -- layer 过滤 -- trigger / collision 事件翻译 - -### 12.2 运行时测试 - -建议覆盖: - -- fixed-step 中重力驱动刚体下落 -- dynamic rigidbody 写回 transform -- kinematic transform 驱动物理体移动 -- 运行时新增/删除物体或组件 - -### 12.3 编辑器测试 - -建议覆盖: - -- Play 模式启动后运行时场景生成物理世界 -- Stop Play 后恢复编辑态场景 -- Inspector 可添加和移除物理组件 - -### 12.4 脚本测试 - -建议覆盖: - -- 脚本 fixed update 修改 kinematic 目标 -- managed `Physics.Raycast` -- managed 碰撞回调 - -## 13. 与线程基础模块的关系 - -当前结论必须明确写死: - -- `PhysX v1` 不依赖 `XCEngine::Threading::TaskSystem` -- `PhysX v1` 不以修复任务系统为前置阻塞项 -- 任务系统修复是后续基础设施整修项目,不并入当前物理主线 - -后续只有在以下条件满足后,才考虑物理并行化: - -1. `TaskSystem::Wait()` 有完整实现 -2. `ParallelFor()` 具备同步闭环 -3. `TaskGroup` 完成计数与回调链修复 -4. 物理 `v1` 的行为语义已经稳定 - -在这之前,不允许把“先做多线程框架”变成物理模块的前置阻塞。 - -## 14. 风险与提前约束 - -### 14.1 最大风险不是 SDK 接入,而是生命周期接入 - -如果组件增删、反序列化补注册、运行时对象创建销毁链没打通,物理模块会表现成“看起来能跑,但一操作就坏”。 - -### 14.2 最大范围风险是功能蔓延 - -最容易拖垮首版的需求是: - -- 多 collider -- mesh collider -- joints -- character controller -- 物理材质资产 -- 多线程 step - -这些都必须严格压到二期。 - -### 14.3 最大实现风险是把 `PhysX` 类型泄露到各层 - -一旦 `Px*` 进入: - -- 公共头文件 -- 脚本桥接 -- 编辑器组件编辑器 - -后续维护成本会快速失控。 - -## 15. 完成标准 - -满足以下条件,才能认为 `PhysX v1` 收口: - -1. 编辑器里可以为对象添加 `Rigidbody` 和基础 `Collider` -2. Scene 保存/加载后物理组件数据不丢失 -3. Play 模式启动后物理世界能从 runtime scene 正式构建 -4. fixed-step 中 dynamic rigidbody 能稳定仿真并写回 transform -5. kinematic rigidbody 能响应 transform 驱动 -6. `Raycast` 与基础 `Overlap` 可用 -7. `Collision / Trigger` 事件可用 -8. Stop Play 后编辑态 scene 不被 runtime 污染 -9. 自动化测试能覆盖最小闭环 -10. 整个链路只依赖 `PhysX` 一个后端 - -## 16. 一句话结论 - -这条线的正确做法不是“先做一个可替换的物理后端框架”,而是“先把 `PhysX-only` 的正式物理子系统在当前引擎里打穿”。 -先收口生命周期、运行时绑定、编辑器和测试链,再考虑二期扩展;线程基础模块的问题先认账,但不让它阻塞 `PhysX v1` 主线。 diff --git a/docs/plan/Unity SRP API参考文档.md b/docs/plan/Unity SRP API参考文档.md deleted file mode 100644 index a4cc08fe..00000000 --- a/docs/plan/Unity SRP API参考文档.md +++ /dev/null @@ -1,621 +0,0 @@ -# Unity SRP (Scriptable Render Pipeline) API 参考文档 - -## 1. 概述 - -Unity 的可编写脚本渲染管线 (Scriptable Render Pipeline, SRP) 是一项允许通过 C# 脚本控制渲染的功能。SRP 是通用渲染管线 (URP) 和高清渲染管线 (HDRP) 的底层技术基础。 - -**命名空间**: `UnityEngine.Rendering` - ---- - -## 2. 核心类 API - -### 2.1 RenderPipeline 类 - -**命名空间**: `UnityEngine.Rendering` - -**描述**: 提供实现可编写脚本渲染管线的渲染逻辑的方式。继承自此类以重写 `Render` 方法来实现自定义 SRP。 - -**示例代码**: -```csharp -using UnityEngine; -using UnityEngine.Rendering; -using System.Collections.Generic; - -public class CustomRenderPipeline : RenderPipeline -{ - protected override void Render(ScriptableRenderContext context, List cameras) - { - // 实现自定义 SRP 渲染逻辑 - } -} -``` - -**属性**: -| 属性 | 描述 | -|------|------| -| `disposed` | 返回 true 表示 RenderPipeline 无效或已销毁 | - -**受保护的方法**: -| 方法 | 描述 | -|------|------| -| `IsPreviewSupported()` | 指定相机是否可以预览 | -| `IsRenderRequestSupported()` | 实现此方法以指定特定的相机和渲染请求组合是否受支持 | -| `ProcessRenderRequests()` | 执行使用 RenderPipeline.SubmitRenderRequest 提交的渲染请求 | -| `Render()` | 定义此 RenderPipeline 自定义渲染的入口点方法 | - -**静态方法**: -| 方法 | 描述 | -|------|------| -| `BeginCameraRendering(ScriptableRenderContext, Camera)` | 调用 RenderPipelineManager.beginCameraRendering 委托 | -| `BeginContextRendering(ScriptableRenderContext, List)` | 调用 RenderPipelineManager.beginContextRendering 和 beginFrameRendering 委托 | -| `BeginFrameRendering(ScriptableRenderContext, List)` | 调用 RenderPipelineManager.beginFrameRendering 委托 | -| `EndCameraRendering(ScriptableRenderContext, Camera)` | 调用 RenderPipelineManager.endCameraRendering 委托 | -| `EndContextRendering(ScriptableRenderContext, List)` | 调用 RenderPipelineManager.endContextRendering 和 endFrameRendering 委托 | -| `EndFrameRendering(ScriptableRenderContext, List)` | 调用 RenderPipelineManager.endFrameRendering 委托 | -| `SubmitRenderRequest(Camera, RenderRequest)` | 向相机提交渲染请求(在 Unity 渲染循环之外) | -| `SupportsRenderRequest(Camera, RequestData)` | 检查渲染管线是否支持使用相机的 RequestData 类型 | -| `ProcessRenderRequests(List)` | 执行使用 Camera.SubmitRenderRequest 提交的渲染请求 | - ---- - -### 2.2 RenderPipelineAsset 类 - -**命名空间**: `UnityEngine.Rendering` - -**继承**: `ScriptableObject` - -**描述**: 生成特定 IRenderPipeline 的资源。IRenderPipelineAsset 的默认实现。管理继承类型的生命周期,并确保创建的 IRenderPipeline 在资源更改时失效。 - -**属性**: -| 属性 | 描述 | -|------|------| -| `autodeskInteractiveMaskedShader` | 获取此管线的默认 Autodesk Interactive 遮罩 Shader | -| `autodeskInteractiveShader` | 获取此管线的默认 Autodesk Interactive Shader | -| `autodeskInteractiveTransparentShader` | 获取此管线的默认 Autodesk Interactive 透明 Shader | -| `default2DMaskMaterial` | 获取 Sprite Masks 使用的默认 2D 遮罩材质 | -| `default2DMaterial` | 返回此管线的默认 2D 材质 | -| `defaultLineMaterial` | 返回此管线的默认线条材质 | -| `defaultMaterial` | 返回此管线的默认材质 | -| `defaultParticleMaterial` | 返回此管线的默认粒子材质 | -| `defaultShader` | 返回此管线的默认 Shader | -| `defaultSpeedTree7Shader` | 返回 SpeedTree v7 Shader | -| `defaultSpeedTree8Shader` | 返回 SpeedTree v8 Shader | -| `defaultSpeedTree9Shader` | 返回 SpeedTree v9 Shader | -| `defaultTerrainMaterial` | 返回此管线的默认地形材质 | -| `defaultUIETC1SupportedMaterial` | 返回默认 UI ETC1 材质 | -| `defaultUIMaterial` | 返回默认 UI 材质 | -| `defaultUIOverdrawMaterial` | 返回默认 UI overdraw 材质 | -| `pipelineType` | 返回与此 RenderPipelineAsset 实例关联的 RenderPipeline 类型 | -| `renderPipelineShaderTag` | 返回此资源描述的渲染管线的 Shader Tag 值 | -| `terrainBrushPassIndex` | 编辑器中地形笔刷的渲染索引 | -| `terrainDetailGrassBillboardShader` | 返回此管线的细节草 Billboard Shader | -| `terrainDetailGrassShader` | 返回此管线的细节草 Shader | -| `terrainDetailLitShader` | 返回此管线的细节光照 Shader | - -**受保护的方法**: -| 方法 | 描述 | -|------|------| -| `CreatePipeline()` | 创建此资源特有的 IRenderPipeline | -| `EnsureGlobalSettings()` | 确保全局设置已准备好并注册到 GraphicsSettings | -| `OnDisable()` | RenderPipelineAsset 的 OnDisable 默认实现 | -| `OnValidate()` | RenderPipelineAsset 的 OnValidate 默认实现 | - ---- - -### 2.3 ScriptableRenderContext 类 - -**命名空间**: `UnityEngine.Rendering` - -**类型**: struct (结构体) - -**描述**: 定义自定义渲染管线使用的状态和绘制命令。当定义自定义 RenderPipeline 时,使用 ScriptableRenderContext 来调度和提交状态更新以及绘制命令到 GPU。 - -**公共方法**: -| 方法 | 描述 | -|------|------| -| `BeginRenderPass(RenderTargetIdentifier, RenderPassAttachment, ClearFlag, Color)` | 调度新渲染 pass 的开始 | -| `BeginScopedRenderPass(...)` | 调度渲染 pass 开始,使用 using 语句会自动调用 EndRenderPass | -| `BeginScopedSubPass(...)` | 调度子 pass 开始,使用 using 语句会自动调用 EndSubPass | -| `BeginSubPass(...)` | 调度子 pass 的开始 | -| `CreateGizmoRendererList(...)` | 创建新的 Gizmo RendererList | -| `CreateRendererList(...)` | 创建新的渲染器 RendererList | -| `CreateShadowRendererList(...)` | 创建新的阴影 RendererList | -| `CreateSkyboxRendererList(...)` | 创建新的天空盒 RendererList | -| `CreateUIOverlayRendererList(...)` | 创建新的 UIOverlay RendererList | -| `CreateWireOverlayRendererList(...)` | 创建新的线框覆盖层 RendererList | -| `Cull(ScriptableCullingParameters)` | 执行基于 ScriptableCullingParameters 的剔除 | -| `CullShadowCasters(...)` | 对所有可见光源执行阴影投射体剔除 | -| `DrawGizmos(Camera, GizmoSubset)` | 调度绘制 Gizmos 子集 | -| `DrawUIOverlay(Camera)` | 绘制 UI 覆盖层 | -| `DrawWireOverlay(Camera)` | 调度线框覆盖层绘制 | -| `EndRenderPass()` | 调度当前活动渲染 pass 的结束 | -| `EndSubPass()` | 调度当前活动子 pass 的结束 | -| `ExecuteCommandBuffer(CommandBuffer)` | 调度自定义图形 CommandBuffer 的执行 | -| `ExecuteCommandBufferAsync(CommandBuffer, ComputeQueueType)` | 在异步计算队列上执行 CommandBuffer | -| `HasInvokeOnRenderObjectCallbacks()` | 检查场景中是否有注册 OnRenderObject 回调的对象 | -| `InvokeOnRenderObjectCallback(Camera)` | 调度 MonoBehaviour 脚本的 OnRenderObject 回调执行 | -| `PrepareRendererListsAsync(List)` | 启动在后台处理提供的 RendererLists | -| `QueryRendererListStatus(RendererList)` | 查询 RendererList 的状态 | -| `SetupCameraProperties(Camera)` | 调度相机特定全局 Shader 变量的设置 | -| `StartMultiEye(Camera)` | 调度在 ScriptableRenderContext 上开始细粒度立体渲染 | -| `StopMultiEye(Camera)` | 调度停止立体渲染 | -| `StereoEndRender(Camera)` | 调度单帧立体渲染完成通知 | -| `Submit()` | 提交所有调度的命令到渲染循环执行 | -| `SubmitForRenderPassValidation()` | 验证渲染 pass 是否可以执行调度的命令 | - -**静态方法**: -| 方法 | 描述 | -|------|------| -| `EmitGeometryForCamera(Camera)` | 为指定相机发射 UI 几何体进行渲染 | -| `EmitWorldGeometryForSceneView(SceneView)` | 为场景视图发射 UI 几何体 | -| `PopDisableApiRenderers()` | 启用渲染器场景节点的即时添加和移除 | -| `PushDisableApiRenderers()` | 阻止渲染器场景节点的即时添加或移除 | - ---- - -### 2.4 ScriptableRenderPass 类 - -**命名空间**: `UnityEngine.Rendering` - -**描述**: 可编写脚本的渲染 Pass 基类。用于实现自定义渲染 passes。 - -**主要成员**: -| 成员 | 描述 | -|------|------| -| `renderPassEvent` | 指定 Pass 在渲染管线中的执行时机 | -| `ConfigureInput()` | 配置渲染器输入 | -| `ConfigureTarget()` | 配置渲染目标 | -| `Execute()` | 执行 Pass 的渲染逻辑 | -| `FrameCleanup()` | 帧清理回调 | -| `OnCameraSetup()` | 相机设置回调 | -| `OnCameraCleanup()` | 相机清理回调 | - ---- - -### 2.5 ScriptableRenderer 类 - -**命名空间**: `UnityEngine.Rendering` - -**描述**: 可编写脚本的渲染器实现基类。 - -**主要方法**: -| 方法 | 描述 | -|------|------| -| `EnqueuePass(ScriptableRenderPass)` | 将渲染 Pass 加入队列 | -| `Execute(ScriptableRenderContext, ref RenderingData)` | 执行渲染 | -| `Initialize()` | 初始化渲染器 | -| `Submit()` | 提交渲染 | -| `ConfigureCameraTarget(RenderTargetIdentifier, RenderTargetIdentifier)` | 配置相机渲染目标 | -| `ConfigureColorStoreAction(RenderTargetStoreAction)` | 配置颜色存储操作 | -| `ConfigureDepthStoreAction(RenderTargetStoreAction)` | 配置深度存储操作 | - -**属性**: -| 属性 | 描述 | -|------|------| -| `supportedRenderingFeatures` | 支持的渲染特性 | -| `rendererFeatures` | 渲染器特性列表 | -| `krenderGraphResourceSet` | 渲染图资源集 | - ---- - -### 2.6 RenderPassEvent 枚举 - -**命名空间**: `UnityEngine.Rendering` - -**描述**: 指定渲染 Pass 在渲染管线中的执行时机。 - -**枚举值**: -| 值 | 描述 | -|------|------| -| `BeforeRendering` | 渲染开始前 | -| `BeforeRenderingShadows` | 阴影渲染前 | -| `BeforeRenderingPrePasses` | 预 Pass (如深度预通道) 前 | -| `BeforeRenderingOpaques` | 不透明物体渲染前 | -| `BeforeRenderingGbuffer` | G-Buffer (延迟渲染) 前 | -| `BeforeRenderingLit` | 光照物体前 | -| `BeforeRenderingTransparents` | 透明物体渲染前 | -| `BeforeRenderingOverlays` | 覆盖层 (如 UI) 前 | -| `BeforeAlphaMask` | Alpha 遮罩前 | -| `AfterRendering` | 渲染结束后 | -| `AfterRenderingShadows` | 阴影渲染后 | -| `AfterRenderingPrePasses` | 预 Pass 后 | -| `AfterRenderingOpaques` | 不透明物体渲染后 | -| `AfterRenderingGbuffer` | G-Buffer 后 | -| `AfterRenderingLit` | 光照物体后 | -| `AfterRenderingTransparents` | 透明物体渲染后 | -| `AfterRenderingOverlays` | 覆盖层后 | -| `AfterAlphaMask` | Alpha 遮罩后 | - ---- - -### 2.7 CullingResults 类 - -**命名空间**: `UnityEngine.Rendering` - -**描述**: 包含剔除操作的结果,用于渲染对象和光源。 - -**主要方法**: -| 方法 | 描述 | -|------|------| -| `FillLightAndReflectionProbeIndices(...)` | 填充光照和反射探针索引 | -| `GetLightIndexMap()` | 获取光照索引映射 | -| `GetReflectionProbeIndexMap()` | 获取反射探针索引映射 | -| `SetLightIndexMap(int[], int[])` | 设置光照索引映射 | - -**属性**: -| 属性 | 描述 | -|------|------| -| `visibleLights` | 可见光源列表 | -| `visibleReflectionProbes` | 可见反射探针列表 | -| `lightAndReflectionProbeIndex` | 光照和反射探针索引数据 | - ---- - -### 2.8 ScriptableCullingParameters 结构体 - -**命名空间**: `UnityEngine.Rendering` - -**描述**: 包含用于剔除操作的参数。 - -**主要成员**: -| 成员 | 描述 | -|------|------| -| `camera` | 剔除相机 | -| `cullingPlaneSet` | 剔除平面集 | -| `cullingMask` | 剔除遮罩 | -| `lightCullingMode` | 光照剔除模式 | -| `reflectionProbeCullingMode` | 反射探针剔除模式 | -| `shadowDistance` | 阴影距离 | -| `shadowNearPlane` | 阴影近平面 | -| `maximumPortalCullingJobs` | 最大门户剔除作业数 | - -**静态方法**: -| 方法 | 描述 | -|------|------| -| `GetCullingParameters(Camera, ScriptableCullingParameters)` | 从相机获取剔除参数 | -| `GetCullingParametersForEditor(Camera, ScriptableCullingParameters)` | 为编辑器获取剔除参数 | - ---- - -## 3. CommandBuffer 相关 API - -### 3.1 CommandBuffer 类 - -**命名空间**: `UnityEngine.Rendering` - -**描述**: 用于存储渲染命令的缓冲区。 - -**常用方法**: -| 方法 | 描述 | -|------|------| -| `Blit()` | 执行纹理拷贝 (blit) 操作 | -| `DrawRenderer()` | 绘制渲染器 | -| `DrawMesh()` | 绘制网格 | -| `DrawMeshInstanced()` | 绘制实例化网格 | -| `SetRenderTarget()` | 设置渲染目标 | -| `SetGlobalTexture()` | 设置全局纹理 | -| `SetGlobalFloat()` | 设置全局浮点数 | -| `SetGlobalVector()` | 设置全局向量 | -| `SetGlobalMatrix()` | 设置全局矩阵 | -| `SetViewProjectionMatrices()` | 设置视图投影矩阵 | -| `ClearRenderTarget()` | 清除渲染目标 | -| `CopyTexture()` | 复制纹理 | -| `ComputeDispatch()` | 计算着色器调度 | - -### 3.2 CommandBufferPool 类 - -**命名空间**: `UnityEngine.Rendering` - -**描述**: CommandBuffer 池化管理器,用于优化内存分配。 - -**静态方法**: -| 方法 | 描述 | -|------|------| -| `Get(string)` | 从池中获取 CommandBuffer | -| `Release(CommandBuffer)` | 释放 CommandBuffer 回池 | -| `Create(string)` | 创建新的 CommandBuffer | -| `ReleaseAll()` | 释放所有池化的 CommandBuffer | - -### 3.3 RenderingData 结构体 - -**命名空间**: `UnityEngine.Rendering` - -**描述**: 包含传递给 ScriptableRenderPass.Execute 的渲染数据。 - -**属性**: -| 属性 | 描述 | -|------|------| -| `cameraData` | 相机数据 | -| `cullingResults` | 剔除结果 | -| `lightData` | 光照数据 | -| `shadowData` | 阴影数据 | - -### 3.4 CameraData 结构体 - -**命名空间**: `UnityEngine.Rendering` - -**描述**: 包含相机相关的渲染数据。 - -**属性**: -| 属性 | 描述 | -|------|------| -| `camera` | Unity Camera 对象 | -| `projectionMatrix` | 投影矩阵 | -| `viewMatrix` | 视图矩阵 | -| `renderer` | 渲染器 | - ---- - -## 4. 渲染相关类 - -### 4.1 ShaderTagId - -**命名空间**: `UnityEngine.Rendering` - -**描述**: Shader 标签标识符,用于标识 Shader 的渲染通道。 - -### 4.2 LightProbe - -**命名空间**: `UnityEngine.Rendering` - -**描述**: 光照探针管理类,用于处理光照探针数据。 - -### 4.3 DrawRendererSettings - -**命名空间**: `UnityEngine.Rendering` - -**描述**: 渲染器绘制设置结构体,用于配置渲染器绘制参数。 - -**主要成员**: -| 成员 | 描述 | -|------|------| -| `sorting` | 排序设置 | -| `flags` | 渲染标志 | -| `rendererConfiguration` | 渲染器配置 | -| `passIndex` | Pass 索引 | - ---- - -## 5. URP (Universal Render Pipeline) 核心 API - -**包**: `com.unity.render-pipelines.universal` - -**命名空间**: `UnityEngine.Rendering.Universal` - -URP 是建立在 SRP Core 之上的渲染管线,提供了更简化的 API。 - -### 5.1 UniversalRenderPipeline 类 - -**命名空间**: `UnityEngine.Rendering.Universal` - -**描述**: URP 的主渲染管线类。 - -**属性**: -| 属性 | 描述 | -|------|------| -| `asset` | 当前 URP 资源 | -| `current` | 当前活动的渲染管线 | - -### 5.2 UniversalRenderPipelineAsset 类 - -**命名空间**: `UnityEngine.Rendering.Universal` - -**描述**: URP 的渲染管线资源资产。 - -### 5.3 ForwardRenderer 类 - -**命名空间**: `UnityEngine.Rendering.Universal` - -**描述**: URP 的前向渲染器实现。 - -### 5.4 RenderingData (URP扩展) - -URP 扩展了基础的 RenderingData,添加了 URP 特有的渲染数据。 - -### 5.5 URP 核心 Pass 类 - -| 类名 | 描述 | -|------|------| -| `ScriptableRenderPass` | URP 中继承自基类的渲染 Pass | -| `ScriptableBlitPass` | Blit 渲染 Pass | -| `ScriptableFullscreenPass` | 全屏渲染 Pass | -| `ColorGradingLutPass` | 颜色分级 LUT Pass | -| `BloomPass` | 泛光 Pass | -| `DepthOfFieldPass` | 景深 Pass | -| `MotionVectorPass` | 运动矢量 Pass | - -### 5.6 RenderPassEvent (URP扩展) - -URP 扩展了标准的 RenderPassEvent,添加了 URP 特有的渲染事件。 - ---- - -## 6. HDRP (High Definition Render Pipeline) 核心 API - -**包**: `com.unity.render-pipelines.high-definition` - -**命名空间**: `UnityEngine.Rendering.HighDefinition` - -HDRP 是建立在 SRP Core 之上的高端渲染管线,提供了高质量的渲染效果。 - -### 6.1 HDRenderPipeline 类 - -**命名空间**: `UnityEngine.Rendering.HighDefinition` - -**描述**: HDRP 的主渲染管线类。 - -### 6.2 HDRenderPipelineAsset 类 - -**命名空间**: `UnityEngine.Rendering.HighDefinition` - -**描述**: HDRP 的渲染管线资源资产。 - -### 6.3 HDRenderer 类 - -**命名空间**: `UnityEngine.Rendering.HighDefinition` - -**描述**: HDRP 的渲染器实现。 - -### 6.4 HDRP 核心组件 - -| 类名 | 描述 | -|------|------| -| `HDRenderPipelineAsset` | HDRP 资源资产 | -| `HDRenderer` | HDRP 渲染器 | -| `HDBaker` | 光照烘焙器 | -| `RayTracingShader` | 光线追踪着色器 | - -### 6.5 HDRP 专用 Pass - -| Pass 类 | 描述 | -|---------|------| -| `DeferredPass` | 延迟渲染 Pass | -| `ForwardEmissivePass` | 前向发光 Pass | -| `TransparentDepthPrePass` | 透明物体深度预 Pass | -| `MotionVectorPass` | 运动矢量 Pass | - ---- - -## 7. SRP Core 核心类 (com.unity.render-pipelines.core) - -### 7.1 核心工具类 - -| 类名 | 描述 | -|------|------| -| `CoreUtils` | SRP 核心工具函数集合 | -| `CoreMatrixUtils` | 矩阵操作工具函数 | -| `Blitter` | Blit (纹理拷贝) 工具 | -| `RTHandle` | 可自动缩放的渲染纹理 | -| `RTHandleSystem` | RTHandle 纹理管理系统 | -| `Volume` | 通用 Volume 组件 | -| `VolumeManager` | Volume 全局管理器 | -| `VolumeProfile` | Volume 设置资源 | -| `VolumeComponent` | Volume 组件基类 | - -### 7.2 调试相关类 - -| 类名 | 描述 | -|------|------| -| `DebugManager` | 调试窗口管理器 | -| `DebugUI` | 调试 UI 类 | -| `DebugUI.Widget` | 调试 UI 组件基类 | -| `ProfilingSampler` | CPU/GPU 性能分析采样器 | -| `ProfilingScope` | 性能分析作用域 | -| `Recorder` | 性能记录器 | -| `Profiler` | 性能分析器 | - -### 7.3 资源管理类 - -| 类名 | 描述 | -|------|------| -| `RenderPipelineGlobalSettings` | 渲染管线全局设置 | -| `RenderPipelineResources` | 渲染管线资源资产基类 | -| `ConstantBuffer` | 常量缓冲区管理 | -| `ShaderVariation` | Shader 变体管理 | -| `MaterialDescription` | 材质描述 | - -### 7.4 渲染 Pass 基类 - -| 类名 | 描述 | -|------|------| -| `ScriptableRenderPass` | 可编写脚本的渲染 Pass 基类 | -| `ScriptableBlitPass` | Blit 操作 Pass 基类 | -| `ScriptableCopyPass` | 拷贝 Pass 基类 | - -### 7.5 渲染器特性 - -| 类名 | 描述 | -|------|------| -| `RenderingFeatures` | 渲染特性集合 | -| `RendererFeatures` | 渲染器特性集合 | -| `RenderPipelineFeature` | 渲染管线特性基类 | - ---- - -## 8. 使用示例 - -### 8.1 创建自定义渲染管线 - -```csharp -using UnityEngine; -using UnityEngine.Rendering; -using System.Collections.Generic; - -public class CustomRenderPipeline : RenderPipeline -{ - protected override void Render(ScriptableRenderContext context, List cameras) - { - foreach (var camera in cameras) - { - // 渲染每个相机 - Render(context, camera); - } - - // 提交渲染命令 - context.Submit(); - } - - private void Render(ScriptableRenderContext context, Camera camera) - { - // 设置相机属性 - context.SetupCameraProperties(camera); - - // 创建命令缓冲区 - CommandBuffer cmd = CommandBufferPool.Get("Clear"); - cmd.ClearRenderTarget(true, true, Color.black); - context.ExecuteCommandBuffer(cmd); - CommandBufferPool.Release(cmd); - - // 提交 - context.Submit(); - } -} -``` - -### 8.2 创建自定义渲染 Pass - -```csharp -using UnityEngine; -using UnityEngine.Rendering; - -public class CustomRenderPass : ScriptableRenderPass -{ - public CustomRenderPass() - { - // 设置 Pass 执行时机 - renderPassEvent = RenderPassEvent.BeforeRenderingOpaques; - } - - public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData) - { - // 相机设置 - } - - public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) - { - // 执行渲染逻辑 - } - - public override void FrameCleanup(CommandBuffer cmd) - { - // 帧清理 - } -} -``` - ---- - -## 9. 官方文档资源 - -- [SRP 官方文档](https://docs.unity3d.com/Manual/ScriptableRenderPipeline.html) -- [URP Scripting API](https://docs.unity3d.com/Packages/com.unity.render-pipelines.universal@latest/api/) -- [HDRP Scripting API](https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@latest/api/) -- [SRP Core Scripting API](https://docs.unity3d.com/Packages/com.unity.render-pipelines.core@latest/api/) - ---- - -*文档版本: 1.1* -*更新日期: 2026-03-16* -*Unity 版本: 6.3 LTS (6000.3)* -*基于官方 Unity API 文档调研整理* diff --git a/docs/plan/Unity绝区零开发文档还原版.md b/docs/plan/Unity绝区零开发文档还原版.md deleted file mode 100644 index 0f1ce005..00000000 --- a/docs/plan/Unity绝区零开发文档还原版.md +++ /dev/null @@ -1,1377 +0,0 @@ -# Unity《绝区零》开发文档还原版 - -更新时间:2026-03-26 -分析对象:`D:\Program Files\ZenlessZoneZero Game` -文档性质:基于安装目录、版本清单、日志、元数据字符串和原生插件信息的“开发细节还原”。 -方法边界:本文不是源码审计,也不是反编译报告;凡是直接由文件、日志、版本信息证明的内容,标为“可确认”;凡是由多个证据合成出来的工程判断,标为“高概率推断”。 - -## 0. 为什么这份文档值得写 - -只看安装目录,很多人最多能得出“这是个 Unity 游戏”。但《绝区零》这个客户端的目录信息量其实非常大,已经足够暴露出: - -1. 它的引擎基底是哪一代 Unity LTS。 -2. 它是不是 IL2CPP、是不是重度原生插件化。 -3. 它的渲染层是不是 SRP 风格、有没有现代 PC 图形能力栈。 -4. 它是不是靠 Addressables/AssetBundle 直出,还是有独立的内容块分发系统。 -5. 它的资源是不是按业务模块分包,尤其是主线、教程、活动、卡池、视频表现。 -6. 它有没有成熟的内部工具链,例如世界图、镜头序列、预加载配置、棋盘玩法插件、体素相关工具等。 -7. 它的平台层是不是已经和 WebView、SDK、ABTest、翻译热更、遥测、Crash、保护驱动深度耦合。 - -换句话说,这个目录足够说明它不是“Unity 默认工作流做出来的大包”,而是一套建立在 Unity 之上的产品化工程栈。 - -## 1. 执行摘要 - -先把最重要的结论放前面: - -### 1.1 可确认结论 - -1. PC 客户端使用 **Unity 2019.4.40 LTS**。 -2. 脚本后端是 **IL2CPP**,不是 Mono。 -3. 客户端重度依赖原生插件,接入了 Wwise、CRI Mana、KCP、WebView/CEF、遥测、Crash、反作弊/保护等多个子系统。 -4. 资源分发不是简单的 Unity Bundle 直发,而是 **`.blk` 块资源 + `.usm` 视频资源 + `.pck` 音频资源 + 独立版本清单** 的多通道体系。 -5. `file_category_launcher` 证明客户端把大量视频媒体资源明确映射到业务分类号,说明启动器/内容系统在“剧情页、教程、活动、卡池、登录页、章节视频”等层面做了分包设计。 -6. 元数据中可直接看到 `NapResources`、`ScriptConfig`、`JsonBytes`、`WorldGraph`、`NapCameraSequence`、`ChessboardPlugin`、`VoxelArray`、`MeshStreamingBuildStates` 等命名,说明工具链深度很高。 - -### 1.2 高概率推断 - -1. Windows 版本的主渲染后端大概率是 **D3D12 优先**,但 Vulkan 代码路径被完整编入并维护。 -2. 项目在 Unity 之上实现了 **自定义 SRP 风格渲染管线**,而不是纯 Built-in Render Pipeline。 -3. 项目内部存在 **ECS 风格运行时框架** 或至少采用 ECS 命名和句柄系统,但未必等于 Unity 官方 DOTS。 -4. `data_version` 很像独立于大资源通道之外的 **小型高频热更通道**。 -5. 当前启动器显式面向用户或内容侧暴露的“分包单元”主要集中在视频媒体层,而大体量内容层走块资源系统。 - -## 2. 调查方法与证据来源 - -本次还原主要依赖以下几个证据面: - -### 2.1 二进制与根目录结构 - -根目录存在的关键文件包括: - -- `ZenlessZoneZero.exe` -- `UnityPlayer.dll` -- `GameAssembly.dll` -- `nvngx_dlss.dll` -- `nvngx_dlssg.dll` -- `sl.dlss.dll` -- `sl.reflex.dll` -- `amd_fidelityfx_upscaler_dx12.dll` -- `amd_fidelityfx_framegeneration_dx12.dll` -- `telemetry.dll` -- `Astrolabe.dll` -- `HoYoKProtect.sys` -- `mhypbase.dll` -- `config.ini` -- `version_info` -- `file_category_launcher` - -只看这一层已经可以初步判断: - -1. 这是标准 Unity Windows 原生壳 + IL2CPP 业务层。 -2. 这是面向现代 PC 图形能力的版本,而不是纯移动移植壳。 -3. 平台层明显带有米哈游的完整发行/SDK/安全/运维组件。 - -### 2.2 StreamingAssets 与 Persistent 目录 - -关键目录: - -- `ZenlessZoneZero_Data\StreamingAssets` -- `ZenlessZoneZero_Data\Persistent` -- `ZenlessZoneZero_Data\Persistent\LogDir` - -这些目录提供了最重要的资源与热更证据。 - -### 2.3 IL2CPP 元数据 - -关键文件: - -- `ZenlessZoneZero_Data\il2cpp_data\Metadata\global-metadata.dat` - -虽然没有源码,但 metadata 中残留了大量: - -- 场景名 -- 资源路径 -- 类型名 -- 配置资产名 -- 内部工具类名 -- 某些运行时系统的符号 - -这是还原内部工程结构的核心来源之一。 - -### 2.4 运行日志 - -关键目录: - -- `ZenlessZoneZero_Data\Persistent\LogDir` - -日志直接暴露了: - -- 场景切换链路 -- SDK 初始化链路 -- WebView 初始化方式 -- ABTest 地址 -- 多语言翻译热更 URL -- 部分渲染和运行时开关 -- 资源系统的异常与配置信息 - -## 3. 引擎与运行时层 - -### 3.1 引擎版本与脚本后端 - -可确认事实: - -- `ZenlessZoneZero.exe` 文件版本:`2019.4.40.14369230` -- `UnityPlayer.dll` 文件版本:`2019.4.40.14369230` -- 存在 `GameAssembly.dll` -- 存在 `ZenlessZoneZero_Data\il2cpp_data\Metadata\global-metadata.dat` - -这说明: - -1. 引擎版本是 **Unity 2019.4.40 LTS**。 -2. 脚本后端是 **IL2CPP**。 -3. 项目不是 HotReload/C# Mono 为主的轻量工程,而是完整 AOT 化的大型正式版客户端。 - -### 3.2 启动配置 - -`boot.config` 中至少可以确认: - -- `wait-for-native-debugger=0` -- `vr-enabled=0` -- `hdr-display-enabled=0` -- `single-instance=` - -这些信息本身不惊人,但结合整个项目体量,它反映的是: - -1. 客户端使用标准 Unity 启动流程,但做了平台化的参数控制。 -2. HDR 至少在当前这份启动配置中没有默认开启。 -3. VR 不是目标运行模式。 - -### 3.3 当前版本与渠道 - -直接来自 `config.ini` 与 `version_info`: - -- `game_version=2.7.0` -- `channel=1` -- `sub_channel=1` -- `cps=mihoyo` -- `version_info = CNPRODWin2.7.0` - -最稳妥的结论是: - -- 当前安装目录对应 **国服 Windows 正式环境 2.7.0**。 - -之前日志痕迹还显示过 2026-03-24 左右从 `CNPRODWin2.6.0` 升级到 `CNPRODWin2.7.0` 的过程,因此这份安装目录并不是“首包静态镜像”,而是经历过真实更新链路的客户端。 - -### 3.4 基础框架命名 - -元数据与日志中反复出现: - -- `MoleMole.*` -- `Foundation.*` -- `MiHoYo.SDK.*` - -这说明 Unity 只是底座,业务层上面还有一层相当厚的内部基础框架。 - -### 3.5 场景结构 - -从 metadata 中可直接提取出的场景: - -- `Assets/Scenes/Logic/FrontendGame.unity` -- `Assets/Scenes/Logic/Launcher.unity` -- `Assets/Scenes/Logic/Real_ECS.unity` -- `Assets/Scenes/Login/Login_MainCityWorkshop.unity` -- `Assets/Scenes/Scene_Dev/DevLevel.unity` - -可推断的工程含义: - -1. `Launcher` 与 `FrontendGame` 分开,说明启动入口和主要业务逻辑不是单场景一把梭。 -2. `Real_ECS` 是非常强的内部命名证据,说明存在面向实体系统或 ECS 风格的逻辑分层。 -3. `DevLevel` 说明正式构建中仍残留研发/验证用场景引用。 -4. `Login_MainCityWorkshop` 暗示登录链路、主城演出和专门的场景工作流紧密相关。 - -### 3.6 是否存在 ECS 风格运行时 - -当前可确认的证据: - -- 场景名包含 `Real_ECS` -- metadata 中出现 `EntityHandle` -- metadata 中出现 `ListEntityHandle` - -高概率判断: - -- 项目内存在一套 **ECS 风格或实体句柄驱动的运行时框架**。 - -需要谨慎的点: - -- 这不能直接等同为 Unity DOTS/Entities Package。 -- 更可能是米哈游内部的“实体句柄 + 数据驱动系统 + 运行时图”式框架。 - -## 4. 渲染与图形后端 - -### 4.1 为什么可以判断不是纯 Built-in RP - -日志和元数据中能看到以下关键证据: - -- `UnityEngine.Rendering.RenderPipelineManager:DoRenderLoop_Internal` -- `disableSRPHelper:0` -- `disableSRPInstancing:0` -- `disableAsyncSRPSubmit:0` -- `ShaderCustomData 无效配置路径 ...` - -如果只是普通 Built-in 渲染项目,这几个特征同时出现的概率很低。它们组合起来,更像: - -1. 项目基于 Unity 的 RenderPipeline 管理链运行。 -2. 团队实现了自己的 SRP 风格辅助层。 -3. Shader 数据和配置路径不是全靠 Unity 默认材质面板,而是有单独配置系统。 - -### 4.2 现代图形特性开关 - -日志中可见: - -- `disableRayTracing:0` -- `disableMeshLet:0` -- `disableAsyncUploadJob:0` -- `disableFlatbufferNativeArrayOpt:0` -- `disableVKPSOJob:0` -- `enablePhyManagerJob:0` - -这些开关说明的不是“这些功能一定默认打开”,而是: - -1. 这些能力在工程层面被明确纳入了可控项。 -2. 团队在渲染/资源/原生数据层做过较多开关治理。 -3. PC 版至少在架构层已经考虑过 Ray Tracing、MeshLet、异步上传、Vulkan PSO 作业化等问题。 - -### 4.3 PC 原生图形能力栈 - -根目录的原生 DLL 明确显示接入了: - -- NVIDIA DLSS -- DLSS Frame Generation -- NVIDIA Reflex -- NIS -- AMD FSR / FidelityFX Upscaler -- AMD Frame Generation -- AMD AGS - -这组 DLL 几乎可以直接说明: - -- 这是一个认真做 PC 高端显示链路适配的版本,而不是随便把移动端资源抬到 PC。 - -### 4.4 Vulkan 证据 - -在 `GameAssembly.dll` 中可检索到大量 Vulkan 相关字符串,包括 `vkCreate*` 系列与 PSO/交换链相关内容。再结合日志中出现的 `disableVKPSOJob`,可得出比较稳的推断: - -- Vulkan 路径被完整编入并维护。 - -更进一步的工程判断: - -- Windows 正式服的大概率主力图形后端仍是 D3D12,Vulkan 更像兼容、实验或特定平台路径,但不是“没写完的残留代码”。 - -### 4.5 对渲染架构的整体推断 - -高概率架构如下: - -1. Unity 2019 LTS 提供平台与底层渲染承载。 -2. 项目在其上实现自定义 SRP 风格框架。 -3. Shader 管线、预热、配置、特效和材质属性存在独立数据层。 -4. PC 版本在原生层桥接 DLSS/FG/Reflex/FSR 等现代能力。 -5. Vulkan 与 D3D12 两条路径至少在代码层被同时维护。 - -## 5. 资源组织、版本清单与热更体系 - -这是这份安装目录里信息量最大的一层,也是最能体现“工程成熟度”的部分。 - -### 5.1 StreamingAssets 顶层结构 - -`ZenlessZoneZero_Data\StreamingAssets` 下可见: - -- `Blocks` -- `Video` -- `Audio` -- `MiHoYoSDKRes` -- `APMConfig.json` -- `res_version` -- `data_version` -- `audio_version` -- `res_revision` -- `data_revision` -- `audio_revision` -- `silence_version` -- `silence_revision` - -这套命名非常像独立于 Unity 默认资源系统之外的发行系统。尤其是 `*_version` 与 `*_revision` 双文件模式,典型用于: - -1. 比较本地与远端版本。 -2. 决定差量下载。 -3. 区分资源通道。 -4. 支持启动器或游戏内更新器。 - -### 5.2 StreamingAssets 实体资源体量 - -从实际目录统计得到: - -| 类别 | 数量 | 体积 | -| --- | --- | --- | -| `Blocks` | 8276 | 45.182 GB | -| `Video` | 1991 | 13.343 GB | -| `Audio` | 94 | 6.705 GB | - -这一层已经能看出几个非常重要的结论: - -1. 真正的大头不是音频,而是 **块资源 + 视频资源**。 -2. 视频资源占比极大,这说明项目对视频表现依赖远高于普通 Unity 游戏。 -3. 音频在安装目录内不是全部内容,完整音频清单还要看 `audio_version`。 - -### 5.3 `res_version`:主资源通道 - -对 `ZenlessZoneZero_Data\StreamingAssets\res_version` 的结构化解析结果: - -- 文件数:`10246` -- 总体积:`58.352 GB` -- 顶层前缀分布: - - `Blocks`:`8255` - - `Video`:`1991` -- 带 `packages` 字段的条目数:`1991` -- 不带 `packages` 字段的条目数:`8255` - -这说明: - -1. 主资源清单只维护两类核心资源:`Blocks` 与 `Video`。 -2. 当前版本中,**所有显式带业务包号的条目正好等于视频条目数**。 -3. 块资源没有在这个清单层直接暴露业务包号,而是以内容块形式存在。 - -这其实非常关键,因为它说明资源系统分成了两层: - -- 一层是“面向分发和业务管理”的视频媒体包。 -- 另一层是“面向运行时加载”的哈希块/内容块系统。 - -### 5.4 `res_version` 的 tag 组合 - -`res_version` 中的 tag 组合分布如下: - -| tag 组合 | 数量 | -| --- | --- | -| `3` | 7731 | -| `3,5` | 1905 | -| `2` | 504 | -| `2,5` | 58 | -| `1,5` | 28 | -| `1` | 18 | -| `` | 2 | - -目前还不能精确破译 `1/2/3/5` 的语义,但可以做比较稳的工程推断: - -1. tag 不是简单的布尔开关,而是资源层级/安装阶段/下载策略的编码。 -2. 视频经常与 `5` 联动,说明 `5` 高概率和“视频媒体分发层”或某类可选分包策略有关。 -3. 大多数主资源是 `3`,说明 `3` 很可能是当前正式主内容层。 -4. 少量 `1` 和 `2` 更像首包/基础包/过渡层,或者历史分层残留。 - -### 5.5 `data_version`:小型数据通道 - -对 `data_version` 的解析结果: - -- 文件数:`21` -- 总体积:`0.173 GB` -- 路径全部位于:`Blocks/Data/*.blk` - -抽样可见条目示例: - -- `Blocks/Data/3749079225.blk` -- `Blocks/Data/632827999.blk` -- `Blocks/Data/2397209110.blk` -- `Blocks/Data/297117219.blk` -- `Blocks/Data/133733816.blk` - -以及 tag 组合里同时出现了 `1`、`2`、`3` 和空值。 - -这层非常像: - -- **独立于大资源通道之外的小体量配置/逻辑/表格数据热更层。** - -这种拆法的工程意义非常大: - -1. 可以高频更新小量关键数据。 -2. 不需要动 58GB 的主资源清单。 -3. 可以缩短更新链路与校验时间。 -4. 对长线运营版本非常友好。 - -### 5.6 `audio_version`:音频完整包与最小安装包分离 - -对 `audio_version` 的解析结果: - -- 文件数:`271` -- 总体积:`17.426 GB` -- 前缀分布: - - `Audio/Windows/Full`:`270` - - `Audio/Windows/Min`:`1` -- tag 组合分布: - - `3,4`:`270` - - `1,4`:`1` - -这几乎可以直接翻译成工程现实: - -1. 音频资源被拆成 **完整音频包** 与 **最小启动音频包**。 -2. `Minimum.pck` 是最小安装音频集。 -3. 大量的正式语音、环境、SFX、流式音频都位于 `Full` 层。 - -从目录结构还能看到 `Full` 下存在语言目录: - -- `Cn` -- `En` -- `Jp` -- `Kr` - -这意味着: - -- 国际化语音不是单独打补丁凑出来的,而是版本体系天然支持的一级资源维度。 - -### 5.7 `silence_version`:存在但当前为空 - -对 `silence_version` 解析结果: - -- 文件数:`0` -- 总体积:`0` - -这说明“silence”这一通道在当前版本中是预留态或暂未启用态。它可能意味着: - -1. 曾经设计过静默下载/静默资源层。 -2. 该版本没有启用。 -3. 或者仅保留了协议/版本结构,但无实际内容。 - -### 5.8 `.blk` 的工程含义 - -虽然目前没有真正解开 `.blk` 内部格式,但从目录结构和清单行为已经能判断: - -1. `.blk` 不是随意命名的一堆松散文件,而是项目主内容承载格式。 -2. 它大概率用于封装游戏运行时真正的大宗资源。 -3. 它和 Unity 默认 AssetBundle 的“直接暴露路径”风格不同,更像内容块或资源块系统。 -4. 这类设计通常对应更好的: - - 断点续传 - - 差量校验 - - 合并更新 - - CDN 分发 - - 运行时缓存与淘汰 - -### 5.9 Persistent 目录揭示客户端下载后的二次落地 - -`ZenlessZoneZero_Data\Persistent` 中可以看到: - -- `Blocks` -- `Audio` -- `Video` -- `Bundles` -- `LogDir` -- `SDKCaches` - -已知统计结果: - -| 类别 | 数量 | 体积 | -| --- | --- | --- | -| `Persistent\Blocks` | 49 | 0.241 GB | -| `Persistent\Audio` | 2 | 0.053 GB | -| `Persistent\Video` | 8 | 0.268 GB | -| `Persistent\Bundles` | 0 | 空 | - -这个现象说明: - -1. 客户端确实支持安装后继续获取和缓存资源。 -2. 缓存形态延续了 `Blocks/Audio/Video` 三通道设计。 -3. `Bundles` 在当前版本里不是核心载体,至少不是主要的实际补丁落地点。 - -### 5.10 为什么说这不是普通 Addressables 项目 - -能看到 `AssetBundle` 相关词不代表它就一定以 Unity Addressables 作为主分发层。相反,从当前目录结构看,更强的证据是: - -1. 主清单完全自定义:`res_version` / `data_version` / `audio_version`。 -2. 主格式是 `.blk` / `.usm` / `.pck`,不是直接暴露的 bundle 目录。 -3. Persistent 落地也延续这一结构。 -4. 业务分类文件单独维护:`file_category_launcher`。 - -更稳妥的判断是: - -- 项目就算内部某处仍可能使用 AssetBundle,也已经被一层更上位的 **自研发行/热更/分包系统** 包住了。 - -## 6. `file_category_launcher` 暴露出的业务分包体系 - -这是本次调查里最有价值的文件之一。它的每一行都把一个具体资源文件映射到一个业务分类号,例如: - -- `Launch/CutScene_StandbyVideo.usm -> 30400` -- `LoginBangumi/Ad_01_mux.usm -> 30500` -- `Battle/TutorialAvatarVideo1011_1.usm -> 31002` -- `Gacha/Gacha_Anbi.usm -> 40100` - -这意味着客户端不是把所有视频混成一个大仓,而是显式把它们挂到业务包上。 - -### 6.1 高频分类号统计 - -当前提取到的高频分类号如下: - -| 分类号 | 数量 | -| --- | --- | -| `20101` | 487 | -| `20103` | 156 | -| `31002` | 131 | -| `31008` | 105 | -| `30600` | 89 | -| `30800` | 66 | -| `31006` | 62 | -| `20207` | 53 | -| `31003` | 47 | -| `31001` | 45 | -| `72001` | 38 | -| `30200` | 36 | -| `31004` | 32 | -| `10115` | 28 | -| `31007` | 26 | -| `30500` | 25 | -| `40100` | 24 | -| `20102` | 23 | -| `10100` | 20 | -| `30700` | 20 | - -高频号本身就能说明: - -1. Hollow / Transfer / Tutorial / Activity / Gacha / DailyChallenge 这些系统在视频资源层都有显式分包。 -2. 客户端大量内容是“带业务标签的媒体资源”,而不是统一的大资源堆。 -3. 教学内容资源规模远超一般游戏,说明项目在“系统教学可视化”上投入很大。 - -### 6.2 明确可识别的分类号 - -#### `30400`:启动与 Logo - -示例: - -- `Video/HD/Launch/CutScene_StandbyVideo.usm` -- `Video/HD/Launch/LogoPerform_CHS.usm` - -这层就是首启或启动页表现资源。 - -#### `30500`:登录电视/广告素材 - -示例: - -- `Video/HD/LoginBangumi/Ad_01_mux.usm` -- `Video/HD/LoginBangumi/TV_TurnOff.usm` - -说明登录页的电视播放内容完全走独立资源包。 - -#### `20101`:Hollow 主体资源 - -示例: - -- `Video/HD/Hollow/Event/General/HollowToBattleBangboo.usm` -- `Video/HD/Hollow/AnimFlow/Black_Switch.usm` -- `Video/HD/Hollow/Avatar/Chat/EventVideoAnbi_Chat.usm` - -它不是单一功能点,而是一个很大的 Hollow 相关表现集合。 - -#### `20102`:Hollow 背景包 - -示例: - -- `Video/HD/Hollow/Background/Boss/ChallengeDeadEndButcherBackground.usm` -- `Video/HD/Hollow/Background/Metro/MetroBackground.usm` - -说明 Hollow 的背景表现还被单独拆层。 - -#### `20103`:Transfer / 切换层 / 假加载 - -示例: - -- `Video/HD/Transfer/General/TransferBattleFakeLoading.usm` -- `Video/HD/Transfer/General/BattleToHollowLoading.usm` -- `Video/HD/Transfer/General/TransferNoiseLoading.usm` - -这体现出项目把“切换体验”和“加载包装”当成独立表现系统处理。 - -#### `30200`:Inter-Knot / 站内帖子视频 - -示例: - -- `Video/HD/InterKnotPost/InterKnotPostVid_1020.usm` -- `Video/HD/InterKnotPost/InterKnotPostVid_11004.usm` - -这说明“站内内容流/帖子视频”不是脚本临时拼接,而是有独立媒体层。 - -#### `30600`:武器或装备展示视频 - -示例: - -- `Video/HD/Weapon/WeaponSpecialVideo_ZhenZhen.usm` -- `Video/HD/Weapon/Weapon_A_1011.usm` - -这说明装备/武器系统有专门的视频包装资源,不是仅靠模型转台或 UI 动画。 - -#### `30700`:日常挑战/训练房 - -示例: - -- `Video/HD/DailyChallenge/DailyChallengePageBg.usm` -- `Video/HD/DailyChallenge/TrainningRoomLayerLoop.usm` - -说明日常挑战体系具备独立的背景/流程媒体资源。 - -#### `31001`:基础战斗教学 - -示例: - -- `TutorialAidAttack_*` -- `TutorialBattleBase.usm` -- `TutorialBeHit.usm` -- `TutorialCounter.usm` -- `TutorialEnemyInformation.usm` - -说明基础战斗教学不靠文字说明,而是媒体资源驱动。 - -#### `31002`:角色专属教学 - -示例: - -- `TutorialAvatarVideo1011_1.usm` -- `TutorialAvatarVideo1031_2.usm` -- `TutorialAvatarVideo1521_3.usm` - -这里数量高达 131,说明很多角色都有自己的动作/机制演示视频。 - -#### `31003`:邦布/特殊玩法教学 - -示例: - -- `Tutorial_BangbooInLevel_1_1.usm` -- `ActivityBattle_StylishMode_Banyue_001.usm` - -#### `31004`:棋盘/空洞/特殊规则教学 - -示例: - -- `ChessboardChapter25_Tutorial_1.usm` -- `ChessboardGame_Tutorial_1.usm` -- `Hollow/TutorialHollowRechallenge.usm` - -#### `31006`:主城/线索/系统教学 - -示例: - -- `TutorialMaincityVideo02.usm` -- `LimboClueSystem_Clue.usm` -- `LimboClueSystem_SpecialArea.usm` - -#### `31007`:额外教学/修复性内容集合 - -示例: - -- `FishMasterColorBlock_1.usm` -- `Hollow/ClassicMovement.usm` -- `TutorialDataFix1.usm` - -#### `31008`:活动机制/特殊战斗教学 - -示例: - -- `Activity/MazingerGoldenBomb_Ability_01.usm` -- `TutorialEtherBarrier_01.usm` -- `Tutorial_ActivityBattle_1.usm` - -这一组说明活动玩法经常伴随独立教学视频包。 - -#### `40100`:角色卡池展示 - -示例: - -- `Video/HD/Gacha/Gacha_Anbi.usm` -- `Video/HD/Gacha/Gacha_Anton.usm` -- `Video/HD/Gacha/Gacha_Ben.usm` - -#### `40300`:卡池通用 OP/通用基底 - -示例: - -- `Video/HD/Gacha/Gacha_Bangboo_Default.usm` -- `Video/HD/Gacha/Gacha_OP_1.usm` -- `Video/HD/Gacha/Gacha_OP_1_Base.usm` - -说明卡池展示分成“角色个体资源”和“通用演出基底”。 - -#### `50300`:街机/小游戏体系 - -示例: - -- `Video/HD/Arcade/ArcadeScreen/ArcadeCompanionIntro_1.usm` -- `Video/HD/Arcade/ArcadeScreen/ArcadeHoundStart.usm` -- `Video/HD/Arcade/ArcadeScreen/ArcadeMach25Intro_1.usm` - -#### `72001`:影院活动 - -示例: - -- `Video/HD/ActivityCinema/CinemaFilm_10_Openning.usm` -- `Video/HD/ActivityCinema/CinemaFilm_11_Playing.usm` - -#### `72104`:机甲活动 - -示例: - -- `ActivityNewMecha_MoveTutorial1.usm` -- `ActivityNewMecha_MoveTutorial8.usm` - -#### `72106`:联机合作活动 - -示例: - -- `Activity/Coop/CoopMatchingBackground_1.usm` -- `Activity/Coop/CoopMatchingBackground_10.usm` - -### 6.3 主线章节媒体包的强证据 - -以下分类号和路径对应关系非常明确: - -| 分类号 | 典型路径 | 还原结论 | -| --- | --- | --- | -| `30101002` | `MainStory/PageVideo/Chapter02/*` | 主线第 2 章页面视频包 | -| `30101004` | `MainStory/PageVideo/Chapter03/*` | 主线第 3 章页面视频包 | -| `30101005` | `MainStory/PageVideo/Chapter04/*` | 主线第 4 章页面视频包 | -| `30101008` | `MainStory/PageVideo/Chapter06/*` | 主线第 6 章页面视频包 | -| `30101009` | `MainStory/PageVideo/Chapter07/*` | 主线第 7 章页面视频包 | - -这说明媒体资源至少在主线页面层已经按章节分包。它的工程价值包括: - -1. 控制首包体积。 -2. 便于预下载未上线章节媒体。 -3. 允许某章节内容独立更新。 -4. 运营与版本管理更清晰。 - -### 6.4 `101xx` 看起来像“流程章节/过场编号包” - -提取到的样本如下: - -- `10100 -> Procedure/C000_*` -- `10105 -> Procedure/C040_*` -- `10106 -> Procedure/C050_*` -- `10110 -> Procedure/C210_*` -- `10111 -> Procedure/C220_*` -- `10112 -> Procedure/C230_*` -- `10113 -> Procedure/C240_*` -- `10114 -> Procedure/C250_*` -- `10115 -> Procedure/C260_*` -- `10117 -> Procedure/C280_*` - -这说明项目的流程过场视频很可能按内部章节/节点编号系统单独打包。 - -### 6.5 这一层最关键的工程判断 - -`file_category_launcher` 最重要的意义不在于“知道几个包号”,而在于它证明了: - -1. 启动器或资源系统不是只认文件哈希,它同时认业务语义。 -2. 客户端内容至少在媒体层已经高度产品化,能按玩法、章节、活动、卡池拆分。 -3. 视频表现资源不是附属物,而是主流程中的一等资源。 - -## 7. 音频、视频与表现系统 - -### 7.1 音频:Wwise 已深入到时间轴/表现层 - -音频侧有几个强证据: - -- `AkSoundEngine.dll` -- `AkMotion.dll` -- `AkWaapiClient.dll` -- metadata 中出现 `MoleMole.Timeline.WwiseAudioTrack` -- 日志中出现 Wwise 生命周期记录 - -这比“项目用了 Wwise”更进一步。它说明: - -1. 音频被集成到了表现/Timeline 系统里。 -2. 团队可能使用 Wwise 管理大量复杂 BGM、事件、流式音频和演出同步。 -3. Unity Timeline 或其自定义扩展在音频上有专门轨道。 - -### 7.2 视频:CRI Mana 不是点缀,而是核心表现依赖 - -视频侧证据: - -- `cri_mana_vpx.dll` -- `cri_ware_unity.dll` -- 资源中存在 1991 个 `.usm` - -从文件类别看,视频被用于: - -1. 启动 Logo -2. 登录页电视/广告 -3. 场景切换和假加载 -4. Hollow 与主城表现 -5. 卡池角色展示 -6. 主线章节页面视频 -7. 街机活动 -8. 联机活动 -9. 角色/机制教学 - -这说明项目的视频依赖不是边角料,而是 UI 包装、流程演出和玩法教学的重要构成部分。 - -### 7.3 表现工具链命名 - -metadata 中可以看到: - -- `NapCameraSequence` -- `NapCameraSequenceHandle` -- `Assets/NapResources/Data/ScriptConfig/Perform/LevelPerformConfig.asset` -- `Assets/NapResources/Data/ScriptConfig/Camera/ConfigFocusCamera.asset` -- `Assets/NapResources/Data/ScriptConfig/AnimatorStateLength/ConfigAnimatorStateLength.asset` -- `Assets/NapResources/Data/ScriptConfig/Comic/BubbleResources` -- `Assets/NapResources/Data/ScriptConfig/Photo/FrameOuter` -- `Assets/NapResources/Data/ScriptConfig/Photo/FramePhoto` - -这说明表现系统至少包含: - -1. 自定义镜头序列。 -2. 关卡表现配置。 -3. 相机聚焦配置。 -4. 动画状态长度或状态时序配置。 -5. 漫画泡泡、摄影框架等特殊 UI/叙事表现资源。 - -## 8. SDK、WebView、ABTest、运维与安全层 - -### 8.1 WebView/CEF 证据非常扎实 - -定位到的相关文件: - -- `ZenlessZoneZero_Data\Plugins\x86_64\zf_cef.dll` -- `ZenlessZoneZero_Data\Plugins\x86_64\ZFGameBrowser.exe` -- `ZenlessZoneZero_Data\Plugins\x86_64\ZFEmbedWeb.dll` -- `ZenlessZoneZero_Data\Plugins\x86_64\ZFProxyWeb.dll` - -其中 `zf_cef.dll` 版本是: - -- `100.0.24+g0783cf8+chromium-100.0.4896.127` - -这说明客户端带的是一套比较完整的桌面嵌入浏览器栈,而不是随手塞个系统 WebView。 - -### 8.2 日志中的 WebView 初始化链 - -最新日志中可以直接看到: - -- `PreLoadSWURL: https://sdk.mihoyo.com/sw.html?...` -- `Init with offScreen: False` -- `InitUI with offScreen: False` -- `mihoyosdk webview created: ...` -- `web view render method: box config use optimization! request ab` -- `read ab test config from combo config` -- `read webview render method scene: MiHoYo.SDK.WebViewRenderMethodScene` -- `Get ABTest: plat - https://data-abtest-api.mihoyo.com/...` - -这几行直接暴露出: - -1. SDK WebView 不是固定策略,而是可走 ABTest 分流。 -2. WebView 的渲染方式可以由服务端配置决定。 -3. WebView 和 Native UI 栈之间存在完整事件交互。 - -### 8.3 多语言翻译热更 - -日志中还能看到: - -- `Request to set language:zh-cn` -- `Update region translation resources:zh-cn` -- `regionTranslationVersionCheck URL:https://webstatic.mihoyo.com/admin/mi18n/...-version.json` - -这说明: - -1. 本地语言资源之外,还有在线翻译资源更新链。 -2. 多语言文本并非完全固化在客户端包内。 -3. 运营层可以对区域文本进行版本化更新。 - -### 8.4 遥测与 Crash 体系 - -日志与文件显示存在: - -- `telemetry.dll` -- `APMCrashReporter` -- `MiHoYoSDKUploader.dll` -- `TelemetryInterface path:...\SDKCaches` -- `InitCrashSDKConfig` - -这基本可以判断: - -1. 客户端具备成熟的异常与性能/行为上报链路。 -2. `SDKCaches` 不只是 SDK 静态文件目录,还是部分运行时缓存和上报链路落地点。 -3. 客户端是围绕长线在线运营构建的,不是离线单机式打包。 - -### 8.5 安全与保护层 - -根目录存在: - -- `HoYoKProtect.sys` -- `mhypbase.dll` -- `Astrolabe.dll` - -这说明正式版客户端带有较明确的保护/反作弊/底层守护组件。对工程层的意义是: - -1. 引擎层与平台层不是纯用户态逻辑。 -2. 正式发行链路包含安全侧部署。 -3. 构建和安装流程考虑过驱动/保护组件的分发。 - -## 9. 内部工具链与数据驱动架构 - -这是最能说明“这不是普通 Unity 项目”的部分。 - -### 9.1 `NapResources`:统一资源命名空间 - -metadata 中反复出现 `Assets/NapResources/...`。这说明: - -1. 团队维护了一套统一的资源命名空间,而不是让资源散落在各个场景和 prefab 里。 -2. 很多系统都围绕 `NapResources` 组织,说明它更像项目级资源根,而不是临时目录。 -3. 命名上 `nap` 与 `nap_cn`、日志中的 `bundle_id=nap_cn` 彼此呼应,说明这不是无关命名。 - -### 9.2 ScriptConfig 根目录样本 - -从 metadata 中提取到的 `ScriptConfig` 根包括: - -- `Activity/ActivityLiveHouse` -- `Activity/ConfigHotPotFoodAssets.asset` -- `Activity/ConfigHotPotForceFields.asset` -- `AnimatorStateLength/ConfigAnimatorStateLength.asset` -- `AttackPropertys/AttackPropertyLibrary.asset` -- `Audio/BasePaths` -- `Camera/ConfigFocusCamera.asset` -- `Camera/Library` -- `Camera/Photo` -- `Camera/PhotoWall` -- `Comic/BubbleResources` -- `Comic/ImgStyle` -- `EtherEyes/ConfigEtherEyes.asset` -- `Level/LevelAddonConfig.asset` -- `LevelNpcGroupData/BladeIllusionNPCDisplayConfig.asset` -- `MaterialPropertyModifier/AutoGen` -- `Perform/LevelPerformConfig.asset` -- `Preload/PreloadLevelConfig.asset` -- `Preload/PreloadUIAssetsConfig.asset` -- `PreloadConfig/PreloadStreamingGameConfig.asset` -- `Preload/ShaderVariantCollection-` -- `ScreenEffects/AutoGen` -- `ShaderCustom/` -- `UI/ConfigUIActivityRhythmClick.asset` -- `UI/ConfigUIUrbanMap.asset` - -这几乎把项目的工程气质说透了: - -1. 活动不是脚本散件,而是有独立配置资产。 -2. 相机、摄影、漫画泡泡、音频路径、材质属性修改、屏幕特效都经过工具化配置。 -3. `AutoGen` 目录表明不少配置是自动生成的,而不是纯手工维护。 -4. `Preload*` 与 `ShaderVariantCollection` 显示预加载和 Shader 变体治理已经进入正式管线。 - -### 9.3 JsonBytes 根目录样本 - -从 metadata 中提取到的 `JsonBytes` 根包括: - -- `ArcadeGame/Cp` -- `AreaData` -- `ChessboardPlugin` -- `HollowEntity` -- `LevelWorld` -- `NewAbility` - -这说明项目存在明确的“编辑器数据 -> 字节化运行时数据”路径。其工程含义: - -1. 运行时不会只依赖 Unity 原生反射式序列化。 -2. 很多系统用的是更轻、更可控的数据载入路径。 -3. Hollow、能力系统、关卡世界、棋盘玩法等核心模块都被放进了这一层。 - -### 9.4 MessagePack 的直接证据 - -metadata 中直接出现: - -- `MessagePack.Resolvers.DynamicUnionResolver` -- `MessagePack.Resolvers.DynamicContractlessObjectResolver` -- `MessagePack.Resolvers.DynamicEnumResolver` -- `MessagePack.Resolvers.DynamicObjectResolver` - -这几乎可以确认: - -- 项目在运行时或工具链的某一层明确使用了 **MessagePack**。 - -这和 `JsonBytes`、`EntityHandle`、`WorldGraph` 放在一起,很像典型的大项目数据链: - -1. 编辑器阶段维护结构化对象。 -2. 构建阶段导出字节化数据。 -3. 运行时以句柄、图结构、表结构快速加载。 - -### 9.5 世界/流程/玩法工具链痕迹 - -metadata 中能够看到的一些极有信息量的名字: - -- `WorldGraph` -- `Assets/NapResources/Data/WorldGraph/Bytes` -- `node_config.bytes` -- `ChessboardPlugin` -- `ChessboardPluginInnerWorldConverter` -- `VoxelArray` -- `Assets/Code/Logic/Data/ScriptObject/Stage/MonoZone3D/Terrain/VoxelArray/UpdateVoxelArrayForOverlay.compute` -- `Assets/Code/Logic/Data/ScriptObject/Stage/MonoZone3D/Terrain/VoxelArray/VoxelArrayForOverlayDebugDisplay.mat` - -这几条证据背后的工程信息非常多: - -1. `WorldGraph` 表明世界/流程/节点关系很可能不是硬编码,而是图配置驱动。 -2. `node_config.bytes` 说明图配置会烘焙成运行时字节数据。 -3. `ChessboardPlugin` 说明棋盘/网格玩法不是零散逻辑,而是插件化模块。 -4. `ChessboardPluginInnerWorldConverter` 暗示棋盘玩法与关卡真实世界之间有转换层。 -5. `VoxelArray` 与相关 compute/material 名称说明至少存在某类基于体素或覆盖层的空间数据处理与可视化手段。 - -### 9.6 预加载、Shader 变体与 Mesh Streaming - -metadata 中还可以直接看到: - -- `PreloadStreamingGameConfig` -- `PreloadUIAssetsConfig` -- `PreloadLevelConfig.asset` -- `ShaderVariantCollection` -- `MeshStreamingBuildStates` -- `Assets/Editor Default Resources/MeshStreamingBuildStates` -- `MeshStreamingRoot` - -这说明项目已经把以下问题工具化处理: - -1. 游戏流式预加载。 -2. UI 资源预加载。 -3. Shader 变体收集和预热。 -4. Mesh Streaming 的构建状态管理。 - -这一点非常重要,因为很多 Unity 项目到后期才会被这些问题反噬,而《绝区零》从安装目录上看已经把它们纳入正式流程。 - -### 9.7 镜头、表现与关卡表现配置 - -直接证据包括: - -- `NapCameraSequence` -- `NapCameraSequenceHandle` -- `LevelPerformConfig.asset` -- `ConfigFocusCamera.asset` - -这说明: - -1. 镜头系统不是零碎 Timeline 片段堆出来的,而是存在独立序列抽象。 -2. 关卡表现可能通过配置驱动,而不是仅靠场景对象硬连。 -3. 镜头和关卡表现有机会在构建阶段被烘焙、索引和统一管理。 - -### 9.8 `Redmoon` 痕迹 - -metadata 中能看到: - -- `Redmoon_Android_Source` -- `Redmoon_IOS_Source` -- `Redmoon_Android_NetRes` -- `Redmoon_IOS_NetRes` -- `Redmoon_Android_Preload` -- `Redmoon_IOS_Preload` -- `__RedmoonCoroutines__` -- `__RedmoonGlobals__` - -当前不能精确确认 `Redmoon` 是什么,但比较稳的判断是: - -1. 它与跨平台资源构建、预加载或网络资源体系相关。 -2. 它不是 PC 独占的临时名,而是面向 Android/iOS 的通用构建概念。 -3. 这再次说明整个项目是多平台共享大部分工具链的工程,而不是 PC 版临时分支。 - -## 10. 从这些痕迹反推研发组织方式 - -虽然目录不会直接写“团队怎么分工”,但它会留下很明显的工程组织痕迹。 - -### 10.1 图形/引擎侧 - -能推断至少有人持续负责: - -- 渲染管线维护 -- SRP 相关系统 -- 原生图形能力接入 -- D3D12/Vulkan 路径维护 -- PSO/Shader/Mesh Streaming/预加载等性能议题 - -### 10.2 资源与构建侧 - -能推断至少有人持续负责: - -- 块资源打包与校验 -- 视频资源与业务分包映射 -- 音频语言包与最小包策略 -- 版本清单生成 -- Revision/version 体系 -- Persistent 落地与更新器行为 - -### 10.3 工具链侧 - -能推断至少有人持续负责: - -- ScriptConfig 资产规范 -- JsonBytes/MessagePack 导出 -- 世界图/棋盘插件/能力系统配置导出 -- 预加载配置和 Mesh Streaming 构建状态 -- 相机与表现系统工具 - -### 10.4 平台与运营侧 - -能推断至少有人持续负责: - -- SDK 接入 -- WebView 与 Native UI 栈 -- ABTest 平台对接 -- 文本翻译热更 -- Telemetry/Crash 链路 -- 安全/保护组件集成 - -换句话说,这个客户端从目录侧看,是一支分工比较清晰的大型团队产物,而不是少量程序维护的 Unity 项目。 - -## 11. 结合日志还原一次运行链路 - -最新日志片段可以串出一条很像正式版真实运行流程的链: - -1. `Startup RegisterInputSystem.` -2. `HTTP interceptors initialized` -3. `CreateMainCityClientServerManager.OnCreate` -4. 语言与地域翻译资源初始化。 -5. `LoadSceneGame` -6. `loaded addtiveScene` -7. `Load scene finish` -8. `OnAllLoadingFadeOut` -9. `EnterScene` -10. `SceneSwitch ... 完成切换` -11. `SDKInit` -12. `Set Game Version: CNPRODWin2.7.0` -13. `InitSDK Success` -14. `PreLoadSWURL: https://sdk.mihoyo.com/sw.html?...` -15. `mihoyosdk webview created` -16. 读取 WebView ABTest 配置。 - -这说明客户端启动顺序大概是: - -1. 引擎和输入系统起起来。 -2. 网络拦截器与客户端管理器先初始化。 -3. 场景切换到主流程。 -4. 之后再完成 SDK 与 WebView 链路。 - -这里特别值得注意的是: - -- `CreateMainCityClientServerManager.OnCreate` - -这条日志说明“主城客户端-服务端管理器”这种概念在架构上是显式存在的。换句话说,主城逻辑并不是简单本地场景,而是带有明确联机/协议/状态管理边界的系统。 - -## 12. 目前最值得相信的总体开发画像 - -综合全部证据,我对《绝区零》开发形态的还原如下: - -### 12.1 引擎画像 - -- Unity 2019 LTS 作为底座。 -- IL2CPP 作为正式版运行时。 -- 上层存在重度自研基础框架。 - -### 12.2 渲染画像 - -- 自定义 SRP 风格渲染框架。 -- PC 原生高端特性接入完整。 -- D3D12 很可能是主后端,Vulkan 被完整保留。 - -### 12.3 资源画像 - -- 主资源走块化分发。 -- 视频是业务分包核心资源。 -- 音频有最小包和完整包。 -- 存在小体量独立数据热更通道。 - -### 12.4 工具链画像 - -- 强配置驱动。 -- 强字节化数据导出。 -- 世界图、棋盘玩法、镜头序列、体素/覆盖层、预加载、Mesh Streaming 等都有工具化痕迹。 - -### 12.5 运营画像 - -- SDK、WebView、ABTest、翻译热更、遥测、Crash、保护都深度集成。 -- 明显是长线在线运营产品,而不是一次性交付的客户端。 - -## 13. 还不能下死结论的部分 - -下面这些点我认为目前还不能写成铁事实: - -1. `Real_ECS` 是否对应完整 ECS Runtime,还是内部一套混合架构场景名。 -2. `.blk` 内部是否封装 Unity AssetBundle、定制归档、压缩块索引,还是多种资源混合容器。 -3. `res_version`/`audio_version` 中 tag `1/2/3/4/5` 的精确定义。 -4. `Redmoon` 的确切职责边界。 -5. Vulkan 在 Windows 正式服中的启用策略。 - -## 14. 下一步继续深挖应该怎么做 - -如果继续往下做,我建议按以下顺序: - -### 14.1 最高优先级:解 `.blk` - -这是整个资源系统最关键的黑箱。只要把 `.blk` 文件头、索引、压缩方式、内容类型摸清,就能把: - -- 资源粒度 -- 依赖组织 -- 是否有 bundle 内嵌 -- 更新策略 -- 缓存策略 - -大幅说透。 - -### 14.2 第二优先级:把分类号表彻底补完 - -当前已经识别出大量分类号,但还可以继续做成完整索引,尤其是: - -- 主线章节包全表 -- 各活动包全表 -- 各系统教学包全表 -- 各卡池通用与角色专属媒体表 - -### 14.3 第三优先级:继续抽日志和 metadata - -重点继续找: - -- SRP 初始化链 -- ShaderCustomData 的实际配置路径 -- Vulkan/D3D12 选择逻辑 -- KCP/网络管理器更多细节 -- Hollow / WorldGraph / LevelWorld / NewAbility 的更多类型名 - -### 14.4 第四优先级:针对 Persistent 做版本前后对比 - -如果能抓到一次完整更新前后 Persistent 的变化,就能更准确地反推: - -- 哪些资源是首包内置 -- 哪些资源是首次进入某系统才下载 -- 哪些资源是活动时再拉取 -- 哪些资源会被清理或覆盖 - -## 15. 最终结论 - -如果只从安装目录出发,我对《绝区零》开发形态的最终判断是: - -- 它是一个建立在 Unity 2019 LTS 之上的、重度工程化的大型在线游戏客户端。 -- Unity 在这里主要提供平台底座和基础生产力,而不是全部开发方式。 -- 真正决定它产品形态的,是叠在 Unity 上面的那一层: - - 自定义渲染与图形接入 - - 自定义资源分发与版本系统 - - 强配置、强字节化的数据管线 - - 丰富的视频表现资源体系 - - SDK/WebView/ABTest/遥测/安全等长线运营基础设施 - -如果让我用一句话概括这次调查结果: - -**《绝区零》不是“用 Unity 做出来的一个游戏”,而是“用 Unity 作为底座承载的一整套产品化引擎栈”。** - -## 16. 附录:本轮调查中最关键的直接证据索引 - -为方便后续继续深挖,先把最关键的直接证据列在这里: - -### 16.1 版本与引擎 - -- `ZenlessZoneZero.exe -> 2019.4.40.14369230` -- `UnityPlayer.dll -> 2019.4.40.14369230` -- `config.ini -> game_version=2.7.0` -- `version_info -> CNPRODWin2.7.0` - -### 16.2 WebView/中间件 - -- `zf_cef.dll -> 100.0.24+g0783cf8+chromium-100.0.4896.127` -- `ZFGameBrowser.exe` -- `ZFEmbedWeb.dll` -- `ZFProxyWeb.dll` -- `AkSoundEngine.dll` -- `cri_ware_unity.dll` -- `kcp.dll` - -### 16.3 场景与工具链 - -- `Assets/Scenes/Logic/FrontendGame.unity` -- `Assets/Scenes/Logic/Launcher.unity` -- `Assets/Scenes/Logic/Real_ECS.unity` -- `Assets/Scenes/Login/Login_MainCityWorkshop.unity` -- `Assets/Scenes/Scene_Dev/DevLevel.unity` -- `Assets/NapResources/Data/ScriptConfig/...` -- `Assets/NapResources/Data/JsonBytes/...` -- `WorldGraph` -- `NapCameraSequence` -- `ChessboardPlugin` -- `VoxelArray` -- `MeshStreamingBuildStates` -- `PreloadStreamingGameConfig` -- `PreloadUIAssetsConfig` - -### 16.4 资源与版本系统 - -- `ZenlessZoneZero_Data\StreamingAssets\res_version` -- `ZenlessZoneZero_Data\StreamingAssets\data_version` -- `ZenlessZoneZero_Data\StreamingAssets\audio_version` -- `file_category_launcher` -- `ZenlessZoneZero_Data\Persistent\Blocks` -- `ZenlessZoneZero_Data\Persistent\Audio` -- `ZenlessZoneZero_Data\Persistent\Video` - -### 16.5 关键日志线索 - -- `CreateMainCityClientServerManager.OnCreate` -- `LoadSceneGame` -- `SceneSwitch 完成切换` -- `SDKInit` -- `InitSDK Success` -- `PreLoadSWURL: https://sdk.mihoyo.com/sw.html?...` -- `Get ABTest: plat - https://data-abtest-api.mihoyo.com/...` -- `regionTranslationVersionCheck URL:https://webstatic.mihoyo.com/admin/mi18n/...` - -## 17. 继续深挖:`.blk` 文件头的新增发现 - -这是在文档主体写完之后继续向下钻出来的新证据,价值很高。 - -### 17.1 `.blk` 不是裸 `UnityFS` - -我直接读取了两个 `.blk` 的前 96 个字节: - -- `ZenlessZoneZero_Data\StreamingAssets\Blocks\1000337454.blk` -- `ZenlessZoneZero_Data\StreamingAssets\Blocks\Data\3749079225.blk` - -两者的共同特征都是: - -- 文件头前 4 字节为 `6D 68 79 31` -- 对应 ASCII 即:`mhy1` - -这说明: - -- `.blk` 至少在容器头层不是裸露的 Unity 资源格式,而是 **米哈游自定义的 `mhy1` 容器头**。 - -这条证据非常关键,因为它把判断从“像自定义块格式”推进到了“文件头层面已经确认是自定义容器”。 - -### 17.2 主内容块里能看到 Unity 资源痕迹,但被包在 `mhy1` 容器里 - -对主内容块快速做可打印字符串扫描时,可以看到: - -- `archive:/CAB-85ea42baeef21686dd408a87d00e9877.res` -- `archive:/CAB-b6525d3ab1c368cdf59db6b227027457.res` -- `archive:/CAB-48c29ccf0721f09a645bfcc0401e0446.res` -- `1437729915099733045.bund` - -这里的 `CAB-...` 形式非常像 Unity 资源系统内部常见的 archive / CAB 命名痕迹。这带来一个更精细的判断: - -1. `.blk` 外层不是 UnityFS。 -2. 但 `.blk` 内部很可能封装了 Unity 资源对象、Unity archive,或者与 Unity bundle 体系相邻的资源块。 -3. 换句话说,**`.blk` 更像“米哈游自定义外层容器 + Unity 资源载荷”**,而不是完全脱离 Unity 资源生态的全新格式。 - -### 17.3 `data_version` 对应的数据块表现不同 - -对 `Blocks\Data\3749079225.blk` 做同类扫描时: - -- 同样有 `mhy1` 文件头。 -- 但没有快速扫到明显的 `UnityFS` 或 `CAB-` 字样。 - -这意味着两种可能: - -1. `Blocks/Data` 通道里的负载和主内容块不同,可能更偏配置/表数据/序列化数据。 -2. `Blocks/Data` 使用了不同的压缩、加密或布局方式,导致字符串特征更少。 - -这和前面基于 `data_version` 得出的判断是相互印证的: - -- `Blocks/Data` 很可能确实不是普通美术资源大块,而是更偏轻量逻辑数据通道。 - -### 17.4 现阶段关于 `.blk` 最稳妥的结论 - -截至目前,我认为最稳妥的描述是: - -- `.blk` 是 **带 `mhy1` 文件头的自定义内容块容器**。 -- 主内容块内部至少包含与 Unity archive / CAB 命名体系相关的资源载荷。 -- `Blocks` 与 `Blocks/Data` 很可能共用同一外层容器协议,但内部负载类型不完全相同。 - -这比“可能是自定义块格式”更进一步,已经接近可以围绕文件头和偏移去继续逆向解析了。 diff --git a/docs/used/3DGS-D3D12最小可行系统计划_2026-04-12.md b/docs/used/3DGS-D3D12最小可行系统计划_2026-04-12.md deleted file mode 100644 index 178cc41e..00000000 --- a/docs/used/3DGS-D3D12最小可行系统计划_2026-04-12.md +++ /dev/null @@ -1,245 +0,0 @@ -# 3DGS-D3D12 最小可行系统计划 - -日期:2026-04-12 - -## 1. 文档定位 - -这份计划用于指导 `D:\Xuanchi\Main\XCEngine\mvs\3DGS-D3D12` 的最小可行系统落地。 - -当前任务边界已经明确: - -1. 只允许在 `mvs/3DGS-D3D12` 目录内开发与新增文件。 -2. `engine` 代码只能引用,禁止修改。 -3. 图形抽象层只能使用现有 `engine` 提供的 `RHI` 接口。 -4. 本轮目标是先把 3DGS 的最小可行系统跑通,而不是把它正式并入引擎主线。 -5. 本轮明确不引入 `chunk` 机制。 - -因此,这份计划不是“继续修补引擎内已有的 GaussianSplat 路径”,而是“在 MVS 目录内重新搭一条干净的、可验证的、无 chunk 的 3DGS-D3D12 最小链路”。 - -## 2. 参考实现与职责拆分 - -本轮只参考两条现有实现,各自承担不同职责: - -### 2.1 `mvs/3DGS Unity Renderer` - -用途: - -1. 参考 `.ply` 的读取方式。 -2. 参考如何从 PLY 属性中提取 3DGS 所需原始数据。 -3. 参考资源准备阶段的数据布局与字段含义。 - -注意: - -1. 这里只借鉴导入与数据准备思路。 -2. 不照搬 Unity Editor 资产工作流。 -3. 不引入 chunk。 - -### 2.2 `mvs/3DGS-Unity` - -用途: - -1. 参考“干净的无 chunk 渲染路径”。 -2. 参考 `prepare -> sort -> draw -> composite` 的最小闭环。 -3. 参考 3DGS 在屏幕空间椭圆展开、混合与合成时的核心着色器语义。 - -注意: - -1. 这里重点参考渲染过程,而不是 Unity 的宿主框架。 -2. 不把 Unity 的 `ScriptableRenderPass`、资产导入器、Inspector 等编辑器逻辑带入本轮实现。 - -## 3. 首轮目标 - -首轮只完成以下闭环: - -1. 在 `mvs/3DGS-D3D12` 内部加载 `room.ply`。 -2. 将 PLY 中的高斯数据转换为无 chunk 的运行时缓冲。 -3. 通过现有 `RHI` 完成 D3D12 路径下的最小渲染。 -4. 输出一张稳定可观察的结果图,证明房间场景已经被正确绘制。 - -首轮验收标准: - -1. 程序能独立编译与运行。 -2. 不依赖修改 `engine` 才能成立。 -3. 渲染结果不再是纯黑、纯白或明显错误的撕裂图。 -4. 渲染链路中不存在任何 chunk 数据结构、chunk 缓冲或 chunk 可见性阶段。 - -## 4. 非目标 - -本轮明确不做: - -1. 不并入 editor。 -2. 不接入引擎现有 Renderer 主线。 -3. 不做 OpenGL / Vulkan 多后端对齐。 -4. 不做正式资源缓存格式。 -5. 不做 chunk、cluster、LOD、streaming 等优化层。 -6. 不做 compute 之外的额外架构扩展。 -7. 不为迎合当前 MVS 去修改 `engine` 的 `RHI`、Renderer、Resources。 - -## 5. 约束与执行原则 - -### 5.1 目录约束 - -本轮新增内容应尽量收敛在 `mvs/3DGS-D3D12` 内,例如: - -1. `src/`:程序入口、渲染器、相机、数据上传。 -2. `include/`:本地头文件。 -3. `shaders/`:本地 HLSL 或中间 shader 资源。 -4. `assets/`:本地测试资源或生成物描述。 -5. `third_party/`:仅当 MVS 自己确实需要额外小型依赖时使用。 - -### 5.2 RHI 使用原则 - -1. 只调用现有 `engine` 的 `RHI` 公共接口。 -2. 如果发现最小系统缺失某项能力,优先在 MVS 内通过更简单的组织方式规避。 -3. 若确实被 `RHI` 能力边界阻塞,先记录问题并汇报,不允许直接改 `engine`。 - -### 5.3 数据路径原则 - -1. 整个系统只保留 positions、other、color、SH 等无 chunk 基础数据。 -2. 不生成 chunk header。 -3. 不上传 chunk buffer。 -4. 不做 visible chunk 标记。 -5. 不保留任何为了兼容旧 chunk 方案而加的临时分支。 - -## 6. 技术路线 - -### Phase 1:梳理 3DGS-D3D12 的骨架与构建入口 - -目标: - -1. 确认 `mvs/3DGS-D3D12` 当前是否只有 `room.ply`。 -2. 建立最小的可执行工程骨架。 -3. 打通对 `engine` 中 `RHI` 的引用与链接。 - -任务: - -1. 规划 `CMakeLists.txt` 与目录结构。 -2. 建立窗口、设备、交换链、命令提交、离屏或在屏渲染的最小入口。 -3. 跑通一个“清屏可显示”的基础版本。 - -验收: - -1. `3DGS-D3D12` 可独立编译。 -2. 程序能启动并输出基础画面。 - -### Phase 2:实现无 chunk 的 PLY 读取与运行时数据打包 - -目标: - -1. 参考 `mvs/3DGS Unity Renderer`,在 MVS 内部完成 PLY 读取。 -2. 输出适合渲染阶段直接上传的高斯原始数组。 - -任务: - -1. 解析 PLY header 与顶点属性。 -2. 提取 position、rotation、scale、opacity、color/SH 等字段。 -3. 明确字段的排列顺序、类型与归一化方式。 -4. 生成 MVS 本地 `GaussianSplatSceneData`。 - -验收: - -1. `room.ply` 可被成功读取。 -2. 点数量、字段长度、边界盒等基础统计合理。 -3. 整条导入链中完全没有 chunk 概念。 - -### Phase 3:对齐无 chunk 的 GPU 数据布局与上传 - -目标: - -1. 把导入结果上传到 GPU。 -2. 数据布局尽量贴近 `mvs/3DGS-Unity` 的渲染输入。 - -任务: - -1. 设计 positions / other / color / SH 的 GPU buffer。 -2. 建立与着色器绑定一致的 SRV/UAV 视图。 -3. 为排序与绘制准备 index、distance、view-data 等工作缓冲。 - -验收: - -1. GPU 侧所有关键缓冲都能正确创建。 -2. 每个绑定槽位与 shader 语义一一对应。 -3. 不包含 chunk buffer / visible chunk buffer。 - -### Phase 4:先打通 prepare 与 sort - -目标: - -1. 先验证“高斯数据被相机看到并被正确排序”。 -2. 在 draw 之前把中间结果可视化或可检查化。 - -任务: - -1. 参考 `mvs/3DGS-Unity` 的 prepare 语义,计算每个 splat 的 view-space 信息。 -2. 生成排序距离。 -3. 跑通最小排序路径。 -4. 必要时增加调试输出,用于检查 order / distance / 计数是否异常。 - -验收: - -1. prepare 结果不是全零或明显错误。 -2. sort 输出索引顺序稳定。 - -### Phase 5:对齐 draw 与 composite - -目标: - -1. 参考 `mvs/3DGS-Unity` 的“干净无 chunk 渲染路径”把核心画面先画出来。 - -任务: - -1. 对齐 Gaussian splat draw shader 的主要输入输出语义。 -2. 对齐椭圆展开、覆盖范围与透明混合约定。 -3. 建立 accumulation target。 -4. 建立 composite pass,把 accumulation 结果合回最终颜色。 - -验收: - -1. 输出图中能看出 `room.ply` 对应的房间结构。 -2. 不出现整屏纯黑、纯白、随机撕裂。 - -### Phase 6:收口与验证 - -目标: - -1. 固化最小系统结果,形成稳定基线。 - -任务: - -1. 规范运行命令与资源路径。 -2. 输出一张固定命名的结果图作为检查基线。 -3. 清理调试残留与临时分支。 -4. 补一份 MVS 内局部说明文档。 - -验收: - -1. 代码、资源、着色器都收敛在 `mvs/3DGS-D3D12`。 -2. 运行方式清晰。 -3. 基线截图稳定。 - -## 7. 关键风险 - -### 7.1 PLY 属性语义与当前样例不一致 - -如果 `room.ply` 的字段命名、顺序或编码方式与参考加载器假设不一致,最先要修的是导入器映射,不是渲染器。 - -### 7.2 RHI 能力与参考实现存在接口差 - -如果 Unity 参考依赖的某些资源绑定或 compute 流程不能直接一比一照搬,本轮优先在 `mvs/3DGS-D3D12` 内部重排执行方式,而不是去动 `engine`。 - -### 7.3 排序与混合契约不一致 - -3DGS 最容易出错的不是“有没有画出来”,而是排序方向、alpha 累积与 composite 契约是否一致。本轮必须把这三者当成同一个问题处理,禁止分开打补丁。 - -## 8. 本轮完成后的下一步 - -当 `mvs/3DGS-D3D12` 的无 chunk 最小系统跑通后,下一步才有意义讨论: - -1. 是否把这条路径收编进引擎正式 Renderer。 -2. 是否把 PLY 导入升级成正式资源导入器。 -3. 是否在引擎层补 structured buffer / compute / renderer pass 抽象。 -4. 是否接入 editor 与资产缓存。 - -在这之前,所有工作都应服务于一个目标: - -先在 `mvs/3DGS-D3D12` 内证明“无 chunk 的 3DGS-D3D12 + 现有 RHI”这条路是通的。 diff --git a/docs/used/3DGS-D3D12针状高斯姿态错乱问题定位与修复计划_过期归档_2026-04-14.md b/docs/used/3DGS-D3D12针状高斯姿态错乱问题定位与修复计划_过期归档_2026-04-14.md deleted file mode 100644 index 7d021068..00000000 --- a/docs/used/3DGS-D3D12针状高斯姿态错乱问题定位与修复计划_过期归档_2026-04-14.md +++ /dev/null @@ -1,294 +0,0 @@ -# 3DGS-D3D12 针状高斯姿态错乱问题定位与修复计划(过期归档) - -日期:2026-04-14 - -## 1. 文档定位 - -这份文档归档一次已经完成的 3DGS-D3D12 最小可行渲染器问题定位与修复过程。 - -本次归档的目标不是再提出一个待执行的新方案,而是把这次问题从现象、排查、排除、锁定根因到最终修复验证的全过程完整落盘,作为后续 3DGS 数学链路的经验记录。 - -本次结论以当前工程代码链路为依据,不以外部实现作为正确性前提。 - ---- - -## 2. 问题定义 - -问题发生在 `MVS/3DGS-D3D12` 这个最小可行系统内。 - -当时的状态是: - -1. 图像已经可以正常出图 -2. 基本算法流程可以跑通 -3. 唯一明显异常是很多高斯球会显示成针状杂乱 -4. 相机旋转时,这些针状结构会跟着相机一起改变方向 -5. 只有少数特定角度看起来正常 -6. 即使只渲染单张图、只用一个机位,这个问题依然存在 -7. 远处高斯同样会出现针状,不是只发生在近处 - -用户随后进一步指出了一个更关键的观察: - -1. 问题不只是“长轴爆掉” -2. 很多异常高斯的旋转姿态本身就不对 -3. 某些区域会出现一整片方向相近的针状高斯 - -这个观察直接改变了问题的判断方向。 - ---- - -## 3. 现象对根因的约束 - -本次定位里,最有价值的不是先改代码,而是先把现象翻译成数学约束。 - -### 3.1 单张静态图也错误 - -这说明问题不是: - -1. 多帧状态累积 -2. 相机移动后排序缓冲没更新 -3. 历史帧残留 - -### 3.2 远处也出现针状 - -这说明问题不能简单归结为: - -1. 只有近裁剪面附近的 splat 发生数值爆炸 -2. 只有 `1 / z` 过大导致的局部异常 - -### 3.3 针状方向会跟着相机旋转 - -这说明错误和视图变换强相关,问题更像出在: - -1. 3D 高斯协方差到 2D 屏幕协方差的投影链路 -2. 高斯主轴方向本身的构造 -3. 视图空间相关的矩阵约定不一致 - -### 3.4 某些区域出现方向统一的异常针状 - -这说明错误不是随机脏数据,更像是: - -1. 一整类高斯被同一种错误的姿态基底解释 -2. 某个矩阵或旋转约定系统性反了 - -基于这四点,问题范围被快速收敛到“高斯姿态和协方差张量的构造链路”,而不是排序、混合、资源上传或多帧状态。 - ---- - -## 4. 实际排查链路 - -真正控制 splat 屏幕方向的关键代码都集中在 `MVS/3DGS-D3D12/shaders/PrepareGaussiansCS.hlsl` 中: - -1. `DecodeRotation` -2. `CalcMatrixFromRotationScale` -3. `CalcCovariance3D` -4. `CalcCovariance2D` -5. `DecomposeCovariance` - -对应的数据流是: - -`rotation/scale -> rotationScaleMatrix -> 3D covariance -> 2D covariance -> 屏幕主轴分解` - -既然用户已经明确指出“旋转姿态对不上”,那就必须沿着这条链一路往前追,直到找到哪个环节把高斯主轴解释错了。 - ---- - -## 5. 被排除的假设 - -这次不是一次命中,而是靠现象和代码链路逐步排除出来的。 - -### 5.1 排序或单帧状态异常 - -这个假设最早就被用户提供的现象推翻了。 - -原因: - -1. 程序本身只出一张图 -2. 单个固定机位也会出错 -3. 所以“相机旋转后顺序缓冲没更新”这一类问题不成立 - -结论: - -排序即使有其他问题,也不是这次针状姿态错乱的主因。 - -### 5.2 只有近裁剪面附近高斯长轴爆炸 - -这个假设也不成立。 - -原因: - -1. 用户明确指出远处也出现针状 -2. 静态单帧也出现 -3. 且异常更像“方向错”而不是纯粹“长度变得极大” - -结论: - -近裁剪面或 `z` 相关放大可能是次级数值风险,但不是根因。 - -### 5.3 PLY 四元数字段顺序或量化打包出错 - -这条链路后续也被排除了。 - -检查点包括: - -1. `room.ply` 头部字段为 `rot_0 / rot_1 / rot_2 / rot_3` -2. 原始样本值显示 `rot_0` 作为 `w` 的可能性最高 -3. CPU 侧 swizzle、smallest-3 pack、`R10G10B10A2` 编码,再到 shader 侧解码的往返误差很小 - -结论: - -四元数数据本身没有被严重破坏,pack/unpack 不是这次主因。 - -### 5.4 简单的 FOV 或 splat scale 参数问题 - -中途确实检查过: - -1. `tanFovY` 的计算 -2. 额外的 splat scale 平方放大 - -但这些尝试不能解释“姿态整片统一地错、并随相机一起转”的现象,也没有构成最终有效修复。 - -结论: - -它们至多是数值调节项,不是这次真正的根因。 - ---- - -## 6. 真正锁定根因的关键证据 - -最后真正把问题锁死的是矩阵约定的一致性检查。 - -### 6.1 渲染器其他地方明确使用 row-vector 风格 - -在 `PrepareGaussiansCS.hlsl` 里,位置变换写法是: - -1. `mul(float4(worldPosition, 1.0), gView)` -2. `mul(float4(position, 1.0), gViewProjection)` - -这说明当前这套 shader 的向量乘法约定是“向量在左,矩阵在右”的 row-vector 风格。 - -### 6.2 高斯旋转缩放矩阵的构造却把旋转基底按另一套方向用了 - -原始实现里: - -1. 四元数先展开成 `rotationMatrix` -2. 再直接返回 `mul(rotationMatrix, scaleMatrix)` - -问题在于: - -1. 这一步构出来的旋转基底方向,与当前 shader 其他地方的矩阵使用约定不一致 -2. 结果不是简单数值变大,而是高斯 3D 主轴方向被系统性地解释错了 - -### 6.3 为什么它会表现成“针状”而不只是“转错一点” - -因为后面还有两步会放大这个错误: - -1. `CalcCovariance3D` 用 `sigma = M * M^T` 构建 3D 协方差 -2. `CalcCovariance2D` 再把这个协方差投影到屏幕平面,并做特征轴分解 - -如果 `M` 的旋转基底方向就错了,那么: - -1. 协方差仍然可能是对称正定的 -2. 长短轴长度看上去未必每次都离谱 -3. 但主轴方向会在投影后明显错位 -4. 这个错位在某些视角下会被放大成非常细长、方向统一的针状结构 - -这也解释了为什么: - -1. 某些角度看起来“碰巧正常” -2. 一换相机角度,异常方向又整体变了 - -本质上不是“有些高斯坏了”,而是“所有高斯的姿态张量都在用错误约定解释,只是不同视角下表现强弱不同”。 - ---- - -## 7. 最终根因 - -本次问题的最终根因是: - -`PrepareGaussiansCS.hlsl` 中用于构建 3D 高斯协方差基底的旋转矩阵使用了错误的矩阵约定,导致高斯主轴方向在进入协方差投影前就已经错了。 - -更直接地说: - -1. 不是排序问题 -2. 不是单帧/多帧状态问题 -3. 不是单纯近裁剪面长轴爆炸 -4. 不是 PLY 四元数字段顺序错误 -5. 不是 pack/unpack 主链错误 -6. 真正错误的是“旋转矩阵在这条协方差构造链里的方向反了” - ---- - -## 8. 最终修复 - -最终有效修复位于: - -`MVS/3DGS-D3D12/shaders/PrepareGaussiansCS.hlsl` - -修复前: - -```hlsl -return mul(rotationMatrix, scaleMatrix); -``` - -修复后: - -```hlsl -return mul(transpose(rotationMatrix), scaleMatrix); -``` - -这一步的含义不是“随便转置试试”,而是把旋转基底重新拉回当前 shader 使用的矩阵约定下。 - -一旦这里修正,后面的: - -1. 3D 协方差构造 -2. 2D 屏幕协方差投影 -3. 特征轴分解 - -都会回到正确的姿态链路上。 - ---- - -## 9. 验证过程 - -修复后做了两类验证。 - -### 9.1 原视角验证 - -重新编译并输出单帧截图后,原先大量针状杂乱的高斯恢复正常,用户直接确认结果已经正确。 - -### 9.2 旋转视角验证 - -为了验证这不是“碰巧某个角度正常”,又临时修改固定相机朝向,重新输出另一张图。 - -结果是: - -1. 换视角后图像依然正常 -2. 原来那种随相机旋转一起转的针状异常不再出现 - -这一步证明修复的是根因,不是局部参数凑巧压住了症状。 - -需要单独说明的是: - -1. `App.cpp` 中改相机朝向只是为了验证 -2. 它不是根因修复的一部分 -3. 真正起作用的修复只有协方差旋转基底那一行 - ---- - -## 10. 本次过期计划的结论 - -这次问题的经验很明确: - -1. 3DGS 里“能出图”不代表高斯姿态链路正确 -2. 针状 splat 不一定是长度爆炸,也可能是主轴方向解释错了 -3. 当异常会随相机一起成片旋转时,首先应该怀疑协方差投影链路里的矩阵约定 -4. 对 3DGS 来说,`rotation -> covariance -> projected covariance -> eigen axes` 这一整条链必须严格使用同一套矩阵语义 - -这份计划已经结束并失效,原因不是任务未完成,而是根因已经确认、修复已经生效、验证已经通过。 - -后续如果再遇到类似“高斯姿态不对、长轴方向成片一致、某些角度正常某些角度错误”的问题,应优先复查: - -1. 四元数到旋转矩阵的展开约定 -2. row-vector / column-vector 的统一性 -3. 协方差构造公式使用的矩阵方向 -4. view/projection 链路里是否存在局部转置或约定漂移 diff --git a/docs/used/3DGS专用PLY导入器与GaussianSplat资源缓存正式化计划_完成归档_2026-04-10.md b/docs/used/3DGS专用PLY导入器与GaussianSplat资源缓存正式化计划_完成归档_2026-04-10.md deleted file mode 100644 index 48047b4c..00000000 --- a/docs/used/3DGS专用PLY导入器与GaussianSplat资源缓存正式化计划_完成归档_2026-04-10.md +++ /dev/null @@ -1,693 +0,0 @@ -# 3DGS 专用 PLY 导入器与 GaussianSplat 资源缓存正式化计划 - -日期:2026-04-10 - -## 1. 文档定位 - -这份计划只覆盖 3DGS 落地中的前两层基础设施: - -1. `3DGS 专用 PLY importer` -2. `GaussianSplat 资源 / Artifact / ResourceManager / 缓存层` - -这份计划明确不讨论以下内容: - -1. 不实现最终的 3DGS 渲染 pass -2. 不实现 editor 里的 3DGS 编辑工具 -3. 不实现 cutout、selection、导出、相机激活等 Unity 参考项目中的高级功能 -4. 不提前把 3DGS 强行塞进现有 mesh / volume 路径 - -这份计划的目标不是“先把 `.ply` 读出来”,而是把 3DGS 资产链正式纳入引擎现有的资源系统,使它从一开始就是一条可缓存、可复用、可异步、可测试、可长期维护的正式路径。 - ---- - -## 2. 当前参考与现状 - -当前参考工程是: - -1. [mvs/3DGS-Unity](D:/Xuanchi/Main/XCEngine/mvs/3DGS-Unity) - -当前测试样本是: - -1. [room.ply](D:/Xuanchi/Main/XCEngine/mvs/3DGS-Unity/room.ply) - -当前已确认的事实: - -1. `3DGS-Unity` 并不是运行时直接渲染 `.ply`,而是先把 `.ply` 转成更接近 GPU 消费形态的运行时资产。 -2. 它的导入工作流核心在 [GaussianSplatAssetCreator.cs](D:/Xuanchi/Main/XCEngine/mvs/3DGS-Unity/Editor/GaussianSplatAssetCreator.cs)。 -3. 它的 `.ply` 读取器 [PLYFileReader.cs](D:/Xuanchi/Main/XCEngine/mvs/3DGS-Unity/Editor/Utils/PLYFileReader.cs) 本质上是一个偏工程化的快速路径,不是健壮的通用 PLY 解析器。 -4. 它的运行时资产 [GaussianSplatAsset.cs](D:/Xuanchi/Main/XCEngine/mvs/3DGS-Unity/Runtime/GaussianSplatAsset.cs) 已经把数据拆成了 `pos / other / sh / color / chunk` 几类 GPU 资源。 -5. 它的运行时渲染 [GaussianSplatRenderer.cs](D:/Xuanchi/Main/XCEngine/mvs/3DGS-Unity/Runtime/GaussianSplatRenderer.cs) 依赖 compute、StructuredBuffer、RawBuffer、procedural draw、fullscreen composite。 - -对当前引擎的现状判断: - -1. 引擎已经具备 `StructuredBuffer / RawBuffer / RWStructuredBuffer / RWRawBuffer` 级别的 shader 资源识别能力。 -2. 引擎已经具备 compute shader 与 `Dispatch` 的 RHI 基础能力。 -3. 引擎已经具备 runtime material buffer binding 能力。 -4. 引擎已经具备 `AssetDatabase -> Library/Artifacts -> ResourceManager` 的正式资源链。 -5. 引擎当前还没有 `GaussianSplat` 这种正式资源类型。 -6. 引擎当前还没有 3DGS 专用 importer、artifact schema、loader、GPU residency cache。 - ---- - -## 3. 本轮最核心的架构决策 - -### 3.1 不允许运行时直接消费 `.ply` - -正式方案必须是: - -1. `Assets/*.ply` -2. 经过 `GaussianSplatImporter` -3. 生成 `Library/Artifacts/.../main.xcgsplat` -4. 运行时只加载 `xcgsplat` - -不允许的错误方案: - -1. renderer 首次遇到 `.ply` 时再现场解析 -2. component 里直接持有 `.ply` 文件句柄 -3. 把 `.ply` 读取逻辑塞进 render pass -4. 为了尽快出图先做“临时直接加载 `.ply`”然后以后再回收 - -原因很明确: - -1. `.ply` 是 source asset,不是 runtime-ready asset -2. 直接 runtime 解析会破坏 `AssetDatabase / ResourceManager / Library` 体系 -3. 3DGS 数据量远大于普通 mesh,更不能把 source 解析和 GPU 上传压到 draw path 上 - -### 3.2 不做通用 PLY 导入器,先做 3DGS 专用 PLY 导入器 - -本轮 importer 的职责不是支持一切 PLY 变体,而是支持当前 3DGS 工作流所需的那类 PLY。 - -正式边界是: - -1. 支持 `binary_little_endian` -2. 只关心 `element vertex` -3. 通过属性名映射识别 3DGS 语义字段 -4. 对不支持的属性布局给出明确错误 - -不做的事: - -1. 不支持带面片索引的通用模型 PLY -2. 不支持 ASCII PLY -3. 不支持任意 list property -4. 不支持“能读但语义不清晰”的模糊推断 - -这不是退让,而是边界明确。当前目标是 3DGS 正式化,不是通用点云 SDK。 - -### 3.3 运行时正式资源类型命名为 `GaussianSplat` - -建议引入: - -1. `ResourceType::GaussianSplat` -2. `Resources::GaussianSplat` -3. `GaussianSplatLoader` -4. `GaussianSplatImporter` - -不建议把运行时资源叫: - -1. `GaussianSplatAsset` -2. `GaussianAsset` -3. `PLYAsset` - -原因: - -1. 引擎当前 `ResourceType` 命名体系都是运行时资源名,不是 editor 资产名 -2. `Asset` 更适合出现在导入流程和文档语义中,不适合塞进正式 runtime `ResourceType` - -### 3.4 Artifact 采用单文件主 artifact,而不是 Unity 式多 TextAsset 拼装 - -建议正式主 artifact 为: - -1. `main.xcgsplat` - -不建议照抄 Unity MVS 的多文件拆分为: - -1. `*_pos.bytes` -2. `*_other.bytes` -3. `*_sh.bytes` -4. `*_col.bytes` -5. `*_chk.bytes` - -Unity 那样做是受 Unity 资产模型约束。我们自己的引擎不需要跟着它的工程妥协走。 - -本轮更合理的正式方案是: - -1. 单个 `xcgsplat` 文件包含 header、section table、payload -2. loader 一次读入 metadata,按 section 定位各 payload -3. 后续如果要做分段流式或 memory mapping,再在 schema 上扩展,不先把文件形态做碎 - -这样做的好处: - -1. artifact 边界清晰 -2. `ArtifactDB` 记录简单 -3. 依赖跟踪简单 -4. reimport 稳定 -5. 不会出现多 sidecar 丢失或部分过期的问题 - ---- - -## 4. 对参考 MVS 的正式吸收方式 - -`mvs/3DGS-Unity` 里真正值得吸收的是流程,不是实现细节原样照搬。 - -本轮吸收的内容: - -1. 输入语义 -2. 数据重排思路 -3. 运行时数据拆分维度 -4. `chunk` 概念 -5. 颜色纹理化而不是纯 buffer 化 -6. 未来 compute 排序与 view-data 预处理对资源格式的需求 - -本轮不直接照搬的内容: - -1. Unity 的 `TextAsset` 资产组织方式 -2. 依赖 `UnsafeUtility.SizeOf() == vertexStride` 的固定内存布局导入 -3. editor 窗口与工具链 -4. HDRP/URP feature 接入方式 -5. 所有编辑态 GPU buffer - -简单说: - -1. 流程借鉴 -2. 数据语义借鉴 -3. 工程架构不照抄 Unity 的壳 - ---- - -## 5. `room.ply` 的正式支持目标 - -当前基线样本 [room.ply](D:/Xuanchi/Main/XCEngine/mvs/3DGS-Unity/room.ply) 已确认包含如下字段: - -1. `x y z` -2. `nx ny nz` -3. `f_dc_0..2` -4. `f_rest_0..44` -5. `opacity` -6. `scale_0..2` -7. `rot_0..3` - -本轮 importer 至少必须把这类文件稳定导入。 - -本轮导入结果必须包含: - -1. splat 数量 -2. bounds -3. position -4. rotation -5. scale -6. opacity -7. color / dc0 -8. SH 数据 -9. 供后续 chunk 化、排序、view-data 预计算所需的稳定 layout - -本轮不要求: - -1. 用 room.ply 直接出图 -2. 完成 chunk 压缩优化后的最终视觉验证 - -但必须做到: - -1. room.ply 可以稳定导入成正式 artifact -2. runtime 可以正式加载该 artifact -3. 资源缓存与异步链路已经为后续渲染阶段准备好 - ---- - -## 6. 目标资源模型设计 - -### 6.1 `Resources::GaussianSplat` - -建议 `GaussianSplat` 运行时资源至少包含: - -1. `splatCount` -2. `boundsMin / boundsMax` -3. `dataFormatVersion` -4. `positionFormat` -5. `otherFormat` -6. `colorFormat` -7. `shFormat` -8. `chunkCount` -9. `cameraInfoCount` -10. 各 section 的只读数据视图 - -这里的 section 建议为: - -1. `positions` -2. `other` -3. `color` -4. `sh` -5. `chunks` -6. `cameras` - -### 6.2 `other` 的语义边界 - -建议 `other` 区域承担: - -1. rotation -2. scale -3. opacity -4. 可选 SH 索引或 chunk 相关辅助字段 - -原因: - -1. 这与参考 MVS 的消费模型更接近 -2. compute 阶段天然会把位置和其它数据拆开消费 -3. 将来做压缩时,位置和其它数据的量化策略也不同 - -### 6.3 `color` 保持纹理友好布局 - -建议 `color` section 不是简单“每 splat 一行 float4”,而是直接按运行时纹理消费友好的布局存储。 - -原因: - -1. 参考 MVS 最终就是把颜色上传成纹理 -2. 对 3DGS 而言,颜色作为纹理读取是合理路径 -3. 如果导入期就固化好 texel 布局,运行时不必再做一次昂贵重排 - -### 6.4 `chunks` 作为正式字段预留 - -即使第一阶段先允许 `chunkCount == 0`,artifact schema 也要正式留出 chunk 区域。 - -因为: - -1. chunk 数据不是可有可无的小优化,它影响后续压缩、解码和排序输入 -2. 后面一旦渲染 pass 接上,就会很自然依赖 chunk -3. 现在先把 schema 打对,比后面再迁移 artifact 版本更划算 - ---- - -## 7. Importer 设计 - -### 7.1 引入 `GaussianSplatImporter` - -`AssetDatabase` 对 `.ply` 的识别不应直接复用 `ModelImporter`。 - -建议规则: - -1. `.ply` 不作为通用模型格式挂进 `ModelImporter` -2. 本轮把 `.ply` 明确识别为 `GaussianSplatImporter` -3. 后续如果将来要支持“通用点云 PLY”,再单独扩展,不污染当前 3DGS 主线 - -### 7.2 Header 解析不能依赖固定顺序 - -正式解析流程必须是: - -1. 读取 header -2. 收集 `element vertex` -3. 收集每个 `property` 的名字、类型、偏移 -4. 建立 3DGS 语义字段到 property 的映射 -5. 校验必需字段是否完整 - -不允许的方案: - -1. 直接假定 `InputSplatData` 与文件二进制布局完全一致 -2. 直接假定 `f_rest_*` 顺序永远固定且不校验 -3. 因为 room.ply 能过就默认所有训练器导出的 PLY 都一样 - -### 7.3 importer 输出的是 cooked runtime layout,不是 source mirror - -导入器的正式职责不是把 `.ply` 原样搬进 artifact,而是做以下转换: - -1. 按语义解包 source data -2. 生成规范化内部 splat 记录 -3. 计算 bounds -4. 可选做 Morton reorder -5. 可选做 chunk 构建 -6. 输出运行时友好的 section layout - -这一步就是 source -> cooked artifact,而不是 source -> source copy。 - -### 7.4 关于压缩策略 - -本轮计划分两步: - -1. 第一阶段先实现无损或近无损基础 cooked 布局 -2. 第二阶段再把参考 MVS 的压缩格式体系正式移植进来 - -原因: - -1. 先把 artifact、loader、cache 链路跑通 -2. 再叠加压缩和 chunking -3. 避免 importer、artifact schema、runtime loader、未来 renderer 四件事同时出错 - -第一阶段允许: - -1. `position = float32` -2. `other = float32 / uint32 packed` -3. `color = RGBA32F` 或 `RGBA16F` -4. `sh = float32` -5. `chunk = 0` - -第二阶段再引入: - -1. `Norm16 / Norm11 / Norm6` -2. `BC7 / Norm8x4` -3. `SH clustering` -4. `chunk` 正式压缩路径 - ---- - -## 8. Artifact 设计 - -### 8.1 主文件 - -主文件建议: - -1. `main.xcgsplat` - -### 8.2 文件内容建议 - -建议 `xcgsplat` 文件包含: - -1. 文件头 -2. schema version -3. source metadata snapshot -4. splat metadata -5. section table -6. payload blob - -section table 至少描述: - -1. section type -2. byte offset -3. byte size -4. element count -5. element stride -6. format enum - -### 8.3 version 策略 - -建议单独引入: - -1. `kGaussianSplatArtifactSchemaVersion` - -不要复用其它 importer 的 schema version。 - -版本提升触发条件: - -1. section 布局改变 -2. chunk 编码改变 -3. 颜色纹理布局改变 -4. SH 格式或 camera 区块布局改变 - -### 8.4 `.meta` 设计 - -即使本轮先不做完整 Inspector,也应该为后续 importer settings 预留正式字段。 - -建议至少预留: - -1. `reorderMorton` -2. `buildChunks` -3. `positionFormat` -4. `otherFormat` -5. `colorFormat` -6. `shFormat` -7. `importCameras` - -第一阶段如果先不开放 UI,也要把默认设置结构体和 hash 纳入 artifact key 计算。 - ---- - -## 9. Loader 与 ResourceManager 接入 - -### 9.1 `GaussianSplatLoader` - -需要新增: - -1. `GaussianSplatLoader` - -职责: - -1. 读取 `xcgsplat` -2. 构建 `Resources::GaussianSplat` -3. 提供各 section 的稳定只读视图 - -### 9.2 `ResourceManager` 正式接入 - -正式链路应支持: - -1. `Load("Assets/.../room.ply")` -2. `AssetDatabase::EnsureArtifact(...)` -3. `ResourceManager` 实际加载 `main.xcgsplat` - -也必须支持: - -1. `Load("Library/Artifacts/.../main.xcgsplat")` - -### 9.3 不能把 GPU 上传塞进 loader - -`GaussianSplatLoader` 只负责 CPU 运行时资源,不负责 GPU residency。 - -原因: - -1. loader 属于资源层 -2. GPU residency 属于渲染缓存层 -3. 如果在 loader 里直接创 GPU 资源,会重复 volume 这条链已经暴露过的架构问题 - ---- - -## 10. 缓存与预热设计 - -### 10.1 资源缓存层必须提前设计 GPU residency 状态机 - -即使本轮还不接最终 render pass,也必须把状态机设计写进正式方案: - -1. `Uninitialized` -2. `CpuReady` -3. `GpuUploading` -4. `GpuReady` -5. `Failed` - -后续 `BuiltinGaussianSplatPass` 只能消费: - -1. `GpuReady` - -不允许 draw path 现场把 `CpuReady -> GpuReady` 做完。 - -### 10.2 建议新增 `CachedGaussianSplat` - -建议未来挂在 `RenderResourceCache` 或其正式拆分后的 GPU 资源缓存模块中。 - -它至少应持有: - -1. `posBuffer` -2. `otherBuffer` -3. `shBuffer` -4. `colorTexture` -5. `chunkBuffer` -6. `sortKeyBuffer` -7. `sortDistanceBuffer` -8. `viewDataBuffer` -9. runtime-ready flag / state - -本轮即使还不把所有 GPU 辅助 buffer 全建出来,也要把正式边界写清楚: - -1. asset static payload buffer/texture -2. per-frame transient / reusable working buffer - -### 10.3 首次可见前预热,而不是首次 draw 同步补做 - -这点必须作为硬约束写死: - -1. `GaussianSplat` 被场景反序列化后,CPU artifact 加载完成就进入 GPU 预热队列 -2. GPU 上传在后台或明确的准备阶段完成 -3. 首次 draw 只允许跳过未 ready 对象,不允许同步创建大资源 - -原因: - -1. 3DGS 资产通常很大 -2. room.ply 这种样本数据量已经足够把 draw path 压垮 -3. 当前 volume 这条链已经证明“首次绘制再上传”不是可接受正式方案 - -### 10.4 warm cache 验收标准 - -本轮资源 / 缓存层至少要达到: - -1. 第二次加载 room.ply 时不重新解析 source `.ply` -2. 直接命中 `Library/Artifacts` -3. `ResourceManager` 不会因为 cache hit 又走 source importer -4. 后续 GPU 预热可以稳定复用 artifact 输出 - ---- - -## 11. 测试计划 - -### 11.1 基线样本 - -统一使用: - -1. [room.ply](D:/Xuanchi/Main/XCEngine/mvs/3DGS-Unity/room.ply) - -它将承担: - -1. importer 基线 -2. artifact 基线 -3. cache hit 基线 -4. future renderer 接入基线 - -### 11.2 Unit Tests - -本轮至少要补齐以下单测: - -1. `PLY header parser` 正确识别 `vertexCount / properties / offsets` -2. `GaussianSplatImporter` 能正确识别 room.ply 的必需字段 -3. 缺字段时给出明确错误 -4. 非法格式时给出明确错误 -5. `xcgsplat` 写入 / 读取 roundtrip 正确 -6. `GaussianSplatLoader` 能读取 artifact 并恢复 metadata 与 section view -7. `ResourceManager` 能从 `Assets/.../room.ply` 正式加载 `GaussianSplat` -8. `AssetDatabase` 对 `.ply` 的 `EnsureArtifact` 能稳定复用 - -### 11.3 Integration Tests - -本轮先做资源链集成测试,不做最终出图测试。 - -至少要有: - -1. `room.ply -> EnsureArtifact -> Load` 全链通过 -2. 二次加载命中 artifact,不触发 reimport -3. 修改 source writeTime 后能触发 reimport -4. 清库后能重建 artifact - -### 11.4 为后续渲染阶段准备的 smoke test - -虽然本轮不做 3DGS pass,但建议提前补一个 GPU 资源 smoke test: - -1. 读取 `GaussianSplat` -2. 构建最小 GPU cache entry -3. 创建 `pos/other/sh/chunk` buffer 与 `color` texture -4. 验证状态进入 `GpuReady` - -这样后续 renderer 接入时,不会把“资源问题”和“渲染问题”混成一团。 - ---- - -## 12. 分阶段执行计划 - -### Phase 1:资源类型与 artifact schema 落地 - -目标: - -1. 正式引入 `ResourceType::GaussianSplat` -2. 正式引入 `Resources::GaussianSplat` -3. 正式定义 `xcgsplat` artifact schema - -任务: - -1. 扩展 `ResourceType` -2. 新增 `GaussianSplat` 运行时资源类 -3. 设计 artifact header 与 section table -4. 新增 `WriteGaussianSplatArtifactFile / LoadGaussianSplatArtifact` - -验收标准: - -1. `xcgsplat` 可写可读 -2. 资源元数据可稳定 roundtrip - -### Phase 2:3DGS 专用 PLY importer 正式化 - -目标: - -1. 把 `.ply` 纳入 `GaussianSplatImporter` - -任务: - -1. 新增 header parser -2. 新增 3DGS property mapping -3. 读取 room.ply 并转换成规范化内部 splat 数据 -4. 输出基础 cooked artifact - -验收标准: - -1. room.ply 可稳定导入 -2. 不依赖固定 struct stride == 文件 stride -3. 错误路径有清晰日志 - -### Phase 3:AssetDatabase / Library / ResourceManager 接入 - -目标: - -1. 把 `GaussianSplat` 完整接进项目资源工作流 - -任务: - -1. `.ply -> GaussianSplatImporter` -2. `EnsureArtifact(..., ResourceType::GaussianSplat)` -3. `GaussianSplatLoader` -4. `Load()` - -验收标准: - -1. 可以通过 `Assets/.../room.ply` 正式加载 -2. cache hit 时不重走 source parse - -### Phase 4:资源缓存与 GPU residency 预热骨架 - -目标: - -1. 正式建立 3DGS GPU 资源缓存的边界 - -任务: - -1. 设计 `CachedGaussianSplat` -2. 建立 GPU residency 状态机 -3. 实现最小 GPU 资源构建 smoke path -4. 明确禁止 draw path 首次同步上传 - -验收标准: - -1. room.ply 对应的 `GaussianSplat` 可以被 GPU cache 预热成 ready 状态 -2. 资源层与渲染层边界清晰 - -### Phase 5:测试补齐与收口 - -目标: - -1. 让这条链路可回归、可持续演进 - -任务: - -1. 补全 importer / loader / cache hit / reimport 单测与集成测试 -2. 输出阶段性说明 -3. 为后续 renderer 接入保留唯一正式资源路径 - -验收标准: - -1. room.ply 全链路测试稳定 -2. 不存在“临时直接读 ply”的旁路 - ---- - -## 13. 明确不允许出现的临时方案 - -以下方案本轮禁止出现: - -1. 为了尽快出图,先在 render pass 里直接解析 `.ply` -2. 先做一个 `BinaryResource` 包 `.ply` 内容,后面再说 -3. 先把 `.ply` 当 `Mesh` 导入 -4. 把 3DGS 的 GPU buffer 直接挂在 `Material` 资源本体上作为持久化资产 -5. 首次 draw 时同步创建 `pos / other / sh / color` GPU 资源 -6. 把 `room.ply` 单独写死成特判 - -这些做法都会把本该正式化的主线重新拉回临时方案。 - ---- - -## 14. 本轮完成标志 - -当以下条件同时成立时,这份计划才算完成: - -1. `.ply` 已正式被 `GaussianSplatImporter` 接管 -2. `GaussianSplat` 已成为正式 `ResourceType` -3. `room.ply` 能稳定导入成 `xcgsplat` -4. `ResourceManager` 能正式加载 `GaussianSplat` -5. 二次加载能稳定命中 artifact -6. GPU residency cache 骨架已经建立,不允许首次 draw 同步补做 -7. 资源层与缓存层测试已经覆盖 room.ply 主路径 - ---- - -## 15. 一句话结论 - -这条主线的第一步不是“做一个 ply 读取器”,而是把 3DGS 正式升级为引擎里的 `GaussianSplat` 资源体系: -由 `GaussianSplatImporter` 把 `.ply` 转成 `xcgsplat` cooked artifact,由 `GaussianSplatLoader` 与 `ResourceManager` 正式接管加载,再由独立的 GPU residency cache 提前完成资源预热,为后续 3DGS 渲染 pass 提供唯一、稳定、无旁路的正式输入。 diff --git a/docs/used/3DGS渲染路径对齐参考实现修复计划_过期归档_2026-04-12.md b/docs/used/3DGS渲染路径对齐参考实现修复计划_过期归档_2026-04-12.md deleted file mode 100644 index 9b254707..00000000 --- a/docs/used/3DGS渲染路径对齐参考实现修复计划_过期归档_2026-04-12.md +++ /dev/null @@ -1,248 +0,0 @@ -# 3DGS 渲染路径对齐参考实现修复计划 - -日期:2026-04-11 - -## 1. 文档定位 - -旧计划《3DGS渲染集成测试与Renderer正式接入计划》已经完成了主体接入工作: - -1. `GaussianSplat` 资源链路已经打通 -2. `GaussianSplatRendererComponent` / `VisibleGaussianSplatItem` / `BuiltinGaussianSplatPass` 已经落地 -3. `tests/Rendering/integration/gaussian_splat_scene` 已经建立 - -当前剩下的不是“是否接进 Renderer”,而是“当前 Renderer 中的 3DGS 正式路径与参考实现仍有关键偏差,导致画面结果错误”。 -因此这份新计划只聚焦当前的渲染正确性修复与测试收口,不再重复旧计划里已经完成的接入事项。 - -## 2. 已确认的根因 - -这轮问题已经不是相机摆放、PLY 导入或简单参数调节问题,而是渲染路径本身与参考实现不一致。 - -### 2.1 当前引擎缺少正式的 accumulation + composite 两段式路径 - -参考实现的关键流程是: - -1. 先把 splat 绘制到单独的半浮点累积 RT -2. splat draw 使用前向累积专用 blend -3. 最后再把累积 RT composite 回主场景颜色缓冲 - -参考位置: - -1. `mvs/3DGS-Unity/Shaders/RenderGaussianSplats.shader` -2. `mvs/3DGS-Unity/Shaders/GaussianComposite.shader` -3. `mvs/3DGS-Unity/Runtime/GaussianSplatRenderer.cs` - -当前引擎却是: - -1. 在 `engine/assets/builtin/shaders/gaussian-splat.shader` 中直接输出到主场景颜色 -2. `BuiltinGaussianSplatPass` 直接把 camera color attachment 作为 render target -3. 虽然 `BuiltinGaussianSplatPassResources` 已经有 `AccumulationSurface` 抽象,但执行路径没有真正使用它 - -这说明当前实现是“资源抽象已经开始正式化,但执行流仍停在临时路径上”,这是第一根因。 - -### 2.2 当前排序方向与当前 blend 方程不匹配 - -当前引擎: - -1. `PrepareOrder` 阶段使用相机空间 `viewCenter.z` 作为排序距离 -2. bitonic 结果本质上是按升序排列 -3. draw shader 却使用普通透明混合 `Blend SrcAlpha OneMinusSrcAlpha` - -如果保留当前普通透明混合,就应当使用严格的 back-to-front 语义; -如果要对齐参考实现的 front-to-back 累积,就必须同时切到参考的累积 blend 与独立 accumulation target。 - -也就是说,当前并不是“排序可能有一点不准”,而是“排序约定与 blend 合约本身冲突”。 - -### 2.3 当前测试参数只是在放大问题,不是问题本体 - -当前集成测试里: - -1. 只取了 `65536` 个 splat 子集 -2. `_PointScale = 3.0` -3. 最终输出直接落在主 backbuffer - -这些会让错误更明显,但不会单独制造当前这种大面积拖影、糊片、黑色尖刺。 -真正的问题仍然是 draw/composite 路径设计错误。 - -## 3. 本轮修复目标 - -本轮只做一件事:把当前引擎中的 3DGS 渲染主链,彻底对齐到参考实现所依赖的那组最小正确语义。 - -具体目标: - -1. 建立正式的 accumulation render target -2. draw shader 改为服务 accumulation 的输出与 blend 合约 -3. 新增正式的 composite pass,把 accumulation 结果混回主场景 -4. 统一排序方向与 blend 语义,不允许继续“排序一套、混合一套” -5. 保证 `gaussian_splat_scene` 在 D3D12 / Vulkan / OpenGL 三后端下都能稳定回归 - -## 4. 非目标 - -这轮明确不做: - -1. 不接 editor 中的 3DGS 显示与交互 -2. 不做 selection / cutout / 编辑工具链 -3. 不做 3DGS 的 streaming / LOD / chunk 级高级优化 -4. 不在这轮重做 `GaussianSplat` artifact schema -5. 不引入 compute shader 之外的新渲染架构分支 - -## 5. 执行原则 - -### 5.1 先对齐正确性,再谈进一步优化 - -这轮优先级必须是: - -1. 先把“渲染方程”和“执行路径”对齐 -2. 先让集成测试输出正确 -3. 再考虑性能、压缩、子集规模、排序频率等问题 - -### 5.2 不允许继续保留半套正式化、半套临时方案 - -一旦正式启用 accumulation/composite,就必须: - -1. 把 draw shader、pass 资源、pass 执行流一起接完整 -2. 把当前直接画到 scene color 的临时路径清掉 -3. 不保留两个语义不同但名字相同的渲染路线 - -### 5.3 修复必须由测试驱动收口 - -本轮所有核心改动都必须由以下验证闭环约束: - -1. `gaussian_splat_scene` 的三后端输出 -2. 中间调试图可视化检查 -3. 现有 `GaussianSplat` 资源与缓存相关测试不回退 - -## 6. 分阶段计划 - -### Phase 1:补齐 3DGS 中间结果观测面 - -目标: - -1. 在不改渲染语义之前,先把中间状态可视化出来 - -任务: - -1. 允许 `gaussian_splat_scene` 在调试模式下输出 accumulation RT -2. 允许单独检查 sort/order 与 view-data 是否为空或异常 -3. 固化一套最小调试截图流程,避免后续继续靠猜 - -验收标准: - -1. 可以直接看到 accumulation RT 的内容 -2. 可以区分“prepare 阶段错误”和“draw/composite 阶段错误” - -### Phase 2:把 accumulation surface 正式接入 `BuiltinGaussianSplatPass` - -目标: - -1. 让 `BuiltinGaussianSplatPassResources::AccumulationSurface` 从未使用状态变成正式执行资源 - -任务: - -1. 在 pass 执行前按 viewport 尺寸创建或复用 accumulation RT -2. 以 `alpha = 0` 清空 accumulation RT -3. draw 阶段只向 accumulation RT 输出,不再直接写主场景颜色 -4. 明确 accumulation RT 的资源状态流转 - -验收标准: - -1. draw pass 不再直接绑定主场景颜色附件 -2. accumulation RT 生命周期与 working set 生命周期边界清晰 - -### Phase 3:对齐 draw shader 的输出语义与 blend 合约 - -目标: - -1. 让 splat draw 输出和参考实现使用同一套前向累积语义 - -任务: - -1. 对齐 draw shader 输出格式 -2. 对齐 draw shader blend 状态 -3. 确认当前相机空间 `z` 约定下,排序方向与累积方向一致 -4. 不允许通过后端分支打补丁解决语义冲突 - -验收标准: - -1. draw shader 的颜色输出、alpha 输出、blend 方程是自洽的 -2. `PrepareOrder -> Sort -> Draw` 是同一套排序约定 - -### Phase 4:新增正式的 composite pass - -目标: - -1. 把 accumulation RT 稳定地合成回主场景颜色缓冲 - -任务: - -1. 新增 builtin composite shader -2. 在 `BuiltinGaussianSplatPass` 内建立 draw + compose 的完整顺序 -3. 明确主场景颜色、深度与 accumulation RT 的交互边界 -4. 不通过测试侧手写 command list 绕过 renderer - -验收标准: - -1. 最终颜色由 composite pass 统一输出 -2. 3DGS pass 对主场景颜色缓冲的写入语义单一清晰 - -### Phase 5:回归基线与参数收口 - -目标: - -1. 在正确渲染路径跑通后,再校正当前测试参数 - -任务: - -1. 重新评估当前子集数量是否足够形成稳定 GT -2. 重新评估 `_PointScale` 的默认值 -3. 必要时重建 `GT.ppm` -4. 三后端重新运行并固定基线 - -验收标准: - -1. `gaussian_splat_scene` 的 GT 是基于正式渲染路径得到的 -2. 不再把“错误输出截图”继续当成 GT - -## 7. 风险与提前约束 - -### 7.1 不能把参考实现照搬成 Unity 特有依赖 - -本轮只吸收以下内容: - -1. 累积 RT + composite 的渲染语义 -2. 排序与 blend 的契约关系 -3. 中间资源与 draw/composite 的分层方式 - -不吸收: - -1. Unity 特有 `CommandBuffer` 接口设计 -2. Unity 运行时生命周期组织方式 -3. Unity 专有材质属性命名与 editor 逻辑 - -### 7.2 不能把 accumulation/composite 做成测试专用旁路 - -这条路径必须属于正式 renderer,而不是: - -1. 只在 `gaussian_splat_scene` 里额外手写一套 -2. 只在 D3D12 下工作 -3. 通过测试宏临时切换 - -### 7.3 不允许继续保留错误 GT - -如果当前 `GT.ppm` 来自错误渲染路径,就必须重建。 -否则后续再怎么修,测试也会被旧基线误导。 - -## 8. 完成标志 - -当以下条件同时成立,这份计划才算完成: - -1. `BuiltinGaussianSplatPass` 正式使用 accumulation surface -2. draw shader 与 sort 语义彻底自洽 -3. composite pass 正式接回主场景颜色 -4. `gaussian_splat_scene` 在 D3D12 / Vulkan / OpenGL 下都能稳定输出正确图像 -5. 调试图证明 accumulation RT 本身干净且可解释 -6. 当前 3DGS 渲染路径中不再残留“直接写 scene color 的临时旧路线” - -## 9. 一句话结论 - -当前 3DGS 的问题不是“参数没调对”,而是“渲染执行链条还停在错误的临时语义上”。 -下一步必须把 `Prepare/Sort -> Accumulation Draw -> Composite` 这条正式路径一次接完整,再重新建立三后端的 GT 基线。 diff --git a/docs/used/3DGS渲染集成测试与Renderer正式接入计划_阶段归档_2026-04-11.md b/docs/used/3DGS渲染集成测试与Renderer正式接入计划_阶段归档_2026-04-11.md deleted file mode 100644 index 3a72b16b..00000000 --- a/docs/used/3DGS渲染集成测试与Renderer正式接入计划_阶段归档_2026-04-11.md +++ /dev/null @@ -1,406 +0,0 @@ -# 3DGS 渲染集成测试与 Renderer 正式接入计划 - -日期:2026-04-10 - -## 1. 文档定位 - -这份计划承接已经完成的 `GaussianSplat 资源 / Artifact / ResourceManager / GPU cache` 正式化工作,正式进入下一阶段: - -1. 在 `tests/Rendering/integration` 中把 3DGS 正式渲染出来 -2. 为引擎 renderer 增加一条正式的 3DGS 场景渲染路径 - -这份计划的首要目标不是 editor 接入,也不是 3DGS 编辑工具,而是: - -1. 让 `room.ply -> GaussianSplat -> Scene -> Renderer -> GT 比对` 成为一条正式、可回归、可维护的链路 - -这份计划明确不做的事: - -1. 不做 editor 里的 3DGS Inspector / gizmo / 交互编辑 -2. 不做 cutout、selection、导出、编辑态 GPU buffer -3. 不在这一轮提前做 streaming / LOD / chunk 压缩升级 -4. 不把 3DGS 临时塞进 `MeshRenderer` 或 `VolumeRenderer` 路径里凑合出图 - ---- - -## 2. 当前基线与已具备能力 - -当前已完成的正式基础: - -1. `.ply -> GaussianSplatImporter -> main.xcgsplat -> GaussianSplatLoader -> ResourceManager` 已打通 -2. `RenderResourceCache` 已具备 `GaussianSplat` 静态 GPU 驻留能力 -3. `room.ply` 已可稳定导入、复用 artifact、并通过测试验证 - -当前 renderer / RHI 已具备的关键能力: - -1. `StructuredBuffer / RawBuffer / RWStructuredBuffer / RWRawBuffer` shader 资源描述能力已具备 -2. compute shader 与 `Dispatch` 已具备 -3. `Draw(vertexCount, instanceCount, ...)` 已具备,可以走 instanced quad 而不需要先补新的 RHI 接口 -4. renderer 已有成熟的 integration test 模式: - 1. 一个场景一个 target - 2. 真正创建 `Scene + SceneRenderer + RenderSurface` - 3. 输出 PPM 并和 `GT.ppm` 比对 -5. 当前已有可借鉴的参考: - 1. [mvs/3DGS-Unity/Runtime/GaussianSplatRenderer.cs](D:/Xuanchi/Main/XCEngine/mvs/3DGS-Unity/Runtime/GaussianSplatRenderer.cs) - 2. [tests/Rendering/integration/volume_scene](D:/Xuanchi/Main/XCEngine/tests/Rendering/integration/volume_scene) - 3. [tests/Rendering/integration/backpack_lit_scene](D:/Xuanchi/Main/XCEngine/tests/Rendering/integration/backpack_lit_scene) - ---- - -## 3. 当前还缺什么 - -虽然资源链已经完整,但 renderer 侧现在仍然没有正式的 3DGS 路径: - -1. 没有 `GaussianSplatRendererComponent` -2. 没有 `VisibleGaussianSplatItem` -3. `RenderSceneData` 没有 `visibleGaussianSplats` -4. `RenderSceneExtractor / RenderSceneUtility` 不会提取 3DGS 场景对象 -5. `BuiltinForwardPipeline` 没有 3DGS 专用 pass -6. 还没有用于 3DGS 的 transient GPU working buffers: - 1. sort distance / key - 2. view-data - 3. offscreen accumulation / compose target -7. `tests/Rendering/integration` 还没有 3DGS 场景测试 - -所以这一步不是“写个测试 main.cpp 就行”,而是要正式把 3DGS 纳入 renderer 架构。 - ---- - -## 4. 本轮核心架构决策 - -### 4.1 必须引入专用场景组件 `GaussianSplatRendererComponent` - -不允许: - -1. 让 `MeshRendererComponent` 直接持有 `GaussianSplat` -2. 复用 `VolumeRendererComponent` -3. 在测试里绕过组件层,直接把 `GaussianSplat` 塞进 pass - -正式方案应为: - -1. 新增 `GaussianSplatRendererComponent` -2. 它持有: - 1. `ResourceHandle` - 2. `Material*` 或正式 `Material` 句柄 - 3. 运行时参数,例如缩放、透明度倍率、SH 阶数、排序策略等 - -原因: - -1. 这条路径是专用渲染 primitive,不是 mesh,也不是 volume -2. 场景提取、渲染排序、后续 editor 支持,都需要一个正式组件边界 -3. 这和参考项目中单组件 `GaussianSplatRenderer` 的职责边界是一致的 - -### 4.2 3DGS 必须走专用 `VisibleGaussianSplatItem` - -应新增: - -1. `Rendering/FrameData/VisibleGaussianSplatItem.h` -2. `RenderSceneData.visibleGaussianSplats` -3. `AppendVisibleGaussianSplatsForGameObject(...)` - -原因: - -1. 3DGS 与 mesh、volume 的排序依据和 GPU 资源都不同 -2. 把它塞到 `visibleItems` 或 `visibleVolumes` 里只会制造更多特判 - -### 4.3 3DGS 必须作为 `BuiltinForwardPipeline` 中的一条正式 pass - -不允许: - -1. 直接把 3DGS 逻辑硬塞进 `DrawVisibleItems` -2. 把 3DGS 伪装成普通 forward lit / unlit surface -3. 在测试代码里直接手写 command list 绕过 renderer - -正式方案应为: - -1. 新增 `BuiltinGaussianSplatPass` -2. 它在 `BuiltinForwardPipeline` 中作为一条显式阶段执行 -3. 先按当前 pipeline 约束放在: - 1. `Opaque` - 2. `Skybox` - 3. `GaussianSplat` - 4. `Volumetric` - 5. `Transparent` - -这一步的理由是: - -1. 参考 Unity MVS 的落点本质上属于 `BeforeForwardAlpha` -2. 3DGS 需要依赖 opaque depth,但又不应混入普通 mesh transparent 队列 -3. 当前 pipeline 还没有更通用的 feature graph,因此先做一条正式内建 pass 是合理路径 - -### 4.4 静态 GPU 资产与每帧 working set 必须分层 - -已经完成的静态层: - -1. `RenderResourceCache::CachedGaussianSplat` - 1. positions - 2. other - 3. color - 4. sh - 5. chunks - -下一步必须新增的是每帧 / 每相机 working set,而不是污染静态 cache: - -1. sort distance buffer -2. sort key / order buffer -3. view-data buffer -4. splat accumulation render target - -结论: - -1. `RenderResourceCache` 继续负责静态 asset residency -2. `BuiltinGaussianSplatPass` 或其专属 surface cache 负责 transient working resources - -### 4.5 第一张图必须先在 rendering integration test 里落地 - -这一轮的第一个正式验收点不是 editor 里看到 3DGS,而是: - -1. 新增 `tests/Rendering/integration/gaussian_splat_scene` -2. 使用真实 `room.ply` -3. 跑通过至少 D3D12 / OpenGL / Vulkan 的 GT 比对 - -原因: - -1. 这能把资源链问题、scene 提取问题、shader 问题、pass 问题一次性锁进正式回归 -2. editor 接入如果先做,调试成本会更高,问题边界也更模糊 - ---- - -## 5. 渲染技术路线 - -### 5.1 第一轮渲染方案 - -当前最合适的正式方案是: - -1. 使用 instanced quad 绘制 splat -2. 顶点着色器通过 `SV_InstanceID / gl_InstanceID` 从 `StructuredBuffer` 读取 per-splat 数据 -3. compute 负责: - 1. 计算相机空间深度或排序 key - 2. 生成 draw 顺序 - 3. 预计算 view-data -4. graphics pass 负责: - 1. 深度测试 - 2. splat 展开 - 3. 累积到专用 render target -5. fullscreen compose pass 负责把结果混合回主场景颜色 - -这条路线的优点: - -1. 与参考 `GaussianSplatRenderer.cs` 的总体思路一致 -2. 与现有 RHI 能力匹配,不需要新发明 RHI 接口 -3. 可以自然落到当前 built-in pipeline 中 - -### 5.2 关于排序 - -第一轮正式实现就应该有排序,不建议无排序硬上 GT。 - -原因: - -1. 3DGS 本质上依赖近似正确的后向前顺序 -2. 如果先做无排序,integration test 的 GT 价值会很差 -3. 后面再补排序,很可能把第一版 shader / pass 全打碎 - -因此建议: - -1. 第一张 integration 图就包含 compute 排序链 -2. 如果排序系统需要简化,也只能简化为“全局每帧排序”,不能退化到“不排序” - -### 5.3 关于 SH 与颜色 - -当前 cooked-v1 中已有: - -1. `color = SH0ToColor(dc0) + opacity` -2. `sh = f_rest_0..44` - -正式阶段建议: - -1. 第一版 pass 即支持当前 artifact 语义 -2. 不为了追求和 Unity 一模一样的颜色纹理缓存形态,先回头推翻当前 cooked-v1 -3. 如果实际实现中发现 `color buffer` 明显不适合后续长期演进,再做正式 schema 升级,而不是临时特判 - -也就是说: - -1. 当前目标是先让 `room.ply` 通过正式 renderer 路径稳定出图 -2. 不是在出第一张图之前就重做第二版 artifact 格式 - ---- - -## 6. Renderer 需要做的正式改动 - -### Phase 1:场景对象与提取层正式接入 - -目标: - -1. 让 Scene 能正式携带 3DGS 渲染对象 - -任务: - -1. 新增 `GaussianSplatRendererComponent` -2. 新增 `VisibleGaussianSplatItem` -3. 扩展 `RenderSceneData` -4. 扩展 `RenderSceneUtility / RenderSceneExtractor` -5. 补对应 unit tests,覆盖: - 1. 提取 - 2. culling mask - 3. render queue - 4. 排序稳定性 - -验收标准: - -1. `RenderSceneExtractor` 能输出 `visibleGaussianSplats` -2. 不污染 mesh / volume 路径 - -### Phase 2:3DGS pass 的 GPU working set 正式化 - -目标: - -1. 让 3DGS 渲染时所需的每帧资源有正式边界 - -任务: - -1. 为 `BuiltinGaussianSplatPass` 设计 transient resource 集合 -2. 明确静态 cache 与 transient working set 的所有权 -3. 实现: - 1. sort distances / keys - 2. order buffer - 3. view-data buffer - 4. accumulation target -4. 补 cache / pass smoke tests - -验收标准: - -1. pass 在没有 editor 的情况下独立准备好运行所需 GPU 资源 -2. 不把每帧资源塞回 `RenderResourceCache` - -### Phase 3:BuiltinGaussianSplatPass 正式落地 - -目标: - -1. 在 renderer 中画出第一张 3DGS 正式图 - -任务: - -1. 新增内建 shader / material 资产: - 1. sort compute - 2. view-data compute - 3. splat draw - 4. compose -2. 新增 `BuiltinGaussianSplatPass` -3. 接入 `BuiltinForwardPipeline` -4. 定义与主深度、主颜色的交互规则 - -验收标准: - -1. pass 不绕过 `SceneRenderer` -2. pass 不绕过 `RenderSceneData` -3. `BuiltinForwardPipeline` 的阶段顺序清晰且可测试 - -### Phase 4:rendering integration test 正式落地 - -目标: - -1. 把 3DGS 第一张图纳入 GT 回归 - -任务: - -1. 新增 `tests/Rendering/integration/gaussian_splat_scene` -2. 使用真实样本: - 1. [room.ply](D:/Xuanchi/Main/XCEngine/mvs/3DGS-Unity/room.ply) -3. 复制运行所需资源到测试输出目录 -4. 建立 GT 基线图 -5. 至少覆盖: - 1. D3D12 - 2. OpenGL - 3. Vulkan - -建议场景内容: - -1. 相机 -2. 黑或深灰背景 -3. 单个 `GaussianSplatRendererComponent` -4. 不引入外部灯光依赖作为第一张图前提 - -原因: - -1. 3DGS 的基础显示不应依赖当前主光 / 多光源系统是否正确 -2. 第一张图应尽量减少无关变量 - -### Phase 5:测试收口与回归闭环 - -任务: - -1. 跑通: - 1. `gaussian_splat_tests` - 2. `rendering_unit_tests` - 3. `rendering_integration_gaussian_splat_scene` - 4. 相关既有 rendering integration tests -2. 修复跨后端 shader / 资源绑定差异 -3. 输出阶段总结 - -验收标准: - -1. 3DGS 进入正式 rendering regression 集 -2. 不存在“只有 editor 里能看,测试里没有”的非正式路径 - ---- - -## 7. 需要提前注意的风险 - -### 7.1 当前 command list 没有 `DrawProcedural` - -这不是 blocker。 - -当前可行正式方案是: - -1. 使用固定 6 顶点 quad -2. `Draw(6, splatCount, 0, 0)` -3. 顶点阶段通过 instance id 读 buffer - -也就是说,不需要先为了 3DGS 强行扩展新的 RHI draw API。 - -### 7.2 3DGS 与 volume 都属于“专用透明路径” - -这意味着当前 built-in pipeline 中将出现: - -1. `VolumetricPass` -2. `GaussianSplatPass` - -两者在这一轮可以并存,但必须保证: - -1. 顺序明确 -2. 资源边界明确 -3. 不相互复用错误的数据结构 - -如果过程中发现当前 `BuiltinForwardPipeline` 已经开始过载,就应该正式抽出更清晰的内建 feature 组织层,而不是继续堆 if 分支。 - -### 7.3 integration test 先用真实 `room.ply` - -这意味着: - -1. 样本量不小 -2. shader / sort / view-data 任何一步出错,画面都会直接坏掉 - -但这恰恰是好事,因为: - -1. 第一张图就能真实暴露架构问题 -2. 不会被“用一个简化假样本先糊过去”的临时路线误导 - ---- - -## 8. 本轮完成标志 - -当以下条件同时成立,这份计划才算完成: - -1. `GaussianSplatRendererComponent` 已存在且被 `SceneRenderer` 正式消费 -2. `RenderSceneData.visibleGaussianSplats` 已成为正式帧数据 -3. `BuiltinGaussianSplatPass` 已正式接入 `BuiltinForwardPipeline` -4. `room.ply` 能在 `tests/Rendering/integration/gaussian_splat_scene` 中稳定渲染 -5. 至少 D3D12 / OpenGL / Vulkan 的 GT 回归通过 -6. 现有 `gaussian_splat_tests` 与相关 rendering tests 不被破坏 -7. 不存在任何 render pass 直接读取 source `.ply` 的旁路 - ---- - -## 9. 一句话结论 - -3DGS 的下一步不是直接去 editor 里“先看见再说”,而是先在 renderer 正式增加一条 `GaussianSplatRendererComponent -> VisibleGaussianSplatItem -> BuiltinGaussianSplatPass -> rendering integration GT` 的闭环路径; -只有这条闭环成立,后面的 editor 显示、选择、材质面板和 SRP 演进才有可靠基础。 diff --git a/docs/used/API文档增量回归并行任务板_2026-04-10_晚间.md b/docs/used/API文档增量回归并行任务板_2026-04-10_晚间.md deleted file mode 100644 index f03ad477..00000000 --- a/docs/used/API文档增量回归并行任务板_2026-04-10_晚间.md +++ /dev/null @@ -1,75 +0,0 @@ -# API文档增量回归并行任务板(2026-04-10,晚间) - -## 当前基线 - -- 截至 `2026-04-10` 晚间,`python -B docs/api/_tools/audit_api_docs.py` 已恢复全绿: - - `Invalid header refs = 0` - - `Invalid source refs = 0` - - `Broken .md links = 0` - - `Missing directory index pages = 0` -- 最近已经收口并推送的高频变动区包括: - - `RHI`:`CreateBuffer(...)` 初始数据重载、`SetSampleQuality` - - `Rendering`:`RenderSurface`、`RenderPassContext`、`BuiltinFinalColorPass`、`BuiltinColorScalePostProcessPass`、`BuiltinInfiniteGridPass` - - `Rendering/Passes`:`BuiltinObjectIdPass`、`BuiltinObjectIdOutlinePass`、`BuiltinSelectionOutlinePass`、`BuiltinVolumetricPass` - - `UI/Resources`:`UISelectionModel`、`Resources.h` umbrella 页 -- 当前主问题已经从“大面积补缺页”转成“增量语义回归 + 历史空目录清理 + 任务板状态同步”。 - -## 并行认领规则 - -- 一个任务块只允许一个会话认领。 -- 每个任务块必须同时处理: - - 目标页 - - 所属模块索引页 - - 相关交叉链接 -- 删除任何目录或历史页前,先执行: - - `rg -n "<名称>" docs/api docs/plan docs/used` -- 每完成一个任务块后都要执行: - - `python -B docs/api/_tools/audit_api_docs.py` -- 每完成一个阶段都要立即: - - `git commit` - - `git push` - -## 当前并发热点 - -以下代码区域仍在持续波动,认领相关文档时默认按 `high-risk` 处理: - -- `editor/src/Viewport/**` -- `engine/include/XCEngine/Rendering/Passes/**` -- `engine/include/XCEngine/Rendering/Planning/**` -- `engine/src/Rendering/Caches/**` -- `new_editor/include/XCEditor/**` - -## 并行任务块 - -| ID | 范围 | 目标改动 | 主要路径 | 风险 | 状态 | 备注 | -|----|------|----------|----------|------|------|------| -| `N1` | Canonical 历史空目录清理 | 复核并清理已确认无源码对应、且无文档反向引用的历史空目录 / 空入口 | `docs/api/XCEngine/**` | `medium` | `pending` | 优先接旧任务板里的 `S9 / G1` 收尾 | -| `N2` | Rendering / Planning & Caches 增量回归 | 对齐 `CameraRenderRequest`、`SceneRenderer`、`FullscreenPassSurfaceCache`、`RenderResourceCache` 在 `sourceSurface` / `sourceColorState` / sample desc 上的新语义 | `docs/api/XCEngine/Rendering/Planning/**`, `docs/api/XCEngine/Rendering/Caches/**` | `high-risk` | `pending` | 适合拆成 1 个小提交 | -| `N3` | Editor / Viewport 增量回归 | 对齐 `SceneViewportSelectionOutlinePass`、overlay builder / hit tester / resource paths 等近期重构后的 API 说明 | `docs/api/XCEngine/Editor/Viewport/**`, `editor/src/Viewport/**` | `high-risk` | `pending` | 当前最容易和别的会话冲突 | -| `N4` | XCEditor / new_editor 壳层回归 | 检查 `Collections`、`Shell` 下近期重构的 dock host / panel frame / viewport slot / tab strip 文档是否仍匹配真实接口 | `docs/api/XCEditor/**`, `new_editor/include/XCEditor/**` | `high-risk` | `pending` | 适合单独开一个高上下文会话 | -| `N5` | Core/Asset 与 Resources 入口复核 | 复核 `AssetDatabase`、`ResourceManager`、`Resources`、`Model`、`GaussianSplat`、`Volume` 入口页的模块级口径是否一致 | `docs/api/XCEngine/Core/Asset/**`, `docs/api/XCEngine/Resources/**` | `medium` | `pending` | 低冲突,适合并行 | -| `N6` | 最终状态回写 | 在本轮增量收口后更新 `rebuild-status`、任务板状态和必要的归档链接 | `docs/api/_meta/rebuild-status.md`, `docs/plan/**`, `docs/used/**` | `low` | `pending` | 必须最后做 | - -## 推荐顺序 - -1. `N2` -2. `N5` -3. `N4` -4. `N3` -5. `N1` -6. `N6` - -## 验收口径 - -- 文档描述必须以当前工作树源码为准,不沿用旧目录结构或旧后端限定说法。 -- 不允许把“补充说明”叠在已经过时的主体段落上;发现旧段落和新行为冲突时,应直接重写主体页。 -- 新增或改写后的页要优先保证: - - 公开签名正确 - - 生命周期 / 状态切换语义正确 - - 真实调用链或测试锚点可追溯 -- 最终仍需保持审计全绿。 - -## 备注 - -- 旧任务板可继续作为历史依据参考,但新的增量认领统一以本文件为准。 -- 如果执行过程中又有别的会话提前提交了同一批文档,先检查 `git log --oneline -n 6`,确认是否已经收口,再决定是否继续接下一块。 diff --git a/docs/used/API文档实时同步任务池_2026-04-03.md b/docs/used/API文档实时同步任务池_2026-04-03.md deleted file mode 100644 index 2f02af0e..00000000 --- a/docs/used/API文档实时同步任务池_2026-04-03.md +++ /dev/null @@ -1,911 +0,0 @@ -# API文档实时同步任务池(2026-04-03,第三轮) - -## 文档定位 - -这份任务池接替已归档的第二轮计划,现已转为归档基线: - -- `docs/used/API文档实时同步任务池_2026-04-03_第二轮归档.md` - -当前活跃 API 工作请优先查看: - -- `docs/plan/API文档目录*.md` - -前两轮已经完成的重点包括: - -- canonical API 目录结构收口 -- 大批缺页补齐 -- 多轮模板页清理与内容重写 -- 结构层审计修复到全绿 - -第三轮不再以“结构补齐”为主,而是专门处理“源码/测试已经变化,但 API 文档内容还没有跟上”的增量漂移。 - -## 当前复核快照 - -- 最近一次结构审计时间:`2026-04-10 17:07:09` -- 审计命令:`python -B docs/api/_tools/audit_api_docs.py` -- 当前结果: - - `Public headers: 384` - - `Editor source headers: 144` - - `Valid header refs (canonical): 384` - - `Invalid header refs: 0` - - `Invalid source refs: 0` - - `Valid source refs (Editor canonical): 144` - - `Broken .md links: 0` - - `Missing directory index pages: 0` - - `Stale canonical doc tokens: 0` - - `Stale editor doc tokens: 0` - - `Stale editor canonical pages: 0` - - `Editor high-risk single-page dirs: 0` - -这说明当前 canonical public header 覆盖与全部 Editor source-backed API 都已跟上重大重构后的最新头文件集;在 `Volume`、`Selection/Volumetric Passes`、`UI/Widgets Helpers` 与 Editor helper/source-backed 页面收口后,本轮新增进入审计口径的 `XCEditor`、`Resources/Model` 与 `Resources/GaussianSplat` 入口也已补齐。 - -### 已复核、暂不新开任务的区域 - -- `Resources/Material` / `RenderMaterialUtility` / `BuiltinForwardPipeline` -- `Components/MeshFilterComponent` / `MeshRendererComponent` -- `Scene` 中 builtin mesh / material 路径 round-trip -- `Components/GameObject` 辅助访问器与层级辅助页 - -这些区域本轮已对照当前源码和测试抽查,暂时没有发现新的明确失配。 - -## 认领规则 - -- 一次只认领 `1` 个任务块。 -- 先把 `状态` 改成 `DOING`,再写 `认领人`。 -- 只允许修改自己任务块声明的 `写入范围`。 -- 所有改动都必须基于“当前头文件 + 当前实现 + 当前测试 + 当前真实调用链”。 -- 如果需要同步 `_guides` 或模块总览页,必须在对应任务块内明确列出,避免多人撞写。 -- 做完后必须补一次最小复核,至少确认相关链接和交叉引用没有坏掉。 - -## 任务池 - -## T01 Core / Asset `AssetDatabase` 显式重导入接口补页 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P0` -- 写入范围: - - `docs/api/XCEngine/Core/Asset/AssetDatabase/**` -- 主要源码依据: - - `engine/include/XCEngine/Core/Asset/AssetDatabase.h` - - `engine/src/Core/Asset/AssetDatabase.cpp` - - `tests/Core/Asset/test_resource_manager.cpp` -- 已确认问题: - - 头文件已新增 `TryGetImportableResourceType()`、`ReimportAsset()`、`ReimportAllAssets()`,但 canonical 目录下没有对应方法页。 - - `AssetDatabase.md` 的公开方法表仍停在旧集合,没有把“可导入类型探测 / 单资产重导入 / 批量重导入”纳入公开能力。 - - `kCurrentImporterVersion` 已升到 `5`,总览页需要同步当前 importer 版本口径。 -- 产出要求: - - 新增 `TryGetImportableResourceType.md`、`ReimportAsset.md`、`ReimportAllAssets.md`。 - - 更新 `AssetDatabase.md`,必要时同步 `ResolvedAsset.md` 的交叉引用。 - - 文档必须写清以下真实边界: - - 只接受项目 `Assets/...` 内、存在且非目录的 source asset。 - - `TryGetImportableResourceType()` 只返回当前 importer 的 primary `ResourceType`;`Unknown` 直接失败。 - - `ReimportAsset()` 会重建单个 artifact、更新 source/artifact DB,并返回可直接消费的 `ResolvedAsset`。 - - `ReimportAllAssets()` 会遍历全部可导入 source record;单个条目失败不会中断全量循环,但最终返回总体成功位。 - -## T02 Core / Asset `AssetImportService` Library 工具接口与 `ImportedAsset` 语义同步 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P0` -- 写入范围: - - `docs/api/XCEngine/Core/Asset/AssetImportService/**` -- 主要源码依据: - - `engine/include/XCEngine/Core/Asset/AssetImportService.h` - - `engine/src/Core/Asset/AssetImportService.cpp` - - `tests/Core/Asset/test_resource_manager.cpp` -- 已确认问题: - - 缺少 `ClearLibraryCache.md`、`ReimportAllAssets.md`、`ReimportAsset.md`、`TryGetImportableResourceType.md`。 - - `AssetImportService.md` 还没有把这批显式工具接口纳入生命周期和能力说明。 - - `ImportedAsset` 页虽然已存在,但需要按当前 `ConvertResolvedAsset()` 口径复核 `runtimeLoadPath`、`artifactDirectory`、`mainLocalID` 和成功路径语义。 -- 产出要求: - - 新增缺失方法页,并更新 `AssetImportService.md`、必要时同步 `ImportedAsset.md` 与 `EnsureArtifact.md`。 - - 文档必须写清以下真实边界: - - `ClearLibraryCache()` 会关停数据库、删除整个 `Library` 目录、再重新初始化;它本身不会批量重导入所有资产。 - - `RebuildLibraryCache()` 等于 `ClearLibraryCache() + ReimportAllAssets()`。 - - `ReimportAsset()` 会先 `Refresh()`,再强制重导入指定路径,返回 `ImportedAsset`。 - - `TryGetImportableResourceType()` 只是服务层转发;项目根无效时必须返回失败并把输出设为 `Unknown`。 - - `AssetImportService_Test.RebuildLibraryCacheKeepsStableAssetRefs` 证明:重建 `Library` 不应破坏现有 `.meta` 驱动的稳定 `AssetRef`。 - -## T03 Core / Asset `ResourceManager` 项目资产工具入口同步 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P0` -- 写入范围: - - `docs/api/XCEngine/Core/Asset/ResourceManager/**` -- 主要源码依据: - - `engine/include/XCEngine/Core/Asset/ResourceManager.h` - - `engine/src/Core/Asset/ResourceManager.cpp` - - `tests/Core/Asset/test_resource_manager.cpp` -- 已确认问题: - - 缺少 `CanReimportProjectAsset.md`、`ReimportProjectAsset.md`、`ClearProjectLibraryCache.md`。 - - `ResourceManager.md` 还没有覆盖当前项目资产工具入口,也没有把这些接口和 `UnloadAll()` / `ProjectAssetIndex::RefreshFrom()` 的关系写清。 - - `RebuildProjectAssetCache.md`、`GetProjectLibraryRoot.md` 需要和新接口形成正确对比与交叉链接。 -- 产出要求: - - 新增缺失方法页,更新 `ResourceManager.md`,必要时补交叉链接。 - - 文档必须写清以下真实边界: - - 这批接口都要求 `m_resourceRoot` 非空;否则直接失败。 - - `CanReimportProjectAsset()` 只做“当前路径是否可导入”的无副作用判断。 - - `ReimportProjectAsset()` 会先 `UnloadAll()`,再重导入单路径资产,随后刷新 `ProjectAssetIndex`,成功时补记 `RememberResolvedPath(...)`。 - - `ClearProjectLibraryCache()` 会先卸载运行时资源,再清空 `Library`,然后刷新 snapshot,但不会自动批量重导入。 - - `ResourceManager_Test.ReimportProjectAssetBuildsArtifactForSelectedPath` 与 `RebuildProjectAssetCacheRefreshesLookupState` 是当前最直接的行为锚点。 - -## T04 Scripting / Mono `[SerializeField] private` 字段发现与持久化语义同步 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P0` -- 写入范围: - - `docs/api/XCEngine/Scripting/Mono/Mono.md` - - `docs/api/XCEngine/Scripting/Mono/MonoScriptRuntime/**` - - `docs/api/_guides/Scripting/Scripting-Runtime-And-Field-Model.md` - - `docs/api/_guides/Scripting/Project-Script-Assembly-And-Field-Sync.md` -- 主要源码依据: - - `engine/include/XCEngine/Scripting/Mono/MonoScriptRuntime.h` - - `engine/src/Scripting/Mono/MonoScriptRuntime.cpp` - - `managed/XCEngine.ScriptCore/SerializeField.cs` - - `managed/GameScripts/FieldMetadataProbe.cs` - - `managed/GameScripts/SerializeFieldProbe.cs` - - `tests/Scripting/test_mono_script_runtime.cpp` - - `tests/Scripting/test_project_script_assembly.cpp` -- 已确认问题: - - `Mono.md`、`TryGetClassFieldMetadata.md`、两篇 scripting guide 仍在沿用“只收录 public 实例字段”的旧口径。 - - 当前真实实现已经变成: - - 过滤掉 `static` / `literal` / `init-only` - - 然后接受“public 字段”或“标了 `[SerializeField]` 的 private 字段” - - 最后再按支持类型过滤 - - 测试已经明确覆盖 `HiddenFlag`、`HiddenCounter`、`HiddenEnabled` 这类 `[SerializeField] private` 字段的元数据发现、默认值读取、运行时写回与场景 round-trip。 - - 未标 `[SerializeField]` 的 private 字段仍应保持忽略,但当前文档没有把这条边界讲清。 -- 产出要求: - - 至少同步 `Mono.md`、`MonoScriptRuntime.md`、`TryGetClassFieldMetadata.md`、`TryGetClassFieldDefaultValues.md`。 - - 两篇 guide 需要补上 Unity 风格设计解释: - - 为什么支持 `[SerializeField] private` - - 这样做对 Inspector、字段持久化、重构安全性有什么好处 - - 哪些 private 字段仍不会进序列化层 - - 文档必须明确当前排除项: - - `static` - - `const/literal` - - `readonly/init-only` - - 不支持的字段类型 - - 未标 `[SerializeField]` 的 private 字段 - -## T05 Editor / Viewport `SceneViewportOverlayBuilder` provider-registry 口径同步 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P1` -- 写入范围: - - `docs/api/XCEngine/Editor/Viewport/SceneViewportOverlayBuilder/**` - - `docs/api/XCEngine/Editor/Viewport/Viewport.md` - - `docs/api/XCEngine/Editor/Viewport/ViewportHostService/**` -- 主要源码依据: - - `editor/src/Viewport/SceneViewportOverlayBuilder.h` - - `editor/src/Viewport/SceneViewportOverlayBuilder.cpp` - - `editor/src/Viewport/SceneViewportOverlayProviders.h` - - `editor/src/Viewport/SceneViewportOverlayProviders.cpp` - - `editor/src/Viewport/ViewportHostService.h` - - `tests/Editor/test_scene_viewport_overlay_providers.cpp` -- 已确认问题: - - `SceneViewportOverlayBuilder.md` 与 `Build.md` 仍沿用旧版“静态 / 无状态 / 单体 builder”口径,没有同步当前实例 builder + provider registry 模型。 - - `SceneViewportOverlayBuilder` 目录缺少 `Constructor.md` 与 `GetProviderRegistry.md`,导致新公开入口没有 canonical 页面。 - - `Viewport.md`、`ViewportHostService.md` 与 `SceneView-Overlay-Frame-Data.md` 仍把基础 editor overlay 的来源写成 `SceneViewportOverlayBuilder::Build(...)`,没有同步宿主当前持有成员 `m_sceneViewportOverlayBuilder` 且默认聚合 camera / light provider 的事实。 -- 完成记录: - - 已重写 `SceneViewportOverlayBuilder.md` 与 `Build.md`,改成当前实例 builder + registry 分发语义。 - - 已新增 `Constructor.md` 与 `GetProviderRegistry.md`。 - - 已同步 `Viewport.md`、`ViewportHostService.md` 与 `SceneView-Overlay-Frame-Data.md` 的上层调用链表述。 - - 已重新运行 `python -B docs/api/_tools/audit_api_docs.py`,并确认 `Broken .md links: 0`、`Stale editor doc tokens: 0`、`Stale editor canonical pages: 0`。 - -## T06 Editor / Viewport HUD / interaction-actions / canonical overlay-state 口径同步 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P1` -- 写入范围: - - `docs/api/XCEngine/Editor/Viewport/Viewport.md` - - `docs/api/XCEngine/Editor/Viewport/IViewportHostService/**` - - `docs/api/XCEngine/Editor/Viewport/ViewportHostService/**` - - `docs/api/XCEngine/Editor/Viewport/SceneViewportHudOverlay/**` - - `docs/api/XCEngine/Editor/Viewport/SceneViewportInteractionResolver/**` - - `docs/api/XCEngine/Editor/Viewport/SceneViewportInteractionActions/**` - - `docs/api/XCEngine/Editor/Viewport/SceneViewportTransformGizmoCoordinator/**` - - `docs/api/XCEngine/Editor/Viewport/SceneViewportOverlayBuilder/**` - - `docs/api/XCEngine/Editor/Viewport/SceneViewportOverlayProviders/**` - - `docs/api/XCEngine/Editor/Viewport/SceneViewportEditorOverlayData/**` - - `docs/api/XCEngine/Editor/Viewport/SceneViewportOverlayHitTester/**` - - `docs/api/XCEngine/Editor/Viewport/Passes/SceneViewportEditorOverlayPass/**` - - `docs/api/XCEngine/Editor/panels/SceneViewPanel/**` - - `docs/api/XCEngine/Editor/Viewport/SceneViewportOverlayHandleBuilder/**` - - `docs/api/XCEngine/Editor/Viewport/SceneViewportMoveGizmo/**` - - `docs/api/XCEngine/Editor/Viewport/SceneViewportRotateGizmo/**` - - `docs/api/XCEngine/Editor/Viewport/SceneViewportScaleGizmo/**` -- 主要源码依据: - - `editor/src/Viewport/SceneViewportHudOverlay.h` - - `editor/src/Viewport/SceneViewportHudOverlay.cpp` - - `editor/src/Viewport/SceneViewportInteractionResolver.h` - - `editor/src/Viewport/SceneViewportInteractionResolver.cpp` - - `editor/src/Viewport/SceneViewportInteractionActions.h` - - `editor/src/Viewport/SceneViewportInteractionActions.cpp` - - `editor/src/Viewport/SceneViewportTransformGizmoCoordinator.h` - - `editor/src/Viewport/SceneViewportTransformGizmoCoordinator.cpp` - - `editor/src/Viewport/IViewportHostService.h` - - `editor/src/Viewport/ViewportHostService.h` - - `editor/src/Viewport/SceneViewportOverlayBuilder.h` - - `editor/src/Viewport/SceneViewportOverlayProviders.h` - - `editor/src/Viewport/SceneViewportOverlayProviders.cpp` - - `editor/src/Panels/SceneViewPanel.cpp` - - `editor/src/Viewport/SceneViewportOverlayHandleBuilder.h` - - `editor/src/Viewport/SceneViewportMoveGizmo.h` - - `editor/src/Viewport/SceneViewportRotateGizmo.h` - - `editor/src/Viewport/SceneViewportScaleGizmo.h` - - `tests/Editor/test_scene_viewport_interaction_actions.cpp` - - `tests/Editor/test_scene_viewport_transform_gizmo_coordinator.cpp` - - `tests/Editor/test_scene_viewport_interaction_resolver.cpp` - - `tests/Editor/test_scene_viewport_overlay_providers.cpp` -- 已确认问题: - - `IViewportHostService` / `ViewportHostService` / `SceneViewPanel` 文档仍在使用 `GetSceneViewInteractionOverlayFrameData(...)` 与 `SetSceneViewTransientTransformGizmoOverlayData(...)` 的旧双轨口径,但当前真实接口只剩 `SetSceneViewTransformGizmoOverlayState(...)` + `GetSceneViewEditorOverlayFrameData(...)`。 - - `SceneViewportOverlayBuilder` / `SceneViewportOverlayProviders` 文档没有同步当前 optional `transformGizmoOverlayState`、`CreateSceneViewportTransformGizmoOverlayProvider()`,以及默认 registry 已注册 camera / light / transform gizmo provider 的事实。 - - `SceneViewportEditorOverlayData` / `SceneViewportEditorOverlayPass` / `SceneViewportOverlayHitTester` 文档仍把 canonical frame 当成“仅世界线和图标”,没有同步 `screenTriangles` 与 `handleRecords` 已并入统一 frame data。 - - 缺少 `SceneViewportInteractionActions.h` 的 canonical 目录,导致交互动作规范层没有文档入口。 - - 缺少 `SceneViewportTransformGizmoCoordinator.h` 的 canonical 目录,且 `SceneViewPanel` 文档没有同步当前 overlay submission / lifecycle command helper 链路。 - - move / rotate / scale gizmo 的 draw-data 与 state-query 页面仍在传播“面板直接把 `GetDrawData()` 变成临时 render overlay”的旧口径,未同步统一 gizmo state + provider 模式。 -- 完成记录: - - 已新增 `SceneViewportInteractionActions/**`,补齐 hover-state / click-action / dispatch API 页面。 - - 已新增 `SceneViewportTransformGizmoCoordinator/**`,补齐 overlay submission 与 gizmo lifecycle command 页面。 - - 已重写 `Viewport.md`、`IViewportHostService/**`、`ViewportHostService/**`、`SceneViewPanel/**`,统一到当前 `SetSceneViewTransformGizmoOverlayState(...)` + canonical `SceneViewportOverlayFrameData` 链路。 - - 已同步 `SceneViewportOverlayBuilder/**`、`SceneViewportOverlayProviders/**`、`SceneViewportEditorOverlayData/**`、`SceneViewportOverlayHitTester/**` 与 `SceneViewportEditorOverlayPass/**` 到当前 provider-registry + 统一 gizmo state 架构。 - - 已同步 move / rotate / scale gizmo 的 draw-data / state-query 页面到当前统一 state 消费口径。 - - 已重新运行 `python -B docs/api/_tools/audit_api_docs.py`,结果为:`Valid source refs (Editor canonical): 129`、`Invalid header refs: 0`、`Invalid source refs: 0`、`Broken .md links: 0`、`Stale editor canonical pages: 0`。 - -## T07 Scripting guide / 项目脚本程序集测试输出目录口径修正 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P1` -- 写入范围: - - `docs/api/_guides/Scripting/Project-Script-Assembly-And-Field-Sync.md` -- 主要源码依据: - - `managed/CMakeLists.txt` - - `tests/Scripting/CMakeLists.txt` - - `tests/Scripting/test_project_script_assembly.cpp` -- 已确认问题: - - guide 仍把 `tests/Scripting/test_project_script_assembly.cpp` 描述成“直接把 `assemblyDirectory` 指向 `project/Library/ScriptAssemblies`”。 - - 当前真实测试口径已经改成优先使用 `XCENGINE_TEST_PROJECT_MANAGED_OUTPUT_DIR`,由 `xcengine_test_project_managed_assemblies` target 提供测试专用输出目录;未配置时 fallback 到 `build/managed/ProjectScriptAssemblies`。 - - `project/Library/ScriptAssemblies/` 仍是 editor/runtime 项目程序集的真实输出目录,但它和测试专用输出目录不能混写成同一个概念。 -- 完成记录: - - 已改写 `Project-Script-Assembly-And-Field-Sync.md`,把“真实项目输出目录”和“测试专用输出目录”拆开描述。 - - 已明确 `test_project_script_assembly.cpp` 验证的是“项目 `Assets/**/*.cs` 被编译成可发现的 `GameScripts.dll`”这条链路,而不是直接依赖工作树里的 `project/Library/ScriptAssemblies/` 快照。 - - 已重新运行 `python -B docs/api/_tools/audit_api_docs.py`,结果为:`Public headers: 244`、`Editor source headers: 129`、`Broken .md links: 0`、`Stale canonical doc tokens: 0`、`Stale editor canonical pages: 0`。 - -## T08 Rendering / `RenderMaterialUtility` builtin-pass 元数据与资源绑定契约补页 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P1` -- 写入范围: - - `docs/api/XCEngine/Rendering/RenderMaterialUtility/**` - - `docs/api/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline/**` - - `docs/api/XCEngine/Rendering/Passes/BuiltinObjectIdPass/**` -- 主要源码依据: - - `engine/include/XCEngine/Rendering/RenderMaterialUtility.h` - - `engine/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp` - - `engine/src/Rendering/Passes/BuiltinObjectIdPass.cpp` - - `tests/Rendering/unit/test_builtin_forward_pipeline.cpp` - - `tests/Rendering/unit/test_render_scene_extractor.cpp` -- 已确认问题: - - `RenderMaterialUtility.h` 当前已经公开 `BuiltinMaterialPass`、binding-plan struct、legacy fallback 绑定构造和 builtin pass 元数据匹配 helper,但 canonical 文档树仍只覆盖旧的材质解析 / render-state 子集。 - - `MaterialConstantPayloadView` 与 `ResolveSchemaMaterialConstantPayload()` 文档仍停留在“只有 `data + size`”的旧口径,没有同步当前 `layout` 视图和 `layout.size == size` 的有效性约束。 - - `BuiltinForwardPipeline` 与 `BuiltinObjectIdPass` 文档虽然已经写到 explicit resource contract,但还没有把 canonical 入口回链到 `RenderMaterialUtility` 的 binding-plan helper。 -- 完成记录: - - 已新增 builtin pass 元数据 / 资源绑定契约页面,覆盖 `BuiltinMaterialPass`、`BuiltinPassResourceBindingPlan`、`TryBuildBuiltinPassResourceBindingPlan()`、legacy fallback 绑定构造与 shader-pass 元数据匹配 helper。 - - 已新增 `MaterialConstantLayoutView.md`、`FindShaderPropertyBySemantic.md`,并同步 `MaterialConstantPayloadView.md`、`ResolveSchemaMaterialConstantPayload.md` 到当前 layout-aware 语义。 - - 已重写 `RenderMaterialUtility.md` 的公开类型 / 函数索引,把 builtin pass 规范化与 binding-plan 契约纳入模块总览。 - - 已同步 `BuiltinForwardPipeline.md` 与 `BuiltinObjectIdPass.md` 到当前 explicit binding-plan + legacy fallback 口径。 - -## T09 Rendering / `RenderMaterialUtility` 顶层 helper completeness 补页 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P1` -- 写入范围: - - `docs/api/XCEngine/Rendering/RenderMaterialUtility/**` -- 主要源码依据: - - `engine/include/XCEngine/Rendering/RenderMaterialUtility.h` - - `engine/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp` - - `tests/Rendering/unit/test_builtin_forward_pipeline.cpp` - - `tests/Rendering/unit/test_render_scene_extractor.cpp` -- 已确认问题: - - `RenderMaterialUtility.h` 的顶层公开符号里,`IsForwardPassName()` 等 pass-name alias helper、`ToRHI*` 状态映射 helper,以及 `MaterialRenderStateHash` 仍无对应 canonical 页面。 - - `RenderMaterialUtility.md` 的索引也没有把这批顶层 helper 纳入公开函数 / 类型列表,导致文档目录和实际公开符号仍有一层颗粒度缺口。 -- 完成记录: - - 已新增 `IsForwardPassName.md`、`IsUnlitPassName.md`、`IsDepthOnlyPassName.md`、`IsShadowCasterPassName.md`、`IsObjectIdPassName.md`。 - - 已新增 `ToRHICullMode.md`、`ToRHIComparisonFunc.md`、`ToRHIBlendFactor.md`、`ToRHIBlendOp.md` 与 `MaterialRenderStateHash.md`。 - - 已同步 `RenderMaterialUtility.md`,把这批 helper 和 hash functor 纳入模块索引。 - - 已对照 `RenderMaterialUtility.h` 的顶层公开类型 / 函数名,确认当前 canonical 页已全覆盖。 - -## T10 Active API docs / 测试路径大小写按真实目录统一 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P1` -- 写入范围: - - `docs/api/_guides/**` - - `docs/api/XCEngine/**` -- 主要源码依据: - - `tests/` -- 已确认问题: - - 活跃文档里曾残留 `tests/core/...`、`tests/math/...`、`tests/scripting/...` 这类小写路径。 - - 当前工作树真实目录名是 `tests/Core/`、`tests/Core/Math/`、`tests/Scripting/`;继续沿用小写写法会让文档和工程现实脱节。 - - 这类问题与此前已收口的 `tests/Editor/` 一样,属于 Windows 工作树里最容易被忽略、但会持续制造噪音的路径口径漂移。 -- 完成记录: - - 已把活跃 guide 与 canonical API 文档中的 `tests/Core/...`、`tests/Core/Math/...`、`tests/Scripting/...` 统一改成真实目录大小写。 - - 本轮原始执行阶段只收口活跃文档;当前归档根统一位于 `docs/used/`。 - -## T11 Editor / Viewport 新增 header helper 补页 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P1` -- 写入范围: - - `docs/api/XCEngine/Editor/Viewport/Viewport.md` - - `docs/api/XCEngine/Editor/Viewport/SceneViewportEditorModes/**` - - `docs/api/XCEngine/Editor/Viewport/SceneViewportInteractionFrame/**` - - `docs/api/XCEngine/Editor/Viewport/SceneViewportNavigation/**` - - `docs/api/_meta/rebuild-status.md` -- 主要源码依据: - - `editor/src/Viewport/SceneViewportEditorModes.h` - - `editor/src/Viewport/SceneViewportInteractionFrame.h` - - `editor/src/Viewport/SceneViewportNavigation.h` - - `editor/src/Panels/SceneViewPanel.cpp` -- 已确认问题: - - 工作树里新增了 `SceneViewportEditorModes.h`、`SceneViewportInteractionFrame.h`、`SceneViewportNavigation.h` 三个 editor source header,但 canonical 文档树还没有对应目录页。 - - `Viewport.md` 也没有把这三块新 helper 纳入当前职责拆分,导致 Scene View 交互装配和导航状态管理的真实入口在模块总览里缺席。 -- 完成记录: - - 已新增 `SceneViewportEditorModes/SceneViewportEditorModes.md`,收口 tool / pivot / transform-space 三类模式枚举。 - - 已新增 `SceneViewportInteractionFrame/SceneViewportInteractionFrame.md`,补齐 tool state、frame geometry、interaction frame state 与 resolve request 装配语义。 - - 已新增 `SceneViewportNavigation/SceneViewportNavigation.md`,补齐快捷键、look/pan 导航、capture flags 与 `SceneViewportInput` 构建逻辑。 - - 已补齐 `BuildSceneViewportTransformGizmoOverlayState.md`,并重写 `SceneViewPanel/**`、`SceneViewportOverlayHandleBuilder/**` 与 `SceneView-Interaction-And-Gizmo-Model.md` 的残留旧口径。 - - 已顺手修复 `BuildSceneViewportGridPassData.md` 指向旧页名的坏链接。 - - 已重新运行 `python -B docs/api/_tools/audit_api_docs.py`,确认 `Editor source headers: 133`、`Valid source refs (Editor canonical): 133`、`Invalid source refs: 0`、`Broken .md links: 0`。 - -## T12 Editor / Viewport `SceneViewportInteractionFrame` Phase 5G focus 与 presentation helper 同步 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P1` -- 写入范围: - - `docs/api/XCEngine/Editor/Viewport/SceneViewportInteractionFrame/**` - - `docs/api/XCEngine/Editor/Viewport/SceneViewportNavigation/SceneViewportNavigationUpdate.md` - - `docs/api/XCEngine/Editor/Viewport/Viewport.md` - - `docs/api/XCEngine/Editor/panels/SceneViewPanel/**` - - `docs/api/_meta/rebuild-status.md` -- 主要源码依据: - - `editor/src/Viewport/SceneViewportInteractionFrame.h` - - `editor/src/panels/SceneViewPanel.cpp` - - `tests/Editor/test_scene_viewport_interaction_frame.cpp` -- 已确认问题: - - `SceneViewportInteractionFrame.h` 当前已经新增 `ShouldFocusSceneViewportAfterInteraction()`、`SceneViewportPresentationRequest` 与 `RefreshAndDrawSceneViewportPresentation()`,但 canonical 目录下还没有对应页面,模块总览也仍停在“只做交互前 frame-state 装配”的旧口径。 - - `SceneViewportNavigationUpdate.md` 仍写成 `beginLookDrag` / `beginPanDrag` 直接决定 `ImGui::SetWindowFocus()`;当前真实逻辑已经改成由 `ShouldFocusSceneViewportAfterInteraction(...)` 统一折叠 tool command、interaction action 与 navigation begin 标志。 - - `Viewport.md` 与 `SceneViewPanel/**` 对 `SceneViewportInteractionFrame` 的职责描述还没有完整纳入 focus / presentation helper 这一层尾段语义。 -- 完成记录: - - 已新增 `ShouldFocusSceneViewportAfterInteraction.md`、`SceneViewportPresentationRequest.md` 与 `RefreshAndDrawSceneViewportPresentation.md`,补齐 `SceneViewportInteractionFrame.h` 的新增公开入口。 - - 已重写 `SceneViewportInteractionFrame.md`,把模块职责从“交互前 frame-state 装配”扩展到当前真实的 focus 决策与 presentation 尾段收口语义。 - - 已同步 `SceneViewportNavigationUpdate.md`、`Viewport.md`、`SceneViewPanel/Render.md` 与 `SceneViewPanel.md`,移除 `SetWindowFocus()` / presentation tail 的旧口径描述。 - - 已重新运行 `python -B docs/api/_tools/audit_api_docs.py`,确认 `Editor source headers: 133`、`Valid source refs (Editor canonical): 133`、`Invalid source refs: 0`、`Broken .md links: 0`、`Stale editor canonical pages: 0`。 - -## T13 Editor / Viewport `SceneViewportChrome` 与 `SceneViewPanel` 当前编排链补正 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P1` -- 写入范围: - - `docs/api/XCEngine/Editor/Viewport/SceneViewportChrome/**` - - `docs/api/XCEngine/Editor/panels/SceneViewPanel/**` - - `docs/api/_meta/rebuild-status.md` -- 主要源码依据: - - `editor/src/Viewport/SceneViewportChrome.h` - - `editor/src/Viewport/SceneViewportChrome.cpp` - - `editor/src/panels/SceneViewPanel.h` - - `editor/src/panels/SceneViewPanel.cpp` - - `tests/Editor/test_scene_viewport_chrome.cpp` -- 已确认问题: - - `SceneViewportChrome.md` 仍写成“没有独立单元测试”,但当前工作树已经存在 `tests/Editor/test_scene_viewport_chrome.cpp`,并覆盖工具命令折叠与执行规则。 - - `SceneViewPanel.md` 与 `Render.md` 仍残留旧的 helper 列表和编排口径,没有完整对齐当前真实调用顺序,包括工具命令执行、focus 决策以及 `RefreshAndDrawSceneViewportPresentation(...)` 收口。 -- 完成记录: - - 已修正 `SceneViewportChrome.md` 的测试锚点描述,明确区分“命令折叠规则已有单测”和“UI 绘制细节仍由源码调用链锚定”。 - - 已重写 `SceneViewPanel.md` 与 `Render.md`,按当前源码同步 `Chrome -> InteractionFrame -> Navigation -> InteractionActions -> Presentation` 的真实编排链。 - - 已重新运行 `python -B docs/api/_tools/audit_api_docs.py`,确认 `Editor source headers: 133`、`Valid source refs (Editor canonical): 133`、`Invalid source refs: 0`、`Broken .md links: 0`、`Stale editor canonical pages: 0`。 - -## T14 Core / Asset `ArtifactFormats` shader artifact 正文布局补页 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P1` -- 写入范围: - - `docs/api/XCEngine/Core/Asset/ArtifactFormats/ArtifactFormats.md` - - `docs/api/_meta/rebuild-status.md` -- 主要源码依据: - - `engine/include/XCEngine/Core/Asset/ArtifactFormats.h` - - `engine/src/Core/Asset/AssetDatabase.cpp` - - `engine/src/Resources/Shader/ShaderLoader.cpp` - - `tests/Resources/Shader/test_shader_loader.cpp` -- 已确认问题: - - `ArtifactFormats.md` 的 shader artifact 小节当前只列出了几种头结构名,没有像 material / mesh 一样写清真实正文布局,缺少 name/source-path 字符串、property 段、per-pass tag/resource/variant 段以及 `compiledBinary` payload 的写入顺序。 - - 同页“读取侧”当前漏写了 `.xcshader` 的真实消费者 `ShaderLoader`。 - - 写入侧对 shader 只写了“产出 shader artifact”,没有和当前实现保持同样的主 artifact 文件名口径 `main.xcshader`。 -- 完成记录: - - 已为 `ArtifactFormats.md` 的 shader artifact 小节补齐当前正文布局,明确名称/路径字符串、property 段、pass tag/resource/variant 段以及 `compiledBinary` payload 的写入顺序。 - - 已补充 shader artifact 的 schema 值、`main.xcshader` 主 artifact 文件名,以及读取侧 `ShaderLoader` 的真实消费链。 - - 已重新运行 `python -B docs/api/_tools/audit_api_docs.py`,确认 `Public headers: 244`、`Editor source headers: 133`、`Invalid header refs: 0`、`Invalid source refs: 0`、`Broken .md links: 0`、`Stale editor canonical pages: 0`。 - -## T15 Resources / Material `Material.h` 公开符号 completeness 补页 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P1` -- 写入范围: - - `docs/api/XCEngine/Resources/Material/Material/**` - - `docs/api/_meta/rebuild-status.md` -- 主要源码依据: - - `engine/include/XCEngine/Resources/Material/Material.h` - - `engine/src/Resources/Material/Material.cpp` - - `tests/Resources/Material/test_material.cpp` - - `tests/Resources/Material/test_material_loader.cpp` - - `tests/Rendering/unit/test_render_scene_extractor.cpp` -- 已确认问题: - - `Material.h` 当前已经公开 `renderQueue / renderState / shaderPass / tags / constant layout / change version` 这一整组运行时接口,但 canonical 文档树仍主要停留在“数值属性 + 纹理绑定”子集,缺少大量类型页与方法页。 - - 缺页不仅包括 `SetRenderQueue()`、`SetRenderState()`、`SetShaderPass()`、tag API、`GetConstantLayout()`、`FindConstantField()`、`GetChangeVersion()`、`RecalculateMemorySize()`,还包括 `MaterialRenderQueue`、`MaterialRenderState`、`MaterialConstantFieldDesc`、`MaterialTagEntry`、`PendingTextureLoadState` 等公开类型。 - - `Material/Material.md` 的声明索引也没有把这批 render metadata / tag / constant-layout 相关公开符号纳入模块总览。 -- 完成记录: - - 已补齐 `Material.h` 当前公开的 render metadata / tag / constant-layout 相关类型页与方法页,并重写 `Material.md` 的模块索引。 - - 已把 `renderQueue / renderState / shaderPass / tags / texture bindings / properties / constant layout / change version` 这一整组运行时接口同步到 canonical 文档树。 - - 已重新运行 `python -B docs/api/_tools/audit_api_docs.py`,确认 `Public headers: 247`、`Editor source headers: 136`、`Invalid header refs: 0`、`Invalid source refs: 0`、`Broken .md links: 0`。 - -## T16 Editor / Viewport `SceneViewportOverlaySpriteResources` 新增头文件补页 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P1` -- 写入范围: - - `docs/api/XCEngine/Editor/Viewport/SceneViewportOverlaySpriteResources/**` - - `docs/api/XCEngine/Editor/Viewport/Viewport.md` - - `docs/api/XCEngine/Editor/Viewport/SceneViewportEditorOverlayData/SceneViewportEditorOverlayData.md` - - `docs/api/XCEngine/Editor/Viewport/Passes/SceneViewportEditorOverlayPass/**` - - `docs/api/_meta/rebuild-status.md` -- 主要源码依据: - - `editor/src/Viewport/SceneViewportOverlaySpriteResources.h` - - `editor/src/Viewport/SceneViewportOverlaySpriteResources.cpp` - - `editor/src/Viewport/Passes/SceneViewportEditorOverlayPass.h` - - `editor/src/Viewport/Passes/SceneViewportEditorOverlayPass.cpp` - - `tests/Editor/test_scene_viewport_overlay_sprite_resources.cpp` -- 已确认问题: - - 工作树当前新增了 `SceneViewportOverlaySpriteResources.h`,但 canonical 文档树还没有对应目录页,导致结构审计一度出现 `Editor source headers: 136` / `Valid source refs (Editor canonical): 135`。 - - `SceneViewportEditorOverlayPass` 文档仍把 camera / light icon 纹理描述成 pass 内部细节,没有同步当前已经拆出的“资源路径解析 + RGBA 解码 + descriptor set 缓存”辅助层。 - - `SceneViewportOverlaySpriteTextureKind` 当前已经不再直接隐含具体文件路径;真实资源解析口径来自新头文件里的 helper 与 cache。 -- 完成记录: - - 已新增 `SceneViewportOverlaySpriteResources` canonical 目录,补齐 texture-kind 映射、asset spec、pixel payload、像素加载与 GPU 资源缓存页面。 - - 已同步 `Viewport.md`、`SceneViewportEditorOverlayData.md` 与 `SceneViewportEditorOverlayPass/**`,把 sprite overlay 资源准备链路改写为当前真实的 helper-header + cache 模型。 - - 已重新运行 `python -B docs/api/_tools/audit_api_docs.py`,确认 `Public headers: 247`、`Editor source headers: 136`、`Valid source refs (Editor canonical): 136`、`Invalid source refs: 0`、`Broken .md links: 0`。 - -## T17 Editor / Viewport `SceneViewportShaderPaths` 兼容层化与 pass-spec wrapper 类型口径收口 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P1` -- 写入范围: - - `docs/api/XCEngine/Editor/Viewport/SceneViewportShaderPaths/**` - - `docs/api/XCEngine/Editor/Viewport/ViewportHostRenderFlowUtils/**` - - `docs/api/XCEngine/Editor/Viewport/SceneViewportRenderPlan/**` - - `docs/api/XCEngine/Editor/Viewport/Passes/SceneViewportGridPass/**` - - `docs/api/XCEngine/Editor/Viewport/Passes/SceneViewportSelectionOutlinePass/**` - - `docs/api/XCEngine/Editor/Viewport/Passes/SceneViewportEditorOverlayPass/**` - - `docs/api/XCEngine/Resources/BuiltinResources/BuiltinResources.md` - - `docs/api/_meta/rebuild-status.md` -- 主要源码依据: - - `editor/src/Viewport/SceneViewportShaderPaths.h` - - `editor/src/Viewport/SceneViewportResourcePaths.h` - - `editor/src/Viewport/SceneViewportPassSpecs.h` - - `editor/src/Viewport/ViewportHostRenderFlowUtils.h` - - `editor/src/Viewport/SceneViewportRenderPlan.h` - - `editor/src/Viewport/Passes/SceneViewportGridPass.h` - - `editor/src/Viewport/Passes/SceneViewportGridPass.cpp` - - `editor/src/Viewport/Passes/SceneViewportSelectionOutlinePass.h` - - `editor/src/Viewport/Passes/SceneViewportSelectionOutlinePass.cpp` - - `editor/src/Viewport/Passes/SceneViewportEditorOverlayPass.h` - - `editor/src/Viewport/Passes/SceneViewportEditorOverlayPass.cpp` - - `engine/include/XCEngine/Resources/BuiltinResources.h` - - `tests/Editor/test_scene_viewport_shader_paths.cpp` - - `tests/Editor/test_viewport_render_flow_utils.cpp` -- 已确认问题: - - `SceneViewportShaderPaths.h` 当前已经退化为只包含 `SceneViewportResourcePaths.h` 的兼容头,但部分文档仍把它写成路径 helper 的真实 owner。 - - `BuildSceneViewportGridPassData()`、`BuildSceneViewportSelectionOutlineStyle()`、两类 factory alias,以及 grid / selection-outline pass 适配页仍沿用旧签名,把 editor wrapper 类型写成 runtime `InfiniteGridPassData` / `ObjectIdOutlineStyle`。 - - `SceneViewportEditorOverlayPass` 与 `BuiltinResources.md` 仍残留一部分旧口径,没有完全同步 sprite resource cache 和新的 builtin shader 集合。 -- 完成记录: - - 已把 `SceneViewportShaderPaths/**` 收口为“兼容 include 层 + 迁移说明”口径,并统一回链当前真实 owner `SceneViewportResourcePaths`。 - - 已同步 `ViewportHostRenderFlowUtils/**`、`SceneViewportRenderPlan/**`、`SceneViewportGridPass/**` 与 `SceneViewportSelectionOutlinePass/**` 到当前 `SceneViewportGridPassData` / `SceneViewportSelectionOutlineStyle` wrapper 类型,以及 `ToBuiltin...` 转换链。 - - 已补充 `SceneViewportEditorOverlayPass/**` 对 `SceneViewportOverlaySpriteResourceCache` 的引用,并把 `BuiltinResources.md` 改到当前 builtin shader 集合 `forward-lit / unlit / depth-only / shadow-caster / object-id`。 - - 已重新运行 `python -B docs/api/_tools/audit_api_docs.py`,确认 `Public headers: 247`、`Editor source headers: 136`、`Valid header refs (canonical): 247`、`Valid source refs (Editor canonical): 136`、`Broken .md links: 0`。 - -## T18 Rendering / `CameraRenderer` depth-only 与 shadow-caster pass 注入链补页 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P1` -- 写入范围: - - `docs/api/XCEngine/Rendering/CameraRenderer/**` -- 主要源码依据: - - `engine/include/XCEngine/Rendering/CameraRenderer.h` - - `engine/src/Rendering/CameraRenderer.cpp` - - `tests/Rendering/unit/test_camera_scene_renderer.cpp` -- 已确认问题: - - `CameraRenderer.h` 当前已经公开 `SetDepthOnlyPass()`、`SetShadowCasterPass()`、`GetDepthOnlyPass()`、`GetShadowCasterPass()`,但 canonical 文档树还停留在只有主管线与 object-id pass 的旧索引。 - - `Constructor.md` 仍把最高阶构造重载写成只有 `(pipeline, objectIdPass)`,没有同步当前 `depthOnlyPass / shadowCasterPass` 注入点。 - - `Render.md` 仍把执行链写成 `pre -> pipeline -> object-id -> post -> overlay`,没有同步当前先跑 `shadowCaster` 和 `depthOnly` 请求的真实顺序。 -- 完成记录: - - 已补齐 `SetDepthOnlyPass.md`、`SetShadowCasterPass.md`、`GetDepthOnlyPass.md`、`GetShadowCasterPass.md`。 - - 已重写 `CameraRenderer.md`、`Constructor.md`、`Render.md` 与 `Destructor.md`,同步当前 depth-only / shadow-caster pass 的持有、回退与执行链语义。 - - 已重新运行 `python -B docs/api/_tools/audit_api_docs.py`,确认 `Public headers: 247`、`Broken .md links: 0`、`Stale canonical doc tokens: 0`。 - -## T19 Editor / Viewport `SceneViewportOverlayFrameCache` 新增头文件补页 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P1` -- 写入范围: - - `docs/api/XCEngine/Editor/Viewport/SceneViewportOverlayFrameCache/**` - - `docs/api/XCEngine/Editor/Viewport/Viewport.md` - - `docs/api/XCEngine/Editor/Viewport/ViewportHostService/SceneView-Overlay-Frame-Data.md` - - `docs/api/_meta/rebuild-status.md` -- 主要源码依据: - - `editor/src/Viewport/SceneViewportOverlayFrameCache.h` - - `editor/src/Viewport/SceneViewportOverlayFrameCache.cpp` - - `editor/src/Viewport/ViewportHostService.h` - - `tests/Editor/test_scene_viewport_overlay_frame_cache.cpp` -- 已确认问题: - - 工作树新增了 `SceneViewportOverlayFrameCache.h`,但 canonical 文档树没有对应目录页,导致结构审计一度出现 `Editor source headers: 137` / `Valid source refs (Editor canonical): 136`。 - - `Viewport` 与 `ViewportHostService/SceneView-Overlay-Frame-Data.md` 仍在用“宿主类内部散落 cache key”的旧口径,没有把 viewport 尺寸解析、内容签名与重建判定收口到新 helper header。 -- 完成记录: - - 已新增 `SceneViewportOverlayFrameCache/**`,补齐 cache state、viewport 尺寸解析、内容签名、overlay 比较与重建判定页面。 - - 已同步 `Viewport.md` 与 `ViewportHostService/SceneView-Overlay-Frame-Data.md`,把 Scene View editor overlay frame 的缓存链路改写到当前 helper-header 模型。 - - 已重新运行 `python -B docs/api/_tools/audit_api_docs.py`,确认 `Public headers: 247`、`Editor source headers: 137`、`Valid source refs (Editor canonical): 137`、`Broken .md links: 0`。 - -## T20 Editor / Viewport `SceneViewportRenderPassBundle` 上层总览与 guide 口径同步 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P1` -- 写入范围: - - `docs/api/XCEngine/Editor/Viewport/Viewport.md` - - `docs/api/XCEngine/Editor/Viewport/ViewportHostService/**` - - `docs/api/XCEngine/Editor/Viewport/SceneViewportRenderPlan/**` - - `docs/api/XCEngine/Editor/Viewport/Passes/SceneViewportGridPass/SceneViewportGridPass.md` - - `docs/api/XCEngine/Editor/Viewport/Passes/SceneViewportSelectionOutlinePass/SceneViewportSelectionOutlinePass.md` - - `docs/api/_guides/Editor/Scene-Viewport-Render-Plan-And-Failure-Flow.md` - - `docs/api/_meta/rebuild-status.md` -- 主要源码依据: - - `editor/src/Viewport/ViewportHostService.h` - - `editor/src/Viewport/SceneViewportRenderPassBundle.h` - - `editor/src/Viewport/SceneViewportRenderPassBundle.cpp` - - `editor/src/Viewport/SceneViewportRenderPlan.h` - - `editor/src/Viewport/Passes/SceneViewportGridPass.h` - - `editor/src/Viewport/Passes/SceneViewportSelectionOutlinePass.h` - - `tests/Editor/test_scene_viewport_render_pass_bundle.cpp` - - `tests/Editor/test_viewport_render_flow_utils.cpp` -- 已确认问题: - - `SceneViewportRenderPassBundle` 的 canonical 页面虽然已经存在,但 `Viewport.md`、`ViewportHostService.md`、`Initialize-And-Shutdown.md`、`RenderRequestedViewports.md`、`SceneViewportRenderPlan.md` 与 Scene View render-plan guide 仍在传播旧口径,把宿主服务写成“直接持有三个 pass renderer”或“直接调用 `BuildSceneViewportRenderPlan(...)`”。 - - `SceneViewportGridPass.md`、`SceneViewportSelectionOutlinePass.md` 与 `SceneViewportGridPassFactory.md` 仍把 factory 绑定位置写成 `ViewportHostService` 上的旧 renderer 成员,而不是当前 bundle 内部成员。 - - `Viewport.md` 还没有把 `SceneViewportResourcePaths`、`SceneViewportPassSpecs`、`SceneViewportRenderPlan`、`SceneViewportRenderPassBundle` 与 `ViewportHostRenderFlowUtils` 纳入当前模块总览。 -- 完成记录: - - 已同步 `Viewport.md`、`ViewportHostService/**`、`SceneViewportRenderPlan/**` 与 Scene View render-plan guide 到当前真实链路:`ViewportHostService -> SceneViewportRenderPassBundle -> BuildSceneViewportRenderPlan / ApplySceneViewportRenderPlan`。 - - 已修正 grid / selection-outline 相关页面,把 factory 绑定和 renderer 生命周期表述改成 bundle 持有模型,并补上新的上层交叉链接。 - - 已重新运行 `python -B docs/api/_tools/audit_api_docs.py`,确认 `Public headers: 247`、`Editor source headers: 138`、`Valid source refs (Editor canonical): 138`、`Broken .md links: 0`、`Stale editor canonical pages: 0`。 - -## T21 XCUI / `Resources/UI` / `UI/Types` / `UI/DrawData` / Editor transition header 补页 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P0` -- 写入范围: - - `docs/api/XCEngine/Resources/UI/**` - - `docs/api/XCEngine/UI/UI.md` - - `docs/api/XCEngine/UI/Types/**` - - `docs/api/XCEngine/UI/DrawData/**` - - `docs/api/XCEngine/Editor/Editor.md` - - `docs/api/XCEngine/Editor/XCUIBackend/**` - - `docs/api/XCEngine/Editor/Platform/Platform.md` - - `docs/api/XCEngine/Editor/Platform/D3D12WindowRendererImGuiInterop/**` - - `docs/api/XCEngine/Editor/panels/panels.md` - - `docs/api/XCEngine/Editor/panels/XCUIDemoPanel/**` -- 主要源码依据: - - `engine/include/XCEngine/Resources/UI/UIDocumentTypes.h` - - `engine/include/XCEngine/Resources/UI/UIDocumentCompiler.h` - - `engine/include/XCEngine/Resources/UI/UIDocuments.h` - - `engine/include/XCEngine/Resources/UI/UIDocumentLoaders.h` - - `engine/src/Resources/UI/UIDocumentCompiler.cpp` - - `engine/src/Resources/UI/UIDocuments.cpp` - - `engine/src/Resources/UI/UIDocumentLoaders.cpp` - - `engine/include/XCEngine/UI/Types.h` - - `engine/include/XCEngine/UI/DrawData.h` - - `editor/src/XCUIBackend/ImGuiTransitionBackend.h` - - `editor/src/XCUIBackend/XCUIDemoRuntime.h` - - `editor/src/Platform/D3D12WindowRendererImGuiInterop.h` - - `editor/src/panels/XCUIDemoPanel.h` - - `editor/src/panels/XCUIDemoPanel.cpp` - - `editor/src/Application.cpp` - - `editor/src/Core/EditorWorkspace.h` - - `tests/Resources/UI/test_ui_document_loader.cpp` - - `tests/Resources/UI/test_ui_schema_document.cpp` - - `tests/UI/Core/unit/test_ui_core.cpp` - - `tests/Editor/test_xcui_imgui_transition_backend.cpp` - - `tests/Editor/test_window_renderer_api.cpp` -- 已确认问题: - - `Resources/UI` 模块总览已经存在,但四个 public header 仍缺少 canonical 页面。 - - `UI/Types.h` 与 `UI/DrawData.h` 已被 runtime、Scene 和 editor transition backend 大量使用,但根模块入口页还没有把它们纳入顶层 API。 - - `editor/src/XCUIBackend`、`editor/src/Platform/D3D12WindowRendererImGuiInterop.h` 与 `editor/src/panels/XCUIDemoPanel.h` 没有 source-backed API 页面,导致 Editor 侧 XCUI transition 链路存在结构缺口。 -- 完成记录: - - 已新增 `UIDocumentTypes`、`UIDocumentCompiler`、`UIDocumentLoaders`、`UIDocuments`、`Types`、`DrawData`、`XCUIBackend`、`ImGuiTransitionBackend`、`XCUIDemoRuntime`、`D3D12WindowRendererImGuiInterop`、`XCUIDemoPanel` 页面。 - - 已同步 `Resources/UI/UI.md`、`UI/UI.md`、`Editor.md`、`Platform.md` 与 `panels.md`,把 UI 资源层、XCUI transition backend 和 demo panel 纳入当前模块入口。 - - 已重新运行 `python -B docs/api/_tools/audit_api_docs.py`,确认 `Editor source headers: 142`、`Valid source refs (Editor canonical): 142`、`Invalid source refs: 0`、`Broken .md links: 0`。 - -## T22 RHI / Shader formalization 文档补页与 `MaterialRenderState` 归位 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P1` -- 写入范围: - - `docs/api/XCEngine/RHI/ShaderCompiler/**` - - `docs/api/XCEngine/Resources/Shader/**` - - `docs/api/XCEngine/Resources/Material/**` -- 主要源码依据: - - `engine/include/XCEngine/RHI/ShaderCompiler/SpirvShaderCompiler.h` - - `engine/src/RHI/ShaderCompiler/SpirvShaderCompiler.cpp` - - `engine/src/RHI/Vulkan/VulkanShaderCompiler.cpp` - - `engine/src/RHI/OpenGL/OpenGLDevice.cpp` - - `engine/include/XCEngine/Resources/Shader/ShaderKeywordTypes.h` - - `engine/src/Resources/Shader/ShaderLoader.cpp` - - `engine/src/Resources/Shader/Shader.cpp` - - `engine/include/XCEngine/Resources/Material/MaterialRenderState.h` - - `engine/src/Resources/Material/Material.cpp` - - `tests/Resources/Shader/test_shader_loader.cpp` - - `tests/Resources/Shader/test_shader.cpp` - - `tests/Resources/Material/test_material.cpp` - - `tests/Rendering/unit/test_render_scene_extractor.cpp` -- 已确认问题: - - `SpirvShaderCompiler.h` 与 `ShaderKeywordTypes.h` 已进入当前工作树,但 canonical API 树缺少对应入口页。 - - `MaterialRenderState` 及其相关枚举页仍沿用旧归属,`头文件` 元信息还停在 `Material.h`。 - - 复核过程中发现 `ShaderRenderState.h` 并不在当前 public header 集合里,说明不能把计划中的 formalized pass-state 设想误写成现有 API 页面。 -- 完成记录: - - 已新增 `SpirvShaderCompiler/SpirvShaderCompiler.md` 与 `ShaderKeywordTypes/ShaderKeywordTypes.md`,并同步 `ShaderCompiler.md`、`Resources/Shader/Shader.md`。 - - 已把 `MaterialRenderState`、`MaterialCullMode`、`MaterialComparisonFunc`、`MaterialBlendOp`、`MaterialBlendFactor` 的 `头文件` 归位到 `MaterialRenderState.h`,并更新 `Resources/Material/Material.md`。 - - 已删除误建的 `ShaderRenderState` canonical 页面,并重新运行审计,确认 `Public headers: 304`、`Invalid header refs: 0`、`Broken .md links: 0`。 - -## T23 UI / Core + Input header coverage 补页 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P0` -- 写入范围: - - `docs/api/XCEngine/UI/Core/**` - - `docs/api/XCEngine/UI/Input/**` - - `docs/api/XCEngine/UI/UI.md` -- 主要源码依据: - - `engine/include/XCEngine/UI/Core/UIBuildContext.h` - - `engine/include/XCEngine/UI/Core/UIContext.h` - - `engine/include/XCEngine/UI/Core/UIElementTree.h` - - `engine/include/XCEngine/UI/Core/UIInvalidation.h` - - `engine/include/XCEngine/UI/Core/UIViewModel.h` - - `engine/include/XCEngine/UI/Input/UIFocusController.h` - - `engine/include/XCEngine/UI/Input/UIInputDispatcher.h` - - `engine/include/XCEngine/UI/Input/UIInputPath.h` - - `engine/include/XCEngine/UI/Input/UIInputRouter.h` - - `engine/include/XCEngine/UI/Input/UIShortcutRegistry.h` - - `engine/src/UI/Core/**` - - `engine/src/UI/Input/**` - - `tests/UI/Core/unit/**` - - `tests/UI/Runtime/unit/**` -- 已确认问题: - - 最新审计显示 `UI` 模块当前只覆盖 `2 / 37`,剩余缺口里最基础的一批就是 `Core` 与 `Input`。 - - 这些头文件已经有真实 `.cpp`、单测和 runtime 调用链,但 canonical API 树仍停留在目录总览页。 -- 完成记录: - - 已新增 `UIBuildContext`、`UIContext`、`UIElementTree`、`UIInvalidation`、`UIViewModel`、`UIFocusController`、`UIInputDispatcher`、`UIInputPath`、`UIInputRouter`、`UIShortcutRegistry` 页面。 - - 已同步 `Core.md`、`Input.md` 与 `UI.md`,把 retained-mode build / diff 主链和输入分发主链纳入当前模块入口。 - - 已重新运行 `python -B docs/api/_tools/audit_api_docs.py`,确认 `Valid header refs (canonical): 279`、`Invalid header refs: 0`、`Invalid source refs: 0`、`Broken .md links: 0`。 - -## T24 UI / Layout + Runtime + Style + Text + Widgets header coverage 补页 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P0` -- 写入范围: - - `docs/api/XCEngine/UI/Layout/**` - - `docs/api/XCEngine/UI/Runtime/**` - - `docs/api/XCEngine/UI/Style/**` - - `docs/api/XCEngine/UI/Text/**` - - `docs/api/XCEngine/UI/Widgets/**` - - `docs/api/XCEngine/UI/UI.md` -- 主要源码依据: - - `engine/include/XCEngine/UI/Layout/*.h` - - `engine/include/XCEngine/UI/Runtime/*.h` - - `engine/include/XCEngine/UI/Style/*.h` - - `engine/include/XCEngine/UI/Text/*.h` - - `engine/include/XCEngine/UI/Widgets/*.h` - - `engine/src/UI/Runtime/**` - - `engine/src/UI/Style/**` - - `engine/src/UI/Text/**` - - `engine/src/UI/Widgets/**` - - `tests/UI/Core/unit/**` - - `tests/UI/Runtime/unit/**` - - `tests/Scene/test_scene_runtime.cpp` -- 已确认问题: - - `Layout / Runtime / Style / Text / Widgets` 还剩 `25` 个未覆盖 public headers,是当前 API 文档最大的连续缺口。 - - 这些头文件里既有 header-only layout / interaction helper,也有真实 source-backed runtime / style / widget 状态模型;如果不拆开写,overview 很容易继续混淆“声明契约”和“当前实现行为”。 -- 当前进展: - - 已新增 `LayoutTypes`、`LayoutEngine`、`UISplitterLayout`、`UITabStripLayout`、`UIScreenTypes`、`UISceneRuntimeContext`、`UIScreenDocumentHost`、`UIScreenPlayer`、`UIScreenStackController`、`UISystem`、`StyleTypes`、`StyleSet`、`Theme`、`StyleResolver`、`DocumentStyleCompiler`、`UITextEditing`、`UITextInputController`、`UIExpansionModel`、`UIFlatHierarchyHelpers`、`UIKeyboardNavigationModel`、`UIPopupOverlayModel`、`UIPropertyEditModel`、`UISelectionModel`、`UISplitterInteraction`、`UITabStripModel` 页面。 - - 已同步 `Layout.md`、`Runtime.md`、`Style.md`、`Text.md`、`Widgets.md` 与 `UI.md`,把 header-only helper 和 source-backed runtime/style/widget 状态模型拆开归位。 - - 已重新运行 `python -B docs/api/_tools/audit_api_docs.py`,结果为:`Valid header refs (canonical): 304`、`UI: 37/37`、`Invalid source refs: 0`、`Broken .md links: 0`。 - -## T25 Editor UI 壳层与 `RenderMaterialResolve` 增量漂移复核 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P1` -- 写入范围: - - `docs/api/XCEngine/Editor/UI/ImGuiSession/ImGuiSession.md` - - `docs/api/XCEngine/Editor/panels/ViewportPanelContent/ViewportPanelContent.md` - - `docs/api/XCEngine/Editor/Layout/DockLayoutController/DockLayoutController.md` - - `docs/api/XCEngine/Rendering/Materials/RenderMaterialResolve/RenderMaterialResolve.md` -- 主要源码依据: - - `editor/src/UI/ImGuiSession.h` - - `editor/src/panels/ViewportPanelContent.h` - - `editor/src/Layout/DockLayoutController.h` - - `engine/include/XCEngine/Rendering/Materials/RenderMaterialResolve.h` - - `engine/src/RHI/D3D12/D3D12ResourceView.cpp` - - `engine/include/XCEngine/Input/InputTypes.h` - - `engine/include/XCEngine/Threading/TaskSystem.h` -- 已确认问题: - - `ImGuiSession.md` 仍停留在旧的“单字体 + 简单初始化”口径,没有同步 `SetProjectPath()` / `GetIniPath()`、双字体回退链和 DPI 配置。 - - `ViewportPanelContent.md` 没有写出当前 `AllowWhenOverlappedByItem` 的 hover/click 采样语义,容易误解 toolbar 覆盖视口时的交互行为。 - - `DockLayoutController.md` 没有同步当前 main viewport work-area、dock tab bar chrome 配置和 reset 延迟到下一帧重建的真实路径。 - - `RenderMaterialResolve.md` 漏掉了一批已经公开的 render-queue / legacy pass-fallback helper,也没把常量 payload 有效性和 skybox 模式优先级写清。 -- 完成记录: - - 已重写 `ImGuiSession.md`,补齐 `Initialize(projectPath, mainDpiScale)`、`GetIniPath()`、`SetProjectPath()`、Segoe UI + 微软雅黑 fallback 和 DPI 钳制逻辑。 - - 已更新 `ViewportPanelContent.md`,补上 overlappable 交互表面、root-child focus 与状态文案依赖 item rect 的当前实现说明。 - - 已更新 `DockLayoutController.md`,同步 dock host work-area、tab bar chrome 配置与 reset/rebuild 触发链。 - - 已更新 `RenderMaterialResolve.md`,补齐 render queue tag 解析、legacy builtin pass fallback、skybox 纹理模式优先级和常量 payload 有效性边界。 - - 抽查 `InputTypes`、`TaskSystem` 与 `D3D12ResourceView` 后,本轮未发现新的明确文档失配。 - -## T26 API 文档目录重大重构回归波次(2026-04-09 / 2026-04-10) - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P0` -- 写入范围: - - `docs/api/XCEngine/Resources/Volume/**` - - `docs/api/XCEngine/Components/VolumeRendererComponent/**` - - `docs/api/XCEngine/Rendering/FrameData/**` - - `docs/api/XCEngine/Rendering/Passes/**` - - `docs/api/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline/**` - - `docs/api/XCEngine/UI/Widgets/**` - - `docs/api/XCEngine/Editor/ComponentEditors/**` - - `docs/api/XCEngine/Editor/panels/**` - - `docs/api/XCEngine/Editor/Viewport/Passes/**` - - `docs/api/_meta/rebuild-status.md` -- 主要源码依据: - - `engine/include/XCEngine/Resources/Volume/VolumeField.h` - - `engine/include/XCEngine/Resources/Volume/VolumeFieldLoader.h` - - `engine/include/XCEngine/Components/VolumeRendererComponent.h` - - `engine/include/XCEngine/Rendering/FrameData/VisibleVolumeItem.h` - - `engine/include/XCEngine/Rendering/Passes/BuiltinSelectionMaskPass.h` - - `engine/include/XCEngine/Rendering/Passes/BuiltinSelectionOutlinePass.h` - - `engine/include/XCEngine/Rendering/Passes/BuiltinVolumetricPass.h` - - `engine/include/XCEngine/UI/Widgets/UIDragDropInteraction.h` - - `engine/include/XCEngine/UI/Widgets/UIScrollModel.h` - - `editor/src/ComponentEditors/VolumeRendererComponentEditor.h` - - `editor/src/panels/MaterialInspectorMaterialState.h` - - `editor/src/panels/MaterialInspectorMaterialStateIO.h` - - `editor/src/Viewport/Passes/SceneViewportSelectionOutlinePass.h` - - `editor/src/Viewport/Passes/SceneViewportSelectionOutlinePass.cpp` -- 已确认问题: - - 重大重构后,`Volume` 资源、体渲染组件、`visibleVolumes` 帧数据主链、selection mask / outline pass、UI widget helpers 与若干 Editor source-backed 页面尚未完全进入 canonical API 树。 - - `SceneViewportSelectionOutlinePass` 文档仍停留在旧的 object-id outline 口径,与当前 `BuiltinSelectionMaskPass + BuiltinSelectionOutlinePass` 的两段链路不符。 - - `XCUIDemoPanel` 历史页与若干旧交叉引用已不再对应当前源码树,需要从 active source-backed 叙事中清理。 -- 完成记录: - - 已补齐 `Volume.md`、`VolumeField.md`、`VolumeFieldLoader.md`、`VolumeRendererComponent.md`、`VisibleVolumeItem.md`、`BuiltinSelectionMaskPass.md`、`BuiltinSelectionOutlinePass.md`、`BuiltinVolumetricPass.md`、`UIDragDropInteraction.md`、`UIScrollModel.md`、`VolumeRendererComponentEditor.md`、`MaterialInspectorMaterialState.md`、`MaterialInspectorMaterialStateIO.md` 等缺页,并同步相关 overview 页面。 - - 已把 `RenderSceneData`、`RenderSceneExtractor`、`BuiltinForwardPipeline`、`Passes.md` 与 `SceneViewportSelectionOutlinePass/**` 对齐到当前真实调用链。 - - 已清理 `XCUIDemoPanel` 作为当前 source-backed 页面/调用链入口的残留口径。 - - 已重新运行 `python -B docs/api/_tools/audit_api_docs.py`,确认 `Public headers: 313`、`Editor source headers: 144`、`Valid header refs (canonical): 313`、`Valid source refs (Editor canonical): 144`、`Broken .md links: 0`、`Missing directory index pages: 0`、`Editor high-risk single-page dirs: 0`。 - -## T27 Rendering / 目录归位收口与新结构口径修正(2026-04-10) - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P0` -- 写入范围: - - `docs/api/XCEngine/Rendering/**` - - `docs/api/_tools/audit_api_docs.py` - - `docs/api/_meta/rebuild-status.md` - - `docs/plan/API文档目录结构第二轮并行任务板_2026-04-09.md` -- 主要源码依据: - - `engine/include/XCEngine/Rendering/Execution/CameraRenderer.h` - - `engine/include/XCEngine/Rendering/Execution/SceneRenderer.h` - - `engine/include/XCEngine/Rendering/Planning/CameraRenderRequest.h` - - `engine/include/XCEngine/Rendering/Planning/SceneRenderRequestPlanner.h` - - `engine/include/XCEngine/Rendering/Extraction/RenderSceneExtractor.h` - - `engine/include/XCEngine/Rendering/Extraction/RenderSceneUtility.h` - - `engine/src/Rendering/Execution/CameraRenderer.cpp` - - `engine/src/Rendering/Execution/SceneRenderer.cpp` - - `engine/src/Rendering/Planning/SceneRenderRequestPlanner.cpp` -- 已确认问题: - - `Rendering` 根目录旧位置页已经删除,但总览页、planner 说明和审计脚本的 stale-token 路径仍部分停留在重构前口径。 - - `Rendering.md` 仍把 fullscreen 阶段漏在主流程外,并继续把 `RenderMaterialUtility` 描述成当前真实头文件入口。 - - `SceneRenderRequestPlanner.md` / `BuildRequests.md` 还没有写出 `DirectionalShadowPlanningSettings` 和自动方向光阴影计划的真实补全过程。 - - `audit_api_docs.py` 的 canonical stale-token 检查仍挂在旧的 `Rendering/CameraRenderer`、`Rendering/CameraRenderRequest` 路径上,无法覆盖新的 `Execution/`、`Planning/` 目录。 -- 完成记录: - - 已确认旧顶层 `CameraRenderer`、`SceneRenderer`、`CameraRenderRequest`、`SceneRenderRequestPlanner`、`SceneRenderRequestUtils`、`RenderCameraData`、`RenderResourceCache`、`RenderSceneExtractor`、`RenderSceneUtility` canonical 目录都已退出 active 树,只保留真实子模块位置。 - - 已更新 `Rendering.md`、`Builtin.md`、`Materials.md`,把 builtin / materials 拆分后的真实入口与兼容主题页边界写清,并把 `postProcess -> finalOutput -> objectId` 重新纳入主流程顺序。 - - 已更新 `SceneRenderRequestPlanner.md` 与 `BuildRequests.md`,补齐 `DirectionalShadowPlanningSettings`、planner 的 setter/getter,以及自动方向光阴影计划写回 `shadowCaster` override 的当前实现。 -- 已把审计脚本中的 stale canonical path 从旧顶层 Rendering 目录迁到新的 `Planning/CameraRenderRequest` 与 `Execution/CameraRenderer` 位置。 -- 已重新运行 `python -B docs/api/_tools/audit_api_docs.py`,确认 `Public headers: 384`、`Valid header refs (canonical): 384`、`Editor source headers: 144`、`Valid source refs (Editor canonical): 144`、`Broken .md links: 0`、`Missing directory index pages: 0`、`Editor high-risk single-page dirs: 0`。 - -## T28 Resources / `Model` + `GaussianSplat` canonical 入口补齐(2026-04-10) - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P0` -- 写入范围: - - `docs/api/XCEngine/Resources/Model/**` - - `docs/api/XCEngine/Resources/GaussianSplat/**` - - `docs/api/XCEngine/Resources/Resources.md` - - `docs/api/_meta/rebuild-status.md` -- 主要源码依据: - - `engine/include/XCEngine/Resources/Model/AssimpModelImporter.h` - - `engine/include/XCEngine/Resources/Model/Model.h` - - `engine/include/XCEngine/Resources/Model/ModelArtifactIO.h` - - `engine/include/XCEngine/Resources/Model/ModelLoader.h` - - `engine/include/XCEngine/Resources/GaussianSplat/GaussianSplat.h` - - `engine/include/XCEngine/Resources/GaussianSplat/GaussianSplatArtifactIO.h` - - `engine/include/XCEngine/Resources/GaussianSplat/GaussianSplatLoader.h` - - `engine/src/Resources/GaussianSplat/GaussianSplat.cpp` - - `engine/src/Resources/GaussianSplat/GaussianSplatArtifactIO.cpp` - - `engine/src/Resources/GaussianSplat/GaussianSplatLoader.cpp` -- 已确认问题: - - `Resources/Model` 细页已经存在,但目录级 `Model.md` 缺失,导致 `../Model.md` 反向链接和目录索引一起失效。 - - 新增的 `Resources/GaussianSplat/**` public headers 已经进入源码树与 `Resources.md` 导航,但 canonical API 树还没有对应目录和头文件页。 - - 这些缺口属于新 dplan 落地后的新增资源子模块入口缺页,不再是旧 Rendering 目录错位问题。 -- 完成记录: - - 已补建 `Resources/Model/Model.md`,把 `Model` 目录收口为正式 submodule 入口,消除了 `AssimpModelImporter`、`Model`、`ModelArtifactIO`、`ModelLoader` 细页对 `../Model.md` 的失效链接。 - - 已补建 `Resources/GaussianSplat/**`,覆盖 `GaussianSplat` submodule、`GaussianSplat` 运行时资源、`GaussianSplatArtifactIO` 与 `GaussianSplatLoader` 三个 public headers。 - - 已同步 `Resources.md`,把 `GaussianSplat` 纳入 `Resources` 模块子目录导航。 - - 已重新运行 `python -B docs/api/_tools/audit_api_docs.py`,确认 `Public headers: 384`、`Editor source headers: 144`、`Valid header refs (canonical): 384`、`Valid source refs (Editor canonical): 144`、`Invalid header refs: 0`、`Invalid source refs: 0`、`Broken .md links: 0`、`Missing directory index pages: 0`。 - -## T29 Rendering / `RenderMaterialResolve` 主页面内容回归(2026-04-10) - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P1` -- 写入范围: - - `docs/api/XCEngine/Rendering/Materials/**` - - `docs/plan/API文档目录结构第二轮并行任务板_2026-04-09.md` - - `docs/api/_meta/rebuild-status.md` -- 主要源码依据: - - `engine/include/XCEngine/Rendering/Materials/RenderMaterialResolve.h` - - `engine/include/XCEngine/Rendering/Builtin/BuiltinPassMetadataUtils.h` - - `engine/src/Rendering/Extraction/RenderSceneUtility.cpp` - - `engine/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp` - - `engine/src/Rendering/Passes/BuiltinDepthStylePassBaseResources.cpp` -- 已确认问题: - - `RenderMaterialResolve.md` 仍停留在旧的 `BuiltinForwardMaterialData` / legacy helper 口径,没有同步 `BuiltinDepthStyleMaterialConstants`、`MaterialBufferResourceView` 与 depth-style 常量 payload 这条新公开链路。 - - 页面还把 `MatchesBuiltinPass(...)` 描述成直接依赖旧材质提示,而当前真实实现已经把匹配规则委托给 `BuiltinPassMetadataUtils::ShaderPassMatchesBuiltinPass(...)`。 - - `Materials.md` 也没有把 structured/raw material buffer binding 视图解析与 depth-style payload 作为当前模块职责写出来。 -- 完成记录: - - 已重写 `RenderMaterialResolve.md` 的主类型、公开能力、关键 helper 和实现边界,移除已不在头文件里的 `BuiltinForwardMaterialData` / legacy helper 描述,补齐 `BuiltinDepthStyleMaterialConstants`、`MaterialBufferResourceView`、`ResolveBuiltinDepthStyleMaterialConstantPayload(...)` 与 `TryResolveMaterialBufferResourceView(...)`。 - - 已同步 `Materials.md`,把 depth-style 常量 payload 与 material buffer 视图解析纳入当前模块职责。 - - 已更新第二轮任务板,把 `RR3` 标记为当前会话完成。 - - 已重新运行 `python -B docs/api/_tools/audit_api_docs.py`,确认 `Valid header refs (canonical): 384`、`Valid source refs (Editor canonical): 144`、`Broken .md links: 0`、`Missing directory index pages: 0`。 - -## 当前结论 - -- 第三轮复核当前已确认 `29` 组失配;`T01` 到 `T29` 已完成,其中 `T27` 收口了 Rendering 目录归位后的总览口径和审计路径,`T28` 补齐了 `Resources/Model` 与 `Resources/GaussianSplat` 的 canonical 入口,`T29` 完成了 `RenderMaterialResolve` 内容回归。 -- 当前 `engine/include/XCEngine/**`、`new_editor/include/XCEditor/**` 与 `editor/src/**` 三条 canonical 审计主线都已经回到全绿:`Valid header refs (canonical) = 384`、`Valid source refs (Editor canonical) = 144`、`Broken .md links = 0`、`Missing directory index pages = 0`。 -- 本轮新增纳入审计口径的 `XCEditor`、`Resources/Model` 与 `Resources/GaussianSplat` 已经进入 canonical API 树,不再是遗留缺口;Rendering 目录错位、overview 口径漂移、`RenderMaterialResolve` 主页面内容漂移和 stale-token 路径也已同步收口。 -- 后续如果 `docs/plan/` 再出现新的 API 相关计划,或工作树新增/改动 public header、Editor source header,应继续在此任务池追加新任务块,并在收口前重新运行 `python -B docs/api/_tools/audit_api_docs.py`。 diff --git a/docs/used/API文档实时同步任务池_2026-04-03_第一轮归档.md b/docs/used/API文档实时同步任务池_2026-04-03_第一轮归档.md deleted file mode 100644 index 789e5017..00000000 --- a/docs/used/API文档实时同步任务池_2026-04-03_第一轮归档.md +++ /dev/null @@ -1,374 +0,0 @@ -# API 文档实时同步任务池(2026-04-03) - -## 文档定位 - -这份任务池现已归档,并且是当前仓库中可追溯到的首轮 API 文档归档起点。更早的 `2026-04-02` 初始任务池原件当前不在仓库内,因此这里不再保留失效链接。 - -旧任务池解决的是: - -- canonical 目录结构收口 -- 历史缺页补齐 -- 第一轮大规模内容重写 - -本轮不再以“补结构”为主,而是以“跟踪当前工作树真实实现变化,持续清理失准内容和过期 API 页面”为主。 - -## 本轮复核方法 - -本轮按下面三层做交叉复核: - -1. 重新执行 `python docs/api/_tools/audit_api_docs.py` -2. 检查当前工作树里真实发生变化的源码与测试: - - `engine/src/Components/MeshFilterComponent.cpp` - - `engine/src/Components/MeshRendererComponent.cpp` - - `editor/src/Actions/EditorActions.h` - - `editor/src/Actions/MainMenuActionRouter.h` - - `editor/src/Commands/ProjectCommands.h` - - `editor/src/Core/IProjectManager.h` - - `editor/src/Managers/ProjectManager.h` - - `editor/src/Managers/ProjectManager.cpp` - - `tests/Components/test_mesh_render_components.cpp` - - `tests/Scene/test_scene.cpp` - - `tests/editor/test_action_routing.cpp` -3. 反向搜索 API 文档里对旧行为、旧菜单和旧序列化键的残留描述 - -## 当前总判断 - -- 旧的内容级失准问题已经全部关闭: - - `Editor`:旧的项目迁移菜单、命令、接口、manager 和报告结构残留页已在 `2026-04-03 14:04:49` 收口完成 - - `Components`:`MeshFilterComponent / MeshRendererComponent` 的旧键名、普通项目路径 fallback 和模块总览口径已在 `2026-04-03 14:09:47` 对齐到当前源码 / 测试 -- 新暴露出的 `SceneViewportRenderPlan.h` 缺页问题已在 `2026-04-03 14:17:02` 收口完成 -- 当前结构项重新回到全绿: - - public headers `246/246` - - Editor source headers `122/122` - - 失效 `.md` 链接 `0` - - 无效 header / source ref `0` - - Editor 显式过期符号残留 `0` - - Editor 残留 canonical 旧页面 `0` - -这说明当前审计工具已经同时具备三种能力:兜住结构一致性、自动报出已删除 API 的旧符号残留、以及发现工作树中新冒出的未补页 header;后续工作进入“继续跟踪新增 API 与计划文件新增任务块”的阶段。 - -## 认领规则 - -- 一次只认领 `1` 个任务块,先改 `状态` 和 `认领人` -- 只修改自己任务块的 `写入范围`,不要跨任务顺手扩写其它模块 -- 每个任务都要以当前工作树源码、测试和真实调用点为依据,不允许只按旧文档重写旧文档 -- 除最后的审计收口任务外,不要随意覆盖 `docs/api/_meta/rebuild-status.md` -- 如果任务涉及删除过期 API 页面,必须同步清理所有交叉链接 - -## 任务池 - -## T01 Components / Mesh 资产引用最终协议同步 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P0` -- 写入范围: - - `docs/api/XCEngine/Components/MeshFilterComponent/**` - - `docs/api/XCEngine/Components/MeshRendererComponent/**` - - 必要时 `docs/api/XCEngine/Components/Components.md` - - 必要时 `docs/api/_guides/Components/**` -- 主要源码依据: - - `engine/src/Components/MeshFilterComponent.cpp` - - `engine/src/Components/MeshRendererComponent.cpp` - - `tests/Components/test_mesh_render_components.cpp` - - `tests/Scene/test_scene.cpp` - - `engine/include/XCEngine/Components/MeshFilterComponent.h` - - `engine/include/XCEngine/Components/MeshRendererComponent.h` -- 当前确认缺口: - - `MeshFilterComponent.md` 仍写“反序列化会读取 `mesh=`”,而当前实现只识别 `meshPath` - - `MeshFilterComponent.md` / `Serialize.md` 仍写“序列化输出 `mesh=`”,而当前实现对项目资产已转向 `meshRef` 主路径,只在 virtual scheme 下保留 `meshPath` - - `MeshFilterComponent::Deserialize.md` 仍写“只有路径时会补算 `AssetRef` 并接受普通路径”,而当前实现已忽略没有 `AssetRef` 的普通项目路径,只保留 virtual scheme 路径 - - `MeshRendererComponent.md` / `Serialize.md` / `Deserialize.md` 仍写“兼容 `materials=` 历史键”和“项目资产路径 fallback 仍是主恢复路径”,而当前实现已经不再读取 `materials=`,并且会清掉无 `AssetRef` 的普通 `materialPaths` - - 组件模块总览当前对“路径 + AssetRef + 运行时句柄双轨”的描述仍偏旧,没有强调“项目资产主协议已经收口为 `AssetRef`,路径主要保留给 `builtin://` / 其它 virtual scheme” -- 完成标准: - - 把 `MeshFilterComponent` / `MeshRendererComponent` 类型页和序列化方法页全部改到当前实现 - - 明确“项目资产只依赖 `AssetRef`,普通项目路径不再作为长期兼容协议” - - 明确“只有 virtual scheme 路径会稳定保留在 `meshPath` / `materialPaths` 里” - - 清掉所有关于 `mesh=`、`materials=` 仍被当前实现兼容的说法 -- 完成记录: - - 已重新核对 `MeshFilterComponent.h/.cpp`、`MeshRendererComponent.h/.cpp`、`tests/Components/test_mesh_render_components.cpp` 与 `tests/Scene/test_scene.cpp` - - `MeshFilterComponent.md`、`Serialize.md`、`Deserialize.md` 与 `MeshRendererComponent.md`、`Serialize.md`、`Deserialize.md` 已确认按当前实现描述 `meshRef / materialRefs` 主协议 - - 已继续修正 `SetMeshPath.md`、`SetMaterialPath.md` 与 `Components.md`,把“运行时路径缓存”和“正式序列化协议”明确拆开 - - `2026-04-03 14:09:47` 审计后,当前仓库中这组页面不再把 `mesh=` / `materials=` 或普通项目路径 fallback 写成当前协议 - -## T02 Editor / 移除场景资产引用迁移链路文档与过期页 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P0` -- 写入范围: - - `docs/api/XCEngine/Editor/Actions/EditorActions/**` - - `docs/api/XCEngine/Editor/Actions/MainMenuActionRouter/**` - - `docs/api/XCEngine/Editor/Commands/ProjectCommands/**` - - `docs/api/XCEngine/Editor/Core/IProjectManager/**` - - `docs/api/XCEngine/Editor/Managers/ProjectManager/**` - - `docs/api/XCEngine/Editor/panels/ProjectPanel/**` - - 必要时 `docs/api/_guides/Editor/**` -- 主要源码依据: - - `editor/src/Actions/EditorActions.h` - - `editor/src/Actions/MainMenuActionRouter.h` - - `editor/src/Commands/ProjectCommands.h` - - `editor/src/Core/IProjectManager.h` - - `editor/src/Managers/ProjectManager.h` - - `editor/src/Managers/ProjectManager.cpp` - - `tests/editor/test_action_routing.cpp` -- 当前确认缺口: - - `EditorActions.md` 仍包含 `MakeMigrateSceneAssetReferencesAction` - - `MainMenuActionRouter.md` 仍把 `File -> Migrate Scene AssetRefs` 当成当前菜单项 - - `ProjectCommands.md` 仍写 `CanMigrateSceneAssetReferences()` / `MigrateSceneAssetReferences()` - - `IProjectManager.md` 仍声明 `SceneAssetReferenceMigrationReport` 和 `MigrateSceneAssetReferences()` - - `ProjectManager.md` 仍把“场景资产引用迁移”写成当前公开职责 - - `ProjectPanel.md` 仍把 `Migrate Scene AssetRefs` 记为项目级命令之一 - - 过期 canonical 页面仍存在: - - `docs/api/XCEngine/Editor/Managers/ProjectManager/MigrateSceneAssetReferences.md` -- 完成标准: - - 把上述页面全部改成当前源码状态 - - 删除已不存在的 API 页面,并清理所有交叉引用 - - 把项目工作流文档收口到当前仍存在的入口:项目切换、保存、脚本重建、资源浏览与文件操作 -- 完成记录: - - 已重写 `EditorActions.md`、`MainMenuActionRouter.md`、`ProjectCommands.md`、`IProjectManager.md`、`ProjectManager.md`、`ProjectPanel.md` - - 已把 `IProjectManager` 进一步拆成 `Current Items And Selection`、`Navigation And Path`、`Initialization And Refresh`、`File Operations` 四页,按当前头文件分组收口 - - `docs/api/XCEngine/Editor/Managers/ProjectManager/MigrateSceneAssetReferences.md` 已删除;旧迁移 API 不再保留兼容入口页 - - `2026-04-03 14:04:49` 已重新执行 `python docs/api/_tools/audit_api_docs.py`,结果为: - - `Stale editor doc tokens: 0` - - `Broken .md links: 0` - - `Valid source refs (Editor canonical): 121` - -## T03 Cross-Module / 资产引用协议与编辑器工作流交叉说明收口 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P1` -- 写入范围: - - `docs/api/XCEngine/Components/Components.md` - - `docs/api/XCEngine/Scene/Scene.md` - - `docs/api/_guides/Editor/Editor-Architecture-And-Workflow.md` - - 必要时 `docs/api/main.md` -- 主要源码依据: - - `engine/src/Components/MeshFilterComponent.cpp` - - `engine/src/Components/MeshRendererComponent.cpp` - - `editor/src/Actions/MainMenuActionRouter.h` - - `editor/src/Commands/ProjectCommands.h` - - `editor/src/Managers/ProjectManager.cpp` - - `tests/Scene/test_scene.cpp` - - `tests/editor/test_action_routing.cpp` -- 当前确认缺口: - - 模块总览层还没有把“项目资产最终序列化协议已收口到 `AssetRef`,virtual path 只保留给 builtin/虚拟资源”讲透 - - Editor 架构层仍有旧项目维护入口的残留心智,需要删掉“场景迁移”这条已不存在的分支 - - 目前用户如果只读模块总览,仍容易得出“普通项目路径仍是第一公民协议”“主菜单还带场景迁移入口”的错误结论 -- 完成标准: - - 模块页和 guide 页不再沿用旧心智模型 - - 用户只看总览页,也能得出当前正确结论: - - 项目资产靠 `AssetRef` - - `builtin://` / 其它 virtual scheme 才保留路径 - - 主菜单已无 `Migrate Scene AssetRefs` -- 完成记录: - - 已修正 `docs/api/XCEngine/Components/Components.md`,明确项目资产正式协议优先 `AssetRef` - - 已复核 `docs/api/XCEngine/Scene/Scene.md`、`docs/api/_guides/Editor/Editor-Architecture-And-Workflow.md` 与 `docs/api/main.md`,确认不再传播旧菜单入口或“普通项目路径仍是第一公民协议”的说法 - - 当前仅阅读模块总览 / guide,也能得出“项目资产靠 `AssetRef`、virtual scheme 才稳定保留路径、主菜单没有旧迁移入口”的正确结论 - -## T04 API 审计工具补强 / 过期 API 页面检测 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P1` -- 写入范围: - - `docs/api/_tools/audit_api_docs.py` - - 必要时 `docs/api/_meta/rebuild-status.md` - - 必要时在 `docs/plan` 中补充审计口径说明 -- 主要依据: - - 本轮复核结论 - - 当前 audit 全绿但仍漏报 `MigrateSceneAssetReferences` 这类已删除 API 残留页 -- 当前确认缺口: - - 现有审计能发现“缺页”和“断链”,但不能发现“源码已经删除,文档页仍然存在” - - 也不能发现模块页仍在描述已删除菜单项或已删除 helper -- 完成标准: - - 至少新增一种轻量检测,能把“已删除 API 的残留 canonical 页面”或“显式过期 helper 名称残留”暴露出来 - - 让后续复核不再完全依赖人工 grep -- 完成记录: - - `docs/api/_tools/audit_api_docs.py` 已新增 Editor 显式过期符号残留检测 - - `2026-04-03 13:53:21` 审计已能自动报出 `49` 处 `MigrateSceneAssetReferences` 相关残留 - -## T05 收口审计与进度回写 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P2` -- 写入范围: - - `docs/api/_meta/rebuild-status.md` - - `docs/used/API文档实时同步任务池_2026-04-03.md` -- 主要依据: - - `T01-T04` 完成结果 -- 完成标准: - - 重新执行 `python docs/api/_tools/audit_api_docs.py` - - 回写新的审计时间和结果 - - 明确记录本轮哪些失准点已关闭、哪些仍待继续跟踪 -- 完成记录: - - 已在 `2026-04-03 14:20:04` 重新执行 `python docs/api/_tools/audit_api_docs.py` - - 已回写 `docs/api/_meta/rebuild-status.md` - - 本轮已关闭: - - `T01` 对应的 Components / Mesh 资产引用协议失准 - - `T02` 对应的 Editor 旧迁移链路文档残留 - - `T03` 对应的跨模块总览与 guide 口径收口 - - `T07` 对应的 `SceneViewportRenderPlan.h` 缺页 - - 本轮剩余待继续跟踪: - - 当前任务池内无未完成块;后续按新增 API 或新增计划任务继续开块 - -## 最新收口结论(2026-04-03 14:20:04) - -- 已重新执行 `python docs/api/_tools/audit_api_docs.py` -- 当前结构性问题重新回到 `0` - - public headers `246/246` - - Editor source headers `122/122` - - 失效 `.md` 链接 `0` - - 无效 header / source ref `0` -- 本轮新增收口结果: - - `T01 Components / Mesh 资产引用最终协议同步` 已完成 - - `T02 Editor / 移除场景资产引用迁移链路文档与过期页` 已完成 - - `T03 Cross-Module / 资产引用协议与编辑器工作流交叉说明收口` 已完成 - - `T07 Editor / SceneViewportRenderPlan 新增头文件补页` 已完成 - - `T09 Editor / EditorConsoleSink 生命周期与 Managers 总览口径复核` 已完成 - - `IProjectManager` 已按当前头文件拆成 4 个职责页,`ProjectManager` / `ProjectCommands` / `MainMenuActionRouter` / `EditorActions` / `ProjectPanel` 已全部同步到当前源码 - - Components 模块总览与 mesh/material 组件页已统一到“项目资产优先 `AssetRef`、virtual scheme 才稳定保留路径”的当前协议 - - 对应的旧迁移 canonical 页已删除,已删除 API 名称也不再出现在正文里 - - `EditorConsoleSink` 文档已改回当前真实生命周期:`GetInstance()` 没有 fallback 实例,未注册时会返回 `nullptr` - - `docs/api/_meta/rebuild-status.md` 当前显示: - - `Markdown pages (canonical): 3273` - - `Stale editor doc tokens: 0` - - `Stale editor canonical pages: 0` - - `Editor high-risk single-page dirs: 0` -- 当前仍待处理的内容级问题: - - 当前任务池内无未完成块 - -## T06 Rendering / CameraRenderer 与主管线职责边界收口 -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P1` -- 写入范围: - - `docs/api/XCEngine/Rendering/CameraRenderer/**` - - `docs/api/XCEngine/Rendering/RenderPipeline/**` - - `docs/api/XCEngine/Rendering/RenderPipelineAsset/**` - - `docs/api/XCEngine/Rendering/Pipelines/BuiltinForwardPipelineAsset/**` - - 必要时 `docs/api/XCEngine/Rendering/Rendering.md` -- 主要源码依据: - - `engine/include/XCEngine/Rendering/CameraRenderer.h` - - `engine/src/Rendering/CameraRenderer.cpp` - - `engine/include/XCEngine/Rendering/RenderPipeline.h` - - `engine/include/XCEngine/Rendering/RenderPipelineAsset.h` - - `engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h` - - `tests/Rendering/unit/test_camera_scene_renderer.cpp` -- 当前缺口: - - 已重写 `CameraRenderer.md`、`Constructor.md`、`Render.md`、`Rendering.md`、`RenderPipeline.md`,把“`SceneRenderer` 负责请求规划、`CameraRenderer` 负责执行单 request”的真实分层,以及默认 `BuiltinForwardPipelineAsset -> BuiltinForwardPipeline` 链路同步到当前实现。 - - 已继续重写 `RenderPipeline/Initialize.md`、`Render.md`、`Shutdown.md`、`Destructor.md`,明确 `RenderPipeline` 只覆盖主场景 runtime 绘制,不承担 object-id / builtin post-process / overlay 编排;同时写清 `Initialize()` 不是上层强制预热点,`Shutdown()` 调用路径由 `CameraRenderer` 托管。 - - 已校正 `RenderPipelineAsset.md`、`CreatePipeline.md`、`BuiltinForwardPipelineAsset.md`、`CreatePipeline.md`,把“asset 负责创建 runtime pipeline”与“空 asset / 空返回后的 fallback 由 `CameraRenderer` 处理”拆开说明,避免把调用方策略误记到 asset 接口本身。 -- 完成标准: - - 用户只看 `CameraRenderer` / `RenderPipeline` / `RenderPipelineAsset` 这一组页面,也能得出当前正确结论: - - `SceneRenderer` 负责请求规划,`CameraRenderer` 负责执行单 request - - `RenderPipeline` 只负责主场景 runtime 绘制 - - fallback 属于调用方装配逻辑,不属于 `RenderPipelineAsset` 接口本身 - -## T07 Editor / SceneViewportRenderPlan 新增头文件补页 -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P1` -- 写入范围: - - `docs/api/XCEngine/Editor/Viewport/SceneViewportRenderPlan/**` - - 必要时 `docs/api/XCEngine/Editor/Viewport/Viewport.md` - - 必要时 `docs/api/_guides/Editor/**` -- 主要源码依据: - - `editor/src/Viewport/SceneViewportRenderPlan.h` - - `editor/src/Viewport/ViewportHostService.h` -- 当前缺口: - - `docs/api/_tools/audit_api_docs.py` 于 `2026-04-03 14:11:12` 报出新增未覆盖 header:`editor/src/Viewport/SceneViewportRenderPlan.h` - - 当前缺的不只是类型页,还包括内联 helper: - - `SceneViewportRenderPlan` - - `SceneViewportRenderPlanBuildResult` - - `BuildSceneViewportRenderPlan(...)` - - `ApplySceneViewportRenderPlan(...)` - - 从 `ViewportHostService.h` 的调用关系看,这个头文件已经成为 Scene View request 装配链的一部分,不能继续留白 -- 完成标准: - - 为 `SceneViewportRenderPlan.h` 建立 canonical 目录与类型页 - - 把 plan 结构、build result、build/apply 两个 helper 的职责边界写清楚 - - 用户只看这组页面,也能得出当前正确结论: - - 它负责把 Scene View 的 builtin post-process、overlay passes 与 clear-color override 收口成一个 request plan - - `Build...` 负责从 overlay / targets 生成 plan - - `Apply...` 负责把 plan 回写到 `CameraRenderRequest` -- 完成记录: - - 已新增: - - `docs/api/XCEngine/Editor/Viewport/SceneViewportRenderPlan/SceneViewportRenderPlan.md` - - `docs/api/XCEngine/Editor/Viewport/SceneViewportRenderPlan/SceneViewportRenderPlanBuildResult.md` - - `docs/api/XCEngine/Editor/Viewport/SceneViewportRenderPlan/BuildSceneViewportRenderPlan.md` - - `docs/api/XCEngine/Editor/Viewport/SceneViewportRenderPlan/ApplySceneViewportRenderPlan.md` - - 已同步更新: - - `docs/api/XCEngine/Editor/Viewport/Viewport.md` - - `docs/api/XCEngine/Editor/Viewport/ViewportHostService/ViewportHostService.md` - - `2026-04-03 14:20:04` 审计结果已恢复为: - - `Editor source headers 122/122` - - `失效 .md 链接 0` - - `Stale editor doc tokens 0` - - `Stale editor canonical pages 0` - -## T08 Rendering / RenderCameraData 与 builtin forward 材质契约补齐 -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P2` -- 写入范围: - - `docs/api/XCEngine/Rendering/RenderCameraData/**` - - `docs/api/XCEngine/Rendering/RenderSceneUtility/**` - - `docs/api/XCEngine/Rendering/RenderMaterialUtility/**` -- 主要源码依据: - - `engine/include/XCEngine/Rendering/RenderCameraData.h` - - `engine/include/XCEngine/Rendering/RenderSceneUtility.h` - - `engine/src/Rendering/RenderSceneUtility.cpp` - - `engine/include/XCEngine/Rendering/RenderMaterialUtility.h` - - `tests/Rendering/unit/test_render_scene_extractor.cpp` - - `tests/Rendering/unit/test_render_scene_utility.cpp` -- 当前缺口: - - 已重写 `RenderCameraData.md`,补齐此前遗漏的 `clearFlags` 字段,以及 `RenderClearFlags` / `HasRenderClearFlag(...)` 的当前位标志语义;同时写清 `BuildRenderCameraData()` 不负责 clear mode 推导,真正的 per-request clear 规则由 `CameraRenderer` 回写。 - - 已重写 `RenderSceneUtility.md` 中 `BuildRenderCameraData()` 的职责边界,明确它只构建矩阵、世界位置、默认清屏色和 viewport,不直接决定相机 request 的 clear 规则。 - - 已补齐 `ResolveBuiltinBaseColorFactor.md`、`ResolveBuiltinBaseColorTexture.md`、`BuildBuiltinForwardMaterialData.md`,并更新 `RenderMaterialUtility.md`,把 builtin forward 当前公开消费的 base-color 因子 / 贴图契约、semantic 优先级、别名回退与 alpha-only fallback 写到当前实现。 -- 完成标准: - - 用户只看 `RenderCameraData` / `RenderSceneUtility` / `RenderMaterialUtility`,也能得出当前正确结论: - - clear flags 不是 extractor 里按相机直接定死的,而是 request 层再覆盖写回 - - builtin forward 当前公开消费的材质契约至少包括 `baseColorFactor` 与 base-color 贴图解析规则 - -## T09 Editor / EditorConsoleSink 生命周期与 Managers 总览口径复核 -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P2` -- 写入范围: - - `docs/api/XCEngine/Editor/Core/EditorConsoleSink/**` - - `docs/api/XCEngine/Editor/Managers/Managers.md` - - `docs/api/_tools/audit_api_docs.py` - - 必要时 `docs/api/_meta/rebuild-status.md` - - 必要时 `docs/used/API文档实时同步任务池_2026-04-03.md` -- 主要源码依据: - - `editor/src/Core/EditorConsoleSink.h` - - `editor/src/Core/EditorConsoleSink.cpp` - - `tests/Editor/test_editor_console_sink.cpp` - - `editor/src/Managers/ProjectManager.h` - - `editor/src/Managers/ProjectManager.cpp` -- 当前缺口: - - `EditorConsoleSink.md`、`GetInstance.md`、`Destructor.md` 仍把 `GetInstance()` 写成“无活动实例时回退到 fallback sink”,但当前实现只是直接返回 `s_instance` - - 新增测试 `GetInstanceTracksRegisteredSinkOnly` 已明确要求:活动实例析构后 `GetInstance()` 返回 `nullptr` - - `Managers.md` 仍把“场景资产引用迁移”写成 `ProjectManager` 的当前职责之一,与本轮已删除的旧迁移链路不一致 -- 完成标准: - - `EditorConsoleSink` 生命周期相关页面全部对齐到当前实现: - - `GetInstance()` 可能返回 `nullptr` - - 没有静态 fallback sink - - `Managers.md` 不再残留旧迁移职责表述 - - 审计工具新增轻量兜底,后续若 `EditorConsoleSink` 文档再次写回 fallback 语义,能够被自动报出 -- 完成记录: - - 已修正 `EditorConsoleSink.md`、`GetInstance.md`、`Destructor.md` - - 已修正 `Managers.md` 中 `ProjectManager` 的职责概述 - - `docs/api/_tools/audit_api_docs.py` 已新增针对 `EditorConsoleSink` 旧 fallback 生命周期表述的定向检测 - -## 现阶段优先级建议 - -- 当前这份实时任务池里的已知问题已全部收口 -- `T01`、`T02`、`T03`、`T05`、`T06`、`T07`、`T09` 都已完成,不再作为并行入口重复认领 -- 若工作树继续新增 API 或计划文件继续追加任务块,再按新增内容开新任务 - -## 一句话结论 - -旧任务池解决了“API 文档树有没有建起来”的问题;这份实时任务池目前已经把 Editor 旧迁移链路、Components 资产引用协议、跨模块总览口径、`SceneViewportRenderPlan.h` 的缺页,以及 `EditorConsoleSink` 生命周期文档失准全部收口,后续只需要继续跟踪新增 API 与新增计划任务。 diff --git a/docs/used/API文档实时同步任务池_2026-04-03_第二轮归档.md b/docs/used/API文档实时同步任务池_2026-04-03_第二轮归档.md deleted file mode 100644 index 61a5536f..00000000 --- a/docs/used/API文档实时同步任务池_2026-04-03_第二轮归档.md +++ /dev/null @@ -1,600 +0,0 @@ -# API 文档实时同步任务池(2026-04-03,第二轮) - -## 文档定位 - -这份任务池现已归档,接替的是第一轮归档: - -- `docs/used/API文档实时同步任务池_2026-04-03_第一轮归档.md` - -第一轮已经解决的重点是: - -- canonical 目录结构收口 -- 历史缺页补齐 -- 第一轮大规模内容重写 - -本轮重点不再是“补结构”,而是: - -- 重新对照当前工作树源码与测试 -- 清理最近重构后重新出现的内容级失配 -- 继续维护一份适合多人并行认领的增量同步清单 - -## 当前复核快照 - -- 最近一次结构审计时间:`2026-04-03 16:21:55` -- 审计命令:`python -B docs/api/_tools/audit_api_docs.py` -- 当前结果: - - `Public headers: 244` - - `Editor source headers: 126` - - `Invalid header refs: 0` - - `Invalid source refs: 0` - - `Broken .md links: 0` - - `Stale canonical doc tokens: 0` - - `Stale editor doc tokens: 0` - - `Stale editor canonical pages: 0` - -这说明当前结构层面已经恢复全绿;本轮已确认的内容级失配也已收口,后续以持续巡检为主。 - -## 认领规则 - -- 一次只认领 `1` 个任务块。 -- 先把 `状态` 改成 `DOING`,再写 `认领人`。 -- 只能改自己任务块的 `写入范围`。 -- 所有改动都必须以“当前源码 + 当前测试 + 当前真实调用链”为依据,不允许按旧文档续写旧行为。 -- 如果清理了过期 API 页面,必须同时清理交叉链接。 - -## 任务池 - -## T01 Editor / Viewport 渲染计划与宿主流程内容同步 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P0` -- 写入范围: - - `docs/api/XCEngine/Editor/Viewport/SceneViewportRenderPlan/**` - - `docs/api/XCEngine/Editor/Viewport/ViewportHostRenderFlowUtils/**` - - `docs/api/XCEngine/Editor/Viewport/ViewportHostService/**` - - 必要时 `docs/api/XCEngine/Editor/Viewport/IViewportHostService/**` -- 主要源码依据: - - `editor/src/Viewport/SceneViewportRenderPlan.h` - - `editor/src/Viewport/ViewportHostRenderFlowUtils.h` - - `editor/src/Viewport/ViewportHostService.h` - - `tests/editor/test_viewport_render_flow_utils.cpp` -- 已关闭问题: - - 旧文档仍把 Scene View 当前主路径写成 builtin post-process 主导 - - 没写清 grid / selection outline 现在已转为显式 `postScenePasses` - - 没写清 overlay pass 是 `editorOverlayFrameData + transientOverlayFrameData` 合并后再创建 - - 没写清 `Scene object id shader view is unavailable` 是局部降级警告,不是整帧失败 -- 完成记录: - - 已重写 `SceneViewportRenderPlan.md`、`BuildSceneViewportRenderPlan.md`、`ApplySceneViewportRenderPlan.md` - - 已同步 `ViewportHostRenderFlowUtils.md`、`ViewportHostService.md`、`RenderRequestedViewports.md` - - 已清理 `IViewportHostService` 中残留的旧表述 - -## T02 Core / Asset 缓存接口改名与语义重构同步 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P0` -- 写入范围: - - `docs/api/XCEngine/Core/Asset/ResourceManager/**` - - `docs/api/XCEngine/Core/Asset/AssetImportService/**` - - `docs/api/XCEngine/Core/Asset/ProjectAssetIndex/**` - - 必要时 `docs/api/XCEngine/Core/Asset/Asset.md` - - 必要时 `docs/api/XCEngine/Core/Asset/ArtifactFormats/**` -- 主要源码依据: - - `engine/include/XCEngine/Core/Asset/ResourceManager.h` - - `engine/src/Core/Asset/ResourceManager.cpp` - - `engine/include/XCEngine/Core/Asset/AssetImportService.h` - - `engine/src/Core/Asset/AssetImportService.cpp` - - `engine/include/XCEngine/Core/Asset/ProjectAssetIndex.h` - - `engine/src/Core/Asset/ProjectAssetIndex.cpp` - - `tests/core/Asset/test_resource_manager.cpp` -- 已关闭问题: - - `RefreshAssetDatabase` 已经不存在,但旧文档仍在沿用 - - `RefreshProjectAssets` / `RebuildProjectAssetCache` / `GetProjectLibraryRoot` 缺页或未写清 - - `AssetImportService::EnsureArtifact()` 旧文档仍把输出写成 `ResolvedAsset` - - `BuildLookupSnapshot()` 旧文档仍按“双 map 出参”描述,而不是 `LookupSnapshot` - - `ImportedAsset::runtimeLoadPath` 语义未同步到上层文档 -- 完成记录: - - 已删除过期页 `ResourceManager/RefreshAssetDatabase.md` - - 已补齐 `RefreshProjectAssets.md`、`RebuildProjectAssetCache.md`、`GetProjectLibraryRoot.md` - - 已补齐 `LookupSnapshot.md`、`ImportedAsset.md`、`GetLibraryRoot.md`、`RebuildLibraryCache.md` - - 已同步 `Asset.md`、`ArtifactFormats.md`、`ResourceManager/Load.md` 的 `runtimeLoadPath` 口径 - -## T03 Scripting / Mono 托管销毁入口同步 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P1` -- 写入范围: - - `docs/api/XCEngine/Scripting/Mono/MonoScriptRuntime/**` -- 主要源码依据: - - `engine/include/XCEngine/Scripting/Mono/MonoScriptRuntime.h` - - `engine/src/Scripting/Mono/MonoScriptRuntime.cpp` - - `tests/scripting/test_mono_script_runtime.cpp` -- 已关闭问题: - - `DestroyManagedObject(MonoObject*)` 已进入头文件与测试,但文档树没有对应页面 - - `MonoScriptRuntime.md` 没写清 `Object.Destroy(...)` 会回落到原生对象 / 组件销毁 -- 完成记录: - - 已新增 `DestroyManagedObject.md` - - 已更新 `MonoScriptRuntime.md` 的 internal call 说明与方法总表 - -## T04 Cross-Module / 教程层与模块总览口径持续复核 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P2` -- 写入范围: - - `docs/api/_guides/**` - - `docs/api/XCEngine/*/*.md` - - 仅限与本轮已确认 API 变更直接相关的总览页 -- 任务目标: - - 继续检查教程页、模块总览页是否仍在传播旧心智模型 - - 尤其关注: - - Scene View 仍被写成 builtin post-process 主导 - - 资源导入链仍被写成 `artifactMainPath` / `RefreshAssetDatabase` 时代的口径 - - 托管对象销毁路径未在教程层被解释 -- 产出要求: - - 只修正已确认失配的 guide / overview 页面 - - 不做与当前源码无关的泛化扩写 -- 完成记录: - - 已完成一轮 guide / overview 复核 - - 已确认 `GameObject / Scene` 教程层仍在传播“tag 只是 name 别名”的旧心智 - - 已把后续需要实改的页面收口到 `T08` - -## T05 增量变更监控 / 新一轮差异发现 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P2` -- 写入范围: - - 只读检查 `engine/include/**`、`engine/src/**`、`editor/src/**`、`tests/**` - - 必要时只向本任务池追加新任务块 -- 任务目标: - - 继续结合工作树最新改动,找出新的“源码已变但文档还没跟上”的内容级失配 - - 优先检查: - - 最近改动过的 public headers - - 最近改动过的 Editor source headers - - 最近新增或更新过的测试 -- 产出要求: - - 只记录已确认的问题 - - 每条新任务都要写明: - - 受影响文档 - - 主要源码依据 - - 真实失配点 - - 建议写入范围 -- 完成记录: - - 已重新扫描当前工作树改动过的 public header、Editor source header、实现文件和测试 - - 已确认并追加 `T08` 与 `T09` 两组新的内容级失配 - -## T06 Rendering / Camera request、Passes 与执行链旧口径清理 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P1` -- 写入范围: - - `docs/api/XCEngine/Rendering/CameraRenderRequest/**` - - `docs/api/XCEngine/Rendering/CameraRenderer/**` - - `docs/api/XCEngine/Rendering/Passes/**` - - `docs/api/XCEngine/Rendering/ObjectIdPass/**` - - `docs/api/XCEngine/Rendering/RenderPipeline/**` - - `docs/api/XCEngine/Rendering/SceneRenderer/**` - - `docs/api/XCEngine/Editor/Viewport/SceneViewportRenderPlan/**` - - `docs/api/XCEngine/Editor/Viewport/Passes/**` - - 必要时 `docs/api/XCEngine/Editor/Viewport/Viewport.md` - - 必要时 `docs/api/XCEngine/XCEngine.md` - - 必要时 `docs/api/_tools/audit_api_docs.py` -- 主要源码依据: - - `engine/include/XCEngine/Rendering/CameraRenderRequest.h` - - `engine/include/XCEngine/Rendering/CameraRenderer.h` - - `engine/include/XCEngine/Rendering/Passes/BuiltinObjectIdPass.h` - - `engine/include/XCEngine/Rendering/Passes/BuiltinObjectIdOutlinePass.h` - - `engine/include/XCEngine/Rendering/Passes/BuiltinInfiniteGridPass.h` - - `engine/include/XCEngine/Rendering/ObjectIdPass.h` - - `engine/include/XCEngine/Rendering/RenderPipeline.h` - - `engine/include/XCEngine/Rendering/SceneRenderer.h` - - `engine/src/Rendering/CameraRenderer.cpp` - - `engine/src/Rendering/SceneRenderer.cpp` - - `tests/Rendering/unit/test_camera_scene_renderer.cpp` - - `editor/src/Viewport/SceneViewportRenderPlan.h` - - `editor/src/Viewport/Passes/SceneViewportGridPass.cpp` - - `editor/src/Viewport/Passes/SceneViewportSelectionOutlinePass.cpp` - - `editor/src/Viewport/ViewportHostRenderFlowUtils.h` - - `tests/Editor/test_viewport_render_flow_utils.cpp` - - `tests/Editor/test_scene_viewport_overlay_renderer.cpp` -- 已关闭问题: - - `CameraRenderRequest.md` 仍描述已删除的 `builtinPostProcess` 子请求 - - 页面仍链接到不存在的 `BuiltinPostProcessRequest/BuiltinPostProcessRequest.md` - - Scene View 已改为通过 `postScenePasses` / `overlayPasses` 写回 request,但该页口径未同步 - - `CameraRenderer.md` 仍保留已删除的 `m_builtinPostProcessBuilder` 历史口径 - - `Passes.md` 的典型链路只写了 `postScenePasses`,遗漏当前 `overlayPasses` 组装路径 - - `Rendering/Passes` 下仍残留已删除的 `BuiltinPostProcessPassPlan` / `BuiltinPostProcessPassSequenceBuilder` 页面 - - `BuiltinObjectIdPass`、`BuiltinInfiniteGridPass` 多个公开入口缺页 - - `ObjectIdPass.md`、`RenderPipeline.md`、`SceneRenderer.md` 与顶层 `XCEngine.md` 仍在传播 builtin-post-process 心智模型 -- 完成记录: - - 已重写 `CameraRenderRequest.md` - - 已清理 `2` 个失效 `.md` 链接 - - 已同步 `CameraRenderer.md` 与 `Passes.md` 的当前执行链路表述 - - 已删除 `BuiltinPostProcessPassPlan.md`、`BuiltinPostProcessPassSequenceBuilder.md` - - 已补齐 `BuiltinObjectIdPass` / `BuiltinInfiniteGridPass` 缺失页面,并补充 `SceneViewportRenderPlan` 下 grid / selection outline pass factory 页面 - - 已新增 `SceneViewportGridPass` / `SceneViewportSelectionOutlinePass` 页面,并同步 `Editor/Viewport/Passes/Passes.md` 与 `Viewport.md` 的当前口径 - - 已同步 `ObjectIdPass.md`、`RenderPipeline.md`、`SceneRenderer.md` 与 `XCEngine.md` 的当前口径 - - 已补充 Rendering guide、`CameraRenderer::Render` 与 `ViewportHostService::RenderRequestedViewports` 的 request 级 pass 注入说明 - - 已为 `builtinPostProcess` / `BuiltinPostProcessRequest` / `m_builtinPostProcessBuilder` 增加 canonical 过期符号审计 - - 已复跑结构审计并确认 `Broken .md links: 0` - -## T07 Editor / Core `EditorConsoleSink` 生命周期说明同步 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P1` -- 写入范围: - - `docs/api/XCEngine/Editor/Core/EditorConsoleSink/**` - - 必要时 `docs/api/XCEngine/Editor/Managers/Managers.md` - - 必要时 `docs/api/_tools/audit_api_docs.py` -- 主要源码依据: - - `editor/src/Core/EditorConsoleSink.h` - - `editor/src/Core/EditorConsoleSink.cpp` - - `editor/src/panels/ConsolePanel.cpp` - - `editor/src/Core/EditorLoggingSetup.h` - - `tests/Editor/test_editor_console_sink.cpp` -- 已关闭问题: - - `EditorConsoleSink::GetInstance()` 旧文档仍把它写成会返回 fallback 实例 - - 相关 overview 页面没有写清活动 sink 销毁后会返回 `nullptr` - - 审计脚本此前无法自动拦截这类旧生命周期表述 -- 完成记录: - - 已同步 `EditorConsoleSink.md`、`GetInstance.md`、`Destructor.md` - - 已同步 `Managers.md` 中对控制台 sink 生命周期的引用口径 - - 已为 `fallback 实例` 与“不会返回空指针”旧表述增加定向审计 - -## T08 Components / Scene / Scripting `GameObject` tag-layer 语义同步 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P0` -- 写入范围: - - `docs/api/XCEngine/Components/GameObject/**` - - `docs/api/XCEngine/Scene/Scene/**` - - `docs/api/XCEngine/Scripting/Mono/MonoScriptRuntime/**` - - 必要时 `docs/api/_guides/Components/GameObject-Component-Lifecycle-And-Serialization.md` - - 必要时 `docs/api/_guides/Scene/Scene-Lifecycle-And-Serialization.md` -- 主要源码依据: - - `engine/include/XCEngine/Components/GameObject.h` - - `engine/src/Components/GameObject.cpp` - - `engine/src/Scene/Scene.cpp` - - `engine/src/Scripting/Mono/MonoScriptRuntime.cpp` - - `managed/XCEngine.ScriptCore/GameObject.cs` - - `managed/XCEngine.ScriptCore/Component.cs` - - `managed/XCEngine.ScriptCore/InternalCalls.cs` - - `tests/Components/test_game_object.cpp` - - `tests/Scene/test_scene.cpp` - - `tests/scripting/test_mono_script_runtime.cpp` -- 已确认问题: - - `GameObject.md` 仍写“没有真正的 tag 系统”,并把 `FindGameObjectsWithTag()` 描述成按名字匹配;这与当前 `m_tag` / `CompareTag()` / `SetTag()` 实现已不符 - - `Scene.md` 与 `FindGameObjectWithTag.md` 仍在传播“按名字查 tag”的旧口径 - - `GameObject` 目录缺少 `GetTag.md`、`SetTag.md`、`CompareTag.md`、`GetLayer.md`、`SetLayer.md` - - `Serialize.md` / `Deserialize.md` 尚未同步 `tag=` 字段、默认 `Untagged` 以及 layer clamp 语义 - - `MonoScriptRuntime` 总览页尚未解释 Unity 风格 `GameObject.tag` / `layer` / `CompareTag()` 已通过 internal call 暴露到托管侧 -- 产出要求: - - 类型页与方法页都要明确区分当前原生实现、场景查询语义和托管暴露语义 - - 教程页要从“tag 只是名字别名”改成“当前已有独立 tag 字段,但仍是轻量 Unity 风格模型” - - 需要写清:空 tag 会回落到 `Untagged`,layer 会 clamp 到 `[0, 31]` -- 完成记录: - - 已重写 `GameObject.md`、`Scene.md`、`FindGameObjectWithTag.md` - - 已补齐 `GetTag.md`、`SetTag.md`、`CompareTag.md`、`GetLayer.md`、`SetLayer.md` - - 已同步 `Serialize.md` / `Deserialize.md` 的 `tag=`、`layer=`、`Untagged` 与 clamp 语义 - - 已同步 `MonoScriptRuntime.md` 对托管 `GameObject.tag` / `layer` / `CompareTag()` 的 internal call 桥接说明 - - 已同步 `GameObject-Component-Lifecycle-And-Serialization.md` 与 `Scene-Lifecycle-And-Serialization.md` 的心智模型口径 - - 已补充 `Constructor`、`AddComponent`、`SetActive`、`SetParent`、`IsActive`、`IsActiveInHierarchy`、`Find`、`FindObjectsOfType`、`Awake`、`Start`、`Update`、`FixedUpdate`、`LateUpdate`、`OnDestroy`、`Destroy` 的真实生命周期与查询语义 - -## T09 Resources / Material 与材质 Artifact v2 语义同步 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P0` -- 写入范围: - - `docs/api/XCEngine/Resources/Material/**` - - `docs/api/XCEngine/Core/Asset/ArtifactFormats/**` - - `docs/api/XCEngine/Core/Asset/AssetDatabase/**` - - 必要时 `docs/api/XCEngine/Core/Asset/Asset.md` -- 主要源码依据: - - `engine/include/XCEngine/Resources/Material/Material.h` - - `engine/src/Resources/Material/Material.cpp` - - `engine/src/Resources/Material/MaterialLoader.cpp` - - `engine/include/XCEngine/Core/Asset/ArtifactFormats.h` - - `engine/src/Core/Asset/AssetDatabase.cpp` - - `tests/Resources/Material/test_material.cpp` - - `tests/Resources/Material/test_material_loader.cpp` -- 已确认问题: - - `Resources/Material/Material.md` 仍是旧模板式总览,没有覆盖当前公开语义 - - `Material` 目录缺少 `SetTextureAssetRef.md`、`GetTextureBindingAssetRef.md`、`GetTextureBindingLoadedTexture.md` - - `ArtifactFormats.md` 仍写 `kMaterialArtifactSchemaVersion = 1`、magic `XCMAT01`,与当前 schema v2 / `XCMAT02` 不符 - - 材质 artifact 文档还没写清每个 texture binding 现在会序列化“编码后的 `AssetRef` + 可选 artifact/path 字符串” - - `Material` 文档尚未解释纹理绑定现在既有 loaded handle,也有稳定 `AssetRef` 元数据,并且 `GetTexture()` 可在首次访问时通过 `TryResolveAssetPath()` 兑现延迟资源 -- 产出要求: - - 重写 `Material.md`,把 shader 属性、texture binding 元数据、lazy resolve、路径与 `AssetRef` 的边界写清 - - 为新增公开方法补页,并把现有 `SetTexture` / `GetTexture` / `GetTextureBindingCount` 等页面更新到当前实现 - - 更新 `ArtifactFormats.md` 与 `AssetDatabase` 相关页面,写清材质 artifact v2 的字段顺序、兼容边界和设计动机 -- 完成记录: - - 已重写 `docs/api/XCEngine/Resources/Material/Material.md` 与 `docs/api/XCEngine/Resources/Material/Material/Material.md` - - 已新增 `SetTextureAssetRef.md`、`SetTexturePath.md`、`GetTextureBindingName.md`、`GetTextureBindingAssetRef.md`、`GetTextureBindingPath.md`、`GetTextureBindingLoadedTexture.md`、`GetTextureBindingTexture.md` - - 已同步 `SetTexture.md`、`GetTexture.md`、`GetTextureBindingCount.md` 的当前 lazy-load 与 binding metadata 语义 - - 已更新 `ArtifactFormats.md`,把材质 artifact 修正到 schema v2 / `XCMAT02`,并写清 texture binding 三元组布局 - - 已更新 `AssetDatabase.md` 的当前 importerVersion 常量,并在 `Asset.md` 补充 `.xcmat` v2 的跨模块说明 - - 已为旧 `kMaterialArtifactSchemaVersion = 1` / `XCMAT01` 文案增加审计回归保护 - - 已复跑结构审计并确认 `Broken .md links: 0` - -## T10 Editor / Viewport `SceneViewportShaderPaths` canonical 补页 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P1` -- 写入范围: - - `docs/api/XCEngine/Editor/Viewport/SceneViewportShaderPaths/**` - - 必要时 `docs/api/XCEngine/Editor/Viewport/Viewport.md` -- 主要源码依据: - - `editor/src/Viewport/SceneViewportShaderPaths.h` - - `editor/src/Viewport/Passes/SceneViewportGridPass.cpp` - - `editor/src/Viewport/Passes/SceneViewportSelectionOutlinePass.cpp` -- 已关闭问题: - - `editor/src/Viewport/SceneViewportShaderPaths.h` 新进入工作树后还没有 canonical 页面 - - 结构审计一度出现 `Editor source headers: 125` 但 `Valid source refs (Editor canonical): 124` -- 完成记录: - - 已新增 `SceneViewportShaderPaths.md` 与 5 个 helper/function 页面 - - 已同步 `Viewport.md` 的目录结构总览 - - 已复跑结构审计并确认 `Valid source refs (Editor canonical): 125` - -## T11 Platform / 概述页与方法页模板化表述清理 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P1` -- 写入范围: - - `docs/api/XCEngine/Platform/**` -- 主要源码依据: - - `engine/include/XCEngine/Platform/GameTime.h` - - `engine/include/XCEngine/Platform/PlatformTypes.h` - - `engine/include/XCEngine/Platform/IClock.h` - - `engine/include/XCEngine/Platform/IWindow.h` - - `engine/include/XCEngine/Platform/IFileSystem.h` - - `engine/include/XCEngine/Platform/IPlatform.h` - - `engine/include/XCEngine/Platform/IDynamicLibrary.h` - - `engine/include/XCEngine/Platform/IDisplayEnumerator.h` - - `engine/include/XCEngine/Platform/Window.h` - - `engine/include/XCEngine/Platform/Windows/WindowsWindow.h` - - `engine/include/XCEngine/Platform/Windows/WindowsInputModule.h` - - `engine/src/Platform/Window.cpp` - - `engine/src/Platform/Windows/WindowsWindow.cpp` - - `engine/src/Platform/Windows/WindowsInputModule.cpp` -- 已关闭问题: - - `Platform` 下多组概述页仍把自己写成“canonical 总览”,没有说明符号本身的职责和边界 - - 大量方法页仍残留“获取相关状态或对象”“公开方法,详见头文件声明”“参数语义详见头文件声明”等模板废话 - - `WindowsInputModule::PumpEvents()` 当前是空实现,但旧页没有说明,容易误导成真实消息泵入口 - - `WindowsWindow` 的 Win32 当前实现路径没有写清,例如 `Create()`、`Destroy()`、`PumpEvents()`、`SetFullscreen()` 与 `ShouldClose()` -- 完成记录: - - 已重写 `GameTime`、`PlatformTypes`、`IClock`、`IWindow`、`IFileSystem`、`IPlatform`、`IDynamicLibrary`、`IDisplayEnumerator`、`Window`、`WindowsWindow`、`WindowsInputModule` 相关 overview 与方法页 - - 已把 `Platform` 下残留模板短语、参数占位语和 canonical 占位说明清理到 `0` - - 已写清 `Window` / `WindowsWindow` / `WindowsInputModule` 的当前实现语义与输入桥接关系 - - 已复跑结构审计并确认 `Old template pages: 0`、`Broken .md links: 0` - -## T12 Cross-Module / canonical 式概述废话清理 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P1` -- 写入范围: - - `docs/api/XCEngine/RHI/D3D12/**` - - `docs/api/XCEngine/Resources/**` -- 主要源码依据: - - `engine/include/XCEngine/RHI/D3D12/D3D12Types.h` - - `engine/include/XCEngine/RHI/D3D12/D3D12Common.h` - - `engine/include/XCEngine/RHI/D3D12/D3D12Enums.h` - - `engine/include/XCEngine/RHI/D3D12/D3D12RootSignature.h` - - `engine/include/XCEngine/RHI/D3D12/D3D12PipelineState.h` - - `engine/include/XCEngine/Resources/Resources.h` - - `engine/include/XCEngine/Resources/Texture/Texture.h` - - `engine/include/XCEngine/Resources/Texture/TextureLoader.h` - - `engine/include/XCEngine/Resources/Texture/TextureImportSettings.h` - - `engine/include/XCEngine/Resources/Mesh/Mesh.h` - - `engine/include/XCEngine/Resources/Mesh/MeshLoader.h` - - `engine/include/XCEngine/Resources/Mesh/MeshImportSettings.h` - - `engine/include/XCEngine/Resources/Shader/Shader.h` - - `engine/include/XCEngine/Resources/Shader/ShaderLoader.h` - - `engine/include/XCEngine/Resources/AudioClip/AudioClip.h` - - `engine/include/XCEngine/Resources/AudioClip/AudioLoader.h` -- 已关闭问题: - - `RHI/D3D12` 与 `Resources` 下仍有一批 overview 页把“概述”写成“当前页面作为平行目录中的 canonical 总览”,没有解释符号本身做什么 - - `Texture`、`Mesh`、`Shader`、`AudioClip` 等资源类型页没有把类型边界、持有的数据和辅助声明写清 - - `D3D12Types`、`D3D12Common`、`D3D12Enums` 这类 helper header 页没有说明它们是转换 / 构造 / 检查函数集合 -- 完成记录: - - 已重写 `RHI/D3D12` 与 `Resources` 下全部 `16` 个 canonical 式 overview 页 - - 已把全仓库 `docs/api/XCEngine/**` 中“当前页面作为平行目录中的 canonical 总览”这类概述废话清理到 `0` - - 已复跑结构审计并确认 `Broken .md links: 0`、`Old template pages: 0` - -## T13 Components / `GameObject` 剩余辅助页内容化补齐 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P2` -- 写入范围: - - `docs/api/XCEngine/Components/GameObject/DetachChildren.md` - - `docs/api/XCEngine/Components/GameObject/DetachFromParent.md` - - `docs/api/XCEngine/Components/GameObject/Destructor.md` - - `docs/api/XCEngine/Components/GameObject/GetChild.md` - - `docs/api/XCEngine/Components/GameObject/GetChildCount.md` - - `docs/api/XCEngine/Components/GameObject/GetChildren.md` - - `docs/api/XCEngine/Components/GameObject/GetComponent.md` - - `docs/api/XCEngine/Components/GameObject/GetComponentInChildren.md` - - `docs/api/XCEngine/Components/GameObject/GetComponentInParent.md` - - `docs/api/XCEngine/Components/GameObject/GetComponents.md` - - `docs/api/XCEngine/Components/GameObject/GetComponentsInChildren.md` - - `docs/api/XCEngine/Components/GameObject/GetID.md` - - `docs/api/XCEngine/Components/GameObject/GetName.md` - - `docs/api/XCEngine/Components/GameObject/GetParent.md` - - `docs/api/XCEngine/Components/GameObject/GetScene.md` - - `docs/api/XCEngine/Components/GameObject/GetTransform.md` - - `docs/api/XCEngine/Components/GameObject/GetUUID.md` - - `docs/api/XCEngine/Components/GameObject/RemoveComponent.md` - - `docs/api/XCEngine/Components/GameObject/SetName.md` -- 主要源码依据: - - `engine/include/XCEngine/Components/GameObject.h` - - `engine/src/Components/GameObject.cpp` - - `tests/Components/test_game_object.cpp` - - `tests/Scene/test_scene.cpp` - - `tests/scripting/test_mono_script_runtime.cpp` -- 已关闭问题: - - `GameObject` 模块的核心生命周期、tag/layer 与场景边界已经完成,但上述辅助页仍残留模板式表述 - - 这些页面尚未写清模板接口的真实语义,例如 `Transform` 特例、层级递归查询、观察指针返回与移除边界 -- 产出要求: - - 把这些方法页全部改成基于当前源码的行为说明 - - 优先写清组件查询模板、层级辅助接口和对象身份访问器的真实边界 - - 不重复改动本轮已经完成内容化的生命周期和 tag/layer 页面 -- 完成记录: - - 已重写 `DetachChildren.md`、`DetachFromParent.md`、`Destructor.md`、`GetChild.md`、`GetChildCount.md`、`GetChildren.md` - - 已重写 `GetComponent.md`、`GetComponentInChildren.md`、`GetComponentInParent.md`、`GetComponents.md`、`GetComponentsInChildren.md`、`RemoveComponent.md` - - 已重写 `GetID.md`、`GetName.md`、`GetParent.md`、`GetScene.md`、`GetTransform.md`、`GetUUID.md`、`SetName.md` - - 已写清 `Transform` 特例、层级查询顺序、`Scene` / registry 观察指针语义,以及 `RemoveComponent` 当前不会补发 `OnDestroy()` - -## T14 Rendering / `RenderMaterialUtility` schema-driven 材质常量 payload 语义同步 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P1` -- 写入范围: - - `docs/api/XCEngine/Rendering/RenderMaterialUtility/**` - - `docs/api/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline/**` - - 必要时 `docs/api/_tools/audit_api_docs.py` -- 主要源码依据: - - `engine/include/XCEngine/Rendering/RenderMaterialUtility.h` - - `engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h` - - `engine/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp` - - `engine/include/XCEngine/Resources/Material/Material.h` - - `tests/Rendering/unit/test_render_scene_extractor.cpp` -- 已关闭问题: - - `RenderMaterialUtility` 总览页尚未覆盖新进入 public header 的 `MaterialConstantPayloadView` 与 `ResolveSchemaMaterialConstantPayload(...)` - - `BuiltinForwardPipeline.md` 仍把逐材质常量写成“目前只写入 `baseColorFactor`”,没有同步当前优先消费 schema-driven payload、仅在缺失时回退 fallback 的实现 - - `BuildBuiltinForwardMaterialData.md` 还没把自己定位成兼容 fallback helper,容易与当前主路径混淆 -- 完成记录: - - 已新增 `BuiltinForwardMaterialData.md`、`MaterialConstantPayloadView.md`、`ResolveSchemaMaterialConstantPayload.md` - - 已重写 `RenderMaterialUtility.md` 与 `BuildBuiltinForwardMaterialData.md`,写清 schema payload 与 builtin forward fallback 的边界 - - 已同步 `BuiltinForwardPipeline.md` 与 `Render.md`,补入 `Material::GetConstantBufferData()` 主路径和 `FallbackPerMaterialConstants` 回退逻辑 -- 已为“逐材质常量目前只写入 `baseColorFactor`”等旧口径增加 canonical 审计回归保护 -- 已复跑结构审计并确认 `Stale canonical doc tokens: 0` - -## T15 Editor / Scripting 脚本程序集重建与运行时锁语义同步 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P1` -- 写入范围: - - `docs/api/XCEngine/Editor/Scripting/EditorScriptAssemblyBuilder/**` - - `docs/api/XCEngine/Editor/Application/RebuildScriptingAssemblies.md` - - 必要时 `docs/api/XCEngine/Editor/Application/Application.md` -- 主要源码依据: - - `editor/src/Scripting/EditorScriptAssemblyBuilder.cpp` - - `editor/src/Application.cpp` - - `tests/Editor/test_editor_script_assembly_builder.cpp` -- 已关闭问题: - - `EditorScriptAssemblyBuilder` 文档仍把 `mscorlib.dll` 写成“每次重建都复制”,没有同步当前“首次复制、后续复用项目本地 corlib”的实现 - - `RebuildProjectAssemblies.md` 没写清活动 Mono runtime 仍持有 `GameScripts.dll` 时可能出现文件锁失败 - - `Application::RebuildScriptingAssemblies` 文档仍写“构建失败会保留旧 runtime”,与当前先 `ShutdownScriptingRuntime()` 再构建的实现不符 -- 完成记录: - - 已同步 `EditorScriptAssemblyBuilder.md` 与 `RebuildProjectAssemblies.md`,补入项目本地 `mscorlib.dll` 复用策略、Mono 文件锁失败路径与测试锚点 - - 已同步 `Application::RebuildScriptingAssemblies` 与 `Application.md`,写清“先卸载当前 runtime 释放程序集锁,再重建并在成功后重载”的真实顺序 - - 已复跑结构审计并确认 `Broken .md links: 0`、`Stale canonical doc tokens: 0` - -## T16 Resources / Rendering Passes SceneView shader ownership 口径同步 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P1` -- 写入范围: - - `docs/api/XCEngine/Resources/BuiltinResources/**` - - `docs/api/XCEngine/Rendering/Passes/BuiltinInfiniteGridPass/**` - - `docs/api/XCEngine/Rendering/Passes/BuiltinObjectIdOutlinePass/**` -- 主要源码依据: - - `engine/include/XCEngine/Resources/BuiltinResources.h` - - `engine/src/Resources/BuiltinResources.cpp` - - `engine/include/XCEngine/Rendering/Passes/BuiltinInfiniteGridPass.h` - - `engine/src/Rendering/Passes/BuiltinInfiniteGridPass.cpp` - - `engine/include/XCEngine/Rendering/Passes/BuiltinObjectIdOutlinePass.h` - - `engine/src/Rendering/Passes/BuiltinObjectIdOutlinePass.cpp` - - `editor/src/Viewport/SceneViewportShaderPaths.h` -- 已关闭问题: - - `BuiltinResources.md` 仍把 `object-id-outline` / `infinite-grid` 写成 engine `builtin://shaders/*` helper 与 builtin shader 工厂的一部分,但当前头文件与实现已只保留 `forward-lit`、`object-id` - - `BuiltinInfiniteGridPass` 与 `BuiltinObjectIdOutlinePass` 已改为由调用方注入 `shaderPath`,文档却仍在传播“固定 builtin shader”口径 - - 两个 pass 的文档树缺少 `SetShaderPath.md`、`GetShaderPath.md`;`BuiltinInfiniteGridPass` 还缺少 `Constructor.md` 与 `Destructor.md` -- 完成记录: - - 已更新 `BuiltinResources.md`,把 builtin shader 集合收口到 `forward-lit` / `object-id`,并写清 Scene View grid / outline shader 现在由 editor 侧 `SceneViewportShaderPaths` 提供 - - 已重写 `BuiltinInfiniteGridPass.md`、`Render.md`、`Shutdown.md`,补上 `Constructor.md`、`Destructor.md`、`SetShaderPath.md`、`GetShaderPath.md` - - 已重写 `BuiltinObjectIdOutlinePass.md`、`Constructor.md`、`Render.md`、`Shutdown.md`,补上 `SetShaderPath.md` 与 `GetShaderPath.md` - -## T17 Editor / Viewport `SceneViewportOverlayProviders` canonical 补页 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P1` -- 写入范围: - - `docs/api/XCEngine/Editor/Viewport/SceneViewportOverlayProviders/**` -- 主要源码依据: - - `editor/src/Viewport/SceneViewportOverlayProviders.h` - - `editor/src/Viewport/SceneViewportOverlayProviders.cpp` - - `editor/src/Viewport/SceneViewportOverlayBuilder.h` - - `tests/editor/test_scene_viewport_overlay_providers.cpp` -- 已关闭问题: - - `editor/src/Viewport/SceneViewportOverlayProviders.h` 新进入工作树后还没有 canonical 页面 - - 结构审计出现 `Editor source headers: 126` 但 `Valid source refs (Editor canonical): 125` - - 缺少对 overlay build context、provider registry 和默认相机 / 灯光 provider 工厂的当前语义说明 -- 完成记录: - - 已新增 `SceneViewportOverlayProviders.md`、`SceneViewportOverlayBuildContext.md`、`ISceneViewportOverlayProvider.md`、`SceneViewportOverlayProviderRegistry.md` - - 已新增 `CreateSceneViewportCameraOverlayProvider.md`、`CreateSceneViewportLightOverlayProvider.md`、`BuildDefaultSceneViewportOverlayProviderRegistry.md` - - 已复跑结构审计并确认 `Valid source refs (Editor canonical): 126`、`Broken .md links: 0` - -## T18 Editor / Viewport overlay builder provider 化口径同步 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P1` -- 写入范围: - - `docs/api/XCEngine/Editor/Viewport/SceneViewportOverlayBuilder/**` - - `docs/api/XCEngine/Editor/Viewport/ViewportHostService/**` - - `docs/api/XCEngine/Editor/Viewport/Viewport.md` - - 必要时 `docs/api/_guides/Editor/**` -- 主要源码依据: - - `editor/src/Viewport/SceneViewportOverlayBuilder.h` - - `editor/src/Viewport/SceneViewportOverlayBuilder.cpp` - - `editor/src/Viewport/SceneViewportOverlayProviders.h` - - `editor/src/Viewport/ViewportHostService.h` - - `tests/editor/test_scene_viewport_overlay_providers.cpp` -- 已关闭问题: - - `SceneViewportOverlayBuilder` 文档仍把类型写成“无状态构建器 + static Build(...)”,没有同步当前实例化 builder + provider registry 的实现 - - `SceneViewportOverlayBuilder` 目录缺少 `Constructor.md` 与 `GetProviderRegistry.md` - - `ViewportHostService.md` 仍把基础 overlay 来源写成 `SceneViewportOverlayBuilder::Build(...)`,没有同步当前成员 `m_sceneViewportOverlayBuilder` - - guide 层仍在传播 `overlay builder -> overlay pass` 与“先把 scene icon 塞进 builder”的旧扩展口径 -- 完成记录: - - 已重写 `SceneViewportOverlayBuilder.md` 与 `Build.md`,把默认 registry、build context 和 provider 聚合链路写清 - - 已新增 `Constructor.md` 与 `GetProviderRegistry.md` - - 已同步 `ViewportHostService.md`、`Viewport.md` 以及两篇 Editor guide 的当前 provider 化心智模型 - - 已把新扩展建议改成 `overlay provider -> overlay builder -> overlay pass` - -## 当前结论 - -- 本轮已经关掉十七组明确的内容级失配: - - `Viewport` 渲染计划与宿主流程 - - `Core/Asset` 缓存接口与导入服务语义 - - `MonoScriptRuntime` 托管销毁入口 - - `Rendering` 相机请求、Passes 与单相机执行链旧口径 - - `EditorConsoleSink` 生命周期与空指针返回语义 - - 跨模块 guide / overview 漂移的本轮复核与拆分 - - `Material` / `ArtifactFormats` / `AssetDatabase` 的材质 artifact v2 与 texture `AssetRef` 语义 - - `GameObject` / `Scene` / `Mono` 的 tag-layer 与托管暴露语义 - - `GameObject` 剩余辅助访问器 / 层级辅助页的内容化补齐 - - `SceneViewportShaderPaths` canonical 缺页 - - `Platform` 模块概述页与方法页的模板化废话清理 -- `RHI/D3D12` 与 `Resources` 的 canonical 式概述废话清理 -- `RenderMaterialUtility` / `BuiltinForwardPipeline` 的 schema-driven 材质常量 payload 语义 -- `Editor` 脚本程序集重建流程中的 corlib 复用与 runtime 文件锁语义 -- `BuiltinResources` 与 Scene View grid / outline shader ownership、`shaderPath` 注入口径 -- `SceneViewportOverlayProviders` canonical 缺页 -- `SceneViewportOverlayBuilder` / `ViewportHostService` / guide 层的 provider 化心智模型同步 -- 当前结构审计为全绿。 -- 当前仍建议保留 `T04` 与 `T05` 作为持续性复核入口,用来承接后续源码变更带来的新文档漂移。 diff --git a/docs/used/API文档并行更新任务池_2026-04-02.md b/docs/used/API文档并行更新任务池_2026-04-02.md deleted file mode 100644 index 9d3e26dd..00000000 --- a/docs/used/API文档并行更新任务池_2026-04-02.md +++ /dev/null @@ -1,590 +0,0 @@ -# API 文档并行更新任务池(2026-04-02) - -## 目的 - -基于 `2026-04-02` 当前工作树,这份清单用于把 API 文档更新任务拆成可并行认领的独立块,供多个会话同时推进。 - -## 认领规则 - -- 一次只认领 `1` 个任务块,先改 `状态` 和 `认领人`。 -- 只修改自己任务块的 `写入范围`,不要跨任务顺手改别的模块页。 -- 除 `T09` 之外,其他任务不要更新 `docs/api/_meta/rebuild-status.md`,避免多人冲突。 -- 每个任务都要以源码、实现、测试、真实调用点为依据,不允许只按命名猜测行为。 -- 如果任务执行中发现需要新增 guide,统一放到 `docs/api/_guides//` 下。 - -## 当前并行推荐 - -- 历史任务 `T01-T20` 已完成,本轮文档内容同步与收口已落地。 -- 如果继续并行推进,重点不再是补历史缺页,而是: - - 跟踪新增源码 API - - 继续人工抽样审阅审计中提示的高风险单页目录 -- `2026-04-03 00:24:08` 这轮复核后,已新增 `T21-T27`;优先从 `Input`、`Editor` 项目工作流、`Game View -> Runtime Input` 三块并行切入。 - -## 任务池 - -## T01 Editor / Viewport 子模块补齐与重写 - -- 状态: `DONE` -- 认领人: `Codex-Viewport` -- 优先级: `P0` -- 写入范围: `docs/api/XCEngine/Editor/Viewport/**`、`docs/api/XCEngine/Editor/panels/SceneViewPanel/**`、`docs/api/XCEngine/Editor/panels/ViewportPanelContent/**` -- 主要源码依据: `editor/src/Viewport/**`、`editor/src/panels/SceneViewPanel.*`、`editor/src/panels/ViewportPanelContent.h`、`tests/editor/test_scene_viewport_camera_controller.cpp` -- 当前缺口: `Viewport` 整个 canonical 树尚未建立;以下页面当前缺失: `SceneViewportCameraController`、`SceneViewportMoveGizmo`、`SceneViewportRotateGizmo`、`SceneViewportScaleGizmo`、`SceneViewportOverlayRenderer`、`ViewportHostService`、`ViewportHostRenderFlowUtils`、`SceneViewportEditorOverlayData`、`SceneViewportOverlayBuilder`、`ViewportPanelContent` -- 完成标准: 补齐 `Viewport/Viewport.md` 与所有类型页;`SceneViewPanel` 文档重写到当前 gizmo / overlay / host flow 实现;写清楚生命周期、交互链路、渲染路径和测试覆盖 - -## T02 Editor / ScriptComponentEditor 补齐 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P0` -- 写入范围: `docs/api/XCEngine/Editor/ComponentEditors/ScriptComponentEditor/**`、`docs/api/XCEngine/Editor/ComponentEditors/ScriptComponentEditorUtils/**`、`docs/api/XCEngine/Editor/ComponentEditors/ComponentEditors.md`、`docs/api/XCEngine/Editor/ComponentEditors/ComponentEditorRegistry/**` -- 主要源码依据: `editor/src/ComponentEditors/ScriptComponentEditor.h`、`editor/src/ComponentEditors/ScriptComponentEditorUtils.h`、`editor/src/ComponentEditors/ComponentEditorRegistry.cpp` -- 当前缺口: `ScriptComponentEditor` 与 `ScriptComponentEditorUtils` 还没有 canonical 页面;组件编辑器总览也需要纳入脚本组件编辑器 -- 完成标准: 补齐缺页;说明 Inspector 侧脚本字段绘制、字段元数据来源、与 `ScriptEngine` / `ScriptComponent` 的关系 - -## T03 Core / AssetDatabase 新建与资产数据库链路说明 - -- 状态: `DONE` -- 认领人: `Codex-Asset` -- 优先级: `P0` -- 写入范围: `docs/api/XCEngine/Core/Asset/AssetDatabase/**`、`docs/api/XCEngine/Core/Asset/Asset.md` -- 主要源码依据: `engine/include/XCEngine/Core/Asset/AssetDatabase.h`、相关 `.cpp` 实现、项目目录下新增的 `.meta` 与 `Library` 资产缓存变化 -- 当前缺口: `AssetDatabase` 对应的 canonical 类型页完全缺失;`Core/Asset` 模块总览需要反映新的数据库/导入缓存方向 -- 完成标准: 建立 `AssetDatabase` 页面,明确 GUID、path、meta、导入缓存、查询职责,以及它和 `ProjectPanel` / `ResourceManager` / 资源导入流程的关系 - -## T04 Rendering / Passes 子模块与 BuiltinObjectIdOutlinePass 补齐 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P0` -- 写入范围: `docs/api/XCEngine/Rendering/Passes/**` -- 主要源码依据: `engine/include/XCEngine/Rendering/Passes/BuiltinObjectIdOutlinePass.h`、`engine/src/Rendering/Passes/BuiltinObjectIdOutlinePass.cpp` -- 当前缺口: `Rendering/Passes` 目录当前没有 canonical 文档树;`BuiltinObjectIdOutlinePass` 页面缺失 -- 完成标准: 新建 `Passes/Passes.md` 和 `BuiltinObjectIdOutlinePass` 类型页;写清楚对象 ID / 轮廓高亮的输入输出、依赖资源、典型使用位置和当前限制 - -## T05 Scripting 模块内容重构 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P1` -- 写入范围: `docs/api/XCEngine/Scripting/**`、`docs/api/_guides/Scripting/**` -- 主要源码依据: `engine/include/XCEngine/Scripting/IScriptRuntime.h`、`Mono/MonoScriptRuntime.h`、`NullScriptRuntime.h`、`ScriptComponent.h`、`ScriptEngine.h` 及对应 `.cpp`;`tests/scripting/**` -- 当前缺口: 结构存在,但脚本运行时、字段同步、项目脚本程序集、空运行时回退等内容需要按当前实现重写 -- 完成标准: 明确运行时抽象、Mono 后端、Null 后端、字段存储与组件生命周期;必要时补一篇项目脚本程序集 / 字段同步 guide - -## T06 Editor 运行时胶水层与面板内容更新 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P1` -- 写入范围: `docs/api/XCEngine/Editor/Application/**`、`docs/api/XCEngine/Editor/Core/EventBus/**`、`docs/api/XCEngine/Editor/panels/InspectorPanel/**`、`docs/api/XCEngine/Editor/panels/ProjectPanel/**`、`docs/api/XCEngine/Editor/UI/Widgets/**`、`docs/api/XCEngine/Editor/Actions/HierarchyActionRouter/**`、`docs/api/XCEngine/Editor/Commands/EntityCommands/**` -- 主要源码依据: `editor/src/Application.*`、`editor/src/Core/EventBus.h`、`editor/src/panels/InspectorPanel.*`、`editor/src/panels/ProjectPanel.*`、`editor/src/UI/Widgets.h`、`editor/src/Actions/HierarchyActionRouter.h`、`editor/src/Commands/EntityCommands.h` -- 当前缺口: 这些页面虽然大多存在,但内容容易落后于当前交互链路;`ProjectPanel` 虽已较新,仍要根据这轮源码变化做二次核对 -- 完成标准: 把“Editor 主循环 -> EventBus -> 面板 -> Action/Command”这条链路写清楚;Inspector/Project/Hierarchy 相关页内容与当前实现严格对齐 - -## T07 Rendering 相机请求与对象 ID 渲染链路更新 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P1` -- 写入范围: `docs/api/XCEngine/Rendering/CameraRenderRequest/**`、`docs/api/XCEngine/Rendering/CameraRenderer/**`、`docs/api/XCEngine/Rendering/RenderMaterialUtility/**`、`docs/api/XCEngine/Rendering/Rendering.md` -- 主要源码依据: `engine/include/XCEngine/Rendering/CameraRenderRequest.h`、`engine/src/Rendering/CameraRenderer.cpp`、`engine/src/Resources/Material/MaterialLoader.cpp`、`engine/src/Rendering/Passes/BuiltinObjectIdOutlinePass.cpp` -- 当前缺口: 文档需要反映这轮 renderer 里对象 ID、outline、camera request、材质 render state 的新关系 -- 完成标准: 写清楚 camera request 的职责边界、camera renderer 的主流程、object-id/outline 的接入点,以及材质 render state 对渲染路径的影响 - -## T08 Components / MeshFilterComponent 与资源绑定链路更新 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P1` -- 写入范围: `docs/api/XCEngine/Components/MeshFilterComponent/**`、`docs/api/XCEngine/Components/Components.md` -- 主要源码依据: `engine/include/XCEngine/Components/MeshFilterComponent.h`、相关 `.cpp`、`tests/Resources/Mesh/test_mesh_loader.cpp`、`tests/Resources/Material/test_material_loader.cpp` -- 当前缺口: `MeshFilterComponent` 页面存在,但需要重新核对 mesh handle / path / 资源解析链路;模块总览也应补充 MeshFilter 在渲染和资产导入链路中的定位 -- 完成标准: 说明 `MeshFilterComponent` 如何保存 mesh 引用、如何与资源系统和渲染提取流程衔接,以及当前限制 - -## T09 根总览与最终审计 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P2` -- 写入范围: `docs/api/XCEngine/XCEngine.md`、受影响的模块总览页、`docs/api/_meta/rebuild-status.md` -- 主要源码依据: 前面所有任务的完成结果 -- 当前缺口: 已完成根总览与模块总览收口;`2026-04-02 23:22:27` 的最终审计结果显示 `246/246` 个 public headers 全量覆盖,结构性问题保持 `0` -- 完成标准: 在 `T10-T13` 完成后统一调整总览页导航;重新执行 `audit_api_docs.py`、覆盖校验与链接校验;确认未覆盖 public headers 归零后写回最终 `rebuild-status.md` - -## 审计结论(2026-04-02 23:22:27) - -- 本轮全量审计已执行: `python docs/api/_tools/audit_api_docs.py` -- 当前结构性问题为 `0`: 无失效 `.md` 链接、无非 `.md` 相对链接、无旧模板页面、无扁平 header 页面、无缺失目录总览页 -- 当前剩余覆盖缺口: `0` 个 public headers 未覆盖 -- 受影响模块: - - `Core`: `0` - - `Rendering`: `0` - - `Resources`: `0` - - `Scene`: `0` -- 说明: - - `T01-T13` 对应的结构重构、基础层补页与总览收口已全部落地 - - `AssetImportService` 与 `ProjectAssetIndex` 两个 `Core/Asset` 尾项已补齐 - - canonical API 文档树当前与 `engine/include/XCEngine` 的 public header 集合保持全量对齐 - -## T10 Core / Asset 标识与产物格式补齐 - -- 状态: `DONE` -- 认领人: `Codex-Asset2` -- 优先级: `P1` -- 写入范围: `docs/api/XCEngine/Core/Asset/ArtifactFormats/**`、`docs/api/XCEngine/Core/Asset/AssetGUID/**`、`docs/api/XCEngine/Core/Asset/AssetRef/**` -- 主要源码依据: `engine/include/XCEngine/Core/Asset/ArtifactFormats.h`、`engine/include/XCEngine/Core/Asset/AssetGUID.h`、`engine/include/XCEngine/Core/Asset/AssetRef.h`、相关使用点 `engine/src/Components/MeshFilterComponent.cpp`、`engine/src/Resources/*/*Loader.cpp`、`tests/core/Asset/test_resource_manager.cpp`、`tests/Resources/Texture/test_texture_loader.cpp` -- 当前缺口: 已补齐 `ArtifactFormats`、`AssetGUID`、`AssetRef` 三个 canonical 页面,并将资产身份 / 引用 / artifact 格式链路并入 `Core/Asset` 模块叙述 -- 完成标准: 建立三个 canonical 类型页,写清楚 GUID 格式、AssetRef 结构语义、artifact 产物文件格式在资源导入与反序列化链路中的作用 - -## T11 Rendering / RenderPass 与 SceneRenderRequest 基础层补齐 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P1` -- 写入范围: `docs/api/XCEngine/Rendering/ObjectIdEncoding/**`、`docs/api/XCEngine/Rendering/ObjectIdPass/**`、`docs/api/XCEngine/Rendering/RenderPass/**`、`docs/api/XCEngine/Rendering/RenderSceneUtility/**`、`docs/api/XCEngine/Rendering/SceneRenderRequestPlanner/**`、`docs/api/XCEngine/Rendering/SceneRenderRequestUtils/**` -- 主要源码依据: `engine/include/XCEngine/Rendering/ObjectIdEncoding.h`、`ObjectIdPass.h`、`RenderPass.h`、`RenderSceneUtility.h`、`SceneRenderRequestPlanner.h`、`SceneRenderRequestUtils.h`,对应 `.cpp`,以及 `tests/Rendering/unit/test_render_pass.cpp`、`test_render_scene_utility.cpp`、`test_scene_render_request_planner.cpp`、`test_scene_render_request_utils.cpp`、`test_camera_scene_renderer.cpp`、`tests/Editor/test_viewport_render_flow_utils.cpp` -- 当前缺口: 已补齐 `ObjectIdEncoding`、`ObjectIdPass`、`RenderPass`、`RenderSceneUtility`、`SceneRenderRequestPlanner`、`SceneRenderRequestUtils` 六个基础层页面,并回写 Rendering 总览导航 -- 完成标准: 补齐六个类型页,明确 render pass 生命周期、object-id 编解码语义、scene request 规划规则,以及这些基础设施与 `CameraRenderer` / editor viewport 的衔接 - -## T12 Resources / BuiltinResources 补齐 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P1` -- 写入范围: `docs/api/XCEngine/Resources/BuiltinResources/**` -- 主要源码依据: `engine/include/XCEngine/Resources/BuiltinResources.h`、`engine/src/Resources/BuiltinResources.cpp`、相关调用点 `editor/src/Commands/EntityCommands.h`、`engine/src/Rendering/Passes/BuiltinObjectIdPass.cpp`、`engine/src/Rendering/Passes/BuiltinObjectIdOutlinePass.cpp`、`engine/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp`、`tests/Resources/Shader/test_shader_loader.cpp` -- 当前缺口: 已建立 `BuiltinResources` canonical 页面,并纳入 `Resources` 模块总览 -- 完成标准: 建立 `BuiltinResources` 类型页,写清楚内置 mesh / material / shader / primitive display name 的职责边界、典型调用点与当前限制 - -## T13 Scene / RuntimeLoop 补齐 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P1` -- 写入范围: `docs/api/XCEngine/Scene/RuntimeLoop/**` -- 主要源码依据: `engine/include/XCEngine/Scene/RuntimeLoop.h`、`engine/src/Scene/RuntimeLoop.cpp`、`editor/src/Core/PlaySessionController.h`、`tests/Scene/test_runtime_loop.cpp` -- 当前缺口: 已建立 `RuntimeLoop` canonical 页面,并把 fixed step / pause / step-frame 语义并入 `Scene` 模块导航 -- 完成标准: 建立 `RuntimeLoop` 类型页,说明 fixed step / max delta / max fixed steps 这些设置的语义,以及它与 play mode、scene tick 生命周期的关系 - -## T14 Core / Asset 服务与索引尾项补齐 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P1` -- 写入范围: `docs/api/XCEngine/Core/Asset/AssetImportService/**`、`docs/api/XCEngine/Core/Asset/ProjectAssetIndex/**`、必要时补充 `docs/api/XCEngine/Core/Asset/Asset.md` -- 主要源码依据: `engine/include/XCEngine/Core/Asset/AssetImportService.h`、`engine/src/Core/Asset/AssetImportService.cpp`、`engine/include/XCEngine/Core/Asset/ProjectAssetIndex.h`、`engine/src/Core/Asset/ProjectAssetIndex.cpp`、`tests/core/Asset/test_resource_manager.cpp` -- 当前缺口: 已补齐 `AssetImportService`、`ProjectAssetIndex` 两个 canonical 页面,并把它们补入 `Core/Asset` 模块总览 -- 完成标准: 建立两个类型页,写清楚 `AssetDatabase -> AssetImportService -> ProjectAssetIndex -> ResourceManager` 这条项目资产查询链路 - -## 二次审核结论(2026-04-02 23:31:03) - -- 本轮全量结构审计已执行: `python docs/api/_tools/audit_api_docs.py` -- 当前结构性问题为 `0`: 无失效 `.md` 链接、无非 `.md` 相对链接、无旧模板页面、无扁平 header 页面、无缺失目录总览页 -- 当前剩余覆盖缺口: `0` 个 public headers 未覆盖 -- 审计口径说明: - - 当前 `docs/api/_meta/rebuild-status.md` 的覆盖口径仍是 `engine/include/XCEngine` 的 public headers。 - - `Editor` 下基于 `editor/src/**` 的源文件页、方法页颗粒度,以及说明性内容是否跟随源码重构同步,仍需要人工复核。 -- 抽样确认出的实际遗留问题: - - `Editor/Application` 目录当前只有 `Application.md`,`ReloadScriptingRuntime()`、`RebuildScriptingAssemblies()`、`GetScriptRuntimeStatus()` 只在总览页有概述,缺少按商业级 API 文档颗粒度拆出的独立方法页。 - - `Editor/panels/SceneViewPanel` 目录当前只有 `SceneViewPanel.md`,尚未把 pivot / center、global / local、scene icon 命中优先级、transient overlay 注入链路拆开讲透。 - -## T15 Components / MeshRendererComponent 内容同步 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P0` -- 写入范围: `docs/api/XCEngine/Components/MeshRendererComponent/**`、必要时 `docs/api/XCEngine/Components/Components.md` -- 主要源码依据: `engine/include/XCEngine/Components/MeshRendererComponent.h`、`engine/src/Components/MeshRendererComponent.cpp`、`tests/Components/test_mesh_render_components.cpp` -- 当前缺口: 已核对 `MeshRendererComponent.md`、`Serialize.md`、`Deserialize.md`、`GetMaterial.md`、`GetMaterialHandle.md`、`SetMaterialPath.md` 与 `Components.md`;文档已覆盖 `materialPaths + materialRefs` 双轨序列化、deferred async material load 与首次访问触发加载的当前行为 -- 完成标准: 重写 `MeshRendererComponent.md`、`Serialize.md`、`Deserialize.md`、`GetMaterial.md`、`GetMaterialHandle.md`、`SetMaterialPath.md` 等核心页面,写清楚 deferred load、asset ref 回填与测试覆盖 - -## T16 Scripting / ScriptEngine 固定步长配置补齐 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P0` -- 写入范围: `docs/api/XCEngine/Scripting/ScriptEngine/**`、必要时 `docs/api/XCEngine/Scripting/Scripting.md`、`docs/api/_guides/Scripting/**` -- 主要源码依据: `engine/include/XCEngine/Scripting/ScriptEngine.h`、`engine/src/Scripting/ScriptEngine.cpp`、`tests/scripting/test_script_engine.cpp` -- 当前缺口: 已补齐 `SetRuntimeFixedDeltaTime()`、`GetRuntimeFixedDeltaTime()`、`DefaultFixedDeltaTime` 页面,并把 fixed delta 配置、运行时启停复位语义与 `OnFixedUpdate()` 的关系补入 `ScriptEngine` 与 `Scripting` 总览 -- 完成标准: 补齐固定步长相关页面,并写清楚它与 `OnFixedUpdate()`、运行时启动/停止、脚本类重绑定之间的关系 - -## T17 Editor / Application 脚本运行时方法页补齐 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P1` -- 写入范围: `docs/api/XCEngine/Editor/Application/**`、必要时 `docs/api/XCEngine/Editor/Scripting/**` -- 主要源码依据: `editor/src/Application.h`、`editor/src/Application.cpp`、`editor/src/Scripting/EditorScriptAssemblyBuilder.h`、`editor/src/Scripting/EditorScriptRuntimeStatus.h`、`tests/Editor/test_editor_script_assembly_builder.cpp` -- 当前缺口: 已补齐 `ReloadScriptingRuntime()`、`RebuildScriptingAssemblies()`、`GetScriptRuntimeStatus()` 独立页面,并补入“程序集缺失时如何降级到不可用状态”的明确说明 -- 完成标准: 为上述方法补齐独立页面,写清楚构建成功/失败路径、Mono 后端缺失时的回退、状态快照如何供 Inspector/脚本 UI 消费 - -## T18 Editor / SceneViewPanel 交互语义深化 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P1` -- 写入范围: `docs/api/XCEngine/Editor/panels/SceneViewPanel/**`、必要时 `docs/api/XCEngine/Editor/Viewport/**`、`docs/api/_guides/Editor/**` -- 主要源码依据: `editor/src/panels/SceneViewPanel.h`、`editor/src/panels/SceneViewPanel.cpp`、`editor/src/Viewport/SceneViewportOverlayHandleBuilder.h`、`editor/src/Viewport/SceneViewportOverlayHitTester.h`、`editor/src/Viewport/IViewportHostService.h` -- 当前缺口: 已重写 `SceneViewPanel.md`,并新增 `SceneView Interaction And Gizmo Model` guide;文档已明确 pivot / center 的计算依据、global / local 轴向语义、scene icon 与 gizmo 的命中优先级,以及 cached / interaction / transient 三层 overlay 数据流 -- 完成标准: 把 Scene View 交互模型补成商业级说明,尤其是 selection pivot、transform space、overlay hit-test 与 scene icon 选择这几条链路 - -## T19 审计工具口径扩展 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P2` -- 写入范围: `docs/api/_tools/audit_api_docs.py`、必要时 `docs/api/_meta/rebuild-status.md` -- 主要源码依据: 当前审计脚本口径与 `docs/api/XCEngine/Editor/**` 的实际页面分布 -- 当前缺口: 自动审计目前能确认 public headers 与链接结构,但无法自动暴露 `Editor` 源文件页是否缺少方法页,也无法报告像 `Application`、`SceneViewPanel` 这种“只有总览页”的高风险目录 -- 完成标准: 让审计结果至少能额外报告 source-file 页覆盖概况或方法页缺口统计,降低后续人工抽样成本 - -## T20 二次内容收口与最终复核 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P2` -- 写入范围: `docs/api/_meta/rebuild-status.md`、`docs/plan/API文档并行更新任务池_2026-04-02.md`、必要时受影响的模块总览页 -- 主要源码依据: `T15-T19` 的完成结果 -- 当前缺口: 已在 `2026-04-03 00:16:11` 重新执行 `python docs/api/_tools/audit_api_docs.py` 并回写 `rebuild-status.md`;结构性问题继续保持 `0`,`246/246` public headers 与 `120/120` Editor source headers 继续全量覆盖;审计中的 `Editor 高风险单页目录数: 19` 属于启发式抽样信号,不等同于仍有 19 处缺页 -- 完成标准: 在 `T15-T19` 完成后重新执行结构审计与人工抽样复核,把新的结论写回进度表 - -## 收口结论(2026-04-03 00:16:11) - -- 本轮收口审计已执行: `python docs/api/_tools/audit_api_docs.py` -- 当前结构性问题继续为 `0`: - - 无失效 `.md` 链接 - - 无非 `.md` 相对链接 - - 无旧模板页面 - - 无扁平 header 页面 - - 无失效 header / source ref -- 当前覆盖状态: - - public headers `246/246` - - Editor source headers `120/120` -- 本轮内容级收口结果: - - `MeshRendererComponent` 内容同步已完成 - - `ScriptEngine` 固定步长配置页已补齐 - - `Application` 脚本运行时方法页已补齐 - - `SceneViewPanel` 交互语义与 guide 已补齐 -- 审计提示解释: - - `Editor 高风险单页目录数: 19` 是新增启发式报告,用于提示“目录只有单页但实现较大,建议继续人工抽样” - - 它不是“当前仍缺 19 个 canonical 页面”的等价表达 - -## 三次审核结论(2026-04-03 00:24:08) - -- 已重新执行 `python docs/api/_tools/audit_api_docs.py` -- 当前结构性问题仍为 `0` - - public headers `246/246` - - Editor source headers `120/120` - - 失效 `.md` 链接 `0` - - 无效 header / source ref `0` -- 本轮新增问题不在“结构覆盖”,而在“内容与源码同步” -- 经过 `git diff`、源码抽样与文档抽样,当前确认的内容级缺口有: - - `Input/InputManager` 文档已失准 - - `Input.md` / `InputManager.md` 仍把 `GetAxisRaw()` 解释成“按下边沿”语义 - - `GetButtonUp()` 的未注册按钮返回值说明仍是旧行为 - - 缺少 `IsKeyReleased.md`、`IsMouseButtonReleased.md`、`IsAnyKeyDown.md`、`IsAnyKeyPressed.md` - - `Editor` 的 Game View 输入桥接链路没有写完整 - - [GameViewPanel](../api/XCEngine/Editor/panels/GameViewPanel/GameViewPanel.md) 还在按“薄视口壳层”描述 - - [EditorEvents](../api/XCEngine/Editor/Core/EditorEvents/EditorEvents.md) 还没纳入 `GameViewInputFrameEvent` - - `GameViewPanel -> EventBus -> PlaySessionController -> InputManager` 这条运行时输入链路没有统一说明 - - `Editor` 项目工作流文档落后于当前功能 - - [MainMenuActionRouter](../api/XCEngine/Editor/Actions/MainMenuActionRouter/MainMenuActionRouter.md) 未写 `Scripts` 菜单与“迁移场景资产引用” - - [ProjectCommands](../api/XCEngine/Editor/Commands/ProjectCommands/ProjectCommands.md) 未写 `MigrateSceneAssetReferences` - - [IProjectManager](../api/XCEngine/Editor/Core/IProjectManager/IProjectManager.md) / [ProjectManager](../api/XCEngine/Editor/Managers/ProjectManager/ProjectManager.md) 未写迁移报告结构与批量重写场景流程 - - `Core/Asset` 局部页存在陈旧说明 - - [AssetDatabase](../api/XCEngine/Core/Asset/AssetDatabase/AssetDatabase.md) 仍提到已不存在的 `ResourceManager::m_ioMutex` - - 新增的 artifact 依赖快照、`BuildLookupSnapshot()` 热路径、`AssetImportService + ProjectAssetIndex` 分层关系需要再校对一次叙述是否完全一致 - - `Scripting` 虽已补齐 fixed delta 页面,但托管输入桥接与类发现/类切换行为还没有在模块页和 guide 层讲透 -- 另外有一个文档组织问题需要收口: - - `docs/api/_guides/Editor/Scene-View-Interaction-Model.md` - - `docs/api/_guides/Editor/SceneView-Interaction-And-Gizmo-Model.md` - - 这两篇主题重叠,当前链接入口分叉,需统一 canonical guide -- `Editor 高风险单页目录数: 19` 仍保留为抽样队列,不等同于这 19 处都已确认失准 - -## T21 Input / InputManager 内容校准与方法补页 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P0` -- 写入范围: `docs/api/XCEngine/Input/**`、必要时 `docs/api/_guides/Input/**` -- 主要源码依据: `engine/include/XCEngine/Input/InputManager.h`、`engine/src/Input/InputManager.cpp`、`tests/Input/test_input_manager.cpp` -- 当前缺口: - - 已重写 `Input.md`、`InputManager.md` 与 `Input-Flow-and-Frame-Semantics.md` - - 已补齐 `IsKeyReleased.md`、`IsMouseButtonReleased.md`、`IsAnyKeyDown.md`、`IsAnyKeyPressed.md` - - 文档已按当前源码校正 `GetAxisRaw()`、`GetButtonUp()`、`ProcessKeyUp()`、`ProcessMouseButton()` 与 `Update()` 的帧语义 - - 已进一步补充 `IsKeyPressed()` / `GetButtonDown()` / `IsAnyKeyPressed()` 的 `repeat` 语义,以及 `IsKeyUp()` / `IsMouseButtonUp()` 和释放边沿接口的区别 -- 完成标准: 重写模块页与类型页,补齐 4 个方法页,并把键鼠释放态、any-key 语义、默认映射和测试覆盖按当前源码写清楚 - -## T22 Editor / Game View 输入桥接链路补写 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P0` -- 写入范围: `docs/api/XCEngine/Editor/panels/GameViewPanel/**`、`docs/api/XCEngine/Editor/Core/EditorEvents/**`、`docs/api/XCEngine/Editor/Core/PlaySessionController/**`、必要时 `docs/api/_guides/Editor/**` -- 主要源码依据: `editor/src/panels/GameViewPanel.cpp`、`editor/src/Core/EditorEvents.h`、`editor/src/Core/PlaySessionController.h`、`editor/src/Core/PlaySessionController.cpp`、`tests/editor/test_play_session_controller.cpp` -- 当前缺口: - - 已重写 `GameViewPanel.md`,补入 ImGui 键鼠采集、逐帧发布 `GameViewInputFrameEvent` 与空事件释放语义 - - `EditorEvents.md` 已纳入 `GameViewInputFrameEvent` - - `PlaySessionController.md` 已明确“只有运行态 `Update()` 才会把 Game View 输入桥接到 `InputManager`”及 `hovered/focused` 门控规则 - - 已补齐 `GameViewPanel/Constructor.md` 与 `GameViewPanel/Render.md` - - 已新增 `docs/api/_guides/Editor/Game-View-Runtime-Input-Bridge.md`,把 `GameViewPanel -> EventBus -> PlaySessionController -> InputManager` 收口成单条连续说明 -- 完成标准: 把 `GameViewPanel -> EventBus -> PlaySessionController -> InputManager` 这条桥接链路写成一组可追踪的 API 文档,明确 `hovered/focused`、键鼠状态快照与帧边界 - -## T23 Editor / 项目工作流与脚本菜单同步 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P0` -- 写入范围: `docs/api/XCEngine/Editor/Actions/MainMenuActionRouter/**`、`docs/api/XCEngine/Editor/Actions/EditorActions/**`、`docs/api/XCEngine/Editor/Actions/ProjectActionRouter/**`、`docs/api/XCEngine/Editor/Commands/ProjectCommands/**`、`docs/api/XCEngine/Editor/Core/IProjectManager/**`、`docs/api/XCEngine/Editor/Managers/ProjectManager/**`、必要时 `docs/api/XCEngine/Editor/panels/ProjectPanel/**` -- 主要源码依据: `editor/src/Actions/MainMenuActionRouter.h`、`editor/src/Commands/ProjectCommands.h`、`editor/src/Core/IProjectManager.h`、`editor/src/Managers/ProjectManager.h`、`editor/src/Managers/ProjectManager.cpp`、`tests/editor/test_action_routing.cpp` -- 当前缺口: - - 已重写 `MainMenuActionRouter.md`,补入 `Scripts` 菜单、`Migrate Scene AssetRefs` 文件菜单入口,以及菜单到 `ProjectCommands` / `EventBus` 的下游链路 - - 已重写 `ProjectCommands.md`,补入 `RebuildScriptAssemblies`、`MigrateSceneAssetReferences`、`SwitchProject` 与 `SaveProject` 的真实 guard 和执行流程 - - 已重写 `IProjectManager.md` / `ProjectManager.md`,补入 `SceneAssetReferenceMigrationReport` 结构、`Assets/**/*.xc` 批量迁移流程、`ResourceManager::ScopedDeferredSceneLoad` 与日志/刷新语义 - - 已进一步补充“菜单层不消费返回值”“脚本重建产物不在 `ProjectPanel` 视图内”“`.meta` sidecar 跟随规则”“迁移报告只给汇总计数”的边界说明 - - 已校对 `ProjectPanel.md` 与 `EditorActions.md`,明确面板只消费资源级命令,而项目保存 / 脚本重建 / 场景迁移入口位于主菜单 -- 完成标准: 把项目工作流文档升级到“菜单入口 -> command -> manager -> 场景重写报告”的完整链路,并校对 `ProjectPanel` 对这些命令的消费关系 - -## T24 Core / AssetDatabase 与项目资产快照链路复核 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P1` -- 写入范围: `docs/api/XCEngine/Core/Asset/AssetDatabase/**`、`docs/api/XCEngine/Core/Asset/ResourceManager/**`、必要时 `docs/api/XCEngine/Core/Asset/Asset.md` -- 主要源码依据: `engine/include/XCEngine/Core/Asset/AssetDatabase.h`、`engine/src/Core/Asset/AssetDatabase.cpp`、`engine/include/XCEngine/Core/Asset/ResourceManager.h`、`engine/src/Core/Asset/ResourceManager.cpp`、`engine/src/Resources/Material/MaterialLoader.cpp`、`tests/core/Asset/test_resource_manager.cpp` -- 当前缺口: - - 已移除 `AssetDatabase.md` 中陈旧的 `m_ioMutex` 叙述,并改成当前 `AssetImportService::recursive_mutex` 的真实封装关系 - - 已重写 `BuildLookupSnapshot()`、`TryGetAssetRef()`、`EnsureArtifact()` 和 `Asset.md` 中关于 snapshot 消费链路、cache miss 自刷新和 artifact 导入边界的说明 - - 已补入材质纹理绑定依赖、`.obj -> .mtl -> texture` 依赖快照,以及 `BuildLookupSnapshot()` 只导出主资产 path/GUID 表而不导出 artifact 路径的语义 - - 已继续重写 `ResourceManager` 下残留的通用占位方法页,补齐 `Get()`、`AddRef()/Release()`、`Find()/Exists()`、`Unload*()`、`ResolvePath()`、`LoadGroup()` 等真实行为与当前限制 -- 完成标准: 把 `AssetDatabase -> ResourceManager` 的当前链路与依赖快照行为写实,并消除与 `AssetImportService` / `ProjectAssetIndex` 相关的陈旧描述 - -## T25 Scripting / 托管输入桥接与类发现链路补强 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P1` -- 写入范围: `docs/api/XCEngine/Scripting/**`、必要时 `docs/api/_guides/Scripting/**` -- 主要源码依据: `engine/include/XCEngine/Scripting/IScriptRuntime.h`、`engine/include/XCEngine/Scripting/Mono/MonoScriptRuntime.h`、`engine/include/XCEngine/Scripting/NullScriptRuntime.h`、`engine/include/XCEngine/Scripting/ScriptComponent.h`、`engine/include/XCEngine/Scripting/ScriptEngine.h`、对应 `.cpp`、`managed/XCEngine.ScriptCore/Input.cs`、`managed/XCEngine.ScriptCore/Time.cs`、`managed/XCEngine.ScriptCore/KeyCode.cs`、`tests/scripting/test_mono_script_runtime.cpp`、`tests/scripting/test_script_engine.cpp` -- 当前缺口: - - 已重写 `Scripting.md`、`ScriptEngine.md` 与 `MonoScriptRuntime.md`,把托管 `Input` / `Time` internal call、`Time.fixedDeltaTime` 配置来源,以及 `InputManager` 消费边界收口到同一条说明链。 - - 已补强 `TryGetAvailableScriptClasses()`、`SetScriptClass()`、`ClearScriptClass()` 与 `IScriptRuntime` 契约页,明确排序、按程序集过滤、项目程序集发现和类切换重建语义。 - - 已重写两篇 `Scripting` guide,把 `project/Assets/**/*.cs -> GameScripts.dll -> ScriptClassDescriptor -> ScriptComponent::SetScriptClass()` 以及运行时切类后的实例销毁/重建流程串成连续入口。 -- 完成标准: 在不扩散成两套文档体系的前提下,把原生 `ScriptEngine`、Mono internal call、项目脚本类发现与类切换重建写成一组连续说明 - -## 四次审核结论(2026-04-03 11:39:21) - -- 已重新执行 `python docs/api/_tools/audit_api_docs.py` -- 当前结构性问题继续为 `0` - - public headers `246/246` - - Editor source headers `121/121` - - 失效 `.md` 链接 `0` - - 无效 header / source ref `0` -- 本轮新增与收口结果: - - `EditorConsoleSink` 已从单页目录重构为“类型页 + record 页 + 方法页” - - `ConsoleFilterState` 已按真实字段与别名关系重写 - - 工作树新增的 `editor/src/Viewport/SceneViewportTransformGizmoFrameBuilder.h` 已补入 canonical 文档树 -- 审计抽样结果: - - `Editor 高风险单页目录数` 已从 `14` 下降到 `10` - - `ConsolePanel` 与 `EditorConsoleSink` 已移出高风险单页目录清单 - - 当前剩余抽样池见 `T26` - -## 五次审核结论(2026-04-03 11:56:30) - -- 已重新执行 `python docs/api/_tools/audit_api_docs.py` -- 当前结构性问题继续为 `0` - - public headers `246/246` - - Editor source headers `121/121` - - 失效 `.md` 链接 `0` - - 无效 header / source ref `0` -- 本轮新增与收口结果: - - 已补齐 `GameViewPanel/Constructor.md` 与 `GameViewPanel/Render.md` - - 已新增 `Game View Runtime Input Bridge` guide,并把 `GameViewPanel`、`EditorEvents`、`PlaySessionController` 和 `Editor-Architecture-And-Workflow` 串成统一入口 -- 审计抽样结果: - - `Editor 高风险单页目录数` 已从 `10` 下降到 `5` - - 当前剩余抽样池已进一步收缩到 `Viewport gizmo + SceneManager` - -## 六次审核结论(2026-04-03 12:00:34) - -- 已重新执行 `python docs/api/_tools/audit_api_docs.py` -- 当前结构性问题继续为 `0` - - public headers `246/246` - - Editor source headers `121/121` - - 失效 `.md` 链接 `0` - - 无效 header / source ref `0` -- 本轮新增与收口结果: - - 已重写 `SceneManager.md`,并补齐实体编辑、场景文件生命周期、快照恢复相关方法页 - - `ISceneManager.md` 已按当前实现职责更新接口说明 -- 审计抽样结果: - - `Editor 高风险单页目录数` 已从 `5` 下降到 `0` - - `T26` 对应的启发式抽样队列已全部清空 - -## T26 Editor / 高风险单页目录抽样复核队列 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P2` -- 写入范围: `docs/api/XCEngine/Editor/Managers/SceneManager/**`、`docs/api/XCEngine/Editor/Viewport/SceneViewportMoveGizmo/**`、`docs/api/XCEngine/Editor/Viewport/SceneViewportRotateGizmo/**`、`docs/api/XCEngine/Editor/Viewport/SceneViewportScaleGizmo/**`、`docs/api/XCEngine/Editor/Viewport/SceneViewportOrientationGizmo/**` -- 主要源码依据: `docs/api/_meta/rebuild-status.md` 中的高风险单页目录清单及对应 `editor/src/**` -- 当前缺口: - - `2026-04-03 12:00:34` 这轮复核后,审计中的 `Editor 高风险单页目录数` 已下降到 `0`。 - - 启发式抽样队列已收口完成,当前不再保留剩余目录。 -- 完成标准: 至少抽样复核一遍源码与现有页面;若发现失准则直接重写,若确认足够则在任务认领记录里注明“已复核” - -## T27 Editor / Scene View 交互 guide 去重与入口收口 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P2` -- 写入范围: `docs/api/_guides/Editor/Scene-View-Interaction-Model.md`、`docs/api/_guides/Editor/SceneView-Interaction-And-Gizmo-Model.md`、以及所有引用这两篇 guide 的 API 页面 -- 主要依据: 当前 guide 内容本身与 `SceneViewPanel` / `IViewportHostService` / `ViewportHostService` / `Viewport.md` 的交叉链接分布 -- 当前缺口: - - 已确认 `SceneView-Interaction-And-Gizmo-Model.md` 作为唯一 canonical guide;旧 `Scene-View-Interaction-Model.md` 保留为兼容入口页,只负责跳转说明。 - - 已把残留的业务页引用统一切到 canonical guide,当前仓库里已无 API 页面继续把旧 guide 当主入口。 -- 完成标准: 统一保留一个 canonical guide 或明确拆分两篇职责,并把全部入口链接收口到一致命名 - -## 四次审核结论(2026-04-03 11:44:52) - -- 已重新执行 `python docs/api/_tools/audit_api_docs.py` -- 当前结构性问题仍为 `0` - - public headers `246/246` - - Editor source headers `121/121` - - 失效 `.md` 链接 `0` - - 无效 header / source ref `0` -- 本轮新增收口结果: - - `T21 Input / InputManager` 已补充 `Pressed` / `Up` / `Released` / `repeat` 的内容级语义说明 - - `Input-Flow-and-Frame-Semantics.md` 已提升为面向接入与设计理解的 canonical guide -- 当前自动审计提示: - - `Editor high-risk single-page dirs: 10` - - 这仍是人工抽样优先队列,不等同于已经确认存在 10 处失准 -- 后续并行优先级建议保持为: - - `T23` - - `T24` / `T25` - - `T26` / `T27` - -## 五次审核结论(2026-04-03 11:53:48) - -- 已重新执行 `python docs/api/_tools/audit_api_docs.py` -- 当前结构性问题仍为 `0` - - public headers `246/246` - - Editor source headers `121/121` - - 失效 `.md` 链接 `0` - - 无效 header / source ref `0` -- 本轮新增收口结果: - - `T23 Editor / 项目工作流与脚本菜单同步` 已继续补强内容级说明 - - 已明确主菜单层只负责触发项目维护动作,不消费 `RebuildScriptAssemblies` / `MigrateSceneAssetReferences` 的返回结果 - - 已明确 `ProjectPanel` 只投影 `/Assets`,因此脚本重建成功后不保证出现可见树变化 - - 已明确 `ProjectManager` 的 `.meta` sidecar 跟随语义、resource root 临时切换 / 恢复,以及迁移报告只提供汇总计数 -- 当前自动审计提示: - - `Editor high-risk single-page dirs: 6` - - 仍属于人工抽样队列,不等同于已经确认失准 -- 后续并行优先级建议调整为: - - `T24` / `T25` - - `T26` - - `T27` - -## 六次审核结论(2026-04-03 11:57:51) - -- 已重新执行 `python docs/api/_tools/audit_api_docs.py` -- 当前结构性问题仍为 `0` - - public headers `246/246` - - Editor source headers `121/121` - - 失效 `.md` 链接 `0` - - 无效 header / source ref `0` -- 本轮新增收口结果: - - `T24 Core / AssetDatabase 与项目资产快照链路复核` 已完成 - - 已修正 `AssetDatabase` 的锁模型、`BuildLookupSnapshot()` 的真实消费链路、`TryGetAssetRef()` 与 `EnsureArtifact()` 的职责边界 - - 已把材质贴图依赖与模型依赖快照的真实采集方式补入文档 -- 当前自动审计提示: - - `Editor high-risk single-page dirs: 4` - - 仍属于人工抽样队列,不等同于已经确认失准 - -## 七次审核结论(2026-04-03 12:16:18) - -- 已重新执行 `python docs/api/_tools/audit_api_docs.py` -- 当前结构性问题仍为 `0` - - public headers `246/246` - - Editor source headers `121/121` - - 失效 `.md` 链接 `0` - - 无效 header / source ref `0` -- 本轮新增收口结果: - - `T24` 已继续补齐 `ResourceManager` 目录下残留的占位方法页 - - `ResolvePath()`、`Exists()/Find()`、`Unload*()`、`LoadGroup()` 等方法页已改成按源码解释当前行为与限制 -- 当前自动审计提示: - - `Editor high-risk single-page dirs: 0` - - 当前追踪表中的内容级收口任务已全部落地 -- 后续并行优先级建议调整为: - - `T25` - - `T26` - - `T27` - -## 八次审核结论(2026-04-03 12:21:03) - -- 已重新执行 `python docs/api/_tools/audit_api_docs.py` -- 当前结构性问题仍为 `0` - - public headers `246/246` - - Editor source headers `121/121` - - 失效 `.md` 链接 `0` - - 无效 header / source ref `0` -- 本轮新增收口结果: - - `T25 Scripting / 托管输入桥接与类发现链路补强` 已完成最后一轮内容级重写 - - 已把托管 `Input` / `Time` internal call、`Time.fixedDeltaTime` 配置来源、`TryGetAvailableScriptClasses()` 的排序与按程序集过滤语义、`SetScriptClass()` / `ClearScriptClass()` 的运行时重建链路补入模块页、类型页与 guide - - `MonoScriptRuntime` 与两篇 `Scripting` guide 已明确项目脚本程序集入口、类发现约束、字段默认值与本地覆盖同步的设计边界 -- 当前自动审计提示: - - `Editor high-risk single-page dirs: 0` - - 当前任务池中的内容级收口任务已全部完成 -- 后续并行优先级建议调整为: - - 跟踪后续源码新增 API - - 发现新的内容失准后再按模块开新任务 - - 保持 `docs/api/_meta/rebuild-status.md` 的周期性审计回写 - -## T28 Rendering / BuiltinForwardPipeline 资源契约与描述符布局同步 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P1` -- 写入范围: `docs/api/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline/**`、必要时 `docs/api/XCEngine/Rendering/Pipelines/Pipelines.md`、`docs/api/XCEngine/Rendering/Rendering.md` -- 主要源码依据: `engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h`、`engine/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp`、`tests/Rendering/unit/test_builtin_forward_pipeline.cpp` -- 当前缺口: - - 已重写 `BuiltinForwardPipeline.md`、`Initialize.md`、`Render.md`、`Shutdown.md`,把 `RenderPassSequence`、`PassResourceLayout`、动态/静态 descriptor set 分流与 legacy binding 回退补齐到当前实现。 - - 已新增 `Constructor.md` 与 `BuildInputLayout.md`,补齐公开构造入口和 `StaticMeshVertex` 输入布局契约。 - - 已新增 `BuiltinForwardPipelineAsset/BuiltinForwardPipelineAsset.md`、`BuiltinForwardPipelineAsset/CreatePipeline.md`,补清默认 pipeline asset 工厂与 runtime pipeline 的衔接关系。 - - 已更新 `Pipelines.md`、`RenderPipeline.md`、`RenderPipelineAsset.md`、`RenderPipelineAsset/CreatePipeline.md`、`Rendering.md` 与渲染 guide,使 `ForwardLit` 资源契约、`visibleItems` 和默认 pipeline asset 链路保持一致。 -- 完成标准: 把 `BuiltinForwardPipeline` 类型页与核心方法页重写到当前实现,明确 shader pass 资源契约、descriptor set 生命周期、legacy 回退语义和当前限制 - -## T29 Rendering / SceneRenderer 与 RenderSceneExtractor 口径复核 - -- 状态: `DONE` -- 认领人: `Codex` -- 优先级: `P1` -- 写入范围: `docs/api/XCEngine/Rendering/SceneRenderer/**`、`docs/api/XCEngine/Rendering/RenderSceneExtractor/**`、`docs/api/XCEngine/Rendering/VisibleRenderObject/**` -- 主要源码依据: `engine/include/XCEngine/Rendering/SceneRenderer.h`、`engine/src/Rendering/SceneRenderer.cpp`、`engine/include/XCEngine/Rendering/RenderSceneExtractor.h`、`engine/src/Rendering/RenderSceneExtractor.cpp`、`tests/Rendering/unit/test_camera_scene_renderer.cpp`、`tests/Rendering/unit/test_scene_render_request_planner.cpp` -- 当前缺口: - - 已重写 `SceneRenderer.md`、`Constructor.md`、`Destructor.md`、`SetPipeline.md`、`GetPipeline.md`、`Render.md`,纠正“`SceneRenderer` 直接做 scene extraction / 直接创建 `BuiltinForwardPipeline`”的旧口径。 - - 已新增 `BuildRenderRequests.md`、`SetPipelineAsset.md`、`GetPipelineAsset.md`,补齐 `SceneRenderer` 当前公开方法页。 - - 已重写 `RenderSceneExtractor.md` 与 `Extract.md`,并新增 `ExtractForCamera.md`、`SelectCamera.md`,把 `visibleItems`、相机选择、光照提取与排序规则同步到当前实现。 - - 已重写 `VisibleRenderObject.md`,把它明确为 `VisibleRenderItem` 的兼容别名入口,并补齐 `material / section / renderQueue / cameraDistanceSq` 等当前字段。 -- 完成标准: 把 `SceneRenderer` 更新为“请求规划 + 转发到 `CameraRenderer`”的真实职责模型;把 `RenderSceneExtractor` 更新为 `visibleItems` 与当前排序 / 相机选择语义 - -## 九次审核结论(2026-04-03 13:30:05) - -- 已重新执行 `python docs/api/_tools/audit_api_docs.py` -- 当前结构性问题仍为 `0` - - public headers `246/246` - - Editor source headers `121/121` - - 失效 `.md` 链接 `0` - - 无效 header / source ref `0` -- 本轮新增收口结果: - - `T28 Rendering / BuiltinForwardPipeline 资源契约与描述符布局同步` 已完成 - - 已补齐 `BuiltinForwardPipeline/Constructor.md`、`BuiltinForwardPipeline/BuildInputLayout.md` 与 `BuiltinForwardPipelineAsset` 类目录 - - 已把默认 `BuiltinForwardPipelineAsset -> BuiltinForwardPipeline -> CameraRenderer` 链路补入 `Pipelines`、`RenderPipelineAsset`、`RenderPipeline`、`Rendering` 与 rendering guide -- 当前自动审计提示: - - `Editor high-risk single-page dirs: 0` - - 当前 canonical API 树与 public headers / Editor source headers 仍保持全量覆盖 - -## 备注 - -- `T01-T20` 当前已全部完成 -- `T21-T27` 当前也已全部完成 -- 下一阶段重点已经从“补历史缺页”切换为“跟踪源码新增行为并持续校正文档内容” diff --git a/docs/used/API文档目录结构第二轮并行任务板_2026-04-09.md b/docs/used/API文档目录结构第二轮并行任务板_2026-04-09.md deleted file mode 100644 index 1d606af9..00000000 --- a/docs/used/API文档目录结构第二轮并行任务板_2026-04-09.md +++ /dev/null @@ -1,101 +0,0 @@ -# API 文档目录结构第二轮并行任务板(2026-04-09) - -## 使用规则 - -- 每个任务块只允许一个会话领取。 -- 每个任务块必须同时处理:主页面、所属索引页、交叉链接。 -- 每完成一个任务块所在阶段,都要先审计,再提交推送。 -- 如果任务路径命中当前并发热点,先不要直接改。 - -## 当前并发热点 - -以下源码区域当前已有并发修改,相关文档任务默认标记为 `high-risk`: - -- `editor/src/Viewport/**` -- `engine/include/XCEngine/RHI/**` -- `engine/include/XCEngine/Rendering/Passes/**` -- `engine/include/XCEngine/Rendering/Materials/RenderMaterialResolve.h` -- `engine/include/XCEngine/UI/Widgets/UISelectionModel.h` -- `engine/include/XCEngine/UI/Widgets/UIDragDropInteraction.h` - -## 任务块 - -| ID | 范围 | 目标改动 | 主要路径 | 风险 | 状态 | 领取人 | -|----|------|----------|----------|------|------|--------| -| `R1` | Rendering / 重复目录归位 | 把旧顶层 `CameraRenderer`、`SceneRenderer`、`CameraRenderRequest`、`SceneRenderRequestPlanner`、`SceneRenderRequestUtils`、`RenderCameraData`、`RenderResourceCache`、`RenderSceneExtractor`、`RenderSceneUtility` 合并到真实子模块位置 | `docs/api/XCEngine/Rendering/**` | `medium` | `completed` | 当前会话 | -| `R2` | Rendering / 旧命名残留审计 | 处理 `ObjectIdEncoding`、`ObjectIdPass`、`RenderMaterialUtility`、`VisibleRenderObject`,判定迁移到哪里或删除 | `docs/api/XCEngine/Rendering/**` | `medium` | `completed` | 当前会话 | -| `E1` | Editor / 历史失效页清理 | 移除 `XCUIDemoPanel` canonical 页面,修正 `panels.md`、`ImGuiTransitionBackend.md` 等反向链接 | `docs/api/XCEngine/Editor/panels/**` | `low` | `completed` | 当前会话 | -| `V1` | Resources / Volume | 建立 `Volume.md`、`VolumeField.md`、`VolumeFieldLoader.md`,同步 `Resources.md` | `docs/api/XCEngine/Resources/Volume/**` | `low` | `completed` | 当前会话 | -| `V2` | Components / Volume | 建立 `VolumeRendererComponent.md`,同步 `Components.md` | `docs/api/XCEngine/Components/VolumeRendererComponent/**` | `low` | `completed` | 当前会话 | -| `V3` | Rendering / Volume FrameData | 建立 `VisibleVolumeItem.md`,同步 `FrameData.md` | `docs/api/XCEngine/Rendering/FrameData/**` | `low` | `completed` | 当前会话 | -| `V4` | Rendering / Volume & Selection Passes | 建立 `BuiltinSelectionMaskPass.md`、`BuiltinSelectionOutlinePass.md`、`BuiltinVolumetricPass.md`,同步 `Passes.md` | `docs/api/XCEngine/Rendering/Passes/**` | `high-risk` | `completed` | 当前会话 | -| `U1` | UI / Widgets Helpers | 建立 `UIDragDropInteraction.md`、`UIScrollModel.md`,同步 `Widgets.md` | `docs/api/XCEngine/UI/Widgets/**` | `high-risk` | `completed` | 当前会话 | -| `ED1` | Editor / ComponentEditors | 建立 `VolumeRendererComponentEditor.md`,同步 `ComponentEditors.md` | `docs/api/XCEngine/Editor/ComponentEditors/**` | `low` | `completed` | 当前会话 | -| `ED2` | Editor / panels Material Authoring | 建立 `MaterialInspectorMaterialState.md`、`MaterialInspectorMaterialStateIO.md`,同步 `panels.md` | `docs/api/XCEngine/Editor/panels/**` | `low` | `completed` | 当前会话 | -| `RR1` | RHI 内容回归 | 根据当前真实头文件更新 `RHI*`、`D3D12`、`OpenGL`、`Vulkan` 文档内容与结构 | `docs/api/XCEngine/RHI/**` | `high-risk` | `completed` | 当前会话 | -| `RR2` | Rendering / Passes 内容回归 | 根据当前修改中的 builtin pass 头文件更新文档内容与链接 | `docs/api/XCEngine/Rendering/Passes/**` | `high-risk` | `completed` | 当前会话 | -| `RR3` | Rendering / Materials 内容回归 | 把 `RenderMaterialResolve` 相关文档与当前头文件重新对齐 | `docs/api/XCEngine/Rendering/Materials/**` | `high-risk` | `completed` | 当前会话 | -| `G1` | 全量审计与空目录清理 | 跑审计、清空旧重复目录、清理空目录与错链 | `docs/api/_meta/**`, `docs/api/XCEngine/**` | `medium` | `pending` | | - -## 最新进度 - -- 截至 `2026-04-10 18:36`,`docs/api/_tools/audit_api_docs.py` 审计已全绿: - - `Invalid header refs = 0` - - `Invalid source refs = 0` - - `Broken .md links = 0` - - `Missing directory index pages = 0` -- 当前第二轮任务板里真正剩余的结构性收口项主要是 `G1`,即历史空目录与重复目录的继续清理。 - -## 推荐阶段顺序 - -### 第一阶段 - -- `R1` -- `R2` -- `E1` - -这一阶段的目标是先把“结构错位”和“失效历史页”清掉。 - -### 第二阶段 - -- `V1` -- `V2` -- `V3` -- `ED1` -- `ED2` - -这一阶段优先补低冲突、可快速收口的缺页。 - -### 第三阶段 - -- `V4` -- `U1` -- `RR1` -- `RR2` -- `RR3` - -这一阶段等源码波动收敛后再做。 - -### 第四阶段 - -- `G1` - -## 验收口径 - -### 结构验收 - -- 每个 API 只保留一个 canonical 目录位置。 -- 文档目录层级必须与真实源码父目录一致。 -- 不再允许顶层旧路径和子模块新路径并存。 - -### 审计验收 - -- `Invalid header refs = 0` -- `Invalid source refs = 0` -- `Broken .md links = 0` -- `Missing directory index pages = 0` - -### 协作验收 - -- 每个阶段完成后立即提交推送。 -- 任务板状态同步更新,避免重复领取。 diff --git a/docs/used/API文档目录结构第二轮重构计划_2026-04-09.md b/docs/used/API文档目录结构第二轮重构计划_2026-04-09.md deleted file mode 100644 index 35d4eebf..00000000 --- a/docs/used/API文档目录结构第二轮重构计划_2026-04-09.md +++ /dev/null @@ -1,227 +0,0 @@ -# API 文档目录结构第二轮重构计划 - -## 1. 背景 - -项目最近又经历了一轮较大的源码重构,`docs/api/XCEngine` 当前虽然顶层大类仍然存在,但内部已经出现了两类更严重的问题: - -- 目录层级错位:文档页名字还对,但挂在了错误的父目录下。 -- 新旧结构并存:旧位置和新位置同时保留,导致同一 API 在文档树里出现两套入口。 - -这轮重构不再是简单补页,而是要把 `docs/api` 再次拉回到“与实际源码模块结构平行”的状态。 - -## 2. 本轮核对基准 - -本轮计划基于以下真实代码树做对照: - -- 运行时 public headers:`engine/include/XCEngine/**` -- 旧版编辑器 source-backed API:`editor/src/**` -- 当前文档树:`docs/api/XCEngine/**` - -同时参考了最新本地审计结果: - -- `python -B docs/api/_tools/audit_api_docs.py` - -## 2.1 2026-04-10 状态更新 - -截至 `2026-04-10 17:07:09`,这份计划里最核心的阶段性目标已经完成: - -- `XCUIDemoPanel` 已从 canonical 树移除,相关活跃反向链接已清理 -- `Rendering` 旧错位目录与 stale-token 路径已完成一轮归位/回归 -- `Volume / Selection / UI helper / Editor helper` 缺页已补齐 -- `docs/api/XCEditor/**`、`Resources/Model/**`、`Resources/GaussianSplat/**` 已进入 canonical 树 -- 审计当前全绿: - - `Invalid header refs = 0` - - `Invalid source refs = 0` - - `Broken .md links = 0` - - `Missing directory index pages = 0` - -因此下文第 `3` 节保留的是“本轮原始发现快照”,不是当前仍未处理的实时状态。 - -## 3. 已确认的结构性问题 - -### 3.1 Rendering 存在成对重复目录 - -当前已确认以下目录同时出现在“旧顶层位置”和“源码对应的新子模块位置”: - -- `docs/api/XCEngine/Rendering/CameraRenderer` -- `docs/api/XCEngine/Rendering/Execution/CameraRenderer` -- `docs/api/XCEngine/Rendering/SceneRenderer` -- `docs/api/XCEngine/Rendering/Execution/SceneRenderer` -- `docs/api/XCEngine/Rendering/CameraRenderRequest` -- `docs/api/XCEngine/Rendering/Planning/CameraRenderRequest` -- `docs/api/XCEngine/Rendering/SceneRenderRequestPlanner` -- `docs/api/XCEngine/Rendering/Planning/SceneRenderRequestPlanner` -- `docs/api/XCEngine/Rendering/SceneRenderRequestUtils` -- `docs/api/XCEngine/Rendering/Planning/SceneRenderRequestUtils` -- `docs/api/XCEngine/Rendering/RenderCameraData` -- `docs/api/XCEngine/Rendering/FrameData/RenderCameraData` -- `docs/api/XCEngine/Rendering/RenderResourceCache` -- `docs/api/XCEngine/Rendering/Caches/RenderResourceCache` -- `docs/api/XCEngine/Rendering/RenderSceneExtractor` -- `docs/api/XCEngine/Rendering/Extraction/RenderSceneExtractor` -- `docs/api/XCEngine/Rendering/RenderSceneUtility` -- `docs/api/XCEngine/Rendering/Extraction/RenderSceneUtility` - -这说明文档树里同时保留了“旧的平铺布局”和“新的源码子模块布局”。后续必须只保留源码对应路径,旧路径下的内容要迁移或删除,不能继续并存。 - -### 3.2 Rendering 还有一批疑似旧命名/旧抽象残留 - -当前已确认下列目录没有直接对应到当前真实头文件命名,属于优先审计对象: - -- `docs/api/XCEngine/Rendering/ObjectIdEncoding` -- `docs/api/XCEngine/Rendering/ObjectIdPass` -- `docs/api/XCEngine/Rendering/RenderMaterialUtility` -- `docs/api/XCEngine/Rendering/VisibleRenderObject` - -这些目录大概率分别对应: - -- 已下沉或改名后的 `Picking/ObjectIdCodec` -- `Passes/BuiltinObjectIdPass` -- `Materials/RenderMaterialResolve` -- `FrameData/VisibleRenderItem` - -但不能直接机械删除,必须先做“旧页内容是否需要迁移”的核对。 - -### 3.3 Editor 仍保留已脱离源码的历史页 - -当前已确认: - -- `docs/api/XCEngine/Editor/panels/XCUIDemoPanel/XCUIDemoPanel.md` - -对应源码: - -- `editor/src/panels/XCUIDemoPanel.h` -- `editor/src/panels/XCUIDemoPanel.cpp` - -这两个文件都已经不存在。 - -当前仍引用该历史页的文档包括: - -- `docs/api/XCEngine/Editor/panels/panels.md` -- `docs/api/XCEngine/Editor/XCUIBackend/ImGuiTransitionBackend/ImGuiTransitionBackend.md` - -`docs/api/XCEngine/UI/DrawData/DrawData.md` 里已经改成“旧链路说明”,这类描述可以保留,但不应该继续把 `XCUIDemoPanel` 作为真实 canonical API 页面入口。 - -### 3.4 审计明确缺页仍然存在 - -最新审计仍明确指出以下缺口需要补齐: - -- `XCEngine/Components/VolumeRendererComponent.h` -- `XCEngine/Rendering/FrameData/VisibleVolumeItem.h` -- `XCEngine/Rendering/Passes/BuiltinSelectionMaskPass.h` -- `XCEngine/Rendering/Passes/BuiltinSelectionOutlinePass.h` -- `XCEngine/Rendering/Passes/BuiltinVolumetricPass.h` -- `XCEngine/Resources/Volume/VolumeField.h` -- `XCEngine/Resources/Volume/VolumeFieldLoader.h` -- `XCEngine/UI/Widgets/UIDragDropInteraction.h` -- `XCEngine/UI/Widgets/UIScrollModel.h` -- `editor/src/ComponentEditors/VolumeRendererComponentEditor.h` -- `editor/src/panels/MaterialInspectorMaterialState.h` -- `editor/src/panels/MaterialInspectorMaterialStateIO.h` - -同时还缺少: - -- `docs/api/XCEngine/Resources/Volume/Volume.md` - -### 3.5 当前存在并发修改热点 - -当前工作区里已有其他会话在修改以下源码区域,对应文档重构要么延后,要么单独协调: - -- `editor/src/Viewport/**` -- `engine/include/XCEngine/RHI/**` -- `engine/include/XCEngine/Rendering/Passes/**` -- `engine/include/XCEngine/Rendering/Materials/RenderMaterialResolve.h` -- `engine/include/XCEngine/UI/Widgets/UISelectionModel.h` -- `engine/include/XCEngine/UI/Widgets/UIDragDropInteraction.h` - -因此本轮执行要按“低冲突优先”组织阶段,避免多个会话同时改同一批文档。 - -## 4. 本轮目标 - -### 4.1 结构目标 - -- `docs/api/XCEngine/**` 内每一个类/模块页都必须与当前真实源码路径平行。 -- 同一 API 只能保留一个 canonical 位置。 -- 旧的错位目录必须迁移或移除,不能继续保留作第二入口。 - -### 4.2 内容目标 - -- 在结构对齐完成后,再逐批做内容重构。 -- 内容必须基于当前源码与测试,而不是沿用旧说明。 - -### 4.3 协作目标 - -- 每一批任务都要能被多个会话独立领取。 -- 每一阶段结束后立即提交并推送,避免长时间悬空。 - -## 5. 分阶段执行 - -## Phase A:建立新计划与任务板 - -- 新开第二轮计划文件 -- 新开第二轮并行任务板 -- 标清“当前确定的问题”和“并发风险路径” - -## Phase B:清理已确认的失效历史页 - -- 处理 `XCUIDemoPanel` -- 修复所有反向链接 -- 让 `Invalid source refs` 归零 - -## Phase C:Rendering 目录结构归位 - -- 把所有错位的顶层 Rendering 目录迁移到真实子模块 -- 合并旧目录里的方法页/细页到正确的新位置 -- 删除旧顶层重复入口 - -## Phase D:补齐审计明确缺页 - -- `Resources/Volume` -- `Components/VolumeRendererComponent` -- `Rendering/VisibleVolumeItem` -- `Rendering` 的体积/选择 pass -- `UI/Widgets` 新 helper -- `Editor` 的体积组件编辑器与材质状态页 - -## Phase E:高风险模块内容回归 - -在相关源码停止波动后,再处理: - -- `RHI` -- `Rendering/Passes` -- `Rendering/Materials` -- `Editor/Viewport` -- `UI/Widgets` - -## Phase F:总回归 - -- 全量跑审计 -- 清理空目录、错链、旧入口 -- 更新阶段状态并准备下一轮内容重构 - -## 6. 本轮优先级判断 - -当前最优先的不是继续写新内容,而是先把“错层级”和“重复入口”消掉。否则后面无论哪个会话补文档,都有较高概率继续写到旧路径。 - -因此第二轮的第一主战场是: - -- `docs/api/XCEngine/Rendering` - -第二主战场是: - -- `docs/api/XCEngine/Editor` 中仍脱离真实源码的历史页 - -第三主战场才是: - -- 审计明确缺页的 Volume / UI / Editor 补齐 - -## 7. 阶段收口要求 - -每完成一个阶段,必须执行: - -1. 本地核对改动范围 -2. `python -B docs/api/_tools/audit_api_docs.py` -3. `git commit` -4. `git push` - -只有在上一阶段已经提交推送后,才进入下一阶段。 diff --git a/docs/used/API文档目录结构重大重构并行任务板_2026-04-09.md b/docs/used/API文档目录结构重大重构并行任务板_2026-04-09.md deleted file mode 100644 index 45ef8ff0..00000000 --- a/docs/used/API文档目录结构重大重构并行任务板_2026-04-09.md +++ /dev/null @@ -1,155 +0,0 @@ -# API 文档目录结构重大重构并行任务板(2026-04-09) - -## 背景 - -项目近期经过了大规模重构,`docs/api/XCEngine` 当前 canonical 树已经和实际源码出现新的结构偏差。 -本任务板用于给多会话 / 多代理并行协作时直接认领任务,避免重复劳动和目录冲突。 - -本轮结论来自两类事实源: - -- 源码目录对照: - - `engine/include/XCEngine` - - `editor/src` - - `new_editor/include/XCEditor` -- 文档审计: - - `python -B docs/api/_tools/audit_api_docs.py` - - 最新生成时间:`2026-04-10 17:07:09` - -## 当前关键问题 - -### 当前审计状态 - -- 当前 `docs/api/XCEngine/**` + `docs/api/XCEditor/**` + `editor/src/**` 范围内审计已经全绿: - - `Invalid header refs = 0` - - `Invalid source refs = 0` - - `Broken .md links = 0` - - `Missing directory index pages = 0` -- 本轮补页已经覆盖: - - `VolumeRendererComponent` - - `VisibleVolumeItem` - - `BuiltinSelectionMaskPass` - - `BuiltinSelectionOutlinePass` - - `BuiltinVolumetricPass` - - `Volume` - - `VolumeField` - - `VolumeFieldLoader` - - `UIDragDropInteraction` - - `UIScrollModel` - - `VolumeRendererComponentEditor` - - `MaterialInspectorMaterialState` - - `MaterialInspectorMaterialStateIO` -- 新增 canonical 根树与资源子模块: - - `docs/api/XCEditor/**` - - `XCEngine/Resources/Model/**` - - `XCEngine/Resources/GaussianSplat/**` -- `XCUIDemoPanel` 已从 canonical API 树中移除;活跃 overview 只保留当前真实调用点和历史说明。 - -### 剩余结构问题 - -- `RHI`、`Rendering/Passes`、`Rendering/Materials` 等高风险内容回归任务已经完成,当前审计维持全绿。 -- 历史空目录与重复目录的进一步清理仍待继续验证,这部分仍对应 `S9`。 -- 后续重点已经从“大面积补缺页”转成“任务板/入口状态同步 + 空目录收口 + 新增头文件增量同步”。 - -### 当前发现的可疑空目录 - -以下目录当前为空,优先视为历史迁移残留;删除前需要先 `rg` 检查是否仍有链接引用: - -- `docs/api/XCEngine/Core/Core` -- `docs/api/XCEngine/Core/Containers/Containers` -- `docs/api/XCEngine/Debug/Debug` -- `docs/api/XCEngine/Editor/Viewport/SceneViewportOverlayRenderer` -- `docs/api/XCEngine/Rendering/CameraRenderRequest/BuiltinPostProcessRequest` -- `docs/api/XCEngine/Rendering/Passes/BuiltinPostProcessPassPlan` -- `docs/api/XCEngine/Rendering/Passes/BuiltinPostProcessPassSequenceBuilder` -- `docs/api/XCEngine/Resources/Shader/ShaderRenderState` - -说明: - -- `docs/api/XCEngine/Components/VolumeRendererComponent` -- `docs/api/XCEngine/Rendering/FrameData/VisibleVolumeItem` -- `docs/api/XCEngine/Rendering/Passes/BuiltinSelectionMaskPass` -- `docs/api/XCEngine/Rendering/Passes/BuiltinSelectionOutlinePass` -- `docs/api/XCEngine/Rendering/Passes/BuiltinVolumetricPass` -- `docs/api/XCEngine/Resources/Volume/VolumeField` -- `docs/api/XCEngine/Resources/Volume/VolumeFieldLoader` - -这些空目录是本轮结构补齐时创建的目标目录,不属于历史残留,需要补页面而不是删除。 - -## 并行认领规则 - -- 一个任务块只允许一个会话认领。 -- 每个任务块必须同时处理: - - 主页面 - - 所在模块索引页 - - 相关交叉链接 -- 删除任何目录或页面前,先执行: - - `rg -n "<名称>" docs/api/XCEngine docs/api/_guides` -- 每个任务块完成后都要执行: - - `python -B docs/api/_tools/audit_api_docs.py` - -## 任务块 - -| ID | 范围 | 目标改动 | 主要路径 | 状态 | 认领人 | -|----|------|----------|----------|------|--------| -| `S1` | Resources / Volume | 新建 `Volume.md`、`VolumeField.md`、`VolumeFieldLoader.md`,并更新 `Resources.md` | `docs/api/XCEngine/Resources/Volume/**` | `completed` | 当前会话 | -| `S2` | Components / Volume | 新建 `VolumeRendererComponent.md`,并更新 `Components.md` | `docs/api/XCEngine/Components/VolumeRendererComponent/**` | `completed` | 当前会话 | -| `S3` | Rendering / Volume FrameData | 新建 `VisibleVolumeItem.md`,并更新 `FrameData.md` | `docs/api/XCEngine/Rendering/FrameData/**` | `completed` | 当前会话 | -| `S4` | Rendering / Volume & Selection Passes | 新建 `BuiltinSelectionMaskPass.md`、`BuiltinSelectionOutlinePass.md`、`BuiltinVolumetricPass.md`,并更新 `Passes.md` | `docs/api/XCEngine/Rendering/Passes/**` | `completed` | 当前会话 | -| `S5` | UI / Widgets Helpers | 新建 `UIDragDropInteraction.md`、`UIScrollModel.md`,并更新 `Widgets.md` | `docs/api/XCEngine/UI/Widgets/**` | `completed` | 当前会话 | -| `S6` | Editor / ComponentEditors | 新建 `VolumeRendererComponentEditor.md`,并更新 `ComponentEditors.md` | `docs/api/XCEngine/Editor/ComponentEditors/**` | `completed` | 当前会话 | -| `S7` | Editor / panels Material Authoring | 新建 `MaterialInspectorMaterialState.md`、`MaterialInspectorMaterialStateIO.md`,并更新 `panels.md` | `docs/api/XCEngine/Editor/panels/**` | `completed` | 当前会话 | -| `S8` | Editor 旧页清理 | 删除 `XCUIDemoPanel` 旧页与空目录,修正所有反向链接 | `docs/api/XCEngine/Editor/panels/XCUIDemoPanel/**` | `completed` | 当前会话 | -| `S9` | Canonical 空目录清理 | 清理历史空目录并验证无死链 | 见“可疑空目录”列表 | `pending` | | -| `S10` | 全量回归 | 运行审计,更新状态,补最后的索引 / 链接 / 空目录问题 | `docs/api/_meta/rebuild-status.md` | `completed` | 当前会话 | - -## 各任务块的最低验收标准 - -### `S1` - `S7` - -- 目录结构与源码平行。 -- 每个类型都是“一个文件夹 + 一个同名主页面”。 -- 页面必须基于当前源码和测试写内容,不能凭旧文档照搬。 -- 必须同步更新所属模块总览页中的目录列表 / 页面列表。 - -### `S8` - -- `XCUIDemoPanel` 页面与目录从 canonical 树中移除。 -- 所有指向它的链接都替换为当前真实调用点描述,或者直接删除。 -- 审计中的 `Invalid source refs` 归零。 - -### `S9` - -- 只清理已经确认无源码对应、且无链接引用的目录。 -- 不能误删本轮待补页面目录。 - -### `S10` - -- `Invalid header refs = 0` -- `Invalid source refs = 0` -- `Broken .md links = 0` -- `Missing directory index pages = 0` -- 缺页计数进一步下降,最好清零 - -## 推荐执行顺序 - -1. `S8` -2. `S1` -3. `S2` -4. `S3` -5. `S4` -6. `S5` -7. `S6` -8. `S7` -9. `S9` -10. `S10` - -## 备注 - -- 当前阶段已经从“补缺页”转为“空目录清理 + 入口状态同步 + 增量回归维护”。 -- 下一轮优先收口: - - `S9` 对应的历史空目录清理 - - `docs/api/_meta/rebuild-status.md` 的例行审计回写 - - 后续新增或继续波动的高风险源码路径增量同步: - - `engine/include/XCEngine/RHI/**` - - `engine/include/XCEngine/Rendering/Passes/**` - - `new_editor/include/XCEditor/**` diff --git a/docs/used/API文档目录结构重构并行任务板_2026-04-09_第二轮.md b/docs/used/API文档目录结构重构并行任务板_2026-04-09_第二轮.md deleted file mode 100644 index 9eed8a3d..00000000 --- a/docs/used/API文档目录结构重构并行任务板_2026-04-09_第二轮.md +++ /dev/null @@ -1,216 +0,0 @@ -# API 文档目录结构重构并行任务板(第二轮,2026-04-09) - -## 背景 - -本轮不是普通补页,而是一次“目录归位 + 失效页清理 + 新模块补平”混合重构。 - -## 状态更新(2026-04-10 17:07:09) - -- 当前 `docs/api/XCEngine/**` + `docs/api/XCEditor/**` + `editor/src/**` 审计已经全绿。 -- `R1` - `R7` 对应的主要结构归位、失效页清理和缺页补齐已经完成。 -- 当前剩余重点不再是“补 XCEditor / Volume / XCUIDemoPanel”,而是: - - `R8` 这类残余结构规范化 - - 更高风险的 `RHI / Rendering / Editor/Viewport` 内容回归 - - 历史空目录与重复入口的持续收口 - -已确认的事实来源: - -- 本地源码目录对照 - - `engine/include/XCEngine/**` - - `editor/src/**` -- 最新审计 - - `python -B docs/api/_tools/audit_api_docs.py` -- 多路 `GPT-5.4 high` 子代理并行审计结论 - -## 当前已确认的结构问题(原始发现快照) - -### 1. Rendering 根目录存在明显错位页面 - -以下文档目录名是对的,但父目录已经错了,应该先迁回真实源码子模块: - -- `docs/api/XCEngine/Rendering/CameraRenderer` - - 实际应对应 `engine/include/XCEngine/Rendering/Execution/CameraRenderer.h` - - 目标路径:`docs/api/XCEngine/Rendering/Execution/CameraRenderer` -- `docs/api/XCEngine/Rendering/SceneRenderer` - - 实际应对应 `engine/include/XCEngine/Rendering/Execution/SceneRenderer.h` - - 目标路径:`docs/api/XCEngine/Rendering/Execution/SceneRenderer` -- `docs/api/XCEngine/Rendering/CameraRenderRequest` - - 实际应对应 `engine/include/XCEngine/Rendering/Planning/CameraRenderRequest.h` - - 目标路径:`docs/api/XCEngine/Rendering/Planning/CameraRenderRequest` -- `docs/api/XCEngine/Rendering/SceneRenderRequestPlanner` - - 实际应对应 `engine/include/XCEngine/Rendering/Planning/SceneRenderRequestPlanner.h` - - 目标路径:`docs/api/XCEngine/Rendering/Planning/SceneRenderRequestPlanner` -- `docs/api/XCEngine/Rendering/SceneRenderRequestUtils` - - 实际应对应 `engine/include/XCEngine/Rendering/Planning/SceneRenderRequestUtils.h` - - 目标路径:`docs/api/XCEngine/Rendering/Planning/SceneRenderRequestUtils` -- `docs/api/XCEngine/Rendering/RenderSceneExtractor` - - 实际应对应 `engine/include/XCEngine/Rendering/Extraction/RenderSceneExtractor.h` - - 目标路径:`docs/api/XCEngine/Rendering/Extraction/RenderSceneExtractor` -- `docs/api/XCEngine/Rendering/RenderSceneUtility` - - 实际应对应 `engine/include/XCEngine/Rendering/Extraction/RenderSceneUtility.h` - - 目标路径:`docs/api/XCEngine/Rendering/Extraction/RenderSceneUtility` -- `docs/api/XCEngine/Rendering/RenderResourceCache` - - 实际应对应 `engine/include/XCEngine/Rendering/Caches/RenderResourceCache.h` - - 目标路径:`docs/api/XCEngine/Rendering/Caches/RenderResourceCache` -- `docs/api/XCEngine/Rendering/RenderCameraData` - - 实际应对应 `engine/include/XCEngine/Rendering/FrameData/RenderCameraData.h` - - 目标路径:`docs/api/XCEngine/Rendering/FrameData/RenderCameraData` -- `docs/api/XCEngine/Rendering/ObjectIdPass` - - 当前属于旧命名 - - 实际应归入 `docs/api/XCEngine/Rendering/Passes/BuiltinObjectIdPass` - -### 1.1 已确认“正确目录已存在,但错位旧目录仍保留”的重复区 - -以下区域不是“目标目录不存在”,而是“新目录已经有页,旧错位目录还没清掉”: - -- `Rendering/CameraRenderer` 与 `Rendering/Execution/CameraRenderer` -- `Rendering/SceneRenderer` 与 `Rendering/Execution/SceneRenderer` -- `Rendering/RenderSceneExtractor` 与 `Rendering/Extraction/RenderSceneExtractor` -- `Rendering/RenderSceneUtility` 与 `Rendering/Extraction/RenderSceneUtility` -- `Rendering/RenderResourceCache` 与 `Rendering/Caches/RenderResourceCache` -- `Rendering/RenderCameraData` 与 `Rendering/FrameData/RenderCameraData` -- `Rendering/CameraRenderRequest` 与 `Rendering/Planning/CameraRenderRequest` -- `Rendering/SceneRenderRequestPlanner` 与 `Rendering/Planning/SceneRenderRequestPlanner` -- `Rendering/SceneRenderRequestUtils` 与 `Rendering/Planning/SceneRenderRequestUtils` - -### 2. 已经脱离源码现实的失效页 - -- `docs/api/XCEngine/Editor/panels/XCUIDemoPanel/XCUIDemoPanel.md` - - 当前仍引用不存在的 `editor/src/panels/XCUIDemoPanel.h` - - 这是当前审计里唯一明确的失效源文件引用 -- `docs/api/XCEngine/Editor/Viewport/SceneViewportOverlayRenderer` - - 当前 `editor/src/Viewport` 下已无对应头文件 - - 优先视为待清理历史目录 -- 以下页面仍引用或延续了这条旧链路: - - `docs/api/XCEngine/Editor/panels/panels.md` - - `docs/api/XCEngine/Editor/XCUIBackend/ImGuiTransitionBackend/ImGuiTransitionBackend.md` - - `docs/api/XCEngine/UI/DrawData/DrawData.md` - -### 3. 旧概念或旧结构残留页 - -以下目录需要先做“是否仍有真实源码对应物”的确认,再决定迁移还是删除: - -- `docs/api/XCEngine/Rendering/RenderMaterialUtility` -- `docs/api/XCEngine/Rendering/ObjectIdEncoding` -- `docs/api/XCEngine/Rendering/VisibleRenderObject` -- `docs/api/XCEngine/Rendering/Pipelines/BuiltinForwardPipelineAsset` -- `docs/api/XCEngine/Rendering/Passes/ObjectIdOutlineStyle` -- `docs/api/XCEngine/Rendering/Passes/BuiltinPostProcessPassPlan` -- `docs/api/XCEngine/Rendering/Passes/BuiltinPostProcessPassSequenceBuilder` -- `docs/api/XCEngine/Resources/Shader/ShaderRenderState` - -### 4. 本轮新暴露出来的结构缺页 - -这些目录现在是真实源码节点,且已经进入主链,应尽快补平: - -- `docs/api/XCEngine/Resources/Volume/Volume` -- `docs/api/XCEngine/Resources/Volume/VolumeField` -- `docs/api/XCEngine/Resources/Volume/VolumeFieldLoader` -- `docs/api/XCEngine/Components/VolumeRendererComponent` -- `docs/api/XCEngine/Rendering/FrameData/VisibleVolumeItem` -- `docs/api/XCEngine/Rendering/Passes/BuiltinSelectionMaskPass` -- `docs/api/XCEngine/Rendering/Passes/BuiltinSelectionOutlinePass` -- `docs/api/XCEngine/Rendering/Passes/BuiltinVolumetricPass` -- `docs/api/XCEngine/UI/Widgets/UIDragDropInteraction` -- `docs/api/XCEngine/UI/Widgets/UIScrollModel` -- `docs/api/XCEngine/Editor/ComponentEditors/VolumeRendererComponentEditor` -- `docs/api/XCEngine/Editor/panels/MaterialInspectorMaterialState` -- `docs/api/XCEngine/Editor/panels/MaterialInspectorMaterialStateIO` - -## 并行认领规则 - -- 一次只认领一个任务块 -- 认领人必须同时处理: - - 目标页面 - - 所在模块索引页 - - 交叉引用修正 -- 做删除前先执行: - - `rg -n "<名称>" docs/api/XCEngine docs/api/_guides docs/plan` -- 每完成一个任务块后必须执行: - - `python -B docs/api/_tools/audit_api_docs.py` -- 每完成一个阶段后必须: - - `git commit` - - `git push` - -## 并行任务块 - -| ID | 范围 | 目标改动 | 主要路径 | 状态 | 认领人 | -|----|------|----------|----------|------|--------| -| `R1` | Rendering 目录归位 | 把错挂在 `Rendering` 根目录下的页面迁回 `Execution / Planning / Extraction / Caches / FrameData / Passes`,并与已存在的正确目录合并 | `docs/api/XCEngine/Rendering/**` | `completed` | 当前会话 | -| `R2` | 旧概念 Rendering 清理 | 核查并清理 `RenderMaterialUtility / ObjectIdEncoding / VisibleRenderObject / BuiltinForwardPipelineAsset / ObjectIdOutlineStyle / BuiltinPostProcessPassPlan / BuiltinPostProcessPassSequenceBuilder / ShaderRenderState` | `docs/api/XCEngine/Rendering/**` `docs/api/XCEngine/Resources/Shader/**` | `completed` | 当前会话 | -| `R3` | Editor 失效页清理 | 删除或退役 `XCUIDemoPanel / SceneViewportOverlayRenderer`,并修正所有反向引用 | `docs/api/XCEngine/Editor/**` | `completed` | 当前会话 | -| `R4` | Volume 资源与运行时主链 | 补齐 `Volume / VolumeField / VolumeFieldLoader / VolumeRendererComponent / VisibleVolumeItem / BuiltinVolumetricPass` | `docs/api/XCEngine/Resources/**` `docs/api/XCEngine/Components/**` `docs/api/XCEngine/Rendering/**` | `completed` | 当前会话 | -| `R5` | Selection pass 结构补齐 | 补齐 `BuiltinSelectionMaskPass` 与 `BuiltinSelectionOutlinePass`,并同步 `Passes.md` | `docs/api/XCEngine/Rendering/Passes/**` | `completed` | 当前会话 | -| `R6` | UI 新 helper | 补齐 `UIDragDropInteraction` 与 `UIScrollModel`,并同步 `Widgets.md` | `docs/api/XCEngine/UI/Widgets/**` | `completed` | 当前会话 | -| `R7` | Editor 当前真实 helper | 补齐 `VolumeRendererComponentEditor / MaterialInspectorMaterialState / MaterialInspectorMaterialStateIO`,并同步 `ComponentEditors.md` 与 `panels.md` | `docs/api/XCEngine/Editor/**` | `completed` | 当前会话 | -| `R8` | 结构规范补齐 | 修正仍不满足“一类型一文件夹”的点,例如 `Editor/UI/UI`、`Resources/Material/MaterialRenderState` 这类混挂页 | `docs/api/XCEngine/**` | `pending` | | -| `R9` | 全量回归 | 统一修链、清理空目录、重跑审计、收口索引页 | `docs/api/**` | `pending` | | - -## 推荐阶段顺序 - -### Phase 1 - -- `R3` -- `R1` -- `R2` - -说明: - -- 先把失效页和错位目录处理掉,后续内容重写才不会继续落在错误路径上 - -### Phase 2 - -- `R4` -- `R5` -- `R6` -- `R7` -- `R8` - -说明: - -- 这一阶段是把新主链和当前真实 helper 补平 - -### Phase 3 - -- `R9` - -说明: - -- 统一收口、审计、整理最终状态 - -## 最低验收标准 - -### `R1` - -- 所有迁移后的页面目录与真实头文件父目录一致 -- 原路径不再保留 active canonical 页面 - -### `R2` - -- 每个旧概念页都给出明确结论: - - 删除 - - 迁移 - - 改写为真实对应页 - -### `R3` - -- `Invalid source refs` 归零 -- `XCUIDemoPanel` 不再作为当前有效 API 页面存在于 canonical 树 - -### `R4` - `R8` - -- 每个类型都是“一文件夹 + 一主页面” -- 页面描述必须基于当前源码与测试 -- 模块索引页同步更新 - -### `R9` - -- `Invalid header refs = 0` -- `Invalid source refs = 0` -- `Broken .md links = 0` -- `Missing directory index pages = 0` - -## 备注 - -- 当前仓库存在多会话并行改动,尤其是 `Rendering`、`RHI`、`Editor/Viewport` 相关头文件;真正执行重构时优先选择干净路径,避免和其他会话冲突 -- 如果某个任务块执行过程中发现真实源码再次变动,应先回到本任务板更新状态,不要硬做 diff --git a/docs/used/API文档目录结构阶段进度_XCEditor与Model收口_2026-04-10.md b/docs/used/API文档目录结构阶段进度_XCEditor与Model收口_2026-04-10.md deleted file mode 100644 index bdac4974..00000000 --- a/docs/used/API文档目录结构阶段进度_XCEditor与Model收口_2026-04-10.md +++ /dev/null @@ -1,38 +0,0 @@ -# API 文档目录结构阶段进度:XCEditor 与 Model 收口 - -## 本阶段范围 - -- 新建 `docs/api/XCEditor/**` canonical 目录树,并生成 `new_editor/include/XCEditor/**` 对应的 header 总览页与方法页。 -- 新建 `docs/api/XCEngine/Resources/Model/Model.md` 模块索引,并生成 `Model` / `ModelArtifactIO` / `ModelLoader` / `AssimpModelImporter` 对应页面。 -- 处理并发新增的 `docs/api/XCEngine/Resources/GaussianSplat/**`,补齐模块索引与 `GaussianSplat` / `GaussianSplatArtifactIO` / `GaussianSplatLoader` 页面。 -- 修正目录脚手架与 canonical 生成器的两个问题: - - 根索引页错误把 `XCEditor` 当成普通子目录,生成了失效的 `../api.md` 链接。 - - 窄范围生成 `Resources/Model` 时,头文件引用缺少 `XCEngine/` 前缀。 - -## 当前结果 - -执行时间:`2026-04-10` - -审计命令: - -```powershell -python -B docs/api/_tools/audit_api_docs.py -``` - -审计结果: - -- `Public headers: 381` -- `Valid header refs (canonical): 381` -- `Invalid header refs: 0` -- `Editor source headers: 144` -- `Valid source refs (Editor canonical): 144` -- `Invalid source refs: 0` -- `Broken .md links: 0` -- `Missing directory index pages: 0` - -## 并行协作说明 - -- `docs/api/XCEditor/**` 本轮已经落成,不再作为待认领空树任务。 -- `docs/api/XCEngine/Resources/Model/**` 本轮已经补齐目录索引与 header 页面,不再重复认领。 -- `docs/api/XCEngine/Resources/GaussianSplat/**` 也已补齐,如头文件继续扩展,请直接在现有树上增量同步。 -- 后续如果 `new_editor/include/XCEditor/**` 或 `engine/include/XCEngine/Resources/Model/**` 再发生结构变动,应直接基于当前树增量同步,不要回退到“先补目录骨架”的阶段。 diff --git a/docs/used/API文档目录重构计划_2026-04-09.md b/docs/used/API文档目录重构计划_2026-04-09.md deleted file mode 100644 index ed3c8207..00000000 --- a/docs/used/API文档目录重构计划_2026-04-09.md +++ /dev/null @@ -1,148 +0,0 @@ -# API文档目录重构计划 - -## 1. 背景 - -当前 API 文档树已经出现明显的“双主线错位”: - -- `docs/api/XCEngine/Editor/**` 主要对应旧 `editor/src/**` 的 ImGui 编辑器应用层。 -- `new_editor/include/XCEditor/**` 与 `new_editor/src/**` 已经形成新的 Editor 基础层主线。 -- 这条主线最初没有进入当前 canonical API 树,审计脚本也一度没有覆盖 `new_editor/include/XCEditor/**`。 - -这意味着继续只在 `docs/api/XCEngine/Editor/**` 上增量修补,会越来越偏离真实代码结构。 - -## 1.1 2026-04-10 状态更新 - -截至 `2026-04-10 17:07:09`,以下阶段已经落地: - -- `docs/api/XCEditor/**` 已建立并进入 canonical 根树 -- 审计脚本已经覆盖: - - `engine/include/XCEngine/**` - - `new_editor/include/XCEditor/**` - - `editor/src/**` -- `docs/api/main.md` 已建立 `XCEngine + XCEditor` 双根入口 -- `XCUIDemoPanel` 已退出当前 canonical API 树 -- `Resources/Model` 与 `Resources/GaussianSplat` 已补齐 canonical 入口 - -因此这份计划里“为什么要拆出 `XCEditor` 根树”的判断仍然有效,但 `Phase B / C` 不再是未来动作,而是已完成事实。 - -## 2. 当前判断 - -### 2.1 旧 `Editor` 文档树继续保留,但必须收紧边界 - -- `docs/api/XCEngine/Editor/**` 继续只文档化旧 `editor/src/**`。 -- 不再把它当成未来 Editor 主线 API 树。 -- 其中 `Application / EditorResources / Theme / XCUIBackend / panels / Viewport` 等内容,明确视为旧编辑器应用层。 - -### 2.2 新主线已拆出独立文档树 - -当前已新增: - -- `docs/api/XCEditor/` - -首层目录直接镜像 `new_editor/include/XCEditor/**`: - -- `Collections` -- `Fields` -- `Foundation` -- `Shell` -- `Widgets` - -这样做的原因很直接: - -- 与真实 include 路径一致,后续自动审计最容易做。 -- 不会把新主线继续混进旧 `XCEngine/Editor` 树里。 -- 可以允许页面里的命名空间继续写真实值 `XCEngine::UI::Editor` / `XCEngine::UI::Editor::Widgets`,而目录只负责和头文件路径对齐。 - -### 2.3 新旧两棵树的职责应明确分开 - -- `docs/api/XCEngine/Editor/**` - - 旧编辑器应用层、宿主层、过渡层、当前产品参考实现。 -- `docs/api/XCEditor/**` - - `new_editor` 的新 Editor 基础层与宿主骨架。 - -## 3. 已确认的结构漂移 - -### 3.1 文档树覆盖不到 `new_editor/include/XCEditor` - -当前新主线 public headers 已经至少包含: - -- `XCEditor/Collections/*` -- `XCEditor/Fields/*` -- `XCEditor/Foundation/*` -- `XCEditor/Shell/*` -- `XCEditor/Widgets/*` - -现在 `docs/api` 里已经有对应的 `XCEditor` 根树。 - -### 3.2 旧树中仍混有已经过时的过渡页 - -当前这类问题已经完成清理,最典型的是: - -- `docs/api/XCEngine/Editor/panels/XCUIDemoPanel/XCUIDemoPanel.md` - - 仍引用已不存在的 `editor/src/panels/XCUIDemoPanel.h` - -这类页面已经从 active canonical 树移除,不再继续假设旧源码路径仍成立。 - -### 3.3 审计工具口径落后 - -当前这项问题已经收口;审计脚本现在已经把: - -- `new_editor/include/XCEditor/**/*.h` - -纳入 canonical 统计,`XCEditor` 缺页会直接出现在审计结果里。 - -## 4. 分阶段执行 - -## Phase A:收紧旧树边界 - -- 在 `docs/api/XCEngine/Editor/Editor.md` 明确“这里只对应旧 `editor/src/**`”。 -- 修掉旧树里已经失效的 source refs。 -- 继续补齐旧 `editor/src/**` 当前缺失的 canonical 页,避免现有审计长期失真。 - -## Phase B:建立 `XCEditor` 新根树(已完成) - -- 新建 `docs/api/XCEditor/XCEditor.md` -- 新建 5 个首层 overview: - - `Collections` - - `Fields` - - `Foundation` - - `Shell` - - `Widgets` -- 建立 `docs/api/main.md` 到新根树的入口链接 - -## Phase C:接入新审计口径(已完成) - -- 扩展审计脚本识别 `new_editor/include/XCEditor/**/*.h` -- 统计 `docs/api/XCEditor/**` 的 canonical 覆盖率 -- 区分: - - 旧 `editor/src/**` 应用层源码页 - - 新 `XCEditor/**` public header 页 - -## Phase D:按新主线分批补页(进行中) - -推荐顺序: - -1. `Foundation` -2. `Widgets` -3. `Collections` -4. `Fields` -5. `Shell` - -原因: - -- `Foundation` 与 `Widgets` 是多数页面的前置语义。 -- `Collections` / `Fields` 数量多,但结构相对规整,适合并行。 -- `Shell` 依赖最多,最好放后面统一写。 - -## Phase E:旧树去历史包袱(进行中) - -- 删除或改写已经脱离旧源码的过渡页 -- 把“旧 editor 参考实现”和“new_editor 正式主线”在相关 overview 中互相链接,但不再混放 -- 避免后续继续把 `new_editor` 内容塞进 `docs/api/XCEngine/Editor/**` - -## 5. 本轮执行口径 - -当前后续重点已经转到: - -1. 继续回归高风险 `RHI / Rendering / Editor/Viewport` 内容页,避免源码继续演进后文档再次漂移。 -2. 继续清理旧树里的历史空目录、重复入口和剩余结构噪音。 diff --git a/docs/used/Assets目录Watcher与自动导入刷新计划_完成归档_2026-04-10.md b/docs/used/Assets目录Watcher与自动导入刷新计划_完成归档_2026-04-10.md deleted file mode 100644 index 5ce70abb..00000000 --- a/docs/used/Assets目录Watcher与自动导入刷新计划_完成归档_2026-04-10.md +++ /dev/null @@ -1,187 +0,0 @@ -# Assets目录Watcher与自动导入刷新计划 - -日期: 2026-04-10 -状态: 已完成并归档 - -## 1. 背景 - -当前工程的资产导入链路已经具备以下能力: - -- 打开项目时执行一次 `BootstrapProject` -- 手动执行 `Refresh / Reimport / Reimport All` -- 资源真正加载时通过 `EnsureArtifact` 懒导入 - -但它还缺少 Unity 风格里最关键的一层: - -- `Assets/` 目录发生外部变化时,编辑器能自动感知并刷新资产数据库 - -这里的“外部变化”包括但不限于: - -- 资源文件新增、删除、覆盖 -- `.meta` 文件新增、删除、修改 -- 文件夹新增、删除、重命名 -- DCC 工具重新导出同路径资源 -- `git pull`、脚本生成、资源同步工具写入 - -所以问题不在“拖进 editor 时是否导入”,而在“是否存在一条持续观察 `Assets` 目录变化并驱动资产系统刷新的通路”。 - -## 2. 现状判断 - -从当前代码结构看,这个功能是好加的,难度中等,不是重写级别: - -- `ResourceManager` 已经有 `RefreshProjectAssets()`、`ReimportProjectAsset()`、`RebuildProjectAssetCache()` -- `AssetImportService / AssetDatabase` 已经能扫描项目、维护 Source DB / Artifact DB -- editor 已经有稳定的每帧更新入口 `EditorWorkspace::Update` -- `ProjectManager` 已经支持目录树刷新 - -真正缺的是一个独立的 watcher 层,把“磁盘变化”转成“主线程上的资产刷新动作”。 - -## 3. 目标 - -第一阶段目标: - -- 编辑器运行时自动感知 `Assets/` 目录外部变化 -- 自动刷新 `AssetDatabase` lookup / metadata 状态 -- 自动刷新 Project 面板当前目录 -- 对批量拷贝和连续覆盖做去抖,不要每个文件都立刻刷新一次 - -长期目标: - -- 接近 Unity 的体验:外部改动几乎无需手动 `Refresh` -- 为后续更细粒度的增量 reimport、缩略图刷新、场景引用修复提供基础设施 - -## 4. 非目标 - -第一阶段不做: - -- 原生 Win32 `ReadDirectoryChangesW` 后台线程 watcher -- 精确到单文件的增量 reimport 调度 -- 已经加载到场景里的资源热替换 -- 资产依赖图级别的自动级联重导 -- 跨平台 watcher 后端抽象 - -这些放到后续阶段。 - -## 5. 分阶段方案 - -### Phase 1: 轮询式 Assets Watcher - -实现一个 editor 内部的 `ProjectAssetWatcher`: - -- 只监听当前项目的 `Assets/` -- 通过定时扫描递归快照检测新增/修改/删除 -- 包含普通资源文件、文件夹、`.meta` -- 通过扫描间隔 + 去抖窗口合并一批变化 -- 在主线程触发: - - `ResourceManager::RefreshProjectAssets()` - - `ProjectManager::RefreshCurrentFolder()` - -优点: - -- 实现快,风险低 -- 不引入后台线程和 Win32 句柄生命周期问题 -- 先把“自动感知外部变化”补上 - -缺点: - -- 不是事件驱动,超大项目下效率一般 -- 只能做到“自动刷新资产数据库”,还不是“精确增量重导” - -### Phase 2: 原生 Win32 Directory Watcher - -把 Phase 1 的扫描后端替换为 Win32 原生目录通知: - -- `ReadDirectoryChangesW` -- 递归监听 `Assets/` -- 把原始事件推入线程安全队列 -- 主线程消费并做路径标准化、重命名配对、去抖合并 - -目标: - -- 降低大项目轮询成本 -- 提高外部改动响应速度 - -### Phase 3: 增量 Reimport 调度 - -在 watcher 已经稳定后,再做更接近 Unity 的行为: - -- 对新增/修改的 importable asset 调用增量 reimport -- 删除时清理 lookup / orphan artifact -- 重命名时保证 `.meta` / GUID / path snapshot 同步 -- 限制一次批量刷新中的 `UnloadAll()` 影响范围 - -这个阶段需要重新审视当前 `ResourceManager::ReimportProjectAsset()` 里 `UnloadAll()` 的语义,否则外部频繁改资源会让缓存抖动太大。 - -## 6. 代码落点 - -第一阶段建议改动: - -- `editor/src/Core/ProjectAssetWatcher.h/.cpp` - - watcher 快照、扫描、去抖、变更合并 -- `editor/src/Core/EditorWorkspace.h` - - `Attach / Detach / Update` 接入 watcher 生命周期 -- `editor/CMakeLists.txt` - - 编译新 watcher 文件 - -可能的辅助修改: - -- 日志输出,方便确认自动刷新是否触发 -- 若后续需要 UI 提示,可再补 editor 事件或状态文本 - -## 7. 第一阶段执行细则 - -### 7.1 快照内容 - -每个 `Assets` 路径记录: - -- 规范化相对路径 key -- 是否目录 -- 文件大小 -- 最后写入时间 - -目录也要纳入快照,这样能检测外部新建/删除文件夹。 - -### 7.2 扫描策略 - -- 默认扫描间隔:`0.75s` -- 默认去抖窗口:`0.35s` -- 若扫描发现连续变动,则不断延长本轮刷新触发时间 -- 当一小批变化稳定下来,再统一执行一次刷新 - -### 7.3 主线程动作 - -统一执行: - -- `ResourceManager::Get().RefreshProjectAssets()` -- `context.GetProjectManager().RefreshCurrentFolder()` - -这一步先只刷新资产数据库和 Project 面板,不直接做逐文件 reimport。 - -## 8. 风险点 - -- 递归扫描频率太高会拖慢超大项目 -- 当前 `RefreshProjectAssets()` 不会主动生成所有 artifact,只是同步资产数据库 -- `ProjectManager::RefreshCurrentFolder()` 是整树重建,不是局部刷新 -- editor 内部自己改文件时,watcher 也会看到这些变化,需要接受重复刷新 - -这些都在第一阶段可控范围内。 - -## 9. 验收标准 - -第一阶段完成后,应满足: - -- 外部往 `Assets/` 复制一个资源文件,Project 面板自动出现 -- 外部删除 `Assets/` 下资源,Project 面板自动消失 -- 外部覆盖修改已有资源,资产数据库自动刷新 -- 不需要用户手点 `Refresh` -- 批量复制多个资源时,不会每个文件都触发一次明显卡顿的刷新 - -## 10. 本次执行范围 - -本次开始执行 Phase 1: - -- 先落地轮询式 watcher -- 打通 editor 生命周期 -- 先让 `Assets` 外部变更自动刷新 - -Phase 2 的 Win32 原生 watcher 放在后续迭代。 diff --git a/docs/used/Audio模块架构最佳实践重构计划_2026-04-14.md b/docs/used/Audio模块架构最佳实践重构计划_2026-04-14.md deleted file mode 100644 index 5fd5754d..00000000 --- a/docs/used/Audio模块架构最佳实践重构计划_2026-04-14.md +++ /dev/null @@ -1,216 +0,0 @@ -# Audio 模块架构最佳实践重构计划 -日期:2026-04-14 - -## 1. 目标 - -本轮不是继续给当前 Audio 模块叠功能,而是把它从“可用的简化运行时”推进到“可扩展、可回归、契约清晰的正式架构”。 - -本计划聚焦以下最佳实践差距: - -1. 混音主链仍是 `game thread push -> backend pending buffer` -2. `AudioSourceComponent` 持有实例级解码 PCM,资源复用模型不对 -3. mixer / effect / routing 仍是裸指针图,ownership 不清晰 -4. backend API 能力声明与实际实现不一致 -5. master gain / mute 的职责落点分裂 -6. 混音路径仍有较多临时分配,RT-safe 程度不足 - -## 2. 本轮边界 - -本轮做: - -1. 先把资源层与 source/voice 层边界做对 -2. 先把 mixer graph 的 ownership 和路由契约做清楚 -3. 先把 backend 抽象语义收口到“说什么就真支持什么” -4. 先把主线程混音路径中的明显重复分配和重复解码收掉 -5. 为后续“真正的音频线程拉取式渲染”铺接口和数据结构基础 - -本轮不直接做: - -1. 不在第一步就推倒重写成完整 callback renderer -2. 不先上 streaming / bank / snapshot / event system -3. 不先扩平台后端 -4. 不把 editor 可视化音频图一起塞进本轮 - -## 3. 重构分期 - -### Phase 0:冻结当前外部行为 - -目标: -先保证现有运行时行为有回归保护,避免架构重构时把已有能力打坏。 - -执行项: - -1. 盘点现有 Audio 单测覆盖范围 -2. 明确现阶段保留行为: - `Play / Pause / Stop / Loop / Seek / Spatial Pan / HRTF / Reverb Send / Mixer Route` -3. 记录当前对外契约: - `AudioClip -> AudioSourceComponent -> AudioSystem -> IAudioBackend` - -完成标准: - -1. 关键行为都有最小回归测试入口 -2. 后续阶段改动不需要靠人工听感判断是否回归 - -### Phase 1:重做资源复用模型 - -目标: -把“音频资源”和“播放实例”彻底拆开。 - -执行项: - -1. 将 decoded float PCM 从 `AudioSourceComponent` 下沉到 `AudioClip` -2. 让 `AudioClip` 负责: - PCM 原始字节 - 派生元数据 - 共享 decoded float cache -3. 让 `AudioSourceComponent` 只保留: - clip 引用 - 播放游标 - voice 参数 - 空间化参数 -4. 统一缓存失效策略: - PCM / channels / bitsPerSample / sampleRate 改动时,duration 与 decoded cache 一起刷新 -5. 补资源层测试,覆盖 decoded cache 和派生元数据刷新 - -完成标准: - -1. 同一 `AudioClip` 被多个 source 使用时不再重复解码 -2. source 不再持有整份 clip 的 float PCM 副本 -3. `AudioClip` 的 duration / frameCount / sampleCount / decoded cache 语义一致 - -当前状态: -`进行中,本轮先执行这一阶段。` - -### Phase 2:收口 mixer graph ownership - -目标: -把当前“裸指针路由”收口成显式可管理的图结构。 - -执行项: - -1. 为 mixer node 建立明确 owner -2. 明确 source 输出路由、listener reverb send、mixer output 的生命周期规则 -3. 禁止悬挂 graph node 被继续访问 -4. 为未来 editor/runtime graph 统一预留 handle 或 registry 入口 - -完成标准: - -1. graph 生命周期可推理 -2. scene 切换与对象销毁不会留下悬挂路由 -3. mixer routing 可以被测试验证 - -### Phase 3:统一控制语义与 backend 能力 - -目标: -去掉“同一语义多处实现”的问题。 - -执行项: - -1. 统一 master volume / mute 的唯一 owner -2. backend 只保留设备与提交职责 -3. 移除误导性的 `WASAPIBackend` 别名或改成真实实现 -4. 明确 `SetDevice()` 是否真支持热切换;如果不支持,就不要暴露伪能力 - -完成标准: - -1. gain staging 只有一套主语义 -2. backend 能力声明与实现一致 - -### Phase 4:从主线程推送过渡到音频线程拉取 - -目标: -把当前 `Update()` 主导混音的模型,重构为更接近实时音频最佳实践的 render path。 - -执行项: - -1. 从 `AudioSystem` 中拆出音频渲染上下文 -2. 让 backend 线程按设备需求拉取 render block -3. 把游戏线程职责收缩到: - 发布 source/listener 状态 - 提交控制参数 - 不直接承担设备节奏的最终混音责任 -4. 明确 block size、latency、underrun fallback 行为 - -完成标准: - -1. 设备消费节奏不再依赖游戏帧节奏 -2. render 线程契约独立成立 - -### Phase 5:RT-safe 清理 - -目标: -减少实时路径上的临时分配和不可控开销。 - -执行项: - -1. 缓存 `AudioSystem` 混音 scratch buffer -2. 减少 `unordered_map` / `vector` 的逐帧临时构建 -3. 把 `Equalizer` / `Reverbation` / `FFTFilter` 的临时缓冲改成复用型工作区 -4. 明确哪些 DSP 允许进实时渲染链,哪些只能做分析 - -完成标准: - -1. 混音热路径不再做明显重复分配 -2. DSP 链路更接近 RT-safe - -### Phase 6:回归测试与阶段收口 - -目标: -让架构重构具备真正的落地闭环。 - -执行项: - -1. 补充资源共享、graph 生命周期、backend 语义、render 路径相关测试 -2. 补阶段性文档 -3. 按阶段提交 git commit,并在每个稳定节点推送 - -完成标准: - -1. 每一阶段都有对应提交点 -2. 关键行为和关键架构约束都有自动化保护 - -## 4. 执行顺序 - -按优先级执行: - -1. `Phase 1` 资源复用模型 -2. `Phase 3` backend 与控制语义收口 -3. `Phase 2` mixer graph ownership -4. `Phase 5` RT-safe 清理 -5. `Phase 4` 音频线程拉取式渲染 -6. `Phase 6` 收口与验证贯穿全程 - -原因: - -1. `Phase 1` 改动收益大、风险低、最容易稳定落地 -2. `Phase 3` 能先消掉接口层误导 -3. `Phase 2` 需要建立在边界先清楚的前提下 -4. `Phase 4` 是最大手术,必须放到前置契约稳定之后 - -## 5. 当前这一步准备执行的内容 - -第一批落地项: - -1. 新增 `AudioClip` 共享 decoded float cache -2. 移除 `AudioSourceComponent` 的实例级 `m_decodedData` -3. 统一 `AudioClip` 派生数据失效与刷新逻辑 -4. 补资源层和 source 层测试 - -这一步完成后,Audio 模块会先从“每个 source 自带一份解码副本”进入“资源共享 + 播放实例分离”的正确方向。 - -## 6. 阶段性提交策略 - -每阶段至少形成一次独立提交: - -1. `phase1/audio-clip-shared-decoded-cache` -2. `phase3/backend-contract-cleanup` -3. `phase2/mixer-graph-ownership` -4. `phase5/rt-safe-buffer-reuse` -5. `phase4/audio-thread-pull-render` - -执行要求: - -1. 每次只提交一个可回归的小阶段 -2. 提交前先跑对应最小测试集 -3. 阶段完成后及时推送,避免大堆积 - diff --git a/docs/used/Audio模块运行时收口与正式化计划_2026-04-13.md b/docs/used/Audio模块运行时收口与正式化计划_2026-04-13.md deleted file mode 100644 index ef2bf199..00000000 --- a/docs/used/Audio模块运行时收口与正式化计划_2026-04-13.md +++ /dev/null @@ -1,515 +0,0 @@ -# Audio 模块运行时收口与正式化计划 - -日期:2026-04-13 - -## 当前执行状态(2026-04-13) - -截至 2026-04-13,本计划的本轮目标已经基本完成,当前状态如下: - -1. `Phase 0 ~ Phase 5` 已完成: - 已统一 `frame / sample / channel / byte` 语义,`AudioClip` 已收口为可直接消费的 PCM 资源,`AudioSourceComponent` 播放状态机已修正,`AudioSystem -> Backend` 缓冲区契约已统一,Windows 后端已按 `WaveOutBackend` 正名并进入正式路径,`AudioMixer` 已成为真实混音节点。 -2. `Phase 6` 已完成: - `Equalizer` 与 `Reverbation` 已接入正式路径并有测试保护;`FFTFilter` 已正式降级为“分析器”而非修改信号的效果器;`HRTF` 已明确归属为 source 侧空间化器,并接入“单声道音源 -> 立体声输出”的正式路径。 -3. `Phase 7` 已完成: - 基础 3D 音频路径已经稳定,监听器位置/旋转/速度发布正确,距离衰减、Doppler、空间声像、spread 与 reverb send 已进入可验证运行时链路。 -4. `Phase 8` 已完成: - 资源层、组件层、系统层、mixer 层和 Windows backend 层都已具备最小回归测试;当前 `components_tests` 全量结果为 `128/128` 通过。 - -当前边界也需要明确保留: - -1. `FFTFilter` 当前是分析器,不修改原始音频缓冲。 -2. `HRTF` 当前仅对“单声道音源 -> 立体声输出”正式生效;立体声或更多声道音源仍走现有 `pan / spread` 路径。 -3. 遮挡、传播、环境响应等高级 3D 音频能力仍不在本轮范围内。 - -## 1. 文档定位 - -这份计划用于指导 `XCEngine` 当前 `Audio` 模块的收口、纠偏与正式化落地。 - -当前音频代码已经具备: - -1. `AudioClip` 资源类型与 `AudioLoader` -2. `AudioSourceComponent` 与 `AudioListenerComponent` -3. `AudioSystem` 单例入口 -4. Windows 音频后端骨架 -5. `AudioMixer`、`FFTFilter`、`Equalizer`、`Reverbation`、`HRTF` 等类定义与初步实现 - -但当前状态仍然是“结构已搭起,主链未正式收口”,主要问题不在于缺少类,而在于: - -1. 资源数据语义不清,`AudioClip` 当前保存的是整文件字节,而不是明确可播放的 PCM 数据 -2. `AudioSystem -> AudioSource -> Backend` 之间的 `bufferSize / sampleCount / frameCount / byteCount` 单位混用 -3. `Pause / Resume`、播放位置推进、3D 衰减等基础行为存在逻辑错误 -4. `AudioMixer` 与 DSP 效果器尚未接入正式混音链 -5. 对 `mp3 / ogg / flac / aiff` 的声明能力与真实加载能力不一致 -6. 自动化测试目前几乎只覆盖 `AudioClip` 资源层,未覆盖运行时链路 - -因此,本计划的目标不是“继续往现有代码上叠功能”,而是: - -1. 先把最小可用播放链稳定下来 -2. 再把资源、组件、混音、后端的职责边界做实 -3. 最后再让 Mixer、DSP、3D 音频能力进入正式路径 - -## 2. 总体目标 - -本轮计划的最终目标如下: - -1. 形成一条语义清晰、行为稳定的最小音频主链: - `AudioClip PCM -> AudioSourceComponent -> AudioSystem Mix -> Windows Backend` -2. 保证基础播放能力正确: - `Play / Pause / Resume / Stop / Loop / Seek / Volume / Mute` -3. 明确并固定音频数据单位语义: - `frame`、`sample`、`channel`、`byte` -4. 资源层只暴露经过规范化的数据,不再让上层直接消费“带文件头的原始文件字节” -5. 音频后端只承担设备与提交职责,不再混杂上层混音语义 -6. `AudioMixer` 成为正式混音节点,而不是仅有音量缩放壳子 -7. DSP 效果器只在正式混音链接通后逐项纳入,避免继续堆未接线的实现 -8. 补齐最小必要测试,使音频模块具备可回归基础 - -## 3. 非目标 - -本轮明确不做以下事项: - -1. 不直接做跨平台后端扩展,不在本轮引入 `OpenAL` / `CoreAudio` -2. 不先做复杂流式音乐播放系统 -3. 不先做完整的音频事件系统、AudioBank、快照混音器等高层玩法 -4. 不先做编辑器内完整音频面板、波形编辑器、可视化曲线编辑器 -5. 不为追求格式广度而牺牲主链稳定,必要时允许本轮只正式支持 `WAV PCM` -6. 不在运行时主链未稳定前推进高级 HRTF、遮挡、传播、环境建模 - -## 4. 当前问题归纳 - -### 4.1 资源层问题 - -1. `AudioLoader` 解析了 WAV 头信息,但仍把整份文件字节写入 `AudioClip` -2. `AudioSourceComponent::DecodeAudioData()` 直接把 `AudioClip` 数据当 PCM 解码,WAV header 也会被误读 -3. 时长计算公式不统一,且对多声道数据存在错误 -4. 资源层声明支持 `ogg / mp3 / flac / aiff`,但当前只有 WAV 真正解析 - -### 4.2 播放组件问题 - -1. `Pause()` 后会注销 source,但 `Play()` 在 `Paused` 分支不会重新注册 -2. 播放位置在 `Update()` 与 `ProcessAudio()` 中可能被双重推进 -3. 3D 衰减直接修改 `m_volume`,会污染用户设置值 -4. `SetTime()`、`GetTime()`、实际播放位置推进逻辑尚未统一 - -### 4.3 系统与后端问题 - -1. `AudioSystem` 与后端之间的缓冲区单位约定不稳定 -2. `AudioSystem::Update()` 当前混音尺寸定义不清 -3. `WASAPIBackend::ProcessAudio()` 仍以错误方式解释 `bufferSize` -4. 当前 Windows 后端类名叫 `WASAPIBackend`,但实现实际使用的是 `waveOut`/WinMM 路径,命名与实现不符 -5. `PrepareBackData()` 为空,双缓冲逻辑尚未真正完成 - -### 4.4 Mixer 与 DSP 问题 - -1. `AudioMixer` 当前没有执行 `m_effects` -2. `m_outputMixer` 没有真正参与节点路由 -3. 通道音量参数没有进入实际处理路径 -4. DSP 类有初步实现,但没有正式接入点和回归验证 - -### 4.5 测试问题 - -1. 现有测试主要集中在 `AudioLoader` -2. 缺少播放状态机测试 -3. 缺少缓冲区混音正确性测试 -4. 缺少循环、seek、多声道映射、静音、后端提交约定等测试 - -## 5. 收口原则 - -### 5.1 先主链,后扩展 - -先保证“单个 WAV 能稳定播放”这件事完整正确,再继续推进 Mixer、DSP、3D 能力。 - -### 5.2 先语义统一,后写实现 - -必须先明确以下术语并在头文件、实现、测试中统一: - -1. `frame`:一个采样时刻内所有声道的样本集合 -2. `sample`:单声道单个样本值 -3. `channel count`:声道数 -4. `frame count`:帧数 -5. `sample count`:总样本数,通常等于 `frameCount * channels` -6. `byte count`:字节数 - -### 5.3 资源层不泄漏文件封装细节 - -`AudioClip` 对运行时暴露的应该是明确的可消费音频数据,而不是原始容器文件本体。 - -### 5.4 先把“宣称能力”收窄到真实能力 - -如果某格式当前未完成正式解析,就不要在 `GetSupportedExtensions()` 与 `CanLoad()` 中冒充支持。 - -### 5.5 测试与修复同步推进 - -音频模块的很多 bug 都是“能编译、很难手查”的类型,必须同步加测试,不应只靠人工试听。 - -## 6. 目标架构 - -本轮收口后的目标结构如下: - -1. `Resources::AudioClip` - 职责:保存已解析、可播放的音频元数据与 PCM 数据 -2. `Resources::AudioLoader` - 职责:读取文件、解析格式、生成标准化 `AudioClip` -3. `Components::AudioSourceComponent` - 职责:维护播放状态、时间、循环、基础参数,并向系统输出源音频 -4. `Components::AudioListenerComponent` - 职责:维护监听器全局参数与变换 -5. `Audio::AudioSystem` - 职责:管理活动 source、执行混音、驱动主输出总线、向后端提交 -6. `Audio::AudioMixer` - 职责:作为正式混音节点,承担音量、静音、路由、效果链 -7. `Audio::IAudioBackend` - 职责:承担设备生命周期与缓冲提交,不承担上层播放状态逻辑 -8. `Audio::WindowsAudioBackend` - 职责:Windows 平台最小可用输出后端 - -## 7. 分阶段计划 - -## Phase 0:冻结语义并补足技术基线 - -### 目标 - -在真正改代码前,先把当前音频主链的术语、边界和最小行为写清楚,避免继续在错误语义上修补。 - -### 任务 - -1. 在 `AudioSystem`、`IAudioBackend`、`AudioSourceComponent` 相关头文件中统一参数命名 -2. 明确 `ProcessAudio()` 的输入输出契约 -3. 确定系统内部统一使用 `float interleaved PCM` -4. 确定资源层的标准存储形式 -5. 确定本轮正式支持的格式范围 -6. 确定 Windows 后端类名与实现是否重命名 - -### 产出 - -1. 一份简短的音频主链约定说明 -2. 头文件参数命名规范 -3. 测试输入输出约定 - -### 完成标准 - -1. 任何人只看头文件就能分清 `frameCount` 与 `sampleCount` -2. 后续实现不再继续使用语义模糊的 `bufferSize` - -## Phase 1:重做 AudioClip 数据契约 - -### 目标 - -让 `AudioClip` 从“文件字节容器”变成“可播放音频资源”。 - -### 任务 - -1. 为 `AudioClip` 明确保存字段: - `sampleRate`、`channels`、`bitsPerSample`、`duration`、`pcmData` -2. 决定是否保留原始文件字节;若保留,必须与 `pcmData` 分离 -3. 修改 `AudioLoader::Load()`,使 WAV 解析后只把有效音频数据写入可播放缓冲 -4. 修正时长计算,统一以 `frameCount / sampleRate` 为准 -5. 对当前非 WAV 格式采取二选一策略: - 方案 A:从能力列表中移除,收窄到真实支持 - 方案 B:接入正式解码库并完成最小解析实现 -6. 确认 `AudioClip::Release()` 的资源释放语义 - -### 产出 - -1. 资源层标准 `AudioClip` -2. WAV 解析主路径 -3. 明确可信的 `GetDuration()`、`GetChannels()`、`GetSampleRate()` - -### 完成标准 - -1. `AudioSourceComponent` 不再需要碰原始文件头 -2. 资源层输出的数据可直接被运行时消费 -3. WAV 样本起始位置与长度正确 - -## Phase 2:修正 AudioSource 播放状态机 - -### 目标 - -把 `AudioSourceComponent` 的状态机修到“能稳定播、停、暂停、恢复、循环、seek”。 - -### 任务 - -1. 明确 `Stopped / Playing / Paused` 的状态转移图 -2. 修复 `Pause()` 后 `Play()` 不重新注册 source 的问题 -3. 明确 `Play()` 在首次播放、暂停恢复、停止后重播三种场景下的行为 -4. 保证 `Stop()` 总是重置播放位置 -5. 修正 `SetTime()` 与内部播放指针的换算关系 -6. 确认 `GetTime()` 返回的是播放时间还是逻辑时间,并与内部推进统一 -7. 解耦 `Update()` 与 `ProcessAudio()` 的职责,避免双重推进播放位置 -8. 修复循环到尾部时的 wrap 行为 - -### 产出 - -1. 稳定的播放状态机 -2. 统一的播放时间推进逻辑 - -### 完成标准 - -1. `Play -> Pause -> Play` 能从暂停点继续 -2. `Play -> Stop -> Play` 能从开头重播 -3. Loop 与 seek 行为可预测 - -## Phase 3:统一 AudioSystem 与 Backend 的缓冲区契约 - -### 目标 - -把 `AudioSystem` 和后端之间的缓冲参数彻底统一,消除当前最危险的单位错误。 - -### 任务 - -1. 定义系统混音输出接口,例如: - `ProcessAudio(float* outInterleavedBuffer, uint32 frameCount, uint32 channels, uint32 sampleRate)` -2. 去掉语义模糊的 `bufferSize`,或者将其限定为明确字节数并彻底隔离 -3. 修正 `AudioSystem::Update()` 中混音缓冲分配方式 -4. 修正 `AudioSourceComponent::ProcessAudio()` 对输入 `frameCount` 的使用 -5. 修正 `WindowsAudioBackend::ProcessAudio()` 当前对 `bufferSize / sizeof(float)` 的错误解释 -6. 明确 backend 接收的是“已经混好的 float PCM”,还是它自己负责中间缓冲 -7. 确认后端提交尺寸与设备缓冲尺寸一致 - -### 产出 - -1. 统一的主输出缓冲契约 -2. 正确的 interleaved 混音路径 - -### 完成标准 - -1. 代码中不再出现同一参数在上游是帧、在下游是字节的情况 -2. 混音和提交不会发生越界或样本数截断 - -## Phase 4:正式收口 Windows 输出后端 - -### 目标 - -把当前 Windows 输出路径从“能编译的骨架”收口为“最小可用的正式后端”。 - -### 任务 - -1. 决定后端命名: - 若继续使用 `waveOut`,建议更名为 `WindowsWaveOutBackend` -2. 若坚持 `WASAPIBackend` 命名,则必须替换为真正 WASAPI 实现 -3. 完成双缓冲或环形缓冲的数据准备逻辑 -4. 明确 `Start / Stop / Suspend / Resume` 的线程与设备语义 -5. 保证缓冲区交换、写入、回调路径一致 -6. 补充设备枚举和当前设备切换的行为边界 -7. 明确 master volume 与 mute 是软件侧控制还是设备侧控制 - -### 产出 - -1. 一个名实一致的 Windows 后端 -2. 稳定的最小输出链 - -### 完成标准 - -1. 后端不再包含空实现的关键路径 -2. 开启后端后能持续稳定输出静态测试信号与真实音频 - -## Phase 5:收口 AudioMixer 成为正式节点 - -### 目标 - -让 `AudioMixer` 从参数壳子变成真正的混音节点。 - -### 任务 - -1. 明确 `AudioMixer` 是“总线”还是“节点”,并固定语义 -2. 让 `AudioSourceComponent` 可以把输出路由到某个 mixer -3. 让 `AudioSystem` 按 mixer 拓扑收集与混合源数据 -4. 在 `AudioMixer::ProcessAudio()` 中正式执行: - 音量、静音、效果链、下游路由 -5. 让 `m_outputMixer` 进入真实路径,而不是仅保存指针 -6. 决定通道音量是否作为本轮正式能力;若是,则必须进入处理路径 -7. 增加主混音器概念,例如 `MasterMixer` - -### 产出 - -1. 可工作的基础 mixer 路由 -2. 主总线与子总线概念 - -### 完成标准 - -1. source 能明确路由到指定 mixer -2. mixer 音量与静音会真实影响结果 -3. effect chain 在 mixer 上有真实接线点 - -## Phase 6:把 DSP 效果器纳入正式路径 - -### 目标 - -在 mixer 生效后,再让 DSP 模块以可验证的方式逐个纳入。 - -### 任务 - -1. 先确定效果器接入点: - source 级、mixer 级、master 级 -2. 优先纳入最容易验证的效果: - `Equalizer`、`Reverbation` -3. 将 `FFTFilter` 明确为“分析器”还是“修改信号的效果器” -4. 明确 `HRTF` 的归属: - 是 source 空间化器,还是 listener 侧处理器 -5. 对每个效果器增加最小行为测试 -6. 对当前实现与真实算法预期不符的部分做降级或重写 - -### 产出 - -1. 已接线的 DSP 主路径 -2. 可验证的效果器行为 - -### 完成标准 - -1. 每个已宣称启用的 DSP 都存在实际调用链 -2. 不再保留“只有类、没有接线”的假能力 - -## Phase 7:收口 3D 音频基础能力 - -### 目标 - -只做基础版 3D 能力,保证不污染核心播放逻辑。 - -### 任务 - -1. 修复当前衰减逻辑,禁止直接修改用户设置的 `m_volume` -2. 把“用户参数”和“运行时计算增益”分离 -3. 明确监听器位置、旋转、速度的更新责任 -4. 实现最小可用的距离衰减 -5. 决定本轮是否正式支持 Doppler;若不支持,则先移除假接线 -6. 决定 `pan` 与 `spatialize` 的叠加关系 -7. 处理单声道资源与立体声输出之间的映射 - -### 产出 - -1. 基础可用的 3D 衰减与声像行为 - -### 完成标准 - -1. 距离变化只影响本帧运行时增益,不污染配置值 -2. 单声道音源在立体声输出下行为稳定 - -## Phase 8:补齐测试与验证基线 - -### 目标 - -让音频模块具备基础可回归能力。 - -### 任务 - -1. 新增 `AudioClip` 资源测试: - WAV data offset、duration、多声道、bitsPerSample -2. 新增 `AudioSourceComponent` 测试: - play/pause/resume/stop/loop/seek -3. 新增 `AudioSystem` 混音测试: - 单源、多源、静音、主音量、sample/frame 契约 -4. 新增 `AudioMixer` 测试: - 路由、音量、静音、effect chain -5. 新增 Windows backend 的最小无设备逻辑测试或 mock 测试 -6. 引入测试用固定短 PCM 数据,避免所有测试都依赖外部文件 - -### 完成标准 - -1. 资源层、组件层、系统层、mixer 层至少各有一组回归测试 -2. 关键 bug 修复都有对应测试保护 - -## 8. 推荐执行顺序 - -建议严格按下面顺序推进,不要跳阶段: - -1. Phase 0:统一语义 -2. Phase 1:修 `AudioClip` 与 `AudioLoader` -3. Phase 2:修 `AudioSource` 状态机 -4. Phase 3:统一 `AudioSystem` 与 backend 契约 -5. Phase 4:收口 Windows 后端 -6. Phase 5:让 `AudioMixer` 成为正式节点 -7. Phase 6:逐项纳入 DSP -8. Phase 7:补基础 3D 音频 -9. Phase 8:完成测试补齐与回归 - -禁止的错误顺序如下: - -1. 在 `AudioClip` 数据契约未修正前继续写 HRTF -2. 在主链单位未统一前继续修 mixer -3. 在 mixer 未接线前继续堆效果器参数 -4. 在测试未补齐前继续扩格式支持 - -## 9. 里程碑定义 - -### Milestone A:最小可用播放 - -完成条件: - -1. 正式支持 `WAV PCM` -2. 单个 `AudioSourceComponent` 可正确播放 -3. `Play / Pause / Resume / Stop / Loop / Seek` 正常 -4. `AudioSystem -> Windows Backend` 主链无单位错误 - -### Milestone B:正式混音 - -完成条件: - -1. 多个 source 能同时混音 -2. `MasterVolume / Mute / Mixer Volume` 生效 -3. `AudioMixer` 拥有正式路由语义 - -### Milestone C:效果器与基础空间化 - -完成条件: - -1. 至少两种 DSP 已正式接线并可验证 -2. 基础距离衰减与立体声输出映射稳定 -3. 测试覆盖核心行为 - -## 10. 风险与应对 - -### 风险 1:当前代码中“看似能用”的路径很多,容易在旧实现上继续打补丁 - -应对: - -1. 先冻结契约 -2. 以测试驱动替换高风险路径 -3. 不继续容忍模糊参数命名 - -### 风险 2:格式支持范围失控 - -应对: - -1. 本轮先正式收窄到 `WAV PCM` -2. 其他格式延后到解码库方案明确后再扩展 - -### 风险 3:后端命名与实现不一致导致维护混乱 - -应对: - -1. 要么改名 -2. 要么换成真实 WASAPI -3. 不允许继续保持“WASAPI 名字 + waveOut 实现”的状态 - -### 风险 4:DSP 提前接线拖慢主链收口 - -应对: - -1. 先保证直通播放 -2. 再逐项接 DSP -3. 每接一个都要有验证方式 - -## 11. 本轮建议的第一批具体落点 - -如果按工程收益排序,本轮建议优先处理以下事项: - -1. 修 `AudioClip` 的 PCM 数据契约 -2. 修 `AudioLoader` 的 WAV 有效数据截取与 duration 计算 -3. 修 `AudioSourceComponent` 的 `Pause/Resume`、播放位置推进、3D 衰减污染 -4. 修 `AudioSystem` 与 backend 的 `frameCount/sampleCount` 契约 -5. 让 Windows 后端具备名实一致的最小可用输出 -6. 补齐对应测试 - -## 12. 完成后的下一步 - -当本计划完成后,Audio 模块才适合进入下一阶段讨论: - -1. 是否支持 `mp3 / ogg / flac` -2. 是否引入 streaming music 路径 -3. 是否做 Audio Event / Audio Bank -4. 是否做编辑器音频预览与可视化 -5. 是否把 3D 音频推进到 HRTF、遮挡、环境响应层 - -在此之前,所有新增能力都应服从一个前提: - -先把 `XCEngine` 的最小正式音频主链做对、做稳、做可测。 diff --git a/docs/used/C#脚本模块下一阶段计划.md b/docs/used/C#脚本模块下一阶段计划.md deleted file mode 100644 index cc148006..00000000 --- a/docs/used/C#脚本模块下一阶段计划.md +++ /dev/null @@ -1,287 +0,0 @@ -# C#脚本模块下一阶段计划 - -日期:2026-04-03 - -## 1. 当前阶段判断 - -C# 脚本模块已经完成了第一阶段的核心闭环,不再是“从 0 到 1 的原型验证”状态,而是进入了“从可运行走向可长期使用”的第二阶段。 - -当前已经完成的关键能力包括: - -1. `ScriptComponent + MonoBehaviour` 基本运行时模型已经建立 -2. `MonoScriptRuntime` 已经能加载程序集、发现脚本类、创建托管实例、调用生命周期 -3. `Awake / OnEnable / Start / FixedUpdate / Update / LateUpdate / OnDisable / OnDestroy` 已经打通 -4. `Debug.Log / LogWarning / LogError` 已经打通到 native logger -5. `Time`、`Input`、`GameObject`、`Transform`、`Behaviour.enabled`、部分内建组件包装已经可用 -6. 脚本字段存储、默认值、覆盖值、运行时值三层模型已经建立 -7. Play / Pause / Resume / Step 已经能驱动脚本生命周期 -8. editor 已经具备脚本类选择、字段 Inspector 编辑、项目脚本程序集重建与重载入口 - -这意味着下一阶段的重点,不再是“脚本能不能跑”,而是: - -1. Unity 风格 API 是否足够严格一致 -2. 项目级脚本工作流是否足够稳定 -3. 字段系统是否能支撑真实项目使用 - ---- - -## 2. 下一阶段总目标 - -下一阶段的总目标是: - -把当前“可运行的 C# 脚本运行时”收口成“命名、语义、工作流都更接近 Unity 的第一版可用脚本模块”。 - -具体聚焦三件事: - -1. **Unity API 严格对齐** -2. **项目脚本程序集链路收口** -3. **脚本字段系统扩展** - ---- - -## 3. 第一优先级:Unity API 严格对齐 - -这是当前最高优先级。 - -原因: - -1. 你已经明确要求 API 命名必须和 Unity C# API 保持严格一致 -2. 运行时主链路已经可用,当前最需要尽快收口的是 API 契约 -3. 如果 API 名字、属性名、行为语义先发散,后面越做越难回收 - -本阶段需要重点检查和补齐以下几类接口: - -### 3.1 基础对象层 - -优先补齐 Unity 风格的基础对象模型: - -1. `Object` -2. `Component` -3. `Behaviour` -4. `MonoBehaviour` - -重点不是“再做一套功能”,而是统一 API 入口和语义边界。 - -### 3.2 GameObject / Component 常用 API - -优先补齐最常用、最影响脚本编写体验的 Unity 接口: - -1. `GetComponent()` -2. `TryGetComponent(out T)` -3. `AddComponent()` -4. `GetComponents()` -5. `GetComponentInChildren()` -6. `GetComponentInParent()` -7. `CompareTag` -8. `tag` -9. `layer` - -其中前四项优先级最高。 - -### 3.3 Object 静态入口 - -优先补齐 Unity 风格最核心的对象静态方法: - -1. `Object.Destroy` -2. `Object.Instantiate` - -这一层非常关键,因为 Unity 用户的很多使用习惯都是围绕 `Object` 静态 API 建立的。 - -### 3.4 Transform 常用缺口补齐 - -当前 `Transform` 已经有较完整的空间变换接口,但还需要继续检查 Unity 一致性,重点看: - -1. 属性命名 -2. 重载形态 -3. `childCount` / `GetChild` -4. `parent` / `SetParent` -5. `Translate` / `Rotate` / `LookAt` -6. 常用便捷属性是否缺失 - -原则是: - -1. 已有 API 尽量不发散出自定义命名 -2. 优先补 Unity 常见重载 - ---- - -## 4. 第二优先级:项目脚本程序集链路收口 - -当前 editor 已经具备: - -1. `project/Assets` 扫描 -2. `GameScripts.dll` 编译 -3. `Rebuild Scripts` -4. `Reload Scripts` -5. Inspector 脚本类发现 - -但这一层还没有完全稳定,尤其是: - -1. 项目程序集输出目录依赖当前工作区状态 -2. 测试环境下 `project/Library/ScriptAssemblies` 还不稳定 -3. editor / 测试 / 项目目录三者之间还缺少更彻底的收口 - -这一阶段要完成的目标是: - -1. `project/Assets/*.cs -> Library/ScriptAssemblies/*.dll` 变成稳定链路 -2. editor 重建后总能看到最新脚本类 -3. 对应测试不依赖手工残留文件 -4. 项目程序集相关测试可以稳定进入常规回归集 - -这部分的目标不是继续扩 API,而是把工程化链路做稳。 - ---- - -## 5. 第三优先级:脚本字段系统扩展 - -当前字段系统已经支持: - -1. `float` -2. `double` -3. `bool` -4. `int32` -5. `uint64` -6. `string` -7. `Vector2` -8. `Vector3` -9. `Vector4` -10. `GameObject` 引用 - -下一阶段应继续向 Unity 常用字段模型推进,优先顺序建议如下: - -1. `enum` -2. `SerializeField` 支持 -3. private 可序列化字段 -4. 组件引用字段 -5. 数组 / List -6. 资源引用字段 - -其中最优先的是: - -1. `enum` -2. `[SerializeField]` -3. 组件引用字段 - -因为这三项最直接决定脚本是否能进入“真实可写”阶段。 - ---- - -## 6. editor 集成边界 - -这一阶段 editor 不再是完全不碰,但仍然不是主战场。 - -原则是: - -1. 只做支撑脚本模块使用所必需的 editor 收口 -2. 不把主要精力放在复杂编辑器体验优化上 -3. 继续优先保证 runtime、程序集链路、字段模型稳定 - -本阶段 editor 侧只建议继续做以下范围: - -1. 脚本类发现与重建流程稳定性 -2. Inspector 字段编辑最小闭环 -3. 类型新增后对应字段控件补齐 - -以下内容暂时不进入主线: - -1. 热重载 -2. 托管调试器 -3. 完整 Console/Exception 调试体验 -4. 高级脚本模板与自动生成工具 - ---- - -## 7. 本阶段不做的内容 - -为避免范围继续膨胀,下一阶段先明确不做: - -1. 域重载 / 热重载 -2. CoreCLR 切换 -3. IL2CPP / AOT -4. 完整 Unity 级别资源 API -5. 复杂序列化对象图 -6. 完整调试器接入 - -这些都可以进入后续阶段,但不应抢占当前优先级。 - ---- - -## 8. 建议执行顺序 - -建议严格按下面顺序推进: - -1. 先做 **Unity API 命名与契约收口** -2. 再做 **项目脚本程序集链路稳定化** -3. 再做 **字段系统扩展** -4. 再做 **新增字段类型对应的 Inspector 支持** -5. 最后再评估热重载、调试器、异常体验 - -原因很简单: - -1. API 契约不先收口,后面所有脚本都会建立在不稳定接口上 -2. 程序集链路不稳定,项目脚本工作流就不可靠 -3. 字段系统是把脚本从“可跑”推进到“可用”的关键 - ---- - -## 9. 验收标准 - -本阶段完成后,至少应满足: - -1. 核心托管 API 的命名与 Unity 保持一致,不再出现明显自定义发散 -2. `Object / GameObject / Component / Behaviour / MonoBehaviour / Transform` 的主干接口基本齐全 -3. 项目脚本程序集能稳定重建、重载、发现 -4. 项目级脚本程序集测试可以稳定跑通 -5. Inspector 能编辑扩展后的核心字段类型 -6. 脚本模块可以支撑真实项目写出第一批常规 gameplay 脚本 - ---- - -## 10. 当前结论 - -脚本模块现在已经完成了“第一阶段:核心运行时落地”。 - -下一阶段不应该再围绕“能不能跑”展开,而应该围绕: - -1. **Unity API 严格一致** -2. **项目工作流稳定** -3. **字段系统可用于真实开发** - -后续执行时,默认按这个优先级推进。 -## 阶段进展 2026-04-03 - -- 已完成第一步 Unity API 对齐收口: -- 新增 `XCEngine.Object` 基类。 -- 新增 `Object.Destroy(Object)` 静态销毁入口。 -- 新增 `GameObject / Component.GetComponentInChildren()`。 -- 新增 `GameObject / Component.GetComponentInParent()`。 -- 已补齐对应 Mono internal call 与 native 销毁路径。 -- 已新增运行时回归用例,验证层级查找、自身命中、组件销毁、GameObject 销毁。 -- 已通过 `MonoScriptRuntimeTest.*` 与 `ProjectScriptAssemblyTest.*` 相关整组验证。 - -- 已完成第二步第一小项:`GetComponents()` -- 已完成第二步第二小项:`tag / CompareTag / layer` -- `GameObject`、`Component` 与 `MonoBehaviour` 已提供 Unity 风格 `tag`、`layer`、`CompareTag(string)` 入口。 -- native `GameObject`、场景查找、序列化与反序列化已同步收口,`FindGameObjectWithTag` / `FindGameObjectsWithTag` 已改为基于 tag。 -- 已新增 `TagLayerProbe` 与对应 C++ / Mono 回归测试,覆盖默认 tag、layer 裁剪、脚本侧读写与场景 roundtrip。 -- 相关定向测试全部通过;完整 `MonoScriptRuntimeTest.*:ProjectScriptAssemblyTest.*` 输出全绿,当前仍存在历史性的 `exit code 3` 异常,需后续单独跟踪。 -- 下一步建议继续做第三小项:`Object.Instantiate` - -- 已完成字段系统第一小项:`enum` 字段支持 -- `MonoScriptRuntime` 现已支持把常见整数底层的 C# `enum` 字段纳入脚本字段模型,并按 `Int32` 进入默认值、存储值、运行时值三层同步链路。 -- 已新增 `FieldMetadataProbeState` / `EnumFieldProbeState` 探针,覆盖枚举字段发现、默认值提取、运行时写回与场景 roundtrip。 -- 已通过新增定向测试,以及完整 `ScriptFieldStorage_Test.*:MonoScriptRuntimeTest.*:ProjectScriptAssemblyTest.*` 整组验证;输出全绿,`exit code 3` 仍为既有历史现象。 -- 已完成字段系统第二小项:`[SerializeField] private` 字段支持 -- 新增 `XCEngine.SerializeField` attribute,命名与 Unity 对齐;runtime 字段发现现已支持“public 字段”与“带 `[SerializeField]` 的 private 字段”双通路。 -- 字段筛选已同步排除 `static` / `const` / `readonly`,并新增 `SerializeFieldProbe`、`FieldMetadataProbe` 扩展与对应 C++ 回归测试,覆盖默认值提取、存储覆盖、运行时写回与场景 roundtrip。 -- `ProjectScriptAssemblyTest.*` 现改为使用独立的测试项目程序集输出目录,不再依赖真实 `project/Library/ScriptAssemblies`,避免与 editor / Mono 持有的文件锁互相干扰。 -- 已通过完整 `ScriptFieldStorage_Test.*:MonoScriptRuntimeTest.*:ProjectScriptAssemblyTest.*` 验证;输出全绿,`exit code 3` 仍为既有历史现象。 -- 字段系统下一步建议切到:组件引用字段支持 -- 已完成字段系统第三小项:具体组件引用字段支持 -- `MonoScriptRuntime` 现已支持把常用具体组件字段纳入脚本字段模型,包括 `Transform`、`Camera`、`Light`、`MeshFilter`、`MeshRenderer`,以及具体脚本类字段(`MonoBehaviour` 子类)。 -- 字段存储层已新增 `ScriptFieldType::Component` 与 `ComponentReference`,序列化格式同时保存 `gameObjectUUID` 与 `scriptComponentUUID`,从而能区分内建组件引用与脚本组件引用。 -- 当前刻意暂不支持抽象基类字段:`Component`、`Behaviour`、`MonoBehaviour`;这样先保证字段定位与场景 roundtrip 语义稳定,不把抽象匹配和 editor 复杂度提前拉进主线。 -- 已新增 `ComponentFieldMetadataProbe` / `ComponentFieldProbe` 与对应 C++ 回归测试,覆盖字段发现、默认空引用、存储值应用、运行时写回,以及场景 roundtrip 恢复。 -- 本步定向测试 `ScriptFieldStorage_Test.*:ScriptComponent_Test.*:MonoScriptRuntimeTest.ClassFieldMetadataListsConcreteComponentReferenceFields:MonoScriptRuntimeTest.ClassFieldDefaultValueQueryReturnsNullComponentReferences:MonoScriptRuntimeTest.ComponentReferenceFieldsApplyStoredValuesAndPersistAcrossSceneRoundTrip` 全部通过。 -- 扩大回归 `ScriptFieldStorage_Test.*:MonoScriptRuntimeTest.*:ProjectScriptAssemblyTest.*` 日志层面全绿;当前测试进程仍存在历史性的异常退出码现象,需要后续单独跟踪,不是本步引入的问题。 -- 字段系统下一步建议切到:资源引用字段,或等 editor Inspector 重构稳定后,再补组件字段的可视化选择器。 diff --git a/docs/used/C#脚本模块现状与双向调用机制笔记_4.13_2026-04-13.md b/docs/used/C#脚本模块现状与双向调用机制笔记_4.13_2026-04-13.md deleted file mode 100644 index 1a0cb332..00000000 --- a/docs/used/C#脚本模块现状与双向调用机制笔记_4.13_2026-04-13.md +++ /dev/null @@ -1,1174 +0,0 @@ -# C#脚本模块现状与双向调用机制笔记 - -日期:4.13(2026-04-13) - -## 1. 结论先说 - -当前这套 `scripting` 不是“C# 直接拿 C++ 指针互调”的实现,而是标准 Mono embedding 路线: - -1. `ScriptEngine` 负责脚本实例生命周期调度。 -2. `MonoScriptRuntime` 负责把 Mono 嵌进来,加载程序集、创建托管对象、调生命周期、读写字段。 -3. `XCEngine.ScriptCore` 是引擎暴露给 C# 的 API 壳子。 -4. `C# -> C++` 走 `InternalCall`。 -5. `C++ -> C#` 走 `mono_runtime_invoke`。 - -最重要的一句: - -- 这套系统跨边界传递对象身份时,核心不是 native 指针,而是 `UUID`。 -- `GameObject`、`Component`、`Behaviour` 这些 C# 对象本质上都是 UUID wrapper。 - ---- - -## 2. 当前脚本模块的整体现状 - -编辑器会先编译两套程序集,再交给 Mono 运行时: - -1. `XCEngine.ScriptCore.dll` -2. `GameScripts.dll` - -对应实现是: - -```cpp -EditorScriptAssemblyBuildResult EditorScriptAssemblyBuilder::RebuildProjectAssemblies(const std::string& projectPath) { - const fs::path projectRoot = fs::path(Platform::Utf8ToWide(projectPath)).lexically_normal(); - const fs::path repositoryRoot = GetRepositoryRoot(); - const fs::path managedRoot = repositoryRoot / "managed"; - const fs::path scriptCoreSourceRoot = managedRoot / "XCEngine.ScriptCore"; - const fs::path outputDirectory = projectRoot / "Library" / "ScriptAssemblies"; - const fs::path scriptCoreOutputPath = outputDirectory / "XCEngine.ScriptCore.dll"; - const fs::path gameScriptsOutputPath = outputDirectory / "GameScripts.dll"; - - std::vector scriptCoreSources = CollectCSharpSourceFiles(scriptCoreSourceRoot); - std::vector projectScriptSources = CollectCSharpSourceFiles(projectRoot / "Assets"); - - if (!RunCSharpCompiler( - dotnetExecutable, - cscDllPath, - projectRoot, - scriptCoreOutputPath, - frameworkReferences, - scriptCoreSources, - compileError)) { - return BuildFailure("Failed to build XCEngine.ScriptCore.dll: " + compileError); - } - - std::vector gameScriptReferences = frameworkReferences; - gameScriptReferences.push_back(scriptCoreOutputPath); - if (!RunCSharpCompiler( - dotnetExecutable, - cscDllPath, - projectRoot, - gameScriptsOutputPath, - gameScriptReferences, - projectScriptSources, - compileError)) { - return BuildFailure("Failed to build GameScripts.dll: " + compileError); - } -} -``` - -编辑器初始化脚本运行时时,会从项目目录下的 `Library/ScriptAssemblies` 加载这些 DLL: - -```cpp -void Application::InitializeScriptingRuntime(const std::string& projectPath) { - const std::filesystem::path assemblyDirectoryPath = - std::filesystem::path(Platform::Utf8ToWide(projectPath)) / L"Library" / L"ScriptAssemblies"; - - ::XCEngine::Scripting::MonoScriptRuntime::Settings settings; - settings.assemblyDirectory = assemblyDirectory; - settings.corlibDirectory = assemblyDirectory; - settings.coreAssemblyPath = assemblyDirectory / L"XCEngine.ScriptCore.dll"; - settings.appAssemblyPath = assemblyDirectory / L"GameScripts.dll"; - - auto runtime = std::make_unique<::XCEngine::Scripting::MonoScriptRuntime>(settings); - if (!runtime->Initialize()) { - ::XCEngine::Scripting::ScriptEngine::Get().SetRuntime(nullptr); - return; - } - - ::XCEngine::Scripting::ScriptEngine::Get().SetRuntime(runtime.get()); - m_scriptRuntime = std::move(runtime); -} -``` - -`MonoScriptRuntime` 这边是标准的 root domain + app domain: - -```cpp -bool MonoScriptRuntime::InitializeRootDomain() { - if (!m_settings.corlibDirectory.empty()) { - const std::string corlibDirectory = m_settings.corlibDirectory.string(); - mono_set_assemblies_path(corlibDirectory.c_str()); - } - - mono_config_parse(nullptr); - - rootState.rootDomain = mono_jit_init_version("XCEngineRootDomain", "v4.0.30319"); - if (!rootState.rootDomain) { - SetError("Failed to initialize the Mono root domain."); - return false; - } - - mono_domain_set(rootState.rootDomain, true); - RegisterInternalCalls(); - rootState.initialized = true; - return true; -} - -bool MonoScriptRuntime::CreateAppDomain() { - mono_domain_set(rootState.rootDomain, true); - m_appDomain = mono_domain_create_appdomain(const_cast("XCEngineScriptDomain"), nullptr); - if (!m_appDomain) { - SetError("Failed to create the Mono app domain."); - return false; - } - - mono_domain_set(m_appDomain, true); - return true; -} - -bool MonoScriptRuntime::LoadAssemblies() { - m_coreAssembly = mono_domain_assembly_open(m_appDomain, m_settings.coreAssemblyPath.string().c_str()); - m_coreImage = mono_assembly_get_image(m_coreAssembly); - - m_appAssembly = mono_domain_assembly_open(m_appDomain, m_settings.appAssemblyPath.string().c_str()); - m_appImage = mono_assembly_get_image(m_appAssembly); - - return true; -} -``` - -所以这套脚本模块当前已经具备这些能力: - -1. 生命周期:`Awake / OnEnable / Start / FixedUpdate / Update / LateUpdate / OnDisable / OnDestroy` -2. 基础 API:`Debug / Time / Input / GameObject / Component / Transform / Camera / Light / MeshFilter / MeshRenderer / Object.Destroy` -3. 运行时创建和销毁 `GameObject` -4. 运行时增加和删除组件 -5. 字段元数据读取、默认值读取、字段写入、字段清除、字段序列化 -6. `public` 字段和 `[SerializeField] private` 字段 -7. `GameObject` 引用、内建组件引用、脚本组件引用 - -但是也有明确边界: - -1. 后端现在只有 Mono,没有 CoreCLR、没有 IL2CPP。 -2. 当前真正加载的“用户脚本程序集”只有一个 `GameScripts.dll`。 -3. `C++ -> C#` 现在基本只支持生命周期调用,不是通用托管方法调用框架。 -4. 字段系统只支持有限类型,不支持任意 C# 类型。 -5. 整套 internal call 依赖全局 `scene` 和 `deltaTime` 上下文,本质上是单活跃场景、单运行时、单线程调用模型。 - ---- - -## 3. `ScriptEngine` 在这套系统里的职责 - -`ScriptEngine` 是脚本生命周期的总调度器,不直接关心 Mono API 细节,它只面向 `IScriptRuntime`。 - -接口定义就是: - -```cpp -class IScriptRuntime { -public: - virtual void OnRuntimeStart(Components::Scene* scene) = 0; - virtual void OnRuntimeStop(Components::Scene* scene) = 0; - - virtual bool CreateScriptInstance(const ScriptRuntimeContext& context) = 0; - virtual void DestroyScriptInstance(const ScriptRuntimeContext& context) = 0; - - virtual void InvokeMethod( - const ScriptRuntimeContext& context, - ScriptLifecycleMethod method, - float deltaTime) = 0; -}; -``` - -`ScriptEngine` 在运行时会为每个 `ScriptComponent` 维护一份状态: - -```cpp -struct ScriptInstanceState { - ScriptRuntimeContext context; - bool instanceCreated = false; - bool awakeCalled = false; - bool enabled = false; - bool startCalled = false; - bool startPending = false; -}; -``` - -核心调度逻辑是: - -```cpp -bool ScriptEngine::EnsureScriptReady(ScriptInstanceState& state, bool invokeEnableIfNeeded) { - if (!state.instanceCreated) { - if (!m_runtime->CreateScriptInstance(state.context)) { - return false; - } - state.instanceCreated = true; - } - - if (!state.awakeCalled) { - InvokeLifecycleMethod(state, ScriptLifecycleMethod::Awake); - state.awakeCalled = true; - } - - if (invokeEnableIfNeeded && !state.enabled && ShouldScriptRun(state)) { - InvokeLifecycleMethod(state, ScriptLifecycleMethod::OnEnable); - state.enabled = true; - if (!state.startCalled) { - state.startPending = true; - } - } - - return true; -} -``` - -`Update` 调度是: - -```cpp -void ScriptEngine::OnUpdate(float deltaTime) { - for (const ScriptInstanceKey& key : updateKeys) { - ScriptInstanceState& state = it->second; - if (!ShouldScriptRun(state) || !EnsureScriptReady(state, true) || !state.enabled) { - continue; - } - - if (state.startPending && !state.startCalled) { - InvokeLifecycleMethod(state, ScriptLifecycleMethod::Start); - state.startCalled = true; - state.startPending = false; - } - - InvokeLifecycleMethod(state, ScriptLifecycleMethod::Update, deltaTime); - } -} -``` - -每次调完生命周期,还会把托管字段同步回存储: - -```cpp -void ScriptEngine::InvokeLifecycleMethod(ScriptInstanceState& state, ScriptLifecycleMethod method, float deltaTime) { - m_runtime->InvokeMethod(state.context, method, deltaTime); - m_runtime->SyncManagedFieldsToStorage(state.context); -} -``` - -这意味着: - -1. 生命周期顺序是 engine 明确调度出来的。 -2. 运行时实例的创建、启用、停用、销毁都走 `ScriptEngine`。 -3. Mono 只是被它驱动,不是反过来掌控主流程。 - ---- - -## 4. C# 调 C++:靠 `InternalCall` - -### 4.1 托管侧先声明 `extern` - -托管侧统一在 `InternalCalls` 里声明: - -```csharp -internal static class InternalCalls -{ - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void Transform_SetLocalPosition(ulong gameObjectUUID, ref Vector3 position); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern Component GameObject_GetComponent(ulong gameObjectUUID, Type componentType); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern Component GameObject_AddComponent(ulong gameObjectUUID, Type componentType); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void Debug_Log(string message, string file, int line, string member); -} -``` - -高层 C# API 只是再包一层: - -```csharp -public sealed class Transform : Component -{ - public Vector3 localPosition - { - get - { - InternalCalls.Transform_GetLocalPosition(GameObjectUUID, out Vector3 position); - return position; - } - set - { - InternalCalls.Transform_SetLocalPosition(GameObjectUUID, ref value); - } - } -} -``` - -```csharp -public sealed class GameObject : Object -{ - public T GetComponent() where T : Component - { - return InternalCalls.GameObject_GetComponent(UUID, typeof(T)) as T; - } - - public T AddComponent() where T : Component - { - return InternalCalls.GameObject_AddComponent(UUID, typeof(T)) as T; - } -} -``` - -### 4.2 native 启动时注册映射 - -Mono runtime 初始化 root domain 时,会统一注册 native 函数: - -```cpp -void RegisterInternalCalls() { - mono_add_internal_call("XCEngine.InternalCalls::Debug_Log", reinterpret_cast(&InternalCall_Debug_Log)); - mono_add_internal_call("XCEngine.InternalCalls::Time_GetDeltaTime", reinterpret_cast(&InternalCall_Time_GetDeltaTime)); - mono_add_internal_call("XCEngine.InternalCalls::GameObject_GetComponent", reinterpret_cast(&InternalCall_GameObject_GetComponent)); - mono_add_internal_call("XCEngine.InternalCalls::GameObject_AddComponent", reinterpret_cast(&InternalCall_GameObject_AddComponent)); - mono_add_internal_call("XCEngine.InternalCalls::Transform_SetLocalPosition", reinterpret_cast(&InternalCall_Transform_SetLocalPosition)); - mono_add_internal_call("XCEngine.InternalCalls::MeshRenderer_SetRenderLayer", reinterpret_cast(&InternalCall_MeshRenderer_SetRenderLayer)); -} -``` - -所以 `transform.localPosition = value` 这条链路实际就是: - -```csharp -InternalCalls.Transform_SetLocalPosition(GameObjectUUID, ref value); -``` - -最终对应的 native 实现: - -```cpp -void InternalCall_Transform_SetLocalPosition(uint64_t gameObjectUUID, XCEngine::Math::Vector3* position) { - if (!position) { - return; - } - - Components::GameObject* gameObject = FindGameObjectByUUID(gameObjectUUID); - if (!gameObject || !gameObject->GetTransform()) { - return; - } - - gameObject->GetTransform()->SetLocalPosition(*position); -} -``` - -### 4.3 这边跨边界传的不是指针,是 UUID - -`Component` 和 `Behaviour` 的托管基类本身就只保存 UUID: - -```csharp -public abstract class Component : Object -{ - internal ulong m_gameObjectUUID; -} - -public class Behaviour : Component -{ - internal ulong m_scriptComponentUUID = 0; -} -``` - -所以 C# 这边拿到的对象,本质是“包着 UUID 的托管 wrapper”,真正访问 native 对象时再回 native 查。 - -### 4.4 值类型参数为什么能直接传 - -`Vector2 / Vector3 / Quaternion` 都是顺序布局: - -```csharp -[StructLayout(LayoutKind.Sequential)] -public struct Vector3 -{ - public float X; - public float Y; - public float Z; -} -``` - -```csharp -[StructLayout(LayoutKind.Sequential)] -public struct Quaternion -{ - public float X; - public float Y; - public float Z; - public float W; -} -``` - -所以 native 可以直接用对应内存布局去解释参数。 - ---- - -## 5. `GetComponent()` / `AddComponent()` 这种泛型调用到底怎么落到 native - -这里关键点是:托管侧传的是 `typeof(T)`,native 收到的是 `MonoReflectionType*`,然后自己判断这个类型到底是: - -1. 内建组件 -2. 脚本组件 -3. 不支持的类型 - -判断逻辑是: - -```cpp -ManagedComponentTypeInfo ResolveManagedComponentTypeInfo(MonoClass* monoClass) { - ManagedComponentTypeInfo typeInfo; - typeInfo.monoClass = monoClass; - typeInfo.namespaceName = SafeString(mono_class_get_namespace(monoClass)); - typeInfo.className = SafeString(mono_class_get_name(monoClass)); - - if (typeInfo.namespaceName == "XCEngine" && typeInfo.className == "Transform") { - typeInfo.kind = ManagedComponentKind::Transform; - return typeInfo; - } - if (typeInfo.namespaceName == "XCEngine" && typeInfo.className == "Camera") { - typeInfo.kind = ManagedComponentKind::Camera; - return typeInfo; - } - if (typeInfo.namespaceName == "XCEngine" && typeInfo.className == "Light") { - typeInfo.kind = ManagedComponentKind::Light; - return typeInfo; - } - - MonoScriptRuntime* runtime = GetActiveMonoScriptRuntime(); - if (runtime - && runtime->IsClassAvailable( - runtime->GetSettings().appAssemblyName, - typeInfo.namespaceName, - typeInfo.className)) { - typeInfo.kind = ManagedComponentKind::Script; - typeInfo.assemblyName = runtime->GetSettings().appAssemblyName; - } - - return typeInfo; -} -``` - -### 5.1 `GetComponent()` - -native 实现是: - -```cpp -MonoObject* InternalCall_GameObject_GetComponent(uint64_t gameObjectUUID, MonoReflectionType* componentType) { - Components::GameObject* gameObject = FindGameObjectByUUID(gameObjectUUID); - MonoScriptRuntime* runtime = GetActiveMonoScriptRuntime(); - const ManagedComponentTypeInfo typeInfo = ResolveManagedComponentTypeInfo(componentType); - - if (typeInfo.kind == ManagedComponentKind::Script) { - ScriptComponent* component = FindMatchingScriptComponent(gameObject, typeInfo); - if (!component) { - return nullptr; - } - - if (!runtime->HasManagedInstance(component)) { - ScriptEngine::Get().OnScriptComponentEnabled(component); - } - - return runtime->GetManagedInstanceObject(component); - } - - if (!HasNativeComponent(gameObject, typeInfo.kind) || !typeInfo.monoClass) { - return nullptr; - } - - return runtime->CreateManagedComponentWrapper(typeInfo.monoClass, gameObjectUUID); -} -``` - -这个实现有个很重要的差异: - -1. 对脚本组件,返回的是那个脚本实例对应的真实托管对象。 -2. 对 `Transform / Camera / Light / MeshFilter / MeshRenderer`,返回的是临时创建的 wrapper。 - -### 5.2 内建组件 wrapper 是怎么造的 - -```cpp -MonoObject* MonoScriptRuntime::CreateManagedComponentWrapper(MonoClass* componentClass, uint64_t gameObjectUUID) { - MonoMethod* constructor = mono_class_get_method_from_name(componentClass, ".ctor", 1); - MonoObject* managedObject = mono_object_new(m_appDomain, componentClass); - - void* args[1]; - uint64_t uuidArgument = gameObjectUUID; - args[0] = &uuidArgument; - - MonoObject* exception = nullptr; - mono_runtime_invoke(constructor, managedObject, args, &exception); - return managedObject; -} -``` - -所以本质上: - -1. `GetComponent()` 拿到的是脚本实例本身。 -2. `GetComponent()` 拿到的是一个根据 `gameObjectUUID` 临时包出来的托管壳子。 - -### 5.3 `AddComponent()` - -native 实现: - -```cpp -MonoObject* InternalCall_GameObject_AddComponent(uint64_t gameObjectUUID, MonoReflectionType* componentType) { - Components::GameObject* gameObject = FindGameObjectByUUID(gameObjectUUID); - MonoScriptRuntime* runtime = GetActiveMonoScriptRuntime(); - const ManagedComponentTypeInfo typeInfo = ResolveManagedComponentTypeInfo(componentType); - - if (typeInfo.kind == ManagedComponentKind::Script) { - ScriptComponent* component = gameObject->AddComponent(); - component->SetScriptClass(typeInfo.assemblyName, typeInfo.namespaceName, typeInfo.className); - return runtime->GetManagedInstanceObject(component); - } - - if (!AddOrGetNativeComponent(gameObject, typeInfo.kind) || !typeInfo.monoClass) { - return nullptr; - } - - return runtime->CreateManagedComponentWrapper(typeInfo.monoClass, gameObjectUUID); -} -``` - -这里说明现在脚本组件是通过加一个 native `ScriptComponent`,再把它的脚本类信息设为目标托管类型来完成绑定的。 - ---- - -## 6. C++ 调 C#:靠 `mono_runtime_invoke` - -`C++ -> C#` 跟 `InternalCall` 是完全反方向的机制。 - -### 6.1 运行时先在类扫描阶段缓存生命周期方法 - -```cpp -for (size_t methodIndex = 0; methodIndex < LifecycleMethodCount; ++methodIndex) { - metadata.lifecycleMethods[methodIndex] = mono_class_get_method_from_name( - monoClass, - ToLifecycleMethodName(static_cast(methodIndex)), - 0); -} -``` - -这里是硬编码找 0 参数方法,也就是说现在只认: - -1. `Awake()` -2. `OnEnable()` -3. `Start()` -4. `FixedUpdate()` -5. `Update()` -6. `LateUpdate()` -7. `OnDisable()` -8. `OnDestroy()` - -### 6.2 创建脚本实例 - -```cpp -bool MonoScriptRuntime::CreateScriptInstance(const ScriptRuntimeContext& context) { - const ClassMetadata* classMetadata = FindClassMetadata( - assemblyName, - context.component->GetNamespaceName(), - context.component->GetClassName()); - - MonoObject* instance = mono_object_new(m_appDomain, classMetadata->monoClass); - mono_runtime_object_init(instance); - - if (!ApplyContextFields(context, instance) || !ApplyStoredFields(context, *classMetadata, instance)) { - return false; - } - - const uint32_t gcHandle = mono_gchandle_new(instance, false); - const InstanceKey key{context.gameObjectUUID, context.scriptComponentUUID}; - m_instances[key] = InstanceData{classMetadata, gcHandle}; - return true; -} -``` - -### 6.3 native 上下文会先写进托管对象 - -```cpp -bool MonoScriptRuntime::ApplyContextFields(const ScriptRuntimeContext& context, MonoObject* instance) { - if (m_gameObjectUUIDField) { - uint64_t gameObjectUUID = context.gameObjectUUID; - mono_field_set_value(instance, m_gameObjectUUIDField, &gameObjectUUID); - } - - if (m_scriptComponentUUIDField) { - uint64_t scriptComponentUUID = context.scriptComponentUUID; - mono_field_set_value(instance, m_scriptComponentUUIDField, &scriptComponentUUID); - } - - return true; -} -``` - -这就和托管基类字段对上了: - -```csharp -public abstract class Component : Object -{ - internal ulong m_gameObjectUUID; -} - -public class Behaviour : Component -{ - internal ulong m_scriptComponentUUID = 0; -} -``` - -所以脚本里的 `gameObject`、`transform`、`enabled` 才能知道自己对应哪个 native 对象。 - -### 6.4 生命周期调用就是 `mono_runtime_invoke` - -```cpp -void MonoScriptRuntime::InvokeMethod( - const ScriptRuntimeContext& context, - ScriptLifecycleMethod method, - float deltaTime) { - const InstanceData* instanceData = FindInstance(context); - MonoMethod* managedMethod = instanceData->classMetadata->lifecycleMethods[static_cast(method)]; - MonoObject* instance = GetManagedObject(*instanceData); - - const float previousDeltaTime = GetInternalCallDeltaTime(); - GetInternalCallDeltaTime() = deltaTime; - InvokeManagedMethod(instance, managedMethod); - GetInternalCallDeltaTime() = previousDeltaTime; -} -``` - -```cpp -bool MonoScriptRuntime::InvokeManagedMethod(MonoObject* instance, MonoMethod* method) { - MonoObject* exception = nullptr; - mono_runtime_invoke(method, instance, nullptr, &exception); - if (exception) { - RecordException(exception); - return false; - } - - return true; -} -``` - -也就是说 `C++ -> C#` 当前核心就是: - -1. 找到 `MonoObject*` -2. 找到缓存好的 `MonoMethod*` -3. `mono_runtime_invoke` - -托管脚本像这样: - -```csharp -public sealed class LifecycleProbe : MonoBehaviour -{ - public int AwakeCount; - public int UpdateCount; - public float Speed; - public string Label = string.Empty; - - public void Awake() - { - AwakeCount += 1; - gameObject.name = gameObject.name + "_Managed"; - Label = Label + "|Awake"; - } - - public void Update() - { - UpdateCount += 1; - Speed += 1.0f; - ObservedLocalPosition = transform.localPosition; - } -} -``` - -就是被上面这条链直接调进去的。 - ---- - -## 7. 字段系统当前到底怎么做 - -这块非常关键,因为它决定哪些数据是可持久化的,哪些只是运行时瞬时值。 - -### 7.1 字段暴露规则 - -字段扫描逻辑: - -```cpp -while (MonoClassField* field = mono_class_get_fields(monoClass, &fieldIterator)) { - const uint32_t fieldFlags = mono_field_get_flags(field); - if ((fieldFlags & MONO_FIELD_ATTR_STATIC) != 0 - || (fieldFlags & MONO_FIELD_ATTR_LITERAL) != 0 - || (fieldFlags & MONO_FIELD_ATTR_INIT_ONLY) != 0) { - continue; - } - - const bool isPublicField = (fieldFlags & MONO_FIELD_ATTR_PUBLIC) != 0; - if (!isPublicField && !HasSerializeFieldAttribute(field)) { - continue; - } - - FieldMetadata fieldMetadata = BuildFieldMetadata(field); - if (fieldMetadata.type == ScriptFieldType::None) { - continue; - } - - metadata.fields.emplace(mono_field_get_name(field), std::move(fieldMetadata)); -} -``` - -所以当前能暴露出来的字段必须满足: - -1. 不是 `static` -2. 不是字面量 -3. 不是 `readonly` -4. 是 `public`,或者是 `[SerializeField] private` -5. 类型在字段系统支持范围内 - -`[SerializeField]` 这个 attribute 本身很简单: - -```csharp -[AttributeUsage(AttributeTargets.Field, Inherited = true)] -public sealed class SerializeField : Attribute -{ -} -``` - -检查逻辑是: - -```cpp -bool MonoScriptRuntime::HasSerializeFieldAttribute(MonoClassField* field) const { - MonoClass* ownerClass = mono_field_get_parent(field); - MonoCustomAttrInfo* attributes = mono_custom_attrs_from_field(ownerClass, field); - const mono_bool hasAttribute = mono_custom_attrs_has_attr(attributes, m_serializeFieldAttributeClass); - mono_custom_attrs_free(attributes); - return hasAttribute != 0; -} -``` - -### 7.2 当前支持的字段类型 - -类型映射是写死的: - -```cpp -switch (mono_type_get_type(monoType)) { - case MONO_TYPE_R4: - metadata.type = ScriptFieldType::Float; - return metadata; - case MONO_TYPE_R8: - metadata.type = ScriptFieldType::Double; - return metadata; - case MONO_TYPE_BOOLEAN: - metadata.type = ScriptFieldType::Bool; - return metadata; - case MONO_TYPE_I4: - metadata.type = ScriptFieldType::Int32; - return metadata; - case MONO_TYPE_U8: - metadata.type = ScriptFieldType::UInt64; - return metadata; - case MONO_TYPE_STRING: - metadata.type = ScriptFieldType::String; - return metadata; - case MONO_TYPE_CLASS: - // GameObject / Component - case MONO_TYPE_VALUETYPE: - // enum / Vector2 / Vector3 / Vector4 -} -``` - -脚本字段系统正式支持这些类型: - -1. `float` -2. `double` -3. `bool` -4. `int` -5. `ulong` -6. `string` -7. `Vector2` -8. `Vector3` -9. `Vector4` -10. `GameObject` -11. 组件引用 -12. enum,但按 `Int32` 落库 - -字段类型枚举本身就是: - -```cpp -enum class ScriptFieldType { - None = 0, - Float, - Double, - Bool, - Int32, - UInt64, - String, - Vector2, - Vector3, - Vector4, - GameObject, - Component -}; -``` - -### 7.3 组件字段支持范围 - -组件字段不是所有 `Component` 子类都支持,当前只支持: - -1. 具体脚本类 -2. `Transform` -3. `Camera` -4. `Light` -5. `MeshFilter` -6. `MeshRenderer` - -对应判断: - -```cpp -bool MonoScriptRuntime::IsSupportedComponentFieldClass(MonoClass* monoClass) const { - if (!IsMonoClassOrSubclass(monoClass, m_componentClass)) { - return false; - } - - if (monoClass == m_componentClass || monoClass == m_behaviourClass || monoClass == m_monoBehaviourClass) { - return false; - } - - if (IsScriptComponentFieldClass(monoClass)) { - return true; - } - - const std::string namespaceName = SafeString(mono_class_get_namespace(monoClass)); - const std::string className = SafeString(mono_class_get_name(monoClass)); - if (namespaceName != m_settings.baseNamespace) { - return false; - } - - return className == "Transform" - || className == "Camera" - || className == "Light" - || className == "MeshFilter" - || className == "MeshRenderer"; -} -``` - -所以这几个字段是支持的: - -```csharp -public sealed class ComponentFieldMetadataProbe : MonoBehaviour -{ - public Transform Pivot; - public Camera SceneCamera; - public ScriptComponentTargetProbe ScriptTarget; -} -``` - -但这种不支持: - -```csharp -public Component UnsupportedComponent; -public Behaviour UnsupportedBehaviour; -public MonoBehaviour UnsupportedMonoBehaviour; -``` - -### 7.4 一个很容易误判的点:`Quaternion` API 支持,但字段系统不支持 - -例如这个类: - -```csharp -public sealed class FieldMetadataProbe : MonoBehaviour -{ - public int Health; - public float Speed; - public string Label = string.Empty; - public Vector3 SpawnPoint; - public FieldMetadataProbeState State = FieldMetadataProbeState.Running; - [SerializeField] private bool HiddenFlag = true; - public GameObject Target; - public Quaternion UnsupportedRotation; - public static int SharedCounter; - private int IgnoredPrivateCounter = 7; -} -``` - -其中能进字段系统的是: - -1. `Health` -2. `Speed` -3. `Label` -4. `SpawnPoint` -5. `State` -6. `HiddenFlag` -7. `Target` - -不能进字段系统的是: - -1. `UnsupportedRotation`,因为字段系统不支持 `Quaternion` -2. `SharedCounter`,因为是 `static` -3. `IgnoredPrivateCounter`,因为只是 private 且没有 `[SerializeField]` - -这里别混淆: - -1. `transform.localRotation` 这种 API 层是支持 `Quaternion` 的。 -2. 但“脚本字段持久化系统”不支持 `Quaternion`。 - -这是两套不同能力。 - ---- - -## 8. 字段值如何从 native 推给 C#,又如何从 C# 回写回来 - -### 8.1 实例创建时,先把存储值推给托管对象 - -```cpp -bool MonoScriptRuntime::ApplyStoredFields( - const ScriptRuntimeContext& context, - const ClassMetadata& metadata, - MonoObject* instance) { - const ScriptFieldStorage& fieldStorage = context.component->GetFieldStorage(); - for (const std::string& fieldName : fieldStorage.GetFieldNames()) { - const StoredScriptField* storedField = fieldStorage.FindField(fieldName); - const auto metadataIt = metadata.fields.find(fieldName); - if (metadataIt == metadata.fields.end() || storedField->type != metadataIt->second.type) { - continue; - } - - if (!TrySetFieldValue(instance, metadataIt->second, storedField->value)) { - return false; - } - } - - return true; -} -``` - -最终就是一堆 `mono_field_set_value`: - -```cpp -case ScriptFieldType::Float: { - float nativeValue = std::get(value); - mono_field_set_value(instance, fieldMetadata.field, &nativeValue); - return true; -} -case ScriptFieldType::String: { - MonoString* managedString = mono_string_new(m_appDomain, std::get(value).c_str()); - mono_field_set_value(instance, fieldMetadata.field, managedString); - return true; -} -case ScriptFieldType::GameObject: { - const GameObjectReference reference = std::get(value); - MonoObject* managedGameObject = CreateManagedGameObject(reference.gameObjectUUID); - mono_field_set_value(instance, fieldMetadata.field, managedGameObject); - return true; -} -``` - -### 8.2 生命周期调用后,把托管字段回刷到 `ScriptFieldStorage` - -```cpp -void ScriptEngine::InvokeLifecycleMethod(ScriptInstanceState& state, ScriptLifecycleMethod method, float deltaTime) { - m_runtime->InvokeMethod(state.context, method, deltaTime); - m_runtime->SyncManagedFieldsToStorage(state.context); -} -``` - -```cpp -void MonoScriptRuntime::SyncManagedFieldsToStorage(const ScriptRuntimeContext& context) { - ScriptFieldStorage& fieldStorage = context.component->GetFieldStorage(); - const std::vector fieldNames = fieldStorage.GetFieldNames(); - for (const std::string& fieldName : fieldNames) { - StoredScriptField* storedField = fieldStorage.FindField(fieldName); - const auto metadataIt = instanceData->classMetadata->fields.find(fieldName); - if (metadataIt == instanceData->classMetadata->fields.end() || storedField->type != metadataIt->second.type) { - continue; - } - - ScriptFieldValue value; - if (!TryReadFieldValue(instance, metadataIt->second, value)) { - continue; - } - - fieldStorage.SetFieldValue(fieldName, storedField->type, value); - } -} -``` - -### 8.3 这套“回写持久化”不是自动全量持久化 - -这个点非常关键。 - -当前逻辑不是“托管对象里所有支持字段只要变了就自动入库”,而是: - -1. 只有已经存在于 `ScriptFieldStorage` 的字段,才会在回刷时被更新。 -2. 不会因为 C# 里某个字段变了,就自动新增一条存储记录。 - -测试把这个行为钉得很死: - -```cpp -component->GetFieldStorage().SetFieldValue("Speed", 5.0f); -component->GetFieldStorage().SetFieldValue("Label", "Configured"); -component->GetFieldStorage().SetFieldValue("Target", GameObjectReference{target->GetUUID()}); -component->GetFieldStorage().SetFieldValue("SpawnPoint", XCEngine::Math::Vector3(2.0f, 4.0f, 6.0f)); - -engine->OnRuntimeStart(runtimeScene); -engine->OnFixedUpdate(0.02f); -engine->OnUpdate(0.016f); -engine->OnLateUpdate(0.016f); - -EXPECT_FLOAT_EQ(storedSpeed, 6.0f); -EXPECT_EQ(storedLabel, "Configured|Awake"); -EXPECT_EQ(storedSpawnPoint, XCEngine::Math::Vector3(3.0f, 4.0f, 6.0f)); -EXPECT_FALSE(component->GetFieldStorage().Contains("AwakeCount")); -``` - -这个测试直接说明: - -1. `Speed / Label / SpawnPoint` 因为事先在 storage 里,所以托管修改后能写回。 -2. `AwakeCount` 虽然在托管里也变了,但因为 storage 里本来没有,所以不会自动入库。 - -这不是 Unity 式的“所有序列化字段自动全量持久化”,现在这套比它更保守。 - ---- - -## 9. 当前 `C++ -> C#` 的边界到底在哪 - -现在引擎正式暴露给 runtime 的主动调用能力,基本只有: - -1. 创建脚本实例 -2. 销毁脚本实例 -3. 调生命周期方法 - -不是一个通用托管方法调用框架。 - -也就是说,今天这套系统正式支持的是: - -```cpp -m_runtime->InvokeMethod(state.context, ScriptLifecycleMethod::Update, deltaTime); -``` - -不支持的是这种通用形式: - -1. native 随便按名字找一个脚本函数调 -2. native 给用户脚本传任意参数 -3. native 统一拿回任意返回值 -4. native 对任意用户自定义托管方法做正式调度 - -所以现在这套 `C++ -> C#` 本质上是: - -1. 生命周期驱动 -2. 字段同步 - -不是通用 RPC。 - ---- - -## 10. 双向调用机制压缩版 - -### 10.1 `C# -> C++` - -托管代码: - -```csharp -transform.localPosition = value; -``` - -托管壳子: - -```csharp -InternalCalls.Transform_SetLocalPosition(GameObjectUUID, ref value); -``` - -native 注册: - -```cpp -mono_add_internal_call("XCEngine.InternalCalls::Transform_SetLocalPosition", ...); -``` - -native 执行: - -```cpp -gameObject->GetTransform()->SetLocalPosition(*position); -``` - -### 10.2 `C++ -> C#` - -engine 调度: - -```cpp -m_runtime->InvokeMethod(state.context, ScriptLifecycleMethod::Update, deltaTime); -``` - -Mono runtime 执行: - -```cpp -MonoMethod* managedMethod = instanceData->classMetadata->lifecycleMethods[static_cast(method)]; -mono_runtime_invoke(managedMethod, instance, nullptr, &exception); -``` - -托管脚本命中: - -```csharp -public void Update() -{ - UpdateCount += 1; - Speed += 1.0f; -} -``` - -### 10.3 一句话记忆 - -1. `C# 调 C++`:`InternalCall` -2. `C++ 调 C#`:`mono_runtime_invoke` -3. 跨边界对象身份:`UUID` -4. 持久化字段缓存:`ScriptFieldStorage` - ---- - -## 11. 当前实现最值得注意的几个现实判断 - -### 11.1 builtin 组件和脚本组件的托管对象不是同一种语义 - -1. 脚本组件返回的是真实脚本实例托管对象。 -2. `Transform / Camera / Light / MeshFilter / MeshRenderer` 返回的是 wrapper。 - -这个差异会直接影响对象身份和引用行为。 - -### 11.2 `assemblyName` 字段设计得比当前 runtime 更泛化 - -`ScriptComponent` 存了: - -```cpp -std::string m_assemblyName = "GameScripts"; -std::string m_namespaceName; -std::string m_className; -``` - -但当前 `MonoScriptRuntime` 实际只正式加载: - -1. `XCEngine.ScriptCore.dll` -2. `GameScripts.dll` - -也就是说,数据模型预留了多程序集能力,但当前 runtime 还没真正展开到多用户程序集装载。 - -### 11.3 当前系统已经不是玩具级别试验,但还没到“通用脚本平台” - -它已经完成了: - -1. editor 编译链 -2. Mono runtime 宿主 -3. 生命周期驱动 -4. 常用 API 桥接 -5. 字段元数据与默认值 -6. 字段存储与序列化 -7. 运行时对象与组件操作 - -但它还缺: - -1. 多程序集用户脚本正式支持 -2. 通用 native -> managed 任意方法调用 -3. 更广的字段类型系统 -4. 更正式的 reload / domain reload 策略抽象 -5. 更强的线程模型和上下文模型 - ---- - -## 12. 最终结论 - -当前 XCEngine 的脚本模块核心机制可以压成一句话: - -这套系统以 `ScriptEngine` 为生命周期调度中心,以 `MonoScriptRuntime` 为 Mono 宿主与桥接层,以 `XCEngine.ScriptCore` 为托管 API 外壳;`C# -> C++` 通过 `InternalCall` 进入 native,`C++ -> C#` 通过 `mono_runtime_invoke` 调生命周期方法,跨边界对象身份依赖 `UUID`,字段持久化依赖 `ScriptFieldStorage`,而不是直接共享 native 指针或做通用托管 RPC。 diff --git a/docs/used/C#脚本模块的设计与实现_阶段一归档_2026-04-03.md b/docs/used/C#脚本模块的设计与实现_阶段一归档_2026-04-03.md deleted file mode 100644 index 87272c1a..00000000 --- a/docs/used/C#脚本模块的设计与实现_阶段一归档_2026-04-03.md +++ /dev/null @@ -1,758 +0,0 @@ -# C#脚本模块的设计与实现 - -日期:3.27 - -## 1. 背景 - -XCEngine 的整体方向是模仿传统 Unity 引擎架构,而不是 DOTS/ECS-first 路线。 -在这一目标下,脚本系统应当满足以下基本预期: - -- 脚本语言使用 `C#` -- 脚本以“挂载到 `GameObject` 上的组件”形式工作 -- 脚本与引擎核心解耦,支持独立编译和运行时加载 -- 脚本可以逐步扩展到 Inspector、场景序列化、Play/Simulate 工作流 - -当前 `editor` 仍处于基础阶段,因此脚本系统第一阶段不应依赖 editor 完整落地。 -第一阶段的目标应当收敛为: - -- 先完成原生运行时与托管运行时之间的桥接 -- 先完成 `ScriptComponent + C# MonoBehaviour` 的基本执行链路 -- 先完成单元测试和最小场景级验证 -- 将 editor 集成需求单独列为 issue,后续补齐 - ---- - -## 2. 设计目标 - -### 2.1 总体目标 - -脚本系统应当提供一条接近 Unity 的开发路径: - -1. 用户在独立的 C# 项目中编写脚本 -2. C# 脚本编译为程序集 -3. Engine Core 在运行时加载程序集 -4. `GameObject` 上挂载 `ScriptComponent` -5. `ScriptComponent` 驱动一个对应的 C# `MonoBehaviour` 实例 -6. 脚本通过引擎暴露的 API 调用原生功能 - -### 2.2 第一阶段目标 - -第一阶段只覆盖以下内容: - -- `engine` 内部的脚本运行时抽象 -- 第一套 C# 运行时实现 -- `ScriptComponent` 原生组件 -- `ScriptCore` 托管基础库 -- 最小可用的 `InternalCall` 绑定 -- 单元测试与最小运行时测试 - -### 2.3 第一阶段非目标 - -第一阶段明确不做以下内容: - -- editor Inspector 脚本字段编辑 -- editor 中的脚本类选择器 -- editor 的 Play/Simulate 集成 -- 自动编译、文件监听、热重载 -- 调试器接入 -- 发布态 AOT / IL2CPP -- 大而全的引擎 API 暴露 - ---- - -## 3. 三方对比结论 - -### 3.1 Unity - -Unity 的典型脚本模型有几个关键特征: - -- 用户脚本通常继承 `MonoBehaviour` -- 一段脚本本质上就是一个组件实例 -- 脚本字段可序列化、可在 Inspector 中编辑 -- 生命周期完整,且与 `GameObject active` / `Component enabled` 语义一致 -- Play 模式运行的是运行时场景副本,而不是直接修改编辑场景 - -### 3.2 参考项目 Fermion - -参考项目已经实现了一套 C# 脚本模块,优点主要在于: - -- 已经证明了 `Mono embedding + InternalCall + C# 程序集加载` 这条路线可行 -- 已经具备脚本类发现、脚本实例创建、字段反射、运行时调用 -- 已经有托管侧 API 包装层和原生侧 `ScriptGlue` - -但它的对象模型并不是 Unity 风格: - -- 托管脚本类继承的是 `Entity` -- 原生挂载结构是 `ScriptContainerComponent` -- 更像“一个实体挂多个脚本类名”,而不是“每个脚本本身就是一个组件” -- 生命周期目前主要聚焦 `OnCreate/OnUpdate` - -结论: - -- Fermion 适合借鉴运行时技术路线 -- Fermion 不适合作为最终 API 形态的直接模板 - -### 3.3 XCEngine 当前情况 - -XCEngine 当前已经具备以下基础: - -- 已有 `GameObject + Component + Scene` 模型 -- `Component` 已定义 Unity 风格生命周期接口 -- `Scene` 和 `GameObject` 已有序列化入口 -- `ComponentFactoryRegistry` 已支持按类型名恢复组件 - -但当前也存在会直接影响脚本系统设计的现实约束: - -- `Scene::Update/FixedUpdate/LateUpdate` 目前只遍历根对象 -- `GameObject::Update/FixedUpdate/LateUpdate` 目前不递归子对象 -- `Start` 的场景级一次性调度路径还未完整建立 -- `SetActive` 尚未真正驱动 `OnEnable/OnDisable` -- editor 的 Play/Simulate 工作流仍未落地 -- `GameObject UUID` 当前未进入场景序列化主路径 - -结论: - -- XCEngine 适合走 Unity 风格脚本模型 -- 但脚本系统第一阶段必须连同一部分运行时地基一起建设 - ---- - -## 4. 总体设计结论 - -XCEngine 的 C# 脚本模块采用以下路线: - -- **脚本语言**:C# -- **脚本挂载模型**:原生 `ScriptComponent` 对应托管 `MonoBehaviour` -- **程序集模型**:`ScriptCore` 与 `GameScripts` 分离 -- **运行时加载模式**:独立编译,运行时加载 -- **桥接方式**:`InternalCall` -- **运行时抽象策略**:先抽象接口,再优先落 Mono 实现 -- **第一阶段验证方式**:单元测试优先,不依赖 editor - -这条路线有两个核心原则: - -1. API 形态尽量接近 Unity -2. 第一阶段严格控制范围,只做运行时闭环和测试闭环 - ---- - -## 5. 运行时选型 - -### 5.1 第一阶段选型:Mono - -第一阶段建议使用 `Mono` 作为第一套 C# 运行时实现,原因如下: - -- 与现有规划文档保持一致 -- 参考项目已经证明这条技术路线可落地 -- `InternalCall` 路线成熟,适合快速建立最小可用系统 -- 便于在 Windows 环境中先做出可运行结果 - -### 5.2 选型边界 - -本设计不把 `Mono` 写死为脚本系统唯一实现,而是将其作为第一实现: - -- 对外暴露 `IScriptRuntime` / `ScriptEngine` 抽象 -- `MonoScriptRuntime` 作为第一套后端 -- 后续如有需要,可以演进到 `CoreCLR` 或其他运行时 - -### 5.3 第一阶段不做的运行时能力 - -Mono 相关的以下复杂能力不进入第一阶段: - -- 域热重载 -- 编辑器内自动重编译后重载 -- 托管调试器接入 -- 发布态 AOT - -第一阶段仅要求: - -- 初始化运行时 -- 加载核心程序集 -- 加载用户程序集 -- 发现脚本类 -- 实例化对象 -- 调用生命周期 -- 读写托管字段 - ---- - -## 6. 模块划分 - -### 6.1 原生侧模块 - -建议在 `engine` 内新增 `Scripting` 模块: - -```text -engine/ -├── include/XCEngine/Scripting/ -│ ├── ScriptEngine.h -│ ├── IScriptRuntime.h -│ ├── ScriptAssembly.h -│ ├── ScriptClass.h -│ ├── ScriptInstance.h -│ ├── ScriptField.h -│ ├── ScriptFieldStorage.h -│ ├── ScriptGlue.h -│ └── ScriptComponent.h -└── src/Scripting/ - ├── ScriptEngine.cpp - ├── ScriptGlue.cpp - ├── ScriptComponent.cpp - └── Mono/ - ├── MonoScriptRuntime.cpp - ├── MonoScriptClass.cpp - └── MonoScriptAssembly.cpp -``` - -### 6.2 托管侧模块 - -建议新增托管核心库 `ScriptCore`: - -```text -managed/ -├── XCEngine.ScriptCore/ -│ ├── XCEngine.ScriptCore.csproj -│ ├── Object.cs -│ ├── Component.cs -│ ├── Behaviour.cs -│ ├── MonoBehaviour.cs -│ ├── GameObject.cs -│ ├── Transform.cs -│ ├── Debug.cs -│ ├── Time.cs -│ └── InternalCalls.cs -└── GameScripts/ - ├── GameScripts.csproj - └── Scripts/*.cs -``` - -### 6.3 程序集分层 - -程序集分为两层: - -- `XCEngine.ScriptCore.dll` - - 由引擎维护 - - 提供托管基类和引擎 API 包装 -- `GameScripts.dll` - - 由项目侧维护 - - 编写具体游戏脚本 - -关系如下: - -```text -GameScripts.dll - └── 引用 XCEngine.ScriptCore.dll - -Engine Core - ├── 先加载 XCEngine.ScriptCore.dll - └── 再加载 GameScripts.dll -``` - ---- - -## 7. 对象模型 - -### 7.1 托管侧模型 - -托管侧应当采用接近 Unity 的对象层次: - -```text -Object -└── Component - └── Behaviour - └── MonoBehaviour -``` - -其中: - -- `Object`:基础托管对象 -- `Component`:挂载到 `GameObject` 上的托管组件基类 -- `Behaviour`:带 `enabled` 语义的组件 -- `MonoBehaviour`:用户脚本直接继承的基类 - -### 7.2 原生挂载模型 - -原生侧脚本挂载使用 `ScriptComponent`,而不是 `ScriptContainerComponent`。 - -原因如下: - -- 更符合 Unity 认知模型 -- 更容易与现有 `GameObject::AddComponent` 思路对齐 -- 更容易在未来做 Inspector 级脚本组件显示 -- 更容易把字段序列化与组件实例对应起来 - -建议 `ScriptComponent` 至少包含: - -- `scriptComponentUUID` -- `assemblyName` -- `namespaceName` -- `className` -- `enabled` -- `fieldStorage` - -推荐接口示意: - -```cpp -class ScriptComponent : public Component { -public: - std::string GetName() const override { return "Script"; } - - const std::string& GetAssemblyName() const; - const std::string& GetNamespaceName() const; - const std::string& GetClassName() const; - std::string GetFullClassName() const; - - uint64_t GetScriptComponentUUID() const; - bool IsRuntimeValid() const; - - void Serialize(std::ostream& os) const override; - void Deserialize(std::istream& is) override; - -private: - uint64_t m_scriptComponentUUID = 0; - std::string m_assemblyName = "GameScripts"; - std::string m_namespaceName; - std::string m_className; - ScriptFieldStorage m_fieldStorage; -}; -``` - -### 7.3 多脚本挂载 - -一个 `GameObject` 应允许挂载多个 `ScriptComponent`。 - -这与 Unity 保持一致: - -- 同一个对象可以挂多个不同脚本 -- 同一个脚本是否允许重复挂载,由后续属性或规则控制 -- 第一阶段不做“禁止重复挂载”的复杂策略 - ---- - -## 8. 身份模型与序列化要求 - -### 8.1 必须使用 UUID,而不是运行时 ID - -脚本系统中,托管实例与原生对象的稳定绑定必须建立在 `UUID` 之上,而不是当前的自增 `ID`。 - -原因如下: - -- 运行时场景复制不能依赖自增 ID 稳定 -- 脚本字段里的对象引用必须有稳定键 -- editor 与 runtime 之间的对象映射必须有稳定键 -- 单元测试和场景恢复也需要稳定身份 - -因此需要补齐以下要求: - -- `GameObject UUID` 进入场景序列化 -- 场景反序列化时恢复 `UUID` -- `ScriptComponent` 自身也应有持久化 UUID - -### 8.2 第一阶段字段序列化策略 - -第一阶段的脚本字段序列化原则如下: - -- 只序列化 `ScriptComponent` 的字段缓存 -- 只序列化脚本作者显式设置的字段值 -- 运行时脚本执行过程中修改的值,不自动回写场景 - -这与 Unity 的 Play 模式行为一致: - -- Play 中的运行时改动不应直接污染编辑数据 - -### 8.3 第一阶段字段支持范围 - -建议第一阶段先支持: - -- `float` -- `double` -- `bool` -- `int32` -- `uint64` -- `string` -- `Vector2` -- `Vector3` -- `Vector4` -- `GameObject` 引用 - -第一阶段不要求支持: - -- `List` -- 自定义托管结构体 -- 嵌套对象图 -- 泛型容器 -- 资源引用对象选择器 - ---- - -## 9. 生命周期设计 - -### 9.1 目标生命周期 - -脚本系统最终应支持以下 Unity 风格生命周期: - -- `Awake` -- `OnEnable` -- `Start` -- `FixedUpdate` -- `Update` -- `LateUpdate` -- `OnDisable` -- `OnDestroy` - -### 9.2 第一阶段生命周期闭环 - -第一阶段就应当把上述生命周期的原生调度链路设计好,哪怕 editor 尚未接入。 - -建议运行时流程如下: - -#### `Scene` 运行时启动 - -1. `ScriptEngine::OnRuntimeStart(scene)` -2. 遍历场景中全部激活对象 -3. 找到所有 `ScriptComponent` -4. 为每个组件创建托管 `MonoBehaviour` 实例 -5. 写入原生对象 UUID / 组件 UUID / 基础上下文 -6. 应用序列化字段缓存 -7. 调用 `Awake` -8. 若对象激活且组件启用,调用 `OnEnable` -9. 标记“等待 Start” - -#### 每帧执行 - -- 物理阶段:`FixedUpdate` -- 普通阶段:`Update` -- 后处理阶段:`LateUpdate` -- 对于尚未执行 `Start` 的脚本,在第一次普通帧前先调用 `Start` - -#### 运行时停止 - -1. 对仍处于启用状态的脚本调用 `OnDisable` -2. 对所有脚本调用 `OnDestroy` -3. 清理托管实例表 -4. 清理运行时场景上下文 - -### 9.3 对现有引擎的前置要求 - -为了让脚本生命周期符合预期,现有引擎需要补齐以下地基: - -- `Scene` 更新必须递归整个层级,而不是只更新根对象 -- `Start` 必须具备“一次且仅一次”语义 -- `SetActive` 必须驱动 `OnEnable/OnDisable` -- 运行时场景启动与停止必须显式化 -- 创建对象时不应直接把“编辑态创建”与“运行态 Awake”混为一谈 - -这些工作虽然不都属于脚本模块,但它们是脚本模块的直接运行前提。 - ---- - -## 10. 原生与托管之间的桥接 - -### 10.1 桥接方式 - -第一阶段使用 `InternalCall`: - -- 托管侧通过 `MethodImplOptions.InternalCall` 声明方法 -- 原生侧通过 `mono_add_internal_call` 注册 - -### 10.2 第一阶段最小 API 集 - -第一阶段建议只暴露最小必需 API: - -- `Debug.Log / LogWarning / LogError` -- `Time.deltaTime` -- `GameObject.GetName / SetName` -- `GameObject.GetTransform` -- `Component.GetGameObject` -- `GameObject.HasComponent` -- `GameObject.GetComponent` -- `Transform` 的本地位置 / 旋转 / 缩放 - -这套 API 足够覆盖以下测试与最小演示: - -- 变换脚本 -- 旋转/移动脚本 -- 生命周期日志验证 -- 组件访问验证 - -第一阶段不建议优先暴露: - -- 物理 API -- 渲染 API -- 音频 API -- 输入系统 -- 资源系统 - -原因很简单: - -- 第一阶段以单元测试闭环为主 -- 暴露面越大,绑定维护成本越高 -- 当前这些系统本身仍在演进 - ---- - -## 11. 类发现与实例管理 - -### 11.1 脚本类发现规则 - -用户脚本类应满足以下条件才被视为可挂载脚本: - -- 定义在 `GameScripts.dll` -- 非抽象类 -- 继承 `XCEngine.MonoBehaviour` - -### 11.2 缓存结构 - -原生运行时需要缓存以下信息: - -- 程序集表 -- 脚本类表 -- 方法句柄表 -- 字段元数据表 -- 运行时实例表 - -建议实例表键使用: - -- `GameObjectUUID + ScriptComponentUUID` - -而不是: - -- 内存地址 -- 组件在容器中的索引 -- 自增 ID - -### 11.3 方法缓存 - -每个脚本类应缓存常用生命周期方法句柄: - -- `Awake` -- `OnEnable` -- `Start` -- `FixedUpdate` -- `Update` -- `LateUpdate` -- `OnDisable` -- `OnDestroy` - -这样可以避免每帧按字符串查找方法。 - ---- - -## 12. 单元测试优先策略 - -### 12.1 原则 - -第一阶段脚本模块不依赖 editor,因此验证策略以单元测试和最小运行时测试为主。 - -### 12.2 测试目录建议 - -```text -tests/ -└── Scripting/ - ├── unit/ - │ ├── test_script_runtime.cpp - │ ├── test_script_metadata.cpp - │ ├── test_script_component.cpp - │ ├── test_script_fields.cpp - │ └── CMakeLists.txt - └── managed/ - ├── XCEngine.ScriptCore/ - └── TestScripts/ -``` - -### 12.3 第一阶段必须覆盖的测试 - -#### 运行时初始化 - -- 能初始化脚本运行时 -- 能加载 `ScriptCore` -- 能加载测试脚本程序集 - -#### 类发现 - -- 只发现继承 `MonoBehaviour` 的类 -- 忽略抽象类 -- 忽略普通工具类 - -#### 生命周期 - -- 能创建托管实例 -- 能按顺序触发 `Awake -> OnEnable -> Start -> Update` -- 能在停止时触发 `OnDisable -> OnDestroy` - -#### 组件桥接 - -- 脚本能访问 `GameObject` -- 脚本能访问 `Transform` -- 脚本能访问最小组件 API - -#### 字段系统 - -- 能发现公共字段 -- 能读写字段 -- 字段缓存可回填到托管实例 -- 场景序列化后字段值不丢失 - -#### 多脚本对象 - -- 同一 `GameObject` 上多个 `ScriptComponent` 都可实例化 -- 不同脚本实例之间不会串字段 - -#### UUID 绑定 - -- 运行时复制或反序列化后仍可稳定恢复脚本绑定键 - -### 12.4 第一阶段不要求的测试 - -- editor Inspector UI -- 热重载 -- 编译器输出面板 -- 文件监听 -- 资源拖拽 - ---- - -## 13. 建议的实现顺序 - -### 阶段 A:补运行时地基 - -先补以下基础能力: - -- `GameObject UUID` 序列化 -- 层级递归更新 -- `Start` 一次性语义 -- `SetActive` 对生命周期的影响 -- `Scene` 运行时启动/停止接口 - -### 阶段 B:脚本最小闭环 - -完成: - -- `Scripting` 模块骨架 -- `ScriptComponent` -- `MonoScriptRuntime` -- `XCEngine.ScriptCore.dll` -- `GameScripts.dll` -- 最小 `InternalCall` - -### 阶段 C:字段与序列化 - -完成: - -- 脚本字段元数据 -- 字段缓存 -- `ScriptComponent` 序列化/反序列化 -- 字段相关单元测试 - -### 阶段 D:最小运行时演示 - -在不依赖 editor 的前提下完成: - -- 纯运行时测试场景 -- 一个或两个最小脚本示例 -- Play 级别运行验证 - -### 阶段 E:editor 集成 - -后续再做: - -- 脚本组件 Inspector -- 类选择器 -- 编译按钮 -- 错误输出 -- 重载流程 - ---- - -## 14. 与 editor 的关系 - -第一阶段文档明确规定: - -- C# 脚本模块**不依赖 editor 完整落地** -- editor 相关工作全部后置 -- editor 相关缺口统一记录在 `docs/issues` - -第一阶段唯一允许与 editor 共用的内容是: - -- 场景序列化格式 -- `GameObject UUID` 语义 -- 运行时场景副本的总体设计方向 - -除此之外,不应把脚本模块第一阶段实现建立在 editor 已具备以下能力的假设上: - -- Play/Simulate 控制条 -- Inspector 自定义字段绘制 -- 项目内脚本自动编译 -- 脚本异常面板 - ---- - -## 15. 风险与权衡 - -### 15.1 Mono 依赖 - -风险: - -- Windows 环境需要显式安装或打包 Mono -- CMake 配置与发布路径管理会变复杂 - -权衡: - -- 第一阶段优先解决“能跑起来”的问题 -- 运行时抽象保留未来替换空间 - -### 15.2 生命周期与现有引擎实现的偏差 - -风险: - -- 如果继续保留当前“创建对象就立即 `Awake`”的行为,脚本生命周期会混乱 - -权衡: - -- 脚本系统建设必须带动运行时生命周期整理 - -### 15.3 字段系统范围 - -风险: - -- 一开始就追求完整序列化会让范围失控 - -权衡: - -- 第一阶段只做基础类型与少量引用类型 -- 复杂容器和高级序列化后置 - -### 15.4 editor 后置 - -风险: - -- 第一阶段用户体验不完整 - -权衡: - -- 可以显著降低实现风险 -- 可以先通过单测和运行时样例把底层做稳 - ---- - -## 16. 最终结论 - -XCEngine 的 C# 脚本系统应当采用: - -- `ScriptComponent + MonoBehaviour` -- `ScriptCore + GameScripts` 双程序集结构 -- `InternalCall` 桥接 -- `Mono` 作为第一套运行时实现 -- `UUID` 作为绑定与序列化的稳定身份 -- 第一阶段只做运行时与单元测试,不依赖 editor - -这条路线既保留了 Unity 风格的一致性,也能适配当前工程实际进度。 - -第一阶段的成功标准不是“Inspector 能编辑脚本字段”,而是: - -- 能加载脚本程序集 -- 能发现脚本类 -- 能在场景运行时驱动 `MonoBehaviour` -- 能通过单元测试验证生命周期、字段和绑定语义 - -达到这一点后,再进入 editor 集成阶段,工程风险会显著更低。 diff --git a/docs/used/C#脚本系统_Editor前置缺口.md b/docs/used/C#脚本系统_Editor前置缺口.md deleted file mode 100644 index 261d2cc2..00000000 --- a/docs/used/C#脚本系统_Editor前置缺口.md +++ /dev/null @@ -1,364 +0,0 @@ -# C#脚本系统 Editor 前置缺口 - -## 1. 目的 - -本文档只记录 **C# 脚本系统接入 editor 时需要补齐的 editor 侧缺口**。 - -这些内容 **不是脚本模块第一阶段的实现范围**。 -第一阶段仍以: - -- 运行时闭环 -- 单元测试闭环 -- 最小场景级验证 - -为主。 - -本文档的作用是防止后续把 editor 集成需求误混到第一阶段里。 - ---- - -## 2. 当前策略 - -当前策略明确如下: - -- 脚本系统第一阶段先不依赖 editor -- editor 只作为后续集成目标 -- editor 相关工作拆分为独立 issue - -因此,下面所有条目均属于: - -- 后续阶段工作 -- 或 editor / runtime 边界层工作 - ---- - -## 3. Issue 列表 - -## E-001 Play / Simulate 工作流尚未落地 - -### 现状 - -editor 当前已经定义了相关事件,但完整的 Play / Simulate 切换链路仍未形成统一工作流。 - -### 影响 - -没有这条链路,脚本系统无法在 editor 中以接近 Unity 的方式运行: - -- 无法从编辑态切换到运行态 -- 无法创建运行时场景副本 -- 无法触发脚本运行时启动与停止 - -### 需要补齐 - -- `EditorMode` 状态机 -- `Edit / Play / Simulate` 三态切换 -- 菜单栏或工具栏中的 Play / Stop / Pause 控件 -- 对应的事件派发和恢复流程 - -### 优先级 - -高,但属于 editor 集成阶段,不属于脚本第一阶段 - ---- - -## E-002 editor 场景与 runtime 场景切换控制未封装 - -### 现状 - -当前 scene 管理具备快照能力,但 editor 还没有一套完整的“运行时场景副本控制器”。 - -### 影响 - -没有这一层,脚本一旦接入 editor,会直接面临以下风险: - -- Play 过程污染编辑场景 -- Stop 后无法恢复编辑态 -- 运行时对象状态和 editor 面板状态错乱 - -### 需要补齐 - -- editor 场景与 runtime 场景的明确所有权 -- Play 前复制 -- Stop 后恢复 -- 运行时期间禁用部分编辑操作 -- 选中对象和层级面板的状态同步策略 - -### 优先级 - -高 - ---- - -## E-003 Inspector 缺少 `ScriptComponent` 作者化 UI - -### 现状 - -当前 editor Inspector 还没有脚本组件专用 UI。 - -### 影响 - -即使底层脚本系统完成,用户也无法在 editor 中完成以下基础操作: - -- 添加脚本组件 -- 删除脚本组件 -- 选择脚本类 -- 查看脚本类名 -- 调整脚本组件顺序 - -### 需要补齐 - -- Add Component 菜单中的 `ScriptComponent` -- 已挂载脚本组件列表 -- 脚本类选择器 -- 脚本丢失 / 类不存在 / 程序集未加载的错误态显示 - -### 优先级 - -高 - ---- - -## E-004 Inspector 缺少脚本字段编辑能力 - -### 现状 - -当前 editor 没有基于脚本字段元数据的动态 Inspector。 - -### 影响 - -无法实现 Unity 风格脚本工作流中最关键的一环: - -- 在 Inspector 中编辑脚本字段 - -### 需要补齐 - -- 字段元数据缓存到 editor 层 -- 基础类型字段绘制 -- `Vector2/3/4` 字段绘制 -- `bool` / `string` / 数值类型编辑 -- 字段值回写 `ScriptComponent` 的字段缓存 - -### 优先级 - -高 - ---- - -## E-005 C# 项目与程序集管理缺失 - -### 现状 - -`Project` 面板目前已经有 `Assets/Scripts` 目录概念,但没有正式的 C# 项目管理机制。 - -### 影响 - -用户无法在 editor 中稳定地管理脚本编译输入和输出: - -- 没有 `GameScripts.csproj` -- 没有 `ScriptCore` 引用管理 -- 没有程序集输出路径约定 - -### 需要补齐 - -- 项目初始化时创建脚本工程模板 -- 脚本程序集输出目录规范 -- `ScriptCore` 引用自动配置 -- 脚本工程文件缺失时的恢复策略 - -### 优先级 - -中到高 - ---- - -## E-006 editor 内缺少脚本编译触发入口 - -### 现状 - -当前 editor 没有“编译脚本”入口,也没有编译状态反馈。 - -### 影响 - -后续即使 Inspector 可选择脚本,用户也无法在 editor 中完成闭环: - -- 修改脚本 -- 编译程序集 -- 重新加载类列表 - -### 需要补齐 - -- 手动编译按钮 -- 后续可扩展到自动编译或文件监听 -- 编译状态显示 -- 成功/失败结果通知 - -### 优先级 - -中到高 - ---- - -## E-007 Console 面板缺少脚本编译错误与运行时异常接入 - -### 现状 - -当前 Console 还没有专门的脚本编译/加载/执行错误通路。 - -### 影响 - -脚本系统接入 editor 后,最基本的错误反馈都会缺失: - -- 编译错误 -- 程序集加载失败 -- 类发现失败 -- 运行时异常 -- 栈信息显示 - -### 需要补齐 - -- 编译器 stdout/stderr 采集 -- 程序集加载错误显示 -- 托管异常栈转发到 Console -- 错误级别区分 -- 后续可扩展到点击跳转文件/行号 - -### 优先级 - -高 - ---- - -## E-008 脚本类数据库与类缓存刷新机制缺失 - -### 现状 - -editor 还没有“当前项目有哪些可挂载脚本类”的缓存层。 - -### 影响 - -没有这一层,Inspector 无法稳定实现: - -- 类下拉列表 -- 类是否可挂载的判断 -- 类丢失提示 -- 程序集重载后的刷新 - -### 需要补齐 - -- 脚本程序集扫描结果缓存 -- 全限定类名列表 -- 类状态标记 -- reload 后缓存刷新 - -### 优先级 - -中 - ---- - -## E-009 对象引用字段的 editor 交互控件缺失 - -### 现状 - -后续脚本字段一旦支持 `GameObject` / 组件引用,editor 目前没有相应控件。 - -### 影响 - -对象引用字段虽然不属于脚本第一阶段必需项,但属于后续可用性的关键能力。 - -### 需要补齐 - -- 场景对象选择器 -- 层级面板拖拽赋值 -- 失效引用显示 -- 类型校验 - -### 优先级 - -中 - ---- - -## E-010 脚本程序集重载的 editor UX 缺失 - -### 现状 - -editor 当前没有定义“脚本重载时如何影响当前状态”的用户体验规则。 - -### 影响 - -后续一旦接入脚本编译与重载,会出现一系列状态问题: - -- 当前选中对象是否刷新 -- Inspector 是否保留编辑状态 -- Play 中是否允许重载 -- 重载失败如何回退 - -### 需要补齐 - -- Edit 模式重载策略 -- Play 模式重载策略 -- 失败回退策略 -- 用户提示文案与状态反馈 - -### 优先级 - -中 - ---- - -## 4. 与 editor 集成直接相关的推荐顺序 - -建议的 editor 集成顺序如下: - -1. `E-001` Play / Simulate 工作流 -2. `E-002` runtime scene 切换控制 -3. `E-003` `ScriptComponent` 作者化 UI -4. `E-004` 脚本字段 Inspector -5. `E-007` Console 错误接入 -6. `E-005` C# 项目与程序集管理 -7. `E-006` 脚本编译入口 -8. `E-008` 类数据库刷新 -9. `E-009` 对象引用字段控件 -10. `E-010` 程序集重载 UX - -这个顺序的核心原则是: - -- 先让脚本能在 editor 中“跑起来” -- 再让脚本能在 editor 中“被作者化” -- 最后再补自动化体验与重载体验 - ---- - -## 5. 关联但不归 editor 的前置项 - -以下事项不是 editor issue,但会直接影响后续 editor 集成质量: - -- `GameObject UUID` 持久化 -- 运行时生命周期规范化 -- `Start` 一次性语义 -- `SetActive` 驱动 `OnEnable/OnDisable` -- 场景递归更新 -- `ScriptComponent` 字段缓存与序列化 - -这些工作应当在脚本第一阶段或 editor 集成前置阶段优先完成。 - ---- - -## 6. 结论 - -脚本系统第一阶段不应被 editor 当前进度绑定。 - -正确的推进方式是: - -- 先做运行时与单测闭环 -- 再按本文档逐项补 editor 集成能力 - -这样可以避免脚本模块在早期同时背负: - -- 运行时实现风险 -- editor 工作流风险 -- UI 设计风险 -- 重载体验风险 - -从而把复杂度拆开,逐步落地。 diff --git a/docs/used/D3D12_RHI_Test_Issue.md b/docs/used/D3D12_RHI_Test_Issue.md deleted file mode 100644 index 2dc51a02..00000000 --- a/docs/used/D3D12_RHI_Test_Issue.md +++ /dev/null @@ -1,189 +0,0 @@ -# D3D12 RHI 测试失败问题报告 - -## 问题描述 - -5个D3D12测试在调用 `CreateCommandList` 时失败,错误码 `DXGI_ERROR_NOT_CURRENTLY_AVAILABLE` (0x887A0005) - -## 失败的测试 - -| 测试名称 | 错误位置 | -|---------|---------| -| `CommandList_ClearRenderTarget_WithRealView` | `CreateCommandList` 返回 nullptr | -| `CommandList_ClearDepthStencil_WithRealView` | `CreateTexture` 返回 nullptr (D24_S8_UInt格式) | -| `CommandList_SetRenderTargets_WithRealViews` | `CreateCommandList` 返回 nullptr | -| `CommandList_BeginEndRenderPass_Basic` | `CreateCommandList` 返回 nullptr | -| `CommandList_BeginEndRenderPass_WithClear` | `CreateCommandList` 返回 nullptr | - -## 错误码分析 - -``` -DXGI_ERROR_NOT_CURRENTLY_AVAILABLE (0x887A0005) -``` - -根据 Microsoft 文档,此错误表示: -> "The resource or operation is not available at the current time. This can be returned when a command is submitted to a queue that is not in a state to process that command." - -## 测试代码(最小复现) - -```cpp -TEST_P(RHITestFixture, CommandList_ClearRenderTarget_WithRealView) { - // 1. 创建纹理 - 成功 - TextureDesc texDesc = {}; - texDesc.width = 256; - texDesc.height = 256; - texDesc.format = static_cast(Format::R8G8B8A8_UNorm); - texDesc.textureType = static_cast(TextureType::Texture2D); - RHITexture* texture = GetDevice()->CreateTexture(texDesc); - ASSERT_NE(texture, nullptr); // ✅ 通过 - - // 2. 创建RTV - 成功 - RHIResourceView* rtv = GetDevice()->CreateRenderTargetView(texture, {}); - ASSERT_NE(rtv, nullptr); // ✅ 通过 - - // 3. 创建命令列表 - 失败 - CommandListDesc cmdDesc = {}; - cmdDesc.commandListType = static_cast(CommandQueueType::Direct); - RHICommandList* cmdList = GetDevice()->CreateCommandList(cmdDesc); - ASSERT_NE(cmdList, nullptr); // ❌ 失败!cmdList == nullptr -} -``` - -## 调试日志 - -``` -CreateTexture: start -CreateRenderTargetView: start -CreateCommandList: start - m_commandQueue=00000180F8F722E0 - m_device=00000180F22B5B30 - CreateCommandAllocator hr=887A0005 -CreateCommandList: allocator init failed -``` - -## 关键发现 - -### 1. 通过的测试(关键差异) - -以下测试**通过**,它们不创建纹理或只创建SRV: - -```cpp -// ✅ 通过 - 只创建纹理,不创建RTV -TEST_P(RHITestFixture, CommandList_Reset_Close) { - CommandListDesc cmdDesc = {}; - cmdDesc.commandListType = static_cast(CommandQueueType::Direct); - RHICommandList* cmdList = GetDevice()->CreateCommandList(cmdDesc); - ASSERT_NE(cmdList, nullptr); // ✅ 通过 - cmdList->Shutdown(); - delete cmdList; -} - -// ✅ 通过 - 创建纹理+SRV,但不创建RTV -TEST_P(RHITestFixture, CommandList_TransitionBarrier_WithRealResource) { - RHITexture* texture = GetDevice()->CreateTexture(texDesc); // ✅ - RHIResourceView* srv = GetDevice()->CreateShaderResourceView(texture, {}); // ✅ - RHICommandList* cmdList = GetDevice()->CreateCommandList(cmdDesc); // ✅ 通过 -} -``` - -### 2. 失败的测试(共同点) - -所有失败的测试都**先创建纹理,再创建RTV,然后创建命令列表**。 - -### 3. OpenGL vs D3D12 - -- **OpenGL**: 全部117个测试通过 ✅ -- **D3D12 (RHITestFixture)**: 112/117 通过,5个失败 ❌ -- **D3D12TestFixture**: 全部53个测试通过 ✅ - -## 代码分析 - -### CreateCommandList 实现 - -```cpp -// engine/src/RHI/D3D12/D3D12Device.cpp:409 -RHICommandList* D3D12Device::CreateCommandList(const CommandListDesc& desc) { - auto* allocator = new D3D12CommandAllocator(); - - // 失败发生在这里 - if (!allocator->Initialize(m_device.Get(), static_cast(desc.commandListType))) { - delete allocator; - return nullptr; // 返回 nullptr - } - - auto* cmdList = new D3D12CommandList(); - if (!cmdList->Initialize(m_device.Get(), ...)) { - delete allocator; - delete cmdList; - return nullptr; - } - return cmdList; -} -``` - -### CreateCommandAllocator 实现 - -```cpp -// engine/src/RHI/D3D12/D3D12CommandAllocator.cpp:15 -bool D3D12CommandAllocator::Initialize(ID3D12Device* device, CommandQueueType type) { - m_type = type; - HRESULT hResult = device->CreateCommandAllocator( - ToD3D12(type), // D3D12_COMMAND_LIST_TYPE_DIRECT - IID_PPV_ARGS(&m_commandAllocator)); - return SUCCEEDED(hResult); // 返回 false,hr=887A0005 -} -``` - -### CreateRenderTargetView 实现 - -```cpp -// engine/src/RHI/D3D12/D3D12Device.cpp:455 -RHIResourceView* D3D12Device::CreateRenderTargetView(RHITexture* texture, const ResourceViewDesc& desc) { - auto* view = new D3D12ResourceView(); - auto* d3d12Texture = static_cast(texture); - ID3D12Resource* resource = d3d12Texture->GetResource(); - - D3D12_RENDER_TARGET_VIEW_DESC rtvDesc = {}; - rtvDesc.Format = static_cast(desc.format); // desc.format = 0 (Unknown!) - rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2D; - - // 创建非 shader-visible 的 descriptor heap - DescriptorHeapDesc heapDesc = {}; - heapDesc.descriptorCount = 1; - heapDesc.heapType = static_cast(DescriptorHeapType::RTV); - heapDesc.shaderVisible = false; // 非 shader-visible - auto* heapPool = CreateDescriptorHeap(heapDesc); - - view->InitializeAsRenderTarget(m_device.Get(), resource, &rtvDesc, heap, 0); - return view; -} -``` - -## 已排除的原因 - -1. ❌ **调试层问题** - 禁用后仍然失败 -2. ❌ **命令队列类型不匹配** - 使用相同的 `CommandQueueType::Direct` -3. ❌ **静态全局状态污染** - 每个测试创建独立设备 -4. ❌ **内存泄漏导致descriptor heap耗尽** - 内存问题不会导致此错误码 -5. ❌ **测试顺序依赖** - 每个测试有独立的 SetUp/TearDown - -## 疑点 - -1. 为什么 `CreateCommandAllocator` 在创建 RTV(非shader-visible descriptor heap)之后会失败? -2. D3D12TestFixture(使用相同设备创建方式)为什么全部通过? -3. 为什么 SRV(shader-visible descriptor heap)不会触发这个问题,只有 RTV 会? - -## 相关文件 - -- `engine/src/RHI/D3D12/D3D12Device.cpp` - CreateCommandList, CreateTexture, CreateRenderTargetView -- `engine/src/RHI/D3D12/D3D12CommandAllocator.cpp` - CreateCommandAllocator -- `engine/src/RHI/D3D12/D3D12DescriptorHeap.cpp` - CreateDescriptorHeap -- `engine/src/RHI/D3D12/D3D12ResourceView.cpp` - InitializeAsRenderTarget -- `tests/RHI/unit/test_command_list.cpp` - 失败的测试 -- `tests/RHI/unit/fixtures/RHITestFixture.cpp` - 测试fixture - -## 测试环境 - -- 平台: Windows -- RHI后端: D3D12 -- 测试框架: Google Test -- 编译配置: Debug diff --git a/docs/used/D3D12_Texture_Architecture_Fix_Plan.md b/docs/used/D3D12_Texture_Architecture_Fix_Plan.md deleted file mode 100644 index acd1ebd6..00000000 --- a/docs/used/D3D12_Texture_Architecture_Fix_Plan.md +++ /dev/null @@ -1,403 +0,0 @@ -# D3D12 Texture 封装架构修复方案 - -## 1. 问题分析 - -### 1.1 当前问题 - -**问题 A:双重 Texture 包装导致悬垂指针** - -```cpp -// D3D12SwapChain 内部: -std::vector m_backBuffers; // SwapChain 内部的包装 - -// minimal/main.cpp: -D3D12Texture gColorRTs[2]; // 用户代码又创建了一套包装! - -for (int i = 0; i < 2; i++) { - ID3D12Resource* buffer = nullptr; - gSwapChain.GetSwapChain()->GetBuffer(i, IID_PPV_ARGS(&buffer)); // 获取原生指针 - gColorRTs[i].InitializeFromExisting(buffer); // 再包装一次 -} -``` - -**问题**: -- `m_backBuffers[i]` 和 `gColorRTs[i]` 是两个不同的 `D3D12Texture` 对象 -- 但它们内部都持有指向**同一个** `ID3D12Resource` 的 `ComPtr` -- 当其中一个调用 `Shutdown()` → `m_resource.Reset()`,另一个就变成悬垂指针! - -**问题 B:`InitializeFromExisting` 不区分所有权语义** - -```cpp -void D3D12Texture::Shutdown() { - m_resource.Reset(); // 无条件释放资源 -} -``` - -如果通过 `InitializeFromExisting` 包装了 SwapChain 的 back buffer,Shutdown 会错误地释放它! - -**问题 C:`D3D12SwapChain::GetBackBuffer()` 返回内部引用,用户可能误用** - -```cpp -D3D12Texture* D3D12SwapChain::GetBackBuffer(uint32_t index) const { - return const_cast(&m_backBuffers[index]); // 返回内部成员引用 -} - -// 用户可能写出: -D3D12Texture* rt = gSwapChain.GetBackBuffer(0); -gSwapChain.Shutdown(); // rt 变成悬垂指针! -``` - -### 1.2 问题根因 - -| 问题 | 根因 | -|------|------| -| 双重包装 | 没有阻止用户创建额外的包装 | -| 所有权模糊 | `InitializeFromExisting` 不区分"接管"和"借用" | -| 悬垂引用 | `GetBackBuffer` 返回内部指针,生命周期不安全 | - ---- - -## 2. 修复方案 - -### 2.1 方案概述 - -引入**所有权语义标记**和**生命周期安全保证**: - -1. **`InitializeFromExisting` 增加 `ownsResource` 参数** -2. **`D3D12Texture` 内部根据标记决定是否释放资源** -3. **`D3D12SwapChain::GetBackBuffer` 返回安全引用(弱引用或克隆)** -4. **提供统一的 Back Buffer 访问接口,避免用户直接调用 `GetSwapChain()->GetBuffer()`** - -### 2.2 详细设计 - -#### 2.2.1 修改 `D3D12Texture` - -**文件**: `engine/include/XCEngine/RHI/D3D12/D3D12Texture.h` - -```cpp -class D3D12Texture : public RHITexture { -public: - D3D12Texture(); - ~D3D12Texture() override; - - // 创建并拥有资源 - bool Initialize(ID3D12Device* device, const D3D12_RESOURCE_DESC& desc, - D3D12_RESOURCE_STATES initialState = D3D12_RESOURCE_STATE_COMMON); - - // 包装已有资源,明确所有权语义 - // ownsResource = true : 获取所有权,Shutdown 时释放 - // ownsResource = false : 不获取所有权,Shutdown 时不释放 - bool InitializeFromExisting(ID3D12Resource* resource, bool ownsResource = false); - - bool InitializeFromData(ID3D12Device* device, ID3D12GraphicsCommandList* commandList, - const void* pixelData, uint32_t width, uint32_t height, DXGI_FORMAT format); - bool InitializeDepthStencil(ID3D12Device* device, uint32_t width, uint32_t height, - DXGI_FORMAT format = DXGI_FORMAT_D24_UNORM_S8_UINT); - - void Shutdown() override; - - ID3D12Resource* GetResource() const { return m_resource.Get(); } - D3D12_RESOURCE_DESC GetDesc() const { return m_resource->GetDesc(); } - - // 检查是否拥有资源所有权 - bool OwnsResource() const { return m_ownsResource; } - - // ... 其他现有方法 ... - -private: - ComPtr m_resource; - ResourceStates m_state = ResourceStates::Common; - std::string m_name; - bool m_ownsResource = false; // 新增:所有权标记 -}; -``` - -**文件**: `engine/src/RHI/D3D12/D3D12Texture.cpp` - -```cpp -bool D3D12Texture::InitializeFromExisting(ID3D12Resource* resource, bool ownsResource) { - m_resource = resource; - m_ownsResource = ownsResource; // 明确设置所有权 - return true; -} - -void D3D12Texture::Shutdown() { - if (m_ownsResource) { - // 仅当拥有所有权时才释放 - m_resource.Reset(); - } - // 如果不拥有资源,只是清除引用,不释放底层的 COM 对象 - m_resource.Reset(); // ComPtr::Reset() 只是减少引用计数,不是释放 - // 但要注意:如果不拥有所有权,我们需要保留原始指针以防止意外释放 - // 实际上 ComPtr::Reset() 总是会调用 Release() - // 所以我们需要用不同的策略 -} -``` - -**等等,`ComPtr::Reset()` 总是会调用 Release()。如果我们要实现"不拥有但不释放",需要用原始指针存储。** - -**修正方案**: - -```cpp -class D3D12Texture : public RHITexture { -private: - ComPtr m_resource; // 始终持有 - ID3D12Resource* m_externalResource = nullptr; // 外部资源指针(不拥有) - bool m_ownsResource = false; - -public: - void Shutdown() override { - if (m_ownsResource) { - m_resource.Reset(); // 释放拥有的资源 - } - // 如果是外部资源(不拥有),只是清除引用 - m_externalResource = nullptr; - m_resource.Reset(); // 总是 Reset,因为 m_resource 可能持有原始指针的副本 - } -}; -``` - -**更简洁的方案**:让用户自己决定是否通过 `Initialize` 创建 texture。`InitializeFromExisting` 包装但不拥有,使用者负责保证生命周期。 - ---- - -#### 2.2.2 修改 `D3D12SwapChain` - -**目标**:提供安全的 BackBuffer 访问接口,阻止用户创建重复包装。 - -**文件**: `engine/include/XCEngine/RHI/D3D12/D3D12SwapChain.h` - -```cpp -class D3D12SwapChain : public RHISwapChain { -public: - // ... 现有接口 ... - - // 获取 BackBuffer - 返回引用而非指针,防止悬垂 - // 返回的引用在 SwapChain 存活期间有效 - D3D12Texture& GetBackBuffer(uint32_t index); - const D3D12Texture& GetBackBuffer(uint32_t index) const; - - // 获取当前 BackBuffer - D3D12Texture& GetCurrentBackBuffer(); - const D3D12Texture& GetCurrentBackBuffer() const; - - // 删除不安全的 GetSwapChain() 暴露方法! - // 旧接口:IDXGISwapChain3* GetSwapChain() const { return m_swapChain.Get(); } - // 新策略:如果确实需要原生指针,提供 GetNativeHandle() 但不返回具体类型 - -private: - // 确保 BackBuffer 不能被外部直接修改 - void SetBackBuffer(uint32_t index, D3D12Texture& texture) = delete; -}; -``` - -**修正 `GetBackBuffer` 返回值**: - -```cpp -D3D12Texture& D3D12SwapChain::GetBackBuffer(uint32_t index) { - assert(index < m_backBuffers.size() && "BackBuffer index out of range"); - return m_backBuffers[index]; -} - -const D3D12Texture& D3D12SwapChain::GetBackBuffer(uint32_t index) const { - assert(index < m_backBuffers.size() && "BackBuffer index out of range"); - return m_backBuffers[index]; -} -``` - ---- - -#### 2.2.3 更新 `minimal/main.cpp` - -```cpp -// 修改前(有问题): -for (int i = 0; i < 2; i++) { - ID3D12Resource* buffer = nullptr; - gSwapChain.GetSwapChain()->GetBuffer(i, IID_PPV_ARGS(&buffer)); // ❌ 原生 API - gColorRTs[i].InitializeFromExisting(buffer); - // ... -} - -// 修改后(使用封装): -for (int i = 0; i < 2; i++) { - // 直接使用封装好的 BackBuffer,不再重复包装 - D3D12Texture& backBuffer = gSwapChain.GetBackBuffer(i); - - // RTV 创建 - CPUDescriptorHandle rtvCpuHandle = gRTVHeap.GetCPUDescriptorHandle(i); - D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle = { rtvCpuHandle.ptr }; - gRTVs[i].InitializeAt(device, backBuffer.GetResource(), rtvHandle, nullptr); -} -``` - -**但是**:`gColorRTs[i]` 仍然存在且被用于后续渲染。我们需要决定是: -- 方案 A:直接使用 `gSwapChain.GetBackBuffer(i)` 替代 `gColorRTs[i]` -- 方案 B:让 `gColorRTs[i]` 变成对 `gSwapChain.GetBackBuffer(i)` 的引用 - -**推荐方案 A**:移除 `gColorRTs` 数组,直接使用 `gSwapChain.GetBackBuffer()`。这样避免重复引用。 - ---- - -### 2.3 完整修改清单 - -| 文件 | 修改内容 | -|------|----------| -| `D3D12Texture.h` | 添加 `m_ownsResource` 成员,修改 `InitializeFromExisting` 签名 | -| `D3D12Texture.cpp` | 实现所有权语义,`Shutdown` 根据所有权决定是否释放 | -| `D3D12SwapChain.h` | 修改 `GetBackBuffer` 返回引用,删除 `GetSwapChain()` 暴露 | -| `D3D12SwapChain.cpp` | 实现返回引用的 `GetBackBuffer`,添加断言检查 | -| `minimal/main.cpp` | 使用封装后的 `GetBackBuffer()`,移除原生 API 调用 | -| `TEST_SPEC.md` | 更新架构说明,移除已知限制 7.2 | - ---- - -## 3. 实施步骤 - -### Phase 1: 基础修改 -1. 修改 `D3D12Texture` 添加所有权语义 -2. 修改 `D3D12SwapChain` 的 `GetBackBuffer` 返回引用 -3. 运行单元测试确保基础功能正常 - -### Phase 2: 更新集成测试 -1. 修改 `minimal/main.cpp` 使用新的 `GetBackBuffer()` API -2. 移除 `gColorRTs` 数组(如果可能) -3. 验证截图功能正常 - -### Phase 3: 文档和清理 -1. 更新 `TEST_SPEC.md` -2. 删除 `OpenGL_Test_Restructuring_Plan.md` 中对 D3D12 的过时引用 -3. 提交所有更改 - ---- - -## 4. 风险和注意事项 - -### 4.1 兼容性风险 -- `InitializeFromExisting` 签名变更会影响所有调用方 -- 需要检查所有使用此方法的代码 - -### 4.2 生命周期风险 -- `GetBackBuffer()` 返回的引用在 `Shutdown()` 后无效 -- 用户必须确保在 SwapChain 存活期间使用 - -### 4.3 ComPtr 行为澄清 - -```cpp -// ComPtr::Reset() 调用 Release() -// 如果多个 ComPtr 指向同一资源,Reset 只会减少引用计数 -// 只有最后一个 Reset 才会真正释放 - -ComPtr a = resource; // AddRef -ComPtr b = resource; // AddRef - -a.Reset(); // Release,资源仍未释放(b 还持有) -b.Reset(); // 最后一个 Release,资源被释放 -``` - -**关键点**:如果 `D3D12Texture` 和 `m_backBuffers` 都持有同一个 `ID3D12Resource` 的 `ComPtr`,那么: -- `gColorRTs[i].InitializeFromExisting(buffer)` 会让 `gColorRTs[i].m_resource` 指向 `buffer` -- `m_backBuffers[i].InitializeFromExisting(buffer)` 已经让 `m_backBuffers[i].m_resource` 指向 `buffer` -- 现在有两个 `ComPtr` 指向同一个资源 - -**问题**:这两个 `ComPtr` 是在不同对象中的,它们各自会增加引用计数。但原始的 `GetBuffer()` 返回的 `buffer` 指针被两个 `ComPtr` 接管了。 - -让我重新审视 `D3D12SwapChain::Initialize`: - -```cpp -m_backBuffers.resize(m_bufferCount); -for (uint32_t i = 0; i < m_bufferCount; ++i) { - ID3D12Resource* resource = nullptr; - m_swapChain->GetBuffer(i, IID_PPV_ARGS(&resource)); // resource 的引用计数 = 1 - m_backBuffers[i].InitializeFromExisting(resource); // ComPtr 接管,引用计数 = 2 -} -``` - -**问题**:`resource` 被传给了 `InitializeFromExisting`,ComPtr 构造时会 `AddRef`。但 `resource` 是局部变量,函数结束后 `resource` 局部变量销毁但不影响 COM 对象的引用计数。 - -等等,这里有个问题: - -```cpp -ID3D12Resource* resource = nullptr; -m_swapChain->GetBuffer(i, IID_PPV_ARGS(&resource)); // GetBuffer 返回的指针赋给 resource,引用计数 = 1 -m_backBuffers[i].InitializeFromExisting(resource); // ComPtr 拷贝构造,引用计数 = 2 -// 函数结束,resource 局部变量销毁(不影响引用计数,因为是指针变量) -``` - -所以 `m_backBuffers[i].m_resource` 和 `m_swapChain` 内部都持有同一个 COM 对象的引用。 - -然后 `minimal/main.cpp` 又做了一次: - -```cpp -ID3D12Resource* buffer = nullptr; -gSwapChain.GetSwapChain()->GetBuffer(i, IID_PPV_ARGS(&buffer)); // buffer 引用计数 = 3 -gColorRTs[i].InitializeFromExisting(buffer); // gColorRTs[i].m_resource 引用计数 = 4 -// buffer 局部变量销毁 -``` - -现在有三个 `ComPtr`(`m_backBuffers[i].m_resource`, `gColorRTs[i].m_resource`, `m_swapChain` 内部)指向同一个对象。引用计数 = 4。 - -**这不是问题**!因为 `ComPtr` 的拷贝构造会 `AddRef`。最终: -- `m_backBuffers[i]` 销毁 → 引用计数-- -- `gColorRTs[i]` 销毁 → 引用计数-- -- `m_swapChain` 销毁 → 引用计数-- - -引用计数最终归零,资源被正确释放。 - -**那问题是什么?** - -回到 `Shutdown`: - -```cpp -void D3D12Texture::Shutdown() { - m_resource.Reset(); // ComPtr::Reset() 调用 Release() -} -``` - -如果 `gColorRTs[i]` 先于 `m_swapChain` 销毁: -- `gColorRTs[i].Shutdown()` → `m_resource.Reset()` → Release() → 引用计数从 4 变成 3 -- `m_swapChain` 销毁时 → 内部资源 Release() → 引用计数从 3 变成 2 - -**这也应该是正常的...** - -但等等!`m_backBuffers[i]` 是在 `D3D12SwapChain` 内部的 vector 中: - -```cpp -std::vector m_backBuffers; -``` - -如果 `minimal/main.cpp` 中 `gSwapChain.Shutdown()` 被调用: -- `D3D12SwapChain::~D3D12SwapChain()` 调用 `Shutdown()` -- `Shutdown()` 调用 `m_swapChain.Reset()`(仅重置 swapchain 指针) -- 然后 vector `m_backBuffers` 销毁,每个 `D3D12Texture` 析构调用 `Shutdown()` -- 每个 `Shutdown()` 调用 `m_resource.Reset()` → 释放 back buffer - -**但是**:`gColorRTs[i]` 是在全局变量中独立创建的: - -```cpp -D3D12Texture gColorRTs[2]; // 独立于 SwapChain -``` - -如果 `gSwapChain.Shutdown()` 先执行,`m_backBuffers` 的 `m_resource` 被释放,那么 `gColorRTs[i].m_resource` 就变成悬垂的 `ComPtr`! - -```cpp -// gSwapChain.Shutdown() 执行后: -m_backBuffers[0].m_resource.Reset(); // 资源被释放! -// 但 gColorRTs[0].m_resource 仍然持有同一个(已释放的)指针! -// 调用 gColorRTs[0].GetResource() 会返回已释放的 COM 对象! -``` - -**这就是 bug!** 当 SwapChain 先 shutdown,用户代码中的 `gColorRTs` 就变成悬垂指针。 - ---- - -## 5. 最终结论 - -### 根因 -`minimal/main.cpp` 创建了与 `D3D12SwapChain` 内部 `m_backBuffers` **重复包装**的 `gColorRTs` 数组。当任何一个先销毁,另一个就变成悬垂指针。 - -### 修复方案 -1. **最小改动**:直接使用 `gSwapChain.GetBackBuffer(i)` 而非创建新的 `gColorRTs` -2. **长期方案**:增强 `D3D12Texture` 的所有权语义,区分拥有和非拥有资源 - -### 实施 -按照第 2.3 节的修改清单执行。 \ No newline at end of file diff --git a/docs/used/D3D12后端测试设计.md b/docs/used/D3D12后端测试设计.md deleted file mode 100644 index a680d3ac..00000000 --- a/docs/used/D3D12后端测试设计.md +++ /dev/null @@ -1,1612 +0,0 @@ -# D3D12 后端测试设计 - -## 1. 概述 - -本文档描述 XCEngine D3D12 渲染后端的测试框架设计,旨在为 D3D12 各组件提供全面、规范的自动化测试覆盖。 - -### 1.1 测试目标 - -- 验证 D3D12 各组件 API 的正确性 -- 确保组件间的协作正常工作 -- 捕获资源泄漏和内存错误 -- 支持持续集成(CI)自动化测试 - -### 1.2 现有问题 - -当前 `tests/D3D12/main.cpp` 存在以下问题: - -| 问题 | 说明 | -|------|------| -| 非自动化测试 | 截图和对比需手动触发 | -| 缺乏单元测试 | 只有一个 Win32 图形程序,无法使用 Google Test | -| 维护困难 | GT.ppm 是纯黑色图像,对比无实际意义 | -| 容差问题 | 1% 阈值对硬件差异过于敏感 | - -## 2. 测试目录结构 - -### 2.1 资源目录(保留现有 tests/D3D12) - -现有 `tests/D3D12` 目录保留,作为测试资源文件来源: - -``` -tests/D3D12/ -├── Res/ # 测试资源文件(供新测试框架使用) -│ ├── Shader/ -│ │ ├── test_vs.hlsl # 测试用顶点着色器 -│ │ ├── test_ps.hlsl # 测试用像素着色器 -│ │ ├── test_gs.hlsl # 测试用几何着色器 -│ │ ├── test_cs.hlsl # 测试用计算着色器 -│ │ └── test_pattern.hlsl # 图案化测试着色器 -│ ├── Texture/ -│ │ ├── test_256x256.png # 测试用 2D 纹理 -│ │ └── test_cube.dds # 测试用立方体纹理 -│ └── Golden/ -│ ├── clear_color_gt.ppm # 清除颜色基准图像 -│ ├── pattern_checker_gt.ppm # 棋盘格基准图像 -│ └── gradient_gt.ppm # 渐变基准图像 -├── main.cpp # 现有渲染示例(保留) -├── CMakeLists.txt # 现有构建配置(保留) -└── ... # 其他现有文件(保留) -``` - -### 2.2 新测试框架目录 - -在 `tests/D3D12_engine/test/` 下创建新的测试模块: - -``` -tests/D3D12_engine/test/ # 新测试目录 -├── CMakeLists.txt # 测试构建配置 -├── fixtures/ -│ ├── D3D12TestFixture.h # 基础测试夹具 -│ └── D3D12TestFixture.cpp # 夹具实现 -├── test_device.cpp # D3D12Device 测试 -├── test_command_queue.cpp # D3D12CommandQueue 测试 -├── test_command_allocator.cpp # D3D12CommandAllocator 测试 -├── test_command_list.cpp # D3D12CommandList 测试 -├── test_buffer.cpp # D3D12Buffer 测试 -├── test_texture.cpp # D3D12Texture 测试 -├── test_descriptor_heap.cpp # D3D12DescriptorHeap 测试 -├── test_pipeline_state.cpp # D3D12PipelineState 测试 -├── test_root_signature.cpp # D3D12RootSignature 测试 -├── test_fence.cpp # D3D12Fence 测试 -├── test_shader.cpp # D3D12Shader 测试 -├── test_views.cpp # RTV/DSV/SRV/UAV 测试 -└── test_screenshot.cpp # 截图功能测试 -``` -engine/src/RHI/D3D12/ -├── test/ # 新测试目录 -│ ├── CMakeLists.txt # 测试构建配置 -│ ├── fixtures/ -│ │ ├── D3D12TestFixture.h # 基础测试夹具 -│ │ ├── D3D12ResourceFixture.h # 资源测试夹具 -│ │ └── D3D12LeakCheckFixture.h # # 泄漏检测夹具 -│ ├── test_device.cpp # D3D12Device 测试 -│ ├── test_command_queue.cpp # D3D12CommandQueue 测试 -│ ├── test_command_allocator.cpp # D3D12CommandAllocator 测试 -│ ├── test_command_list.cpp # D3D12CommandList 测试 -│ ├── test_buffer.cpp # D3D12Buffer 测试 -│ ├── test_texture.cpp # D3D12Texture 测试 -│ ├── test_descriptor_heap.cpp # D3D12DescriptorHeap 测试 -│ ├── test_pipeline_state.cpp # D3D12PipelineState 测试 -│ ├── test_root_signature.cpp # D3D12RootSignature 测试 -│ ├── test_fence.cpp # D3D12Fence 测试 -│ ├── test_shader.cpp # D3D12Shader 测试 -│ ├── test_views.cpp # RTV/DSV/SRV/UAV 测试 -│ ├── test_types.cpp # 类型转换测试 -│ └── test_screenshot.cpp # 截图功能测试 -│ -├── D3D12Device.cpp # 现有源文件 -├── D3D12CommandQueue.cpp # 现有源文件 -└── ... # 其他现有源文件 -``` - -### 2.3 构建配置说明 - -新的测试框架通过 CMake 添加到引擎构建中: - -```cmake -# engine/CMakeLists.txt 中添加 -if(BUILD_D3D12_TESTS) - enable_testing() - add_subdirectory(src/RHI/D3D12/test) -endif() -``` - -```cmake -# engine/src/RHI/D3D12/test/CMakeLists.txt -cmake_minimum_required(VERSION 3.15) - -project(D3D12EngineTests) - -set(CMAKE_CXX_STANDARD 17) - -# 查找 Google Test -find_package(GTest REQUIRED) - -# 测试源文件 -set(TEST_SOURCES - test_device.cpp - test_command_queue.cpp - test_buffer.cpp - # ... 其他测试文件 -) - -add_executable(d3d12_engine_tests ${TEST_SOURCES}) - -target_link_libraries(d3d12_engine_tests PRIVATE - d3d12 - dxgi - d3dcompiler - XCEngine - GTest::gtest - GTest::gtest_main -) - -# 设置资源目录路径 -target_compile_definitions(d3d12_engine_tests PRIVATE - TEST_RESOURCES_DIR="${CMAKE_SOURCE_DIR}/../../tests/D3D12/Res" -) - -add_test(NAME D3D12EngineTests COMMAND d3d12_engine_tests) - -## 3. 测试夹具设计 - -### 3.1 基础夹具 - -```cpp -// fixtures/D3D12TestFixture.h -#pragma once - -#include -#include -#include - -using namespace Microsoft::WRL; - -class D3D12TestFixture : public ::testing::Test { -protected: - static void SetUpTestSuite() { - // 创建全局 D3D12 设备(所有测试共享) - HRESULT hr = D3D12CreateDevice( - nullptr, // 默认适配器 - D3D_FEATURE_LEVEL_12_0, // 最低支持特性等级 - IID_PPV_ARGS(&mDevice) - ); - ASSERT_TRUE(SUCCEEDED(hr)) << "Failed to create D3D12 device"; - } - - static void TearDownTestSuite() { - mDevice.Reset(); - } - - void SetUp() override { - // 创建命令队列 - D3D12_COMMAND_QUEUE_DESC queueDesc = {}; - queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; - - HRESULT hr = mDevice->CreateCommandQueue( - &queueDesc, - IID_PPV_ARGS(&mCommandQueue) - ); - ASSERT_TRUE(SUCCEEDED(hr)); - - // 创建命令分配器 - hr = mDevice->CreateCommandAllocator( - D3D12_COMMAND_LIST_TYPE_DIRECT, - IID_PPV_ARGS(&mCommandAllocator) - ); - ASSERT_TRUE(SUCCEEDED(hr)); - - // 创建命令列表 - hr = mDevice->CreateCommandList( - 0, - D3D12_COMMAND_LIST_TYPE_DIRECT, - mCommandAllocator.Get(), - nullptr, - IID_PPV_ARGS(&mCommandList) - ); - ASSERT_TRUE(SUCCEEDED(hr)); - } - - void TearDown() override { - // 等待所有命令执行完成 - WaitForGPU(); - - mCommandList.Reset(); - mCommandAllocator.Reset(); - mCommandQueue.Reset(); - } - - // 常用辅助方法 - ID3D12Device* GetDevice() { return mDevice.Get(); } - ID3D12CommandQueue* GetCommandQueue() { return mCommandQueue.Get(); } - ID3D12CommandList* GetCommandList() { return mCommandList.Get(); } - - void WaitForGPU() { - ComPtr fence; - UINT64 fenceValue = 1; - - HRESULT hr = mDevice->CreateFence( - 0, - D3D12_FENCE_FLAG_NONE, - IID_PPV_ARGS(&fence) - ); - if (SUCCEEDED(hr)) { - mCommandQueue->Signal(fence.Get(), fenceValue); - HANDLE eventHandle = CreateEvent(nullptr, FALSE, FALSE, nullptr); - fence->SetEventOnCompletion(fenceValue, eventHandle); - WaitForSingleObject(eventHandle, INFINITE); - CloseHandle(eventHandle); - } - } - -private: - static ComPtr mDevice; - ComPtr mCommandQueue; - ComPtr mCommandAllocator; - ComPtr mCommandList; -}; - -// 静态成员定义 -ComPtr D3D12TestFixture::mDevice; -``` - -### 3.2 资源测试夹具 - -```cpp -// fixtures/D3D12ResourceFixture.h -#pragma once - -#include "D3D12TestFixture.h" - -class D3D12ResourceFixture : public D3D12TestFixture { -protected: - void CreateUploadBuffer(size_t size, ID3D12Resource** outResource) { - CD3DX12_HEAP_PROPERTIES heapProps(D3D12_HEAP_TYPE_UPLOAD); - CD3DX12_RESOURCE_DESC bufferDesc = CD3DX12_RESOURCE_DESC::Buffer(size); - - HRESULT hr = GetDevice()->CreateCommittedResource( - &heapProps, - D3D12_HEAP_FLAG_NONE, - &bufferDesc, - D3D12_RESOURCE_STATE_GENERIC_READ, - nullptr, - IID_PPV_ARGS(outResource) - ); - ASSERT_TRUE(SUCCEEDED(hr)); - } - - void CreateDefaultBuffer(size_t size, ID3D12Resource** outResource) { - CD3DX12_HEAP_PROPERTIES heapProps(D3D12_HEAP_TYPE_DEFAULT); - CD3DX12_RESOURCE_DESC bufferDesc = CD3DX12_RESOURCE_DESC::Buffer(size); - - HRESULT hr = GetDevice()->CreateCommittedResource( - &heapProps, - D3D12_HEAP_FLAG_NONE, - &bufferDesc, - D3D12_RESOURCE_STATE_COPY_DEST, - nullptr, - IID_PPV_ARGS(outResource) - ); - ASSERT_TRUE(SUCCEEDED(hr)); - } -}; -``` - -## 4. 测试分类 - -### 4.1 单元测试 - -测试单一 API 的基本功能,不依赖图形硬件渲染结果。 - -```cpp -TEST(D3D12_Buffer, Create_ValidSize_ReturnsSuccess) { - const size_t bufferSize = 1024; - - D3D12Buffer buffer; - bool result = buffer.Initialize(GetDevice(), bufferSize, - D3D12_RESOURCE_STATE_GENERIC_READ, - D3D12_HEAP_TYPE_UPLOAD); - - ASSERT_TRUE(result); - ASSERT_NE(buffer.GetResource(), nullptr); - ASSERT_EQ(buffer.GetResource()->GetDesc().Width, bufferSize); -} - -TEST(D3D12_Buffer, Map_ValidRange_ReturnsValidPointer) { - D3D12Buffer buffer; - buffer.Initialize(GetDevice(), 256, D3D12_RESOURCE_STATE_GENERIC_READ, D3D12_HEAP_TYPE_UPLOAD); - - void* mappedData = buffer.Map(0, nullptr); - ASSERT_NE(mappedData, nullptr); - - // 写入测试数据 - memset(mappedData, 0xAB, 256); - buffer.Unmap(0, nullptr); -} - -TEST(D3D12_DescriptorHeap, Create_RTVHeap_ReturnsValidHeap) { - D3D12DescriptorHeap heap; - bool result = heap.Initialize(GetDevice(), D3D12_DESCRIPTOR_HEAP_TYPE_RTV, 4); - - ASSERT_TRUE(result); - ASSERT_NE(heap.GetDescriptorHeap(), nullptr); -} -``` - -### 4.2 集成测试 - -测试多个组件的协作,例如 Buffer 数据上传到 GPU。 - -```cpp -TEST(D3D12_Buffer, UploadToGPU_DataIntegrity) { - const size_t dataSize = sizeof(float) * 4; - float testData[] = { 1.0f, 2.0f, 3.0f, 4.0f }; - - // 创建默认缓冲(GPU 只读) - D3D12Buffer gpuBuffer; - gpuBuffer.Initialize(GetDevice(), dataSize, D3D12_RESOURCE_STATE_COPY_DEST, D3D12_HEAP_TYPE_DEFAULT); - - // 创建上传缓冲(CPU 可写) - D3D12Buffer uploadBuffer; - uploadBuffer.Initialize(GetDevice(), dataSize, D3D12_RESOURCE_STATE_GENERIC_READ, D3D12_HEAP_TYPE_UPLOAD); - - // 复制数据到上传缓冲 - void* mappedData = uploadBuffer.Map(0, nullptr); - memcpy(mappedData, testData, dataSize); - uploadBuffer.Unmap(0, nullptr); - - // 通过命令列表复制 - GetCommandList()->CopyBufferRegion( - gpuBuffer.GetResource(), 0, - uploadBuffer.GetResource(), 0, - dataSize - ); - - // 执行并等待 - GetCommandList()->Close(); - ID3D12CommandList* cmdLists[] = { GetCommandList() }; - GetCommandQueue()->ExecuteCommandLists(1, cmdLists); - WaitForGPU(); - - // 验证:使用映射读取返回的数据 - // (需要转换状态到 GENERIC_READ) -} -``` - -### 4.3 渲染结果测试 - -渲染到纹理并验证像素数据,而非渲染到屏幕。 - -#### 4.3.1 测试策略 - -| 方案 | 优点 | 缺点 | -|------|------|------| -| 渲染到 RTT | 无窗口依赖,可 CI | 需要 Mipmap 比较算法 | -| Shader 输出测试值 | 精确验证 | 需要特殊 Shader | -| Golden Image 对比 | 直观 | 维护成本高 | - -#### 4.3.2 推荐方案:渲染特定图案 - -生成一个已知的测试图案(如渐变、棋盘格),渲染后读取像素验证: - -```cpp -TEST(D3D12_RenderTarget, ClearColor_VerifyPixelValue) { - // 创建渲染目标纹理 - const uint32_t width = 64; - const uint32_t height = 64; - - D3D12Texture renderTarget; - renderTarget.InitializeAsRenderTarget( - GetDevice(), width, height, - DXGI_FORMAT_R8G8B8A8_UNORM - ); - - // 创建 RTV - D3D12DescriptorHeap rtvHeap; - rtvHeap.Initialize(GetDevice(), D3D12_DESCRIPTOR_HEAP_TYPE_RTV, 1); - - D3D12RenderTargetView rtv; - rtv.InitializeAt( - GetDevice(), - renderTarget.GetResource(), - rtvHeap.GetCPUDescriptorHandleForHeapStart(), - nullptr - ); - - // 清空为特定颜色 - float clearColor[] = { 0.25f, 0.5f, 0.75f, 1.0f }; // R=64, G=128, B=192 - GetCommandList()->ClearRenderTargetView( - rtvHeap.GetCPUDescriptorHandleForHeapStart(), - clearColor, 0, nullptr - ); - - // 转换状态用于读取 - GetCommandList()->TransitionBarrier( - renderTarget.GetResource(), - D3D12_RESOURCE_STATE_RENDER_TARGET, - D3D12_RESOURCE_STATE_COPY_SOURCE - ); - - GetCommandList()->Close(); - ID3D12CommandList* cmdLists[] = { GetCommandList() }; - GetCommandQueue()->ExecuteCommandLists(1, cmdLists); - WaitForGPU(); - - // 读取像素数据 - std::vector pixels(width * height * 4); - ReadBackTexture(GetDevice(), GetCommandQueue(), - renderTarget.GetResource(), pixels.data(), pixels.size()); - - // 验证中心像素 - uint32_t centerIndex = (height / 2 * width + width / 2) * 4; - EXPECT_NEAR(pixels[centerIndex + 0], 64, 2); // R - EXPECT_NEAR(pixels[centerIndex + 1], 128, 2); // G - EXPECT_NEAR(pixels[centerIndex + 2], 192, 2); // B -} -``` - -#### 4.3.3 图案化渲染测试 - -使用纯色 Shader 渲染特定图案: - -```hlsl -// TestPattern.hlsl - 输出棋盘格图案 -float4 PSMain(PSInput input) : SV_Target { - uint2 pixelPos = uint2(input.Position.xy); - bool isWhite = ((pixelPos.x / 8) % 2) == ((pixelPos.y / 8) % 2); - return isWhite ? float4(1,1,1,1) : float4(0,0,0,1); -} -``` - -### 4.4 性能测试 - -使用 Google Benchmark 或简单计时: - -```cpp -TEST(D3D12_Performance, BufferCreation_1000Times) { - const int iterations = 1000; - - auto start = std::chrono::high_resolution_clock::now(); - - for (int i = 0; i < iterations; ++i) { - D3D12Buffer buffer; - buffer.Initialize(GetDevice(), 1024, - D3D12_RESOURCE_STATE_GENERIC_READ, - D3D12_HEAP_TYPE_UPLOAD); - } - - auto end = std::chrono::high_resolution_clock::now(); - auto duration = std::chrono::duration_cast(end - start); - - printf("Created %d buffers in %ld ms\n", iterations, duration.count()); -} -``` - -## 5. 组件测试详情 - -### 5.1 D3D12Device - -```cpp -TEST(D3D12_Device, CheckFeatureSupport_D3D12OK) { - D3D12_FEATURE_DATA_FEATURE_LEVELS featureLevels = {}; - featureLevels.NumFeatureLevels = 1; - featureLevels.pFeatureLevelsRequested = &mFeatureLevel; - - HRESULT hr = GetDevice()->CheckFeatureSupport( - D3D12_FEATURE_FEATURE_LEVELS, - &featureLevels, - sizeof(featureLevels) - ); - - ASSERT_TRUE(SUCCEEDED(hr)); - ASSERT_EQ(featureLevels.MaxSupportedFeatureLevel, D3D_FEATURE_LEVEL_12_0); -} -``` - -### 5.2 D3D12CommandList - -```cpp -TEST(D3D12_CommandList, Reset_AfterClose_Succeeds) { - // 第一次使用 - GetCommandList()->Close(); - - // 重置并再次使用 - HRESULT hr = GetCommandList()->Reset(mCommandAllocator.Get(), nullptr); - ASSERT_TRUE(SUCCEEDED(hr)); -} -``` - -### 5.3 D3D12Fence - -```cpp -TEST(D3D12_Fence, SignalAndWait) { - ComPtr fence; - UINT64 fenceValue = 1; - - HRESULT hr = GetDevice()->CreateFence( - 0, D3D12_FENCE_FLAG_NONE, - IID_PPV_ARGS(&fence) - ); - ASSERT_TRUE(SUCCEEDED(hr)); - - // Signal - GetCommandQueue()->Signal(fence.Get(), fenceValue); - - // 等待 - ASSERT_EQ(fence->GetCompletedValue(), fenceValue); -} -``` - -## 6. 资源泄漏检测 - -### 6.1 使用 D3D12 Debug Layer - -```cpp -#ifdef _DEBUG -class D3D12LeakDetector { -public: - static void BeginFrame() { - if (sDebugDevice) { - sDebugDevice->SetName(L"Leak Detection Frame Start"); - } - } - - static void EndFrame() { - if (sDebugDevice) { - sDebugDevice->SetName(L"Leak Detection Frame End"); - sDebugDevice->ReportLiveDeviceObjects( - D3D12_RDO_FLAGS::D3D12_RDO_FLAG_NONE - ); - } - } - - static void SetDebugDevice(ID3D12DebugDevice* device) { - sDebugDevice = device; - } - -private: - static ID3D12DebugDevice* sDebugDevice; -}; -#endif -``` - -### 6.2 在测试夹具中使用 - -```cpp -class D3D12LeakCheckFixture : public D3D12TestFixture { -protected: - void TearDown() override { - D3D12TestFixture::TearDown(); - -#ifdef _DEBUG - D3D12LeakDetector::EndFrame(); -#endif - } -}; -``` - -## 7. 构建配置 - -### 7.1 CMakeLists.txt 修改 - -```cmake -cmake_minimum_required(VERSION 3.15) - -project(D3D12Tests) - -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_STANDARD_REQUIRED ON) - -# Google Test -include(FetchContent) -FetchContent_Declare( - googletest - GIT_REPOSITORY https://gitee.com/mirrors/googletest.git - GIT_TAG v1.14.0 -) -set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) -FetchContent_MakeAvailable(googletest) - -enable_testing() - -# 测试源文件 -set(TEST_SOURCES - test_device.cpp - test_command_queue.cpp - test_command_allocator.cpp - test_command_list.cpp - test_buffer.cpp - test_texture.cpp - test_descriptor_heap.cpp - test_pipeline_state.cpp - test_root_signature.cpp - test_fence.cpp - test_shader.cpp - test_views.cpp -) - -add_executable(d3d12_tests ${TEST_SOURCES}) - -target_link_libraries(d3d12_tests PRIVATE - d3d12 - dxgi - d3dcompiler - XCEngine - GTest::gtest - GTest::gtest_main -) - -target_include_directories(d3d12_tests PRIVATE - ${CMAKE_SOURCE_DIR}/engine/include - ${CMAKE_CURRENT_SOURCE_DIR}/fixtures -) - -add_test(NAME D3D12Tests COMMAND d3d12_tests) -``` - -## 8. CI 集成 - -### 8.1 Windows CI 配置示例 - -```yaml -# .github/workflows/d3d12-tests.yml -name: D3D12 Tests - -on: [push, pull_request] - -jobs: - test: - runs-on: windows-latest - - steps: - - uses: actions/checkout@v3 - - - name: Configure - run: cmake -B build -S . -G "Visual Studio 17 2022" - - - name: Build - run: cmake --build build --config Debug - - - name: Run Tests - run: ctest --test-dir build -C Debug --output-on-failure -``` - -## 9. 测试覆盖矩阵 - -| 组件 | 单元测试 | 集成测试 | 渲染测试 | 性能测试 | -|------|:--------:|:--------:|:--------:|:--------:| -| D3D12Device | ✓ | - | - | ✓ | -| D3D12CommandQueue | ✓ | ✓ | - | ✓ | -| D3D12CommandAllocator | ✓ | - | - | - | -| D3D12CommandList | ✓ | ✓ | - | - | -| D3D12Buffer | ✓ | ✓ | - | ✓ | -| D3D12Texture | ✓ | ✓ | ✓ | ✓ | -| D3D12DescriptorHeap | ✓ | - | - | - | -| D3D12PipelineState | ✓ | - | - | - | -| D3D12RootSignature | ✓ | - | - | - | -| D3D12Fence | ✓ | ✓ | - | - | -| D3D12SwapChain | - | - | ✓ | - | -| D3D12Shader | ✓ | - | - | - | -| RTV/DSV/SRV/UAV | ✓ | ✓ | ✓ | - | - -## 10. 组件详细测试用例设计 - -基于对所有 D3D12 组件源代码的深入分析,以下是每个组件的具体可测试 API 点和测试用例设计。 - -### 10.1 D3D12Device 测试 - -**文件**: `test_device.cpp` - -**可测试 API 点**: - -| 测试类别 | 测试用例 | 验证内容 | -|----------|----------|----------| -| 初始化 | `Initialize_WithDebugLayer` | 启用 Debug Layer 后设备创建成功 | -| 初始化 | `Initialize_WithoutDebugLayer` | 不启用 Debug Layer 正常创建 | -| 初始化 | `Initialize_InvalidAdapter` | 无效适配器返回失败 | -| 适配器 | `EnumerateAdapters_ReturnsValidList` | 返回至少一个适配器 | -| 适配器 | `EnumerateAdapters_ContainsRequiredInfo` | 适配器信息包含 Name/VRAM | -| 特性查询 | `CheckFeatureSupport_FeatureLevels` | 查询支持的特性等级 | -| 特性查询 | `CheckFeatureSupport_D3D12Options` | 查询 D3D12 选项支持 | -| 描述符 | `GetDescriptorHandleIncrementSize_RTV` | RTV 描述符增量大小正确 | -| 描述符 | `GetDescriptorHandleIncrementSize_DSV` | DSV 描述符增量大小正确 | -| 描述符 | `GetDescriptorHandleIncrementSize_CBV_SRV_UAV` | SRV/CBV/UAV 增量正确 | -| 工厂方法 | `CreateCommandQueue_ReturnsValidObject` | 创建命令队列成功 | -| 工厂方法 | `CreateBuffer_ReturnsValidObject` | 创建 Buffer 成功 | -| 工厂方法 | `CreateTexture_ReturnsValidObject` | 创建 Texture 成功 | -| 工厂方法 | `CreateDescriptorHeap_ReturnsValidObject` | 创建描述符堆成功 | -| 工厂方法 | `CreatePipelineState_ReturnsValidObject` | 创建 PSO 成功 | -| 工厂方法 | `CreateRootSignature_ReturnsValidObject` | 创建根签名成功 | -| 设备状态 | `SetDeviceRemoved_MarksAsRemoved` | 设备移除标记正确 | -| 设备状态 | `IsDeviceRemoved_ReturnsCorrectState` | 移除状态查询正确 | - -**测试代码示例**: - -```cpp -TEST(D3D12Device, Initialize_WithDebugLayer_Success) { - D3D12Device device; - bool result = device.Initialize(true); - - ASSERT_TRUE(result); - ASSERT_NE(device.GetDevice(), nullptr); - - // 验证 Debug Layer 已启用 - ComPtr debugDevice; - if (SUCCEEDED(device.GetDevice()->QueryInterface(IID_PPV_ARGS(&debugDevice)))) { - ASSERT_NE(debugDevice.Get(), nullptr); - } - - device.Shutdown(); -} - -TEST(D3D12Device, EnumerateAdapters_ContainsValidInfo) { - D3D12Device device; - device.Initialize(); - - auto adapters = device.EnumerateAdapters(); - - ASSERT_FALSE(adapters.empty()); - for (const auto& info : adapters) { - EXPECT_FALSE(info.name.empty()); - EXPECT_GE(info.vram, 0); - } -} - -TEST(D3D12Device, CreateBuffer_FactoryMethod) { - D3D12Device device; - device.Initialize(); - - BufferDesc desc; - desc.size = 1024; - desc.usage = ResourceUsage::VertexBuffer; - - auto* buffer = device.CreateBuffer(desc); - ASSERT_NE(buffer, nullptr); - ASSERT_EQ(buffer->GetSize(), 1024); -} -``` - ---- - -### 10.2 D3D12CommandQueue 测试 - -**文件**: `test_command_queue.cpp` - -**可测试 API 点**: - -| 测试类别 | 测试用例 | 验证内容 | -|----------|----------|----------| -| 初始化 | `Initialize_DirectQueue` | Direct 类型队列创建成功 | -| 初始化 | `Initialize_ComputeQueue` | Compute 类型队列创建成功 | -| 初始化 | `Initialize_CopyQueue` | Copy 类型队列创建成功 | -| 同步 | `Signal_SetsFenceValue` | Signal 设置围栏值 | -| 同步 | `Wait_BlocksUntilSignal` | Wait 阻塞直到信号 | -| 同步 | `SignalAndWait_Completes` | 信号后等待完成 | -| 同步 | `WaitForIdle_BlocksAllWork` | 等待空闲完成所有工作 | -| 查询 | `GetTimestampFrequency_ReturnsValid` | 时间戳频率有效 | -| 查询 | `GetType_ReturnsCorrect` | 队列类型正确 | - -**测试代码示例**: - -```cpp -TEST(D3D12CommandQueue, Signal_SetsFenceValue) { - D3D12Fence fence; - fence.Initialize(GetDevice(), 0); - - GetCommandQueue()->Signal(fence.GetFence(), 1); - - // 需要等待完成 - fence.Wait(1); - EXPECT_EQ(fence.GetCompletedValue(), 1); -} - -TEST(D3D12CommandQueue, WaitForIdle_BlocksAllWork) { - // 先执行一些命令 - D3D12Buffer buffer; - buffer.Initialize(GetDevice(), 256, D3D12_RESOURCE_STATE_GENERIC_READ, D3D12_HEAP_TYPE_UPLOAD); - - GetCommandList()->Close(); - ID3D12CommandList* lists[] = { GetCommandList() }; - GetCommandQueue()->ExecuteCommandLists(1, lists); - - // 等待空闲 - GetCommandQueue()->WaitForIdle(); - - // 验证完成 - EXPECT_TRUE(true); // 如果没崩溃说明成功 -} -``` - ---- - -### 10.3 D3D12CommandAllocator 测试 - -**文件**: `test_command_allocator.cpp` - -**可测试 API 点**: - -| 测试类别 | 测试用例 | 验证内容 | -|----------|----------|----------| -| 初始化 | `Initialize_DirectType` | Direct 类型创建成功 | -| 初始化 | `Initialize_ComputeType` | Compute 类型创建成功 | -| 初始化 | `Initialize_CopyType` | Copy 类型创建成功 | -| 重置 | `Reset_AfterCommandList` | 命令列表执行后重置成功 | -| 状态 | `IsReady_BeforeAndAfterReset` | 重置前后状态正确 | - -**测试代码示例**: - -```cpp -TEST(D3D12CommandAllocator, Reset_AfterCommandListExecution) { - D3D12CommandAllocator allocator; - allocator.Initialize(GetDevice(), CommandQueueType::Direct); - - // 执行命令列表 - GetCommandList()->Close(); - ID3D12CommandList* lists[] = { GetCommandList() }; - GetCommandQueue()->ExecuteCommandLists(1, lists); - WaitForGPU(); - - // 重置分配器 - allocator.Reset(); - - // 验证可以创建新的命令列表 - ComPtr newList; - HRESULT hr = GetDevice()->CreateCommandList( - 0, D3D12_COMMAND_LIST_TYPE_DIRECT, - allocator.GetCommandAllocator(), nullptr, - IID_PPV_ARGS(&newList) - ); - ASSERT_TRUE(SUCCEEDED(hr)); -} -``` - ---- - -### 10.4 D3D12CommandList 测试 - -**文件**: `test_command_list.cpp` - -**可测试 API 点**: - -| 测试类别 | 测试用例 | 验证内容 | -|----------|----------|----------| -| 生命周期 | `Initialize_CreatesValidList` | 创建成功 | -| 生命周期 | `Reset_ReusesAllocator` | 重置成功 | -| 生命周期 | `Close_BeforeExecute` | 关闭后可以执行 | -| 资源屏障 | `TransitionBarrier_ValidStates` | 状态转换正确 | -| 资源屏障 | `UAVBarrier_CreatesBarrier` | UAV 屏障创建 | -| 资源屏障 | `AliasBarrier_CreatesBarrier` | 别名屏障创建 | -| 状态设置 | `SetViewport_SetsCorrectValues` | 视口设置正确 | -| 状态设置 | `SetScissorRect_SetsCorrectValues` | 裁剪矩形设置正确 | -| 状态设置 | `SetPrimitiveTopology_TriangleList` | 图元拓扑设置 | -| 绘制 | `Draw_ValidParameters` | 绘制调用参数正确 | -| 绘制 | `DrawIndexed_ValidParameters` | 索引绘制调用正确 | -| 清除 | `ClearRenderTargetView_ClearsColor` | 清除颜色正确 | -| 清除 | `ClearDepthStencilView_ClearsDepth` | 清除深度模板正确 | -| 复制 | `CopyBuffer_TransfersData` | Buffer 复制正确 | -| 复制 | `CopyTexture_TransfersData` | Texture 复制正确 | -| 查询 | `BeginQuery_StartsQuery` | 查询开始 | -| 查询 | `EndQuery_EndsQuery` | 查询结束 | -| 查询 | `ResolveQueryData_CopiesResults` | 查询数据解析 | - -**测试代码示例**: - -```cpp -TEST(D3D12CommandList, TransitionBarrier_ValidStates) { - D3D12Buffer buffer; - buffer.Initialize(GetDevice(), 1024, D3D12_RESOURCE_STATE_GENERIC_READ, D3D12_HEAP_TYPE_UPLOAD); - - // 从通用读取状态转换到渲染目标状态 - GetCommandList()->TransitionBarrier( - buffer.GetResource(), - ResourceStates::GenericRead, - ResourceStates::RenderTarget - ); - - GetCommandList()->Close(); - - // 执行命令 - ID3D12CommandList* lists[] = { GetCommandList() }; - GetCommandQueue()->ExecuteCommandLists(1, lists); - WaitForGPU(); - - // 成功执行无崩溃 - SUCCEED(); -} - -TEST(D3D12CommandList, ClearRenderTargetView_VerifyColor) { - // 创建渲染目标 - D3D12Texture rt; - D3D12DescriptorHeap heap; - heap.Initialize(GetDevice(), DescriptorHeapType::RTV, 1); - - // 简化测试:创建 RTV - // ... - - float clearColor[] = { 0.25f, 0.5f, 0.75f, 1.0f }; - GetCommandList()->ClearRenderTargetView( - heap.GetCPUDescriptorHandleForHeapStart(), - clearColor, 0, nullptr - ); - - GetCommandList()->Close(); - ID3D12CommandList* lists[] = { GetCommandList() }; - GetCommandQueue()->ExecuteCommandLists(1, lists); - WaitForGPU(); - - SUCCEED(); -} -``` - ---- - -### 10.5 D3D12Buffer 测试 - -**文件**: `test_buffer.cpp` - -**可测试 API 点**: - -| 测试类别 | 测试用例 | 验证内容 | -|----------|----------|----------| -| 初始化 | `Initialize_DefaultHeap` | 默认堆创建成功 | -| 初始化 | `Initialize_UploadHeap` | 上传堆创建成功 | -| 初始化 | `Initialize_ReadbackHeap` | 回读堆创建成功 | -| 初始化 | `InitializeFromExisting_WrapsResource` | 包装现有资源 | -| 初始化 | `InitializeWithData_CopiesData` | 带数据初始化复制数据 | -| 数据操作 | `Map_ReturnsValidPointer` | Map 返回有效指针 | -| 数据操作 | `Unmap_ReleasesPointer` | Unmap 释放指针 | -| 数据操作 | `UpdateData_ModifiesContent` | 更新数据正确 | -| 属性 | `GetGPUVirtualAddress_ReturnsValid` | GPU 虚拟地址有效 | -| 属性 | `GetDesc_ReturnsCorrectDesc` | 描述符正确 | -| 属性 | `GetSize_ReturnsCorrectSize` | 大小正确 | -| 状态 | `GetState_ReturnsCurrent` | 状态查询正确 | -| 状态 | `SetState_ChangesState` | 状态设置正确 | - -**测试代码示例**: - -```cpp -TEST(D3D12Buffer, Initialize_UploadHeap_MapsSuccessfully) { - D3D12Buffer buffer; - bool result = buffer.Initialize(GetDevice(), 1024, - D3D12_RESOURCE_STATE_GENERIC_READ, - D3D12_HEAP_TYPE_UPLOAD); - - ASSERT_TRUE(result); - - // Map 操作 - void* data = buffer.Map(0, nullptr); - ASSERT_NE(data, nullptr); - - // 写入测试数据 - memset(data, 0xAB, 1024); - buffer.Unmap(0, nullptr); - - // 再次 Map 验证 - data = buffer.Map(0, nullptr); - ASSERT_NE(data, nullptr); - - // 验证数据 - uint8_t* byteData = static_cast(data); - for (int i = 0; i < 1024; ++i) { - EXPECT_EQ(byteData[i], 0xAB); - } - - buffer.Unmap(0, nullptr); -} - -TEST(D3D12Buffer, InitializeWithData_DataIntegrity) { - const size_t dataSize = 256; - std::vector testData(dataSize); - for (size_t i = 0; i < dataSize; ++i) { - testData[i] = static_cast(i & 0xFF); - } - - D3D12Buffer buffer; - bool result = buffer.InitializeWithData( - GetDevice(), GetCommandList(), - testData.data(), dataSize, - ResourceStates::VertexAndConstantBuffer - ); - - ASSERT_TRUE(result); - - WaitForGPU(); - - // 验证 GPU 地址有效 - EXPECT_NE(buffer.GetGPUVirtualAddress(), 0); -} -``` - ---- - -### 10.6 D3D12Texture 测试 - -**文件**: `test_texture.cpp` - -**可测试 API 点**: - -| 测试类别 | 测试用例 | 验证内容 | -|----------|----------|----------| -| 初始化 | `Initialize_2DTexture` | 2D 纹理创建成功 | -| 初始化 | `Initialize_3DTexture` | 3D 纹理创建成功 | -| 初始化 | `Initialize_CubeTexture` | 立方体纹理创建成功 | -| 初始化 | `Initialize_WithMipLevels` | 多 Mip 级别创建 | -| 初始化 | `Initialize_DepthStencil` | 深度模板纹理创建 | -| 初始化 | `InitializeFromData_CopiesPixels` | 带像素数据初始化 | -| 属性 | `GetWidth_ReturnsCorrect` | 宽度正确 | -| 属性 | `GetHeight_ReturnsCorrect` | 高度正确 | -| 属性 | `GetDepth_ReturnsCorrect` | 深度正确 | -| 属性 | `GetMipLevels_ReturnsCorrect` | Mip 级别数正确 | -| 属性 | `GetFormat_ReturnsCorrect` | 格式正确 | -| 状态 | `GetState_ReturnsCurrent` | 状态查询正确 | -| 状态 | `SetState_ChangesState` | 状态设置正确 | - -**测试代码示例**: - -```cpp -TEST(D3D12Texture, Initialize_2DTexture_CorrectDimensions) { - D3D12Texture texture; - - D3D12_RESOURCE_DESC desc = CD3DX12_RESOURCE_DESC::Tex2D( - DXGI_FORMAT_R8G8B8A8_UNORM, 512, 512 - ); - - bool result = texture.Initialize(GetDevice(), desc, D3D12_RESOURCE_STATE_COMMON); - - ASSERT_TRUE(result); - EXPECT_EQ(texture.GetWidth(), 512); - EXPECT_EQ(texture.GetHeight(), 512); - EXPECT_EQ(texture.GetDepth(), 1); - EXPECT_EQ(texture.GetMipLevels(), 1); -} - -TEST(D3D12Texture, InitializeDepthStencil_CorrectFormat) { - D3D12Texture depthStencil; - bool result = depthStencil.InitializeDepthStencil( - GetDevice(), 1280, 720, DXGI_FORMAT_D24_UNORM_S8_UINT - ); - - ASSERT_TRUE(result); - EXPECT_EQ(depthStencil.GetFormat(), Format::D24_UNorm_S8_UInt); -} -``` - ---- - -### 10.7 D3D12DescriptorHeap 测试 - -**文件**: `test_descriptor_heap.cpp` - -**可测试 API 点**: - -| 测试类别 | 测试用例 | 验证内容 | -|----------|----------|----------| -| 初始化 | `Initialize_RTVHeap` | RTV 堆创建成功 | -| 初始化 | `Initialize_DSVHeap` | DSV 堆创建成功 | -| 初始化 | `Initialize_CBV_SRV_UAVHeap` | CBV/SRV/UAV 堆创建 | -| 初始化 | `Initialize_SamplerHeap` | 采样器堆创建 | -| 初始化 | `Initialize_ShaderVisible` | 着色器可见堆创建 | -| 句柄 | `GetCPUDescriptorHandle_ValidIndex` | CPU 句柄正确 | -| 句柄 | `GetGPUDescriptorHandle_ValidIndex` | GPU 句柄正确 | -| 句柄 | `GetCPUDescriptorHandleForHeapStart` | 起始句柄正确 | -| 属性 | `GetDescriptorCount_ReturnsCorrect` | 描述符数量正确 | -| 属性 | `GetDescriptorSize_ReturnsCorrect` | 描述符大小正确 | -| 属性 | `GetType_ReturnsCorrect` | 类型正确 | - -**测试代码示例**: - -```cpp -TEST(D3D12DescriptorHeap, Initialize_SRVHeapWithShaderVisible) { - D3D12DescriptorHeap heap; - bool result = heap.Initialize( - GetDevice(), - DescriptorHeapType::CBV_SRV_UAV, - 16, - true // shader visible - ); - - ASSERT_TRUE(result); - EXPECT_EQ(heap.GetDescriptorCount(), 16); - EXPECT_EQ(heap.GetType(), DescriptorHeapType::CBV_SRV_UAV); - - // 验证 GPU 句柄可用 - auto gpuHandle = heap.GetGPUDescriptorHandle(0); - EXPECT_NE(gpuHandle.ptr, 0); -} - -TEST(D3D12DescriptorHeap, GetDescriptorSize_MatchesIncrement) { - D3D12DescriptorHeap heap; - heap.Initialize(GetDevice(), DescriptorHeapType::RTV, 4); - - UINT incrementSize = GetDevice()->GetDescriptorHandleIncrementSize( - D3D12_DESCRIPTOR_HEAP_TYPE_RTV - ); - - EXPECT_EQ(heap.GetDescriptorSize(), incrementSize); -} -``` - ---- - -### 10.8 D3D12Shader 测试 - -**文件**: `test_shader.cpp` - -**可测试 API 点**: - -| 测试类别 | 测试用例 | 验证内容 | -|----------|----------|----------| -| 编译 | `CompileFromFile_VertexShader` | VS 编译成功 | -| 编译 | `CompileFromFile_PixelShader` | PS 编译成功 | -| 编译 | `CompileFromFile_GeometryShader` | GS 编译成功 | -| 编译 | `CompileFromFile_ComputeShader` | CS 编译成功 | -| 编译 | `CompileFromFile_InvalidFile` | 无效文件返回失败 | -| 编译 | `CompileFromFile_InvalidEntry` | 无效入口点返回失败 | -| 编译 | `CompileFromFile_InvalidTarget` | 无效目标返回失败 | -| 字节码 | `GetD3D12Bytecode_ReturnsValid` | 字节码有效 | -| 字节码 | `GetBytecodeSize_ReturnsNonZero` | 字节码大小非零 | -| 属性 | `GetType_ReturnsCorrect` | 类型正确 | - -**测试代码示例**: - -```cpp -TEST(D3D12Shader, CompileFromFile_VertexShader_Success) { - D3D12Shader shader; - bool result = shader.CompileFromFile( - L"Res/Shader/test_vs.hlsl", - "main", - "vs_5_1" - ); - - ASSERT_TRUE(result); - EXPECT_EQ(shader.GetType(), ShaderType::Vertex); - EXPECT_GT(shader.GetBytecodeSize(), 0); -} - -TEST(D3D12Shader, CompileFromFile_InvalidFile_ReturnsFalse) { - D3D12Shader shader; - bool result = shader.CompileFromFile( - L"NonExistent.hlsl", - "main", - "vs_5_1" - ); - - EXPECT_FALSE(result); -} -``` - ---- - -### 10.9 D3D12PipelineState 测试 - -**文件**: `test_pipeline_state.cpp` - -**可测试 API 点**: - -| 测试类别 | 测试用例 | 验证内容 | -|----------|----------|----------| -| 初始化 | `Initialize_GraphicsPipeline` | 图形管线创建成功 | -| 初始化 | `Initialize_ComputePipeline` | 计算管线创建成功 | -| 工厂方法 | `CreateDesc_ValidParameters` | 描述符创建正确 | -| 工厂方法 | `CreateInputElement_ValidParams` | 输入元素创建正确 | -| 属性 | `GetPipelineState_ReturnsValid` | PSO 对象有效 | -| 属性 | `GetType_ReturnsCorrect` | 类型正确 | - -**测试代码示例**: - -```cpp -TEST(D3D12PipelineState, Initialize_GraphicsPipeline_Success) { - // 创建根签名 - D3D12RootSignature rootSig; - D3D12_ROOT_PARAMETER params[1] = {}; - params[0] = D3D12RootSignature::CreateCBV(0); - D3D12_ROOT_SIGNATURE_DESC rsDesc = D3D12RootSignature::CreateDesc(params, 1); - rootSig.Initialize(GetDevice(), rsDesc); - - // 编译简单 Shader - D3D12Shader vs, ps; - vs.CompileFromFile(L"Res/Shader/test_vs.hlsl", "main", "vs_5_1"); - ps.CompileFromFile(L"Res/Shader/test_ps.hlsl", "main", "ps_5_1"); - - // 创建 PSO - D3D12PipelineState pso; - auto desc = D3D12PipelineState::CreateDesc( - rootSig.GetRootSignature(), - vs.GetD3D12Bytecode(), - ps.GetD3D12Bytecode(), - {}, // GS - 0, nullptr // input elements - ); - - bool result = pso.Initialize(GetDevice(), desc); - ASSERT_TRUE(result); - ASSERT_NE(pso.GetPipelineState(), nullptr); -} -``` - ---- - -### 10.10 D3D12RootSignature 测试 - -**文件**: `test_root_signature.cpp` - -**可测试 API 点**: - -| 测试类别 | 测试用例 | 验证内容 | -|----------|----------|----------| -| 初始化 | `Initialize_ValidDesc` | 有效描述符创建成功 | -| 初始化 | `Initialize_WithCBV` | 带 CBV 参数创建 | -| 初始化 | `Initialize_WithSRV` | 带 SRV 参数创建 | -| 初始化 | `Initialize_WithDescriptorTable` | 带描述符表创建 | -| 初始化 | `Initialize_WithStaticSampler` | 带静态采样器创建 | -| 工厂方法 | `CreateCBV_ReturnsValid` | CBV 创建正确 | -| 工厂方法 | `CreateSRV_ReturnsValid` | SRV 创建正确 | -| 工厂方法 | `CreateUAV_ReturnsValid` | UAV 创建正确 | -| 工厂方法 | `Create32BitConstants_ReturnsValid` | 常量创建正确 | -| 工厂方法 | `CreateDescriptorTable_ReturnsValid` | 描述符表创建正确 | -| 工厂方法 | `CreateSamplerDesc_ReturnsValid` | 采样器描述创建正确 | -| 属性 | `GetRootSignature_ReturnsValid` | 根签名对象有效 | -| 属性 | `GetParameterCount_ReturnsCorrect` | 参数数量正确 | - -**测试代码示例**: - -```cpp -TEST(D3D12RootSignature, Initialize_WithDescriptorTable) { - D3D12_DESCRIPTOR_RANGE ranges[1]; - ranges[0] = D3D12RootSignature::CreateDescriptorRange( - D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 0, 1 - ); - - D3D12_ROOT_PARAMETER params[1]; - params[0] = D3D12RootSignature::CreateDescriptorTable(1, ranges); - - D3D12_ROOT_SIGNATURE_DESC desc = D3D12RootSignature::CreateDesc(params, 1); - - D3D12RootSignature rootSig; - bool result = rootSig.Initialize(GetDevice(), desc); - - ASSERT_TRUE(result); - ASSERT_NE(rootSig.GetRootSignature(), nullptr); - EXPECT_EQ(rootSig.GetParameterCount(), 1); -} - -TEST(D3D12RootSignature, CreateSamplerDesc_AllParameters) { - auto desc = D3D12RootSignature::CreateSamplerDesc( - FilterMode::Linear, - TextureAddressMode::Clamp, - 16.0f - ); - - EXPECT_EQ(desc.Filter, D3D12_FILTER_MIN_MAG_MIP_LINEAR); - EXPECT_EQ(desc.AddressU, D3D12_TEXTURE_ADDRESS_MODE_CLAMP); - EXPECT_EQ(desc.MaxLOD, 16.0f); -} -``` - ---- - -### 10.11 视图测试 (RTV/DSV/SRV/UAV/CBV) - -**文件**: `test_views.cpp` - -**可测试 API 点**: - -| 组件 | 测试用例 | 验证内容 | -|------|----------|----------| -| RTV | `Initialize_2DTexture` | 2D 纹理 RTV 创建 | -| RTV | `CreateDesc_ValidFormat` | RTV 描述符创建 | -| DSV | `Initialize_DepthTexture` | 深度纹理 DSV 创建 | -| DSV | `CreateDesc_ValidFormat` | DSV 描述符创建 | -| SRV | `Initialize_Texture` | 纹理 SRV 创建 | -| SRV | `CreateDesc_WithMipLevels` | 带 Mip 级别 SRV | -| UAV | `Initialize_Buffer` | Buffer UAV 创建 | -| UAV | `Initialize_Texture` | Texture UAV 创建 | -| CBV | `Initialize_Buffer` | Buffer CBV 创建 | -| CBV | `Initialize_AutoDesc` | 自动描述符 CBV | - -**测试代码示例**: - -```cpp -TEST(D3D12RenderTargetView, Initialize_2DTexture_Success) { - D3D12Texture texture; - D3D12_RESOURCE_DESC desc = CD3DX12_RESOURCE_DESC::Tex2D( - DXGI_FORMAT_R8G8B8A8_UNORM, 256, 256 - ); - texture.Initialize(GetDevice(), desc); - - D3D12DescriptorHeap heap; - heap.Initialize(GetDevice(), DescriptorHeapType::RTV, 1); - - D3D12RenderTargetView rtv; - rtv.Initialize( - GetDevice(), - texture.GetResource(), - nullptr - ); - - EXPECT_NE(rtv.GetCPUDescriptorHandle().ptr, 0); -} - -TEST(D3D12DepthStencilView, Initialize_DepthTexture_Success) { - D3D12Texture depthTexture; - depthTexture.InitializeDepthStencil( - GetDevice(), 1280, 720, DXGI_FORMAT_D24_UNORM_S8_UINT - ); - - D3D12DescriptorHeap heap; - heap.Initialize(GetDevice(), DescriptorHeapType::DSV, 1); - - D3D12DepthStencilView dsv; - auto desc = D3D12DepthStencilView::CreateDesc(Format::D24_UNorm_S8_UInt); - dsv.Initialize( - GetDevice(), - depthTexture.GetResource(), - &desc - ); - - EXPECT_NE(dsv.GetCPUDescriptorHandle().ptr, 0); -} -``` - ---- - -### 10.12 D3D12Fence 测试 - -**文件**: `test_fence.cpp` - -**可测试 API 点**: - -| 测试类别 | 测试用例 | 验证内容 | -|----------|----------|----------| -| 初始化 | `Initialize_DefaultValue` | 默认初始值创建 | -| 初始化 | `Initialize_CustomValue` | 自定义初始值创建 | -| 同步 | `Signal_SetsValue` | Signal 设置值 | -| 同步 | `Wait_BlocksUntilSignaled` | Wait 阻塞等待 | -| 同步 | `GetCompletedValue_ReturnsCurrent` | 完成值查询 | -| 事件 | `GetEventHandle_ReturnsValid` | 事件句柄有效 | - -**测试代码示例**: - -```cpp -TEST(D3D12Fence, Signal_UpdatesCompletedValue) { - D3D12Fence fence; - fence.Initialize(GetDevice(), 0); - - GetCommandQueue()->Signal(fence.GetFence(), 5); - - fence.Wait(5); - - EXPECT_EQ(fence.GetCompletedValue(), 5); -} -``` - ---- - -### 10.13 类型转换测试 - -**文件**: `test_types.cpp` - -**可测试 API 点**: - -| 组件 | 测试用例 | 验证内容 | -|------|----------|----------| -| D3D12Enum | `ToD3D12_FillMode_All` | 所有填充模式转换 | -| D3D12Enum | `ToD3D12_CullMode_All` | 所有剔除模式转换 | -| D3D12Enum | `ToD3D12_ComparisonFunc_All` | 所有比较函数转换 | -| D3D12Enum | `ToD3D12_BlendOp_All` | 所有混合操作转换 | -| D3D12Enum | `ToD3D12_Format_Common` | 常用格式转换 | -| D3D12Enum | `ToD3D12_ResourceStates_All` | 所有资源状态转换 | -| D3D12Types | `ToD3D12_Viewport_Correct` | Viewport 转换正确 | -| D3D12Types | `ToD3D12_TextureDesc_Correct` | 纹理描述符转换正确 | -| D3D12Types | `ToD3D12_BufferDesc_Correct` | Buffer 描述符转换正确 | -| D3D12Common | `CheckFormatSupport_CommonFormats` | 常用格式支持检查 | -| D3D12Common | `IsRenderTargetFormatSupported_Common` | 渲染目标格式支持 | - -**测试代码示例**: - -```cpp -TEST(D3D12Enum, ToD3D12_FillMode_All) { - EXPECT_EQ(ToD3D12(FillMode::Wireframe), D3D12_FILL_MODE_WIREFRAME); - EXPECT_EQ(ToD3D12(FillMode::Solid), D3D12_FILL_MODE_SOLID); -} - -TEST(D3D12Enum, ToD3D12_ResourceStates_All) { - EXPECT_EQ(ToD3D12(ResourceStates::Common), D3D12_RESOURCE_STATE_COMMON); - EXPECT_EQ(ToD3D12(ResourceStates::RenderTarget), D3D12_RESOURCE_STATE_RENDER_TARGET); - EXPECT_EQ(ToD3D12(ResourceStates::DepthWrite), D3D12_RESOURCE_STATE_DEPTH_WRITE); - EXPECT_EQ(ToD3D12(ResourceStates::VertexAndConstantBuffer), D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER); - EXPECT_EQ(ToD3D12(ResourceStates::IndexBuffer), D3D12_RESOURCE_STATE_INDEX_BUFFER); -} -``` - ---- - -## 11. 测试实现优先级 - -根据组件的依赖关系和重要性,建议按以下顺序实现测试: - -### Phase 1: 核心基础设施 (高优先级) - -1. **D3D12Device** - 所有其他组件依赖 -2. **D3D12CommandQueue** - 命令执行基础 -3. **D3D12CommandAllocator** - 命令分配基础 -4. **D3D12Fence** - 同步基础 - -### Phase 2: 资源管理 (中优先级) - -5. **D3D12Buffer** - 最常用的资源 -6. **D3D12Texture** - 纹理资源 -7. **D3D12DescriptorHeap** - 描述符管理 - -### Phase 3: 渲染管线 (中优先级) - -8. **D3D12Shader** - 着色器编译 -9. **D3D12RootSignature** - 根签名 -10. **D3D12PipelineState** - 管线状态 - -### Phase 4: 视图和命令 (低优先级) - -11. **RTV/DSV/SRV/UAV/CBV** - 各种视图 -12. **D3D12CommandList** - 命令录制 - -### Phase 5: 高级功能 (最低优先级) - -13. **D3D12SwapChain** - 需要窗口 -14. **D3D12Screenshot** - 截图功能 -15. **类型转换** - 辅助函数测试 - ---- - -## 12. 测试数据文件 - -测试所需资源文件从 `tests/D3D12/Res/` 目录获取: - -``` -tests/D3D12/Res/ -├── Shader/ -│ ├── test_vs.hlsl # 测试用顶点着色器 -│ ├── test_ps.hlsl # 测试用像素着色器 -│ ├── test_gs.hlsl # 测试用几何着色器 -│ ├── test_cs.hlsl # 测试用计算着色器 -│ └── test_pattern.hlsl # 图案化测试着色器 -├── Texture/ -│ ├── test_256x256.png # 测试用 2D 纹理 -│ └── test_cube.dds # 测试用立方体纹理 -└── Golden/ - ├── clear_color_gt.ppm # 清除颜色基准图像 - ├── pattern_checker_gt.ppm # 棋盘格基准图像 - └── gradient_gt.ppm # 渐变基准图像 -``` - -### 12.1 在测试中引用资源 - -```cpp -// 通过编译定义获取资源路径 -#ifndef TEST_RESOURCES_DIR -#define TEST_RESOURCES_DIR "tests/D3D12/Res" -#endif - -TEST(D3D12Shader, CompileFromFile_VertexShader) { - D3D12Shader shader; - - // 构建资源路径 - std::wstring shaderPath = std::wstring(TEST_RESOURCES_DIR) + L"/Shader/test_vs.hlsl"; - - bool result = shader.CompileFromFile(shaderPath.c_str(), "main", "vs_5_1"); - - ASSERT_TRUE(result); -} - ---- - -## 13. 构建计划与执行流程 - -本文档描述的测试框架将按照以下计划逐步构建,每完成一个步骤即进行测试、提交并推送。 - -### 13.1 构建计划总览 - -| 步骤 | 文件 | 内容 | 验证方式 | -|------|------|------|----------| -| **步骤 1: 基础设施搭建** | -| 1.1 | `engine/src/RHI/D3D12/test/CMakeLists.txt` | 测试构建配置 | CMake 配置检查 | -| 1.2 | `engine/src/RHI/D3D12/test/fixtures/D3D12TestFixture.h` | 基础测试夹具 | 编译通过 | -| **步骤 2: 核心组件测试** | -| 2.1 | `test_device.cpp` | D3D12Device 初始化、适配器枚举、特性查询 | 运行测试 | -| 2.2 | `test_fence.cpp` | D3D12Fence 同步、Signal/Wait | 运行测试 | -| **步骤 3: 命令系统测试** | -| 3.1 | `test_command_queue.cpp` | 队列创建、执行命令、同步 | 运行测试 | -| 3.2 | `test_command_allocator.cpp` | 分配器创建、重置 | 运行测试 | -| 3.3 | `test_command_list.cpp` | 命令录制、资源屏障、绘制 | 运行测试 | -| **步骤 4: 资源测试** | -| 4.1 | `test_buffer.cpp` | Buffer 创建、Map/Unmap、数据上传 | 运行测试 | -| 4.2 | `test_texture.cpp` | 纹理创建、深度模板 | 运行测试 | -| 4.3 | `test_descriptor_heap.cpp` | 描述符堆创建、句柄获取 | 运行测试 | -| **步骤 5: 渲染管线测试** | -| 5.1 | `test_shader.cpp` | Shader 编译 | 运行测试 | -| 5.2 | `test_root_signature.cpp` | 根签名创建、参数 | 运行测试 | -| 5.3 | `test_pipeline_state.cpp` | PSO 创建 | 运行测试 | -| **步骤 6: 视图测试** | -| 6.1 | `test_views.cpp` | RTV/DSV/SRV/UAV/CBV | 运行测试 | - -### 13.2 执行流程 - -每完成一个步骤,按以下流程操作: - -```bash -# 1. 编译项目 -cmake --build build --config Debug - -# 2. 运行测试 -ctest --test-dir build -C Debug --output-on-failure - -# 3. 提交更改 -git add . -git commit -m "test: 添加 D3D12 Device 测试" - -# 4. 推送更改 -git push -``` - -### 13.3 CMake 集成方式 - -采用方案 A:在 `tests/CMakeLists.txt` 中添加子目录引用。 - -在 `tests/CMakeLists.txt` 中添加: -```cmake -# 在现有 add_subdirectory 行之后添加 -add_subdirectory(D3D12) -``` - -创建 `tests/D3D12/CMakeLists.txt` 用于将 `engine/src/RHI/D3D12/test` 添加到构建: -```cmake -# tests/D3D12/CMakeLists.txt -cmake_minimum_required(VERSION 3.15) - -project(D3D12Integration) - -# 将 engine 测试目录添加到父级 CMake -add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../../engine/src/RHI/D3D12/test ${CMAKE_BINARY_DIR}/D3D12_test) -``` - -### 13.4 测试夹具代码结构 - -基础测试夹具 `D3D12TestFixture.h` 提供以下功能: - -- 全局 D3D12 设备(所有测试共享) -- 每测试周期的命令队列、分配器、命令列表 -- GPU 等待辅助方法 -- 资源清理保证 - -### 13.5 资源路径配置 - -测试资源从 `tests/D3D12/Res/` 获取,通过 CMake 编译定义传递路径: - -```cmake -target_compile_definitions(d3d12_tests PRIVATE - TEST_RESOURCES_DIR="${CMAKE_SOURCE_DIR}/tests/D3D12/Res" -) -``` - -### 13.6 注意事项 - -1. **测试隔离**:每个测试用例应独立创建所需的资源 -2. **资源清理**:在 TearDown 中确保等待 GPU 完成后释放资源 -3. **调试层**:建议在 Debug 构建设中启用 D3D12 Debug Layer -4. **窗口依赖**:SwapChain 等需要窗口的测试放在最后 - ---- - -## 14. 后续改进 - -- [x] 在 `tests/D3D12_engine/test/` 创建测试夹具文件 -- [ ] 实现 Phase 1 核心基础设施测试 -- [ ] 实现 Phase 2 资源管理测试 -- [ ] 实现 Phase 3 渲染管线测试 -- [ ] 实现 Phase 4 视图和命令测试 -- [ ] 实现 Phase 5 高级功能测试 -- [ ] 添加资源泄漏检测工具 -- [ ] 完善渲染结果测试图案 -- [ ] 添加性能基准测试 -- [ ] 配置 CI 自动测试 -- [ ] 支持 Vulkan 后端测试(复用测试夹具抽象) diff --git a/docs/used/EditorViewport宿主渲染收口总结4.1.md b/docs/used/EditorViewport宿主渲染收口总结4.1.md deleted file mode 100644 index 60c16d20..00000000 --- a/docs/used/EditorViewport宿主渲染收口总结4.1.md +++ /dev/null @@ -1,128 +0,0 @@ -# Editor Viewport 宿主渲染收口总结 4.1 - -## 当前判断 - -截至 2026-04-01,这一阶段的主线应视为: - -- 把 `Editor -> ViewportHost -> Renderer -> RHI` 这条链路接通 -- 把 editor viewport 的宿主层从 panel 内部逻辑中收出来 -- 把这层宿主代码压到“可继续演进,但先停止扩张”的状态 - -这一阶段的目标不是继续往 editor viewport 里塞更多功能,也不是提前做完未来 renderer 的全部能力。 - -如果继续在本阶段里混入 gizmo 完整体系、GPU picking 正式方案、多 pass 大改、后处理、光照系统等内容,只会让阶段边界再次失控。 - -## 已完成的收口结果 - -### 1. editor viewport 已经不再是空壳 - -当前 `SceneView` 与 `GameView` 已经能通过统一的 viewport host 路径请求离屏 render target,并把 renderer 输出展示到 editor 面板中。 - -当前已实际接通: - -- Scene viewport 渲染 -- Game viewport 渲染 -- viewport resize 资源重建 -- backpack 等真实模型内容在 editor 中显示 -- grid / 选中 / 描边 / object id 读取链路 - -### 2. viewport host 的职责边界已经成型 - -当前 viewport 宿主层已经被拆成几块相对明确的职责: - -- [editor/src/Viewport/ViewportHostSurfaceUtils.h](D:/Xuanchi/Main/XCEngine/editor/src/Viewport/ViewportHostSurfaceUtils.h) - - viewport surface / texture / reuse 相关纯工具 -- [editor/src/Viewport/ViewportHostRenderTargets.h](D:/Xuanchi/Main/XCEngine/editor/src/Viewport/ViewportHostRenderTargets.h) - - viewport render target 生命周期与创建销毁 -- [editor/src/Viewport/ViewportObjectIdPicker.h](D:/Xuanchi/Main/XCEngine/editor/src/Viewport/ViewportObjectIdPicker.h) - - viewport object id 读取与像素映射 -- [editor/src/Viewport/ViewportHostRenderFlowUtils.h](D:/Xuanchi/Main/XCEngine/editor/src/Viewport/ViewportHostRenderFlowUtils.h) - - scene/game viewport 的失败回退策略、request 组装、成功态迁移 -- [editor/src/Viewport/ViewportHostService.h](D:/Xuanchi/Main/XCEngine/editor/src/Viewport/ViewportHostService.h) - - 保留真正的 orchestration、scene view camera、post pass 组装与 clear 执行 - -这意味着 viewport host 已经不再是“一个面板私有的大杂烩类”,而是一个有明确宿主边界的 editor 侧接入层。 - -### 3. 现阶段测试闭环已经补上 - -当前 editor 侧已经有明确的宿主层单测覆盖: - -- [tests/editor/test_viewport_host_surface_utils.cpp](D:/Xuanchi/Main/XCEngine/tests/editor/test_viewport_host_surface_utils.cpp) -- [tests/editor/test_viewport_render_targets.cpp](D:/Xuanchi/Main/XCEngine/tests/editor/test_viewport_render_targets.cpp) -- [tests/editor/test_viewport_object_id_picker.cpp](D:/Xuanchi/Main/XCEngine/tests/editor/test_viewport_object_id_picker.cpp) -- [tests/editor/test_viewport_render_flow_utils.cpp](D:/Xuanchi/Main/XCEngine/tests/editor/test_viewport_render_flow_utils.cpp) - -本轮收口完成后,`editor_tests` 已覆盖 viewport host 中最容易回归的纯策略与资源管理逻辑。 - -## 为什么到这里应当停止继续拆 - -当前 [editor/src/Viewport/ViewportHostService.h](D:/Xuanchi/Main/XCEngine/editor/src/Viewport/ViewportHostService.h) 剩下的主要内容,是以下几类“真正属于宿主编排”的职责: - -- scene view camera 的创建与驱动 -- focus / orientation axis 等 editor 专属视图控制 -- scene view post pass 的装配 -- scene/game viewport 的 render dispatch -- clear 执行 - -这些逻辑如果继续硬拆,很容易为了“文件更小”而把真正依赖 editor 运行时状态的 orchestration 人为打散,收益已经明显下降。 - -所以这一阶段的明确结论是: - -- `ViewportHostService` 继续保持为宿主编排入口是合理的 -- 不再以“继续拆文件”为本阶段目标 -- 下一阶段应把重点转移到 renderer 本身的演进,而不是继续挤压 editor host - -## 本阶段的结束标准 - -这一阶段以以下标准视为完成: - -1. `editor_tests` 全部通过 -2. `XCEditor` 能正常编译 -3. viewport host 的主要纯逻辑都有独立单测 -4. `ViewportHostService` 不再继续承载资源创建、object id 读取、失败策略等细碎职责 -5. 明确哪些问题属于后续 renderer 阶段,而不是继续留在当前阶段消耗 - -## 明确不属于本阶段的内容 - -以下内容不再计入“editor viewport 宿主渲染收口”阶段: - -- GPU object id 正式拾取方案 -- renderer 内部通用多 pass / render graph 体系 -- 更正式的 editor outline 方案升级 -- gizmo 全量产品化 -- 光照、阴影、后处理 -- runtime 渲染管线的完整 SRP 式抽象 - -这些内容应进入 renderer 后续阶段,而不是继续塞回当前 viewport host 收口任务。 - -## 下一阶段建议 - -下一阶段主线应转为: - -### 1. renderer 能力继续上移 - -把目前 editor 层为了接通 viewport 而保留的一些临时职责,逐步让 renderer 吸收为正式能力,例如: - -- 多 pass 组织能力 -- 正式的 object id / editor helper pass 接口 -- editor 与 runtime 共享的 camera render path - -### 2. editor 只消费 renderer 提供的正式输出 - -editor viewport 后续应更多扮演: - -- render target 宿主 -- 输入转发 -- overlay / gizmo 宿主 -- editor 专属交互入口 - -而不是继续承载 renderer 内部演进本体。 - -## 阶段性结论 - -这一阶段现在可以正式收口: - -- editor viewport 已经从“空面板”进入“真实宿主层” -- renderer 与 RHI 已经能稳定把离屏结果送进 editor -- viewport host 的边界已经明确 -- 后续不应继续在本阶段内部无限拆分,而应切换到 renderer 下一阶段 diff --git a/docs/used/Editor架构说明_2026-04-15归档.md b/docs/used/Editor架构说明_2026-04-15归档.md deleted file mode 100644 index 472974ab..00000000 --- a/docs/used/Editor架构说明_2026-04-15归档.md +++ /dev/null @@ -1,446 +0,0 @@ -# Editor 架构说明 - -## 1. 当前目标 - -当前这一轮 editor 重构,目标不是继续在各个 panel 上零散修 UI,而是先把 editor 自身的架构层次收稳: - -- 把视觉样式、交互路由、编辑命令、dock 布局、面板壳层拆开。 -- 把 `Hierarchy / Project / Inspector / Console / MenuBar` 统一到同一套 shared UI 和 action 语义上。 -- 把 `Scene / Game` viewport 保持在当前真实主链上:`ViewportHostService -> Rendering + RHI -> ImGui panel`。 -- 把 `Assets + .meta + Library` 项目工作流、脚本程序集构建与运行时状态纳入 editor 正式分层,而不是继续当外围脚本。 - -这意味着当前 editor 的重点已经不是“先把外壳搭出来”,而是: - -- 在现有外壳分层上继续收口真实可运行的 viewport / project / scripting 主链。 -- 让 panel、viewport helper、commands、managers 和引擎侧 `Rendering / Resources / Scene / Scripting` 的边界继续清晰化。 - -## 2. 总体分层 - -当前 editor 推荐按下面的依赖方向理解: - -`Application -> EditorLayer -> EditorWorkspace -> Panels/Viewport -> Actions -> Commands -> Managers/Core` - -同时还有一条横向的共享 UI 层: - -`Panels / Actions / ComponentEditors -> UI` - -以及一条 inspector 专用扩展链路: - -`InspectorPanel -> ComponentEditorRegistry -> IComponentEditor` - -以及一条已经落地的 editor 到引擎主链: - -`Viewport / Managers / Scripting -> Rendering / Resources / Scene / Scripting / RHI` - -允许依赖的基本原则: - -- `UI` 只负责样式 token、共享控件、popup/property-grid 等表现层能力,不承担业务语义。 -- `Actions` 负责把 button/menu/shortcut/context menu 这些 UI 意图转换为命令调用、事件请求或共享状态更新。 -- `Commands` 负责真正修改 scene/project/component 数据,并处理 dirty/undo/selection 的业务边界。 -- `Panels` 只保留最小的渲染壳层和少量局部瞬时状态,不直接堆业务逻辑。 -- `Layout` 只负责 dockspace 和布局持久化,不处理 scene/project 业务。 -- `Core/Managers` 提供 editor 运行时共享状态与数据入口。 -- `Viewport` 是 editor 宿主和引擎渲染主链之间的正式桥接层,不是 panel 内随手拼的辅助代码。 - -不允许继续扩散的方向: - -- 不要在 panel 里直接散落 scene/project 业务修改。 -- 不要把菜单、快捷键、右键菜单逻辑分别复制到不同 panel。 -- 不要把样式常量重新写回 panel 本地。 -- 不要让 `UI` 反向依赖 `Commands` 或具体 panel。 - -## 3. 主要模块职责 - -### 3.1 Application / Platform - -关键文件: - -- `editor/src/Application.cpp` -- `editor/src/Platform/Win32EditorHost.h` -- `editor/src/Platform/D3D12WindowRenderer.h` -- `editor/src/UI/ImGuiSession.h` -- `editor/src/UI/ImGuiBackendBridge.h` - -职责: - -- 创建窗口、D3D12 renderer、ImGui session 与 backend bridge。 -- 初始化 `EditorContext`。 -- 监听 editor 级退出事件。 -- 驱动 layer attach/detach/update/render 主循环。 -- 更新窗口标题。 - -边界: - -- 这里是 editor 的宿主壳层,不负责具体 panel 交互。 -- 这里可以处理平台与渲染 backend 生命周期,但不应该继续写 editor 业务逻辑。 - -### 3.2 EditorLayer / EditorWorkspace - -关键文件: - -- `editor/src/Layers/EditorLayer.cpp` -- `editor/src/Core/EditorWorkspace.h` - -职责: - -- `EditorLayer` 只做 layer 生命周期转发。 -- `EditorWorkspace` 负责组装 panel 集合、初始化 project panel、加载启动场景、挂接 dock layout controller。 -- 统一调度 panel 的 attach/detach/update/render/event。 - -边界: - -- `EditorLayer` 不能回到“自己直接维护一堆 panel 生命周期”的旧结构。 -- 新增 panel 时,优先在 `EditorWorkspace` 里做装配,不回写到 `Application`。 - -### 3.3 UI Shared Layer - -关键文件: - -- `editor/src/UI/BaseTheme.h` -- `editor/src/UI/StyleTokens.h` -- `editor/src/UI/DockHostStyle.h` -- `editor/src/UI/PanelChrome.h` -- `editor/src/UI/Widgets.h` -- `editor/src/UI/PopupState.h` -- `editor/src/UI/PropertyGrid.h` -- `editor/src/UI/UI.h` - -职责: - -- 提供 editor 统一主题、尺寸、留白、颜色和面板 chrome。 -- 提供 toolbar、tab、popup、dialog、property row、asset tile、empty state 等共享控件。 -- 提供 inspector/property-grid 的公共表现层。 - -边界: - -- 这一层可以知道 ImGui,但不应该知道 scene/project 业务对象如何变化。 -- 这一层输出的是“怎样画”,不是“点了以后改什么”。 - -### 3.4 Actions / Routers - -关键文件: - -- `editor/src/Actions/EditorActions.h` -- `editor/src/Actions/ActionBinding.h` -- `editor/src/Actions/ActionRouting.h` -- `editor/src/Actions/EditActionRouter.h` -- `editor/src/Actions/MainMenuActionRouter.h` -- `editor/src/Actions/HierarchyActionRouter.h` -- `editor/src/Actions/ProjectActionRouter.h` -- `editor/src/Actions/InspectorActionRouter.h` -- `editor/src/Actions/ConsoleActionRouter.h` - -职责: - -- 定义 editor 内部动作项的文案、快捷键、可用状态。 -- 统一菜单点击、快捷键触发、toolbar 按钮、右键菜单动作。 -- 处理 active route/focused route,把 `Edit` 类动作路由到正确 panel。 -- 处理 popup 请求、rename 请求、局部状态切换等“轻量交互编排”。 - -边界: - -- `Actions` 可以依赖 `Commands`、`EventBus`、`UI`,但不负责底层数据结构修改细节。 -- `Actions` 不负责持久保存复杂状态;复杂状态应留给 manager 或共享 state object。 -- `Actions` 不应该包含大量 panel 私有布局代码。 - -判断标准: - -- 一个交互如果同时被 menu、shortcut、context menu、toolbar 复用,它应先进入 router。 -- 一个交互如果只是“请求 panel 开一个 popup/进入 rename”,更适合走 `Actions + EventBus/shared state`。 - -### 3.5 Commands - -关键文件: - -- `editor/src/Commands/SceneCommands.h` -- `editor/src/Commands/EntityCommands.h` -- `editor/src/Commands/ProjectCommands.h` -- `editor/src/Commands/ComponentCommands.h` - -职责: - -- 处理 scene/project/component 的实际编辑行为。 -- 统一 undo 快照、dirty 标记、selection reset 等业务边界。 -- 统一保存、加载、复制、粘贴、重命名、重挂接、资源移动等操作。 - -边界: - -- `Commands` 不依赖 ImGui。 -- `Commands` 不直接绘制 UI。 -- `Commands` 返回的是业务结果,不承载 popup 绘制和菜单拼装。 - -经验规则: - -- 只要一个操作会改 scene/project 数据,就优先考虑落到 `Commands`。 -- 只要一个操作涉及 undo/redo,就不要留在 panel 本地手写。 - -### 3.6 Core / Managers - -关键文件: - -- `editor/src/Core/EditorContext.h` -- `editor/src/Core/EventBus.h` -- `editor/src/Core/SelectionManager.h` -- `editor/src/Core/UndoManager.h` -- `editor/src/Managers/SceneManager.h` -- `editor/src/Managers/ProjectManager.h` - -职责: - -- `EditorContext` 聚合 event bus、selection、scene、project、undo、active action route。 -- `SceneManager` 持有场景对象、根节点、场景路径、dirty 状态、clipboard。 -- `ProjectManager` 扫描 `Assets`、维护当前目录、选择索引、文件夹导航。 -- `SelectionManager` 统一发布 selection changed 事件。 -- `UndoManager` 负责快照历史、interactive change 边界、undo/redo。 - -所有权约定: - -- selection 所有权在 `SelectionManager`。 -- undo 历史所有权在 `UndoManager`。 -- scene dirty 与当前场景路径所有权在 `SceneManager`。 -- 当前活动编辑路由所有权在 `EditorContext`。 - -### 3.7 Layout - -关键文件: - -- `editor/src/Layout/DockLayoutController.h` - -职责: - -- 创建 dockspace。 -- 应用默认布局。 -- 响应 reset layout 请求。 -- 持久化 ImGui layout 到项目目录下的 `.xceditor/imgui_layout.ini`。 - -边界: - -- layout controller 不应该知道 hierarchy/project/scene 的编辑业务。 -- panel 是否存在由 workspace 决定,不由 layout controller 反向创建。 - -### 3.8 Panels - -关键文件: - -- `editor/src/panels/MenuBar.cpp` -- `editor/src/panels/HierarchyPanel.cpp` -- `editor/src/panels/ProjectPanel.cpp` -- `editor/src/panels/InspectorPanel.cpp` -- `editor/src/panels/ConsolePanel.cpp` - -当前 panel 应保留的内容: - -- 窗口壳层。 -- 生命周期订阅与退订。 -- 非共享的极少量局部瞬时状态。 -- 调用 shared UI/widget 和 action router 进行拼装。 - -当前 panel 不应该再承载的内容: - -- undo/dirty/selection 的业务判断。 -- 菜单和快捷键的重复定义。 -- 资源/实体操作的底层命令执行细节。 -- 大量散落的视觉常量。 - -### 3.9 ComponentEditors - -关键文件: - -- `editor/src/ComponentEditors/IComponentEditor.h` -- `editor/src/ComponentEditors/ComponentEditorRegistry.h` -- `editor/src/ComponentEditors/ComponentEditorRegistry.cpp` -- `editor/src/ComponentEditors/TransformComponentEditor.h` -- `editor/src/ComponentEditors/CameraComponentEditor.h` -- `editor/src/ComponentEditors/LightComponentEditor.h` - -职责: - -- 为某一类 component 提供 inspector 内容绘制。 -- 定义该 component 的显示名、是否能添加、是否能删除、添加禁用原因。 -- 通过 registry 统一注册到 inspector 与 component commands。 - -当前注册机制: - -- `ComponentEditorRegistry` 是 editor 侧单例注册表。 -- 注册发生在 `ComponentEditorRegistry` 构造函数内。 -- `RegisterEditor(std::unique_ptr)` 将 editor 同时放入顺序列表和按类型名索引表。 -- inspector 绘制时通过 component 实例类型名查找 editor。 -- Add Component 菜单通过遍历 registry 的已注册 editor 生成。 -- `ComponentCommands` 通过相同 registry 复用 add/remove 规则,而不是在 inspector 单独硬编码。 - -这套机制的意义: - -- inspector 的“显示逻辑”和“组件增删能力”在同一处收口。 -- 新增一个 component editor 时,不需要去 panel 里到处补分支。 -- 后续如果要做脚本组件、自定义组件 inspector,优先扩展 registry,而不是污染 panel。 - -新增 component editor 的推荐步骤: - -1. 新建一个实现 `IComponentEditor` 的 editor。 -2. 在其中定义 `GetComponentTypeName / GetDisplayName / Render / CanAddTo / CanRemove`。 -3. 在 `ComponentEditorRegistry.cpp` 注册。 -4. 如果涉及复杂交互,优先复用 `UI::PropertyGrid` 和 undo interactive change 机制。 - -### 3.10 Viewport - -关键文件: - -- `editor/src/Viewport/ViewportHostService.h` -- `editor/src/Viewport/IViewportHostService.h` -- `editor/src/Viewport/SceneViewportChrome.h` -- `editor/src/Viewport/SceneViewportInteractionFrame.h` -- `editor/src/Viewport/SceneViewportNavigation.h` -- `editor/src/Viewport/SceneViewportTransformGizmoCoordinator.h` -- `editor/src/Viewport/SceneViewportOverlayBuilder.h` -- `editor/src/Viewport/SceneViewportOverlayFrameCache.h` -- `editor/src/Viewport/SceneViewportOverlaySpriteResources.h` -- `editor/src/Viewport/SceneViewportResourcePaths.h` -- `editor/src/Viewport/Passes/SceneViewportEditorOverlayPass.h` - -职责: - -- 作为 editor 宿主与引擎 `Rendering + RHI` 的桥接层,维护 Scene / Game viewport 的离屏渲染接线。 -- 维护当前 Scene View helper 主链: - - `SceneViewportChrome` - - `SceneViewportInteractionFrame` - - `SceneViewportNavigation` - - `SceneViewportTransformGizmoCoordinator` - - `ViewportHostService` -- 负责 overlay builder、overlay frame cache、sprite 资源准备、object-id picking、grid、outline 和 gizmo overlay state。 - -边界: - -- `Viewport` 是正式子系统,不再是“等待未来回归”的空位。 -- panel 不应自己拼接一套独立渲染路径;新的 viewport 行为优先落到 helper、host service 或 overlay pass。 -- `SceneViewportShaderPaths.h` 当前主要是兼容 include;资源路径真实 owner 已经转到 `SceneViewportResourcePaths.h`。 - -### 3.11 Scripting / Project Workflow - -关键文件: - -- `editor/src/Scripting/EditorScriptAssemblyBuilder.h` -- `editor/src/Scripting/EditorScriptRuntimeStatus.h` -- `editor/src/Managers/ProjectManager.h` -- `editor/src/Managers/SceneManager.h` -- `editor/src/Utils/ProjectFileUtils.h` - -职责: - -- 解析项目根目录、读写 `Project.xcproject`,并把仓库内 `project/` 或 `--project ` 解析为当前工程。 -- 驱动 `Assets + .meta + Library` 风格项目 workflow,包括项目资源浏览、脚本程序集重建入口和运行时状态反馈。 -- 把项目脚本程序集与 editor/runtime 实际消费的 `Library/ScriptAssemblies/` 目录连接起来。 - -边界: - -- 与项目资产、`.meta`、`Library`、脚本程序集相关的问题,不应再按“editor 仍处于无工程状态”理解。 -- `ProjectManager`、`SceneManager`、`EditorScriptAssemblyBuilder` 与引擎侧 `ResourceManager / AssetImportService / ScriptEngine` 是协作关系,不应在 panel 里各自复制一层状态机。 - -## 4. EventBus 使用规则 - -`EventBus` 现在主要承担两类职责: - -- editor 范围的状态通知,例如 selection changed、entity created/deleted、scene changed。 -- UI 请求类事件,例如 rename request、exit request、reset layout request。 - -推荐规则: - -- “请求某个 panel 进入某种 UI 状态”时,优先用事件或共享 popup state。 -- “真正修改数据”时,优先走 command,不要只发 event 期待别人去改数据。 -- 事件用于解耦,不用于隐藏业务路径。 - -一个简单判断: - -- 如果动作需要 undo/dirty,通常应该先有 `Command`。 -- 如果动作只是让某个 panel 弹窗或进入 rename,通常可以只发 `Event`。 - -## 5. Undo / Dirty / Selection 约定 - -这是 editor 最容易再次失控的地方,必须保持统一: - -- scene 数据修改通过 `Commands` 收口。 -- `UndoUtils::ExecuteSceneCommand(...)` 负责把一次编辑包成可回退命令。 -- inspector 连续拖拽这类交互,使用 interactive change 边界,不在 panel 里手搓多次提交。 -- 场景切换、新建场景、加载场景后,selection 与 undo 历史由 scene command 统一重置。 -- scene dirty 由 `SceneManager` 维护,保存成功后归零。 - -禁止事项: - -- 不要在 panel 里直接偷偷 `MarkSceneDirty()` 来替代 command。 -- 不要让某个 panel 自己保存一份 selection。 -- 不要让快捷键直接跨过 command 改 manager。 - -## 6. 快捷键与菜单路由规则 - -当前 editor 已形成两条主路由: - -- `MainMenuActionRouter` 负责 `File / View / Help / global shortcut`。 -- `EditActionRouter` 负责 `Edit` 菜单和 panel-focused edit shortcut。 - -约定: - -- `Global` 类快捷键由 menu bar 统一分发。 -- `Hierarchy / Project` 等焦点相关动作,先根据 `EditorActionRoute` 解析目标,再执行对应 action。 -- panel 只负责声明自己何时成为 active route,不负责重复实现整套 edit 菜单。 - -这样做的收益: - -- 同一个动作不会再出现“菜单能点、快捷键失效、右键菜单又是另一套”的问题。 -- 后续增加新 panel 的编辑语义时,只需要接入 route,不必复制整套菜单逻辑。 - -## 7. 当前已完成与明确暂缓的部分 - -已完成的重点: - -- 统一 UI token、panel chrome、popup/property-grid/shared widgets。 -- menu/shortcut/context menu/action 的大部分共享路由。 -- scene/project/entity/component 的主要编辑命令收口。 -- dock layout controller 与 editor workspace 装配。 -- inspector 的 component editor registry 接入。 -- editor 级回归测试基础框架与关键命令测试。 -- `Scene / Game` viewport 已经重新接回当前正式主链:引擎 `Rendering + RHI` 离屏输出 -> `ViewportHostService` -> ImGui panel。 -- `Assets + .meta + Library` 项目工作流、`Project.xcproject`、脚本程序集重建与脚本运行时状态已经进入 editor 正式工作流。 -- viewport helper 已按 `Chrome -> InteractionFrame -> Navigation -> TransformGizmoCoordinator -> ViewportHostService` 显式拆层。 - -当前明确暂缓: - -- 把 editor 主线程上残留的同步资源兜底点继续清理到更严格的异步消费模型。 -- 把 `Library bootstrap / scene streaming / explicit import` 的状态模型继续正式化到 UI。 -- 继续减少 panel 内局部瞬时状态与 helper 间重复规则。 - -原因很明确: - -- 前三项已经不是“没接回来的未来工作”,而是已经落地、但还要继续收口的当前主线。 - -## 8. 后续新增功能时的落点原则 - -如果以后继续扩 editor,推荐按下面的判断落点: - -- 新视觉样式或共享控件:放 `UI` -- 新菜单项/快捷键/右键菜单复用:放 `Actions` -- 新的 scene/project/component 编辑行为:放 `Commands` -- 新 inspector 组件面板:放 `ComponentEditors` -- 新 dock/window 布局控制:放 `Layout` -- 新 editor 全局状态:放 `Core/Managers` -- 新 viewport host / overlay / interaction helper:放 `Viewport` -- 只是把已有能力拼进某个窗口:放 `Panels` - -如果一个功能不知道放哪,一般先问自己: - -- 它是不是只关乎“怎么画”? -- 它是不是多个入口共享的交互? -- 它是不是会真正改数据并进入 undo? - -这三个问题基本能把落点判断清楚。 - -## 9. 当前收尾阶段剩余事项 - -从 UI 架构角度看,当前已经不是“推倒重来”阶段,而是最后的封口阶段。剩余事项主要有: - -- 继续压缩少量 panel 本地瞬时状态,能下沉的继续下沉。 -- 继续补命令/路由回归测试,尤其是 inspector interactive undo 边界。 -- 继续补 viewport helper、project workflow、script assembly builder 与 scene streaming 相关回归测试。 -- 把 `Library bootstrap`、显式导入、后台 scene asset streaming 这三类状态在 editor UI 上分开表达。 - -结论: - -editor 当前已经形成稳定分层,而且这套分层已经承接了真实的 viewport / project / scripting 工作流。后面再做功能迭代时,应坚持“先看边界,再落代码”,不要回到 panel 内部堆逻辑的旧路线。 diff --git a/docs/used/Editor模块_CMake直链Release版XCEngine库破坏配置一致性3.28.md b/docs/used/Editor模块_CMake直链Release版XCEngine库破坏配置一致性3.28.md deleted file mode 100644 index bb2319e2..00000000 --- a/docs/used/Editor模块_CMake直链Release版XCEngine库破坏配置一致性3.28.md +++ /dev/null @@ -1,78 +0,0 @@ -# Editor模块 CMake直链Release版XCEngine库破坏配置一致性 -## 1. 问题定义 - -当前 editor 可执行目标的链接方式仍然是硬编码文件路径: - -- [`editor/CMakeLists.txt`](D:\Xuanchi\Main\XCEngine\editor\CMakeLists.txt) - -当前写法: - -- `target_link_libraries(... ${CMAKE_CURRENT_SOURCE_DIR}/../build/engine/Release/XCEngine.lib)` - -这不是正常的 CMake target 依赖,而是直接绑死到一个磁盘上的 `Release` 静态库文件。 - ---- - -## 2. 当前影响 - -这会导致几个非常实际的问题: - -1. Debug editor 也可能链接到旧的 Release 版引擎库 -2. editor 对 `XCEngine` 目标没有真实依赖关系 -3. 引擎库变更后,editor 可能不会按正确配置自动重建 -4. Debug / Release 混链风险被隐藏 - -当前本地状态就能看到: - -- `build/engine/Debug/XCEngine.lib` -- `build/engine/Release/XCEngine.lib` - -两者时间戳和体积明显不同,但 editor 目标仍然硬连 Release 文件。 - ---- - -## 3. 为什么这是重大缺陷 - -这条会直接污染后续 Viewport 对接的开发过程: - -- 你以为 editor 正在吃最新的 Renderer / RHI 改动 -- 实际上它可能仍在链接旧的 Release 库 - -这样一来: - -- viewport 接入问题很难调 -- Debug 行为和 Release 行为可能不一致 -- CI / 新机器 / 干净构建环境也更容易炸 - -它不是“代码风格问题”,而是构建依赖关系本身不正确。 - ---- - -## 4. 建议方案 - -应改成标准 CMake target 依赖: - -1. editor 直接链接 `XCEngine` -2. 不再手写 `../build/engine/Release/XCEngine.lib` -3. 由 CMake 自己处理 Debug / Release / RelWithDebInfo 的库选择 -4. editor/include 路径和 link 关系都从 target 传播,而不是继续手写 build 输出路径 - ---- - -## 5. 验收标准 - -完成后至少应满足: - -1. `editor` 目标通过 `target_link_libraries(... XCEngine)` 链接引擎 -2. Debug editor 自动吃 Debug `XCEngine` -3. Release editor 自动吃 Release `XCEngine` -4. 修改 engine 后重新构建 editor 时,依赖关系正确生效 -5. 干净构建环境不依赖预先存在的某个磁盘库文件 - ---- - -## 6. 优先级 - -中高。 - -它未必第一时间阻塞面板 UI 开发,但会显著污染后续所有 viewport / renderer 联调结果,建议尽快修。 diff --git a/docs/used/Editor模块_宿主渲染与EngineRHI未统一导致Viewport纹理无法接入3.28.md b/docs/used/Editor模块_宿主渲染与EngineRHI未统一导致Viewport纹理无法接入3.28.md deleted file mode 100644 index b77f68e9..00000000 --- a/docs/used/Editor模块_宿主渲染与EngineRHI未统一导致Viewport纹理无法接入3.28.md +++ /dev/null @@ -1,94 +0,0 @@ -# Editor模块 宿主渲染与EngineRHI未统一导致Viewport纹理无法接入 -## 1. 问题定义 - -当前 editor 的窗口宿主渲染仍然是一套独立的原生 D3D12 路径: - -- [`editor/src/Application.h`](D:\Xuanchi\Main\XCEngine\editor\src\Application.h) -- [`editor/src/Application.cpp`](D:\Xuanchi\Main\XCEngine\editor\src\Application.cpp) -- [`editor/src/Platform/D3D12WindowRenderer.h`](D:\Xuanchi\Main\XCEngine\editor\src\Platform\D3D12WindowRenderer.h) - -这套路径只负责: - -- 创建 Win32 窗口交换链 -- 创建独立的 `ID3D12Device / ID3D12CommandQueue` -- 渲染 ImGui 主界面 - -而当前引擎的 RHI / Renderer 则是另一套独立设备与上下文体系。 - -这意味着后续即使 Renderer 能把 `SceneView/GameView` 渲染到离屏目标,editor 侧也没有稳定的统一设备桥接层把那张纹理安全地贴到 ImGui。 - ---- - -## 2. 当前现状 - -当前 `Application::InitializeWindowRenderer()` 直接创建原生 D3D12 宿主: - -- editor 只知道 `Platform::D3D12WindowRenderer` -- editor 并不持有 `RHIDevice / RenderContext / RenderSurface` -- `SceneViewPanel / GameViewPanel` 也没有可复用的 viewport host 对象 - -结果是: - -- editor 主界面渲染和引擎 Renderer 渲染仍然分裂 -- 未来 viewport 要么走不通 -- 要么被迫写一层高风险的 D3D12 私有纹理互拷/句柄桥接 -- 要么反向污染 Renderer,使其去适配 editor 私有宿主 - ---- - -## 3. 为什么这是重大缺陷 - -这不是“面板里还没把图显示出来”的小缺口,而是 viewport 接入的根部边界问题。 - -如果不先统一宿主层,后面很容易走成错误路线: - -- Editor 继续维护一套私有 D3D12 设备 -- Renderer 再维护一套引擎 RHI 设备 -- `SceneView` 和 `GameView` 为了显示纹理被迫做后端专用互操作 -- Vulkan / OpenGL 路径在 editor 中天然失去接入可能 - -这会直接破坏: - -- RHI 抽象边界 -- Renderer 的后端无关性 -- 后续 editor viewport 与 runtime 共用同一渲染链路的目标 - ---- - -## 4. 建议方案 - -正确方向应该是: - -1. editor 宿主层只保留“窗口 + ImGui 宿主”职责 -2. viewport 输出统一来自引擎 Renderer 的 `RenderSurface` -3. editor 增加专门的 viewport bridge / host 层,而不是把渲染实现塞进 panel -4. 该 bridge 层需要明确处理: - - 使用哪个 RHI backend - - 如何创建离屏 color/depth 目标 - - 如何把离屏结果暴露为 ImGui 可显示纹理 - - resize 生命周期 - - device/context 所有权 - -建议不要继续扩张 `D3D12WindowRenderer` 的职责。 - -它可以继续作为 editor 主窗口 UI 宿主,但不应成为 viewport 真实渲染实现本体。 - ---- - -## 5. 验收标准 - -完成后至少应满足: - -1. editor 可以不依赖私有 D3D12 纹理路径来显示 viewport -2. `SceneView` 和 `GameView` 都走统一的 viewport host 接口 -3. viewport 输出来自引擎 `RenderSurface` -4. editor 不需要因为切换 OpenGL / D3D12 / Vulkan 而重写面板逻辑 -5. Renderer 不需要反向依赖 editor 平台实现 - ---- - -## 6. 优先级 - -高。 - -在开始正式做 Scene/Game Viewport 之前必须先收敛这个边界,否则后面的接入实现会天然带着架构债。 diff --git a/docs/used/Editor模块_项目根路径仍绑定可执行目录阻塞真实场景与资源加载3.28.md b/docs/used/Editor模块_项目根路径仍绑定可执行目录阻塞真实场景与资源加载3.28.md deleted file mode 100644 index 7dde01f8..00000000 --- a/docs/used/Editor模块_项目根路径仍绑定可执行目录阻塞真实场景与资源加载3.28.md +++ /dev/null @@ -1,87 +0,0 @@ -# Editor模块 项目根路径仍绑定可执行目录阻塞真实场景与资源加载 -## 1. 问题定义 - -当前 editor 初始化 `EditorContext` 时,把 project path 直接设成了可执行文件目录: - -- [`editor/src/Application.cpp`](D:\Xuanchi\Main\XCEngine\editor\src\Application.cpp) - -具体行为是: - -- 通过 `GetExecutableDirectoryUtf8()` 拿 exe 目录 -- 用这个目录初始化 `EditorContext` -- `ProjectPanel` 和 `SceneManager::LoadStartupScene()` 都基于这个路径工作 - -这意味着 editor 当前没有“真实工程根目录”的概念。 - ---- - -## 2. 当前影响 - -基于当前实现: - -- `ProjectManager::Initialize()` 会把 `/Assets` 当作项目资源根 -- [`editor/src/Managers/ProjectManager.cpp`](D:\Xuanchi\Main\XCEngine\editor\src\Managers\ProjectManager.cpp) -- [`editor/src/Core/EditorWorkspace.h`](D:\Xuanchi\Main\XCEngine\editor\src\Core\EditorWorkspace.h) - -但 `projectPath` 现在是 `editor/bin/...` - -结果就是: - -- Project 面板看到的是 exe 目录下的 `Assets` -- Startup Scene 也是从 exe 目录下找 `Assets/Scenes/Main.xc` -- viewport 后面如果要加载真实 scene / material / texture / mesh,也会默认走错根目录 - -对“真正接引擎工程内容”来说,这是实打实的阻塞项。 - ---- - -## 3. 为什么这是重大缺陷 - -Scene/Game Viewport 一旦接 Renderer,就不再只是“显示一个空测试图”。 - -它需要基于当前工程: - -- 加载场景 -- 找到 mesh / material / texture / shader 资产 -- 正确解析 `Assets/...` 相对路径 - -如果 editor 的项目根仍然绑定在 exe 目录: - -- 资源加载结果会和实际工程目录脱钩 -- Editor 和运行时看到的资产树不是同一棵 -- 后续 Project 面板、Scene 保存、Viewport 渲染都会形成伪项目环境 - ---- - -## 4. 建议方案 - -建议尽快引入明确的工程根路径入口,而不是继续默认 exe 目录: - -1. `Application` 启动时明确解析 editor project root -2. 最少先支持: - - 命令行传入工程根 - - 或固定读取 workspace/project 配置 -3. `EditorContext / ProjectManager / SceneManager` 统一只认这一份 project root -4. `ProjectPanel`、startup scene、future viewport asset loading 全部复用同一来源 - -在没有真实 project root 之前,不建议开始做 viewport 里的正式资源接入。 - ---- - -## 5. 验收标准 - -完成后至少应满足: - -1. editor 能明确知道当前工程根目录,而不是推断 exe 目录 -2. Project 面板显示的是工程真实 `Assets` -3. Startup Scene 从真实工程根加载 -4. Scene/Game Viewport 后续使用的资源路径与 Project 面板一致 -5. Debug / Release / editor/bin 变化不会改变 editor 看到的项目内容 - ---- - -## 6. 优先级 - -高。 - -这条不解决,Viewport 接入后只会跑在一个“伪项目目录”里,后面越做越难回收。 diff --git a/docs/used/Editor设计与实现.md b/docs/used/Editor设计与实现.md deleted file mode 100644 index e4611bfd..00000000 --- a/docs/used/Editor设计与实现.md +++ /dev/null @@ -1,940 +0,0 @@ -# XCEngine Editor 设计分析与改进计划 - -## 1. 项目概述 - -### 1.1 当前状态 - -XCEngine Editor 是一个基于 ImGui 的 Unity 风格编辑器 UI,目前具备以下基础功能: - -- **渲染基础**: DirectX 12 + ImGui 对接 -- **面板框架**: Docking 布局系统 -- **UI 主题**: Unity 风格深色主题 - -### 1.2 对比参考 - -参考项目 Fermion Boson Editor 是一个功能完整的游戏引擎编辑器,具有: -- 完整的 3D Viewport -- 场景层级系统 -- 组件检查器 -- 资源浏览器 -- 材质编辑器 -- Play/Edit/Simulate 模式切换 - ---- - -## 2. 整体架构对比 - -### 2.1 架构图 - -**Fermion 架构**: -``` -Fermion::Application - └── LayerStack - ├── ImGuiLayer (overlay) - └── BosonLayer (editor layer) - ├── ViewportPanel (3D viewport + gizmos) - ├── SceneHierarchyPanel (entity tree) - │ └── InspectorPanel (embedded, component editor) - ├── ContentBrowserPanel (asset browser) - ├── MaterialEditorPanel (node-based material) - ├── SettingsPanel (editor settings) - ├── OverlayRenderPanel (physics debug) - └── MenuBarPanel (menu bar) -``` - -**XCEngine 架构**: -``` -UI::Application - └── Panels (独立 Panel 类) - ├── MenuBar - ├── HierarchyPanel - ├── InspectorPanel - ├── SceneViewPanel (占位符) - ├── GameViewPanel (占位符) - ├── ProjectPanel - └── ConsolePanel -``` - -### 2.2 关键差异 - -| 方面 | Fermion | XCEngine | -|------|---------|----------| -| Layer 系统 | Application → LayerStack → BosonLayer | Application 直接管理 Panels | -| Panel 集成 | SceneHierarchy 内嵌 InspectorPanel | 各 Panel 独立 | -| Scene 管理 | BosonLayer 持有 Scene/SceneRenderer | SceneManager 单例 | -| 渲染集成 | BosonLayer → SceneRenderer → Framebuffer | 无对接 | -| 事件系统 | Layer::onEvent 统一分发 | 各 Panel 独立处理 | - ---- - -## 3. 各面板详细分析 - -### 3.1 SceneHierarchyPanel / HierarchyPanel - -#### 功能对比 - -| 功能 | Fermion | XCEngine | 状态 | -|------|---------|----------|------| -| 树节点渲染 | ✅ `TreeNodeEx` + UUID | ✅ `TreeNodeEx` + EntityID | 完成 | -| 选中高亮 | ✅ | ✅ | 完成 | -| 展开/折叠 | ✅ 自动记忆状态 | ✅ `isOpen` 状态 | 完成 | -| 重命名 | ✅ 双击触发 | ⚠️ 有 bug | 需修复 | -| 拖拽重排 | ✅ 完整 + 循环检测 | ⚠️ 基本实现 | 需完善 | -| 右键菜单 | ✅ Create Child, Detach, Delete | ✅ Create, Rename, Delete, Copy/Paste, Duplicate | 需补充 | -| 空区域点击 | ✅ 取消选中 | ✅ 取消选中 | 完成 | -| 嵌入 Inspector | ✅ Panel 内嵌 InspectorPanel | ❌ 分离 | 架构差异 | - -#### XCEngine 问题详解 - -**问题 1: 重命名逻辑 bug** - -`HierarchyPanel.cpp` lines 106-113: -```cpp -if (!ImGui::IsItemActive() && ImGui::IsMouseClicked(0)) { - if (strlen(m_renameBuffer) > 0) { - sceneManager.RenameEntity(id, m_renameBuffer); - } - m_renaming = false; - m_renamingEntity = INVALID_ENTITY_ID; -} -``` - -问题:点击 InputText 外部时,`IsItemActive()` 已经变为 false,此时 `IsMouseClicked(0)` 也已经错过。 - -**问题 2: 缺少 "Create Child" 选项** - -右键菜单只有: -- Create → Empty Object, Camera, Light, Cube, Sphere, Plane -- Rename, Delete -- Copy, Paste, Duplicate - -缺少 "Create Child" 选项来在选中物体下创建子物体。 - -**问题 3: 没有 "Detach from Parent" 选项** - -无法将子物体脱离父物体,直接变为根层级。 - ---- - -### 3.2 InspectorPanel - -#### 功能对比 - -| 功能 | Fermion (1173 行) | XCEngine (94 行) | 状态 | -|------|-------------------|------------------|------| -| 组件显示 | ✅ 模板系统 | ⚠️ 只实现 2 种组件 (Engine 有完整组件库) | 需完善 | -| 动态增删组件 | ✅ 分类菜单 | ❌ 无 | 需实现 | -| 删除组件 | ✅ 右键 Remove | ❌ 无 | 需实现 | -| Transform 编辑 | ✅ `drawVec3Control` | ⚠️ `DragFloat3` 简陋 | 需改进 | -| Mesh 编辑 | ✅ Drag-drop + popup | ⚠️ 只有 InputText | 需改进 | -| 材质预览 | ✅ 缩略图渲染 | ❌ 无 | 需实现 | -| Camera 编辑 | ✅ 完整 | ❌ 无 | 需实现 | -| Light 编辑 | ✅ D/P/S Light | ❌ 无 | 需实现 | -| Physics 编辑 | ✅ 完整 | ❌ 无 | 需实现 | -| Script 编辑 | ✅ 字段编辑 | ❌ 无 | 需实现 | -| Entity Picking | ✅ 关节连接 | ❌ 无 | 需实现 | - -#### XCEngine 实现现状 - -`InspectorPanel.cpp` lines 44-92: -```cpp -void InspectorPanel::RenderComponent(Component* component) { - if (auto* transform = dynamic_cast(component)) { - ImGui::DragFloat3("##Position", transform->position, 0.1f); - ImGui::DragFloat3("##Rotation", transform->rotation, 1.0f); - ImGui::DragFloat3("##Scale", transform->scale, 0.1f); - } - else if (auto* meshRenderer = dynamic_cast(component)) { - // 只有 InputText,没有预览,没有拖拽 - } -} -``` - -**主要问题**: -1. Editor Inspector 面板只实现了 Transform 和 MeshRenderer 显示编辑,但 Engine 核心已有完整组件系统 -2. 没有 Add Component 菜单 -3. 没有 Remove Component 功能 -4. 没有 CollapsingHeader 折叠 -5. 布局简陋,没有缩进和对齐 - ---- - -### 3.3 ProjectPanel / ContentBrowserPanel - -#### 功能对比 - -| 功能 | Fermion | XCEngine | 状态 | -|------|---------|----------|------| -| 布局 | ✅ 两列 (树 + 网格) | ⚠️ 只有网格 | 需补充 | -| 文件夹树 | ✅ 左侧导航树 | ❌ 无 | 需实现 | -| 缩略图 | ✅ 加载 Texture | ⚠️ 彩色方块 | 需改进 | -| 材质预览 | ✅ 缩略图渲染 | ❌ 无 | 需实现 | -| 拖拽移动 | ✅ 完整实现 | ⚠️ stubbed | 需修复 | -| 双击打开 | ✅ 打开文件夹 | ✅ 实现 | 完成 | -| 面包屑导航 | ✅ 路径按钮 | ✅ 实现 | 完成 | -| 搜索过滤 | ✅ 实现 | ✅ 实现 | 完成 | - -#### XCEngine 问题详解 - -**问题: HandleDrop() 返回 false** - -`ProjectPanel.cpp` line 294: -```cpp -bool ProjectPanel::HandleDrop(const AssetItemPtr& targetFolder) { - return false; // STUBBED OUT - 拖拽移动功能不工作 -} -``` - -虽然 `RenderAssetItem()` 中有拖拽目标代码,但实际上 `HandleDrop()` 从未被调用,拖拽移动功能完全失效。 - ---- - -### 3.4 MenuBar - -#### 功能对比 - -| 功能 | Fermion (271 行) | XCEngine (55 行) | 状态 | -|------|------------------|-------------------|------| -| 实现方式 | ✅ 自定义窗口边框 | ✅ `BeginMainMenuBar` | 完成 | -| 菜单项 | ✅ 实际回调 | ❌ **全部 stubbed** | 需实现 | -| 项目操作 | ✅ new/open/save | ❌ 无 | 需实现 | -| 创建菜单 | ✅ Material/Texture | ❌ 无 | 需实现 | -| 窗口控制 | ✅ 有 (已注释) | ❌ 无 | 可选 | - -#### XCEngine 问题详解 - -**所有菜单项都是空的**: -```cpp -// MenuBar.cpp -if (ImGui::MenuItem("New Scene", "Ctrl+N")) {} // 空 -if (ImGui::MenuItem("Open Scene", "Ctrl+O")) {} // 空 -if (ImGui::MenuItem("Save Scene", "Ctrl+S")) {} // 空 -// ... 所有项都是空的 -``` - ---- - -### 3.5 ConsolePanel - -#### 功能对比 - -| 功能 | Fermion (84 行) | XCEngine (70 行) | 状态 | -|------|------------------|-------------------|------| -| 日志显示 | ✅ 彩色 + 前缀 | ✅ 彩色 + 前缀 | 完成 | -| 命令输入 | ✅ 底部输入框 | ❌ 无 | 需补充 | -| 命令处理 | ✅ clear/help | ❌ 无 | 需实现 | -| 自动滚动 | ✅ 新日志时滚动 | ⚠️ flag 设置但逻辑不完整 | 需改进 | - -#### XCEngine 优点 - -- 测试按钮注入日志功能完善 -- 日志级别颜色区分正确 -- 基本显示功能正常 - ---- - -### 3.6 SceneViewPanel / ViewportPanel - -#### 功能对比 - -| 功能 | Fermion (356 行) | XCEngine (54 行) | 状态 | -|------|------------------|-------------------|------| -| 3D 渲染 | ✅ Framebuffer | ❌ **只有网格** | 需重构 | -| 相机 | ✅ EditorCamera | ❌ 无 | 需实现 | -| 鼠标拾取 | ✅ readPixel | ❌ 无 | 需实现 | -| Gizmo | ✅ ImGuizmo | ❌ 无 | 需实现 | -| 工具栏 | ✅ Q/W/E/R | ❌ 无 | 需实现 | -| Play 控制 | ✅ Play/Stop/Pause/Step | ❌ 无 | 需实现 | -| 状态切换 | ✅ Edit/Play/Simulate | ❌ 无 | 需实现 | - -#### XCEngine 实现现状 - -```cpp -// SceneViewPanel.cpp -void SceneViewPanel::RenderGrid() { - // 只画了 50px 网格背景 - for (float x = 0; x < canvasSize.x; x += gridSize) { - drawList->AddLine(...); - } - // 没有任何 3D 渲染 -} -``` - -**问题**: 完全没有任何 3D 渲染能力,只是占位符。 - ---- - -### 3.7 GameViewPanel - -| 功能 | Fermion | XCEngine | 状态 | -|------|---------|----------|------| -| 游戏视图 | ✅ Framebuffer 渲染 | ❌ **纯占位符** | 需重构 | -| 状态栏 | ✅ Play/Simulate 指示 | ❌ 无 | 需实现 | - ---- - -## 4. XCEngine 缺失的面板 - -XCEngine Editor 只有 7 个面板,而 Fermion 有 10 个。以下是缺失的面板: - -### 4.1 SettingsPanel - 编辑器设置 - -**Fermion 功能**: -- Renderer Info: 相机设置、设备信息、帧时间统计 -- Environment Settings: Skybox、阴影、IBL、HDR 加载、环境光强度、法线强度 -- Debug Settings: 渲染模式 (Forward/Deferred Hybrid)、GBuffer 调试、深度视图、物理调试开关、无限网格、轮廓设置 -- Project Settings: 项目信息、默认字体配置 - -**XCEngine 状态**: 无此面板 - -**实现建议**: 可以作为 InspectorPanel 的扩展,或者单独的设置浮窗 - ---- - -### 4.2 OverlayRenderPanel - 调试可视化 - -**Fermion 功能**: -```cpp -class OverlayRenderPanel { - void render(const Context &ctx) const; - void renderPhysicsColliders(); // 物理碰撞体可视化 - void renderPhysics2DColliders(); // 2D 碰撞体 - void renderPhysics3DColliders(); // 3D 碰撞体 - void renderJoints(); // 关节连接可视化 - void renderSelectedEntityOutline(); // 选中物体轮廓 -}; -``` - -**功能说明**: -- 在 Viewport 上叠加渲染物理调试信息 -- 2D 碰撞体: Box、Circle、BoxSensor、CircleSensor -- 3D 碰撞体: Box、Sphere、Capsule、Mesh (三角形线框) -- 关节: RevoluteJoint (锚点圆)、DistanceJoint (锚点圆 + 线段) -- 选中物体白色轮廓 - -**XCEngine 状态**: 无此面板,但可以在 SceneViewPanel 完善后添加 - ---- - -### 4.3 MaterialEditorPanel - 材质编辑器 - -**Fermion 功能**: -- 基于 `ax::NodeEditor` (imnodes) 的节点式材质编辑器 -- PBR Output 节点 (ID=1) 固定在右侧 -- Texture 节点可动态创建 -- 引脚连接系统 (Pin ID 约定: `nodeID*100 + pinIndex`) -- 材质预览渲染 (`MaterialPreviewRenderer`) -- 材质编译 (`compileMaterial()`) - -**节点系统**: -```cpp -// PBR Output 节点输入引脚 -enum PBRInputPin { - Albedo = 1, // 引脚 ID = 101 - Normal = 2, // 引脚 ID = 102 - Metallic = 3, // 引脚 ID = 103 - Roughness = 4,// 引脚 ID = 104 - AO = 5 // 引脚 ID = 105 -}; - -// Texture 节点输出引脚 -// 引脚 ID = nodeID * 100 + 1 -``` - -**UI 布局**: -``` -┌─────────────────────────────────────────────────────┐ -│ [Texture Node] ──────→ [Albedo] │ -│ [Texture Node] ──────→ [Normal] │ -│ [Texture Node] ──────→ [PBR Output] → [Material] │ -│ → [Metallic] │ -│ → [Roughness] │ -│ → [AO] │ -└─────────────────────────────────────────────────────┘ -``` - -**XCEngine 状态**: 无此面板,需要独立开发 - -**实现难度**: 高,需要 imnodes 库集成和材质编译系统 - ---- - -### 4.4 TextureConfigPanel - 纹理配置 - -**Fermion 功能**: -```cpp -class TextureConfigPanel { - void loadTexture(const std::filesystem::path &ftexPath); - void loadTextures(const vector &ftexPaths); - void saveTexture(TextureConfigData &data); - void saveAllTextures(); - - struct TextureConfigData { - std::filesystem::path FtexPath; - std::string Name; - TextureAssetSpecification Spec; - bool Modified = false; - }; - - bool m_BatchMode = false; // 批量配置模式 - TextureAssetSpecification m_BatchSpec; -}; -``` - -**功能**: -- 批量纹理加载 -- 纹理规格编辑 (格式、过滤器、Mipmaps 等) -- 保存/导出功能 - -**XCEngine 状态**: 无此面板,ProjectPanel 只做文件浏览 - ---- - -### 4.5 AssetManagerPanel - 资源注册表 - -**Fermion 功能**: -```cpp -class AssetManagerPanel { - void onImGuiRender(); - // 显示所有注册的资源 - // 可搜索、可过滤 - // Asset 类型过滤 - // Load/Unload 按钮 - // 显示: asset path, type, handle ID, loading status -}; -``` - -**功能**: -- 查看所有已注册资产 -- 按类型过滤 (Texture, Mesh, Material, etc.) -- 搜索功能 -- 手动加载/卸载资产 -- 显示资源路径、类型、句柄 ID、加载状态 - -**XCEngine 状态**: 无此面板,ProjectPanel 只做文件系统浏览 - ---- - -## 5. 核心系统差异分析 - -### 5.1 Application 架构对比 - -**Fermion Application**: -```cpp -class Application { - std::unique_ptr m_window; - std::unique_ptr m_imGuiLayer; - std::unique_ptr m_layerStack; - - void pushLayer(std::unique_ptr layer); // 在 ImGui 之前更新 - void pushOverlay(std::unique_ptr overlay); // 在 ImGui 之后更新 -}; -``` - -**XCEngine Application**: -```cpp -class Application { - HWND m_hwnd; - ID3D12Device* m_device; - // 直接管理所有 Panels - std::unique_ptr m_menuBar; - std::unique_ptr m_hierarchyPanel; - // ... -}; -``` - -**关键差异**: -1. Fermion 使用 Layer/Overlay 模式,事件按倒序传播 -2. XCEngine 直接管理 Panels,缺乏层次化设计 -3. Fermion 有 ImGuiLayer 专门处理 ImGui 事件阻塞 -4. XCEngine 需要手动在 Panel 内部处理焦点判断 - ---- - -### 5.2 SceneManager vs Scene/EntityManager - -**XCEngine SceneManager**: -```cpp -class SceneManager { - std::unordered_map m_entities; - std::vector m_rootEntities; - - EntityID CreateEntity(name, parent); - void DeleteEntity(id); - void MoveEntity(id, newParent); - EntityID CopyEntity(id); - EntityID PasteEntity(parent); -}; -``` - -**Fermion Scene + EntityManager**: -```cpp -class Scene { - std::unique_ptr m_entityManager; - std::unique_ptr m_physicsWorld2D; - std::unique_ptr m_physicsWorld3D; -}; - -class EntityManager { - entt::registry m_registry; // EnTT ECS - std::unordered_map m_entityMap; -}; -``` - -**XCEngine 实际情况**: -1. ✅ 有 Scene 类和 SceneManager (见 engine/include/XCEngine/Scene/) -2. ❌ 没有物理世界集成 -3. ✅ 有 Component/GameObject 系统,但非 EnTT ECS (使用 dynamic_cast) -4. ✅ EntityID 是 uint64_t 递增整数,不是 UUID -5. ❌ Editor 没有场景状态 (Edit/Play/Simulate) 切换机制 - ---- - -### 5.3 BosonUI 自定义控件 - -Fermion 定义了 `ui` 命名空间的自定义控件,XCEngine 没有: - -**Fermion ui 控件**: -```cpp -namespace ui { - // 向量编辑器 (带 XYZ 标签和 reset 按钮) - void drawVec3Control(const char* label, glm::vec3& values, - float resetValue = 0.0f, float columnWidth = 120.0f); - - // 浮点数编辑器 - bool drawFloatControl(const char* label, float& value, - float columnWidth = 120.0f, float speed = 0.1f); - - // 复选框 - void drawCheckboxControl(const char* label, bool& value, float width = 120.0f); - - // 自定义 Popup 包装 - bool BeginPopup(const char* name); - void EndPopup(); -} -``` - -**XCEngine**: 直接使用原生 ImGui API,没有封装 - ---- - -### 5.4 选择系统差异 - -**Fermion 选择 + Entity Picking**: -```cpp -// InspectorPanel 实体拾取用于关节连接 -bool m_entityPickingActive = false; -Entity m_pickingTargetEntity; - -// 当用户在 Viewport 点击时 -void deliverPickedEntity(Entity pickedEntity) { - if (m_pickingTargetEntity.hasComponent()) { - m_pickingTargetEntity.getComponent() - .connectedBodyID = pickedEntity.getUUID(); - } -} -``` - -**XCEngine SelectionManager**: -```cpp -class SelectionManager { - Event OnSelectionChanged; - EntityID GetSelectedEntity(); - void SetSelectedEntity(EntityID entity); -}; -``` - -**XCEngine 问题**: 没有 Entity Picking 机制,无法在编辑关节时拾取场景中的物体 - ---- - -### 5.5 资源系统对比 - -**Fermion 资源系统**: -``` -AssetManager (抽象基类) -├── EditorAssetManager (编辑器资源管理) -│ └── importAsset() 导入资源 -└── RuntimeAssetManager (运行时资源管理) - └── getAsset() 获取资源 - -AssetRegistry (资源注册表) -AssetHandle (资源句柄) -``` - -**XCEngine 资源系统**: -``` -ProjectManager (文件系统浏览) -└── GetCurrentItems() 返回 AssetItem 列表 - -AssetItem (只包含文件信息) -├── name -├── type -├── isFolder -└── fullPath -``` - -**XCEngine 问题**: -1. 没有真正的资源导入流程 -2. 没有 AssetRegistry 资源注册表 -3. 没有 AssetHandle 统一资源句柄 -4. 没有资源缓存和加载/卸载管理 - ---- - -## 7. 核心问题总结 - -### 7.1 已完成功能 - -| 面板 | 完成度 | 说明 | -|------|--------|------| -| HierarchyPanel | 70% | 基本树视图、选中、重命名、复制、拖拽 | -| ProjectPanel | 60% | 文件浏览、网格显示、导航,但拖拽移动失效 | -| ConsolePanel | 60% | 日志显示,缺少命令输入 | -| MenuBar | 30% | 菜单结构存在,但所有项 stubbed | -| InspectorPanel | 20% | 只实现 Transform 和 MeshRenderer 编辑,但 Engine 核心有完整组件库 | -| SceneViewPanel | 5% | 纯占位符 | -| GameViewPanel | 5% | 纯占位符 | - -### 7.2 高优先级问题 - -1. **MenuBar 所有菜单项 stubbed** - 无法进行任何 Scene/Project 操作 -2. **Inspector 没有 Add/Remove Component** - 无法添加/删除组件 -3. **SceneViewPanel 完全是占位符** - 无法预览场景 -4. **Project 拖拽移动失效** - HandleDrop() 返回 false -5. **缺少 5 个重要面板** - SettingsPanel, OverlayRenderPanel, MaterialEditorPanel, TextureConfigPanel, AssetManagerPanel - -### 7.3 RHI 限制 - -当前 RHI 模块状态: -- D3D12: PipelineState 返回 nullptr,无法创建 3D 渲染 Pipeline -- OpenGL: Framebuffer、PipelineState、Shader 正常工作 - -**影响**: 在 RHI 重构完成前,3D Viewport 无法实现。 - ---- - -## 8. 改进计划 - -### 8.1 第一阶段: 完善现有面板功能 - -**目标**: 不依赖 RHI,完善 Editor UI 功能 - -#### 8.1.1 MenuBar 菜单功能对接 - -``` -优先级: 高 -预计工作量: 1-2 天 -依赖: 无 -``` - -**需要实现**: -- File 菜单: New Scene, Open Scene, Save Scene, Exit -- Edit 菜单: Undo, Redo (需要命令历史系统), Cut, Copy, Paste -- View 菜单: Reset Layout -- Help 菜单: About - -**实现方案**: -```cpp -// MenuBar.cpp -if (ImGui::MenuItem("New Scene", "Ctrl+N")) { - m_onNewSceneCallback(); // 通过回调连接到 BosonLayer -} -``` - -#### 8.1.2 Inspector Add/Remove Component - -``` -优先级: 高 -预计工作量: 2-3 天 -依赖: 组件系统完善 -``` - -**需要实现**: -1. 添加 "Add Component" 按钮 -2. 分类组件菜单 (2D, 3D, Other) -3. 右键 "Remove Component" 选项 - -**实现方案**: -```cpp -// InspectorPanel.cpp -void InspectorPanel::drawComponent(Entity entity, UIFunction uiFunction) { - // 右键菜单 - if (ImGui::BeginPopupContextItem("ComponentSettings")) { - if (ImGui::MenuItem("Remove Component")) - entity.removeComponent(); - ImGui::EndPopup(); - } -} - -// Add Component 按钮 -if (ImGui::Button("Add Component")) { - ImGui::OpenPopup("AddComponentMenu"); -} -// 分类显示组件列表 -``` - -#### 8.1.3 修复 Project 拖拽移动 - -``` -优先级: 中 -预计工作量: 0.5 天 -依赖: 无 -``` - -**问题**: `HandleDrop()` 返回 false,从未被调用 - -**修复方案**: 在双击打开文件夹时,检查是否正在拖拽,如果是则执行移动操作。 - ---- - -### 8.2 第二阶段: Inspector 组件系统完善 - -**目标**: 实现完整的组件编辑功能 - -#### 8.2.1 Inspector 组件编辑支持 - -Engine 核心已有完整组件系统,Editor Inspector 需要添加对这些组件的编辑支持: - -| 组件 | Engine 状态 | Inspector 编辑复杂度 | 说明 | -|------|------------|---------------------|------| -| TransformComponent | ✅ | 低 | 需改进 UI (drawVec3Control) | -| MeshRendererComponent | ✅ | 中 | 需添加拖拽、预览 | -| CameraComponent | ✅ | 中 | 投影、FOV、近远裁剪 | -| DirectionalLightComponent | ✅ | 低 | 颜色、强度 | -| PointLightComponent | ✅ | 低 | 颜色、强度、范围 | -| SpotLightComponent | ✅ | 中 | 颜色、强度、范围、角度 | -| Rigidbody2DComponent | ✅ | 高 | 物理引擎对接 | -| Rigidbody3DComponent | ✅ | 高 | 物理引擎对接 | -| Collider2D/3D | ✅ | 高 | 碰撞体 | -| SpriteRendererComponent | ✅ | 中 | 2D 精灵 | -| CircleRendererComponent | ✅ | 低 | 圆形渲染 | -| TextComponent | ✅ | 中 | 文本渲染 | -| ScriptComponent | ✅ | 高 | 脚本字段编辑 | - -#### 8.2.2 Transform UI 改进 - -**当前**: -```cpp -ImGui::DragFloat3("##Position", transform->position, 0.1f); -``` - -**改进方案** (参考 Fermion `ui::drawVec3Control`): -- 添加 XYZ 标签 -- 添加 Per-axis reset 按钮 -- 统一对齐和缩进 -- 添加合适的刻度 (speed) - ---- - -### 8.3 第三阶段: 场景序列化 - -**目标**: 实现 Scene Save/Load - -``` -优先级: 中 -预计工作量: 2-3 天 -依赖: Scene 序列化框架 (Engine Scene 类已有基础 Serialize/Deserialize) -``` - -**需要实现**: -- Scene 序列化到文件 (Engine 核心已有基础) -- Scene 反序列化 -- 资源路径管理 -- Editor/Runtime 场景分离 - ---- - -### 8.4 第四阶段: 3D Viewport (依赖 RHI) - -**目标**: 实现完整的 3D Viewport - -``` -优先级: 高 (但依赖 RHI) -预计工作量: 2-4 周 -依赖: RHI 重构完成 -``` - -#### 8.4.1 OpenGL Backend 优先方案 - -由于 D3D12 PipelineState 当前 broken,可以先用 OpenGL Backend 实现 Viewport: - -**优势**: -- OpenGL Framebuffer 正常工作 -- OpenGL PipelineState 正常工作 -- Shader uniform 设置正常工作 - -**需要工作**: -1. EditorCamera 实现 (Orbit/Pan/Zoom) -2. SceneRenderer 中间层 -3. ViewportPanel Framebuffer 渲染 -4. 鼠标拾取 (readPixel from entity ID attachment) -5. ImGuizmo 集成 - -#### 8.4.2 实现步骤 - -**Step 1: EditorCamera** (3-5 天) -```cpp -class EditorCamera { - // Orbit 模式 - void orbit(float deltaX, float deltaY); - void pan(float deltaX, float deltaY); - void zoom(float delta); - - // FPS 模式 - void moveForward(float delta); - void moveRight(float delta); -}; -``` - -**Step 2: SceneRenderer 中间层** (1 周) -```cpp -class ViewportRenderer { - std::shared_ptr m_framebuffer; - std::shared_ptr m_scene; - - void beginScene(const EditorCamera& camera); - void submitMesh(MeshComponent& mesh, glm::mat4 transform); - void endScene(); -}; -``` - -**Step 3: ViewportPanel 集成** (3-5 天) -```cpp -void ViewportPanel::onImGuiRender() { - // 渲染到 framebuffer - m_framebuffer->bind(); - m_viewportRenderer->beginScene(m_editorCamera); - // ... 提交场景物体 - m_viewportRenderer->endScene(); - m_framebuffer->unbind(); - - // 显示到 ImGui - ImGui::Image(m_framebuffer->getColorTexture(), ...); - - // 鼠标拾取 - int entityID = m_framebuffer->readPixel(1, pixelX, pixelY); -} -``` - -**Step 4: ImGuizmo 集成** (2-3 天) -```cpp -ImGuizmo::SetOrthographic(isOrthographic); -ImGuizmo::SetRect(viewportBounds); -ImGuizmo::Manipulate( - cameraView, cameraProjection, - operation, LOCAL, - glm::value_ptr(worldTransform) -); -``` - ---- - -### 8.5 第五阶段: Play/Edit 模式 - -**目标**: 实现场景状态切换 - -``` -优先级: 中 -预计工作量: 1-2 周 -依赖: 3D Viewport + 组件系统 -``` - -**状态机**: -```cpp -enum class SceneState { Edit, Play, Simulate }; - -void BosonLayer::onScenePlay() { - m_runtimeScene = Scene::copy(m_editorScene); - m_activeScene = m_runtimeScene; - m_activeScene->onRuntimeStart(); -} - -void BosonLayer::onSceneStop() { - m_activeScene->onRuntimeStop(); - m_activeScene = m_editorScene; -} -``` - ---- - -## 9. 技术债务 - -### 9.1 架构问题 - -1. **Editor 和 Engine 使用两套不同的 GameObject/Component 系统** - - **Editor** (`editor/src/Core/GameObject.h`): 简化版,使用 `float position[3]` 数组存储变换 - - **Engine** (`engine/include/XCEngine/Components/`): 完整 ECS,使用 `TransformComponent` 组件 - - **后果**: Editor 无法直接使用 Engine 的组件系统,需要重新实现 - - **建议**: Editor 应复用 Engine 的 Components::GameObject/Scene 系统 - -2. **Panel 独立设计 vs Layer 组合** - - Fermion: BosonLayer 组合所有面板,共享 Context - - XCEngine: 各 Panel 独立,通过 SceneManager 单例通信 - -3. **SceneManager 单例 vs Scene 指针传递** - - Editor 使用简化版 SceneManager,与 Engine 的 Scene 类不兼容 - - 建议: 考虑使用 Context 模式传递 Scene 引用 - -4. **缺少 BosonUI 自定义控件封装** - - Fermion 有 ui::drawVec3Control, ui::drawFloatControl 等 - - XCEngine 直接使用原生 ImGui API,代码重复 - -### 9.2 代码质量问题 - -1. **注释掉的代码** - 建议清理 -2. **TODO/FIXME** - 建议跟踪 -3. **Magic numbers** - 建议定义常量 - ---- - -## 10. 建议实施路线 - -### 短期 (1-2 周) - -``` -□ MenuBar 菜单功能对接 -□ Inspector Add/Remove Component -□ 修复 Project 拖拽移动 -□ Transform UI 改进 -□ Console 命令输入 -``` - -### 中期 (1-2 月,RHI 完成后) - -``` -□ EditorCamera 实现 -□ SceneRenderer 中间层 -□ ViewportPanel 3D 渲染 -□ 鼠标拾取 -□ ImGuizmo 集成 -□ Play/Edit/Simulate 模式 -``` - -### 长期 - -``` -□ 完整的 Inspector 组件编辑界面 (Engine 核心组件已有) -□ 场景序列化 -□ 资源导入系统 -□ 材质编辑器 -□ 动画编辑器 -□ OverlayRenderPanel (物理调试可视化) -□ TextureConfigPanel (纹理配置) -□ AssetManagerPanel (资源注册表) -□ SettingsPanel (编辑器设置) -□ Play/Edit/Simulate 模式切换 -``` - ---- - -## 11. 参考资料 - -- Fermion Boson Editor 源码: `参考/Fermion/Boson/` -- XCEngine Editor 源码: `editor/` -- RHI 模块: `engine/include/XCEngine/RHI/` - ---- - -*文档生成时间: 2026-03-24* -*最后更新: 2026-03-24 (更正 Component 系统描述,补充 Editor/Engine 架构问题)* diff --git a/docs/used/Editor资源缓存.png b/docs/used/Editor资源缓存.png deleted file mode 100644 index 255d23d4..00000000 Binary files a/docs/used/Editor资源缓存.png and /dev/null differ diff --git a/docs/used/Editor重构3.26.md b/docs/used/Editor重构3.26.md deleted file mode 100644 index 01e0b7c3..00000000 --- a/docs/used/Editor重构3.26.md +++ /dev/null @@ -1,165 +0,0 @@ -# Editor 重构 3.26 - -## 当前判断 - -截至 2026-03-27,如果只看 editor 的 UI 架构与编辑器壳层整理,不把 `Viewport / RHI` 和 `Scene / Game` 的真实内容算进去,这一轮重构大约已经完成 **96%**。 - -如果把“编辑器整体可用度”一起算进去,则仍然没有结束,因为: - -- `Scene` panel 仍然是空壳。 -- `Game` panel 仍然是空壳。 -- `Viewport` 尚未跟随 RHI 重构完成回归。 - -所以现在的真实状态是: - -- **UI 架构已经基本收稳** -- **编辑器产品功能还没有做完** - -## 已经完成的核心重构 - -### 1. 分层已经基本建立 - -当前 editor 已经形成比较稳定的职责划分: - -- `UI` 负责主题、token、共享控件、popup、property-grid。 -- `Actions` 负责菜单、快捷键、右键菜单、按钮动作的共享路由。 -- `Commands` 负责 scene/project/entity/component 的编辑行为。 -- `Layout` 负责 dock host、默认布局、布局重置与持久化。 -- `Panels` 逐步退化为窗口壳层。 -- `Core / Managers` 负责 editor context、selection、undo、scene、project 等共享状态。 -- `ComponentEditors` 负责 inspector 中各组件的编辑器与注册体系。 - -这说明 editor 已经从“每个 panel 自己堆逻辑”的结构,切换到“共享层驱动”的结构。 - -### 2. 共享 UI 基础层已经落稳 - -已经把大量原先散落在 panel 内部的视觉和控件逻辑收口到 shared UI 层,包括: - -- 主题和样式 token -- panel chrome -- toolbar/search/button -- popup / modal state -- empty state -- asset tile -- hierarchy tree node -- component section -- property grid / scalar / vector 编辑控件 - -这部分的意义是:后面再调 editor 外观,不应该回到 panel 内部逐个修补。 - -### 3. Action 路由已经成型 - -当前已形成两条主路由: - -- `MainMenuActionRouter` -- `EditActionRouter` - -并且 `Hierarchy / Project / Inspector / Console / MenuBar` 的主要菜单和快捷键语义已经接入共享 action/router。 - -当前已经做到: - -- `Edit` 菜单不再只是一套写死逻辑,而是跟随 active action route 切换。 -- menu / shortcut / context menu / toolbar 的动作开始共用同一套 action 定义。 -- rename、popup、about、exit、reset layout 等交互已不再零散写在 panel 内部。 - -### 4. Commands 层已经承担主要编辑语义 - -当前主要编辑行为已进入 command 层: - -- 新建 / 打开 / 保存场景 -- 脏场景 fallback 保存 -- 创建 / 删除 / 复制 / 粘贴 / duplicate / rename / reparent entity -- 创建文件夹 / 删除资源 / 打开资源 / 移动资源 -- 添加 / 删除组件 - -同时,undo / dirty / selection reset 等关键边界,已经尽量不再散落在 panel。 - -### 5. Dock / Workspace / Application 壳层已收口 - -当前已经完成: - -- `EditorWorkspace` 统一 panel 装配与生命周期调度 -- `DockLayoutController` 统一 dockspace 和 layout reset -- ImGui layout 持久化到项目 `.xceditor/imgui_layout.ini` -- `Application.cpp` 中的窗口、renderer、ImGui session、layer attach/detach 已完成明显拆分 - -这说明 editor 顶层壳层已经不再像之前那样把 UI、layout、backend、panel 生命周期混在一起。 - -### 6. Inspector 的 ComponentEditor 注册体系已稳定 - -当前 inspector 不再直接硬编码全部组件逻辑,而是通过: - -- `IComponentEditor` -- `ComponentEditorRegistry` -- 各具体 `Transform / Camera / Light` component editor - -来统一: - -- 组件显示 -- Add Component 菜单构建 -- 组件可添加性 / 可删除性判断 - -这部分已经是后续扩展自定义组件 inspector 的正确落点。 - -### 7. 回归测试基础已补齐 - -当前已新增 `editor_tests`,并已覆盖关键 editor 行为,包括: - -- hierarchy edit route 的 copy / paste / duplicate / delete / rename request -- project edit route 的 open / back / delete -- scene dirty save + load 后的 selection / undo reset -- reparent 时的 parent 切换、cycle 拦截与 world transform 保持 -- main menu 的 exit / reset layout / about popup request -- hierarchy rename helper -- project create-folder / move-asset / open-folder helper - -本轮收尾继续补充后,测试会进一步覆盖: - -- clean scene 下的新建场景重置行为 -- clean scene 下 fallback save 的 no-op 路径 -- project move-asset 的非法输入保护 - -## 当前仍然剩下什么 - -### 架构收尾项 - -1. 继续压缩少量 panel 本地瞬时状态 - 目标是把还能共享的 popup / router / state 再往 shared 层收一点。 - -2. 继续补 editor 回归测试 - 重点补 command/router 边界,而不是 UI 像素测试。 - -3. 为 viewport 回归保留 editor shell 接口 - 但暂时不把 RHI/renderer 接进来。 - -### 暂缓项 - -以下内容不计入当前这一轮 UI 架构收尾: - -- `Scene` panel 真正内容 -- `Game` panel 真正内容 -- `Viewport` 渲染接入 - -原因不是不做,而是这些工作和 renderer / RHI 重构直接绑定,应该放到后续阶段。 - -## 本轮新增文档 - -本次已补正式架构说明: - -- `docs/plan/Editor架构说明.md` - -这个文档用于明确: - -- 各层职责边界 -- 允许依赖方向 -- panel / action / command / manager / component editor 的落点规则 -- event bus、undo、dirty、selection 的统一约定 - -## 阶段性结论 - -当前 editor 可以明确地说: - -- **UI 架构层面已经基本重构完成** -- **剩余主要是封口、验证和后续 viewport 接入准备** - -也就是说,后面不应该再回到“看到一个 panel 问题就地补一段特殊逻辑”的方式,而应该继续沿着现有分层做增量完善。 diff --git a/docs/used/Editor重构计划.md b/docs/used/Editor重构计划.md deleted file mode 100644 index 4cccb40d..00000000 --- a/docs/used/Editor重构计划.md +++ /dev/null @@ -1,590 +0,0 @@ -# Editor 重构计划 - -## 一、当前问题总结 - -### 1.1 架构问题 - -| 问题 | 位置 | 严重程度 | -|------|------|----------| -| `Panel` 滥用 `Core::Layer` 继承 | `editor/src/panels/Panel.h` | P0 | -| 单例模式滥用 | 所有 Manager 类 | P1 | -| 直接使用 `GameObject*` 裸指针 | 各 Panel 文件 | P1 | -| `EditorSceneManager` 职责过重 | `editor/src/Managers/SceneManager.h` | P1 | -| 无统一事件总线 | 整体架构 | P1 | - -### 1.2 功能缺失 - -| 功能 | 当前状态 | -|------|----------| -| Undo/Redo | 完全缺失 | -| 场景序列化/反序列化 | 完全缺失 | -| 资源引用系统 | 完全缺失 | -| Play/Edit 模式切换 | 只有框架 | -| Transform Gizmos | 完全缺失 | -| Inspector 组件扩展 | 仅支持 TransformComponent | -| 场景/游戏视图相机控制 | 完全缺失 | - -### 1.3 代码质量 - -- 硬编码路径(崩溃日志路径) -- 调试日志残留(InspectorPanel 中大量无意义的 Debug 日志) -- 内存泄漏风险(raw new 无智能指针) -- 异常处理硬编码 - ---- - -## 二、重构目标 - -### 2.1 短期目标(原型验证) - -- [ ] 解决 P0 级架构问题 -- [ ] 实现基础 Undo/Redo -- [ ] 实现场景序列化 -- [ ] 分离引擎核心与编辑器状态 - -### 2.2 中期目标(功能完善) - -- [ ] 实现 Transform Gizmos -- [ ] 实现 Inspector 组件扩展机制 -- [ ] 实现场景/游戏视图相机控制 -- [ ] 实现 Play/Edit 模式切换 -- [ ] 实现资源引用系统 - -### 2.3 长期目标(生产可用) - -- [ ] 编辑器配置序列化 -- [ ] 插件系统 -- [ ] 多场景编辑 -- [ ] 性能优化 - ---- - -## 三、重构方案 - -### 3.1 架构重构 - -#### 3.1.1 Panel 独立化 - -**现状**: -```cpp -class Panel : public Core::Layer { // ❌ 编辑器面板不是游戏层级 -``` - -**重构**: -```cpp -namespace XCEngine { -namespace Editor { - -class Panel { -public: - Panel(const std::string& name) : m_name(name), m_isOpen(true) {} - virtual ~Panel() = default; - - virtual void OnAttach() {} - virtual void OnDetach() {} - virtual void OnUpdate(float dt) {} - virtual void OnEvent(void* event) {} - virtual void Render() = 0; - - bool IsOpen() const { return m_isOpen; } - void SetOpen(bool open) { m_isOpen = open; } - -protected: - std::string m_name; - bool m_isOpen; -}; - -} // namespace Editor -} // namespace XCEngine -``` - -**理由**:`Core::Layer` 设计用于游戏层级系统(UI/World/Background),编辑器面板只是 UI 控件,不应继承它。 - -#### 3.1.2 单例改造为依赖注入 - -**现状**: -```cpp -class SelectionManager { -public: - static SelectionManager& Get() { ... } // ❌ 单例 -}; -``` - -**重构**: -```cpp -class IEditorContext { -public: - virtual ~IEditorContext() = default; - virtual ISelectionManager& GetSelectionManager() = 0; - virtual ISceneManager& GetSceneManager() = 0; - virtual IProjectManager& GetProjectManager() = 0; - virtual IUndoRedoSystem& GetUndoRedoSystem() = 0; -}; - -class EditorLayer { -public: - void SetContext(std::shared_ptr context) { m_context = context; } - -private: - std::shared_ptr m_context; -}; -``` - -#### 3.1.3 引入事件总线 - -**新增 `EditorEventBus`**: -```cpp -class EditorEventBus { -public: - template - void Subscribe(std::function handler); - template - void Unsubscribe(uint64_t id); - template - void Publish(const T& event); - -private: - std::unordered_map>> m_handlers; - std::unordered_map m_nextId; -}; -``` - -**定义事件类型**: -```cpp -struct SelectionChangedEvent { - std::vector selectedObjects; -}; - -struct EntityCreatedEvent { - GameObjectID entityId; -}; - -struct EntityDestroyedEvent { - GameObjectID entityId; -}; -``` - -#### 3.1.4 GameObject 生命周期管理 - -**现状**: -```cpp -::XCEngine::Components::GameObject* m_selectedGameObject = nullptr; // ❌ 裸指针 -``` - -**重构**: -```cpp -using GameObjectHandle = ResourceHandle; - -struct SelectionState { - std::vector selectedObjects; - GameObjectHandle primarySelection; // 最后选中的 -}; -``` - ---- - -### 3.2 功能重构 - -#### 3.2.1 Undo/Redo 系统 - -**Command Pattern 实现**: -```cpp -class ICommand { -public: - virtual ~ICommand() = default; - virtual void Execute() = 0; - virtual void Undo() = 0; - virtual std::string GetName() const = 0; -}; - -class CreateEntityCommand : public ICommand { -public: - CreateEntityCommand(ISceneManager& scene, GameObjectHandle parent, std::string name); - void Execute() override; - void Undo() override; - std::string GetName() const override { return "Create " + m_name; } - -private: - ISceneManager& m_scene; - GameObjectHandle m_parent; - std::string m_name; - GameObjectHandle m_createdEntity; -}; - -class DeleteEntityCommand : public ICommand { -public: - DeleteEntityCommand(ISceneManager& scene, GameObjectHandle entity); - void Execute() override; - void Undo() override; - std::string GetName() const override { return "Delete " + m_entityName; } - -private: - ISceneManager& m_scene; - GameObjectHandle m_entity; - std::string m_entityName; - std::vector m_children; - GameObjectHandle m_parent; - // 存储删除前的完整状态用于恢复 -}; - -class TransformCommand : public ICommand { -public: - TransformCommand(GameObjectHandle entity, const Transform& oldState, const Transform& newState); - void Execute() override { Apply(m_newState); } - void Undo() override { Apply(m_oldState); } - -private: - void Apply(const Transform& t); - GameObjectHandle m_entity; - Transform m_oldState; - Transform m_newState; -}; -``` - -**Undo/Redo Manager**: -```cpp -class UndoRedoSystem { -public: - void Execute(std::unique_ptr command); - void Undo(); - void Redo(); - bool CanUndo() const { return m_undoStack.size() > m_historyIndex + 1; } - bool CanRedo() const { return m_historyIndex >= 0; } - -private: - std::vector> m_undoStack; - int m_historyIndex = -1; - static constexpr size_t MAX_HISTORY = 100; -}; -``` - -#### 3.2.2 场景序列化 - -**Scene 序列化接口**: -```cpp -class ISceneSerializer { -public: - virtual ~ISceneSerializer() = default; - virtual bool Serialize(IScene* scene, const std::string& path) = 0; - virtual bool Deserialize(IScene* scene, const std::string& path) = 0; -}; - -class JsonSceneSerializer : public ISceneSerializer { -public: - bool Serialize(IScene* scene, const std::string& path) override; - bool Deserialize(IScene* scene, const std::string& path) override; - -private: - nlohmann::json GameObjectToJson(GameObject* go); - GameObject* JsonToGameObject(const nlohmann::json& j, GameObject* parent); -}; -``` - -**序列化格式(JSON)**: -```json -{ - "version": "1.0", - "name": "MainScene", - "entities": [ - { - "id": "1234567890", - "name": "Player", - "components": [ - { - "type": "TransformComponent", - "position": [0, 0, 0], - "rotation": [0, 0, 0], - "scale": [1, 1, 1] - }, - { - "type": "MeshRendererComponent", - "mesh": "Assets/Meshes/cube.mesh", - "material": "Assets/Materials/default.mat" - } - ], - "children": [ - { - "id": "9876543210", - "name": "Weapon", - "components": [...] - } - ] - } - ] -} -``` - -#### 3.2.3 Inspector 组件扩展机制 - -**抽象 Component 编辑器**: -```cpp -class IComponentEditor { -public: - virtual ~IComponentEditor() = default; - virtual std::string GetComponentTypeName() const = 0; - virtual bool CanEdit(Component* component) const = 0; - virtual void Render(Component* component) = 0; -}; - -class TransformComponentEditor : public IComponentEditor { -public: - std::string GetComponentTypeName() const override { return "TransformComponent"; } - bool CanEdit(Component* component) const override { - return dynamic_cast(component) != nullptr; - } - void Render(Component* component) override; -}; - -class InspectorPanel { -public: - void RegisterComponentEditor(std::unique_ptr editor); - void RenderGameObject(GameObject* gameObject); - -private: - std::vector> m_editors; - std::unique_ptr GetEditorFor(Component* component); -}; -``` - -**注册默认编辑器**: -```cpp -void InspectorPanel::Initialize() { - RegisterComponentEditor(std::make_unique()); - // 后续添加更多编辑器 -} -``` - -#### 3.2.4 资源引用系统 - -**AssetHandle**: -```cpp -template -class AssetHandle { -public: - AssetHandle() = default; - explicit AssetHandle(AssetID id, std::shared_ptr asset = nullptr); - - AssetID GetID() const { return m_id; } - T* Get() const { return m_asset.get(); } - T* operator->() const { return m_asset.get(); } - explicit operator bool() const { return m_asset != nullptr; } - -private: - AssetID m_id; - std::shared_ptr m_asset; -}; - -class MeshRendererComponent { -public: - AssetHandle GetMesh() const { return m_mesh; } - void SetMesh(AssetHandle mesh) { m_mesh = mesh; } - -private: - AssetHandle m_mesh; - AssetHandle m_material; -}; -``` - ---- - -### 3.3 界面重构 - -#### 3.3.1 SceneView 实现 - -**相机控制器**: -```cpp -class SceneViewCameraController { -public: - enum class Mode { Pan, Orbit, Zoom }; - - void OnUpdate(float dt); - void OnMouseMove(int x, int y); - void OnMouseWheel(int delta); - - Matrix4 GetViewMatrix() const; - Matrix4 GetProjectionMatrix() const; - -private: - Mode m_mode = Mode::Orbit; - Vector3 m_target = Vector3::Zero(); - float m_distance = 10.0f; - float m_pitch = 0.0f; - float m_yaw = 0.0f; - - bool m_isDragging = false; - ImVec2 m_lastMousePos; -}; -``` - -**Gizmos 渲染**: -```cpp -class GizmosRenderer { -public: - void DrawTranslateGizmo(GameObjectHandle target); - void DrawRotateGizmo(GameObjectHandle target); - void DrawScaleGizmo(GameObjectHandle target); - - enum class Axis { X, Y, Z, XY, XZ, YZ, XYZ }; - std::optional HandleInput(const Ray& ray); -}; -``` - -#### 3.3.2 Editor 模式状态机 - -```cpp -enum class EditorMode { Edit, Play, Paused }; - -class EditorStateMachine { -public: - void SetMode(EditorMode mode); - EditorMode GetMode() const { return m_mode; } - - void Update(float dt); - -private: - EditorMode m_mode = EditorMode::Edit; - - void EnterEditMode(); - void ExitEditMode(); - void EnterPlayMode(); - void ExitPlayMode(); -}; -``` - ---- - -## 四、重构实施计划 - -### 阶段一:架构重构(第 1-2 周) - -| 任务 | 依赖 | 状态 | -|------|------|------| -| Panel 类独立化 | - | ✅ 完成 | -| 引入事件总线 | - | ✅ 完成 | -| EditorContext 接口定义 | Panel 独立化 | ✅ 完成 | -| SelectionManager 改造 | 事件总线 | ✅ 完成 | -| EditorLayer 重构 | 上述全部 | ✅ 完成 | - -**已完成的文件**: -- `editor/src/panels/Panel.h/cpp` - 独立的 Panel 基类 -- `editor/src/Core/EventBus.h` - 类型安全事件总线 -- `editor/src/Core/EditorEvents.h` - 编辑器事件类型定义 -- `editor/src/Core/ISelectionManager.h` - 选择管理器接口 -- `editor/src/Core/SelectionManager.h` - 选择管理器实现 -- `editor/src/Core/IEditorContext.h` - 编辑器上下文接口 -- `editor/src/Core/EditorContext.h` - 编辑器上下文实现 -- `editor/src/Core/EditorConsoleSink.h/cpp` - Editor 专用日志 Sink - -**2026-03-25 更新**: -- `editor/src/panels/HierarchyPanel.h/cpp` - 已迁移至 IEditorContext -- `editor/src/panels/InspectorPanel.h/cpp` - 已迁移至 IEditorContext + EventBus -- 移除了所有 Panel 中的 `SelectionManager::Get()` 和 `EditorSceneManager::Get()` 单例调用 -- SceneManager::RenameEntity 实现已添加 -- **日志系统统一**:删除了独立的 LogSystem 单例,Editor 和 Engine 共用 Debug::Logger -- ConsolePanel 改用 EditorConsoleSink 从 Logger 读取日志 -- 删除了 `editor/src/Managers/LogSystem.h/cpp` 和 `editor/src/Core/LogEntry.h` -- **命名规范修复**:SelectionManagerImpl → SelectionManager,EditorContextImpl → EditorContext -- 删除了未使用的 SceneManagerImpl 和 ISceneManager -- **ProjectManager 单例移除**:创建了 IProjectManager 接口,ProjectManager 实现该接口,EditorContext 拥有实例 -- **ISceneManager 接口**:创建了 ISceneManager 接口,SceneManager 实现该接口 -- **IEditorContext::GetSceneManager() 返回类型**:从 `void*` 改为 `ISceneManager&` -- **HierarchyPanel EventBus 订阅**:HierarchyPanel 现在订阅 SelectionChangedEvent - -### 阶段二:核心功能(第 3-4 周) - -| 任务 | 依赖 | 状态 | -|------|------|------| -| Undo/Redo 系统 | 事件总线 | TODO | -| Command 基类 | - | TODO | -| 常用 Command 实现 | Undo/Redo 系统 | TODO | -| 场景序列化 | Command 基类 | TODO | -| SceneManager 重构 | 事件总线、Command | TODO | - -### 阶段三:Inspector 改进(第 5-6 周) - -| 任务 | 依赖 | 状态 | -|------|------|------| -| ComponentEditor 接口 | 阶段一完成 | TODO | -| TransformComponentEditor | ComponentEditor 接口 | TODO | -| MeshRendererComponentEditor | AssetHandle 系统 | TODO | -| 动态添加组件 UI | ComponentEditor 接口 | TODO | - -### 阶段四:SceneView 实现(第 7-8 周) - -| 任务 | 依赖 | 状态 | -|------|------|------| -| CameraController | - | TODO | -| GizmosRenderer | CameraController | TODO | -| Selection Outline | GizmosRenderer | TODO | -| Grid Renderer | - | TODO | - -### 阶段五:Play/Edit 模式(第 9-10 周) - -| 任务 | 依赖 | 状态 | -|------|------|------| -| EditorStateMachine | 阶段一完成 | TODO | -| 场景状态保存/恢复 | 序列化 | TODO | -| GameView 集成 | StateMachine | TODO | -| 工具栏 UI | GizmosRenderer | TODO | - ---- - -## 五、注意事项 - -### 5.1 向后兼容 - -- 保持 `GameObject`、`Component` 等核心类接口不变 -- 新增接口使用 `virtual` 或默认实现 -- 序列化格式预留 `version` 字段 - -### 5.2 性能考虑 - -- Undo/Redo 栈限制最大容量 -- 事件总线使用 `std::function` 的 `std::any` 存储避免类型擦除开销 -- Inspector 使用 `std::unordered_map` 加速组件编辑器查找 - -### 5.3 测试计划 - -- 单元测试:Command、Serializer、EventBus -- 集成测试:Undo/Redo 完整流程 -- UI 测试:手动测试为主 - ---- - -## 六、代码规范 - -### 6.1 命名规范 - -- Editor 相关类放在 `XCEngine::Editor` 命名空间 -- 接口类前缀 `I`(如 `ISelectionManager`) -- Editor 内部实现后缀 `Impl`(如 `SelectionManagerImpl`) - -### 6.2 文件组织 - -``` -editor/src/ -├── Core/ -│ ├── EditorContext.h/cpp -│ ├── EventBus.h/cpp -│ └── UndoRedo.h/cpp -├── Commands/ -│ ├── Command.h -│ ├── CreateEntityCommand.h/cpp -│ ├── DeleteEntityCommand.h/cpp -│ └── TransformCommand.h/cpp -├── Panels/ -│ ├── Panel.h # 基础类 -│ ├── HierarchyPanel.h/cpp -│ ├── InspectorPanel.h/cpp -│ └── ... -└── UI/ - ├── Gizmos.h/cpp - └── CameraController.h/cpp -``` - -### 6.3 禁止事项 - -- ❌ 禁止在 Editor 代码中使用 `static` 单例模式 -- ❌ 禁止直接持有 `GameObject*` 裸指针 -- ❌ 禁止在 Release 版本保留 `Debug::Logger` 调用 -- ❌ 禁止硬编码任何路径 diff --git a/docs/used/Engine_NewEditor_RenderingEditorSupportBoundaryPlan_完成归档_2026-04-22.md b/docs/used/Engine_NewEditor_RenderingEditorSupportBoundaryPlan_完成归档_2026-04-22.md deleted file mode 100644 index cac44440..00000000 --- a/docs/used/Engine_NewEditor_RenderingEditorSupportBoundaryPlan_完成归档_2026-04-22.md +++ /dev/null @@ -1,202 +0,0 @@ -# Engine / NewEditor RenderingEditorSupport Boundary Plan - -Date: 2026-04-22 - -## 1. 目标 - -这次收口要解决的不是“`object-id` 这个文件放错目录”这么表面的事,而是把 `engine`、`rendering`、`new_editor` 三者之间的职责边界正式落下来。 - -最终结论只有三条: - -1. `ObjectId` 不应该搬进 `new_editor`。它本质上是 renderer 提供的能力,不是编辑器自己实现的渲染器。 -2. `ObjectId` 也不应该继续伪装成通用 runtime 主链能力。它应当归属为 engine 侧的 editor-support 渲染能力。 -3. `new_editor` 只负责请求、读回、解析、选择和可视化,不负责承载 object-id 渲染实现。 - -## 2. 最终边界 - -### 2.1 `engine` 运行时核心负责什么 - -`engine` 里的运行时渲染核心负责: - -1. scene extraction -2. renderer list -3. render graph / frame stage -4. main scene / shadow / post-process / final output 等通用主链 -5. 与编辑器无关的 builtin runtime resource - -### 2.2 `engine` 的 RenderingEditorSupport 负责什么 - -这层仍然在 `engine` 体系内,但不再属于默认 shipping runtime 主链: - -1. `RenderObjectId` -2. `ObjectIdCodec` -3. `RenderObjectIdRegistry` -4. `BuiltinObjectIdPass` -5. object-id request / stage / pass 接口 - -### 2.3 `new_editor` 负责什么 - -`new_editor` 只负责编辑器行为: - -1. Scene View 何时申请 object-id surface -2. object-id 纹理读回 -3. render id 到 runtime object 的解析 -4. selection state -5. selection outline / selection mask / grid / helpers -6. editor-only shader 与资源路径 - -## 3. 关键设计决定 - -### 3.1 `ObjectId` 不迁到 `new_editor` - -原因很明确: - -1. 生成 object-id buffer 的地方是 renderer,不是编辑器 UI。 -2. 物体如何编码、如何出 pass、如何组织 renderer list,都是渲染器契约。 -3. 成熟引擎的分层不是“编辑器想用的都塞到 editor 目录”,而是“renderer 提供 editor support,editor 负责消费”。 - -### 3.2 `RenderObjectId` 不再直接等于 `GameObject::ID` - -正式契约现在是: - -1. `GameObject::ID` 是 runtime identity。 -2. `RenderObjectId` 是 renderer identity。 -3. 两者通过 `RenderObjectIdRegistry` 做正式映射。 -4. `ObjectIdCodec` 只负责 `RenderObjectId <-> RGBA8`,不再负责 runtime id 互转。 - -### 3.3 editor-only shader 必须离开 engine builtin editor 资源污染 - -`selection-mask` 和 `selection-outline` 已迁到 `new_editor/resources/shaders/scene-viewport/`。 - -保留在 `engine` 的 builtin shader 只应是: - -1. runtime 通用 shader -2. renderer 自己必须拥有的 object-id shader - -## 4. 已执行阶段 - -### Phase A. 正式化 `RenderObjectId` 契约 - -已完成: - -1. 新增 `RenderObjectIdRegistry` -2. `RenderSceneUtility` 改为通过 registry 分配 render id -3. 删除 runtime id 与 render id 的直接互转 helper - -### Phase B. 改写 `new_editor` picking 契约 - -已完成: - -1. 读回结果先得到 `RenderObjectId` -2. `SceneViewportRenderService` 负责解析 render id -3. `SceneViewportController` 只消费解析后的 runtime 选择结果 -4. 加入 frame serial / invalidation 保护,避免 stale picking - -### Phase C. editor-only Scene View shader 迁移 - -已完成: - -1. `selection-mask.shader` 迁到 `new_editor/resources` -2. `selection-outline.shader` 迁到 `new_editor/resources` -3. `new_editor` 改为本地资源路径加载 -4. engine builtin registry 移除对应入口 - -### Phase D. 清理旧残留 - -已完成: - -1. 删除 `object-id-outline` builtin registry 入口 -2. 删除 `engine/assets/builtin/shaders/object-id-outline.shader` - -### Phase E. 清理运行时公开 API - -已完成: - -1. managed 移除 `CameraFrameStage.ObjectId` -2. managed 移除 `RendererListType.ObjectId` -3. managed 收回 `FilteringSettings.requireRenderObjectId` -4. native bridge 不再接受这些 runtime-facing managed 值 - -### Phase F. 建立 build graph 边界 - -已完成: - -1. 新增 CMake option `XCENGINE_ENABLE_RENDERING_EDITOR_SUPPORT` -2. 新增目标 `XCEngineRenderingEditorSupport` -3. `BuiltinObjectIdPass` 与 `RenderObjectIdRegistry` 从 `XCEngine` 主源列表中拆出 -4. `XCUIEditorAppCore` 显式链接 `XCEngineRenderingEditorSupport` -5. `XCEngine` 在 editor-support 构建下链接该目标 -6. `XCEngine` 在关闭该开关时会编译掉 object-id 相关主链接入 - -## 5. 当前最终状态 - -当前结构已经变成: - -```text -Editor Build - XCEngine - + XCEngineRenderingEditorSupport - XCUIEditorAppCore - + XCEngineRenderingEditorSupport - -Runtime-Only Build - XCEngine - XCENGINE_ENABLE_RENDERING_EDITOR_SUPPORT=OFF -``` - -这意味着: - -1. editor build 仍可使用 object-id picking -2. runtime-only build 可以完全关闭这层支持 -3. `new_editor` 不再承担 object-id 渲染实现 -4. shipping runtime 不再被 Scene View 的 object-id 链路默认污染 - -## 6. 验证结果 - -### 6.1 每阶段 editor 验证 - -已多次通过: - -1. `cmake --build build --config Debug --target XCUIEditorApp` -2. 启动 `build/new_editor/Debug/XCUIEditor.exe` -3. 检查 `build/new_editor/Debug/logs/runtime.log` - -验证点: - -1. `initialize begin/end` -2. `SceneManager::LoadScene begin/end` -3. `EnsureEditorStartupScene loaded scene=Main Scene` -4. 正常关闭与 `shutdown end` - -### 6.2 runtime-only 边界验证 - -已通过独立构建目录验证: - -1. `cmake -S . -B build_runtime_no_editor_support -G "Visual Studio 17 2022" -DXCENGINE_BUILD_XCUI_EDITOR_APP=OFF -DXCENGINE_ENABLE_RENDERING_EDITOR_SUPPORT=OFF` -2. `cmake --build build_runtime_no_editor_support --config Debug --target XCEngine` - -结果: - -1. `XCEngine` 在关闭 editor support 的配置下独立编译成功 -2. 该配置没有构建 `XCEngineRenderingEditorSupport` - -## 7. 后续非阻塞项 - -这些不是本次收口的阻塞条件,但建议后续补齐: - -1. 补 `RenderObjectIdRegistry` 单测 -2. 补 scene reload / stale pick 回归测试 -3. 若后续继续细分 engine,可再把这层从“大一统 `XCEngine`”继续拆成更细的 runtime/rendering/editor-support 库 - -## 8. 最终结论 - -这次问题的正确答案不是“把 `ObjectId` 挪进 `new_editor`”。 - -正确答案是: - -1. `ObjectId` 留在 engine renderer 侧 -2. 但它不再属于默认 runtime 主链 -3. 它现在被正式收口为 `XCEngineRenderingEditorSupport` -4. `new_editor` 只负责消费这层能力 - -这才是和成熟引擎一致的分层方向。 diff --git a/docs/used/Library启动预热与运行时异步加载混合重构计划_过期归档_2026-04-11.md b/docs/used/Library启动预热与运行时异步加载混合重构计划_过期归档_2026-04-11.md deleted file mode 100644 index 02524b88..00000000 --- a/docs/used/Library启动预热与运行时异步加载混合重构计划_过期归档_2026-04-11.md +++ /dev/null @@ -1,694 +0,0 @@ -# Library启动预热与运行时异步加载混合重构计划 - -归档状态:2026-04-11。本计划不再作为活跃主计划继续推进;其未收口部分已被后续更细分的 `NanoVDB` 首帧阻塞修复、`Shader` 并入 `Library`、以及 editor/runtime 收口方案分别接管,因此转入 `docs/used` 作为历史归档。 - -文档日期:2026-04-04 - -适用范围:当前仓库中的 `project` 单项目工作流。 - -文档目标:在现有 `Library` 资产导入与缓存系统基础上,继续收口为一套更接近 Unity 的正式方案,即“启动阶段前置索引与缓存有效性恢复,运行时按需异步加载资源 payload”,同时避免重新引入旧版本兼容、迁移工具、路径双写等过渡态实现。 - ---- - -## 1. 这轮重构要解决什么问题 - -当前 `Library` 模块已经完成了第一阶段收口,具备了以下能力: - -- `Assets.meta` / `*.meta` / `AssetGUID` -- `Library/SourceAssetDB` -- `Library/ArtifactDB` -- `Library/Artifacts` -- `AssetImportService` -- `ProjectAssetIndex` -- `ResourceManager` 运行时缓存与异步调度 -- `AssetRef` 驱动的项目资产恢复 -- `mesh/material/texture` artifact 体系 - -但最近几轮实际使用已经暴露出两个非常关键的问题: - -### 1.1 问题一:artifact 命中路径不够纯净 - -已经生成好的 artifact 路径,例如: - -- `Library/Artifacts/.../main.xcmesh` -- `Library/Artifacts/.../material_0.xcmat` -- `Library/Artifacts/.../texture_0.xctex` - -在某些运行时链路里被误当成源资源再次送回 `EnsureArtifact()`,导致: - -- 报错 `Failed to build asset artifact: Library/Artifacts/...` -- 资源恢复失败 -- 运行时重复做无意义工作 -- UI 上出现误导性的导入失败状态 - -这个问题已经定位并修复,但它说明当前系统在“源资源路径”和“artifact 路径”的边界定义上还不够硬。 - -### 1.2 问题二:缓存命中了,但热路径仍然很重 - -这是最近 “为什么打开带 obj 的 scene 还是会卡” 的根本原因。 - -当 `backpack.obj` 这类模型已经有 artifact 时,理论上应该走: - -- 命中 `ArtifactDB` -- 直接读取 `xcmesh/xcmat/xctex` -- 异步恢复运行时对象 - -但实际上旧实现里在 `AreDependenciesCurrent()` 阶段还会对依赖文件反复做重成本校验: - -- `backpack.mtl` -- `diffuse.jpg` -- `normal.png` -- `specular.jpg` - -尤其是对大贴图重复做 hash,会把“缓存命中”退化成“每次打开 scene 都重新扫描大资源”。 - -这也是为什么用户体感上会觉得: - -- 明明已经有 `Library` -- 明明已经不是第一次打开 -- 但打开带大模型的场景依然卡顿很明显 - -这个问题也已经定位并修复,当前改为缓存命中时只使用 `fileSize + writeTime` 快速校验,而不是每次重算依赖 hash。 - -### 1.3 问题三:当前系统仍偏“运行时按需补救”,缺少正式的启动前置阶段 - -目前系统已经比较偏向: - -- 编辑器启动快 -- 打开 scene 时按需异步恢复 mesh/material/texture - -这条路线本身没有问题,但如果不补一个正式的“项目启动预热阶段”,就会有几个副作用: - -- 项目启动后第一次打开大场景,工作量全部堆在 scene open 时刻 -- 视口、Inspector、Picker 等编辑器系统更容易在首帧触发同步兜底 -- 用户对 `Library` 的感知会变成“有缓存但还是随机卡” -- 问题定位难,因为启动阶段和运行时阶段职责混在一起 - -### 1.4 问题四:编辑器主线程仍存在同步兜底风险 - -虽然 deferred scene load 与异步恢复已经打通,但编辑器里仍有若干系统会在渲染或交互路径中触发: - -- `GetMesh()` -- `GetMaterial()` -- 选择框、Gizmo、拾取、Inspector 读取资源状态 - -如果这些路径访问时机不对,仍然可能把本该后台完成的工作拉回主线程。 - -也就是说,现在系统已经不是“完全同步导入”,但还没完全达到“严格异步、主线程只消费结果”的成熟状态。 - ---- - -## 2. 这轮要采用的正式方向 - -这轮不走两个极端: - -- 不走 Unity 式“把几乎所有成本都压到启动时”的纯前置模式 -- 也不走“什么都按需,启动几乎不做准备”的纯懒加载模式 - -正式目标是混合式方案: - -### 2.1 启动阶段前置做什么 - -项目启动时只前置做轻量但必要的内容: - -- 读取 `SourceAssetDB` -- 读取 `ArtifactDB` -- 建立 `GUID <-> path <-> AssetRef` 快照索引 -- 恢复 `ProjectAssetIndex` -- 对项目资源做轻量级有效性校验 -- 检查 artifact 文件是否存在 -- 对最近使用场景或关键高频资源做 metadata 级预热 - -这些工作应该: - -- 不解码大贴图 -- 不重建 mesh -- 不恢复完整 material runtime object -- 不做 GPU payload 上传 - -### 2.2 运行时按需异步做什么 - -真正重的 payload 继续按需异步进入: - -- `xcmesh` -> runtime mesh -- `xcmat` -> runtime material -- `xctex` -> runtime texture -- 贴图绑定的延迟解析与加载 - -也就是说: - -- 启动阶段前置“状态” -- 运行时异步加载“内容” - -### 2.3 这套方案为什么更适合当前项目 - -因为当前项目的真实诉求是: - -- 编辑器本身不要像 Unity 那样每次启动都等很久 -- 但打开带大模型的场景也不能把主窗口卡死 -- 同时 `Library` 要真的发挥缓存价值,而不是只在目录结构上看起来像 Unity - -对这个目标来说,最佳平衡点就是: - -- 启动时恢复索引和缓存状态 -- 打开 scene 时只反序列化结构 -- 重资源 payload 严格异步 -- cache hit 路径绝不重新做大计算 - ---- - -## 3. 本轮重构后的目标架构 - -### 3.1 模块职责划分 - -#### `AssetDatabase` - -只作为底层数据库与导入实现细节: - -- 源资源记录 -- artifact 记录 -- `.meta` 文件管理 -- importer 分派 -- artifact 构建 -- 依赖采集 -- 缓存有效性校验 - -不直接承担 editor 级工作流协调。 - -#### `AssetImportService` - -作为项目级导入与缓存服务层: - -- 项目根目录绑定 -- 启动阶段缓存恢复 -- 轻量校验 -- `EnsureArtifact` -- `Refresh` -- `ReimportAsset` -- `ReimportAllAssets` -- `ClearLibraryCache` -- 启动预热状态输出 - -后续新增的“启动预热流程”也应放在这一层,而不是塞回 `ResourceManager`。 - -#### `ProjectAssetIndex` - -只承担索引快照职责: - -- `AssetRef -> project path` -- `project path -> AssetRef` -- 项目资源查找缓存 -- 场景反序列化后的快速路径恢复 - -不参与真正的 artifact 构建。 - -#### `ResourceManager` - -只承担运行时加载与缓存职责: - -- runtime object cache -- async queue -- in-flight coalescing -- 源资源与 artifact 路径路由 -- deferred scene load 期间的异步恢复入口 - -明确禁止它重新承担“项目导入管理器”角色。 - -### 3.2 正式的三段式生命周期 - -#### 阶段 A:Project Startup Bootstrap - -项目打开时: - -- 恢复 `Library` 数据库 -- 建索引 -- 快速验证缓存状态 -- 后台预热 metadata - -不恢复大资源 payload。 - -#### 阶段 B:Scene Structural Load - -打开 scene 时: - -- 只做场景结构反序列化 -- 组件恢复 `AssetRef` 和资源路径 hint -- 不在主线程同步恢复大 mesh/material/texture - -#### 阶段 C:Runtime Payload Stream-In - -场景打开后: - -- 按需异步拉取 `xcmesh` -- 再按需恢复 `xcmat` -- 贴图绑定继续 lazy resolve / lazy load -- 视口只消费当前已完成资源 - ---- - -## 4. 当前已完成内容与后续计划分界 - -## 4.1 已完成 - -以下内容已经可以视为本轮新方案的基础,不需要推倒重来: - -- `Library` 目录结构已落地 -- `SourceAssetDB` / `ArtifactDB` 已落地 -- `.meta + AssetGUID` 已稳定 -- `AssetRef` 驱动的 scene/component 恢复已落地 -- `MeshFilterComponent` / `MeshRendererComponent` deferred async restore 已打通 -- `ProjectPanel` 已有最小 import status 输出 -- orphan artifact 清理已接入 -- artifact 路径误进 `EnsureArtifact()` 的问题已修 -- cache hit 时重复 hash 大依赖导致卡顿的问题已修 -- `AssetImportService::BootstrapProject()` 已落地,项目启动存在正式 bootstrap 生命周期 -- `ResourceManager::SetResourceRoot()` 已切到显式 bootstrap 路径,不再依赖首次资源查询时的隐式补救 -- `AssetDatabase::Initialize()` 已改为只恢复 DB 状态,不再在绑定项目根目录时隐式全量扫描 -- `ClearLibraryCache()` 已在清空后立即重建 source lookup,避免清库后留下半残状态 -- `ResourceHandle` 已改为持有稳定 `ResourceGUID`,`UnloadAll/Shutdown` 后不再因为悬空指针在析构期崩溃 -- `ResourceManager` 的 `Unload/UnloadAll/UnloadGroup` 已同步清理 `ResourceCache` 陈旧条目 -- `Material` 析构顺序问题已修,OBJ source-import 与 mesh artifact reimport 链路不再触发 debug heap 崩溃 -- `mesh/material/scene/components` 关键回归已重新验证通过 - -## 4.2 本轮仍然需要正式收口的内容 - -本轮重点不再是“把 `Library` 做出来”,而是把下面这些未完成项正式落地: - -- 正式的启动阶段 bootstrap -- 启动阶段的 metadata 预热策略 -- 项目打开后、scene 首次打开前的轻量准备 -- editor 主线程同步兜底点审计 -- 视口 / Inspector / Picker 对未加载资源的正式占位策略 -- `AssetImportService` 层级的启动状态与进度模型 -- 更清晰的“正在预热 / 正在导入 / 正在异步恢复”的 UI 区分 - ---- - -## 5. 详细实施阶段 - -## 阶段 0:基线固化与指标采样 - -### 目标 - -先把当前性能问题量化,避免后续优化没有参照系。 - -### 任务 - -- 为以下阶段分别记录耗时: - - 项目启动到 editor 可交互 - - 打开 `Backpack.xc` 到 scene graph 可见 - - 打开 `Backpack.xc` 到 mesh 可见 - - 打开 `Backpack.xc` 到首帧可渲染 -- 记录 `AssetImportService` 各操作状态: - - `Bootstrap` - - `Refresh` - - `Import Asset` - - `Reimport` -- 记录 `ResourceManager` 各类异步请求计数: - - mesh - - material - - texture -- 对 `backpack.obj` 链路增加 focused trace 开关 - -### 交付物 - -- 明确的启动时长基线 -- 明确的 scene 打开耗时基线 -- 一组稳定可复现的回归测试与日志样本 - -### 完成标准 - -- 以后任何优化都可以明确比较 “优化前 vs 优化后” - ---- - -## 阶段 1:启动阶段 Bootstrap 正式化 - -### 目标 - -把当前隐式分散在多个点的项目初始化流程,收成一个正式的启动阶段。 - -### 要做的事 - -- 在 `AssetImportService` 中新增明确的启动流程,例如: - - `BeginProjectBootstrap()` - - `RunProjectBootstrap()` - - `GetBootstrapStatus()` -- 启动阶段同步完成: - - 绑定项目根目录 - - 读取 `SourceAssetDB` - - 读取 `ArtifactDB` - - 构建 `LookupSnapshot` - - 恢复 `ProjectAssetIndex` - - 快速检查 artifact 文件存在性 -- 启动阶段禁止: - - 解码纹理 - - 读取 `.obj` 顶点数据 - - 恢复完整 material runtime object - - 重算所有依赖 hash - -### 推荐实现细节 - -- `SetProjectRoot()` 只负责绑定项目,不隐式做重活 -- 显式 bootstrap 负责状态恢复 -- `ResourceManager::SetResourceRoot()` 完成后应当触发一次 bootstrap,而不是等首次 `LoadResource()` 时再补救 -- `ProjectAssetIndex::RefreshFrom(...)` 要明确依赖 bootstrap 产出的快照,而不是在运行时随机 fallback - -### 结果要求 - -- 项目启动后,`AssetRef` 和项目资源索引已经可用 -- 打开 scene 时不需要再顺便补建整套索引 - ---- - -## 阶段 2:缓存命中路径彻底轻量化 - -### 目标 - -保证 “有 artifact 且未变化” 真正意味着快。 - -### 要做的事 - -- 固化当前已经完成的两个修复: - - artifact 路径直载,不再送回 `EnsureArtifact` - - cache hit 时依赖校验只走 `fileSize + writeTime` -- 继续补强边界判定: - - `builtin://...` - - `Library/Artifacts/...` - - `.xcmesh/.xcmat/.xctex/.xcshader` - - `Assets/...` - - 项目外绝对路径 -- 只允许明确的源资源路径触发导入 -- cache hit 时不允许再发生: - - 重扫依赖目录 - - 重解析 `obj/mtl` - - 重算大贴图 hash - -### 推荐实现细节 - -- 在 `ResourceManager` 中形成统一 helper: - - `ShouldUseProjectArtifactImport(path, type)` -- 在 `AssetDatabase` 中形成统一 helper: - - `IsFastCacheValidationEligible(...)` -- 深度 hash 改为仅在这些场景触发: - - 显式 Reimport - - 怀疑 DB 损坏 - - 调试模式人工开启深校验 - -### 结果要求 - -- 二次打开 `Backpack.xc` 这类场景时,命中链路不应再接近首次导入成本 - ---- - -## 阶段 3:Scene Open 零阻塞主路径收口 - -### 目标 - -打开 scene 时只恢复结构,不同步等 payload。 - -### 要做的事 - -- 审计 scene load 首帧中所有可能触发同步资源恢复的位置: - - `MeshFilterComponent::GetMesh()` - - `MeshRendererComponent::GetMaterial()` - - render item 构建 - - scene picker - - gizmo frame builder - - inspector 材质预览 -- 禁止在 scene 刚打开时,视口渲染链路用同步 `Load()` 兜底 -- 对未完成的资源加载使用正式占位策略: - - mesh 未到位:跳过 render item 或显示加载占位 - - material 未到位:使用稳定 fallback material - - texture 未到位:保持材质对象,但贴图槽位为空 - -### 推荐实现细节 - -- `GetMesh()` / `GetMaterial()` 保持“只触发异步请求,不阻塞等待” -- 场景渲染代码消费的是“当前已完成状态”,而不是强行等待最终状态 -- `Loading scene assets...` 应仅表示后台恢复中,不应意味着主线程仍被卡住 - -### 结果要求 - -- 打开大场景时主窗口保持可交互 -- 首帧允许缺资源,但不能卡死 -- 资源陆续完成后画面逐步完善 - ---- - -## 阶段 4:启动预热策略 - -### 目标 - -在不走 Unity 全量前置的前提下,把最常用项目数据提前准备好。 - -### 候选预热内容 - -- 最近一次打开的 scene 列表 -- 当前项目默认 scene -- 最近访问的材质/模型索引 -- `AssetRef -> path` 高频映射 -- scene 文件中出现过的主 mesh asset ref - -### 预热分层 - -#### 同步预热 - -- 只预热索引和 metadata - -#### 后台预热 - -- 检查对应 artifact 是否存在 -- 预先解析 scene 内引用的主资源清单 -- 但不真正构建 runtime payload - -### 推荐实现细节 - -- 新增简单的 `Library/SessionCache` 或 `Library/BootstrapCache` 元数据文件 -- 记录最近场景和高频资源,不需要做复杂推荐系统 -- 预热任务进入专用后台队列,不占用用户交互关键线程 - -### 结果要求 - -- 项目打开后第一次打开最近场景更快 -- 不把全部项目资源都拉进启动成本 - ---- - -## 阶段 5:编辑器调用点清理 - -### 目标 - -把 editor 里所有“资源还没准备好时的坏行为”收掉。 - -### 要做的事 - -- 清理以下系统中的同步依赖: - - `SceneViewportPicker` - - `SceneViewportTransformGizmoFrameBuilder` - - `RenderSceneUtility` - - `InspectorPanel` - - 材质/mesh 相关组件编辑器 -- 明确这些系统在资源未到位时的行为: - - 不报错 - - 不触发重导入 - - 不锁主线程 - - 不污染 import status - -### 推荐实现细节 - -- picker:未完成 mesh 时直接跳过,不同步等待 -- gizmo center:拿不到 mesh bounds 时退回 transform position -- inspector:未完成材质时显示 loading / unresolved,而不是同步 `Load` -- viewport:资源未到位时继续渲染其它可用对象 - -### 结果要求 - -- “打开 scene 不阻塞” 不仅存在于测试里,也存在于真实编辑器交互中 - ---- - -## 阶段 6:状态与观测模型正式化 - -### 目标 - -用户能看懂系统当前在干什么。 - -### 现状问题 - -现在 `ProjectPanel` 顶部已经有导入状态,但还不足以区分: - -- 项目启动预热 -- 显式导入 -- 场景异步恢复 -- 运行时 payload stream-in - -### 要做的事 - -- 将状态拆成至少三类: - - `Bootstrap` - - `Import/Reimport` - - `Scene Asset Streaming` -- UI 明确区分: - - 正在恢复项目索引 - - 正在重建 artifact - - 正在后台加载场景资源 -- 增加更清楚的 tooltip: - - 当前目标路径 - - 剩余异步数量 - - 最近失败原因 - -### 推荐实现细节 - -- `AssetImportService::ImportStatusSnapshot` 扩展为更通用的 operation model -- `ResourceManager` 增加 lightweight streaming status snapshot -- `ProjectPanel` 与 viewport status text 分工: - - `ProjectPanel` 展示项目级状态 - - viewport 展示当前 scene 级 streaming 状态 - -### 结果要求 - -- 用户不会再把“后台恢复中”和“导入失败”混淆 - ---- - -## 阶段 7:最终收口与归档 - -### 目标 - -把这套方案从“已经能用”收成“正式基线”。 - -### 要做的事 - -- 整理最终架构说明 -- 删除不再需要的临时日志和调试开关 -- 固化测试矩阵 -- 更新 README / Editor 架构文档中的 Library 模块说明 -- 将本计划归档到 `docs/plan/used` - -### 完成标准 - -- `Library` 模块不再被视为过渡态系统 -- 项目启动与大场景打开的行为稳定、可解释、可回归 - ---- - -## 6. 需要修改或重点审查的代码范围 - -本轮预计主要涉及以下文件: - -- `engine/src/Core/Asset/AssetDatabase.cpp` -- `engine/include/XCEngine/Core/Asset/AssetDatabase.h` -- `engine/src/Core/Asset/AssetImportService.cpp` -- `engine/include/XCEngine/Core/Asset/AssetImportService.h` -- `engine/src/Core/Asset/ProjectAssetIndex.cpp` -- `engine/include/XCEngine/Core/Asset/ProjectAssetIndex.h` -- `engine/src/Core/Asset/ResourceManager.cpp` -- `engine/include/XCEngine/Core/Asset/ResourceManager.h` -- `engine/src/Core/Asset/AsyncLoader.cpp` -- `engine/src/Components/MeshFilterComponent.cpp` -- `engine/src/Components/MeshRendererComponent.cpp` -- `engine/src/Rendering/RenderSceneUtility.cpp` -- `editor/src/Viewport/SceneViewportPicker.cpp` -- `editor/src/Viewport/SceneViewportTransformGizmoFrameBuilder.h` -- `editor/src/panels/InspectorPanel.cpp` -- `editor/src/Viewport/ViewportHostService.h` -- `editor/src/Managers/SceneManager.cpp` -- `tests/Scene/test_scene.cpp` -- `tests/Components/test_mesh_render_components.cpp` -- `tests/Resources/Texture/test_texture_loader.cpp` -- `tests/Core/Asset/test_resource_manager.cpp` - ---- - -## 7. 验收标准 - -## 7.1 功能层 - -- 关闭 editor 再打开 `Backpack.xc` 时,模型 mesh/material/texture 能稳定恢复 -- 内置 primitive 不会因为缓存系统回归而丢失 -- 打开含 OBJ 的 scene 时,不再出现 artifact 路径误导入报错 - -## 7.2 性能层 - -- 项目启动时不会做完整 payload 导入 -- 打开 `Backpack.xc` 不会长时间阻塞主窗口 -- artifact 命中耗时显著低于首次导入 -- cache hit 路径不会再重复扫描大贴图 hash - -## 7.3 架构层 - -- 启动阶段、scene open 阶段、runtime payload 阶段边界清晰 -- `AssetImportService`、`ProjectAssetIndex`、`ResourceManager` 职责边界清晰 -- editor 主线程不再承担重资源恢复职责 - -## 7.4 观测层 - -- 用户能区分 `Bootstrap`、`Import`、`Scene Streaming` -- 失败原因可见 -- 后台工作数量可见 - -## 7.5 回归测试层 - -至少保住以下 focused 回归: - -- `Scene_ProjectSample.AsyncLoadBackpackMeshArtifactCompletes` -- `Scene_ProjectSample.DeferredLoadBackpackSceneEventuallyRestoresBackpackMesh` -- `Scene_ProjectSample.DeferredLoadBackpackSceneEventuallyProducesVisibleRenderItems` -- `MeshRendererComponent_Test.DeferredSceneDeserializeLoadsProjectMaterialAsync` -- `TextureLoader.ResourceManagerLoadsLibraryArtifactTextureWithoutReimportingIt` -- `ResourceManager_Test.ConcurrentAsyncLoadsCoalesceSameMeshPath` - ---- - -## 8. 当前阶段完成情况 - -截至 2026-04-04 当前这一轮代码与测试状态,按这份新计划计: - -| 阶段 | 状态 | 说明 | -| --- | --- | --- | -| 阶段 0:基线固化 | 进行中 | focused test 已补强,但还缺项目启动耗时与 editor 实机场景打开指标归档 | -| 阶段 1:启动阶段 Bootstrap 正式化 | 已完成 | `BootstrapProject()` / `BootstrapProjectAssets()` 已接入正式启动链路 | -| 阶段 2:缓存命中路径轻量化 | 已完成 | artifact 直载、依赖快速校验、误重导入路径问题均已修复 | -| 阶段 3:Scene Open 零阻塞主路径 | 已完成首轮 | backpack 场景异步恢复链路已稳定,scene open 不再因为 Library 命中路径本身阻塞主线程 | -| 阶段 4:启动预热策略 | 未开始 | 还没有最近场景/高频资源 metadata 预热 | -| 阶段 5:编辑器调用点清理 | 进行中 | 主要阻塞点已清掉,但 Inspector / Picker / viewport 仍需继续做系统化审计 | -| 阶段 6:状态与观测模型 | 进行中 | 已有 bootstrap/import 状态基础,但还未正式区分 bootstrap / streaming / scene restore | -| 阶段 7:最终收口与归档 | 未开始 | 等上述阶段完成后执行 | - -补充说明: - -- 本轮新增并通过的关键回归包括: - - `AssetImportService_Test.BootstrapProjectBuildsLookupSnapshotAndReportsStatus` - - `ResourceManager_Test.SetResourceRootBootstrapsProjectAssetCache` - - `ResourceHandle.ResetDoesNotDereferenceDestroyedResourcePointer` - - `MeshLoader.AssetDatabaseReimportsModelWhenDependencyChanges` - - `MeshLoader.ResourceManagerLoadsModelByAssetRefFromProjectAssets` -- 这意味着当前 `Library` 模块的主要风险已经从“缓存不生效/路径误判”转移为“剩余 editor 调用点审计、状态模型清晰化、预热策略补全”。 - ---- - -## 9. 推荐执行顺序 - -建议严格按下面顺序推进: - -1. 先继续做阶段 5,把 editor 主线程剩余同步兜底点系统性扫完。 -2. 然后做阶段 4,把“最近场景 metadata 预热”补上。 -3. 接着做阶段 6,把状态模型和 UI 区分补完整。 -4. 再做阶段 0,把项目启动和大场景打开指标正式采样落档。 -5. 最后统一做阶段 7 收口归档。 - -原因很明确: - -- 如果不先清主线程调用点,任何异步链路都可能再次被同步兜底破坏。 -- 如果不补预热策略,首次打开高频场景仍然会把轻量准备工作堆到打开时刻。 -- 如果不把状态模型做清楚,用户会继续把“后台恢复”误解成“又卡死了”。 -- 如果不把基线指标整理出来,后续收口就没有稳定对照。 - ---- - -## 10. 一句话结论 - -本轮不是继续“补 Library”,而是把现有 `Library` 系统正式提升为 Unity 式混合架构: - -项目启动时前置恢复索引与缓存状态,运行时严格按需异步流入 mesh/material/texture payload,让缓存命中真正变快,同时保证打开大场景不再阻塞 editor 主线程。 diff --git a/docs/used/Library启动预热与运行时异步加载混合重构计划_进度更新_过期归档_2026-04-11.md b/docs/used/Library启动预热与运行时异步加载混合重构计划_进度更新_过期归档_2026-04-11.md deleted file mode 100644 index 62d07e12..00000000 --- a/docs/used/Library启动预热与运行时异步加载混合重构计划_进度更新_过期归档_2026-04-11.md +++ /dev/null @@ -1,93 +0,0 @@ -# Library 启动预热与运行时异步加载混合重构计划进度更新 - -归档状态:2026-04-11。本进度更新对应的主计划已被后续细分方案覆盖,不再单独作为活跃跟踪文档保留,现转入 `docs/used` 作为阶段历史记录。 - -文档日期:2026-04-04 - -对应主计划: -- `docs/plan/Library启动预热与运行时异步加载混合重构计划_2026-04-04.md` - -## 本轮已完成 - -- `mesh_tests` 全量 `34/34` 通过。 -- `scene_tests` 全量 `63/63` 通过。 -- `asset_tests` 全量 `55/55` 通过。 -- `texture_tests` 全量 `28/28` 通过。 -- `XCEditor` Debug 构建通过。 - -- `Main.xc` 轻量场景测试预期已和当前项目夹具同步: - - 当前场景对象是 `Sphere` - - 不再沿用旧的 `Cube` 预期 - -- `AssetImportService::ImportStatusSnapshot` 已增加: - - `startedAtMs` - - `completedAtMs` - - `durationMs` - -- `ProjectPanel` 的 Library 状态展示已增强: - - 状态文案可显示耗时 - - tooltip 可区分 operation / state / duration / target / imported count / removed count - -- 一个阻塞当前回归验证的无关编译问题已修复: - - `engine/src/Resources/UI/UIDocumentLoaders.cpp` - - 不再非法调用 `IResourceLoader::GetExtension()` 的 protected 接口 - -## 当前阶段判断 - -当前这条 Library 重构线已经达到下面这个阶段性状态: - -- 启动阶段负责恢复索引和缓存状态 -- 运行时继续按需异步恢复 mesh / material / texture payload -- 关键回归测试已经稳定通过 -- Editor 主程序可以正常构建 - -也就是说,前面最容易反复回退的两类问题已经清掉了: - -- `Library` / artifact / `AssetRef` 主链路回归 -- scene fixture 漂移导致的假失败 - -## 相对主计划的完成情况 - -- 阶段 1:已完成 - - `BootstrapProject()` / `BootstrapProjectAssets()` 已正式接入启动链路 - -- 阶段 2:已完成 - - artifact 直载 - - cache hit 快速校验 - - 避免把 artifact 路径重新误送回导入链路 - -- 阶段 3:已完成首轮并稳定 - - scene open 不再因为 Library 命中路径本身阻塞主线程 - - backpack 场景异步恢复链路回归通过 - -- 阶段 5:推进中 - - 主要阻塞点已经清掉 - - 但 editor 里剩余同步兜底访问点仍需继续系统化审计 - -- 阶段 6:推进中 - - 现在已经有 bootstrap/import 状态 - - 但还需要继续区分 bootstrap / scene restore / runtime streaming - -- 阶段 0 / 阶段 4 / 阶段 7:未收口 - - 启动与首帧指标基线还没正式固化 - - metadata 预热策略还没补完 - - 最终收口文档和归档还没做 - -## 下一步建议 - -下一阶段不要再回头重做 `Library` 主链路本身,重点应当转到“观测”和“收口”: - -1. 固化启动基线与场景首帧指标 -2. 审计 editor 主线程剩余同步兜底点 -3. 给 viewport / inspector / picker 加上未完成异步资源的占位与状态提示 -4. 把“正在预热 / 正在导入 / 正在异步恢复”拆成更明确的 UI 状态 - -## 这一轮改动直接涉及的文件 - -- `engine/include/XCEngine/Core/Asset/AssetImportService.h` -- `engine/src/Core/Asset/AssetImportService.cpp` -- `editor/src/panels/ProjectPanel.cpp` -- `tests/core/Asset/test_resource_manager.cpp` -- `tests/Resources/Texture/test_texture_loader.cpp` -- `tests/Scene/test_scene.cpp` -- `engine/src/Resources/UI/UIDocumentLoaders.cpp` diff --git a/docs/used/Library统一Artifact容器与多后端Shader缓存重构计划_阶段归档_2026-04-11.md b/docs/used/Library统一Artifact容器与多后端Shader缓存重构计划_阶段归档_2026-04-11.md deleted file mode 100644 index 176bd447..00000000 --- a/docs/used/Library统一Artifact容器与多后端Shader缓存重构计划_阶段归档_2026-04-11.md +++ /dev/null @@ -1,736 +0,0 @@ -# Library 统一 Artifact 容器与多后端 Shader 缓存重构计划 - -日期:2026-04-11 - -## 0. 当前执行状态(2026-04-11 更新) - -当前整体进度:约 `88%` - -已完成: - -1. `ArtifactContainer` 读写、校验、虚拟 entry path 已落地,并有核心单测覆盖。 -2. `ShaderCompilationCache` 已正式进入 `Library` 体系,`D3D12` 已有稳定命中路径与单测覆盖。 -3. `Texture / Material / Shader / Model / Volume / GaussianSplat` 已切到单文件 artifact container 写入与读取语义。 -4. `Model` 子资源已从真实 sibling 文件切到 container entry,`AssetDatabase` 子资源解析优先走 entry 解析。 -5. `XCUI` 文档 artifact 也已补齐为单文件 container,避免 `Library` 内继续残留旧式 artifact directory 生产路径。 -6. 新格式写入路径已经统一到 `Library/Artifacts//.`,旧格式保持读取兼容。 -7. `artifact_inspect` 已落地,可直接查看 container entry 列表并导出指定 entry payload。 - -已验证: - -1. `ArtifactContainer.*` -2. `ShaderCompilationCache.*` -3. `TextureLoader` 关键 artifact 回归 -4. `MaterialLoader` 关键 artifact / reimport 回归 -5. `ShaderLoader` 关键 artifact / dependency 回归 -6. `ModelLoader / ModelImportPipeline / MeshLoader` 关键 container-entry 回归 -7. `VolumeFieldLoader` 关键 artifact 回归 -8. `GaussianSplatLoader.*` -9. `UIDocumentLoader.*` -10. `UISchemaDocument.*` - -尚未收口: - -1. `ArtifactDB` 还没有彻底升级到“显式 entry 元数据” schema,当前仍保留一部分旧字段语义用于兼容迁移。 -2. `Library` 清理工具、最终性能报告、旧格式彻底收口文档还没完成。 -3. `Vulkan / OpenGL` 目前已经接入统一 shader cache 框架边界,但还没有做到和 `D3D12` 同等级的完整产物复用能力。 - -## 1. 文档定位 - -这份计划不是在讨论“把 `Library/Artifacts` 的目录名字改得更像 Unity”。 - -这份计划要解决的是更底层的问题: - -1. 现在的 artifact 基本存储单位是“一个目录 + 多个真实文件”,不是“一个统一工件”。 -2. 复杂资源类型一增加,导入器、加载器、子资源解析、测试都会继续把这种多文件假设扩散到更多模块。 -3. shader 运行时编译缓存虽然已经开始建立,但还没有上升为整个 `Library` 体系中的正式基础设施。 - -因此,本计划的目标是正式把 `Library` 重构为一套统一的缓存架构: - -1. 导入产物统一落成单文件 artifact container。 -2. 子资源从“磁盘上的 sibling 文件”变成“artifact 内部 entry”。 -3. shader 编译产物纳入 `Library` 正式缓存体系,并支持多后端。 -4. 整个系统继续保留现有 `AssetDatabase / ResourceManager / ArtifactDB` 的架构思路,不搞一套平行新系统。 - ---- - -## 2. 当前状态与根本问题 - -### 2.1 当前已经成立的基础 - -目前 `Library` 体系已经具备以下基础能力: - -1. `assets.db` 与 `artifacts.db` 已经位于 `Library` 根目录。 -2. `AssetDatabase` 已经负责 source asset 扫描、依赖签名、artifact 复用和重导。 -3. 各资源导入器已经能够把 source asset 写入 artifact。 -4. shader 编译缓存已经有初步命中机制,且收益已经被实际验证过。 - -### 2.2 当前真正的问题 - -当前问题不在于“有没有缓存”,而在于“缓存的基本抽象还不够统一”。 - -以模型为例,当前一个导入结果并不是一个真正的工件,而是一整个目录: - -1. `main.xcmodel` -2. `mesh_0.xcmesh` -3. `material_0.xcmat` -4. `texture_0.xctex` -5. `subassets.tsv` - -这意味着: - -1. artifact 的语义是“目录”,不是“文件”。 -2. loader 和测试可以直接依赖某个 sibling 文件的真实路径。 -3. 子资源关系是通过外部文件名和外部 manifest 维持的。 -4. 新增资源类型时,导入器很容易继续沿用“往 artifact 目录里再塞几个文件”的模式。 - -### 2.3 为什么这个问题越晚改越难 - -随着资源类型增加,当前模式会继续扩散到: - -1. `Model` -2. `Shader` -3. `Volume` -4. `GaussianSplat` -5. 未来的 `AnimationClip / Skeleton / Audio / VFX / Prefab-like Asset` - -越往后,耦合会越来越深: - -1. 更多 importer 会直接生成多个磁盘文件。 -2. 更多 loader 会默认自己拿到的就是磁盘路径。 -3. 更多测试会把 artifact 文件名结构视为系统契约。 -4. 后面再做统一 container 时,每种资源都要单独回迁一次。 - -所以这次重构如果要做,越早越值。 - ---- - -## 3. 重构目标 - -本轮重构的最终目标如下: - -1. `Library` 中“一个 artifactKey 对应一个单文件 container”。 -2. artifact container 内部可以保存主资源和多个子资源 entry。 -3. `ArtifactDB` 记录的是“工件文件 + 条目索引信息”,不再把目录结构当成核心语义。 -4. loader 不再依赖 sibling 文件真实存在,而是通过 container entry 加载。 -5. shader 编译缓存成为正式的 `Library` 子系统,支持 `D3D12 / Vulkan / OpenGL` 多后端扩展。 -6. 老项目和旧 `Library` 尽量兼容迁移,避免一次性炸库。 - ---- - -## 4. 核心架构决策 - -### 4.1 一个 artifact 必须变成一个单文件 container - -正式改为: - -1. 一个 `artifactKey` 只对应一个 artifact 文件。 -2. artifact 文件内部包含 header、目录表、entry 表、payload 区。 -3. entry 可以表达: - - main asset - - subasset - - import metadata - - dependency snapshot - - debug manifest - -建议目标形态: - -1. `Library/Artifacts/07/071822a133c727103210833b93d2b846.xca` -2. 或保留无扩展 hash 文件形式,但内部仍然是统一 container - -这里更重要的是“单文件容器语义”,不是扩展名本身。 - -### 4.2 子资源必须从真实文件变成逻辑 entry - -当前的: - -1. `mesh_0.xcmesh` -2. `material_0.xcmat` -3. `texture_0.xctex` -4. `subassets.tsv` - -后续都不再作为 artifact 目录中的真实文件暴露。 - -改为: - -1. container entry `main` -2. container entry `mesh/0` -3. container entry `material/0` -4. container entry `texture/0` -5. container entry `manifest/subassets` - -这样做的目的不是“隐藏文件”,而是统一系统抽象: - -1. importer 只负责产出 entry。 -2. loader 只负责按 entry 解析。 -3. 外部磁盘布局不再影响运行时语义。 - -### 4.3 `ArtifactDB` 只维护工件语义,不维护目录语义 - -`ArtifactDB` 需要从当前的字段语义升级为: - -1. `artifactKey` -2. `artifactPath` -3. `artifactFormatVersion` -4. `mainEntryId` -5. `resourceType` -6. `dependencySignature` -7. `sourceHash / metaHash / importerVersion` -8. 可选的 `subasset index snapshot` - -不再把下列内容作为核心前提: - -1. `artifactDirectory` -2. `mainArtifactPath` 必须是磁盘上的某个独立 sibling 文件 - -### 4.4 Shader 编译缓存必须纳入 Library 正式体系 - -shader 编译缓存不能继续以“某个功能修复补丁”的方式存在,必须被定义为 `Library` 正式缓存层的一部分。 - -正式目标: - -1. 以编译 key 唯一标识一份 shader bytecode / program binary。 -2. 编译 key 必须包含: - - shader 源文件签名 - - include 依赖签名 - - keyword / variant 宏 - - backend 类型 - - compiler 类型与版本 - - profile / entry point - - 相关编译选项 -3. 多后端共用同一套 key 生成规则和缓存查询流程。 -4. 后端各自写入自己的 payload,不共享错误的二进制格式。 - -### 4.5 必须坚持“统一接口,多后端实现” - -这次重构不能只为 `D3D12` 做一套临时特化路径。 - -必须采用: - -1. 上层统一的 `ShaderCompilationCache` 接口 -2. 后端相关的 key 参与规则 -3. 后端相关的 payload 类型 -4. 后端相关的反序列化逻辑 - -这样后面: - -1. `D3D12` 存 DXIL / DXBC -2. `Vulkan` 存 SPIR-V -3. `OpenGL` 可以先存 source variant 或 program binary capability 结果 - -都能挂到同一架构下,而不是分裂成三套缓存系统。 - ---- - -## 5. 目标目录与数据布局 - -### 5.1 Library 根目录 - -建议最终保留如下根层文件: - -1. `Library/assets.db` -2. `Library/artifacts.db` -3. `Library/shadercache.db` -4. `Library/assets.db.tmp/.bak/.lock` -5. `Library/artifacts.db.tmp/.bak/.lock` -6. `Library/shadercache.db.tmp/.bak/.lock` - -说明: - -1. 单文件数据库应放在 `Library` 根目录,不再额外套目录。 -2. `.tmp/.bak/.lock` 是数据库运行期一致性与恢复所需的辅助文件,属于正常成本,应保留。 - -### 5.2 Artifact 内容目录 - -建议统一为: - -1. `Library/Artifacts/<2-hex-shard>/.xca` - -说明: - -1. 仍然保留分片目录,避免单目录堆积过多文件。 -2. 从“目录工件”改为“单文件工件”。 -3. 这里已经足够接近 Unity 的外观,但关键收益来自统一 container,不来自“像 Unity”本身。 - -### 5.3 Shader 编译缓存目录 - -建议使用: - -1. `Library/ShaderCache//<2-hex-shard>/.xcbc` - -说明: - -1. 这是运行时编译产物,不建议和 source artifact 混在同一目录。 -2. 它本质上也是缓存工件,但生命周期和失效条件不同。 -3. 数据库仍然在根目录,二进制 payload 用分片目录管理。 - ---- - -## 6. Artifact Container 格式设计 - -### 6.1 容器最小结构 - -每个 `.xca` 至少包含: - -1. 文件头 -2. 格式版本 -3. 工件类型 -4. entry 数量 -5. entry 目录表 -6. payload 区 -7. 校验信息 - -### 6.2 Entry 元信息 - -每个 entry 需要至少记录: - -1. `entryId` -2. `entryName` -3. `resourceType` -4. `localID` -5. `offset` -6. `size` -7. `compression` -8. `flags` - -### 6.3 Manifest 内置化 - -原本外置的: - -1. `subassets.tsv` - -后续变为: - -1. container 内置 manifest entry -2. 或者直接由 entry table 本身承担 manifest 作用 - -原则是: - -1. 子资源映射不能再依赖工件目录里的额外文本文件。 - -### 6.4 调试能力不能丢 - -虽然外部从多文件变成单文件,但不能把可调试性彻底丢掉。 - -因此建议提供: - -1. debug dump 工具 -2. artifact inspect 命令 -3. container 列表查看能力 -4. 按需导出某个 entry 的开发期工具 - -这样能避免后续排障时变成纯黑盒。 - ---- - -## 7. 对导入链路的改造要求 - -### 7.1 Importer 层统一改为“写 entry” - -各 importer 不再自己决定在 artifact 目录里创建几个文件。 - -统一改为: - -1. importer 构建内存中的 artifact package -2. 向 package 写入 main entry -3. 按需写入 subasset entry -4. 由统一 `ArtifactWriter` 一次性落成单文件 - -### 7.2 各类资源的落地规则 - -建议采用如下规则: - -1. `Texture` - - 通常一个 main entry 即可 -2. `Material` - - 通常一个 main entry 即可 -3. `Shader` - - 主 entry + 变体元数据 entry + 预编译记录 entry -4. `Model` - - 主 model entry + mesh/material/texture subasset entries -5. `Volume` - - 主 volume entry + 可选页表/brick 元数据 entry -6. `GaussianSplat` - - 主 splat entry + 索引/排序辅助元数据 entry - -### 7.3 导入器必须停止暴露真实 sibling 文件路径 - -后续 importer 不应再让外部系统依赖: - -1. `mesh_0.xcmesh` -2. `material_3.xcmat` -3. `texture_1.xctex` - -如果某处仍然需要这种路径式访问,只能通过兼容层临时桥接,不能作为新架构继续扩散。 - ---- - -## 8. 对加载链路的改造要求 - -### 8.1 Loader 必须从“读文件路径”改成“读 artifact entry” - -未来 loader 的输入应逐步变为: - -1. artifact 文件路径 -2. entry id / local id / entry name - -而不是: - -1. 一个已经存在于磁盘上的子文件路径 - -### 8.2 ResourceManager / AssetDatabase 需要新增解析能力 - -需要正式提供: - -1. `ResolveMainArtifactEntry(...)` -2. `ResolveSubAssetEntry(...)` -3. `OpenArtifactContainer(...)` -4. `LoadResourceFromArtifactEntry(...)` - -这样 `AssetDatabase` 和 `ResourceManager` 就能以“工件条目”作为统一边界。 - -### 8.3 允许阶段性兼容旧式 Loader - -为了降低一次性改造风险,允许在迁移阶段存在: - -1. 新容器 loader -2. 旧文件路径 loader - -但要求: - -1. 新导入默认只产出新容器 -2. 旧 loader 仅承担读旧缓存或读测试基线的过渡职责 -3. 最终必须收口到容器 entry 模型 - ---- - -## 9. Shader 编译缓存设计 - -### 9.1 目标 - -shader 缓存系统要解决三件事: - -1. 避免同一 shader variant 在同一 backend 上反复现场编译 -2. 避免 editor 启动时因大批 shader 同步编译造成长时间卡顿 -3. 把编译结果纳入 `Library` 生命周期管理,而不是散落在临时模块里 - -### 9.2 Shader 缓存分层 - -建议分成两层: - -1. `Shader Artifact` - - 记录 shader 源资源、变体定义、依赖签名、反射元数据 -2. `Shader Bytecode Cache` - - 记录某 backend、某 profile、某 variant 的实际编译产物 - -这样做的原因是: - -1. shader 资源本身是 source artifact 的一部分 -2. 字节码则是“shader artifact 在某后端上的派生产物” - -### 9.3 多后端适配策略 - -必须从一开始就定义好多后端字段: - -1. `backend = D3D12 / Vulkan / OpenGL` -2. `compiler = DXC / glslang / driver / other` -3. `profile = ps_6_0 / vs_6_0 / spirv-vulkan1.2 / glsl-450` -4. `bytecodeFormat = DXIL / SPIRV / GL_PROGRAM_BINARY / SOURCE_FALLBACK` - -### 9.4 OpenGL 的现实处理 - -OpenGL 不一定能像 D3D12 一样稳定落真正可移植字节码,因此计划要区分: - -1. 理想路径:驱动支持 program binary,可缓存 program binary -2. 保守路径:缓存预处理后的 shader source / variant expansion 结果 -3. 最低保证:即使不能完全复用最终 program,也要复用前置预处理和 variant 展开结果 - -也就是说,多后端适配不等于三端完全同构,而是统一框架下允许 payload 能力不同。 - ---- - -## 10. 迁移策略 - -### 10.1 不能一次性硬切 - -因为当前项目已经存在: - -1. 老 `Library` -2. 老 artifact 目录结构 -3. 依赖老结构的 loader -4. 依赖老结构的测试 - -所以必须采用兼容迁移。 - -### 10.2 建议迁移阶段 - -#### 阶段 A:底层能力先落地 - -1. 新增 `ArtifactContainerReader/Writer` -2. 新增 container format version -3. 新增 `ShaderCompilationCache` 正式接口 -4. 新增调试工具和 dump 工具 - -#### 阶段 B:数据库与解析接口升级 - -1. `ArtifactDB` 加入新字段 -2. `AssetDatabase` 提供 entry 级解析 -3. `ResourceManager` 能按 entry 加载 - -#### 阶段 C:先迁移低复杂度资源 - -1. `Texture` -2. `Material` -3. `Shader` - -原因: - -1. 这三类资源 entry 结构简单 -2. 容器化收益直接 -3. 风险比 `Model` 低 - -#### 阶段 D:迁移复杂资源 - -1. `Model` -2. `Volume` -3. `GaussianSplat` - -这里才是这次重构的真正硬骨头。 - -#### 阶段 E:兼容收口 - -1. 新导入全面只写新 container -2. 老 artifact 仅保留读取兼容 -3. 清理临时桥接路径 -4. 测试与工具全部对齐新语义 - -### 10.3 旧缓存处理策略 - -建议采用: - -1. 读旧、写新 -2. 命中新格式则直接用 -3. 命中旧格式则读取并标记可重导 -4. 必要时提供“一键清空旧缓存并全量重导”入口 - ---- - -## 11. 分阶段执行计划 - -## Phase 1:方案冻结与底层抽象 - -目标: - -1. 冻结 artifact container 格式 -2. 冻结 shader compile key 规则 -3. 冻结 `ArtifactDB` 新字段语义 - -交付物: - -1. `ArtifactContainer` 设计文档 -2. `ShaderCompilationCache` 设计文档 -3. `Library` 目录与文件命名规范 - -验收: - -1. 所有核心模块对新名词和新边界达成一致 -2. 不再新增基于目录 sibling 文件的新逻辑 - -## Phase 2:容器读写基础设施 - -目标: - -1. 写出可工作的 `.xca` 读写器 -2. 写出 entry table、payload、校验、版本管理 - -交付物: - -1. `ArtifactContainerWriter` -2. `ArtifactContainerReader` -3. artifact inspect / dump 工具 -4. 单元测试 - -验收: - -1. 可写入多个 entry -2. 可随机读取指定 entry -3. 可检测格式版本与损坏文件 - -## Phase 3:数据库与资源解析升级 - -目标: - -1. 让 `AssetDatabase` 和 `ResourceManager` 理解 entry 语义 - -交付物: - -1. `ArtifactDB` schema 升级 -2. entry 级 resolve API -3. 新旧格式兼容查询路径 - -验收: - -1. 不依赖真实 sibling 文件也能定位主资源和子资源 -2. 旧缓存仍可读 - -## Phase 4:Shader 缓存正式化 - -目标: - -1. 把 shader 编译缓存正式接入 `Library` -2. 打通多后端 key 体系 - -交付物: - -1. `shadercache.db` -2. `Library/ShaderCache/...` -3. D3D12 正式字节码缓存 -4. Vulkan/OpenGL 占位与接口接入 -5. 计时日志、命中日志、失效日志 - -验收: - -1. D3D12 重启 editor 后能稳定命中缓存 -2. shader compile log 能明确区分 cache hit / miss -3. Vulkan/OpenGL 不破坏接口一致性 - -## Phase 5:低复杂度资源迁移 - -目标: - -1. 先迁移 `Texture / Material / Shader` - -交付物: - -1. 新 importer 写 container -2. 新 loader 读 container -3. 旧逻辑兼容桥接 - -验收: - -1. 三类资源脱离目录式 artifact -2. 现有测试通过 - -## Phase 6:复杂资源迁移 - -目标: - -1. 迁移 `Model / Volume / GaussianSplat` - -交付物: - -1. `Model` 子资源 entry 化 -2. `Volume` 复杂数据 entry 化 -3. `GaussianSplat` 复杂数据 entry 化 -4. 新的 subasset resolve 机制 - -验收: - -1. 不再需要 `subassets.tsv` -2. 不再依赖 `mesh_0.xcmesh/material_0.xcmat/...` 真实文件 -3. 场景加载、运行时 streaming、编辑器选择与重载行为正常 - -## Phase 7:兼容收口与库清理 - -目标: - -1. 清理临时桥接逻辑 -2. 完成旧格式收口 - -交付物: - -1. 旧 artifact 兼容策略文档 -2. `Library` 清理工具 -3. 最终回归测试与性能报告 - -验收: - -1. 新项目默认只产生新格式 -2. 旧项目迁移路径明确 -3. 文档、测试、工具全部收口 - ---- - -## 12. 风险评估 - -### 12.1 高风险点 - -1. `Model` 子资源引用路径改造 -2. 旧 loader 和新 loader 并存期间的行为分叉 -3. `ArtifactDB` schema 迁移 -4. shader 缓存 key 设计不完整导致误命中或漏命中 -5. OpenGL 后端缓存能力不如 D3D12/Vulkan 稳定 - -### 12.2 主要回归风险 - -1. 资源重复导入 -2. 子资源引用失效 -3. 重启后缓存不命中 -4. editor 启动时再次出现同步长阻塞 -5. 清理 `Library` 后行为和旧项目不一致 - -### 12.3 风险控制原则 - -1. 每种资源类型单独迁移、单独验收 -2. 每阶段都保留明确日志 -3. 优先做“读旧写新”,不要先做“只认新格式” -4. 性能优化必须建立在日志可证伪的基础上,不能靠感觉判断 - ---- - -## 13. 验收标准 - -重构完成后,应满足以下标准: - -1. `Library/Artifacts` 中一个 artifactKey 对应一个单文件 container。 -2. `assets.db / artifacts.db / shadercache.db` 位于 `Library` 根目录。 -3. `Texture / Material / Shader / Model / Volume / GaussianSplat` 都能通过统一 container 读写。 -4. 新增资源类型时,不需要再发明“这个类型自己往 artifact 目录塞几个文件”的模式。 -5. shader 缓存在 `D3D12` 上稳定命中,`Vulkan / OpenGL` 具备同框架下的扩展位。 -6. editor 启动和场景 ready 时间相较当前基线有可测收益。 -7. 调试工具可以查看 container 内部 entry,不会因为单文件化而失去排障能力。 - ---- - -## 14. 本计划明确不做的事情 - -本计划不做以下偏题内容: - -1. 为了“长得像 Unity”而做无意义目录重命名 -2. 把所有缓存强行塞进一个数据库 blob 字段里 -3. 为了单文件化而牺牲调试能力 -4. 只做 D3D12 特化、把其他后端晾着不管 -5. 一次性删掉旧 `Library` 兼容路径 - ---- - -## 15. 推荐执行顺序 - -建议严格按下面顺序推进: - -1. 先冻结 container 格式和 shader key 规则 -2. 再做底层 reader/writer 与数据库升级 -3. 先迁移 `Texture / Material / Shader` -4. 再迁移 `Model / Volume / GaussianSplat` -5. 最后做兼容收口、清理和性能复盘 - -这条顺序的原因很简单: - -1. 先做底层,后续每种资源都能复用 -2. 先啃简单资源,能尽快验证架构不是错的 -3. 把最难的复杂资源放在基础设施稳定之后处理,整体风险最低 - ---- - -## 16. 最终判断 - -这个重构的代价不小,但如果项目接下来还会持续扩展资源类型,那么它属于“越早做越划算”的长期基础设施工程。 - -真正值得做的不是把目录外观改得像 Unity,而是: - -1. 统一 artifact 的存储模型 -2. 统一子资源的表达方式 -3. 统一 shader 编译缓存的 Library 化管理 -4. 统一多后端的缓存接口 - -只要这四件事做对了,后面资源类型再增加,系统复杂度才不会继续失控。 diff --git a/docs/used/Library资产导入与缓存系统收口计划_完成归档_2026-04-03.md b/docs/used/Library资产导入与缓存系统收口计划_完成归档_2026-04-03.md deleted file mode 100644 index 6b254216..00000000 --- a/docs/used/Library资产导入与缓存系统收口计划_完成归档_2026-04-03.md +++ /dev/null @@ -1,536 +0,0 @@ -# Library资产导入与缓存系统收口计划 - -归档状态:2026-04-03,当前 `project` 范围内的 Library 导入与缓存系统收口已完成,本文档转为归档记录。 - -## 0. 文档定位 - -这份文档不是上一轮“从零搭一套 Unity 式 Library 体系”的继续描述,而是当前阶段的正式收口文档。 - -### 0.1 当前执行进度(2026-04-03) - -| 阶段 | 状态 | 当前结果 | -| --- | --- | --- | -| 阶段 A:协议定稿与旧兼容删除 | 已完成 | 已删除 `mesh=` / `materials=` 旧兼容读取,项目资产序列化正式收敛到 `AssetRef`,路径字段仅保留给虚拟资源;scene 迁移工具链已移除。 | -| 阶段 B:导入与缓存边界收口 | 已完成 | `AssetImportService` 已接管 Library 根目录、快照构建、缓存重建与 artifact 保证入口;`ProjectAssetIndex` 已只消费服务快照;`ResourceManager` 已收口为运行时缓存/异步调度入口,并提供 `RefreshProjectAssets` / `RebuildProjectAssetCache`。 | -| 阶段 C:进行中 | 已完成 `xcmat` 纹理绑定的 `AssetRef + lazy resolve` 首轮收口;下一步继续压缩缓存命中后的恢复成本,并检查是否还存在主线程等待点。 | -| 阶段 D:最小工具闭环 | 已完成 | 已完成 `Reimport Selected Asset / Reimport All Assets / Clear Library` 正式入口;已补 `Project` 面板最小导入状态输出;已接入 orphan artifact 清理。 | - -### 0.2 当前已完成的收口结果(2026-04-03) - -- 阶段 A 已完成并验证通过: - - `MeshFilterComponent` 对项目 mesh 只序列化 `meshRef`,`meshPath` 只保留给 `builtin://...` - - `MeshRendererComponent` 对项目 material 只序列化 `materialRefs`,`materialPaths` 只保留给 `builtin://...` - - 旧的 `mesh=` / `materials=` 兼容读取已移除 - - scene 迁移命令、菜单、report 链路已删除 -- 阶段 B 已完成并验证通过: - - `AssetImportService` 对外新增 `LookupSnapshot`、`ImportedAsset`、`GetLibraryRoot()`、`RebuildLibraryCache()` - - `ProjectAssetIndex` 不再直接拼装 `AssetDatabase` 细节,而是只刷新服务快照 - - `ResourceManager` 不再暴露旧式导入数据库心智,新增 `RefreshProjectAssets()`、`RebuildProjectAssetCache()`、`GetProjectLibraryRoot()` - - `AssetDatabase` 初始化后会落盘 `ArtifactDB/artifacts.db`,使空 Library 结构稳定成型 -- 阶段 C 已开始并完成首个子项: - - `Material` 纹理绑定新增稳定 `AssetRef` 元数据,运行时按需把 `AssetRef` 解析为加载路径 - - `xcmat` 协议已升级为纹理绑定写入 `AssetRef + load path hint` - - `AssetDatabase` importer version 已提升,旧 material artifact 将自动失效并重建 -- 阶段 D 已开始并完成首轮工具口: - - `AssetDatabase` 已新增 `TryGetImportableResourceType()`、`ReimportAsset()`、`ReimportAllAssets()` - - `AssetImportService` 已新增 `ClearLibraryCache()`、`ReimportAllAssets()`、`ReimportAsset()` - - `ResourceManager` 已新增 `CanReimportProjectAsset()`、`ReimportProjectAsset()`、`ClearProjectLibraryCache()` - - editor `Assets` 菜单已新增 `Reimport Selected Asset`、`Reimport All Assets`、`Clear Library` - - `AssetImportService` 已新增导入状态快照,`Project` 面板工具栏会显示最近一次导入/清库/清理状态 - - `AssetDatabase` 已新增 orphan artifact 清理,`Refresh / Reimport / 自动重导入` 后都会回收未被 `ArtifactDB` 引用的 `Library/Artifacts/*` 目录 -- 已完成 focused 验证: - - `asset_tests`: - - `ResourceManager_Test.ConcurrentAsyncLoadsCoalesceSameMeshPath` - - `ResourceManager_Test.AssetLookupFallbackRefreshesSnapshotForNewProjectAsset` - - `ProjectAssetIndex_Test.RefreshesSnapshotThroughImportServiceOnCacheMiss` - - `AssetImportService_Test.RebuildLibraryCacheKeepsStableAssetRefs` - - `ResourceManager_Test.RebuildProjectAssetCacheRefreshesLookupState` - - `AssetImportService_Test.ClearLibraryAndReimportAllAssetsManageArtifactsExplicitly` - - `AssetImportService_Test.ImportStatusTracksExplicitOperationsAndRefreshCleanup` - - `ResourceManager_Test.ReimportProjectAssetBuildsArtifactForSelectedPath` - - `editor_tests`: - - `EditorActionRoutingTest.ProjectCommandsExposeAssetCacheMaintenanceActions` - - `EditorActionRoutingTest.ProjectCommandsReimportSelectedAssetAndClearLibraryDriveAssetCache` - - `EditorActionRoutingTest.ProjectCommandsReportWhenScriptAssembliesCanBeRebuilt` - - `components_tests`: - - `MeshRendererComponent_Test.SerializeAndDeserializeLoadsProjectMaterialByAssetRef` - - `MeshRendererComponent_Test.DeferredSceneDeserializeLoadsProjectMaterialAsync` - - `material_tests`:46/46 通过 - - `mesh_tests`:33/33 通过 - - `scene_tests`: - - `Scene_ProjectSample.DeferredLoadBackpackSceneEventuallyRestoresBackpackMesh` - - `Scene_ProjectSample.DeferredLoadBackpackSceneEventuallyProducesVisibleRenderItems` - - `XCEditor` Release 已成功编译 - -旧方案文档已经归档到: - -- `docs/plan/used/Unity式Library资产导入与缓存系统重构方案.md` - -归档原因很简单: - -- 旧文档解决的是“从没有 Library,到建立 Library 基础设施”的问题。 -- 现在的问题已经不是“要不要做 Library”,而是“怎么把当前这套半过渡、半正式的实现收成一套干净的正式系统”。 - -本轮收口以以下前提为准: - -- 只服务当前 `project` 这一个项目。 -- 不兼容旧版本 scene / component 资产引用格式。 -- 不再提供旧格式迁移工具。 -- 可以直接改写 `project` 下现有场景和资源源文件。 -- 目标不是继续堆补丁,而是形成一套边界清晰、行为稳定、可长期维护的正式实现。 - ---- - -## 1. 当前收口基线 - -当前系统已经具备以下基础,这些不是本轮要推倒重来,而是本轮收口的起点: - -- 已有 Unity 风格的 `Library` 目录结构: - - `Library/SourceAssetDB` - - `Library/ArtifactDB` - - `Library/Artifacts` -- 已有 `.meta + AssetGUID` 机制,项目资产已经具备稳定身份。 -- 已有 `AssetRef` 结构,运行时已经可以通过 `AssetRef` 回查项目资产。 -- 已有纹理、材质、模型 artifact: - - `xctex` - - `xcmat` - - `xcmesh` -- 已有 `AssetImportService`,用于承接导入与 artifact 保证逻辑。 -- 已有 `ProjectAssetIndex`,用于承接项目资产路径与 `AssetRef` 快照索引。 -- `ResourceManager` 已不再直接承担全部导入数据库职责,而是开始回到运行时调度器角色。 -- scene 打开链路已经接入 deferred restore,`MeshFilterComponent` / `MeshRendererComponent` 能在反序列化后异步恢复项目资源。 -- 当前 `project/Assets/Scenes/Main.xc`、`project/Assets/Scenes/Backpack.xc` 已经具备 `AssetRef` 版本数据,可作为最终格式收口的直接修改对象。 - -当前真正没有收口的,不是“功能不存在”,而是下面这三类问题还在: - -- 还保留着旧格式兼容和双写逻辑。 -- 组件 / scene / editor 工具链里还残留过渡期代码。 -- 材质与贴图恢复仍不够轻,缓存命中后仍有额外同步成本。 - ---- - -## 2. 本轮收口的核心原则 - -### 2.1 不保留旧版本兼容 - -这是本轮与上一轮最大的区别。 - -本轮明确不做以下事情: - -- 不兼容历史 `mesh=` / `materials=` 场景字段。 -- 不兼容“项目资产仍按普通文件路径序列化”的旧格式。 -- 不保留 `path + AssetRef` 双写作为长期状态。 -- 不保留 `Migrate Scene AssetRefs` 之类的迁移工具与菜单。 - -允许保留的只有两类稳定引用: - -- `AssetRef` - - 用于 `project/Assets` 下的正式项目资产。 -- `builtin://...` - - 用于内置几何体、内置默认材质等虚拟内置资源。 - -这里的 `builtin://` 不是旧版本兼容,而是引擎内部稳定资源协议,可以继续保留。 - -### 2.2 只对当前项目做干净落地 - -因为当前只服务一个项目,所以这轮不需要为了“以后可能导入旧项目”保留复杂迁移链路。 - -本轮允许直接做以下事情: - -- 直接改写 `project/Assets/Scenes/*.xc` -- 直接删除旧序列化字段的读写逻辑 -- 直接删除旧迁移命令、菜单、报告结构 -- 直接调整测试数据到最终格式 - -### 2.3 运行时只负责“用缓存”,不负责“解释历史” - -收口完成后,运行时层要满足: - -- scene 反序列化只恢复最终格式数据 -- `ResourceManager` 只做运行时缓存、异步调度、artifact 读取入口 -- 导入与索引职责留在 `AssetImportService` / `ProjectAssetIndex` -- 不再在运行时主链路里背负旧协议兼容判断 - -### 2.4 收口优先级高于“继续设计更大系统” - -本轮目标是把现有系统收干净,不追求继续扩成更庞大的框架。 - -因此本轮只做两件事: - -- 删除过渡态 -- 补齐正式版闭环 - -不在本轮追求: - -- prefab 全面资产管线化 -- 动画、音频、shader 全类型导入器 -- 远程缓存 -- 多项目兼容框架 -- 一步到位把所有内部类都拆到最细 - ---- - -## 3. 收口后的目标形态 - -### 3.1 最终数据协议 - -收口后,scene / component 的资产引用协议定为: - -- 项目 mesh:只写 `meshRef` -- 项目 material:只写 `materialRefs` -- 内置 mesh:写 `meshPath=builtin://...` -- 内置 material:写 `materialPaths=builtin://...` - -不再允许以下状态作为正式协议存在: - -- 项目资产依旧只存普通文件路径 -- 项目资产同时写路径和 `AssetRef` -- 反序列化时优先猜测旧字段再兜底 - -### 3.2 最终运行时边界 - -收口后各层职责如下: - -- `AssetImportService` - - 工程扫描 - - `.meta` 管理 - - SourceAssetDB / ArtifactDB 维护 - - `EnsureArtifact` - - `Refresh` - - `Reimport` / `Clear Library` 入口 -- `ProjectAssetIndex` - - 项目资产路径快照 - - `AssetRef <-> 项目路径` 映射 - - 项目资产查找缓存 -- `ResourceManager` - - runtime object cache - - async load coalescing - - artifact runtime load dispatch - - deferred scene load 控制 -- Component / Scene - - 只保存最终资产引用 - - 不承担旧协议迁移职责 - -### 3.3 最终用户行为 - -收口后应达到以下体验: - -- 第一次导入可能慢,但之后再打开同一个 scene,不应再同步重跑原始 `obj/png/jpg` 导入。 -- 打开含有大型 OBJ 的 scene 时,不应长时间卡死 editor 主窗口。 -- 命中 artifact 后,mesh/material 恢复应尽量走异步路径。 -- 关闭 editor 再打开后,项目资产引用应稳定恢复,不出现“模型丢失、材质丢失、内置 sphere 丢失”这类状态回退问题。 -- 用户能明确知道当前是“命中缓存”还是“正在导入/重建缓存”。 - ---- - -## 4. 本轮必须删除的过渡态 - -这部分是收口计划的关键,不删掉这些内容,系统就始终处于过渡状态。 - -### 4.1 删除旧序列化兼容 - -目标: - -- 删除 `MeshFilterComponent` 对旧 `mesh=` 字段的长期兼容读取。 -- 删除 `MeshRendererComponent` 对旧 `materials=` 字段的长期兼容读取。 -- 删除“项目资产 path 与 `AssetRef` 双写”的长期行为。 - -允许保留: - -- 对 `builtin://` 的显式处理。 - -禁止保留: - -- “先读 path,不行再猜 `AssetRef`” -- “为了兼容老场景继续支持项目文件路径” - -### 4.2 删除场景迁移工具 - -以下过渡期工具应整体移除: - -- `Migrate Scene AssetRefs` 菜单项 -- 对应 action / command / project manager migration report -- 专门为旧场景迁移设计的 editor 流程 - -原因: - -- 当前项目只有一个,已有 scene 可以直接重写。 -- 继续保留迁移工具,只会让新系统一直背着旧协议。 - -### 4.3 删除长期双轨测试 - -需要同步清理测试中的过渡态假设: - -- 旧格式 scene 兼容测试 -- 双写格式测试 -- 迁移工具测试 - -替换成正式版测试: - -- 最终 scene 协议测试 -- 关闭 editor 重开后的稳定恢复测试 -- artifact 命中路径测试 -- 异步恢复与渲染可见性测试 - ---- - -## 5. 本轮必须补齐的正式能力 - -### 5.1 材质与贴图真正 lazy 化 - -当前模型 artifact 虽然已经存在,但材质恢复后仍然容易把关联贴图一起拉进来,导致: - -- 场景虽然不在反序列化阶段阻塞 -- 但资源真正恢复时仍然偏重 - -本轮必须补齐: - -- `xcmat` 成为正式稳定 artifact -- `Material` 运行时对象只持有 texture `AssetRef` -- texture runtime handle 按槽位、按需解析 -- mesh 恢复不顺手触发整套材质贴图同步加载 - -这是“缓存命中了但还是觉得重”的核心收口项。 - -### 5.2 明确导入与运行时边界 - -虽然最近已经引入: - -- `AssetImportService` -- `ProjectAssetIndex` - -但本轮要进一步明确: - -- `ResourceManager` 外部不再暴露旧式导入心智 -- 项目资产查找统一经过 `ProjectAssetIndex` -- artifact 生成统一经过 `AssetImportService` -- `AssetDatabase` 作为底层实现细节继续收口到 service 后面 - -本轮的目标不是一定把 `AssetDatabase` 拆成三个类,而是先把“对外边界”收干净。 - -也就是说,本轮要求: - -- 类内可暂时还偏胖 -- 但对外职责必须已经清晰 - -### 5.3 缓存运维闭环 - -正式版至少要有以下最小运维能力: - -- 单资源 Reimport -- `Reimport All` -- `Clear Library` -- 项目启动时检查并重建缺失缓存 -- 删除源资源后识别 orphan artifact - -如果这些没有,系统虽然能工作,但一旦缓存脏掉就难以恢复,仍然不算收口。 - -### 5.4 可观测性闭环 - -本轮至少要做到用户能看见: - -- 当前资源是命中 artifact 还是触发重导入 -- 正在导入哪个资源 -- 导入失败原因 -- scene 正在等待哪些资源恢复 - -不要求本轮先做完整面板,但至少要把日志、状态文案、必要的调试输出整理成稳定可用的最小版本。 - ---- - -## 6. 实施阶段 - -## 阶段 A:协议定稿与旧兼容删除 - -### 目标 - -把当前半兼容、半正式的 scene / component 数据协议收成最终格式。 - -### 任务 - -- 改 `MeshFilterComponent`: - - 项目资产只写 `meshRef` - - 只对 `builtin://` 保留 `meshPath` - - 删除旧 `mesh=` 历史兼容读取 -- 改 `MeshRendererComponent`: - - 项目资产只写 `materialRefs` - - 只对 `builtin://` 保留 `materialPaths` - - 删除旧 `materials=` 历史兼容读取 -- 直接改写 `project/Assets/Scenes/Main.xc` -- 直接改写 `project/Assets/Scenes/Backpack.xc` -- 如果项目里还有其他 scene / snapshot / prefab 使用旧项目资产路径,一并直接改成最终格式 - -### 同步删除 - -- `Migrate Scene AssetRefs` action -- `ProjectCommands` 中对应 migration command -- `ProjectManager` 中对应 migration report / migration logic -- 相关测试 - -### 阶段完成标准 - -- 项目场景中不再存在旧项目资产路径协议 -- engine/editor 中不再存在迁移工具入口 -- 组件反序列化代码只处理最终协议与 builtin 协议 - -## 阶段 B:导入与缓存边界收口 - -### 目标 - -让 `Library` 系统对外呈现为一套明确的正式接口,而不是几个类拼在一起的过渡实现。 - -### 任务 - -- 把项目资产索引访问统一走 `ProjectAssetIndex` -- 把 artifact 生成与保证统一走 `AssetImportService` -- 收敛 `ResourceManager` 中剩余的导入数据库耦合 -- 梳理 `AssetDatabase` 内部接口,限制它作为 service 内部实现使用 -- 增加明确的 `Refresh / Reimport / Clear Library` 入口 - -### 阶段完成标准 - -- editor / runtime 外部调用时,心智模型已经稳定为: - - 找项目资产:`ProjectAssetIndex` - - 产出或校验 artifact:`AssetImportService` - - 取 runtime object:`ResourceManager` - -## 阶段 C:性能闭环 - -### 目标 - -把“看起来已经异步,但恢复仍然偏重”的成本继续压下去。 - -### 任务 - -- 材质贴图 lazy resolve -- 减少 mesh 恢复时顺手加载整套关联资源 -- 校验首次导入、二次打开、关闭重开三种路径的时间差异 -- 清理仍可能在主线程触发的大锁与同步等待点 -- 必要时加入轻量 placeholder / loading state 文案 - -### 阶段完成标准 - -- 含 OBJ 的 scene 再次打开时,editor 主窗口不出现长时间假死 -- artifact 命中时恢复成本显著低于首次导入 -- 关闭 editor 重开后的恢复链路仍然稳定 - -## 阶段 D:最小工具闭环 - -### 目标 - -把这套系统变成“可维护”的,而不是只能靠调试日志救火。 - -### 任务 - -- 已完成:增加 `Reimport Asset` -- 已完成:增加 `Reimport All` -- 已完成:增加 `Clear Library` -- 已完成:增加最小导入状态输出 -- 已完成:增加 orphan artifact 清理 - -### 阶段完成标准 - -- 用户可以主动重建缓存 -- 缓存脏掉时不需要手工删文件再碰运气 -- 导入失败时能直接看到原因 - ---- - -## 7. 验收标准 - -本轮收口完成,必须同时满足以下条件: - -### 7.1 协议层 - -- 项目资产引用正式统一为 `AssetRef` -- 不再保留旧 scene / component 项目资产路径兼容协议 -- 不再保留迁移工具 - -### 7.2 架构层 - -- `AssetImportService` / `ProjectAssetIndex` / `ResourceManager` 的对外职责稳定 -- 运行时不再承担旧协议解释职责 -- `Library` 已经是正式依赖,而不是临时缓存补丁 - -### 7.3 功能层 - -- OBJ / 贴图 / 材质二次打开优先命中 artifact -- 关闭 editor 重开后项目 scene 能稳定恢复 -- builtin sphere / cube / default material 不会因为缓存系统而丢失 - -### 7.4 性能层 - -- 打开含大型 OBJ 的 scene 时不长时间卡死 editor -- 二次打开与首次导入的体感耗时有明显区分 -- 命中 artifact 后不再退回源文件同步导入主路径 - -### 7.5 工具层 - -- 具备最小 `Reimport / Reimport All / Clear Library` 能力 -- 有最小可观测性输出 - ---- - -## 8. 本轮不做的内容 - -为了尽快收口,本轮明确不做以下扩展: - -- prefab 全量资产协议翻新 -- 动画 / 音频 / shader 新 importer -- 远程缓存 -- 多项目历史版本兼容框架 -- 完整图形化导入面板 -- 一次性把所有 `AssetDatabase` 内部实现拆到最细 - -这些内容后续可以继续做,但不应该阻塞当前正式收口。 - ---- - -## 9. 推荐执行顺序 - -建议按下面顺序推进,避免边做边反复回滚: - -1. 先做阶段 A,直接把项目协议和旧兼容删干净。 -2. 再做阶段 B,把对外边界稳定下来。 -3. 然后做阶段 C,把“缓存命中了但恢复仍然重”的问题收掉。 -4. 最后做阶段 D,补齐最小运维与可观测性。 - -原因是: - -- 不先删旧兼容,后面所有逻辑都要双轨维护。 -- 不先把边界定清,性能问题会一直和历史兼容问题缠在一起。 -- 不先把主链路收稳,工具做出来也只是给过渡态续命。 - ---- - -## 10. 本轮涉及的主要代码范围 - -本轮预计重点落在以下文件: - -- `engine/src/Components/MeshFilterComponent.cpp` -- `engine/src/Components/MeshRendererComponent.cpp` -- `engine/include/XCEngine/Core/Asset/ResourceManager.h` -- `engine/src/Core/Asset/ResourceManager.cpp` -- `engine/include/XCEngine/Core/Asset/AssetImportService.h` -- `engine/src/Core/Asset/AssetImportService.cpp` -- `engine/include/XCEngine/Core/Asset/ProjectAssetIndex.h` -- `engine/src/Core/Asset/ProjectAssetIndex.cpp` -- `engine/include/XCEngine/Core/Asset/AssetDatabase.h` -- `engine/src/Core/Asset/AssetDatabase.cpp` -- `editor/src/Managers/ProjectManager.cpp` -- `editor/src/Commands/ProjectCommands.h` -- `editor/src/Actions/EditorActions.h` -- `editor/src/Actions/MainMenuActionRouter.h` -- `tests/core/Asset/test_resource_manager.cpp` -- `tests/Components/test_mesh_render_components.cpp` -- `tests/Scene/test_scene.cpp` -- `project/Assets/Scenes/Main.xc` -- `project/Assets/Scenes/Backpack.xc` - ---- - -## 11. 一句话结论 - -从这一刻开始,这个模块的目标不再是“继续兼容旧系统”,而是: - -把当前已经跑起来的 `Library`、artifact、`AssetRef`、异步恢复链路,收成一套只服务当前项目、无旧包袱、可稳定重开的正式资产系统。 diff --git a/docs/used/MainLight方向光阴影修复计划_2026-04-13.md b/docs/used/MainLight方向光阴影修复计划_2026-04-13.md deleted file mode 100644 index 4878265e..00000000 --- a/docs/used/MainLight方向光阴影修复计划_2026-04-13.md +++ /dev/null @@ -1,356 +0,0 @@ -# MainLight 方向光阴影修复计划(归档) - -日期: `2026-04-13` - -状态: `已归档到 docs/plan/used` - -## 1. 文档定位 - -这份文档记录本轮 `MainLight` 单张方向光阴影修复工作的执行结果、参数含义和后续边界。 - -需要明确: - -- 本轮目标是先把单张 `MainLight` 阴影从“占位实现”修到“基本可用”。 -- 本轮明确不做 `CSM`、点光阴影、聚光阴影和多光源阴影编排。 -- 本轮完成后,后续如果继续推进大范围覆盖质量问题,应单开 `CSM` 专项计划。 - -## 2. 修复前的问题定义 - -修复前的核心问题有两个: - -1. 自阴影过重,表面出现明显 `acne`。 -2. 阴影边缘呈现大块锯齿,单张阴影图完全不可用。 - -这些问题在修复前同时叠加在: - -- `caster` 端深度偏移没有正式参数契约。 -- `receiver` 端 bias 没有稳定基线。 -- 单张阴影图覆盖范围过大,近处 `texel density` 太低。 -- 接收端采样只有基础方案,阴影边缘完全是硬台阶。 - -## 3. 阴影主链路 - -当前单张 `MainLight` 阴影主链如下: - -`SceneRenderRequestPlanner` --> `DirectionalShadowRenderPlan` --> `CameraRenderer` --> `ShadowCaster Pass` --> `RenderSceneData::lighting.mainDirectionalShadow` --> `BuiltinForwardPipeline` --> `forward-lit.shader / Toon.shader` - -关键代码位置: - -- 规划层 - - `engine/include/XCEngine/Rendering/Planning/SceneRenderRequestPlanner.h` - - `engine/src/Rendering/Planning/Internal/DirectionalShadowPlanning.cpp` -- 请求与运行时数据 - - `engine/include/XCEngine/Rendering/Planning/CameraRenderRequest.h` - - `engine/include/XCEngine/Rendering/FrameData/RenderSceneData.h` - - `engine/src/Rendering/Execution/CameraRenderer.cpp` -- `ShadowCaster` 偏移 - - `engine/src/Rendering/Passes/BuiltinDepthStylePassBaseResources.cpp` - - `engine/include/XCEngine/Rendering/Materials/RenderMaterialStateUtils.h` - - `engine/assets/builtin/shaders/shadow-caster.shader` -- 接收端采样 - - `engine/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp` - - `engine/assets/builtin/shaders/forward-lit.shader` - - `project/Assets/Shaders/Toon.shader` -- 面板与参数归属 - - `engine/include/XCEngine/Components/LightComponent.h` - - `engine/src/Components/LightComponent.cpp` - - `editor/src/ComponentEditors/LightComponentEditor.h` - -## 4. 本轮已落地结果 - -### Phase 0:阴影参数契约正式化 - -已完成。 - -对应提交: - -- `2ee74e7` `rendering: formalize main light shadow params` - -完成结果: - -- `MainLight shadow` 的 `map metrics / sampling` 数据有了正式结构。 -- 规划层到运行时数据链路不再依赖匿名 `float` 参数拼装。 - -### Phase 1:bias 设置正式化并接入 ShadowCaster - -已完成。 - -对应提交: - -- `1d6f2e2` `rendering: formalize main light shadow bias settings` -- `00875e0` `rendering: tune main light shadow bias defaults` - -完成结果: - -- 新增 `DirectionalShadowSamplingSettings` -- 新增 `DirectionalShadowCasterBiasSettings` -- `ShadowCaster` pass 正式消费运行时 `caster bias` -- `forward-lit / toon` 接收端继续消费统一的 `sampling contract` -- 默认值基线已经收敛到当前可用区间 - -当前默认基线: - -- `receiverDepthBias = 0.0010f` -- `normalBiasScale = 2.0f` -- `shadowStrength = 0.85f` -- `depthBiasFactor = 2.5f` -- `depthBiasUnits = 4` - -### Phase 2:单张阴影图覆盖范围收紧 - -已完成到“单图可用化”阶段。 - -对应提交: - -- `95edf04` `rendering: stabilize single-map directional shadow fitting` - -完成结果: - -- `receive-only` 物体不再参与方向光阴影拟合包围。 -- 单张阴影图默认分辨率从 `1024` 提升到 `2048`。 -- `maxFocusDistance` 从 `64.0f` 收紧到 `32.0f`。 -- `boundsPadding` 进一步从 `1.0f` 收紧到 `0.5f`。 - -当前 planner 侧有效默认值: - -- `mapDimension = 2048` -- `minFocusDistance = 5.0` -- `maxFocusDistance = 32.0` -- `minDepthRange = 20.0` -- `boundsPadding = 0.5` -- `minDepthPadding = 2.0` - -说明: - -- 这一步本质上是在提升单张阴影图的有效 `texel density`。 -- 它不能替代 `CSM`,但在不做 `CSM` 的前提下,这是当前最值当的单图修复手段。 - -### Phase 3:receiver 端滤波升级 - -已完成当前阶段的最小落地。 - -对应提交: - -- `adb6fe4` `rendering: improve main light shadow receiver filtering` - -完成结果: - -- `forward-lit.shader` 与 `Toon.shader` 已统一切到更稳定的手写 `PCF`。 -- 当前滤波的作用是软化既有阴影台阶,不再让边缘完全是 `1-bit` 硬切。 - -说明: - -- `PCF` 有作用,但它只能软化边缘,不能凭空增加几何细节。 -- 单张阴影图的覆盖范围和 `texel density` 仍然是大块感的主因。 - -### Phase 4:MainLight 面板调参入口接通 - -已完成,当前工作区已接通。 - -完成结果: - -- `Directional Light` 在开启 `Cast Shadows` 后,会出现 `Override Shadow Params` 开关。 -- 打开后,可以在灯光面板直接调整当前最关键的 5 个阴影参数。 -- 参数已挂到 `LightComponent`,并接通序列化 / 反序列化。 -- `SceneRenderRequestPlanner` 会在主方向光开启覆盖时优先读取该灯光上的阴影参数。 - -重要限制: - -- 这些覆盖参数当前只作用在被选为 `MainLight` 的方向光上。 -- 它们不是“所有灯统一生效”的全局参数。 -- `mapDimension / focusDistance / boundsPadding / minDepthPadding` 这类拟合参数仍然属于 planner 侧全局参数,没有塞进 `LightComponent`。 - -## 5. 面板参数完整说明 - -当前在 `Directional Light + Cast Shadows + Override Shadow Params` 条件下,会暴露以下 5 个参数。 - -### 5.1 Receiver Depth Bias - -作用位置: - -- `receiver` 端 shader。 -- 本质上是在阴影比较前,把当前像素的 `receiverDepth` 略微往“更靠前”方向偏移。 - -作用目的: - -- 抑制自阴影 `acne`。 - -调大后的结果: - -- 更不容易长 `acne` -- 但接触阴影更容易发飘、漏光 - -调小后的结果: - -- 阴影更贴物体 -- 但更容易重新出现 `acne` - -适用场景: - -- 主要用于小幅微调,不应作为主修复手段。 - -### 5.2 Normal Bias Scale - -作用位置: - -- `receiver` 端 shader。 -- 采样位置沿法线方向推开,且偏移量与 `shadow texel world size` 挂钩。 - -作用目的: - -- 重点压制掠射角表面的 `acne`。 - -调大后的结果: - -- 角色曲面、斜面更不容易脏 -- 但阴影边缘更容易从物体表面脱开 - -调小后的结果: - -- 阴影更贴 -- 但掠射角面更容易重新长脏斑 - -适用场景: - -- 用于补掠射角问题,不应替代 `caster bias`。 - -### 5.3 Shadow Strength - -作用位置: - -- `receiver` 端最终混合阶段。 - -作用目的: - -- 纯视觉强度控制,不参与几何修正。 - -调大后的结果: - -- 阴影更黑、更实 - -调小后的结果: - -- 阴影更淡、更灰 - -说明: - -- 它不会解决 `acne`,也不会解决锯齿,只改观感。 - -### 5.4 Depth Bias Factor - -作用位置: - -- `ShadowCaster` pass 的 `slope-scaled depth bias`。 - -作用目的: - -- 对有斜率的表面提供更强的投射端深度偏移。 - -调大后的结果: - -- 更能压住 `caster` 端 `acne` -- 但更容易出现 `peter-panning` - -调小后的结果: - -- 阴影更贴 -- 但斜面 `acne` 更容易回来 - -适用场景: - -- 是 `caster bias` 的主调参项之一。 - -### 5.5 Depth Bias Units - -作用位置: - -- `ShadowCaster` pass 的常量 `depth bias`。 - -作用目的: - -- 作为基础兜底偏移,对低斜率面和平面也生效。 - -调大后的结果: - -- 平面更不容易脏 -- 但阴影也更容易整体悬浮 - -调小后的结果: - -- 阴影更贴地 -- 但基础 `acne` 更容易冒出来 - -适用场景: - -- 适合作为 `Depth Bias Factor` 的辅助项。 - -## 6. 推荐调参顺序 - -建议按下面顺序调: - -1. 先调 `Depth Bias Factor` -2. 再调 `Depth Bias Units` -3. 再用 `Normal Bias Scale` 补掠射角表面问题 -4. `Receiver Depth Bias` 只做小幅微调 -5. 最后再调 `Shadow Strength` - -这样做的原因是: - -- 先把 `caster` 端基线压稳,能更系统地治 `acne` -- 再用 `receiver` 端去补细节,而不是反过来拿大量 `receiver bias` 去硬顶 - -## 7. 常见症状与对应调法 - -如果出现下面这些问题,优先这样处理: - -### 症状 1:表面一片脏斑,自阴影很重 - -优先操作: - -- 小幅增加 `Depth Bias Factor` -- 必要时小幅增加 `Depth Bias Units` - -### 症状 2:角色曲面、斜面还是脏 - -优先操作: - -- 小幅增加 `Normal Bias Scale` - -### 症状 3:阴影离脚、接触处发飘 - -优先操作: - -- 先降低 `Depth Bias Factor / Depth Bias Units` -- 再降低 `Normal Bias Scale / Receiver Depth Bias` - -### 症状 4:只是觉得阴影太浅或太黑 - -优先操作: - -- 直接调 `Shadow Strength` - -## 8. 本轮结论 - -本轮单张 `MainLight` 阴影修复已经完成“从占位实现到可用基线”的目标,当前结果可以概括为: - -1. 自阴影 `acne` 已经被压到可接受范围。 -2. 单张阴影图的覆盖范围已经显著收紧,近处密度明显提升。 -3. `PCF` 已经接入,阴影边缘不再完全是硬切台阶。 -4. 最关键的 5 个参数已经暴露到方向光面板,后续可直接做小步视觉标定。 - -仍需明确: - -- 单张阴影图的大范围覆盖问题不会被彻底解决。 -- 这一类问题的正式解法仍然是 `CSM`。 -- 但 `CSM` 不应建立在一套不稳定的单图 `bias / filter / fit` 基线上。 - -因此,本轮工作的正确归档结论是: - -- 单张 `MainLight` 阴影修复已完成本阶段目标。 -- 后续如继续推进,应以 `CSM` 为新阶段目标重新立项,而不是继续在这份单图修复计划上扩写。 diff --git a/docs/used/Material Inspector与Shader属性面板收口计划_2026-04-07.md b/docs/used/Material Inspector与Shader属性面板收口计划_2026-04-07.md deleted file mode 100644 index 1f3b44fe..00000000 --- a/docs/used/Material Inspector与Shader属性面板收口计划_2026-04-07.md +++ /dev/null @@ -1,567 +0,0 @@ -# Material Inspector与Shader属性面板收口计划 - -日期:2026-04-07 - -## 1. 背景 - -当前 Rendering / Shader / Material 主线已经基本建立起正式架构: - -- Shader 侧已经具备统一的 authoring/schema 能力。 -- Material 运行时与 artifact 路径已经逐步从旧兼容逻辑中收口。 -- Editor 中的 Material Inspector 仍然没有完全切换到“由 Shader schema 驱动”的正式工作流。 - -当前最突出的实际问题有两类: - -1. Material 选择 Shader 后,Shader 中声明的属性没有被正式暴露到 Inspector 面板上。 -2. Material 面板上还残留了一些历史字段或临时 UI,用户无法清晰判断哪些是正式能力,哪些只是过渡产物。 - -此外,当前工程条件已经允许做更彻底的清理: - -- `project/` 中旧材质资产可直接编辑。 -- 当前不存在必须长期兼容的历史材质资产包袱。 - -因此,这一阶段的目标不是“继续兼容旧面板”,而是把 Material 编辑链路彻底切换到正式路径,并为后续 Shader/Material 编辑器扩展打好基础。 - -## 2. 本阶段目标 - -本阶段要完成以下目标: - -1. 让 Material Inspector 成为 Shader schema 驱动的正式编辑入口。 -2. 让材质面板只暴露当前正式架构中的概念,不再混入旧字段和误导性配置。 -3. 保证 Editor 面板、运行时 `Material`、源资产、artifact、reimport 之间的数据一致性。 -4. 用单测和必要的编辑器验证把这条链路收口,而不是停留在“能显示”层面。 - -## 3. 不在本阶段处理的内容 - -以下内容不属于本阶段主目标,避免范围失控: - -- 自定义 Material Drawer 系统。 -- Shader Graph / 节点式材质编辑器。 -- 面向美术工作流的复杂 Material 预设库。 -- SRP 层级的材质检查器定制机制。 -- 完整的 Shader GUI 仿 Unity 高级扩展体系。 - -这些能力后续可以做,但必须建立在当前正式链路已经稳定收口的前提上。 - -## 4. 当前已确认的问题 - -### 4.1 Inspector 没有正式反射 Shader 属性 - -当前 Material Inspector 还没有完整读取 Shader schema 并按类型生成属性控件,导致: - -- Shader 中声明的属性无法完整暴露。 -- 用户无法直接编辑当前材质真正生效的参数。 -- 材质默认值、覆盖值、纹理槽与资源引用状态之间关系不清晰。 - -### 4.2 材质面板仍存在历史路径残留 - -之前材质层面曾承担过一些本不应由材质承担的职责,例如: - -- 材质自己选择 builtin pass。 -- Inspector 里出现与当前正式架构不一致的旧字段。 - -这类逻辑已经开始清理,但 Editor 面板仍需同步彻底收口。 - -### 4.3 Editor 与运行时 Material 状态可能脱节 - -如果面板的编辑状态不是直接围绕正式 `Material` 数据模型建立,就容易出现: - -- Inspector 显示值与运行时实际值不一致。 -- 资源重载后 UI 状态失真。 -- artifact / reimport 后材质参数丢失或回退异常。 - -### 4.4 缺少围绕正式工作流的测试闭环 - -仅靠手工点 Inspector 验证不足以支撑后续迭代。必须补足以下覆盖: - -- Shader 切换后属性集合重建。 -- 默认值与显式覆盖值的切换。 -- Texture 属性与资源引用链路。 -- Keyword 与 Render State 的持久化。 -- artifact/reimport 后数据一致性。 - -## 5. 设计原则 - -本阶段严格遵循以下原则: - -### 5.1 Shader 定义什么,Material 就暴露什么 - -Material 不是独立定义属性结构的地方。属性结构必须由 Shader schema 决定,Inspector 只是将其可视化并允许编辑。 - -### 5.2 Material 只承载实例数据,不承载管线选择策略 - -材质应承载: - -- Shader 引用 -- Shader schema 对应的属性值 -- Keyword 状态 -- 合法的 render state 覆盖 - -材质不应再承载: - -- 独立指定 builtin pass 的旧路径 -- 与 Shader metadata 冲突的临时策略字段 - -### 5.3 Editor 不发明第二套数据模型 - -Inspector 的中间态必须服务于正式数据模型,而不是绕开 `Material` 自己维护一套平行逻辑。 - -### 5.4 面板必须可验证 - -每一步改动都必须能通过测试或明确的编辑器验证闭环证明正确,不接受只靠目测“看起来差不多”。 - -## 6. 分阶段执行计划 - -## Phase 1:现状审查与残留路径清点 - -### 目标 - -彻底梳理当前 Material Inspector、Material 资源、Shader schema、artifact 序列化之间的实际数据流。 - -### 任务 - -- 审查 `InspectorPanel` 当前 Material 面板的数据来源和写回路径。 -- 审查 `Material` 当前正式字段与历史遗留字段。 -- 审查 `MaterialLoader`、`AssetDatabase`、artifact schema 当前是否仍保留不必要兼容路径。 -- 识别 Material 面板中哪些字段是正式路径,哪些属于旧方案残留。 - -### 完成标准 - -- 列清楚当前正式数据流。 -- 列清楚必须删除的旧字段/旧 UI。 -- 列清楚缺失的 schema 反射入口。 - -## Phase 2:Material Inspector 数据模型收口 - -### 目标 - -让 Material 面板的数据模型只围绕正式架构组织。 - -### 任务 - -- 移除 Material 层面的旧 pass 选择 UI 与残余兼容路径。 -- 整理 Inspector 内部状态结构,只保留: - - Shader 资源引用 - - Shader schema 对应属性 - - Keywords - - Render Queue / Render State - - Tags(仅保留当前正式允许暴露的部分) -- 避免 Inspector 保存与运行时 `Material` 不一致的冗余字段。 - -### 完成标准 - -- 面板字段与正式 `Material` 模型一一对应。 -- 旧字段彻底不再出现在材质编辑界面和保存链路中。 - -## Phase 3:Shader schema 驱动的属性面板生成 - -### 目标 - -Material Inspector 能基于 Shader schema 动态生成属性编辑 UI。 - -### 任务 - -- 读取 Shader 声明的属性定义与默认值。 -- 按属性类型渲染控件: - - `float` - - `int` - - `bool` - - `float2/3/4` - - `texture` -- 正确显示每个属性的: - - 属性名 - - 显示名 - - 默认值 - - 当前覆盖值 - - 纹理槽绑定状态 - - 资源引用路径/AssetRef 状态 -- 明确“未覆盖时使用 Shader 默认值”的表现形式。 - -### 完成标准 - -- 选定 Shader 后,Inspector 中能稳定暴露该 Shader 的正式属性集合。 -- 面板能正确编辑并持久化这些属性。 - -## Phase 4:Editor 与运行时一致性收口 - -### 目标 - -保证 Material Inspector 编辑结果与运行时 `Material`、artifact、reimport 行为一致。 - -### 任务 - -- 对齐 Inspector 写回逻辑与 `Material` 正式 API。 -- 验证 Shader 切换时属性重建、默认值回退、无效属性清理。 -- 验证纹理属性在源资产、artifact、异步资源加载中的一致性。 -- 验证关键词与 render state 在 reload/reimport 后不丢失。 - -### 完成标准 - -- Inspector 改动能被运行时正确读取。 -- Reload / Reimport / Artifact Round Trip 后材质数据不漂移。 - -## Phase 5:测试补齐与阶段验收 - -### 目标 - -为正式工作流建立可持续回归验证。 - -### 任务 - -- 补充或更新 `material_tests`。 -- 补充或更新 `asset_tests`。 -- 若材质解析或 builtin pass 匹配受影响,补充必要的 `rendering_unit_tests`。 -- 重新编译 `XCEditor`,执行人工冒烟验证。 - -### 验收项 - -- `material_tests` 全绿。 -- `asset_tests` 全绿。 -- 必要的 `rendering_unit_tests` 全绿。 -- `XCEditor` 编译通过。 -- Material Inspector 中 Shader 属性暴露、编辑、保存、重载行为正确。 - -## 7. 预期交付结果 - -本阶段完成后,工程应达到以下状态: - -1. Material 面板正式切换为 Shader schema 驱动。 -2. 材质不再承担旧 pass 选择职责。 -3. Inspector 中只剩下当前正式架构允许存在的字段。 -4. 材质属性、纹理槽、关键词、render state 都能稳定编辑并正确持久化。 -5. Shader / Material 后续扩展拥有清晰入口,不再建立在旧兼容逻辑之上。 - -## 8. 风险与注意事项 - -### 8.1 Shader 切换会触发属性重建 - -必须定义清楚以下规则: - -- 与新 Shader schema 匹配的属性如何保留。 -- 不匹配的旧属性如何移除。 -- 默认值何时回退,何时保留用户覆盖。 - -### 8.2 Texture 属性不仅是 UI 问题 - -Texture 属性同时涉及: - -- Inspector 显示 -- AssetRef -- 资源路径 -- artifact 存储 -- 延迟加载 - -因此不能只改面板显示,必须连同资源路径一起验证。 - -### 8.3 Render State 需要坚持“正式最小集” - -Material 面板不应再次演化成随意堆字段的临时入口。所有 render state 暴露都必须建立在当前正式支持的能力之上。 - -## 9. 建议执行顺序 - -建议严格按以下顺序落地: - -1. 先完成旧字段与旧路径清点。 -2. 再重构 Inspector 数据模型。 -3. 再接 Shader schema 驱动属性 UI。 -4. 再收口 Editor 与运行时一致性。 -5. 最后统一补测与验收。 - -不能反过来先堆 UI,再回头补数据模型,否则很容易再次生成新的临时方案。 - -## 10. 阶段结论 - -当前最应该推进的不是继续增加 Material 编辑功能花样,而是把 Material Inspector 这条正式链路做对、做稳、做干净。 - -只要这一阶段收口完成,后续无论是: - -- 更完整的 Shader authoring -- Material 默认面板 -- 针对 Renderer/SRP 的材质体系扩展 -- 更接近 Unity 的 Shader/Material 编辑工作流 - -都会建立在清晰、稳定、可验证的基础之上。 - -## 11. Phase 1 审查结果 - -状态:已完成 - -本阶段已对当前 Material Inspector、运行时 `Material`、Shader schema、material source/artifact 路径进行了实际代码审查,结论如下。 - -### 11.1 当前 Inspector 数据模型先天不完整 - -当前 `InspectorPanel::MaterialAssetState` 只维护了以下内容: - -- `shaderPath` -- `renderQueue` -- `renderState` -- `tags` - -它没有正式承载以下关键数据: - -- Shader schema 属性列表 -- 材质属性当前值 -- 纹理槽与贴图引用 -- 关键词状态 -- Render State Override 开关本身 - -这意味着当前 Inspector 不是“少画了几个控件”,而是其内部状态模型本身就无法表示正式的材质编辑数据。 - -### 11.2 当前 Inspector 保存链路会丢失材质的正式内容 - -当前 `BuildMaterialAssetFileText()` 仅写出: - -- `shader` -- `renderQueue` -- `tags` -- `renderState` - -它不会写出: - -- `properties` -- `textures` -- `keywords` - -因此,只要一个材质已经拥有 Shader 属性、纹理槽或关键词,若用户通过当前 Inspector 保存一次,该材质源文件就可能被覆盖成一个严重简化后的版本,导致正式材质数据丢失。 - -### 11.3 当前 Inspector 的实时写回同样不完整 - -当前 `ApplyResolvedMaterialStateToResource()` 仅把面板状态写回到运行时 `Material` 的以下部分: - -- `SetShader` -- `SetRenderQueue` -- `SetRenderState` -- `ClearTags` / `SetTag` - -它不会写回: - -- Shader schema 属性值 -- 纹理绑定 -- 关键词 - -因此 Inspector 当前即使支持继续加控件,如果不先重构数据模型,运行时同步仍然会不完整。 - -### 11.4 Render State Override 当前没有被正式建模 - -运行时 `Material::SetRenderState()` 会自动把 `HasRenderStateOverride` 置为 `true`。 - -但当前 Inspector: - -- 没有暴露 “是否启用 Render State Override” -- 保存时始终写出完整 `renderState` -- 应用时始终调用 `SetRenderState` - -这意味着只要用户通过当前材质面板保存,材质就会被强制推进到显式 Render State Override 路径。这个行为不符合正式设计,必须在下一阶段一起修正。 - -### 11.5 运行时底层能力其实已经基本齐备 - -当前 runtime/resource 层已经具备正式所需的大部分能力: - -- `ShaderPropertyDesc` 已包含 `name / displayName / type / defaultValue / semantic` -- `Shader` 已提供 `GetProperties()` / `FindProperty()` / keyword declaration 能力 -- `Material` 已提供完整的 `SetFloat/SetInt/SetBool/SetTexture...` -- `Material` 已支持 Shader schema 默认值同步 -- `MaterialLoader` 已支持解析 `properties / textures / keywords / tags / renderState` -- material artifact 当前已支持这些正式数据的持久化 - -也就是说,当前问题的主要矛盾不在资源系统,而在 Editor 侧没有接入正式模型。 - -### 11.6 当前阶段的根本性结论 - -下一阶段不能直接在现有材质面板上继续堆控件。 - -必须先完成: - -1. `MaterialAssetState` 的正式重构 -2. Save/Reload/Apply 链路对 `properties / textures / keywords / renderState override` 的建模 -3. Inspector 与运行时 `Material` 之间的一致性重建 - -只有先做完这些,后续的 Shader schema 驱动属性 UI 才不会继续建立在错误基础上。 - -### 11.7 Phase 2 的直接执行范围 - -基于本阶段审查结果,下一阶段将直接进入以下工作: - -1. 扩展 `MaterialAssetState`,让其正式承载属性、纹理槽、关键词与 render state override。 -2. 重构 `Populate / Apply / Save / Reload` 这四条关键链路。 -3. 彻底消除当前“保存一次就丢属性”的结构性问题。 - -## 12. Phase 2 执行结果 - -状态:已完成 - -本阶段已经完成 Material Inspector 数据模型的第一轮正式收口,重点是先把状态模型与保存链路做正确,而不是提前堆属性 UI。 - -### 12.1 已完成内容 - -- `MaterialAssetState` 已扩展为正式承载: - - `keywords` - - `properties` - - `texture bindings` - - `renderState override` -- `PopulateMaterialAssetStateFromResource()` 已从运行时 `Material` 同步: - - Shader 路径 - - Render Queue - - Render State - - Render State Override 标志 - - Tags - - Keywords - - Properties / Texture Bindings -- `ApplyResolvedMaterialStateToResource()` 已开始完整写回: - - Shader - - Render Queue - - Render State - - Render State Override - - Tags - - Keywords - - Properties / Texture Bindings -- `BuildMaterialAssetFileText()` 已开始正式写出: - - `keywords` - - `properties` - - `textures` - - 仅在启用 override 时才写 `renderState` -- Inspector 已增加 `Render State Override` 开关,避免默认把材质强行推进到显式 override 路径。 - -### 12.2 本阶段解决的核心问题 - -本阶段已经解决了最危险的结构性问题: - -- 当前 Inspector 再保存材质时,不会像之前那样天然丢掉 `properties / textures / keywords`。 -- Render State 是否为显式 override,已经不再是隐藏副作用,而成为正式状态的一部分。 - -### 12.3 本阶段仍未完成的部分 - -以下能力还没有在本阶段完成,这是下一阶段的工作重点: - -- 基于 Shader schema 的属性 UI 自动生成 -- 各属性类型的可视化编辑控件 -- 默认值/覆盖值的明确表现 -- 纹理槽的正式资源选择 UI -- 关键词与属性在 Inspector 中的可视化编辑 - -因此,Phase 2 的性质是“先把材质状态模型与保存链路做对”,而不是“材质面板功能已经完整”。 - -## 13. Phase 3 执行结果 - -状态:已完成 - -本阶段已经开始把正确的材质状态模型真正暴露到 Inspector 上,重点是基于 Shader schema 生成属性面板,并处理 Shader 切换时的状态重建。 - -### 13.1 已完成内容 - -- Inspector 已新增 `Properties` 区块。 -- 属性区会基于当前 Shader 的 schema 动态生成,而不是写死字段。 -- 当前已接入的属性类型包括: - - `Float / Range` - - `Int` - - `Color` - - `Vector` - - `Texture2D / TextureCube` -- Texture 类型已接入资源选择控件,不再只是文本占位。 -- 每个属性当前会直接显示 Shader 中声明的默认值文本,作为当前参数的基线提示。 - -### 13.2 Shader 切换行为已收口 - -本阶段同时处理了一个关键一致性问题: - -- 当用户切换 Shader 时,Inspector 会先基于新 Shader schema 重建 `MaterialAssetState` -- 能与新 Shader 对齐的同名属性会尽量保留原值 -- 已不被新 Shader 声明的旧属性不会继续残留在保存结果里 -- 与新 Shader 不匹配的旧关键词也会被清理 - -这样可以避免旧材质属性被继续写回到新 Shader 材质文件中,从而减少生成无效材质源文件的风险。 - -### 13.3 本阶段仍未完成的部分 - -以下内容还需要后续阶段继续收口: - -- 属性默认值与显式覆盖值的正式“重置/回退”交互 -- 关键词的可视化编辑 UI -- 更完整的属性类型与显示策略细化 -- 针对 Inspector 材质链路的专门自动化测试 - -因此,Phase 3 的完成标准是“Shader schema 驱动的属性面板已经建立起来”,但还不是最终形态。 - -## 14. Phase 4 执行结果 - -状态:已完成 - -本阶段重点处理的是 authoring-state 与 runtime-state 的一致性问题,避免 Inspector 因为单纯从运行时对象反推而破坏材质源文件的语义。 - -### 14.1 已完成内容 - -- Inspector 现在会回读材质源文件中实际 authored 的: - - `properties` - - `textures` - - `keywords` - - `renderState` -- 材质状态中已加入“是否需要序列化回源文件”的 authored 标记。 -- 保存材质时,不再无条件把所有运行时属性都写回源文件。 -- 对于未在源文件中显式 authored 的属性,当前会继续保持“继承 Shader 默认值”的语义。 -- 当用户在 Inspector 中修改属性或贴图后,对应项才会被标记为显式 authored 并写回源文件。 - -### 14.2 本阶段解决的核心问题 - -本阶段解决的是一个架构层面的隐患: - -- 如果只从运行时 `Material` 反推回材质源文件,打开并保存一次材质,就会把 Shader 默认值全部固化进 `.mat`。 -- 一旦默认值被固化,后续 Shader 默认值再调整,材质就不再继承新的默认值。 - -当前这条链路已经收口到更合理的状态: - -- 只有显式 authored 的 override 才会写回 -- 默认值仍然可以继续作为 Shader 侧的基线被继承 - -### 14.3 本阶段仍未完成的部分 - -以下内容仍然需要下一阶段继续完成: - -- 针对 Inspector 材质链路的专门自动化测试 -- 属性“重置到默认值”的正式交互 -- 关键词的可视化编辑 UI -- 更完整的属性类型/显示策略覆盖 - -因此,Phase 4 的性质是“先把 authoring 语义做正确”,为最后的测试与收口创造条件。 - -## 15. Phase 5 执行结果 - -状态:已完成 - -本阶段重点不是继续扩材质面板功能,而是把已经形成的正式链路变成“可持续回归验证”的状态,并把测试面收口到 Inspector 实际依赖的核心逻辑上。 - -### 15.1 已完成内容 - -- 新增 `MaterialInspectorMaterialState` / `MaterialInspectorMaterialStateIO` 辅助模块,承载: - - Material Inspector 状态结构 - - source authored presence 解析 - - Shader 切换时的属性/关键词重同步 - - 材质源文件序列化文本生成 -- 新增 `tests/editor/test_material_inspector_material_state_io.cpp`,覆盖: - - authored 属性/纹理/关键词标记识别 - - Shader 切换时保留兼容 override、清理陈旧字段 - - 非 authored 默认值不写回源文件 - - 纹理 override 与 render state override 的正式序列化 -- `editor_tests` 构建链路已接入上述 helper 与新测试文件。 - -### 15.2 已执行验证 - -- `cmake --build build --config Debug --target XCEditor -j 8` -- `cmake --build build --config Debug --target editor_tests -- /v:minimal` -- `build/tests/Resources/Material/Debug/material_tests.exe` -- `build/tests/Core/Asset/Debug/asset_tests.exe` -- `build/tests/Editor/Debug/editor_tests.exe --gtest_filter=MaterialInspectorMaterialStateIOTest.*` - -验证结果: - -- `XCEditor` 编译通过 -- `material_tests`:61 / 61 通过 -- `asset_tests`:56 / 56 通过 -- `MaterialInspectorMaterialStateIOTest`:4 / 4 通过 - -### 15.3 当前剩余风险 - -- 全量 `editor_tests.exe` 在顺序执行 `EditorActionRoutingTest.*` 时仍存在既有的共享状态级别挂起现象。 -- 该挂起并非本次新增的材质面板测试本身触发: - - 新增 `MaterialInspectorMaterialStateIOTest` 单独执行通过 - - `EditorActionRoutingTest.PlayModeAllowsRuntimeSceneUndoRedoButKeepsSceneDocumentCommandsBlocked` 单独执行通过 -- 因此,本阶段围绕 Material Inspector / Shader 属性面板的测试收口已经完成,但 Editor 其余历史测试链路仍需单独排查。 diff --git a/docs/used/Nahida Unity工程迁移与卡通角色渲染落地计划_完成归档_2026-04-11.md b/docs/used/Nahida Unity工程迁移与卡通角色渲染落地计划_完成归档_2026-04-11.md deleted file mode 100644 index 75e343d2..00000000 --- a/docs/used/Nahida Unity工程迁移与卡通角色渲染落地计划_完成归档_2026-04-11.md +++ /dev/null @@ -1,342 +0,0 @@ -# Nahida Unity工程静态卡通渲染迁移计划 -日期: 2026-04-10 -状态: 已按“仅静态卡通渲染”范围重写 - -## 1. 范围锁定 - -这份计划只服务一个目标: - -- 把 Nahida 的静态卡通渲染效果迁到 XCEngine。 - -本轮明确不做: - -- 骨骼动画 -- 运行时 skinning -- `SkinnedMeshRenderer` 运行链路 -- blend shape 驱动 -- Animator / Humanoid / Retargeting -- Unity 动画系统还原 - -如果后面发现 Unity 原样例里某些视觉细节依赖骨骼或 blend shape,本轮处理原则是: - -- 先接受静态近似 -- 不为此把范围扩成动画系统 - -## 2. 当前目标 - -本轮要落地的是一套“静态可渲染”的 Nahida 角色效果,重点是: - -- body / hair / face / brow / dress 的分材质关系 -- toon 明暗分层 -- face shadow 基本成立 -- outline 基本成立 -- 在独立预览场景里稳定对比和调试 - -换句话说,本轮不是“把 Unity 角色系统搬过来”,而是: - -- 先把 Nahida 的静态画面效果在你自己的引擎里立住 - -## 3. 已知事实 - -### 3.1 资源基线已经整理 - -当前已经完成的 Phase 0 资源整备: - -- 引擎项目内静态资源基线: - - `project/Assets/Characters/Nahida/` -- Unity 原始参考快照: - - `docs/reference/NahidaUnity/` - -已落地的关键资源包括: - -- Nahida 的 `FBX` -- body / hair / face / shared 贴图集 -- `Nahida_Body_Smooth.mesh` -- Unity 材质、shader、scene、脚本参考文件 - -### 3.2 当前运行时能力 - -当前引擎已经具备: - -- `MeshFilterComponent + MeshRendererComponent` 静态网格渲染路径 -- `MeshRendererComponent` 的多材质槽能力 -- `Material` / `Shader` 资源导入体系 - -当前引擎没有现成闭环: - -- skinning -- skeleton runtime -- blend shape runtime - -所以当前正确路线不是补动画,而是: - -- 先基于现有静态渲染路径做 Nahida 的静态效果落地 - -### 3.3 Nahida 的 Unity 语义里哪些需要保留 - -从 Unity 原工程看,本轮静态渲染仍然必须保留这些语义: - -- 7 份材质的分工关系 -- `_LightMap` 驱动的 toon 阴影分层 -- `_FaceLightMap + _FaceShadow + _FaceDirection` 的面部阴影语义 -- `_MetalMap`、rim、outline 的材质逻辑 -- `Nahida_Body_Smooth.mesh` 对描边效果的贡献 - -### 3.4 哪些 Unity 语义本轮只记录、不实现 - -本轮只作为参考保留,不进入当前执行范围: - -- `SkinnedMeshRenderer` -- blend shape 默认值 -- 头骨驱动的面部朝向更新 -- 完整 URP 后处理还原 - -## 4. 迁移原则 - -### 4.1 先静态成立,再谈角色系统 - -当前第一优先级是: - -- 让 Nahida 在静态场景里正确显示 - -不是: - -- 先做角色运行时架构大一统 - -### 4.2 先复用现有静态网格路径 - -只要当前导入出来的 Nahida mesh / section / material slot 足够支撑静态装配,就优先用现有: - -- `MeshFilterComponent` -- `MeshRendererComponent` - -不额外引入: - -- `ModelComponent` -- `SkinnedMeshRendererComponent` - -### 4.3 Unity 文件继续只做参考 - -`docs/reference/NahidaUnity/` 里的 `.mat`、`.shader`、`.unity` 继续只作为语义来源。 - -最终落地仍然必须是: - -- 引擎自己的 `Shader` -- 引擎自己的 `Material` -- 引擎自己的 `Scene` - -### 4.4 面部朝向先做静态近似 - -本轮不做基于头骨的 `_FaceDirection` 驱动。 - -优先级改为: - -- 先给面部材质写一个可接受的静态 `_FaceDirection` -- 如果后面确实需要,再加一个基于对象整体朝向的简化驱动 - -不进入本轮的: - -- 基于 head bone 的动态朝向更新 - -## 5. 分阶段方案 - -### Phase 0: 资源整备 - -状态: - -- 已完成 - -已完成内容: - -- 建立 `project/Assets/Characters/Nahida/` 资源树 -- 补齐缺失贴图 -- 补齐 `Nahida_Body_Smooth.mesh` -- 建立 `docs/reference/NahidaUnity/` 参考区 -- 写出资源说明与依赖映射 - -### Phase 1: 静态装配基线 - -目标: - -- 确认当前 Nahida 的 FBX 导入产物能否直接支撑静态场景装配 -- 建立一个专门用于调试 Nahida 的静态预览场景 - -任务: - -- 盘点 Nahida 当前导入出来的 mesh / submesh / material slot -- 确认应该使用哪些 meshRef 来组装静态角色 -- 创建 `NahidaPreview.xc` -- 在场景中放入 Nahida 的静态实例 -- 先用占位或默认材质把所有 slot 挂齐 - -验收: - -- Nahida 能在独立场景中稳定显示 -- mesh slot 拆分关系清楚 -- 预览场景不依赖主场景的历史脏数据 - -### Phase 2: Toon Shader移植 - -目标: - -- 在 XCEngine 里建立 Nahida 用的引擎原生 toon shader - -任务: - -- 新建 Nahida toon shader -- 先移植最关键的静态视觉能力: - - base map - - base color - - `_LightMap` - - `_FaceLightMap` - - `_FaceShadow` - - `_MetalMap` - - rim - - outline -- 不依赖 Unity URP include - -验收: - -- body / hair / face / dress 的 toon 分层开始成立 -- 面部和头发不再是默认 PBR 外观 - -### Phase 3: 引擎原生材质落地 - -目标: - -- 建立 Nahida 的引擎原生材质资产 - -任务: - -- 建立: - - `Nahida_Base.material` - - `Nahida_Body.material` - - `Nahida_Hair.material` - - `Nahida_Face.material` - - `Nahida_Brow.material` - - `Nahida_Dress1.material` - - `Nahida_Dress2.material` -- 按依赖图绑定贴图和参数 -- 把材质正式挂到静态预览场景 - -验收: - -- 7 份材质语义清楚 -- 各 slot 的贴图和参数不串位 -- 删除 Unity 参考 `.mat` 后不影响引擎内显示 - -### Phase 4: 描边与面部阴影补全 - -目标: - -- 把最关键的卡通识别特征补完整 - -任务: - -- 接入 `Nahida_Body_Smooth.mesh` 或等价静态描边路径 -- 补全 outline 参数 -- 给 face 材质写静态 `_FaceDirection` -- 如确有必要,增加基于角色整体朝向的简化 `_FaceDirection` 驱动 - -验收: - -- outline 视觉成立 -- face shadow 不明显错误 -- 不引入骨骼依赖 - -### Phase 5: 预览场景与视觉调优 - -目标: - -- 做一个可稳定对照 Unity 的静态 Nahida 预览场景 - -任务: - -- 固定相机 -- 固定主光 -- 简化背景 -- 对 body / hair / face / outline 做迭代调参 -- 如确有必要,再补最小后处理 - -验收: - -- 可以稳定截图对比 -- 能快速判断问题属于材质、shader、描边还是灯光 - -## 6. 风险点 - -### 6.1 静态路线与 Unity 原样不会完全一致 - -因为本轮不做: - -- skinning -- blend shape -- head bone 驱动 - -所以最终效果可以接近,但不保证和 Unity 样例逐像素一致。 - -### 6.2 描边依赖特殊 mesh - -如果忽略 `Nahida_Body_Smooth.mesh`,outline 很可能明显跑偏。 - -### 6.3 Face shadow 不是纯贴图问题 - -即使不做骨骼,`_FaceDirection` 也至少要给一个合理静态值,否则面部阴影会别扭。 - -### 6.4 不应该一上来做后处理 - -如果在 shader、材质、outline 还没立住前就先做 bloom / tonemap,会把问题源混在一起。 - -## 7. 验收标准 - -### 档位A:静态资源正确 - -- `project/Assets/Characters/Nahida/` 资源完整 -- `docs/reference/NahidaUnity/` 参考区完整 -- 预览场景可稳定打开 - -### 档位B:静态角色正确 - -- Nahida 静态显示成立 -- body / hair / face / brow / dress 分材质正确 -- toon 阴影、outline、face shadow 基本成立 - -### 档位C:视觉接近样例 - -- 角色整体风格接近 Unity 样例 -- 关键差异只剩高级细节,而不是基础渲染错误 - -## 8. 推荐执行顺序 - -1. 先确认 Nahida 当前导入产物的静态装配方式 -2. 再做引擎原生 toon shader -3. 再做 7 份引擎原生材质 -4. 再补 outline 和 face shadow -5. 最后做预览场景调优与可选后处理 - -明确不要做成: - -1. 先补 skinning -2. 先补动画系统 -3. 先补骨骼驱动组件 - -这不在当前范围内。 - -## 9. 下一步 - -基于当前状态,下一步不是继续做计划,也不是做骨骼运行时,而是: - -1. 先确认 Nahida 当前导入出来的静态 mesh / section / material slot 布局是否足够支撑静态装配。 -2. 如果足够,直接开始建立 `NahidaPreview.xc`、引擎原生 toon shader、以及 Nahida 的材质壳子。 - -当前代码现状对这个判断是偏乐观的: - -- 现有渲染路径已经支持 `MeshFilter + MeshRenderer` -- `MeshRendererComponent` 已经支持多材质槽 -- Nahida 当前导入产物里已经存在大量静态 mesh 子资源 - -所以最合理的下一执行切口是: - -- 直接进入“静态预览场景 + toon shader + native materials”这一段 - -而不是去碰动画系统。 diff --git a/docs/used/Nahida Unity式Model导入与Genshin卡通渲染正式化计划_2026-04-11.md b/docs/used/Nahida Unity式Model导入与Genshin卡通渲染正式化计划_2026-04-11.md deleted file mode 100644 index 2dff3444..00000000 --- a/docs/used/Nahida Unity式Model导入与Genshin卡通渲染正式化计划_2026-04-11.md +++ /dev/null @@ -1,174 +0,0 @@ -# Nahida Unity式Model导入与Genshin卡通渲染正式化计划 - -日期:2026-04-11 - -## 1. 目标 - -这一轮不再按“静态拆件预览”推进,而是正式切到更接近 Unity 的方案: - -- FBX/OBJ 等外部模型统一作为 `Model` 主资产导入 -- 场景侧通过 `Model -> GameObject hierarchy` 实例化恢复节点层级 -- `Mesh` 继续只负责底层几何与 section/material slot -- Nahida 的卡通渲染改为“对齐 Unity 旧工程 `Genshin.shader` 语义”,而不是继续维持近似版 shader - -本轮仍然明确不做: - -- 骨骼动画 -- GPU skinning -- `SkinnedMeshRenderer` 运行时 -- BlendShape -- Animator / AnimationClip 播放系统 - -## 2. 当前判断 - -### 2.1 Model链路已经具备的能力 - -代码里已经有以下基础设施: - -- `ResourceType::Model` -- `Model` / `ModelLoader` -- `xcmodel` artifact -- `AssimpModelImporter` -- `AssetDatabase` 中 `ModelImporter` 以 `Model` 为主资产导入 -- sub-asset manifest 与 `LocalID -> artifact path` 解析 - -这说明“Unity式 Model 主资产”不是从零开始,而是缺最后几段关键闭环。 - -### 2.2 当前最主要的缺口 - -真正还没闭环的是: - -1. `Model` 还不能正式实例化成场景层级 -2. `MeshFilter/MeshRenderer` 还缺稳定的 sub-asset `AssetRef` 绑定入口 -3. 编辑器侧还没有把模型资产当成“可实例化层级对象”来用 -4. Nahida 当前 `XCCharacterToon.shader` 不是 Unity 旧工程 `Genshin.shader` 的等价移植 -5. FBX 导入后的静态 mesh 目前丢失了 Unity shader 依赖的顶点语义,尤其是 `vertex color` 与 `UV1/backUV` - -### 2.3 关于当前 shader 的明确结论 - -当前使用的是: - -- `project/Assets/Shaders/XCCharacterToon.shader` - -Unity 参考原件是: - -- `docs/reference/NahidaUnity/Shaders/Genshin.shader` -- `docs/reference/NahidaUnity/Shaders/GenshinInput.hlsl` -- `docs/reference/NahidaUnity/Shaders/GenshinForwardPass.hlsl` -- `docs/reference/NahidaUnity/Shaders/GenshinOutlinePass.hlsl` - -当前 shader 只是第一版近似实现,不是原 shader 原样移植。当前效果发怪,核心原因不是简单调参,而是语义缺口: - -1. 没有按 Unity 的 `_IS_FACE / _SPECULAR / _RIM / _NORMAL_MAP / _DOUBLE_SIDED` 关键字分支工作 -2. 面部阴影没有按 `_FaceDirection + _FaceLightMap + _FaceShadow + _FaceShadowOffset` 的逻辑算 -3. 阴影 ramp 还没有按 Unity 的 material ID 分层抽样 -4. specular 还不是 Unity 那套 `lightMap + metalMap + matcap` 路径 -5. rim 不是基于 scene depth 的 URP 风格边缘检测 -6. outline pass 还没有正式接回 `Nahida_Body_Smooth.mesh` 与 outline color 分档逻辑 -7. Unity 里还有 `MaterialUpdater.cs` 在运行时写 `_FaceDirection`,当前引擎里这条驱动链不存在 -8. 更关键的是,当前引擎导入出的 Nahida mesh 只有 `Position/Normal/Tangent/Bitangent/UV0`,缺 `Color/UV1` -9. Unity 的 `GenshinForwardPass.hlsl` 明确依赖 `input.color.r` 与 `backUV` -10. 这会直接破坏 body/hair 分支的 `aoFactor = lightMap.g * input.color.r` 与双面材质背面采样,因此“脸基本对、身体和头发大面积偏色”是符合代码现状的结果 - -所以它现在看起来不像原工程,是正常结果。 - -## 3. 本轮正式范围 - -### Phase 1:Model实例化基础链路 - -目标: - -- 把 `Model` 正式实例化为场景 `GameObject` 层级 - -任务: - -- 为 `MeshFilterComponent` 增加 mesh sub-asset `AssetRef` 绑定入口 -- 为 `MeshRendererComponent` 增加 material sub-asset `AssetRef` 绑定入口 -- 保证这些 sub-asset 引用能被场景序列化稳定保存 -- 新增 `Model -> Scene hierarchy` 实例化工具 -- 恢复节点本地 TRS -- 恢复 mesh binding 与 material slot binding - -验收: - -- 导入一个 `OBJ/FBX Model` 后,可以在场景里创建出层级对象 -- 场景存盘后 sub-asset 引用不丢 -- 不需要把 FBX 拆成手工摆放的多个静态对象 - -### Phase 2:编辑器侧 Model使用工作流 - -目标: - -- 让编辑器把 `Model` 当成 Unity 式模型资产使用 - -任务: - -- 增加“从模型资产创建场景对象”的命令入口 -- 后续接到 Project/Hierarchy/Viewport 的拖拽放置链路 -- 让 Nahida 预览场景切到 `Model` 驱动的实例化路径 - -验收: - -- 编辑器里不再主要依赖“手工往 `MeshFilter` 塞 FBX 路径” -- Nahida 预览对象结构来自 `Model` 实例化,而不是手工拆件 - -### Phase 3:Genshin shader 语义对齐 - -目标: - -- 让 Nahida 使用更接近 Unity 旧工程的卡通渲染语义 - -任务: - -- 先补齐静态 mesh 顶点语义链路:`Color`、`UV1/backUV` -- 让前向渲染输入布局与 `XCCharacterToon.shader` 能真正接到 `COLOR` 与 `TEXCOORD1` -- 恢复 body/hair 分支里的 `lightMap.g * input.color.r` -- 恢复双面材质对 `backUV` 的切换采样 -- 按 Unity 原 shader 拆分 forward / outline 两条主路径 -- 补 `_IS_FACE / _SPECULAR / _RIM / _NORMAL_MAP / _DOUBLE_SIDED` 语义 -- 补 face shadow 正式逻辑 -- 补 outline pass 与 `Nahida_Body_Smooth.mesh` -- 视情况补一个轻量 `_FaceDirection` 运行时驱动 - -验收: - -- 角色明暗分层、面部阴影、描边、头发高光接近 Unity 参考 - -## 4. 执行顺序 - -这一轮按下面顺序推进: - -1. 先补 `Model` 实例化与 sub-asset 引用闭环 -2. 再把 Nahida 预览场景切到 `Model` 路径 -3. 再补 FBX 静态 mesh 的 `Color/UV1` 导入、artifact 保存、渲染输入布局与 shader 接线 -4. 再做 shader 语义对齐 -5. 最后做画面调参与残余语义收口 - -不能反过来做。因为当前 shader 再怎么调,只要场景组织和资源绑定语义不对,结果都不稳定。 - -## 5. 当前这一刀 - -这一轮立刻执行的第一刀是: - -- 补齐 FBX 静态 mesh 的 `vertex color` 与 `UV1/backUV` 导入 -- 让 `BuiltinForwardPipeline` 与 `XCCharacterToon.shader` 真正吃到 `COLOR/TEXCOORD1` -- 补 Nahida 集成诊断与回归验证,先把 body/hair 的基础色输入纠正 -- 在这个基础上继续向 Unity 式 `Model -> hierarchy` 与完整 shader 语义收口推进 - -这是 Nahida 当前从“基础贴图都不稳定”切回“先保证静态基础颜色正确”的前置条件,也是后续继续 Unity 式正式方案的必要补丁。 - -## 6. 2026-04-11 新发现补充 - -在实际跑 `tests/Rendering/integration/nahida_preview_scene` 之后,问题又进一步收窄了: - -- Nahida 的 `Body_Mesh0` 运行时已经有 `uv1` 数值,且当前实现会在缺少第二套 UV 时回填 `uv1 = uv0` -- 但导入器只在 `mesh.HasTextureCoords(1)` 为真时才打 `VertexAttribute::UV1` 标记 -- 结果就是:数据层面有 fallback `uv1`,语义层面却仍被当成“没有 UV1” -- 这会导致后续依赖 `backUV` 的 shader / 诊断逻辑继续走错分支 - -因此当前这一刀的执行重点再精确一步,变成: - -1. 修复 `AssimpModelImporter` / `MeshLoader` 中“fallback uv1 已写入但 `UV1` flag 未置位”的不一致 -2. 提升 `ModelImporter` 版本,强制 Nahida 现有 `.xcmodel` artifact 重导 -3. 补针对 Nahida FBX 的导入回归测试,确保 `UV1` fallback 与 `Color` 语义不会再次退化 -4. 再继续跑 Nahida 集成图验证 body/hair 基础色是否进一步收敛 diff --git a/docs/used/NanoVDB体积云加载阻塞与Runtime上传修复计划_完成归档_2026-04-10.md b/docs/used/NanoVDB体积云加载阻塞与Runtime上传修复计划_完成归档_2026-04-10.md deleted file mode 100644 index 48ae67be..00000000 --- a/docs/used/NanoVDB体积云加载阻塞与Runtime上传修复计划_完成归档_2026-04-10.md +++ /dev/null @@ -1,485 +0,0 @@ -# NanoVDB 体积云加载阻塞与 Runtime 上传修复计划 - -文档日期:2026-04-10 - -适用范围:当前 `XCEngine` 的 `Resources / Asset / Rendering / RHI / Editor` 主线,目标问题为 editor 打开主场景后 `cloud.nvdb` 体积云加载时间长、首帧解锁后再次长时间卡死,而 `mvs/VolumeRenderer` 使用同一份 `cloud.nvdb` 仅约 3 秒即可完成加载与显示。 - -文档目标:把当前 editor 中 `NanoVDB` 体积云的加载链路,从“CPU 异步读完后在首个可见渲染帧同步创建并写入大体积 GPU 资源,导致主线程长时间阻塞”的错误运行模式,重构为接近 `mvs/VolumeRenderer` 的正确模式,即“CPU 异步读取 + GPU 本地 buffer 上传 + 上传完成前不阻塞编辑器交互 + 运行时不再把大体积 payload 留在 draw path 上处理”。 - ---- - -## 1. 问题结论 - -当前问题不是单点 bug,而是三段成本叠加: - -1. `AssetDatabase` 对 `.nvdb` 的 `main.xcvol` artifact 只是“metadata + 原始 NanoVDB payload”包装,几乎没有降低运行时 payload 成本。 -2. `VolumeRendererComponent` 的 deferred scene load 只把 CPU 资源异步读到 `VolumeField`,没有提前完成 GPU 上传。 -3. `BuiltinVolumetricPass` 首次真正消费 `VolumeField` 时,`RenderResourceCache::UploadVolumeField()` 在渲染线程同步创建 `StorageBuffer` 并调用 `SetData()` 写入整个 payload,直接把首个可见帧变成一次大块同步上传。 - -最关键的问题在第 3 条。 - -当前 D3D12 后端里: - -- `BufferType::Storage` 默认走 `UPLOAD heap` -- `SetData()` 走 `Map + memcpy` -- 于是体积云最终 shader 访问的不是 GPU 本地 `DEFAULT heap` buffer,而是 CPU 可写的 upload buffer - -这与 `mvs/VolumeRenderer` 的链路根本不同。`mvs` 是: - -1. 创建最终 `DEFAULT heap` buffer -2. 创建临时 `UPLOAD heap` staging buffer -3. CPU 只写 staging -4. 通过 `CopyBufferRegion` 拷到默认堆 -5. 之后 shader 从 GPU 本地 buffer 读取 - -因此,第一阶段收益最大的修复,不是继续优化 artifact 文件,而是把 editor/runtime 的 volume buffer 上传路径改成和 `mvs` 同构。 - ---- - -## 2. 现状与根因拆解 - -## 2.1 当前 editor 链路 - -### 场景打开阶段 - -1. `SceneManager::LoadScene()` 在 deferred scene load 作用域中恢复场景结构。 -2. `VolumeRendererComponent` 仅恢复 `volumeRef -> path`,不立即同步 load。 -3. 直到渲染抽取阶段调用 `GetVolumeField()` 时,才触发 `LoadAsync()`。 -4. editor 状态栏中的 `Runtime streaming scene assets...` 只反映 CPU 侧异步资源请求计数。 - -### CPU 资源完成阶段 - -1. `LoadAsync()` 完成后,`VolumeField` 已在 CPU 内存中可用。 -2. 此时 editor 可以结束“streaming”状态,但 GPU 尚未完成 volume payload 驻留。 -3. 首个真正绘制体积云的 pass 进入 `BuiltinVolumetricPass::DrawVisibleVolume()`。 -4. `RenderResourceCache::GetOrCreateVolumeField()` 发现无缓存,触发 `UploadVolumeField()`。 -5. `UploadVolumeField()` 在渲染线程里同步分配 buffer / SRV,并写入整个 payload。 - -### 结果 - -1. 用户前面数秒可以交互,因为 CPU 异步读取没有阻塞主线程。 -2. 当 CPU 资源可用且首次 draw 发生时,主线程突然承担完整 GPU 上传。 -3. 由于 payload 约 `590 MB`,首个可见渲染帧被长时间卡死。 - -## 2.2 当前 artifact 链路的问题边界 - -`cloud.nvdb` 已经命中 `Library/Artifacts/.../main.xcvol`,因此问题不是“每次重导入”。 - -但当前 `xcvol` 也没有真正消除运行时成本: - -1. 写 artifact 时直接写出 `VolumeField` payload。 -2. 读 artifact 时重新读入整个 payload 到 CPU 数组。 -3. 运行时依然需要再把整块 payload 上传到 GPU。 - -换言之,当前 artifact 的价值主要是: - -- 导入结果稳定 -- metadata 结构化 -- 允许项目资产走统一 `AssetRef` / `Library` 流程 - -它还没有做到: - -- 运行时零拷贝或近零拷贝装载 -- GPU 驻留态预烘焙 -- 直接针对 volume draw path 的运行时加速 - -## 2.3 与 `mvs/VolumeRenderer` 的本质差异 - -不是“都在传同一个 buffer,所以理论上应该一样快”,而是当前两者的最终资源模型不同: - -### `mvs/VolumeRenderer` - -- 最终资源:`DEFAULT heap` GPU 本地 buffer -- 中转资源:临时 `UPLOAD heap` -- 上传模式:copy queue / direct queue 提交拷贝后等待完成 -- draw path:只消费已上传完成的 GPU buffer - -### 当前 editor - -- 最终资源:`UPLOAD heap` buffer -- 中转资源:无专门 staging -- 上传模式:draw path 内同步 `Map + memcpy` -- draw path:首次消费时同时承担资源上传职责 - -这意味着: - -1. editor 首帧 draw path 的职责过重 -2. volume payload 的最终落点错误 -3. 即使 CPU 读取时间相近,GPU 上传和后续 shader 读取性能仍会明显落后于 `mvs` - ---- - -## 3. 修复目标 - -本次修复分为三个层级目标。 - -## 3.1 一级目标:先把“8 秒后突然卡死十几秒”彻底打掉 - -要求: - -1. 打开主场景后,editor 在 volume payload 首次可见前后都不出现长时间主线程冻结。 -2. `BuiltinVolumetricPass` 不再承担大体积同步上传职责。 -3. `StorageBuffer` 不再默认把 volume payload 留在 `UPLOAD heap` 作为最终运行时资源。 - -## 3.2 二级目标:让 editor 的 volume GPU 上传路径和 `mvs` 同构 - -要求: - -1. D3D12 下 volume payload 最终驻留在 `DEFAULT heap`。 -2. CPU 只写 staging / upload 资源。 -3. GPU 通过 copy 提交完成真正拷贝。 -4. shader 后续只读取 GPU 本地资源。 - -## 3.3 三级目标:继续把总加载时间向 `mvs` 靠近 - -要求: - -1. 逐步削减 `xcvol -> CPU payload` 的运行时装载成本。 -2. 未来允许 volume artifact 直接流向 GPU upload 路径,而不是“先完整常驻 CPU,再完整复制到 GPU”。 -3. 在保持引擎正式资源体系一致性的前提下,把总时间尽量压向 `mvs` 的约 3 秒基线。 - ---- - -## 4. 非目标 - -本轮不做以下内容,避免修复方向失焦: - -1. 不重写 NanoVDB ray marching 算法本身。 -2. 不把正式主线路径退化回 `mvs/VolumeRenderer` 的孤立 sample 结构。 -3. 不先做 volume 压缩格式、体素裁剪重编码、分块稀疏 streaming。 -4. 不先重构完整 SRP / render graph。 -5. 不以“删除 Library 重建”作为修复方案。 -6. 不为体积云单独发明 editor 私有旁路渲染器。 - ---- - -## 5. 正式修复方向 - -## 5.1 方向一:补齐“不可变 GPU 本地 buffer + 初始数据上传”能力 - -这是本轮最高优先级。 - -### 当前缺口 - -当前 `RHIDevice::CreateBuffer(const BufferDesc&)` 只描述“创建一个 buffer”,但没有能力表达: - -- 最终资源要落在 GPU 本地内存 -- 初始数据通过 staging copy 进入 -- 创建后立即进入某个最终状态 - -因此 `RenderResourceCache::UploadVolumeField()` 只能: - -1. `CreateBuffer()` -2. `CreateShaderResourceView()` -3. `SetData()` - -对 D3D12 volume 来说,这条路是错的。 - -### 目标能力 - -新增正式 buffer 创建能力,语义类似: - -- `CreateBuffer(const BufferDesc& desc, const void* initialData, size_t initialDataSize, ResourceStates finalState)` - -或 - -- `CreateInitializedBuffer(...)` - -要求: - -1. D3D12 volume storage buffer 走 `DEFAULT heap` -2. 使用 upload staging 完成拷贝 -3. 拷贝后转为 `GenericRead` 或对应 shader 可读状态 -4. 返回对象仍然是统一 `RHIBuffer` - -### 设计原则 - -1. 不要全局修改现有 `CreateBuffer(BufferType::Storage)` 的默认语义 -2. 仅给需要“设备本地 + 初始数据上传”的资源新增专用路径 -3. 保持旧代码依赖 `SetData()` 的场景继续可用 - -这一步是最大收益点,因为它同时解决: - -1. 首帧主线程同步大 memcpy -2. volume 最终资源落在 upload heap 的错误模型 - -## 5.2 方向二:把 volume GPU 上传从 draw path 前移 - -仅修正 D3D12 buffer 落点,还不够。 - -如果 volume 仍在 `BuiltinVolumetricPass::DrawVisibleVolume()` 首次执行时触发上传,那么: - -1. 即使上传路径更正确 -2. 首个可见帧依旧要等待 GPU upload 完成 -3. editor 仍会出现明显顿挫 - -因此还需要把职责改成: - -### 正确职责分层 - -#### CPU 异步资源层 - -- `VolumeRendererComponent` 异步拿到 `VolumeField` - -#### GPU 上传调度层 - -- 检测到 `VolumeField` CPU 资源完成后,提交 GPU upload 请求 -- volume cache 进入 `Uploading` 状态 - -#### 渲染消费层 - -- `BuiltinVolumetricPass` 只消费 `GpuReady` 的 volume -- 对尚未就绪的 volume 不绘制,不再临时上传 - -### 目标状态机 - -建议为 volume runtime cache 明确引入: - -1. `Uninitialized` -2. `CpuReady` -3. `Uploading` -4. `GpuReady` -5. `Failed` - -首帧 draw path 不再负责从 `Uninitialized/CpuReady` 直接推进到 `GpuReady`。 - -## 5.3 方向三:给 volume 增加正式上传队列或帧外预热入口 - -本轮至少需要一个最小正式机制,用于承接 GPU 上传工作。 - -### 最小可落地形式 - -1. 在渲染系统或 `RenderResourceCache` 外围增加 volume upload service -2. 在主循环中轮询已完成的 CPU async load -3. 将 volume GPU upload 提交给渲染设备层 -4. 上传完成后切换到 `GpuReady` - -### 推荐正式方向 - -统一成“渲染资源上传服务”,后续 mesh / large texture 也可以逐步收口到这里。 - -本次 volume 修复可以先做 volume-only 版本,但接口命名不要把未来扩展堵死。 - -## 5.4 方向四:削减 `xcvol` 的运行时 CPU 装载成本 - -这是第二优先级的大项。 - -当前 artifact 仍然要求: - -1. 打开文件 -2. 读 header -3. 分配 payload 数组 -4. 把整个 payload 读入 CPU -5. 再把整个 payload 上传到 GPU - -要接近 `mvs` 的总时间,后面必须继续推进: - -### 正式方向 - -1. volume artifact header 与 payload 更明确分段 -2. 支持 volume payload memory-mapping 或流式读入 upload buffer -3. 非必要时不长期保留 `590 MB` CPU payload 常驻 - -### 本轮边界 - -本轮不要求一步做到 memory-mapped 零拷贝,但文档和接口设计必须为此留口。 - ---- - -## 6. 分阶段实施计划 - -## Phase 0:指标固化与基线采样 - -### 目标 - -在改代码前先量化基线,避免后续只凭体感判断。 - -### 任务 - -1. 记录当前 editor 打开 `Main.xc` 的三个时间点: - - 场景结构恢复完成 - - `Runtime streaming scene assets...` 结束 - - volume 真正可见 -2. 记录当前 `mvs/VolumeRenderer` 从启动到 volume 可见耗时。 -3. 为 volume load / upload 补 focused trace,区分: - - CPU artifact load - - GPU upload begin - - GPU upload complete -4. 记录 D3D12 volume buffer 创建类型与 heap 类型。 - -### 交付 - -1. 一组可复现实测数字 -2. 一组可复现日志样本 - -## Phase 1:RHI 补齐 initialized GPU-local buffer 能力 - -### 目标 - -让 D3D12 volume storage buffer 走 `DEFAULT heap + staging copy`。 - -### 任务 - -1. 在 `RHIDevice` 层新增 initialized buffer 创建接口。 -2. D3D12 实现复用现有纹理上传思路,构建: - - upload queue - - upload allocator - - upload command list - - fence / idle wait -3. volume 使用新接口,不再 `CreateBuffer + SetData()`。 -4. 保证 SRV 创建逻辑不变,shader 读取接口不变。 - -### 验收标准 - -1. D3D12 下 volume payload 最终 buffer 不再是 upload heap。 -2. `RenderResourceCache::UploadVolumeField()` 不再通过 `SetData()` 写入大 payload。 -3. 场景首次可见时的卡顿时长明显下降。 - -## Phase 2:把 GPU 上传从 draw path 中移除 - -### 目标 - -首个可见帧只消费 ready 资源,不执行大资源上传。 - -### 任务 - -1. 在 volume runtime cache 层引入上传状态。 -2. 在 CPU async load 完成后提交 GPU upload 请求。 -3. `BuiltinVolumetricPass` 遇到 `Uploading` 状态时跳过该 volume。 -4. editor 状态条可选择扩展为同时显示: - - CPU streaming count - - GPU upload pending count - -### 验收标准 - -1. 打开场景后不再出现“streaming 结束后突然长时间卡死”。 -2. editor 在 volume GPU 上传期间依旧保持交互。 -3. volume 在 upload 完成后自然出现。 - -## Phase 3:降低 `xcvol` 运行时 CPU 成本 - -### 目标 - -继续逼近 `mvs` 的总时间,而不只是解决阻塞。 - -### 任务 - -1. 评估 `VolumeFieldLoader` 是否允许 payload 延迟所有权或映射式读取。 -2. 将 `xcvol` 的 metadata 和 payload 读取职责分层。 -3. 评估“直接读入 upload staging”路径,减少中间副本。 - -### 验收标准 - -1. 打开主场景后的总 volume 可见时间继续下降。 -2. CPU 峰值内存和中间复制次数下降。 - -## Phase 4:正式化验证与回归保护 - -### 目标 - -确保修复不会在后续 volume / mesh / texture 路径上回归。 - -### 任务 - -1. 增加 volume upload 相关单测或最小集成验证。 -2. 补 D3D12 路径日志断言或 profiling 钩子。 -3. 验证 Scene View / Game View / 运行时体积渲染路径一致。 - -### 验收标准 - -1. 主场景 volume 打开稳定 -2. `mvs` 与 editor 行为差距有明确量化解释 -3. 后续可以继续迭代到更强的 runtime streaming 架构 - ---- - -## 7. 涉及模块 - -本轮实现预计涉及以下模块: - -### RHI - -- `engine/include/XCEngine/RHI/RHIDevice.h` -- `engine/src/RHI/D3D12/D3D12Device.cpp` -- 必要时: - - `engine/include/XCEngine/RHI/RHITypes.h` - - `engine/include/XCEngine/RHI/RHIEnums.h` - - `engine/include/XCEngine/RHI/D3D12/D3D12Buffer.h` - - `engine/src/RHI/D3D12/D3D12Buffer.cpp` - -### Rendering - -- `engine/src/Rendering/Caches/RenderResourceCache.cpp` -- `engine/include/XCEngine/Rendering/Caches/RenderResourceCache.h` -- `engine/src/Rendering/Passes/BuiltinVolumetricPass.cpp` -- 必要时新增 volume upload service 或相关状态结构 - -### Resources / Components - -- `engine/src/Components/VolumeRendererComponent.cpp` -- `engine/include/XCEngine/Components/VolumeRendererComponent.h` -- `engine/src/Resources/Volume/VolumeFieldLoader.cpp` -- `engine/include/XCEngine/Resources/Volume/VolumeField.h` - -### Editor / Telemetry - -- `editor/src/Managers/SceneManager.cpp` -- `editor/src/Viewport/ViewportHostService.h` - -### Tests - -- `tests/Resources/Volume/` -- `tests/Components/test_volume_renderer_component.cpp` -- 必要时新增 rendering / integration 回归验证 - ---- - -## 8. 风险与约束 - -## 8.1 不能粗暴全局改 `StorageBuffer = DEFAULT heap` - -原因: - -1. 现有其他调用方可能默认依赖 `SetData()` -2. 全局改语义会引入隐藏回归 -3. 本轮应以“新增 initialized immutable path”为主 - -## 8.2 首先只确保 D3D12 主线正确 - -当前用户问题发生在 editor D3D12 主线,因此: - -1. 本轮优先保证 D3D12 正确和快 -2. Vulkan / OpenGL 保持兼容,不要求一步做到同等级优化 -3. 但接口设计不要阻断后续跨后端统一 - -## 8.3 不能让 draw path 同时承担恢复和上传职责 - -这条是硬约束。 - -只要 draw path 里仍然存在“第一次看到资源就同步上传 590MB”这件事,问题就没有从根上解决。 - ---- - -## 9. 完成标准 - -认为本轮修复完成,至少要同时满足以下条件: - -1. editor 打开 `Main.xc` 时,`Runtime streaming scene assets...` 结束后不再出现十几秒主线程卡死。 -2. `cloud.nvdb` 的 volume payload 在 D3D12 下最终驻留于 GPU 本地 buffer,而不是 upload heap。 -3. `BuiltinVolumetricPass` 不再在首次 draw 时同步执行大体积上传。 -4. editor 的 volume 可见总时间相比当前主线显著下降。 -5. 修复后的行为可以用日志和代码路径明确解释,而不是只靠体感判断“快了”。 - ---- - -## 10. 本轮执行顺序 - -严格按以下顺序推进: - -1. 固化指标与日志 -2. 补 RHI initialized GPU-local buffer 能力 -3. volume 改走新上传路径 -4. 把 GPU upload 从 draw path 前移 -5. 再评估 `xcvol` 运行时 CPU 装载优化 - -不跳步骤,不同时展开多个大方向,先把最大收益点打掉。 diff --git a/docs/used/NanoVDB体积云场景首帧阻塞根因修复计划_2026-04-10.md b/docs/used/NanoVDB体积云场景首帧阻塞根因修复计划_2026-04-10.md deleted file mode 100644 index eed1a073..00000000 --- a/docs/used/NanoVDB体积云场景首帧阻塞根因修复计划_2026-04-10.md +++ /dev/null @@ -1,650 +0,0 @@ -# NanoVDB 体积云场景首帧阻塞根因修复计划 - -日期:2026-04-10 - -## 1. 文档定位 - -这份计划只解决一个具体问题: - -- 在 `project` 项目中打开带有 `cloud.nvdb` 体积云对象的 `Main.xc` 场景时,Editor 在前期可交互,但在约 8 秒后出现一次明显的整窗卡死,随后再经过十多秒体积云才真正显示出来。 - -这份计划不讨论以下内容: - -- 不讨论 build/package/runtime 发布格式。 -- 不讨论 NanoVDB 渲染算法本身是否继续升级。 -- 不讨论多后端 rollout。 -- 不讨论重新设计整个 `Library` 体系。 - -这份计划关注的是: - -- 为什么当前 `Library` 已经存在,但含 `.nvdb` 的场景打开仍然会在首帧附近严重阻塞。 -- 为什么 `mvs/VolumeRenderer` 用同一份 `cloud.nvdb` 可以约 3 秒完成显示,而 Editor 要慢很多。 -- 应该如何把当前这条链路收口成一套真正可用的正式方案。 - ---- - -## 2. 问题现象 - -当前复现现象已经很明确: - -1. 直接打开 `project` 项目。 -2. 打开 `project/Assets/Scenes/Main.xc`。 -3. 界面会显示 `Runtime streaming scene assets...`,这段时间窗口还能继续操作。 -4. 在大约 8000 ms 左右,Editor 突然开始明显卡死。 -5. 卡死十多秒后,体积云对象才真正显示出来,随后窗口恢复。 - -对比样本: - -- `mvs/VolumeRenderer` 使用的是同一份 `cloud.nvdb`。 -- 运行 `mvs/VolumeRenderer/run.bat` 时,大约 3 秒左右即可加载并显示。 - -这说明: - -- 慢点不在“这份 `.nvdb` 根本无法解析”。 -- 慢点也不在“当前机器根本无法承载这份体积数据”。 -- 真正的问题出在 Editor 主线里的额外同步收口路径。 - ---- - -## 3. 当前已经确认的事实 - -### 3.1 当前 `Library` 对体积资源做的不是“运行时加速缓存” - -当前源文件: - -- `project/Assets/cloud.nvdb` -- 文件大小:`590,241,000` bytes - -当前 artifact: - -- `project/Library/Artifacts/.../main.xcvol` -- 文件大小:`590,240,896` bytes - -这说明当前 `.xcvol` 基本就是: - -- 一个较小的 header -- 加上一份几乎原样的 NanoVDB payload - -也就是说,当前 `Library` 在体积资源这条链路上做的是: - -- 导入身份缓存 -- metadata 缓存 -- source -> artifact 统一入口 - -但它还没有做到: - -- 为运行时准备更轻的 cooked payload -- 为 GPU 上传准备更直接的 runtime-ready 数据 -- 为首帧显示准备真正低成本的预热结果 - -结论: - -- 当前 `Library` 对 `.nvdb` 是“导入缓存”,不是“运行时性能缓存”。 - -### 3.2 当前 VolumeField CPU 侧存在多次大拷贝 - -当前体积资源进入运行时时,大致会经历: - -1. 从 `.xcvol` 读取整个 payload 到临时缓冲。 -2. `VolumeField::Create(...)` 再把 payload 拷贝进 `VolumeField::m_payload`。 -3. `RenderResourceCache::UploadVolumeField(...)` 再构造一个新的 `uploadData`。 -4. 再把这份数据写入 RHI buffer。 - -对 590MB 级别的体积数据来说,这不是“小开销”,而是主路径上的重负担。 - -### 3.3 当前 GPU 上传不是在后台完成,而是在第一次真正绘制时同步触发 - -当前体积云真正进入渲染时,会在 `BuiltinVolumetricPass::DrawVisibleVolume(...)` 中走: - -- `RenderResourceCache::GetOrCreateVolumeField(...)` -- `UploadVolumeField(...)` - -也就是说: - -- 体积资源即使 CPU 侧已经异步读好了, -- GPU 侧 residency 的真正建立仍然是在第一次真正绘制该体积对象时才触发, -- 而且当前实现是同步发生在渲染路径里。 - -这正好解释了现在的现象: - -- 前期 `Runtime streaming scene assets...` 时还能操作。 -- 到第一次真正要画体积云时,主线程/渲染线程突然进入大开销同步路径。 - -### 3.4 当前体积 shader / PSO 首次创建也会叠加到这次阻塞里 - -当前体积材质使用的是: - -- `builtin://shaders/volumetric` - -该 builtin shader 最终会加载: - -- `engine/assets/builtin/shaders/volumetric.shader` - -这个 shader 直接包含: - -- `PNanoVDB.hlsl` - -而当前 D3D12 shader 编译路径明确使用: - -- `D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION` - -这意味着: - -- 体积 pass 第一次真正创建 pipeline state 时, -- 很可能还会在主线程/渲染线程上同步现编一个包含 `PNanoVDB` 的大 shader 变体。 - -因此这次卡死不是单一开销,而是两类重活叠加: - -1. 590MB 体积 payload 的运行时实现化 -2. 体积 shader / PSO 的首次真实可绘制化 - -### 3.5 当前场景加载状态文本会误导真实阶段 - -当前 `Runtime streaming scene assets...` 的状态主要依赖: - -- `ResourceManager::GetAsyncPendingCount()` - -它只能说明: - -- 异步 loader 队列还有没有待完成任务 - -它不能说明: - -- GPU 上传是否已经完成 -- 体积 shader 是否已经编好 -- PSO 是否已经 ready -- 体积对象是否真的达到 render-ready - -所以现在 UI 上看到的“streaming 快结束了”并不等于体积云真的快 ready 了。 - ---- - -## 4. 最根本的原因 - -把这次问题压缩成一句话: - -**当前 `Library` 缓存住的是体积资源的“导入结果”,但没有缓存住体积云真正昂贵的“运行时实现化结果”;而这部分实现化又被推迟到了第一次真正绘制时同步完成。** - -更展开一点,根因可以拆成四层: - -### 4.1 第一层根因:缓存层次不对 - -现在缓存住的是: - -- source asset 身份 -- artifact 文件 -- metadata - -没有缓存住的是: - -- 低拷贝可消费的 volume payload 视图 -- GPU-ready residency -- shader / PSO 可直接绘制状态 - -### 4.2 第二层根因:时机不对 - -当前重活发生在: - -- 不是项目打开时 -- 不是场景结构恢复后后台预热时 -- 而是在第一次真正绘制体积云时 - -这导致体感非常差,因为卡顿被集中释放在用户已经开始操作视口之后。 - -### 4.3 第三层根因:实现路径太重 - -当前 590MB 体积数据在 Editor 里会经历多次 CPU 侧复制和一次同步 GPU 上传。 -对这种量级的数据,这本身就足以形成长时间阻塞。 - -### 4.4 第四层根因:渲染首帧还叠加了 shader / PSO 首编 - -体积 pass 的首次真正绘制不是“只差最后一次 draw call”,而是还可能同时触发: - -- shader variant 首编 -- pipeline layout 创建 -- PSO 创建 -- descriptor set 初始化 - -所以这次卡死是“重资源 + 重 shader”叠加,不是单点故障。 - ---- - -## 5. 为什么 `mvs/VolumeRenderer` 更快 - -`mvs/VolumeRenderer` 当前更快,不是因为它“缓存更高级”,而是因为它路径短得多。 - -它做的是: - -- 直接读源 `.nvdb` -- 直接生成 GPU buffer -- 直接编少量 shader -- 直接渲染 - -它没有承担下面这些 Editor 主线额外职责: - -- `AssetDatabase` -- `AssetRef -> path` 解析 -- scene/component 反序列化恢复 -- `VolumeField` 运行时抽象包装 -- `RenderResourceCache` 的通用缓存层 -- `BuiltinVolumetricPass` 的通用 descriptor / pass / pipeline 约束 -- 视口首帧时的 editor 额外渲染流 - -所以 MVS 快,说明的是: - -- 这份 `.nvdb` 并不是天然慢到 15 秒 - -而不是说明: - -- 现在的 Editor 路线只需要继续堆更多导入缓存就能变快 - ---- - -## 6. 修复目标 - -本轮修复目标不是“让 `.xcvol` 体积更小”,也不是“强行把所有工作前置到项目启动”。 -本轮的正式目标是: - -### 6.1 交互目标 - -- 打开 `Main.xc` 后,不允许再出现“先可交互,再突然长时间整窗卡死”的体验。 -- 一旦场景已经进入可交互状态,后续体积云就只能继续后台预热,不能把窗口重新拖回不可响应。 - -### 6.2 架构目标 - -- 把体积资源的“导入缓存”和“运行时实现化”明确分成两个阶段。 -- 首次绘制路径不能再承载 590MB 级别的同步重活。 -- 首次绘制路径只能消费已经 ready 的资源,或优雅跳过未 ready 的体积对象。 - -### 6.3 性能目标 - -在当前开发机和当前 `cloud.nvdb` 样本下: - -- warm cache 场景再次打开时: - - 不允许出现超过 `200 ms` 的二次窗口无响应段。 - - 体积云从场景打开到可见的时间目标收敛到 `3 s` 以内。 - - 该目标以当前 `mvs/VolumeRenderer` 的约 `3 s` 作为对齐基线,正式目标是不慢于 MVS。 -- cold import 首次打开时: - - 允许总体耗时更长, - - 但不允许在体积资源真正显示前出现长时间窗口卡死。 - -### 6.4 诊断目标 - -- 必须能明确区分: - - scene structure ready - - CPU payload ready - - GPU upload in progress - - shader / PSO prewarm in progress - - render-ready - ---- - -## 7. 本轮明确不做的错误修法 - -以下方案不能作为本轮主方案: - -### 7.1 不能只继续优化 `AssetDatabase::EnsureArtifact()` - -原因: - -- 这次 warm cache 场景下的主问题已经不是 source import 了。 -- 再继续只抠导入判定和 reimport,不会解决“第一次真正绘制才卡死”。 - -### 7.2 不能只加更多“异步读文件” - -原因: - -- 当前前半段已经异步了。 -- 真正卡死点在后半段 render-time realization。 - -### 7.3 不能一上来就先加第二套磁盘缓存目录 - -原因: - -- 当前最大问题首先是同步时机和多次拷贝。 -- 如果不先把“谁在什么时候做重活”改对,再加新 cache 文件夹只是继续堆复杂度。 - -### 7.4 不能只通过隐藏 UI 文本来掩盖问题 - -原因: - -- 现在不是提示文案不对,而是真有一段重度同步阻塞。 - ---- - -## 8. 正式修复方向 - -本轮采用四条主线并行收口,但执行顺序必须严格分阶段。 - -### 8.1 主线 A:先把真实耗时切开,看清楚谁最重 - -虽然根因已经明确,但时间占比仍需正式打点。 -本阶段必须先拿到真实分段耗时,不允许后续继续靠体感猜。 - -需要新增的时间切片: - -1. `Scene Deserialize` -2. `AssetRef Resolve` -3. `VolumeFieldLoader.ReadArtifact` -4. `VolumeField.Create` -5. `Volume GPU Upload` -6. `Volumetric Shader Variant Compile` -7. `Volumetric PSO Create` -8. `First Volume Visible` - -需要新增的日志与状态: - -- `Main.xc` 打开时,针对 `cloud.nvdb` 输出完整链路耗时。 -- 把“异步流式加载完成”和“体积 render-ready”拆开显示。 - -这一阶段的目的不是修性能,而是: - -- 锁死真正的大头时间占比 -- 避免后续错误优化无关路径 - -### 8.2 主线 B:去掉体积 payload 的多次大拷贝 - -这是本轮最核心的工程改动之一。 - -正式方向: - -1. `VolumeField` 不能继续默认把 590MB payload 再拷进一份新的 `m_payload`。 -2. `.xcvol` 读取后,应该改成: - - 文件映射 - - 或共享只读 blob - - 或单所有权 payload 容器 -3. `RenderResourceCache::UploadVolumeField(...)` 不能再额外构造一份等体积的 `uploadData` 再拷一次。 -4. 上传路径必须直接消费 loader 产出的只读 payload 视图。 - -本阶段完成后,应达到: - -- `.xcvol -> VolumeField` 不再发生无意义的大内存复制。 -- `VolumeField -> RenderResourceCache` 也不再产生第二份 590MB 临时副本。 - -这一步做完,即使还没异步 GPU 上传,卡顿也会先明显下降。 - -### 8.3 主线 C:把 GPU 上传从首次绘制路径里拿出去 - -当前真正错误的不是“上传很重”,而是“上传发生在第一次真正 draw 的时候”。 - -正式方向: - -1. 为 `VolumeField` 建立明确的 runtime residency 状态机: - - `Unloaded` - - `CpuReady` - - `GpuUploading` - - `GpuReady` - - `Failed` -2. CPU 侧 payload 一旦 ready,就立即进入独立的 GPU 预热队列。 -3. `BuiltinVolumetricPass::DrawVisibleVolume(...)` 不允许再承担首次重量级上传。 -4. draw path 的职责改为: - - 如果 `GpuReady`,正常绘制 - - 如果未 ready,跳过或显示占位,不得同步收口 - -对 D3D12 的具体要求: - -1. `BufferType::Storage` 不能继续把大体积 volume payload 当普通 upload-heap 常驻缓冲来处理。 -2. 需要引入正式的: - - staging/upload buffer - - default heap storage buffer - - copy queue 或专用 upload path -3. volume buffer 上传完成后再切换到可读状态,而不是在 draw path 上临时补。 - -这一步是解决“8000 ms 后突然卡死”的主修复点。 - -### 8.4 主线 D:把体积 shader / PSO 首编从体积首帧里拿出去 - -当前体积云第一次真正绘制时,渲染链路里还会叠加: - -- builtin volumetric shader variant 首次真正编译 -- pipeline layout / descriptor layout 构建 -- PSO 创建 - -正式方向: - -1. `builtin://shaders/volumetric` 的实际运行时使用变体需要正式预热。 -2. 预热时机不放在“第一次真正 draw”。 -3. 预热应在以下时机之一完成: - - scene structure ready 之后的后台预热阶段 - - volume material 绑定后立刻排队预热 -4. `BuiltinVolumetricPass` 首次执行时只能命中已存在的 shader variant / PSO cache。 - -当前 D3D12 路线还要额外处理一个问题: - -- 现在编译 flags 是 `D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION` - -这本身会显著拉长编译时间。 -本轮需要明确一条正式策略: - -1. Debug 能力是否仍要保留。 -2. 如果保留,默认 editor 交互路径不能在首帧同步承担这份成本。 -3. 可以接受“后台慢编”,不能接受“首帧卡死慢编”。 - -### 8.5 主线 E:修正场景加载进度模型 - -当前 `Runtime streaming scene assets...` 的语义不完整。 -这会直接误导调试,也会误导后续调度逻辑。 - -正式方向: - -1. 把 scene load progress 拆成四段: - - `Structure Ready` - - `CPU Asset Streaming` - - `GPU Residency Prewarm` - - `Render Warmup` -2. `pendingAsyncLoads == 0` 时,只能说明 CPU loader 阶段接近结束。 -3. 只有 volume GPU ready 且体积 pass 关键 shader/PSO ready 时,才能算真正 ready。 -4. UI 需要把这几个阶段明确显示出来,避免再把后半段卡顿误判成“明明 streaming 都结束了却又卡”。 - ---- - -## 9. 执行阶段 - -### Phase 0:诊断与基线固化 - -目标: - -- 用日志和时间切片把当前问题彻底量化。 - -任务: - -1. 为 `Main.xc` 的 `cloud.nvdb` 打通完整性能时间线。 -2. 输出 warm cache / cold cache 两组基线。 -3. 输出 MVS 对比基线。 - -验收标准: - -1. 能明确给出 CPU 读取、CPU 拷贝、GPU 上传、shader 编译、PSO 创建各自耗时。 -2. 不再用“感觉像是这里慢”做判断。 - -### Phase 1:体积 payload 低拷贝重构 - -目标: - -- 消除 `.xcvol -> VolumeField -> UploadVolumeField` 链路中的重复大拷贝。 - -任务: - -1. 重构 `VolumeField` 的 payload 持有方式。 -2. 重构 `.xcvol` loader 的结果表示。 -3. 重构 `RenderResourceCache::UploadVolumeField(...)` 的输入形式。 - -验收标准: - -1. warm cache 情况下,CPU 内存峰值明显下降。 -2. `VolumeField` 载入完成时不再出现等量级的重复 payload 副本。 - -### Phase 2:异步 GPU 上传与渲染脱钩 - -目标: - -- 第一次绘制体积对象时,不再承担首次 GPU upload。 - -任务: - -1. 增加 volume GPU prewarm 队列。 -2. 建立 GPU residency 状态机。 -3. 改掉 draw path 的同步上传兜底。 - -验收标准: - -1. 首次打开 `Main.xc` 后,不再在体积第一次出现在视野中时发生长时间整窗阻塞。 -2. 体积对象未 ready 时允许暂时不显示,但不允许卡死窗口。 - -### Phase 3:体积 shader / PSO 预热 - -目标: - -- 不再把 `volumetric.shader` 的首次真实可绘制准备放在体积首帧。 - -任务: - -1. 为 active backend 预热体积 shader variant。 -2. 为 volume material / pass 预热关键 PSO。 -3. 把 shader/PSO 首编首建从 draw path 移走。 - -验收标准: - -1. 首次显示体积云时,不再同时伴随大段 shader/PSO 同步创建时间。 -2. 日志能证明首帧命中的是已准备好的可绘制状态。 - -### Phase 4:场景状态机与 UI 收口 - -目标: - -- 让场景打开状态和真实资源准备阶段一致。 - -任务: - -1. 拆分 load status 阶段。 -2. 修正 `Runtime streaming scene assets...` 的语义。 -3. 在 Project/Viewport/Console 中统一反映同一份阶段状态。 - -验收标准: - -1. UI 文案与真实阶段一致。 -2. 不再出现“streaming 结束了但实际上后面还有一次大卡死”的错误认知。 - -### Phase 5:回归、压力样本与收口 - -目标: - -- 用真实样本和自动化验证确认这条路线已经稳定。 - -任务: - -1. 用当前 `cloud.nvdb` 做 warm/cold 双场景回归。 -2. 回归 `Main.xc` 打开、关闭、再次打开。 -3. 检查体积云显示、Editor 响应性、日志阶段切片。 - -验收标准: - -1. warm cache 打开 `Main.xc` 时无二次长阻塞。 -2. cold cache 首次导入时也保持可交互。 -3. 体积云显示时间显著收敛,接近 MVS 量级。 - ---- - -## 10.1 当前执行进展(2026-04-10) - -当前代码实现已经进入正式重构阶段,已落地的第一批改动如下: - -1. `VolumeField` 新增 owned payload 创建路径,`.xcvol` 读取结果不再在 `VolumeField::Create(...)` 内发生第二次整块复制。 -2. `VolumeFieldLoader` 的 artifact 载入路径已改为“读入 payload -> 直接 move 进 `VolumeField`”,去掉 artifact load 阶段的重复大拷贝。 -3. `RenderResourceCache::UploadVolumeField(...)` 不再构造整块 `uploadData` 临时副本,改为直接消费 `VolumeField` payload。 -4. `RenderResourceCache` 已为 volume 引入最小可用的 residency 状态: - - `Uninitialized` - - `Uploading` - - `Ready` - - `Failed` -5. volume GPU 上传已改为分帧推进,当前实现按固定 chunk 预算逐帧写入,避免第一次真正绘制时一次性同步吞下整块 payload。 -6. `BuiltinVolumetricPass` 已拆出资源预热步骤: - - 先推进 volume upload - - 再预建 volume pass layout / pipeline - - draw path 只消费 `Ready` 的 volume,未 ready 时直接跳过,不再兜底触发重量级上传 -7. 当前已补最小日志: - - `Volume GPU upload started` - - `Volume GPU upload ready` - -这批改动的意义是: - -- 先把 warm cache 路径里最重的两类同步收口拆开: - - `.xcvol -> VolumeField` 的重复 CPU 大拷贝 - - 首次 draw path 内的一次性整块 GPU 上传 -- 先把“卡死 Editor”问题从根上打散成可推进、可观察、可继续优化的状态。 - -当前这批改动还没有完成的部分: - -1. 还没有把 `.xcvol` 升级成真正 runtime-ready 的 cooked artifact,当前只是先把现有 artifact 的运行时消费链路做轻。 -2. 还没有补齐完整的分段耗时打点。 -3. 还没有把 volumetric shader / PSO 的首编完全前移到更早阶段。 - ---- - -## 11. 涉及模块范围 - -预计会涉及但不限于以下模块: - -- `engine/include/XCEngine/Resources/Volume/VolumeField.h` -- `engine/src/Resources/Volume/VolumeField.cpp` -- `engine/src/Resources/Volume/VolumeFieldLoader.cpp` -- `engine/include/XCEngine/Rendering/Caches/RenderResourceCache.h` -- `engine/src/Rendering/Caches/RenderResourceCache.cpp` -- `engine/include/XCEngine/Rendering/Passes/BuiltinVolumetricPass.h` -- `engine/src/Rendering/Passes/BuiltinVolumetricPass.cpp` -- `engine/src/RHI/D3D12/D3D12Device.cpp` -- `engine/src/RHI/D3D12/D3D12Buffer.cpp` -- `engine/src/Resources/BuiltinResources.cpp` -- `editor/src/Managers/SceneManager.cpp` -- `editor/src/Viewport/ViewportHostService.h` - -如果 Phase 2 仍然不足,再考虑是否需要新增专门的 runtime-prewarm 辅助模块。 -但在本轮中,不应先为了结构好看而过早引入新的大模块。 - ---- - -## 12. 风险与边界 - -### 11.1 风险一:只优化 CPU 拷贝,但仍保留首帧同步 GPU 上传 - -结果: - -- 会变快,但不会从根上解决“突然卡死”。 - -### 11.2 风险二:只做 GPU 上传异步,但 shader / PSO 首编仍在首帧 - -结果: - -- 体积数据路径变快,但体积首帧依然可能因为 shader 现编而卡死。 - -### 11.3 风险三:一上来先设计新的磁盘 runtime cache - -结果: - -- 复杂度先上去了, -- 但如果真正的大头是同步上传和首编,收益会被高估。 - -因此本轮策略必须是: - -1. 先打点 -2. 先移走同步重活 -3. 再决定是否需要更重的磁盘级 runtime cache - ---- - -## 13. 完成标志 - -当以下条件同时成立时,这份计划才算完成: - -1. 打开 `project/Assets/Scenes/Main.xc` 时,Editor 不再出现二次长时间整窗卡死。 -2. `cloud.nvdb` 的 warm cache 路径已经不再依赖首次绘制时的同步重资源收口。 -3. 体积 shader / PSO 的首次准备不再挤在体积首帧。 -4. 场景进度状态能正确区分 CPU streaming、GPU prewarm 和 render-ready。 -5. 当前这条路径的耗时分布可以通过日志直接解释,不再需要靠猜。 - ---- - -## 14. 一句话结论 - -这次问题的根不在“`Library` 有没有命中”,而在“当前 `Library` 只缓存了导入结果,没有缓存运行时真正昂贵的实现化结果,而且这部分实现化被错误地放到了体积第一次真正绘制时同步完成”。 -本轮修复必须围绕这个根因展开,而不是继续把注意力放回导入判定本身。 diff --git a/docs/used/NanoVDB稀疏体积渲染后续正式化计划_阶段归档_2026-04-10.md b/docs/used/NanoVDB稀疏体积渲染后续正式化计划_阶段归档_2026-04-10.md deleted file mode 100644 index bf7dd60c..00000000 --- a/docs/used/NanoVDB稀疏体积渲染后续正式化计划_阶段归档_2026-04-10.md +++ /dev/null @@ -1,159 +0,0 @@ -# NanoVDB 稀疏体积渲染后续正式化计划 - -日期:2026-04-09 - -## 1. 文档定位 - -旧的 `NanoVDB` 大计划已经归档为第一阶段完成文档。 - -当前这份计划只保留 NanoVDB 体积渲染在第一阶段落地之后,真正还没有完全收口的少数事项。 - -这意味着: - -1. `.nvdb -> artifact -> VolumeField -> VolumeRendererComponent -> BuiltinVolumetricPass -> Editor 基本接入` 已经视为第一阶段完成。 -2. 当前不再讨论那一整套已经落地的基础链路。 -3. 当前只推进仍然明确未完成、且会影响后续长期演进的后续正式化工作。 - ---- - -## 2. 第一阶段已完成范围 - -以下内容已经不再作为活跃计划项: - -1. `.nvdb` 资源导入与 artifact 化。 -2. `VolumeField` 运行时资源模型。 -3. `VolumeRendererComponent` 组件接入。 -4. `RenderSceneExtractor` 对体积对象的提取。 -5. `BuiltinVolumetricPass` 在内建前向链路中的正式接入。 -6. D3D12 正式链路点亮。 -7. D3D12 体积渲染集成测试与 GT 对比。 -8. Editor 中最小可用的组件面板接入。 - -这些内容已经不该继续挂在 `docs/plan` 里作为未完成主线。 - ---- - -## 3. 当前仍然活跃的真实问题 - -现在剩下的 NanoVDB 后续工作,核心只有两类。 - -### 3.1 多后端尚未完成正式 rollout - -当前体积渲染集成测试仍然只在 D3D12 路径上正式实例化与验证。 - -这意味着下面这些工作还没完成: - -1. Vulkan 的正式点亮与稳定验证。 -2. OpenGL 的正式点亮与稳定验证。 -3. 三后端统一 GT 基线与回归验证。 -4. capability 检测、禁用行为、错误信息是否完整。 - -### 3.2 与未来高层系统的契约还没有最终落地 - -当前底层 buffer 资源能力已经具备,但和未来高层系统的最终契约还没有必要现在就完全做死。 - -真正还需要收口的是: - -1. 明确 NanoVDB 路线如何映射到未来 Unity 风格 `SetBuffer` 使用面。 -2. 明确当 C# / SRP 正式接入时,体积渲染不会反向逼迫底层重写。 -3. 把当前已经存在的运行时绑定约束整理成正式文档边界,而不是继续散落在实现里。 - -注意: - -这不意味着现在要实现完整 C# 绑定层。 -本阶段只需要把边界冻结清楚。 - ---- - -## 4. 本轮明确不做什么 - -1. 不重做第一阶段已经落地的 D3D12 体积渲染主链路。 -2. 不扩展新的体积特性,例如全局体积雾、体积阴影系统、froxel fog。 -3. 不引入 `Texture3D` 替代 NanoVDB 正式路线。 -4. 不提前实现完整 C# Volume API。 -5. 不把体积渲染主线改造成依赖 `SRP` 才能继续推进。 - ---- - -## 5. 执行阶段 - -### Phase 1:Vulkan 路线正式点亮 - -目标: - -把当前已经在 D3D12 上验证过的 NanoVDB 正式链路,推进到 Vulkan。 - -任务: - -1. 审查 StructuredBuffer / storage buffer 绑定路径在 Vulkan 下的编译与运行时绑定。 -2. 补齐体积渲染 shader 在 Vulkan 路线上的资源布局与反射验证。 -3. 让 `volume_scene`、`volume_occlusion_scene`、`volume_transform_scene` 至少能在 Vulkan 路线上正式跑通。 -4. 固化 Vulkan 路径的 GT 对比与失败日志。 - -验收标准: - -1. Vulkan 路线上能稳定出图。 -2. 集成测试能稳定通过。 -3. 不是“偶然能跑”,而是正式进入体积渲染支持集合。 - -### Phase 2:OpenGL 路线正式点亮 - -目标: - -把 NanoVDB 路线推进到 OpenGL,并明确 SSBO/GLSL 转译的正式边界。 - -任务: - -1. 审查当前 StructuredBuffer 到 OpenGL/SSBO 的转译链路。 -2. 解决体积 shader 在 OpenGL 路线上可能出现的 layout、binding、对齐问题。 -3. 让三组体积集成测试至少具备 OpenGL 正式验证能力。 -4. 明确 OpenGL 不支持场景下的禁用与报错策略,禁止 silent fallback。 - -验收标准: - -1. OpenGL 路线能稳定出图。 -2. 三后端测试输出都可统一对同一 GT 做比较。 -3. 不再存在“D3D12 独有体积功能”的阶段性状态。 - -### Phase 3:多后端能力收口与文档冻结 - -目标: - -在 D3D12 / Vulkan / OpenGL 三后端都跑通之后,把 NanoVDB 这条能力从“已点亮”收口成“正式能力”。 - -任务: - -1. 明确三后端 capability 表达方式。 -2. 明确资源绑定失败、shader 编译失败、后端不支持时的统一错误行为。 -3. 冻结未来高层 `SetBuffer` 风格接口与当前底层资源绑定的对应关系。 -4. 更新文档并把这份后续计划归档。 - -验收标准: - -1. 三后端行为边界清晰。 -2. 错误路径明确,不靠试错排查。 -3. 文档和代码边界一致。 - ---- - -## 6. 当前阶段完成标志 - -当下面条件同时成立时,这份后续计划才算真正完成: - -1. `volume_scene` -2. `volume_occlusion_scene` -3. `volume_transform_scene` - -上述三组集成测试已经在: - -1. D3D12 -2. Vulkan -3. OpenGL - -三后端上完成正式验证,且对应错误处理、能力边界、文档说明都已经收口。 - ---- - -## 7. 一句话结论 - -NanoVDB 不是“还没做完”,而是“第一阶段已经完成,剩下的是多后端 rollout 与最终正式化边界”;从现在开始,这条主线不再需要一份大而全计划,只需要一份小而硬的后续收口计划。 diff --git a/docs/used/NanoVDB稀疏体积渲染正式集成计划_第一阶段完成归档_2026-04-09.md b/docs/used/NanoVDB稀疏体积渲染正式集成计划_第一阶段完成归档_2026-04-09.md deleted file mode 100644 index 22bed9e4..00000000 --- a/docs/used/NanoVDB稀疏体积渲染正式集成计划_第一阶段完成归档_2026-04-09.md +++ /dev/null @@ -1,765 +0,0 @@ -# NanoVDB 稀疏体积渲染正式集成计划(Unity 对齐版) - -文档日期:2026-04-08 - -适用范围:当前 `XCEngine` 的 `Resources / Shader / RHI / Rendering / Components / Editor` 体系,以及未来要对齐 Unity 的 C#、Shader、SRP 工作流。 - -文档目标:把 `MVS/VolumeRenderer` 中已经验证过的 `NanoVDB + GPU Buffer + Ray Marching` 原型,整理成一套能正式进入引擎主线、且从一开始就严格兼容 Unity 风格 shader/C# 语义的落地方案。 - ---- - -## 1. 先给结论 - -这条主线的正式方向不是: - -1. 把体积数据退化成 `Texture3D` -2. 给当前引擎临时塞一套 volume 专用 shader 语法 -3. 只在某个后端里私下打通一条 demo 路径 - -这条主线的正式方向必须是: - -1. 保留 `NanoVDB` 的稀疏体积本质 -2. 把 buffer 资源能力补齐到引擎正式架构里 -3. 让高层 authoring 语义严格对齐 Unity -4. 让低层 RHI 分类只存在于引擎内部 -5. 让 volumetric 成为 renderer / future SRP 中的正式 pass,而不是特例后处理 - -因此,这份计划默认坚持一条红线: - -`StructuredBuffer / RawBuffer` 可以作为引擎内部资源分类存在,但作者可见语义、未来 C# API 语义、shader 写法语义,都必须对齐 Unity 既有做法,而不是引擎自创。 - ---- - -## 2. 设计总原则 - -### 2.1 Unity 对齐原则 - -后续所有设计都必须同时满足下面这些条件: - -1. shader 文件继续朝 Unity 风格 `.shader + HLSLPROGRAM` 组织收口。 -2. 作者侧 buffer 语义必须沿用 HLSL/Unity 现成写法,例如: - - `StructuredBuffer` - - `ByteAddressBuffer` - - `RWStructuredBuffer` - - `RWByteAddressBuffer` -3. 不允许为了体积渲染单独发明新的高层 buffer 关键字、声明块或材质 schema。 -4. `Properties` 块只用于可序列化作者参数,不用于声明或承载 GPU buffer。 -5. 未来 C# 层的 buffer 接口方向,必须对齐 Unity 的: - - `ComputeBuffer` - - `GraphicsBuffer` - - `Material.SetBuffer` - - `Shader.SetGlobalBuffer` - - `CommandBuffer.SetGlobalBuffer` -6. renderer 侧对 buffer 的真正绑定,必须是运行时 per-pass / per-object / global resource binding,而不是材质资产直接持有底层 view。 -7. `StructuredBuffer / RawBuffer` 在引擎里首先是底层编译和绑定分类,不是高层 authoring 概念。 - -### 2.2 稀疏体积原则 - -1. `NanoVDB` 必须按稀疏体积方案正式接入。 -2. 不允许把正式路线退化成 `Texture3D` 替代方案。 -3. `Texture3D` 可以作为引擎未来的独立能力预留,但不是这条主线的收口路径。 - -### 2.3 SRP 兼容原则 - -1. volumetric 必须被设计成正式 scene pass。 -2. 它的输入和生命周期要能自然映射到未来 SRP 的 renderer feature / render pass event。 -3. 不允许把它设计成只能在 builtin pipeline 中硬编码存在的特例逻辑。 - ---- - -## 3. Unity 参考基线 - -`参考/Unity NanoVDB` 已经给出了一条对本引擎非常重要的参考方向: - -1. C# 侧使用 `ComputeBuffer` -2. 运行时通过 `Material.SetBuffer("buf", gpuBuffer)` 绑定 -3. shader 侧通过 `StructuredBuffer` 读取 -4. `ComputeBufferType.Default` 对应的正是通用 GPU buffer 资源路径,而不是材质属性系统 - -这说明后续正式集成时,高层语义应该对齐的是: - -1. Unity 的 buffer authoring / binding 模式 -2. Unity 的 shader 资源声明模式 -3. Unity/HDRP 中把体积渲染插入 render pass 的方式 - -而不是: - -1. 直接照抄 `MVS` 的 D3D12 私有代码 -2. 用引擎内部 `RawBuffer` 这个名词去污染高层 shader/C# 接口 -3. 为了先出图,临时开 volume 专用旁路 - ---- - -## 4. 本轮明确不做什么 - -为了防止架构被 demo 反向拖偏,本轮明确不做下面这些事: - -1. 不把 `NanoVDB` 退化成正式 `Texture3D` 方案。 -2. 不把 `MVS/VolumeRenderer` 的 D3D12 私有 helper、私有绑定逻辑直接搬进引擎主线。 -3. 不先做全局体积雾、froxel fog、clustered volumetrics。 -4. 不先做复杂编辑器 authoring 工具,例如体积笔刷和节点编辑器。 -5. 不先做 DXR / path tracing 体渲染。 -6. 不为体积渲染另开一套与 Unity 不一致的 shader/material/C# 表层语法。 - ---- - -## 5. 当前真正缺的不是“算法”,而是正式能力 - -当前原型已经证明了下面几件事: - -1. `NanoVDB` 数据可以读。 -2. `NanoVDB` 数据可以上传到 GPU buffer。 -3. shader 可以基于 `pnanovdb` 做树遍历、HDDA 跳空和 ray marching。 -4. D3D12 原型能完成从数据到画面的闭环。 - -但引擎主线真正还缺下面这些正式能力: - -1. `.nvdb` 如何进入资产与 artifact 体系。 -2. shader 如何用 Unity 风格语义正式声明 buffer 资源。 -3. 材质系统如何和运行时 buffer 绑定做严格职责分离。 -4. RHI 三后端如何正式支持 buffer SRV/UAV。 -5. renderer 如何把体积渲染作为正式 pass 插入 frame。 -6. 未来 C# 层如何以 Unity 风格接口绑定 buffer。 -7. editor 和测试如何验证整条链路不会回归。 - -所以本计划的本质不是“移植 demo”,而是“先补正式能力,再把 demo 算法放到正式能力之上”。 - ---- - -## 6. 正式架构目标 - -### 6.1 作者可见层:严格对齐 Unity 风格 - -这一层是未来引擎用户、shader 作者、C# 脚本作者看到的语义表面。 - -要求如下: - -1. `.shader` 文件语法继续按 Unity 风格推进。 -2. buffer 在 shader 中的声明使用 HLSL 现成资源类型,不使用自定义资源关键字。 -3. 材质面板暴露的是作者参数,而不是底层 GPU buffer 本体。 -4. 如果未来提供脚本绑定接口,脚本看到的应该是 `ComputeBuffer / GraphicsBuffer` 风格对象和 `SetBuffer` 风格 API。 - -这层明确禁止: - -1. 在 `Properties` 中定义 `StructuredBuffer` 或 `RawBuffer` -2. 在 `.shader` 外层再发明 `BufferResources { ... }` 之类自定义块 -3. 让作者直接接触 `RHIResourceView` 或后端 view descriptor - -### 6.2 引擎中层:运行时绑定契约 - -这一层负责把“作者声明的 shader 资源”和“运行时真实绑定的 GPU 资源”接起来。 - -正式边界应该是: - -1. `Material` 只持有: - - shader 引用 - - keyword / render state - - 序列化参数 - - 常规纹理/采样器类作者资源 -2. `VolumeField` 或其他 runtime producer 提供真实 GPU buffer 资源 -3. `Renderer` / `CommandBuffer` / future script binding API 在渲染阶段把 buffer 绑定进 pass - -也就是说: - -1. `NanoVDB` 主数据不是普通 material property -2. 它是运行时资源绑定 -3. 材质只负责“怎么渲染” -4. `VolumeField` 负责“渲染的数据是什么” - -### 6.3 引擎底层:RHI 资源分类 - -在 RHI 与编译反射层,可以存在正式的内部分类,例如: - -1. `StructuredBuffer` -2. `RawBuffer` -3. 后续可扩展: - - `ByteAddressBuffer` 的内部映射 - - `RWStructuredBuffer` - - `RWByteAddressBuffer` - -但要强调: - -1. 这些是内部分类,不是高层 authoring 语法。 -2. 高层写的是 Unity/HLSL 资源声明。 -3. 引擎负责把这些声明反射并落到底层分类。 - -### 6.4 资源层:正式 VolumeField 资产 - -新增正式资源抽象: - -1. `ResourceType::VolumeField` -2. `Resources::VolumeField` -3. `VolumeFieldLoader` -4. `NanoVDBVolumeImporter` - -导入链路建议: - -1. 源资产:`Assets/.../*.nvdb` -2. artifact:`Library/Artifacts/.../main.xcvol` - -`main.xcvol` 建议包含: - -1. schema version -2. storage kind -3. grid type / grid class -4. world bbox -5. voxel size -6. active voxel statistics -7. payload byte size -8. 原始 `NanoVDB` blob - -原则: - -1. 第一阶段不做有损转换。 -2. artifact 中保留原始稀疏表示。 -3. metadata 单独结构化,供 loader / renderer 快速读取。 - -### 6.5 渲染层:正式体积 pass - -正式新增: - -1. `VolumeRendererComponent` -2. `VisibleVolumeItem` -3. `RenderSceneData.visibleVolumes` -4. `BuiltinVolumetricPass` - -该 pass 的职责: - -1. 消费 `VisibleVolumeItem` -2. 绑定 camera / depth / lighting / shadow / volume buffer / material 参数 -3. 执行 ray marching -4. 合成到 scene color - -推荐阶段位置: - -1. `ShadowCaster` -2. `Depth / Opaque` -3. `Skybox` -4. `Volumetrics` -5. `Transparent` -6. `PostProcess` -7. `FinalColor` - -这保证它天然兼容未来 SRP 的 renderer feature / pass event 方向。 - -### 6.6 C# 层预留目标 - -即使当前 C# 模块还没完全落地,这条计划也必须先把接口方向冻结。 - -后续需要预留的高层语义包括: - -1. `ComputeBuffer`/`GraphicsBuffer` 风格资源对象 -2. `Material.SetBuffer` -3. `Shader.SetGlobalBuffer` -4. `CommandBuffer.SetGlobalBuffer` - -这一层的原则是: - -1. 先冻结语义,再决定托管层具体封装细节 -2. 不允许等到以后 C# 接入时再发现底层 buffer 路径与 Unity 语义冲突 - ---- - -## 7. 关键命名与职责冻结 - -为了避免后续反复推翻命名,这一轮先冻结: - -1. 正式资源抽象:`VolumeField` -2. 存储种类:`VolumeStorageKind::NanoVDB` -3. 导入器:`NanoVDBVolumeImporter` -4. 运行时加载器:`VolumeFieldLoader` -5. 组件:`VolumeRendererComponent` -6. frame data:`VisibleVolumeItem` -7. pass:`BuiltinVolumetricPass` - -命名原则: - -1. 引擎对外抽象是 `VolumeField` -2. `NanoVDB` 是第一种正式存储后端 -3. 不把具体文件格式命名泄露到整个 renderer 表面 - ---- - -## 8. Shader 与 Buffer 的正式契约 - -这是整个计划最关键的部分。 - -### 8.1 shader 作者看到的语义 - -作者应该写的是: - -1. `StructuredBuffer` 这类 Unity/HLSL 现成声明 -2. 未来必要时的 `ByteAddressBuffer` -3. 如果进入写路径,再扩到 `RWStructuredBuffer` / `RWByteAddressBuffer` - -作者不应该写的是: - -1. `RawBuffer` -2. `StructuredRawBuffer` -3. 任何引擎自创的高层 buffer 类型关键字 - -### 8.2 材质系统的职责边界 - -材质系统只负责: - -1. 密度、吸收、散射、步长、相位函数参数 -2. tint / emission -3. shader variant / keyword -4. render state - -材质系统不负责: - -1. 序列化 `NanoVDB` payload -2. 序列化 GPU buffer handle -3. 序列化底层 SRV/UAV view - -### 8.3 运行时绑定职责 - -运行时绑定应该由以下层级之一完成: - -1. `Renderer` -2. `CommandBuffer` -3. future script API - -典型语义应接近: - -1. `material.SetBuffer("buf", volumeBuffer)` -2. `commandBuffer.SetGlobalBuffer(name, buffer)` - -### 8.4 内部映射规则 - -建议的内部映射是: - -1. `StructuredBuffer` -> `ShaderResourceType::StructuredBuffer` -2. `ByteAddressBuffer` -> `ShaderResourceType::RawBuffer` -3. `RWStructuredBuffer` -> UAV + structured 分类 -4. `RWByteAddressBuffer` -> UAV + raw 分类 - -这里的重点不是名字,而是边界: - -1. 高层保持 Unity/HLSL 语义 -2. 中层做反射和 metadata -3. 低层做真正 view 创建与 descriptor 绑定 - -### 8.5 一个硬性前置条件 - -如果当前 shader authoring 体系还不能正式表达 Unity 风格 buffer 声明,那么这个缺口的优先级高于体积渲染本身。 - -也就是说: - -1. 不允许为了先做 volumetric,单独给它走私有 shader 语法 -2. 必须先把正式 shader authoring / reflection / runtime binding 路径打通 - ---- - -## 9. RHI 正式能力目标 - -RHI 侧至少需要补齐: - -1. `RHIDevice::CreateShaderResourceView(RHIBuffer*, const ResourceViewDesc&)` -2. `RHIDevice::CreateUnorderedAccessView(RHIBuffer*, const ResourceViewDesc&)` -3. `ResourceViewDesc` 的 buffer 字段: - - firstElement - - elementCount - - structureByteStride - - raw / structured / formatted 标记 -4. `RHIResourceView` 对 buffer SRV/UAV 的统一表达 -5. descriptor set / bind group 更新路径对 buffer view 的正式支持 - -三后端预期映射: - -1. D3D12: - - buffer SRV - - buffer UAV -2. Vulkan: - - storage buffer - - 需要时使用 texel buffer 路径 -3. OpenGL: - - SSBO 优先 - - 明确 GLSL 转译和绑定规则 - -这里必须强调: - -1. 这些是底层实现能力 -2. 它们服务于高层 Unity 风格语义 -3. 它们不能反过来决定高层作者接口长什么样 - ---- - -## 10. 正式实施阶段 - -## 阶段 0:冻结 Unity 对齐边界 - -### 目标 - -在改代码前,先冻结高层语义、中层契约、低层分类三层边界。 - -### 任务 - -1. 冻结 `VolumeField` / `VolumeRendererComponent` / `VisibleVolumeItem` / `BuiltinVolumetricPass` 命名 -2. 冻结体积渲染在 scene pass 中的阶段位置 -3. 冻结高层 buffer 语义: - - 只接受 Unity/HLSL 写法 -4. 冻结材质边界: - - buffer 不进 `Properties` -5. 冻结未来 C# 方向: - - `ComputeBuffer / GraphicsBuffer + SetBuffer` 语义 -6. 冻结内部映射: - - `StructuredBuffer` -> internal structured - - `ByteAddressBuffer` -> internal raw - -### 验收标准 - -1. 后续实现阶段不再反复摇摆高层语义 - ---- - -## 阶段 1:先补底层 buffer 资源视图能力 - -### 目标 - -补齐 renderer 目前缺失的“buffer 作为正式 shader 资源”的底层能力。 - -### 任务 - -1. 完成 `RHIBuffer` 的 SRV/UAV 创建接口 -2. 补齐 `ResourceViewDesc` 的 buffer 视图字段 -3. 让 descriptor set 更新路径正式支持 buffer 类 view -4. D3D12 / Vulkan / OpenGL 三后端同时按正式接口设计 -5. 明确 backend capability 与错误回报,不允许 silent wrong image - -### 测试 - -1. `tests/RHI/unit` 增加 buffer SRV/UAV 创建测试 -2. descriptor set buffer binding 测试 -3. 三后端分别验证 buffer view 不再被误当成 texture view - -### 验收标准 - -1. 引擎能在不依赖 texture hack 的前提下,正式把 GPU buffer 绑定给 shader - ---- - -## 阶段 2:打通 Unity 风格 shader buffer 语义 - -### 目标 - -让 shader authoring / parser / reflection / artifact / runtime binding 能正式表达 Unity/HLSL 风格 buffer 资源。 - -### 任务 - -1. 扩展 shader 反射模型以支持 structured/raw buffer 资源 -2. 明确 `StructuredBuffer` 与 `ByteAddressBuffer` 的内部映射 -3. 扩展 runtime build、pass layout、descriptor metadata 对新资源类型的支持 -4. 明确 OpenGL 的 GLSL/SSBO 转译规则 -5. 严格禁止 volume 专属私有 shader 语法旁路 - -### 强约束 - -1. 不新增高层 `RawBuffer` 关键字 -2. 不新增 `MaterialPropertyType::StructuredBuffer / RawBuffer` -3. 如果 `Properties` 中尝试声明 buffer,必须报错或拒绝导入 - -### 测试 - -1. `tests/Resources/Shader` -2. `tests/Resources/Material` -3. `tests/Rendering/unit` 中的 pass layout / metadata 测试 -4. authoring 约束测试: - - Unity/HLSL 风格 buffer 声明可以被正确反射 - - 非法把 buffer 塞进 `Properties` 会被拒绝 - -### 验收标准 - -1. shader 资源描述层正式支持 buffer 资源 -2. 高层 authoring 语义仍保持 Unity 风格,不被底层分类污染 - ---- - -## 阶段 3:冻结 future C# buffer 绑定契约 - -### 目标 - -在 C# 模块尚未完全落地前,先把与本主线相关的 buffer 绑定语义冻结。 - -### 任务 - -1. 定义 native 侧可对应 future C# 的 buffer 绑定入口 -2. 约束 API 形状向 Unity 对齐: - - `Material.SetBuffer` - - `Shader.SetGlobalBuffer` - - `CommandBuffer.SetGlobalBuffer` -3. 明确 `VolumeField` 与 runtime buffer producer 的关系 -4. 保证未来托管层不会直接看到 RHI view 对象 - -### 测试 - -1. native 侧 binding metadata 测试 -2. material/runtime/global 三类 buffer 绑定路径测试 - -### 验收标准 - -1. 当前实现方向已经为 future C# 留好 Unity 风格接口落点 - ---- - -## 阶段 4:接入正式 VolumeField 资产链路 - -### 目标 - -让 `.nvdb` 成为项目里的正式资源,而不是 demo 私有文件。 - -### 任务 - -1. 新增 `ResourceType::VolumeField` -2. 新增 `Resources::VolumeField` -3. 新增 `VolumeFieldLoader` -4. 新增 `NanoVDBVolumeImporter` -5. `AssetDatabase` 识别 `.nvdb` -6. 生成 `main.xcvol` -7. 接入 reimport / dependency / cache / runtime load - -### 测试 - -1. `tests/Resources/Volume` -2. `tests/Core/Asset` -3. `.nvdb` 改动后的 reimport 测试 - -### 验收标准 - -1. `.nvdb` 可以像 mesh / material / shader 一样进入正式资产体系 - ---- - -## 阶段 5:接入组件、提取与 frame data - -### 目标 - -让场景系统知道“什么是可渲染的体积对象”。 - -### 任务 - -1. 新增 `VolumeRendererComponent` -2. 组件持有: - - `VolumeField` - - `Material` - - enable/disable - - 局部参数 override - - bounds / transform -3. `RenderSceneExtractor` 增加体积对象提取 -4. 新增 `VisibleVolumeItem` -5. `RenderSceneData.visibleVolumes` 接入 - -### 测试 - -1. 组件序列化 / 反序列化 -2. `VisibleVolumeItem` 提取测试 -3. frustum / bounds culling 与稳定顺序测试 - -### 验收标准 - -1. renderer 已经能从正式场景数据中拿到体积对象输入 - ---- - -## 阶段 6:BuiltinVolumetricPass 首次正式点亮 - -### 目标 - -在当前 renderer 正式链路中点亮第一版 `NanoVDB` 稀疏体积渲染。 - -### 任务 - -1. 新增 `BuiltinVolumetricPass` -2. pass 输入包括: - - camera - - depth - - scene color - - light / shadow - - volume buffer - - material parameters -3. D3D12 首次点亮 -4. 允许保留一条集成测试级别的 fullscreen debug path -5. 正式运行时路径收口到 proxy box / unit cube volume renderer - -### 强约束 - -1. fullscreen path 只能作为 bring-up / integration debug 路径 -2. 正式场景运行时不能停留在 fullscreen 方案 -3. 不得保留 `MVS` 风格直接文件加载或私有绑定路径 - -### 测试 - -1. `tests/Rendering/integration/volume_nanovdb_minimal` -2. GT 对比 -3. 中间调试图输出: - - `volume_mask_debug` - - `depth_debug` - - 必要时的 shadow debug - -### 验收标准 - -1. 正式 renderer 已经能稳定渲染一个来自 `.nvdb` 的体积对象 - ---- - -## 阶段 7:深度、阴影、合成规则收口 - -### 目标 - -把“能出图”收口成“行为正确”。 - -### 任务 - -1. 深度遮挡规则确定 -2. scene color 合成规则确定 -3. 主方向光与阴影采样接入 -4. transform 与 bbox 变换一致 -5. 步长、阴影采样步数、质量参数稳定化 - -### 测试 - -1. `volume_nanovdb_occlusion` -2. `volume_nanovdb_transform` -3. 光照方向变化的回归测试 - -### 验收标准 - -1. 画面行为能解释、能回归、能维护,不再只是 demo level - ---- - -## 阶段 8:多后端 rollout - -### 目标 - -把已在 D3D12 验证的正式能力推进到 Vulkan 与 OpenGL。 - -### 任务 - -1. Vulkan storage buffer 路线点亮 -2. OpenGL SSBO 路线点亮 -3. 明确 capability 检测与 fallback / disable 行为 -4. 补齐 backend-specific shader compile / runtime binding 回归 - -### 测试 - -1. 各后端统一 GT 对比 -2. backend-specific shader compile 测试 -3. buffer binding 回归测试 - -### 验收标准 - -1. 不是只有 D3D12 demo 可用,而是正式进入多后端 renderer 能力集合 - ---- - -## 阶段 9:Editor 最小工作流接入 - -### 目标 - -让这项能力进入项目工作流,而不是只能靠测试程序看图。 - -### 任务 - -1. `ProjectPanel` 识别 `.nvdb` -2. `Inspector` 显示 `VolumeField` metadata -3. `VolumeRendererComponent` inspector 编辑 -4. scene view / game view 中体积对象可见 -5. 资源缺失、编译失败、后端不支持时给出明确状态 - -### 验收标准 - -1. 用户可以从资产、组件、场景三个层面完整使用这项能力 - ---- - -## 11. 测试体系规划 - -### 11.1 单元测试 - -需要新增或扩展: - -1. `tests/RHI/unit` - - buffer SRV/UAV 创建 - - descriptor set buffer binding -2. `tests/Resources/Shader` - - Unity/HLSL 风格 buffer 声明解析 - - 非法 `Properties` buffer 声明拒绝 -3. `tests/Resources/Material` - - 材质参数 schema 与 runtime buffer 绑定边界 -4. `tests/Resources/Volume` - - `.nvdb -> .xcvol` - - metadata roundtrip - - reimport -5. `tests/Rendering/unit` - - `VisibleVolumeItem` 提取 - - `BuiltinVolumetricPass` 输入校验 - -### 11.2 集成测试 - -建议新增: - -1. `tests/Rendering/integration/volume_nanovdb_minimal` -2. `tests/Rendering/integration/volume_nanovdb_occlusion` -3. `tests/Rendering/integration/volume_nanovdb_transform` - -GT 规则: - -1. 使用单一 `GT.ppm` -2. 各后端输出都对同一张 `GT.ppm` 做对比 -3. 必要时保留中间调试图,但不把调试图当正式输出 - -### 11.3 性能与稳定性验收 - -至少记录: - -1. 单体积对象 frame time -2. 多体积对象 frame time -3. `.nvdb` 首次导入时长 -4. artifact 命中后的加载时长 - ---- - -## 12. 关键风险 - -### 12.1 最大风险不是算法,而是语义边界被做脏 - -如果一开始把 `StructuredBuffer / RawBuffer` 直接暴露成高层 authoring 概念,后面整个 shader/material/C# 体系都会被污染。 - -### 12.2 OpenGL 路线风险最高 - -原因: - -1. `StructuredBuffer` 到 GLSL/SSBO 的映射最容易出边角问题 -2. `PNanoVDB` 这种大 include 在 GLSL 转译路径上更容易暴露兼容性问题 - -### 12.3 fullscreen debug path 容易反客为主 - -它只能用于 bring-up 和集成测试,不能变成正式场景架构。 - -### 12.4 如果当前 shader 体系还没正式支持 Unity 风格 buffer 语义,这会成为主阻塞项 - -这不是额外问题,而是本主线的前置依赖。 - ---- - -## 13. 本阶段收口标准 - -当下面条件同时满足时,可以认为“稀疏体积渲染第一阶段正式完成”: - -1. `.nvdb` 已成为正式项目资源,可导入、可 reimport、可 artifact 化。 -2. 引擎 shader / runtime binding / RHI 已正式支持 Unity 风格 buffer 资源链路。 -3. 高层 authoring 没有引入任何体积专用自定义 buffer 语法。 -4. `VolumeRendererComponent` 能把 `VolumeField` 放进场景。 -5. `BuiltinVolumetricPass` 已作为正式 scene pass 存在。 -6. D3D12 至少已经在正式链路中点亮。 -7. 集成测试能稳定出图并做 GT 对比。 -8. future C# 的 `SetBuffer` 风格接口已经有明确落点,不会和当前底层设计冲突。 -9. 代码中不再依赖 `MVS/VolumeRenderer` 那套 D3D12 私有 demo 实现。 - ---- - -## 14. 一句话结论 - -这条主线的正确做法不是“先做出体积渲染再说”,而是“从第一天开始就按 Unity 风格的 shader/C# 语义设计 buffer 路径,把 `NanoVDB` 作为正式稀疏体积能力接入 renderer”,这样后面无论是 C# 层还是 SRP,都不会被今天的底层实现反噬。 diff --git a/docs/used/NewEditor_3D渲染主链正式接入计划_2026-04-12.md b/docs/used/NewEditor_3D渲染主链正式接入计划_2026-04-12.md deleted file mode 100644 index e956ab62..00000000 --- a/docs/used/NewEditor_3D渲染主链正式接入计划_2026-04-12.md +++ /dev/null @@ -1,333 +0,0 @@ -# NewEditor 3D渲染主链正式接入计划 - -日期: `2026-04-12` - -## 1. 文档定位 - -这份计划替代此前那份偏宽泛的《NewEditor 对标旧 Editor 迁移重建计划》。 - -当前主线已经很明确: - -- `new_editor` 的 UI 基础壳已经足够继续往上承接。 -- 真正卡住后续重建编辑器的,不再是普通面板样式或 tab/dock 行为。 -- 当前最大的主阻塞,是 `Scene / Game` 还没有接上旧 `editor` 里已经跑通的真实 3D 渲染主链。 - -因此,接下来要先做的不是继续铺业务面板,而是: - -1. 先把 `new_editor` 的宿主渲染链补齐。 -2. 再把旧 `editor` 的 viewport/rendering 主链正式迁入 `new_editor`。 -3. 等 `Scene / Game` 真正稳定输出真实 3D 画面后,再继续后续编辑器业务迁移。 - -## 2. 当前事实与根因判断 - -### 2.1 旧 editor 当前的真实可用主链 - -旧 `editor` 的 Scene/Game viewport 不是面板里临时拼的,它已经形成了明确主链: - -`Application -> D3D12WindowRenderer -> ViewportHostService -> SceneRenderer -> ViewportRenderTargets -> ImGui texture presentation` - -当前关键代码入口: - -- `editor/src/Application.cpp` -- `editor/src/Platform/D3D12WindowRenderer.h` -- `editor/src/Viewport/ViewportHostService.h` -- `editor/src/panels/SceneViewPanel.cpp` -- `editor/src/panels/GameViewPanel.cpp` - -### 2.2 new_editor 当前的真实状态 - -`new_editor` 现在已经有: - -- `XCEditor` UI 基础层 -- Win32 + `NativeRenderer` 宿主层 -- `Hierarchy / Inspector / Console / Project` 这些 hosted content 面板外壳 - -但 `Scene / Game` 这条线并没有真正接上旧 editor 的 render path。 - -当前缺口是结构性的: - -1. `new_editor/app/Shell/ProductShellAsset.cpp` 里,`scene` 和 `game` 仍然只是 `HostedContent`,还没有切到正式的 `ViewportShell` 主线。 -2. `new_editor/app/Workspace/ProductEditorWorkspace.cpp` 里,没有真正的 viewport service owner,也没有 scene/game render request 生命周期。 -3. `new_editor/app/Host/NativeRenderer.h/.cpp` 当前是 Direct2D/DirectWrite 宿主,只支持它自己的位图纹理路径。 -4. `engine/include/XCEngine/UI/Types.h` 虽然已经有 `UITextureHandleKind::ShaderResourceView`,但 `NativeRenderer` 目前并没有真正消费这类引擎 render target。 - -### 2.3 根因结论 - -当前问题的根因不是“Scene 面板还没抄过来”,而是: - -- `new_editor` 宿主层还没有与引擎 `Rendering + RHI` 建立正式的 viewport 纹理桥接。 -- `XCEditor` 的 viewport 壳层虽然已经存在,但 `new_editor` 还没把它真正接到 GPU viewport frame 上。 - -所以这件事必须从宿主渲染架构开始修,不能从面板层糊。 - -## 3. 不可违背的执行原则 - -### 3.1 根因优先 - -- 禁止 CPU 截图回读后再贴回 UI。 -- 禁止用 PNG/中间 bitmap/调试贴图冒充正式 viewport 输出。 -- 禁止在 `Scene` 或 `Game` 面板内部偷偷持有一套临时渲染路径。 - -### 3.2 旧 editor 只作为基线,不照搬 ImGui 壳 - -- 要复用旧 editor 已经验证过的 render host、viewport service、render target、object id、overlay 这些真实主链能力。 -- 但不要把 `ImGui panel` 那一层硬抄到 `new_editor`。 -- 迁移时要抽出“引擎桥接能力”,接回 `XCEditor` 的 viewport shell。 - -### 3.3 Scene/Game 必须统一走正式 viewport 子系统 - -- `Scene` 和 `Game` 不能继续当普通 hosted content 面板处理。 -- 它们必须切到同一套 `ViewportShell -> ViewportService -> RenderTargets -> RenderFrame` 路径。 -- Scene/Game 的差异只能体现在 camera / overlay / input policy,不应该体现在宿主渲染基础设施层。 - -### 3.4 每阶段都能独立验证 - -每一阶段结束都必须满足: - -- 能编译 `XCUIEditorApp` -- 能运行 exe -- 能给出明确的人工检查点 - -## 4. 目标架构 - -## 4.1 new_editor 最终应形成的 viewport 主链 - -目标主链: - -`Win32 Application -> Host Window Renderer -> XCEditor ViewportShell -> ProductViewportHostService -> SceneRenderer -> RHI RenderTargets -> Viewport Texture Bridge -> XCEditor Compose -> Window Present` - -分层责任: - -- `Host` - - 管窗口、swapchain、command queue、command list、descriptor heap、present -- `XCEditor` - - 管 viewport 壳层布局、top bar、bottom bar、input bridge、状态显示 -- `new_editor/app/Viewport` - - 管 Scene/Game viewport 请求、render target 生命周期、editor scene camera、input 解释、selection/picking/overlay -- `Rendering + RHI` - - 管真正的 scene render requests 和 GPU 输出 - -## 4.2 宿主层方向 - -`NativeRenderer` 当前的 D2D-only `HwndRenderTarget` 不能作为长期正式宿主。 - -新的宿主方向应当是: - -- 以 D3D12/RHI swapchain 作为主呈现链 -- 在这个主呈现链上承接 `XCEditor` UI 组合与 engine viewport 纹理 - -关于 2D UI 绘制策略,允许按实际情况调整实现,但边界必须固定: - -- 可以保留 D2D/DWrite 作为 2D 绘制后端,但它只能作为从属 UI compositor,不能继续做整个窗口的唯一主呈现链 -- 如果 D2D 与 D3D12 的互操作复杂度失控,就切换为更直接的 GPU UI compositor -- 无论选择哪条实现路径,都不能回退到 CPU 回读/贴图绕路 - -## 5. 执行阶段 - -## 阶段 A: 宿主渲染基础设施对齐 - -目标: - -- 先把 `new_editor` 宿主从“D2D-only 窗口绘制器”升级为“真正的窗口 render host” - -主要工作: - -1. 在 `new_editor/app/Host` 下建立正式的 window renderer -2. 对齐旧 `editor/src/Platform/D3D12WindowRenderer.h` 的能力边界 -3. 让 `Application` 能拿到: - - `RHIDevice` - - `RHICommandQueue` - - `RHICommandList` - - swapchain backbuffer surface - - shader-visible descriptor heap - - 每帧 begin/present 生命周期 -4. 保留并接回现有 DPI / resize / cursor / screenshot 生命周期 - -验收: - -- `XCUIEditor.exe` 能用新宿主稳定打开 -- 调整窗口尺寸时不会再出现位图式整体拉伸 -- 空窗口 clear/present 稳定,无闪烁、无明显 resize 延迟 - -## 阶段 B: Viewport 纹理桥接正式化 - -目标: - -- 让 `XCEditor` viewport slot 真正能展示来自引擎 RHI 的 render target - -主要工作: - -1. 明确 `UITextureHandle` 在 `new_editor` 中的正式 viewport 语义 -2. 补齐 `ShaderResourceView` 路径,而不是继续只支持文件位图 -3. 建立 render target -> UI texture handle 的生命周期管理 -4. 处理 descriptor/state/resize/recreate 规则 - -建议测试: - -- 在 `tests/UI/Editor/integration` 下新增专门的 viewport GPU 纹理验证用例 -- 先用一个最小 GPU clear/checkerboard 纹理跑通 `ViewportShell` - -验收: - -- `ViewportShell` 可显示真实 GPU render target -- viewport 尺寸变化时,纹理尺寸和显示内容立即同步 -- 整个链路不含 readback / screenshot / 中间文件 - -## 阶段 C: Scene/Game 从 HostedContent 切到 ViewportShell - -目标: - -- 让 `scene` 和 `game` 不再是伪面板,而是正式 viewport panel - -主要工作: - -1. 修改 `ProductShellAsset` 中 `scene/game` 的 presentation 模型 -2. 在 `new_editor/app` 建立正式的 viewport panel 装配点 -3. 建立 viewport request/response 数据模型: - - requested size - - presented frame - - status text - - capture/focus/input state - -验收: - -- `Scene` 和 `Game` 面板进入统一 viewport 壳层 -- 可以显示“真实 viewport frame”,不再是纯 placeholder/调试文案 - -## 阶段 D: Scene 真实渲染最小闭环 - -目标: - -- 先让 `Scene` 面板输出旧 editor 那条真实 3D 场景渲染 - -主要工作: - -1. 在 `new_editor/app/Viewport` 下建立 `ProductViewportHostService` -2. 先迁移旧 `ViewportHostService` 的最小 spine: - - scene editor camera state - - scene viewport render targets - - `SceneRenderer` 调用 - - status/failure policy -3. 只先做“能稳定渲染”的最小闭环 -4. 暂时不带 gizmo,不带复杂 overlay,不带过多面板业务联动 - -验收: - -- `Scene` 面板显示真实 3D 场景 -- resize 时 render target 立即更新 -- 画面不是占位实现,不是 2D 假画面 - -## 阶段 E: Game 真实渲染闭环 - -目标: - -- 接上 `Game` 面板的真实 camera render path - -主要工作: - -1. 对齐旧 editor 的 game viewport 请求方式 -2. 使用 active scene camera 或旧 editor 当前约定的 camera 选择规则 -3. 接上 game viewport 状态显示和 resize 规则 - -验收: - -- `Game` 面板显示真实场景相机输出 -- 与 `Scene` 共享宿主基础设施,但 camera 语义独立 - -## 阶段 F: Scene 输入、导航、拾取 - -目标: - -- 在真实 3D 画面已经稳定的前提下,补回 Scene 交互 - -主要工作: - -1. 迁移 scene camera navigation -2. 迁移 focus selection -3. 迁移 object id picking -4. 接回 selection 路由 - -验收: - -- 右键看、平移、缩放、聚焦等基础导航恢复 -- 点击对象能正确选中 - -## 阶段 G: Overlay / Gizmo / Editor 辅助渲染 - -目标: - -- 继续对齐旧 editor 的 editor-only viewport 叠加能力 - -主要工作: - -1. overlay frame cache -2. overlay builder -3. selection outline / orientation / transform gizmo -4. Scene/Game 差异化 overlay policy - -验收: - -- Scene overlay 与 gizmo 基本达到旧 editor 可用水平 -- 不因 overlay 引入新的渲染路径分叉 - -## 6. 并行切分策略 - -这条主线不是完全串行,但前两阶段有明显关键路径。 - -### 必须串行的部分 - -1. 阶段 A 宿主 render host -2. 阶段 B viewport 纹理桥接 - -这两步没完成,后面的 Scene/Game 真渲染都只能是假接入。 - -### 可以并行的部分 - -在 A/B 基本定型后,可并行拆为 3 组: - -1. `Host` - - window renderer - - present/frame lifecycle - - viewport texture bridge -2. `app/Viewport` - - render targets - - scene/game render request owner - - status/failure policy -3. `XCEditor` / `app/Shell` - - scene/game 切换到 `ViewportShell` - - viewport 状态展示 - - 相关 integration tests - -## 7. 明确不做的事 - -这份计划期间,不做以下偏移主线的工作: - -- 不继续优先铺新的业务面板细节 -- 不先做 inspector/component editor 的深层业务联动 -- 不把 runtime UI 主题资源机制重新塞回 editor UI -- 不用临时截图/静态贴图假装 viewport - -## 8. 收口标准 - -满足以下条件,才算 `new_editor` 的 3D 渲染基础层正式收口: - -1. `Scene` 和 `Game` 都显示真实 3D GPU 输出 -2. resize、DPI、focus、capture 行为稳定 -3. viewport 纹理链路不依赖 ImGui -4. Scene 基础导航和 picking 恢复 -5. 后续继续重建 `new_editor` 时,不需要再回头推倒宿主渲染基础层 - -## 9. 旧计划归档说明 - -本轮归档: - -- `docs/plan/used/NewEditor对标旧Editor迁移重建计划_2026-04-12.md` - -保留为参考文档,不归档: - -- `docs/plan/Editor架构说明.md` - -原因: - -- 它仍然是旧 editor 分层与边界的有效参考 -- 但当前主线已经从“泛迁移计划”切换为“viewport / rendering 主链正式接入计划” diff --git a/docs/used/NewEditor_ArchitectureClosurePlan_完成归档_2026-04-22.md b/docs/used/NewEditor_ArchitectureClosurePlan_完成归档_2026-04-22.md deleted file mode 100644 index 635e10d4..00000000 --- a/docs/used/NewEditor_ArchitectureClosurePlan_完成归档_2026-04-22.md +++ /dev/null @@ -1,196 +0,0 @@ -# NewEditor Architecture Closure Plan - -Date: 2026-04-22 - -## 1. Objective - -彻底收口 `new_editor` 当前仍然残留的旧语义、冗余结构和重复控制面。 - -这次不是补丁式修正,而是把当前主架构重新收成单一路径: - -1. 主窗口 UI 渲染只保留一条 `D3D12` 实例化 primitive pass。 -2. 启动截图策略只保留一个控制源。 -3. `Rendering/Native` 这层已经失效的旧模块边界被移除。 -4. 已确认无用的死代码和错误语义命名被清理。 - -## 2. Confirmed Root Issues - -### 2.1 D3D12 UI renderer still contains a legacy geometry sub-pipeline - -当前 `D3D12UiRenderer` 虽然已经是 `D3D12` 主路径,但内部仍然并存两套 UI 几何编译/提交策略: - -1. `QuadInstanced` -2. `LegacyIndexed` - -其中绝大多数 UI 命令已经走实例化 primitive 路径,只有少量三角形命令仍然维持整套: - -- legacy vertex buffer -- legacy index buffer -- legacy pipeline state -- legacy batch kind - -这会导致: - -- UI renderer 不是单一路径 -- 缓冲管理、缓存结构、绘制分发继续为旧几何方案付复杂度成本 -- “legacy” 语义真正活着,而不是只有名字没改 - -### 2.2 Startup auto-capture ownership is split across two layers - -当前 startup capture 既在 `EditorWindowScreenshotController::Initialize(...)` 内通过环境变量触发, -又在 `EditorWindowRuntimeController::Initialize(...)` 内通过 -`autoCaptureOnStartup + env` 触发。 - -这导致: - -- 策略决定权不是单点 -- `autoCaptureOnStartup` 形参不是唯一真实来源 -- 非主窗口也可能被环境变量隐式触发 startup request - -### 2.3 Rendering/Native module boundary is obsolete - -`app/Rendering/Native/` 目录现在只剩 `AutoScreenshot.*`。 - -它已经不再承担旧的 native renderer backend 职责,但目录和 include 关系仍然保留旧时代边界。 - -### 2.4 Confirmed dead code remains in the hot renderer unit - -`D3D12UiRenderer.cpp` 里存在已无调用方的辅助函数,说明之前的旧几何阶段没有清理彻底。 - -## 3. Target End State - -完成后应满足: - -1. `D3D12UiRenderer` 不再存在 `LegacyIndexed` / `legacy*` 动态几何资源。 -2. 所有 UI draw command 都编译为同一类实例化 primitive 数据。 -3. 渲染时只绑定一套 UI primitive pipeline。 -4. `EditorWindowScreenshotController` 不再自行读取 startup env,也不再拥有策略判断权。 -5. startup capture 是否发生,只由窗口创建策略单点决定。 -6. `Rendering/Native` 目录被清空或完全移除。 -7. 已确认无调用的渲染死代码删除。 - -## 4. Refactor Strategy - -### Phase A. Collapse UI geometry into one primitive pipeline - -把当前 UI pass 改成单一实例化 primitive 方案: - -- 保留单位 quad carrier vertex/index buffer -- 删除动态 legacy vertex/index buffer -- 删除 legacy pipeline state -- 删除 legacy batch kind -- 统一 batch 为 instance range + texture + scissor - -需要新增的 primitive 能力: - -- 现有 rect / outline / circle / text / image / gradient -- 新增 triangle primitive,让 `FilledTriangle` 也走实例化路径 - -triangle 的正确实现目标: - -- 不是回退到 indexed geometry -- 不是临时特判 draw call -- 而是作为实例化 primitive 的一种 kind,被统一编译、缓存、上传和绘制 - -### Phase B. Rebuild renderer data model around the unified path - -同步调整: - -- `UiBatchKind` 去除或退化 -- `UiQuadInstance` 升级为更通用的 `UiPrimitiveInstance` -- `CompiledDrawList` 不再缓存 legacy vertices / indices -- `EnsureFrameBufferCapacity(...)` 只维护 primitive instance buffer -- render dispatch 只保留单一 pipeline bind 分支 - -### Phase C. Collapse startup capture ownership to one source of truth - -把 startup capture 的策略权上移并单点化: - -- `Application` 或窗口创建层读取 env / policy -- `CreateParams.autoCaptureOnStartup` 成为唯一窗口级输入 -- `EditorWindowRuntimeController` 只消费该参数 -- `EditorWindowScreenshotController` 只做请求/路径/结果管理,不做策略判断 - -### Phase D. Remove obsolete Native module shell - -将截图控制器从 `app/Rendering/Native/` 迁移到更合理的位置, -预计是窗口运行时或通用 support/capture 模块。 - -要求: - -- 迁移后 active include 不再引用 `Rendering/Native` -- `new_editor/CMakeLists.txt` 同步更新 - -### Phase E. Delete dead helpers and normalize semantics - -删除已经确认无调用的旧辅助函数,并统一命名: - -- `legacy*` -> 按真实职责改名,或随着删除一起消失 -- 清理无用 helper -- 保证 renderer 术语与当前架构一致 - -## 5. File-Level Work - -### 5.1 UI renderer core - -Primary: - -- `new_editor/app/Rendering/D3D12/D3D12UiRenderer.h` -- `new_editor/app/Rendering/D3D12/D3D12UiRenderer.cpp` - -### 5.2 Startup capture boundary - -Primary: - -- `new_editor/app/Bootstrap/Application.cpp` -- `new_editor/app/Platform/Win32/EditorWindow.cpp` -- `new_editor/app/Platform/Win32/EditorWindowRuntimeController.h` -- `new_editor/app/Platform/Win32/EditorWindowRuntimeController.cpp` -- `new_editor/app/Platform/Win32/EditorWindowManager.h` - -### 5.3 Screenshot controller relocation - -Current source: - -- `new_editor/app/Platform/Win32/EditorWindowScreenshotController.h` -- `new_editor/app/Platform/Win32/EditorWindowScreenshotController.cpp` - -Target: - -- move into non-legacy module location - -### 5.4 Build graph cleanup - -- `new_editor/CMakeLists.txt` - -## 6. Validation - -### 6.1 Structural validation - -完成后搜索应满足: - -- no `LegacyIndexed` in active UI renderer -- no `m_legacyPipelineState` -- no `legacyVertexBuffer` -- no `legacyIndexBuffer` -- no `app/Rendering/Native/AutoScreenshot.*` active include path - -### 6.2 Runtime validation - -必须验证: - -1. `XCUIEditorApp` Debug 构建通过 -2. editor 正常启动 -3. 主窗口 UI 正常显示与交互 -4. startup auto-capture 正常生成 -5. detached window 不会错误触发 startup capture - -## 7. Completion Criteria - -只有满足以下条件,这个 plan 才算真正收口: - -1. UI 渲染路径内部不再存在旧几何子管线 -2. startup capture 策略控制权单点化 -3. 旧 `Native` 模块边界消失 -4. 死代码删除 -5. 构建、启动、截图验证全部通过 diff --git a/docs/used/NewEditor_ContentCapabilityRefactorPlan_完成归档_2026-04-23.md b/docs/used/NewEditor_ContentCapabilityRefactorPlan_完成归档_2026-04-23.md deleted file mode 100644 index 41416216..00000000 --- a/docs/used/NewEditor_ContentCapabilityRefactorPlan_完成归档_2026-04-23.md +++ /dev/null @@ -1,168 +0,0 @@ -# NewEditor Content Capability Refactor Plan - -Date: 2026-04-23 -Status: Completed - -## 1. Objective - -Refactor the `new_editor/app/Platform/Win32` window-content boundary so that optional behaviors are expressed as explicit capabilities instead of being stuffed into one fat `EditorWindowContentController` interface. - -This plan targets the most severe remaining architecture issue in Win32: - -1. `EditorWindowContentController` currently mixes base rendering lifecycle, workspace mutation, dock-host interaction, capture/cursor feedback, and title-bar policy in one interface. -2. `EditorWindowRuntimeController` mirrors that bloated surface almost one-to-one, so every optional behavior is forwarded twice before it reaches real implementation. -3. Workspace content implements nearly the entire interface, while utility content implements only a thin subset and inherits a large set of meaningless default methods. - -The goal is to stop capability sprawl at the root instead of continuing to delete wrappers one file at a time. - -## 2. Confirmed Root Cause - -The current layering drift is: - -1. `EditorWindowContentController` acts as both: - - mandatory window-content contract - - optional capability registry -2. `EditorWindowRuntimeController` repeats those same optional operations as direct forwarding methods. -3. Callers such as: - - `EditorWindow` - - `EditorWindowMessageDispatcher` - - `EditorWindowWorkspaceCoordinator` - - `EditorWindowChromeController` - consume the mirrored runtime API instead of consuming small, explicit capability contracts. - -That creates two kinds of redundancy: - -1. interface redundancy: - optional methods live on a base type that many implementations do not meaningfully support -2. forwarding redundancy: - runtime methods exist only to forward a single optional call to content - -## 3. Target End State - -After the refactor: - -1. `EditorWindowContentController` keeps only mandatory content lifecycle and frame responsibilities. -2. Optional behaviors become explicit capability interfaces: - - workspace binding - - dock-host interaction - - input/capture feedback - - title-bar presentation policy -3. `EditorWindowRuntimeController` exposes capability lookup, not one forwarding method per optional behavior. -4. Win32 callers use those capability interfaces directly and apply local fallback behavior when a capability is absent. - -## 4. Capability Split - -### 4.1 Mandatory base contract - -Keep in `EditorWindowContentController` only behavior that every content implementation must provide: - -1. initialize / shutdown / reset -2. viewport presentation enablement -3. frame update and viewport render -4. shell frame and shell interaction state -5. minimum outer size - -### 4.2 Optional capability contracts - -Introduce explicit optional interfaces: - -1. `EditorWindowWorkspaceBinding` - - already exists -2. `EditorWindowDockHostBinding` - - external dock preview - - dock tab drag hotspot - - dock drop target resolution -3. `EditorWindowInputFeedbackBinding` - - hosted content capture - - shell capture - - generic interactive capture - - hosted content cursor - - dock cursor -4. `EditorWindowTitleBarBinding` - - detached title-bar tab strip policy - - detached tab title resolution - - detached window title resolution - -## 5. Execution Phases - -### Phase A. Introduce capability interfaces - -1. Extend `EditorWindowContentController.h` with the new capability types. -2. Replace optional default methods on the base class with `TryGet...Binding()` queries. - -### Phase B. Move workspace content onto explicit capabilities - -1. Make `EditorWorkspaceWindowContentController` implement: - - `EditorWindowDockHostBinding` - - `EditorWindowInputFeedbackBinding` - - `EditorWindowTitleBarBinding` -2. Keep utility-window content minimal; it should not implement capabilities it does not need. - -### Phase C. Shrink runtime surface - -1. Delete mirrored forwarding methods from `EditorWindowRuntimeController` for: - - dock preview / hotspot / drop target - - capture feedback / cursor feedback - - title-bar policy helpers -2. Replace them with capability accessors. - -### Phase D. Update Win32 callers - -Update callers to query the capability they actually need: - -1. `EditorWindow` -2. `EditorWindowMessageDispatcher` -3. `EditorWindowWorkspaceCoordinator` -4. `EditorWindowChromeController` - -Each caller must own its fallback behavior instead of assuming every content type supports everything. - -### Phase E. Validate - -1. build `XCUIEditorApp` -2. confirm no stale content/runtime forwarding methods remain -3. confirm utility-window content still compiles without fake overrides - -## 6. Concrete File Plan - -### Files to update - -1. `new_editor/app/Platform/Win32/Content/EditorWindowContentController.h` -2. `new_editor/app/Platform/Win32/Content/EditorWorkspaceWindowContentController.h` -3. `new_editor/app/Platform/Win32/Content/EditorWorkspaceWindowContentController.cpp` -4. `new_editor/app/Platform/Win32/Runtime/EditorWindowRuntimeController.h` -5. `new_editor/app/Platform/Win32/Runtime/EditorWindowRuntimeController.cpp` -6. `new_editor/app/Platform/Win32/Windowing/EditorWindow.cpp` -7. `new_editor/app/Platform/Win32/Windowing/EditorWindowMessageDispatcher.cpp` -8. `new_editor/app/Platform/Win32/Windowing/EditorWindowWorkspaceCoordinator.cpp` -9. `new_editor/app/Platform/Win32/Chrome/EditorWindowChromeController.cpp` - -## 7. Validation Criteria - -The refactor is only considered correct if all of the following are true: - -1. optional content behaviors are no longer declared as default no-op methods on the main base class -2. runtime no longer mirrors those same optional methods one-by-one -3. callers consume explicit capability types -4. the editor build still reaches at least the same link stage as before the refactor - -## 8. Non-Goals - -This plan does not include: - -1. removing `EditorWindowManager` -2. splitting `EditorWindowWorkspaceCoordinator` -3. merging steady-state and immediate frame finalization flows -4. changing utility-window feature behavior - -## 9. Completion Notes - -Completed on: 2026-04-23 - -Validation result: - -1. `EditorWindowContentController` no longer carries the old default no-op optional behavior surface. -2. `EditorWindowRuntimeController` no longer mirrors those optional behaviors as one-by-one forwarding methods. -3. Win32 callers now consume capability bindings instead of the old mirrored runtime helpers. -4. `XCUIEditorApp` builds successfully with: - `cmake --build D:\Xuanchi\Main\XCEngine\build\new_editor --config Debug --target XCUIEditorApp` diff --git a/docs/used/NewEditor_D3D12_LegacyD2DClosurePlan_完成归档_2026-04-22.md b/docs/used/NewEditor_D3D12_LegacyD2DClosurePlan_完成归档_2026-04-22.md deleted file mode 100644 index 0719e318..00000000 --- a/docs/used/NewEditor_D3D12_LegacyD2DClosurePlan_完成归档_2026-04-22.md +++ /dev/null @@ -1,274 +0,0 @@ -# NewEditor D3D12 Legacy D2D Closure Plan - -Date: 2026-04-22 - -## 1. Problem Statement - -`new_editor` main-window realtime presentation has already moved onto the native `D3D12` path, but the codebase still retains a legacy `D2D / D3D11On12` bridge in the host layer. - -That residual path is no longer the main realtime hot path, but it is still architecturally serious because it keeps a second rendering/composition implementation alive for the same UI draw model. - -Current residual seam: - -- `app/Rendering/Native/NativeRenderer.*` -- `app/Rendering/D3D12/D3D12WindowInteropContext.*` -- `app/Rendering/D3D12/D3D12WindowInteropHelpers.h` -- host/build references that still compile and link the old bridge - -## 2. Confirmed Current Facts - -### 2.1 Main window realtime presentation no longer depends on D2D - -Main realtime presentation flows through: - -- `D3D12WindowRenderer` -- `D3D12UiRenderer` -- `D3D12WindowRenderLoop` - -The active main path does not call `NativeRenderer::Render(...)`. - -### 2.2 Residual D2D bridge is concentrated, not scattered - -The old bridge is now effectively concentrated in: - -- screenshot/export flow -- dead hwnd-native D2D window rendering code that is no longer wired into the main frame loop - -### 2.3 NativeRenderer is no longer the main texture host - -`TexturePort` for the active editor path is already `D3D12UiTextureHost`. - -This means the remaining `NativeRenderer` responsibilities can be removed instead of preserved as an active subsystem. - -## 3. Root Architectural Issue - -The root issue is not “some D2D symbols still exist”. - -The root issue is that `new_editor` still retains two incompatible host-side ways to turn UI output into pixels: - -1. Active path: native `D3D12` rendering into the swapchain backbuffer. -2. Residual path: `UIDrawData -> D2D replay`, with optional `D3D11On12` resource interop. - -As long as both remain alive: - -- draw semantics can drift between live output and capture/export output -- future UI/rendering changes must be kept compatible with two host backends -- dead-window code remains available to be accidentally reused -- build/link dependencies preserve obsolete platform coupling - -So the correct goal is full single-path closure, not incremental patching. - -## 4. Target End State - -After this refactor: - -1. Main-window realtime rendering remains native `D3D12`. -2. Main-window screenshot capture also uses native `D3D12` backbuffer capture. -3. `new_editor` no longer contains any host-side `D3D11On12` or `D2D` bridge code for the main window. -4. Old hwnd `D2D` renderer code is deleted, not merely disconnected. -5. `XCUIEditorHost` no longer compiles obsolete interop units. -6. Host link dependencies only retain what is still genuinely required. - -## 5. Non-Goals - -- Do not roll the editor back to `D2D`. -- Do not reintroduce a fallback composition backend. -- Do not change the active `D3D12UiRenderer` architecture back into draw-data replay through another API. -- Do not remove `DirectWrite` from `D3D12UiTextSystem`; text shaping/raster generation remains valid there. - -## 6. Refactor Strategy - -### Phase A. Replace screenshot bridge with native D3D12 capture - -Introduce a native `D3D12` capture path that reads the final swapchain backbuffer and writes PNG through CPU-side WIC encoding. - -This capture path must: - -- capture the final composited editor frame -- avoid `UIDrawData` replay -- avoid `D3D11On12` -- avoid `D2D` -- operate on the same swapchain backbuffer the user actually sees - -Preferred integration point: - -- after UI rendering has finished for the active backbuffer -- after the frame has been submitted to the queue -- before `PresentFrame()` - -Reason: - -- the frame is already fully rendered -- the current backbuffer is still the one about to be presented -- capture remains aligned with the user-visible final image - -### Phase B. Collapse screenshot orchestration around the active D3D12 path - -Rework `AutoScreenshotController` so it only: - -- tracks pending capture requests -- resolves output/history paths -- finalizes success/error summaries - -It must no longer own the rendering backend choice. - -### Phase C. Delete obsolete NativeRenderer window-render path - -Delete the old hwnd `D2D` window renderer behavior: - -- `Initialize(HWND)` -- `Resize(...)` -- `Render(...)` -- hwnd render-target management -- D2D draw command replay entrypoint for window presentation - -If a remaining utility is still needed after this phase, it must live in a narrowly-scoped replacement component, not inside a zombie renderer class. - -### Phase D. Delete D3D11On12 interop bridge - -Once native `D3D12` capture is live: - -- delete `D3D12WindowInteropContext.*` -- delete `D3D12WindowInteropHelpers.h` -- delete all `NativeRenderer` interop code - -### Phase E. Build and link closure - -Update host build definitions so obsolete units and libraries are removed. - -Expected removals if no remaining consumer exists: - -- `app/Rendering/Native/NativeRenderer.cpp` -- `app/Rendering/D3D12/D3D12WindowInteropContext.cpp` -- `d2d1.lib` -- `d3d11.lib` - -Expected retained dependencies: - -- `d3d12.lib` -- `d3dcompiler.lib` -- `dwrite.lib` -- `dxgi.lib` -- `windowscodecs.lib` - -## 7. Concrete File-Level Work - -### 7.1 Add new native capture unit - -Add new host-side unit(s), likely under `app/Rendering/D3D12/`: - -- `D3D12WindowCapture.h` -- `D3D12WindowCapture.cpp` - -Responsibilities: - -- copy current swapchain backbuffer to readback -- map CPU pixels -- encode PNG via WIC -- return rich error text - -### 7.2 Extend window renderer / render loop - -Modify: - -- `app/Rendering/D3D12/D3D12WindowRenderer.h` -- `app/Rendering/D3D12/D3D12WindowRenderer.cpp` -- `app/Rendering/D3D12/D3D12WindowRenderLoop.h` -- `app/Rendering/D3D12/D3D12WindowRenderLoop.cpp` - -Needed changes: - -- expose a native capture operation on the active backbuffer -- extend present result with capture outcome -- allow present flow to execute capture before `PresentFrame()` - -### 7.3 Rework screenshot controller boundary - -Modify: - -- `app/Rendering/Native/AutoScreenshot.h` -- `app/Rendering/Native/AutoScreenshot.cpp` -- `app/Platform/Win32/EditorWindowRuntimeController.h` -- `app/Platform/Win32/EditorWindowRuntimeController.cpp` -- `app/Platform/Win32/EditorWindow.cpp` - -Needed changes: - -- stop passing `UIDrawData` into screenshot rendering -- stop depending on `NativeRenderer` -- move screenshot triggering into the D3D12 present path - -### 7.4 Delete obsolete renderer/interop code - -Delete if fully unreferenced after the above: - -- `app/Rendering/Native/NativeRenderer.h` -- `app/Rendering/Native/NativeRenderer.cpp` -- `app/Rendering/Native/NativeRendererHelpers.h` -- `app/Rendering/D3D12/D3D12WindowInteropContext.h` -- `app/Rendering/D3D12/D3D12WindowInteropContext.cpp` -- `app/Rendering/D3D12/D3D12WindowInteropHelpers.h` - -### 7.5 Build-system cleanup - -Modify: - -- `new_editor/CMakeLists.txt` - -Remove: - -- old source units -- dead link libraries - -## 8. Validation Requirements - -### 8.1 Build validation - -Must build: - -- `XCUIEditorHost` -- `XCUIEditorAppLib` -- `XCUIEditorApp` - -### 8.2 Runtime validation - -Must validate: - -1. editor launches normally -2. main window still presents correctly -3. manual screenshot still succeeds -4. startup auto-capture still succeeds if enabled -5. no screenshot path closes or stalls the main window unexpectedly - -### 8.3 Structural validation - -Searches after completion must show: - -- no `NativeRenderer` references in `new_editor/app` -- no `D3D12WindowInteropContext` references in `new_editor` -- no `D3D11On12` references in `new_editor` -- no `ID2D` / `D2D1` references in `new_editor/app` - -Exception: - -- `DirectWrite` references inside `D3D12UiTextSystem` remain allowed - -## 9. Execution Order - -1. Add native `D3D12` backbuffer capture path. -2. Route `AutoScreenshotController` through that new path. -3. Remove runtime-controller dependence on `NativeRenderer`. -4. Delete `NativeRenderer` and `D3D12WindowInteropContext`. -5. Clean CMake/link dependencies. -6. Build and smoke-test. -7. Run structural grep validation. - -## 10. Success Criteria - -This plan is complete only if all of the following are true: - -- screenshots come from the native `D3D12` backbuffer path -- no main-window host rendering code path can fall back to `D2D` -- no `D3D11On12` bridge remains in `new_editor` -- old dead window-render code is physically removed -- build and smoke validation pass diff --git a/docs/used/NewEditor_D3D12_UI_Pass_RefactorPlan_Archived_2026-04-21.md b/docs/used/NewEditor_D3D12_UI_Pass_RefactorPlan_Archived_2026-04-21.md deleted file mode 100644 index 9fdf90f3..00000000 --- a/docs/used/NewEditor_D3D12_UI_Pass_RefactorPlan_Archived_2026-04-21.md +++ /dev/null @@ -1,919 +0,0 @@ -# new_editor 主窗口 UI 原生 D3D12 UI Pass 重构计划 -日期: `2026-04-21` - -## 1. 文档定位 - -这份计划只解决一件事: - -把 `new_editor` 主窗口 UI 从当前这条 - -`D3D12 -> D3D11On12 -> D2D/DirectWrite -> D3D11 Flush -> Present` - -的每帧跨 API 合成链里彻底拿出来,改成同一条 `D3D12 command list / direct queue` 内完成的原生 `UI pass`。 - -这不是“补一个优化点”,而是一次热路径架构替换。目标不是在现有 `D2D` 链上继续打补丁,而是把主窗口 UI 的显示方案改对。 - -## 2. 范围与边界 - -本次重构纳入范围: - -- `new_editor` 主窗口和 detached editor window 的 UI 热路径。 -- 主窗口 swapchain backbuffer 的准备、UI 录制、提交、显式回到 `Present`。 -- `UIDrawData` 到 GPU draw call 的整条路径。 -- editor 自己的图片纹理、asset preview 缩略图、embedded PNG、scene/game viewport 纹理在 UI pass 中的采样。 -- 文本的测量、排版、字形缓存、GPU 绘制。 -- 自动截图/手动截图,要求最终走和屏幕一致的渲染路径。 -- 旧的 `D3D11On12/D2D` 热路径删除和收口。 - -本次重构明确不纳入范围: - -- scene 本身的渲染优化。 -- shell/workspace/dock 交互层的 CPU 端重复构建问题。 -- 非 `new_editor` 的游戏运行时渲染路径。 -- Vulkan/OpenGL 后端的 editor host 实现。 - -## 3. 代码事实与当前根问题 - -当前主窗口 UI 热路径由下列代码组成: - -- `new_editor/app/Rendering/Native/NativeRenderer.cpp` -- `new_editor/app/Rendering/D3D12/D3D12WindowInteropContext.cpp` -- `new_editor/app/Rendering/D3D12/D3D12WindowRenderLoop.cpp` -- `new_editor/app/Rendering/D3D12/D3D12WindowRenderer.cpp` -- `new_editor/app/Rendering/D3D12/D3D12WindowSwapChainPresenter.cpp` - -当前每帧主窗口 UI 的实际流程是: - -```text -BeginFrame --> scene viewport 等内容先在 D3D12 command list 上录制 --> PreparePresentSurface() --> SubmitFrame(false) --> D3D11On12 AcquireWrappedResources(backbuffer + source textures) --> D2D/DirectWrite 逐命令绘制 UIDrawData --> D3D11On12 ReleaseWrappedResources(...) --> ID3D11DeviceContext::Flush() --> SignalFrameCompletion() --> PresentFrame() -``` - -这里有几个必须正视的事实: - -1. `NativeRenderer` 现在不是单一职责类。 - 它同时承担了: - - D2D 主窗口绘制 - - DWrite 文本测量 - - WIC 图片解码 - - D3D11On12 interop - - 截图输出 - -2. `D3D12WindowInteropContext` 把 backbuffer 和 viewport source texture 每帧包成 `ID3D11Resource/ID2D1Bitmap`。 - 这意味着 UI 最终不是在原生 D3D12 backbuffer 上完成,而是在跨 API 包装后再画一遍。 - -3. `D3D12WindowSwapChainPresenter::PreparePresentSurface()` 只做了 `Present -> RenderTarget`。 - 当前 `RenderTarget -> Present` 的回归是依赖 `D3D11On12 ReleaseWrappedResources(...)` 隐式完成的。 - 一旦移除 interop,这个状态回归必须由 D3D12 显式补齐。 - -4. `UITextureHandle` 现在有两套旧语义: - - `DescriptorHandle` 用于 `NativeRenderer` 自己的 CPU/WIC/D2D 纹理。 - - `ShaderResourceView` 用于 viewport 纹理,再经 `D3D11On12/D2D` 包装成可绘制 bitmap。 - 这套语义是被旧显示方案绑出来的,不适合原生 D3D12 UI pass。 - -5. 当前图片解码路径输出的是 `32bppPBGRA`,而 engine RHI 公共格式里只有 `R8G8B8A8_UNorm`,没有 `BGRA` 公共格式。 - 这意味着 texture contract 也必须一起收口,不能直接把旧 `D2D` 像素契约搬过来。 - -## 4. 重构完成后的目标架构 - -重构后的目标架构是: - -```text -UIDrawData --> CPU batch/build --> same D3D12 command list --> same direct queue --> current swapchain backbuffer --> explicit RenderTarget -> Present --> Present -``` - -最终形态必须满足以下硬约束: - -1. 正常运行一帧时,主窗口 UI 热路径里不再创建或使用: - - `ID3D11Device` - - `ID3D11On12Device` - - `ID2D1DeviceContext` - - `ID2D1Bitmap` - - `AcquireWrappedResources` - - `ReleaseWrappedResources` - - `ID3D11DeviceContext::Flush` - -2. UI 必须和 viewport 内容共用同一个 frame command list。 - -3. 当前 backbuffer 的状态切换必须是显式对称的: - - `Present -> RenderTarget` - - UI 绘制 - - `RenderTarget -> Present` - -4. viewport 纹理不再经过 `D3D11On12 + D2D bitmap` 中转,而是直接作为 GPU SRV 采样。 - -5. 主窗口 path 不允许再保留 “D3D12 失败就回退 D2D hwnd render target” 这种长期热路径 fallback。 - 调试阶段可以临时保留兼容开关,但最终收口版本必须删掉。 - -## 5. 核心设计决策 - -### 5.1 不再保留一个“大而杂”的 NativeRenderer - -`NativeRenderer` 当前把五种职责揉在一起,这会直接导致重构不彻底。 - -最终必须拆成以下职责层: - -- `D3D12UiRenderer` - 负责把 `UIDrawData` 变成 D3D12 draw calls。 - -- `D3D12UiTextureHost` - 实现 `TexturePort`,负责 editor 自己的图片纹理加载、上传、SRV 分配与释放。 - -- `DirectWriteTextSystem` - 负责文本测量、排版、glyph cache、atlas 更新。 - 它可以继续依赖 `DirectWrite`,但不能再依赖 `D2D` 作为每帧显示后端。 - -- `D3D12UiCaptureRenderer` - 负责截图,把同一套 UI pass 录制到离屏纹理,再读回 PNG。 - -是否保留 `NativeRenderer` 这个类型名不是重点,重点是最终不能再有一个类同时握着: - -- D2D render target -- D3D11On12 interop -- 纹理加载 -- 文本测量 -- 主窗口 present 逻辑 - -### 5.2 UI pass 使用“CPU 批处理三角形 + 单一 alpha blend pipeline”路线 - -主窗口 UI 的 draw command 类型并不复杂: - -- FilledRect -- FilledRectLinearGradient -- RectOutline -- Line -- FilledTriangle -- FilledCircle -- CircleOutline -- Text -- Image -- PushClipRect / PopClipRect - -因此最稳妥的路线不是搞多套 D2D 等价抽象,而是: - -把全部 UI 命令转换为 GPU triangle batches。 - -最终统一成一套基本顶点格式: - -- `position` -- `uv` -- `color` - -最终统一成一条主图形 pipeline: - -- alpha blend 打开 -- 深度关闭 -- cull 关闭 -- scissor 打开 -- 正交投影 - -具体策略: - -- 纯色/描边/线段/圆/圆角矩形: - 由 CPU 直接三角化,采样一个 `1x1 white texture`。 - -- 线性渐变矩形: - 仍然三角化,但颜色写到顶点颜色里,shader 只做 `texture * vertexColor`。 - -- 图片: - 采样对应 SRV。 - -- 文本: - 采样 glyph atlas 对应 SRV。 - -这样做的好处是: - -- 主窗口 UI pass 可以收口成一条真正原生的 GPU 渲染路径。 -- 不需要在 GPU 侧再保留 D2D 风格图元接口。 -- draw order 与现有 `UIDrawData` 顺序一致,行为最稳定。 -- clip 直接映射成 scissor,简单明确。 - -### 5.3 文本继续使用 DirectWrite 做“CPU 端排版/字形分析”,但不再用 D2D 绘制 - -成熟编辑器不是每帧把 swapchain 包成 D2D bitmap 再 `DrawTextW`。 -更合理的做法是: - -- 用 `DirectWrite` 做字体测量和文本 shaping。 -- 把 glyph coverage 缓存进 atlas。 -- 真正显示时在 GPU 上绘制 glyph quads。 - -因此本次文本路线定为: - -- 保留 `DirectWrite` 作为文本系统。 -- 删除 `D2D::DrawTextW` 作为主窗口显示后端。 -- 新建 glyph atlas。 -- 文本 draw command 最终落成普通 textured quads。 - -### 5.4 viewport 纹理直接采样,不再每帧 CreateWrappedResource/CreateBitmapFromDxgiSurface - -scene/game viewport 纹理当前已经有 `UITextureHandle.nativeHandle = GPU descriptor handle`。 - -这意味着在原生 D3D12 UI pass 里,viewport 图片本质上已经具备直接采样条件。 - -因此: - -- `D3D12WindowInteropContext::PrepareSourceTextures()` 整条链直接删除。 -- UI pass 直接读取 `UITextureHandle.nativeHandle` 对应的 SRV。 -- `resourceHandle` 只保留给释放路径或调试路径使用,不再参与渲染期查表。 - -### 5.5 截图也必须切到同一套 GPU 路径 - -如果主窗口显示已经换成原生 D3D12 UI pass,但截图仍然走旧的 `D2D offscreen render target`,那验证链路会分叉,后续会一直出现: - -- 屏幕上看见的是一套 -- 自动截图导出的是另一套 - -所以截图也必须纳入重构,而不是留作旧系统残留。 - -## 6. 模块拆分计划 - -### 6.1 D3D12UiRenderer - -职责: - -- 接收 `UIDrawData` -- 维护 per-frame vertex/index/upload ring -- 维护 pipeline / root signature / sampler / white texture -- 维护 clip stack -> scissor -- 将 batches 录制到当前 `D3D12CommandList` - -必须覆盖的命令: - -- `FilledRect` -- `FilledRectLinearGradient` -- `RectOutline` -- `Line` -- `FilledTriangle` -- `FilledCircle` -- `CircleOutline` -- `Text` -- `Image` -- `PushClipRect` -- `PopClipRect` - -必须支持的渲染状态: - -- alpha blend -- cull none -- depth off -- scissor on -- swapchain RT 格式自适应 - -### 6.2 D3D12UiTextureHost - -职责: - -- 实现 `Ports::TexturePort` -- 加载文件/内存/RGBA 纹理 -- 创建 `R8G8B8A8_UNorm` GPU texture -- 在 shader-visible descriptor heap 中分配 SRV -- 为 `UITextureHandle` 填入: - - `nativeHandle = GPU descriptor handle` - - `width` - - `height` - - `kind` - - 必要时 `resourceHandle` -- 管理纹理释放和 descriptor 回收 - -这里必须明确: - -- 旧的 `NativeTextureResource::cachedBitmap` 模型要彻底删除。 -- 图片像素契约要改成适合 D3D12 的 `RGBA straight alpha` 或统一后的 GPU 格式。 -- 不能再保留 `D2D bitmap lazy-create`。 - -### 6.3 DirectWriteTextSystem - -职责: - -- 实现 `UIEditorTextMeasurer` -- 复用/管理 `IDWriteFactory` -- 缓存 `IDWriteTextFormat` -- 缓存文本 layout 结果 -- 把 glyph rasterize 到 atlas -- 为文本 draw 生成 glyph quads - -必须考虑的点: - -- 现有 editor 既有英文,也必须允许中文文本正确显示。 -- 需要支持 DPI scale。 -- 需要支持重复标签的缓存复用,避免每帧重新 shape。 -- `MeasureTextWidth()` 和真正渲染应共享同一套文本 layout cache,避免两套逻辑各算一遍。 - -### 6.4 D3D12UiCaptureRenderer - -职责: - -- 创建离屏 color texture -- 用同一套 `D3D12UiRenderer` 逻辑把 `UIDrawData` 画进去 -- GPU readback -- WIC 编码 PNG - -必须保证: - -- 截图结果与真实主窗口 UI pass 使用同一套 shader / batch / text atlas / texture contract。 - -## 7. 关键数据契约调整 - -### 7.1 UITextureHandle 语义收口 - -受影响文件: - -- `engine/include/XCEngine/UI/Types.h` -- `new_editor/app/Rendering/Native/NativeRenderer.cpp` -- `new_editor/app/Rendering/Native/NativeRendererHelpers.h` -- `new_editor/app/Rendering/D3D12/D3D12WindowRenderer.cpp` -- `new_editor/app/Rendering/D3D12/D3D12WindowInteropContext.cpp` - -需要做的事: - -1. 去掉“D2D bitmap handle”和“viewport SRV handle”这两套历史含义。 -2. 统一主窗口 UI 渲染期对 `nativeHandle` 的解释: - `nativeHandle` 就是 GPU 可采样 descriptor handle。 -3. `resourceHandle` 不再用于每帧渲染查找 `D3D11On12 wrapped resource`。 -4. `kind` 要么重命名为更准确的 GPU 语义,要么保留枚举名但重新定义 contract,并把所有调用点一起收口。 - -### 7.2 图片像素格式收口 - -当前 `DecodeTextureFrame()` 输出 `32bppPBGRA`,这是为 D2D 服务的。 - -原生 D3D12 UI pass 必须改成统一的 GPU 纹理契约: - -- 统一使用 `R8G8B8A8_UNorm` -- 明确使用 straight alpha 还是 premultiplied alpha -- blend state 和 shader 按这一个约定实现 - -这个点必须全链路一致: - -- 文件纹理加载 -- 内存纹理加载 -- embedded PNG -- asset preview 缩略图 -- glyph atlas -- white texture - -## 8. D3D12 渲染链改造计划 - -### 8.1 D3D12WindowSwapChainPresenter - -受影响文件: - -- `new_editor/app/Rendering/D3D12/D3D12WindowSwapChainPresenter.h` -- `new_editor/app/Rendering/D3D12/D3D12WindowSwapChainPresenter.cpp` - -必须改造: - -1. `PreparePresentSurface()` 要改成更明确的 backbuffer render begin 语义。 -2. 新增对称的 backbuffer render end / present finalize 语义。 -3. 不再依赖 interop 帮忙把 backbuffer 从 `RenderTarget` 回到 `Present`。 -4. `RenderSurface` 元数据要和真实 backbuffer 状态一致。 - -建议改成两段式接口: - -- `PrepareCurrentBackBufferForUiRender(renderContext)` -- `FinalizeCurrentBackBufferForPresent(renderContext)` - -### 8.2 D3D12WindowRenderer - -受影响文件: - -- `new_editor/app/Rendering/D3D12/D3D12WindowRenderer.h` -- `new_editor/app/Rendering/D3D12/D3D12WindowRenderer.cpp` - -必须改造: - -1. 保留 `BeginFrame()`。 -2. 不再让 `SubmitFrame(false)` 提前提交,再由别的 API 补 UI。 -3. 提供当前 frame 的: - - `RenderContext` - - `D3D12CommandList` - - 当前 backbuffer surface -4. `PresentFrame()` 前必须确保本 frame command list 已经包含 UI pass。 - -### 8.3 D3D12WindowRenderLoop - -受影响文件: - -- `new_editor/app/Rendering/D3D12/D3D12WindowRenderLoop.h` -- `new_editor/app/Rendering/D3D12/D3D12WindowRenderLoop.cpp` - -这是主窗口热路径的 orchestrator,必须重写逻辑。 - -当前职责里最需要删除的是: - -- interop attach/detach -- “present failure -> 回退 hwnd D2D renderer” - -最终流程应当是: - -```text -BeginFrame() --> EditorShellRuntime 更新并生成 UIDrawData --> RenderRequestedViewports(renderContext) --> PrepareCurrentBackBufferForUiRender(renderContext) --> D3D12UiRenderer::Render(drawData, renderContext, backbufferSurface) --> FinalizeCurrentBackBufferForPresent(renderContext) --> SubmitFrame() --> SignalFrameCompletion() --> PresentFrame() -``` - -也就是说,`D3D12WindowRenderLoop::Present()` 不能再只是“调用某个 UI renderer 再兜底”,而必须变成真正的原生 D3D12 submit/present 收口点。 - -## 9. UI pass 具体实现计划 - -### 9.1 Batch 构建 - -建议新增一层 CPU batch builder。 - -输入: - -- `UIDrawData` - -输出: - -- 顶点流 -- 索引流 -- batch 列表 - -每个 batch 至少包含: - -- `firstIndex` -- `indexCount` -- `scissorRect` -- `textureDescriptor` - -batch 切分条件: - -- clip/scissor 变化 -- texture 变化 -- pipeline 变化 - -禁止的做法: - -- 继续一条命令调用一次 D3D API -- 继续在渲染期动态创建图形对象 -- 继续依赖 D2D 帮忙做 clip/gradient/text - -### 9.2 几何生成 - -必须明确每种命令的几何策略: - -- `FilledRect` - 两三角形,圆角时用 CPU 扇形近似。 - -- `RectOutline` - 生成为四条边的条带几何,或拆成若干三角形。 - -- `Line` - 生成厚线四边形。 - -- `FilledTriangle` - 直接三角形。 - -- `FilledCircle` - CPU 分段近似生成扇形。 - -- `CircleOutline` - CPU 分段生成环带。 - -- `FilledRectLinearGradient` - 与普通矩形相同,但顶点颜色按方向插值。 - -- `Image` - 生成标准 textured quad。 - -- `Text` - 生成 glyph textured quads。 - -### 9.3 Clip 栈 - -现有 `PushClipRect/PopClipRect` 必须映射成真正的 scissor 逻辑。 - -具体要求: - -- CPU 维护 clip stack。 -- `intersectWithCurrentClip` 必须保留语义。 -- clip 变化时强制 flush 当前 batch。 -- D3D12 command list 在每个 batch 提交前设置 scissor。 - -### 9.4 Per-frame 上传资源 - -不能每帧创建/销毁 vertex/index buffer。 - -必须改成: - -- 以 `frame slot` 为单位分配 upload buffer ring -- 每个 frame slot 至少持有: - - vertex upload buffer - - index upload buffer - - 必要时 frame constants buffer -- 重用现有 `D3D12HostDevice::kFrameContextCount` - -这里要注意: - -- 现有 `D3D12HostDevice` 已经按 frame slot 做 fence 跟踪。 -- UI pass 的动态上传资源也必须跟这个 frame slot 生命周期绑定,不能另起一套未受 fence 保护的资源轮换。 - -### 9.5 Root signature / pipeline / sampler - -推荐把 UI pass 收敛为一套固定资源布局: - -- root constants 或 frame CBV: - 正交投影、屏幕尺寸等 frame 常量 -- descriptor table: - 当前 batch 使用的 SRV -- static sampler: - 至少一个 `linear clamp` - -需要明确: - -- 是否保留 `point clamp` 给像素风图标或 object-id 类资源 -- 是否通过 `white texture` 统一纯色图元路径 - -## 10. 文本系统重构计划 - -### 10.1 测量与渲染共享同一套文本缓存 - -受影响文件: - -- `new_editor/include/XCEditor/Foundation/UIEditorTextMeasurement.h` -- `new_editor/app/Rendering/Native/NativeRenderer.h` -- `new_editor/app/Rendering/Native/NativeRenderer.cpp` -- `new_editor/src/Shell/UIEditorShellInteraction.cpp` -- `new_editor/app/Features/Project/ProjectPanel.cpp` -- `new_editor/app/Platform/Win32/EditorWindowChromeController.cpp` - -必须改造: - -1. `MeasureTextWidth()` 不再挂在一个 D2D renderer 身上。 -2. 文本测量与渲染共享一套 `DirectWriteTextSystem`。 -3. 同一个 `(font size, string)` 的 layout 结果可以同时服务: - - 宽度测量 - - glyph quad 生成 - -### 10.2 Glyph atlas - -必须解决的问题: - -- atlas 尺寸策略 -- glyph key 设计 -- atlas 满了之后的增长或淘汰 -- atlas dirty 区域上传 -- 多 DPI / 多字号支持 - -推荐 key: - -- font family -- font size -- weight/style -- glyph index -- antialias mode - -推荐策略: - -- 初始 atlas 固定尺寸 -- dirty rect 子区域上传 -- 如 atlas 不够则扩容并重建 descriptor,不做每帧整图重建 - -### 10.3 文字行为一致性 - -必须验证: - -- 标题栏 FPS 文本 -- 面板标题 -- 菜单栏 -- tab 文本 -- ProjectPanel 路径 breadcrumb -- Inspector / Hierarchy / StatusBar -- 中文字符串显示 -- DPI 切换后文字大小与定位 - -## 11. 纹理与图片路径重构计划 - -### 11.1 现有图片调用点 - -必须覆盖以下来源: - -- `EmbeddedPngLoader` -- `BuiltInIcons` -- `SceneViewportToolOverlay` -- asset preview 缩略图 -- viewport surface texture handle - -对应文件: - -- `new_editor/app/Support/EmbeddedPngLoader.cpp` -- `new_editor/app/Rendering/Assets/BuiltInIcons.cpp` -- `new_editor/app/Features/Scene/SceneViewportToolOverlay.cpp` -- `new_editor/app/Rendering/Viewport/ViewportRenderTargets.cpp` -- `new_editor/app/Rendering/D3D12/D3D12WindowRenderer.cpp` - -### 11.2 descriptor 分配器 - -`D3D12ShaderResourceDescriptorAllocator` 现在只明确服务 viewport texture。 - -重构后它要么: - -- 直接升级为 editor 全局 UI SRV 分配器 - -要么: - -- viewport texture allocator 和 editor texture allocator 分离 - -但最终必须解决三个问题: - -1. editor 自己的图片纹理和 viewport 纹理都能在 UI pass 中按同一方式绑定。 -2. descriptor 不泄漏。 -3. resize / window shutdown / texture release 时 descriptor 生命周期正确。 - -## 12. 截图链路重构计划 - -受影响文件: - -- `new_editor/app/Rendering/Native/AutoScreenshot.cpp` -- `new_editor/app/Rendering/Native/AutoScreenshot.h` -- 以及新的 GPU capture 模块 - -重构后截图流程: - -```text -UIDrawData --> D3D12 offscreen color texture --> same UI pipeline render --> readback buffer --> WIC encode PNG -``` - -必须删除: - -- `CreateWicBitmapRenderTarget` -- `ID2D1RenderTarget::EndDraw` 截图路径 - -## 13. 旧代码删除与收口计划 - -### 13.1 必删模块 - -以下旧模块在最终收口时不应继续存在于主窗口热路径: - -- `new_editor/app/Rendering/D3D12/D3D12WindowInteropContext.h` -- `new_editor/app/Rendering/D3D12/D3D12WindowInteropContext.cpp` -- `new_editor/app/Rendering/D3D12/D3D12WindowInteropHelpers.h` - -以下旧模块要么删除,要么被拆空成兼容壳,最终不能继续承担主窗口 UI 显示: - -- `new_editor/app/Rendering/Native/NativeRenderer.h` -- `new_editor/app/Rendering/Native/NativeRenderer.cpp` -- `new_editor/app/Rendering/Native/NativeRendererHelpers.h` - -### 13.2 构建系统收口 - -受影响文件: - -- `new_editor/CMakeLists.txt` - -需要做的事: - -1. 移除 interop 相关源文件。 -2. 新增 `D3D12UiRenderer`、text system、texture host、capture renderer 源文件。 -3. 最终主窗口热路径不再依赖 `d3d11.lib`。 -4. 如果截图也已完全切走,则 `d2d1.lib` 也应移除。 - -`dwrite.lib` 和 `windowscodecs.lib` 可以继续保留,因为它们分别服务: - -- 文本测量/字形分析 -- PNG/WIC 编码解码 - -## 14. 受影响文件总表 - -### 14.1 必改文件 - -- `new_editor/CMakeLists.txt` -- `engine/include/XCEngine/UI/Types.h` -- `new_editor/app/Platform/Win32/EditorWindowRuntimeController.h` -- `new_editor/app/Platform/Win32/EditorWindowRuntimeController.cpp` -- `new_editor/app/Rendering/D3D12/D3D12WindowRenderLoop.h` -- `new_editor/app/Rendering/D3D12/D3D12WindowRenderLoop.cpp` -- `new_editor/app/Rendering/D3D12/D3D12WindowRenderer.h` -- `new_editor/app/Rendering/D3D12/D3D12WindowRenderer.cpp` -- `new_editor/app/Rendering/D3D12/D3D12WindowSwapChainPresenter.h` -- `new_editor/app/Rendering/D3D12/D3D12WindowSwapChainPresenter.cpp` -- `new_editor/app/Rendering/D3D12/D3D12ShaderResourceDescriptorAllocator.h` -- `new_editor/app/Rendering/D3D12/D3D12ShaderResourceDescriptorAllocator.cpp` -- `new_editor/app/Rendering/Assets/BuiltInIcons.cpp` -- `new_editor/app/Support/EmbeddedPngLoader.cpp` -- `new_editor/app/Rendering/Native/AutoScreenshot.cpp` -- `new_editor/app/Rendering/Native/AutoScreenshot.h` - -### 14.2 高概率新增文件 - -- `new_editor/app/Rendering/D3D12/D3D12UiRenderer.h` -- `new_editor/app/Rendering/D3D12/D3D12UiRenderer.cpp` -- `new_editor/app/Rendering/D3D12/D3D12UiBatchBuilder.h` -- `new_editor/app/Rendering/D3D12/D3D12UiBatchBuilder.cpp` -- `new_editor/app/Rendering/D3D12/D3D12UiTextureHost.h` -- `new_editor/app/Rendering/D3D12/D3D12UiTextureHost.cpp` -- `new_editor/app/Rendering/D3D12/DirectWriteTextSystem.h` -- `new_editor/app/Rendering/D3D12/DirectWriteTextSystem.cpp` -- `new_editor/app/Rendering/D3D12/D3D12UiCaptureRenderer.h` -- `new_editor/app/Rendering/D3D12/D3D12UiCaptureRenderer.cpp` -- `new_editor/resources/shaders/ui/...` - -### 14.3 必删文件 - -- `new_editor/app/Rendering/D3D12/D3D12WindowInteropContext.h` -- `new_editor/app/Rendering/D3D12/D3D12WindowInteropContext.cpp` -- `new_editor/app/Rendering/D3D12/D3D12WindowInteropHelpers.h` - -## 15. 分阶段实施顺序 - -### 阶段 A: 先把显示热路径切正 - -目标: - -- 主窗口不再走 `D3D11On12/D2D` -- viewport 纹理直接采样 -- backbuffer 状态显式闭环 - -执行顺序: - -1. 引入新的 `D3D12UiRenderer` 骨架。 -2. 改 `D3D12WindowSwapChainPresenter`,补齐 backbuffer begin/end。 -3. 改 `D3D12WindowRenderLoop`,把 UI pass 放进同一 command list。 -4. 先支持: - - FilledRect - - RectOutline - - Line - - Image - - PushClipRect / PopClipRect -5. 让空面板、普通图标 UI、viewport 纹理直采先跑通。 - -阶段 A 完成标准: - -- 主窗口帧内不再出现 interop acquire/release/flush。 -- 关闭 scene 只留空面板时,仍然走原生 D3D12 UI pass。 - -### 阶段 B: 补齐全部 UI 命令 - -目标: - -- 所有现有 `UIDrawCommandType` 行为可用 - -执行顺序: - -1. 补三角化命令: - - gradient rect - - triangle - - circle - - outline circle -2. 补圆角矩形/描边近似策略。 -3. 调整 batch 合并与 scissor flush。 - -### 阶段 C: 文本系统切换 - -目标: - -- 删除 `D2D::DrawTextW` 作为显示手段 - -执行顺序: - -1. 引入 `DirectWriteTextSystem` -2. 先完成文本测量迁移 -3. 再完成 glyph atlas 与文本 quad 输出 -4. 替换主窗口文本绘制 - -阶段 C 完成标准: - -- FPS 文本、菜单、tab、ProjectPanel、StatusBar 文本都走 atlas 路径。 - -### 阶段 D: 截图链路切换 - -目标: - -- 屏幕和截图使用同一 UI 渲染路径 - -执行顺序: - -1. 新建离屏 UI capture renderer -2. 接到 `AutoScreenshot` -3. 删除 D2D screenshot path - -### 阶段 E: 删除旧代码与链接依赖 - -目标: - -- 真正收口,而不是新旧并存 - -执行顺序: - -1. 删除 `D3D12WindowInteropContext` -2. 删除 interop helpers -3. 删除旧 `NativeRenderer` 中的 D2D/D3D11On12 代码 -4. 清理 `CMake` - -## 16. 风险清单 - -### 16.1 状态回归风险 - -如果只删 interop、不补 `RenderTarget -> Present`,就会直接出现: - -- present 失败 -- 黑屏 -- DXGI 报错 - -这是第一优先级风险。 - -### 16.2 文本外观差异风险 - -从 D2D 文本切到 atlas 后,最容易变化的是: - -- baseline -- 字距 -- 灰度抗锯齿观感 -- 中文字形边缘 - -这需要单独做 UI 对比验证。 - -### 16.3 像素格式风险 - -当前图片链路是 `PBGRA`,新链路如果切成 `RGBA` 但 blend/shader 没一起改,会出现: - -- 图标发黑 -- 半透明边缘发灰 -- 预乘错误 - -### 16.4 descriptor 生命周期风险 - -如果 SRV 分配与释放没有彻底梳理,会出现: - -- viewport 纹理释放后 descriptor 悬挂 -- asset preview 刷新泄漏 descriptor -- 多窗口关闭后 descriptor 未回收 - -### 16.5 截图路径分叉风险 - -如果截图仍走旧 D2D 路径,后续任何 UI 行为验证都会变得不可信。 - -## 17. 验证标准 - -### 17.1 代码级验证 - -最终热路径代码里不应再存在以下调用: - -- `D3D11On12CreateDevice` -- `AcquireWrappedResources` -- `ReleaseWrappedResources` -- `CreateBitmapFromDxgiSurface` -- `ID3D11DeviceContext::Flush` -- `ID2D1DeviceContext::SetTarget(backbuffer)` -- `DrawTextW` 用于主窗口 present - -### 17.2 运行时验证 - -必须覆盖以下场景: - -- 只留一个空白面板 -- 只留 scene 面板 -- scene 为空场景 -- 多面板正常布局 -- tab 拖出独立窗口 -- DPI 改变 -- 窗口 resize -- asset preview 缩略图出现与回收 -- scene/game viewport 纹理显示 -- 自动截图 -- 标题栏 FPS 持续刷新 - -### 17.3 图形调试验证 - -建议用 PIX 或 RenderDoc 验证: - -- 一帧内主窗口 UI 是否确实录制在同一条 direct command list 上 -- swapchain backbuffer 状态切换是否完整 -- 是否已不存在 D3D11 packet / interop 提交 - -## 18. 收口判定 - -只有同时满足以下条件,这次重构才算收口彻底: - -1. 主窗口 UI 每帧显示热路径完全不再依赖 `D3D11On12/D2D`。 -2. viewport 纹理改为 GPU 直接采样。 -3. backbuffer 显式状态闭环完成。 -4. 文本显示改为 atlas + GPU quad,而不是 `DrawTextW`。 -5. 截图与屏幕使用同一套 UI pass。 -6. 旧 interop 代码被删除,而不是隐藏保留。 - -如果只做到其中前两三项,那只是“部分切换”,不算这次重构真正结束。 diff --git a/docs/used/NewEditor_D3D12_UI_RootArchitectureRefactorPlan_过期归档_2026-04-22.md b/docs/used/NewEditor_D3D12_UI_RootArchitectureRefactorPlan_过期归档_2026-04-22.md deleted file mode 100644 index b7d63c76..00000000 --- a/docs/used/NewEditor_D3D12_UI_RootArchitectureRefactorPlan_过期归档_2026-04-22.md +++ /dev/null @@ -1,443 +0,0 @@ -# NewEditor D3D12 UI Root Architecture Refactor Plan - -Date: `2026-04-21` - -Supersedes: -- `docs/used/NewEditor_D3D12_UI_Pass_RefactorPlan_Archived_2026-04-21.md` - -## Goal - -Keep `new_editor` on the native `D3D12` main-window path and refactor the UI stack from the root so the renderer can actually reach native-backend performance ceilings. - -This plan is not a patch plan. It is a structural replacement plan. - -## Hard Constraints - -- Do not roll the main editor window back to `D2D` or `D3D11On12`. -- Do not keep long-term fallback-heavy mixed rendering for the main window. -- Do not treat scene rendering optimization as part of this task. -- Do not optimize around symptoms while preserving the current multi-rebuild architecture. -- Do not touch unrelated dirty worktree changes outside the scoped editor/UI files. - -## Confirmed Root Problems - -### 1. The editor frame graph rebuilds shell/workspace layout too many times per frame - -Confirmed call-chain facts: - -- `new_editor/app/Platform/Win32/EditorWindow.cpp` -- `new_editor/app/Platform/Win32/EditorWindowFrameOrchestrator.cpp` -- `new_editor/app/Composition/EditorShellRuntime.cpp` -- `new_editor/src/Shell/UIEditorShellInteraction.cpp` -- `new_editor/src/Workspace/UIEditorWorkspaceInteraction.cpp` -- `new_editor/src/Workspace/UIEditorWorkspaceCompose.cpp` -- `new_editor/src/Docking/UIEditorDockHostInteraction.cpp` -- `new_editor/src/Docking/UIEditorDockHost.cpp` - -Current behavior: - -- the shell request is rebuilt multiple times inside one frame -- the workspace compose path is rebuilt again for input ownership preview and again for final compose -- dock host interaction rebuilds dock layout twice inside one rebuild -- the renderer then consumes a fresh immediate `UIDrawData` again instead of a retained render product - -Result: - -- CPU cost scales with repeated full-tree work before rendering even begins -- a fast renderer still receives expensive, redundant input - -### 2. The current native D3D12 UI renderer is not GPU-native in architecture - -Confirmed files: - -- `new_editor/app/Rendering/D3D12/D3D12UiRenderer.cpp` -- `new_editor/app/Rendering/D3D12/D3D12UiTextureHost.cpp` -- `new_editor/app/Rendering/Native/NativeRenderer.cpp` - -Current behavior: - -- rounded rects, circles, outlines and fills are tessellated on the CPU every frame -- text is cached as `string -> texture`, not `glyph -> atlas` -- rasterized text misses go through `DirectWrite + WIC + upload` -- the renderer rebuilds vertices, indices and batches from high-level draw commands every frame - -Result: - -- `D3D12` is only the final API, not the actual rendering architecture -- the current implementation replaces a mature high-level renderer with a naive CPU front-end - -### 3. The main editor output is still immediate-command oriented instead of retained and dirty-driven - -Confirmed files: - -- `new_editor/app/Composition/EditorShellRuntime.cpp` -- `new_editor/app/Features/Project/ProjectPanel.cpp` -- `new_editor/app/Features/Hierarchy/HierarchyPanel.cpp` -- `new_editor/app/Features/Inspector/InspectorPanel.cpp` -- `new_editor/src/Shell/UIEditorShellCompose.cpp` - -Current behavior: - -- panels already keep interaction and layout state -- but final rendering still emits immediate `UIDrawList` commands every frame -- there is no long-lived UI render scene, panel packet cache, or subtree dirty invalidation on the final render path - -Result: - -- stable UI still pays repeated build and translation costs - -## End State - -The final main-window rendering chain must be: - -```text -single authoritative shell/workspace frame --> retained UI scene / panel render packets --> native D3D12 primitive pass + native D3D12 text pass --> same command list --> same direct queue --> explicit backbuffer state transitions --> present -``` - -The final main-window chain must not contain: - -- `D3D11On12` -- `D2D` draw submission -- per-frame CPU polygon generation for common UI primitives -- per-string text textures as the primary text path -- repeated full compose/layout rebuilds inside one frame - -## Non-Goals - -- scene renderer optimization -- viewport render graph redesign -- Vulkan/OpenGL editor-host parity during this task -- cosmetic UI redesign - -## Refactor Principles - -- Freeze the already validated present/swapchain/backbuffer path first. -- Refactor top-down ownership and bottom-up rendering architecture together. -- Make one frame produce one authoritative layout/compose snapshot. -- Move from immediate draw commands to retained render packets. -- Move from CPU geometry generation to GPU-friendly primitive evaluation. -- Move from string texture caching to glyph atlas rendering. -- Remove dead compatibility paths after replacement is validated. - -## Workstream A: Freeze The Validated D3D12 Present Path - -Purpose: - -- protect the working `D3D12` present path from further accidental churn while higher layers are rebuilt - -Scope: - -- `new_editor/app/Rendering/D3D12/D3D12WindowRenderLoop.*` -- `new_editor/app/Rendering/D3D12/D3D12WindowRenderer.*` -- `new_editor/app/Rendering/D3D12/D3D12WindowSwapChainPresenter.*` -- `new_editor/app/Rendering/D3D12/D3D12HostDevice.*` - -Required outcome: - -- preserve current explicit `Present -> RenderTarget -> Present` flow -- preserve current `Immediate + maxFrameLatency=1` policy unless later measurement proves a different policy is better -- do not mix this workstream with UI-pass redesign logic - -## Workstream B: Collapse Shell And Workspace To One Authoritative Frame Build - -Purpose: - -- remove repeated layout/compose/rebuild work inside a single frame - -Primary files: - -- `new_editor/app/Platform/Win32/EditorWindowFrameOrchestrator.cpp` -- `new_editor/app/Composition/EditorShellRuntime.cpp` -- `new_editor/src/Shell/UIEditorShellInteraction.cpp` -- `new_editor/src/Shell/UIEditorShellCompose.cpp` -- `new_editor/src/Workspace/UIEditorWorkspaceInteraction.cpp` -- `new_editor/src/Workspace/UIEditorWorkspaceCompose.cpp` -- `new_editor/src/Docking/UIEditorDockHostInteraction.cpp` -- `new_editor/src/Docking/UIEditorDockHost.cpp` -- `new_editor/src/Panels/UIEditorPanelContentHost.cpp` - -Required structural changes: - -- introduce one authoritative frame object for shell/workspace layout, hit-test inputs, mounted panel bounds and viewport slots -- stop rebuilding shell request multiple times unless a real state mutation invalidates the frame -- remove preview compose + final compose duplication where the same layout data is recomputed from scratch -- remove the double `BuildUIEditorDockHostLayout()` pattern inside dock-host interaction rebuilds -- make input ownership resolution consume the authoritative layout snapshot instead of forcing an extra full compose -- ensure panel mount bounds, viewport slot bounds and dock hit-test data all come from the same frame snapshot - -Deliverables: - -- one frame build entrypoint -- one dock-host layout build per frame in the steady state -- explicit invalidation points when menu state, dock mutation, panel visibility or viewport mounts actually change - -Acceptance criteria: - -- no steady-state frame should perform repeated full shell/workspace compose passes without a state mutation that requires recomputation - -## Workstream C: Introduce A Retained UI Scene Instead Of Per-Frame Immediate UIDrawData - -Purpose: - -- stop rebuilding the final render payload from scratch for stable UI - -Primary files: - -- `new_editor/app/Composition/EditorShellRuntime.cpp` -- `new_editor/app/Features/Project/ProjectPanel.*` -- `new_editor/app/Features/Hierarchy/HierarchyPanel.*` -- `new_editor/app/Features/Inspector/InspectorPanel.*` -- `new_editor/app/Features/Console/ConsolePanel.*` -- `new_editor/app/Features/ColorPicker/ColorPickerPanel.*` -- `new_editor/app/Features/Scene/SceneViewportFeature.*` -- `new_editor/app/Platform/Win32/EditorWindow.cpp` - -Required structural changes: - -- define a retained `UiScene` or equivalent render-product layer for the main editor window -- each major shell region and hosted panel produces a stable render packet instead of immediate draw commands -- packets carry primitive instances, image instances, text runs, clip rectangles and z/order information -- packets are rebuilt only when their owning state is dirty -- stable frame append becomes packet aggregation, not command regeneration - -Recommended ownership model: - -- shell chrome packet -- menu bar packet -- toolbar packet -- status bar packet -- dock host packet -- per-panel packet -- overlay packet - -Acceptance criteria: - -- a steady-state empty panel must not rebuild full command streams each frame -- unchanged panels must be reusable across frames - -## Workstream D: Replace The Current D3D12UiRenderer With A Real Native Primitive Pass - -Purpose: - -- stop treating `D3D12` as a thin API wrapper around CPU-generated meshes - -Primary files: - -- `new_editor/app/Rendering/D3D12/D3D12UiRenderer.*` -- potentially new files under `new_editor/app/Rendering/D3D12/` - -Current path to retire: - -- CPU polygon generation for rounded rects and circles -- CPU-generated outline rings for common primitives -- direct translation of every immediate command to transient vertices and indices - -Target design: - -- fixed quad or minimal base geometry -- instance buffers for: - - solid rect - - rounded rect - - border rect - - line - - circle - - image quad -- shader-side shape evaluation for fill, border and clipping -- batch grouping by pipeline, texture set, clip state and blend behavior - -Required renderer modules: - -- `D3D12UiPrimitivePass` -- `D3D12UiFrameResources` -- `D3D12UiClipState` or equivalent scissor/clip stream -- `D3D12UiImagePass` or merged image primitive path - -Acceptance criteria: - -- no CPU tessellation for common editor primitives in the steady state -- vertex/index growth no longer scales with roundness or circle segment counts - -## Workstream E: Replace String Texture Text Rendering With Glyph Atlas Text Rendering - -Purpose: - -- remove the largest text-side architectural bottleneck - -Primary files: - -- `new_editor/app/Rendering/D3D12/D3D12UiRenderer.*` -- `new_editor/app/Rendering/D3D12/D3D12UiTextureHost.*` -- `new_editor/app/Rendering/Native/NativeRenderer.*` -- new text-system files under `new_editor/app/Rendering/D3D12/` or a dedicated text namespace - -Current path to retire: - -- `text + font size + dpi -> whole string texture` -- `RasterizeTextMask()` -- transient WIC bitmap creation for display text misses - -Target design: - -- `DirectWrite` remains for shaping, metrics and glyph raster generation -- per-glyph atlas pages on the GPU -- glyph run cache keyed by text content, font, size, DPI and shaping result -- render text as glyph instances or glyph quads, not per-string textures -- share text metrics cache with render cache so measure and render do not do separate work - -Required renderer modules: - -- `D3D12UiTextSystem` -- `D3D12UiGlyphAtlas` -- `D3D12UiGlyphRunCache` -- `D3D12UiTextPass` - -Acceptance criteria: - -- no main-window display text path uses per-string textures as the primary render mechanism -- repeated labels reuse shaped runs and glyph atlas entries - -## Workstream F: Establish Dirty/Invalidation-Driven Packet Rebuild Rules - -Purpose: - -- make retained rendering actually effective - -Primary files: - -- shell/workspace interaction and compose files -- panel state files -- new retained-scene cache files - -Required invalidation classes: - -- layout dirty -- visual dirty -- text content dirty -- texture binding dirty -- clip hierarchy dirty -- panel visibility/mount dirty -- DPI dirty - -Rules: - -- layout dirty rebuilds bounds and dependent packets -- visual dirty only rebuilds affected render packet contents -- text dirty only rebuilds affected text runs -- viewport texture changes do not invalidate unrelated shell packets -- panel-local changes do not invalidate the full editor scene - -Acceptance criteria: - -- dirty propagation is explicit and local -- unchanged subtrees survive frame-to-frame without packet regeneration - -## Workstream G: Remove Obsolete Main-Window Compatibility Paths - -Purpose: - -- finish the refactor cleanly instead of leaving dead bridges in the hot path - -Primary files: - -- `new_editor/app/Rendering/Native/NativeRenderer.*` -- `new_editor/app/Rendering/D3D12/D3D12WindowInteropContext.*` -- `new_editor/app/Rendering/Native/AutoScreenshot.*` -- any remaining main-window D3D11On12 bridge code - -Required cleanup: - -- remove old main-window `D3D11On12/D2D` composition entrypoints -- keep screenshot/offscreen interop only if it still serves a non-main-window path -- remove unused string-texture caches once glyph atlas path is validated -- remove dead UI texture semantics only needed by the old D2D bridge - -Acceptance criteria: - -- no main-window render code path references D3D11On12 wrapped resources -- screenshot/offscreen helpers are clearly isolated from the main editor hot path - -## Workstream H: Instrumentation And Verification - -Purpose: - -- prevent another round of “wrong bottleneck, wrong fix” - -Required measurements before and during each phase: - -- shell/workspace frame build count per presented frame -- dock-host layout rebuild count per presented frame -- panel packet rebuild count per frame -- primitive instance counts by type -- text run cache hit/miss counts -- glyph atlas upload count and uploaded pixel count -- batch count -- total draw call count -- CPU time for: - - shell/workspace frame build - - retained scene rebuild - - primitive pass build - - text shaping/raster/atlas upload - - final render submission - -Instrumentation targets: - -- `EditorWindowRuntimeController` -- `EditorWindowFrameOrchestrator` -- shell/workspace interaction and compose files -- `D3D12UiRenderer` -- future primitive/text pass modules - -Acceptance criteria: - -- each major architectural replacement can be validated against measured cost shifts - -## Recommended Execution Order - -1. Freeze present path and add instrumentation. -2. Collapse shell/workspace to one authoritative frame build. -3. Introduce retained UI scene and per-panel render packets. -4. Replace primitive rendering with GPU-native instance-based primitive pass. -5. Replace text rendering with glyph atlas rendering. -6. Add explicit dirty propagation and cache invalidation rules. -7. Remove obsolete main-window D2D/D3D11On12 paths. -8. Tighten screenshot/offscreen ownership boundaries. -9. Final performance verification and cleanup pass. - -## Commit Strategy - -Recommended submission boundaries: - -1. docs and instrumentation only -2. shell/workspace authoritative frame collapse -3. retained UI scene and panel packet introduction -4. primitive pass replacement -5. text system replacement -6. cleanup and dead-path removal -7. final verification and documentation - -## Risks To Manage Explicitly - -- input hit-testing drift if layout ownership changes without updating all consumers -- stale packet reuse if dirty propagation is incomplete -- glyph atlas eviction bugs causing missing text -- clip/scissor mismatches after primitive-pass conversion -- hidden dependencies on old `UIDrawData` immediate ordering -- viewport texture lifetime issues when packet caching crosses frame boundaries - -## Final Acceptance Standard - -This refactor is only considered complete when all of the following are true: - -- the main editor window stays on the native `D3D12` path only -- one steady-state frame does not repeatedly rebuild shell/workspace layout -- unchanged panels reuse retained render products -- common UI primitives are not CPU-tessellated per frame -- main-window display text is rendered from a glyph atlas path -- old D3D11On12/D2D main-window composition code is removed or fully isolated from the hot path -- performance analysis shows the dominant steady-state cost moved away from repeated CPU rebuild work and naive UI translation diff --git a/docs/used/NewEditor_FilterableTreeHostUnificationPlan_2026-04-22.md b/docs/used/NewEditor_FilterableTreeHostUnificationPlan_2026-04-22.md deleted file mode 100644 index 5c887157..00000000 --- a/docs/used/NewEditor_FilterableTreeHostUnificationPlan_2026-04-22.md +++ /dev/null @@ -1,279 +0,0 @@ -# NewEditor Filterable Tree Host Unification Plan - -Date: 2026-04-22 -Status: Completed - -## Scope - -This pass only covers shared tree filtering for: - -1. `new_editor/app/Features/Hierarchy/HierarchyPanel` -2. `new_editor/app/Features/Project/ProjectPanel` left folder tree - -It does not widen into: - -1. Project right asset browser filtering -2. global editor search -3. `UIEditorTreeView` visual redesign -4. docking / workspace / detached window behavior changes - -## Confirmed Problem - -The current architecture has three clear layers: - -1. canonical tree data -2. tree host logic -3. pure tree widget - -But only layer 3 is currently reusable. - -Current facts: - -1. `HierarchyModel` owns canonical scene tree data and emits flat `UIEditorTreeViewItem` rows from nested `HierarchyNode`s. -2. `ProjectBrowserModel::RefreshFolderTree()` owns canonical folder tree data and emits flat `UIEditorTreeViewItem` rows for the left folder tree. -3. `UIEditorTreeView` and `UIEditorTreeViewInteraction` are still pure tree control code: - - tree layout - - scroll - - hit testing - - selection / expansion / keyboard / rename support - - no search query state - - no text field state - - no filtering semantics - -So if filtering is added directly inside `HierarchyPanel` and again inside `ProjectPanel`, the editor will duplicate: - -1. search field state -2. search field layout / drawing -3. search input interaction and focus arbitration -4. filter query normalization -5. ancestor-retention filtering logic -6. filtered-tree expansion override logic -7. query-change scroll reset / clamp behavior - -That would create the exact kind of host-level duplication we have been trying to eliminate. - -## Architectural Decision - -Do not stuff search UI directly into `UIEditorTreeView`. - -`UIEditorTreeView` must stay a pure tree widget. - -Instead, introduce a reusable host layer above it: - -1. shared filter host UI and interaction -2. shared tree filtering algorithm -3. panel-owned canonical source items - -The ownership split after this pass should be: - -1. `UIEditorTreeView` - - remains a pure rendering / interaction widget for already-supplied tree items -2. new shared filterable-tree host layer - - owns search box UI - - owns search query state - - owns shared query normalization and filtered-item derivation - - owns filtered-view expansion override and scroll reset behavior -3. `HierarchyPanel` / `ProjectPanel` - - continue to own canonical source trees and panel-specific commands - - consume the shared host instead of reimplementing search locally - -## Target End State - -After this pass: - -1. both `HierarchyPanel` and `ProjectPanel` left tree expose the same first-pass filter behavior -2. `UIEditorTreeView` remains free of search-field UI and search-specific business state -3. canonical tree data in `HierarchyModel` and `ProjectBrowserModel` remains unfiltered and authoritative -4. the filtered view is derived at host level from canonical items, not written back into model ownership -5. existing selection / rename / drag / context behavior continues to operate on stable original item ids - -## Shared First-Pass Filter Semantics - -The first implementation for both trees must be identical: - -1. case-insensitive substring match against `UIEditorTreeViewItem.label` -2. if a descendant matches, retain the full ancestor path -3. hide branches that contain neither a direct match nor a matching descendant -4. while filtering is active, matched ancestor paths are forced visible without mutating the user’s persistent expansion state -5. clearing the query restores the normal tree view and the panel’s real expansion state - -This pass intentionally does not add: - -1. path-token matching -2. fuzzy ranking -3. tag / type filtering -4. result counters - -## Root Cause - -The root problem is not “tree filtering does not exist”. - -The root problem is that the editor currently has: - -1. a reusable pure tree widget -2. no reusable host above it for tree-specific search - -That missing middle layer is why the same feature would otherwise be copied into both `HierarchyPanel` and `ProjectPanel`. - -This pass closes that missing host layer instead of adding two panel-local patches. - -## Execution Plan - -### Phase A. Freeze the Behavioral Contract - -Before editing code, lock these rules: - -1. both trees share the same filter behavior in v1 -2. canonical tree models stay unchanged as source of truth -3. `UIEditorTreeView` does not gain embedded search-bar UI -4. filter activation must not rewrite persistent expansion state - -### Phase B. Introduce Shared Filtered-Tree Derivation - -Add a reusable filtered-tree helper that works from canonical flat pre-order `UIEditorTreeViewItem` input. - -This helper must: - -1. accept canonical source items -2. accept a normalized query -3. retain matching rows and their ancestor chain -4. preserve original `itemId` -5. preserve correct visible depth -6. return enough metadata to drive temporary expansion override while filtering - -Important constraint: - -`ProjectBrowserModel` and `HierarchyModel` must not be rewritten to permanently store filtered state. - -They keep producing canonical source items. - -### Phase C. Introduce a Shared Filterable Tree Host Layer - -Add a reusable host abstraction above `UIEditorTreeView`, conceptually: - -1. search text-field state -2. search text-field layout / draw -3. text input interaction -4. filtered-item cache / frame result -5. temporary expansion override when query is active -6. tree viewport layout split: - - top search box - - tree body below it -7. query-change scroll reset and offset clamp - -This host is the shared reusable layer both panels consume. - -It may live alongside `UIEditorTreeView` in the shared collections layer, but it must remain composition over `UIEditorTreeView`, not mutation of `UIEditorTreeView` itself. - -### Phase D. Integrate `HierarchyPanel` - -Refit `HierarchyPanel` to: - -1. keep `HierarchyModel` as canonical source -2. continue building canonical tree items from `HierarchyModel` -3. feed those canonical items into the shared filter host -4. pass the host’s filtered tree output into existing tree interaction / draw paths -5. keep rename / selection / drag addressing canonical ids - -While filtering is active: - -1. matching nodes and retained ancestors remain interactive -2. selection and rename continue to target original ids -3. drag / reparent logic still resolves against original ids visible in the filtered view - -### Phase E. Integrate `ProjectPanel` Left Tree - -Refit `ProjectPanel` left tree to: - -1. keep `ProjectBrowserModel::m_treeItems` as canonical source tree -2. keep `m_folderEntries` and folder navigation ownership unchanged -3. feed canonical left-tree items into the shared filter host -4. run tree interaction against the host output instead of duplicating project-only filtering logic - -The filter affects only the left folder tree. - -It must not change: - -1. current folder ownership -2. right asset browser model ownership -3. breadcrumb ownership - -### Phase F. Reconcile Selection, Expansion, and Query Lifecycle - -Make lifecycle rules explicit: - -1. when query changes, clamp or reset tree scroll to avoid stale offsets -2. when query becomes empty, restore normal expansion semantics -3. if the selected item is still visible in filtered results, keep selection -4. if the selected item is hidden by filter, do not rewrite canonical selection just because the view is filtered -5. rename session startup must only begin for currently visible rows - -### Phase G. Validate - -After implementation: - -1. build `XCUIEditorApp` -2. manual smoke-test `HierarchyPanel` -3. manual smoke-test `ProjectPanel` left tree -4. confirm no regression in detached windows / workspace / docking paths from this pass - -## Verification Matrix - -## Progress Update (2026-04-23) - -Completed in code: - -1. shared `UIEditorFilterableTreeHost` host layer is in place -2. `HierarchyPanel` now routes search UI, filtered-item derivation, rename bounds, drag/drop, selection, and draw through the shared host output -3. `ProjectPanel` left folder tree now routes host layout, tree interaction, rename bounds, drop hit-testing, drag preview, and draw through the same shared host output -4. canonical source ownership remains in `HierarchyModel` and `ProjectBrowserModel`; filtering stays transient at host layer - -Current validation state: - -1. `XCUIEditorApp` build was re-run on 2026-04-23 -2. the build reached compilation of `HierarchyPanel.cpp` and `ProjectPanel.cpp` after this pass -3. full app build is currently blocked by unrelated existing errors in the Win32 window shell path, currently surfacing in `new_editor/app/Platform/Win32/EditorWindowChromeController.cpp` and `new_editor/app/Platform/Win32/EditorWindow.cpp` -4. because the app does not currently finish building, manual UI smoke validation for this plan is still pending - -### Hierarchy - -1. search box appears at top of hierarchy panel -2. typing filters rows by object name -3. matched descendants keep ancestor chain visible -4. clearing the query restores full tree -5. selection still works -6. rename still works -7. drag / reparent still works on visible nodes -8. tree scroll still works before, during, and after filtering - -### Project Left Tree - -1. search box appears at top of left folder tree -2. typing filters folders by label -3. matched descendants keep ancestor chain visible -4. clearing the query restores full tree -5. folder navigation still works -6. right asset browser updates normally when a visible folder is selected -7. left-tree drag / reparent still works on visible nodes -8. tree scroll still works before, during, and after filtering - -## Red Lines - -Do not: - -1. embed search-bar UI directly into `UIEditorTreeView` -2. mutate canonical `HierarchyModel` / `ProjectBrowserModel` into filtered source-of-truth state -3. create separate duplicated search implementations in `HierarchyPanel` and `ProjectPanel` -4. rewrite unrelated project browser right-pane logic in this pass -5. break current tree rename / drag / selection behavior just to simplify the filter implementation - -## Completion Criteria - -This pass is complete only when: - -1. both trees use one shared host-level filter architecture -2. `UIEditorTreeView` stays a pure tree widget -3. both panels share the same first-pass filter semantics -4. canonical tree models remain unpolluted by transient filter state -5. the editor builds -6. hierarchy and project-left-tree smoke validation both pass diff --git a/docs/used/NewEditor_FrameResizeSynchronousPresentationRootFixPlan_完成归档_2026-04-22.md b/docs/used/NewEditor_FrameResizeSynchronousPresentationRootFixPlan_完成归档_2026-04-22.md deleted file mode 100644 index 7dd9f063..00000000 --- a/docs/used/NewEditor_FrameResizeSynchronousPresentationRootFixPlan_完成归档_2026-04-22.md +++ /dev/null @@ -1,118 +0,0 @@ -# NewEditor Frame Resize Synchronous Presentation Root Fix Plan - -Date: 2026-04-22 -Status: Completed Archive - -## 1. Objective - -Restore the original `new_editor` resize-time anti-deformation contract without adding fallback branches, duplicated resize paths, or new ownership confusion. - -The fix must re-establish one clean rule: - -1. when a host-side transition needs a new window size before the native window visibly adopts it, the window content must be rendered synchronously for that target size -2. when Win32 enters paint / size / DPI lifecycle messages, the host must be able to drive a synchronous frame before returning control -3. normal steady-state rendering must remain on the centralized frame driver path - -## 2. Confirmed Root Cause - -The regression was introduced by commit `b44f5ca9` (`refactor(new_editor): centralize win32 frame execution`). - -That refactor removed the synchronous render step from the critical resize path: - -1. `EditorWindowChromeController::HandleResizePointerMove(...)` -2. `EditorWindowChromeController::ApplyPredictedWindowRectTransition(...)` -3. `EditorWindowMessageDispatcher` handling of `WM_SIZE`, `WM_DPICHANGED`, `WM_EXITSIZEMOVE`, and `WM_PAINT` - -and replaced it with deferred `RequestFrame(...)` bookkeeping. - -That bookkeeping does not preserve the old contract because: - -1. `RequestFrame(...)` only records a bitmask -2. rendering now happens later from the app main loop -3. the window can already be resized before the new frame is presented -4. the old presented surface is therefore stretched by the platform / swap chain path - -This means the actual regression is not the custom title bar itself. The regression is the loss of synchronous presentation ownership in the Win32 host lifecycle. - -## 3. Red Lines - -The implementation must not: - -1. add a second resize algorithm beside the current borderless resize path -2. add ad hoc `if (interactiveResize)` branches throughout unrelated rendering code -3. keep a dead frame-request state machine that no longer owns rendering policy -4. duplicate `RenderFrame(...)` orchestration in multiple modules -5. touch workspace transfer / docking semantics unrelated to resize-time presentation - -## 4. Target End State - -After the fix: - -1. `EditorWindowFrameDriver` remains the single general-purpose frame execution entry point -2. synchronous Win32 lifecycle rendering uses that same driver wherever possible -3. `WM_PAINT` remains a special case only because it requires `BeginPaint` / `EndPaint` -4. borderless predicted transitions render the target-size frame synchronously before `SetWindowPos(...)` -5. dead `RequestFrame(...)` / `EditorWindowFrameRequestReason` plumbing is removed if it no longer owns any real policy - -## 5. Implementation Plan - -### Phase A. Remove the dead deferred frame-request layer - -Completed on 2026-04-22. - -1. removed `EditorWindowFrameRequestReason` -2. removed `EditorWindow::RequestFrame(...)` -3. removed `EditorWindow::ConsumePendingFrameRequestReasons()` -4. removed all call sites that only existed to defer rendering into the main loop - -### Phase B. Rebuild one synchronous frame-execution helper path - -Completed on 2026-04-22. - -1. kept `EditorWindowFrameDriver::DriveFrame(...)` as the shared frame executor -2. added a dispatcher-local helper that drives a frame immediately and forwards transfer requests to `EditorWindowWorkspaceCoordinator` -3. restored that helper to `WM_SIZE`, `WM_DPICHANGED`, and `WM_EXITSIZEMOVE` - -### Phase C. Restore paint-time synchronous rendering - -Completed on 2026-04-22. - -1. restored `WM_PAINT` to render synchronously inside the paint handling path -2. kept the paint special case local to `BeginPaint` / `EndPaint` - -### Phase D. Restore predicted-transition pre-presentation - -Completed on 2026-04-22. - -1. `HandleResizePointerMove(...)` once again renders the target-size frame synchronously before `SetWindowPos(...)` -2. `ApplyPredictedWindowRectTransition(...)` does the same for maximize / restore style transitions -3. this was restored on the original transition path without adding a second resize subsystem - -### Phase E. Verify no unrelated ownership was widened - -Completed on 2026-04-22. - -1. workspace transfer / docking semantics were left untouched -2. `EditorWindowHostRuntime::RenderAllWindows(...)` still owns steady-state frame pumping -3. only Win32 host lifecycle and predicted transition code were changed - -## 6. Validation Requirements - -Validation completed on 2026-04-22: - -1. `XCUIEditorApp` Debug build passed after the fix -2. user verified the resize deformation regression was gone in actual runtime - -Pending after this archive: - -1. follow-up investigation for remaining resize jitter -2. any later refinement should stay on the same host lifecycle boundary and must not reintroduce a deferred fake scheduling layer - -## 7. Completion Criteria - -This work was completed with the following outcomes: - -1. the regression source introduced by `b44f5ca9` was structurally removed -2. one clear synchronous presentation path was restored for resize-time lifecycle transitions -3. the dead deferred request layer was removed -4. the resulting code path remained a mainline host-lifecycle design instead of a post hoc fallback patch diff --git a/docs/used/NewEditor_Inspector面板与组件系统收口计划_阶段归档_2026-04-19.md b/docs/used/NewEditor_Inspector面板与组件系统收口计划_阶段归档_2026-04-19.md deleted file mode 100644 index 0729479e..00000000 --- a/docs/used/NewEditor_Inspector面板与组件系统收口计划_阶段归档_2026-04-19.md +++ /dev/null @@ -1,368 +0,0 @@ -# NewEditor Inspector 面板与组件系统收口计划 -日期: `2026-04-18` -状态: `待执行` - -## 1. 当前分析结论 - -### 1.1 当前 `new_editor` 的 Inspector 还只是展示壳 - -`new_editor/app/Features/Inspector/InspectorPanel.*` 现在只做了三件事: - -- 根据 `EditorSession` 和 `EditorSceneRuntime` 判断当前检视来源 -- 把选中对象或资源转成几组字符串行 -- 直接把这些字符串画成静态 section - -这意味着它现在还不是一个真正的 Inspector,只是一个“当前选择摘要面板”。 - -### 1.2 缺的不是底层控件,而是 Inspector 业务层 - -`new_editor` 的基础 UI 模块其实已经有不少可复用能力: - -- `UIEditorPropertyGrid` -- `UIEditorObjectField` -- `UIEditorAssetField` -- 各类 number / bool / enum / color / vector 字段及交互状态 - -但这些能力还没有被组织成一套正式的 Inspector 业务模型。当前 `InspectorPanel` 直接退化成了 `Section + SectionRow(string,string)`,等于绕开了新 UI 模块真正可复用的部分。 - -### 1.3 Inspector 没有自己的命令路由和编辑 owner - -当前 host command 桥只绑定了: - -- `HierarchyPanel` -- `ProjectPanel` - -`Inspector` 对应的 `EditorActionRoute::Inspector` 还是显式 disabled 状态。也就是说,即便后面把 UI 画出来了,Inspector 依然没有正式的命令所有权,无法干净地接入: - -- 删除/复制/重命名类编辑命令 -- 组件添加/移除 -- 未来的 reset / paste component values / multi-edit - -### 1.4 Scene runtime 目前只覆盖“对象层”编辑,不覆盖“组件层”编辑 - -`EditorSceneRuntime` 现在主要提供: - -- selection -- rename / delete / duplicate / reparent / move to root -- scene open / viewport camera - -但它没有 Inspector 真正需要的组件编辑入口,例如: - -- 枚举选中对象上的组件 -- 添加组件 / 移除组件 -- 对组件字段做受控写回 -- 为未来 undo/redo 预留正式事务边界 - -如果不先补这一层,后面组件编辑大概率会变成 panel 直接摸 engine 对象,继续把业务逻辑散回 UI 层。 - -### 1.5 旧版 editor 可以参考能力,不应该照搬结构 - -旧版 `editor` 里 Inspector 的能力是完整的,至少已经覆盖: - -- `GameObject` 检视 -- `ComponentEditorRegistry` -- 各组件编辑器 -- `Add Component` -- `Material` 资源 Inspector -- `ScriptComponent` 字段编辑 - -但旧版 `editor/src/panels/InspectorPanel.cpp` 本身也是一个明显的“大文件总控”结构,组件检视、材质检视、popup、上下文行为都堆在一起。新编辑器应当复用能力拆分思路,不应把这个单文件架构直接搬过来。 - -### 1.6 当前最大的问题不是“少几个组件”,而是缺了一整层 Inspector 应用架构 - -真正的根因是: - -- 没有 Inspector subject/runtime/presentation model -- 没有 Inspector component editor abstraction -- 没有 Inspector asset editor abstraction -- 没有 Inspector command route - -所以现在最需要收的是“架构口”,不是先硬补几个组件 UI。 - -## 2. 根因判断 - -当前 Inspector 的最大根因问题可以概括成一句话: - -**Inspector 还没有从“静态信息展示”升级为“正式编辑域”。** - -这会直接导致后面所有功能都只能以补丁形式接入: - -- 组件越加越多,`InspectorPanel.cpp` 越来越肥 -- 每个组件都自己决定怎么写回 runtime -- 资源 Inspector 和对象 Inspector 会走两套平行逻辑 -- 命令、选择、字段交互、弹窗、右键菜单会继续分散 - -因此本轮必须先把 Inspector 的 owner、模型和扩展点建立起来,再往里补组件能力。 - -## 3. 重构原则 - -### 3.1 单一事实源 - -- Scene 对象与组件的真实状态只能来自 scene runtime / scene bridge -- Project 资源状态只能来自 project runtime -- Inspector 只读这些事实源,不自建镜像真相 - -### 3.2 Panel 只做宿主,不做业务中心 - -`InspectorPanel` 只负责: - -- 面板挂载状态 -- 输入分发 -- 绘制宿主 -- 调用 Inspector 业务层产出的 presentation - -不再继续把 subject 判断、组件枚举、字段写回、资源保存都堆进 panel 本体。 - -### 3.3 复用新 UI 模块,不回退到旧 ImGui 方案 - -- 旧 editor 只参考能力清单和交互契约 -- 新 editor 继续使用 XCUI 的 field / grid / popup / selection 模型 -- 不把 ImGui 风格的即时状态机直接拷回 `new_editor` - -### 3.4 不再制造第二个超大总控文件 - -目标不是把旧版 `InspectorPanel.cpp` 从 1 个大文件换成新的 1 个大文件,而是明确拆层: - -- Inspector subject/runtime -- Inspector presentation model -- scene component editors -- asset inspectors -- panel host - -## 4. 目标结构 - -建议在 `new_editor/app/Features/Inspector/` 下形成正式结构,至少拆出以下职责: - -- `InspectorPanel.*` - - 面板宿主、输入入口、绘制入口 -- `InspectorSubject.*` - - 解析当前检视对象,统一 `None / SceneObject / ProjectAsset / UnsupportedAsset` -- `InspectorPresentationModel.*` - - Inspector 的中间表示,禁止继续使用纯字符串行模型 -- `Components/` - - `IInspectorComponentEditor.*` - - `InspectorComponentEditorRegistry.*` - - 各组件 editor -- `Assets/` - - `IInspectorAssetEditor.*` - - `MaterialAssetInspector.*` - - 其他资源 inspector -- `Runtime/` 或 `Bindings/` - - scene/project runtime 适配层 - - 组件增删改命令入口 - -如果实现过程中确认 `Runtime/` 不需要单独成层,也至少要保留 `Subject / Presentation / Components / Assets` 这四个边界,不能再回到单文件混写。 - -## 5. 执行计划 - -### Phase A: 建立 Inspector Subject 与 Presentation 主线 - -目标: -先把 Inspector 从“字符串摘要面板”重构成“正式业务宿主”。 - -任务: - -- 提取当前的 selection source 解析逻辑,形成正式的 `InspectorSubject` -- 定义 Inspector presentation model,至少能表达: - - title / subtitle - - section - - 只读信息行 - - 可编辑字段 - - object field / asset field - - action row / hint row -- `InspectorPanel` 改为只消费 presentation model,不再手写静态 section 行 -- 保留当前 project / scene 选择切换规则,但迁移到新的 subject 构建链路 - -完成标准: - -- `InspectorPanel` 不再直接拼接业务字符串 section -- 新结构可以同时承载 scene object 和 asset inspector - -### Phase B: 建立 Inspector 的 Scene 组件编辑抽象 - -目标: -给 scene object inspector 建正式扩展点,而不是把每个组件写死在 panel 里。 - -任务: - -- 新增 `IInspectorComponentEditor` -- 新增 `InspectorComponentEditorRegistry` -- 建立统一组件编辑上下文,至少包含: - - 当前 `GameObject` - - 当前 `Component` - - scene runtime 写回入口 - - inspector presentation builder -- 先迁移旧 editor 中最核心、最常用的组件能力: - - `Transform` - - `Camera` - - `Light` - - `MeshFilter` - - `MeshRenderer` - - `ScriptComponent` -- 第二批再补: - - `AudioSource` - - `AudioListener` - - `Rigidbody` - - `BoxCollider` - - `SphereCollider` - - `CapsuleCollider` - - `VolumeRenderer` - -完成标准: - -- 组件检视不依赖 `InspectorPanel` 写死分支 -- 新增组件 inspector 只需要注册 editor,不需要改 panel 主线 - -### Phase C: 补齐 Scene runtime 的 Inspector 编辑入口 - -目标: -把 Inspector 对 scene 的修改收回 runtime/bridge,避免 UI 直接摸底层对象。 - -任务: - -- 为 `EditorSceneRuntime` 或其绑定层补齐 Inspector 所需入口: - - 枚举选中对象组件 - - 添加组件 - - 移除组件 - - 安全字段写回 - - 组件级别的只读/不可移除判断 -- 明确 built-in 组件规则,例如: - - `Transform` 不可移除 -- 为未来 undo/redo 预留受控命令边界,不允许组件 editor 各自随意直接改对象后不留挂点 -- 复用已有 `EditorSceneBridge` 的复制/序列化能力,但不要把 Inspector 逻辑继续散落到 bridge 里 - -完成标准: - -- 组件 editor 不直接承担 scene 对象所有权 -- Scene 组件编辑有正式 owner - -### Phase D: 建立 Inspector 资源检视抽象 - -目标: -不要让 project 资源在 Inspector 里继续退化成 `Identity + Location` 文本摘要。 - -任务: - -- 新增 `IInspectorAssetEditor` -- 按 `ProjectBrowserModel::ItemKind` 建立资源 inspector 分发 -- 第一优先级先补 `Material` 资源 Inspector - - 旧 editor 的材质 inspector 只参考能力,不搬旧结构 -- 其他资产先补结构化只读 inspector,而不是继续保留散乱字符串: - - `Scene` - - `Model` - - `Texture` - - `Script` - - 普通文件 -- 对“不支持专用 inspector 的资源”给出正式 unsupported asset presentation,而不是临时字符串 - -完成标准: - -- Project 选中不同类型资源时,Inspector 走正式分发表 -- 材质资源至少回到旧 editor 的核心工作流能力级别 - -### Phase E: 接入 Inspector 命令路由 - -目标: -让 Inspector 成为正式编辑域,而不是永远处在 “does not expose edit commands yet” 状态。 - -任务: - -- 让 `EditorHostCommandBridge` 支持绑定 inspector route -- 为 Inspector 明确实现: - - `EvaluateEditCommand` - - `DispatchEditCommand` -- 至少覆盖: - - `edit.delete` - - `edit.duplicate` - - `edit.rename` - - Inspector 自己的 `remove component` - - `add component` -- 如果当前全局 undo/redo 体系还没正式落地,这一轮不强行做假支持,但必须把接口边界留好,避免后面重构第二次 - -完成标准: - -- Inspector 不再是 host command 中的 disabled route -- 激活 Inspector 时,编辑命令能落到 Inspector owner - -### Phase F: 统一交互与验证 - -目标: -补齐这条主线的回归保护,避免后面边做组件边回归崩。 - -任务: - -- 单元测试覆盖: - - subject 解析 - - presentation builder - - component registry 分发 - - add/remove component - - 关键字段写回 - - material inspector 分发与保存 -- 集成验证覆盖: - - Hierarchy 选中对象 -> Inspector 刷新 - - Scene picking -> Inspector 刷新 - - Project 选中资源 -> Inspector 切换 - - Inspector 编辑后 scene/project 事实源保持一致 -- 构建 smoke: - - `XCUIEditorApp` - - 相关单测 target - -完成标准: - -- Inspector 主线不再依赖手工点点点回归 -- 新增组件 editor 时有稳定测试模板可复用 - -## 6. 范围优先级 - -### 本轮必须完成 - -- Inspector 正式 subject/presentation 架构 -- scene component editor 抽象与 registry -- scene runtime 的 Inspector 组件入口 -- Inspector edit route -- 首批核心组件 inspector -- material asset inspector - -### 本轮可以排在后面 - -- 全量旧 editor 组件一口气搬完 -- 多对象同时编辑 -- component reorder -- copy/paste component values -- 完整 undo/redo 命令栈 -- 所有资产类型的深度专用 inspector - -## 7. 非目标 - -本计划明确不做: - -- 直接把旧版 ImGui Inspector 原样移植到 `new_editor` -- 继续把业务堆回一个超大 `InspectorPanel.cpp` -- 为了临时出效果,绕过 runtime 直接在 panel 里改 engine 对象 -- 为了适配单个组件,临时再造一套与 XCUI 字段体系平行的控件模型 - -## 8. 验收标准 - -本计划完成后,至少需要满足: - -- 选中 scene object 时,Inspector 能按组件分 section 正式编辑 -- `Transform / Camera / Light / MeshFilter / MeshRenderer / ScriptComponent` 可以进入新 Inspector 主线 -- 可通过 Inspector 添加和移除组件,且规则正确 -- 选中材质资源时,Inspector 不再只是静态文本摘要 -- 激活 Inspector 后,host edit command 不再返回 “Inspector does not expose edit commands yet.” -- `InspectorPanel` 本体不再承担组件业务和资源业务的主要逻辑 - -## 9. 实施顺序建议 - -严格按以下顺序收口,不要反过来: - -1. `Inspector subject + presentation` -2. `scene component editor abstraction` -3. `scene runtime component editing entry` -4. `Inspector edit route` -5. `core components` -6. `material asset inspector` -7. `第二批组件与补充资产 inspector` - -原因很简单: -如果前四步不先收,后面的组件和资源 inspector 只会继续堆成新的屎山。 diff --git a/docs/used/NewEditor_RHI_PresentPolicy_RootRefactorPlan_2026-04-21.md b/docs/used/NewEditor_RHI_PresentPolicy_RootRefactorPlan_2026-04-21.md deleted file mode 100644 index d709aa1d..00000000 --- a/docs/used/NewEditor_RHI_PresentPolicy_RootRefactorPlan_2026-04-21.md +++ /dev/null @@ -1,232 +0,0 @@ -# NewEditor RHI Present Policy Root Refactor Plan - -## Background - -The current `new_editor` empty-window frame rate is limited by the windowed presentation path rather than by panel content cost. The deeper problem is not a single `Present(0, 0)` call, but that the RHI swapchain abstraction does not model presentation policy in a backend-correct way: - -- D3D12 expresses most presentation policy at swapchain creation time plus DXGI present flags. -- Vulkan expresses it mainly through swapchain present mode selection. -- OpenGL expresses it through swap interval state, not per-present flags. - -The current `RHISwapChain::Present(syncInterval, flags)` interface mixes these semantics and leaves each backend to interpret them differently. - -## Goals - -- Fix the problem at the RHI root instead of patching `new_editor` locally. -- Introduce a formal, cross-backend presentation policy model. -- Make D3D12 support unlocked windowed presentation when the platform allows it. -- Preserve compatibility for existing call sites during the first refactor stage. -- Keep backend fallback behavior explicit and queryable. - -## Non-Goals - -- Do not optimize panel/widget content costs in this refactor. -- Do not redesign the entire window manager or render loop. -- Do not remove legacy `Present(syncInterval, flags)` calls in one step if doing so would cause broad compile breakage. - -## Root Cause - -1. `SwapChainDesc` does not carry a formal presentation policy. -2. `RHISwapChain::Present(syncInterval, flags)` is not a sound cross-backend abstraction. -3. D3D12 swapchain creation is still using a conservative DXGI path and does not expose tearing/unlocked presentation as a first-class capability. -4. `new_editor` cannot request a high-FPS presentation policy through the RHI because the RHI does not represent that choice. - -## Refactor Strategy - -### Stage 1: Introduce formal presentation policy in the RHI - -- Add `SwapChainPresentMode` to `RHITypes.h`. -- Add `SwapChainPresentPolicy` to `RHITypes.h`. -- Extend `SwapChainDesc` with `presentPolicy`. -- Extend `RHISwapChain` with read-only introspection for the active presentation policy/mode. -- Keep the old `Present(syncInterval, flags)` signature temporarily for compatibility, but move backend behavior to the stored policy instead of the ad hoc parameters. - -### Stage 2: Rework D3D12 swapchain creation around policy - -- Change D3D12 swapchain initialization to consume the full `SwapChainDesc`. -- Detect tearing support through DXGI. -- Move D3D12 creation to a modern `CreateSwapChainForHwnd` path with explicit flags. -- Map policy to actual D3D12 behavior: - - `Fifo` -> vsync path - - `Immediate` -> tearing/unlocked path when supported, otherwise explicit fallback - - `Mailbox` -> explicit fallback because DXGI has no direct mailbox equivalent -- Store both requested policy and active mode on the swapchain instance. - -### Stage 3: Align Vulkan and OpenGL to the same policy model - -- Vulkan: - - choose present mode from policy - - store active mode after fallback resolution -- OpenGL: - - apply swap interval from policy - - store active mode after fallback resolution - -### Stage 4: Rewire `new_editor` to request the intended policy - -- `new_editor` D3D12 presenter should request `Immediate` presentation for editor responsiveness. -- Keep the choice explicit in the presenter instead of hard-coding magic present flags. - -### Stage 5: Compatibility cleanup - -- Update direct window renderer usages that create swapchains. -- Keep old `Present(syncInterval, flags)` call sites compiling during this stage. -- After the new policy path is proven, consider a later cleanup pass to remove the legacy arguments entirely. - -## Files Expected To Change - -- `engine/include/XCEngine/RHI/RHITypes.h` -- `engine/include/XCEngine/RHI/RHISwapChain.h` -- `engine/include/XCEngine/RHI/D3D12/D3D12SwapChain.h` -- `engine/src/RHI/D3D12/D3D12SwapChain.cpp` -- `engine/src/RHI/D3D12/D3D12Device.cpp` -- `engine/include/XCEngine/RHI/Vulkan/VulkanSwapChain.h` -- `engine/src/RHI/Vulkan/VulkanSwapChain.cpp` -- `engine/include/XCEngine/RHI/OpenGL/OpenGLSwapChain.h` -- `engine/src/RHI/OpenGL/OpenGLSwapChain.cpp` -- `new_editor/app/Rendering/D3D12/D3D12WindowSwapChainPresenter.cpp` -- `editor/src/Platform/D3D12WindowRenderer.h` -- `editor/src/Platform/D3D12WindowRendererImGuiInterop.h` - -## Validation Plan - -- Build `XCUIEditorApp` -- Build `XCUIEditorAppLib` -- Build any engine targets directly consuming the modified swapchain interfaces -- Verify that: - - RHI compiles across D3D12/Vulkan/OpenGL backends - - `new_editor` requests `Immediate` - - D3D12 reports a sane fallback when tearing is unavailable - - no existing call site is left using removed API - -## Risks - -- Old editor code may still depend on the legacy present arguments. -- OpenGL swap interval extension availability may differ by platform/driver. -- D3D12 fallback semantics must be explicit to avoid silent behavior changes. - -## Immediate Execution Order - -1. Add RHI types for present policy. -2. Update `RHISwapChain` interface. -3. Port D3D12 swapchain internals to the new model. -4. Update `new_editor` to request `Immediate`. -5. Port Vulkan/OpenGL to the same policy model. -6. Build and fix fallout. - -## Detailed Execution Checklist - -### Phase 0: Stabilize the in-progress refactor so the tree is buildable again - -- Remove or re-declare any stale `D3D12SwapChain::Initialize(...)` overloads so header/source signatures match. -- Stop hard-coding `Format::R8G8B8A8_UNorm` inside D3D12 swapchain creation and consume `SwapChainDesc.format`. -- Ensure D3D12 swapchain state resets are complete: - - `m_presentPolicy` - - `m_activePresentMode` - - `m_tearingSupported` - - `m_bufferCount` -- Preserve legacy `Present(syncInterval, flags)` call sites for now, but make backend behavior come from stored policy. - -### Phase 1: Move present semantics into backend-owned swapchain state - -- D3D12: - - carry full `SwapChainDesc` through initialization - - resolve requested mode to actual mode once, during creation - - derive DXGI creation flags and present flags from resolved mode - - apply maximum frame latency from `SwapChainPresentPolicy` in the backend, not in the editor wrapper -- Vulkan: - - carry requested `SwapChainPresentPolicy` - - pick a Vulkan present mode from supported modes with explicit fallback order - - expose both requested policy and resolved active mode -- OpenGL: - - carry requested `SwapChainPresentPolicy` - - resolve swap interval once from policy - - explicitly report fallback when immediate swap interval is unavailable - -### Phase 2: Rewire caller intent explicitly - -- `new_editor` main window presenter: - - request `SwapChainPresentMode::Immediate` - - request low frame latency explicitly through `SwapChainDesc.presentPolicy` - - stop passing magic `Present(0, 0)` semantics as the source of truth -- legacy editor D3D12 window renderer: - - request the same policy so both editors exercise the same RHI path - -### Phase 3: Validation and fallout cleanup - -- Build `XCUIEditorAppLib` -- Build `XCUIEditorApp` -- Fix any pure-virtual fallout in non-D3D12 backends -- Verify these invariants in code: - - `SwapChainDesc.presentPolicy` is the only place callers express intent - - `RHISwapChain::GetActivePresentMode()` reports the real backend-resolved mode - - D3D12 resize preserves tearing-capable flags when immediate mode is active - - no editor path still depends on ad hoc present flags for behavior - -## File-Level Work Breakdown - -### RHI contract - -- `engine/include/XCEngine/RHI/RHITypes.h` -- `engine/include/XCEngine/RHI/RHISwapChain.h` - -Work: - -- keep policy types centralized in the public RHI contract -- make swapchain introspection part of the base interface - -### D3D12 backend - -- `engine/include/XCEngine/RHI/D3D12/D3D12SwapChain.h` -- `engine/src/RHI/D3D12/D3D12SwapChain.cpp` -- `engine/src/RHI/D3D12/D3D12Device.cpp` - -Work: - -- consume full `SwapChainDesc` -- resolve present mode once -- create flip-model swapchain with policy-derived flags -- apply frame latency in the backend -- preserve compatibility surface for existing callers during this pass - -### Vulkan backend - -- `engine/include/XCEngine/RHI/Vulkan/VulkanSwapChain.h` -- `engine/src/RHI/Vulkan/VulkanSwapChain.cpp` -- `engine/src/RHI/Vulkan/VulkanDevice.cpp` - -Work: - -- route policy through initialization -- map policy to Vulkan present modes with explicit fallback -- publish resolved mode through the common interface - -### OpenGL backend - -- `engine/include/XCEngine/RHI/OpenGL/OpenGLSwapChain.h` -- `engine/src/RHI/OpenGL/OpenGLSwapChain.cpp` -- `engine/src/RHI/OpenGL/OpenGLDevice.cpp` - -Work: - -- store requested policy -- map policy to swap interval behavior -- publish resolved mode through the common interface - -### Editor call sites - -- `new_editor/app/Rendering/D3D12/D3D12WindowSwapChainPresenter.cpp` -- `editor/src/Platform/D3D12WindowRenderer.h` -- `editor/src/Platform/D3D12WindowRendererImGuiInterop.h` - -Work: - -- make intent explicit through `SwapChainDesc.presentPolicy` -- keep presentation calls compatible while backend semantics migrate - -## Acceptance Criteria For This Refactor Pass - -- The codebase builds for the edited targets without interface drift. -- `new_editor` and legacy editor both request immediate presentation through the RHI contract. -- D3D12 no longer depends on caller-supplied per-present sync arguments to decide whether it runs unlocked. -- Vulkan/OpenGL no longer fail to compile because of the new `RHISwapChain` pure virtual interface. -- Any remaining performance limit after this pass can be attributed to another subsystem with the present path already architecturally corrected. diff --git a/docs/used/NewEditor_Scene工具域与TransformGizmo计划_阶段归档_2026-04-19.md b/docs/used/NewEditor_Scene工具域与TransformGizmo计划_阶段归档_2026-04-19.md deleted file mode 100644 index e1ad22fa..00000000 --- a/docs/used/NewEditor_Scene工具域与TransformGizmo计划_阶段归档_2026-04-19.md +++ /dev/null @@ -1,189 +0,0 @@ -# NewEditor Scene 工具域与 Transform Gizmo 计划 -日期: `2026-04-18` -状态: `待执行` - -## 0. 归档说明 - -上一阶段和当前主线直接相关的旧计划已归档到 `docs/used`: - -- `XCUI_NewEditor收口重构计划_2026-04-17.md` -- `XCUI_Project面板参考旧Editor完善计划_2026-04-18.md` -- `XCUI_Hierarchy真实场景绑定计划_2026-04-17.md` -- `XCUI_Project运行时解耦收口计划_2026-04-18.md` - -这份文档只覆盖下一阶段,不再重复承载已经完成或已经停用的旧范围。 - -## 1. 当前主线判断 - -`new_editor` 的 `Scene` 主链已经完成了这几个关键基础: - -- 正式 scene runtime 已建立 -- scene viewport 渲染链已打通 -- picking / selection outline / infinite grid 已接通 -- 视口相机导航主链已接通,并且最近一轮已经修正了输入方向与滚轮缩放语义 - -当前最大的主线缺口已经不是“让 Scene 跑起来”,而是“让 Scene 真正能编辑对象”。 - -如果现在继续做: - -- `overlay / HUD` -- `orientation gizmo` -- `Game` 侧复用 -- 更多渲染表现细节 - -那本质上仍然是在给一个“能看、能选、能飞行”的场景浏览器继续堆能力,而不是把它收成正式编辑器。 - -所以下一阶段应该优先做 `Scene Tool Domain + Transform Gizmo`,先补齐最核心的编辑闭环。 - -## 2. 本阶段范围 - -本阶段只做以下内容: - -- 建立正式的 `Scene Tool Domain` -- 接入 `Transform Gizmo` 最小正式闭环 -- 把 gizmo 编辑写回真实场景对象,而不是停留在视口临时态 -- 建立最小工具入口和回归验证 - -## 3. 非目标 - -这一阶段明确不做: - -- 不做完整 `overlay / HUD` 体系 -- 不做 `orientation gizmo` -- 不做 `Game viewport` 复用 -- 不继续扩更多渲染特效或场景装饰能力 -- 不做大规模 UI 外观重做 - -这些都应该在 `Transform Gizmo` 主链收稳之后,再单独开下一阶段计划。 - -## 4. 根因判断 - -当前 `Scene` 相关结构已经有: - -- `EditorSceneRuntime` -- `SceneViewportController` -- `SceneViewportRenderPlan / PassBundle` - -但“编辑工具状态”本身还没有正式 owner。 - -如果直接把 gizmo 的: - -- hover -- active axis -- drag session -- local/world -- pivot 语义 -- 相机输入互斥 - -继续散落塞进 `SceneViewportController` 或 panel 层,本质上会重复之前已经收过一次的老问题: - -- 输入逻辑和领域状态缠在一起 -- 渲染显示和编辑命令缠在一起 -- Inspector / Scene / 未来 toolbar 的状态源不统一 -- 后面再补 rotate/scale、undo/redo、多选时会继续返工 - -因此这轮必须先把 `Scene Tool Domain` 建成正式结构,再在这个结构上接 gizmo。 - -## 5. 执行计划 - -### Phase A:建立 Scene Tool Domain - -目标:先定义“谁拥有 Scene 工具事实”。 - -任务: - -- 明确 `tool state owner`,优先收口到 `EditorSceneRuntime` 或其明确子域,而不是散落在 controller/panel 私有状态里。 -- 正式定义最小工具状态: - - `tool mode` - - `pivot mode` - - `space mode` - - `active axis / plane` - - `drag session` - - `interaction lock` -- 明确 gizmo 交互与视口相机导航的互斥规则: - - gizmo drag 时禁止相机导航抢输入 - - 仅在“无 gizmo 命中且无拖拽会话”时允许回到普通视口导航 - -完成标准: - -- Scene 工具状态有单一事实源 -- 工具状态不依赖 panel 局部临时变量拼接 - -### Phase B:Transform Gizmo 最小正式闭环 - -目标:让选中对象后,Scene 里第一次真正具备“编辑对象”的能力。 - -任务: - -- 建立 gizmo 命中测试、hover、高亮和拖拽状态机 -- 先保证单选对象的最小主链: - - `Translate` - - `Rotate` - - `Scale` -- gizmo 不直接写 UI 临时结果,最终必须写回真实对象 transform -- gizmo 的绘制与交互数据都从统一 scene tool state 派生,不再各处自行判断 - -完成标准: - -- 单选对象可以在 Scene 中通过 gizmo 做真实变换 -- gizmo 拖拽语义稳定,不和视口导航串台 - -### Phase C:编辑命令与状态同步 - -目标:让 transform 编辑进入正式编辑链路,而不是“能拖但不正式”。 - -任务: - -- 变换修改接入正式编辑命令或等价事务路径 -- 确保与 `Inspector / Hierarchy / Scene selection` 共用同一事实源,不重新引入镜像状态 -- 验证变换后的: - - 选中状态 - - focus 行为 - - undo/redo - - inspector 刷新 - -完成标准: - -- 拖 gizmo 后 inspector 数值同步更新 -- undo/redo 能正确回放变换 - -### Phase D:最小入口与验证 - -目标:给这条主链补上最基本的用户入口和测试闭环。 - -任务: - -- 接入最小 scene tool 入口: - - 工具模式切换 - - 快捷键入口(按现有约定接 `Q/W/E/R`) - - 必要时补最小 toolbar 状态展示 -- 新增回归测试,重点覆盖: - - gizmo 与相机输入互斥 - - tool mode 切换 - - transform 写回 - - undo/redo -- 完成 editor 可执行程序 smoke 验证 - -完成标准: - -- 这条主链既能在测试里回归,也能在真实 editor 里操作验证 - -## 6. 验收标准 - -本阶段完成后,至少要满足: - -- 选中对象后可通过 gizmo 做平移、旋转、缩放 -- 拖拽 gizmo 时不会错误触发相机导航或窗口拖动 -- `Inspector` 与 Scene 中的 transform 结果实时一致 -- undo/redo 可以回放 gizmo 产生的变换 -- Scene 工具状态有明确 owner,而不是散落在 panel/controller 临时态里 - -## 7. 完成后再开的下一阶段 - -这阶段收完之后,后续再单独开下一阶段计划,优先顺序建议为: - -1. `Scene overlay / HUD` -2. `orientation gizmo` -3. `Game viewport` 复用与 editor-play 共享视口语义 - -顺序不要反过来。没有正式 `Transform Gizmo` 的 Scene,不应该先做更多外围表现层。 diff --git a/docs/used/NewEditor_Scene面板后续收口计划_阶段归档_2026-04-18.md b/docs/used/NewEditor_Scene面板后续收口计划_阶段归档_2026-04-18.md deleted file mode 100644 index 5cb1ba0c..00000000 --- a/docs/used/NewEditor_Scene面板后续收口计划_阶段归档_2026-04-18.md +++ /dev/null @@ -1,75 +0,0 @@ -# NewEditor Scene 面板后续收口计划 -日期: `2026-04-18` -状态: `阶段归档,暂停新功能推进,切换到问题修复模式` - -## 归档说明 - -这份计划原本用于继续推进 `Scene` 面板的后续正式化收口,包括: - -- 单一真源最终收口 -- `transform gizmo` -- `overlay / HUD / orientation gizmo` -- Scene 工具栏、输入、渲染表现闭环 - -当前决定先暂停这条“继续扩功能”的主线,优先处理现有可见问题,因此这份计划不再作为活动计划继续执行,转为阶段归档。 - -## 本轮已落实内容 - -在归档前,这份计划对应的第一阶段已经实际推进了一步,且是从根因上收口,而不是补丁式修复: - -- `EditorSceneRuntime` 已补上 scene selection 的正式时间戳语义。 -- `Inspector` 不再依赖 `session.selection.kind == HierarchyNode` 这类镜像判断来决定展示 scene 对象。 -- `WorkspaceEventSync` 已移除 hierarchy/scene selection 回写 `session.selection` 的职责,不再把 scene selection 镜像回 session。 -- `Scene / Hierarchy / Inspector` 的 selection owner 已进一步向 `EditorSceneRuntime` 收拢。 -- 已补充对应测试,验证: - - scene selection stamp 的推进 - - inspector 对 `Project / Scene / None` 三种来源的裁决逻辑 - -## 当前完成度评估 - -### 阶段 A:单一真源最终收口 -状态: `已明显推进,但验证未完全闭环` - -已完成: - -- scene selection 真源继续收口到 `EditorSceneRuntime` -- inspector 对 scene selection 的消费不再依赖旧的 hierarchy 镜像链 -- `WorkspaceEventSync` 不再承担 scene selection 镜像同步职责 - -当前阻塞: - -- 相关 feature tests 已编译到链接阶段,但完整测试运行被工作区中无关的 `engine` 脏改动阻塞: - - `engine/src/Rendering/Passes/BuiltinColorScalePostProcessPass.cpp` 当前缺失 - - 导致 `XCEngine.lib` 无法重建 - - 从而阻塞 `editor_app_feature_tests` 的完整链接/执行 - -结论: - -- 从代码结构上看,阶段 A 的主改动已落地。 -- 从“完整验证闭环”角度看,仍有外部阻塞,不能宣称该阶段完全验收完成。 - -### 阶段 B:Scene Tool Domain 与 Transform Gizmo -状态: `未开始` - -### 阶段 C:Overlay / HUD / Orientation Gizmo -状态: `未开始` - -### 阶段 D:Scene 工具闭环与验证 -状态: `未开始` - -## 暂停原因 - -当前优先级已切换: - -- 先解决现有可见问题和回归问题 -- 暂不继续推进新的 Scene 编辑能力 - -因此这份计划在此归档,等问题修完、主线重新切回新功能推进时,再单开新的活动计划。 - -## 后续切换说明 - -从现在开始: - -- `docs/plan` 下不再保留这份活动 Scene 收口计划 -- 当前工作模式切换为“问题修复 / 回归修复” -- 后续如果要继续做 `gizmo / overlay / HUD / orientation gizmo`,再新开计划,不直接复用这份归档文档 diff --git a/docs/used/NewEditor_Scene面板渲染打通计划_阶段归档_2026-04-18.md b/docs/used/NewEditor_Scene面板渲染打通计划_阶段归档_2026-04-18.md deleted file mode 100644 index e67d5be5..00000000 --- a/docs/used/NewEditor_Scene面板渲染打通计划_阶段归档_2026-04-18.md +++ /dev/null @@ -1,82 +0,0 @@ -# NewEditor Scene 面板渲染打通计划 -日期: `2026-04-18` -状态: `阶段归档,已被后续收口计划接替` - -## 归档说明 - -这份计划已经完成了“打通主链”的核心目标,但并没有完成整个 `Scene` 面板的最终收口,因此不再继续作为当前执行计划使用。 - -归档原因: - -- `EditorSceneRuntime`、场景视口正式渲染链、相机导航、对象拾取、选中描边、无限网格已经落地,旧计划的大部分“打通”目标已经完成。 -- 剩余工作已经不再是“让 Scene 跑起来”,而是“把 Scene 真正收干净”,重点转向单一真源、变换工具、Overlay/HUD、朝向 gizmo、工具栏与交互语义统一。 -- 继续在旧计划上叠加内容,会把“已完成主链”和“尚未完成收口”混在一起,失去计划边界。 - -## 已完成项 - -- 已建立正式的 `EditorSceneRuntime`,不再以临时 panel state 充当场景域 owner。 -- 已建立 `SceneViewportRenderRequest` 和正式 render plan/pass bundle 主链。 -- 已接通场景相机交互: - - 右键观察 - - 中键平移 - - 滚轮缩放 - - `W/A/S/D/Q/E` 飞行 - - `Shift` 加速 - - `F` 聚焦选中对象 -- 已接通 `object-id` picking。 -- 已接通选中描边 pass。 -- 已把无限网格作为正式 render pass 纳入场景 render plan。 -- 已补充对应单元测试并完成本地主线构建验证。 - -## 完成度评估 - -### A. EditorSceneRuntime 建立 -状态: `已完成` - -### B. Hierarchy / Inspector / Scene 单一真源统一 -状态: `部分完成` - -现状判断: - -- `Hierarchy` 真实场景绑定已经推进。 -- 但仍需继续审查 `Inspector`、`session.selection`、`WorkspaceEventSync` 是否还保留镜像式同步链路。 - -### C. Scene 正式渲染主链 -状态: `已完成` - -### D. Scene 相机交互 -状态: `已完成` - -### E. Picking / Outline / Gizmo / Overlay -状态: `部分完成` - -已完成: - -- picking -- outline -- infinite grid - -未完成: - -- transform gizmo -- overlay / HUD -- orientation gizmo -- 更完整的 Scene 工具交互闭环 - -### F. Game 侧复用跟进 -状态: `未开始` - -## 当前遗留问题 - -- Scene 相关状态是否已经完全收口到 `EditorSceneRuntime`,还需要再做一次系统审查。 -- 变换工具还没有正式落地,当前 Scene 只能看、选、导航,不能形成完整编辑闭环。 -- Overlay/HUD/朝向 gizmo 还没有纳入正式结构。 -- Scene 工具栏、工具模式、输入语义还没有完全统一到新的场景域模型。 - -## 接替计划 - -后续执行计划已切换到: - -- `docs/plan/NewEditor_Scene面板后续收口计划_2026-04-18.md` - -该计划只处理剩余收口项,不再重复记录已经完成的“打通主链”工作。 diff --git a/docs/used/NewEditor_Tab脱离独立窗口重构计划_2026-04-14.md b/docs/used/NewEditor_Tab脱离独立窗口重构计划_2026-04-14.md deleted file mode 100644 index b28e6f92..00000000 --- a/docs/used/NewEditor_Tab脱离独立窗口重构计划_2026-04-14.md +++ /dev/null @@ -1,368 +0,0 @@ -# NewEditor Tab 脱离独立窗口重构计划 - -日期: `2026-04-14` - -## 1. 目标 - -把 `new_editor` 当前“只能在主窗口内部重排 / dock”的 tab 系统,重构为支持: - -1. tab 从主窗口拖出后,生成独立窗口 -2. 独立窗口中的 tab 可以继续拖回主窗口 -3. 独立窗口之间可以继续相互 dock / 合并 -4. 现有单窗口 DockHost 布局与 panel 内容代码尽量保持复用 - -核心原则: - -1. 不在现有 `UIEditorWorkspaceNodeKind` 里硬塞 `FloatingWindow` -2. 保持“一棵 workspace 树只描述一个窗口内部的 dock 布局” -3. 浮动窗口是宿主层与窗口集合层的概念,不是 dock 树节点概念 -4. 先把结构做对,再接拖拽与窗口生命周期 - -## 2. 当前根因 - -当前不能把 tab 拖成独立窗口,不是交互细节 bug,而是架构缺层。 - -现状如下: - -1. `UIEditorWorkspaceModel` 只描述一棵窗口内 dock tree -2. `UIEditorDockHostInteraction` 只支持: - - `ReorderTab(...)` - - `MoveTabToStack(...)` - - `DockTabRelative(...)` -3. 指针离开 dock host 以后,只会清空 `dropPreview`,不会生成新的宿主窗口实体 -4. `Application` / `ProductEditorWorkspace` / `ProductEditorContext` 当前都只服务一个主窗口实例 - -所以必须先补上“窗口集合 + 每窗口独立 shell 状态 + 跨窗口拖拽转移”这一层。 - -## 3. 设计原则 - -### 3.1 保留单窗口 workspace 模型 - -保留当前这条边界: - -- `UIEditorWorkspaceModel` -- `UIEditorWorkspaceController` -- `UIEditorDockHost` -- `UIEditorDockHostInteraction` - -它们继续只处理“一个窗口内部”的 dock、split、tab、panel。 - -### 3.2 新增窗口集合层 - -新增一层更高的宿主模型,例如: - -- `UIEditorWindowWorkspaceState` -- `UIEditorWindowWorkspaceController` -- `UIEditorWindowWorkspaceSet` - -职责是: - -1. 持有主窗口 workspace -2. 持有多个 detached window workspace -3. 管理跨窗口 tab 转移 -4. 管理 detached window 的创建、关闭、回收 - -### 3.3 拖拽是全局会话,不是单窗口局部状态 - -当前 tab 拖拽状态挂在单个 `UIEditorDockHostInteractionState` 里,这只适用于单窗口。 - -重构后要拆成两层: - -1. 窗口内局部状态 - - tab strip hover / active / splitter drag -2. 跨窗口全局拖拽状态 - - 正在拖哪个 panel - - 来源窗口 id / 来源 node id - - 当前鼠标屏幕坐标 - - 当前命中的目标窗口 / 目标 tab stack - - 是否已经触发“脱离为新窗口” - -### 3.4 panel 内容与业务状态不跟随 HWND 绑死 - -`Hierarchy / Inspector / Project / Console / Scene / Game` 的内容逻辑仍然由 editor 业务层统一提供。 - -窗口只负责: - -1. 展示哪些 panel -2. 当前 panel 在这个窗口里的 dock 关系 -3. 这个窗口自己的输入、hover、capture、draw data - -## 4. 目标架构 - -## 4.1 数据层 - -新增建议: - -- `new_editor/include/XCEditor/Shell/UIEditorWindowWorkspaceModel.h` -- `new_editor/src/Shell/UIEditorWindowWorkspaceModel.cpp` - -建议结构: - -```cpp -struct UIEditorWindowWorkspaceState { - std::string windowId; - UIEditorWorkspaceModel workspace; -}; - -struct UIEditorWindowWorkspaceSet { - std::string primaryWindowId; - std::vector windows; - std::string activeWindowId; -}; -``` - -说明: - -1. 每个窗口持有一份自己的 `UIEditorWorkspaceModel` -2. 主窗口只是 `primaryWindowId` -3. detached window 只是 `windows` 里的非主窗口项 - -## 4.2 控制层 - -新增建议: - -- `new_editor/include/XCEditor/Shell/UIEditorWindowWorkspaceController.h` -- `new_editor/src/Shell/UIEditorWindowWorkspaceController.cpp` - -职责: - -1. 创建 detached window workspace -2. 销毁空窗口 -3. 把 panel 从源窗口移到目标窗口 -4. 把单 panel 初始化成新窗口根 tab stack -5. 支持: - - 同窗口重排 - - 跨窗口移入已有 stack - - 跨窗口 dock relative - - 拖出生成新窗口 - -这里不要复制已有 dock 逻辑,而是复用现有 `UIEditorWorkspaceController` 的布局变换能力。 - -## 4.3 交互层 - -新增建议: - -- `new_editor/include/XCEditor/Shell/UIEditorWindowDragSession.h` -- `new_editor/src/Shell/UIEditorWindowDragSession.cpp` - -全局拖拽状态建议: - -```cpp -struct UIEditorWindowTabDragSession { - bool active = false; - std::string sourceWindowId; - std::string sourceNodeId; - std::string sourcePanelId; - UIPoint screenPointerPosition = {}; - bool detachedWindowCreated = false; -}; -``` - -规则: - -1. 在某个窗口内开始 tab drag 时,建立全局 drag session -2. 鼠标还在某个 dock host 内时,按原有 drop preview 逻辑走 -3. 鼠标离开所有窗口且超过脱离阈值时,创建新 detached window -4. 新窗口创建后,把拖拽中的 panel 挂到这个窗口 -5. 后续拖拽继续以新窗口为目标窗口参与命中 - -## 4.4 宿主层 - -当前 `Application` 只对应一个 HWND,需要补成“主窗口 + detached 窗口宿主实例集合”。 - -建议新增: - -- `new_editor/app/Host/EditorHostWindow.h` -- `new_editor/app/Host/EditorHostWindow.cpp` -- `new_editor/app/Host/EditorHostWindowManager.h` -- `new_editor/app/Host/EditorHostWindowManager.cpp` - -职责拆分: - -### `EditorHostWindow` - -负责单个 HWND: - -1. Win32 消息 -2. 单窗口 render loop -3. 单窗口 workspace bounds -4. 单窗口 shell interaction / draw / cursor / capture - -### `EditorHostWindowManager` - -负责多窗口: - -1. 创建主窗口 -2. 创建 detached window -3. 维护 `windowId -> host window instance` -4. 在窗口关闭时回收 state -5. 协调全局 tab drag session - -## 5. 执行阶段 - -## 阶段 A:先补窗口集合模型,不改宿主 - -### 目标 - -先把“一个窗口集合里有多棵 workspace”建立起来,但仍然只跑主窗口。 - -### 任务 - -1. 新增 `UIEditorWindowWorkspaceSet` -2. 新增集合级 controller -3. 实现: - - `DetachPanelToNewWindow(...)` - - `MovePanelBetweenWindowsToStack(...)` - - `DockPanelBetweenWindowsRelative(...)` -4. 给这些操作补基础单元测试 - -### 验收 - -1. 不开第二个 HWND,也能在纯数据层完成 panel 跨窗口迁移 -2. 源窗口 panel 被正确移除 -3. 目标窗口 root 布局正确生成 - -## 阶段 B:把单窗口宿主抽成可复用的 window instance - -### 目标 - -把当前 `Application` 里只适用于单 HWND 的状态拆出来,为多窗口做准备。 - -### 任务 - -1. 提取单窗口运行时状态 -2. 提取单窗口 render/update/append/input 流程 -3. 让主窗口也走 `EditorHostWindow` - -### 验收 - -1. 主窗口行为不回退 -2. 代码里不再默认“全局只有一个 workspace / 一个 shell state / 一个 hwnd” - -## 阶段 C:接入 detached window 的 HWND 生命周期 - -### 目标 - -真正把第二个窗口创建出来,但先不接完整跨窗口拖拽。 - -### 任务 - -1. `EditorHostWindowManager` 支持创建第二个 `EditorHostWindow` -2. detached window 拥有自己的: - - HWND - - render loop - - interaction state - - draw pass -3. 空窗口关闭回收 - -### 验收 - -1. 可以通过代码直接创建一个带单 panel 的 detached window -2. 该窗口可独立显示、移动、关闭 - -## 阶段 D:接入 tab 拖出创建 detached window - -### 目标 - -把“拖出”真正打通。 - -### 任务 - -1. tab drag 启动时创建全局 drag session -2. 当指针离开全部 dock host 且满足阈值时: - - 从源窗口移除 panel - - 创建 detached window model - - 创建 detached HWND -3. 新窗口初始位置跟随拖拽起点与屏幕坐标 - -### 验收 - -1. 从主窗口拖出任意 tab,可变成独立窗口 -2. 不是复制,是迁移 -3. 原窗口布局自动 canonicalize,不留下空 stack / 空 split - -## 阶段 E:接入跨窗口 re-dock - -### 目标 - -支持 detached window 和主窗口双向回 dock。 - -### 任务 - -1. 命中其他窗口 dock host 时生成目标窗口 drop preview -2. pointer up 时执行跨窗口: - - move to stack - - dock relative -3. 如果源窗口被拖空,自动关闭该 detached window - -### 验收 - -1. detached -> main 可回 dock -2. detached -> detached 可互相 dock -3. 空窗口自动销毁,非空窗口保持 - -## 阶段 F:补交互与收口 - -### 目标 - -把功能补到可长期使用,不留明显交互破洞。 - -### 任务 - -1. 处理激活窗口切换 -2. 处理 capture / focus / cursor 所有权 -3. 处理窗口关闭时的 panel 回收策略 -4. 预留布局持久化结构 - -### 验收 - -1. 多窗口拖拽不闪退 -2. capture 不串窗口 -3. focus / active panel 行为稳定 - -## 6. 关键风险 - -### 6.1 不能让 panel 业务实例被窗口复制 - -如果直接复制 `ProductProjectPanel / ProductHierarchyPanel` 这类对象,很容易出现双状态漂移。 - -要先确认: - -1. 哪些状态是全局业务状态 -2. 哪些状态是窗口内展示状态 - -### 6.2 指针捕获要统一到宿主管理层 - -当前 capture 大多按单窗口处理。多窗口后如果还各管各的,很容易出现: - -1. 源窗口还认为自己在 drag -2. 目标窗口已经开始 preview -3. 鼠标松开后状态残留 - -所以 drag session 与 capture 仲裁必须提升到 window manager。 - -### 6.3 不能把跨窗口逻辑塞回 DockHostInteraction - -`UIEditorDockHostInteraction` 继续只处理单窗口内部命中与 preview。 - -跨窗口逻辑应放在更上层,否则很快会变成屎山。 - -## 7. 本轮执行顺序 - -这一轮按下面顺序推进: - -1. 阶段 A:补窗口集合模型与 controller -2. 阶段 B:提取单窗口 host instance -3. 阶段 C:先用代码路径创建 detached window,打通多 HWND -4. 阶段 D:接 tab 拖出创建新窗口 -5. 阶段 E:接跨窗口 re-dock - -## 8. 收口标准 - -满足以下条件,才算这次重构收口: - -1. tab 可以从主窗口拖出成为独立窗口 -2. 独立窗口可以拖回主窗口 -3. 独立窗口之间可以继续 dock -4. 不出现空窗口残留、空 stack 残留、拖拽状态残留 -5. 不破坏当前主窗口已有的 tab 重排 / split / dock 功能 diff --git a/docs/used/NewEditor_TreeActualRedundancyReductionPlan_2026-04-22.md b/docs/used/NewEditor_TreeActualRedundancyReductionPlan_2026-04-22.md deleted file mode 100644 index 0fe83de8..00000000 --- a/docs/used/NewEditor_TreeActualRedundancyReductionPlan_2026-04-22.md +++ /dev/null @@ -1,187 +0,0 @@ -# NewEditor Tree Actual Redundancy Reduction Plan - -Date: `2026-04-22` -Status: `In Progress` - -## Goal - -- Remove only the redundancy that is structurally real in `new_editor` tree-related code. -- Keep the existing panel refresh split, frame ownership, and hosted-panel dispatch architecture intact. -- Accept only changes that produce clear net code reduction and keep ownership clearer, not blurrier. - -## Why The Previous Plan Was Deleted - -- The deleted plan incorrectly treated the rename flow as a shared tree host skeleton. -- `HierarchyPanel` is a single-tree, single-surface rename owner. -- `ProjectPanel` is a tree/grid dual-surface rename owner and its rename bounds, state, and commit side-effects differ materially. -- Because of that difference, extracting rename state handling upward adds request/status/surface glue instead of deleting real code. -- Any cross-panel rename abstraction is now considered a wrong direction for this codebase. - -## Hard Constraints - -- `HierarchyPanel` / `ProjectPanel` rename flow is explicitly not a dedup target. -- Do not merge panel refresh loops. -- Do not merge project tree and asset grid behavior. -- Do not change viewport independent request/render flow. -- Do not change `EditorWindowFrameDriver` single frame owner semantics. -- Do not move project or scene runtime semantics into `XCEditor`. -- Do not create new `Behavior`, `Host`, `Helper`, `Support`, `Tooling`, or similar glue files. -- Any phase must have a credible negative net diff before implementation starts. - -## Real Remaining Redundancy Targets - -### 1. `ProjectPanel` Left-Tree Internal Repetition - -This is the first valid target. - -Current repetition shape: - -- repeated left-tree layout rebuild sequences after navigation / drop / refresh paths -- repeated left-tree draw call setup for background / foreground / rename overlay / drop preview -- repeated left-tree state sync and cleanup around operations inside the same panel - -Rules: - -- Keep this work inside `ProjectPanel` and existing tree modules. -- Prefer local private functions or same-file narrowing over new files. -- Do not generalize tree/grid business logic together. - -### 2. Cross-Panel Tree Draw Chain Re-Audit - -This is only a candidate target, not an automatic extraction target. - -Possible shared shape: - -- append tree background -- append tree foreground -- append inline rename overlay -- append drop preview - -Rules: - -- Only extract if the result is a pure UI draw chain with no business ownership leakage. -- Only extract into existing `UIEditorTreeView.*` if total code clearly goes down. -- If the net diff is not negative, stop and keep the code local. - -### 3. Final Intentional-Duplication Audit - -After the valid reductions above, re-check what remains. - -Expected intentional duplicates: - -- rename ownership between `HierarchyPanel` and `ProjectPanel` -- scene-specific drag/drop commit behavior -- project-specific folder / asset / breadcrumb / splitter / context menu orchestration - -If a remaining overlap is only superficial orchestration around different business surfaces, record it as intentional and stop. - -## Execution Plan - -### Phase A. Freeze The Rename Boundary - -Status: `Completed` - -Target: - -- Remove the invalid rename-dedup direction from the active plan baseline. - -Completed result: - -- Deleted the wrong plan that targeted cross-panel rename host dedup. -- Re-established rename as panel-owned business logic, not a shared tree abstraction target. - -Validation: - -- Documentation baseline corrected. - -### Phase B. Reduce `ProjectPanel` Left-Tree Internal Repetition - -Status: `Completed` - -Target: - -- Shrink repeated left-tree orchestration inside `ProjectPanel` without changing behavior boundaries. - -Allowed scope: - -- `new_editor/app/Features/Project/ProjectPanel.cpp` -- `new_editor/app/Features/Project/ProjectPanel.h` - -Rules: - -- No new file. -- No tree/grid semantic merge. -- No rename abstraction promotion. -- Only keep helpers that delete more code than they add. - -Validation: - -- `cmake --build build --config Debug --target XCUIEditorApp` -- smoke-launch `build/new_editor/Debug/XCUIEditor.exe` -- confirm latest `runtime.log` contains: - - `EnsureEditorStartupScene loaded scene=Main Scene` - - `[app] initialize end` - - `[app] shutdown end` - -Completed result: - -- Reduced the real repeated reconcile chain inside `ProjectPanel`. -- Kept tree/grid business ownership split intact. -- Landed local private narrowing only: - - runtime-to-UI selection sync now funnels through one panel-local path - - layout/tree-frame rebuild now funnels through one panel-local path -- `ProjectBrowserModel` also now owns a single browser-state refresh chain instead of repeating: - - `RefreshFolderTree()` - - `EnsureValidCurrentFolder()` - - `RefreshAssetList()` - -### Phase C. Re-Audit The Shared Tree Draw Chain - -Status: `Pending` - -Target: - -- Check whether a narrow draw-chain entry in existing `UIEditorTreeView.*` can reduce real code across `HierarchyPanel` and the Project left tree. - -Allowed scope: - -- `new_editor/include/XCEditor/Collections/UIEditorTreeView.h` -- `new_editor/src/Collections/UIEditorTreeView.cpp` -- the two panel files only where call sites get smaller - -Rules: - -- No state machine extraction. -- No rename surface abstraction. -- No drag/drop business extraction. -- Cancel the phase immediately if the predicted diff is not net-negative. - -Validation: - -- `rg` verification of targeted duplicate draw blocks -- `cmake --build build --config Debug --target XCUIEditorApp` -- smoke-launch `build/new_editor/Debug/XCUIEditor.exe` - -### Phase D. Final Closure Audit - -Status: `Pending` - -Target: - -- Verify that all remaining overlap is either reduced or explicitly intentional. - -Validation: - -- targeted `rg` audit -- `git diff --stat` confirms accepted phases reduced code overall -- `cmake --build build --config Debug --target XCUIEditorApp` -- smoke-launch `build/new_editor/Debug/XCUIEditor.exe` - -## Acceptance - -- The wrong rename-dedup plan no longer exists in `docs/plan`. -- Cross-panel rename dedup is explicitly excluded. -- The next implementation target is `ProjectPanel` left-tree internal repetition, not rename. -- No new glue-layer file is introduced. -- Each accepted phase must reduce code, not grow it. -- Build and smoke validation pass after each completed implementation phase. diff --git a/docs/used/NewEditor_TreePanelHostRedundancyRefactorPlan_完成归档_2026-04-22.md b/docs/used/NewEditor_TreePanelHostRedundancyRefactorPlan_完成归档_2026-04-22.md deleted file mode 100644 index ffa8f226..00000000 --- a/docs/used/NewEditor_TreePanelHostRedundancyRefactorPlan_完成归档_2026-04-22.md +++ /dev/null @@ -1,79 +0,0 @@ -# NewEditor Tree Root Refactor Plan - -Date: `2026-04-22` -Status: `Completed` - -## Goal - -- Remove `TreePanelHost` / `UIEditorTreePanelBehavior` style glue layers from `new_editor`. -- Keep tree shared logic inside existing `XCEditor` tree modules instead of app-level host wrappers. -- Keep `HierarchyPanel` and `ProjectPanel` focused on scene/project specific behavior only. -- Preserve existing performance and dispatch boundaries. - -## Root Cause - -- Shared tree code was split across app panels and thin glue files. -- Those glue files did not represent a stable domain module. They only forwarded tree input, rename, draw, and drag helpers. -- That produced extra file count, repeated filtering helpers, and fuzzy ownership. - -## Final Module Boundary - -- `UIEditorTreeView.*` - - tree layout - - tree rendering - - inline rename bounds/metrics/palette helpers - - tree drop preview helpers -- `UIEditorTreeViewInteraction.*` - - hosted tree input filtering - - tree interaction update -- `UIEditorTreeDragDrop.h` - - drag preview filtering - - pointer suppression filtering for tree drag lanes - - drag/drop processing -- `HierarchyPanel.cpp` - - scene selection - - hierarchy rename commit - - hierarchy reparent commit - - hierarchy event emission -- `ProjectPanel.cpp` - - project layout - - breadcrumb/grid/context menu behavior - - folder and asset rename commit - - project drag/drop commit - -## Explicit Non-Goals - -- Do not merge panel refresh loops. -- Do not change viewport request/render ownership. -- Do not change `EditorWindowFrameDriver` single frame owner semantics. -- Do not change hosted panel dispatch architecture. -- Do not move scene/project runtime semantics into `XCEditor`. - -## Execution - -1. Removed the old app-side `TreePanelHost` path. -2. Moved durable inline rename and drop-preview helpers into `UIEditorTreeView.*`. -3. Moved hosted tree input filtering into `UIEditorTreeViewInteraction.*`. -4. Moved tree pointer suppression filtering into `UIEditorTreeDragDrop.h`. -5. Replaced remaining `HierarchyPanel` and `ProjectPanel` calls to `UIEditorTreePanelBehavior` with direct tree-module calls. -6. Deleted `new_editor/include/XCEditor/Collections/UIEditorTreePanelBehavior.h`. -7. Deleted `new_editor/src/Collections/UIEditorTreePanelBehavior.cpp`. -8. Removed `src/Collections/UIEditorTreePanelBehavior.cpp` from `new_editor/CMakeLists.txt`. - -## Acceptance Result - -- `UIEditorTreePanelBehavior.*` no longer exists. -- Tree shared logic now lives in existing tree modules, not new helper layers. -- Tree hosted input filtering is bottomed out and reusable instead of duplicated in each panel. -- Panel code keeps only business-specific logic and no longer owns thin tree glue. - -## Verification - -- Build command: - - `cmake --build build --config Debug --target XCUIEditorApp -- /m:1` -- Smoke test: - - launched `build/new_editor/Debug/XCUIEditor.exe` - - confirmed `runtime.log` contains: - - `EnsureEditorStartupScene loaded scene=Main Scene` - - `initialize end` - - `shutdown end` diff --git a/docs/used/NewEditor_UI_TextMeasurementSemanticUnificationPlan_完成归档_2026-04-22.md b/docs/used/NewEditor_UI_TextMeasurementSemanticUnificationPlan_完成归档_2026-04-22.md deleted file mode 100644 index df16e474..00000000 --- a/docs/used/NewEditor_UI_TextMeasurementSemanticUnificationPlan_完成归档_2026-04-22.md +++ /dev/null @@ -1,146 +0,0 @@ -# NewEditor UI Text Measurement Semantic Unification Plan - -Date: `2026-04-22` - -## Goal - -Refactor the shared `new_editor` UI widget layer so text measurement, layout sizing, draw positioning and hit-test geometry stop sharing ambiguous width fields. - -This is not a symptom patch. -This is a semantic cleanup of the shared UI model. - -## Root Problem - -The shared widget layer currently mixes multiple width meanings under vague names such as: - -- `desiredLabelWidth` -- `desiredShortcutWidth` -- `desiredWidth` - -Across different controls, those fields are used as one or more of: - -- measured glyph advance -- padded control width input -- arranged rect width surrogate -- draw-time text positioning basis - -That breaks the semantic boundary between: - -- text measurement -- layout width solving -- final arranged geometry -- draw clipping and hit testing - -The `tab strip` bug was only one visible symptom of this broader design problem. - -## Confirmed Shared Impacted Controls - -- `new_editor/include/XCEditor/Collections/UIEditorTabStrip.h` -- `new_editor/include/XCEditor/Menu/UIEditorMenuBar.h` -- `new_editor/include/XCEditor/Menu/UIEditorMenuPopup.h` -- `new_editor/include/XCEditor/Shell/UIEditorStatusBar.h` -- `new_editor/include/XCEditor/Viewport/UIEditorViewportSlot.h` - -Confirmed propagation paths: - -- `new_editor/src/Shell/UIEditorShellInteraction.cpp` -- `new_editor/src/Shell/UIEditorShellCompose.cpp` -- `new_editor/src/Workspace/UIEditorWorkspaceCompose.cpp` -- `new_editor/src/Workspace/UIEditorWorkspaceInteraction.cpp` -- `new_editor/src/Viewport/UIEditorViewportShell.cpp` -- `new_editor/src/Docking/UIEditorDockHost.cpp` - -## End State - -All shared text-bearing widgets must follow one rule: - -- model fields store measured text width only -- layout derives padded control width from measured text width -- arranged rects remain the only source of final geometry -- draw and hit-test consume arranged rects plus measured text width, never repurposed control widths - -## Refactor Principles - -- Use one semantic name for one meaning. -- Do not reuse a measured text width field as a control width field. -- Do not let individual widgets invent incompatible width semantics. -- Push actual text measurement to request/model build points where a `UIEditorTextMeasurer` is available. -- Keep estimated glyph-width fallback only as a fallback, not the primary semantic. - -## Workstream A: Introduce Shared Text-Width Semantics - -Required changes: - -- add shared helper(s) for resolving measured text width from: - - cached measured width - - `UIEditorTextMeasurer` - - estimated glyph fallback -- document the semantic contract in the shared widget structs - -Acceptance: - -- shared controls no longer expose ambiguous width names for pure text measurement - -## Workstream B: Normalize Shared Widget Models - -Required changes: - -- convert the following fields to explicit measured-text semantics: - - `UIEditorTabStripItem` - - `UIEditorMenuBarItem` - - `UIEditorMenuPopupItem` - - `UIEditorStatusBarSegment` - - `UIEditorViewportSlotToolItem` -- update each widget so layout derives control width from measured text width locally - -Acceptance: - -- measurement/layout/draw code paths in those widgets are semantically aligned - -## Workstream C: Push Real Measurement Through Shared Request Paths - -Required changes: - -- shell interaction: - - measure menu bar items - - measure popup labels and shortcuts - - measure shell status-bar segments -- workspace / viewport compose: - - measure viewport tool-item labels - - measure viewport status-bar segments - - carry the measured viewport shell model through compose/frame so append uses the same measured data as layout - -Acceptance: - -- top-level editor shell and external viewport chrome no longer rely on ad-hoc estimated widths in the steady state - -## Workstream D: Regression Validation - -Required checks: - -- build `XCUIEditorLib` -- build `XCUIEditorAppLib` -- build `XCUIEditorApp` -- smoke-launch `build/new_editor/Debug/XCUIEditor.exe` - -Manual follow-up target: - -- verify long/short mixed menu labels -- verify long tab labels -- verify status-bar and viewport toolbar labels remain aligned after measurement unification - -## Out Of Scope For This Pass - -- tree/list/property-grid virtualization -- scene rendering optimization -- renderer backend work -- visual redesign - -## Success Criteria - -This pass is complete only if: - -- the shared widget layer uses one consistent measured-text semantic -- layout width derivation is local and explicit -- shell/workspace/viewport request paths feed measured text widths into shared widgets -- the tab fix becomes one instance of a general rule, not a special case diff --git a/docs/used/NewEditor_UI模块三层与编辑器实现审核方向_阶段归档_2026-04-19.md b/docs/used/NewEditor_UI模块三层与编辑器实现审核方向_阶段归档_2026-04-19.md deleted file mode 100644 index 2c8f3f0d..00000000 --- a/docs/used/NewEditor_UI模块三层与编辑器实现审核方向_阶段归档_2026-04-19.md +++ /dev/null @@ -1,176 +0,0 @@ -# NewEditor UI模块三层与编辑器实现审核方向 - -日期:2026-04-18 - -## 目的 - -本文档不直接下具体问题结论,只定义后续审核 `UI基础层`、`UI runtime层`、`UI editor层` 以及 `new_editor` 实现时应采用的审查方向。所有审查都以软件工程最佳实践为准,优先关注边界、依赖、状态、抽象与可演进性,而不是先陷入局部功能细节。 - -## 一、分层边界审核 - -重点确认三层职责是否真正清晰: - -- 基础层只承载通用能力,例如输入分发、布局、绘制、基础状态机、通用 widget 模型与无业务语义的交互原语。 -- runtime 层只面向运行时 UI 场景与游戏内使用,不应反向依赖 editor 语义、编辑器数据结构或编辑器交互逻辑。 -- editor 层只承载编辑器专用抽象,例如面板、dock、property grid、viewport shell、editor command、workspace model,不应把平台细节和项目业务直接揉进通用控件。 -- `new_editor/app` 应被视为产品组装层与宿主集成层,而不是继续沉淀“半通用框架能力”。 - -审核标准: - -- 下层不知道上层。 -- 通用层不带具体业务名词。 -- 产品层不反向定义基础规则。 - -## 二、依赖方向审核 - -重点确认依赖是否单向、可解释、可约束: - -- CMake target 依赖是否符合层次关系。 -- 头文件暴露面是否过宽,是否存在把实现细节暴露成公共 API 的情况。 -- include 路径是否允许跨层随意直连。 -- 命名空间是否真实表达层次,而不是目录分了层、代码却还是互相穿透。 -- `new_editor` 是否仍在通过直接复用旧 editor 文件形成隐式耦合。 - -审核标准: - -- 依赖方向必须单向。 -- 公共 API 必须小而稳定。 -- 跨层访问必须通过明确桥接或契约,而不是直接拿内部实现。 - -## 三、抽象质量审核 - -重点确认当前抽象是不是“真抽象”,而不是“为某个面板包一层名字”: - -- Tree、List、TabStrip、DockHost、Workspace、PropertyGrid、ViewportShell 这些是否能服务多个场景。 -- 抽象内部是否塞了大量 feature 特判。 -- 同类问题是否复用同一套模型、布局、命中测试、交互状态与事件语义。 -- editor 抽象与 app feature 是否耦合过深。 - -审核标准: - -- 抽象必须稳定。 -- 抽象必须可复用。 -- 抽象必须能脱离某个 feature 单独成立。 - -## 四、状态模型审核 - -重点确认状态是否单一、清晰、可追踪: - -- 持久状态、会话状态、瞬时交互状态是否分层保存。 -- selection、focus、hover、active、capture、drag、dock、undo 是否各有唯一真源。 -- 是否存在多个模块同时维护一份接近但不完全一致的状态。 -- UI 视觉状态是否只是状态投影,而不是新的状态源。 - -审核标准: - -- 每类核心状态只能有一个权威来源。 -- 交互状态的生命周期必须明确。 -- 状态同步必须可删减,而不是越补越多。 - -## 五、输入与交互一致性审核 - -重点确认整个 UI 模块是否存在统一交互规则: - -- 鼠标、键盘、快捷键、焦点、指针捕获、拖拽、弹出层、多窗口输入是否走统一分发路径。 -- 复杂控件是否共用统一输入桥,而不是每个 feature 私自命中、私自切焦点、私自维护 active 态。 -- 多窗口、dock、tab 拖拽、viewport 操作之间是否有一致的优先级与冲突消解规则。 -- editor 特有交互是否建立在通用输入机制之上。 - -审核标准: - -- 输入规则统一。 -- 捕获规则统一。 -- 拖拽与命中优先级统一。 - -## 六、渲染职责审核 - -重点确认“UI 逻辑”和“渲染后端”是否分离: - -- widget、panel、viewport shell 是否只产出布局与绘制意图。 -- native renderer、D3D12 host、平台窗口层是否只执行渲染与呈现职责。 -- scene viewport、overlay、gizmo、selection outline、host texture 是否边界清楚。 -- UI 抽象是否过度依赖某个 renderer 或平台宿主。 - -审核标准: - -- 逻辑层不理解渲染后端细节。 -- 渲染层不承载业务决策。 -- viewport 是组合点,不是大杂烩。 - -## 七、平台宿主隔离审核 - -重点确认平台细节是否被关在宿主层: - -- Win32 消息、窗口生命周期、D3D12 设备、swapchain、原生纹理互操作是否留在宿主适配层。 -- editor feature 是否直接依赖窗口句柄、平台消息或图形后端对象。 -- 平台适配接口是否足够窄,是否能支撑后续替换或重构。 - -审核标准: - -- 平台代码不泄漏到通用 editor 逻辑。 -- 业务层只能看到抽象宿主能力。 - -## 八、文件夹结构与命名审核 - -重点确认工程组织是否能表达真实架构: - -- 目录是否按层次与职责划分,而不是按历史来源堆积。 -- `include` 是否只暴露正式公共契约。 -- `src` 是否只包含内部实现。 -- `app` 是否只包含产品装配、feature 实现、平台宿主与渲染接线。 -- 命名中的 `UI`、`UIEditor`、`Shell`、`Workspace`、`Viewport`、`Runtime`、`Host`、`Bridge` 是否语义稳定。 - -审核标准: - -- 目录结构能反映架构。 -- 命名能反映边界。 -- 公开层和内部层必须分开。 - -## 九、测试体系审核 - -重点确认测试是否保护了架构,而不只是保护表面功能: - -- 基础层是否有纯单元测试,覆盖布局、命中、状态机、输入路由。 -- editor 层是否有交互级测试,覆盖 panel lifecycle、dock、workspace、tree、property grid、viewport。 -- app 层是否有 smoke test 与关键集成测试。 -- 复杂输入链路和多窗口行为是否有回归测试。 - -审核标准: - -- 基础抽象必须可单测。 -- 关键交互必须可回归。 -- 集成层必须可冒烟验证。 - -## 十、迁移与收口路径审核 - -重点确认当前架构是不是在朝终态收敛: - -- legacy editor 代码的复用是临时桥接还是永久依赖。 -- 每一个 bridge、compat、temporary、legacy 入口是否都有退出路径。 -- 是否存在“先能跑起来,后面再整理”的长期滞留区域。 -- 三层 UI 的长期归属是否已经定义清楚。 - -审核标准: - -- 临时桥必须可拆。 -- 迁移路径必须可描述。 -- 每次重构都应让终态更清晰,而不是更模糊。 - -## 建议审核顺序 - -建议按以下顺序推进,而不是一开始就逐个面板挑 bug: - -1. 先审分层图、target 依赖图、命名空间边界。 -2. 再审核心状态图,包括 workspace、selection、focus、capture、viewport interaction。 -3. 再审通用抽象,包括 tree、dock、viewport、property grid。 -4. 最后审具体 feature,包括 hierarchy、project、inspector、scene。 - -## 核心判断标准 - -后续所有审核都可以收敛到三个总标准: - -- 边界是否清晰。 -- 状态是否单一。 -- 交互是否统一。 - -如果这三项不成立,后面的命名、目录、实现细节都会持续反复出问题。 diff --git a/docs/used/NewEditor_UI第一阶段边界收口子计划_阶段归档_2026-04-19.md b/docs/used/NewEditor_UI第一阶段边界收口子计划_阶段归档_2026-04-19.md deleted file mode 100644 index a30b0f41..00000000 --- a/docs/used/NewEditor_UI第一阶段边界收口子计划_阶段归档_2026-04-19.md +++ /dev/null @@ -1,122 +0,0 @@ -# NewEditor UI第一阶段边界收口子计划 - -日期:2026-04-19 - -## 目标 - -本阶段不扩功能,专门处理 `UI模块三层 + new_editor` 在第一审核方向下暴露出来的根因级边界问题,优先把当前最危险的越层依赖和错误落点收住。 - -本阶段完成后应达到以下结果: - -- `new_editor` 不再直接引用 `editor/src` 的源码或内部头文件。 -- `new_editor/app` 不再依赖 `engine/src` 暴露出来的内部实现头。 -- `XCUIEditorLib` 的公共层不再继续扩散明显的产品业务语义。 -- `core / host / app` 三层的代码落点开始和真实职责一致。 - -## 范围 - -本阶段只覆盖以下问题: - -1. `new_editor -> editor/src` 直接源码依赖。 -2. `new_editor -> engine/src` 直接内部实现依赖。 -3. `XCUIEditorLib` 公共层中的产品语义泄漏。 -4. `app/Features/Shared` 中实际上已通用化的交互逻辑落点错误。 - -本阶段不覆盖以下内容: - -- 新功能开发。 -- 大规模视觉重设计。 -- 所有 `App` 语义一次性全部拆空。 -- 所有 feature 的深度重写。 - -## 子问题与处理策略 - -### 一、切断 `new_editor -> editor/src` - -现状: - -- `new_editor/CMakeLists.txt` 直接编入旧 `editor/src/Viewport/*.cpp`。 -- `new_editor/app/Features/Scene/LegacySceneViewportGizmo.cpp` 直接包含旧 editor 内部头并使用 `XCEngine::Editor::*` 类型。 - -处理策略: - -- 把当前 `new_editor` 依赖的旧 viewport gizmo/picker 相关代码迁入 `new_editor` 自己的源码边界。 -- 新迁入代码放在 `new_editor/app/Legacy/Viewport` 或等价清晰目录中,由 `new_editor/app` 自己维护。 -- `new_editor` 后续只能通过自身目录内的实现使用 legacy 逻辑,不能再跨目录吃旧 editor 内部实现。 - -完成标准: - -- `new_editor/CMakeLists.txt` 中不再出现 `../editor/src/...`。 -- `new_editor/app` 中不再出现 ``、`` 这类旧 editor 内部 include。 - -### 二、切断 `new_editor -> engine/src` - -现状: - -- `XCUIEditorAppLib` 当前把 `${CMAKE_SOURCE_DIR}/engine/src` 加进 include path。 -- 这意味着 `new_editor/app` 可以越过 engine 正式 API 直接吃内部实现。 - -处理策略: - -- 逐个排查当前依赖 `engine/src` 的 include。 -- 能上提到 `engine/include` 的,上提正式 API。 -- 不适合公开给 engine 的,不允许继续从 `new_editor` 访问,改为在 `new_editor` 本地实现或封装。 -- 修改完成后移除 `engine/src` include path。 - -完成标准: - -- `new_editor/CMakeLists.txt` 不再给 `XCUIEditorAppLib` 暴露 `${CMAKE_SOURCE_DIR}/engine/src`。 -- `new_editor` 编译通过且不依赖 engine 内部头。 - -### 三、收缩 `XCUIEditorLib` 公共层中的产品语义 - -现状: - -- `XCUIEditorLib` 当前公共头里包含 `EditorSession`、`EditorPanelIds`、`EditorEditCommandRoute` 等明显产品语义。 -- `BuildEditorFoundationPanelRegistry`、`BuildEditorFoundationWorkspaceModel`、`BuildEditorFoundationShellAsset` 这类默认产品组装入口仍留在公共层。 - -处理策略: - -- 把明显属于当前产品的 `App` 语义从 `XCUIEditorLib` 公共层迁出,优先改为 `XCUIEditorAppLib` 私有头或 `new_editor/app` 内部头。 -- 把默认产品组装入口迁到 `new_editor/app/Composition`,core 只保留模型、校验和交互契约。 -- 对暂时无法一次性完全抽离的部分,先缩窄可见性,不再继续扩散。 - -完成标准: - -- `XCUIEditorLib` 不再对外暴露当前产品固定 panel 路由和会话语义。 -- 默认 panel/workspace/shell 组装不再由 core 公共 API 提供。 - -### 四、纠正通用交互逻辑的代码落点 - -现状: - -- `TreeItemDragDrop`、`GridItemDragDrop` 已经被多个 feature 复用,但仍放在 `app/Features/Shared`。 - -处理策略: - -- 将这类通用交互状态机上提到 editor core 的 interaction 层。 -- app feature 只保留领域回调、领域校验和事件解释。 - -完成标准: - -- 通用拖拽状态机不再落在 `app/Features/Shared`。 -- `Hierarchy` 和 `Project` 通过统一抽象复用,不再共享 app 层“影子框架”。 - -## 执行顺序 - -1. 先迁出 legacy gizmo/picker 代码,切断 `new_editor -> editor/src`。 -2. 再排查并切断 `new_editor -> engine/src`。 -3. 再把公共层里的产品语义和默认组装入口下沉出 `XCUIEditorLib`。 -4. 最后调整通用交互逻辑落点,并补验证。 - -## 验证方式 - -- 重新配置并编译 `XCUIEditorAppLib` / `XCUIEditorApp` / `editor_app_feature_tests`。 -- 重点回归 `SceneViewportRuntimeTests.*`。 -- 补查 `CMakeLists.txt`、include path 和代码搜索结果,确认越层依赖已经被删除。 - -## 风险 - -- legacy gizmo 迁移会牵出旧 editor 内部辅助类型,必须控制迁移范围,避免把旧 editor 整块复制进 `new_editor`。 -- 移除 `engine/src` include path 后,可能暴露出之前被掩盖的正式 API 缺口,需要补正式头或本地封装。 -- 公共层语义收缩如果一次改太大,容易把现有测试打散,因此本阶段优先做“切断外部暴露”和“迁出默认组装”。 diff --git a/docs/used/NewEditor_UI第七方向平台宿主隔离子计划_阶段归档_2026-04-19.md b/docs/used/NewEditor_UI第七方向平台宿主隔离子计划_阶段归档_2026-04-19.md deleted file mode 100644 index 05e18ed1..00000000 --- a/docs/used/NewEditor_UI第七方向平台宿主隔离子计划_阶段归档_2026-04-19.md +++ /dev/null @@ -1,383 +0,0 @@ -# NewEditor UI第七方向平台宿主隔离子计划 2026-04-19 - -## 1. 目标 - -本阶段聚焦 `NewEditor_UI模块三层与编辑器实现审核方向` 中的第七方向: - -**把 Win32 / D3D12 / 原生系统交互严格关回宿主层,避免平台细节继续向 editor feature、runtime 组合层和通用状态层渗透。** - -这次不做表面补丁,不做“只是把 include 挪一下”的伪重构。 -目标是直接处理目前已经确认的根问题。 - -## 2. 已确认的根问题 - -### 2.1 通用多窗口状态被错误放进 `Platform/Win32` - -已确认: - -- `EditorWindowWorkspaceStore` 本质上只是多窗口 workspace/session 的权威状态存储与校验器。 -- 它不依赖 `HWND`、Win32 message、DPI、窗口句柄,也不依赖 D3D12。 -- 但它当前位于 `app/Platform/Win32/WindowManager/`,命名和目录都把它伪装成了平台层。 - -这会造成两个后果: - -- 平台层目录被产品逻辑污染。 -- 后续继续做平台替换、宿主重构、多后端支持时,这部分通用逻辑会被错误地认为只能留在 Win32 层。 - -### 2.2 Project feature 直接调用系统 API - -已确认: - -- `ProjectPanel.cpp` 直接包含 `windows.h` / `shellapi.h`。 -- 它直接调用剪贴板、Explorer 打开、系统双击时间、系统 tick 等 API。 - -这说明: - -- feature 层没有通过宿主抽象访问系统能力。 -- 产品逻辑和平台行为被写死在一起。 -- 后续测试替身、跨平台迁移、甚至简单的宿主替换都会被卡住。 - -### 2.3 路径与字符串编码工具把 Win32 细节扩散到 runtime / feature - -已确认: - -- `app/Internal/StringEncoding.h` 基于 Win32 `MultiByteToWideChar/WideCharToMultiByte`。 -- `ProjectBrowserModel` / `ProjectBrowserModelInternal` / `ProjectPanel` 等产品逻辑直接依赖这些工具。 - -根问题不是“用了宽字符”。 -根问题是: - -- 产品层在直接理解 Windows 的编码转换路径。 -- 这类问题本应收敛在平台适配或标准库层,不应扩散到 feature/runtime。 - -### 2.4 Window message host/dispatcher 承载了过多产品语义 - -已确认: - -- `WindowMessageHost` / `WindowMessageDispatcher` 虽然名义上是宿主消息分发层, - 但接口里直接包含 `EditorContext`、`EditorWindowFrameTransferRequests`、global tab drag 等产品语义。 -- `WindowMessageDispatcher` 直接依赖 `EditorWindow`、shell capture 状态和编辑器窗口交互策略。 - -这说明: - -- 当前宿主消息层不是窄接口。 -- 平台消息调度和编辑器产品交互策略被混在一起。 - -这一项本阶段先做审查定型和边界收紧,不强行一次性全拆完; -但必须把前面三项先处理掉,否则后续消息层根本没法继续收口。 - -## 3. 本阶段收口目标 - -本子计划完成后,应达到以下结果: - -1. 通用多窗口 workspace store 不再位于 `Platform/Win32` 目录。 -2. `ProjectPanel` 不再直接包含或调用 Win32 / Shell API。 -3. `ProjectBrowserModel` 链路不再依赖 Win32 字符串编码工具。 -4. 平台层继续保留 Win32 窗口创建、消息分发、原生调用实现; - 但产品 feature 只通过抽象宿主服务消费系统能力。 -5. 相关测试仍可回归通过。 - -## 4. 执行步骤 - -### Phase A: 通用状态归位 - -1. 把 `EditorWindowWorkspaceStore` 从 `Platform/Win32/WindowManager/` 移出。 -2. 放到更接近 composition / window workspace orchestration 的目录。 -3. 更新所有 include、测试和 CMake 归属。 -4. 保持行为不变,只修正边界与归位。 - -### Phase B: Project feature 脱离系统 API - -1. 新增窄宿主接口,承载: - - 复制文本到剪贴板 - - 在系统文件管理器中显示路径 - - 需要的时间查询能力 - - 需要的双击阈值查询能力 -2. 由 Win32 宿主提供具体实现。 -3. `ProjectPanel` 改为依赖该接口,不再包含 `windows.h` / `shellapi.h`。 - -### Phase C: 编码与路径工具去 Win32 化 - -1. 引入基于标准库的 UTF-8 / `std::filesystem::path` 转换工具。 -2. 把 `ProjectBrowserModel` 链路改为只依赖标准库工具。 -3. 删除 project/runtime 对 `app/Internal/StringEncoding.h` 的依赖。 - -### Phase D: 验证 - -1. 构建 `XCUIEditorAppCore`。 -2. 构建并运行 `editor_app_feature_tests`。 -3. 如有必要,补充受影响单测。 - -## 5. 本阶段不做的事 - -以下内容本轮不一次性展开: - -1. 不强行重写整个 `EditorWindow`。 -2. 不一次性拆穿所有 `WindowMessageDispatcher` 产品语义。 -3. 不扩散到新的功能开发。 -4. 不修改 Win32 宿主行为表现,只做边界收口。 - -## 6. 完成判定 - -满足以下条件,才算本子计划完成: - -1. `EditorWindowWorkspaceStore` 已脱离 `Platform/Win32`。 -2. `ProjectPanel.cpp` 不再包含 `windows.h` / `shellapi.h`。 -3. `ProjectBrowserModel*` 不再依赖 `Internal/StringEncoding.h`。 -4. 构建与回归测试通过。 -## 7. Execution Status 2026-04-19 - -Completed in this round: - -1. Moved `EditorWindowWorkspaceStore` out of `app/Platform/Win32/WindowManager/` - into `app/Composition/` and updated includes, tests, and CMake ownership. -2. Introduced `Host/SystemInteractionHost.h` plus - `Platform/Win32/Win32SystemInteractionHost.*`, and routed `ProjectPanel` - clipboard / file-browser actions through the host abstraction instead of - direct Win32 or Shell calls. -3. Removed `ProjectPanel` dependence on Win32 timing and key constants: - double-click detection now uses `std::chrono::steady_clock`, and escape - handling uses the editor input enum instead of `VK_ESCAPE`. -4. Removed `ProjectBrowserModel` and `ProjectBrowserModelInternal` - dependence on `app/Internal/StringEncoding.h` by switching the project-path - conversion path to `std::filesystem` + UTF-8 helpers. -5. Verified with: - - build target `XCUIEditorAppCore` - - build target `editor_app_feature_tests` - - test filter - `EditorWindowWorkspaceStoreTest.*:ProjectPanelTests.*:ProjectBrowserModelTests.*` - and all 22 tests passed. - -Deferred to the next sub-phase: - -1. `WindowMessageHost` / `WindowMessageDispatcher` still carry editor product - semantics inside the Win32 host message layer. This remains the next root - isolation problem under direction 7, but it is intentionally left out of - this phase to keep the current refactor bounded and behavior-safe. - -## 8. Execution Status 2026-04-19 Phase 2 - -Completed in this round: - -1. Removed the fake `WindowMessageHost` abstraction entirely. `Application` - no longer acts as a product-level message host facade. -2. Moved Win32 window message dispatch ownership into - `EditorWindowManager::TryDispatchWindowMessage(...)`, making the manager the - single editor-window orchestration entry point for native messages. -3. Replaced the old host-layer dispatcher with - `app/Platform/Win32/WindowManager/EditorWindowMessageDispatcher.*`, so the - message dispatcher is now explicitly app-specific and no longer shipped from - `XCUIEditorHost`. -4. Removed the old `app/Platform/Win32/WindowMessageDispatcher.*` files from - the host library source set and updated CMake ownership accordingly. -5. Verified with: - - build target `XCUIEditorAppCore` - - build target `editor_app_feature_tests` - - build target `XCUIEditorApp` - - test filter - `EditorWindowInputRoutingTests.*:EditorWindowWorkspaceStoreTest.*:ProjectPanelTests.*:ProjectBrowserModelTests.*` - and all 28 tests passed - - smoke run of `XCUIEditor.exe` with - `XCUIEDITOR_SMOKE_TEST=1`, `XCUIEDITOR_SMOKE_TEST_FRAME_LIMIT=4`, - `XCUI_AUTO_CAPTURE_ON_STARTUP=0` - and the process exited successfully - -Current direction-7 remaining root issue: - -1. The Win32 message dispatcher is now correctly owned by the editor app - platform layer, but `EditorWindow` itself still directly mixes Win32 window - behavior, composition runtime orchestration, and render-loop control inside - one class. If direction 7 continues, the next real收口 point is - `EditorWindow` responsibility decomposition rather than host/message facade - cleanup. - -## 9. Execution Status 2026-04-19 Phase 3 - -Completed in this round: - -1. Introduced `app/Platform/Win32/EditorWindowRuntimeController.*` as the - dedicated owner of editor-window runtime state, including: - `NativeRenderer`, `D3D12WindowRenderer`, `D3D12WindowRenderLoop`, - `AutoScreenshotController`, title-bar icon texture, `UIEditorWorkspaceController`, - and `EditorShellRuntime`. -2. Removed render/composition ownership from - `app/Platform/Win32/EditorWindowInternalState.h`. `EditorWindowState` now - only keeps platform-window state, input state, and borderless chrome runtime - state. -3. Rewired `EditorWindowLifecycle.cpp`, `EditorWindowFrame.cpp`, - `EditorWindowInput.cpp`, and `EditorWindowTitleBarRendering.cpp` so - `EditorWindow` delegates runtime/render/composition work through - `EditorWindowRuntimeController` instead of directly mutating a mixed state - blob. -4. Verified the boundary cleanup by checking that there are no remaining - `m_state->render` / `m_state->composition` accesses under - `app/Platform/Win32/`. -5. Verified with: - - build target `XCUIEditorAppCore` - - build target `editor_app_feature_tests` - - build target `XCUIEditorApp` - - test filter - `EditorWindowInputRoutingTests.*:EditorWindowWorkspaceStoreTest.*:ProjectPanelTests.*:ProjectBrowserModelTests.*` - and all 28 tests passed - - smoke run of `XCUIEditor.exe` with - `XCUIEDITOR_SMOKE_TEST=1`, `XCUIEDITOR_SMOKE_TEST_FRAME_LIMIT=4`, - `XCUI_AUTO_CAPTURE_ON_STARTUP=0` - and the process exited successfully - -Current direction-7 remaining root issue: - -1. `EditorWindow` no longer owns runtime state, but it still contains both - platform window behavior and part of the frame orchestration policy - (`RenderFrame`, shell-pointer-capture reconciliation, transfer-request - building, chrome/layout interaction decisions). The next root cleanup point - is to separate Win32 host-window mechanics from window-level frame/runtime - orchestration policy. - -## 10. Execution Status 2026-04-19 Phase 4 - -Completed in this round: - -1. Introduced `app/Platform/Win32/EditorWindowFrameOrchestrator.*` to own - shell-frame update/render orchestration policy instead of keeping that logic - on `EditorWindow`. -2. Moved invalid-frame rendering, verbose input tracing, shell-frame result - tracing, and cross-window transfer-request construction out of - `EditorWindow` and into the new orchestrator. -3. Simplified `EditorWindowFrame.cpp` so the window shell now mainly handles - window metrics, draw-data presentation, pointer-capture application, and - Win32-facing frame entry points, while delegating shell-frame policy to the - orchestrator and runtime state to `EditorWindowRuntimeController`. -4. Verified with: - - build target `XCUIEditorAppCore` - - build target `editor_app_feature_tests` - - build target `XCUIEditorApp` - - test filter - `EditorWindowInputRoutingTests.*:EditorWindowWorkspaceStoreTest.*:ProjectPanelTests.*:ProjectBrowserModelTests.*` - and all 28 tests passed - - smoke run of `XCUIEditor.exe` with - `XCUIEDITOR_SMOKE_TEST=1`, `XCUIEDITOR_SMOKE_TEST_FRAME_LIMIT=4`, - `XCUI_AUTO_CAPTURE_ON_STARTUP=0` - and the process exited successfully - -Current direction-7 remaining root issue: - -1. `EditorWindow` is now mostly a Win32 shell, but input-event queueing, - pointer-capture reconciliation, and borderless chrome interaction are still - stored and coordinated from the same class. The next root cleanup point is - to split input/capture routing from the remaining host-window chrome - mechanics. - -## 11. Execution Status 2026-04-19 Phase 5 - -Completed in this round: - -1. Introduced `app/Platform/Win32/EditorWindowInputController.*` as the - dedicated owner of editor-window input state, including modifier tracking, - pending input events, mouse-leave tracking, and pointer-capture ownership. -2. Removed input ownership from - `app/Platform/Win32/EditorWindowInternalState.h`. `EditorWindowState` now - only keeps platform-window state and borderless chrome runtime state. -3. Rewired `EditorWindowLifecycle.cpp`, `EditorWindowInput.cpp`, and - `EditorWindowFrame.cpp` so `EditorWindow` delegates input queueing, - capture-owner mutation, modifier synchronization, and pending-event transfer - through `EditorWindowInputController`. -4. Verified the boundary cleanup by checking that there are no remaining - `m_state->input` accesses under `app/Platform/Win32/`. -5. Verified with: - - build target `XCUIEditorAppCore` - - build target `editor_app_feature_tests` - - build target `XCUIEditorApp` - - test filter - `EditorWindowInputRoutingTests.*:EditorWindowWorkspaceStoreTest.*:ProjectPanelTests.*:ProjectBrowserModelTests.*` - and all 28 tests passed - - smoke run of `XCUIEditor.exe` with - `XCUIEDITOR_SMOKE_TEST=1`, `XCUIEDITOR_SMOKE_TEST_FRAME_LIMIT=4`, - `XCUI_AUTO_CAPTURE_ON_STARTUP=0` - and the process exited successfully - -Current direction-7 remaining root issue: - -1. `EditorWindow` no longer owns runtime state, frame orchestration state, or - input state, but borderless chrome behavior is still spread across - `EditorWindowBorderlessResize.cpp`, `EditorWindowTitleBarInteraction.cpp`, - `EditorWindowTitleBarRendering.cpp`, and window-level methods on - `EditorWindow`. The next root cleanup point is to collapse the remaining - borderless chrome policy into a dedicated controller so `EditorWindow` - becomes a thin Win32 composition shell. - -## 12. Execution Status 2026-04-19 Phase 6 - -Completed in this round: - -1. Introduced `app/Platform/Win32/EditorWindowChromeController.*` as the - dedicated owner of borderless chrome state and host runtime state, - including chrome hover/press state, DPI state, resize state, predicted - client size, maximize/restore placement state, and drag-restore state. -2. Removed chrome ownership from - `app/Platform/Win32/EditorWindowInternalState.h`. `EditorWindowState` now - only keeps platform-window metadata. -3. Rewired lifecycle, input, borderless placement, borderless resize, title - bar interaction, and title bar rendering paths so `EditorWindow` delegates - chrome-state access through `EditorWindowChromeController`. -4. Verified the boundary cleanup by checking that there are no remaining - `m_state->chrome` / `m_state->input` accesses under - `app/Platform/Win32/`. -5. Verified with: - - build target `XCUIEditorAppCore` - - build target `editor_app_feature_tests` - - build target `XCUIEditorApp` - - test filter - `EditorWindowInputRoutingTests.*:EditorWindowWorkspaceStoreTest.*:ProjectPanelTests.*:ProjectBrowserModelTests.*` - and all 28 tests passed - - smoke run of `XCUIEditor.exe` with - `XCUIEDITOR_SMOKE_TEST=1`, `XCUIEDITOR_SMOKE_TEST_FRAME_LIMIT=4`, - `XCUI_AUTO_CAPTURE_ON_STARTUP=0` - and the process exited successfully - -Current direction-7 remaining root issue: - -1. `EditorWindow` now delegates all major state ownership, but borderless - chrome behavior itself is still implemented as distributed window methods. - The next root cleanup point is behavioral, not state ownership: move the - remaining borderless chrome interaction/render policy out of `EditorWindow` - so the class becomes a pure Win32 shell facade. - -## 13. Execution Status 2026-04-19 Phase 7 - -Completed in this round: - -1. Upgraded `app/Platform/Win32/EditorWindowChromeController.*` from a pure - state holder into the unified borderless chrome behavior controller. It now - owns resize behavior, title-bar interaction behavior, placement/maximize - behavior, layout/hit-test behavior, and title-bar chrome rendering behavior. -2. Rewired: - - `EditorWindowBorderlessPlacement.cpp` - - `EditorWindowBorderlessResize.cpp` - - `EditorWindowTitleBarInteraction.cpp` - - `EditorWindowTitleBarRendering.cpp` - so these files now mainly act as thin delegation layers from `EditorWindow` - into `EditorWindowChromeController`. -3. Preserved `EditorWindow` as the Win32-facing facade for message dispatch, - but removed the last major in-class borderless behavior implementation from - it. At this point `EditorWindowState` only stores window metadata, while - runtime/frame/input/chrome state and chrome behavior all live outside the - window shell. -4. Verified with: - - build target `XCUIEditorAppCore` - - build target `editor_app_feature_tests` - - build target `XCUIEditorApp` - - test filter - `EditorWindowInputRoutingTests.*:EditorWindowWorkspaceStoreTest.*:ProjectPanelTests.*:ProjectBrowserModelTests.*` - and all 28 tests passed - - smoke run of `XCUIEditor.exe` with - `XCUIEDITOR_SMOKE_TEST=1`, `XCUIEDITOR_SMOKE_TEST_FRAME_LIMIT=4`, - `XCUI_AUTO_CAPTURE_ON_STARTUP=0` - and the process exited successfully - -Current direction-7 assessment: - -1. No further root state-ownership or behavior-ownership violation remains in - the `EditorWindow` host boundary at the same severity as earlier phases. - The remaining work under this direction is mostly API-surface slimming - around `EditorWindow`'s many delegation methods, which is lower-risk cleanup - rather than a blocking architectural fault. diff --git a/docs/used/NewEditor_UI第九方向测试体系子计划_阶段归档_2026-04-19.md b/docs/used/NewEditor_UI第九方向测试体系子计划_阶段归档_2026-04-19.md deleted file mode 100644 index 39b59e86..00000000 --- a/docs/used/NewEditor_UI第九方向测试体系子计划_阶段归档_2026-04-19.md +++ /dev/null @@ -1,64 +0,0 @@ -# NewEditor UI第九方向测试体系子计划 - -日期:2026-04-19 - -## 一、问题定义 - -当前 `UI` 三层与 `new_editor` 的测试体系已经积累出较多用例,但第九方向的根问题不是“缺少若干测试 case”,而是测试类型、测试边界与架构边界没有对齐: - -1. `tests/UI/Editor/integration` 实际承载的是人工验证场景与截图宿主,不是自动回归测试。 -2. `tests/UI/Editor` 顶层构建对 `new_editor/app` 目录结构存在全局 include 级别的硬耦合,导致纯 `XCUIEditorLib` 测试边界被宿主实现污染。 -3. 多窗口、全局 tab drag、宿主捕获与跨窗口 drop 这条链路只在模型层和局部判定层有覆盖,缺少针对宿主编排关键契约的自动回归保护。 -4. `xcui_editor_app_smoke` 目前只验证进程可启动并渲染少量帧后退出,没有验证主窗口与核心 shell 是否真正进入稳定可用状态。 - -## 二、重构目标 - -本阶段只解决第九方向,不扩散到其他审核方向。目标如下: - -1. 明确拆分“自动回归测试”和“人工验证场景”,从命名、目录、CMake 目标三个层面统一语义。 -2. 消除 `tests/UI/Editor` 顶层对 `new_editor/app` 的全局 include 污染,把 app/host 依赖收束到确实需要它们的目标上。 -3. 为宿主层全局 tab drag / cross-window drop 的关键判定链路建立可自动执行的回归保护。 -4. 将 app smoke 从“进程能起来”提升为“主窗口与 shell 已完成基本装配”。 - -## 三、执行原则 - -1. 不做补丁式修修补补,直接调整测试体系边界。 -2. 不把人工验证 exe 继续伪装成自动测试。 -3. 不让纯 UI 库测试通过顶层 include 偷拿 app 内部头文件。 -4. 新增自动回归时,优先抽出可复用、可单测的宿主判定逻辑,而不是继续把逻辑埋在 Win32 消息处理里。 - -## 四、执行步骤 - -### 阶段 1:测试体系正名与目录重构 - -1. 将 `tests/UI/Editor/integration` 重构为 `tests/UI/Editor/manual_validation`。 -2. 同步更新 Editor 侧 README、场景路径、截图输出路径与 CMake 入口命名。 -3. 将顶层聚合目标改为“自动测试目标”和“人工验证目标”分离,避免继续使用误导性的 `integration_tests` 语义。 - -### 阶段 2:清理测试构建边界 - -1. 移除 `tests/UI/Editor/CMakeLists.txt` 中对 `new_editor/app` 的全局 include。 -2. 将带有 app/host 依赖的测试归并到 app feature test 目标。 -3. 只在 manual validation host 目标上声明 `app` 级 include,避免污染纯 `XCUIEditorLib` 测试。 - -### 阶段 3:补宿主级自动回归保护 - -1. 抽离全局 tab drag / cross-window drop 的纯判定逻辑。 -2. 为 drop target 解析、插入索引、边缘 docking 方向建立 gtest 回归。 -3. 保持 Win32 消息编排只负责调度,不再把关键判定藏在匿名静态函数里。 - -### 阶段 4:增强 app smoke - -1. 在 smoke 模式下显式验证主窗口、workspace shell 与基本 compose 结果已建立。 -2. 对“到帧预算仍未 ready”的情况返回失败退出码。 -3. 保留现有快速退出特性,但把 smoke 结果从“能跑”升级为“已装配完成”。 - -## 五、完成标准 - -满足以下条件,才算第九方向完成: - -1. `tests/UI/Editor` 目录语义与测试职责一致。 -2. `XCUIEditorLib` 纯测试目标不再被 `new_editor/app` 全局 include 污染。 -3. 宿主层全局 tab drag / cross-window drop 至少有一组自动回归保护关键判定。 -4. smoke 在核心 shell 未 ready 时会失败,而不是静默通过。 -5. 相关目标编译通过,新增或调整后的自动测试通过。 diff --git a/docs/used/NewEditor_UI第二阶段依赖方向子计划_阶段归档_2026-04-19.md b/docs/used/NewEditor_UI第二阶段依赖方向子计划_阶段归档_2026-04-19.md deleted file mode 100644 index e9eaec84..00000000 --- a/docs/used/NewEditor_UI第二阶段依赖方向子计划_阶段归档_2026-04-19.md +++ /dev/null @@ -1,227 +0,0 @@ -# NewEditor UI第二阶段依赖方向子计划 -日期:2026-04-19 - -## 目标 - -本阶段只覆盖审核方向中的“依赖方向审核”,从软件工程最佳实践出发,严格审查 `UI基础层 / UI runtime层 / UI editor层 / new_editor` 当前的真实依赖关系,确认依赖是否单向、公开面是否稳定、跨层访问是否被明确约束。 - -本阶段完成后,应得到两类结果: - -- 一份基于真实代码与构建图的依赖问题清单 -- 一套从根源收口依赖方向的整改方案,而不是继续靠局部兼容和路径补丁维持 - -## 范围 - -本阶段聚焦以下对象: - -- `engine/include`、`engine/src` 中被 UI 模块与 `new_editor` 使用到的接口 -- `new_editor/include`、`new_editor/src`、`new_editor/app` -- UI 三层相关的 CMake target 与 link 关系 -- 公开头文件、include path、命名空间、桥接层、适配层 -- `new_editor` 与旧 `editor` 之间是否仍存在隐式耦合 - -本阶段不覆盖: - -- 新功能开发 -- 纯视觉重做 -- 与依赖方向无关的局部交互 bug -- feature 级表现优化 - -## 审核重点 - -### 一、Target 依赖图是否真实表达层次 - -- `XCUIEditorLib`、`XCUIEditorHost`、`XCUIEditorAppLib`、`XCUIEditorApp` 的依赖方向是否符合分层 -- target 的 `PUBLIC / PRIVATE / INTERFACE` 暴露是否正确 -- 是否存在“编译能过,但架构已经反向引用”的目标关系 - -### 二、公开头文件暴露面是否过宽 - -- `include` 下是否仍暴露实现细节、产品语义或临时桥接 -- 公共头是否继续向上游泄漏具体 feature、平台、宿主、旧实现细节 -- 是否存在头文件看似是公共 API,实际只能在当前产品里使用 - -### 三、include path 是否允许跨层直连 - -- 是否仍通过过宽的 include path 允许访问不该访问的内部目录 -- 是否存在“目录分层了,但 include 仍然可以随意穿透”的情况 -- 是否存在 `src` 内部实现被上层直接 include 的情况 - -### 四、命名空间与目录是否表达同一层次 - -- 命名空间是否真实表达所属层,而不是只改了目录没改责任 -- 是否存在挂在通用命名空间下、实则依赖 app/product 语义的代码 -- 是否存在桥接层、compat 层、legacy 层长期停留在错误边界 - -### 五、new_editor 是否仍对旧 editor 或其他内部实现形成隐式耦合 - -- 是否仍有直接源码复用、头文件直连、实现复制后未完成本地化的情况 -- 是否仍依赖旧 editor 的内部约定、类型命名、生命周期或行为假设 -- 若存在旧实现复用,是否已经被真正下沉为正式能力,而不是继续临时借用 - -## 根因导向的检查方法 - -本阶段不接受“看到一个 include 就删一个 include”的补丁式处理,优先从根因定位: - -1. 先画真实 target 依赖图,再看代码依赖是否与其一致 -2. 再看公开 API 面是否逼着上层跨层访问 -3. 再看 include path 与命名空间是否放大了错误依赖 -4. 最后才决定是: - - 缩小公开面 - - 下沉正式接口 - - 上提通用能力 - - 本地化 legacy 逻辑 - - 删除错误桥接 - -## 执行步骤 - -1. 盘点 `new_editor` 与 UI 三层相关 target 的 link 关系、include path、公开头暴露面 -2. 基于代码搜索列出所有跨层访问、旧 editor 耦合、`engine/src` 直连、产品语义泄漏点 -3. 把问题按根因分组,而不是按文件零散罗列 -4. 为每类根因给出正式整改策略: - - 调整 target 依赖 - - 收缩公共头 - - 重放目录与命名空间边界 - - 建立明确桥接契约 - - 将临时 legacy 依赖正式化或彻底移除 -5. 对确定可以立即收口的问题,给出下一轮可执行子任务 - -## 预期产出 - -- 第二方向依赖审核报告 -- 依赖问题的根因分类 -- 下一轮可执行的整改子计划 - -## 验收标准 - -- 能明确说明当前依赖图中哪些部分是健康的,哪些部分是错误的 -- 每个问题都能落到“根因 + 正式解法”,而不是临时规避 -- 能明确回答 `new_editor` 是否还在通过任何形式依赖旧 `editor` 内部实现 -- 能明确回答 UI 三层与 `new_editor` 的公共暴露面是否符合长期演进要求 - -## 严格审查结论 - -### 当前健康项 - -- 当前 `new_editor` 产品代码中,未发现新的 `new_editor -> editor/src` 直接源码依赖 -- 当前 `new_editor` 产品代码中,未发现新的 `new_editor -> engine/src` 直接 include 依赖 -- `new_editor/include/XCEditor` 当前未再暴露 `XCEditor/App/*` 这一类产品层公开头 -- `app/Commands`、`app/State`、`app/Composition` 等产品层头文件当前都留在 `new_editor/app`,没有重新回流到公共 `include` - -### 根因一:`XCUIEditorHost` 与 `XCUIEditorAppLib` 的 target 边界仍然是假的 - -问题现状: - -- `XCUIEditorHost` 与 `XCUIEditorAppLib` 都通过 `${CMAKE_CURRENT_SOURCE_DIR}/app` 作为私有 include 根目录 -- 这导致 `XCUIEditorAppLib` 虽然在 target 依赖上“依赖 Host”,但代码层仍可直接 include Host 实现头,边界没有被真实约束 -- 典型表现: - - `app/Composition/EditorShellRuntime.cpp` 直接 include `Rendering/D3D12/D3D12WindowRenderer.h` 与 `Rendering/Native/NativeRenderer.h` - - `app/Rendering/Assets/BuiltInIcons.h` - - `app/Internal/EmbeddedPngLoader.h` - - `app/Rendering/Viewport/ViewportRenderTargets.h` - - `app/Rendering/Viewport/ViewportHostService.h` - 都直接暴露了 Host/D3D12 实现头或具体实现类型 - -根因判断: - -- 不是“某几个 include 写错了”,而是 Host 与 App 的内部契约没有被单独建模,导致产品层只能直接抓宿主实现头 -- target 分层已经存在,但头文件分层与 include 根没有跟上,所以依赖方向在代码层是松的 - -正式解决方案: - -1. 先建立 App 内部使用的窄 Host 契约前向声明层,禁止 App-facing 头文件继续直接暴露 Host 实现头 -2. 把 App-facing 头中的 Host 具体成员改为前向声明 + 指针/句柄/PIMPL,避免具体实现类型出现在上层可见头中 -3. 下一批继续收口 `app` 目录内部的 include 边界,让 App 不能再默认直连 Host 实现目录 - -执行状态: - -- 已开始执行并完成第一批收口: - - 新增 `new_editor/app/Host/HostFwd.h` - - `BuiltInIcons.h`、`EmbeddedPngLoader.h`、`ViewportRenderTargets.h`、`ViewportHostService.h` 已改为不直接暴露 Host 实现头 - - `ViewportHostService` 已改为通过前向声明与延迟持有方式使用 Host 资源分配器 - -### 根因二:`XCUIEditorLib` 的公共 include path 仍然过宽 - -问题现状: - -- `XCUIEditorLib` 当前把 `${CMAKE_SOURCE_DIR}/engine/include/XCEngine` 作为 `PUBLIC` include path 暴露 -- 这会放大跨层访问面,让依赖方更容易绕开正式头路径,弱化公共 API 的边界 - -根因判断: - -- 这是构建层面对公共暴露面的放宽,不是单个头文件问题 -- 正式公共能力应只通过稳定的 `engine/include` 根暴露,而不是把更深一层目录继续公开 - -正式解决方案: - -1. 删除冗余的 `${CMAKE_SOURCE_DIR}/engine/include/XCEngine` 公共暴露 -2. 要求依赖方统一经由正式 `XCEngine/...` 路径访问 Engine 公共头 - -执行状态: - -- 已执行:`new_editor/CMakeLists.txt` 中已移除该冗余 `PUBLIC` include path - -### 根因三:测试 target 没有保护分层,反而重新放开了错误依赖 - -问题现状: - -- `editor_ui_tests` 当前原本只应验证 `XCUIEditorLib` 的公共层,但其 include path 仍加入了整个 `new_editor` 根目录 -- 这使测试可以直接 include `app/*` -- 实际已出现越层测试: - - `test_editor_window_input_routing.cpp` 直接 include `app/Platform/Win32/EditorWindowPointerCapture.h` - - `test_viewport_object_id_picker.cpp` 直接 include `app/Rendering/Viewport/ViewportObjectIdPicker.h` - -根因判断: - -- 不是“测试写错了两处”这么简单,而是 `editor_ui_tests` 的 target 边界定义本身就错了 -- 当测试 target 可以随便看到 `app`,它就不再能作为 `XCUIEditorLib` 公共边界的保护网 - -正式解决方案: - -1. 从 `editor_ui_tests` 中移除整个 `new_editor` 根目录 include path -2. 把依赖 `app/*` 的测试迁移到 `editor_app_feature_tests` -3. 后续继续以 target 为单位区分“公共层测试”和“产品层测试” - -执行状态: - -- 已执行: - - `editor_ui_tests` 已移除 `${XCENGINE_EDITOR_UI_TESTS_EDITOR_ROOT}` - - `test_editor_window_input_routing.cpp` - - `test_viewport_object_id_picker.cpp` - 已迁移到 `editor_app_feature_tests` - -### 根因四:公共 API 中仍有少量“默认装配策略”与“产品默认值”残留 - -问题现状: - -- `XCEditor/Workspace/*` 里仍保留 `BuildDefaultUIEditorWorkspaceController`、`BuildDefaultUIEditorWindowWorkspaceController`、`BuildDefaultUIEditorWindowWorkspaceSet`、`BuildDefaultUIEditorWorkspaceSession` -- `XCEditor/Shell/UIEditorShellAsset.h` 中仍内嵌 `screenId = "editor.shell"` 这样的默认产品值 - -根因判断: - -- 这部分不是立即破坏依赖方向的最高风险项,但它说明公共层里仍残留默认装配策略与产品默认语义 -- 是否保留这些 API,需要明确回答:它们究竟是稳定的 editor 通用装配能力,还是当前产品的默认策略 - -正式解决方案: - -1. 下一批逐个判定这些 API 的真实归属 -2. 真正通用的保留在公共层,但去掉产品默认字面量 -3. 仅服务当前产品装配的,迁回 `new_editor/app/Composition` - -执行状态: - -- 已纳入下一批整改范围,本轮先不做补丁式挪动 - -## 当前阶段执行项 - -### 已完成 - -1. 收紧 `XCUIEditorLib` 的 Engine 公共 include 暴露面 -2. 收紧 `editor_ui_tests` 的 include 边界,并把 app 级测试迁移到 `editor_app_feature_tests` -3. 为 App-facing 头文件建立 Host 前向声明层,减少 Host 实现头外溢 - -### 下一批继续执行 - -1. 继续收紧 `XCUIEditorHost` 与 `XCUIEditorAppLib` 的内部 include 边界,让 target 边界在代码层也成立 -2. 逐步把 App-facing 头里残留的 Host/D3D12 具体实现类型继续下沉到 `.cpp` 或私有实现 -3. 决定 `BuildDefault*` 与 `EditorShellAsset` 默认值的真实归属,并做正式迁移 diff --git a/docs/used/NewEditor_UI第二阶段依赖方向补充收口计划_阶段归档_2026-04-19.md b/docs/used/NewEditor_UI第二阶段依赖方向补充收口计划_阶段归档_2026-04-19.md deleted file mode 100644 index 0703ee24..00000000 --- a/docs/used/NewEditor_UI第二阶段依赖方向补充收口计划_阶段归档_2026-04-19.md +++ /dev/null @@ -1,216 +0,0 @@ -# NewEditor UI第二阶段依赖方向补充收口计划 - -日期:2026-04-19 - -## 背景 - -这是对 `docs/plan/NewEditor_UI第二阶段依赖方向子计划_2026-04-19.md` 的补充收口说明。 -本补充只覆盖本轮严格审查后确认的两个根因,以及对应的正式解法与本轮落地结果。 - -## 严格审查补充结论 - -### 1. 真正的问题不是“几个 include 写错了”,而是 `new_editor/app` 里混放了两种责任 - -- 一类是 `AppCore` 责任:产品状态、面板逻辑、场景工具、viewport 业务渲染组织。 -- 一类是 `Win32/Host` 组合责任:窗口宿主、D3D12 设备、NativeRenderer、窗口渲染循环。 -- 现在这两类代码虽然目录上看似分层,但仍然通过具体实现头直接耦合,导致 `XCUIEditorAppLib` 的边界仍然偏假。 - -根因判断: - -- 真正缺失的是一层窄契约,导致 `AppCore` 代码只能直接抓 `NativeRenderer`、`D3D12WindowRenderer`、`D3D12ShaderResourceDescriptorAllocator` 这类宿主实现。 -- 只要没有窄契约,后续即使继续删 include,也只是补丁式修补,无法为后续 target 拆分提供稳定基础。 - -正式解法: - -1. 先在 `new_editor/app/Host` 建立窄契约层,把 `AppCore` 真实需要的能力抽出来。 -2. `AppCore` 只依赖窄契约,不再直接依赖 Host 具体实现头。 -3. 在此基础上,再进入下一步 target 收口:把当前 `XCUIEditorAppLib` 继续拆成真正的 `AppCore` 与 `Win32 组合边界`。 - -### 2. 公共 API 中真正存在的产品默认值泄漏,当前只确认到 `EditorShellAsset.screenId` - -- `BuildDefaultUIEditorWorkspaceSession` -- `BuildDefaultUIEditorWorkspaceController` -- `BuildDefaultUIEditorWindowWorkspaceSet` -- `BuildDefaultUIEditorWindowWorkspaceController` - -严格审查结论: - -- 这四个 `BuildDefault*` 当前仍被大量公共层单测、集成测试和 structured shell 装配逻辑直接使用。 -- 从行为上看,它们表达的是 UI workspace 的通用默认装配语义,不是 `new_editor` 产品私有策略。 -- 因此这一批 API 当前不应强行下沉,否则会把通用装配能力误收口成产品实现。 - -真正的问题点是: - -- `XCEditor/Shell/UIEditorShellAsset.h` 的 `screenId = "editor.shell"` 把产品默认值放进了公共结构体默认值里。 - -正式解法: - -1. 公共 `EditorShellAsset` 只保留空默认值。 -2. `"editor.shell"` 只在 `new_editor/app/Composition/EditorShellAssetBuilder.cpp` 这种产品装配位置赋值。 - -## 本轮执行项 - -### 已执行:抽离 Host 窄契约 - -新增: - -- `new_editor/app/Host/TextureHost.h` -- `new_editor/app/Host/ViewportRenderHost.h` -- `new_editor/app/Host/ShaderResourceDescriptorAllocator.h` - -本轮已经把以下 `AppCore` 代码改为依赖窄契约,而不是直接依赖 Host 具体实现: - -- `BuiltInIcons` -- `EmbeddedPngLoader` -- `SceneViewportToolOverlay` -- `SceneViewportController` -- `ViewportRenderTargets` -- `ViewportHostService` -- `EditorShellRuntime` - -本轮效果: - -- feature / rendering / composition 中真正属于 `AppCore` 的代码,已经不再直接 include `NativeRenderer.h` / `D3D12WindowRenderer.h` / `D3D12ShaderResourceDescriptorAllocator.h` -- 当前残留的具体 Host 头依赖,已经主要收缩到真正的 Win32/Host 组合边界: - - `new_editor/app/Platform/Win32/EditorWindowInternalState.h` - - `new_editor/app/Rendering/D3D12/D3D12WindowRenderLoop.h` - - `new_editor/app/Rendering/Native/NativeRenderer.h` - - `new_editor/app/Rendering/Viewport/ViewportHostService.cpp` 中的具体 allocator 构造点 - -### 已执行:收掉公共 API 的产品默认值 - -已调整: - -- `new_editor/include/XCEditor/Shell/UIEditorShellAsset.h` - -当前状态: - -- `EditorShellAsset.screenId` 默认值已改为空 -- `"editor.shell"` 继续只在 `new_editor/app/Composition/EditorShellAssetBuilder.cpp` 中设置 - -## 验证结果 - -使用全新验证构建目录 `build_codex_verify` 完成了本轮验证。 - -已通过: - -- `cmake -S . -B build_codex_verify -G "Visual Studio 17 2022" -A x64 -DXCENGINE_BUILD_XCUI_EDITOR_APP=ON -DFETCHCONTENT_SOURCE_DIR_GOOGLETEST=D:/Xuanchi/Main/XCEngine/build/_deps/googletest-src` -- `cmake --build build_codex_verify --config Debug --target editor_app_feature_tests` -- `build_codex_verify/tests/UI/Editor/unit/Debug/editor_app_feature_tests.exe` - -结果: - -- `82 / 82` 通过 - -## 本轮追加执行结果 - -### 已完成:把 `XCUIEditorAppLib` 正式拆成 `AppCore` 与 Win32 组合层 - -已调整: - -- `new_editor/CMakeLists.txt` -- `tests/UI/Editor/unit/CMakeLists.txt` - -当前 target 结构: - -- `XCUIEditorAppCore` - - 承接 `app/State` - - 承接 `app/Composition` - - 承接 `app/Features` - - 承接 `app/Rendering/Viewport` 与 `app/Rendering/Assets` - - 承接 `app/Project` / `app/Scene` / `app/Internal` - - 承接 `src/App` -- `XCUIEditorAppLib` - - 只承接 `app/Platform/Win32` - - 作为 Win32 产品组合层,依赖 `XCUIEditorAppCore` - -本轮效果: - -- `AppCore` 已经从构建层面独立出来,不再和 Win32 窗口宿主代码混编在同一个 target 中 -- `EditorWindow`、`WindowManager`、`ApplicationBootstrap` 这类窗口组合代码继续保留在最外层产品组合边界 -- `editor_app_feature_tests` 现已显式链接 `XCUIEditorAppCore`,不再把所有产品逻辑都隐式压在 `XCUIEditorAppLib` 一个 target 上 - -### 已完成:补齐 `XCUIEditorApp` 的运行时 DLL 部署规则 - -根因: - -- 干净构建目录下 `xcui_editor_app_smoke` 失败,退出码 `0xc0000135` -- 原因不是代码逻辑错误,而是 `XCUIEditorApp` target 缺少 `assimp` 与 `PhysX` 运行时 DLL 的 post-build 拷贝规则 - -已修复: - -- `new_editor/CMakeLists.txt` - -结果: - -- `XCUIEditor.exe` 输出目录已自动部署: - - `assimp-vc143-mt.dll` - - `PhysXCommon_64.dll` - - `PhysXCooking_64.dll` - - `PhysXFoundation_64.dll` - - `PhysX_64.dll` - - `PVDRuntime_64.dll` - -### 追加验证 - -已通过: - -- `cmake --build build_codex_verify --config Debug --target XCUIEditorApp editor_app_feature_tests` -- `build_codex_verify/tests/UI/Editor/unit/Debug/editor_app_feature_tests.exe` -- `ctest --output-on-failure -C Debug --test-dir build_codex_verify -R '^xcui_editor_app_smoke$'` - -结果: - -- `editor_app_feature_tests`: `82 / 82` 通过 -- `xcui_editor_app_smoke`: 通过 - -### 已完成:把 `EditorWindow` 的传输数据与私有状态正式拆开 - -已调整: - -- `new_editor/app/Platform/Win32/EditorWindow.h` -- `new_editor/app/Platform/Win32/EditorWindowTransferRequests.h` -- `new_editor/app/Platform/Win32/EditorWindowInternalState.h` -- `new_editor/app/Platform/Win32/EditorWindowLifecycle.cpp` -- `new_editor/app/Platform/Win32/EditorWindowFrame.cpp` -- `new_editor/app/Platform/Win32/EditorWindowInput.cpp` -- `new_editor/app/Platform/Win32/EditorWindowBorderlessPlacement.cpp` -- `new_editor/app/Platform/Win32/EditorWindowBorderlessResize.cpp` -- `new_editor/app/Platform/Win32/EditorWindowTitleBarInteraction.cpp` -- `new_editor/app/Platform/Win32/EditorWindowTitleBarRendering.cpp` -- `new_editor/app/Platform/Win32/WindowMessageDispatcher.cpp` -- `new_editor/app/Platform/Win32/WindowManager/Internal.h` -- `new_editor/app/Platform/Win32/WindowManager/Lifecycle.cpp` - -根因与处理: - -- 旧的 `EditorWindowState.h` 同时承载了两类语义: - - 跨窗口/管理层需要消费的传输请求 - - 只属于 `EditorWindow` 实现细节的渲染、输入、shell runtime、chrome 运行时状态 -- 这会让 `EditorWindow.h` 把 `EditorShellRuntime`、`NativeRenderer`、`D3D12WindowRenderer`、`D3D12WindowRenderLoop`、`AutoScreenshot` 等具体实现全部泄漏到 Win32 公开头面。 -- 本轮将 `EditorWindowPanelTransferRequest` 与 `EditorWindowFrameTransferRequests` 独立到轻量头 `EditorWindowTransferRequests.h`。 -- 将窗口私有实现状态收回到 `EditorWindowInternalState.h`,并由 `EditorWindow` 通过 `std::unique_ptr` 私有持有。 - -本轮效果: - -- `EditorWindow.h` 不再直接暴露私有状态聚合,也不再 include `Composition/EditorShellRuntime.h`、`Rendering/Native/NativeRenderer.h`、`Rendering/D3D12/D3D12WindowRenderer.h`、`Rendering/D3D12/D3D12WindowRenderLoop.h` 等宿主实现头。 -- `EditorWindow.h` 进一步收缩为以前向声明为主,不再顺带带出 `XCEditor` 的 shell/workspace/dock 公开头。 -- `WindowManager/Internal.h` 也不再顺带 include `UIEditorWindowWorkspaceController.h` 与 `UIEditorWorkspaceController.h`,这些公开 workspace 类型的完整依赖已回收到真正使用它们的 `WindowManager/Lifecycle.cpp`。 -- 当前 `new_editor/app/Platform/Win32` 头文件中,上述重依赖已只剩 `EditorWindowInternalState.h` 一处持有,Win32 公开头面的边界已经和实现边界对齐。 - -追加验证: - -- `cmake --build build_codex_verify --config Debug --target XCUIEditorApp editor_app_feature_tests` -- `build_codex_verify/tests/UI/Editor/unit/Debug/editor_app_feature_tests.exe` -- `ctest --output-on-failure -C Debug --test-dir build_codex_verify -R '^xcui_editor_app_smoke$'` - -结果: - -- `editor_app_feature_tests`: `82 / 82` 通过 -- `xcui_editor_app_smoke`: 通过 - -## 下一步收口 - -1. 在 `XCUIEditorAppCore` / `XCUIEditorAppLib` 已分离、`EditorWindow` 公开头已收缩的基础上,继续收紧 `target_include_directories`,把当前“目录还可穿透、但代码已不再依赖”的状态彻底变成“构建层也不能穿透”。 -2. 继续审查 `WindowManager` / `Bootstrap` 头面的公开依赖,确认是否还存在类似 `EditorWindow` 这种“实现状态被头面顺带暴露”的问题。 -3. 视下一轮审查结果,再决定是否需要把 `Bootstrap` 从可执行入口中进一步下沉为单独的产品装配 target。 diff --git a/docs/used/NewEditor_UI第五方向输入与交互一致性子计划_阶段归档_2026-04-19.md b/docs/used/NewEditor_UI第五方向输入与交互一致性子计划_阶段归档_2026-04-19.md deleted file mode 100644 index 9cd34317..00000000 --- a/docs/used/NewEditor_UI第五方向输入与交互一致性子计划_阶段归档_2026-04-19.md +++ /dev/null @@ -1,113 +0,0 @@ -# NewEditor UI 第五方向输入与交互一致性子计划 - -## 背景 - -当前 `new_editor` 在单窗口内同时存在多套输入焦点判断: - -- workspace 仍把 `activePanelId` 同时当成“活动 tab”和“输入归属” -- viewport bridge 自己维护 `focused/captured` -- hosted panel 通过 `panelActive` 近似判断是否允许键盘输入 -- dock host 还保留独立的 `focused` 视觉状态 - -这导致输入所有权不是单一真相源,窗口内不同子系统对“谁拥有键盘/焦点”会得出不同结论。 - -## 根因 - -根因不是 Win32 消息入口,而是 `new_editor` 内部缺少一个窗口级 / workspace 级的统一输入所有者模型。 - -结果是: - -- tab 选中状态与输入焦点耦合 -- viewport 与 hosted panel 之间只能靠局部布尔值抢焦点 -- runtime 路由只能依赖 `activePanelId` 做近似分发 -- 交互问题一旦出现,只能继续在各处补特判 - -## 本阶段目标 - -建立统一的 workspace 输入所有者模型,并让以下路径全部基于该模型工作: - -- workspace interaction -- viewport shell / viewport input bridge -- hosted panel runtime update -- dock host 焦点可视状态 - -`activePanelId` 继续只表示 workspace 的活动面板 / 活动 tab,不再承担输入焦点语义。 - -## 重构范围 - -### 1. 建立统一输入所有者模型 - -新增统一输入所有者抽象,至少覆盖: - -- `None` -- `DockHost` -- `HostedPanel(panelId)` -- `Viewport(panelId)` - -该状态只存在于交互态,不进入持久化 workspace model。 - -### 2. 由 workspace 统一解析输入归属 - -在 workspace 交互层统一根据以下信息解析 owner: - -- dock host hit target -- externally hosted panel bounds -- viewport bounds -- focus lost / panel unmount / 隐藏后的归一化 - -不再让 runtime 或各 panel 自己推断当前输入归属。 - -### 3. viewport bridge 改为可被外部 owner 驱动 - -viewport bridge 保留指针捕获、按键跟踪、delta 计算职责,但焦点语义改为: - -- 支持由外部 owner 驱动 focus -- workspace 场景下不再自己发明长期 focus 真相 - -### 4. hosted panel 去掉对 `activePanelId` 的输入依赖 - -Hierarchy / Project / Inspector 的键盘与 focus 过滤改为基于统一 owner,而不是 `panelActive`。 - -相关命名同步收口,避免继续使用会误导含义的参数名。 - -### 5. dock host 焦点可视状态与统一 owner 对齐 - -dock host 的视觉 focus 状态不再作为另一套事实来源存在,而是与统一 owner 对齐。 - -## 执行步骤 - -1. 新增 workspace 输入 owner 抽象与解析逻辑 -2. 将 workspace interaction 输出扩展为可暴露当前 owner -3. 改造 viewport bridge,使 workspace 路径由外部 owner 驱动 focus -4. 改造 runtime update 与 hosted panel 更新签名,移除 `activePanelId == panelId` 输入门控 -5. 补齐单元测试,覆盖 owner 切换、viewport focus、hosted panel 键盘路由 -6. 编译并跑相关 editor 测试,确认没有交互回退 - -## 完成标准 - -- `activePanelId` 不再作为 hosted panel / viewport 的输入焦点真相 -- 同一窗口内存在唯一输入 owner -- viewport 与 hosted panel 的键盘路由都来自统一 owner -- dock host、viewport、hosted panel 的焦点语义一致 -- 相关测试覆盖 owner 切换与交互回归 - -## 当前状态 - -已完成。 - -## 完成说明 - -- 已建立 `UIEditorWorkspaceInputOwner`,由 workspace interaction 统一解析并维护当前输入 owner -- `activePanelId` 已退回为 tab / 活动面板状态,不再承担输入焦点语义 -- viewport bridge 已支持外部 focus owner 驱动,workspace 路径下不再自持长期 focus 真相 -- Hierarchy / Project / Inspector 已改为基于 `PanelInputContext` 与统一 owner 路由输入 -- hosted panel 焦点切换已补齐 synthetic `FocusGained` / `FocusLost` 事件 -- dock host 的可视 focus 已对齐到统一 owner - -## 验证结果 - -- `cmake -S . -B build_codex_verify` -- `cmake --build . --config Debug --target editor_ui_tests` -- `cmake --build . --config Debug --target editor_app_feature_tests` -- `build_codex_verify/tests/UI/Editor/unit/Debug/editor_ui_tests.exe` 通过,`345 / 345` 通过 -- `build_codex_verify/tests/UI/Editor/unit/Debug/editor_app_feature_tests.exe` 通过,`89 / 89` 通过 diff --git a/docs/used/NewEditor_UI第八方向文件夹结构与命名子计划_阶段归档_2026-04-19.md b/docs/used/NewEditor_UI第八方向文件夹结构与命名子计划_阶段归档_2026-04-19.md deleted file mode 100644 index 472d5256..00000000 --- a/docs/used/NewEditor_UI第八方向文件夹结构与命名子计划_阶段归档_2026-04-19.md +++ /dev/null @@ -1,92 +0,0 @@ -# NewEditor UI第八方向文件夹结构与命名子计划 - -日期:2026-04-19 - -## 目标 - -针对 `new_editor` 在文件夹结构、目标归属、公共头暴露面与命名语义上的根问题做正式收口,确保目录结构能够真实表达架构,而不是继续承载历史迁移痕迹与职责漂移。 - -本计划只覆盖第八方向,不扩散到状态模型、输入模型或具体 feature 行为修复。 - -## 根问题 - -### 1. app 与 lib 的源码归属错位 - -- `new_editor/src/App` 中存在明显的 app-only 实现,但 `src` 应只承载 `XCUIEditorLib` 的内部实现。 -- 这会让 `src` 和 `app` 的边界失真,后续继续放置文件时没有稳定规则。 - -### 2. 公共头中混入装配逻辑 - -- `include/XCEditor` 应暴露稳定契约,不应继续承载产品装配细节或内联构建逻辑。 -- 这类逻辑进入公共头后,会放大 include 面并削弱层次边界。 - -### 3. app/State 目录职责漂移 - -- `app/State` 目前不只承载 session、selection、focus 这类状态对象,还承载了带强装配语义的 `EditorContext`。 -- 目录名与真实职责不一致,继续演进会让状态层再次膨胀成“什么都能放”的目录。 - -### 4. Legacy 命名仍在主路径中 - -- 现役主链中仍保留 `Legacy` 命名,会持续模糊“正式实现”和“迁移桥接”的边界。 -- 如果该实现已是正式方案,就必须去 `Legacy`;如果只是临时桥,则必须显式标注退出路径。 - -### 5. 词汇表不稳定 - -- `Host`、`Runtime`、`Bridge`、`Shell` 等词在不同目录中承担了不同强度的语义。 -- 若不先收拢命名语法,目录收口后仍会反复退化。 - -## 执行原则 - -- 不靠补丁式兼容层维持现状。 -- 不引入 `new_editor -> editor` 反向依赖。 -- 优先修正目录与 target ownership,再收公共 API 暴露面。 -- 每一步都必须让终态更清晰,而不是增加新的过渡名词。 - -## 分阶段计划 - -### 阶段 1:源码归属归位 - -- 将 `src/App` 中的 app-only 实现迁回 `app` 下的真实职责目录。 -- 同步修正 `CMakeLists.txt` 中对应 source ownership。 -- 验证 `XCUIEditorAppCore` 与相关测试目标仍然通过。 - -### 阶段 2:公共头装配逻辑下沉 - -- 收缩 `include/XCEditor/Shell/UIEditorStructuredShell.h`。 -- 将内联装配实现下沉到 `src`,只保留稳定声明。 -- 保持测试语义不变,但让公共头不再承载具体 build 过程。 - -### 阶段 3:app 状态目录归位 - -- 重新定义 `app/State` 的合法内容,只保留状态对象与轻状态服务。 -- 将 `EditorContext` 迁移到更准确的 application/composition 归属位置。 -- 修正其引用路径,避免继续把编排器挂在 `State` 名下。 - -### 阶段 4:Legacy 主链命名正式化 - -- 审查 `Scene` 主链中仍保留的 `Legacy` 命名。 -- 若实现已成为现役主路径,则做正式命名替换。 -- 若仍为临时桥,则补齐显式退出路径并限制传播范围。 - -### 阶段 5:目录词汇表统一 - -- 为 `Host`、`Runtime`、`Bridge`、`Shell`、`Controller`、`Service` 建立稳定语义。 -- 只在语义已经明确的前提下做命名替换,避免“只改名不改边界”。 - -## 本轮执行范围 - -本轮先执行阶段 1 与阶段 2,并在编译验证后评估是否直接进入阶段 3。 - -## 当前进展 - -- 已完成阶段 1:`src/App` 中的 app-only 实现已迁回 `app/Commands` 与 `app/State`,并同步修正 CMake source ownership。 -- 已完成阶段 2:`UIEditorStructuredShell` 的装配实现已从公共头下沉到 `src/Shell`。 -- 已完成阶段 3:`EditorContext` 已从 `app/State` 迁移到 `app/Composition`,`app/State` 的目录语义已恢复为状态与轻状态服务。 -- 已完成阶段 4:Scene 现役 gizmo 主链已从 `Legacy*` 命名和 `app/Legacy` 目录中迁出,正式落到 `Features/Scene/SceneViewportTransformGizmo*`。 -- 已完成阶段 5:原 `app/Host` 接口层已重构为 `app/Ports`,抽象端口与 concrete host 实现完成词汇分离,`Host` 不再同时表示接口与实现。 - -## 当前结论 - -- 本子计划当前范围已完成。 -- `new_editor` 的目录归属、公共头装配边界、`app/State` 语义、Scene gizmo 正式命名与接口端口词汇边界均已收口。 -- 后续如果继续沿第八方向深入,应只做增量命名统一,不再需要重复处理同级别结构根问题。 diff --git a/docs/used/NewEditor_UI第四方向状态模型子计划_阶段归档_2026-04-19.md b/docs/used/NewEditor_UI第四方向状态模型子计划_阶段归档_2026-04-19.md deleted file mode 100644 index 3bbcb6ce..00000000 --- a/docs/used/NewEditor_UI第四方向状态模型子计划_阶段归档_2026-04-19.md +++ /dev/null @@ -1,132 +0,0 @@ -# NewEditor UI 第四方向状态模型子计划 - -日期:2026-04-19 - -状态:已完成 - -## 目标 - -本子计划只覆盖 `NewEditor UI 模块三层与 new_editor 实现审核` 中的第四个审核方向:状态模型。 - -目标不是继续靠局部补丁修交互,而是从根源上收口 `new_editor` 的状态组织方式,解决以下四类问题: - -1. 全局选择状态存在多真源。 -2. 命令焦点被错误地建模为 `workspace.activePanelId` 的派生物。 -3. 指针交互所有权分散在 host、shell、viewport、feature 多层。 -4. 多窗口 workspace 以每个窗口的 controller 副本为基础,缺少中心真源。 - -## 根因结论 - -### 1. 全局选择没有 editor 级唯一真源 - -`EditorSession`、`EditorProjectRuntime`、`EditorSceneRuntime` 都在维护“当前选择”,`Inspector` 再通过来源竞争去决定显示谁。这不是同步时机问题,而是状态主模型缺失问题。 - -### 2. 命令焦点被错误绑定到布局激活状态 - -`workspace.activePanelId` 本质是布局状态,不是真实命令焦点。继续用它驱动 `edit.*` 路由,会让 viewport、property grid、rename、context menu 等真实交互所有者无法成为正式命令焦点源。 - -### 3. 指针 owner 没有统一模型 - -host capture、shell capture、hosted content capture、viewport capture、scene tool capture 各自维护状态,最后再靠布尔值拼接。结果就是 capture 生命周期分裂,冲突只能靠事件过滤硬挡。 - -### 4. 多窗口 workspace 没有中心 store - -原实现把每个 `EditorWindow` 里的 `UIEditorWorkspaceController` 当作活的权威副本,再由窗口协调层从这些窗口副本反向拼出 `UIEditorWindowWorkspaceSet`。这会让“窗口投影”和“全局真源”混成一层,分离、合并、关闭窗口都只能通过重建和回灌状态维持正确性。 - -## 重构原则 - -1. 先建立 editor 级真源,再把窗口、runtime、UI 降级成投影或领域解释层。 -2. 不再接受双向同步、stamp 竞争、布尔拼接这类维持性方案。 -3. 任何“当前状态”都必须能明确回答: - - 谁是唯一写入者? - - 谁只是投影或缓存? -4. 每次收口都要让最终结构更清晰,而不是再引入新的兼容层长期滞留。 - -## 执行结果 - -### 阶段 1:统一全局选择真源 - -已完成。 - -落地结果: - -- 新增 `EditorSelectionService`,作为 editor 级唯一选择真源。 -- `EditorProjectRuntime` 与 `EditorSceneRuntime` 在 app 模式下绑定共享选择服务;测试或独立场景下才使用各自回退状态。 -- `EditorSession.selection` 降级为投影快照,不再作为权威来源。 -- `InspectorSubject` 不再在 project/scene 之间做 stamp 竞争,而是直接消费统一选择快照。 -- `WorkspaceEventSync` 改为从选择服务同步会话投影。 - -### 阶段 2:建立独立命令焦点服务 - -已完成。 - -落地结果: - -- 新增 `EditorCommandFocusService`。 -- `EditorHostCommandBridge` 的 `edit.*` 路由先读取命令焦点服务,`activePanelId` 只保留为兜底投影。 -- `HierarchyPanel`、`ProjectPanel`、`InspectorPanel`、`SceneViewportController` 在真实交互发生时显式声明命令焦点。 -- `EditorSession.activeRoute` 改为命令焦点投影,不再由 workspace 同步逻辑直接写入。 - -### 阶段 3:统一指针交互 owner - -已完成。 - -落地结果: - -- 新增 `EditorShellPointerInteraction`,把 shell 层输出统一成单一 owner 语义。 -- `EditorShellRuntime` 统一对外暴露当前指针 owner。 -- `EditorWindow` 的 Win32 capture 投影改为只根据 shell runtime 的当前 owner 应用,不再同时拼接 shell 结果和 hosted-content 请求。 - -### 阶段 4:收口多窗口 workspace 真源 - -已完成。 - -落地结果: - -- 新增 `EditorWindowWorkspaceStore`,作为多窗口 workspace 的中心状态源。 -- `EditorWindowManager::CreateEditorWindow()` 创建窗口后立即把窗口投影注册进中心 store。 -- `EditorWindowHostRuntime::RenderAllWindows()` 在每帧渲染后把窗口内产生的 workspace/session 变更回写到中心 store。 -- `EditorWindowWorkspaceCoordinator` 不再从 open windows 反向重建 `UIEditorWindowWorkspaceSet`。 -- detach / drag-merge / dock 这类跨窗口操作改为: - 1. 从中心 store 构造 mutation controller。 - 2. 直接在中心 window set 上执行操作。 - 3. 把结果同步回各窗口投影。 - 4. 成功后再提交回中心 store。 -- 窗口销毁时,中心 store 会移除对应窗口状态;主窗口销毁则清空整套多窗口状态。 -- `EditorWindow` 对外不再暴露可变 `workspace controller`,窗口内 controller 进一步明确为内部可变投影,而不是外部可写权威对象。 - -## 验收结论 - -当前验收结论: - -1. 多窗口状态不再通过“从窗口副本反向拼全局状态”的方式工作。 -2. 跨窗口操作已经直接作用于中心 store,再把结果投影回窗口。 -3. 窗口内局部布局变更会在渲染后回写中心 store,窗口 controller 不再承担长期权威职责。 -4. 状态模型四个阶段已经全部落地。 - -## 测试结果 - -已验证: - -- `cmake --build build_codex_verify --config Debug --target editor_app_feature_tests` -- `build_codex_verify/tests/UI/Editor/unit/Debug/editor_app_feature_tests.exe` - -结果: - -- 89 tests from 14 test suites -- all passed - -本轮新增覆盖: - -- `test_editor_window_workspace_store.cpp` - -覆盖点包括: - -- 主窗口投影注册进入中心 store -- 窗口内局部变更回写中心 store -- 跨窗口 mutation 基于中心 store 执行 -- 移除 detached window 后自动修复 active window 引用 - -## 归档建议 - -该子计划已经完成,可以归档到 `docs/used`。 diff --git a/docs/used/NewEditor_VectorFieldFamilyRootDedupPlan_完成归档_2026-04-22.md b/docs/used/NewEditor_VectorFieldFamilyRootDedupPlan_完成归档_2026-04-22.md deleted file mode 100644 index 97da3112..00000000 --- a/docs/used/NewEditor_VectorFieldFamilyRootDedupPlan_完成归档_2026-04-22.md +++ /dev/null @@ -1,213 +0,0 @@ -# NewEditor Vector Field Family Root Dedup Plan - -Date: 2026-04-22 -Owner: Codex -Scope: `new_editor` vector field family only - -## Background - -`new_editor` currently carries three parallel vector field families: - -- `UIEditorVector2Field*` -- `UIEditorVector3Field*` -- `UIEditorVector4Field*` - -The duplication is not limited to one call site. It exists in four stacked layers: - -1. Field widget types and widget implementation -2. Field interaction state machine implementation -3. PropertyGrid vector adapters -4. PropertyGrid style adapters - -This means the real root problem is not one repeated helper function. The root problem is that one vector-field capability was modeled as three separate families, so every upper layer had to copy itself three times. - -## Objective - -Fix the vector field family from the root without damaging behavior, architecture, or existing call sites. - -The target state is: - -- one shared vector field widget core -- one shared vector field interaction core -- thin `Vector2/3/4` compatibility wrappers at the public API boundary -- reduced duplicate adapter logic in PropertyGrid/style layers - -## Non-Goals - -This plan will not: - -- change panel refresh architecture -- change PropertyGrid field kind semantics -- merge `Vector2/3/4` external command/event meanings -- redesign unrelated field families -- rename public editor commands - -## Constraints - -1. External behavior must stay stable. -2. Existing `Vector2/3/4` entry points must remain callable. -3. No fake abstraction that increases total code. -4. Each phase must compile `XCUIEditorApp`. -5. Each phase must run a startup-level smoke test. - -## Strategy - -Use a bottom-up unification strategy. - -Do not start from `PropertyGrid`. - -Start from the vector field family core, because that is where the duplication originates. Once the core is unified, upper-layer duplication can be removed safely instead of being papered over with more traits and wrappers. - -## Phase A: Shared Widget Core - -### Goal - -Collapse `UIEditorVector2Field.cpp`, `UIEditorVector3Field.cpp`, and `UIEditorVector4Field.cpp` onto one shared implementation core while preserving the public `Vector2/3/4` function names. - -### Planned Work - -1. Introduce one shared internal vector-field widget core for: - - metrics resolution - - palette resolution - - component number-spec projection - - row/component color resolution - - point-inside test - - normalize/parse/format plumbing - - layout construction - - hit testing - - background/foreground drawing -2. Keep `UIEditorVector2Field.*`, `UIEditorVector3Field.*`, `UIEditorVector4Field.*` as compatibility shells over the shared core. -3. Preserve all existing public names: - - `BuildUIEditorVector2FieldLayout` - - `BuildUIEditorVector3FieldLayout` - - `BuildUIEditorVector4FieldLayout` - - `AppendUIEditorVector2Field*` - - `AppendUIEditorVector3Field*` - - `AppendUIEditorVector4Field*` - -### Guardrails - -1. Axis colors must remain dimension-correct: - - `X`, `Y`, `Z`, `W` -2. Existing palette member layout and public signatures must remain intact. -3. No PropertyGrid logic changes in this phase. - -### Validation Gate - -- `cmake --build build --config Debug --target XCUIEditorApp` -- smoke launch `build/new_editor/Debug/XCUIEditor.exe` - -## Phase B: Shared Interaction Core - -### Goal - -Collapse `UIEditorVector2FieldInteraction.cpp`, `UIEditorVector3FieldInteraction.cpp`, and `UIEditorVector4FieldInteraction.cpp` onto one shared interaction core while preserving public `UpdateUIEditorVector[2/3/4]FieldInteraction` entry points. - -### Planned Work - -1. Introduce one shared internal interaction core for: - - selection movement - - begin/commit/cancel edit - - step application - - drag application - - focus handling - - pointer hit processing - - keyboard handling - - frame result accumulation -2. Keep per-dimension public wrappers thin. -3. Preserve current behavior for: - - active component tracking - - focused/editing transitions - - commit rejection - - drag sensitivity - - hit target propagation - -### Guardrails - -1. No semantic merging with unrelated editable-field families. -2. No behavior rewrite of keyboard/pointer policy. -3. No change to public result payloads. - -### Validation Gate - -- `cmake --build build --config Debug --target XCUIEditorApp` -- smoke launch `build/new_editor/Debug/XCUIEditor.exe` - -## Phase C: PropertyGrid And Style Adapter Closure - -### Goal - -Remove the adapter duplication that only exists because the vector family used to be triplicated. - -### Planned Work - -1. Deduplicate PropertyGrid vector spec builders where safe. -2. Deduplicate PropertyGrid vector interaction traits where safe. -3. Deduplicate vector style adapter builders for metrics/palette hosting where safe. -4. Keep `UIEditorPropertyGridFieldKind::{Vector2, Vector3, Vector4}` unchanged. - -### Guardrails - -1. Do not invent a fake shared rename/state-machine layer. -2. Do not collapse `PropertyGrid` storage semantics that are still legitimately field-kind specific. -3. Only deduplicate logic that becomes structurally identical after Phase A and B. - -### Validation Gate - -- `cmake --build build --config Debug --target XCUIEditorApp` -- smoke launch `build/new_editor/Debug/XCUIEditor.exe` - -## Phase D: Closure Review And Final Reduction - -### Goal - -Do one final review pass on the new shared vector core and the remaining PropertyGrid vector adapters, and only keep changes that still reduce code after the root unification is already in place. - -### Planned Work - -1. Recheck the new shared widget/interaction headers for mechanical repetition that can be reduced without adding another abstraction layer. -2. Recheck remaining `PropertyGrid` vector helpers and keep only reductions that are obviously net-negative in code size. -3. Stop immediately if the remaining duplication is only API-boundary compatibility code. - -### Guardrails - -1. Do not reopen panel architecture, refresh policy, or tree code. -2. Do not invent a second-round trait layer just to compress wrappers. -3. If the remaining code is already the thinnest safe compatibility shell, declare closure instead of forcing more edits. - -### Validation Gate - -- `cmake --build build --config Debug --target XCUIEditorApp` -- smoke launch `build/new_editor/Debug/XCUIEditor.exe` - -## Expected Outcome - -After completion: - -- vector field widget logic exists once instead of three times -- vector field interaction logic exists once instead of three times -- upper-layer vector adapters become thinner -- code volume drops materially instead of shifting sideways - -## Stop Conditions - -Stop and reassess if any of the following happens: - -1. public API compatibility starts requiring broader engine/editor rewiring -2. behavior drift appears in focus/edit/drag handling -3. total code starts increasing instead of decreasing - -## Deliverables - -1. shared vector field core implementation -2. shared vector field interaction implementation -3. reduced PropertyGrid/style duplication -4. final closure review of remaining vector-family duplication -5. per-phase build and smoke validation - -## Execution Status - -- Phase A: completed -- Phase B: completed -- Phase C: completed -- Phase D: completed diff --git a/docs/used/NewEditor_Win32NoOpRedundancyCleanupPlan_完成归档_2026-04-23.md b/docs/used/NewEditor_Win32NoOpRedundancyCleanupPlan_完成归档_2026-04-23.md deleted file mode 100644 index b8c6e15a..00000000 --- a/docs/used/NewEditor_Win32NoOpRedundancyCleanupPlan_完成归档_2026-04-23.md +++ /dev/null @@ -1,56 +0,0 @@ -# NewEditor Win32 No-Op Redundancy Cleanup Plan - -日期:2026-04-23 - -## 目标 - -在 `new_editor/app/Platform/Win32` 内只清理可证明为 no-op 的真冗余代码,要求: - -- 必须减少代码量 -- 不改变现有 Win32 生命周期、即时帧、分路径刷新机制 -- 每个阶段完成后立即编译 `XCUIEditorApp` -- 每个阶段完成后立即执行 `new_editor` 启动/关闭冒烟 - -## 冗余判定 - -本次只处理两类已经确认的 no-op 冗余: - -1. `EditorWindowContentController` 接口中本就具备天然默认值的可选能力,被强制下发到 `EditorStandaloneUtilityWindowContentController`,形成一大片空 override 样板。 -2. `EditorUtilityWindowCoordinator` 中完全不产生行为的空接口: - - `RegisterExistingWindow` - - `RefreshWindowPresentation` - 以及它们在 host runtime / message dispatcher / window manager 中对应的空调用链。 - -## Phase A - -下沉 `EditorWindowContentController` 的可选默认行为,删除 utility content controller 的空 override / 空实现,只保留 utility 窗口真正需要的行为。 - -验证: - -- 编译 `XCUIEditorApp` -- 启动 `XCUIEditor.exe` -- 观察初始化与关闭日志是否正常 - -## Phase B - -删除 `EditorUtilityWindowCoordinator` 的空注册 / 空 presentation API 与所有调用点,只保留真正处理 utility window transfer request 的路径。 - -验证: - -- 编译 `XCUIEditorApp` -- 启动 `XCUIEditor.exe` -- 观察初始化与关闭日志是否正常 - -## 收口 - -- 更新计划状态 -- 将完成计划归档到 `docs/used` - -## 完成结果 - -- Phase A 完成:`EditorWindowContentController` 下沉了可选默认行为,`EditorStandaloneUtilityWindowContentController` 删除了大段空 override / 空实现。 -- Phase B 完成:`EditorUtilityWindowCoordinator` 的空注册 / 空 presentation API 及对应空调用链已经删除。 -- 两次验证均通过: - - `cmake --build build --config Debug --target XCUIEditorApp` - - 启动 `build/new_editor/Debug/XCUIEditor.exe` - - 运行日志出现新的 `initialize end` 与 `shutdown end` 闭环 diff --git a/docs/used/NewEditor_WindowWorkspaceSingleSourceRefactorPlan_完成归档_2026-04-22.md b/docs/used/NewEditor_WindowWorkspaceSingleSourceRefactorPlan_完成归档_2026-04-22.md deleted file mode 100644 index e6c8de71..00000000 --- a/docs/used/NewEditor_WindowWorkspaceSingleSourceRefactorPlan_完成归档_2026-04-22.md +++ /dev/null @@ -1,291 +0,0 @@ -# NewEditor Window Workspace Single-Source Refactor Plan - -Date: 2026-04-22 -Status: Completed - -## Progress - -Completed on 2026-04-22: - -1. `EditorWindowWorkspaceCoordinator::BuildWorkspaceMutationController()` now builds mutation input from live `EditorWindow` controllers at call time -2. `EditorWindowHostRuntime::RenderAllWindows()` no longer performs per-frame workspace projection commit -3. detached window title refresh now runs as an explicit live-window frame step instead of piggybacking on projection -4. `EditorWindowWorkspaceStore` no longer persists mirrored `workspace/session`; it is reduced to panel-registry-backed validation only -5. dead projection / mirrored-workspace bridge APIs were removed from `EditorWindowWorkspaceStore` -6. close-path workspace description now derives from live windows instead of the old store mirror -7. `XCUIEditorApp` Debug rebuild passed after the final mirror-removal changes -8. step-1 manual verification already passed before the final mirror-removal phase: - - detach - - re-dock - - open / close baseline behavior - - detached title baseline behavior - -Archived on 2026-04-22: - -1. final detached-window / re-dock / close-path verification has been accepted -2. the refactor no longer depends on per-frame mirrored workspace projection -3. the remaining unrelated editor follow-up bugs are handled outside this plan scope -## 1. Objective - -这份计划只处理 `new_editor` 当前最严重的一处结构性冗余: - -1. 每个 `EditorWindow` 已经各自持有一份 live `UIEditorWorkspaceController` -2. `EditorWindowWorkspaceStore` 又额外持有一份完整的 `UIEditorWindowWorkspaceSet` -3. 宿主层每帧把前者整份投影回后者 -4. 跨窗口操作再从后者反向重建 controller 灌回窗口 - -本计划的目标不是“看起来更抽象”,而是把窗口 workspace 的单一事实源收回到 live window,自根源消除双写状态同步环,同时不破坏以下现有行为: - -1. 分离窗口打开与关闭 -2. Tab 跨窗口拖拽 -3. 面板 detach / re-dock -4. detached tool window 标题与尺寸策略 -5. 现有 `UIEditorWindowWorkspaceController` 上的跨窗口变换语义 - -## 2. Confirmed Problem - -当前重复链路已经形成完整闭环: - -1. live state: - - `EditorWindowRuntimeController::m_workspaceController` -2. mirrored state: - - `EditorWindowWorkspaceStore::m_windowSet` -3. frame-time projection: - - `EditorWindowHostRuntime::RenderAllWindows(...)` - - `EditorWindowWorkspaceCoordinator::CommitWindowProjection(...)` -4. mutation-time reverse application: - - `EditorWindowWorkspaceCoordinator::BuildWorkspaceMutationController()` - - `EditorWindowWorkspaceCoordinator::SynchronizeWindowsFromWindowSet(...)` - -这不是普通 cache,而是双向同步的第二套真相源。 - -## 3. Root Cause - -根因不在某一个函数,而在宿主层把“跨窗口协调”实现成了“跨窗口状态镜像”。 - -当前设计默认: - -1. window 内的 live controller 负责真实交互与更新 -2. store 内的 window set 负责跨窗口 mutation 的输入 -3. 二者通过每帧投影保持近似一致 - -这导致: - -1. 同一语义的 workspace/session 状态有两份 owner -2. 每帧发生整份复制和整套验证 -3. mutation 逻辑被迫依赖 mirror,而不是直接面向 live windows -4. 标题刷新、窗口集合描述、关闭路径,也被顺带绑在 mirror 上 - -## 4. Why This Is The Most Serious Redundancy - -这处问题同时命中四个严重特征: - -1. 同一职责重复实现 - - live window controller 和 mirrored window set 都在表达同一份窗口 workspace 状态 -2. 同一帧重复计算 - - 每帧 `CommitWindowProjection(...)` 都会复制 `m_windowSet`、覆写某个窗口状态、再验证整套 window set -3. 结构扩散 - - 打开窗口、关闭窗口、跨窗口拖拽、标题刷新都被迫围绕 mirror 组织 -4. 没有独立 owner - - `EditorWindowWorkspaceStore` 并没有独立业务语义,只是在宿主层替 live state 保存一份副本 - -## 5. Red Lines - -这次重构明确禁止以下做法: - -1. 只删除 `CommitWindowProjection(...)`,但继续让 mutation 从旧 store 读数据 -2. 先删 store,再临时把跨窗口逻辑散落回 `EditorWindowWorkspaceCoordinator` -3. 改写 `UIEditorWindowWorkspaceController` 的跨窗口变换语义 -4. 修改 dock / transfer / extract 的业务规则,只为了配合结构调整 -5. 以“后面再补验证”为理由跳过 detached window / global tab drag 的回归验证 - -其中第 1 条最危险。它一定会造成功能回归,因为当前 detached 窗口标题刷新和 mutation 输入都还绑在 projection 路径上。 - -## 6. Target End State - -目标终态必须满足: - -1. live `EditorWindow` 持有的 `UIEditorWorkspaceController` 成为唯一可变真相源 -2. 跨窗口 mutation 需要的 `UIEditorWindowWorkspaceSet` 改为按需从 live windows 现采样构建 -3. `EditorWindowWorkspaceStore` 不再保存整份 `workspace/session` -4. detached 窗口标题刷新从 projection 逻辑中独立出来 -5. primary / active / existing window 集合信息只保留真正被消费的最小元数据 - -允许两种落地形式: - -1. 保留 `EditorWindowWorkspaceStore`,但只保留窗口元数据和注册信息,不再保存 workspace/session -2. 完全删除 `EditorWindowWorkspaceStore`,由 coordinator 直接从 host runtime 构建 snapshot - -## 7. Refactor Strategy - -### Phase A. Freeze semantics and add live snapshot builder - -先新增从 live windows 构建 `UIEditorWindowWorkspaceSet` 的单一路径,例如: - -1. `BuildLiveWindowWorkspaceSnapshot()` -2. 或 `BuildWorkspaceMutationControllerFromLiveWindows()` - -要求: - -1. snapshot 必须完整覆盖所有活跃窗口 -2. 每个窗口的 workspace/session 直接来自 live controller -3. primary window id 必须来自 live host state -4. active window id 只保留当前真实需要的最小语义 - -这一步先增加,不删旧路径。 - -### Phase B. Make mutation read from live snapshot instead of store mirror - -把以下入口改为依赖 live snapshot: - -1. `BuildWorkspaceMutationController()` -2. `TryStartGlobalTabDrag(...)` -3. `TryProcessDetachRequest(...)` -4. `TryProcessOpenDetachedPanelRequest(...)` -5. cross-window drop commit path - -要求: - -1. mutation 前只从 live windows 取状态 -2. mutation 后仍复用 `SynchronizeWindowsFromWindowSet(...)` 回灌窗口 -3. 在这一阶段仍允许 store 暂时存在,但不能再是 mutation 输入源 - -### Phase C. Split title refresh from projection - -当前 `RefreshWindowTitle(...)` 被挂在 `CommitWindowProjection(...)` 后面,这是一种错误耦合。 - -必须单独拆出标题同步路径: - -1. detached 窗口标题直接基于 live controller 刷新 -2. 标题刷新继续允许 frame-time 执行 -3. 但标题刷新不能再隐含 workspace mirror commit - -完成后,删除“为了标题而保留每帧 projection”的借口。 - -### Phase D. Remove mirrored workspace/session from store - -在 mutation 输入已经切换到 live snapshot 后: - -1. 删除 `EditorWindowWorkspaceStore` 中的 `workspace/session` 镜像 -2. 删除 `BuildWindowState(...)` 这类整份复制逻辑 -3. 删除每帧 `CommitWindowProjection(...)` -4. 删除围绕 projection 的整套验证与回退路径 - -如果此时 store 还存在,它只能保存: - -1. 已注册窗口 id -2. primary window id -3. 可能仍有意义的轻量元数据 - -## 8. Functional Risk Analysis - -这次重构不是零风险,但风险集中且可控。 - -### 8.1 Real risk: title synchronization - -如果直接删除每帧 projection,而不拆标题刷新: - -1. detached 窗口标题可能停留在旧 active panel -2. 同一窗口内切 tab 后标题可能不再更新 - -因此标题刷新必须先和 projection 解耦,再删 projection。 - -### 8.2 Real risk: mutation input staleness - -如果 mutation 仍从 store 读状态,而 store 不再每帧同步: - -1. detach 可能从旧 workspace 提取 panel -2. cross-window drop 可能基于过期 session 判断 panel owner -3. open detached panel 可能错误判断某 panel 已经打开或未打开 - -因此必须先引入 live snapshot builder,再切 mutation 输入。 - -### 8.3 Medium risk: lifecycle and close path assumptions - -当前 `IsPrimaryWindowId(...)`、`DescribeWindowSet()`、`RemoveWindowProjection(...)` 都依赖 store。 - -需要逐条确认: - -1. 哪些只用于日志 -2. 哪些只用于关闭路径 -3. 哪些真影响行为 - -如果不区分,容易把“日志依赖”误当成“结构依赖”。 - -### 8.4 Low risk: cross-window mutation algorithm itself - -`UIEditorWindowWorkspaceController` 和 `UIEditorWorkspaceTransfer` 负责的是跨窗口 panel 变换语义。 - -本计划不改: - -1. extract 规则 -2. insert 规则 -3. dock 规则 -4. detached workspace 生成规则 - -因此只要 mutation 输入快照正确,这一层不是主要风险源。 - -## 9. Why This Refactor Is Actually Simplifying - -它不是表面简化,而是实质上减少状态 owner。 - -完成后会得到: - -1. 窗口 workspace 只在 live window 内持有一份 -2. mutation 输入不再依赖长期镜像,而是按需快照 -3. frame path 不再做整份 workspace/session 复制与整套 validate -4. coordinator 的职责变成: - - 从 live windows 取快照 - - 调用 mutation controller - - 把 mutation 结果同步回 live windows -5. title refresh 变成一个独立的小职责,而不是夹带在 projection 里 - -也就是说,复杂度不是被藏起来,而是真的减少了一层。 - -## 10. Why This Refactor Is Root-Cause-Oriented - -它之所以是从根源出发,是因为它处理的是“谁拥有窗口 workspace 真相”。 - -如果不解决 owner 问题,只做局部删减,最终都会回到: - -1. 某处还需要一份 mirror -2. 某帧还得再同步一次 -3. 某条宿主功能又悄悄绑回 projection - -而这份计划直接把真相源收回 live window,把跨窗口 controller 降级为临时 snapshot consumer,这才是对根因动刀。 - -## 11. Validation Requirements - -没有以下验证,不允许落地删除 projection: - -1. detached window 内切换 active panel 后,窗口标题实时更新 -2. primary window detach panel 正常创建新窗口 -3. detached window 再次拖拽 panel 发起 global tab drag 正常 -4. 跨窗口 drop 到 tab stack 和 relative dock 都正常 -5. 已打开 tool window 的 panel 重复打开请求仍然正确复用 -6. 关闭 detached window 后,其余窗口状态保持正确 -7. 关闭 primary window 时,其他窗口生命周期不异常 -8. `XCUIEditorApp` Debug 构建通过 - -## 12. Completion Criteria - -只有满足以下条件,这次重构才算真正完成: - -1. `EditorWindowWorkspaceStore` 不再持有整份 `workspace/session` 镜像 -2. `EditorWindowHostRuntime::RenderAllWindows(...)` 不再每帧调用 workspace projection commit -3. 所有跨窗口 mutation 都从 live snapshot 构建输入 -4. detached 标题刷新与 projection 彻底解耦 -5. 现有 detach / drag / re-dock / close 行为无回归 - -## 13. Final Statement - -这次重构不能靠“删几行同步代码”完成。 - -真正安全、真正彻底的路径只有一条: - -1. 先建立 live snapshot 输入 -2. 再把 mutation 输入切过去 -3. 再拆标题刷新 -4. 最后删除 mirror - -只有按这个顺序,才能既不破坏功能,又从根上消灭这套双写宿主残片。 diff --git a/docs/used/NewEditor_WorkspaceUtilityWindowSemanticSplitPlan_2026-04-22.md b/docs/used/NewEditor_WorkspaceUtilityWindowSemanticSplitPlan_2026-04-22.md deleted file mode 100644 index 6596a53d..00000000 --- a/docs/used/NewEditor_WorkspaceUtilityWindowSemanticSplitPlan_2026-04-22.md +++ /dev/null @@ -1,529 +0,0 @@ -# NewEditor Workspace Utility Window Semantic Split Plan - -Date: 2026-04-22 -Status: Complete and Archived - -## 0. Execution Progress - -### Completed on 2026-04-22 - -Phase A shell/content split foundation is now in place. - -Completed changes: - -1. `EditorWindow` no longer constructs itself from `UIEditorWorkspaceController` -2. `EditorWindowRuntimeController` no longer directly owns `m_workspaceController` -3. `EditorWindowHostRuntime::CreateEditorWindow(...)` no longer accepts raw workspace controller input -4. a new content abstraction now exists: - - `EditorWindowContentController` - - `EditorWorkspaceWindowContentController` -5. current workspace-backed behavior has been moved behind the new content layer without changing workspace semantics -6. detached workspace chrome policy now reads window-content capabilities instead of shell-owned workspace state -7. `XCUIEditorApp` Debug build passes after this phase - -What is intentionally not done yet: - -1. utility window domain does not exist yet -2. color picker still opens through detached panel workflow -3. `toolWindow` still exists in workspace panel descriptors as legacy leakage to remove in later phases - -This means the root shell/content ownership boundary has been extracted first, while feature semantics remain unchanged for safety. - -### Completed on 2026-04-22 - -Phase C, Phase D, and Phase E implementation are now in place. - -Completed changes: - -1. a first-class utility window domain now exists: - - `EditorUtilityWindowKind` - - `EditorWindowOpenUtilityWindowRequest` - - `EditorUtilityWindowCoordinator` - - `EditorUtilityWindowRegistry` -2. color picker open intent no longer routes through detached workspace mutation -3. the color picker now opens through utility-window coordination and utility content -4. color picker has been removed from: - - `UIEditorPanelRegistry` - - shell hosted-panel composition - - workspace detached-panel request plumbing -5. `openDetachedPanel` has been deleted from frame transfer routing -6. `UIEditorWindowWorkspaceController::OpenPanelInNewWindow(...)` has been deleted -7. `toolWindow` and related workspace utility inference have been deleted -8. detached floating-window placement logic is now shared instead of duplicated -9. `XCUIEditorApp` Debug build passes after this phase - -### Verified and Archived on 2026-04-22 - -Section 10 manual verification has now passed. - -Verified outcomes: - -1. workspace regression checks passed -2. utility window checks passed -3. cross-boundary checks passed -4. `XCUIEditorApp` Debug build passes -5. `XCUIEditorApp` Release build passes - -## 1. Objective - -This plan solves the problem from the architectural root, not by adding another special case. - -The goal is to completely separate: - -1. native window shell -2. workspace-backed dockable windows -3. utility windows that are inherently standalone - -The immediate trigger is the color picker window, but the real issue is broader: - -1. `EditorWindow` is still specialized around `UIEditorWorkspaceController` -2. utility windows are currently modeled as panels with a `toolWindow` flag -3. the color picker is opened through `openDetachedPanel` -4. utility window policy is inferred indirectly from workspace root content - -This architecture is semantically wrong. - -If a window is inherently standalone and can never merge back into the main workspace, it must not live inside the workspace panel model. - -## 2. Architectural Judgment - -The current implementation shares too much. - -Sharing the native host is correct: - -1. HWND lifetime -2. D3D render loop -3. DPI handling -4. input capture -5. title bar / chrome -6. screenshot and frame pacing - -Sharing the workspace panel abstraction is not correct for the color picker. - -The color picker is currently treated as: - -1. a panel descriptor in `UIEditorPanelRegistry` -2. a hosted panel inside shell composition -3. a detached panel opened via `OpenPanelInNewWindow(...)` -4. a single-root detached workspace that is merely styled as a tool window - -That means the color picker still belongs to the dock / detach / re-dock / transfer universe. - -This is the wrong owner domain. - -## 3. Confirmed Root Cause - -The root cause is not one bad function. - -The root cause is that the codebase currently conflates two different semantics: - -1. workspace window - - owns a `UIEditorWorkspaceController` - - participates in layout, docking, tab stacks, cross-window transfer - - can detach and re-merge -2. utility window - - owns standalone tool content - - does not belong to workspace layout - - does not expose `nodeId` / `panelId` docking semantics - - cannot merge into the main window - -Today those two semantics are both forced through `EditorWindow` + `UIEditorWorkspaceController`. - -That is why the color picker can only exist as a fake panel. - -## 4. Current Coupling Points - -These are the concrete places where the semantic mistake is encoded today: - -1. `new_editor/app/Platform/Win32/EditorWindow.h` - - `EditorWindow` constructor directly requires `UIEditorWorkspaceController` -2. `new_editor/app/Platform/Win32/EditorWindowRuntimeController.h` - - runtime directly stores `m_workspaceController` -3. `new_editor/app/Platform/Win32/EditorWindowTransferRequests.h` - - utility-open intent is modeled as `EditorWindowOpenDetachedPanelRequest` -4. `new_editor/app/Platform/Win32/EditorWindowFrameOrchestrator.cpp` - - color picker open is converted into `openDetachedPanel` -5. `new_editor/app/Platform/Win32/WindowManager/EditorWindowWorkspaceCoordinator.cpp` - - color picker window creation is routed through workspace mutation -6. `new_editor/src/Workspace/UIEditorWindowWorkspaceController.cpp` - - `OpenPanelInNewWindow(...)` is used to create the color picker window -7. `new_editor/include/XCEditor/Panels/UIEditorPanelRegistry.h` - - `toolWindow` is attached to panel descriptors -8. `new_editor/src/Workspace/UIEditorDetachedWindowPolicy.cpp` - - utility semantics are inferred from detached workspace root content -9. `new_editor/app/Composition/EditorShellAssetBuilder.cpp` - - color picker is registered as a panel -10. `new_editor/app/Composition/EditorShellHostedPanelCoordinator.cpp` - - color picker lives inside hosted panel update / dispatch infrastructure -11. `new_editor/app/State/EditorColorPickerToolState.h` - - color picker open intent is expressed as `requestOpenDetachedPanel` - -This is not one bug. This is an ownership boundary failure. - -## 5. Refactor Red Lines - -The refactor must not degrade into any of the following: - -1. keeping the color picker in `UIEditorPanelRegistry` and merely adding more `if (panelId == "color-picker")` branches -2. keeping `toolWindow` as the long-term mechanism for non-workspace windows -3. keeping `openDetachedPanel` as the generic request type for utility windows -4. allowing utility windows to remain inside `UIEditorWindowWorkspaceSet` -5. preserving `nodeId` / `panelId` transfer semantics for utility windows -6. building a second special-case bypass inside `EditorWindowWorkspaceCoordinator` -7. renaming the current path without changing ownership - -If any of those remain in the end state, the architecture is still wrong. - -## 6. Target End State - -The correct end state has three layers. - -### 6.1 Native Shell Layer - -A shared native shell owns only platform and rendering responsibilities: - -1. HWND creation and destruction -2. D3D renderer / swap chain / present loop -3. window chrome and title -4. DPI, sizing, screenshot, pointer capture plumbing -5. input event collection and dispatch to content - -This layer must not know whether the content is workspace-backed or utility-backed. - -Suggested shape: - -1. `EditorNativeWindowShell` -2. `EditorNativeWindowRuntime` -3. `IEditorWindowContentController` - -The names can change, but the boundary cannot. - -### 6.2 Workspace Window Layer - -Workspace windows are the only windows allowed to own: - -1. `UIEditorWorkspaceController` -2. `EditorShellRuntime` -3. dock host interaction state -4. detached panel transfer requests -5. cross-window tab drag and dock / re-dock semantics - -A workspace window can: - -1. represent the main window -2. represent a detached panel window -3. merge back into another workspace window - -Suggested shape: - -1. `EditorWorkspaceWindowContent` -2. `EditorWorkspaceWindowCoordinator` -3. `EditorWorkspaceWindowTransferRequests` - -### 6.3 Utility Window Layer - -Utility windows are standalone tool surfaces. - -They must not own: - -1. `UIEditorWorkspaceController` -2. dock layout -3. tab stack state -4. `nodeId` -5. `panelId` workspace transfer semantics - -They may own: - -1. dedicated tool state -2. dedicated rendering and input logic -3. explicit request / response channels to editor features -4. separate focus / reuse / close rules - -Suggested shape: - -1. `EditorUtilityWindowContent` -2. `EditorUtilityWindowCoordinator` -3. `EditorOpenUtilityWindowRequest` -4. `EditorUtilityWindowKind` -5. `EditorColorPickerUtilityWindowContent` - -## 7. Required Ownership Changes - -The refactor must move ownership, not just code location. - -### 7.1 Window Identity - -Today: - -1. detached color picker window identity is derived from `panelId` -2. detached utility behavior is inferred from the root workspace panel descriptor - -After refactor: - -1. workspace windows are identified inside workspace domain -2. utility windows are identified inside utility domain -3. utility window identity is no longer a panel identity - -### 7.2 Open Intent - -Today: - -1. color picker open intent means "open this panel in a detached window" - -After refactor: - -1. color picker open intent means "open or focus utility window of kind color picker" -2. the request carries explicit tool payload: - - initial color - - alpha mode - - inspector target binding - - focus / reuse policy - -### 7.3 Persistence - -Today: - -1. utility-like windows still pass through workspace layout concepts - -After refactor: - -1. workspace window state remains in workspace layout persistence -2. utility window state uses separate persistence or no persistence -3. utility windows never enter `UIEditorWindowWorkspaceSet` - -### 7.4 Visual Policy - -Today: - -1. tool-window policy is inferred from detached workspace content - -After refactor: - -1. workspace detached-window policy is only about workspace windows -2. utility-window policy belongs to utility window descriptors or utility content - -## 8. Migration Strategy - -The migration must happen in strict phases. - -### Phase A. Extract a Content-Neutral Native Window Shell - -Goal: - -1. stop making the native window host depend directly on `UIEditorWorkspaceController` - -Required changes: - -1. split the current `EditorWindow` responsibilities into: - - shell responsibilities - - content responsibilities -2. extract a content interface for: - - update - - draw - - input dispatch - - external preview / title contributions if needed -3. make `EditorWindowHostRuntime::CreateEditorWindow(...)` create a shell plus content, not a workspace-bound window object - -Success criteria: - -1. a shell instance can host workspace content -2. a shell instance can host non-workspace content -3. no shell constructor directly requires `UIEditorWorkspaceController` - -Red line: - -1. do not keep a hidden `m_workspaceController` in shell-level runtime - -### Phase B. Move Existing Workspace Windows onto the New Content Layer - -Goal: - -1. preserve all existing main-window and detached-panel behavior while isolating it into workspace-specific content - -Required changes: - -1. create `EditorWorkspaceWindowContent` -2. move current workspace-only responsibilities into it: - - `UIEditorWorkspaceController` - - `EditorShellRuntime` - - workspace frame orchestration - - dock transfer request generation -3. keep `EditorWindowWorkspaceCoordinator` but narrow its responsibility to workspace windows only - -Success criteria: - -1. main window still works -2. detached panel windows still work -3. cross-window tab drag and re-dock still work -4. utility windows are still not introduced yet, but the shell is already ready for them - -### Phase C. Introduce a Real Utility Window Domain - -Goal: - -1. create a first-class utility window pipeline independent from workspace mutation - -Required changes: - -1. add utility window request types -2. add `EditorUtilityWindowCoordinator` -3. add utility window registry / descriptor model -4. define utility window lifecycle: - - open - - reuse - - focus - - close - - app-shutdown behavior -5. define utility window payload passing - -Success criteria: - -1. utility windows can be created without touching `UIEditorWindowWorkspaceController` -2. utility windows can be reused without entering workspace state -3. utility windows do not generate dock transfer requests - -### Phase D. Migrate Color Picker Out of the Panel System - -Goal: - -1. make the color picker the first true utility window - -Required changes: - -1. remove color picker from `UIEditorPanelRegistry` -2. remove color picker from shell hosted-panel composition -3. replace `requestOpenDetachedPanel` with utility-window open intent -4. implement `EditorColorPickerUtilityWindowContent` -5. wire inspector -> color picker communication through explicit tool contracts instead of workspace panel visibility - -Success criteria: - -1. opening the color picker never calls `OpenPanelInNewWindow(...)` -2. color picker never appears in workspace visible panels -3. color picker never participates in detach / re-dock / cross-window panel transfer -4. color picker still updates inspector-bound color correctly - -### Phase E. Delete Transitional Workspace Utility Leakage - -Goal: - -1. remove all leftover semantic leakage after color picker migration - -Required deletions: - -1. remove `toolWindow` from `UIEditorPanelDescriptor` -2. remove color-picker-specific detached-panel request plumbing -3. remove utility inference from `UIEditorDetachedWindowPolicy` -4. remove any remaining workspace special cases that exist only for the old color picker path - -Success criteria: - -1. workspace code no longer knows the color picker exists -2. utility code no longer depends on panel registry membership -3. no root-content-based inference is needed to determine window kind - -## 9. Files Expected to Change - -This refactor is architecture-level and will necessarily touch a broad slice. - -### Shell / Host Layer - -1. `new_editor/app/Platform/Win32/EditorWindow.h` -2. `new_editor/app/Platform/Win32/EditorWindow.cpp` -3. `new_editor/app/Platform/Win32/EditorWindowRuntimeController.h` -4. `new_editor/app/Platform/Win32/EditorWindowRuntimeController.cpp` -5. `new_editor/app/Platform/Win32/WindowManager/EditorWindowHostRuntime.h` -6. `new_editor/app/Platform/Win32/WindowManager/EditorWindowHostRuntime.cpp` -7. `new_editor/app/Platform/Win32/WindowManager/EditorWindowManager.cpp` -8. `new_editor/app/Platform/Win32/WindowManager/EditorWindowMessageDispatcher.cpp` - -### Workspace Layer - -1. `new_editor/app/Platform/Win32/WindowManager/EditorWindowWorkspaceCoordinator.h` -2. `new_editor/app/Platform/Win32/WindowManager/EditorWindowWorkspaceCoordinator.cpp` -3. `new_editor/app/Platform/Win32/EditorWindowFrameOrchestrator.cpp` -4. `new_editor/app/Platform/Win32/EditorWindowTransferRequests.h` -5. `new_editor/src/Workspace/UIEditorWindowWorkspaceController.cpp` -6. `new_editor/src/Workspace/UIEditorDetachedWindowPolicy.cpp` - -### Utility Layer - -1. new utility window coordinator files -2. new utility window descriptor / request files -3. new color picker utility content files -4. possibly a host-level coordinator that routes shell instances to workspace or utility content - -### Color Picker / Feature Layer - -1. `new_editor/app/State/EditorColorPickerToolState.h` -2. `new_editor/app/State/EditorColorPickerToolState.cpp` -3. `new_editor/app/Features/ColorPicker/ColorPickerPanel.h` -4. `new_editor/app/Features/ColorPicker/ColorPickerPanel.cpp` -5. `new_editor/app/Composition/EditorShellAssetBuilder.cpp` -6. `new_editor/app/Composition/EditorShellHostedPanelCoordinator.cpp` -7. `new_editor/app/Composition/EditorShellRuntime.h` -8. `new_editor/app/Composition/EditorShellRuntime.cpp` -9. `new_editor/app/Composition/EditorShellDrawComposer.cpp` - -## 10. Verification Matrix - -This refactor is not done unless all of the following are checked. - -### Workspace Regression Checks - -1. main window render / input / title behavior is intact -2. panel detach still creates detached workspace windows -3. detached workspace windows can re-dock -4. cross-window tab drag-drop still works -5. workspace close path still closes detached workspace windows correctly -6. workspace layout persistence still excludes utility windows - -### Utility Window Checks - -1. opening color picker creates or focuses a utility window, not a detached panel window -2. color picker cannot be docked into the main window -3. color picker cannot be merged by tab drag -4. color picker does not appear in workspace visible-panel lists -5. color picker close does not mutate workspace layout -6. color picker reuse policy is correct: - - single-instance reuse or explicit multi-instance rule, but not accidental workspace reuse - -### Cross-Boundary Checks - -1. inspector -> color picker color synchronization still works -2. closing the main app tears down utility windows correctly -3. focus changes between workspace and utility windows do not break input capture -4. utility windows do not accidentally receive workspace drop preview / transfer state - -### Build Checks - -1. `XCUIEditorApp` Debug build passes -2. `XCUIEditorApp` Release build passes if used by the team - -## 11. Completion Criteria - -This refactor is complete only when all of these are true: - -1. no utility window is represented as a workspace panel -2. `EditorWindow` shell no longer requires `UIEditorWorkspaceController` -3. workspace and utility windows share only native shell infrastructure -4. `toolWindow` is not the long-term carrier of utility-window semantics -5. the color picker opens through utility window coordination, not workspace mutation -6. workspace code can be reasoned about without referencing utility windows -7. utility windows can be reasoned about without referencing dock / transfer semantics - -## 12. Final Statement - -The correct fix is not: - -1. a new bypass for the color picker -2. another flag on panel descriptors -3. another branch inside workspace mutation - -The correct fix is to restore the missing architectural boundary: - -1. native shell is shared -2. workspace semantics are isolated -3. utility semantics are isolated - -Only after this split is in place will the color picker stop being a fake detached panel and become a real standalone editor window type. diff --git a/docs/used/NewEditor_XCUIEditorLibRetirementPlan_2026-04-22.md b/docs/used/NewEditor_XCUIEditorLibRetirementPlan_2026-04-22.md deleted file mode 100644 index b6e2e7d5..00000000 --- a/docs/used/NewEditor_XCUIEditorLibRetirementPlan_2026-04-22.md +++ /dev/null @@ -1,90 +0,0 @@ -# NewEditor XCUIEditorLib Retirement Plan - -Date: `2026-04-22` -Status: `Completed` - -## Goal - -- Remove `XCUIEditorLib` as a first-class build target from `new_editor`. -- Stop treating `new_editor/include + new_editor/src` as a separately promoted module-layer library in the current product build. -- Flatten the current XCUI shared sources directly back into `XCUIEditorApp`, so the build graph matches the actual product shape. - -## Why The Previous Plan Was Deleted - -- The deleted plan assumed `XCUIEditorLib` should keep growing into a formal editor module layer. -- That premise conflicts with the current architecture direction and with the explicit requirement that this extra lib layer should not continue to exist. -- Continuing to move logic into `XCUIEditorLib` would deepen the wrong boundary instead of removing it. - -## Current Reality - -- `XCUIEditorLib` is only defined in `new_editor/CMakeLists.txt`. -- `XCUIEditorApp` is the only active build target that links it. -- The current source lists are already centralized in one file, so removing the lib layer is mainly a build-graph flattening task, not a source-tree migration task. - -## Constraints - -- Do not introduce a renamed replacement library target. -- Do not replace `XCUIEditorLib` with another fake public layer. -- Do not move app business sources into `new_editor/src`. -- Do not split this into new glue targets just to preserve the old layering narrative. -- Keep the active executable target as `XCUIEditorApp`. - -## Execution Plan - -### Phase A. Remove The Wrong Plan Baseline - -Status: `Completed` - -Target: - -- Delete the invalid `XCUIEditorLib`-promotion plan from `docs/plan`. -- Replace it with a retirement plan that matches the actual desired direction. - -Validation: - -- `docs/plan` no longer contains the deleted plan. -- The active plan explicitly treats `XCUIEditorLib` as a retirement target, not a destination layer. - -### Phase B. Flatten XCUIEditorLib Into XCUIEditorApp - -Status: `Completed` - -Target: - -- Remove the `add_library(XCUIEditorLib ...)` target. -- Compile the same `include/src` source set directly as part of `XCUIEditorApp`. -- Move any target-local include paths or compile settings required by those sources onto `XCUIEditorApp`. -- Remove the executable link dependency on `XCUIEditorLib`. -- Clean up stale post-build visible lib artifacts if needed. - -Validation: - -- `new_editor/CMakeLists.txt` no longer defines `XCUIEditorLib`. -- `XCUIEditorApp` directly owns the old XCUI shared source set. -- `XCUIEditorApp` no longer links `XCUIEditorLib`. - -### Phase C. Reconfigure, Build, Smoke - -Status: `Completed` - -Target: - -- Reconfigure `build/new_editor`. -- Build `XCUIEditorApp`. -- Launch `build/new_editor/Debug/XCUIEditor.exe` for a short smoke run. - -Validation: - -- Configure succeeds. -- Build reaches `XCUIEditorApp`. -- Smoke log contains: - - `EnsureEditorStartupScene loaded scene=Main Scene` - - `[app] initialize end` - - `[app] shutdown end` - -## Acceptance - -- `XCUIEditorLib` is no longer an active build target in `new_editor/CMakeLists.txt`. -- `XCUIEditorApp` becomes the direct owner of the former XCUI shared source set. -- No replacement fake public lib target is introduced. -- The active plan no longer frames `XCUIEditorLib` as a future architecture destination. diff --git a/docs/used/NewEditor_反过度拆分重构计划_2026-04-15.md b/docs/used/NewEditor_反过度拆分重构计划_2026-04-15.md deleted file mode 100644 index c46ac005..00000000 --- a/docs/used/NewEditor_反过度拆分重构计划_2026-04-15.md +++ /dev/null @@ -1,243 +0,0 @@ -# NewEditor 反过度拆分重构计划 - -日期: `2026-04-15` - -## 1. 文档定位 - -这份文档是当前 `new_editor` 唯一有效的主计划。 - -归档文档: -1. `docs/plan/used/NewEditor_架构收口计划_2026-04-15.md` - -归档原因: -1. 旧计划的方向总体没错,但粒度过粗,已经不足以指导当前最主要的问题。 -2. 当前 `new_editor` 的主矛盾已经不是“还差哪些功能”,而是“有些地方拆得过碎,目录和文件边界开始偏离最佳实践”。 -3. 如果不先把这部分结构问题收回来,后续继续搭新 editor,只会继续把宿主层和产品层切成越来越多的薄文件。 - -## 2. 当前主问题 - -### 2.1 过度拆分已经成为第一矛盾 - -当前最需要处理的不是大文件本身,而是一些模块被机械切成了过多薄文件。 - -典型区域: -1. `new_editor/app/Platform/Win32/WindowManager/*` -2. `new_editor/app/Platform/Win32/EditorWindow*.cpp` -3. 少量为了“看起来像分层”而存在的目录和空壳实现文件 - -问题表现: -1. 同一条协作链被拆成多个很薄的翻译单元 -2. 理解一次完整流程时必须在很多文件间来回跳 -3. 文件名和目录名越来越像技术噪音,而不是稳定 owner -4. 继续这样发展,后面会越来越像“工程化外观”,而不是可维护架构 - -### 2.2 宿主层仍有部分边界不够稳定 - -虽然 `WindowMessageDispatcher` 和 `DockHost` 已经收过一轮,但宿主层还没有完全回到稳定形态。 - -当前症状: -1. `EditorWindow` 公开面已经收紧,但内部协作仍然偏碎 -2. `WindowManager` 仍承担一条比较长的跨窗口协作链 -3. 有些文件虽然名字不同,但变化原因高度耦合,应该由同一个 owner 负责 - -### 2.3 目录结构里仍有收尾不彻底的痕迹 - -例如: -1. 空目录 -2. 旧命名残留 -3. 名义上是分层,实际上没有独立 owner 的子目录 - -这些东西不一定会立刻导致 bug,但会持续降低结构质量。 - -## 3. 本轮目标 - -这一轮的目标不是继续加功能,而是把 `new_editor` 的结构拉回到真正符合最佳实践的状态。 - -目标如下: -1. 取消“为了拆而拆”的倾向 -2. 让宿主层文件组织回到少量稳定 owner -3. 让目录结构能直接表达职责,而不是表达切片历史 -4. 保留真正有价值的分层,收回没有价值的薄分片 -5. 为后续继续构建新 editor 提供稳定底座 - -## 4. 必须严格遵守的原则 - -### 4.1 最佳实践不是越碎越好 - -真正的拆分标准只能是稳定职责和稳定 owner,不能是: -1. 行数多了就拆 -2. 某几个函数看起来可以单独放文件就拆 -3. 为了让目录“看起来更专业”就拆 - -### 4.2 `src` 和 `app` 的判断标准不同 - -1. `src` 是 Editor UI 库层,允许存在中等偏大的 owner 文件,只要控件/状态机边界稳定。 -2. `app` 是新 editor 宿主和产品层,应该更强调 feature owner 和完整协作链,不能切成一堆 30 到 80 行的小碎片。 -3. 不能拿库层拆法直接套到宿主层。 - -### 4.3 允许大文件,但不允许混乱文件 - -一个 400 到 700 行的文件如果只服务于一个稳定 owner,可以接受。 - -一个 80 行的文件如果只是某个流程的边角料切片,反而更差。 - -### 4.4 目录名必须表达 owner,不表达历史 - -目录和文件命名必须让人一眼看出: -1. 谁拥有这部分逻辑 -2. 它为什么会独立变化 -3. 它是否值得单独存在 - -不能再继续保留“只是因为之前拆出来了,所以它还在”的结构。 - -## 5. 目标结构 - -### 5.1 `new_editor/src` - -原则: -1. 保持 `widgets / shell / workspace / docking / fields / collections` 这种稳定 owner 分区。 -2. 对于单个控件或状态机,优先保留单一 owner 文件。 -3. 只有在一个文件明确混合了多类职责时,才做有限拆分。 - -### 5.2 `new_editor/app` - -原则: -1. `Bootstrap` 负责应用启动和宿主装配。 -2. `Composition` 负责把 editor 产品状态装配到 UI shell。 -3. `Features` 负责具体业务面板 owner。 -4. `Platform/Win32` 负责原生窗口宿主。 -5. `Rendering` 负责 viewport 宿主和宿主级资源。 -6. `Internal` 只保留真正跨 app 子模块复用、且明确是 app 私有的小工具。 - -明确禁止: -1. 再新增 `Support` 这种泛化噪音目录。 -2. 再把一个完整流程切成很多超薄 `.cpp`。 -3. 再保留空目录或无 owner 的中间层。 - -## 6. 分阶段计划 - -### 阶段 A: 归档旧计划并冻结新主线 - -目标: -1. 归档旧的泛化收口计划。 -2. 明确新主线是“反过度拆分”。 - -完成标准: -1. `docs/plan` 中只保留新的 `new_editor` 主计划。 -2. 旧计划进入 `docs/plan/used`。 - -### 阶段 B: 审计宿主层过度拆分点 - -目标: -1. 系统审查 `Platform/Win32` 与 `Rendering` 的文件组织。 -2. 找出真正拆过头的薄文件和伪分层。 - -重点检查: -1. `EditorWindow*` -2. `WindowManager/*` -3. `CrossWindowDrop*` -4. 宿主级 `Internal` / 空目录 / 纯转发式文件 - -完成标准: -1. 输出一份明确的“保留/合并/删除”名单。 -2. 不再凭感觉重构。 - -### 阶段 C: 收回 WindowManager 过度碎片 - -目标: -1. 让 `WindowManager` 回到少量稳定 owner 文件。 - -执行策略: -1. 合并只是同一流程切片的超薄文件。 -2. 保留真正独立变化的部分,例如跨窗口拖拽、生命周期、窗口集合管理。 -3. 禁止把多个文件重新包装成另一层空壳。 - -完成标准: -1. `WindowManager` 文件数量明显下降。 -2. 单次理解窗口协作流程不再需要来回跳很多文件。 - -### 阶段 D: 收回 EditorWindow 过度碎片 - -目标: -1. 让 `EditorWindow` 相关文件回到稳定宿主边界。 - -执行策略: -1. 合并只是在生命周期内被硬切开的超薄实现。 -2. 保留真正稳定的子 owner,例如 title bar、borderless chrome、frame runtime。 -3. 确保 `EditorWindow.h` 继续保持收敛,不回退成 god object。 - -完成标准: -1. `EditorWindow` 相关实现文件更少但更稳定。 -2. 公开面不膨胀,内部协作也不碎裂。 - -### 阶段 E: 清理目录与命名噪音 - -目标: -1. 删除空目录、无意义目录和残留噪音命名。 - -执行策略: -1. 删除空的 `app/UI/Styles` 之类残留。 -2. 清理不再有存在价值的中间层。 -3. 统一 `Internal` 作为内部私有命名。 - -完成标准: -1. `new_editor` 目录树能直接表达当前架构。 -2. 不再残留已经失效的旧组织痕迹。 - -### 阶段 F: 复查热点 owner 文件 - -目标: -1. 重新检查当前最大的几个热点文件,但只处理真正混乱的,不盲拆。 - -重点对象: -1. `src/Docking/UIEditorDockHostInteraction.cpp` -2. `src/Workspace/UIEditorWorkspaceModel.cpp` -3. `app/Features/Project/ProjectPanel.cpp` -4. `app/Features/Hierarchy/HierarchyPanel.cpp` - -执行策略: -1. 如果是单一 owner,则保留。 -2. 如果确实混合多类职责,则按稳定边界有限拆分。 -3. 明确禁止再走“机械切块”老路。 - -完成标准: -1. 热点文件要么被证明合理保留。 -2. 要么被有限重构到稳定结构。 - -### 阶段 G: 最终验证与收口 - -目标: -1. 确认这轮反过度拆分重构已经完成。 - -验证项: -1. `XCUIEditorApp` 编译通过。 -2. `XCUIEditorHost` 编译通过。 -3. `editor_ui_tests` 编译通过。 -4. `editor_ui_tests.exe` 全绿。 -5. `new_editor` 目录结构不再存在明显“拆过头”区域。 - -## 7. 当前第一批明确对象 - -当前最优先处理的对象: -1. `new_editor/app/Platform/Win32/WindowManager/TabDrag.cpp` -2. `new_editor/app/Platform/Win32/WindowManager/Creation.cpp` -3. `new_editor/app/Platform/Win32/WindowManager/Lookup.cpp` -4. `new_editor/app/Platform/Win32/WindowManager/WindowSet.cpp` -5. `new_editor/app/Platform/Win32/WindowManager/Detach.cpp` -6. `new_editor/app/Platform/Win32/EditorWindow*.cpp` -7. 空的 `new_editor/app/UI/Styles` - -这些地方不是说一定都要合并成一个文件,而是必须重新判断: -1. 哪些是真正稳定子 owner。 -2. 哪些只是拆分历史残留。 - -## 8. 收口判定 - -满足以下条件,才能认为这一轮真正收口: - -1. `new_editor/app` 中不再存在明显过度拆分区域。 -2. `WindowManager` 回到少量稳定 owner。 -3. `EditorWindow` 相关实现不再机械碎片化。 -4. 目录树中不再残留空目录和旧命名噪音。 -5. 核心热点文件的保留或拆分都有清晰理由。 -6. 编译与测试持续通过。 diff --git a/docs/used/NewEditor_审查遗留总收口计划_2026-04-19.md b/docs/used/NewEditor_审查遗留总收口计划_2026-04-19.md deleted file mode 100644 index c7ae4a89..00000000 --- a/docs/used/NewEditor_审查遗留总收口计划_2026-04-19.md +++ /dev/null @@ -1,271 +0,0 @@ -# NewEditor 审查遗留总收口计划 - -## 背景 - -当前 `new_editor` 已完成旧 `editor` / ImGui 依赖切离,也完成了 Scene、Project、Hierarchy、Win32 宿主、多窗口、测试体系等多轮迁移与重构。但从现状审查结果看,仍有一整组未收口的结构问题,它们彼此关联,不能只挑一部分处理: - -1. `include/XCEditor` 的公共头并没有稳定地充当接口层,而是在持续聚合具体实现域。 -2. `new_editor/src` 与 `new_editor/app` 的拆分标准不一致,出现“过碎薄文件”和“超大职责文件”并存。 -3. `EditorShellRuntime` 仍是 feature 编排、状态汇总、事件中转、渲染协调、指针捕获决策的中心大对象。 -4. `EditorWindow` 与 Win32 controller 的 ownership 没有真正收拢,仍保留大量私有转发行为与跨对象透权。 -5. 平台层仍直接依赖上层 UI 语义与 theme / panel registry 等对象,平台宿主隔离未彻底完成。 -6. `ProjectPanel` 仍然是严重超载的 feature,树、网格、布局、命令、菜单、拖放、重命名全部耦合在一起。 -7. `HierarchyPanel`、`InspectorPanel`、Scene feature 的边界虽然比 `ProjectPanel` 清楚,但仍缺少统一 feature event 契约。 -8. `WorkspaceEventSync` 仍是 feature event 到 `EditorContext` / runtime 的临时胶水,而不是正式 app-level 事件入口。 -9. Scene gizmo / viewport 相关实现仍有大块高复杂度单体文件,后续会持续阻碍维护与验证。 - -这些问题的根因不是“代码多”本身,而是边界定义、文件粒度、ownership、公共接口层设计都还没有稳定下来。 - -## 本计划覆盖的全部未收口问题 - -### 一. 公共接口层失真 - -问题: - -1. `UIEditorTheme.h` 等公共头直接吸入 list / tree / field / dock / shell / viewport 具体类型。 -2. `UIEditorWorkspaceCompose.h` 同时承载 compose types、update API、append API,并直接依赖 dock host、panel host、viewport shell。 -3. 一些 `XCEditor` 头更像“实现域聚合入口”而不是“稳定公共接口”。 - -根因: - -- 公共头、契约头、实现头没有分层。 -- 迁移阶段用聚合头降低了接线成本,但没有在后续回收。 - -收口动作: - -1. 将公共头拆成 `types/contracts` 与 `ops/services` 两层。 -2. 公共头只保留稳定数据结构、前置声明、轻量枚举和必要查询接口。 -3. 将 update / resolve / append / interaction 这类依赖实现细节的 API 下沉到独立 ops 头。 -4. 全量清理 `new_editor/app` 与 `tests/UI/Editor` 的 include 面。 - -验收: - -- `XCEditor` 主要公共头不再通过一层 include 拉入整片实现域。 -- 任一 feature 引入 theme / compose 合同时,不再被动依赖大量 widget/field/shell 类型。 - -### 二. 文件粒度标准失控 - -问题: - -1. 某些文件过碎,只剩几行转发或包装,没有形成独立 ownership。 -2. 某些文件又持续膨胀到数百上千行,承载多个变化理由。 -3. `new_editor` 的拆分策略与 `engine` 主体并不一致,当前粒度不稳定。 - -典型表现: - -1. `EditorWindowTitleBarRendering.cpp` 这类纯转发薄文件。 -2. `ProjectPanel.cpp`、`SceneViewportTransformGizmoSupport.cpp`、`EditorWindowChromeController.cpp` 这类超大文件。 - -根因: - -- 一部分文件是重构中间态,没有在边界明确后回收。 -- 拆分依据偏向“临时改动落点”,而不是“稳定职责边界”。 - -收口动作: - -1. 建立明确拆分标准:按 ownership、状态归属、修改耦合、测试边界拆,不按函数数量拆。 -2. 回收纯转发薄文件,合并到真正拥有行为的实现文件。 -3. 将超大文件按真实子域拆开,而不是继续切更多壳文件。 - -验收: - -- 不再存在大量只做单纯转发的宿主文件。 -- 大文件拆分后每个子文件都对应稳定责任,而不是“某个大文件的一个切片”。 - -### 三. EditorShellRuntime 中心化过重 - -问题: - -1. `EditorShellRuntime` 同时拥有 panel feature、viewport host、built-in icons、pointer ownership、workspace trace。 -2. 它直接暴露 `HierarchyPanel::Event`、`ProjectPanel::Event` 等 feature-specific 细节。 -3. 顶层 runtime 既是编排器,又是状态中转站,又承担 feature 间粘合。 - -根因: - -- 缺少统一 feature contract。 -- feature 输出和 app-level 输出没有分层。 - -收口动作: - -1. 提取统一 feature presentation / interaction / event contract。 -2. 让 `EditorShellRuntime` 只承担编排与生命周期,不再泄露具体 panel 事件容器。 -3. 让 pointer capture / hosted content cursor / shell interaction 聚合为统一 runtime 输出模型。 - -验收: - -- `EditorShellRuntime` 对外只暴露 app-level 结果,不直接透出具体 panel 类型。 -- 新增 feature 时不需要继续给 runtime 增加一组定制 getter。 - -### 四. Win32 宿主 ownership 不清 - -问题: - -1. `EditorWindow` 仍保留大量窗口、输入、渲染、DPI、capture、chrome、workspace 行为。 -2. controller 已经存在,但很多只是借 `friend` 操作 `EditorWindow` 内部状态。 -3. 宿主与 controller 同时拥有职责,边界不稳定。 - -根因: - -- controller 是从大对象里抽出来的,但 `EditorWindow` 自身没有同步瘦身。 -- 共享状态 contract 未明确,导致继续透权。 - -收口动作: - -1. 重新定义 `EditorWindow` 的职责,只保留 HWND / message / facade /生命周期入口。 -2. 将输入、chrome、frame orchestration、runtime 更新的实际 ownership 下沉到 controller。 -3. 用明确 state contract 替代大面积 `friend` 与私有转发。 -4. 清理薄转发 cpp 文件。 - -验收: - -- `EditorWindow.h` 私有方法数量显著下降。 -- controller 不再依赖大量跨域私有入口。 - -### 五. 平台层反向依赖 UI 语义 - -问题: - -1. `EditorWindowChromeController` 仍直接依赖 `UIEditorTheme`、`UIEditorPanelRegistry` 等上层 UI 类型。 -2. 平台层对“标题栏显示什么 tab、什么 panel title、什么 theme metrics”知道得太多。 - -根因: - -- 宿主 chrome 渲染逻辑仍混入编辑器语义,而不是依赖独立的宿主展示 contract。 - -收口动作: - -1. 提取宿主 chrome 所需的独立 presentation contract。 -2. 平台层只消费宿主展示结果,不直接查询 panel registry 或 theme 聚合头。 -3. 将 detached tab/title 的编辑器语义计算上移到 app/runtime 层。 - -验收: - -- `app/Platform/Win32` 不再直接 include 聚合 UI 头来计算编辑器展示语义。 - -### 六. ProjectPanel 职责堆积 - -问题: - -1. `ProjectPanel` 同时管理 tree、grid、breadcrumb、rename、context menu、drag/drop、命令路由、命中测试、布局。 -2. 头文件里聚集了大量私有子域结构与帮助函数。 -3. 后续任何功能调整都会继续把该类推大。 - -根因: - -- Project feature 尚未形成独立子域。 -- 之前为尽快打通真实行为,把所有逻辑先集中放进了主 panel。 - -收口动作: - -1. 提取 layout / hit test 子域。 -2. 提取 breadcrumb / grid presentation 子域。 -3. 提取 context menu / rename / command target 子域。 -4. 提取 tree-grid drag/drop 协调子域。 -5. `ProjectPanel` 只保留 runtime 注入、focus 编排、事件汇总。 - -验收: - -- `ProjectPanel.h/.cpp` 不再承载全部 UI 子域细节。 -- Project feature 的每个子文件都有明确 ownership。 - -### 七. Feature 事件模型不统一 - -问题: - -1. `HierarchyPanel`、`ProjectPanel` 使用各自的局部 event 结构。 -2. Scene / Inspector 也在各自路径里用专有交互结果。 -3. 顶层同步逻辑必须知道每个 feature 的细节。 - -根因: - -- 缺少统一的 app event 契约和 feature output 契约。 - -收口动作: - -1. 定义统一 `EditorAppEvent` 或等价契约。 -2. feature 局部事件先映射为 app event,再由同步入口消费。 -3. 保留 feature 内部私有状态,但不再将私有事件结构外泄。 - -验收: - -- 顶层不再显式依赖多个 feature-specific event 类型。 - -### 八. WorkspaceEventSync 仍是临时胶水 - -问题: - -1. `WorkspaceEventSync` 直接读 panel event,再同步 `EditorContext` / `SceneRuntime`。 -2. 它仍包含具体 panel 事件描述逻辑和面板级状态文案拼装。 - -根因: - -- 统一 app event 管线尚未建立。 - -收口动作: - -1. 让 `WorkspaceEventSync` 只消费统一 app event。 -2. 把 panel-specific 事件描述、状态消息格式化下沉到各 feature adapter。 -3. 将 scene open、selection sync、status update 都纳入正式事件消费流程。 - -验收: - -- `WorkspaceEventSync` 不再包含对具体 panel event 结构的硬编码。 - -### 九. Scene viewport / gizmo 复杂度仍过高 - -问题: - -1. `SceneViewportTransformGizmoSupport.cpp` 仍是超大单体实现。 -2. gizmo 数学、命中测试、绘制、操作状态、模式逻辑仍高度耦合。 - -根因: - -- 旧逻辑迁移后完成了行为闭环,但还没有按稳定子域回收。 - -收口动作: - -1. 按 move / rotate / scale 或按 pick / render / manipulate 子域拆分 gizmo 支撑实现。 -2. 提取共享数学 / 投影 / 命中工具。 -3. 让 controller 只编排,不直接承担高复杂度细节。 - -验收: - -- gizmo 支撑逻辑不再集中在一个超大文件中。 -- gizmo 模式切换与交互行为更容易定向测试。 - -## 执行顺序 - -为了避免返工,按以下顺序执行: - -1. 公共接口层去聚合 -2. 文件粒度与 ownership 标准归一 -3. Win32 宿主 ownership 收拢 -4. 平台层反向 UI 依赖切除 -5. `EditorShellRuntime` 去中心化 -6. `ProjectPanel` 子域拆分 -7. feature 统一事件契约 -8. `WorkspaceEventSync` 正式化 -9. Scene viewport / gizmo 子域拆分 -10. 测试与回归收口 - -## 验证 - -每个阶段至少执行: - -1. `cmake --build build_codex_verify --config Debug --target XCUIEditor editor_ui_smoke_targets` -2. `ctest --test-dir build_codex_verify -C Debug -R xcui_editor_app_smoke --output-on-failure` -3. `tests/UI/Editor/unit` 的相关定向回归 - -必要时补充: - -1. 公共头 include 面回归 -2. Win32 多窗口 / tab 拖放 / 独立窗口回归 -3. Scene gizmo 交互回归 -4. Project tree / grid / rename / context menu / drag-drop 回归 - -## 风险控制 - -1. 不修改 `managed`、`SRP`、用户其他工作流中的脏改动。 -2. 任何拆分都必须围绕稳定 ownership,不允许再出现“一两个函数一个 cpp”的假拆分。 -3. 任何合并都必须基于修改耦合与职责一致性,不做机械回并。 -4. 若执行中发现某条边界不足以支撑后续步骤,优先补 contract,再迁移实现。 diff --git a/docs/used/NewEditor_宿主重构计划_2026-04-13.md b/docs/used/NewEditor_宿主重构计划_2026-04-13.md deleted file mode 100644 index 033efbdc..00000000 --- a/docs/used/NewEditor_宿主重构计划_2026-04-13.md +++ /dev/null @@ -1,180 +0,0 @@ -# NewEditor 宿主重构计划 - -## 目标 - -把 `new_editor` 当前的窗口宿主从“功能可运行的过渡方案”收敛成可长期演进的 Editor 宿主架构,核心目标如下: - -1. 主显示链最终统一到纯 D3D12。 -2. 窗口线程只负责消息和状态,不承担 GPU 重活。 -3. live resize 必须真实更新,但不能再走同步阻塞窗口线程的路径。 -4. Editor shell 和 Scene/Game viewport 统一纳入宿主合成层。 -5. 当前 `D3D11On12 + D2D` 只允许作为过渡路径,不能继续加深耦合。 - -## 当前问题 - -### 1. 宿主职责混在 `Application` - -当前 `Application` 同时承担: - -- Win32 消息调度 -- 宿主运行时状态 -- resize / dpi / deferred render 调度 -- editor 状态更新 -- present 前后的宿主控制 - -这会导致宿主问题难以单独定位和演进。 - -### 2. 主显示链过厚 - -当前主路径仍然依赖: - -- D3D12 viewport 渲染 -- swapchain backbuffer -- D3D11On12 wrapped resource -- D2D 绘制 shell -- present - -这条链在 live resize、frame pacing、backbuffer 生命周期上都偏重。 - -### 3. resize 热路径仍然偏保守 - -虽然已经把 `WM_SIZE` 从直接同步 resize 调整为 deferred render 触发,但 resize 热路径里仍存在: - -- backbuffer interop target 重建 -- swapchain resize -- 资源生命周期收束 - -这还不是最终架构。 - -## 重构阶段 - -## 阶段 1:宿主分层 - -### 目标 - -先把结构理顺,停止继续把宿主逻辑堆进 `Application`。 - -### 任务 - -1. 拆出宿主运行时状态对象。 -2. 拆出窗口消息调度与 deferred render 调度。 -3. 让 `Application` 只保留 editor 业务更新与高层协作职责。 -4. 为后续 HostRenderer / HostCompositor 留清晰边界。 - -### 完成标准 - -1. resize / dpi / deferred render / interactive resize 状态不再散落在 `Application` 成员里。 -2. `Application` 的宿主状态字段明显减少。 -3. 现有功能与布局不回退。 - -## 阶段 2:建立 HostRenderer / HostCompositor 边界 - -### 目标 - -把宿主渲染拆成明确两层: - -1. `HostRenderer` - 责任:device / queue / swapchain / backbuffer / fence / present -2. `HostCompositor` - 责任:把 shell draw data、viewport texture、icon/text 统一合成到 backbuffer - -### 任务 - -1. 停止让 `NativeRenderer` 既像窗口绘制器又像过渡 compositor。 -2. 把“主窗口显示”和“UI 绘制命令解释”职责显式拆开。 -3. 为纯 D3D12 compositor 做接口准备。 - -### 完成标准 - -1. `HostRenderer` 不依赖 D2D 语义。 -2. `HostCompositor` 成为唯一的宿主 UI 合成入口。 -3. 现有 `NativeRenderer` 明确退化为过渡层或 fallback。 - -## 阶段 3:主显示链切换到纯 D3D12 - -### 目标 - -去掉 `D3D11On12 + D2D` 在主显示链中的核心地位。 - -### 任务 - -1. shell 矩形、线条、图像、文字统一进入 D3D12 UI compositor。 -2. Scene/Game viewport 作为普通 SRV 输入参与同一条 compositor pass。 -3. backbuffer 只通过 D3D12 呈现。 - -### 完成标准 - -1. 主窗口显示不再依赖 D2D。 -2. resize 时不再重建 D3D11On12 backbuffer interop target。 -3. `new_editor` 主显示链可在没有 D3D11On12 的条件下工作。 - -## 阶段 4:重写 resize 状态机 - -### 目标 - -做到真实 live resize,但不阻塞窗口线程。 - -### 任务 - -1. `WM_SIZE` 只更新最新目标尺寸。 -2. render tick 只消费最新尺寸,不处理过期尺寸。 -3. resize 不允许在消息处理里做 GPU 等待。 -4. resize / present / viewport surface 生命周期统一到宿主状态机。 - -### 完成标准 - -1. 拖动窗口边界时不黑屏。 -2. 不出现黑白垃圾区。 -3. 窗口拖动体感明显优于当前实现。 - -## 阶段 5:去掉 resize 路径里的全队列等待 - -### 目标 - -去掉当前最重的同步点。 - -### 任务 - -1. 改成 per-backbuffer / per-frame 生命周期管理。 -2. 只等待必须退休的资源代际。 -3. 禁止 resize 路径里的整队列 idle 等待。 - -### 完成标准 - -1. resize 期间 CPU/GPU 同步明显减少。 -2. live resize 手感继续改善。 - -## 阶段 6:viewport 生命周期收口 - -### 目标 - -让 Scene/Game viewport 与外层宿主真正解耦。 - -### 任务 - -1. viewport render target 长期存活,不跟着宿主链大拆大建。 -2. 宿主 compositor 只采样 viewport 结果,不干预其内部资源生命周期。 -3. Scene/Game 在 interactive resize 期间保持稳定。 - -### 完成标准 - -1. viewport 不再因宿主 resize 逻辑出现黑屏或闪烁。 -2. 宿主和 viewport 各自职责明确。 - -## 执行顺序 - -1. 先完成阶段 1。 -2. 然后搭好阶段 2 的 HostRenderer / HostCompositor 边界。 -3. 再推进阶段 3,切主显示链到纯 D3D12。 -4. 最后做阶段 4、5、6 的性能与生命周期收口。 - -## 当前落点 - -当前阶段 1 已完成,阶段 2 已开始收口: - -1. 已新增 `HostRuntimeState`,把宿主 DPI / interactive resize / pending resize / deferred render 状态从 `Application` 中抽离。 -2. 已新增 `WindowMessageDispatcher`,把 `WndProc` 中的宿主消息调度与 deferred render 调度拆到 `Host` 层。 -3. 已把 `D3D12WindowRenderLoop` 从悬空 helper 升级为主窗口帧编排入口,开始统一 `BeginFrame / viewport render / UI present / fallback / resize interop` 这条链。 -4. 已把 viewport render target 资源工厂从 `ProductViewportRenderTargets.h` 收成独立 manager,`ProductViewportHostService` 只保留请求/返回 frame 的业务外壳。 -5. 已把 shader resource descriptor 分配职责从 `D3D12WindowRenderer` 抽到独立的 `D3D12ShaderResourceDescriptorAllocator`,减少窗口呈现器的非 swapchain 职责。 -6. 下一步进入阶段 2 主体:继续拆 `NativeRenderer` 中的窗口互操作 / present 组合路径,把 D2D UI 光栅化与 D3D11On12 窗口合成进一步分离。 diff --git a/docs/used/NewEditor_帧调度与UI合成性能重构计划_过期归档_2026-04-21.md b/docs/used/NewEditor_帧调度与UI合成性能重构计划_过期归档_2026-04-21.md deleted file mode 100644 index 6440aaf5..00000000 --- a/docs/used/NewEditor_帧调度与UI合成性能重构计划_过期归档_2026-04-21.md +++ /dev/null @@ -1,112 +0,0 @@ -# NewEditor 帧调度与 UI 合成性能重构计划 2026-04-21 - -## 说明 - -- 本计划只覆盖 `new_editor` 当前最关键的底层性能问题,不扩散到一般性 UI 绘制优化。 -- 当前判断的主因不是单个控件或某个面板,而是 `D3D12 viewport -> D3D11On12/D2D UI 合成 -> frame fence wait` 这条热路径的帧调度设计。 -- 本计划的目标不是先做零散 patch,而是先把帧上下文复用、swapchain/backbuffer 复用、UI 合成提交三者的边界理顺。 - -## 当前问题 - -### 1. 帧上下文复用与 backbuffer 复用被错误耦合 - -- `D3D12WindowRenderer::BeginFrame()` 当前按 backbuffer index 驱动 `D3D12HostDevice::BeginFrame(frameIndex)`。 -- `D3D12HostDevice::BeginFrame()` 会在帧入口直接 `WaitForFrame(frameIndex)`。 -- 当前 frame fence 记录的不是“命令列表/allocator 可以复用”的完成点,而是整轮 viewport 渲染加 UI 合成之后的完成点。 - -这会导致: - -- CPU 录制下一帧命令时,被迫等待上一轮同 backbuffer 的整帧工作完成。 -- 帧上下文复用不能独立于 swapchain buffer 轮转。 -- 多帧在飞被压缩成接近串行执行。 - -### 2. UI 合成处于 D3D11On12 热路径并且每帧强制提交 - -- `NativeRenderer::RenderToWindowRenderer()` 会在 viewport D3D12 提交后进入 D3D11On12/D2D 合成。 -- 合成路径每帧会 `AcquireWrappedResources` / `ReleaseWrappedResources`,并且最终 `d3d11DeviceContext->Flush()`。 -- frame completion fence 在这轮 interop flush 之后才发出。 - -这意味着: - -- UI 合成不是异步附属工作,而是当前帧主路径的一部分。 -- 下一帧等待会覆盖 UI interop 的全部提交成本。 -- 当前架构天然放大 D3D11On12 互操作开销。 - -### 3. swapchain 延迟策略过于激进 - -- `D3D12WindowSwapChainPresenter::ConfigureFrameLatency()` 当前固定 `SetMaximumFrameLatency(1u)`。 -- `D3D12WindowSwapChainPresenter` 当前仅使用 2-buffer swapchain。 -- `D3D12HostDevice` 实际已经准备了 3 份 frame context,但没有被正确利用。 - -这会进一步压缩: - -- CPU/GPU 重叠空间 -- queue 深度 -- backbuffer 可回旋余量 - -## 重构目标 - -### Goal 1. 拆开 frame slot 与 backbuffer index - -- 引入独立的 frame slot 轮转,不再直接把 backbuffer index 作为 host frame context index。 -- frame fence 仅用于 frame slot 级别的命令列表/allocator 复用。 -- backbuffer 仍由 swapchain 当前 index 决定,但不再承担 frame slot 生命周期管理职责。 - -### Goal 2. 放宽当前帧流水深度 - -- 把 swapchain buffer count 与 host frame context 数量对齐到 3。 -- 去掉当前 `maximum frame latency = 1` 的极端限制,恢复合理的 frame queue 深度。 -- 让 CPU 可以在不破坏正确性的前提下,至少维持 2 到 3 帧的录制/执行重叠。 - -### Goal 3. 保持现有 UI backend 可运行前提下,降低热路径同步强度 - -- 不在本轮直接重写成 D3D12-native UI renderer。 -- 先保证当前 D3D11On12 合成链不再把 frame slot 复用错误地锁死在 backbuffer 完成点上。 -- 如有必要,额外清理 interop 资源生命周期中明显的每帧重复初始化行为。 - -## 执行阶段 - -### Phase 1. 帧调度语义重构 - -1. 审查 `D3D12WindowRenderer` 当前 active frame index 与 active backbuffer index 的耦合点。 -2. 引入独立的 frame slot index。 -3. 修改 `GetRenderContext()`、`BeginFrame()`、`SignalFrameCompletion()` 等调用链,使其按 frame slot 工作。 -4. 确保 frame fence 的含义收敛为“该 frame slot 可安全复用”。 - -### Phase 2. swapchain 策略重构 - -1. 将 swapchain buffer count 调整为 3。 -2. 调整 `SetMaximumFrameLatency(...)` 策略,不再固定 1。 -3. 检查 resize、backbuffer target rebuild、present 路径是否仍然成立。 - -### Phase 3. interop 热路径复核 - -1. 复核 `PrepareSourceTextures()` 是否存在每帧重复创建包装资源的重活。 -2. 若重构 frame slot 后仍存在显著不合理同步,再做最小范围 interop 资源缓存。 -3. 不在本轮扩展为完整 D3D12-native UI 渲染器,避免范围失控。 - -## 验收标准 - -- `XCUIEditorAppLib` 与 `XCUIEditorApp` 能重新编译通过。 -- `new_editor` 主窗口仍可正常显示 viewport 与 shell UI。 -- 渲染路径中不再出现“frame slot 等待直接绑定 backbuffer index”的旧语义。 -- swapchain/backbuffer 数量与 frame slot 配置一致,frame latency 不再固定为 1。 -- 本轮修改不引入新的窗口 resize、present、viewport 显示回退问题。 - -## 提交策略 - -### Commit A. 当前 new_editor 工作树快照 - -- 仅提交当前 `new_editor/**` 改动与本计划文档。 -- 目标是先固化当前正在进行的大重构工作,避免后续性能重构混入历史改动。 - -### Commit B. 帧调度/同步链重构 - -- 只提交本次性能问题修复直接相关的帧调度与 present/interop 变更。 -- 提交前至少完成 `XCUIEditorAppLib` 或 `XCUIEditorApp` 构建验证。 - -## 风险 - -- 当前仓库存在大量 `new_editor` 以外的脏改动,不能被这轮误带入提交。 -- `XCUIEditor.exe` 若在运行,会阻塞最终链接验证。 -- 即便本轮重构完成,也不能提前承诺最终帧率一定达到某个绝对数字;本轮目标是拆除最明显的底层串行瓶颈。 diff --git a/docs/used/NewEditor_收口主线计划_2026-04-15.md b/docs/used/NewEditor_收口主线计划_2026-04-15.md deleted file mode 100644 index 94eaaa60..00000000 --- a/docs/used/NewEditor_收口主线计划_2026-04-15.md +++ /dev/null @@ -1,419 +0,0 @@ -# NewEditor 收口主线计划 - -日期: `2026-04-15` - -## 1. 文档定位 - -这份文档是当前 `new_editor` 主线的唯一有效收口计划。 - -当前已经归档的旧文档: - -1. `docs/plan/used/NewEditor_结构收口与正式迁移计划_2026-04-15.md` -2. `docs/plan/used/Editor架构说明_2026-04-15归档.md` - -归档原因不是这些文档完全错误,而是它们分别对应了更早阶段的主线判断: - -1. 一份偏“先把基础设施收口再说” -2. 一份偏“旧 editor / ImGui 架构说明” - -现在的真实主线已经变成: - -1. `new_editor` 已经不是单纯试验场,而是新的正式编辑器产品线 -2. 需要一边继续按旧 `editor` 验证过的行为迁移,一边把已经暴露出来的结构性临时实现收回到底层 -3. 任何新的功能推进,都必须服务于“让 `new_editor` 真正具备替代旧 editor 的基础” - -## 2. 当前事实 - -### 2.1 已经具备的基础 - -当前 `new_editor` 已经具备以下正式基础: - -1. `XCEditor` 库层目录边界已经明确: - `Collections / Docking / Fields / Foundation / Menu / Panels / Shell / Viewport / Widgets / Workspace` -2. `new_editor/app` 宿主层目录边界已经明确: - `Bootstrap / Composition / Features / Platform / Rendering / State / Support / UI` -3. `new_editor` 已经具备以下真实能力: - - 主窗口与无边框宿主 - - Dock 布局 - - Tab 拖拽与独立窗口 - - 基本的 Scene / Game / Hierarchy / Inspector / Project / Console 面板壳层 - - Viewport 宿主接线 - - Editor 风格样式体系 -4. 当前工作已经不是“先证明 UI 能画出来”,而是“把它收成一个能继续承载产品迁移的正式系统” - -### 2.2 当前最明显的结构债 - -结合最近代码审查,当前最需要收掉的不是零散视觉问题,而是下面几类根因问题: - -1. 宿主层 `app/Composition` 仍然承接了不该由宿主拥有的布局算法和纠偏逻辑 - 典型位置: - - `new_editor/app/Composition/EditorShellRuntimeUpdate.cpp` -2. Dock preview 的一部分表现仍然通过宿主层绕行 - 典型位置: - - `new_editor/app/Composition/EditorShellRuntimeRendering.cpp` -3. 编辑命令桥仍然存在明确的 placeholder / not attached yet 路由 - 典型位置: - - `new_editor/app/Composition/EditorHostCommandBridge.cpp` -4. `XCEditor` 库层部分控件交互实现已经出现明显复制型膨胀 - 典型位置: - - `new_editor/src/Fields/UIEditorVector2FieldInteraction.cpp` - - `new_editor/src/Fields/UIEditorVector3FieldInteraction.cpp` - - `new_editor/src/Fields/UIEditorVector4FieldInteraction.cpp` -5. `Workspace / Docking / PropertyGrid` 仍然存在热点超大文件,继续堆功能会快速失控 - -## 3. 当前主线目标 - -当前这轮工作的目标,不是一次性做完旧 editor 的所有业务面板,而是先让 `new_editor` 达到下面这个状态: - -1. 它的宿主层、库层、产品层边界稳定 -2. 它的 Dock / Workspace / Commands / Viewport / Panel Shell 主链稳定 -3. 它已经可以持续承接旧 `editor` 的真实功能迁移,而不会每迁一个功能就反向污染底层 -4. 它已经具备“替代 ImGui 版本 editor 作为未来主产品线”的工程条件 - -换句话说,当前收口的终点不是“所有功能全做完”,而是: - -1. `new_editor` 的底座已经稳定到可以放心继续重建新编辑器 -2. 旧 editor 后续只作为行为和工作流参考,不再作为架构依赖 - -## 4. 不可违背的执行原则 - -### 4.1 问题必须从根因修 - -禁止继续通过下面这些方式推进: - -1. 宿主层补一层临时纠偏 -2. 复制一份相似交互逻辑先跑起来 -3. 额外加 wrapper 或 fallback bridge 掩盖边界错误 -4. 在产品层手写布局/渲染补丁,反向规避库层问题 - -只要问题根因在: - -1. `XCEditor` -2. `new_editor/app` -3. `ViewportHostService` -4. Dock / Workspace / Commands 的 owner 划分 - -就必须回到底层修。 - -### 4.2 Editor UI 不走 Runtime 的资源样式体系 - -`Runtime UI` 继续保留资源化样式能力。 - -`Editor UI` 固定使用代码内建样式语义,不再继续为 editor 加厚资源样式体系。 - -### 4.3 宿主层只做宿主该做的事 - -`new_editor/app` 允许拥有: - -1. Win32 窗口与无边框宿主 -2. D3D12 / Native 渲染接线 -3. Viewport 目标窗口和交换链生命周期 -4. 产品层状态装配 -5. 面板 feature 的真实业务 owner - -`new_editor/app` 不应继续拥有: - -1. Dock 布局算法 -2. Workspace 布局测量算法 -3. Dock preview 绘制 owner -4. 通用 field 交互算法 - -### 4.4 `XCEditor` 必须是可复用的 Editor UI 基础库 - -`XCEditor` 负责: - -1. 共享主题、控件、交互状态 -2. Dock / Workspace / Shell / Menu / Panel 壳层 -3. Editor 风格字段控件与 property grid -4. 与宿主无关的布局、命中测试、交互、绘制逻辑 - -它不负责: - -1. 具体项目资源管理 -2. 具体实体、组件、场景业务 -3. 项目级 commands 的最终执行业务 - -### 4.5 旧 editor 只参考行为,不复制旧问题 - -旧 `editor` 是行为和工作流基准,不是必须逐文件照搬的结构模板。 - -迁移时遵循: - -1. 旧 editor 的功能行为、交互语义、信息组织需要对齐 -2. 旧 editor 中不合理的 owner、重复逻辑、历史包袱不需要照搬 - -## 5. 收口阶段划分 - -整个收口按 5 个阶段推进。 - -其中: - -1. 阶段 A、B 是结构债清理 -2. 阶段 C、D 是正式产品骨架收口 -3. 阶段 E 是进入长期迁移前的验收闭环 - -## 6. 阶段 A:宿主层越界逻辑回收 - -### 6.1 目标 - -把当前最明显的“本该属于库层,却临时堆在宿主层”的逻辑收回去。 - -### 6.2 主要任务 - -1. 回收 splitter drag correction 相关逻辑 - 当前问题: - - `EditorShellRuntimeUpdate.cpp` 内部已经包含 workspace node 测量、split chain 构建、ratio 修正、snapshot restore - 目标: - - 把这些能力收回 `XCEditor/Workspace` 或 `XCEditor/Docking` - - 宿主层只保留“请求更新”和“应用结果” - -2. 回收 dock preview overlay owner - 当前问题: - - `EditorShellRuntimeRendering.cpp` 通过“先去掉 preview,再宿主层追加 overlay”绕过层级问题 - 目标: - - preview 的绘制与层级关系重新回到 `XCEditor` 的 compose/render 主链 - - 宿主层不再手工补画 - -3. 宿主层 update/render 职责再切薄 - 目标: - - `EditorShellRuntimeUpdate.cpp` - - `EditorShellRuntimeRendering.cpp` - - `EditorShellRuntime.cpp` - 三者只承担编排,不再继续承接底层算法 - -### 6.3 完成标准 - -满足以下条件,阶段 A 才算完成: - -1. `app/Composition` 中不再存在 Dock / Workspace 的核心算法复制实现 -2. 宿主层不再手动画 dock preview -3. 宿主层 update/render 文件只保留装配、状态同步、宿主调用 - -## 7. 阶段 B:命令桥与面板路由收口 - -### 7.1 目标 - -把 `new_editor` 当前“看起来已经有菜单和命令,但里面还是 placeholder route”的部分收成真实链路。 - -### 7.2 主要任务 - -1. 清理 `EditorHostCommandBridge.cpp` 中的 placeholder message - 包括但不限于: - - `About placeholder is ready.` - - `Hierarchy edit route placeholder is active.` - - `Project edit route placeholder is active.` - - `not attached yet` - -2. 建立真实的命令 owner 分层 - 目标划分: - - `XCEditor` 只负责命令意图与交互事件 - - `new_editor/app/Features/*` 负责真实业务执行 - - `Composition` 只负责桥接和分发,不负责“假装已经接好” - -3. 明确 `File / Edit / View / Help / Run` 的状态语义 - 每个命令都必须属于下面三类之一: - - 已接通并可执行 - - 当前明确禁用,并给出真实原因 - - 当前暂不支持,但不再伪装成 ready - -4. 打通至少一条完整 edit route 主链 - 最低要求: - - `Hierarchy` - - `Project` - 这两个 route 的 rename / delete / duplicate / copy / paste 状态判断与分发必须真实,不再是空路由 - -### 7.3 完成标准 - -1. `EditorHostCommandBridge.cpp` 中不再存在 placeholder route 文案 -2. 菜单可用状态与实际执行状态一致 -3. route owner 清楚:UI -> bridge -> feature/service -> 业务执行 - -## 8. 阶段 C:XCEditor 热点文件拆分与去复制化 - -### 8.1 目标 - -把当前已经出现明显膨胀趋势的 `XCEditor` 热点实现收成稳定粒度,避免后续继续往上堆功能时直接炸开。 - -### 8.2 第一优先级热点 - -1. `Workspace` -2. `Docking` -3. `Fields/PropertyGrid` -4. `Fields/Vector*Interaction` - -### 8.3 主要任务 - -1. 拆分 `Workspace` 热点 - 典型目标: - - model mutation - - snapshot / persistence - - layout transform - - selection / active tab 修复逻辑 - -2. 拆分 `Docking` 热点 - 典型目标: - - hit test - - drag session - - drop preview resolve - - splitter interaction - - compose / rendering helpers - -3. 拆分 `PropertyGrid` 热点 - 典型目标: - - layout build - - field spec adapter - - popup/menu interaction - - inline edit session - - rendering helpers - -4. 去掉向量控件复制型实现 - 目标: - - 把 `Vector2 / Vector3 / Vector4` 公共交互骨架抽成共享内部实现 - - 维度差异只保留在 spec、layout、component count 层 - -### 8.4 完成标准 - -1. 热点文件职责按“模型 / 布局 / 交互 / 绘制 / helper”拆清 -2. 不再保留明显三份复制的向量交互主逻辑 -3. 新增功能不需要再在多个近似文件上同步改三四遍 - -## 9. 阶段 D:NewEditor 产品骨架正式收口 - -### 9.1 目标 - -在结构债收掉之后,把 `new_editor` 的正式产品骨架补到可持续迁移旧 editor 的程度。 - -### 9.2 范围 - -这一阶段不是“把全部业务一次性补完”,而是把未来所有迁移都要依赖的产品骨架收好。 - -### 9.3 主要任务 - -1. 收好 panel feature owner - 明确以下模块的 owner 与边界: - - `Features/Hierarchy` - - `Features/Project` - - `Features/Inspector` - - `Features/Console` - - `Rendering/Viewport` - -2. 继续对齐旧 editor 的核心壳层行为 - 包括但不限于: - - 顶部栏 - - Tab stack - - Dock 布局 - - 独立窗口 - - Hierarchy tree - - Project 左树右内容基础工作流 - -3. 把 Scene / Game / Hierarchy / Project / Inspector / Console 的“面板骨架 + 事件主链”收完整 - 要求: - - 面板之间的 selection / route / active panel 关系稳定 - - 不是只有 UI 外观,而是具备正式的事件 owner - -4. Viewport 主链继续按旧 editor 行为校准 - 包括但不限于: - - resize - - render target 生命周期 - - scene/game panel 内画布响应 - - 宿主窗口与 viewport 同步关系 - -### 9.4 完成标准 - -1. `new_editor` 的产品层已经不是一组零散试验 panel -2. 每个主面板都有清楚的 feature owner -3. 继续迁移旧 editor 功能时,优先是补 feature/service,不再反向污染 `XCEditor` - -## 10. 阶段 E:测试、文档与进入长期迁移前的验收 - -### 10.1 目标 - -让这轮收口以可验证的方式结束,而不是“表面能跑”。 - -### 10.2 主要任务 - -1. 补齐测试归属 - 规则固定为: - - Core 能力进 `tests/UI/Core` - - Runtime UI 能力进 `tests/UI/Runtime` - - Editor UI 能力进 `tests/UI/Editor` - -2. 区分 unit 与 integration - - unit:自动验证模型、路由、状态、布局 - - integration:可交互 exe,用于人工检查 - -3. 给 `new_editor` 建立最小 smoke 验证清单 - 至少覆盖: - - 启动 - - 主窗口布局 - - Dock 拖拽 - - Tab 独立窗口 - - 拖回合并 - - splitter resize - - viewport resize - - hierarchy / project 基础交互 - -4. 文档同步 - - 当前主线计划保留在 `docs/plan` - - 阶段性计划与过期计划放 `docs/plan/used` - - 测试规范留在 `tests/UI` - -### 10.3 完成标准 - -1. 这轮收口有明确 smoke checklist -2. 测试归属不再混乱 -3. `docs/plan` 中只保留当前有效主线文档 - -## 11. 并行切分建议 - -在不打乱 owner 的前提下,当前主线可以拆成 6 个并行工作流: - -1. 宿主层越界逻辑回收 - 负责 `app/Composition`、`app/Rendering` 与 `XCEditor` 的边界纠正 - -2. Dock / Workspace 收口 - 负责 `Workspace`、`Docking` 的算法 owner 与文件拆分 - -3. Fields / PropertyGrid 收口 - 负责 property grid、enum/color/vector 字段交互与绘制拆分 - -4. Commands / Route 收口 - 负责菜单、快捷键、active route、feature 命令桥 - -5. Product Features 对齐旧 editor - 负责 `Hierarchy / Project / Inspector / Console / Viewport` 的壳层与工作流迁移 - -6. 测试与 smoke 体系 - 负责 `tests/UI/*`、集成测试 exe、冒烟清单 - -并行规则: - -1. 同一时刻只允许一个工作流修改同一块 owner 文件 -2. 如果一个问题的根因在底层,产品层工作流必须停下来,先等底层收掉 - -## 12. 收口判断 - -当满足以下条件时,说明这轮 `new_editor` 收口真正完成,可以放心继续作为未来正式编辑器主线推进: - -1. `app/Composition` 不再保留明显越界的 Dock / Workspace 临时算法 -2. 命令桥不再存在 placeholder route -3. `XCEditor` 的 Workspace / Docking / PropertyGrid / VectorField 热点完成拆分与去复制化 -4. `new_editor` 的主面板 feature owner 清晰,继续迁移旧 editor 功能时不再反向污染底层 -5. UI 测试归属明确,至少有稳定 smoke 清单 -6. `new_editor` 可以作为正式产品线继续承接旧 editor 的剩余功能迁移 - -## 13. 当前阶段的直接下一步 - -如果按当前代码现状继续推进,下一步的优先顺序固定为: - -1. 先处理阶段 A - 先把 `EditorShellRuntimeUpdate.cpp` 与 `EditorShellRuntimeRendering.cpp` 的越界逻辑回收到 `XCEditor` - -2. 再处理阶段 B - 把 `EditorHostCommandBridge.cpp` 的 placeholder route 清成真实桥接 - -3. 再处理阶段 C - 从 `Workspace / Docking / PropertyGrid / Vector*Interaction` 开始拆热点 - -4. 然后继续阶段 D - 在稳定底座上继续对齐旧 editor 的产品工作流 diff --git a/docs/used/NewEditor_方案1无边框宿主采用计划_2026-04-14.md b/docs/used/NewEditor_方案1无边框宿主采用计划_2026-04-14.md deleted file mode 100644 index 1a2cbede..00000000 --- a/docs/used/NewEditor_方案1无边框宿主采用计划_2026-04-14.md +++ /dev/null @@ -1,283 +0,0 @@ -# NewEditor 方案1无边框宿主采用计划 - -日期: `2026-04-14` - -## 1. 目标 - -把 `new_editor` 从当前的原生 `WS_OVERLAPPEDWINDOW + DWM + HWND swapchain` 宿主模式,重构为: - -- 无边框顶层窗口 -- 自绘标题栏与边框 -- 应用自己接管 move / resize / maximize / restore / hit-test -- 宿主渲染链与窗口尺寸变化由同一套状态机驱动 - -核心目的只有一个: - -尽可能消除当前原生窗口 live resize 期间那种“旧帧被系统先拉伸,再等新帧补上”的视觉变形。 - -## 2. 为什么必须走这条路 - -当前路径的问题不是 XCUI 布局树本身,而是宿主显示链顺序决定的: - -1. Windows 先改变原生窗口外框尺寸。 -2. DWM 立即需要一张图去填新窗口区域。 -3. `new_editor` 再收到 `WM_SIZE`,之后才开始: - - `ResizeBuffers` - - backbuffer / interop target 处理 - - UI + viewport 合成 - - `Present` -4. 这中间只要慢一个 compositor tick,就会看到旧尺寸帧被拉伸。 - -只要继续使用原生 non-client resize,这个问题就只能减轻,不能彻底归零。 - -## 3. 方案1的总思路 - -不要再让系统驱动 resize 交互。 - -改为: - -1. 窗口本身使用无边框样式。 -2. 顶部标题栏、拖动区、8 个 resize grip 全部放到 client area。 -3. 鼠标拖动边界时,不进入系统的 `WM_ENTERSIZEMOVE` 模态循环。 -4. 应用自己维护一套 `WindowFrameController`: - - 记录拖动起点 - - 计算目标外框矩形 - - 先驱动宿主渲染链切到目标尺寸 - - 再提交窗口矩形变化与新帧 present - -这样窗口尺寸变化、swapchain 尺寸变化、UI 布局变化、present 节奏都由应用自己掌控,而不是被 Windows 原生外框拆成两段。 - -## 4. 重构边界 - -这次重构只动 `new_editor/app/Host` 宿主层,不把业务逻辑塞进去。 - -### 4.1 保留不动的层 - -- `XCEditor` 基础 UI 组件层 -- `new_editor/app/Workspace` -- `new_editor/app/Panels` -- `Viewport` 业务层 - -### 4.2 主要改造层 - -- [Application.cpp](D:/Xuanchi/Main/XCEngine/new_editor/app/Application.cpp) -- [Application.h](D:/Xuanchi/Main/XCEngine/new_editor/app/Application.h) -- [WindowMessageDispatcher.cpp](D:/Xuanchi/Main/XCEngine/new_editor/app/Host/WindowMessageDispatcher.cpp) -- [WindowMessageDispatcher.h](D:/Xuanchi/Main/XCEngine/new_editor/app/Host/WindowMessageDispatcher.h) -- [HostRuntimeState.h](D:/Xuanchi/Main/XCEngine/new_editor/app/Host/HostRuntimeState.h) -- [D3D12WindowRenderLoop.cpp](D:/Xuanchi/Main/XCEngine/new_editor/app/Host/D3D12WindowRenderLoop.cpp) -- [D3D12WindowRenderer.cpp](D:/Xuanchi/Main/XCEngine/new_editor/app/Host/D3D12WindowRenderer.cpp) -- [D3D12WindowSwapChainPresenter.cpp](D:/Xuanchi/Main/XCEngine/new_editor/app/Host/D3D12WindowSwapChainPresenter.cpp) - -### 4.3 新增宿主对象 - -建议新增以下文件: - -- `new_editor/app/Host/WindowFrameMetrics.h` -- `new_editor/app/Host/WindowFrameController.h` -- `new_editor/app/Host/WindowFrameController.cpp` -- `new_editor/app/Host/WindowFrameInteractionState.h` -- `new_editor/app/Host/BorderlessWindowStyle.h` - -职责划分: - -- `WindowFrameMetrics` - - 统一管理标题栏高度、resize 边缘厚度、阴影扩展、caption button 区域 -- `WindowFrameInteractionState` - - 记录当前是否在 move / resize - - 记录激活边、起始窗口矩形、起始鼠标屏幕坐标 -- `WindowFrameController` - - 处理 hit-test、开始拖动、更新拖动、结束拖动 - - 计算目标窗口矩形 - - 输出“本帧宿主需要切到什么尺寸” -- `BorderlessWindowStyle` - - 集中处理 `WS_POPUP / WS_THICKFRAME / WS_CAPTION` 等样式组合与 DWM 扩展 - -## 5. 分阶段执行 - -## 阶段 A:把窗口宿主从原生外框切到无边框 - -### 目标 - -先完成“视觉上还是一个正常窗口,但 non-client 已经不再由系统绘制”。 - -### 任务 - -1. 创建窗口时移除 `WS_OVERLAPPEDWINDOW`,改为适合无边框的样式组合。 -2. 顶部工具栏正式承担标题栏职责。 -3. 处理: - - 最小化 - - 最大化 - - 还原 - - 关闭 -4. 保留 Windows 阴影、任务栏行为、Alt+Tab 行为。 - -### 验收 - -1. 窗口外框由 XCUI 自己绘制。 -2. 顶部区域可拖动移动窗口。 -3. 基本窗口控制按钮可用。 - -## 阶段 B:接管 hit-test 与 8 向 resize 手势 - -### 目标 - -不再依赖系统 non-client resize。 - -### 任务 - -1. 在 client area 内定义: - - 左 / 右 / 上 / 下 - - 左上 / 右上 / 左下 / 右下 - 的 resize 热区。 -2. 鼠标 hover 时稳定切换对应 cursor。 -3. 鼠标按下后进入 `WindowFrameInteractionState`。 -4. 鼠标移动时由 `WindowFrameController` 计算目标外框矩形。 - -### 验收 - -1. 8 个方向 resize 都能工作。 -2. cursor 不闪烁。 -3. 不再进入 `WM_ENTERSIZEMOVE / WM_EXITSIZEMOVE` 原生模态链。 - -## 阶段 C:把窗口尺寸变化改成“应用驱动” - -### 目标 - -让窗口矩形变化和新尺寸帧提交进入同一条应用控制链。 - -### 任务 - -1. 取消当前“完全依赖 `WM_SIZE` 才处理 resize”的模式。 -2. 鼠标拖动过程中,先由 `WindowFrameController` 产出目标 client size。 -3. 宿主渲染层按目标 size: - - resize swapchain - - rebuild backbuffer view - - 重新布局 UI - - render + present -4. 然后再提交窗口矩形更新。 - -这里的关键不是“永远先后顺序绝对固定”,而是宿主要从“被动响应 Windows resize”改成“主动推进目标尺寸帧”。 - -### 验收 - -1. live resize 期间不再有明显的整窗拉伸。 -2. 内部 UI 不再跟随旧帧一起被整体放大缩小。 -3. resize 时不黑屏、不闪退。 - -## 阶段 D:把 Scene / Game viewport 也纳入同一节奏 - -### 目标 - -避免窗口外层尺寸变化和 viewport target 更新脱节。 - -### 任务 - -1. 宿主 resize 状态机输出统一尺寸变更事件。 -2. `ProductViewportHostService` 消费同一目标尺寸。 -3. viewport render target 与 host swapchain 的 resize 节奏统一。 - -### 验收 - -1. 调外层窗口尺寸时,Scene / Game 内部画布不再滞后。 -2. 不再出现外层变了、内部蓝底画布慢一拍才追上的情况。 - -## 阶段 E:补齐无边框窗口的系统行为 - -### 目标 - -把“能用”补到“能长期替代正式编辑器宿主”。 - -### 任务 - -1. 双击标题栏最大化 / 还原。 -2. 拖到屏幕顶部最大化。 -3. 屏幕边缘吸附。 -4. 多显示器 DPI / work area 正确处理。 -5. 最小窗口尺寸约束。 -6. 系统菜单与快捷键兼容。 - -### 验收 - -1. 宿主交互接近常规桌面应用。 -2. 不因无边框而丢失基本桌面体验。 - -## 6. 架构原则 - -### 6.1 不允许把窗口交互逻辑混回 `Application` - -`Application` 只能做: - -- 应用生命周期 -- 编辑器业务装配 -- 驱动一帧更新与渲染 - -窗口移动、边框 hit-test、resize 手势状态机都必须沉淀到 `Host`。 - -### 6.2 不允许为了“先跑通”堆事件补丁 - -禁止继续沿这些方向堆补丁: - -- `WM_SIZE` 后疯狂补 render -- `ValidateRect` / `InvalidateRect` 来回试 -- `DwmFlush` 之类的消息尾部补丁 -- 在 resize 过程中乱加全局 GPU idle - -这些都只能缓解,不能把宿主控制权拿回来。 - -### 6.3 宿主渲染链必须单向清晰 - -目标链路必须收敛成: - -`WindowFrameController -> Host resize state -> swapchain/backbuffer resize -> UI + viewport compose -> present` - -而不是: - -`Windows non-client -> WM_SIZE -> 若干临时补丁 -> 侥幸赶上 compositor` - -## 7. 风险点 - -### 7.1 这不是小修 - -这是宿主交互模型切换,不是单点 bugfix。 - -### 7.2 需要额外补齐桌面窗口语义 - -无边框之后,很多系统行为都要自己接: - -- caption drag -- 双击最大化 -- hit-test -- snap -- monitor work area -- DPI 变更 - -### 7.3 需要严格做阶段验收 - -每一阶段都必须编译并人工验证,不然很容易把宿主架构搞乱。 - -## 8. 推荐执行顺序 - -1. 阶段 A:无边框窗口切换 -2. 阶段 B:hit-test / resize 手势 -3. 阶段 C:应用驱动 resize 主链 -4. 阶段 D:viewport resize 节奏统一 -5. 阶段 E:补系统级桌面行为 - -## 9. 收口标准 - -满足以下条件,才算方案1真正落地: - -1. `new_editor` 已彻底脱离原生 non-client resize。 -2. 窗口拖边 resize 时,整窗拉伸变形基本消失。 -3. Scene / Game viewport 尺寸变化与外层窗口同步。 -4. resize 过程中无黑屏、无崩溃、无 cursor 闪烁。 -5. 顶部标题栏、最大化、吸附、DPI 行为达到正式编辑器可用水平。 - -## 10. 结论 - -如果目标是“尽可能彻底消除 resize 变形”,就不该继续在当前原生外框链路上修修补补。 - -正确主线是: - -把 `new_editor` 宿主改成无边框、自管标题栏、自管 resize、自管 present 节奏的 editor shell。 diff --git a/docs/used/NewEditor_架构收口计划_2026-04-15.md b/docs/used/NewEditor_架构收口计划_2026-04-15.md deleted file mode 100644 index c76f323e..00000000 --- a/docs/used/NewEditor_架构收口计划_2026-04-15.md +++ /dev/null @@ -1,289 +0,0 @@ -# NewEditor 架构收口计划 - -日期: `2026-04-15` - -## 1. 文档定位 - -这份文档是当前 `new_editor` 唯一有效的架构收口计划。 - -归档文档: -1. `docs/plan/used/NewEditor_收口主线计划_2026-04-15.md` - -归档原因: -1. 旧计划更偏“产品主线推进”和“继续迁移旧 editor 功能”。 -2. 当前暴露出来的主矛盾已经不是缺少某个具体功能,而是 `new_editor` 内部边界不稳定。 -3. 如果不先把这些结构问题收口,后续继续迁移旧 editor,只会继续把宿主、产品、库层搅乱。 - -## 2. 当前客观问题 - -### 2.1 宿主核心对象过胖 - -`new_editor/app/Platform/Win32/EditorWindow.h` - -当前 `EditorWindow` 同时承担: -1. 原生窗口生命周期 -2. 输入采集与转发 -3. borderless chrome 与 resize -4. 渲染帧驱动 -5. viewport 宿主更新 -6. tab detach / drop preview -7. screenshot / trace - -这已经不是单一职责对象,而是宿主级 god object。 - -### 2.2 样式归属不干净 - -当前 Editor 风格语义分散在三处: -1. `XCEditor/Foundation/UIEditorTheme.*` -2. `new_editor/app/UI/Styles/*` -3. 各个 panel 的 `Support.h` / `.cpp` 局部常量 - -这会导致: -1. Editor 风格的 owner 不明确 -2. 同一类控件在库层和产品层重复定义视觉 tokens -3. 后续为了改统一风格,不得不跨层改动 - -### 2.3 Support / Internal 头文件实现化 - -当前存在多类“名义上是内部头,实际上已经塞进大量实现”的文件: -1. `app/Features/*Support.h` -2. `src/*/*Internal.h` - -问题不在于名字,而在于: -1. 非模板逻辑被塞进头文件 -2. 头文件变成实现垃圾桶 -3. 编译依赖和可读性都被恶化 - -### 2.4 WindowMessageDispatcher 拆分方式错误 - -当前这组文件: -1. `WindowMessageDispatcher.h` -2. `WindowMessageDispatchHandlers.h` -3. `WindowMessageDispatcher.cpp` -4. `WindowMessageDispatcherChrome.cpp` -5. `WindowMessageDispatcherInput.cpp` -6. `WindowMessageDispatcherPointer.cpp` -7. `WindowMessageDispatcherLifecycle.cpp` - -问题不是“文件多”本身,而是: -1. 它们都服务于同一个 `WndProc` 分发状态机 -2. 共享同一套窗口状态和交互语义 -3. 拆分标准偏向“按消息类别拆”,而不是“按稳定职责拆” -4. 现在形成了“外壳 + handlers 头 + 多个实现分片”的结构噪音 - -### 2.5 若干核心实现文件过胖 - -当前存在一些已经明显过胖的热点文件,例如: -1. `src/Fields/PropertyGridRendering.cpp` -2. `src/Fields/ColorFieldRendering.cpp` -3. `src/Workspace/UIEditorWorkspaceModel.cpp` -4. `src/Shell/ShellInteractionRequest.cpp` - -这些文件的问题不是单纯行数多,而是: -1. 布局 -2. 命中测试 -3. 状态变换 -4. 绘制命令生成 - -开始在同一实现单元里混堆。 - -### 2.6 命名和边界约定没有完全统一 - -当前同时存在: -1. `Support` -2. `Internal` -3. `Runtime` -4. `Lifecycle` -5. `Frame` -6. `Handlers` - -这些后缀的含义并不总是一致,容易让目录和文件名越来越失控。 - -## 3. 收口目标 - -本轮目标不是继续堆功能,而是让 `new_editor` 达到以下状态: - -1. 宿主层、Editor UI 库层、产品层的 owner 明确 -2. Editor 风格 tokens 的 owner 明确 -3. 宿主入口消息分发清晰且可维护 -4. 头文件不再承担大块非模板实现 -5. 核心大文件按稳定职责收敛,而不是盲目拆碎 -6. 后续继续迁移旧 editor 时,不再反向污染底层 - -## 4. 不可违背的原则 - -### 4.1 不按“文件越碎越好”推进 - -拆分标准只能是稳定职责边界,不能是: -1. 按消息名拆 -2. 按函数名拆 -3. 按“看起来像分层”拆 - -### 4.2 Editor 风格 owner 固定在 Editor UI 层 - -1. `Runtime UI` 继续保留资源化样式能力 -2. `Editor UI` 不再走 runtime 那套资源规则 -3. `Editor UI` 的通用视觉 tokens 收口在 `XCEditor` -4. `app` 只允许保留真正的产品级视觉语义,不再重复定义通用控件样式 - -### 4.3 头文件只保留必要接口 - -1. 模板和必须内联的小函数可以留在头里 -2. 非模板、非性能关键的大段实现必须回到 `.cpp` -3. 禁止继续新增“Support 头即实现容器”的模式 - -### 4.4 宿主层只做宿主该做的事 - -`new_editor/app` 允许拥有: -1. Win32 窗口 -2. D3D12 / Native 渲染接线 -3. viewport 与 swap chain 生命周期 -4. 产品状态装配 -5. 具体面板业务 owner - -`new_editor/app` 不应继续拥有: -1. 通用 Editor 控件样式 owner -2. 通用布局/交互算法 -3. 为规避底层问题而堆的纠偏逻辑 - -## 5. 分阶段计划 - -### 阶段 A: 计划归档与基线冻结 - -目标: -1. 归档旧计划 -2. 用新计划明确本轮唯一主线 - -完成标准: -1. `docs/plan` 中只有一份新的 `new_editor` 架构主计划 -2. 旧主线计划归档到 `docs/plan/used` - -### 阶段 B: 宿主消息入口收口 - -目标: -1. 收回 `WindowMessageDispatcher` 过度拆分 -2. 让宿主消息入口回到清晰、稳定、低噪音结构 - -执行策略: -1. 保留一个主入口文件负责 `WndProc` 总路由 -2. 只保留少量真正稳定的实现分组 -3. 删除 `Handlers` 这类额外中间层 -4. 防止再次按 `WM_*` 类别继续分裂 - -完成标准: -1. 入口结构可一眼看清 -2. 不再存在“外壳 + handlers + 多个碎片”的三层结构 - -### 阶段 C: 缩减 EditorWindow 职责 - -目标: -1. 把 `EditorWindow` 从宿主 god object 收回到稳定外壳 - -执行策略: -1. 先界定 `EditorWindow` 的唯一职责边界 -2. 把窗口框架、输入、渲染帧驱动、tab transfer 等内部实现拆成稳定私有协作者 -3. 但不为了“拆文件”而制造新的空壳或 wrapper - -完成标准: -1. `EditorWindow.h` 的公开面明显收缩 -2. 核心状态和行为 owner 清晰 - -### 阶段 D: Editor 风格 owner 统一 - -目标: -1. 统一 Editor 样式归属 - -执行策略: -1. 收口通用控件 tokens 到 `XCEditor` -2. 清理 `app/UI/Styles` 中本应属于库层的内容 -3. 收缩 panel 局部视觉常量,只保留真正业务语义 - -完成标准: -1. 改 Editor 通用风格时,不需要在 `app` 和 `src` 之间来回改 - -### 阶段 E: Support / Internal 头文件去实现化 - -目标: -1. 把 `Support.h` / `Internal.h` 从“实现垃圾桶”收回成真正内部接口 - -执行策略: -1. 把非模板 helper 移回 `.cpp` -2. 只保留必要声明 -3. 对仍然需要内部实现分层的模块,改成 `.cpp + 最小内部头` - -完成标准: -1. 头文件依赖明显下降 -2. 不再出现几百行的“内部实现头” - -### 阶段 F: 大文件按稳定职责收敛 - -目标: -1. 处理已经过胖的核心实现单元 - -执行策略: -1. 只针对真正混合多类职责的文件动刀 -2. 按布局 / hit test / 状态更新 / 绘制命令生成等稳定边界收口 -3. 禁止按函数名或代码块机械切碎 - -完成标准: -1. 热点大文件职责更集中 -2. 没有新增空壳翻译单元 - -### 阶段 G: 统一命名与收尾验证 - -目标: -1. 统一后缀语义 -2. 完成构建与测试验证 - -执行策略: -1. `Internal` 统一表示内部私有实现接口 -2. `Support` 只在确有必要时保留,并逐步消退 -3. 清理无意义后缀和重复命名 - -完成标准: -1. 命名规则稳定 -2. `XCUIEditorApp` 和 `editor_ui_tests` 通过 - -## 5.1 当前进度快照 - -截至 `2026-04-15` 当前已完成: -1. `WindowMessageDispatcher` 已从“外壳 + handlers + 多个消息分片”收回成主头 + 主实现。 -2. `EditorWindow.h` 的公开职责面已经收紧,宿主内部实现主要通过 friend 协作者进入。 -3. Editor 通用 TreeView 样式 owner 已从 `app/UI/Styles` 收回到 `XCEditor`。 -4. `HierarchyPanelSupport.h` / `ProjectPanelSupport.h` 已清理为真正的 `Internal.h + .cpp`。 -5. `ProjectBrowserModelSupport.h`、`EditorWindowInputSupport.h` 已清理为最小内部头 + `.cpp`。 -6. `CrossWindowDropInternal.h`、`ApplicationBootstrapSupport.h` 已去实现化。 -7. `UIEditorColorUtils.h` 已从公有头实现改成声明 + `src/Widgets/UIEditorColorUtils.cpp`。 -8. viewport render target helper 已从 `ViewportRenderTargetSupport.h` 收为 `ViewportRenderTargetInternal.h/.cpp`。 -9. `EmbeddedPngLoader.h`、`EditorWindowPlatformSupport.h`、`EditorWindowRuntimeSupport.h` 已迁回 `.cpp`。 -10. 当前 `XCUIEditorApp` 与 `editor_ui_tests` 持续通过,`editor_ui_tests` 为 `335/335`。 - -当前仍然没有完成、而且是真正的主问题: -1. `EditorWindow` 与 `WindowManager` 的宿主边界仍然偏胖,虽然公开面收紧了,但内部职责尚未完全稳定。 -2. `Workspace` 子系统仍存在过度按流程片段切文件的问题,需要按稳定状态机边界继续收敛。 -3. `Hierarchy` / `Project` 等 feature 面板还存在“按 Commands / Interaction / Rendering 切碎”的结构噪音,后续要按 feature owner 再收一次。 -4. 少量局部 helper 头仍保留实现,必须继续区分“真正值得内联的小工具”和“应回到 `.cpp` 的平台/运行时逻辑”。 - -## 6. 执行顺序 - -严格按以下顺序推进: - -1. 阶段 A: 归档旧计划并建立新计划 -2. 阶段 B: 收口 `WindowMessageDispatcher` -3. 阶段 C: 缩减 `EditorWindow` -4. 阶段 D: 统一 Editor 风格 owner -5. 阶段 E: 清理 `Support / Internal` 头实现 -6. 阶段 F: 收敛热点大文件 -7. 阶段 G: 统一命名并完成验证 - -## 7. 收口判断 - -满足以下条件,才能认为这一轮收口完成: - -1. `WindowMessageDispatcher` 不再存在明显结构噪音 -2. `EditorWindow` 不再是宿主 god object -3. Editor 通用风格 owner 收口明确 -4. `Support / Internal` 头不再承担大块非模板实现 -5. 若干核心热点文件的职责边界更稳定 -6. 命名与目录约定不再继续发散 -7. 编译和测试通过 diff --git a/docs/used/NewEditor_结构收口与正式迁移计划_2026-04-15.md b/docs/used/NewEditor_结构收口与正式迁移计划_2026-04-15.md deleted file mode 100644 index 74ee356d..00000000 --- a/docs/used/NewEditor_结构收口与正式迁移计划_2026-04-15.md +++ /dev/null @@ -1,253 +0,0 @@ -# NewEditor 结构收口与正式迁移计划 - -日期: `2026-04-15` - -## 1. 文档定位 - -这份文档是当前 `new_editor` 主线的唯一有效计划。 - -本轮已经把此前分散的 `NewEditor_*` 阶段性计划全部归档到 `docs/plan/used/`,原因不是这些计划完全错误,而是它们都只覆盖了某一段局部问题: - -- 3D 渲染主链接入 -- 宿主重构 -- 无边框宿主采用 -- Tab 脱离独立窗口 - -当前主线已经进入新的阶段,继续靠这些局部计划推进,只会让执行顺序继续发散。 - -现在真正需要的是一份单一主线文档,把 `new_editor` 接下来必须先做完的结构收口路线写死。 - -## 2. 当前事实 - -### 2.1 已经具备的基础 - -当前 `new_editor` 已经不再是一个单纯的 UI 试验壳,已经具备继续向上承接正式编辑器重建的基础: - -- `XCEditor` 库层已经形成明确目录边界: - - `Collections` - - `Docking` - - `Fields` - - `Foundation` - - `Menu` - - `Panels` - - `Shell` - - `Viewport` - - `Workspace` -- `new_editor/app` 宿主层也已经开始从旧的扁平 `Host/*` 结构,收敛为: - - `Bootstrap` - - `Platform` - - `Rendering` - - `Composition` - - `Features` - - `State` - - `Support` - - `UI` -- `XCUIEditorApp` 当前可以稳定编译通过,说明宿主主链并没有断。 - -### 2.2 当前真正的主要问题 - -当前最大的阻塞已经不是“有没有这个面板”或者“某个小功能能不能先做出来”,而是结构债还没有清干净。 - -现在最明显的两个问题是: - -1. `new_editor/app` 还处在一次目录迁移的中间态 - 旧 `app/Host/*` 的删除和新 `Bootstrap / Platform / Rendering` 的接管同时存在,工作区里会看到大量 `D` 与 `??` 混杂。这说明方向是对的,但这次重构还没有形成一个真正干净的闭环。 - -2. `XCEditor` 库层还有几块超大实现文件没有拆干净 - 目前最危险的热点主要集中在: - - `Workspace` - - `Docking` - - `PropertyGrid` - -如果不先收掉这两类结构债,后面继续往 `new_editor` 里迁产品层,只会继续把问题滚大。 - -## 3. 当前主线判断 - -当前主线不是继续加更多业务面板,也不是继续单点修某个局部交互。 - -当前主线固定为三步,顺序不能反: - -1. 先把 `new_editor/app` 这次宿主目录迁移彻底收口 -2. 再把 `XCEditor` 库层几个最大热点文件拆薄并稳定下来 -3. 只有前两步完成后,才正式进入 `new_editor` 产品层迁移 - -这三步里,前两步是后续所有产品迁移的地基。 - -## 4. 不可违背的执行原则 - -### 4.1 先结构,后功能 - -在宿主层目录迁移和 `XCEditor` 热点拆分没有完成前,不继续扩写新的产品功能面板,不继续铺新的产品工作流。 - -### 4.2 不允许临时兼容层 - -不允许为了“先跑起来”继续加 wrapper、双入口、临时桥接壳、一次性兼容适配层。 - -要么把旧结构彻底迁走,要么明确保留为正式边界;不允许长期中间态并存。 - -### 4.3 Editor UI 不走 Runtime 的资源样式体系 - -`Runtime UI` 允许继续保留样式资源机制; -`Editor UI` 不再往那条路径上加厚,编辑器样式固定在代码里。 - -### 4.4 问题必须从根因修 - -如果在迁移过程中暴露出: - -- 宿主层边界错误 -- `XCEditor` 库层接口职责错误 -- 状态 owner 错位 -- 文件和目录层级不合理 - -必须先回到底层修根因,再继续往上迁。 - -## 5. 阶段 A:宿主目录迁移收口 - -### 5.1 目标 - -把 `new_editor/app` 从当前的“迁移进行中状态”收成一个正式、稳定、可继续扩展的宿主结构。 - -### 5.2 主要任务 - -1. 把旧 `app/Host/*` 体系彻底退场 -2. 确认新结构成为唯一正式入口: - - `Bootstrap` - - `Platform/Win32` - - `Rendering/D3D12` - - `Rendering/Native` - - `Rendering/Viewport` - - `Composition` -3. 清理迁移残留: - - CMake 源文件接线 - - include 路径 - - 旧头源文件引用 - - 目录层级命名不一致 -4. 把当前仍然过厚的宿主热点继续拆薄,但只做职责拆分,不改行为语义: - - `BorderlessWindowChrome` - - `EditorWindowLifecycle` - - `ViewportHostService` - - 其他仍然显著偏厚的宿主实现 - -### 5.3 完成标准 - -满足以下条件,阶段 A 才算完成: - -- `new_editor/app` 不再同时保留旧 `Host/*` 正式实现和新目录正式实现 -- 目录边界对外清晰,职责一眼可读 -- `XCUIEditorApp` 编译通过 -- `XCUIEditor.exe` 运行不回退 -- 这次迁移可以形成一笔干净的 `refactor(new_editor/app): ...` 提交 - -## 6. 阶段 B:XCEditor 库层热点拆分收口 - -### 6.1 目标 - -在继续迁产品层之前,把 `XCEditor` 当前最容易继续膨胀成屎山的几块核心实现拆薄。 - -### 6.2 优先级 - -优先级固定为: - -1. `Workspace` -2. `Docking` -3. `PropertyGrid` - -也就是先处理这些热点: - -- `new_editor/src/Workspace/UIEditorWorkspaceModel.cpp` -- `new_editor/src/Workspace/UIEditorWorkspaceController.cpp` -- `new_editor/src/Docking/UIEditorDockHost.cpp` -- `new_editor/src/Docking/UIEditorDockHostInteraction.cpp` -- `new_editor/src/Fields/UIEditorPropertyGrid.cpp` -- `new_editor/src/Fields/UIEditorPropertyGridInteraction.cpp` - -### 6.3 拆分原则 - -1. 按职责拆,不按“随便分行数”拆 -2. 模型、状态变换、命中测试、交互会话、渲染组织分开 -3. 不把产品层业务逻辑塞回库层 -4. 先保行为稳定,再收接口命名和文件粒度 - -### 6.4 完成标准 - -满足以下条件,阶段 B 才算完成: - -- `Workspace / Docking / PropertyGrid` 三组热点都被拆成清晰子职责文件 -- 单文件复杂度明显下降,不再存在新的上千行核心实现文件继续堆积 -- 相关 unit / integration 测试仍可通过或至少能稳定编译 -- `XCUIEditorApp` 继续可编译运行 - -## 7. 阶段 C:正式进入 NewEditor 产品层迁移 - -### 7.1 前提 - -只有阶段 A 和阶段 B 都完成后,才进入这一阶段。 - -### 7.2 目标 - -开始把 `new_editor` 从“宿主 + XCEditor 库 + 少量产品面板”推进到“正式的新编辑器产品层”。 - -### 7.3 迁移顺序 - -产品层迁移不从“继续多堆几个面板”开始,而是从全局 owner 开始: - -1. `State / Context / Selection / Commands / Actions / Managers` -2. `Scene / Game / Viewport` 产品工作流 owner -3. `Hierarchy / Inspector / Project / Console` 等业务面板继续正式化 -4. `ComponentEditors` 与旧 editor 业务能力迁入 - -### 7.4 原则 - -- 参考旧 `editor` 的成熟实现与行为 -- 但不原样照搬 ImGui 壳 -- 旧 editor 里正确的 owner、工作流和数据流要迁过来 -- 不合理的旧结构允许在迁移时顺手正规化 - -### 7.5 完成标准 - -进入这一阶段后,`new_editor` 的主线定义就正式从“基础设施收口”切换为“新编辑器产品重建”。 - -## 8. 当前明确不做的事 - -在这份计划生效期间,以下工作不作为主线优先项: - -- 不继续把精力放在零散小 UI 效果修补上 -- 不继续围绕旧阶段性 `NewEditor_*` 子计划扩写 -- 不把 `Editor UI` 再拉回资源样式体系 -- 不为未收口的架构继续追加临时功能 - -## 9. 可并行切分方式 - -### 9.1 可以并行的部分 - -在阶段 A 内部,可并行切分为: - -- `Bootstrap / Application` 收口 -- `Platform/Win32` 宿主收口 -- `Rendering/D3D12 + Native + Viewport` 收口 - -在阶段 B 内部,可并行切分为: - -- `Workspace` -- `Docking` -- `PropertyGrid` - -### 9.2 必须串行的部分 - -以下顺序不能乱: - -1. 先完成阶段 A -2. 再完成阶段 B -3. 再进入阶段 C - -原因很简单:产品层迁移不能建立在尚未稳定的宿主层和库层上。 - -## 10. 收口判断 - -当满足以下条件时,说明当前这轮结构收口真正完成: - -1. `new_editor/app` 迁移闭环完成 -2. `XCEditor` 三大热点收薄完成 -3. `new_editor` 可以在稳定宿主和稳定库层之上,开始正式承接旧 editor 的产品工作流迁移 - -在这之前,所有主线工作都应服务于“收结构债”,而不是继续发散。 diff --git a/docs/used/NewEditor对标旧Editor迁移重建计划_2026-04-12.md b/docs/used/NewEditor对标旧Editor迁移重建计划_2026-04-12.md deleted file mode 100644 index 993e16f3..00000000 --- a/docs/used/NewEditor对标旧Editor迁移重建计划_2026-04-12.md +++ /dev/null @@ -1,600 +0,0 @@ -# NewEditor 对标旧 Editor 迁移重建计划 - -日期: `2026-04-12` - -## 1. 文档定位 - -这份计划用于正式切换主线: - -- 旧 `editor/` 继续作为参考实现与行为基线。 -- `new_editor/` 从“XCEditor 基础层试验宿主”升级为“新编辑器正式重建宿主”。 -- 后续工作重点不再是单独补 `XCEditor` 的孤立控件,而是参考旧 `editor/`,在 `new_editor/` 内逐步重建完整编辑器应用。 - -但有一个硬约束必须始终遵守: - -- 如果在重建 `new_editor` 的过程中暴露出 `XCEditor`、宿主层或输入/布局/viewport 基础设施的结构问题,必须先回到底层修根因,再继续往上迁移。 -- 不允许在 `new_editor` 业务层堆临时兼容逻辑来掩盖底层问题。 - -这份计划替代此前“先单独收口 dock/UI 基础层,再暂停业务”的阶段性计划。当前主线已经进入 `new_editor` 正式重建阶段。 - -## 2. 不可违背的设计原则 - -### 2.1 根因优先 - -- 任何交互、布局、DPI、capture、拖拽、文本测量、渲染清晰度问题,优先在 `XCEditor` / `Host` / shared editor infrastructure 修。 -- 禁止在某个 panel 内写只为绕过去的局部 hack。 - -### 2.2 旧 Editor 是参考基线,不是直接复制目标 - -- 旧 `editor/` 的行为、工作流、信息架构、可用性和视觉结果是当前最可靠的参考。 -- 旧 `editor/` 里凡是 ImGui 特有的壳代码、临时 helper、历史包袱,不应机械照搬。 -- 迁移过程中允许顺手做架构收敛,但必须保持行为兼容,不允许一边“优化”一边偏离旧版交互。 - -### 2.3 `XCEditor` 只承载 Editor UI 基础层,不承载 Editor 业务 - -- `new_editor/include/XCEditor/**` + `new_editor/src/**` 负责 editor-only 的 UI primitive、shell、dock、field、tree/list、menu、viewport slot、workspace 等基础能力。 -- `new_editor` 的正式编辑器业务必须放在应用层,不得继续把业务逻辑塞回 `XCEditor` 库层。 - -### 2.4 Editor 样式固定写死在代码里 - -- `Runtime UI` 保留资源化/主题化能力,服务游戏开发者。 -- `Editor UI` 当前不走资源驱动主线,样式、palette、metrics、chrome 由代码固定维护。 -- 不再给 `Editor UI` 扩一套厚重的主题资源解释系统。 - -### 2.5 测试体系与产品宿主分离 - -- `tests/UI/Core|Runtime|Editor` 继续作为 UI 模块验证入口。 -- `new_editor/` 是产品宿主,不是测试场景堆放区。 -- 某项能力先在对应层的 `tests/UI` 里做 unit/integration 回归,再接入 `new_editor`。 - -## 3. 当前基线判断 - -## 3.1 已经完成并可复用的部分 - -当前 `new_editor` 已经具备一套可继续向上承接的 Editor UI 基础层: - -- `XCEditor` 库层已经有: - - `Fields` - - `Tree/List/Scroll/TabStrip` - - `MenuBar/MenuPopup/StatusBar` - - `DockHost/Workspace/PanelHost` - - `ViewportSlot/ViewportShell` - - shortcut、command registry、workspace persistence -- `Host` 层已经有: - - Win32 宿主 - - Direct2D/DirectWrite 文本与图像绘制 - - DPI 感知 - - 自动截图 - - 自有 exe 截图输出 -- `new_editor` 当前已经验证过: - - Unity 风格的基础 chrome - - tab 拖拽重排与停靠 - - project 左树与右侧浏览器的基础外壳 - - hierarchy / project 图标与文本对齐 - - 多项 `tests/UI/Editor` unit / integration 回归 - -结论: - -- `XCEditor` 不再是“完全不能承接产品”的状态。 -- 当前真正缺的不是几个零散 widget,而是 `new_editor` 应用层本身还没有长成旧 `editor/` 那样完整的编辑器架构。 - -## 3.2 当前仍然缺失的关键能力 - -距离“可以真正替代旧 `editor/` 开始长期迭代”的状态,当前还缺: - -1. `new_editor` 自己的应用层骨架还不完整。 -2. 缺少类似旧 `editor/src/Core + Managers + Actions + Commands + Panels + Viewport + ComponentEditors + Scripting` 的正式分层。 -3. `Hierarchy` 仍是 demo/static 数据,不是场景真实数据。 -4. `Project` 虽然外壳已成型,但还不是旧 editor 的完整资产工作流。 -5. `Inspector` 及 `ComponentEditorRegistry` 体系尚未接回。 -6. `Console` 与日志桥接尚未接回。 -7. `Scene/Game viewport` 还没有接回旧 editor 的真实渲染/交互主链。 -8. project / scene / undo / selection / action route / runtime mode 这些编辑器全局状态在 `new_editor` 里还没有正式 owner。 -9. 脚本、运行模式、保存/打开、项目切换、Library/Asset 流程都还未接回。 - -## 4. 旧 Editor 与 NewEditor 的真实差距 - -## 4.1 旧 Editor 的实际分层 - -旧 `editor/` 当前已经形成的主干是: - -`Application -> EditorLayer -> EditorWorkspace -> Panels/Viewport -> Actions -> Commands -> Managers/Core` - -横向还包含: - -- `UI` -- `ComponentEditors` -- `Layout` -- `Scripting` -- `Platform` - -这意味着旧 editor 已经不是“只有一堆 panel”,而是一套完整的编辑器应用结构。 - -## 4.2 NewEditor 当前的真实分层 - -`new_editor/` 当前更接近: - -`Application + Host + XCEditorLib + 少量 Product 面板` - -其中: - -- `include/XCEditor/**` + `src/**` 是 Editor UI 库层。 -- `Host/**` 是平台与绘制宿主。 -- `app/**` 目前还只是轻量应用壳与少量试制 panel。 - -缺口在于: - -- 旧 editor 的“应用层中间层”几乎还没完整迁过来。 -- 当前 `new_editor` 的 panel 仍然偏 prototype,不是正式产品模块。 - -## 4.3 模块映射与结论 - -| 旧 editor 模块 | 当前状态 | new_editor 目标落点 | -| --- | --- | --- | -| `editor/src/UI/**` | 大部分语义已被 `XCEditor` 吸收 | 继续沉淀在 `include/XCEditor/**` + `src/**` | -| `editor/src/Layout/**` | 旧版依赖 ImGui docking | 由 `XCEditor/Shell/**` 的 workspace/dock host 接替 | -| `editor/src/Application.*` | 旧版完整 | `new_editor` 应保留为宿主壳,避免塞业务 | -| `editor/src/Core/**` | 旧版完整 | `new_editor` 需要建立对应 app-level Core | -| `editor/src/Managers/**` | 旧版完整 | `new_editor` 需要建立对应 app-level Managers | -| `editor/src/Actions/**` | 旧版完整 | `new_editor` 需要正式迁入 | -| `editor/src/Commands/**` | 旧版完整 | `new_editor` 需要正式迁入 | -| `editor/src/panels/**` | 旧版完整 | `new_editor` 需要按新 shell 重写,但行为对齐旧版 | -| `editor/src/ComponentEditors/**` | 旧版完整 | `new_editor` 需要迁入并适配 `XCEditor` property grid/field | -| `editor/src/Viewport/**` | 旧版完整 | `new_editor` 需要重新桥接到 `XCEditor` viewport slot/shell | -| `editor/src/Scripting/**` | 旧版完整 | `new_editor` 后期需要接回 | - -结论: - -- `XCEditor` 已经覆盖了大量旧 `editor/src/UI/**` 与 `Layout` 的职责。 -- 但 `new_editor` 还没有真正补上旧 editor 的应用层与工作流层。 -- 因此接下来的重点不是“继续造更多基础控件”,而是“用旧 editor 的结构和行为为参照,把新 editor 的应用层补齐”。 - -## 5. 迁移总策略 - -迁移策略固定为两条线并行推进: - -### 5.1 主线:重建 NewEditor 应用层 - -- 以旧 `editor/` 为参考,在 `new_editor` 下逐步建立正式的: - - `Core` - - `Managers` - - `Actions` - - `Commands` - - `Panels` - - `Viewport` - - `ComponentEditors` - - `Scripting` - -### 5.2 副线:按需回补 XCEditor / Host - -- 当主线重建暴露底层能力缺口时,再回补: - - `XCEditor` - - `Host` - - `tests/UI/Editor` - -副线只为解决主线遇到的真实阻塞,不再无边界扩 editor-only widget。 - -## 6. 目标架构 - -## 6.1 `XCEditor` 的职责边界 - -保留在库层的内容: - -- fixed-code editor theme / metrics / palette -- menu / popup / dock / workspace / tab strip -- tree / list / scroll / inline rename / property grid / fields -- viewport slot / viewport shell -- editor UI input model、session、interaction state machine - -不放进库层的内容: - -- project 资产语义 -- scene/entity/component 语义 -- console/log bridge -- project/scene open/save 工作流 -- inspector 组件注册表 -- editor 菜单业务动作 - -## 6.2 `new_editor` 应用层目标结构 - -应用层建议逐步收敛到与旧 `editor/src` 接近的形态: - -```text -new_editor/ - Host/ - include/XCEditor/ - src/ - Core/ - Managers/ - Actions/ - Commands/ - Panels/ - Viewport/ - ComponentEditors/ - Scripting/ - Shell/ - Icons/ - Application/ -``` - -说明: - -- `include/XCEditor/**` + `src/**` 中的库层文件继续保留并演进。 -- `new_editor` 的产品应用代码不应长期散落在轻量 `app/*` 原型目录里。 -- 迁移早期可以暂时复用现有 `app/*`,但必须尽快收敛到正式应用层目录,避免“基础层代码”和“产品层代码”混杂。 - -## 7. 分阶段执行计划 - -## Phase 0:文档与目录主线切换 - -### 目标 - -把主线明确切到 `new_editor` 正式重建,清理过期计划入口。 - -### 任务 - -- 归档旧的阶段性 dock 收口计划。 -- 保留 `Editor架构说明.md` 作为旧 editor 参考文档,不归档。 -- 新建本迁移计划,作为当前唯一 editor 重建主线。 -- 在计划中正式写死: - - `old editor = reference` - - `new_editor = product host` - - `XCEditor = editor ui library` - - `tests/UI = module verification entry` - -### 完成标准 - -- 后续不再以旧 dock 收口计划作为主线驱动文档。 - -## Phase 1:收敛 NewEditor 应用层骨架 - -### 目标 - -先把 `new_editor` 从“壳 + 若干 product panel”收敛成“真正的编辑器应用结构”。 - -### 任务 - -- 建立 `new_editor` 自己的应用层目录与命名边界。 -- 提炼 `Application` 的纯宿主职责: - - window / DPI / renderer / input pump / screenshot / main loop -- 建立 `EditorWorkspace` 式的应用装配层: - - panel registry - - workspace compose - - panel attach/detach/update/render -- 建立 `EditorContext` / `IEditorContext` 式的全局状态容器。 -- 定义新 editor 的状态所有权: - - selection owner - - scene owner - - project owner - - undo owner - - active action route owner - - runtime mode owner - -### 根因要求 - -- 不允许继续让 `Application` 直接知道越来越多 project/hierarchy 细节。 -- 任何产品语义都必须逐步从 `Application` 下沉到 context / managers / panels。 - -### 完成标准 - -- `new_editor` 具备清晰的 app-level `Core/Workspace/Panels` 骨架。 -- 现有 `ProductHierarchyPanel` / `ProductProjectPanel` 不再是孤立 demo 对象,而是正式 panel 接口的一部分。 - -## Phase 2:迁入全局动作路由与命令层 - -### 目标 - -把旧 editor 的 “menu / shortcut / context menu / toolbar -> action/router -> command” 主链迁进来。 - -### 任务 - -- 对照旧 `editor/src/Actions/**` 梳理新 editor 的动作集: - - `File` - - `Edit` - - `Assets` - - `Run` - - `Scripts` - - `View` - - `Help` -- 建立新的 action route 机制: - - global actions - - focused panel actions - - active route dispatch -- 将真正修改数据的行为放入 `Commands/**`。 -- 让菜单、快捷键、右键菜单、toolbar 共享同一套动作定义。 - -### 根因要求 - -- 不允许 menu、shortcut、context menu 各自复制一套业务逻辑。 -- 不允许 panel 直接偷偷修改 scene/project 状态绕开 command。 - -### 完成标准 - -- 所有 editor 主动作都有稳定 owner。 -- UI 入口与业务执行不再混杂。 - -## Phase 3:接回 Hierarchy 正式数据模型 - -### 目标 - -把 `Hierarchy` 从 demo tree 改成真实 scene-backed 面板。 - -### 任务 - -- 用真实场景层级替换当前静态 `m_treeItems`。 -- 建立 hierarchy 展平与增量同步路径。 -- 接回 selection 同步。 -- 接回 rename。 -- 接回 create/delete/duplicate。 -- 接回拖拽 reparent: - - drop on entity => 成为子节点 - - drop on empty area => 回到 root -- 接回 context menu。 -- 接回旧 editor 的层级树图标/展开/焦点/多选策略。 - -### 优先参考文件 - -- `editor/src/panels/HierarchyPanel.*` -- `editor/src/Actions/HierarchyActionRouter.h` -- `editor/src/Commands/EntityCommands.h` -- `editor/src/Managers/SceneManager.*` - -### 根因要求 - -- 先建立可变 hierarchy source model,再做拖拽。 -- 如拖拽/capture 冲突,需要在 shared hosted-content capture 机制上统一修复,不允许再写 panel 特判。 - -### 完成标准 - -- `Hierarchy` 具备旧 editor 的核心编辑语义,而不是展示假数据。 - -## Phase 4:接回 Project 正式资产工作流 - -### 目标 - -把已经有外壳的 `Project` 面板接回真实 project/Assets 工作流。 - -### 任务 - -- 用正式 `ProjectManager`/Asset data source 替换当前轻量扫描逻辑。 -- 保持已完成的外观与交互风格。 -- 接回: - - folder tree - - breadcrumb navigation - - asset grid selection - - open / double click - - rename - - create folder - - delete - - drag move - - context menu - - refresh / watcher -- 对齐旧 editor 的 `.meta` / `Library` / 资源管理语义。 - -### 优先参考文件 - -- `editor/src/panels/ProjectPanel.*` -- `editor/src/Actions/ProjectActionRouter.h` -- `editor/src/Commands/ProjectCommands.h` -- `editor/src/Managers/ProjectManager.*` - -### 根因要求 - -- Project 右侧 grid 的事件必须继续保持语义化输出,不要退回 raw hit test 乱写业务。 -- 资产操作必须收敛到 command / manager,不能把文件系统操作塞在 UI 绘制逻辑里。 - -### 完成标准 - -- `Project` 成为真正可用的资产浏览与操作入口。 - -## Phase 5:接回 Inspector 与 ComponentEditorRegistry - -### 目标 - -把 `XCEditor` 已有的 field/property grid 真正用于 Inspector,而不是停留在基础控件演示。 - -### 任务 - -- 建立新的 `ComponentEditors/**` 注册表。 -- 接回 `InspectorPanel`。 -- 首批接回: - - Transform - - Camera - - Light - - MeshFilter - - MeshRenderer - - Script -- 接回 object/asset reference field。 -- 处理 interactive undo 边界。 -- 对齐旧 editor 的 Add Component 入口与规则。 - -### 优先参考文件 - -- `editor/src/panels/InspectorPanel.*` -- `editor/src/ComponentEditors/**` -- `editor/src/Commands/ComponentCommands.h` - -### 根因要求 - -- 通用字段行为沉淀在 `XCEditor/Fields/**`。 -- 组件语义停留在 app-level `ComponentEditors/**`。 -- 不允许把具体组件判断重新塞回 shared field 层。 - -### 完成标准 - -- `Inspector` 可以对真实选中对象执行稳定编辑。 - -## Phase 6:接回 Console 与编辑器日志桥接 - -### 目标 - -补齐旧 editor 的 console 工作流。 - -### 任务 - -- 迁入 console data model、过滤状态与 sink。 -- 接回日志分级、清空、过滤、点击定位等基础行为。 -- 与菜单/快捷键/上下文操作对齐。 - -### 优先参考文件 - -- `editor/src/panels/ConsolePanel.*` -- `editor/src/UI/ConsoleFilterState.h` -- `editor/src/Core/EditorConsoleSink.*` -- `editor/src/Actions/ConsoleActionRouter.h` - -### 完成标准 - -- `Console` 不再是空 panel,能承担真实调试反馈。 - -## Phase 7:接回 Scene / Game Viewport 正式主链 - -### 目标 - -这是替代旧 editor 的真正硬门槛。 - -### 任务 - -- 将旧 editor 的 viewport host/render path 重新桥接到 `XCEditorViewportSlot / XCEditorViewportShell`。 -- 接回: - - scene viewport 离屏渲染 - - game viewport 渲染 - - viewport resize - - navigation - - gizmo - - overlay - - selection / picking - - edit / play mode 切换 -- 处理 viewport 与 shell 输入焦点、capture、cursor 的统一。 - -### 优先参考文件 - -- `editor/src/panels/SceneViewPanel.*` -- `editor/src/panels/GameViewPanel.*` -- `editor/src/Viewport/**` -- `editor/src/Application.*` -- `editor/src/Platform/D3D12WindowRenderer*` - -### 根因要求 - -- viewport 相关问题优先在 app viewport layer / host bridge 修。 -- 不允许在 panel 里私拼第二套渲染路径。 - -### 完成标准 - -- `Scene/Game` 具备旧 editor 的基础可用性。 - -## Phase 8:接回项目生命周期、运行模式与脚本工作流 - -### 目标 - -把编辑器从“几个面板能显示”推进到“有完整项目工作流”。 - -### 任务 - -- project 打开 / 切换 / 保存状态 -- scene 打开 / 保存 / Save As -- runtime mode / play session controller -- 脚本程序集重建与状态显示 -- Library/bootstrap/import 状态展示 -- window title 与 dirty state - -### 优先参考文件 - -- `editor/src/Core/PlaySessionController.*` -- `editor/src/Core/EditorWindowTitle.h` -- `editor/src/Scripting/**` -- `editor/src/Application.*` - -### 完成标准 - -- `new_editor` 能跑通真实项目编辑主流程,而不是只展示 UI 壳。 - -## Phase 9:视觉一致性、回归与旧 Editor 退场条件 - -### 目标 - -在核心工作流都接回后,统一做收口。 - -### 任务 - -- 对关键界面做逐项比对: - - menu bar - - dock/tab - - hierarchy - - project - - inspector - - console - - scene/game -- 修正与旧 editor 的视觉与交互偏差。 -- 清理 `new_editor` 中所有 demo 数据、临时状态、过渡适配层。 -- 给每一块关键能力补齐: - - `tests/UI/Editor` - - product-level smoke validation - -### 最终收口标准 - -满足以下条件后,才算真正达到“可替代旧 editor”的程度: - -1. `new_editor` 已接回旧 editor 的核心面板与主工作流。 -2. `Scene/Game` viewport 可稳定工作。 -3. `Hierarchy/Project/Inspector/Console` 都是正式数据源,不再有 demo 假数据。 -4. menu / shortcut / context / toolbar 已共用动作层。 -5. undo / selection / runtime mode / active route 有清晰 owner。 -6. 没有为了赶进度堆出来的 panel-local hack。 - -## 8. 并行拆分建议 - -可以并行,但必须分主从关系: - -### 主依赖顺序 - -1. `Phase 1` 应用层骨架 -2. `Phase 2` 动作与命令层 -3. `Phase 3/4/5/6` 面板业务回接 -4. `Phase 7` viewport 主链 -5. `Phase 8/9` 生命周期与总收口 - -### 可并行子线 - -1. `Core/Managers/Context` 收敛 -2. `Actions/Commands` 迁移 -3. `Hierarchy + SceneManager` 接回 -4. `Project + ProjectManager` 接回 -5. `Inspector + ComponentEditors` 接回 -6. `Console + tests/UI/Editor` 回归补齐 - -说明: - -- `Viewport` 尽量单独作为高风险主线,不和别的大改混在一起。 -- 一旦并行子线发现 `XCEditor` 基础问题,优先暂停子线、回底层修复。 - -## 9. 立即执行顺序 - -当前建议的第一批执行顺序是: - -1. 先收敛 `new_editor` 应用层骨架与目录边界。 -2. 立刻建立新的 `EditorContext / Managers / Workspace`。 -3. 然后先接 `Hierarchy` 和 `Project` 的真实数据源,因为这两块当前已有可见 UI 外壳。 -4. 再接 `Inspector` 与 `Console`。 -5. 最后接 `Scene/Game viewport` 与运行模式主链。 - -原因: - -- `Hierarchy` / `Project` 已经有可见壳层,最适合先把产品语义接回来。 -- `Viewport` 风险最高,应在应用层骨架稳定后单独推进。 - -## 10. 当前需要归档的旧计划 - -本轮建议归档到 `docs/plan/used/` 的文档: - -- `XCEditor_Dock统一与Tab拖拽停靠收口计划_2026-04-10.md` - -保留在 `docs/plan/` 的参考文档: - -- `Editor架构说明.md` - -原因: - -- 前者属于阶段性基础层收口计划,主线已被当前迁移重建计划替代。 -- 后者不是过期计划,而是旧 editor 的分层参考资料,后续迁移时仍需频繁对照。 diff --git a/docs/used/OpenGL_Test_Restructuring_Plan.md b/docs/used/OpenGL_Test_Restructuring_Plan.md deleted file mode 100644 index 36aab7e6..00000000 --- a/docs/used/OpenGL_Test_Restructuring_Plan.md +++ /dev/null @@ -1,731 +0,0 @@ -# OpenGL 测试架构重构方案 - -本文档是 XCEngine 测试规范的 OpenGL 专项补充,旨在将 OpenGL 后端测试体系重构为与 D3D12 同样规范的标准架构。 - -**前置阅读**: -- [tests/TEST_SPEC.md](../tests/TEST_SPEC.md) - 通用测试规范 -- [tests/RHI/D3D12/TEST_SPEC.md](./D3D12/TEST_SPEC.md) - D3D12 专项规范(参考模板) - -**规范版本**: 1.0 -**最后更新**: 2026-03-20 - ---- - -## 1. 现状分析 - -### 1.1 当前目录结构 - -``` -tests/RHI/OpenGL/ -├── CMakeLists.txt # 混乱的一级配置 -├── fixtures/ -│ ├── OpenGLTestFixture.h -│ └── OpenGLTestFixture.cpp -├── test_device.cpp -├── test_buffer.cpp # 被注释,未启用 -├── test_fence.cpp # 被注释,未启用 -├── test_texture.cpp -├── test_shader.cpp # 被注释,未启用 -├── test_pipeline_state.cpp -├── test_vertex_array.cpp -├── test_command_list.cpp -├── test_render_target_view.cpp -├── test_depth_stencil_view.cpp -├── test_swap_chain.cpp -├── test_sampler.cpp -└── Res/ # 空文件夹,无实际资源 - ├── Data/ - ├── Shader/ - └── Texture/ -``` - -### 1.2 当前测试统计 - -| 组件 | 文件 | 测试数 | 状态 | -|------|------|--------|------| -| Device | `test_device.cpp` | 6 | ✅ 启用 | -| Buffer | `test_buffer.cpp` | 6 | ❌ 被注释 | -| Fence | `test_fence.cpp` | 5 | ❌ 被注释 | -| Texture | `test_texture.cpp` | 4 | ✅ 启用 | -| Shader | `test_shader.cpp` | 4 | ❌ 被注释 | -| PipelineState | `test_pipeline_state.cpp` | 3 | ✅ 启用 | -| VertexArray | `test_vertex_array.cpp` | 1 | ✅ 启用 | -| CommandList | `test_command_list.cpp` | 14 | ✅ 启用 | -| Sampler | `test_sampler.cpp` | 2 | ✅ 启用 | -| SwapChain | `test_swap_chain.cpp` | 3 | ✅ 启用 | -| RTV | `test_render_target_view.cpp` | 2 | ✅ 启用 | -| DSV | `test_depth_stencil_view.cpp` | 2 | ✅ 启用 | -| **总计** | | **52** | **37 启用, 15 被注释** | - -### 1.3 与 D3D12 对比 - -| 方面 | D3D12 (规范) | OpenGL (现状) | -|------|-------------|--------------| -| **目录分层** | `unit/` + `integration/` 分离 | 全部扁平 | -| **CMake 结构** | 顶层 + unit + integration 三级 | 仅一级 | -| **Fixture 设计** | 每测试独立设备 | 静态共享上下文 | -| **被注释测试** | 无 | 3 个文件被注释 | -| **集成测试** | 有 (Python + Golden Image) | **缺失** | -| **测试规范文档** | `TEST_SPEC.md` | **缺失** | -| **Res 资源** | 按测试隔离 | 空文件夹 | -| **资源复制** | POST_BUILD 自动复制 | 未配置 | - ---- - -## 2. 问题详解 - -### 2.1 Fixture 设计缺陷 - -**当前问题**: -```cpp -// OpenGL - 静态成员所有测试共享,存在状态污染 -static GLFWwindow* m_window; // 共享窗口 -static bool m_contextInitialized; // 共享状态 -static OpenGLDevice* m_device; // 共享设备 -``` - -**D3D12 规范做法**: -```cpp -// 每个测试独立创建设备 -void D3D12TestFixture::SetUp() { - D3D12CreateDevice(nullptr, D3D_FEATURE_LEVEL_12_0, ...); -} -``` - -**OpenGL 重构方向**: -```cpp -// 方案A: 每测试创建独立上下文 (推荐) -class OpenGLTestFixture : public ::testing::Test { -protected: - void SetUp() override { - // 创建独立 OpenGL 上下文 - glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); - m_window = glfwCreateWindow(640, 480, "Test", nullptr, nullptr); - glfwMakeContextCurrent(m_window); - gladLoadGLLoader(...); - } -}; -``` - -### 2.2 CMake 配置混乱 - -**当前问题**: -```cmake -# 硬编码绝对路径 -include_directories(${CMAKE_SOURCE_DIR}/tests/OpenPackage/include/) -include_directories(${CMAKE_SOURCE_DIR}/tests/OpenPackage/lib/) -link_directories(${CMAKE_SOURCE_DIR}/tests/OpenPackage/lib/) - -# 被注释的测试源文件 -# test_buffer.cpp -# test_fence.cpp -# test_shader.cpp -``` - -**D3D12 规范做法**: -```cmake -# 顶层 CMakeLists.txt 仅做 add_subdirectory -add_subdirectory(unit) -add_subdirectory(integration) - -# unit/CMakeLists.txt 独立配置 -# integration/CMakeLists.txt 独立配置 -``` - -### 2.3 资源目录为空 - -当前 `Res/` 下的 `Data/`、`Shader/`、`Texture/` 均为空目录,没有实际测试资源。 - ---- - -## 3. 重构目标 - -### 3.1 目标目录结构 - -``` -tests/RHI/OpenGL/ -├── CMakeLists.txt # 顶层配置 -├── TEST_SPEC.md # 本文档 (OpenGL 专项) -├── TEST_IMPROVEMENT_PLAN.md # 改进计划 -├── unit/ -│ ├── CMakeLists.txt # 单元测试构建 -│ ├── fixtures/ -│ │ ├── OpenGLTestFixture.h -│ │ └── OpenGLTestFixture.cpp -│ ├── test_device.cpp -│ ├── test_buffer.cpp -│ ├── test_fence.cpp -│ ├── test_texture.cpp -│ ├── test_shader.cpp -│ ├── test_pipeline_state.cpp -│ ├── test_vertex_array.cpp -│ ├── test_command_list.cpp -│ ├── test_render_target_view.cpp -│ ├── test_depth_stencil_view.cpp -│ ├── test_swap_chain.cpp -│ └── test_sampler.cpp -└── integration/ - ├── CMakeLists.txt - ├── run_integration_test.py # 公共脚本 - ├── compare_ppm.py # PPM 图像比对 - ├── run.bat # Windows 启动脚本 - ├── minimal/ # 最小化测试 - │ ├── main.cpp - │ ├── GT_minimal.ppm - │ └── Res/ - │ └── Shader/ - │ ├── simple.vert - │ └── simple.frag - └── render_model/ # 模型渲染测试 - ├── main.cpp - ├── GT.ppm - └── Res/ - ├── Image/ - ├── Model/ - └── Shader/ -``` - -### 3.2 测试数量目标 - -| 组件 | 当前 | 重构后 | 变化 | -|------|------|--------|------| -| Device | 6 | 6 | - | -| Buffer | 0 (被注释) | 6 | +6 | -| Fence | 0 (被注释) | 5 | +5 | -| Texture | 4 | 4 | - | -| Shader | 0 (被注释) | 4 | +4 | -| PipelineState | 3 | 3 | - | -| VertexArray | 1 | 2 | +1 | -| CommandList | 14 | 14 | - | -| Sampler | 2 | 2 | - | -| SwapChain | 3 | 3 | - | -| RTV | 2 | 2 | - | -| DSV | 2 | 2 | - | -| **单元测试总计** | **37** | **53** | **+16** | -| **集成测试** | **0** | **2** | **+2** | - ---- - -## 4. 分阶段实施计划 - -### Phase 1: 目录结构重构 - -**目标**: 建立与 D3D12 一致的目录分层 - -**步骤**: -1. 创建 `unit/` 和 `integration/` 子目录 -2. 移动现有测试文件到 `unit/` -3. 创建顶层 `CMakeLists.txt` 做 `add_subdirectory` -4. 重构 `unit/CMakeLists.txt` 独立配置 - -**目录变更**: -``` -# 重构前 -tests/RHI/OpenGL/CMakeLists.txt -tests/RHI/OpenGL/test_*.cpp -tests/RHI/OpenGL/fixtures/ - -# 重构后 -tests/RHI/OpenGL/CMakeLists.txt # 顶层,仅 add_subdirectory -tests/RHI/OpenGL/unit/CMakeLists.txt # 单元测试构建 -tests/RHI/OpenGL/unit/test_*.cpp # 测试文件 -tests/RHI/OpenGL/unit/fixtures/ # Fixture -tests/RHI/OpenGL/integration/ # 新增 -tests/RHI/OpenGL/integration/... -``` - -### Phase 2: Fixture 重构 - -**目标**: 消除静态共享状态,避免测试间污染 - -**重构内容**: - -```cpp -// OpenGLTestFixture.h 重构 -class OpenGLTestFixture : public ::testing::Test { -protected: - void SetUp() override; - void TearDown() override; - - GLFWwindow* GetWindow() { return m_window; } - void MakeContextCurrent(); - void DoneContextCurrent(); - - void ClearGLErrors(); - bool CheckGLError(const char* file, int line); - void ResetGLState(); - -private: - GLFWwindow* m_window = nullptr; - OpenGLDevice* m_device = nullptr; -}; - -// OpenGLTestFixture.cpp 重构 -void OpenGLTestFixture::SetUp() { - glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6); - glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); - - m_window = glfwCreateWindow(640, 480, "OpenGL Tests", nullptr, nullptr); - if (!m_window) { - GTEST_SKIP() << "Failed to create GLFW window"; - return; - } - - glfwMakeContextCurrent(m_window); - - if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { - GTEST_SKIP() << "Failed to initialize GLAD"; - glfwDestroyWindow(m_window); - m_window = nullptr; - return; - } - - m_device = new OpenGLDevice(); - m_device->CreateRenderWindow(640, 480, "Test Window", false); - - ClearGLErrors(); -} - -void OpenGLTestFixture::TearDown() { - ResetGLState(); - - if (m_device) { - delete m_device; - m_device = nullptr; - } - - if (m_window) { - glfwDestroyWindow(m_window); - m_window = nullptr; - } -} -``` - -### Phase 3: 启用被注释的测试 - -**目标**: 激活 `test_buffer.cpp`、`test_fence.cpp`、`test_shader.cpp` - -**步骤**: -1. 取消 CMakeLists.txt 中的注释 -2. 检查并修复可能的编译错误 -3. 运行测试验证 - -**预期新增测试**: -- Buffer: 6 tests -- Fence: 5 tests -- Shader: 4 tests - -### Phase 4: 完善单元测试 - -**目标**: 补充缺失的测试用例 - -**新增测试计划**: - -| 组件 | 新增测试 | 说明 | -|------|---------|------| -| VertexArray | 1 | `VertexArray_Bind_MultipleAttributes` | - -### Phase 5: 建立集成测试体系 - -**目标**: 建立与 D3D12 一致的集成测试框架 - -**步骤**: -1. 创建 `integration/` 目录结构 -2. 复制 `run_integration_test.py` 和 `compare_ppm.py` -3. 创建 `minimal/` 集成测试 -4. 创建 `render_model/` 集成测试 -5. 配置 CTest 注册 - -**minimal/ 集成测试**: -```cpp -// integration/minimal/main.cpp -// 渲染一个简单三角形,输出 PPM 截图 -int main() { - // 1. 初始化 GLFW + OpenGL - // 2. 创建窗口 - // 3. 渲染简单场景 - // 4. 截图保存为 minimal.ppm - // 5. 退出 -} -``` - -**Golden Image 生成流程**: -1. 在干净硬件环境运行集成测试 -2. 截图保存为 `GT_.ppm` -3. 人工验证截图正确性 -4. 提交到版本控制 - -### Phase 6: 编写测试规范文档 - -**目标**: 创建 `TEST_SPEC.md` 和 `TEST_IMPROVEMENT_PLAN.md` - ---- - -## 5. CMake 重构详细方案 - -### 5.1 顶层 CMakeLists.txt - -```cmake -cmake_minimum_required(VERSION 3.15) - -project(OpenGLEngineTests) - -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_STANDARD_REQUIRED ON) - -add_subdirectory(unit) -add_subdirectory(integration) -``` - -### 5.2 unit/CMakeLists.txt - -```cmake -cmake_minimum_required(VERSION 3.15) - -get_filename_component(PROJECT_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../../.. ABSOLUTE) - -find_package(OpenGL REQUIRED) - -include_directories(${PROJECT_ROOT_DIR}/engine/include) -include_directories(${PROJECT_ROOT_DIR}/engine/src) - -link_directories(${CMAKE_SOURCE_DIR}/tests/OpenGL/package/lib/) - -find_package(GTest REQUIRED) - -set(TEST_SOURCES - ${CMAKE_SOURCE_DIR}/tests/OpenGL/package/src/glad.c - fixtures/OpenGLTestFixture.cpp - test_device.cpp - test_buffer.cpp - test_fence.cpp - test_texture.cpp - test_shader.cpp - test_pipeline_state.cpp - test_vertex_array.cpp - test_command_list.cpp - test_render_target_view.cpp - test_depth_stencil_view.cpp - test_swap_chain.cpp - test_sampler.cpp -) - -add_executable(opengl_engine_tests ${TEST_SOURCES}) - -target_link_libraries(opengl_engine_tests PRIVATE - opengl32 - glfw3 - XCEngine - GTest::gtest - GTest::gtest_main -) - -target_include_directories(opengl_engine_tests PRIVATE - ${CMAKE_CURRENT_SOURCE_DIR}/fixtures - ${PROJECT_ROOT_DIR}/engine/include - ${PROJECT_ROOT_DIR}/engine/src -) - -target_compile_definitions(opengl_engine_tests PRIVATE - TEST_RESOURCES_DIR="${PROJECT_ROOT_DIR}/tests/RHI/OpenGL/integration/minimal/Res" -) - -add_custom_command(TARGET opengl_engine_tests POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_directory - ${PROJECT_ROOT_DIR}/tests/RHI/OpenGL/integration/minimal/Res - $/Res -) - -enable_testing() -add_test(NAME OpenGLEngineTests COMMAND opengl_engine_tests) -``` - -### 5.3 integration/CMakeLists.txt - -```cmake -cmake_minimum_required(VERSION 3.15) - -project(OpenGL_Integration) - -set(ENGINE_ROOT_DIR ${CMAKE_SOURCE_DIR}/engine) - -find_package(Python3 REQUIRED) - -enable_testing() - -# Minimal test -add_executable(OpenGL_Minimal - WIN32 - minimal/main.cpp -) - -target_include_directories(OpenGL_Minimal PRIVATE - ${CMAKE_CURRENT_SOURCE_DIR}/minimal - ${ENGINE_ROOT_DIR}/include -) - -target_link_libraries(OpenGL_Minimal PRIVATE - opengl32 - glfw3 - d3d12 - dxgi - d3dcompiler - XCEngine -) - -# Copy Res folder -add_custom_command(TARGET OpenGL_Minimal POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_directory - ${CMAKE_CURRENT_SOURCE_DIR}/minimal/Res - $/Res -) - -# Copy test scripts -add_custom_command(TARGET OpenGL_Minimal POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different - ${CMAKE_CURRENT_SOURCE_DIR}/run.bat - $/ - COMMAND ${CMAKE_COMMAND} -E copy_if_different - ${CMAKE_CURRENT_SOURCE_DIR}/compare_ppm.py - $/ - COMMAND ${CMAKE_COMMAND} -E copy_if_different - ${CMAKE_CURRENT_SOURCE_DIR}/run_integration_test.py - $/ -) - -# Register integration test with CTest -add_test(NAME OpenGL_Minimal_Integration - COMMAND ${Python3_EXECUTABLE} $/run_integration_test.py - $ - minimal.ppm - ${CMAKE_CURRENT_SOURCE_DIR}/minimal/GT_minimal.ppm - 5 - WORKING_DIRECTORY $ -) -``` - ---- - -## 6. 测试前缀对应表 - -| 类名 | 测试前缀 | -|------|---------| -| OpenGLDevice | Device | -| OpenGLBuffer | Buffer | -| OpenGLFence | Fence | -| OpenGLTexture | Texture | -| OpenGLShader | Shader | -| OpenGLPipelineState | PipelineState | -| OpenGLVertexArray | VertexArray | -| OpenGLCommandList | CommandList | -| OpenGLSampler | Sampler | -| OpenGLSwapChain | SwapChain | -| OpenGLRenderTargetView | RTV | -| OpenGLDepthStencilView | DSV | - ---- - -## 7. 测试执行 - -### 7.1 单元测试 - -```bash -# 方式 1: 使用统一脚本 -python scripts/run_tests.py --unit-only - -# 方式 2: 直接使用 CTest -cd build/tests/RHI/OpenGL/unit -ctest -C Debug --output-on-failure -``` - -### 7.2 集成测试 - -```bash -# 方式 1: 使用统一脚本 -python scripts/run_tests.py --integration - -# 方式 2: 直接使用 CTest -cd build/tests/RHI/OpenGL/integration -ctest -C Debug --output-on-failure -``` - -### 7.3 构建和测试 - -```bash -# 构建 -cmake --build . --target OpenGL_Minimal --config Debug - -# 运行测试 -python scripts/run_tests.py --build -``` - ---- - -## 8. CI 集成 - -### 8.1 GitHub Actions 配置 - -```yaml -name: OpenGL Tests -on: [push, pull_request] - -jobs: - test: - runs-on: windows-latest - steps: - - uses: actions/checkout@v4 - - - name: Configure CMake - run: cmake -B build -DCMAKE_BUILD_TYPE=Debug - - - name: Build OpenGL Tests - run: cmake --build build --target opengl_engine_tests OpenGL_Minimal --config Debug - - - name: Run Unit Tests - run: cd build/tests/RHI/OpenGL/unit && ctest -C Debug --output-on-failure - - - name: Run Integration Tests - run: python scripts/run_tests.py --integration -``` - -### 8.2 CI 模式 - -`--ci` 模式会跳过需要 GUI 的集成测试: -```bash -python scripts/run_tests.py --ci # 仅运行单元测试 -``` - ---- - -## 9. 文件清单 - -### 9.1 需创建的文件 - -| 文件路径 | 说明 | -|---------|------| -| `tests/RHI/OpenGL/CMakeLists.txt` | 顶层 CMake 配置 | -| `tests/RHI/OpenGL/TEST_SPEC.md` | OpenGL 专项规范 | -| `tests/RHI/OpenGL/TEST_IMPROVEMENT_PLAN.md` | 改进计划 | -| `tests/RHI/OpenGL/unit/CMakeLists.txt` | 单元测试构建配置 | -| `tests/RHI/OpenGL/integration/CMakeLists.txt` | 集成测试构建配置 | -| `tests/RHI/OpenGL/integration/run_integration_test.py` | 集成测试运行脚本 | -| `tests/RHI/OpenGL/integration/compare_ppm.py` | PPM 图像比对脚本 | -| `tests/RHI/OpenGL/integration/run.bat` | Windows 启动脚本 | -| `tests/RHI/OpenGL/integration/minimal/main.cpp` | 最小化集成测试 | -| `tests/RHI/OpenGL/integration/minimal/GT_minimal.ppm` | Golden Image | -| `tests/RHI/OpenGL/integration/minimal/Res/Shader/*.vert` | Vertex Shader | -| `tests/RHI/OpenGL/integration/minimal/Res/Shader/*.frag` | Fragment Shader | - -### 9.2 需修改的文件 - -| 文件路径 | 修改内容 | -|---------|---------| -| `tests/RHI/OpenGL/fixtures/OpenGLTestFixture.h` | 重构 Fixture 接口 | -| `tests/RHI/OpenGL/fixtures/OpenGLTestFixture.cpp` | 重构 Fixture 实现 | -| `tests/RHI/OpenGL/CMakeLists.txt` | 简化为 add_subdirectory | - -### 9.3 需移动的文件 - -| 原路径 | 新路径 | -|-------|-------| -| `tests/RHI/OpenGL/test_*.cpp` | `tests/RHI/OpenGL/unit/test_*.cpp` | -| `tests/RHI/OpenGL/fixtures/*` | `tests/RHI/OpenGL/unit/fixtures/*` | - ---- - -## 10. OpenGL 与 D3D12 测试差异说明 - -### 10.1 平台特性差异 - -| 方面 | D3D12 | OpenGL | -|------|-------|--------| -| **设备创建** | `D3D12CreateDevice()` 每测试独立 | 共享 GLFWcontext + Glad | -| **上下文管理** | 无 | GLFWwindow 生命周期 | -| **错误检查** | `HRESULT` 返回值 | `glGetError()` 状态码 | -| **渲染目标** | RTV/DSV descriptor | OpenGL Framebuffer Object | -| **同步原语** | `ID3D12Fence` | `glFenceSync` + `glClientWaitSync` | -| **管线状态** | PSO 对象 | OpenGL State Machine | -| **资源绑定** | CommandList + DescriptorHeap | `glBindBuffer`, `glBindTexture` | - -### 10.2 Fixture 设计差异 - -```cpp -// D3D12 - 每测试独立 COM 对象 -class D3D12TestFixture : public ::testing::Test { - ComPtr mDevice; - ComPtr mCommandQueue; -}; - -// OpenGL - 需要共享 context,但每测试独立 window -class OpenGLTestFixture : public ::testing::Test { - GLFWwindow* m_window; // 每测试独立 - OpenGLDevice* m_device; // 每测试独立 -}; -``` - -### 10.3 测试资源差异 - -| 方面 | D3D12 | OpenGL | -|------|-------|--------| -| **Shader 格式** | HLSL (.hlsl) | GLSL (.vert, .frag, .geom) | -| **纹理格式** | DDS | PNG/BMP/TGA | -| **模型格式** | 自定义 .lhsm | 自定义 .lhsm | - ---- - -## 11. 已知问题与待办 - -### 11.1 Phase 1 待办 - -- [ ] 创建 `unit/` 和 `integration/` 目录 -- [ ] 移动测试文件到 `unit/` -- [ ] 创建顶层 `CMakeLists.txt` -- [ ] 重构 `unit/CMakeLists.txt` - -### 11.2 Phase 2 待办 - -- [ ] 重构 `OpenGLTestFixture` 消除静态成员 -- [ ] 验证测试隔离效果 - -### 11.3 Phase 3 待办 - -- [ ] 启用 `test_buffer.cpp` -- [ ] 启用 `test_fence.cpp` -- [ ] 启用 `test_shader.cpp` -- [ ] 修复编译错误 - -### 11.4 Phase 4 待办 - -- [ ] 补充 `VertexArray_Bind_MultipleAttributes` 测试 - -### 11.5 Phase 5 待办 - -- [ ] 创建 `integration/` 目录结构 -- [ ] 复制并适配 `run_integration_test.py` -- [ ] 复制并适配 `compare_ppm.py` -- [ ] 创建 `minimal/` 集成测试 -- [ ] 创建 `render_model/` 集成测试 -- [ ] 生成 Golden Image - -### 11.6 Phase 6 待办 - -- [ ] 编写 `TEST_SPEC.md` -- [ ] 编写 `TEST_IMPROVEMENT_PLAN.md` - ---- - -## 12. 规范更新记录 - -| 版本 | 日期 | 变更 | -|------|------|------| -| 1.0 | 2026-03-20 | 初始版本,参考 D3D12 TEST_SPEC.md 制定重构方案 | - ---- - -**规范版本**: 1.0 -**最后更新**: 2026-03-20 -**前置文档**: -- [tests/TEST_SPEC.md](../tests/TEST_SPEC.md) -- [tests/RHI/D3D12/TEST_SPEC.md](./D3D12/TEST_SPEC.md) \ No newline at end of file diff --git a/docs/used/OpenGL后端测试设计.md b/docs/used/OpenGL后端测试设计.md deleted file mode 100644 index 664b2fd1..00000000 --- a/docs/used/OpenGL后端测试设计.md +++ /dev/null @@ -1,1186 +0,0 @@ -# OpenGL 后端测试设计 - -## 1. 概述 - -本文档描述 XCEngine OpenGL 渲染后端的测试框架设计。OpenGL 后端与 D3D12 后端有显著差异,主要体现在: - -- **窗口依赖**:OpenGL 需要 GLFW 窗口上下文才能执行任何图形操作 -- **对象模型**:使用 GL 句柄(unsigned int)而非 COM 接口 -- **状态管理**:OpenGL 是立即模式渲染,状态通过 GL 函数设置 -- **同步机制**:使用 GLsync 对象进行 GPU/CPU 同步 - -### 1.1 测试目标 - -- 验证 OpenGL 各组件 API 的正确性 -- 确保组件间的协作正常工作 -- 捕获 GL 错误和状态问题 -- 支持持续集成(CI)自动化测试 - -### 1.2 特殊挑战 - -| 挑战 | 说明 | 解决方案 | -|------|------|----------| -| 窗口依赖 | OpenGL 需要 GL 上下文 | 使用无头渲染(headless)或 offscreen 渲染 | -| CI 环境 | 无显示器无法创建窗口 | 使用 OSMesa 或 EGL offscreen 上下文 | -| GL 状态污染 | 全局 GL 状态影响测试 | 每个测试独立设置状态 + 状态重置 | - -### 1.3 与 D3D12 测试对比 - -| 特性 | D3D12 测试 | OpenGL 测试 | -|------|-------------|-------------| -| 设备创建 | D3D12CreateDevice | GLFW + glfwMakeContextCurrent | -| 错误检查 | HRESULT 返回值 | glGetError() | -| 对象类型 | COM 接口 (IUnknown) | GL 句柄 (unsigned int) | -| 资源清理 | Release() | glDelete*() | -| 同步原语 | ID3D12Fence | GLsync | -| 状态管理 | 显式状态对象 | 全局 GL 状态 | - -## 2. 测试目录结构 - -``` -tests/RHI/OpenGL/ -├── CMakeLists.txt # 测试构建配置 -├── fixtures/ -│ ├── OpenGLTestFixture.h # 基础测试夹具 -│ └── OpenGLTestFixture.cpp # 夹具实现 -├── test_device.cpp # OpenGLDevice 测试 -├── test_buffer.cpp # OpenGLBuffer 测试 -├── test_texture.cpp # OpenGLTexture 测试 -├── test_shader.cpp # OpenGLShader 测试 -├── test_pipeline_state.cpp # OpenGLPipelineState 测试 -├── test_vertex_array.cpp # OpenGLVertexArray 测试 -├── test_command_list.cpp # OpenGLCommandList 测试 -├── test_sampler.cpp # OpenGLSampler 测试 -├── test_fence.cpp # OpenGLFence 测试 -├── test_render_target_view.cpp # OpenGLRenderTargetView 测试 -├── test_depth_stencil_view.cpp # OpenGLDepthStencilView 测试 -└── test_swap_chain.cpp # OpenGLSwapChain 测试 -``` - -## 3. 测试夹具设计 - -### 3.1 基础夹具 - -```cpp -// fixtures/OpenGLTestFixture.h -#pragma once - -#include -#include - -namespace XCEngine { -namespace RHI { - -class OpenGLTestFixture : public ::testing::Test { -protected: - static void SetUpTestSuite(); - static void TearDownTestSuite(); - - void SetUp() override; - void TearDown() override; - - GLFWwindow* GetWindow() { return m_window; } - void MakeContextCurrent(); - void DoneContextCurrent(); - - void ClearGLErrors(); - bool CheckGLError(const char* file, int line); - -private: - static GLFWwindow* m_window; - static bool m_contextInitialized; -}; - -} // namespace RHI -} // namespace XCEngine -``` - -### 3.2 设计决策 - -**为什么使用 GLFW 窗口?** - -OpenGL 必须有有效的 GL 上下文才能执行。测试框架使用 GLFW 创建窗口并获取 GL 上下文。在 CI 环境中可能需要使用 OSMesa 或 offscreen 渲染。 - -### 3.3 GL 错误检查辅助 - -```cpp -// fixtures/OpenGLTestFixture.cpp -#include "OpenGLTestFixture.h" - -GLFWwindow* OpenGLTestFixture::m_window = nullptr; -bool OpenGLTestFixture::m_contextInitialized = false; - -void OpenGLTestFixture::SetUpTestSuite() { - if (!glfwInit()) { - GTEST_SKIP() << "Failed to initialize GLFW"; - return; - } - - // 创建隐藏窗口用于测试 - glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6); - glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); - - m_window = glfwCreateWindow(640, 480, "OpenGL Tests", nullptr, nullptr); - if (!m_window) { - GTEST_SKIP() << "Failed to create GLFW window"; - glfwTerminate(); - return; - } - - glfwMakeContextCurrent(m_window); - m_contextInitialized = true; -} - -void OpenGLTestFixture::TearDownTestSuite() { - if (m_window) { - glfwDestroyWindow(m_window); - m_window = nullptr; - } - glfwTerminate(); - m_contextInitialized = false; -} - -void OpenGLTestFixture::SetUp() { - if (!m_window || !m_contextInitialized) { - GTEST_SKIP() << "OpenGL context not available"; - return; - } - - glfwMakeContextCurrent(m_window); - ClearGLErrors(); -} - -void OpenGLTestFixture::TearDown() { - // 确保没有未检查的 GL 错误 - GLenum error = glGetError(); - if (error != GL_NO_ERROR) { - // 记录但不一定失败,因为某些 GL 状态改变会产生预期内的错误 - } - - // 重置 GL 状态 - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - glUseProgram(0); - glBindVertexArray(0); - glBindFramebuffer(GL_FRAMEBUFFER, 0); - - glDisable(GL_DEPTH_TEST); - glDisable(GL_STENCIL_TEST); - glDisable(GL_BLEND); - glDisable(GL_CULL_FACE); - glDisable(GL_SCISSOR_TEST); -} - -void OpenGLTestFixture::ClearGLErrors() { - while (glGetError() != GL_NO_ERROR); -} - -bool OpenGLTestFixture::CheckGLError(const char* file, int line) { - GLenum error = glGetError(); - if (error != GL_NO_ERROR) { - GTEST_MESSAGE_ << "OpenGL Error: " << error - << " (" << GetGLErrorString(error) << ")" - << " at " << file << ":" << line; - return false; - } - return true; -} - -const char* OpenGLTestFixture::GetGLErrorString(GLenum error) { - switch (error) { - case GL_NO_ERROR: return "GL_NO_ERROR"; - case GL_INVALID_ENUM: return "GL_INVALID_ENUM"; - case GL_INVALID_VALUE: return "GL_INVALID_VALUE"; - case GL_INVALID_OPERATION: return "GL_INVALID_OPERATION"; - case GL_INVALID_FRAMEBUFFER_OPERATION: return "GL_INVALID_FRAMEBUFFER_OPERATION"; - case GL_OUT_OF_MEMORY: return "GL_OUT_OF_MEMORY"; - default: return "Unknown error"; - } -} -``` - -### 3.4 GL 检查宏 - -```cpp -// 在头文件中定义检查宏 -#define GL_CLEAR_ERRORS() \ - do { while (glGetError() != GL_NO_ERROR); } while(0) - -#define GL_CHECK(call) \ - do { \ - call; \ - ASSERT_TRUE(OpenGLTestFixture::CheckGLError(__FILE__, __LINE__)) \ - << "GL error after " << #call; \ - } while(0) - -#define GL_EXPECT_SUCCESS(call) \ - do { \ - call; \ - EXPECT_EQ(glGetError(), GL_NO_ERROR) \ - << "GL error after " << #call; \ - } while(0) -``` - -bool OpenGLTestFixture::CheckGLError(const char* file, int line) { - GLenum error = glGetError(); - if (error != GL_NO_ERROR) { - GTEST_MESSAGE_ << "OpenGL Error: " << error - << " at " << file << ":" << line; - return false; - } - return true; -} - -#define GL_CHECK() ASSERT_TRUE(CheckGLError(__FILE__, __LINE__)) -``` - -## 4. 组件测试详情 - -### 4.1 OpenGLDevice - -**文件**: `test_device.cpp` - -**可测试 API 点**: - -| 测试类别 | 测试用例 | 验证内容 | -|----------|----------|----------| -| 初始化 | `CreateRenderWindow_ValidParams` | 窗口创建成功 | -| 初始化 | `InitializeWithExistingWindow` | 现有窗口初始化 | -| 设备信息 | `GetDeviceInfo_ReturnsValid` | 供应商/渲染器信息 | -| 窗口操作 | `SwapBuffers_ClearsScreen` | 交换缓冲区 | -| 窗口操作 | `PollEvents_ProcessesInput` | 事件轮询 | -| 窗口操作 | `ShouldClose_ReturnsBool` | 关闭状态查询 | - -**测试代码示例**: - -```cpp -#include "fixtures/OpenGLTestFixture.h" - -TEST_F(OpenGLTestFixture, Device_CreateWindow_Success) { - OpenGLDevice device; - - bool result = device.CreateRenderWindow(800, 600, "Test Window", false); - - ASSERT_TRUE(result); - ASSERT_NE(device.GetWindow(), nullptr); -} - -TEST_F(OpenGLTestFixture, Device_GetDeviceInfo) { - OpenGLDevice device; - device.CreateRenderWindow(800, 600, "Test", false); - - const auto& info = device.GetDeviceInfo(); - - EXPECT_FALSE(info.vendor.empty()); - EXPECT_FALSE(info.renderer.empty()); - EXPECT_GE(info.majorVersion, 4); - EXPECT_GE(info.minorVersion, 0); -} - -TEST_F(OpenGLTestFixture, Device_PollEvents) { - OpenGLDevice device; - device.CreateRenderWindow(800, 600, "Test", false); - - bool result = device.PollEvents(); - EXPECT_TRUE(result); -} - -TEST_F(OpenGLTestFixture, Device_SwapBuffers) { - OpenGLDevice device; - device.CreateRenderWindow(800, 600, "Test", false); - - device.SwapBuffers(); - - GL_CHECK(); -} -``` - -### 4.2 OpenGLBuffer - -**文件**: `test_buffer.cpp` - -**可测试 API 点**: - -| 测试类别 | 测试用例 | 验证内容 | -|----------|----------|----------| -| 初始化 | `Initialize_VertexBuffer` | 顶点缓冲创建 | -| 初始化 | `Initialize_IndexBuffer` | 索引缓冲创建 | -| 初始化 | `Initialize_UniformBuffer` | Uniform 缓冲创建 | -| 初始化 | `Initialize_Dynamic` | 动态缓冲创建 | -| 绑定 | `Bind_Unbind` | 缓冲绑定/解绑 | -| 绑定 | `BindBase_TargetIndex` | 缓冲绑定到目标索引 | -| 数据操作 | `Map_Unmap` | 数据映射操作 | -| 数据操作 | `SetData_UpdatesContent` | 数据更新 | - -**测试代码示例**: - -```cpp -#include "fixtures/OpenGLTestFixture.h" -#include "OpenGLBuffer.h" - -TEST_F(OpenGLTestFixture, Buffer_InitializeVertexBuffer) { - OpenGLBuffer buffer; - float vertices[] = { 0.0f, 0.0f, 0.5f, 0.5f }; - - bool result = buffer.InitializeVertexBuffer(vertices, sizeof(vertices)); - - ASSERT_TRUE(result); - EXPECT_NE(buffer.GetID(), 0u); - EXPECT_EQ(buffer.GetSize(), sizeof(vertices)); - EXPECT_EQ(buffer.GetType(), OpenGLBufferType::Vertex); -} - -TEST_F(OpenGLTestFixture, Buffer_InitializeIndexBuffer) { - OpenGLBuffer buffer; - unsigned int indices[] = { 0, 1, 2 }; - - bool result = buffer.InitializeIndexBuffer(indices, sizeof(indices)); - - ASSERT_TRUE(result); - EXPECT_NE(buffer.GetID(), 0u); - EXPECT_EQ(buffer.GetSize(), sizeof(indices)); -} - -TEST_F(OpenGLTestFixture, Buffer_InitializeUniformBuffer) { - OpenGLBuffer buffer; - float data[16] = {}; - - bool result = buffer.Initialize( - OpenGLBufferType::Uniform, - sizeof(data), - data, - true - ); - - ASSERT_TRUE(result); - EXPECT_TRUE(buffer.IsDynamic()); - EXPECT_EQ(buffer.GetType(), OpenGLBufferType::Uniform); -} - -TEST_F(OpenGLTestFixture, Buffer_BindUnbind) { - OpenGLBuffer buffer; - buffer.InitializeVertexBuffer(nullptr, 16); - - buffer.Bind(); - GLenum boundBuffer = 0; - glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &boundBuffer); - EXPECT_EQ(boundBuffer, static_cast(buffer.GetID())); - - buffer.Unbind(); - glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &boundBuffer); - EXPECT_EQ(boundBuffer, 0); -} - -TEST_F(OpenGLTestFixture, Buffer_MapUnmap) { - OpenGLBuffer buffer; - buffer.Initialize(OpenGLBufferType::CopyRead, 256, nullptr, true); - - void* mappedData = buffer.Map(); - ASSERT_NE(mappedData, nullptr); - - memset(mappedData, 0xAB, 256); - - buffer.Unmap(); - - GL_CHECK(); -} -``` - -### 4.3 OpenGLTexture - -**文件**: `test_texture.cpp` - -**可测试 API 点**: - -| 测试类别 | 测试用例 | 验证内容 | -|----------|----------|----------| -| 初始化 | `Initialize_2DTexture` | 2D 纹理创建 | -| 初始化 | `Initialize_3DTexture` | 3D 纹理创建 | -| 初始化 | `Initialize_CubeMap` | 立方体纹理创建 | -| 初始化 | `Initialize_FromFile` | 从文件加载纹理 | -| 绑定 | `Bind_Unbind` | 纹理绑定/解绑 | -| 绑定 | `BindImage_ReadWrite` | 图像绑定 | -| 生成 | `GenerateMipmap` | Mipmap 生成 | -| 参数 | `SetFiltering` | 过滤参数设置 | -| 参数 | `SetWrapping` | 环绕参数设置 | - -### 4.4 OpenGLShader - -**文件**: `test_shader.cpp` - -**可测试 API 点**: - -| 测试类别 | 测试用例 | 验证内容 | -|----------|----------|----------| -| 编译 | `Compile_VertexFragment` | 顶点和片段着色器编译 | -| 编译 | `Compile_WithGeometry` | 几何着色器编译 | -| 编译 | `Compile_Compute` | 计算着色器编译 | -| 编译 | `Compile_InvalidSource` | 无效源码编译失败 | -| 链接 | `Link_ValidProgram` | 程序链接成功 | -| 链接 | `Link_MissingStage` | 缺失阶段链接失败 | -| Uniform | `SetInt_SetFloat` | Uniform 设置 | -| Uniform | `SetVec3_SetMat4` | 向量/矩阵 Uniform | - -**测试代码示例**: - -```cpp -#include "fixtures/OpenGLTestFixture.h" -#include "OpenGLShader.h" - -TEST_F(OpenGLTestFixture, Shader_Compile_VertexFragment) { - const char* vertexSource = R"( - #version 460 core - layout(location = 0) in vec3 aPosition; - void main() { - gl_Position = vec4(aPosition, 1.0); - } - )"; - - const char* fragmentSource = R"( - #version 460 core - out vec4 FragColor; - void main() { - FragColor = vec4(1.0, 0.0, 0.0, 1.0); - } - )"; - - OpenGLShader shader; - bool result = shader.Compile(vertexSource, fragmentSource); - - ASSERT_TRUE(result); - EXPECT_TRUE(shader.IsValid()); - EXPECT_NE(shader.GetID(), 0u); -} - -TEST_F(OpenGLTestFixture, Shader_SetUniforms) { - OpenGLShader shader; - const char* vs = R"( - #version 460 core - void main() { gl_Position = vec4(0.0); } - )"; - const char* fs = R"( - #version 460 core - uniform int uIntValue; - uniform float uFloatValue; - uniform vec3 uVec3Value; - uniform mat4 uMat4Value; - out vec4 FragColor; - void main() { FragColor = vec4(1.0); } - )"; - shader.Compile(vs, fs); - - shader.Use(); - - shader.SetInt("uIntValue", 42); - shader.SetFloat("uFloatValue", 3.14f); - shader.SetVec3("uVec3Value", 1.0f, 2.0f, 3.0f); - - float mat[16] = {}; - mat[0] = 1.0f; mat[5] = 1.0f; mat[10] = 1.0f; mat[15] = 1.0f; - shader.SetMat4("uMat4Value", mat); - - GL_CHECK(); -} - -TEST_F(OpenGLTestFixture, Shader_Use_Unbind) { - OpenGLShader shader; - shader.Compile( - "void main() { gl_Position = vec4(0.0); }", - "void main() { }" - ); - - shader.Use(); - - GLint currentProgram = 0; - glGetIntegerv(GL_CURRENT_PROGRAM, ¤tProgram); - EXPECT_EQ(currentProgram, static_cast(shader.GetID())); - - shader.Unbind(); - - glGetIntegerv(GL_CURRENT_PROGRAM, ¤tProgram); - EXPECT_EQ(currentProgram, 0); -} -``` - -### 4.5 OpenGLPipelineState - -**文件**: `test_pipeline_state.cpp` - -**可测试 API 点**: - -| 测试类别 | 测试用例 | 验证内容 | -|----------|----------|----------| -| 深度状态 | `SetDepthStencilState` | 深度模板状态 | -| 深度状态 | `ApplyDepthStencil` | 应用深度状态 | -| 混合状态 | `SetBlendState` | 混合状态设置 | -| 混合状态 | `ApplyBlend` | 应用混合状态 | -| 光栅化 | `SetRasterizerState` | 光栅化状态 | -| 视口 | `SetViewport` | 视口设置 | -| 裁剪 | `SetScissor` | 裁剪矩形 | -| 清除 | `SetClearColor` | 清除颜色 | -| 清除 | `Clear_ColorDepthStencil` | 清除颜色/深度/模板 | - -### 4.6 OpenGLVertexArray - -**文件**: `test_vertex_array.cpp` - -**可测试 API 点**: - -| 测试类别 | 测试用例 | 验证内容 | -|----------|----------|----------| -| 初始化 | `Initialize_CreatesVAO` | VAO 创建 | -| 属性 | `AddVertexBuffer` | 顶点缓冲添加 | -| 属性 | `SetIndexBuffer` | 索引缓冲设置 | -| 绑定 | `Bind_Unbind` | VAO 绑定/解绑 | -| 属性 | `GetIndexCount` | 索引数量 | - -### 4.7 OpenGLCommandList - -**文件**: `test_command_list.cpp` - -**可测试 API 点**: - -| 测试类别 | 测试用例 | 验证内容 | -|----------|----------|----------| -| 清除 | `Clear_ColorBuffer` | 清除颜色缓冲 | -| 清除 | `Clear_DepthStencil` | 清除深度/模板 | -| 管线 | `SetPipelineState` | 设置管线状态 | -| 顶点 | `SetVertexBuffer` | 设置顶点缓冲 | -| 索引 | `SetIndexBuffer` | 设置索引缓冲 | -| 绘制 | `Draw_Triangles` | 绘制三角形 | -| 绘制 | `DrawIndexed_Indices` | 索引绘制 | -| 绘制 | `DrawInstanced` | 实例化绘制 | -| 计算 | `Dispatch_ComputeShader` | 计算着色器分发 | -| 同步 | `MemoryBarrier` | 内存屏障 | -| 纹理 | `BindTexture` | 纹理绑定 | - -### 4.8 OpenGLSampler - -**文件**: `test_sampler.cpp` - -**可测试 API 点**: - -| 测试类别 | 测试用例 | 验证内容 | -|----------|----------|----------| -| 初始化 | `Initialize_DefaultDesc` | 默认采样器创建 | -| 初始化 | `Initialize_CustomDesc` | 自定义采样器创建 | -| 绑定 | `Bind_Unbind` | 采样器绑定/解绑 | -| 参数 | `GetID_ReturnsValid` | 采样器 ID 有效 | - -### 4.9 OpenGLFence - -**文件**: `test_fence.cpp` - -**可测试 API 点**: - -| 测试类别 | 测试用例 | 验证内容 | -|----------|----------|----------| -| 初始化 | `Initialize_Unsignaled` | 未 signaled 状态创建 | -| 初始化 | `Initialize_Signaled` | signaled 状态创建 | -| 同步 | `Signal_SetsValue` | Signal 设置值 | -| 同步 | `Wait_Blocks` | Wait 阻塞等待 | -| 状态 | `IsSignaled_ReturnsState` | signaled 状态查询 | -| 状态 | `GetStatus_ReturnsCorrect` | 状态查询 | - -**测试代码示例**: - -```cpp -#include "fixtures/OpenGLTestFixture.h" -#include "OpenGLFence.h" - -TEST_F(OpenGLTestFixture, Fence_Initialize_Unsignaled) { - OpenGLFence fence; - - bool result = fence.Initialize(false); - - ASSERT_TRUE(result); - EXPECT_EQ(fence.GetStatus(), FenceStatus::Unsignaled); -} - -TEST_F(OpenGLTestFixture, Fence_Initialize_Signaled) { - OpenGLFence fence; - - bool result = fence.Initialize(true); - - ASSERT_TRUE(result); - // 初始 signaled 状态可能立即完成 -} - -TEST_F(OpenGLTestFixture, Fence_Signal_Wait) { - OpenGLFence fence; - fence.Initialize(false); - - fence.Signal(1); - - // 等待完成 - fence.Wait(1); - - EXPECT_TRUE(fence.IsSignaled()); - EXPECT_EQ(fence.GetCompletedValue(), 1u); -} - -TEST_F(OpenGLTestFixture, Fence_GetStatus) { - OpenGLFence fence; - fence.Initialize(false); - - FenceStatus status = fence.GetStatus(); - EXPECT_TRUE(status == FenceStatus::Signaled || - status == FenceStatus::Unsignaled); -} - -TEST_F(OpenGLFence, Fence_MultipleSignals) { - OpenGLFence fence; - fence.Initialize(false); - - fence.Signal(1); - fence.Wait(1); - - fence.Signal(2); - fence.Wait(2); - - EXPECT_EQ(fence.GetCompletedValue(), 2u); -} -``` - -### 4.10 OpenGLRenderTargetView - -**文件**: `test_render_target_view.cpp` - -**可测试 API 点**: - -| 测试类别 | 测试用例 | 验证内容 | -|----------|----------|----------| -| 初始化 | `Initialize_Texture2D` | 2D 纹理 RTV 创建 | -| 初始化 | `Initialize_Cubemap` | 立方体 RTV 创建 | -| 绑定 | `Bind_Unbind` | RTV 绑定/解绑 | -| 清除 | `Clear_Color` | 清除颜色 | - -### 4.11 OpenGLDepthStencilView - -**文件**: `test_depth_stencil_view.cpp` - -**可测试 API 点**: - -| 测试类别 | 测试用例 | 验证内容 | -|----------|----------|----------| -| 初始化 | `Initialize_Texture2D` | 2D 纹理 DSV 创建 | -| 初始化 | `Initialize_Cubemap` | 立方体 DSV 创建 | -| 绑定 | `Bind_Unbind` | DSV 绑定/解绑 | -| 清除 | `ClearDepthStencil` | 清除深度/模板 | - -### 4.12 OpenGLSwapChain - -**文件**: `test_swap_chain.cpp` - -**可测试 API 点**: - -| 测试类别 | 测试用例 | 验证内容 | -|----------|----------|----------| -| 初始化 | `Initialize_Window` | 交换链初始化 | -| 初始化 | `Initialize_WithSize` | 指定尺寸初始化 | -| 显示 | `Present_VSync` | 垂直同步显示 | -| 显示 | `Present_Immediate` | 立即显示 | -| 调整 | `Resize_ChangesSize` | 调整大小 | -| 属性 | `GetWidth_GetHeight` | 宽高查询 | - -## 5. 测试分类 - -### 5.1 单元测试 - -测试单一 API 的基本功能: - -```cpp -TEST(OpenGL_Buffer, Initialize_VertexBuffer_ReturnsValidID) { - OpenGLBuffer buffer; - float data[] = { 1.0f, 2.0f, 3.0f }; - - bool result = buffer.InitializeVertexBuffer(data, sizeof(data)); - - ASSERT_TRUE(result); - ASSERT_NE(buffer.GetID(), 0u); -} -``` - -### 5.2 集成测试 - -测试多个组件的协作: - -```cpp -TEST(OpenGL_Buffer, UploadToTexture_DataIntegrity) { - // 创建纹理 - OpenGLTexture texture; - texture.Initialize2D(64, 64, 4, nullptr, false); - - // 创建缓冲并上传数据 - OpenGLBuffer buffer; - buffer.Initialize(OpenGLBufferType::CopyRead, 64 * 64 * 4, nullptr, false); - - // 使用命令列表复制 - OpenGLCommandList cmdList; - cmdList.BindTexture(GL_TEXTURE_2D, 0, texture.GetID()); - - GL_CHECK(); -} -``` - -### 5.3 渲染结果测试 - -渲染到帧缓冲并验证像素: - -```cpp -TEST(OpenGL_RenderTarget, ClearColor_VerifyPixelValue) { - // 创建帧缓冲 - OpenGLRenderTargetView rtv; - rtv.Initialize(0); // 默认帧缓冲 - - // 清除为特定颜色 - rtv.Clear(0.25f, 0.5f, 0.75f, 1.0f); - - // 读取像素 - std::vector pixels(4); - glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); - - // 验证 - EXPECT_NEAR(pixels[0], 64, 2); // R - EXPECT_NEAR(pixels[1], 128, 2); // G - EXPECT_NEAR(pixels[2], 192, 2); // B -} -``` - -## 6. 构建配置 - -### 6.1 CMakeLists.txt - -```cmake -cmake_minimum_required(VERSION 3.15) - -project(OpenGLTests) - -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_STANDARD_REQUIRED ON) - -# 查找 OpenGL -find_package(OpenGL REQUIRED) - -# 查找 GLFW -find_package(GLFW3 REQUIRED) - -# Google Test -include(FetchContent) -FetchContent_Declare( - googletest - GIT_REPOSITORY https://gitee.com/mirrors/googletest.git - GIT_TAG v1.14.0 -) -set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) -FetchContent_MakeAvailable(googletest) - -enable_testing() - -# 测试源文件 -set(TEST_SOURCES - test_device.cpp - test_buffer.cpp - test_texture.cpp - test_shader.cpp - test_pipeline_state.cpp - test_vertex_array.cpp - test_command_list.cpp - test_sampler.cpp - test_fence.cpp - test_render_target_view.cpp - test_depth_stencil_view.cpp - test_swap_chain.cpp -) - -# 可执行文件 -add_executable(opengl_tests ${TEST_SOURCES}) - -# 链接库 -target_link_libraries(opengl_tests PRIVATE - OpenGL::GL - GLFW3::GLFW - XCEngine - GTest::gtest - GTest::gtest_main -) - -# 包含目录 -target_include_directories(opengl_tests PRIVATE - ${CMAKE_SOURCE_DIR}/engine/include - ${CMAKE_CURRENT_SOURCE_DIR}/fixtures -) - -# 编译定义 -target_compile_definitions(opengl_tests PRIVATE - GL_GLEXT_PROTOTYPES -) - -# 测试资源路径 -target_compile_definitions(opengl_tests PRIVATE - TEST_RESOURCES_DIR="${CMAKE_SOURCE_DIR}/tests/RHI/OpenGL/Res" -) - -add_test(NAME OpenGLTests COMMAND opengl_tests) -``` - -### 6.2 tests/CMakeLists.txt 更新 - -在 `tests/CMakeLists.txt` 中添加: - -```cmake -# OpenGL Tests -add_subdirectory(RHI/OpenGL) -``` - -## 7. CI 集成 - -### 7.1 GitHub Actions (Linux) - -```yaml -# .github/workflows/opengl-tests.yml -name: OpenGL Tests - -on: [push, pull_request] - -jobs: - test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Install dependencies - run: | - sudo apt-get update - sudo apt-get install -y \ - libgl1-mesa-dev \ - libglfw3-dev \ - libglm-dev - - - name: Configure - run: cmake -B build -S . -G "Unix Makefiles" - - - name: Build - run: cmake --build build --config Debug - - - name: Run Tests - run: ctest --test-dir build -C Debug --output-on-failure -``` - -### 7.2 GitHub Actions (Windows) - -```yaml -# .github/workflows/opengl-tests-windows.yml -name: OpenGL Tests (Windows) - -on: [push, pull_request] - -jobs: - test: - runs-on: windows-latest - steps: - - uses: actions/checkout@v4 - - - name: Configure - run: cmake -B build -S . -G "Visual Studio 17 2022" - - - name: Build - run: cmake --build build --config Debug - - - name: Run Tests - run: ctest --test-dir build -C Debug --output-on-failure -``` - -### 7.3 测试执行流程 - -```bash -# 1. 配置项目 -cmake -B build -S . -G "Unix Makefiles" - -# 2. 编译测试 -cmake --build build --config Debug - -# 3. 运行测试 -./build/tests/RHI/OpenGL/Debug/opengl_tests.exe - -# 或者使用 CTest -ctest --test-dir build -C Debug --output-on-failure - -# 4. 运行特定测试 -./build/tests/RHI/OpenGL/Debug/opengl_tests.exe --gtest_filter=OpenGL_Buffer.* - -# 5. 详细输出 -./build/tests/RHI/OpenGL/Debug/opengl_tests.exe --gtest_also_run_disabled_tests --gtest_print_time=1 -``` - -### 7.4 测试输出示例 - -``` -[==========] Running 54 tests from 1 test suite. -[----------] Global test environment set-up. -[----------] 54 tests from OpenGLTestFixture -[ RUN ] OpenGLTestFixture.Buffer_InitializeVertexBuffer -[ OK ] OpenGLTestFixture.Buffer_InitializeVertexBuffer (12 ms) -[ RUN ] OpenGLTestFixture.Buffer_InitializeIndexBuffer -[ OK ] OpenGLTestFixture.Buffer_InitializeIndexBuffer (8 ms) -... -[----------] Global test environment tear-down -[==========] 54 tests from 1 test suite ran. -[ PASSED ] 54 tests. -``` - -## 8. 测试覆盖矩阵 - -| 组件 | 单元测试 | 集成测试 | 渲染测试 | 性能测试 | -|------|:--------:|:--------:|:--------:|:--------:| -| OpenGLDevice | ✓ | - | - | ✓ | -| OpenGLBuffer | ✓ | ✓ | - | ✓ | -| OpenGLTexture | ✓ | ✓ | ✓ | ✓ | -| OpenGLShader | ✓ | - | - | - | -| OpenGLPipelineState | ✓ | - | - | - | -| OpenGLVertexArray | ✓ | ✓ | - | - | -| OpenGLCommandList | ✓ | ✓ | - | - | -| OpenGLSampler | ✓ | - | - | - | -| OpenGLFence | ✓ | ✓ | - | - | -| OpenGLRenderTargetView | ✓ | - | ✓ | - | -| OpenGLDepthStencilView | ✓ | - | ✓ | - | -| OpenGLSwapChain | - | - | - | - | - -## 9. 测试资源文件 - -### 9.1 资源目录结构 - -``` -tests/RHI/OpenGL/ -├── Res/ -│ ├── Shader/ -│ │ ├── simple_vs.glsl # 简单顶点着色器 -│ │ ├── simple_fs.glsl # 简单片段着色器 -│ │ ├── compute_cs.glsl # 计算着色器 -│ │ └── quad_vs.glsl # Quad 顶点着色器 -│ ├── Texture/ -│ │ ├── test_256x256.png # 测试纹理 -│ │ └── cube_pos_x.jpg # 立方体纹理面 -│ └── Data/ -│ ├── triangle_verts.bin # 三角形顶点数据 -│ └── quad_verts.bin # Quad 顶点数据 -└── ... -``` - -### 9.2 测试着色器示例 - -```glsl -// Res/Shader/simple_vs.glsl -#version 460 core -layout(location = 0) in vec3 aPosition; -layout(location = 1) in vec3 aNormal; -layout(location = 2) in vec2 aTexCoord; - -layout(std140, binding = 0) uniform Camera { - mat4 uModelViewProjection; - mat4 uModel; - mat3 uNormalMatrix; -}; - -out vec3 vPosition; -out vec3 vNormal; -out vec2 vTexCoord; - -void main() { - vPosition = (uModel * vec4(aPosition, 1.0)).xyz; - vNormal = normalize(uNormalMatrix * aNormal); - vTexCoord = aTexCoord; - gl_Position = uModelViewProjection * vec4(aPosition, 1.0); -} -``` - -```glsl -// Res/Shader/simple_fs.glsl -#version 460 core -in vec3 vPosition; -in vec3 vNormal; -in vec2 vTexCoord; - -uniform vec3 uLightDirection; -uniform vec3 uLightColor; -uniform vec3 uAmbientColor; -uniform sampler2D uTexture; - -out vec4 FragColor; - -void main() { - vec3 normal = normalize(vNormal); - float diff = max(dot(normal, uLightDirection), 0.0); - vec3 diffuse = diff * uLightColor; - vec3 ambient = uAmbientColor; - - vec4 texColor = texture(uTexture, vTexCoord); - FragColor = vec4(ambient + diffuse, 1.0) * texColor; -} -``` - -### 9.3 在测试中加载资源 - -```cpp -TEST_F(OpenGLTestFixture, Shader_CompileFromFile) { - OpenGLShader shader; - - std::string vertexPath = std::string(TEST_RESOURCES_DIR) + "/Shader/simple_vs.glsl"; - std::string fragmentPath = std::string(TEST_RESOURCES_DIR) + "/Shader/simple_fs.glsl"; - - // 加载文件内容并编译 - // ... -} - -TEST_F(OpenGLTestFixture, Texture_LoadFromFile) { - OpenGLTexture texture; - - std::string texturePath = std::string(TEST_RESOURCES_DIR) + "/Texture/test_256x256.png"; - - bool result = texture.LoadFromFile(texturePath.c_str()); - - ASSERT_TRUE(result); - EXPECT_EQ(texture.GetWidth(), 256); - EXPECT_EQ(texture.GetHeight(), 256); -} -``` - -## 9. 实现优先级 - -### Phase 1: 核心基础设施 - -1. **OpenGLDevice** - 窗口和上下文管理 -2. **OpenGLBuffer** - 基础数据缓冲 -3. **OpenGLFence** - 同步基础 - -### Phase 2: 资源管理 - -4. **OpenGLTexture** - 纹理资源 -5. **OpenGLSampler** - 采样器 - -### Phase 3: 渲染管线 - -6. **OpenGLShader** - 着色器编译 -7. **OpenGLPipelineState** - 管线状态 -8. **OpenGLVertexArray** - 顶点数组 - -### Phase 4: 命令与视图 - -9. **OpenGLCommandList** - 命令录制 -10. **OpenGLRenderTargetView** - 渲染目标 -11. **OpenGLDepthStencilView** - 深度模板 - -### Phase 5: 窗口管理 - -12. **OpenGLSwapChain** - 交换链 - -## 10. 特殊考虑 - -### 10.1 无头渲染 - -在 CI 环境中没有显示器,需要使用 offscreen 渲染: - -```cpp -// 使用 OSMesa 创建 offscreen 上下文 -#ifdef __linux__ - glfwWindowHint(GLFW_PLATFORM, GLFW_PLATFORM_OSMESA); -#endif - -// 或使用 Mesa 的软件渲染llvmpipe -// 运行测试时设置环境变量: -// MESA_GL_VERSION_OVERRIDE=4.6 MESA_GALLIUM_DRIVER=llvmpipe -``` - -### 10.2 GL 状态隔离 - -OpenGL 状态是全局的,测试需要注意: - -- 每个测试开始时重置 GL 状态 -- 使用独立的 VAO 隔离顶点数组状态 -- 使用独立的 Program 隔离着色器状态 -- 测试结束后清理所有绑定的对象 - -```cpp -void OpenGLTestFixture::ResetGLState() { - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - glBindBuffer(GL_UNIFORM_BUFFER, 0); - - glUseProgram(0); - - glBindVertexArray(0); - - glBindFramebuffer(GL_FRAMEBUFFER, 0); - glBindRenderbuffer(GL_RENDERBUFFER, 0); - - glActiveTexture(GL_TEXTURE0); - for (int i = 0; i < 16; ++i) { - glBindTexture(GL_TEXTURE_2D, 0); - } - - glDisable(GL_DEPTH_TEST); - glDisable(GL_STENCIL_TEST); - glDisable(GL_BLEND); - glDisable(GL_CULL_FACE); - glDisable(GL_SCISSOR_TEST); - - glDepthFunc(GL_LESS); - glStencilFunc(GL_ALWAYS, 0, 0xFF, 0xFF); - glBlendFunc(GL_ONE, GL_ZERO); -} -``` - -### 10.3 错误处理 - -```cpp -// 检查所有 GL 调用后的错误 -#define GL_CALL(call) \ - call; \ - CheckGLError(__FILE__, __LINE__, #call) - -// 期望成功的调用 -#define GL_EXPECT_SUCCESS(call) \ - do { \ - call; \ - EXPECT_EQ(glGetError(), GL_NO_ERROR) \ - << "GL error after " << #call; \ - } while(0) - -// 检查特定错误 -#define GL_EXPECT_ERROR(expectedError, call) \ - do { \ - glGetError(); /* clear existing */ \ - call; \ - EXPECT_EQ(glGetError(), expectedError) \ - << "Expected " << #expectedError << " after " << #call; \ - } while(0) -``` - -### 10.4 跨平台考虑 - -| 平台 | 特殊处理 | -|------|----------| -| Windows | 使用 WGL 创建 GL 上下文 | -| Linux | 使用 GLX 或 Mesa 软件渲染 | -| macOS | 使用 Cocoa/NSOpenGL(需要特殊处理) | -| CI/无头 | 使用 OSMesa 或 EGL offscreen | - -## 11. 后续改进 - -- [ ] 实现 Phase 1 核心基础设施测试 -- [ ] 实现 Phase 2 资源管理测试 -- [ ] 实现 Phase 3 渲染管线测试 -- [ ] 实现 Phase 4 命令与视图测试 -- [ ] 实现 Phase 5 窗口管理测试 -- [ ] 添加资源泄漏检测工具 -- [ ] 添加性能基准测试 -- [ ] 配置 CI 自动测试 -- [ ] 支持 macOS 测试 - ---- - -**文档版本**:1.1 -**创建日期**:2026年3月17日 -**最后更新**:2026年3月17日 diff --git a/docs/used/OpenGL测试实施计划.md b/docs/used/OpenGL测试实施计划.md deleted file mode 100644 index 65344b5e..00000000 --- a/docs/used/OpenGL测试实施计划.md +++ /dev/null @@ -1,410 +0,0 @@ -# OpenGL 测试实施计划 - -## 1. 概述 - -本文档是 `docs/OpenGL后端测试设计.md` 的实施细化计划,基于设计文档中的 5 阶段实现优先级,结合 D3D12 测试经验(54 个测试)制定。 - -### 1.1 目标 - -- 搭建完整的 OpenGL 后端测试框架 -- 实现与 D3D12 对齐的 54+ 个测试用例 -- 确保 CI 环境可用 -- 支持无头渲染(headless)测试 - -### 1.2 测试组件总览 - -| # | 组件 | 测试数量 | 文件 | 优先级 | -|---|------|:--------:|------|:------:| -| 1 | OpenGLDevice | 6 | test_device.cpp | Phase 1 | -| 2 | OpenGLBuffer | 6 | test_buffer.cpp | Phase 1 | -| 3 | OpenGLFence | 5 | test_fence.cpp | Phase 1 | -| 4 | OpenGLTexture | 5 | test_texture.cpp | Phase 2 | -| 5 | OpenGLSampler | 4 | test_sampler.cpp | Phase 2 | -| 6 | OpenGLShader | 4 | test_shader.cpp | Phase 3 | -| 7 | OpenGLPipelineState | 3 | test_pipeline_state.cpp | Phase 3 | -| 8 | OpenGLVertexArray | 4 | test_vertex_array.cpp | Phase 3 | -| 9 | OpenGLCommandList | 8 | test_command_list.cpp | Phase 4 | -| 10 | OpenGLRenderTargetView | 4 | test_render_target_view.cpp | Phase 4 | -| 11 | OpenGLDepthStencilView | 4 | test_depth_stencil_view.cpp | Phase 4 | -| 12 | OpenGLSwapChain | 3 | test_swap_chain.cpp | Phase 5 | -| **总计** | | **56** | | | - ---- - -## 2. 实施阶段 - -### Phase 1: 核心基础设施(约 1 天) - -#### 2.1 创建目录结构 - -``` -tests/RHI/OpenGL/ -├── CMakeLists.txt -├── fixtures/ -│ ├── OpenGLTestFixture.h -│ └── OpenGLTestFixture.cpp -└── Res/ - ├── Shader/ - ├── Texture/ - └── Data/ -``` - -#### 2.2 实现测试夹具 - -**OpenGLTestFixture** 是所有测试的基础: - -| 功能 | 描述 | -|------|------| -| `SetUpTestSuite()` | 创建隐藏 GLFW 窗口和 GL 上下文 | -| `TearDownTestSuite()` | 销毁窗口,终止 GLFW | -| `SetUp()` | 确保上下文激活,清除 GL 错误 | -| `TearDown()` | 重置 GL 状态,检查泄漏 | -| `CheckGLError()` | 检查并报告 GL 错误 | - -**GL 错误检查宏**: - -```cpp -#define GL_CLEAR_ERRORS() do { while (glGetError() != GL_NO_ERROR); } while(0) -#define GL_CHECK(call) do { call; ASSERT_TRUE(CheckGLError(__FILE__, __LINE__)); } while(0) -#define GL_EXPECT_SUCCESS(call) do { call; EXPECT_EQ(glGetError(), GL_NO_ERROR); } while(0) -``` - -#### 2.3 OpenGLDevice 测试(6 个) - -| 测试用例 | 验证内容 | -|----------|----------| -| `CreateRenderWindow_ValidParams` | 窗口创建成功 | -| `CreateRenderWindow_DebugMode` | 调试模式创建 | -| `InitializeWithExistingWindow` | 现有窗口初始化 | -| `GetDeviceInfo_ReturnsValid` | 供应商/渲染器/版本信息 | -| `SwapBuffers_NoErrors` | 交换缓冲区无错误 | -| `PollEvents_ReturnsTrue` | 事件轮询正常 | - -#### 2.4 OpenGLBuffer 测试(6 个) - -| 测试用例 | 验证内容 | -|----------|----------| -| `Initialize_VertexBuffer` | 顶点缓冲创建 | -| `Initialize_IndexBuffer` | 索引缓冲创建 | -| `Initialize_UniformBuffer` | Uniform 缓冲创建 | -| `Initialize_Dynamic` | 动态缓冲创建 | -| `Bind_Unbind` | 缓冲绑定/解绑 | -| `Map_Unmap` | 数据映射操作 | - -#### 2.5 OpenGLFence 测试(5 个) - -| 测试用例 | 验证内容 | -|----------|----------| -| `Initialize_Unsignaled` | 未 signaled 状态创建 | -| `Initialize_Signaled` | signaled 状态创建 | -| `Signal_SetsValue` | Signal 设置值 | -| `Wait_Blocks` | Wait 阻塞等待 | -| `IsSignaled_ReturnsState` | signaled 状态查询 | - -#### 2.6 验证步骤 - -```bash -# 构建 -cmake --build build --config Debug - -# 运行测试 -./build/tests/RHI/OpenGL/Debug/opengl_tests.exe --gtest_filter=OpenGLTestFixture.Device*:OpenGLTestFixture.Buffer*:OpenGLTestFixture.Fence* - -# 预期结果 -[==========] 17 tests from OpenGLTestFixture -[ PASSED ] 17 tests -``` - ---- - -### Phase 2: 资源管理(约 1 天) - -#### 2.7 OpenGLTexture 测试(5 个) - -| 测试用例 | 验证内容 | -|----------|----------| -| `Initialize_2DTexture` | 2D 纹理创建 | -| `Initialize_CubeMap` | 立方体纹理创建 | -| `Bind_Unbind` | 纹理绑定/解绑 | -| `GenerateMipmap` | Mipmap 生成 | -| `SetFiltering_SetWrapping` | 过滤/环绕参数 | - -#### 2.8 OpenGLSampler 测试(4 个) - -| 测试用例 | 验证内容 | -|----------|----------| -| `Initialize_Default` | 默认采样器创建 | -| `Initialize_Custom` | 自定义采样器创建 | -| `Bind_Unbind` | 采样器绑定/解绑 | -| `GetID_ReturnsValid` | 采样器 ID 有效 | - -#### 2.9 验证步骤 - -```bash -# 运行资源管理测试 -./build/tests/RHI/OpenGL/Debug/opengl_tests.exe --gtest_filter=OpenGLTestFixture.Texture*:OpenGLTestFixture.Sampler* - -# 预期结果 -[==========] 9 tests from OpenGLTestFixture -[ PASSED ] 9 tests -``` - ---- - -### Phase 3: 渲染管线(约 1.5 天) - -#### 2.10 OpenGLShader 测试(4 个) - -| 测试用例 | 验证内容 | -|----------|----------| -| `Compile_VertexFragment` | 顶点和片段着色器编译 | -| `Compile_WithGeometry` | 几何着色器编译 | -| `Compile_InvalidSource` | 无效源码编译失败 | -| `SetUniforms` | Uniform 设置 (int/float/vec3/mat4) | - -#### 2.11 OpenGLPipelineState 测试(3 个) - -| 测试用例 | 验证内容 | -|----------|----------| -| `SetDepthStencilState` | 深度/模板状态设置 | -| `SetBlendState` | 混合状态设置 | -| `SetViewport_SetScissor` | 视口/裁剪矩形 | - -#### 2.12 OpenGLVertexArray 测试(4 个) - -| 测试用例 | 验证内容 | -|----------|----------| -| `Initialize_CreatesVAO` | VAO 创建 | -| `AddVertexBuffer` | 顶点缓冲添加 | -| `SetIndexBuffer` | 索引缓冲设置 | -| `Bind_Unbind` | VAO 绑定/解绑 | - -#### 2.13 验证步骤 - -```bash -# 运行渲染管线测试 -./build/tests/RHI/OpenGL/Debug/opengl_tests.exe --gtest_filter=OpenGLTestFixture.Shader*:OpenGLTestFixture.PipelineState*:OpenGLTestFixture.VertexArray* - -# 预期结果 -[==========] 11 tests from OpenGLTestFixture -[ PASSED ] 11 tests -``` - ---- - -### Phase 4: 命令与视图(约 1.5 天) - -#### 2.14 OpenGLCommandList 测试(8 个) - -| 测试用例 | 验证内容 | -|----------|----------| -| `Clear_ColorBuffer` | 清除颜色缓冲 | -| `Clear_DepthStencil` | 清除深度/模板 | -| `SetVertexBuffer` | 设置顶点缓冲 | -| `SetIndexBuffer` | 设置索引缓冲 | -| `Draw_Triangles` | 绘制三角形 | -| `DrawIndexed_Indices` | 索引绘制 | -| `DrawInstanced` | 实例化绘制 | -| `Dispatch_ComputeShader` | 计算着色器分发 | - -#### 2.15 OpenGLRenderTargetView 测试(4 个) - -| 测试用例 | 验证内容 | -|----------|----------| -| `Initialize_Texture2D` | 2D 纹理 RTV 创建 | -| `Initialize_Default` | 默认帧缓冲 RTV | -| `Bind_Unbind` | RTV 绑定/解绑 | -| `Clear_Color` | 清除颜色 | - -#### 2.16 OpenGLDepthStencilView 测试(4 个) - -| 测试用例 | 验证内容 | -|----------|----------| -| `Initialize_Texture2D` | 2D 纹理 DSV 创建 | -| `Initialize_DepthOnly` | 仅深度 DSV | -| `Bind_Unbind` | DSV 绑定/解绑 | -| `ClearDepthStencil` | 清除深度/模板 | - -#### 2.17 验证步骤 - -```bash -# 运行命令与视图测试 -./build/tests/RHI/OpenGL/Debug/opengl_tests.exe --gtest_filter=OpenGLTestFixture.CommandList*:OpenGLTestFixture.RenderTargetView*:OpenGLTestFixture.DepthStencilView* - -# 预期结果 -[==========] 16 tests from OpenGLTestFixture -[ PASSED ] 16 tests -``` - ---- - -### Phase 5: 窗口管理(约 0.5 天) - -#### 2.18 OpenGLSwapChain 测试(3 个) - -| 测试用例 | 验证内容 | -|----------|----------| -| `Initialize_Window` | 交换链初始化 | -| `Present_VSync` | 垂直同步显示 | -| `Resize_ChangesSize` | 调整大小 | - -#### 2.19 验证步骤 - -```bash -# 运行窗口管理测试 -./build/tests/RHI/OpenGL/Debug/opengl_tests.exe --gtest_filter=OpenGLTestFixture.SwapChain* - -# 预期结果 -[==========] 3 tests from OpenGLTestFixture -[ PASSED ] 3 tests -``` - ---- - -## 3. 完整验证 - -### 3.1 运行所有测试 - -```bash -# 完整测试 -cmake --build build --config Debug -ctest --test-dir build -C Debug --output-on-failure - -# 预期结果 -[==========] 56 tests from 1 test suite. -[ PASSED ] 56 tests -``` - -### 3.2 测试分类统计 - -| 分类 | 数量 | 占比 | -|------|:----:|:----:| -| 初始化测试 | 18 | 32% | -| 绑定/解绑测试 | 10 | 18% | -| 数据操作测试 | 8 | 14% | -| 状态设置测试 | 10 | 18% | -| 绘制/执行测试 | 10 | 18% | - ---- - -## 4. CI 配置 - -### 4.1 GitHub Actions - -```yaml -# .github/workflows/opengl-tests.yml -name: OpenGL Tests - -on: [push, pull_request] - -jobs: - test: - runs-on: windows-latest - steps: - - uses: actions/checkout@v4 - - - name: Configure - run: cmake -B build -S . -G "Visual Studio 17 2022" - - - name: Build - run: cmake --build build --config Debug - - - name: Run Tests - run: ctest --test-dir build -C Debug --output-on-failure -``` - -### 4.2 Linux CI(可选) - -Linux 下需要配置 Mesa 软件渲染: - -```yaml -- name: Run Tests - env: - MESA_GL_VERSION_OVERRIDE: 4.6 - MESA_GALLIUM_DRIVER: llvmpipe - run: ctest --test-dir build -C Debug --output-on-failure -``` - ---- - -## 5. 任务清单 - -### 阶段 1:基础设施 - -- [ ] 1.1 创建 `tests/RHI/OpenGL/CMakeLists.txt` -- [ ] 1.2 创建 `tests/RHI/OpenGL/fixtures/OpenGLTestFixture.h` -- [ ] 1.3 创建 `tests/RHI/OpenGL/fixtures/OpenGLTestFixture.cpp` -- [ ] 1.4 实现 `test_device.cpp`(6 个测试) -- [ ] 1.5 实现 `test_buffer.cpp`(6 个测试) -- [ ] 1.6 实现 `test_fence.cpp`(5 个测试) -- [ ] 1.7 构建验证(17 个测试) - -### 阶段 2:资源管理 - -- [ ] 2.1 实现 `test_texture.cpp`(5 个测试) -- [ ] 2.2 实现 `test_sampler.cpp`(4 个测试) -- [ ] 2.3 构建验证(9 个测试) - -### 阶段 3:渲染管线 - -- [ ] 3.1 实现 `test_shader.cpp`(4 个测试) -- [ ] 3.2 实现 `test_pipeline_state.cpp`(3 个测试) -- [ ] 3.3 实现 `test_vertex_array.cpp`(4 个测试) -- [ ] 3.4 构建验证(11 个测试) - -### 阶段 4:命令与视图 - -- [ ] 4.1 实现 `test_command_list.cpp`(8 个测试) -- [ ] 4.2 实现 `test_render_target_view.cpp`(4 个测试) -- [ ] 4.3 实现 `test_depth_stencil_view.cpp`(4 个测试) -- [ ] 4.4 构建验证(16 个测试) - -### 阶段 5:窗口管理 - -- [ ] 5.1 实现 `test_swap_chain.cpp`(3 个测试) -- [ ] 5.2 完整测试验证(56 个测试) -- [ ] 5.3 提交并推送 - ---- - -## 6. 关键注意事项 - -### 6.1 窗口依赖 - -- OpenGL 必须有 GLFW 窗口上下文 -- 使用 `GLFW_VISIBLE = GLFW_FALSE` 创建隐藏窗口 -- 每个测试前 `glfwMakeContextCurrent()` - -### 6.2 GL 状态污染 - -- OpenGL 状态是全局的,必须隔离 -- `TearDown()` 中重置所有 GL 状态 -- 测试结束后 `glBindBuffer(..., 0)` 等 - -### 6.3 错误检查 - -- 使用 `GL_CHECK()` 宏验证每个 GL 调用 -- 测试开始时 `GL_CLEAR_ERRORS()` -- 捕获 GL 错误并转换为测试失败 - -### 6.4 与 D3D12 对齐 - -- 保持相同的测试数量级(54+) -- 使用相同的 TEST_F 宏 -- 遵循相同的命名约定 - ---- - -## 7. 后续工作 - -- [ ] 资源泄漏检测工具 -- [ ] 性能基准测试 -- [ ] 截图对比测试 -- [ ] Linux CI 配置 -- [ ] macOS 支持 - ---- - -**文档版本**:1.0 -**创建日期**:2026年3月17日 -**基于文档**:`docs/OpenGL后端测试设计.md`、`tests/RHI/D3D12/` diff --git a/docs/used/RHI_Design_Issues.md b/docs/used/RHI_Design_Issues.md deleted file mode 100644 index d205b879..00000000 --- a/docs/used/RHI_Design_Issues.md +++ /dev/null @@ -1,609 +0,0 @@ -# RHI 模块设计问题分析报告(第二版) - -## 1. 项目背景 - -本项目 RHI 模块参考 Unity 渲染架构设计,面向 **Direct3D 12** 和 **Vulkan** 等现代图形 API,目标是为引擎上层(SRP/RenderGraph)提供统一的渲染硬件抽象层,屏蔽 API 差异,实现跨后端移植。 - -当前已实现两个后端:D3D12 和 OpenGL。基础框架已有雏形(Reset/Close、TransitionBarrier、PipelineState),但存在**被 OpenGL 风格带偏、不符合现代 D3D12/Vulkan 显式设计**的关键问题。 - ---- - -## 2. 做得好的地方 - -1. **有显式生命周期管理**:`Reset()` / `Close()` 是 D3D12/Vulkan 风格,理解"录制-提交"的分离 -2. **考虑了资源状态转换**:`TransitionBarrier()` 是现代 API 必须的,预留了接口 -3. **覆盖了图形+计算**:`Draw` / `DrawIndexed` / `Dispatch` 都有,功能完整 -4. **预留了原生句柄**:`GetNativeHandle()` 方便调试和特殊场景扩展 - ---- - -## 3. 核心问题(按严重程度排序) - -### 3.1 最大的坑:字符串查找 SetUniform 是 OpenGL 风格 - -**问题描述**:当前接口使用字符串名称设置 Uniform/Texture: -```cpp -virtual void SetUniformInt(const char* name, int value) = 0; -virtual void SetGlobalTexture(const char* name, RHIResourceView* texture) = 0; -``` - -这是**典型的 OpenGL 立即模式风格**,问题极大: - -| 问题 | 说明 | -|------|------| -| **性能差** | 运行时通过字符串查找 uniform 位置,驱动开销大 | -| **无法多线程** | 字符串查找不是线程安全的,不利于多线程录制 CommandList | -| **接 Vulkan 不可能** | D3D12/Vulkan 用 **DescriptorSet + PipelineLayout**,根本没有"按名字设 uniform"的概念 | - -**根本原因**:这是 OpenGL 风格的设计,没有对齐 D3D12/Vulkan 的显式 Descriptor 绑定模型。 - -**D3D12/Vulkan 正确做法**: -```cpp -// D3D12: 通过 Root Signature + Descriptor Table 绑定 -commandList->SetGraphicsRootDescriptorTable(rootIndex, gpuDescriptorHandle); - -// Vulkan: 通过 DescriptorSet 绑定 -vkCmdBindDescriptorSets(commandBuffer, ..., descriptorSet, ...); -``` - -**修改建议**: -```cpp -// 新增:DescriptorSet 绑定接口(替代 SetUniform/SetGlobal) -virtual void SetGraphicsDescriptorSets( - uint32_t firstSet, - uint32_t count, - RHIDescriptorSet** descriptorSets, - RHIPipelineLayout* pipelineLayout) = 0; - -virtual void SetComputeDescriptorSets( - uint32_t firstSet, - uint32_t count, - RHIDescriptorSet** descriptorSets, - RHIPipelineLayout* pipelineLayout) = 0; -``` - ---- - -### 3.1.1 ✅ 已完成:DescriptorSet 抽象 - -**实现状态**:2026-03-25 已完成 - -**新增抽象**: -| 类名 | 说明 | -|------|------| -| `RHIDescriptorSet` | DescriptorSet 基类,含 Update/UpdateSampler/GetNativeHandle/GetBindingCount/GetBindings | -| `RHIDescriptorPool` | DescriptorPool 基类,含 AllocateSet/FreeSet | -| `D3D12DescriptorSet` | D3D12 实现,使用 DescriptorHeap 分配 | -| `D3D12DescriptorHeap` | 扩展支持 AllocateSet/FreeSet | -| `OpenGLDescriptorSet` | OpenGL 实现,使用 TextureUnitAllocator | -| `OpenGLDescriptorPool` | OpenGL 实现 | - -**RHICommandList 新增接口**: -```cpp -virtual void SetGraphicsDescriptorSets( - uint32_t firstSet, - uint32_t count, - RHIDescriptorSet** descriptorSets, - RHIPipelineLayout* pipelineLayout) = 0; - -virtual void SetComputeDescriptorSets( - uint32_t firstSet, - uint32_t count, - RHIDescriptorSet** descriptorSets, - RHIPipelineLayout* pipelineLayout) = 0; -``` - -**RHIDevice 新增工厂方法**: -```cpp -virtual RHIDescriptorPool* CreateDescriptorPool(const DescriptorPoolDesc& desc) = 0; -virtual RHIDescriptorSet* CreateDescriptorSet(RHIDescriptorPool* pool, const DescriptorSetDesc& desc) = 0; -``` - -**实现说明**: -- D3D12:使用 `D3D12DescriptorHeap` 的 GPU/CPU descriptor 分配 -- OpenGL:使用 `TextureUnitAllocator` 分配 texture unit -- 旧的 `SetUniform*/SetGlobal*` 仍保留,向后兼容 - ---- - -### 3.2 缺少显式 RenderPass - -**问题描述**:当前只有 `SetRenderTargets()`,没有 `BeginRenderPass()` / `EndRenderPass()`: - -```cpp -// 当前设计 -virtual void SetRenderTargets(uint32_t count, RHIResourceView** renderTargets, ...) = 0; -virtual void ClearRenderTarget(RHIResourceView* renderTarget, const float color[4]) = 0; -``` - -这是**不符合现代 API 设计**的: - -| 问题 | 说明 | -|------|------| -| **不符合显式设计** | D3D12/Vulkan 要求显式定义渲染通道的开始和结束 | -| **清屏逻辑不规范** | 现代 API 推荐用 RenderPass 的 `LoadOp`(Clear/Load/DontCare)清屏 | -| **Tile GPU 性能差** | 对移动端 Tile GPU 至关重要,隐式行为无法优化 | - -**根本原因**:OpenGL 的 `glClear()` 是立即模式,没有 RenderPass 概念。 - -**D3D12/Vulkan 正确做法**: -```cpp -// D3D12: Begin/End Render Pass -commandList->BeginRenderPass(...); -commandList->EndRenderPass(); - -// Vulkan: vkCmdBeginRenderPass / vkCmdEndRenderPass -vkCmdBeginRenderPass(commandBuffer, &renderPassBeginInfo, ...); -vkCmdEndRenderPass(commandBuffer); -``` - -**修改建议**: -```cpp -// 新增:显式渲染通道 -virtual void BeginRenderPass( - RHIRenderPass* renderPass, - RHIFramebuffer* framebuffer, - const Rect& renderArea, - uint32_t clearValueCount, - const ClearValue* clearValues) = 0; - -virtual void EndRenderPass() = 0; - -// 移除 SetRenderTargets,改由 Framebuffer 定义 -// 移除或废弃 ClearRenderTarget,改为 BeginRenderPass 的 LoadOp -``` - -### 3.2.1 ✅ 已完成:显式 RenderPass 实现 - -**实现状态**:2026-03-24 已完成 - -**新增抽象**: -| 类名 | 说明 | -|------|------| -| `RHIFramebuffer` | Framebuffer 基类,含 Initialize/Bind/GetHandle | -| `RHIRenderPass` | RenderPass 基类,含 AttachmentDesc | -| `D3D12Framebuffer` | D3D12 Framebuffer 实现 | -| `D3D12RenderPass` | D3D12 RenderPass 实现(使用 OMSetRenderTargets) | -| `OpenGLFramebuffer` | OpenGL 实现(继承自 RHIFramebuffer) | -| `OpenGLRenderPass` | OpenGL RenderPass 实现 | - -**RHICommandList 新增接口**: -```cpp -virtual void BeginRenderPass(RHIRenderPass* renderPass, RHIFramebuffer* framebuffer, - const Rect& renderArea, uint32_t clearValueCount, - const ClearValue* clearValues) = 0; -virtual void EndRenderPass() = 0; -``` - -**AttachmentDesc 结构**(定义在 RHIRenderPass.h): -```cpp -struct AttachmentDesc { - Format format = Format::Unknown; - LoadAction loadOp = LoadAction::Undefined; // Undefined/Load/Clear - StoreAction storeOp = StoreAction::Store; // Store/Resolve/Discard - LoadAction stencilLoadOp = LoadAction::Undefined; - StoreAction stencilStoreOp = StoreAction::Undefined; - ClearValue clearValue; -}; -``` - -**实现说明**: -- D3D12:使用 `OMSetRenderTargets` + `ClearRenderTargetView`/`ClearDepthStencilView` -- OpenGL:使用 `glBindFramebuffer` + `glClearBufferfv` 处理 LoadOp -- 旧的 `SetRenderTargets`/`ClearRenderTarget` 仍保留,向后兼容 - ---- - -### 3.3 动态状态太多,应该收敛到 PipelineState - -**问题描述**:当前把 DepthStencilState、BlendState、PrimitiveTopology 都放在 CommandList 里动态设置: - -```cpp -virtual void SetDepthStencilState(const DepthStencilState& state) = 0; -virtual void SetBlendState(const BlendState& state) = 0; -virtual void SetPrimitiveTopology(PrimitiveTopology topology) = 0; -``` - -这是**OpenGL 风格的残留**: - -| 问题 | 说明 | -|------|------| -| **驱动开销大** | 现代 API 中这些状态大部分是 PipelineState 的一部分,是不可变的 | -| **不符合显式设计** | 动态状态应该只保留极少数(Viewport/Scissor/StencilRef/BlendFactor) | -| **状态不一致风险** | CommandList 动态设置的状态可能与 PSO 中定义的状态冲突 | - -**根本原因**:OpenGL 是状态机,可以在任何时候设置任何状态。D3D12/Vulkan 把大部分状态打包到 PSO 中。 - -**D3D12/Vulkan 正确做法**: -- `DepthStencilState`、`BlendState`、`PrimitiveTopology` 在 **PSO 创建时就确定** -- CommandList 只设置**真正的动态状态**:Viewport、Scissor、StencilRef、BlendFactor - -**修改建议**: -```cpp -// PipelineState 应该包含: -// - Shader -// - VertexLayout -// - RenderPass 兼容 -// - DepthStencilState(不可变) -// - BlendState(不可变) -// - PrimitiveTopology(不可变) -// - 动态状态掩码 - -// CommandList 只保留必要的动态状态: -virtual void SetViewports(uint32_t count, const Viewport* viewports) = 0; -virtual void SetScissorRects(uint32_t count, const Rect* rects) = 0; -virtual void SetStencilRef(uint8_t ref) = 0; -virtual void SetBlendFactor(const float factor[4]) = 0; - -// 移除: -virtual void SetDepthStencilState(const DepthStencilState& state) = 0; // 移到 PSO -virtual void SetBlendState(const BlendState& state) = 0; // 移到 PSO -virtual void SetPrimitiveTopology(PrimitiveTopology topology) = 0; // 移到 PSO -``` - -### 3.3.1 ✅ 已完成:动态状态移至 PSO - -**实现状态**:2026-03-25 已完成 - -**移除的无效方法**: -| 方法 | 问题 | -|------|------| -| `SetDepthStencilState` | D3D12 中是空实现(TODO),调用无效;OpenGL 中直接调用 GL 函数但状态应通过 PSO 配置 | -| `SetBlendState` | D3D12 中只设置 BlendFactor,其他参数被忽略;OpenGL 中直接调用 GL 函数但状态应通过 PSO 配置 | - -**保留的真正动态状态**: -| 方法 | 说明 | -|------|------| -| `SetViewport/SetViewports` | 真正的动态状态 | -| `SetScissorRect/SetScissorRects` | 真正的动态状态 | -| `SetStencilRef` | D3D12 动态状态 | -| `SetBlendFactor` | D3D12 动态状态 | - -**修改的文件**: -- `RHICommandList.h` - 移除 SetDepthStencilState/SetBlendState 纯虚方法 -- `D3D12CommandList.h/cpp` - 移除 SetDepthStencilState/SetBlendState 实现 -- `OpenGLCommandList.h/cpp` - 移除 SetDepthStencilState/SetBlendState 实现 -- `tests/RHI/unit/test_command_list.cpp` - 移除相关测试 -- `tests/RHI/OpenGL/unit/test_command_list.cpp` - 移除相关测试 - -**说明**: -- D3D12 中 PSO 是不可变的,SetDepthStencilState/SetBlendState 调用原本就是无效代码 -- OpenGL 中状态通过 OpenGLPipelineState::Apply() 在绑定 PSO 时应用 -- PrimitiveTopology 保留在 CommandList,因为 D3D12 允许动态改变 topology type - ---- - -### 3.4 ResourceView 类型不明确 ✅ 基本完成 - -**问题描述**:当前用 `RHIResourceView*` 代表所有视图: - -```cpp -virtual void SetVertexBuffer(uint32_t slot, RHIResourceView* buffer, ...) = 0; -virtual void SetRenderTargets(uint32_t count, RHIResourceView** renderTargets, ...) = 0; -``` - -这会导致: - -| 问题 | 说明 | -|------|------| -| **类型不安全** | 容易把 SRV 当成 RTV 传,运行时才发现错误 | -| **后端处理麻烦** | D3D12/Vulkan 对不同视图有严格区分(RTV/DSV/SRV/UAV/VBV/IBV) | -| **语义模糊** | `RHIResourceView` 到底是哪种视图?调用者必须查文档 | - -**根本原因**:没有在类型层面区分不同视图的语义。 - -**修改建议**:定义明确的视图类型层次结构 -```cpp -// 基类 -class RHIResourceView {}; - -// 具体视图类型 -class RHIRenderTargetView : public RHIResourceView {}; -class RHIDepthStencilView : public RHIResourceView {}; -class RHIShaderResourceView : public RHIResourceView {}; -class RHIUnorderedAccessView : public RHIResourceView {}; -class RHIVertexBufferView : public RHIResourceView {}; -class RHIIndexBufferView : public RHIResourceView {}; -class RHIConstantBufferView : public RHIResourceView {}; -``` - -**实现状态**:✅ 基本完成 - `RHIResourceView.h` 中定义了具体视图类型,但 `RHICommandList` 仍使用 `RHIResourceView*` 以保持兼容性 - ---- - -### 3.5 TransitionBarrier 针对 View 而非 Resource ✅ 已完成 - -**问题描述**:当前接口: -```cpp -virtual void TransitionBarrier(RHIResourceView* resource, ResourceStates stateBefore, ResourceStates stateAfter) = 0; -``` - -资源状态转换是针对 **Resource(Buffer/Texture 本身)** 的,不是针对 View 的。一张 Texture 可能创建多个 View(SRV、RTV、DSV),但状态转换是对整个 Texture 做的。 - -**根本原因**:没有区分 Resource 和 ResourceView 的概念。 - -**修改建议**: -```cpp -// 新增 Resource 基类 -class RHIResource {}; -class RHIBuffer : public RHIResource {}; -class RHITexture : public RHIResource {}; - -// 修改 TransitionBarrier -struct ResourceBarrier { - RHIResource* resource; - ResourceStates stateBefore; - ResourceStates stateAfter; - uint32_t subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; -}; - -virtual void ResourceBarrier(uint32_t count, const ResourceBarrier* barriers) = 0; -``` - -**实现状态**:✅ 已完成 - `RHIResource.h` 已创建,`RHIBuffer` 和 `RHITexture` 已继承自 `RHIResource` - -**问题描述**:当前接口: -```cpp -virtual void TransitionBarrier(RHIResourceView* resource, ResourceStates stateBefore, ResourceStates stateAfter) = 0; -``` - -资源状态转换是针对 **Resource(Buffer/Texture 本身)** 的,不是针对 View 的。一张 Texture 可能创建多个 View(SRV、RTV、DSV),但状态转换是对整个 Texture 做的。 - -**根本原因**:没有区分 Resource 和 ResourceView 的概念。 - -**修改建议**: -```cpp -// 新增 Resource 基类 -class RHIResource {}; -class RHIBuffer : public RHIResource {}; -class RHITexture : public RHIResource {}; - -// 修改 TransitionBarrier -struct ResourceBarrier { - RHIResource* resource; - ResourceStates stateBefore; - ResourceStates stateAfter; - uint32_t subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; -}; - -virtual void ResourceBarrier(uint32_t count, const ResourceBarrier* barriers) = 0; -``` - ---- - -## 4. 其他需要修改的问题 - -### 4.1 SetGlobal* 是空操作 ✅ 已完成 - -**问题**:SetGlobal* 方法(SetGlobalInt/SetGlobalFloat/SetGlobalVec3/SetGlobalVec4/SetGlobalMat4/SetGlobalTexture)在 D3D12 和 OpenGL 中只是缓存值,从不提交到 GPU。 - -**实现状态**:2026-03-25 已完成 - 移除所有 SetGlobal* 方法 - -**移除的方法**(共6个): -| 方法 | 问题 | -|------|------| -| `SetGlobalInt` | 只缓存到 unordered_map,从未提交到 GPU | -| `SetGlobalFloat` | 只缓存到 unordered_map,从未提交到 GPU | -| `SetGlobalVec3` | 只缓存到 unordered_map,从未提交到 GPU | -| `SetGlobalVec4` | 只缓存到 unordered_map,从未提交到 GPU | -| `SetGlobalMat4` | 只缓存到 unordered_map,从未提交到 GPU | -| `SetGlobalTexture` | 只缓存到 unordered_map,从未提交到 GPU | - -**移除的缓存成员变量**: -- D3D12CommandList: `m_globalIntCache`, `m_globalFloatCache`, `m_globalVec3Cache`, `m_globalVec4Cache`, `m_globalMat4Cache`, `m_globalTextureCache` -- OpenGLCommandList: 同上 - -**说明**: -- `SetGlobal*` 从未被代码库中任何地方调用(死代码) -- `SetUniform*` 方法已正常工作,使用 shader reflection + 实际 GPU 绑定 -- 移除后无功能损失 - -### 4.2 PrimitiveType 和 PrimitiveTopology 冲突 - -详见第一版文档,此处不再赘述。 - -### 4.3 OpenGL 特有方法暴露 ✅ 已完成 - -**实现状态**:2026-03-25 已完成 - -**问题**:OpenGLDevice 和 OpenGLCommandList 暴露了 OpenGL 特有方法,违反"上层只调用抽象接口"原则。 - -**修复内容**: - -1. **OpenGLDevice 内部化方法**: - - 移除 `GetTextureUnitAllocator()` 和 `GetUniformBufferManager()` 公开访问(移到 private) - - 移除 `SwapBuffers()` 公开方法(OpenGLSwapChain 作为 friend 仍可访问) - - `GetPresentationDC()` 和 `GetGLContext()` 保留在 private,通过 friend 访问 - -2. **新增抽象接口**: - - `MakeContextCurrent()` - 封装 `wglMakeCurrent` 操作 - - `GetNativeContext()` - 返回 GL Context 供 RenderDoc 使用 - -3. **保留的逃生舱方法**: - - OpenGLCommandList 中的 `PrimitiveType` 相关方法是显式的 OpenGL 逃生舱 - - 这些方法使用 OpenGL 特有类型,文档化为"backend-specific escape hatch" - - 抽象层正确使用 `PrimitiveTopology` 枚举 - -**修改的文件**: -- `engine/include/XCEngine/RHI/OpenGL/OpenGLDevice.h` - 内部化特有方法,新增 MakeContextCurrent/GetNativeContext -- `engine/src/RHI/OpenGL/OpenGLDevice.cpp` - 实现 MakeContextCurrent -- `tests/RHI/OpenGL/unit/test_device.cpp` - 移除 SwapBuffers 测试 -- `tests/RHI/OpenGL/integration/minimal/main.cpp` - 使用 MakeContextCurrent -- `tests/RHI/OpenGL/integration/triangle/main.cpp` - 使用 MakeContextCurrent -- `tests/RHI/OpenGL/integration/quad/main.cpp` - 使用 MakeContextCurrent -- `tests/RHI/OpenGL/integration/sphere/main.cpp` - 使用 MakeContextCurrent - -**说明**: -- `PrimitiveType` 枚举在 OpenGL 专用逃生舱方法中使用是可接受的,因为这些是显式的后端特定接口 -- 抽象层正确使用 `PrimitiveTopology`,不存在类型泄漏问题 - -### 4.4 缺少 Compute Pipeline 抽象 - -详见第一版文档,此处不再赘述。 - -### 4.5 RHIPipelineLayout 空壳 - -详见第一版文档,此处不再赘述。 - ---- - -## 5. 修改后的 CommandList 核心接口 - -```cpp -class RHICommandList { -public: - virtual ~RHICommandList() = default; - - // ========== 生命周期 ========== - virtual void Reset(RHICommandPool* pool = nullptr) = 0; - virtual void Close() = 0; - - // ========== 资源状态转换 ========== - virtual void ResourceBarrier(uint32_t count, const ResourceBarrier* barriers) = 0; - - // ========== 渲染通道(新增)========== - virtual void BeginRenderPass( - RHIRenderPass* renderPass, - RHIFramebuffer* framebuffer, - const Rect& renderArea, - uint32_t clearValueCount, - const ClearValue* clearValues) = 0; - virtual void EndRenderPass() = 0; - - // ========== 管线与描述符 ========== - virtual void SetPipelineState(RHIPipelineState* pso) = 0; - virtual void SetGraphicsDescriptorSets( - uint32_t firstSet, - uint32_t count, - RHIDescriptorSet** descriptorSets, - RHIPipelineLayout* pipelineLayout) = 0; - - // ========== 动态状态(精简后)========== - virtual void SetViewports(uint32_t count, const Viewport* viewports) = 0; - virtual void SetScissorRects(uint32_t count, const Rect* rects) = 0; - virtual void SetStencilRef(uint8_t ref) = 0; - virtual void SetBlendFactor(const float factor[4]) = 0; - - // ========== 顶点/索引 ========== - virtual void SetVertexBuffers( - uint32_t startSlot, - uint32_t count, - RHIVertexBufferView** buffers, - const uint64_t* offsets, - const uint32_t* strides) = 0; - virtual void SetIndexBuffer( - RHIIndexBufferView* buffer, - uint64_t offset, - IndexFormat format) = 0; - - // ========== 绘制 ========== - virtual void Draw(uint32_t vertexCount, uint32_t instanceCount = 1, - uint32_t startVertex = 0, uint32_t startInstance = 0) = 0; - virtual void DrawIndexed(uint32_t indexCount, uint32_t instanceCount = 1, - uint32_t startIndex = 0, int32_t baseVertex = 0, - uint32_t startInstance = 0) = 0; - - // ========== 计算 ========== - virtual void SetComputePipelineState(RHIPipelineState* pso) = 0; - virtual void SetComputeDescriptorSets( - uint32_t firstSet, - uint32_t count, - RHIDescriptorSet** descriptorSets, - RHIPipelineLayout* pipelineLayout) = 0; - virtual void Dispatch(uint32_t x, uint32_t y, uint32_t z) = 0; - - // ========== 复制 ========== - virtual void CopyResource(RHIResource* dst, RHIResource* src) = 0; - - // ========== 原生句柄 ========== - virtual void* GetNativeHandle() = 0; -}; -``` - ---- - -## 6. 需要同时新增的抽象 - -### 6.1 RHIDescriptorSet -```cpp -class RHIDescriptorSet { -public: - virtual ~RHIDescriptorSet() = default; - virtual void Update(uint32_t offset, RHIResourceView* view) = 0; - virtual void UpdateSampler(uint32_t offset, RHISampler* sampler) = 0; - virtual void* GetNativeHandle() = 0; -}; -``` - -### 6.2 RHIRenderPass -```cpp -struct AttachmentDesc { - Format format; - LoadAction loadOp = LoadAction::DontCare; - StoreAction storeOp = StoreAction::Store; - LoadAction stencilLoadOp = LoadAction::DontCare; - StoreAction stencilStoreOp = StoreAction::DontCare; -}; - -class RHIRenderPass { -public: - virtual ~RHIRenderPass() = default; - virtual bool Initialize(uint32_t colorAttachmentCount, const AttachmentDesc* colorAttachments, - const AttachmentDesc* depthStencilAttachment) = 0; - virtual void Shutdown() = 0; - virtual void* GetNativeHandle() = 0; -}; -``` - -### 6.3 RHIFramebuffer -```cpp -class RHIFramebuffer { -public: - virtual ~RHIFramebuffer() = default; - virtual bool Initialize(RHIRenderPass* renderPass, uint32_t attachmentCount, - RHIResourceView** attachments, uint32_t width, uint32_t height) = 0; - virtual void Shutdown() = 0; - virtual void* GetNativeHandle() = 0; -}; -``` - -### 6.4 RHIResource 基类 -```cpp -class RHIResource { -public: - virtual ~RHIResource() = default; - virtual void* GetNativeHandle() = 0; - virtual ResourceStates GetState() const = 0; - virtual void SetState(ResourceStates state) = 0; -}; - -class RHIBuffer : public RHIResource { ... }; -class RHITexture : public RHIResource { ... }; -``` - ---- - -## 7. 问题优先级总结 - -| 优先级 | 问题 | 严重性 | 修复难度 | 状态 | -|--------|------|--------|----------|------| -| 1 | 字符串查找 SetUniform 不符合 D3D12/Vulkan | 🔴 致命 | 高 | ✅ 已完成 | -| 2 | 缺少显式 RenderPass | 🔴 致命 | 高 | ✅ 已完成 | -| 3 | 动态状态太多 | 🔴 高 | 高 | ✅ 已完成 | -| 4 | ResourceView 类型不明确 | 🟡 中 | 中 | ✅ 基本完成 | -| 5 | TransitionBarrier 针对 View 而非 Resource | 🟡 中 | 中 | ✅ 已完成 | -| 6 | SetGlobal* 空操作 | 🟡 中 | 低 | ✅ 已完成 | -| 7 | OpenGL 特有方法暴露 | 🟡 中 | 高 | ✅ 已完成 | -| 8 | 缺少 Compute Pipeline 抽象 | 🟡 中 | 中 | ✅ 已完成 | - ---- - -## 8. 总结 - -当前 RHI 模块的基础框架有 D3D12/Vulkan 的影子,但**被 OpenGL 风格带偏**了。最核心的问题是: - -1. **用字符串查找设 uniform** —— 这是 OpenGL 风格,必须改成 DescriptorSet 绑定 -2. **缺少显式 RenderPass** —— 现代 API 的基础,必须添加 -3. **动态状态太多** —— 应该收敛到 PSO,只保留必要的动态状态 - -修复这些问题后,RHI 抽象层将完全对齐 D3D12/Vulkan 的显式模型,未来接入 Vulkan 将会非常顺利。 diff --git a/docs/used/RHI_OpenGL_State_Not_Applied.md b/docs/used/RHI_OpenGL_State_Not_Applied.md deleted file mode 100644 index d97390b3..00000000 --- a/docs/used/RHI_OpenGL_State_Not_Applied.md +++ /dev/null @@ -1,144 +0,0 @@ -# RHI 模块严重问题:OpenGL 后端 RHI 状态不生效 - -## 问题严重程度 - -**严重级别**: 🔴 Critical Bug - -## 问题定位 - -`OpenGLPipelineState` 维护了两套状态系统,但它们之间没有关联。 - -### 两套状态系统 - -**RHI 抽象层状态** (`OpenGLPipelineState.h:131-133`): - -```cpp -RasterizerDesc m_rasterizerDesc; -BlendDesc m_blendDesc; -DepthStencilStateDesc m_depthStencilDesc; -``` - -**OpenGL 特定状态** (`OpenGLPipelineState.h:141-143`): - -```cpp -OpenGLDepthStencilState m_glDepthStencilState; -OpenGLBlendState m_glBlendState; -OpenGLRasterizerState m_glRasterizerState; -``` - -## 问题详述 - -### RHI 接口设置的状态只存储不生效 - -```cpp -// OpenGLPipelineState.cpp:19-21 -void OpenGLPipelineState::SetRasterizerState(const RasterizerDesc& state) { - m_rasterizerDesc = state; // ❌ 只存储到 m_rasterizerDesc -} - -void OpenGLPipelineState::SetBlendState(const BlendDesc& state) { - m_blendDesc = state; // ❌ 只存储到 m_blendDesc -} - -void OpenGLPipelineState::SetDepthStencilState(const DepthStencilStateDesc& state) { - m_depthStencilDesc = state; // ❌ 只存储到 m_depthStencilDesc -} -``` - -### Apply() 使用的是另一套状态 - -```cpp -// OpenGLPipelineState.cpp:133-149 -void OpenGLPipelineState::ApplyRasterizer() { - if (m_glRasterizerState.cullFaceEnable) { // ✅ 使用的是 m_glRasterizerState - glEnable(GL_CULL_FACE); - glCullFace(static_cast(m_glRasterizerState.cullFace)); - // ... - } -} - -void OpenGLPipelineState::ApplyBlend() { - if (m_glBlendState.blendEnable) { // ✅ 使用的是 m_glBlendState - glEnable(GL_BLEND); - // ... - } -} - -void OpenGLPipelineState::ApplyDepthStencil() { - if (m_glDepthStencilState.depthTestEnable) { // ✅ 使用的是 m_glDepthStencilState - glEnable(GL_DEPTH_TEST); - // ... - } -} -``` - -## 导致的后果 - -**通过 RHI 抽象层设置的状态完全不会生效!** - -```cpp -// 上层代码 -RasterizerDesc raster; -raster.cullMode = 2; // 设置剔除模式 -pso->SetRasterizerState(raster); // 通过 RHI 接口设置 - -// 实际渲染时 -pso->Bind(); // 调用 ApplyRasterizer() -// ❌ m_glRasterizerState.cullMode 仍然是默认值,不是设置的值! -``` - -## 对比 D3D12 后端 - -D3D12 后端正确地在 `CreateD3D12PSO()` 中使用了 RHI 状态: - -```cpp -// D3D12PipelineState.cpp:157-161 -desc.RasterizerState.FillMode = static_cast(m_rasterizerDesc.fillMode); -desc.RasterizerState.CullMode = static_cast(m_rasterizerDesc.cullMode); -// ✅ 直接使用 m_rasterizerDesc -``` - -## 根本原因 - -OpenGL 后端有两个 `SetRasterizerState` 重载: - -```cpp -// RHI 接口 - 只存储 -void SetRasterizerState(const RasterizerDesc& state); - -// OpenGL 特定接口 - 会生效 -void SetRasterizerState(const OpenGLRasterizerState& state); -``` - -RHI 接口的实现缺少状态转换逻辑。 - -## 修复方案 - -在 `SetRasterizerState(const RasterizerDesc& state)` 中添加转换逻辑: - -```cpp -void OpenGLPipelineState::SetRasterizerState(const RasterizerDesc& state) { - m_rasterizerDesc = state; - - // 转换到 OpenGL 状态 - m_glRasterizerState.cullFaceEnable = (state.cullMode != 0); // CullMode::None - m_glRasterizerState.cullFace = static_cast(state.cullMode); - m_glRasterizerState.frontFace = static_cast(state.frontFace); - m_glRasterizerState.polygonMode = static_cast(state.fillMode); - m_glRasterizerState.depthClipEnable = state.depthClipEnable; - // ... 其他字段 -} -``` - -同样需要修复 `SetBlendState` 和 `SetDepthStencilState`。 - -## 相关文件 - -- `engine/include/XCEngine/RHI/OpenGL/OpenGLPipelineState.h` -- `engine/src/RHI/OpenGL/OpenGLPipelineState.cpp` - -## 影响范围 - -1. **所有通过 RHI 抽象层设置的状态都不会生效** -2. **OpenGL 集成测试能工作是因为使用了 OpenGL 特定 API** -3. **上层代码如果使用 RHI 接口,行为会与预期不符** \ No newline at end of file diff --git a/docs/used/RHI抽象层设计与实现.md b/docs/used/RHI抽象层设计与实现.md deleted file mode 100644 index 1e7dbab0..00000000 --- a/docs/used/RHI抽象层设计与实现.md +++ /dev/null @@ -1,1069 +0,0 @@ -# 跨平台 RHI 渲染架构设计文档 -## 1. 项目背景 -本项目旨在参考 Unity 渲染架构,为已有的 **OpenGL** 和 **Direct3D 12** 图形 API 后端设计统一的**渲染硬件抽象层(RHI)**,屏蔽 API 差异,实现引擎上层逻辑与底层图形 API 的解耦。 - -### 现有项目结构 -``` -engine/ -├── include/XCEngine/RHI/ -│ ├── Enums.h # 通用枚举定义 -│ ├── Types.h # 通用结构体定义 -│ ├── D3D12/ # D3D12 后端实现 -│ └── OpenGL/ # OpenGL 后端实现 -└── src/RHI/ - ├── D3D12/ # D3D12 源码 - └── OpenGL/ # OpenGL 源码 -``` - - -## 2. 核心设计理念 -### 2.1 总体原则 -**求同存异,分层抽象,特性降级,底层逃逸** -- **求同存异**:优先提取 API 共性作为核心抽象,差异部分通过模拟/降级处理 -- **分层抽象**:通过清晰的层级结构隔离 API 差异 -- **特性降级**:对高级特性提供能力检测和替代方案 -- **底层逃逸**:允许直接访问原生 API 以满足极端需求 - -### 2.2 差异分类与处理策略 -| 差异类型 | 典型示例 | 处理方案 | -|----------|----------|----------| -| 概念命名不同但本质相似 | D3D12 `CommandList` ≈ OpenGL 状态机绘制 | 直接统一抽象 | -| 显式控制 vs 隐式管理 | D3D12 `DescriptorHeap` vs OpenGL 纹理单元 | 提供"自动模式"+"显式模式" | -| API 独有高级特性 | D3D12 光线追踪、网格着色器 | 特性检测 + 降级方案 + 底层逃逸 | - - -## 3. RHI 分层架构 -### 3.1 通用分层模型 -``` -┌─────────────────────────────────┐ -│ 引擎功能层(场景/材质/光照) │ -├─────────────────────────────────┤ -│ 渲染管线层(SRP/Render Graph) │ -├─────────────────────────────────┤ -│ RHI 抽象层(统一接口) │ ← 核心设计重点 -├─────────────────────────────────┤ -│ API 后端层(D3D12/OpenGL) │ -├─────────────────────────────────┤ -│ 驱动/硬件层 │ -└─────────────────────────────────┘ -``` - -### 3.2 层级职责说明 -1. **引擎功能层**:提供场景管理、材质系统、光照等高级功能接口 -2. **渲染管线层**:定义渲染流程(前向/延迟渲染) -3. **RHI 抽象层**:统一图形 API 接口,屏蔽差异 -4. **API 后端层**:针对具体 API 的实现 -5. **驱动/硬件层**:最终执行渲染指令 - - -## 4. RHI 抽象基类设计 -### 4.1 目录结构调整建议 -``` -include/XCEngine/RHI/ -├── RHIEnums.h # 通用枚举 -├── RHITypes.h # 通用结构体 -├── RHICapabilities.h # 硬件能力检测 -├── RHIDevice.h # 设备抽象(核心入口) -├── RHICommandQueue.h # 命令队列抽象 -├── RHICommandList.h # 命令列表抽象 -├── RHIBuffer.h # 缓冲区抽象 -├── RHITexture.h # 纹理抽象 -├── RHIShader.h # 着色器抽象 -├── RHIPipelineLayout.h # 管线布局抽象(替代 RootSignature) -├── RHIPipelineState.h # 管线状态抽象 -├── RHISwapChain.h # 交换链抽象 -├── RHIFence.h # 同步栅栏抽象 -├── RHIDescriptorPool.h # 描述符池抽象 -└── RHIFactory.h # RHI 工厂类 -``` - -### 4.2 核心抽象基类定义 -#### 4.2.1 通用类型定义(Types.h) -```cpp -#pragma once -#include - -// 通用资源格式 -enum class RHIFormat { - R8G8B8A8_UNORM, - D32_FLOAT, - // ... 其他格式 -}; - -// 缓冲区用途 -enum class RHIBufferUsage { - VertexBuffer, - IndexBuffer, - ConstantBuffer, - // ... 其他用途 -}; - -// 通用缓冲区描述 -struct RHIBufferDesc { - uint64_t size; - RHIBufferUsage usage; - // ... 其他参数 -}; - -// 通用纹理描述 -struct RHITextureDesc { - uint32_t width; - uint32_t height; - RHIFormat format; - // ... 其他参数 -}; - -// 通用渲染通道描述 -struct RHIRenderPassDesc { - struct Attachment { - RHITexture* texture; - float clearColor[4]; - // ... 其他参数 - } colorAttachments[8]; - uint32_t colorAttachmentCount; - Attachment depthStencilAttachment; - // ... 其他参数 -}; -``` - -#### 4.2.2 硬件能力检测(RHICapabilities.h) -```cpp -#pragma once - -struct RHICapabilities { - bool bSupportsRayTracing = false; - bool bSupportsMeshShaders = false; - bool bSupportsExplicitMultiThreading = false; - // ... 其他特性 -}; -``` - -#### 4.2.3 设备抽象(RHIDevice.h) -```cpp -#pragma once -#include "Types.h" -#include "RHICapabilities.h" - -class RHISwapChain; -class RHICommandQueue; -class RHIBuffer; -class RHITexture; - -class RHIDevice { -public: - virtual ~RHIDevice() = default; - - // 初始化设备 - virtual bool Initialize(const RHIDeviceDesc& desc) = 0; - - // 资源创建接口 - virtual RHIBuffer* CreateBuffer(const RHIBufferDesc& desc) = 0; - virtual RHITexture* CreateTexture(const RHITextureDesc& desc) = 0; - virtual RHICommandQueue* CreateCommandQueue(const RHICommandQueueDesc& desc) = 0; - virtual RHISwapChain* CreateSwapChain(const RHISwapChainDesc& desc) = 0; - - // 硬件能力查询 - virtual const RHICapabilities& GetCapabilities() const = 0; - - // 底层逃逸口(谨慎使用) - virtual void* GetNativeDevice() = 0; - - // 清理 - virtual void Shutdown() = 0; -}; -``` - -#### 4.2.4 命令列表抽象(RHICommandList.h) -```cpp -#pragma once -#include "Types.h" - -class RHIBuffer; -class RHITexture; -class RHIPipelineState; - -class RHICommandList { -public: - virtual ~RHICommandList() = default; - - // 命令录制 - virtual void Begin() = 0; - virtual void End() = 0; - - // 渲染通道 - virtual void BeginRenderPass(const RHIRenderPassDesc& desc) = 0; - virtual void EndRenderPass() = 0; - - // 状态设置 - virtual void SetPipelineState(RHIPipelineState* pso) = 0; - virtual void SetVertexBuffers(uint32_t slot, RHIBuffer* const* buffers, uint32_t count) = 0; - virtual void SetIndexBuffer(RHIBuffer* buffer, RHIFormat format, uint32_t offset) = 0; - - // 绘制命令 - virtual void DrawIndexed(uint32_t indexCount, uint32_t instanceCount, - uint32_t startIndex, int32_t baseVertex, - uint32_t startInstance) = 0; - - // 资源拷贝 - virtual void CopyBuffer(RHIBuffer* dst, RHIBuffer* src, uint64_t size) = 0; -}; -``` - -#### 4.2.5 管线布局抽象(RHIPipelineLayout.h) -```cpp -#pragma once -#include "Types.h" - -// 管线布局描述(统一描述资源绑定规则) -struct RHIPipelineLayoutDesc { - uint32_t constantBufferCount; - uint32_t textureCount; - uint32_t samplerCount; - // ... 其他参数 -}; - -class RHIPipelineLayout { -public: - virtual ~RHIPipelineLayout() = default; - // 具体实现由后端处理 -}; -``` - -#### 4.2.6 RHI 工厂类(RHIFactory.h) -```cpp -#pragma once -#include "RHIDevice.h" -#include - -enum class RHIType { - D3D12, - OpenGL, - // 未来扩展 Vulkan/Metal -}; - -class RHIFactory { -public: - // 根据类型创建 RHI 设备 - static RHIDevice* CreateRHIDevice(RHIType type); - - // 根据字符串选择(从配置文件读取) - static RHIDevice* CreateRHIDevice(const std::string& typeName); -}; -``` - - -## 5. 核心差异处理方案 - -### 5.1 缓冲区(RHIBuffer)抽象设计 - -#### 5.1.1 现有实现对比 - -| 功能 | D3D12Buffer | OpenGLBuffer | 处理方案 | -|------|-------------|--------------|----------| -| 初始化参数 | 需要 device + heapType | 不需要 | 后端各自实现初始化逻辑 | -| 句柄类型 | `ID3D12Resource*` | `GLuint` | 统一返回 `void*`,底层逃逸 | -| Map/Unmap | `Map()` / `Unmap()` | `glMapBuffer` | 后端各自实现 | -| 上传数据 | `UpdateData()` | `SetData()` | 统一为 `SetData()` | -| 获取大小 | `GetSize()` | `GetSize()` | 通用 | -| Vertex Stride | 显式存储 `m_stride` | VAO 管理 | 基类提供接口 | -| 资源状态 | `ResourceStates` 显式管理 | 无 | D3D12 维护,OpenGL 透明 | - -#### 5.1.2 抽象接口定义 - -```cpp -// engine/include/XCEngine/RHI/RHIBuffer.h -class RHIBuffer { -public: - virtual ~RHIBuffer() = default; - - // 数据操作 - virtual void* Map() = 0; - virtual void Unmap() = 0; - virtual void SetData(const void* data, size_t size, size_t offset = 0) = 0; - - // 属性查询 - virtual uint64_t GetSize() const = 0; - virtual BufferType GetBufferType() const = 0; - virtual void SetBufferType(BufferType type) = 0; - virtual uint32_t GetStride() const = 0; - virtual void SetStride(uint32_t stride) = 0; - - // 底层逃逸 - virtual void* GetNativeHandle() = 0; - - // 调试 - virtual const std::string& GetName() const = 0; - virtual void SetName(const std::string& name) = 0; - - // 生命周期 - virtual void Shutdown() = 0; -}; -``` - -#### 5.1.3 差异处理策略 - -1. **初始化参数差异** - - D3D12:在 `CreateBuffer()` 时由 Device 传入 device 参数,内部调用 `Initialize(device, size, ...)` - - OpenGL:直接调用 `Initialize(type, size, data, dynamic)`,不需要 device - -2. **句柄类型差异** - - `GetNativeHandle()` 返回 `void*` - - D3D12 后端:`return m_resource.Get();` - - OpenGL 后端:`return (void*)(uintptr_t)m_buffer;` - -3. **Vertex Stride** - - D3D12:显式存储在 `m_stride` - - OpenGL:在 VertexArray 中管理,可在基类中提供接口 - -4. **Device 创建接口** - ```cpp - // RHIDevice 中 - virtual RHIBuffer* CreateBuffer(const BufferDesc& desc) = 0; - - // D3D12Device 实现 - RHIBuffer* CreateBuffer(const BufferDesc& desc) override { - auto* buffer = new D3D12Buffer(); - buffer->Initialize(m_device.Get(), desc.size, ...); - return buffer; - } - - // OpenGLDevice 实现 - RHIBuffer* CreateBuffer(const BufferDesc& desc) override { - auto* buffer = new OpenGLBuffer(); - buffer->Initialize(glType, desc.size, ...); - return buffer; - } - ``` - -### 5.2 纹理(RHITexture)抽象设计 - -#### 5.2.1 设计理念对应 - -| 差异点 | 设计理念 | 处理方案 | -|--------|---------|---------| -| 初始化参数差异 | 求同存异 | 后端各自实现初始化,基类定义统一创建接口 | -| 句柄类型差异 | 底层逃逸 | 统一返回 void*,通过 GetNativeHandle() 获取原生类型 | -| 资源状态管理 | 特性降级 | D3D12 显式管理 ResourceStates,OpenGL 无需管理(透明) | -| 格式枚举差异 | 求同存异 | 基类统一 Format 枚举,后端映射到各自格式 | - -#### 5.2.2 现有实现对比 - -| 功能 | D3D12Texture | OpenGLTexture | 处理方案 | -|------|--------------|---------------|----------| -| 初始化参数 | device + resource desc | type + dimensions + format | 后端各自实现 | -| 句柄类型 | `ID3D12Resource*` | `GLuint` | 统一返回 `void*` | -| 尺寸获取 | GetWidth/Height/Depth | 同上 | 统一接口 | -| Mip Levels | GetMipLevels() | GetMipLevels() | 通用 | -| 格式 | DXGI_FORMAT | OpenGLFormat | 基类统一 Format | -| 纹理类型 | TextureType enum | OpenGLTextureType enum | 基类统一 TextureType | -| 绑定 | 通过 DescriptorHeap | glBindTexture | 后端实现 Bind() | -| Mipmap 生成 | 需手动计算 | glGenerateMipmap | 后端实现 | -| 资源状态 | ResourceStates | 无 | D3D12 维护,OpenGL 透明 | -| 深度纹理 | InitializeDepthStencil() | 普通纹理 | 后端实现 | - -#### 5.2.3 抽象接口定义 - -```cpp -class RHITexture { -public: - virtual ~RHITexture() = default; - - // 属性(求同存异) - virtual uint32_t GetWidth() const = 0; - virtual uint32_t GetHeight() const = 0; - virtual uint32_t GetDepth() const = 0; - virtual uint32_t GetMipLevels() const = 0; - virtual Format GetFormat() const = 0; - virtual TextureType GetTextureType() const = 0; - - // 资源状态(特性降级) - virtual ResourceStates GetState() const = 0; - virtual void SetState(ResourceStates state) = 0; - - // 底层逃逸 - virtual void* GetNativeHandle() = 0; - - // 调试 - virtual const std::string& GetName() const = 0; - virtual void SetName(const std::string& name) = 0; - - // 生命周期 - virtual void Shutdown() = 0; -}; -``` - -#### 5.2.4 差异处理策略 - -1. **初始化参数差异(求同存异)** - - D3D12:需要 device + resource desc - - OpenGL:需要 type + dimensions + format - - 解决:Device 的 CreateTexture() 统一接收 TextureDesc,后端各自实现 Initialize() - -2. **句柄类型差异(底层逃逸)** - - `GetNativeHandle()` 返回 `void*` - - D3D12:`return m_resource.Get();` - - OpenGL:`return reinterpret_cast(static_cast(m_texture));` - -3. **资源状态管理(特性降级)** - - D3D12:显式 ResourceStates 管理,需要转换 barrier - - OpenGL:无资源状态概念 - - 解决:基类提供 GetState/SetState,D3D12 实现具体逻辑,OpenGL 空实现 - -4. **格式枚举差异(求同存异)** - - D3D12:DXGI_FORMAT - - OpenGL:OpenGLFormat - - 解决:基类统一使用 Format 枚举,后端内部映射 - -### 5.7 着色器(RHIShader)抽象设计 - -#### 5.7.1 设计理念对应 - -| 差异点 | 设计理念 | 处理方案 | -|--------|---------|---------| -| 编译方式差异 | 求同存异 | 统一 Compile 接口,后端各自实现 | -| 句柄类型差异 | 底层逃逸 | 统一返回 void* | -| Uniform 设置 | 特性降级 | OpenGL 实现,D3D12 空实现 | -| 绑定方式 | 求同存异 | 基类提供 Bind/Unbind,后端实现 | - -#### 5.7.2 现有实现对比 - -| 功能 | D3D12Shader | OpenGLShader | 处理方案 | -|------|-------------|--------------|----------| -| 编译 | CompileFromFile/Compile | 同上 | 统一接口 | -| 字节码/Program | ID3DBlob | GLuint | 统一 void* | -| 类型 | ShaderType | 自有 enum | 基类统一 | -| Uniform 设置 | 无 | SetInt/SetFloat... | OpenGL 实现,D3D12 空 | -| 绑定 | 通过 CommandList | Use()/Bind() | 基类提供 Bind/Unbind | - -#### 5.7.3 抽象接口定义 - -```cpp -class RHIShader { -public: - virtual ~RHIShader() = default; - - // 编译(求同存异) - virtual bool CompileFromFile(const wchar_t* filePath, const char* entryPoint, const char* target) = 0; - virtual bool Compile(const void* sourceData, size_t sourceSize, const char* entryPoint, const char* target) = 0; - - // 属性 - virtual ShaderType GetType() const = 0; - virtual bool IsValid() const = 0; - - // 绑定(求同存异) - virtual void Bind() = 0; - virtual void Unbind() = 0; - - // 底层逃逸 - virtual void* GetNativeHandle() = 0; - - // Uniform 设置(特性降级) - virtual void SetInt(const char* name, int value) = 0; - virtual void SetFloat(const char* name, float value) = 0; - virtual void SetVec3(const char* name, float x, float y, float z) = 0; - virtual void SetVec4(const char* name, float x, float y, float z, float w) = 0; - virtual void SetMat4(const char* name, const float* value) = 0; - - // 生命周期 - virtual void Shutdown() = 0; -}; -``` - -#### 5.7.4 差异处理策略 - -1. **编译方式差异(求同存异)** - - D3D12:需要 entry point + target(如 "vs_6_0") - - OpenGL:根据 source 类型自动判断 - - 解决:统一 Compile 接口,后端各自处理 - -2. **句柄类型差异(底层逃逸)** - - D3D12:ID3DBlob - - OpenGL:GLuint (program) - - 解决:GetNativeHandle() 返回 void* - -3. **Uniform 设置(特性降级)** - - D3D12:通过 RootSignature/CBV 设置,不在 Shader 类 - - OpenGL:通过 glUniform* 设置 - - 解决:D3D12 实现为空,OpenGL 实现具体逻辑 - -4. **绑定方式** - - D3D12:通过 CommandList->SetPipelineState - - OpenGL:glUseProgram - - 解决:基类提供 Bind/Unbind 接口 - -### 5.8 采样器(RHISampler)抽象设计 - -#### 5.8.1 设计理念对应 - -| 差异点 | 设计理念 | 处理方案 | -|--------|---------|---------| -| 初始化参数差异 | 求同存异 | 后端各自实现初始化 | -| 句柄类型差异 | 底层逃逸 | 统一返回 void* | -| 绑定方式 | 求同存异 | 基类提供 Bind/Unbind | - -#### 5.8.2 现有实现对比 - -| 功能 | D3D12Sampler | OpenGLSampler | -|------|--------------|---------------| -| 初始化 | Initialize(device, desc) | Initialize(desc) | -| 销毁 | Shutdown() | Shutdown() | -| 绑定 | 通过 DescriptorHeap | Bind(unit) | -| 句柄 | GetNativeHandle() | GetID() | - -#### 5.8.3 抽象接口定义 - -```cpp -class RHISampler { -public: - virtual ~RHISampler() = default; - - virtual void Shutdown() = 0; - - virtual void Bind(unsigned int unit) = 0; - virtual void Unbind(unsigned int unit) = 0; - - virtual void* GetNativeHandle() = 0; - virtual unsigned int GetID() = 0; -}; -``` - -#### 5.8.4 差异处理策略 - -1. **初始化参数差异(求同存异)** - - D3D12:需要 device 参数 - - OpenGL:不需要 device - - 解决:Device 的 CreateSampler() 统一接收 SamplerDesc,后端各自实现 Initialize() - -2. **句柄类型差异(底层逃逸)** - - D3D12:通过 DescriptorHeap 管理 - - OpenGL:GLuint - - 解决:GetNativeHandle() 返回 void* - -### 5.9 同步栅栏(RHIFence)抽象设计 - -#### 5.9.1 设计理念对应 - -| 差异点 | 设计理念 | 处理方案 | -|--------|---------|---------| -| 初始化参数差异 | 求同存异 | 后端各自实现初始化 | -| 句柄类型差异 | 底层逃逸 | 统一返回 void* | -| 状态查询 | 求同存异 | 统一 IsSignaled/GetCompletedValue 接口 | - -#### 5.9.2 现有实现对比 - -| 功能 | D3D12Fence | OpenGLFence | -|------|-------------|-------------| -| 初始化 | Initialize(device, value) | Initialize(signaled) | -| Signal | Signal(value) | Signal()/Signal(value) | -| Wait | Wait(value) | Wait(timeoutNs) | -| 状态 | GetCompletedValue() | IsSignaled()/GetStatus() | -| 句柄 | ID3D12Fence* | GLsync | - -#### 5.9.3 抽象接口定义 - -```cpp -class RHIFence { -public: - virtual ~RHIFence() = default; - - virtual void Shutdown() = 0; - - virtual void Signal(uint64_t value) = 0; - virtual void Wait(uint64_t value) = 0; - virtual uint64_t GetCompletedValue() = 0; - virtual bool IsSignaled() const = 0; - - virtual void* GetNativeHandle() = 0; -}; -``` - -#### 5.9.4 差异处理策略 - -1. **初始化参数差异(求同存异)** - - D3D12:需要 device 参数 - - OpenGL:不需要 device - - 解决:后端各自实现 Initialize() - -2. **句柄类型差异(底层逃逸)** - - D3D12:ID3D12Fence* - - OpenGL:GLsync - - 解决:GetNativeHandle() 返回 void* - -### 5.10 设备(RHIDevice)抽象设计 - -#### 5.10.1 设计理念对应 - -| 差异点 | 设计理念 | 处理方案 | -|--------|---------|---------| -| 初始化参数差异 | 求同存异 | 统一 Initialize 接口,后端各自处理 | -| 工厂方法差异 | 特性降级 | D3D12 实现,OpenGL 返回 nullptr | -| 窗口管理差异 | 特性降级 | OpenGL 有,D3D12 通过 SwapChain 处理 | -| 适配器枚举差异 | 特性降级 | D3D12 实现,OpenGL 空实现 | -| 底层设备类型差异 | 底层逃逸 | 统一返回 void* | - -#### 5.10.2 现有实现对比 - -| 功能 | D3D12Device | OpenGLDevice | 处理方案 | -|------|--------------|---------------|----------| -| 初始化 | Initialize(debugLayer) | CreateRenderWindow/InitializeWithExistingWindow | 统一 Initialize | -| 关闭 | Shutdown() | Shutdown() | 统一 | -| 获取设备 | GetDevice() | 无 | GetNativeHandle 统一 | -| 适配器信息 | GetAdapterInfo() | GetDeviceInfo() | 统一 GetDeviceInfo | -| 工厂方法 | CreateBuffer/Texture/Shader... | 无 | OpenGL 返回 nullptr | -| 窗口管理 | 通过 SwapChain | GetWindow/SwapBuffers/PollEvents | OpenGL 有,D3D12 无 | - -#### 5.10.3 抽象接口定义 - -```cpp -class RHIDevice { -public: - virtual ~RHIDevice() = default; - - virtual bool Initialize(const RHIDeviceDesc& desc) = 0; - virtual void Shutdown() = 0; - - virtual const RHIDeviceInfo& GetDeviceInfo() const = 0; - virtual const RHICapabilities& GetCapabilities() const = 0; - - virtual RHIBuffer* CreateBuffer(const BufferDesc& desc) = 0; - virtual RHITexture* CreateTexture(const TextureDesc& desc) = 0; - virtual RHIShader* CompileShader(const ShaderCompileDesc& desc) = 0; - virtual RHISampler* CreateSampler(const SamplerDesc& desc) = 0; - virtual RHIFence* CreateFence(const FenceDesc& desc) = 0; - - virtual void* GetNativeDevice() = 0; - - virtual void* GetNativeHandle() const = 0; -}; -``` - -#### 5.10.4 差异处理策略 - -1. **初始化参数差异(求同存异)** - - D3D12:`Initialize(enableDebugLayer)` - - OpenGL:`CreateRenderWindow` / `InitializeWithExistingWindow` - - 解决:统一 `Initialize(const RHIDeviceDesc& desc)` 接口,Desc 中包含窗口参数 - -2. **工厂方法差异(特性降级)** - - D3D12:有完整的工厂方法 - - OpenGL:资源直接用 glGen* 创建,没有工厂方法 - - 解决:OpenGL 后端返回 nullptr,上层需直接创建 OpenGL 资源 - -3. **窗口管理差异(特性降级)** - - OpenGL:直接管理窗口,有 GetWindow/SwapBuffers/PollEvents - - D3D12:通过 SwapChain 处理 - - 解决:OpenGL 提供窗口管理接口,D3D12 空实现 - -4. **适配器枚举差异(特性降级)** - - D3D12:支持 EnumerateAdapters - - OpenGL:无此概念 - - 解决:D3D12 实现具体功能,OpenGL 返回空 - -5. **底层设备类型差异(底层逃逸)** - - D3D12:ID3D12Device* - - OpenGL:无对等概念 - - 解决:GetNativeDevice() 返回 void*,OpenGL 返回 nullptr 或窗口句柄 -- **D3D12**:显式 `RootSignature` 定义资源绑定规则 -- **OpenGL**:隐式通过 `glUniformLocation`、`glBindTextureUnit` 绑定 -- **解决方案**: - - 用 `RHIPipelineLayout` 统一抽象 - - D3D12 后端:内部创建 `ID3D12RootSignature` - - OpenGL 后端:存储绑定点数量,绘制时自动调用 `glBind*` - -### 5.4 描述符堆 vs OpenGL 纹理单元 -- **D3D12**:显式 `DescriptorHeap` 管理资源视图 -- **OpenGL**:隐式通过 `glActiveTexture` 绑定 -- **解决方案**: - - 用 `RHIDescriptorPool` 统一抽象 - - 提供"自动模式"(默认)和"显式模式"(可选) - - D3D12 后端:从堆分配槽位,更新描述符 - - OpenGL 后端:维护绑定点计数器,绘制时自动绑定 - -### 5.5 多线程命令录制 -- **D3D12**:原生支持多线程录制不同 `CommandList` -- **OpenGL**:状态机模型,单线程上下文 -- **解决方案**: - - 抽象层统一提供多线程接口 - - D3D12 后端:真·并行提交 - - OpenGL 后端:命令缓冲队列,单线程回放 - -### 5.6 高级特性(光线追踪等) -- **解决方案**: - 1. **特性检测**:通过 `RHICapabilities` 查询支持情况 - 2. **降级方案**:不支持时用传统路径替代 - 3. **底层逃逸**:通过 `GetNativeDevice()` 直接访问原生 API - - -### 5.11 命令队列(RHICommandQueue)抽象设计 - -#### 5.11.1 设计理念对应 - -| 差异点 | 设计理念 | 处理方案 | -|--------|---------|---------| -| 初始化参数差异 | 求同存异 | 后端各自实现初始化 | -| 执行命令差异 | 特性降级 | D3D12 实现,OpenGL 空实现 | -| 同步机制差异 | 特性降级 | D3D12 实现,OpenGL 空实现 | -| 句柄类型差异 | 底层逃逸 | 统一返回 void* | - -#### 5.11.2 现有实现对比 - -| 功能 | D3D12CommandQueue | OpenGL | 处理方案 | -|------|-------------------|--------|----------| -| 初始化 | Initialize(device, type) | 无 | OpenGL 空实现 | -| 执行命令 | ExecuteCommandLists() | 无 | OpenGL 空实现 | -| 信号同步 | Signal/Wait | 无 | OpenGL 空实现 | -| 空闲等待 | WaitForIdle() | 无 | OpenGL 空实现 | -| 句柄 | ID3D12CommandQueue* | 无 | GetNativeHandle | - -#### 5.11.3 抽象接口定义 - -```cpp -class RHICommandQueue { -public: - virtual ~RHICommandQueue() = default; - - virtual void Shutdown() = 0; - - virtual void ExecuteCommandLists(uint32_t count, void** lists) = 0; - virtual void Signal(RHIFence* fence, uint64_t value) = 0; - virtual void Wait(RHIFence* fence, uint64_t value) = 0; - virtual uint64_t GetCompletedValue() = 0; - virtual void WaitForIdle() = 0; - - virtual CommandQueueType GetType() const = 0; - virtual uint64_t GetTimestampFrequency() const = 0; - - virtual void* GetNativeHandle() = 0; -}; -``` - -#### 5.11.4 差异处理策略 - -1. **初始化参数差异(求同存异)** - - D3D12:需要 device + type 参数 - - OpenGL:无对等概念 - - 解决:Device 的 CreateCommandQueue() 统一接收 CommandQueueDesc - -2. **执行命令差异(特性降级)** - - D3D12:ExecuteCommandLists() 提交命令列表 - - OpenGL:状态机模型,无命令队列概念 - - 解决:OpenGL 空实现 - -3. **同步机制差异(特性降级)** - - D3D12:Signal/Wait/Fence 完整实现 - - OpenGL:无对等概念 - - 解决:OpenGL 空实现 - -4. **句柄类型差异(底层逃逸)** - - D3D12:ID3D12CommandQueue* - - OpenGL:无对等概念 - - 解决:GetNativeHandle() 返回 void* - - -### 5.12 命令列表(RHICommandList)抽象设计 - -#### 5.12.1 设计理念对应 - -| 差异点 | 设计理念 | 处理方案 | -|--------|---------|---------| -| 资源状态转换 | 特性降级 | D3D12 实现转换,OpenGL 空实现 | -| 绑定方式 | 求同存异 | 统一接口,后端各自实现 | -| 渲染目标 | 求同存异 | 统一接口,后端各自实现 | - -#### 5.12.2 现有实现对比 - -| 功能 | D3D12CommandList | OpenGLCommandList | 处理方案 | -|------|-------------------|-------------------|----------| -| 资源转换 | TransitionBarrier | glBarrier | 统一 TransitionBarrier | -| PSO 设置 | SetPipelineState | UseShader | 统一 SetPipelineState | -| 视口/裁剪 | SetViewport/ScissorRect | SetViewport/Scissor | 统一 | -| 顶点缓冲 | SetVertexBuffer | BindVertexArray | 统一 | -| 索引缓冲 | SetIndexBuffer | BindVertexArray | 统一 | -| 绘制 | Draw/DrawIndexed | Draw/DrawIndexed | 统一 | -| 渲染目标 | SetRenderTargets | glBindFramebuffer | 统一 | -| 清除 | ClearRenderTargetView | Clear | 统一 | -| 复制 | CopyResource | CopyImageSubData | 统一 | -| 计算 | Dispatch | DispatchCompute | 统一 | - -#### 5.12.3 抽象接口定义 - -```cpp -class RHICommandList { -public: - virtual ~RHICommandList() = default; - - virtual void Shutdown() = 0; - - virtual void Reset() = 0; - virtual void Close() = 0; - - virtual void TransitionBarrier(void* resource, ResourceStates stateBefore, ResourceStates stateAfter) = 0; - - virtual void SetPipelineState(void* pso) = 0; - virtual void SetViewport(const Viewport& viewport) = 0; - virtual void SetScissorRect(const Rect& rect) = 0; - virtual void SetRenderTargets(uint32_t count, void** renderTargets, void* depthStencil = nullptr) = 0; - - virtual void SetVertexBuffer(uint32_t slot, void* buffer, uint64_t offset, uint32_t stride) = 0; - virtual void SetIndexBuffer(void* buffer, uint64_t offset, Format format) = 0; - - virtual void Draw(uint32_t vertexCount, uint32_t instanceCount = 1, uint32_t startVertex = 0, uint32_t startInstance = 0) = 0; - virtual void DrawIndexed(uint32_t indexCount, uint32_t instanceCount = 1, uint32_t startIndex = 0, int32_t baseVertex = 0, uint32_t startInstance = 0) = 0; - - virtual void Clear(float r, float g, float b, float a, uint32_t buffers) = 0; - virtual void ClearRenderTarget(void* renderTarget, const float color[4]) = 0; - virtual void ClearDepthStencil(void* depthStencil, float depth, uint8_t stencil) = 0; - - virtual void CopyResource(void* dst, void* src) = 0; - - virtual void Dispatch(uint32_t x, uint32_t y, uint32_t z) = 0; -}; -``` - -#### 5.12.4 差异处理策略 - -1. **资源状态转换(特性降级)** - - D3D12:显式 TransitionBarrier 管理资源状态 - - OpenGL:无资源状态概念 - - 解决:D3D12 实现转换,OpenGL 空实现 - -2. **绑定方式(求同存异)** - - D3D12:通过 DescriptorHeap 和 RootSignature - - OpenGL:通过 glBind* 和 VAO - - 解决:统一 Set* 接口,后端各自实现 - -3. **渲染目标(求同存异)** - - D3D12:SetRenderTargets 设置渲染目标视图 - - OpenGL:glBindFramebuffer 绑定帧缓冲 - - 解决:统一 SetRenderTargets 接口 - - -### 5.13 交换链(RHISwapChain)抽象设计 - -#### 5.13.1 设计理念对应 - -| 差异点 | 设计理念 | 处理方案 | -|--------|---------|---------| -| 初始化参数差异 | 求同存异 | 统一 Desc,后端各自处理 | -| 窗口管理差异 | 特性降级 | OpenGL 有,D3D12 无 | -| 呈现方式差异 | 求同存异 | 统一 Present 接口 | - -#### 5.13.2 现有实现对比 - -| 功能 | D3D12SwapChain | OpenGLSwapChain | 处理方案 | -|------|----------------|------------------|----------| -| 初始化 | factory/queue/hwnd | window | 统一 Initialize(Desc) | -| 后缓冲 | GetBackBuffer() | 无 | 通过 GetCurrentBackBuffer | -| 显示 | Present(sync, flags) | Present() | 统一 Present() | -| 调整大小 | Resize(w, h) | Resize(w, h) | 统一 | -| 窗口事件 | 无 | ShouldClose/PollEvents | OpenGL 实现 | - -#### 5.13.3 抽象接口定义 - -```cpp -class RHISwapChain { -public: - virtual ~RHISwapChain() = default; - - virtual void Shutdown() = 0; - - virtual uint32_t GetCurrentBackBufferIndex() const = 0; - virtual RHITexture* GetCurrentBackBuffer() = 0; - virtual void Present(uint32_t syncInterval = 1, uint32_t flags = 0) = 0; - virtual void Resize(uint32_t width, uint32_t height) = 0; - - virtual bool ShouldClose() const = 0; - virtual void SetShouldClose(bool shouldClose) = 0; - virtual void PollEvents() = 0; -}; -``` - -#### 5.13.4 差异处理策略 - -1. **初始化参数差异(求同存异)** - - D3D12:factory + commandQueue + hwnd - - OpenGL:window - - 解决:Device 的 CreateSwapChain() 统一接收 SwapChainDesc - -2. **窗口管理差异(特性降级)** - - OpenGL:ShouldClose/PollEvents - - D3D12:无 - - 解决:OpenGL 实现具体功能,D3D12 空实现 - -3. **呈现方式差异(求同存异)** - - D3D12:Present(syncInterval, flags) - - OpenGL:Present() / SwapBuffers() - - 解决:统一 Present(syncInterval, flags) - - -### 5.14 管线状态(RHIPipelineState)抽象设计 - -#### 5.14.1 设计理念对应 - -| 差异点 | 设计理念 | 处理方案 | -|--------|---------|---------| -| 初始化差异 | 求同存异 | 后端各自处理 | -| 状态管理差异 | 特性降级 | D3D12 实现状态块,OpenGL 分离状态 | -| 绑定方式差异 | 求同存异 | 统一 Bind 接口 | - -#### 5.14.2 现有实现对比 - -| 功能 | D3D12PipelineState | OpenGLPipelineState | 处理方案 | -|------|-------------------|-------------------|----------| -| 初始化 | device + desc | 构造函数 | 后端各自处理 | -| 状态设置 | 单一 PSO 对象 | 分离状态对象 | 后端各自处理 | -| 绑定 | CommandList->SetPipelineState | AttachShader/Apply | 统一 Bind | -| 句柄 | ID3D12PipelineState* | GLuint (program) | 统一 void* | - -#### 5.14.3 抽象接口定义 - -```cpp -class RHIPipelineState { -public: - virtual ~RHIPipelineState() = default; - - virtual void Shutdown() = 0; - - virtual void Bind() = 0; - virtual void Unbind() = 0; - - virtual void* GetNativeHandle() = 0; - virtual PipelineType GetType() const = 0; -}; -``` - -#### 5.14.4 差异处理策略 - -1. **初始化差异(求同存异)** - - D3D12:device + D3D12_GRAPHICS_PIPELINE_STATE_DESC - - OpenGL:通过 SetDepthStencilState/BlendState/RasterizerState - - 解决:Device 的 CreatePipelineState() 统一接收 PipelineStateDesc - -2. **状态管理差异(特性降级)** - - D3D12:单一 PSO 对象包含所有状态 - - OpenGL:分离的状态对象 - - 解决:后端各自实现,基类提供统一 Bind/Unbind - -3. **绑定方式差异(求同存异)** - - D3D12:通过 CommandList->SetPipelineState - - OpenGL:AttachShader + Apply - - 解决:统一 Bind/Unbind 接口 - - -## 6. 后端实现示例 -### 6.1 D3D12 设备实现(D3D12Device.h) -```cpp -#pragma once -#include "../RHIDevice.h" -#include "D3D12Common.h" - -class D3D12Device : public RHIDevice { -public: - virtual bool Initialize(const RHIDeviceDesc& desc) override; - virtual RHIBuffer* CreateBuffer(const RHIBufferDesc& desc) override; - virtual RHITexture* CreateTexture(const RHITextureDesc& desc) override; - virtual RHICommandQueue* CreateCommandQueue(const RHICommandQueueDesc& desc) override; - virtual RHISwapChain* CreateSwapChain(const RHISwapChainDesc& desc) override; - virtual const RHICapabilities& GetCapabilities() const override; - virtual void* GetNativeDevice() override { return m_pd3d12Device; } - virtual void Shutdown() override; - - // D3D12 特有接口 - ID3D12Device* GetD3D12Device() const { return m_pd3d12Device; } - -private: - ID3D12Device* m_pd3d12Device = nullptr; - ID3D12CommandQueue* m_pd3d12CommandQueue = nullptr; - RHICapabilities m_capabilities; -}; -``` - -### 6.2 OpenGL 设备实现(OpenGLDevice.h) -```cpp -#pragma once -#include "../RHIDevice.h" -#include "OpenGLCommon.h" - -class OpenGLDevice : public RHIDevice { -public: - virtual bool Initialize(const RHIDeviceDesc& desc) override; - virtual RHIBuffer* CreateBuffer(const RHIBufferDesc& desc) override; - virtual RHITexture* CreateTexture(const RHITextureDesc& desc) override; - virtual RHICommandQueue* CreateCommandQueue(const RHICommandQueueDesc& desc) override; - virtual RHISwapChain* CreateSwapChain(const RHISwapChainDesc& desc) override; - virtual const RHICapabilities& GetCapabilities() const override; - virtual void* GetNativeDevice() override { return m_context; } - virtual void Shutdown() override; - - // OpenGL 特有接口 - HGLRC GetContext() const { return m_context; } - -private: - HGLRC m_context = nullptr; - RHICapabilities m_capabilities; -}; -``` - - -## 7. 测试用例示例 -### 7.1 最小渲染循环测试 -```cpp -#include "XCEngine/RHI/RHIFactory.h" -#include "XCEngine/RHI/RHIDevice.h" -#include "XCEngine/RHI/RHISwapChain.h" -#include "XCEngine/RHI/RHICommandList.h" - -int main() { - // 1. 创建 RHI 设备(可切换 D3D12/OpenGL) - RHIDevice* pDevice = RHIFactory::CreateRHIDevice(RHIType::D3D12); - pDevice->Initialize(RHIDeviceDesc{...}); - - // 2. 创建交换链和命令队列 - RHISwapChain* pSwapChain = pDevice->CreateSwapChain(RHISwapChainDesc{...}); - RHICommandQueue* pCommandQueue = pDevice->CreateCommandQueue(RHICommandQueueDesc{...}); - RHICommandList* pCommandList = pDevice->CreateCommandList(RHICommandListDesc{...}); - - // 3. 渲染循环 - while (!ShouldQuit()) { - // 录制命令 - pCommandList->Begin(); - - // 清屏 - RHIRenderPassDesc renderPassDesc = {}; - renderPassDesc.colorAttachments[0].texture = pSwapChain->GetCurrentBackBuffer(); - renderPassDesc.colorAttachments[0].loadOp = RHIRenderPassLoadOp::Clear; - renderPassDesc.colorAttachments[0].clearColor = {0.2f, 0.4f, 0.8f, 1.0f}; - pCommandList->BeginRenderPass(renderPassDesc); - pCommandList->EndRenderPass(); - - // 提交命令 - pCommandList->End(); - pCommandQueue->SubmitCommandList(pCommandList); - - // 呈现 - pSwapChain->Present(); - } - - // 4. 清理 - pDevice->Shutdown(); - delete pDevice; - return 0; -} -``` - - -## 8. 下一步行动指南 -1. **补全抽象基类**:重点实现 `RHIDevice`、`RHICommandList`、`RHIPipelineLayout` -2. **改造现有后端**:让 `D3D12Device`、`OpenGLDevice` 继承抽象基类并实现接口 -3. **实现工厂类**:完成 `RHIFactory` 以支持动态切换后端 -4. **测试验证**:用最小渲染循环测试 D3D12 和 OpenGL 后端 -5. **扩展功能**:逐步添加资源管理、着色器跨平台、多线程渲染等功能 - - -## 9. 关键注意事项 -- **上层只调用抽象接口**:绝不直接访问 D3D12/OpenGL 特有类 -- **合理使用底层逃逸**:仅在必要时使用 `GetNativeDevice()`,并注明破坏跨平台性 -- **优先保证核心功能**:先实现 90% 常用功能的统一抽象,再处理高级特性 -- **保持设计可扩展**:为未来支持 Vulkan/Metal 预留空间 - -需要我帮你细化某个具体模块的实现代码吗? \ No newline at end of file diff --git a/docs/used/RHI模块测试重构.md b/docs/used/RHI模块测试重构.md deleted file mode 100644 index 68b7b92a..00000000 --- a/docs/used/RHI模块测试重构.md +++ /dev/null @@ -1,774 +0,0 @@ -最最最重要的是,在重构RHI测试的过程中,如果发现了RHI模块设计上的根本问题,需要紧急向我汇报!!!!!!!! - - -# RHI 模块单元测试重构计划 - -## 1. 项目背景 - -本文档分析 XCEngine RHI(渲染硬件抽象层)模块的三个单元测试层次的覆盖度和质量问题,并提出重构建议。 - -### 1.1 测试层次结构 - -| 层次 | 位置 | 测试数量 | 特点 | -|------|------|----------|------| -| **RHI 抽象层** | `tests/RHI/unit/` | 75 tests × 2 backends = 150 | 参数化测试,跨 D3D12/OpenGL | -| **D3D12 后端** | `tests/RHI/D3D12/unit/` | ~58 tests | 非参数化,直接测试 D3D12 API | -| **OpenGL 后端** | `tests/RHI/OpenGL/unit/` | ~58 tests | 非参数化,直接测试 OpenGL API | - -### 1.2 当前测试文件分布 - -#### RHI 抽象层 (`tests/RHI/unit/`) - -| 文件 | 测试数 | 测试类 | -|------|--------|--------| -| `test_device.cpp` | 8 | RHIDevice | -| `test_buffer.cpp` | 8 | RHIBuffer | -| `test_texture.cpp` | 5 | RHITexture | -| `test_swap_chain.cpp` | 4 | RHISwapChain | -| `test_command_list.cpp` | 14 | RHICommandList | -| `test_command_queue.cpp` | 6 | RHICommandQueue | -| `test_shader.cpp` | 7 | RHIShader | -| `test_fence.cpp` | 10 | RHIFence | -| `test_sampler.cpp` | 4 | RHISampler | -| `test_factory.cpp` | 5 | RHIFactory | -| `test_pipeline_state.cpp` | 10 | RHIPipelineState | -| `test_render_pass.cpp` | 10 | RHIRenderPass | -| `test_framebuffer.cpp` | 8 | RHIFramebuffer | -| `test_compute.cpp` | 8 | Compute/Dispatch | - -#### D3D12 后端 (`tests/RHI/D3D12/unit/`) - -| 文件 | 测试数 | 测试类/功能 | -|------|--------|-------------| -| `test_device.cpp` | 6 | D3D12Device | -| `test_fence.cpp` | 9 | D3D12Fence | -| `test_command_queue.cpp` | 2 | D3D12CommandQueue | -| `test_command_allocator.cpp` | 3 | D3D12CommandAllocator | -| `test_command_list.cpp` | 2 | D3D12CommandList | -| `test_buffer.cpp` | 6 | D3D12Buffer | -| `test_texture.cpp` | 4 | D3D12Texture | -| `test_descriptor_heap.cpp` | 9 | D3D12DescriptorHeap | -| `test_shader.cpp` | 3 | D3D12Shader (trivial) | -| `test_root_signature.cpp` | 2 | D3D12RootSignature | -| `test_pipeline_state.cpp` | 2 | D3D12 PSO (trivial) | -| `test_views.cpp` | 4 | RTV/DSV/CBV | -| `test_swap_chain.cpp` | 5 | D3D12SwapChain | - -#### OpenGL 后端 (`tests/RHI/OpenGL/unit/`) - -| 文件 | 测试数 | 测试类/功能 | -|------|--------|-------------| -| `test_device.cpp` | 2 | OpenGLDevice | -| `test_buffer.cpp` | 6 | OpenGLBuffer | -| `test_fence.cpp` | 8 | OpenGLFence | -| `test_texture.cpp` | 5 | OpenGLTexture | -| `test_shader.cpp` | 4 | OpenGLShader | -| `test_pipeline_state.cpp` | 3 | OpenGLPipelineState | -| `test_vertex_array.cpp` | 5 | OpenGLVertexArray | -| `test_command_list.cpp` | 9 | OpenGLCommandList | -| `test_render_target_view.cpp` | 4 | OpenGLRenderTargetView | -| `test_depth_stencil_view.cpp` | 4 | OpenGLDepthStencilView | -| `test_swap_chain.cpp` | 3 | OpenGLSwapChain | -| `test_sampler.cpp` | 4 | OpenGLSampler | - ---- - -## 2. 严重问题(P0 - 必须修复) - -### 2.1 `tests/RHI/unit/test_shader.cpp` - 9个测试完全相同 ✅ 已修复 - -**状态**: ✅ 已完成 (2026-03-25) - -**修复内容**: - -1. **Shader 编译抽象重构** - 扩展 `ShaderCompileDesc` 支持内嵌源码: - ```cpp - struct ShaderCompileDesc { - std::wstring fileName; // 文件路径(可选) - std::vector source; // 内嵌源码(可选) - ShaderLanguage sourceLanguage; // 源码语言:HLSL/GLSL/SPIRV - std::wstring entryPoint; // Entry point 名称 - std::wstring profile; // Profile - std::vector macros; - }; - ``` - -2. **新增 `ShaderLanguage` 枚举**: - ```cpp - enum class ShaderLanguage : uint8_t { - Unknown, - HLSL, // D3D11/D3D12 - GLSL, // OpenGL/Vulkan - SPIRV // Vulkan (pre-compiled) - }; - ``` - -3. **测试重构** - 9个相同测试 → 7个有效测试: - - `Shader_Compile_EmptyDesc_ReturnsNullptr` - 空描述符测试 - - `Shader_Compile_ValidVertexShader` - 顶点着色器编译测试 - - `Shader_Compile_ValidFragmentShader` - 片段着色器编译测试 - - `Shader_GetType_VertexShader` - 获取顶点着色器类型 - - `Shader_GetType_FragmentShader` - 获取片段着色器类型 - - `Shader_GetNativeHandle_ValidShader` - 获取原生句柄 - - `Shader_Shutdown_Invalidates` - 关闭后验证失效 - -**测试结果**: 14/14 通过 (D3D12: 7, OpenGL: 7) - ---- - -### 2.2 关键 RHI 抽象类完全没有测试 - -**严重程度**: 🔴 致命 - -**问题描述**: - -以下 RHI 抽象类在 `tests/RHI/unit/` 中完全没有测试: - -| 类 | 说明 | 影响 | -|----|------|------| -| `RHIPipelineState` | 管线状态对象 | 无法验证 PSO 创建、配置、绑定 | -| `RHIPipelineLayout` | 管线布局 | 无法验证 descriptor layout | -| `RHIDescriptorPool` | 描述符池 | Descriptor 管理完全未覆盖 | -| `RHIDescriptorSet` | 描述符集 | 无法验证 descriptor 更新 | -| `RHIRenderPass` | 渲染通道 | RenderPass API 完全未覆盖 | -| `RHIFramebuffer` | 帧缓冲 | Framebuffer API 完全未覆盖 | - -**修复建议 - 新增 `test_pipeline_state.cpp`**: - -```cpp -// 文件: tests/RHI/unit/test_pipeline_state.cpp -#include "fixtures/RHITestFixture.h" -#include "XCEngine/RHI/RHIPipelineState.h" -#include "XCEngine/RHI/RHIDevice.h" - -using namespace XCEngine::RHI; - -TEST_P(RHITestFixture, PipelineState_Create) { - GraphicsPipelineDesc desc = {}; - // 配置基本描述符 - RHIPipelineState* pso = GetDevice()->CreatePipelineState(desc); - ASSERT_NE(pso, nullptr); - pso->Shutdown(); - delete pso; -} - -TEST_P(RHITestFixture, PipelineState_SetGet_RasterizerState) { - GraphicsPipelineDesc desc = {}; - RHIPipelineState* pso = GetDevice()->CreatePipelineState(desc); - ASSERT_NE(pso, nullptr); - - RasterizerDesc rasterizer = {}; - rasterizer.cullMode = CullMode::Back; - pso->SetRasterizerState(rasterizer); - - EXPECT_EQ(pso->GetRasterizerState().cullMode, CullMode::Back); - pso->Shutdown(); - delete pso; -} - -TEST_P(RHITestFixture, PipelineState_SetGet_BlendState) { - // ... -} - -TEST_P(RHITestFixture, PipelineState_SetGet_DepthStencilState) { - // ... -} - -TEST_P(RHITestFixture, PipelineState_Finalize) { - GraphicsPipelineDesc desc = {}; - RHIPipelineState* pso = GetDevice()->CreatePipelineState(desc); - ASSERT_NE(pso, nullptr); - - bool finalized = pso->Finalize(); - EXPECT_TRUE(finalized); - EXPECT_TRUE(pso->IsFinalized()); - - pso->Shutdown(); - delete pso; -} - -TEST_P(RHITestFixture, PipelineState_Bind_Unbind) { - // ... -} -``` - -**修复建议 - 新增 `test_render_pass.cpp`**: - -```cpp -// 文件: tests/RHI/unit/test_render_pass.cpp -#include "fixtures/RHITestFixture.h" -#include "XCEngine/RHI/RHIRenderPass.h" - -using namespace XCEngine::RHI; - -TEST_P(RHITestFixture, RenderPass_Create) { - AttachmentDesc colorAttachment = {}; - colorAttachment.format = Format::R8G8B8A8_UNorm; - colorAttachment.loadOp = LoadAction::Clear; - colorAttachment.storeOp = StoreAction::Store; - - RHIRenderPass* renderPass = GetDevice()->CreateRenderPass(1, &colorAttachment, nullptr); - ASSERT_NE(renderPass, nullptr); - - EXPECT_EQ(renderPass->GetColorAttachmentCount(), 1); - renderPass->Shutdown(); - delete renderPass; -} - -TEST_P(RHITestFixture, RenderPass_BeginEnd) { - // 需要先创建 framebuffer - // cmdList->BeginRenderPass(renderPass, framebuffer, ...); - // cmdList->EndRenderPass(); -} -``` - -**修复建议 - 新增 `test_framebuffer.cpp`**: - -```cpp -// 文件: tests/RHI/unit/test_framebuffer.cpp -#include "fixtures/RHITestFixture.h" -#include "XCEngine/RHI/RHIFramebuffer.h" - -using namespace XCEngine::RHI; - -TEST_P(RHITestFixture, Framebuffer_Create) { - // 先创建 renderPass 和 texture - RHIRenderPass* renderPass = ...; - RHITexture* texture = ...; - RHIResourceView* rtv = GetDevice()->CreateRenderTargetView(texture, {}); - - RHIFramebuffer* fb = GetDevice()->CreateFramebuffer(renderPass, 1, &rtv, nullptr, 256, 256); - ASSERT_NE(fb, nullptr); - - EXPECT_EQ(fb->GetWidth(), 256); - EXPECT_EQ(fb->GetHeight(), 256); - EXPECT_TRUE(fb->IsValid()); - - fb->Shutdown(); - delete fb; -} -``` - -**修复建议 - 新增 `test_descriptor.cpp`**: - -```cpp -// 文件: tests/RHI/unit/test_descriptor.cpp -#include "fixtures/RHITestFixture.h" -#include "XCEngine/RHI/RHIDescriptorPool.h" -#include "XCEngine/RHI/RHIDescriptorSet.h" - -using namespace XCEngine::RHI; - -TEST_P(RHITestFixture, DescriptorPool_Create) { - DescriptorPoolDesc desc = {}; - desc.maxSets = 10; - desc.poolSize = 100; - - RHIDescriptorPool* pool = GetDevice()->CreateDescriptorPool(desc); - ASSERT_NE(pool, nullptr); - pool->Shutdown(); -} - -TEST_P(RHITestFixture, DescriptorSet_Allocate_Update) { - // 创建 pool - RHIDescriptorPool* pool = GetDevice()->CreateDescriptorPool(desc); - - // 创建 layout - DescriptorSetLayoutDesc layoutDesc = {}; - DescriptorSetLayoutBinding binding = {}; - binding.binding = 0; - binding.type = DescriptorType::CBV; - binding.count = 1; - layoutDesc.bindings = &binding; - layoutDesc.bindingCount = 1; - - RHIDescriptorSet* set = GetDevice()->CreateDescriptorSet(pool, layoutDesc); - ASSERT_NE(set, nullptr); - - EXPECT_EQ(set->GetBindingCount(), 1); - - set->Shutdown(); - pool->Shutdown(); -} -``` - ---- - -## 3. 中等问题(P1 - 应该修复) - -### 3.1 `tests/RHI/unit/test_command_list.cpp` - 大量测试传递 nullptr - -**问题描述**: - -很多 CommandList 测试只验证调用不崩溃,不验证实际功能: - -```cpp -// 这些测试传递 nullptr,无法验证真实功能 -TEST_P(RHITestFixture, CommandList_ClearRenderTarget) { - // ... 创建了 texture 但实际传递 nullptr - cmdList->ClearRenderTarget(static_cast(nullptr), color); - // 没有验证任何实际行为 -} - -TEST_P(RHITestFixture, CommandList_TransitionBarrier) { - // 创建了 texture 但传递 nullptr - cmdList->TransitionBarrier(static_cast(nullptr), ...); -} - -TEST_P(RHITestFixture, CommandList_SetVertexBuffer_WithResourceView) { - // 直接传递 nullptr - RHIResourceView* buffer = nullptr; - cmdList->SetVertexBuffers(0, 1, &buffer, nullptr, nullptr); -} -``` - -**缺少的真实测试**: - -| 测试内容 | 状态 | 建议 | -|----------|------|------| -| 有资源的 SetVertexBuffer | ❌ | 添加 | -| 有资源的 SetIndexBuffer | ❌ | 添加 | -| 有资源的 ClearRenderTarget | ❌ | 添加 | -| BeginRenderPass/EndRenderPass | ❌ | 添加 | -| Dispatch (计算着色器) | ❌ | 添加 | -| SetGraphicsDescriptorSets | ❌ | 添加 | -| SetComputeDescriptorSets | ❌ | 添加 | - -**修复建议**: - -```cpp -// 新增真实资源测试 -TEST_P(RHITestFixture, CommandList_SetVertexBuffer_WithRealBuffer) { - // 1. 创建 buffer - BufferDesc bufDesc = {}; - bufDesc.size = 1024; - RHIBuffer* buffer = GetDevice()->CreateBuffer(bufDesc); - ASSERT_NE(buffer, nullptr); - - // 2. 创建 cmdList 并设置 - RHICommandList* cmdList = GetDevice()->CreateCommandList({}); - cmdList->Reset(); - - RHIResourceView* bufferView = buffer->GetView(); - cmdList->SetVertexBuffers(0, 1, &bufferView, nullptr, nullptr); - - cmdList->Close(); - - // 3. 清理 - cmdList->Shutdown(); - buffer->Shutdown(); - delete buffer; - delete cmdList; -} - -TEST_P(RHITestFixture, CommandList_BeginEndRenderPass) { - // 1. 创建 renderPass - AttachmentDesc colorDesc = {}; - colorDesc.format = Format::R8G8B8A8_UNorm; - RHIRenderPass* renderPass = GetDevice()->CreateRenderPass(1, &colorDesc, nullptr); - ASSERT_NE(renderPass, nullptr); - - // 2. 创建 framebuffer - TextureDesc texDesc = {}; - texDesc.width = 256; - texDesc.height = 256; - RHITexture* texture = GetDevice()->CreateTexture(texDesc); - RHIResourceView* rtv = GetDevice()->CreateRenderTargetView(texture, {}); - - RHIFramebuffer* fb = GetDevice()->CreateFramebuffer(renderPass, 1, &rtv, nullptr, 256, 256); - - // 3. 测试 Begin/End - RHICommandList* cmdList = GetDevice()->CreateCommandList({}); - cmdList->Reset(); - - Rect renderArea = {0, 0, 256, 256}; - ClearValue clearValue = {}; - cmdList->BeginRenderPass(renderPass, fb, renderArea, 1, &clearValue); - cmdList->EndRenderPass(); - - cmdList->Close(); - - // 清理 - cmdList->Shutdown(); - fb->Shutdown(); - delete fb; - delete rtv; - texture->Shutdown(); - delete texture; - renderPass->Shutdown(); - delete renderPass; -} - -TEST_P(RHITestFixture, CommandList_Dispatch) { - // 1. 创建 compute shader - ShaderCompileDesc csDesc = {}; - RHIShader* computeShader = GetDevice()->CompileShader(csDesc); - // ... - - // 2. 创建并设置 compute pipeline - RHIPipelineState* computePSO = GetDevice()->CreatePipelineState(computeDesc); - computePSO->SetComputeShader(computeShader); - computePSO->Finalize(); - - // 3. 测试 Dispatch - RHICommandList* cmdList = GetDevice()->CreateCommandList({}); - cmdList->Reset(); - cmdList->SetPipelineState(computePSO); - cmdList->Dispatch(8, 8, 1); - cmdList->Close(); - - // 清理 - cmdList->Shutdown(); - computePSO->Shutdown(); - delete computePSO; - computeShader->Shutdown(); - delete computeShader; -} -``` - ---- - -### 3.2 D3D12 后端测试问题 - -| 文件 | 问题 | 建议 | -|------|------|------| -| `test_pipeline_state.cpp` | 只测试 struct 默认值 | 添加真实 PSO 创建测试 | -| `test_shader.cpp` | 只做字符串比较 | 添加 shader 编译测试 | -| `test_root_signature.cpp` | 只测试空签名和 CBV | 添加复杂参数测试 | -| `test_command_list.cpp` | 只有 2 个测试 | 补充 Reset/ClearRenderTargetView 等 | - -**修复建议 - `test_pipeline_state.cpp`**: - -```cpp -// 当前只有 trivial 测试 -TEST_F(D3D12TestFixture, PipelineState_Get_GraphicsPipelineDescDefaults) { - D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = {}; - // 只验证 struct 默认值 - EXPECT_EQ(psoDesc.PrimitiveTopologyType, ...); -} - -// 应该添加 -TEST_F(D3D12TestFixture, PipelineState_Create_GraphicsPipeline) { - // 1. 创建 root signature - D3D12_ROOT_SIGNATURE_DESC rootSigDesc = {}; - D3D12RootSignature rootSig; - rootSig.Initialize(GetDevice()->GetDevice(), rootSigDesc); - - // 2. 创建 shader (需要 HLSL 源码或编译好的 bytecode) - D3D12Shader* vs = new D3D12Shader(); - vs->CompileFromFile(L"shaders/vertex.cso"); - - // 3. 创建 PSO - D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = {}; - psoDesc.pRootSignature = rootSig.GetRootSignature(); - psoDesc.VS = vs->GetBytecode(); - // ... 设置其他字段 - - ID3D12PipelineState* d3d12PSO = nullptr; - HRESULT hr = GetDevice()->GetDevice()->CreateGraphicsPipelineState( - &psoDesc, IID_PPV_ARGS(&d3d12PSO)); - ASSERT_EQ(hr, S_OK); - - d3d12PSO->Release(); - rootSig.Shutdown(); -} -``` - ---- - -### 3.3 OpenGL 后端测试问题 - -| 文件 | 问题 | 建议 | -|------|------|------| -| `test_command_list.cpp` | 缺少 SetShader/SetUniform 测试 | 添加 | -| `test_shader.cpp` | 无 compute shader 测试 | 添加 GL_COMPUTE_SHADER | -| 无 compute 相关测试 | 缺少 Dispatch 测试 | 添加 | - -**修复建议**: - -```cpp -// 新增 test_compute.cpp -TEST_F(OpenGLTestFixture, ComputeShader_Compile) { - const char* computeSrc = R"( - #version 460 - layout(local_size_x = 1, local_size_y = 1) in; - void main() { - // compute shader - } - )"; - - OpenGLShader computeShader; - bool compiled = computeShader.CompileCompute(computeSrc); - EXPECT_TRUE(compiled); -} - -TEST_F(OpenGLTestFixture, CommandList_Dispatch) { - // 编译 compute shader - OpenGLShader computeShader; - computeShader.CompileCompute(computeSrc); - - // 创建 pipeline state - OpenGLPipelineState pipeline; - pipeline.SetComputeShader(&computeShader); - pipeline.Apply(); - - // 测试 Dispatch - OpenGLCommandList cmdList; - cmdList.Reset(); - cmdList.SetPipelineState(&pipeline); - cmdList.Dispatch(8, 8, 1); - cmdList.Close(); -} -``` - ---- - -## 4. 测试覆盖度矩阵 - -### 4.1 RHI 抽象层覆盖度 - -| RHI 抽象接口 | 覆盖状态 | 备注 | -|--------------|----------|------| -| Device 创建/初始化 | ✅ 已覆盖 | | -| Buffer 创建/映射 | ✅ 已覆盖 | | -| Texture 创建 | ✅ 已覆盖 | | -| SwapChain | ✅ 已覆盖 | | -| CommandList 基础 | ⚠️ 太弱 | 大部分传递 nullptr | -| CommandQueue | ✅ 已覆盖 | | -| Fence | ✅ 已覆盖 | | -| Sampler | ✅ 已覆盖 | | -| Shader 编译 | ✅ 已覆盖 | 7个有效测试,使用内嵌源码 | -| **PipelineState** | ✅ 已覆盖 | 10个测试,已完成 | -| **PipelineLayout** | ❌ 无测试 | **必须添加** | -| **DescriptorPool** | ✅ 已覆盖 | 8个测试,已完成 | -| **DescriptorSet** | ⚠️ 部分覆盖 | OpenGL 需要 GL context | -| **RenderPass** | ✅ 已覆盖 | 10个测试,已完成 | -| **Framebuffer** | ✅ 已覆盖 | 8个测试,已完成 | -| **Compute/Dispatch** | ✅ 已覆盖 | 8个测试,已完成 | - -### 4.2 D3D12 后端覆盖度 - -| D3D12 特定功能 | 覆盖状态 | 备注 | -|----------------|----------|------| -| 设备特性检测 | ✅ 已覆盖 | | -| Descriptor Heap | ✅ 已覆盖 | | -| Command Allocator | ✅ 已覆盖 | | -| Fence Timeline | ✅ 已覆盖 | | -| RootSignature | ⚠️ 弱 | 只测试空签名和 CBV | -| PipelineState | ⚠️ 弱 | 只测试 struct 默认值 | -| Shader | ⚠️ 弱 | 只做字符串比较 | -| SwapChain | ✅ 已覆盖 | | -| Views (RTV/DSV) | ✅ 已覆盖 | | - -### 4.3 OpenGL 后端覆盖度 - -| OpenGL 特定功能 | 覆盖状态 | 备注 | -|----------------|----------|------| -| Buffer (VBO/IBO/UBO) | ✅ 已覆盖 | | -| Texture (2D/Cube) | ✅ 已覆盖 | | -| VertexArray | ✅ 已覆盖 | | -| Shader 编译 | ✅ 已覆盖 | | -| PipelineState | ✅ 已覆盖 | | -| Sampler | ✅ 已覆盖 | | -| Fence | ✅ 已覆盖 | | -| SwapChain | ✅ 已覆盖 | | -| **Compute Shader** | ❌ 无测试 | **必须添加** | -| **Dispatch** | ❌ 无测试 | **必须添加** | -| Memory Barrier | ❌ 无测试 | 可选 | - ---- - -## 5. 重构优先级总结 - -### P0 - 必须修复(影响功能验证) - -| 优先级 | 问题 | 工作量 | 影响 | 状态 | -|--------|------|--------|------|------| -| 1 | Shader 测试重构 | 中 | 9个测试无效 | ✅ 已完成 | -| 2 | 添加 PipelineState 测试 | 大 | RHI 核心组件无测试 | ✅ 已完成 | -| 3 | 添加 RenderPass 测试 | 中 | 重要 API 未覆盖 | ✅ 已完成 | -| 4 | 添加 Framebuffer 测试 | 中 | 重要 API 未覆盖 | ✅ 已完成 | - -### P1 - 应该修复(提高覆盖率) - -| 优先级 | 问题 | 工作量 | 影响 | 状态 | -|--------|------|--------|------|------| -| 5 | 添加 DescriptorPool 测试 | 中 | 已实现未测试 | ✅ 已完成 (DescriptorPool) | -| 5b | DescriptorSet 测试 | 中 | OpenGL 需要 GL context,暂跳过 | ⚠️ 待完成 | -| 6 | CommandList 测试增强 | 大 | 大部分传递 nullptr | ⏳ 待完成 | -| 7 | 添加 Compute/Dispatch 测试 | 中 | 重要功能缺失 | ✅ 已完成 | -| 8 | D3D12 PSO/shader 测试增强 | 小 | 当前测试 trivial | ⏳ 待完成 | - -### P2 - 可以修复(完善细节) - -| 优先级 | 问题 | 工作量 | 影响 | -|--------|------|--------|------| -| 9 | OpenGL CommandList SetShader 测试 | 小 | 缺失测试 | -| 10 | 复杂 RootSignature 参数测试 | 小 | 当前只测试 CBV | -| 11 | OpenGL Compute Shader 测试 | 小 | 缺失测试 | - ---- - -## 6. 新增测试文件清单 - -| 文件路径 | 测试内容 | 优先级 | 状态 | -|----------|----------|--------|------| -| `tests/RHI/unit/test_pipeline_state.cpp` | PipelineState 创建/配置/绑定 | P0 | ✅ 已完成 | -| `tests/RHI/unit/test_render_pass.cpp` | RenderPass 创建/Begin/End | P0 | ✅ 已完成 | -| `tests/RHI/unit/test_framebuffer.cpp` | Framebuffer 创建/绑定 | P0 | ✅ 已完成 | -| `tests/RHI/unit/test_descriptor.cpp` | DescriptorPool 创建/更新 | P1 | ✅ DescriptorPool完成, Set待完成 | -| `tests/RHI/unit/test_compute.cpp` | Compute shader/Dispatch | P1 | ✅ 已完成 | -| `tests/RHI/unit/test_pipeline_layout.cpp` | PipelineLayout 创建 | P2 | ⏳ 待完成 | - ---- - -## 7. 测试质量改进建议 - -### 7.1 测试命名规范 - -遵循 `Component_Category_SubBehavior` 格式: - -``` -Good: -- Buffer_Create_DefaultHeap -- CommandList_SetViewport_ValidRect -- Shader_Compile_ValidSource - -Bad: -- Test1 -- BufferTest -- test_buffer -``` - -### 7.2 测试结构规范 - -每个测试应包含: - -```cpp -TEST_P(RHITestFixture, CommandList_SetVertexBuffer_WithRealBuffer) { - // 1. Arrange - 准备测试数据 - BufferDesc bufDesc = {}; - bufDesc.size = 1024; - RHIBuffer* buffer = GetDevice()->CreateBuffer(bufDesc); - ASSERT_NE(buffer, nullptr); // 使用 ASSERT 防止空指针解引用 - - // 2. Act - 执行被测操作 - RHICommandList* cmdList = GetDevice()->CreateCommandList({}); - cmdList->Reset(); - RHIResourceView* view = buffer->GetView(); - cmdList->SetVertexBuffers(0, 1, &view, nullptr, nullptr); - cmdList->Close(); - - // 3. Assert - 验证结果 - // 注意:有些测试难以直接验证结果,可以通过不崩溃来间接验证 - - // 4. Cleanup - 清理资源 - cmdList->Shutdown(); - delete cmdList; - buffer->Shutdown(); - delete buffer; -} -``` - -### 7.3 避免的问题 - -```cpp -// 问题1: 测试逻辑完全相同 -TEST_P(RHITestFixture, Shader_Test1) { shader = nullptr; EXPECT_EQ(shader, nullptr); } -TEST_P(RHITestFixture, Shader_Test2) { shader = nullptr; EXPECT_EQ(shader, nullptr); } // 相同! - -// 问题2: 传递 nullptr 不验证功能 -TEST_P(RHITestFixture, CommandList_Test) { - cmdList->ClearRenderTarget(nullptr, color); // 无意义 -} - -// 问题3: 不清理资源 -TEST_P(RHITestFixture, Buffer_Test) { - RHIBuffer* buffer = device->CreateBuffer(desc); - // 忘记 buffer->Shutdown() 和 delete buffer -} - -// 问题4: 缺少 ASSERT -TEST_P(RHITestFixture, Buffer_Test) { - RHIBuffer* buffer = device->CreateBuffer(desc); - buffer->SetData(...); // 如果 buffer 是 nullptr 则崩溃 - // 应该先 ASSERT_NE(buffer, nullptr); -} -``` - ---- - -## 8. 附录:测试执行命令 - -```bash -# 增量构建 RHI 测试 -cmake --build . --target rhi_unit_tests --config Debug -cmake --build . --target rhi_d3d12_tests --config Debug -cmake --build . --target rhi_opengl_tests --config Debug - -# 运行 RHI 抽象层测试 (同时验证 D3D12 和 OpenGL) -ctest -R "^D3D12/|^OpenGL/" -C Debug --output-on-failure - -# 运行 D3D12 后端专用测试 -ctest -R "D3D12TestFixture|SwapChainTestFixture" -C Debug --output-on-failure - -# 运行 OpenGL 后端专用测试 -ctest -R "OpenGLTestFixture" -C Debug --output-on-failure - -# 运行所有 RHI 测试 -ctest -R "D3D12|OpenGL|RHITestFixture" -C Debug --output-on-failure -``` - ---- - -**文档版本**: 1.4 -**最后更新**: 2026-03-25 -**作者**: XCEngine Team - -## 更新日志 - -### v1.4 (2026-03-25) -- P1-7: Compute/Dispatch 测试 ✅ 已完成 - - 新增 `test_compute.cpp`,8个测试 - - 测试通过:D3D12 8 + OpenGL 8 = 16 tests - - 修复 shader type bugs: - - `D3D12Shader::Compile` 不设置 `m_type` - - `OpenGLShader::Compile(const void*,...)` 硬编码 Fragment - - `OpenGLShader::CompileCompute` 不设置 `m_type` - - 修复 `D3D12CommandList::SetPipelineState` 对 Compute PSO 用错 handle -- 测试结果:D3D12 116测试 + OpenGL 116测试 = 232测试全部通过 -- 集成测试:8/8 全部通过 - -### v1.3 (2026-03-25) -- P1-5: DescriptorPool 测试 ✅ 已完成 - - 新增 `test_descriptor.cpp`,8个 DescriptorPool 测试 - - 测试通过:D3D12 8 + OpenGL 8 = 16 tests - - DescriptorSet 测试暂跳过(OpenGL 需要 GL context) -- 测试结果:D3D12 108测试 + OpenGL 108测试 = 216测试全部通过 -- 集成测试:8/8 全部通过 - -### v1.2 (2026-03-25) -- P0-3: RenderPass 测试 ✅ 已完成 - - 新增 `test_render_pass.cpp`,10个测试 - - 添加 `CreateRenderPass/CreateFramebuffer` 到 `RHIDevice` 接口 - - 实现 D3D12 和 OpenGL 后端 - - 修复 D3D12 depth stencil 资源创建(自动设置 `D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL`) - - 修复 D3D12 RTV/DSV 空指针检查 -- P0-4: Framebuffer 测试 ✅ 已完成 - - 新增 `test_framebuffer.cpp`,8个测试 - - 修复 `D3D12RenderPass` 的 `StoreAndResolve` 映射问题 -- 测试结果:D3D12 100测试 + OpenGL 100测试 = 200测试全部通过 -- 集成测试:8/8 全部通过 - -### v1.1 (2026-03-25) -- P0-1: Shader 测试重构 ✅ 已完成 - - 添加 `ShaderLanguage` 枚举 - - 扩展 `ShaderCompileDesc` 支持内嵌源码 - - 9个相同测试 → 7个有效测试 -- P0-2: PipelineState 测试 ✅ 已完成 - - 新增 `test_pipeline_state.cpp` - - 修复 PSO 验证接口:`IsFinalized/Finalize` → `IsValid/EnsureValid` - - 10个测试覆盖创建、配置、状态查询、生命周期 - -### v1.0 (2026-03-25) -- 初始版本 diff --git a/docs/used/Renderer_C++层第一阶段收口计划_2026-04-13_晚间.md b/docs/used/Renderer_C++层第一阶段收口计划_2026-04-13_晚间.md deleted file mode 100644 index 9dd2dc8c..00000000 --- a/docs/used/Renderer_C++层第一阶段收口计划_2026-04-13_晚间.md +++ /dev/null @@ -1,310 +0,0 @@ -# Renderer C++层第一阶段收口计划 - -日期: `2026-04-13` - -## 1. 文档定位 - -这份文档是 `Render Graph` 之前的最后一轮 `native render kernel v1` 收口计划。 - -目标不是继续往当前 `Rendering` 层里塞新功能,而是先把下面几件事彻底做清楚: - -- `request -> frame plan -> execution` 三层语义分开。 -- `RenderSceneExtractor -> CullingResults -> RendererList -> DrawSettings` 这条数据组织链正式化。 -- `BuiltinForwardPipeline` 从“大杂烩渲染器”收成“协调器 + 子能力”。 -- `Editor` 注入点、阴影、主场景、后处理之间的边界说清楚。 -- 给下一步 `C++ Render Graph` 留出稳定输入,而不是把当前混乱直接 graph 化。 - -结论先写在前面: - -- `Render Graph` 应该做在 `C++ native` 层。 -- 现在还不应该直接开做 `Render Graph` 或 `Deferred Renderer`。 -- 当前更优先的是把现有渲染层整理干净,再往 SRP/URP 方向演进。 - -## 2. 当前阶段已经完成的收口 - -截至 `2026-04-13` 晚间,已经落地的内容: - -- `CameraRenderRequest -> CameraFramePlan` 已经分层,`CameraRenderer` 开始正式消费 plan。 -- `CullingResults / RendererList` contract 已经进入 `RenderSceneData`。 -- `Forward / Depth / ObjectId` 已经优先走 `RendererList`,旧 `visibleItems` 只保留过渡 fallback。 -- `RendererListUtils` 已经把可见项遍历规则收口成公共工具。 -- `Gaussian / Volumetric` 已经从 `BuiltinForwardPipeline` 中抽成统一 `SceneRenderFeaturePass`。 -- `FrameExecutionContext / ScenePhase / DrawSettings` 已经引入。 -- `RenderPipeline` 已经支持新的 `FrameExecutionContext` 入口,旧三参数入口保留为兼容适配层。 -- `BuiltinForwardPipeline` 已经开始按 scene phase 组织主场景执行。 -- `CameraRenderer` 主场景阶段已经走新的 execution context,而不是继续只传三参数。 - -本轮验证结果: - -- `XCEditor` 已重新编译通过。 -- `rendering_unit_tests` 已重新编译通过。 -- `editor_tests` 已重新编译通过。 -- `editor_tests --gtest_filter=*ViewportRenderFlow*:*SceneViewportRenderPassBundle* --gtest_brief=1` 通过 `14/14`。 -- `rendering_unit_tests --gtest_filter=*BuiltinForwardPipeline*:*RenderPass*:*CameraSceneRenderer*:*Shadow*:*ObjectId* --gtest_brief=1` 通过 `67/68`。 -- 唯一失败仍然是既有老问题: - `BuiltinForwardPipeline_Test.OpenGLRuntimeTranspilesForwardShadowVariantToLegacyClipConventions` - -## 3. 为什么现在还不能直接上 Render Graph - -现在直接上 `Render Graph`,本质上是在 graph 里继续承接当前的隐式耦合,问题只会换个位置存在。 - -当前还没完全收口的核心问题不是“没有 graph”,而是下面这些边界还不够正式: - -- `CameraRenderer` 和各类 scene pass 之间,谁负责组织阶段,谁负责执行,还没有完全模块化。 -- `Shadow` 相关逻辑仍然分散在 planner、camera execution、pipeline、surface cache 几处。 -- `BuiltinForwardPipeline` 虽然已经开始瘦身,但还没有彻底收成一个明确的 coordinator。 -- `Editor` 的扩展点虽然已可用,但 contract 还偏“约定式”,还不够像以后给 `C# SRP` 暴露的正式接口。 -- 目录结构仍然在暴露旧历史:一些“子系统”目前还是文件堆,不是真正的模块。 - -如果这些问题不先解决,`Render Graph` 只会变成一个新的承压层,后面再拆更贵。 - -## 4. Render Graph 和未来 SRP/URP 的边界 - -未来的推荐结构仍然是这条线: - -`RHI -> Native Render Kernel -> Render Graph -> SRP-like Contract -> Universal Renderer -> C# Custom Pipeline` - -其中边界应当明确成这样: - -- `C++ native` 保留: - - `Render Graph` - - `CullingResults / RendererList` - - draw / dispatch context - - GPU resource cache - - shadow map / atlas 执行能力 - - Gaussian 排序与 working set - - Volume 资源上传与底层执行 -- `URP-like` 包层保留: - - feature 编排 - - pass 注入顺序 - - renderer feature 配置 - - 用户级 pipeline 组合 - - 未来的 `C#` 自定义渲染管线 API - -这意味着: - -- 阴影、Gaussian、Volumetric 不是整块搬去包层。 -- 包层更像 Unity 的 `URP renderer` 和 `renderer feature`。 -- native 层更像 Unity 没公开给用户、但真正负责执行和图资源生命周期的内核。 - -## 5. 第一阶段还差哪些工作才能收口 - -### 5.1 Shadow 子系统正式化 - -这是当前最值得先做的一块。 - -现状问题: - -- 阴影规划在 `Planning`。 -- 阴影 surface/cache 在 `Execution/Caches`。 -- 阴影执行又是 standalone pass。 -- 主场景采样阴影的状态切换仍在 `BuiltinForwardPipeline`。 - -这说明阴影现在“能跑”,但还不是一个正式子系统。 - -收口目标: - -- 明确 `shadow planning / shadow resources / shadow execution / shadow sampling contract` 四层边界。 -- 把方向光阴影相关共享数据收成一组正式类型,而不是散落在多个调用点上。 -- 为以后扩展 `cascades / shadow atlas / additional light shadows / baked shadowmask` 留出位置。 - -### 5.2 Editor 注入点正式化 - -现状问题: - -- 当前有 `pre scene / post scene / overlay / object id / outline / grid` 多种 editor 路径。 -- 这些能力已经能工作,但还没有统一成“正式 stage 注入 contract”。 - -收口目标: - -- 明确哪些注入点属于 runtime 正式阶段,哪些属于 editor-only extension。 -- 让 `CameraFramePlan` 对这些阶段的描述更加稳定。 -- 给以后 `C# renderer feature` 的注入点设计提供 native 对照物。 - -### 5.3 BuiltinForwardPipeline 继续瘦身 - -本轮已经完成第一刀,但还没收完。 - -还需要继续做的事情: - -- 把 forward scene phase 执行和底层 draw/resource 逻辑彻底分开。 -- 把 forward renderer 自己的内部资源、skybox、feature 协调代码进一步收拢。 -- 保证 `BuiltinForwardPipeline` 自己只表达“阶段顺序和协调”,不再继续膨胀。 - -### 5.4 旧 fallback 的裁边 - -当前有一些过渡兼容层是故意保留的: - -- `RenderPipeline::Render(context, surface, sceneData)` 旧入口 -- `visibleItems` fallback -- `BuildRenderRequests` 旧习惯入口 - -这些现在不该硬删,因为会影响现有 tests 和 editor 链路。 - -但第一阶段收口前,至少要做到: - -- 新主链默认只走正式 contract。 -- 旧入口只作为 adapter,不再新增依赖。 -- 每个 fallback 都有明确退出条件。 - -### 5.5 测试和文档补齐 - -当前已有回归还不够系统。 - -还需要补的重点测试: - -- `FrameExecutionContext` 是否完整透传 source surface / source color view / state。 -- `BuiltinForwardPipeline` scene phase 顺序是否稳定。 -- feature pass 的 `Prepare -> Execute` 顺序是否稳定。 -- editor 注入点在有后处理和无后处理两种情况下是否仍能保持正确 source/destination surface。 -- shadow planning 和 main scene shadow sampling 之间的契约测试。 - -## 6. 下一步最优先该做什么 - -下一步最优先建议做: - -`Shadow 子系统收口` - -原因很直接: - -- 它横跨 `Planning / Execution / Pipeline / Resources / Sampling`。 -- 它是以后 `Deferred / Light Baking / ShadowMask / Additional Light Shadows` 的共同基础。 -- 它也是未来 `Render Graph` 最敏感的一类资源依赖链。 -- 如果阴影先不收,后面 graph 化时 barrier、resource lifetime、pass dependency 都会更乱。 - -建议顺序: - -1. 先把方向光阴影的 plan/data/resource/execute contract 固化。 -2. 再把 editor 注入 contract 固化。 -3. 然后做第一轮目录收拢。 -4. 最后判断这一阶段是否可以正式收口,进入 `Render Graph` 设计。 - -## 7. 当前 Rendering 目录结构存在的问题 - -### 7.1 `FrameData` 目录太杂 - -现在 `FrameData` 里同时混着: - -- camera/environment 数据 -- scene snapshot -- visible item -- culling results -- renderer list utils - -这说明它不是一个明确模块,而是“跟帧有关的先放这”。 - -建议方向: - -- `FrameData` 保留纯只读帧快照类型。 -- 把 `CullingResults / RendererList / Filtering / Sorting / DrawSettings` 逐步收进更明确的 `Execution` 或 `Culling` 子域。 -- `RendererListUtils` 这种执行辅助逻辑,不适合长期留在 `FrameData`。 - -### 7.2 `Passes` 目录混合了三类东西 - -现在 `Passes` 里同时有: - -- 主场景 pass -- fullscreen/post-process pass -- editor pass - -这会让“runtime 正式渲染能力”和“editor 辅助渲染能力”继续混在一起。 - -建议方向: - -- `Passes/Scene` -- `Passes/Fullscreen` -- `Passes/Editor` -- `Passes/Shadow` - -不一定要一次性全搬,但后面新增文件不应继续平铺。 - -### 7.3 `BuiltinForwardPipeline` 还没形成真正子模块 - -当前 forward 相关代码已经分散在: - -- `BuiltinForwardPipeline.h/.cpp` -- `BuiltinForwardPipelineSkybox.cpp` -- `Internal/BuiltinForwardPipelineResources.cpp` - -这已经具备“应该单独成目录”的条件。 - -建议方向: - -- `Rendering/Pipelines/BuiltinForward/` - - `BuiltinForwardPipeline.h` - - `BuiltinForwardPipeline.cpp` - - `BuiltinForwardPipelineScene.cpp` - - `BuiltinForwardPipelineResources.cpp` - - `BuiltinForwardPipelineSkybox.cpp` - -这样后面 forward / deferred / universal renderer 才能并列演进。 - -### 7.4 `Shadow` 目录还不算真正的 shadow 子系统 - -目前虽然已经有 `Rendering/Shadow`,但从全链路看,阴影的责任仍然散在其他目录。 - -建议方向: - -- `Shadow` 目录最终至少容纳: - - planning types - - shared frame data - - runtime resources / caches - - shadow executor or shadow renderer - -### 7.5 `Internal` 边界不稳定 - -现在 `src/Rendering/Internal` 更像历史遗留缓冲区。 - -长期风险: - -- 真正属于 forward 的内部实现放在全局 `Internal` -- 真正属于 volume / splat / shadow 的内部实现也可能继续堆进去 - -建议方向: - -- 全局 `Internal` 只保留跨模块公共 helper。 -- 各子系统自己的内部文件放回各自目录。 - -## 8. 建议的第一轮目录收拢动作 - -这一轮只做低风险收拢,不做大搬家。 - -建议动作: - -- 把 `BuiltinForwardPipeline*` 相关实现收成 `Pipelines/BuiltinForward/` 子目录。 -- 给 `Passes` 至少先分出 `Editor` 和 `Fullscreen` 两个子目录。 -- 给 `Shadow` 补一组正式 shared types,再决定是否移动更多实现文件。 -- 暂时不大动 `include` 层 public 路径,先保证编译和引用稳定。 - -原则: - -- 先按职责拆,再按目录搬。 -- 不为了“好看”做纯目录手术。 -- 每次收拢都必须带编译和回归。 - -## 9. 第一阶段收口完成的判定标准 - -满足下面这些条件,就可以认为 `Render Graph` 之前这一阶段基本收口: - -- `CameraFramePlan` 成为主执行入口,旧 request 入口只保留兼容壳。 -- `FrameExecutionContext / ScenePhase / DrawSettings` 成为主场景执行正式 contract。 -- `CullingResults / RendererList` 成为主要 draw organization contract。 -- `BuiltinForwardPipeline` 被收成 coordinator,而不是继续长成总垃圾堆。 -- `Shadow` 成为清晰子系统,而不是散点逻辑。 -- `Editor` 注入点形成稳定 contract。 -- 目录结构不再继续鼓励“新逻辑直接往老文件里塞”。 -- 相关 tests 和编译链稳定。 - -到这一步,再开始 `C++ Render Graph`,代价才是可控的。 - -## 10. 当前建议 - -当前最佳路线不变: - -- 短期:继续按 Unity 的 `SRP + URP` 方向做 native 基座。 -- 中期:先在 `C++` 做出稳定的 `Render Graph + RendererList + Feature Injection` 内核。 -- 中长期:再暴露 `C#` 自定义渲染管线接口,做你自己的 `URP-like` 包层。 - -眼下最正确的动作不是“赶紧补 deferred”,也不是“先把所有特效搬去包层”,而是: - -`先把当前 native rendering layer 整理成一个真正可扩展的内核。` diff --git a/docs/used/Renderer_C++层第一阶段重构计划_RenderGraph前_2026-04-13.md b/docs/used/Renderer_C++层第一阶段重构计划_RenderGraph前_2026-04-13.md deleted file mode 100644 index 594cc027..00000000 --- a/docs/used/Renderer_C++层第一阶段重构计划_RenderGraph前_2026-04-13.md +++ /dev/null @@ -1,462 +0,0 @@ -# Renderer C++层第一阶段重构计划(Render Graph 前) - -日期:`2026-04-13` - -## 1. 文档定位 - -这份计划只处理 `Render Graph` 之前的那一步,也就是把当前 `Rendering` 原生主链先整理成一个可长期演进的正式架构。 - -这一步的目标不是新增大功能,而是先把下面这些边界收口: - -1. `SceneRenderer / CameraRenderer / SceneRenderRequestPlanner` 的职责边界 -2. `RenderSceneExtractor / RenderSceneData / VisibleRenderItem` 的数据边界 -3. `RenderPipeline / RenderPass / BuiltinForwardPipeline` 的执行边界 -4. Runtime 主链与 Editor 注入点的边界 - -这一步做完之后,下一阶段才适合引入真正的 `Render Graph`。 - ---- - -## 2. 关于 Render Graph 的结论 - -结论很明确: - -1. `Render Graph` 应该先做在 `C++ native` 层 -2. 它不应该直接作为第一阶段的一部分 -3. 第一阶段的任务,是先把当前 native renderer 收口到足够清晰,避免后面只是把现有混乱机械搬进 graph - -后面的目标结构应该是: - -`RHI -> Native Render Kernel -> Render Graph -> SRP-like Contract -> Universal Renderer -> C# Custom Pipeline` - -其中: - -1. `Render Graph` 属于 `Native Render Kernel` -2. `C#` 层以后可以编排 pass,但图资源生命周期、资源状态、barrier、跨 pass 读写依赖仍应先由 native graph 掌控 - ---- - -## 3. 当前代码里的真实主链 - -当前项目并不是没有架构,而是已经形成了一条手工编排的 mini-SRP 主链: - -1. `SceneRenderRequestPlanner` - - 收集 camera - - 生成 `CameraRenderRequest` - - 负责方向光阴影规划参数 -2. `SceneRenderer` - - 调 planner - - 解析 final color policy - - 为 post-process / final-output 附加 fullscreen stage request -3. `CameraRenderer` - - 做 shadow request resolve - - 提取 `RenderSceneData` - - 按固定 stage 顺序执行 -4. `BuiltinForwardPipeline` - - 负责主场景绘制 - - 同时背着 shader 绑定、PSO 缓存、材质资源解析、skybox、splat、volumetric 等大量职责 - -对应的当前入口主要是: - -1. `engine/include/XCEngine/Rendering/Execution/SceneRenderer.h` -2. `engine/include/XCEngine/Rendering/Execution/CameraRenderer.h` -3. `engine/include/XCEngine/Rendering/Planning/SceneRenderRequestPlanner.h` -4. `engine/include/XCEngine/Rendering/Planning/CameraRenderRequest.h` -5. `engine/include/XCEngine/Rendering/FrameData/RenderSceneData.h` -6. `engine/include/XCEngine/Rendering/FrameData/VisibleRenderItem.h` -7. `engine/include/XCEngine/Rendering/RenderPipeline.h` -8. `engine/include/XCEngine/Rendering/RenderPipelineAsset.h` -9. `engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h` -10. `engine/src/Rendering/Execution/SceneRenderer.cpp` -11. `engine/src/Rendering/Execution/CameraRenderer.cpp` -12. `engine/src/Rendering/Extraction/RenderSceneExtractor.cpp` -13. `engine/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp` -14. `engine/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineResources.cpp` - ---- - -## 4. 当前真正的问题 - -当前主要不是“功能太少”,而是“职责层次还不够正式”。 - -### 4.1 request、plan、execution 混在一起 - -`CameraRenderRequest` 现在既像外部请求,又像内部执行计划,还夹带了一部分 runtime surface / fullscreen chain 组织信息。 -`SceneRenderer` 也同时承担了 request build、final color resolve、fullscreen intermediate surface ownership 这几类事情。 - -这样的问题是: - -1. 后面很难区分“用户想渲染什么”和“引擎决定怎么执行” -2. Editor 也会继续直接依赖底层 stage 细节 -3. Render Graph 以后很难接到一个稳定的 frame plan 输入 - -### 4.2 extraction 数据还偏早期 - -`RenderSceneExtractor` 现在能工作,但产物仍然偏“把可见对象收集成数组”。 -它还没有正式长成后续 SRP / Render Graph 真正需要的那种中间层: - -1. `CullingResults` -2. `RendererList` -3. `FilteringSettings` -4. `SortingSettings` -5. `DrawSettings` - -如果这层不先正式化,后面 forward、deferred、shadow、object-id、editor overlay 都会继续各自拿 `visibleItems` 做自己的隐式解释。 - -### 4.3 BuiltinForwardPipeline 过重 - -当前 `BuiltinForwardPipeline` 不只是一个 renderer,它已经同时包含: - -1. 主场景 pass 编排 -2. 材质资源布局解析 -3. descriptor set 组织 -4. pipeline state cache -5. lighting / shadow 绑定 -6. skybox 逻辑 -7. gaussian splat 特殊路径 -8. volumetric 特殊路径 - -这会带来两个后果: - -1. 后面任何主场景新能力都会继续往这个类里堆 -2. 未来要抽 `Universal Renderer` 或切出 `Deferred` 时,拆分成本会很高 - -### 4.4 runtime 主线和 editor 扩展点还没正式隔离 - -现在 Editor 已经深度依赖 request stage 注入点,这个方向本身没错,但 contract 还不够正式。 -如果不先收口,后面 graph 化时 editor 特殊路径会变成额外复杂度来源。 - -### 4.5 pipeline contract 还不够像未来的 SRP 底座 - -当前 `RenderPipeline` 还是: - -`Render(context, surface, sceneData)` - -这个接口对当前 builtin forward 足够,但对未来这些东西不够: - -1. renderer list -2. graph resource declaration -3. per-frame context -4. pass feature 注入 -5. 多渲染路径共存 - -第一阶段不要求一步到位,但至少要把它朝这个方向整理。 - ---- - -## 5. 本阶段明确不做的事 - -为了保证收口不失控,这一阶段明确不做下面这些: - -1. 不引入真正运行时 `Render Graph` -2. 不做完整 `Deferred Renderer` -3. 不做 `C# SRP` 暴露 -4. 不重写 RHI -5. 不新增大型渲染特性来打断架构收口 - -本阶段只做一件事: - -`把当前 native rendering layer 整理成 Render Graph 之前的正式形态` - ---- - -## 6. 第一阶段完成后应该达到什么状态 - -本阶段完成后,native renderer 至少应满足下面这些条件: - -1. 外部 request、内部 frame plan、实际 execution 三层语义分开 -2. extraction / culling / draw organization 有正式中间层,不再只靠裸 `visibleItems` -3. `BuiltinForwardPipeline` 明显瘦身,不再继续当总垃圾桶 -4. runtime pass 与 editor pass 有明确注入 contract -5. 后续 `Render Graph` 能接一个稳定的 `Frame Plan + Renderer Lists + Resource Needs` 输入 - ---- - -## 7. 第一阶段的重构原则 - -### 7.1 先 formalize,再 graphize - -先把 contract 和边界做清楚,再做 graph。 -不要反过来。 - -### 7.2 不破坏现有主链闭环 - -当前 `Scene / Game / Editor Viewport` 已经能跑通,第一阶段不能为了“好看”把现有可运行链条打散。 - -### 7.3 新中间层优先 native internal - -这一阶段新补的 `Frame Plan`、`CullingResults`、`RendererList`、`DrawSettings` 都先是 C++ 内部契约,不急着暴露给 C#。 - -### 7.4 先解耦职责,再决定目录长相 - -不要先做大范围目录搬家。 -先把职责拆开,目录只是最后的外显结果。 - -### 7.5 editor contract 正式化,但不去特殊化主链 - -Editor 的 object-id、outline、grid、overlay 依然要保留,但它们应该依附在正式 contract 上,而不是继续共享 runtime 的隐式细节。 - ---- - -## 8. 工作包拆分 - -## 工作包 A:request / frame plan 分层 - -目标: - -把“用户想渲染什么”和“引擎这帧怎么执行”拆开。 - -建议结果: - -1. 保留 `CameraRenderRequest` 作为外部请求描述 -2. 新增内部 `CameraFramePlan` 或等价类型,承载: - - 实际启用的 stage - - fullscreen chain - - resolved target/surface - - 阴影计划结果 - - editor 注入点 -3. `SceneRenderer` 只负责: - - 请求收集 - - 请求排序 - - request -> frame plan 转换 -4. `CameraRenderer` 只负责: - - frame plan 执行 - - 调 scene extraction - - 调 pipeline / standalone pass - -优先涉及文件: - -1. `engine/include/XCEngine/Rendering/Planning/CameraRenderRequest.h` -2. `engine/include/XCEngine/Rendering/Execution/SceneRenderer.h` -3. `engine/src/Rendering/Execution/SceneRenderer.cpp` -4. `engine/include/XCEngine/Rendering/Execution/CameraRenderer.h` -5. `engine/src/Rendering/Execution/CameraRenderer.cpp` - -完成标准: - -1. request 不再混入过多 runtime-owned intermediate state -2. frame plan 成为 camera 执行的正式输入 -3. SceneRenderer 和 CameraRenderer 的边界可以一句话说清 - -## 工作包 B:scene extraction -> culling results / renderer list - -目标: - -把当前 `RenderSceneExtractor` 的数组式输出,推进为后续 SRP/RenderGraph 可用的原生中间层。 - -建议结果: - -1. 在 native 层引入等价于以下概念的类型: - - `CullingResults` - - `RendererListDesc` - - `RendererList` - - `FilteringSettings` - - `SortingSettings` - - `DrawSettings` -2. `RenderSceneData` 不再承担过多“什么都往里塞”的职责 -3. `VisibleRenderItem` 继续保留为底层记录,但不再直接成为所有 pass 的唯一公共输入 -4. shadow、main scene、object-id 等路径逐步改为消费 `RendererList` - -优先涉及文件: - -1. `engine/include/XCEngine/Rendering/FrameData/RenderSceneData.h` -2. `engine/include/XCEngine/Rendering/FrameData/VisibleRenderItem.h` -3. `engine/src/Rendering/Extraction/RenderSceneExtractor.cpp` -4. `engine/include/XCEngine/Rendering/Extraction/` -5. `engine/include/XCEngine/Rendering/Builtin/` - -完成标准: - -1. opaque、transparent、shadow、object-id 至少能共用同一套 draw organization contract -2. 后面新增 deferred 时,不需要再发明第二套“可见对象数组解释逻辑” - -## 工作包 C:pipeline contract 收口 - -目标: - -把当前 `RenderPipeline / RenderPass / RenderPipelineAsset` 收口到更像未来 SRP native kernel 的形态。 - -建议结果: - -1. 继续保留 `RenderPipelineAsset -> RenderPipeline` 这条总方向 -2. 明确区分: - - pipeline asset:配置与默认策略 - - pipeline runtime:本帧执行 - - standalone pass:独立功能 pass -3. 给主场景 pipeline 引入更正式的执行输入,而不是继续只吃 `surface + sceneData` -4. 为下一阶段 graph 化预留清晰输入面: - - frame context - - renderer lists - - per-frame resources - -优先涉及文件: - -1. `engine/include/XCEngine/Rendering/RenderPipeline.h` -2. `engine/include/XCEngine/Rendering/RenderPipelineAsset.h` -3. `engine/include/XCEngine/Rendering/RenderPass.h` -4. `engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h` - -完成标准: - -1. 主 pipeline 输入面能明显映射到未来 `Universal Renderer` -2. standalone pass 与 main scene pipeline 的边界清楚 - -## 工作包 D:BuiltinForwardPipeline 瘦身 - -目标: - -把 `BuiltinForwardPipeline` 从“大一统实现类”拆成可长期维护的 renderer 内部模块。 - -建议结果: - -1. 按职责切成内部模块或 helper: - - main scene draw - - skybox draw - - lighting binding - - shadow binding - - material binding - - pipeline state cache - - per-frame resource cache -2. `GaussianSplat` 和 `Volumetric` 继续保留,但从“主 pipeline 杂糅逻辑”变成清晰的 feature-style 子模块 -3. `BuiltinForwardPipelineResources.cpp` 里和运行时编排无关的资源/绑定组织进一步下沉 - -优先涉及文件: - -1. `engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h` -2. `engine/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp` -3. `engine/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineResources.cpp` -4. `engine/include/XCEngine/Rendering/Materials/RenderMaterialResolve.h` - -完成标准: - -1. `BuiltinForwardPipeline` 主类显著变薄 -2. 新增一种主场景能力时,不需要继续把所有逻辑堆回同一个 cpp - -## 工作包 E:editor 注入点正式化 - -目标: - -保留当前 editor 渲染能力,但把它从“依赖阶段细节”变成“依赖正式 extension contract”。 - -建议结果: - -1. 继续保留: - - object-id - - outline - - grid - - overlay -2. 把这些能力明确分成: - - runtime stage - - editor-only extension stage -3. 收口 `RenderPassSequence` 在 request 上的使用边界,避免后续 graph 化时出现“谁都能往 request 里塞东西”的状态 - -优先涉及文件: - -1. `engine/include/XCEngine/Rendering/Planning/CameraRenderRequest.h` -2. `editor/src/Viewport/SceneViewportRenderPlan.h` -3. `editor/src/Viewport/` -4. `engine/src/Rendering/Execution/CameraRenderer.cpp` - -完成标准: - -1. Editor 仍能完整接入当前主链 -2. 但 editor 不再直接绑死 runtime 内部实现细节 - -## 工作包 F:测试与文档同步 - -目标: - -把第一阶段重构做成可回归、可交接的正式收口,而不是一次只靠手工验证的大重排。 - -建议结果: - -1. 补 `tests/Rendering/unit` -2. 保底回归现有 `tests/Rendering/integration` -3. 必要时补 editor viewport 级验证 -4. 更新 API 文档与后续计划入口 - -完成标准: - -1. request / extractor / pipeline 边界都有对应测试覆盖 -2. 当前 forward、shadow、object-id、editor scene view 不回退 - ---- - -## 9. 执行顺序 - -必须按下面的顺序推进: - -1. 先做工作包 A -2. 再做工作包 B -3. 再做工作包 C -4. 然后做工作包 D -5. 接着做工作包 E -6. 最后统一做工作包 F - -原因: - -1. 不先分清 request 和 frame plan,后面所有 contract 都会悬空 -2. 不先把 extraction 推进到 `renderer list` 层,pipeline 收口只是表面整理 -3. 不先把 pipeline contract 稳住,`BuiltinForwardPipeline` 的瘦身会缺少稳定目标 -4. 不先把 native 主线收紧,editor contract 很容易继续沿着旧细节扩张 - ---- - -## 10. 建议新增或调整的 native 类型 - -第一阶段不要求名字完全按这个来,但建议至少出现这些等价层: - -1. `CameraFramePlan` -2. `FrameExecutionContext` -3. `CullingResults` -4. `RendererListDesc` -5. `RendererList` -6. `DrawSettings` -7. `FilteringSettings` -8. `SortingSettings` -9. `EditorRenderExtensions` 或等价 editor 注入描述 - -注意: - -1. 这些类型先是 `C++ internal contract` -2. 这一阶段不急着桥接到 `managed` -3. 下一阶段 `Render Graph` 会直接消费其中一部分 - ---- - -## 11. 这一阶段完成的验收标准 - -当以下条件同时成立时,这一阶段才算完成: - -1. `SceneRenderer / CameraRenderer / Planner` 三者职责边界稳定 -2. native 渲染主链内部已经形成 request、frame plan、execution 三层 -3. `RenderSceneExtractor` 不再只是输出裸数组,而是能支持正式的 draw organization -4. `BuiltinForwardPipeline` 不再承担明显超载职责 -5. editor 的注入点已经正规化,且没有破坏当前 Scene View / Game View 能力 -6. 现有 forward、shadow、post-process、final-output、object-id、overlay 主路径回归通过 - ---- - -## 12. 下一阶段如何接 Render Graph - -等本阶段完成后,下一阶段才进入真正的 `Render Graph`。 - -那时的接法应该是: - -1. `CameraFramePlan` 提供本帧逻辑阶段与 feature 需求 -2. `CullingResults / RendererList` 提供 draw 输入 -3. `FrameExecutionContext` 提供本帧统一资源上下文 -4. `Render Graph` 负责: - - pass declaration - - resource creation/import - - read/write dependency - - barrier / lifetime - - transient resource reuse - -也就是说,`Render Graph` 不是来替代第一阶段,而是建立在第一阶段之上。 - ---- - -## 13. 一句话结论 - -当前最佳路线不是立刻补 `Deferred`,也不是立刻补 `Render Graph`,而是先把你现有这条 native rendering 主链整理成真正的 `Render Kernel v1`;这一步做实了,后面的 `Render Graph`、`Universal Renderer`、`C# SRP` 才会接得稳。 diff --git a/docs/used/Renderer_C++层第七阶段计划_渲染模块收口与SRP宿主去临时化_完成归档_2026-04-15.md b/docs/used/Renderer_C++层第七阶段计划_渲染模块收口与SRP宿主去临时化_完成归档_2026-04-15.md deleted file mode 100644 index 634e970e..00000000 --- a/docs/used/Renderer_C++层第七阶段计划_渲染模块收口与SRP宿主去临时化_完成归档_2026-04-15.md +++ /dev/null @@ -1,71 +0,0 @@ -# Renderer C++层第七阶段计划:渲染模块收口与 SRP 宿主去临时化 - -## 阶段目标 - -本阶段不继续扩展新渲染特性,重点处理当前 Rendering 模块里仍然存在的临时组织、隐式依赖和未收口边界,为后续正式构建 SRP/URP 风格宿主层做准备。 - -收口标准: - -1. `CameraFramePlan` 成为真正可安全返回、复制、缓存的值对象,不再依赖 builder 内部临时托管资源的生命周期。 -2. `RenderPipeline` / `RenderPipelineStageRecorder` 生命周期进入统一主链,不再依赖 builtin 分支各自兜底初始化。 -3. builtin 渲染 policy 从 `CameraRenderer` / planner / host 中外提到 renderer asset / renderer 实现层,不再由 engine core 直接决定。 -4. RenderGraph fallback 仅保留必要兼容点,去掉重复桥接和职责重叠实现。 -5. editor 专用 overlay / picking / viewport pass 与 runtime builtin 渲染边界更清晰。 - -## 当前确认的问题 - -### P0 - -1. `CameraFramePlan` 内部保存的 fullscreen `RenderPassSequence*` 由 `CameraFramePlanBuilder` 内部成员托管,plan 对外按值返回时存在悬空生命周期风险。 -2. `ScriptableRenderPipelineHost` 已有生命周期接口,但运行主链没有真正统一调用,managed recorder 未来会直接踩到这个缺口。 -3. 阴影、天空盒、final color、post-process、默认 standalone stage pass 等 policy 仍散落在 engine core,而不是 renderer asset / renderer 自身闭合。 - -### P1 - -1. native RenderGraph 仍保留多处 fallback 录制与重复 surface helper,说明兼容层还未完全收口。 -2. builtin forward 与 graph builder 之间仍存在 `friend` 级耦合,部分逻辑仍是“双轨并存”。 -3. editor 专用 pass 还在 runtime rendering core 中,占用 runtime builtin 命名空间。 - -## 执行顺序 - -### Step 1:修复 CameraFramePlan 所有权 - -目标: - -1. generated fullscreen sequence 由 `CameraFramePlan` 自持有。 -2. `CameraFramePlanBuilder` 不再保管 `postProcess/finalOutput` sequence。 -3. 补测试覆盖“plan 返回后 / 复制后 / 再次 build 后”sequence 仍有效。 - -### Step 2:统一 pipeline 生命周期 - -目标: - -1. 明确 `RenderPipeline`、`RenderPipelineRenderer`、`RenderPipelineStageRecorder` 的初始化时机。 -2. 让 graph 录制与执行链共享同一套 lifecycle 入口。 -3. 去掉 builtin forward 在 graph execute callback 里的额外初始化兜底。 - -### Step 3:回收 builtin policy - -目标: - -1. 方向光阴影启用策略、环境数据、final color 默认策略、fullscreen stage 生成策略逐步下沉到 builtin renderer asset / renderer。 -2. `CameraRenderer` 退化为 scene extract + render plan execute 的薄协调层。 - -### Step 4:压缩 RenderGraph 兼容层 - -目标: - -1. 清理重复的 graph-managed surface helper。 -2. 收敛 fallback raster pass 触发条件。 -3. 继续削减 `RecordCompatible*` 路径在核心主线中的存在感。 - -### Step 5:整理 editor/runtime 渲染边界 - -目标: - -1. editor viewport grid / outline / selection mask 等 pass 从 runtime builtin 语义上剥离。 -2. 形成 editor renderer bundle 或 editor-only pass package 的组织方式。 - -## 本轮起手项 - -本轮先执行 Step 1。 diff --git a/docs/used/Renderer_C++层第三阶段计划_RenderGraph骨架与资源调度_2026-04-14.md b/docs/used/Renderer_C++层第三阶段计划_RenderGraph骨架与资源调度_2026-04-14.md deleted file mode 100644 index d5a37011..00000000 --- a/docs/used/Renderer_C++层第三阶段计划_RenderGraph骨架与资源调度_2026-04-14.md +++ /dev/null @@ -1,505 +0,0 @@ -# Renderer C++层第三阶段计划:Render Graph 骨架与资源调度 - -日期:`2026-04-14` - -## 1. 文档定位 - -第二阶段已经收口,当前 native 渲染层已经完成了两件关键事情: - -- `request -> frame plan -> execution` 主链基本收干净 -- `BuiltinForwardPipeline` 已经从“大杂烩”开始拆成更清晰的 builtin renderer 雏形 - -所以下一阶段不应该继续围着 `BuiltinForwardPipeline` 做零散瘦身,而是要正式进入: - -`Render Graph` - -这一阶段的目标不是立刻做完整 `Deferred Renderer`,也不是立刻做 `C# SRP`,而是先在 `C++ native` 层建立真正可长期演进的图式调度内核。 - ---- - -## 2. 什么是 Render Graph - -Render Graph 不是“一个新 pass 管理器”,也不是“把现有 stage 改个名字”。 - -它本质上是: - -- 一套按帧构建的 pass / resource 依赖描述 -- 一套根据读写关系编译执行顺序的系统 -- 一套统一管理 transient 资源、资源生命周期和 barrier 的系统 - -也就是说,pass 不再手写: - -- 我自己创建哪张中间 RT -- 我自己决定什么时候切状态 -- 我自己告诉下一个 pass 用哪张 source/destination surface - -而是改成声明: - -- 我读什么 -- 我写什么 -- 我输出到哪类 attachment -- 我需要什么输入上下文 - -然后由 graph 编译器统一决定: - -- 顺序 -- 资源创建/复用 -- barrier -- 最终执行计划 - -一句话说,当前是“手写命令式渲染编排”,Render Graph 是“声明式依赖编排 + 编译执行”。 - ---- - -## 3. 为什么现在该做 - -当前这套渲染层已经到了一个临界点: - -- `CameraFramePlan` 已经把 camera 级执行入口整理出来了 -- `SceneRenderFeatureHost` 已经把 feature 注入点整理出来了 -- `BuiltinForwardPipeline` 已经拆出 frame / lifecycle / resources / scene phases / surface - -这意味着“谁来做什么”已经比之前清楚很多了。 - -但现在仍然有三个核心问题没解决: - -### 3.1 资源流还是手工传递 - -现在很多阶段仍然是: - -- `RenderSurface` -- `sourceColorView` -- `sourceSurface` -- `destinationSurface` - -靠手工在 `CameraFramePlan` 和 `CameraRenderer` 之间传。 - -这对少量固定阶段还能忍,但一旦要上: - -- 更长的 post-process 链 -- object id / overlay / outline 混合 -- deferred gbuffer -- SSAO / SSR / TAA / bloom / DoF -- feature 自定义中间纹理 - -就会越来越乱。 - -### 3.2 依赖关系还是隐式的 - -当前 feature 注入点已经有了,但依赖还是隐式的。 - -例如一个 feature pass 到底: - -- 读 scene color -- 读 depth -- 读 shadow map -- 写 camera color -- 写独立 intermediate - -现在不是 native 核心显式知道的,而是散落在调用代码里。 - -### 3.3 Deferred 还没有合适宿主 - -Deferred 不应该直接加在现在这套手工 surface 编排上。 - -因为 deferred 天生会带来: - -- gbuffer A/B/C/depth -- lighting buffer -- transparent / skybox / post-process 组合 -- optional prepass / depth pyramid / SSAO - -这些东西如果没有 Render Graph,代码很快又会炸回“大 pipeline 手工串 stage”的旧路。 - -所以顺序必须是: - -1. 先做 Render Graph -2. 再在 graph 上接 deferred - ---- - -## 4. 这一阶段的总目标 - -这一阶段结束后,native 层要达到下面这个结构: - -`RHI -> Render Graph Kernel -> Builtin Renderer Graph Builder -> Feature Injection -> Camera Execution` - -更具体一点: - -- `RHI` 继续负责底层资源、命令列表、PSO、descriptor、render pass -- `Render Graph Kernel` 负责 pass/resource/依赖/barrier/lifetime -- `Builtin Renderer` 负责“这一帧要往 graph 里注册哪些 scene pass” -- `Feature` 负责在指定注入点向 graph 追加 pass -- `CameraRenderer` 只负责 build scene data -> build graph -> execute graph - ---- - -## 5. 和你当前代码怎么对接 - -### 5.1 先保留 CameraFramePlan,但降级为“高层帧意图” - -现在的 `CameraFramePlan` 里还带着不少具体 surface/sourceColor 细节。 - -Render Graph 上来之后,`CameraFramePlan` 更应该表达: - -- 这台 camera 本帧需不需要 shadow -- 需不需要 object id -- 需不需要 post-process -- 需不需要 final output -- 需不需要 overlay -- 允许哪些 feature 注入 - -而不是继续手工保存一串中间 surface 流。 - -也就是说: - -- `CameraFramePlan` 保留 -- 但它从“半执行脚本”收成“高层执行意图” - -### 5.2 CameraRenderer 改成 build graph,而不是手工串 stage - -当前 `CameraRenderer` 还是在做: - -- build scene data -- 执行 shadow -- 执行 main scene -- 执行 post-process -- 执行 final output -- 执行 object id / overlay - -下一阶段要改成: - -1. `BuildSceneData` -2. `BuildRendererLists` -3. `BuildCameraRenderGraph` -4. `ExecuteRenderGraph` - -也就是说它变成 graph 驱动器,而不是手工编排器。 - -### 5.3 BuiltinForwardPipeline 不再直接代表“一整帧” - -这一步很关键。 - -当前 `BuiltinForwardPipeline` 虽然已经清爽很多,但本质上还是: - -- 一个 camera main scene renderer - -Render Graph 之后,它更应该变成: - -- 一个向 graph 注册 forward scene pass 的 builtin renderer - -也就是它的职责改成: - -- 注册 opaque pass -- 注册 skybox pass -- 注册 transparent pass -- 在合适注入点让 feature host 往 graph 塞 pass - -而不是自己继续拥有整帧级别的中间表面和编排职责。 - -### 5.4 SceneRenderFeatureHost 保留,但执行方式改掉 - -`SceneRenderFeatureHost` 现在是直接 `Prepare/Execute`。 - -上 Render Graph 后,它更应该变成: - -- 在 `BeforeOpaque / AfterOpaque / BeforeTransparent / AfterTransparent` 等注入点 -- 由 feature 向 graph builder 注册 pass - -所以 concept 保留,执行方式要改成: - -- 现在:`feature.Execute(passContext)` -- 以后:`feature.Record(renderGraphBuilder, injectionPoint, context)` - -这会非常接近 Unity URP 的 `RendererFeature -> AddRenderPasses` 那个味道。 - -### 5.5 RenderSurface 只保留 external/import 场景 - -Graph 上来之后,不应该继续让内部 pass 大量互相传裸 `RenderSurface`。 - -`RenderSurface` 主要应该用于: - -- swapchain backbuffer -- editor viewport offscreen target -- object id target -- 已有外部资源 import 进 graph - -而 graph 内部中间纹理应该改成: - -- graph texture handle -- graph imported texture -- graph transient texture - ---- - -## 6. 这一阶段的实现策略 - -### 6.1 先做最小可用 Render Graph,不求一步到位 - -第一版 graph 只做下面这些: - -- texture resource -- imported texture -- transient texture -- raster pass -- compute pass -- read/write dependency -- 简单拓扑排序 -- 简单 barrier 推导 -- 按帧创建与释放 transient 资源 - -第一版明确先不做: - -- 复杂 aliasing -- pass merge -- async compute 调度 -- memory budget 优化器 -- 跨 queue graph - -先把“能正确替代手工 surface 编排”做出来。 - -### 6.2 先 graph 化 camera 级 pass,不先 graph 化单个 draw call - -这一步也很重要。 - -先 graph 化的是: - -- shadow caster -- main scene opaque/skybox/transparent -- object id -- post-process -- final output -- overlay / outline / grid - -不是一上来就把 mesh draw 拆成图节点。 - -也就是说,graph 的粒度先是“渲染 pass 级”,不是“每个物体 draw 级”。 - -这是成本最低、风险最小、收益最大的切法。 - -### 6.3 先替掉中间 surface 管理,再替掉 feature 执行模型 - -推荐顺序: - -1. 先让 post-process / final output / object-id / overlay 改成 graph 资源流 -2. 再让 builtin forward main scene 改成 graph pass -3. 最后让 feature host 改成 graph 注入 - -原因很简单: - -- 最乱的其实是中间资源流 -- 资源流一旦 graph 化,后面的 feature / deferred 才有稳定宿主 - ---- - -## 7. 建议新增的 native 模块 - -建议直接新开一个 `Rendering/Graph/` 模块。 - -第一版建议至少有: - -- `RenderGraph.h` -- `RenderGraphBuilder.h` -- `RenderGraphPass.h` -- `RenderGraphResource.h` -- `RenderGraphTypes.h` -- `RenderGraphCompiler.h` -- `RenderGraphExecutor.h` -- `RenderGraphBlackboard.h` - -第一版资源类型建议先只做: - -- `RenderGraphTextureHandle` -- `ImportedTextureDesc` -- `TransientTextureDesc` - -第一版 pass builder 建议至少支持: - -- `ReadTexture` -- `WriteColor` -- `WriteDepth` -- `ReadDepth` -- `CreateTransientTexture` -- `ImportTexture` -- `SetExecuteCallback` - ---- - -## 8. 对当前文件结构的具体改造方向 - -### 8.1 CameraRenderer - -从: - -- 手工执行 frame stage - -改成: - -- build scene data -- build renderer lists -- build render graph -- execute render graph - -### 8.2 CameraFramePlan - -从: - -- 带大量具体 source/destination surface 细节 - -改成: - -- 表达这一帧需要哪些逻辑阶段和输出能力 - -### 8.3 BuiltinForwardPipeline - -从: - -- 一个直接渲染 camera main scene 的 pipeline - -改成: - -- 一个 forward renderer graph builder / pass registrar - -### 8.4 FullscreenPassSurfaceCache - -从: - -- runtime 手工中间表面缓存 - -改成: - -- graph import/export 辅助 -- 后续逐步淡出 - -### 8.5 SceneRenderFeaturePass / Host - -从: - -- runtime execute - -改成: - -- graph record / graph contribute - ---- - -## 9. 分阶段执行 - -## 阶段 A:Render Graph Core - -目标: - -- 建立 graph 基础数据结构和最小编译/执行器 - -交付: - -- pass/resource/handle/builder/compiler/executor -- 支持 imported + transient texture -- 支持 raster + compute pass - -验收: - -- 有独立 unit tests -- 能构造最小 graph 并正确执行顺序与 barrier - -## 阶段 B:Camera 级资源流 graph 化 - -目标: - -- 把 post-process / final output / object id / overlay 的中间 surface 手工管理替成 graph - -交付: - -- `CameraRenderer` 开始 build graph -- `FullscreenPassSurfaceCache` 职责收缩 - -验收: - -- 现有 viewport / object id / final color 路径不回退 - -## 阶段 C:Builtin Forward Main Scene graph 化 - -目标: - -- 把 main scene 从手工 frame 执行切到 graph raster pass - -交付: - -- opaque / skybox / transparent 作为 graph pass 注册 -- forward scene 仍保留当前 renderer list / draw settings 逻辑 - -验收: - -- 当前 forward 主链功能不回退 -- `BuiltinForwardPipeline` 不再继续持有整帧调度职责 - -## 阶段 D:Feature 注入 graph 化 - -目标: - -- 让 `SceneRenderFeatureHost` 从直接执行改成向 graph 注入 - -交付: - -- feature pass graph record contract -- 注入点继续沿用当前 `SceneRenderInjectionPoint` - -验收: - -- gaussian / volumetric / editor feature 能继续挂入 - -## 阶段 E:为 Deferred 做宿主准备 - -目标: - -- 不做 deferred 本身,只把 graph 宿主和 renderer contract 整理到可以接 deferred - -交付: - -- scene color/depth/object id/shadow/final output 都成为正式 graph resource -- renderer feature 和 builtin renderer 关系稳定 - -验收: - -- 下一阶段可以直接进入 deferred / gbuffer 设计 - ---- - -## 10. 本阶段明确不做 - -- 不在这一阶段直接落完整 deferred -- 不在这一阶段做 C# SRP API -- 不在这一阶段做全量资源 aliasing 优化 -- 不在这一阶段做 async compute graph -- 不在这一阶段推倒重写 RHI - ---- - -## 11. 这一阶段完成后的状态 - -如果这一阶段做完,native 渲染层应该变成下面这样: - -- `CameraFramePlan` 负责高层帧意图 -- `CameraRenderer` 负责 graph build + execute -- `RenderGraph` 负责资源生命周期与依赖调度 -- `BuiltinForwardRenderer` 负责向 graph 注册 forward scene pass -- `FeatureHost` 负责在 injection point 向 graph 贡献 pass - -到这一步,再做: - -- deferred -- universal renderer -- C# 自定义 renderer feature -- C# 自定义 pipeline - -才是顺的。 - ---- - -## 12. 一句话结论 - -下一阶段不是继续修补 `BuiltinForwardPipeline`,而是要把你现在这套已经整理好的 native 渲染主链,正式提升成: - -`Render Graph 驱动的 Render Kernel` - -这一步一旦做实,后面的 deferred 和 Unity 式 SRP/URP 才有真正稳定的底座。 diff --git a/docs/used/Renderer_C++层第二阶段计划_BuiltinRenderer与FeatureContract_2026-04-14.md b/docs/used/Renderer_C++层第二阶段计划_BuiltinRenderer与FeatureContract_2026-04-14.md deleted file mode 100644 index 373158ea..00000000 --- a/docs/used/Renderer_C++层第二阶段计划_BuiltinRenderer与FeatureContract_2026-04-14.md +++ /dev/null @@ -1,211 +0,0 @@ -# Renderer C++层第二阶段计划:Builtin Renderer 与 Feature Contract - -日期:`2026-04-14` - -## 1. 文档定位 - -第一阶段已经把最乱的执行契约收干净了: - -- `request -> frame plan -> execution` 已经分层 -- `SceneRenderer / CameraRenderer` 的公开执行入口已经收敛到 `CameraFramePlan` - -第二阶段不做 `Render Graph`,也不做 `Deferred Renderer`。 -第二阶段要做的是: - -`先把 native C++ 层整理成一个真正能承接 URP-like 包层的内核` - -也就是先把“内建 renderer、feature 注入点、pass 编排契约”做对。 - -## 2. 为什么下一步不是先搞 Shadow - -`Shadow` 后面当然会进入 `URP-like` 层参与编排,但那说的是: - -- cascade 策略 -- filter 策略 -- screen-space shadow -- additional light shadow -- debug / feature 开关 - -这些“策略和管线组织”应该放在包层。 - -但 native C++ 层里仍然必须保留最小阴影执行能力: - -- shadow map / atlas 资源 -- shadow caster pass 执行 -- light-space 数据布局 -- resource state / barrier / cache -- 主场景采样阴影所依赖的底层 contract - -所以现在如果先把 `shadow` 当第一优先去扩,会把顺序做反。 -更正确的路线是: - -1. 先把 `renderer / feature / pass` 契约做稳定 -2. 再让 `shadow` 挂到这个契约上 -3. 再往 `URP-like` 层放阴影策略 - -## 3. 本阶段总目标 - -把当前 `BuiltinForwardPipeline + SceneRenderer + editor 注入点` 这一坨, -整理成更接近下面这条线的形态: - -`RHI -> Native Render Kernel -> Builtin Renderer Contract -> URP-like Renderer Layer` - -这一阶段结束后,native 层要能回答清楚三件事: - -- 一个 builtin renderer 的正式输入是什么 -- 一个 renderer feature 怎么注入 pass -- 哪些东西永远属于 native 执行层,哪些东西以后应该上 URP-like 包层 - -## 4. 这一阶段真正该做什么 - -### 4.1 先正式化 Builtin Renderer Contract - -目标: - -- 不再让 `BuiltinForwardPipeline` 既像 pipeline、又像 renderer、又像 feature 垃圾桶 - -这一轮要先说清楚: - -- renderer runtime 的输入 contract -- main scene phase 的组织方式 -- fullscreen / object-id / editor extension 的挂接边界 -- feature pass 和 renderer 自身职责边界 - -预期结果: - -- `BuiltinForwardPipeline` 更像一个内建 renderer 雏形 -- 后面无论叫 `UniversalRenderer` 还是别的名字,都有 native 对照物 - -### 4.2 正式化 Feature / Pass 注入契约 - -目标: - -- 把现在已经存在的各种 pass 注入点,收成一个以后能类比 `URP Renderer Feature` 的 native contract - -这一轮重点不是把所有功能迁走,而是先把“怎么挂”定义清楚。 - -至少要覆盖: - -- pre-scene -- main-scene 前后扩展点 -- object-id -- post-process -- final-output -- editor overlay / outline / grid - -预期结果: - -- feature 注入点不再依赖隐式约定 -- `CameraFramePlan` 对阶段的描述更稳定 -- 以后 C# 包层要做 renderer feature 时,native 侧已经有对应的宿主契约 - -### 4.3 BuiltinForwardPipeline 继续瘦身,但方向改成“内建 renderer 化” - -目标: - -- 不是继续散着拆 helper -- 而是按“renderer 自身 / feature / 资源绑定 / scene draw”这几个方向收 - -这一轮优先处理: - -- scene phase 调度 -- feature pass 调度 -- renderer 级资源绑定 -- 与 shadow / skybox / gaussian / volumetric 的宿主边界 - -预期结果: - -- `BuiltinForwardPipeline` 主类继续变薄 -- 新增渲染能力时,优先新增 feature 或独立模块,而不是继续堆进主 cpp - -### 4.4 Shadow 在本阶段只保留“最小 native service” - -目标: - -- 先不扩 shadow 策略层 -- 只把 native 必需能力收成清晰 service - -也就是只做这些: - -- shadow resource ownership -- shadow caster execution contract -- 主场景采样所需共享数据 -- 与 builtin renderer 的边界 - -明确不做这些: - -- cascade 新策略 -- additional light shadows -- screen-space shadow -- 各种阴影 feature 开关扩展 - -这些以后都应该挂到 `URP-like` 层。 - -### 4.5 Rendering 目录第一轮低风险收口 - -目标: - -- 只在职责边界已经清楚的前提下收目录 - -优先考虑: - -- 把 `BuiltinForwardPipeline*` 相关实现收成更明确的 builtin renderer 子目录 -- 让 `Passes` 不再无限平铺 -- 给 feature / editor / fullscreen 形成更清晰的落点 - -原则: - -- 先拆职责,再收目录 -- 不为“好看”做纯搬家 -- 每次目录调整都必须带编译验证 - -## 5. 明确不做的事 - -这一阶段明确不做: - -- 不引入真正运行时 `Render Graph` -- 不做完整 `Deferred Renderer` -- 不做 `C# SRP` 暴露 -- 不把 Gaussian / Volumetric / Shadow 整块直接搬到包层 -- 不为了目录整齐做大搬家 - -## 6. 推荐执行顺序 - -1. 先做 `Builtin Renderer Contract` -2. 再做 `Feature / Pass 注入契约` -3. 再做 `BuiltinForwardPipeline` 的 renderer 化瘦身 -4. 同步把 `Shadow` 收成最小 native service -5. 最后做第一轮目录收口 - -这个顺序的原因很简单: - -- 没有 renderer contract,feature 就没有宿主 -- 没有 feature contract,URP-like 层以后只能继续硬绑 native 细节 -- 没有 builtin renderer 化,forward pipeline 还会继续长歪 -- shadow 应该挂在契约上收,而不是反过来主导这一阶段 - -## 7. 交付标准 - -满足下面这些条件,就可以认为第二阶段基本收口: - -- builtin renderer 的输入和执行边界能一句话说清 -- feature / pass 注入点形成稳定 native contract -- `BuiltinForwardPipeline` 更像 builtin renderer,而不是继续当大杂烩 -- shadow 留在 native 层的部分被收成最小 service -- 目录结构开始反映真实模块边界 -- `XCEditor`、`rendering_unit_tests`、`editor_tests`、相关 rendering integration target 持续通过 - -## 8. 这一阶段完成后的下一步 - -当这一阶段做完,下一步才适合正式进入: - -`C++ Render Graph + URP-like Renderer Layer` - -到那时再接: - -- `Render Graph` -- `Universal Renderer` -- `Renderer Feature` -- `C# Custom Pipeline` - -顺序才是对的。 diff --git a/docs/used/Renderer_C++层第五阶段计划_SRPHost_v1骨架与BuiltinForward复用_2026-04-15.md b/docs/used/Renderer_C++层第五阶段计划_SRPHost_v1骨架与BuiltinForward复用_2026-04-15.md deleted file mode 100644 index d9ec54a8..00000000 --- a/docs/used/Renderer_C++层第五阶段计划_SRPHost_v1骨架与BuiltinForward复用_2026-04-15.md +++ /dev/null @@ -1,152 +0,0 @@ -# Renderer C++层第五阶段计划:SRP Host v1骨架与 Builtin Forward 复用 - -日期:`2026-04-15` - -## 1. 阶段定位 - -第四阶段已经把 `native planning -> graph build` 这条主线基本收口: - -- `CameraFramePlanBuilder` 的 fullscreen/color-chain 规划已拆出 -- `CameraFramePlan` 已从 public 头里下沉主要实现 -- `BuiltinForwardPipeline` 的 main-scene graph 录制已抽出 internal builder -- `CameraFrame` / `SceneRenderFeatureHost` / `BuiltinForward` 共用的 graph recording context 已集中到统一 builder - -现在可以正式进入下一阶段: - -`SRP Host v1` - -这一阶段不是直接做完整 URP,也不是直接开放最终用户级 API。 -这一阶段的目标是先把 native host、renderer builder、C# 管线入口三者接稳。 - -## 2. 这一阶段要解决的核心问题 - -### 2.1 目前还没有正式的 SRP host - -当前引擎已经有: - -- `SceneRenderer` -- `CameraRenderer` -- `RenderGraph` -- `BuiltinForwardPipeline` - -但还没有一个明确的“脚本侧组织 renderer / feature / pass,native 侧负责承载与执行”的正式宿主层。 - -### 2.2 当前 builtin forward 仍然是“内建管线实现”,不是“可被 SRP host 复用的 builtin renderer” - -虽然第四阶段已经把 main-scene graph builder 抽出来了,但 `BuiltinForwardPipeline` 仍然更像一个完整管线对象。 -SRP host 要求它至少能被看成: - -- 一个可复用的 native renderer -- 一个可被 host 调度的 graph contributor -- 一个默认 builtin implementation - -而不是 host 本身。 - -### 2.3 C# 入口现在还缺少正式边界 - -后面如果要像 Unity 一样让用户用 C# 组织 renderer / feature / pass,最先需要的不是完整功能,而是稳定边界: - -- C# 能创建/持有一条 render pipeline asset -- native 能向脚本暴露受控的 renderer host 入口 -- host 能回落复用 builtin forward renderer - -## 3. 本阶段总目标 - -这一阶段结束后,主线应接近: - -`C# Pipeline Asset / Native Pipeline Asset -> SRP Host -> Builtin Renderer / Renderer Feature -> RenderGraph Host -> Execute` - -也就是: - -- `RenderGraph` 仍然留在 C++ 层 -- `SRP Host` 负责组织一帧 renderer graph contribution -- `BuiltinForward` 首先作为默认 builtin renderer 被 host 复用 -- 后续 `URP-like package` 再放到更高层,不直接塞回 C++ host - -## 4. 明确边界 - -### 4.1 这一阶段明确不做 - -- 不做完整 deferred renderer -- 不做完整 URP 包 -- 不做完整 renderer feature 生态 -- 不做最终用户可自由扩展的全部 C# API - -### 4.2 这一阶段必须做成 - -- 一个正式的 `SRP Host v1` -- 一个可被 host 调度的 builtin forward renderer 入口 -- 一个最小可运行的 C# pipeline asset / host bridge -- host 能把 builtin forward 作为默认 fallback 跑通 - -## 5. 具体执行顺序 - -### 5.1 先把 builtin forward 从“管线实现”整理成“host 可复用 renderer” - -目标: - -- 明确 builtin forward 的 renderer 身份 -- 减少 host 未来直接依赖 `BuiltinForwardPipeline` 大对象内部细节 -- 为后续 deferred / custom renderer 留统一接缝 - -建议收口方向: - -- `BuiltinForwardRenderer` 或等价 internal builder host -- `RenderPipeline` 与 `Renderer Builder` 边界再清一次 - -### 5.2 引入 `SRP Host v1` native 宿主 - -目标: - -- host 持有一帧 renderer graph contribution 列表 -- host 负责组织 main scene / feature / fullscreen stages 的 graph 录制 -- 默认仍可回落到 builtin forward renderer - -第一版不追求花哨能力,先把“host 存在且稳定”做出来。 - -### 5.3 打通最小 C# 管线入口 - -目标: - -- C# 侧能描述“使用哪条 pipeline asset / renderer” -- native 侧能从脚本入口落到 `SRP Host v1` -- 至少存在一条 builtin forward 的脚本可达路径 - -这一层只做最小闭环,不追求完整编辑器 UX。 - -### 5.4 最后再决定是否开 `URP-like builtin package` - -当下面三件事成立,再开下一阶段: - -1. `SRP Host v1` 已稳定 -2. `BuiltinForward` 已经是可复用 builtin renderer,而不是一坨特殊 case -3. C# 入口已经能稳定驱动默认 renderer - -## 6. 完成标准 - -满足下面这些条件,就可以认定第五阶段收口: - -- native 已存在正式的 `SRP Host v1` -- builtin forward 已能作为 host 下的默认 builtin renderer 运行 -- 脚本侧至少能以最小路径创建并驱动默认 pipeline -- `CameraRenderer` 不会重新长成新的总调度中心 -- `rendering_unit_tests / editor_tests / XCEditor smoke` 继续稳定 - -## 7. 阶段后下一步 - -第五阶段完成后,才进入: - -`URP-like Builtin Package v1` - -也就是: - -- C# 侧 builtin package -- renderer feature / pass 组合能力 -- forward first,deferred later - -正确顺序仍然是: - -1. `SRP Host v1` -2. `URP-like builtin package` -3. `Deferred renderer` -4. 更复杂的 feature 生态 diff --git a/docs/used/Renderer_C++层第六阶段计划_RenderGraphBuilder与NativePass正式化_2026-04-15.md b/docs/used/Renderer_C++层第六阶段计划_RenderGraphBuilder与NativePass正式化_2026-04-15.md deleted file mode 100644 index b226d40d..00000000 --- a/docs/used/Renderer_C++层第六阶段计划_RenderGraphBuilder与NativePass正式化_2026-04-15.md +++ /dev/null @@ -1,190 +0,0 @@ -# Renderer C++层第六阶段计划:RenderGraph Builder 与 Native Pass 正式化 - -日期:`2026-04-15` - -## 1. 阶段定位 - -第五阶段已经完成: - -- `SceneRenderer -> RenderPipelineHost -> CameraRenderer` 的主链职责切开 -- `ScriptableRenderPipelineHost` 已经成为默认 native 宿主 -- `BuiltinForward` 已经被 host 复用,而不是继续长在 `CameraRenderer` -- C# 侧已经有最小 `RenderPipelineAsset / GraphicsSettings` 选择入口 - -下一阶段不再继续堆 SRP 表面 API,而是回头把 C++ 里的 RenderGraph 组织层做实。 - -现在的问题不是“有没有 RenderGraph”,而是“RenderGraph 还没有成为渲染层唯一正式的组织语言”。 - -## 2. 当前核心问题 - -### 2.1 graph core 已有,但 renderer authoring 还不正式 - -当前已经有: - -- `RenderGraph` -- `CameraFramePlan` -- `BuiltinForward` 内部 graph 录制 -- feature / fullscreen / final color 若干 graph 录制路径 - -但现在仍然缺: - -- 稳定的 pass builder 录制接口 -- 统一的 frame data / blackboard / graph context -- 资源声明、导入、导出、跨 pass 依赖的正式边界 - -结果就是: - -- builtin renderer 仍然夹杂较多“知道内部细节的 graph 录制代码” -- 后续要做 deferred、lightmap、custom renderer feature 时,容易继续散着长 -- C# SRP 想组织 pass 时,没有稳定 native 承接点 - -### 2.2 现在的 builtin pass 更像功能块,不像正式 graph node - -当前很多 pass 还停留在: - -- 按功能拆出来了 -- 能被 builtin forward 调 -- 但还没有统一的“声明资源 -> 录制 graph -> 执行 payload”模型 - -这会导致后续每加一个新特性,都容易再复制一套组织方式。 - -### 2.3 SRP 已有入口,但还没有可扩展的 native renderer contract - -第五阶段做的是: - -- 先把 host 立起来 -- 让 managed 侧能选 pipeline -- 默认仍回落到 builtin forward - -这一步是必要的,但还不是最终可扩展状态。 - -如果现在直接往 C# 侧暴露更多 renderer / feature API,会把当前 native 的不稳定组织方式直接固化出去。 - -## 3. 本阶段目标 - -这一阶段结束后,要达到的状态是: - -`RenderPipelineHost / ScriptableRenderPipelineHost` -`-> Native Renderer Contract` -`-> RenderGraph Builder` -`-> Compiled RenderGraph` -`-> Execute` - -也就是: - -- host 只负责组织 renderer 与 frame 级上下文 -- renderer 通过统一 builder 录制 graph -- pass 通过统一 contract 声明输入输出资源 -- builtin forward、post process、shadow、object id、editor overlay 都走同一种 graph authoring 方式 - -## 4. 本阶段明确要做的事 - -### 4.1 建立正式的 RenderGraph 录制上下文 - -新增或收口统一概念: - -- `RenderGraphBuilderContext` -- `RenderGraphFrameData` 或等价 blackboard -- `RendererFeatureContext` -- `PassRecordContext` - -目标: - -- renderer 不直接到处自己拼资源名字和跨阶段状态 -- graph 录制代码读写统一的 frame 数据 -- camera/frame 级公共数据有固定入口 - -### 4.2 正式化 native pass contract - -把现有 builtin pass 整理成统一模型: - -- pass 声明输入资源 -- pass 声明输出资源 -- pass 录制 render/compute callback -- pass 执行期只消费编译后的 graph 结果 - -至少先把这几类 pass 收进统一 contract: - -- main scene color/depth -- shadow map -- object id -- fullscreen post process -- final color resolve / present 前 copy - -### 4.3 收口资源导入导出与命名 - -重点不是做花哨 API,而是避免 graph 资源边界继续散: - -- camera color/depth -- intermediate post-process chain -- shadow atlases / shadow maps -- object id / selection outline 相关纹理 -- editor viewport 附加 surface - -阶段目标是让“哪些资源是 imported,哪些是 transient,哪些跨 renderer 共享”全部有明确归属。 - -### 4.4 让 BuiltinForward 真正成为标准 renderer 实现 - -本阶段完成后,`BuiltinForward` 应该只是: - -- 一个标准 native renderer -- 使用标准 pass contract -- 通过标准 RenderGraph builder 录制 - -而不是继续保留特殊地位。 - -### 4.5 给下阶段 SRP/URP-like 层预留稳定承接点 - -这个阶段暂时不扩写完整 C# renderer feature API。 - -但必须把 native 边界准备好,让下阶段可以安全承接: - -- managed renderer asset 配置下发 -- renderer feature 列表 -- renderer pass 描述 -- future deferred renderer - -## 5. 本阶段明确不做 - -- 不做完整 deferred pipeline -- 不做完整 URP-like builtin package -- 不做大规模 C# renderer feature authoring API -- 不做 lightmap、probe、GI 全链路 -- 不做 HDRP 风格多路径并行体系 - -这些东西都应该建立在“RenderGraph 组织层正式化”之后。 - -## 6. 推荐执行顺序 - -1. 先整理 frame data / graph builder / pass record context -2. 再把 main scene、fullscreen、final color 三条主链统一到新 contract -3. 再把 shadow / object id / editor viewport 附加 pass 接入同一模型 -4. 再清理 builtin forward 内部遗留 special case -5. 最后补齐 renderer/host 级测试,并验证 editor 主链 - -## 7. 验收标准 - -满足以下条件即可认为第六阶段收口: - -- `BuiltinForward` 已完全通过统一 RenderGraph builder 录制 -- fullscreen / final color / object id / shadow 不再各自维护不同 graph 组织方式 -- host、renderer、pass 三层职责清晰 -- 新增一个 native pass 不需要再改 `CameraRenderer` 主调度逻辑 -- `rendering_unit_tests` 新增覆盖 renderer contract 与 graph 录制边界 -- `scripting_tests`、`editor_tests`、`XCEditor` smoke 继续稳定 - -## 8. 第六阶段结束后的下一步 - -第六阶段结束后,才进入真正面向用户的下一阶段: - -`SRP/URP-like package v1` - -那时再做才是对的: - -- C# renderer asset 配置对象 -- renderer feature 列表 -- forward renderer data -- deferred renderer 原型 -- 用户级自定义渲染管线入口 - -顺序不能反。 diff --git a/docs/used/Renderer_C++层第四阶段计划_RenderGraph规划层收口与SRP启动前条件_2026-04-15.md b/docs/used/Renderer_C++层第四阶段计划_RenderGraph规划层收口与SRP启动前条件_2026-04-15.md deleted file mode 100644 index f32baec8..00000000 --- a/docs/used/Renderer_C++层第四阶段计划_RenderGraph规划层收口与SRP启动前条件_2026-04-15.md +++ /dev/null @@ -1,180 +0,0 @@ -# Renderer C++层第四阶段计划:RenderGraph规划层收口与SRP启动前条件 - -日期:`2026-04-15` - -## 1. 文档定位 - -第三阶段已经把 RenderGraph native host 的骨架、阶段上下文、运行时分发、stage surface/source 解析、fullscreen color chain 意图、internal policy 边界,连续收了一轮。 - -这一阶段不直接开做面向用户的 `C# SRP`。 - -这一阶段要做的是: - -- 把 `CameraFramePlanBuilder` 和 planning 层继续收干净。 -- 让 `CameraFramePlan` 更像高层 frame intent,而不是执行细节载体。 -- 给后面的 `native renderer builder / SRP host seam` 做稳定落点。 -- 明确“什么时候可以正式开始做 SRP”。 - -一句话: -先把 `native planning -> graph build` 这条线收口,再开 SRP。 - -## 2. 当前进展基线 - -截至当前主线,已经完成并推送: - -- `8232f59` `Extract camera frame render-graph recording session` -- `d92afa2` `Extract camera frame render-graph stage record context` -- `5edc4ed` `Extract camera frame render-graph stage pass runtime` -- `ac836ae` `Extract camera frame stage surface resolver` -- `00fa6ff` `Group camera frame fullscreen color chain intent` -- `0afcaa0` `Move render-graph stage policy into internal host` - -当前验证基线: - -- `rendering_unit_tests`: `209 passed / 1 failed` -- 唯一剩余失败:`BuiltinForwardPipeline_Test.OpenGLRuntimeTranspilesForwardShadowVariantToLegacyClipConventions` -- `editor_tests`: `191 passed` -- `XCEditor` 12s smoke:通过 - -## 3. 当前还没收口的核心问题 - -### 3.1 `CameraFramePlanBuilder` 仍然过重 - -现在 `AttachFullscreenStageRequests` 还在同时做: - -- camera 后处理/最终输出需求判断 -- graph-managed color chain 规划 -- sequence 绑定 -- destination/source 语义拼装 - -这说明 planning 还是有点像“半执行期拼装器”。 - -### 3.2 `CameraFramePlan` 还没有完全变成高层意图对象 - -虽然已经收掉了一批低层 getter,也把 fullscreen chain 收成了 `colorChain`,但 plan 里仍然保留着一些偏执行期的结构和默认推导痕迹。 - -后面如果直接在这上面做 SRP,暴露出去的 API 很容易绑定到当前 native 内部实现细节。 - -### 3.3 还缺一个稳定的 native renderer builder 接缝 - -现在的 RenderGraph host 已经能工作,但还没有一个非常明确的 “renderer 向 graph 注册本帧 pass” 的正式层。 - -这层不稳定,后面: - -- forward renderer -- deferred renderer -- feature injection -- C# SRP host - -都会继续直接碰当前 host 细节。 - -## 4. 本阶段总目标 - -这一阶段结束后,native 渲染主线要更接近: - -`Scene/Camera Planning -> CameraFramePlan -> Native Renderer Builder -> RenderGraph Host -> Execute` - -也就是: - -- `Planning` 只负责“这一帧需要什么”。 -- `Renderer Builder` 只负责“把这些意图翻译成 graph passes/resources”。 -- `RenderGraph Host` 只负责“记录、编译、执行图”。 - -## 5. 具体执行顺序 - -### 5.1 先拆 `CameraFramePlanBuilder` - -优先拆出与 fullscreen chain 相关的独立 planner / helper,目标是让 `SceneRenderer` 和 `CameraFramePlanBuilder` 不再直接持有一坨后处理/最终输出拼装逻辑。 - -建议收口方向: - -- `CameraFrameFullscreenStagePlanner` -- `CameraFrameColorChainPlanner` -- `CameraFramePlanValidation` 或等价 helper - -### 5.2 再把 plan 进一步高层化 - -继续减少 plan 里和具体 surface/source/sequence 拼装绑定过深的部分,让 plan 更像: - -- 有哪些 stage -- stage 之间是什么来源关系 -- 哪些输出是 graph-managed -- 哪些是 external/imported target - -而不是继续堆更多执行期细节。 - -### 5.3 引入 native renderer builder 接缝 - -在 native 侧明确一层“renderer 向 RenderGraph 贡献 pass”的正式接口。 - -这一层的目标不是立刻做成用户 API,而是先给: - -- `BuiltinForwardPipeline` -- 未来 `DeferredRenderer` -- 未来 `RendererFeature` - -提供统一宿主。 - -### 5.4 最后判断是否进入 SRP - -当且仅当下面三件事成立,才正式开始做 SRP: - -- `Planning` 和 `Execution` 边界稳定。 -- `Renderer Builder` 这一层稳定。 -- 新增渲染能力时,不需要再直接改 `CameraRenderer` 主线大函数。 - -## 6. 何时可以开始做 SRP - -当前结论仍然是: - -还不能正式开始做面向用户的 `C# SRP`。 - -可以开始 SRP 的条件不是“已经有 RenderGraph 了”,而是: - -1. `CameraFramePlanBuilder` 已收口。 -2. `CameraFramePlan` 已足够高层稳定。 -3. native `renderer builder` 接缝已存在。 -4. `BuiltinForwardPipeline` 已经可以被看成一个 builtin renderer,而不是一坨 host 逻辑。 - -满足这四条之后,就可以开始第一版 SRP。 - -我的判断是: - -- 如果第四阶段顺利,SRP 就已经进入可启动状态。 -- 也就是说,SRP 不是现在开。 -- SRP 是这一个 native 收口阶段之后开。 - -## 7. 本阶段完成标准 - -满足下面这些条件,就可以认定第四阶段收口: - -- `CameraFramePlanBuilder` 明显变薄,fullscreen/color-chain planning 被拆出。 -- `CameraFramePlan` 不再继续回流低层执行细节。 -- native 出现稳定的 renderer builder 接缝。 -- `CameraRenderer` 继续变薄,没有重新长回大调度函数。 -- `rendering_unit_tests / editor_tests / XCEditor smoke` 继续稳定通过。 - -## 8. 阶段后下一步 - -第四阶段完成后,下一步就是正式开: - -`SRP Host v1` - -但第一版 SRP 仍然只建议做到: - -- native host + C# 管线入口 -- C# 侧可组织 renderer / feature / pass -- 先复用 builtin forward renderer - -不建议一上来就同时做: - -- 完整 deferred -- 完整 URP 包 -- 全量 renderer feature 生态 - -正确顺序仍然是: - -1. 先 native 收口 -2. 再 SRP host -3. 再 URP-like builtin package -4. 最后再 deferred / 更复杂 feature diff --git a/docs/used/Renderer下一阶段_CameraPostProcess描述层正式化计划_完成归档_2026-04-06.md b/docs/used/Renderer下一阶段_CameraPostProcess描述层正式化计划_完成归档_2026-04-06.md deleted file mode 100644 index 49b7b467..00000000 --- a/docs/used/Renderer下一阶段_CameraPostProcess描述层正式化计划_完成归档_2026-04-06.md +++ /dev/null @@ -1,127 +0,0 @@ -# Renderer 下一阶段: Camera PostProcess 描述层正式化计划 - -日期: `2026-04-06` - -## 1. 阶段目标 - -在已经完成 runtime `SceneColor -> PostProcess -> FinalOutput` 主链收口的基础上, -把相机上的后处理配置从临时的 `ColorScale` 专用字段,提升为正式的描述层。 - -这一阶段只解决一件事: - -- `CameraComponent` 持有正式的 post-process pass 描述 -- `SceneRenderer` 通过工厂把描述翻译成 runtime pass sequence -- 旧的 `ColorScale` 接口继续兼容,旧场景序列化继续可读 - -## 2. 已完成内容 - -### 2.1 相机后处理描述层 - -已新增: - -- `CameraPostProcessPassType` -- `CameraPostProcessPassDesc` -- `CameraPostProcessStack` - -当前首个正式 builtin effect 仍然是: - -- `ColorScale` - -但它已经不再通过 `CameraComponent -> vector -> SceneRenderer if/for` 这种硬编码链路传递。 - -### 2.2 CameraComponent 正式化 - -已完成: - -- `CameraComponent` 内部存储迁移到 `CameraPostProcessStack` -- 新增通用接口: - - `GetPostProcessPasses` - - `SetPostProcessPasses` - - `AddPostProcessPass` - - `ClearPostProcessPasses` -- 旧接口继续保留: - - `SetColorScalePostProcessEnabled` - - `SetColorScalePostProcessScale` - - `SetColorScalePostProcessPasses` - - `GetColorScalePostProcessPasses` - -兼容规则: - -- 旧接口继续映射到 `ColorScale` 类型的描述 -- 新格式序列化写出正式 `postProcessPassN*` 字段 -- 反序列化同时兼容旧的 `colorScalePostProcess*` 字段 - -### 2.3 SceneRenderer 工厂翻译层 - -已完成: - -- 从 `SceneRenderer` 中移除 `ColorScale` 专用硬编码构造 -- 新增 `BuildCameraPostProcessPassSequence(...)` -- `SceneRenderer` 现在只关心: - - 读取相机的 `CameraPostProcessStack` - - 为 post-process 分配 source surface - - 调用工厂生成 `RenderPassSequence` - -这意味着后续增加新的 builtin camera post-process 时, -修改点已经收缩到描述层和工厂,而不是继续把分支堆进 `SceneRenderer`。 - -## 3. 验证结果 - -### 3.1 构建 - -已通过: - -- `cmake --build --preset debug --target components_tests rendering_unit_tests rendering_integration_camera_post_process_scene rendering_integration_post_process_scene -- /m:1` - -### 3.2 单元测试 - -已通过: - -- `components_tests --gtest_filter=CameraComponent_Test.*` -- `rendering_unit_tests --gtest_filter=SceneRenderer_Test.*:CameraRenderer_Test.*` - -覆盖点包括: - -- 新描述层 round-trip -- 旧 `ColorScale` 字段反序列化兼容 -- `SceneRenderer` 从 camera post-process stack 构建正式 request - -### 3.3 集成测试 - -已通过: - -- `rendering_integration_camera_post_process_scene` - - D3D12 - - OpenGL - - Vulkan -- `rendering_integration_post_process_scene` - - D3D12 - - OpenGL - - Vulkan - -说明: - -- `camera_post_process_scene` 验证 formal scene path -- `post_process_scene` 保留为 manual / low-level multi-pass 覆盖 - -## 4. 当前结论 - -这一阶段已经完成。 - -当前 renderer 在 camera post-process 这一层,已经从“单个效果的临时接线”升级为“正式描述 + 工厂翻译”的结构。 - -但现阶段仍然有两个边界: - -- 描述层虽然正式化了,但当前 builtin effect 仍只有 `ColorScale` -- editor / asset / material 侧还没有把 camera post-process 做成完整的资产化与配置面板体系 - -## 5. 下一步建议 - -下一步不要回头继续往 `SceneRenderer` 里塞特判,而应沿着下面的方向推进: - -1. 继续扩展 `CameraPostProcessPassDesc` - - 例如 tone mapping / exposure / gamma / final color transform -2. 让新的 builtin camera effect 继续只经过“描述层 + 工厂” -3. 等 shader/material 主线更稳定后,再决定 camera post-process 的资产化形式和 editor 配置入口 - -本阶段的核心收口标准已经满足,可以提交归档。 diff --git a/docs/used/Renderer下一阶段_FinalColorPipeline正式化计划_完成归档_2026-04-06.md b/docs/used/Renderer下一阶段_FinalColorPipeline正式化计划_完成归档_2026-04-06.md deleted file mode 100644 index ee14c64a..00000000 --- a/docs/used/Renderer下一阶段_FinalColorPipeline正式化计划_完成归档_2026-04-06.md +++ /dev/null @@ -1,374 +0,0 @@ -# Renderer 下一阶段: FinalColorPipeline 正式化计划 - -日期: `2026-04-06` - -## 1. 阶段定位 - -上一阶段已经完成: - -- `CameraPostProcess` 描述层正式化 -- `SceneRenderer` 从相机描述层自动构建 post-process request -- manual path 与 formal scene path 都已测试闭环 - -下一阶段不再讨论“要不要继续往引擎里内置几个后处理特效”。 - -下一阶段真正要做的是: - -- 把 `Final Color Pipeline` 作为 renderer 主链的正式组成部分建立起来 -- 并且从架构上明确对齐 Unity 风格的 SRP 演进方向 - -也就是把当前链路从: - -`SceneColor -> CameraPostProcessStack -> Surface` - -推进到: - -`SceneColor -> CameraPostProcessStack -> FinalColorPipeline -> Output Surface` - -其中: - -- `CameraPostProcessStack` 是相机上的可扩展全屏效果链 -- `FinalColorPipeline` 是 renderer 内置的最终出图规则 - -二者不是同一个概念。 - -## 2. 与 Unity/SRP 对齐的核心结论 - -如果目标是后续在这套 rendering 之上做 Unity 风格的 `SRP`,那这一步必须遵守下面这条主线: - -### 2.1 FinalColor 不能只挂在 Camera 上 - -Unity 风格的渲染架构里,最终出图规则不应该被设计成“Camera 私有特效配置”。 - -更合理的来源解析链应该是: - -`Pipeline/Renderer defaults -> Camera override -> future Volume override -> resolved per-camera final color policy` - -也就是说: - -- 默认值来自 pipeline/renderer 级配置 -- 相机只做少量 override -- 以后 volume 再覆盖部分结果 -- renderer 最终对每个 camera 解析出一份正式的 final color policy - -### 2.2 FinalColor 属于 renderer 主链,不属于任意 post-process stack - -下面这些应被视为 renderer 正式内置能力: - -- linear -> sRGB output transfer -- exposure -- tone mapping -- final color transform - -而不是被混成“任意全屏效果链”里的普通 pass。 - -### 2.3 必须提前预留 SRP pass injection 点 - -如果以后要做 Unity 风格 SRP / RendererFeature / 自定义 pass: - -- 现在就不能把 final color policy 写成一堆散落在 `SceneRenderer` 里的硬编码分支 -- 必须明确 runtime 主链阶段 -- 必须给 future renderer feature / custom fullscreen pass 预留稳定插入点 - -## 3. 当前问题 - -当前 renderer 虽然已经具备: - -- forward runtime scene renderer -- shadow / skybox / multi-light -- camera post-process 描述层 -- formal render request / final output stage 框架 - -但距离 SRP-ready 还差下面这些正式收口: - -1. `FinalOutput` 还没有真正变成正式的 final color 执行层 -2. `FinalColorPolicy` 还没有独立于 `CameraPostProcessStack` -3. pipeline 默认值、camera override、future volume override 还没有分层 -4. 最终出图的颜色空间 contract 还没有制度化 -5. 还没有给 future `RendererFeature / Pass injection` 明确主链挂点 - -## 4. 本阶段目标 - -本阶段完成后,应达到: - -1. 引入正式的 `FinalColorSettings / FinalColorPolicy` -2. 形成明确的数据来源链: - - pipeline/renderer defaults - - camera override - - reserved future volume override -3. `SceneRenderer` 负责解析每个 camera 的最终 final color policy -4. `CameraRenderer` 的 `FinalOutput` stage 正式承担最终出图职责 -5. `CameraPostProcessStack` 与 `FinalColorPipeline` 完全分层 -6. 为 future `RendererFeature / Pass injection` 预留清晰的阶段和 contract -7. 三后端对 final color path 有稳定测试覆盖 - -## 5. 非目标 - -本阶段明确不做: - -- bloom -- SSAO -- DOF -- motion blur -- 完整 LUT color grading 套件 -- volume 系统完整落地 -- renderer feature 全量系统 -- render graph -- editor-only 特效 - -本阶段的重点是: - -- 把最终出图主链做正式 - -而不是: - -- 堆更多视觉效果 - -## 6. 设计原则 - -### 6.1 三层来源解析 - -第一层: `Pipeline / Renderer defaults` - -这里放全局默认 final color 行为,例如: - -- output transfer mode -- default exposure mode/value -- default tone mapping mode -- 是否启用 final color stage - -第二层: `Camera override` - -相机只做少量覆盖,例如: - -- 是否允许 post-process/final color -- override exposure -- override final color transform -- 指定 renderer / pipeline variant - -第三层: `Future Volume override` - -当前阶段只预留 contract,不完整落地,但必须明确将来会从这里覆写: - -- exposure -- tone mapping -- color adjustments - -最终由 renderer 解析出: - -- `ResolvedFinalColorPolicy` - -### 6.2 PostProcess 与 FinalColor 必须分层 - -约定如下: - -#### `CameraPostProcessStack` - -职责: - -- 相机上的可扩展 fullscreen effect chain -- 偏向“特效层”与“自定义效果层” - -例子: - -- color scale -- 以后可能的 edge effect / custom fullscreen effect - -#### `FinalColorPipeline` - -职责: - -- renderer 正式最终出图 -- 负责把线性 scene color 变成最终显示输出 - -例子: - -- output transfer -- exposure -- tone mapping -- final color transform - -### 6.3 FinalOutput 是正式主链阶段 - -这一阶段完成后,需要把 `FinalOutput` 明确成正式 runtime stage,而不是“名义上存在、实际上只留空接口”的阶段。 - -主链应明确为: - -`MainScene -> CameraPostProcess -> FinalColor -> Output` - -### 6.4 先做 contract,再做更多效果 - -第一版不追求效果数量,先把下面这些做对: - -- 数据模型 -- 来源解析 -- 执行阶段 -- 三后端一致性 - -## 7. 核心方案 - -### 7.1 引入 FinalColorSettings / ResolvedFinalColorPolicy - -建议引入两层数据: - -#### 配置层 - -- `FinalColorSettings` - -来源: - -- pipeline defaults -- camera override -- future volume override - -#### 执行层 - -- `ResolvedFinalColorPolicy` - -用途: - -- renderer 在本帧、对这个 camera 最终真正执行的 final color 规则 - -### 7.2 第一版字段建议 - -第一版保持克制,建议只引入最小必要字段: - -- output transfer mode - - disabled - - linear to sRGB -- exposure mode - - disabled - - fixed exposure -- exposure value -- tone mapping mode - - disabled - - reserved future enum -- final color scale - -注意: - -- tone mapping mode 可以先留枚举和数据入口 -- 第一阶段不要求完整 tone mapping 实装 - -### 7.3 将 FinalOutput 接入正式工厂化执行 - -当前已有 post-process 的描述层和工厂翻译思路。 - -这一阶段要把同样的正式化思路用于 `FinalOutput`: - -- `SceneRenderer` 负责根据 resolved final color policy 构建 final output request -- `CameraRenderer` 负责执行 final output stage -- final output 相关 pass 不再靠零散 if 分支拼装 - -### 7.4 预留 future RendererFeature / Pass injection 点 - -这一步必须把下面这些插入点概念固定下来: - -- before main scene -- after main scene -- after camera post-process -- before final output -- after final output / before editor composite - -当前阶段不要求把完整 feature 系统实现完,但必须在 plan 和 runtime stage 语义上先定好位置。 - -## 8. 执行阶段 - -### Phase A: SRP-ready FinalColor contract - -目标: - -- 定义 `FinalColorSettings` -- 定义 `ResolvedFinalColorPolicy` -- 明确三层来源解析 - -完成标准: - -- pipeline defaults / camera override / future volume override 的职责写清楚 -- 不破坏现有 `CameraPostProcess` 主链 - -### Phase B: FinalOutput 正式执行层 - -目标: - -- 让 `FinalOutput` 成为正式执行阶段 - -完成标准: - -- `SceneRenderer` 能自动附加 final output request -- `CameraRenderer` 走正式 final-output chain -- 不再依赖零散条件分支 - -### Phase C: 最小 builtin final color 闭环 - -目标: - -- 用最小 final color policy 验证 contract - -建议首选: - -- `LinearToSRGB / OutputTransfer` - -可选再加: - -- fixed exposure -- final color scale - -完成标准: - -- 三后端输出一致 -- 与已有 `camera_post_process_scene` / `post_process_scene` 不冲突 - -### Phase D: 测试与文档收口 - -目标: - -- 补足 unit / integration coverage -- 把这一步与 future SRP 承接关系写回文档 - -完成标准: - -- 计划可归档到 `docs/used` - -## 9. 测试策略 - -至少覆盖: - -- `components_tests` -- `rendering_unit_tests` -- 一条新的 `final_color_scene` 集成测试 -- `camera_post_process_scene` -- `post_process_scene` -- 必要时 `skybox_scene` - -新的 `final_color_scene` 必须验证: - -- `FinalOutput` stage 真的参与了最终出图 -- 输出 transfer / exposure 等策略真的生效 -- D3D12 / OpenGL / Vulkan 三后端一致 - -## 10. 收口判定 - -满足下面条件时,本阶段可视为完成: - -1. `FinalColorSettings / ResolvedFinalColorPolicy` 已正式建模 -2. `FinalOutput` 已成为正式 runtime stage -3. `CameraPostProcessStack` 与 `FinalColorPipeline` 已彻底分层 -4. pipeline defaults / camera override / future volume override 的职责边界已写死 -5. future renderer feature / pass injection 点已在架构上预留 -6. 至少一条正式 final color path 完成三后端闭环 -7. 现有 rendering 主线测试不回退 - -## 11. 本阶段之后再做什么 - -等这一步完成后,后续 SRP 方向的推进顺序才合理: - -1. 扩更多 builtin final color policy -2. 再做 renderer feature / custom fullscreen pass -3. 再做 volume 系统正式接入 -4. 再做 C# 层对 pipeline/camera/feature 的承接 - -也就是说: - -如果你的目标是最终做 Unity 风格 SRP, -那这一阶段就是把 renderer 从“能出图”推进到“能作为 SRP 底座”的关键收口阶段。 diff --git a/docs/used/Renderer下一阶段_ShaderMaterial与Pass体系设计.md b/docs/used/Renderer下一阶段_ShaderMaterial与Pass体系设计.md deleted file mode 100644 index f51d5d5c..00000000 --- a/docs/used/Renderer下一阶段_ShaderMaterial与Pass体系设计.md +++ /dev/null @@ -1,656 +0,0 @@ -# Renderer 下一阶段:Shader、Material 与 Pass 体系设计 - -日期:`2026-04-02` - -## 1. 阶段判断 - -当前 Renderer 阶段已经完成的事情,是把下面这条主链正式接通并收口: - -`RHI -> Rendering -> Editor Scene/Game Viewport` - -当前已经具备: - -- `SceneRenderer -> CameraRenderer -> RenderPipeline` 的主执行边界 -- scene camera request 组织能力 -- built-in forward 主几何绘制 -- `object-id` 渲染与 editor picking -- built-in post-process 入口 -- editor viewport 宿主接入 -- 对应的 renderer/editor 自动化测试闭环 - -这意味着 Renderer 已经不再是“RHI 之上的一堆零散 draw call”,而是已经形成了真实的模块边界。 - -但这并不意味着 Renderer 已经进入“可长期扩展”的状态。 - -当前阶段的真正短板,不是 render graph,而是: - -- shader 还没有进入正式 renderer 主路径 -- material 还不是正式 GPU 参数绑定载体 -- pass contract 还不完整 -- 三后端虽然都能跑,但 shader authoring 仍是内建硬编码 - -所以 Renderer 的下一阶段主线,不应优先做 render graph,而应优先完成: - -- `Shader` -- `Material` -- `Builtin Pass Contract` -- `Renderer-owned Feature Contract` - ---- - -## 2. 为什么下一阶段不是 Render Graph - -`render graph` 不是简单优化项,它本质上是更高一层的资源依赖与多 pass 调度框架。 - -但当前工程还没有满足它最该承接的前提: - -1. 还没有足够正式的 pass 分类体系 -2. 还没有正式的 shader / material 执行契约 -3. editor helper pass 与 runtime pass 还没有统一语义 -4. 还没有稳定的 renderer feature 输入输出边界 - -如果现在直接上 render graph,会出现一个问题: - -- graph 框架先做了 -- 真正的 shader/material/pass 契约还没收紧 -- 最后 graph 里承载的还是一批语义松散的临时 pass - -这会让架构“看起来高级”,但基础层仍然不稳。 - -因此下一阶段正确顺序应当是: - -1. 先收紧 shader/material/pass contract -2. 再把更多 renderer feature 统一为正式能力 -3. 等真正的多 pass 复杂度上来后,再引入 render graph - ---- - -## 3. 当前 Renderer 的真实问题 - -### 3.1 Shader 仍未进入正式主路径 - -当前 built-in forward pipeline 的 shader 仍然是直接硬编码在 C++ 中: - -- D3D12: HLSL -- OpenGL: GLSL 430 -- Vulkan: GLSL 450 - -这意味着: - -- `Material::GetShader()` 虽然存在,但不控制当前主渲染 -- shader 资源尚未成为正式运行时契约 -- 新增 pass 或 shader 变体时,仍然需要直接改 pipeline C++ - -这不符合后续 Unity 风格 SRP 的方向。 - -### 3.2 Material 还是“资源状态载体”,不是正式 GPU 材质实例 - -当前 `Material` 已具备: - -- render queue -- rasterizer / blend / depth-stencil render state -- tag -- property -- texture binding -- shader 引用 - -但真正进入 GPU 执行链路的内容,仍然很少: - -- per-object constant -- 一张主纹理 -- 一个 sampler -- 少量 pass metadata 过滤 - -也就是说: - -- material property 还没有正式映射到 GPU layout -- material constant buffer 还没有进入正式绑定链路 -- texture binding 还没有从“约定名字查一张图”升级为“按 pass layout 正式绑定” - -### 3.3 Pass contract 仍然不够完整 - -当前比较明确的 pass 只有: - -- forward 主几何 -- object-id -- grid -- selection outline -- debug mask - -但如果后面要走 Unity 风格 renderer,至少需要明确区分: - -- `ForwardLit` -- `Unlit` -- `DepthOnly` -- `ShadowCaster` -- `ObjectId` -- editor helper pass - -否则后面一旦加入阴影、深度预通道、材质分流、后处理和 SRP 注入,就会重新回到“if/else 管线”。 - -### 3.4 三后端能跑,但还不是正式 shader 资产体系 - -当前三后端运行没问题,不代表 shader 体系已经成熟。 - -当前缺的是: - -- 统一的 shader 资产描述 -- 每个 shader pass 的 backend variant 管理 -- 统一的 descriptor / constant layout 描述 -- renderer 内部的 pipeline cache key 规范 - -现在只是“每个 backend 有一份能工作的内建 shader”,离正式体系还有明显距离。 - ---- - -## 4. 下一阶段的核心目标 - -下一阶段目标不是“把所有渲染功能做完”,而是建立正式可扩展的 renderer 执行契约。 - -核心目标分四层。 - -### 4.1 正式建立 Shader Asset Contract - -下一阶段应把 `Shader` 从资源占位,提升为 renderer 正式消费的资源。 - -建议 shader 资产至少包含: - -- shader 名称与 GUID -- pass 列表 -- 每个 pass 的逻辑名称 -- 每个 pass 的 tag,例如 `LightMode` -- 每个 pass 的 property layout -- 每个 pass 的 resource binding layout -- 每个 backend 的 shader variant - -建议的概念模型: - -```text -ShaderAsset - -> ShaderPassDesc[] - -> passName - -> tags - -> propertyLayout - -> backendVariants -``` - -这里的关键点不是一开始就做复杂 shader graph,而是先明确: - -- renderer 以后创建 pipeline,不再直接从 C++ 字符串取 shader -- renderer 应从 shader asset 的某个 pass 中取当前 backend 对应 variant - -### 4.2 正式建立 Material Instance Contract - -`Material` 下一阶段要从“资源状态容器”升级为“可绑定的材质实例”。 - -Material 至少应当明确: - -- 它引用哪个 shader -- 它选择 shader 的哪个 pass -- 它当前有哪些 property override -- 它有哪些 texture binding -- 它导出的 render state 是什么 - -下一阶段不要求一开始就做复杂材质编辑器,但必须完成: - -- property -> GPU 常量区布局 -- texture binding -> descriptor binding -- pass 过滤规则 -- material instance 的缓存与脏标记 - -### 4.3 正式建立 Builtin Pass Contract - -下一阶段应当把当前 renderer 内建 pass 明确分层: - -- 几何主 pass -- 深度/阴影类 pass -- object-id/editor helper pass -- post-process / overlay pass - -建议第一批正式化的 pass 名称: - -- `ForwardLit` -- `Unlit` -- `DepthOnly` -- `ShadowCaster` -- `ObjectId` - -说明: - -- `ForwardLit` 支撑当前主线 -- `Unlit` 用于 editor helper、gizmo、调试对象、简单 UI mesh -- `DepthOnly` 和 `ShadowCaster` 为后面阴影与可见性阶段铺路 -- `ObjectId` 让 editor/runtime picking 有正式 renderer 合约 - -### 4.4 正式建立 Renderer Feature Contract - -当前 grid / outline / object-id / debug mask 已经部分进入 renderer,但仍然带有明显 editor 来路。 - -下一阶段应继续把它们定义为 renderer feature,而不是 editor 特判: - -- object-id output -- selection / mask debug -- overlay helper contract -- camera request 上的 feature request - -目标不是马上做完整 feature graph,而是明确: - -- 哪些 feature 属于 renderer -- 哪些输入由 editor 组装 -- 哪些输出由 renderer 提供 - ---- - -## 5. 三后端 Shader 策略 - -这一节必须进一步说透两个问题: - -1. 三后端语言不同,shader 资产到底怎么组织 -2. shader authoring 到底采用“Unity 式一份 shader 里写多个 stage”,还是把 vertex / fragment 拆成独立文件 - -### 5.1 当前阶段不建议直接追求“单源码全平台自动转译” - -理论上可以追求: - -- 统一 HLSL -- 再编到 SPIR-V / GLSL - -但这会立刻引入: - -- 工具链依赖 -- shader reflection -- backend 兼容差异处理 -- 调试复杂度 - -对于当前工程,这不是下一阶段最优先的问题。 - -### 5.2 下一阶段建议采用“统一逻辑资产 + 后端分 variant”的务实方案 - -建议下一阶段的 shader 策略是: - -- 逻辑上一个 shader asset -- 资产里按 pass 持有多个 backend variant -- renderer 根据 backend 选择对应源码/二进制 - -例如: - -```text -BuiltinLit.shader - Pass: ForwardLit - D3D12 -> HLSL - OpenGL -> GLSL 430 - Vulkan -> GLSL 450 / SPIR-V -``` - -这样做的优点: - -- 三后端路径清晰 -- 不引入过早的跨编译复杂度 -- 仍然能在资产层统一 shader 逻辑身份 -- 后续要切换成统一 authoring 也有演进空间 - -### 5.3 Vulkan 的长期方向 - -Vulkan 长期更适合进入: - -- 预编译 SPIR-V -- 反射生成 binding layout - -但这属于再下一阶段的工程化增强。 - -当前下一阶段只要求: - -- Vulkan 不再依赖 pipeline cpp 内嵌 shader 字符串的散乱模式 -- Vulkan variant 被纳入正式 shader asset contract - -### 5.4 推荐的 Shader 资产组织方式 - -建议下一阶段采用: - -- **逻辑上一个 ShaderAsset** -- **资产内部按 Pass 组织** -- **每个 Pass 内再按 Stage 与 Backend 持有 variant** - -推荐概念模型: - -```text -ShaderAsset - -> Pass: ForwardLit - -> Stage: Vertex - -> D3D12 : xxx.vs.hlsl - -> OpenGL : xxx.vs.glsl - -> Vulkan : xxx.vs.vk.glsl / xxx.vs.spv - -> Stage: Fragment - -> D3D12 : xxx.ps.hlsl - -> OpenGL : xxx.fs.glsl - -> Vulkan : xxx.fs.vk.glsl / xxx.fs.spv - - -> Pass: DepthOnly - -> ... -``` - -也就是说: - -- 对 renderer 来说,真正识别的是一个 `ShaderAsset` -- 对 pass 来说,拿到的是“这个 pass 在当前 backend 下对应的各 stage 变体” -- 对后端来说,最终看到的仍然是它自己能吃的 shader 源码或二进制 - -这样处理后: - -- shader 逻辑身份是统一的 -- backend 差异被收进 variant 层 -- pass / stage / backend 三个维度都清楚 - -### 5.5 不建议直接照搬 Unity ShaderLab 的单文件大一统方案 - -如果完全模仿 Unity,最直观的形式是: - -- 一份 shader 文件 -- 里面写 `Pass` -- `Pass` 里面再写 `Vertex/Fragment` - -这种方式的优点是: - -- 对材质和 pass 关系表达很强 -- 很适合后续 editor / inspector / C# SRP 暴露 - -但它当前直接落地的缺点也很明显: - -- 你现在有三个 backend -- backend shader 语言并不统一 -- 还没有自己的 shader import / include / preprocess / reflection 工具链 - -如果现在直接做成 Unity 那种单文件 DSL,等于同时要解决: - -- shader 语法设计 -- parser -- include 系统 -- multi-pass 语义 -- backend variant 分发 -- material property layout - -这会把下一阶段的复杂度一下子拉爆。 - -### 5.6 下一阶段更务实的做法 - -下一阶段建议采用“两层模型”: - -#### 第一层:逻辑层接近 Unity - -保留 Unity 风格的核心语义: - -- 一个 shader 有多个 pass -- 每个 pass 有名字和 tag -- material 绑定的是 shader 与 pass - -#### 第二层:物理文件层先按 stage 分开 - -实际文件先拆成: - -- `*.vs.hlsl` -- `*.ps.hlsl` -- `*.vs.glsl` -- `*.fs.glsl` -- `*.vs.vk.glsl` -- `*.fs.vk.glsl` - -或者编译后: - -- `*.spv` - -也就是说: - -- **逻辑上用 Unity 式“一个 shader 拥有多个 pass”** -- **物理落地上暂时不用 Unity 式“一个文件里硬塞所有 backend/stage”** - -这是当前阶段最稳妥的方案。 - -### 5.7 对“vertex / fragment 是合一还是分开”的明确结论 - -结论分两层: - -#### 从逻辑资产视角看 - -应当是“合一”的。 - -也就是: - -- 一个 `ShaderAsset` -- 下面有一个或多个 `Pass` -- 一个 `Pass` 同时拥有 vertex / fragment 等 stage - -这和 Unity 的思路一致。 - -#### 从实际源码文件视角看 - -应当先“分开”。 - -也就是: - -- vertex shader 单独文件 -- fragment shader 单独文件 -- backend variant 单独文件 - -原因: - -- 更容易做 backend 分发 -- 更容易调试编译错误 -- 更容易做最小 shader import -- 更适合你当前三后端并行维护 - -所以不要把“逻辑合一”和“源码物理文件合一”混为一谈。 - -最终建议是: - -- **逻辑模型采用 Unity 风格** -- **文件组织先采用分 stage、分 backend** - -### 5.8 未来再向 Unity ShaderLab 靠拢的演进路径 - -等下一阶段把下面这些都做稳之后: - -- shader asset contract -- material property binding -- backend variant 选择 -- pass contract - -后面再往上加一层更接近 Unity ShaderLab 的 authoring 语法,就顺理成章: - -```text -ShaderLab-like Authoring - -> Shader Importer - -> ShaderAsset / Pass / Variant - -> RenderPipeline Runtime -``` - -也就是说: - -- 现在先做 runtime contract -- 以后再做更高级的 shader authoring front-end - -这样不会把工程顺序做反。 - ---- - -## 6. 建议的新分层 - -建议 Renderer 下一阶段形成下面这套更稳定的分层: - -```text -Scene / Components / Resources - -> RenderSceneExtractor / RenderRequestPlanner - -> Shader & Material Runtime - -> CameraRenderer / RenderPipeline - -> Builtin Feature Passes - -> RHI - -> D3D12 / OpenGL / Vulkan -``` - -其中新增的重点层是: - -### 6.1 Shader & Material Runtime - -负责: - -- 解析 shader asset / material asset -- 生成 pass 级 binding layout -- 维护 material GPU 数据与脏标记 -- 提供 pipeline cache key - -### 6.2 Builtin Feature Passes - -负责: - -- object-id -- grid -- outline -- shadow/depth 等 builtin pass - -这样可以让: - -- `RenderPipeline` 更聚焦于场景主流程组织 -- 各 pass 更聚焦于自己真正的职责 - ---- - -## 7. 推荐的落地顺序 - -### 阶段 A:Formalize Builtin Pass Metadata - -先完成: - -- `ForwardLit` -- `Unlit` -- `DepthOnly` -- `ObjectId` - -需要落地的内容: - -- pass name -- pass tag -- renderer 如何选择某个 material 的 pass -- builtin pipeline 不再依赖模糊字符串判断 - -完成标志: - -- `MatchesBuiltinPass(...)` 不再只是当前这种最小过滤,而是更接近正式 pass contract - -### 阶段 B:Material GPU Binding 最小闭环 - -先完成: - -- material 常量数据打包 -- texture binding 正式映射 -- sampler 策略统一 -- material 脏标记 -> GPU 缓存更新 - -完成标志: - -- forward pipeline 不再只会找一张 `_MainTex` 风格贴图 -- material property 真正进入 shader 执行链 - -### 阶段 C:Shader Asset 真正进入 Render Pipeline - -先完成: - -- builtin shader 资产化 -- 每个 builtin shader 具备 backend variant -- pipeline state 从 shader asset pass 创建 - -完成标志: - -- `BuiltinForwardPipeline.cpp` 中的大段后端 shader 字符串不再是长期正式实现 - -### 阶段 D:Renderer-owned Feature Contract 收口 - -先完成: - -- object-id request/output 正式化 -- outline/grid 继续从 editor 逻辑脱耦 -- camera request 上的 feature request 更明确 - -完成标志: - -- editor 侧更多只做 request 装配,而不是 feature 逻辑承担者 - -### 阶段 E:验证与回归 - -必须补的测试: - -- renderer unit tests -- shader/material runtime unit tests -- pass metadata 选择测试 -- backend integration smoke tests -- editor viewport regression - ---- - -## 8. 下一阶段明确不做的内容 - -以下内容不进入下一阶段主线: - -- render graph -- 完整 shader graph -- 完整 deferred renderer -- 大规模后处理栈 -- 完整阴影系统产品化 -- C# SRP 真正脚本驱动落地 - -原因不是这些不重要,而是它们依赖下一阶段先把基础契约做稳。 - ---- - -## 9. 与 Unity 风格架构的承接关系 - -如果后面目标是做 Unity 风格的 C# SRP,那么下一阶段必须先把“原生 renderer 可被脚本驱动”的基础层做好。 - -Unity 风格承接关系应理解为: - -```text -Shader / Material / Pass Contract - -> Native Render Pipeline Runtime - -> ScriptableRenderContext / CommandBuffer - -> C# RenderPipelineAsset / RenderPipeline -``` - -也就是说: - -- 当前下一阶段做的不是 SRP 本身 -- 但做的是 SRP 能否成立的地基 - -如果这一层继续缺失,后面脚本层会直接耦合到一堆临时内建逻辑上,最后 SRP 只会沦为“脚本包装的硬编码 forward pipeline”。 - ---- - -## 10. 阶段成功标准 - -这一阶段完成时,至少应满足: - -1. shader asset 已进入 renderer 主路径 -2. material property 与 texture binding 已形成正式 GPU 绑定链 -3. builtin pass contract 已具备最小完整集 -4. 三后端 shader 不再依赖 pipeline cpp 中散乱硬编码作为长期实现 -5. object-id / outline / grid 等 renderer feature 边界进一步明确 -6. renderer/editor 对应测试体系同步补齐 - ---- - -## 11. 一句话总结 - -Renderer 下一阶段的正确主线不是 render graph,而是: - -- 先把 `Shader` -- `Material` -- `Pass Contract` -- `Renderer Feature Contract` - -做成正式能力。 - -只有这一层站稳之后,后面的: - -- 阴影 -- 更多 pass -- render graph -- C# SRP - -才会是顺势生长,而不是继续堆临时方案。 diff --git a/docs/used/Renderer下一阶段_Skybox环境与FrameComposition正式化计划_完成归档_2026-04-05.md b/docs/used/Renderer下一阶段_Skybox环境与FrameComposition正式化计划_完成归档_2026-04-05.md deleted file mode 100644 index c35cadad..00000000 --- a/docs/used/Renderer下一阶段_Skybox环境与FrameComposition正式化计划_完成归档_2026-04-05.md +++ /dev/null @@ -1,412 +0,0 @@ -# Renderer 下一阶段:Skybox 环境与 Frame Composition 正式化计划 -日期:`2026-04-05` - -## 1. 阶段定位 - -多光源 forward runtime 这一阶段已经闭环: -- `Directional / Point / Spot` 已接入正式 lighting contract -- `main directional shadow` 已保持稳定 -- 三后端 lighting integration 已有完整回归矩阵 -- Scene / Game View 已复用同一条 runtime renderer 主链 - -因此 renderer 主线现在不应该继续停留在“补光照地基”,而应该进入下一阶段: - -**把当前 renderer 从“单次场景绘制”推进成“正式的相机帧合成框架”,先补齐 `Opaque -> Skybox -> Transparent -> PostProcess -> Final Output` 这条主链。** - -这一步完成后,rendering 主线才算真正具备继续承接: -- skybox / environment -- 正式后处理入口 -- 更稳定的 camera output composition -- 后续 Unity 风格 renderer feature / C# SRP 对接 - -## 2. 为什么现在必须先做这个 - -当前 renderer 虽然已经能稳定画出 lit / unlit / shadow / object-id / editor overlay,但它还不是完整的相机帧合成体系: - -- `BuiltinForwardPipeline` 目前本质上还是“一次场景遍历直接打到最终目标” -- 没有正式 `Skybox` 阶段 -- 没有正式 `Environment` 数据入口 -- 没有 runtime 级别的 `PostProcess` 输入 / 输出约定 -- 还没有“只在需要时启用 intermediate color target”的相机合成策略 - -如果这个阶段不先做,后面不管是: -- 天空盒 -- 环境贴图 -- 曝光 / 色调映射 -- 颜色调整 -- 屏幕空间效果 - -都会继续以临时 pass 拼接的方式往上叠,最后会把当前已经比较干净的 renderer 主链重新拉乱。 - -## 3. 与 Unity / SRP 对齐的原则 - -这一阶段继续严格遵守当前工程的核心分层: - -- `RHI` 只负责 GPU 抽象,不知道 skybox / environment / post-process 语义 -- `Renderer` 负责相机帧规划、scene extraction、frame composition、runtime pass orchestration -- `Editor` 仍然只是 renderer 的宿主和 overlay 使用方 - -与 Unity 风格保持对齐时,本阶段遵守以下原则: - -1. `Skybox / Environment / PostProcess` 都属于 runtime renderer 正式能力,不是 editor 特供逻辑 -2. editor 的 grid / gizmo / icon / selection outline 不进入 runtime frame composition 主链 -3. 不引入 `render graph` -4. 不直接跳到 deferred / clustered / HDRP 级复杂度 -5. 先建立正式 `frame composition seam`,再谈更复杂的 renderer feature - -## 4. 当前真实状态 - -### 4.1 已经具备的基础 - -- `CameraRenderer` 已有明确的请求装配入口 -- `SceneRenderer -> CameraRenderer -> RenderPipeline` 主链稳定 -- 已支持: - - `shadowCaster` - - `depthOnly` - - `main pipeline` - - `objectId` - - `overlayPasses` -- `RenderSceneExtractor` 已有稳定的: - - visible item 提取与排序 - - main light / additional lights 提取 -- `transparent_material_scene`、`depth_sort_scene`、`offscreen_scene` 已证明当前透明与离屏基础可用 - -### 4.2 还没正式化的缺口 - -- 没有 renderer 级 `Skybox` pass contract -- 没有 `RenderEnvironmentData` -- `BuiltinForwardPipeline` 还没有正式拆分成 `Opaque / Transparent` -- 没有“需要 post-process 时自动切到 intermediate color target”的约定 -- 没有 runtime fullscreen pass 基础设施 -- 没有证明 `Skybox + Transparent + PostProcess` 同时存在时仍然稳定的 integration coverage - -## 5. 本阶段总目标 - -本阶段只做一件事: - -**建立一条正式、稳定、三后端一致、可测试的 camera frame composition 主链。** - -收口后应达到: - -1. 场景绘制正式分为 `Opaque -> Skybox -> Transparent` -2. renderer 有正式 `Environment` 数据入口 -3. renderer 有正式 `PostProcess` 输入 / 输出入口 -4. 只在确实需要时才分配 intermediate color target -5. Scene / Game View 继续复用同一条 runtime frame composition 主链 -6. 为后续 Unity 风格 renderer feature / C# SRP 留出清晰承接点 - -## 6. 非目标 - -本阶段明确不做: - -- `render graph` -- deferred rendering -- clustered / tiled lighting -- IBL / reflection probe / light probe -- point / spot shadow -- 全量 post-process 套件 -- 完整 HDR pipeline -- editor-only 视觉效果继续扩张 - -## 7. 设计方案 - -### 7.1 正式化 Camera Frame Composition - -建议把相机级 runtime composition 明确写成固定阶段: - -1. `shadowCaster` -2. `depthOnly`(按需求) -3. `opaque scene` -4. `skybox` -5. `transparent scene` -6. `post-process` -7. `final output resolve / blit` -8. `objectId` -9. `overlay` - -关键原则: - -- `objectId` 继续独立,不混入 runtime final color 主链 -- `overlay` 继续是宿主级 / editor 级最后附加层 -- runtime 的 `Skybox / PostProcess` 是相机帧的一部分,不应再伪装成 editor overlay - -### 7.2 拆分 BuiltinForwardPipeline 的 scene 阶段 - -当前 `BuiltinForwardPipeline` 虽然能依赖 render queue 正确排序,但 skybox 正式接入后,必须让 runtime scene path 至少具备: - -- `OpaquePass` -- `TransparentPass` - -原因很直接: - -- skybox 必须有正式插入点 -- transparent 需要在 skybox 之后 -- 以后 post-process 输入也需要明确“scene color 到哪里结束” - -这里不需要推翻现有 forward lit / unlit 合约,只需要把当前单次遍历拆成正式两个阶段,并保持现有材质状态和排序行为不回退。 - -### 7.3 正式化 Environment 数据入口 - -建议新增 renderer 级环境数据模型,例如: - -- `RenderEnvironmentData` - - `clearMode` - - `skyboxMaterial` - - `skyboxEnabled` - - 后续可扩展环境颜色 / 曝光 / cubemap 引用 - -第一版原则: - -1. 先解决“环境如何进入 renderer” -2. 不在这一阶段强行上完整 IBL -3. 不把 environment 设计成 editor 专属状态 - -### 7.4 Skybox 首版策略 - -首版建议优先做 **builtin procedural skybox**,而不是一开始就把阶段拖进 cubemap / probe / importer 链路。 - -理由: - -- 目标是先打通正式 `skybox pass` -- procedural skybox 更容易稳定三后端一致 -- 更容易写 GT 和像素断言 -- 不依赖 cubemap 资源导入闭环 - -首版可以支持: - -- top / horizon / bottom color -- 视线方向驱动的渐变 - -后续再扩成 cubemap skybox,不会推翻本阶段的 frame composition 设计。 - -### 7.5 Post-Process 正式入口的最小闭环 - -本阶段不追求“后处理功能多”,只追求“后处理入口正式化”。 - -建议最小闭环: - -1. 建立 fullscreen pass 基础设施 -2. 建立 `post-process source -> destination` 约定 -3. 建立 intermediate color target 的按需分配策略 -4. 用一个简单、稳定、三后端一致的 builtin fullscreen effect 做验证 - -验证 effect 不需要复杂,建议: - -- `ColorTint` -或 -- `GammaAdjust` -或 -- `FinalCopy + optional color scale` - -关键不是效果本身,而是证明: - -- scene color 能被正式读回 -- fullscreen pass 能稳定写到目标 -- camera final output 路径已具备后续扩展能力 - -### 7.6 Intermediate Color Target 策略 - -不能把所有相机都默认改成“永远先画离屏再 blit”,否则会徒增复杂度和性能成本。 - -建议规则: - -- 默认直接渲染到最终目标 -- 当存在 `Skybox / PostProcess / 特定 composition 需求` 时,按需启用 intermediate color target -- 这个判断应由 `CameraRenderer` 或 request planner 明确控制,而不是散落在具体 pass 内部 - -### 7.7 与 Unity 风格 SRP 的承接关系 - -这一阶段完成后,renderer 主链会更接近 Unity/URP 的基本心智模型: - -- scene opaque -- skybox -- transparent -- post-process -- final target - -这一步不是为了机械模仿 Unity,而是为了给以后 C# 层 SRP / renderer feature 建立正确的宿主结构。 - -## 8. 实施分阶段 - -## 8.1 Phase A:Frame Composition 合约正式化 - -### 目标 - -把相机级帧合成顺序从“隐式拼接”改成正式 contract。 - -### 工作项 - -- 明确 runtime frame composition 阶段枚举 / 顺序 -- 调整 `CameraRenderer` 的 orchestration 结构 -- 明确 `objectId`、`overlay` 与 runtime frame composition 的边界 - -### 验收标准 - -- 相机帧阶段顺序明确且可追踪 -- 现有 `multi_light / transparent / offscreen` 不回退 - -## 8.2 Phase B:Opaque / Skybox / Transparent 正式分段 - -### 目标 - -让 builtin forward runtime scene path 具备正式 `Opaque / Transparent` 结构,并为 skybox 提供插入点。 - -### 工作项 - -- 拆分 `BuiltinForwardPipeline` -- 保持现有 render queue 语义 -- 保持透明排序与材质状态回归不破坏 - -### 验收标准 - -- `transparent_material_scene` -- `depth_sort_scene` -- `material_state_scene` - -三后端继续通过 - -## 8.3 Phase C:Skybox / Environment 首版闭环 - -### 目标 - -让 renderer 正式支持 runtime skybox。 - -### 工作项 - -- 新增 `RenderEnvironmentData` -- 新增 builtin procedural skybox pass / shader -- 将 skybox 接入 camera frame composition -- 保持 clear color fallback 行为可控 - -### 验收标准 - -- 新增 `skybox_scene` -- 同时存在 opaque + skybox + transparent 时顺序正确 -- 三后端 GT 稳定 - -## 8.4 Phase D:Post-Process 入口闭环 - -### 目标 - -建立正式 fullscreen post-process 入口,而不是继续依赖临时 pass。 - -### 工作项 - -- fullscreen pass helper -- intermediate color target 按需分配 -- final blit / resolve 规则 -- 一个最小 builtin post-process 验证效果 - -### 验收标准 - -- 新增 `post_process_scene` -- `offscreen_scene` 不回退 -- 三后端 GT 稳定 - -## 8.5 Phase E:测试与文档收口 - -### 目标 - -让本阶段收口后,`docs/plan` 的主线入口与真实实现再次一致。 - -### 工作项 - -- 更新 `tests/TEST_SPEC.md` -- 输出 renderer frame composition 阶段说明 -- 将本阶段完成计划归档到 `docs/used` - -### 验收标准 - -- 文档、实现、测试矩阵三者口径一致 - -## 9. 测试策略 - -### 9.1 Unit - -- frame composition 阶段决策 -- intermediate target 启用条件 -- skybox / post-process request 装配 - -### 9.2 Integration - -新增: - -- `skybox_scene` -- `post_process_scene` - -回归: - -- `multi_light_scene` -- `spot_light_scene` -- `directional_shadow_scene` -- `camera_stack_scene` -- `transparent_material_scene` -- `cull_material_scene` -- `depth_sort_scene` -- `material_state_scene` -- `offscreen_scene` - -### 9.3 Editor Runtime Smoke - -- SceneView / GameView 继续复用同一条 runtime frame composition -- object-id picking 不因 frame composition 改造回退 -- editor overlay 不污染 runtime skybox / post-process - -## 10. 风险与控制 - -### 风险 1:为了接 skybox 强行把所有相机都改成离屏 - -后果: - -- 复杂度和成本无意义上升 -- 调试链路变长 - -控制策略: - -- intermediate target 只按需启用 - -### 风险 2:skybox 设计一开始就绑死 cubemap / IBL - -后果: - -- 阶段再次失焦 -- 资源链路耦合过早 - -控制策略: - -- 首版先做 builtin procedural skybox - -### 风险 3:post-process 入口继续走 editor 旧路径 - -后果: - -- runtime / editor 边界重新混乱 - -控制策略: - -- runtime post-process 必须是 renderer 正式阶段 -- editor overlay 继续留在最后 - -## 11. 阶段完成判定 - -满足以下条件时,本阶段可视为收口: - -1. renderer 具备正式 `Opaque -> Skybox -> Transparent -> PostProcess -> Final Output` 结构 -2. skybox 有正式 runtime 数据入口与首版实现 -3. post-process 有正式 fullscreen 入口与最小闭环 -4. `skybox_scene` 与 `post_process_scene` 三后端通过 -5. 现有 lighting / transparency / offscreen / camera stack 回归不破坏 - -## 12. 本阶段之后的正确下一步 - -当这一步收口后,renderer 主线的下一阶段才应该考虑: - -1. cubemap skybox / environment map -2. 曝光 / tone mapping / color grading 正式化 -3. 更高阶的 renderer feature / C# SRP 承接层 - -而不是现在就跳去: - -- `render graph` -- deferred -- editor-only 特效堆叠 diff --git a/docs/used/Renderer下一阶段_Unity风格Shader体系正式化计划_完成归档_2026-04-07.md b/docs/used/Renderer下一阶段_Unity风格Shader体系正式化计划_完成归档_2026-04-07.md deleted file mode 100644 index 0eaf6fc4..00000000 --- a/docs/used/Renderer下一阶段_Unity风格Shader体系正式化计划_完成归档_2026-04-07.md +++ /dev/null @@ -1,841 +0,0 @@ -# Renderer 下一阶段:Unity 风格 Shader 体系正式化计划 - -日期:`2026-04-06` - -## 1. 阶段结论 - -当前 renderer 主线已经可以阶段性收口,但 `Shader / Material / Pass` 体系还没有真正统一。 - -现在仓库里的 shader 体系处于一个过渡态: - -- 逻辑上已经有 `Shader -> Pass -> Variant` 模型 -- authoring 外观上已经接近 Unity ShaderLab -- 运行时已经能按 pass 和 backend variant 选择 shader -- 三后端已经能稳定跑通 builtin shader - -但它仍然不是 Unity 风格的正式体系,因为当前 authoring 仍然暴露了太多 backend 与 binding 细节: - -- `.shader` 文件里仍然显式写 `#pragma backend D3D12/OpenGL/Vulkan` -- `.shader` 文件里仍然显式写 `Resources { name(type, set, binding) }` -- material 仍然可以显式指定 `shaderPass` -- shader 关键字、变体、include、pass state、SubShader 选择还没有真正成体系 - -所以这一阶段的主线不是继续做新渲染效果,而是把 shader 体系升级成: - -- **authoring 层完全按 Unity 风格书写** -- **三后端编译路径统一收敛到 importer / compiler 层** -- **runtime 只消费正式 shader artifact / material contract** - -这一步不是 SRP 本身,但它是后续 SRP 能否成立的硬地基。 - ---- - -## 2. 阶段目标 - -本阶段要达成的核心目标只有一件事: - -**把当前“伪 ShaderLab + 后端直连”的过渡体系,升级为“Unity 风格 authoring + 引擎内部统一 shader IR + 三后端编译产物”的正式体系。** - -完成后应达到: - -1. shader authoring 采用 Unity 风格 `.shader` 语法,不再要求作者显式写 backend variant 分发表。 -2. 新语法不再要求作者手写 `set/binding` 级资源绑定表。 -3. HLSL 成为 raster shader 的单一 authoring 语言源。 -4. D3D12 / Vulkan / OpenGL 的差异退到 importer / compiler / artifact 层。 -5. material 只管 property / keyword / texture / render state,不再负责点名 pass。 -6. renderer 按 `LightMode` / pass contract 选 pass,而不是靠 material 的临时字符串兜底。 -7. builtin shader 全部迁移到新体系并保持三后端回归稳定。 -8. 保留 legacy shader 兼容层,避免一次性炸掉现有工程内容。 - ---- - -## 3. 非目标 - -本阶段明确不做: - -- SRP 本体 -- render graph -- deferred / clustered -- Shader Graph -- Surface Shader -- 完整 Unity ShaderLab 100% 全语法一次性覆盖 -- volume 系统 -- 更多新渲染效果 - -本阶段也不追求一口气把 Unity 的所有 authoring 特性补完,例如: - -- `Fallback` -- `UsePass` -- `CustomEditor` -- `GrabPass` -- tessellation / geometry / ray tracing authoring - -这些可以后续补,但不应阻塞当前阶段把“统一 shader 体系”先做正确。 - ---- - -## 4. 当前真实状态与根本问题 - -### 4.1 当前已经具备的能力 - -当前工程已经具备: - -- `.shader` 文件解析能力 -- `Shader` 的 pass / property / resource / variant 数据模型 -- builtin shader 资产化 -- material property / texture / render state / tag 载体 -- renderer 中按 builtin pass metadata 选择可用 shader pass - -这意味着当前不是从零开始设计。 - -### 4.2 当前最根本的问题 - -当前最大的问题不是“没有 shader 体系”,而是“authoring 层和 runtime 层的边界还没收干净”。 - -具体表现为: - -1. **authoring 层暴露后端差异** - - `.shader` 文件直接写 `#pragma backend D3D12/OpenGL/Vulkan` - - shader 作者必须知道后端与源码文件映射 - -2. **authoring 层暴露 RHI binding 细节** - - `.shader` 文件显式写 `Resources { ConstantBuffer / Texture2D / Sampler, set, binding }` - - 这更像 Vulkan/D3D12 binding 清单,不是 Unity 风格 shader authoring - -3. **material 还带着临时 pass 选择职责** - - `Material::SetShaderPass()` 仍然存在 - - renderer 仍然优先吃 material 显式指定的 pass - - 这会阻碍未来 RendererFeature / SRP 规范化 - -4. **关键字与变体体系缺失** - - 没有正式的 `multi_compile / shader_feature` - - 没有变体剥离与编译缓存策略 - -5. **include 与共享库体系缺失** - - 没有正式的 shader include 搜索路径、预处理、公共库组织 - -6. **pass state 仍然不在 shader authoring 的统一语义内** - - 诸如 `Cull / ZWrite / ZTest / Blend / ColorMask / Stencil` 还没有完整进入 shader authoring contract - -7. **三后端仍然是物理三套源码直连** - - 当前虽然“逻辑上一个 shader asset” - - 但作者本质上还在维护三套 shader stage 文件 - ---- - -## 5. 核心设计结论 - -### 5.1 目标不是“看起来像 Unity”,而是“真正采用 Unity 风格 authoring 模型” - -最终目标应当是: - -- 一个 `.shader` 文件描述一个逻辑 shader -- shader 内部有 `Properties / SubShader / Pass / Tags / State / Program` -- renderer 消费的是 import 后的统一 IR / artifact -- backend 差异不暴露给 shader 作者 - -### 5.2 HLSL 作为单一 authoring 语言源 - -本阶段必须明确: - -- **新体系下 raster shader 统一使用 HLSL authoring** -- D3D12 直接编 HLSL -- Vulkan 由 HLSL 编到 SPIR-V -- OpenGL 由 HLSL 编到 SPIR-V,再转 GLSL 430 - -原因: - -- 如果 authoring 仍然保留 GLSL/HLSL 三套并行,永远不可能真正统一写法 -- 只有 single-source authoring,才能接近 Unity 的真实体验 - -### 5.3 backend 差异必须退到 importer / compiler 层 - -新 authoring 文件中不应再出现: - -- `#pragma backend ...` -- backend 专属 stage 文件路径表 - -这些内容应由 importer 根据 target backend 生成产物。 - -### 5.4 resource binding 不再由 shader 作者手写 `Resources(set,binding)` - -Unity 风格 shader authoring 不要求作者手写 descriptor set / binding。 - -因此新体系下应改为: - -- material 暴露属性来自 `Properties` -- engine 内建 constant buffer / texture / sampler 来自约定与 reflection -- importer 通过 HLSL reflection + 约定库推导 runtime resource layout - -也就是说: - -- authoring 层写“语义” -- importer 层生成“绑定布局” -- runtime 层消费“绑定布局” - -### 5.5 pass 选择必须回归 renderer,而不是 material - -新体系中: - -- material 只绑定 shader 与 property / keyword / texture -- renderer 按 `LightMode` 选 pass -- `Material::shaderPass` 进入弃用与最终移除路径 - -这与 Unity 的 `ShaderTagId / LightMode` 思路对齐,也更利于未来 SRP。 - -### 5.6 必须保留 legacy 兼容层 - -当前仓库已经有一批 builtin shader 和测试资产。 - -因此不能激进地“一刀切重做”,而应: - -- legacy `.shader` 继续可加载 -- 新 Unity 风格 `.shader` 进入新 importer 路径 -- builtin shader 分批迁移 -- runtime 统一落在同一套 `Shader` / artifact / variant 模型上 - ---- - -## 6. 目标架构 - -建议把 shader 体系正式分成 5 层。 - -### 6.1 Authoring 层 - -职责: - -- 让开发者以 Unity 风格书写 shader - -建议语法子集: - -- `Shader` -- `Properties` -- `SubShader` -- `Pass` -- `Tags` -- `LOD` -- `HLSLINCLUDE` -- `HLSLPROGRAM` -- `ENDHLSL` -- `#pragma vertex` -- `#pragma fragment` -- `#pragma target` -- `#pragma multi_compile` -- `#pragma shader_feature` -- `#pragma shader_feature_local` -- `Cull` -- `ZWrite` -- `ZTest` -- `Blend` -- `ColorMask` -- `Stencil` -- `Offset` - -### 6.2 Importer / Parser 层 - -职责: - -- 解析 Unity 风格 `.shader` -- 生成统一的内部 `ShaderIR` - -建议引入: - -- `ShaderAuthoringParser` -- `ShaderIR` -- `ShaderSubShaderIR` -- `ShaderPassIR` -- `ShaderKeywordDecl` -- `ShaderPassStateDesc` -- `ShaderProgramIR` - -### 6.3 Compiler / Reflection 层 - -职责: - -- 编译 authoring 中的 HLSL -- 为不同 backend 生成最终编译产物 -- 生成 resource layout / constant layout / keyword variant metadata - -建议技术路径: - -- D3D12:`DXC -> DXIL/DXBC` -- Vulkan:`DXC -> SPIR-V` -- OpenGL:`DXC -> SPIR-V -> SPIRV-Cross -> GLSL 430` - -输出: - -- 每个 pass / stage / keyword-set / backend 的编译产物 -- 反射出的 constant buffer / texture / sampler 布局 - -### 6.4 Artifact 层 - -职责: - -- 保存运行时真正消费的 shader 产物 - -建议引入新版 artifact: - -- `xcshader2` 或继续升级现有 `xcshader` - -artifact 内容至少包含: - -- shader 名称 / guid -- properties -- subshader / pass tags -- pass state -- keyword declarations -- keyword variant table -- backend binaries / backend source payload -- reflected resource layout -- include 依赖与 hash - -### 6.5 Runtime 层 - -职责: - -- renderer 根据 pass contract、keywords、backend 选择最终 variant -- material 根据 property/texture 生成常量与资源绑定 -- pipeline cache 根据 shader variant + render state 建 key - ---- - -## 7. Unity 风格 authoring 范围定义 - -### 7.1 第一阶段必须支持的语法 - -第一阶段建议正式支持: - -```shaderlab -Shader "XCEngine/Example/Lit" -{ - Properties - { - _BaseColor ("Base Color", Color) = (1,1,1,1) - _BaseMap ("Base Map", 2D) = "white" {} - _Cutoff ("Alpha Cutoff", Range(0,1)) = 0.5 - } - - HLSLINCLUDE - #include "ShaderLibrary/Core.hlsl" - ENDHLSL - - SubShader - { - Tags { "RenderType"="Opaque" "Queue"="Geometry" } - LOD 200 - - Pass - { - Name "ForwardLit" - Tags { "LightMode"="ForwardLit" } - Cull Back - ZWrite On - ZTest LEqual - Blend One Zero - - HLSLPROGRAM - #pragma target 4.5 - #pragma vertex Vert - #pragma fragment Frag - #pragma multi_compile _ XC_MAIN_LIGHT_SHADOWS - #pragma shader_feature_local _ XC_ALPHA_TEST - ENDHLSL - } - } -} -``` - -### 7.2 第一阶段暂不支持的语法 - -第一阶段可暂缓: - -- `Fallback` -- `UsePass` -- `GrabPass` -- `CustomEditor` -- `Category` -- `Dependency` -- Surface Shader -- CG fixed-function 时代遗留语义 - -这些需要列入兼容性说明,但不应阻塞首版落地。 - ---- - -## 8. 统一的 shader library 与 include 体系 - -这一层是“写法统一”能否成立的关键。 - -### 8.1 必须引入正式的 include 库 - -建议新增: - -- `engine/assets/shaderlib/ShaderLibrary/Core.hlsl` -- `engine/assets/shaderlib/ShaderLibrary/Common.hlsl` -- `engine/assets/shaderlib/ShaderLibrary/SpaceTransforms.hlsl` -- `engine/assets/shaderlib/ShaderLibrary/Lighting.hlsl` -- `engine/assets/shaderlib/ShaderLibrary/MaterialInput.hlsl` -- `engine/assets/shaderlib/ShaderLibrary/Shadow.hlsl` - -目标: - -- builtin shader 共用统一宏与公共函数 -- authoring 层不再重复声明一堆 per-object / lighting 结构 - -### 8.2 统一的内建常量组 - -建议统一为接近 Unity 的内建分组: - -- `UnityPerFrame` 或 `XCPerFrame` -- `UnityPerCamera` 或 `XCPerCamera` -- `UnityPerDraw` 或 `XCPerDraw` -- `UnityPerMaterial` 或 `XCPerMaterial` - -建议引擎内部最终保留 `XC*` 前缀实现名,但 authoring 宏层提供 Unity 风格别名。 - -### 8.3 内建纹理/采样器由 include 库与 reflection 管理 - -例如: - -- 主纹理 -- 阴影图 -- 环境图 -- sampler state - -这些都不再由 `.shader` 手填 `Resources(set,binding)`。 - ---- - -## 9. Material 体系同步改造 - -shader 统一如果不带 material 一起改,最后会停在半路。 - -### 9.1 material 的职责边界 - -新体系里 material 负责: - -- 选择 shader -- 保存 property override -- 保存 texture override -- 保存 keyword 开关 -- 保存材质级 render queue / tag override(如保留) - -新体系里 material 不再负责: - -- 指定 `shaderPass` -- 硬编码 backend 资源名 -- 猜测 shader 内的 descriptor set / binding - -### 9.2 material constant buffer 正式化 - -必须建立: - -- `Properties` -> `UnityPerMaterial/XCPerMaterial` 布局 -- importer 生成 property layout -- material 按 layout 打包 GPU 常量 -- layout/hash 进入 pipeline/material cache key - -### 9.3 keyword 体系正式化 - -建议引入: - -- `global keywords` -- `local keywords` -- material keyword set -- variant lookup key - -material 应持有: - -- `ShaderKeywordSet` - -renderer 运行时根据: - -- shader -- pass -- keyword set -- backend - -选择最终 shader variant。 - ---- - -## 10. Renderer 运行时契约调整 - -### 10.1 pass 选择规则统一为 `LightMode` - -renderer 选 pass 时只看: - -- 当前 pipeline 阶段需要的 `LightMode` -- shader/subshader/pass 是否匹配 -- backend 是否有有效 variant - -例如: - -- 主几何:`ForwardLit` / `Unlit` -- 阴影:`ShadowCaster` -- 深度:`DepthOnly` -- ObjectId:`ObjectId` -- FinalOutput:`FinalColor` - -### 10.2 material 显式 `shaderPass` 进入弃用路径 - -建议执行顺序: - -1. 第一阶段保留字段,但标记为 legacy -2. runtime 优先按 pass contract / LightMode 选 pass -3. 只有 legacy 资产才允许 fallback 到 `shaderPass` -4. migration 完成后移除 `shaderPass` 主路径职责 - -### 10.3 builtin pass metadata 继续保留,但收进 importer/runtime - -当前基于 semantic 的 builtin pass binding 解析仍然有价值,但应改成: - -- authoring/IR 层表达语义 -- compiler/reflection 层生成 binding plan -- runtime 只消费 binding plan - -而不是继续靠散落的名字匹配和 fallback。 - ---- - -## 11. 三后端统一策略 - -### 11.1 D3D12 - -目标: - -- 直接消费 HLSL 编译产物 -- 反射得到 root signature / resource layout 所需元数据 - -第一阶段可继续沿用 `ps_5_0 / vs_5_0`,但建议同时规划升级: - -- 后续逐步转 `SM 6.x` - -### 11.2 Vulkan - -目标: - -- 统一吃由 HLSL 编到 SPIR-V 的产物 -- 摆脱独立 `.vk.glsl` 长期维护 - -### 11.3 OpenGL - -目标: - -- 不再长期维护独立 `.glsl` authoring 文件 -- importer 自动生成 OpenGL 目标 GLSL 430 - -这一步会是整个阶段最大的工程风险之一,但它是“写法统一”绕不开的核心点。 - -### 11.4 迁移期间的策略 - -在新体系落稳前,允许: - -- legacy backend-specific variant 继续存在 -- 新 Unity 风格 shader 走统一 HLSL single-source 路径 - -两套 importer 并行一段时间,最终再逐步淘汰 legacy path。 - ---- - -## 12. 分阶段实施计划 - -### Phase A:冻结目标与建立兼容边界 - -目标: - -- 明确“什么叫 Unity 风格 shader” -- 明确 legacy 与新 authoring 的兼容边界 - -工作项: - -1. 写清 Unity 风格支持子集。 -2. 明确旧 `.shader` 的 legacy 模式规则。 -3. 明确新 authoring 中禁止出现: - - `#pragma backend` - - `Resources(set,binding)` -4. 明确 material 中 `shaderPass` 的弃用策略。 - -完成标准: - -- 文档、命名、兼容边界全部写死 - -### Phase B:建立新的 Shader Authoring Parser 与 IR - -目标: - -- 新 `.shader` authoring 能导入到统一 `ShaderIR` - -工作项: - -1. 新增 parser,支持: - - `Shader / Properties / SubShader / Pass / Tags` - - `HLSLINCLUDE / HLSLPROGRAM` - - `#pragma vertex / fragment / target / multi_compile / shader_feature` - - pass state DSL -2. 生成 `ShaderIR` -3. 支持 include 依赖收集 -4. 保留 legacy importer - -完成标准: - -- authoring parser 单测齐全 -- 可以把一个 Unity 风格 shader 解析成稳定 IR - -### Phase C:建立单一 HLSL 编译链 - -目标: - -- 打通 `HLSL -> D3D12/Vulkan/OpenGL` 编译管线 - -工作项: - -1. 接入 DXC 编译 HLSL。 -2. Vulkan 产出 SPIR-V。 -3. OpenGL 产出 GLSL 430。 -4. 建立 reflection 数据抽取: - - cbuffer - - texture - - sampler - - entry point - - keywords -5. 缓存编译产物与依赖 hash。 - -完成标准: - -- 一份 HLSL authoring 能生成三后端产物 -- OpenGL 不再依赖手写 `.glsl` 作为新体系长期主路径 - -### Phase D:Material 与 property/keyword/runtime binding 正式化 - -目标: - -- material 能正式驱动新 shader artifact - -工作项: - -1. 引入正式 property layout。 -2. 引入 material keyword set。 -3. 生成 `PerMaterial` 常量缓冲布局。 -4. texture/sampler 绑定从 reflection/约定生成。 -5. 让 material 运行时不再关心 `set/binding`。 - -完成标准: - -- material property / texture / keyword 真正接入 GPU 绑定链 - -### Phase E:renderer pass 选择与 pipeline cache 收口 - -目标: - -- renderer 完全按 pass contract 驱动 shader - -工作项: - -1. 按 `LightMode` 选 pass。 -2. `shaderPass` 降级为 legacy fallback。 -3. pipeline cache key 引入: - - shader artifact id - - pass id - - keyword variant id - - render state -4. builtin pass 的 runtime contract 全部切到新 artifact。 - -完成标准: - -- renderer 主路径不再依赖 material 显式 pass 指定 - -### Phase F:分批迁移 builtin shader - -建议迁移顺序: - -1. `unlit` -2. `forward-lit` -3. `depth-only` -4. `shadow-caster` -5. `object-id` -6. `skybox` -7. `color-scale-post-process` -8. `final-color` - -完成标准: - -- builtin shader 全部有新 authoring 版本 -- 旧版 backend 分发文件不再是长期主定义来源 - -### Phase G:文档、测试、旧路径收口 - -目标: - -- 让新旧体系的边界最终收口 - -工作项: - -1. 更新 `tests/TEST_SPEC.md` 中 shader/material 测试矩阵。 -2. 增加 authoring parser / compiler / runtime 回归测试。 -3. 更新开发文档。 -4. 标记 legacy 路径弃用阶段。 - -完成标准: - -- 新体系文档、测试、builtin 迁移都完成 - ---- - -## 13. 测试策略 - -### 13.1 Parser / Importer 单测 - -必须覆盖: - -- Properties 解析 -- SubShader / Pass / Tags 解析 -- HLSLINCLUDE / HLSLPROGRAM 解析 -- pragma 解析 -- pass state 解析 -- include 依赖收集 -- legacy / 新 authoring 双路径兼容 - -### 13.2 Compiler 单测 - -必须覆盖: - -- 单一 HLSL 源能生成三后端产物 -- reflection 结果稳定 -- keyword variant 正确展开 -- 编译错误日志可读、可定位到 authoring 源文件 - -### 13.3 Material 单测 - -必须覆盖: - -- property 默认值 -- property override -- texture binding -- keyword set -- 常量缓冲布局打包 - -### 13.4 Rendering 单测 - -必须覆盖: - -- renderer 按 `LightMode` 选 pass -- legacy `shaderPass` fallback 行为 -- keyword variant 参与 pipeline cache key -- final color / post-process / shadow / object-id 不回退 - -### 13.5 集成测试 - -至少回归: - -- `material_state_scene` -- `transparent_material_scene` -- `camera_stack_scene` -- `directional_shadow_scene` -- `multi_light_scene` -- `skybox_scene` -- `post_process_scene` -- `final_color_scene` -- `object_id_scene` - -要求: - -- 三后端全部跑通 -- GT 不回退 - ---- - -## 14. 风险与控制策略 - -### 风险 1:OpenGL 是统一写法最难的一环 - -原因: - -- OpenGL 当前直接吃 GLSL -- 统一 authoring 要求它改为编译链生成目标 GLSL - -控制策略: - -- 先把 importer/IR 做好 -- OpenGL 先走“生成 GLSL 文本产物”路径 -- legacy OpenGL GLSL 文件在迁移期保留 fallback - -### 风险 2:一次性追 full Unity 语法会把阶段拖爆 - -控制策略: - -- 明确 first-class 子集 -- 先做 SRP 真正依赖的 authoring 基础 -- 非关键语法延后 - -### 风险 3:material / pass 迁移会破坏当前 builtin renderer - -控制策略: - -- legacy runtime path 保留一段时间 -- builtin shader 分批迁移 -- 每迁移一个 shader 就跑对应 integration - -### 风险 4:编译错误如果不可读,会极大拖慢落地 - -控制策略: - -- 必须做 authoring 源到 backend 编译日志的映射 -- 错误日志要带 shader 名、pass 名、stage、backend、源文件行号 - ---- - -## 15. 收口判定 - -满足下面条件时,本阶段可视为完成: - -1. 新 `.shader` authoring 采用 Unity 风格子集。 -2. 新体系 shader authoring 中不再出现 `#pragma backend`。 -3. 新体系 shader authoring 中不再出现 `Resources(set,binding)`。 -4. HLSL single-source 能生成 D3D12 / Vulkan / OpenGL 三后端产物。 -5. material 已正式接入 property / keyword / texture binding runtime。 -6. renderer 按 `LightMode` 正式选择 pass。 -7. `shaderPass` 只剩 legacy fallback,不再是主路径。 -8. builtin shader 已完成新体系迁移。 -9. 三后端关键集成测试全部通过。 - ---- - -## 16. 与后续 SRP 的承接关系 - -这一阶段完成后,才能真正自然地承接: - -- `RenderPipelineAsset` -- `RenderPipeline` -- `RendererFeature` -- `ScriptableRenderPass` -- C# 层对 shader/material/keyword 的控制 - -承接关系应当是: - -```text -Unity-style Shader Authoring - -> Shader Importer / IR / Artifact - -> Native Material & Pass Runtime - -> Native Renderer Pass Contract - -> C# SRP / RendererFeature -``` - -也就是说: - -- 这一阶段不是 SRP -- 但这是 SRP 成立前必须先做完的最后一层底座 - ---- - -## 17. 一句话总结 - -当前 shader 体系不是没有,而是还停在“过渡态”。 - -下一阶段的正确方向不是继续堆更多 shader 功能,而是: - -- **把 `.shader` 的 authoring 真正统一成 Unity 风格** -- **把 backend 差异与 binding 细节收回 importer / compiler 层** -- **把 material / pass / variant/runtime contract 一次性做正式** - -只有这样,后面的 SRP 才不会建立在一层伪统一的 shader 体系上。 diff --git a/docs/used/Renderer下一阶段_多光源正式化与Lighting闭环计划_完成归档_2026-04-05.md b/docs/used/Renderer下一阶段_多光源正式化与Lighting闭环计划_完成归档_2026-04-05.md deleted file mode 100644 index 63e645dc..00000000 --- a/docs/used/Renderer下一阶段_多光源正式化与Lighting闭环计划_完成归档_2026-04-05.md +++ /dev/null @@ -1,526 +0,0 @@ -# Renderer 下一阶段:多光源正式化与 Lighting 闭环计划 -日期:`2026-04-05` - -## 1. 阶段定位 - -当前 rendering 主线已经完成了“默认 runtime forward renderer 第一阶段”的主体闭环: - -- `SceneRenderer -> CameraRenderer -> RenderPipeline` 主链已经落地 -- 主方向光阴影已经由 renderer 自动规划并接入 forward pass -- `SceneView / GameView` 已统一接到 runtime renderer 主链 -- `object-id` 已经是正式的 GPU 路线 -- 三后端已有一批稳定的 rendering integration coverage - -因此,当前阶段真正未收口的主线不再是: - -- `render graph` -- editor 专属效果扩展 -- 更复杂的后处理系统 - -而是: - -**把当前“只支持主方向光”的 lighting runtime,推进成一个正式可扩展的多光源 forward runtime。** - -这份计划只聚焦这一条主线。 - -## 2. 为什么现在必须先做多光源 - -当前源码真实状态已经说明问题: - -- `RenderLightingData` 仍然只有 `mainDirectionalLight` 和 `mainDirectionalShadow` -- `RenderSceneExtractor` 只会提取一个主方向光 -- `BuiltinForwardPipeline` 只消费这一套最小 lighting 数据 -- `LightComponent` 虽然已经有 `Directional / Point / Spot` 三种类型,但 runtime renderer 还没有把它们正式接通 - -这意味着现在的 renderer 已经不是“不能用”,而是: - -**单方向光路径可用,但正式多光源路径缺失。** - -如果这时去做 `render graph`,只是给尚未完全闭环的 runtime lighting 套上更复杂的调度壳。 -如果这时去继续堆 editor 视觉功能,也是在建立在不完整 runtime 光照基础之上。 - -所以正确顺序必须是: - -1. 先把多光源正式化 -2. 再进入 skybox / environment / post-process seam -3. 最后才考虑 render graph 这类更高层调度框架 - -## 3. 与 Unity 风格架构的对齐原则 - -本阶段继续严格遵守当前工程的核心分层: - -- `RHI` 只负责 GPU 抽象 -- `Renderer` 负责 scene extraction、lighting data、pass orchestration、shader/material contract -- `Editor` 只作为 renderer 宿主与 overlay 使用方,不拥有独立 runtime lighting 主链 - -与 Unity 风格对齐时,本阶段遵守以下原则: - -1. 主方向光与 additional lights 属于 runtime renderer 正式能力 -2. editor 的 grid / outline / gizmo / icon 不是本阶段主线 -3. shader/material 仍然走统一 `.shader + Pass + backend variants` 路线,而不是退回后端硬编码 -4. 当前阶段先做“有限 additional lights 的前向提交”,不直接跳到 clustered / deferred - -## 4. 当前真实现状 - -### 4.1 已完成的部分 - -- 自动方向光阴影规划已经存在 -- `CameraRenderer` 已有正式 pass 顺序: - - `preScenePasses` - - `shadowCaster` - - `depthOnly` - - `main pipeline` - - `objectId` - - `postScenePasses` - - `overlayPasses` -- `BuiltinForwardPipeline` 已形成正式 shader/material/pass contract -- editor Scene/Game viewport 已经走 runtime renderer 主链 -- `object-id` picking 已经是正式主路径 - -### 4.2 还没收口的部分 - -- `RenderLightingData` 没有正式 additional light 数据模型 -- `RenderSceneExtractor` 没有 point / spot / additional directional 的提取与排序 -- `BuiltinForwardPipeline` 没有正式的 additional light GPU 提交路径 -- 当前 forward lit shader 常量组织仍偏“单主光最小路径” -- integration matrix 中还没有 `multi_light_scene` 与 `spot_light_scene` - -## 5. 本阶段总目标 - -本阶段的目标只有一句话: - -**建立一个正式、稳定、可测试、三后端一致的 bounded forward multi-light runtime。** - -收口后应达到: - -1. `Directional / Point / Spot` 三类光源能通过 runtime renderer 正式影响画面 -2. 主方向光阴影能力保持不回退 -3. 三后端使用同一套 shader/material contract -4. Scene/Game View 继续直接复用同一条 runtime renderer 光照路径 -5. 为下一阶段 skybox / environment / post-process 留出清晰接入点 - -## 6. 非目标 - -本阶段明确不做: - -- `render graph` -- deferred rendering -- tiled / clustered lighting -- cascaded shadow maps -- point / spot shadow -- GI / IBL / probe / reflection probe -- PBR 大重构 -- editor 专属渲染效果继续扩张 - -## 7. 设计方案 - -### 7.1 正式化 lighting 数据模型 - -建议把当前 `RenderLightingData` 从: - -- `mainDirectionalLight` -- `mainDirectionalShadow` - -扩展为: - -- `mainDirectionalLight` -- `mainDirectionalShadow` -- `additionalLights` - -其中 `additionalLights` 的单项建议包含: - -- `type` -- `enabled` -- `color` -- `intensity` -- `position` -- `direction` -- `range` -- `spotAngle` -- `castsShadows` - -这里的关键原则是: - -1. 主方向光继续单独建模 -2. additional lights 统一为一个正式数组,而不是分散塞进临时常量 -3. 当前阶段允许 additional lights 无阴影 -4. 数据模型要能自然承接后续 per-object light list 或 clustered light list,而不是很快被推翻 - -### 7.2 限定本阶段的 light strategy - -本阶段不追求“无限灯光”,而采用**有限 additional lights 前向提交**。 - -建议首版规则: - -- 保留 1 个 `main directional light` -- 额外提交最多 `8` 个 `additional lights` -- `additional lights` 可以包含: - - 非主方向光的 directional - - point - - spot - -之所以建议先定成 `8`: - -- 与常见 forward runtime 的第一阶段预算接近 -- 常量布局、三后端一致性和 integration GT 更容易稳定 -- 以后即使扩到 `16` 或改成 per-object list,也不会推翻整体 contract - -### 7.3 CPU 侧提取、裁剪与排序策略 - -`RenderSceneExtractor` 需要正式负责 additional light 提取。 - -首版建议策略: - -1. 过滤掉无效灯光 - - component disabled - - game object inactive - - 不在 camera culling mask 内 -2. 选择主方向光 - - 仍沿用“最重要的 directional light 单独提升为 main light”的策略 -3. 其余光源进入 additional list -4. 对 additional list 做稳定排序 -5. 截断到最大上限 - -为了保证输出稳定、便于 GT 固化,建议排序规则写死为: - -1. additional directional lights 优先 -2. point / spot 在后 -3. 同类型内按影响力降序 -4. 影响力相同则按 object/entity id 稳定排序 - -影响力计算不必一步到位做复杂物理模型,首版只需要稳定且可解释。建议: - -- directional:按 `intensity` -- point / spot:按 `intensity / max(distance^2, epsilon)` 的近似影响值 - -这里先按 camera 视角做 bounded list,不在本阶段直接引入 per-object light assignment。 - -### 7.4 收紧 shader / material / pass contract - -当前 forward path 的一个根本问题是:场景级 lighting 数据仍部分塞在 `PerObjectConstants` 里。 -这对 additional lights 来说不是正式方案。 - -因此本阶段需要把 forward lit 资源合约收紧为: - -- `PerObject` - - 只保留与单个 draw object 强相关的数据 - - 例如 `model / inverse model / view / projection` -- `PerMaterial` - - 继续承载材质数据 -- `Lighting` - - 新增正式 scene/frame lighting constant buffer - - 包含主光与 additional lights -- `ShadowReceiver` - - 继续承载主方向光阴影矩阵与 shadow 参数 -- `BaseColorTexture` -- `LinearClampSampler` -- `ShadowMapTexture` -- `ShadowMapSampler` - -这一步非常关键,因为它决定后续扩展是否正规: - -1. scene/frame lighting 不再随每个 draw 重复写入 `PerObject` -2. additional light 数组有正式宿主 -3. 三后端可以共享同一份高层资源声明 - -### 7.5 三后端 shader 路线必须保持统一 - -本阶段继续沿用当前已经建立起来的 shader 路线: - -- 高层作者视图仍是统一 `.shader` -- 每个 `Pass` 通过 backend variants 指向: - - OpenGL GLSL - - Vulkan GLSL - - D3D12 HLSL - -也就是说: - -1. 不在材质系统层面分裂成三套 API 专属 shader 入口 -2. forward lit pass 的资源声明只保留一份 canonical contract -3. 三后端只在 stage variant 实现层分开 - -这点必须继续保持,因为它是后续 Unity-like shader authoring 的前提。 - -### 7.6 Forward shader 的首版光照职责边界 - -本阶段只要求把光照主路径打通,不把 PBR 一起打包进来。 - -建议职责边界: - -- `main directional light` - - 支持现有阴影 -- `additional directional lights` - - 无阴影 -- `point lights` - - 范围衰减 -- `spot lights` - - 范围衰减 + 锥角衰减 - -首版 shading model 以稳定为主: - -- 延续当前 forward lit 的 diffuse / lambert 风格 -- 不在本阶段引入新的 specular / BRDF 复杂度 - -### 7.7 Spot light 参数策略 - -当前 `LightComponent` 只有一个 `spotAngle`,没有 `innerSpotAngle`。 - -为了避免本阶段扩散到组件/序列化/UI 全链路重构,建议首版这样处理: - -1. `outer cone` 直接使用 `spotAngle` -2. `inner cone` 由 runtime 以固定比例推导 - - 例如 `inner = outer * 0.8` -3. 等 shader/material 与 renderer 多光源闭环稳定后,再决定是否把 `innerSpotAngle` 正式上升为组件字段 - -这能保证当前阶段先把 runtime 多光源打通,而不是被 editor 数据建模拖慢。 - -### 7.8 BuiltinForwardPipeline 的正式改造方向 - -`BuiltinForwardPipeline` 本阶段要完成的,不只是“shader 多写几行光照循环”,而是正式接管以下职责: - -1. 创建/维护 `Lighting` 常量资源 -2. 每帧更新一份 bounded additional light 数据 -3. 在 draw 时稳定绑定 `Lighting` 与 `ShadowReceiver` -4. 保持 `Unlit / ObjectId / DepthOnly / ShadowCaster` 不被污染 - -简化后目标是: - -- lit path 正式消费 multi-light 数据 -- unlit path 保持简单 -- object-id / shadow-caster 不承担 lighting 语义 - -## 8. 实施分阶段 - -## 8.1 Phase A:Lighting Contract 正式化 - -### 目标 - -把 scene/frame lighting 常量从 `PerObject` 里拆出来,建立正式 `Lighting` 合约。 - -### 具体工作 - -- 重构 forward lit shader 的资源声明 -- 新增 `Lighting` 常量缓冲结构 -- 将主方向光数据迁移到 `Lighting` -- 保留 `ShadowReceiver` 只处理阴影接收参数 -- 调整 `BuiltinForwardPipeline` 的 descriptor set 解析与绑定逻辑 - -### 验收标准 - -- 主方向光现有画面不回退 -- 三后端 forward lit pass 都能正确解析新资源布局 -- `unlit / object-id / shadow-caster` 不受影响 - -## 8.2 Phase B:CPU Additional Light Extraction - -### 目标 - -让 `RenderSceneExtractor` 正式产出 bounded additional lights。 - -### 具体工作 - -- 扩展 `RenderLightingData` -- 增加 additional light 数据结构 -- 实现过滤、排序、截断策略 -- 补 unit tests,覆盖: - - 主光选择 - - additional list 排序 - - 上限裁剪 - - point / spot 提取正确性 - -### 验收标准 - -- extractor 可稳定输出 additional light 列表 -- 输出顺序稳定、可预测 -- 现有单主光场景输出保持一致 - -## 8.3 Phase C:Forward Multi-Light Consumption - -### 目标 - -让 `BuiltinForwardPipeline` 和 builtin forward lit shader 正式消费 additional lights。 - -### 具体工作 - -- 新增 additional light 常量布局 -- 在 shader 中加入 bounded light loop -- 实现 point / spot 的首版 attenuation -- 保持 main directional shadow 不回退 -- 为 additional directional / point / spot 明确统一数学路径 - -### 验收标准 - -- 多灯光真实影响画面 -- 主方向光阴影仍正确 -- 三后端输出保持一致,没有明显分叉 - -## 8.4 Phase D:正式集成测试补齐 - -### 目标 - -把 multi-light 路径钉死在 integration matrix 中。 - -### 新增场景 - -- `tests/Rendering/integration/multi_light_scene` -- `tests/Rendering/integration/spot_light_scene` - -### 场景设计要求 - -`multi_light_scene`: - -- 至少 1 个主方向光 -- 至少 2 个 additional lights -- 场景几何要让不同灯光覆盖区域明显分离 -- GT 图必须一眼能看出 additional lights 是否生效 - -`spot_light_scene`: - -- 一个明显的 spot hot area -- 同时能看到范围边界和角度边界 -- 最好包含一个立体物体和一个地面,避免只看平面亮斑 - -### 附加要求 - -- 三后端都必须有 GT -- 除整图 GT 比对外,可补少量像素采样断言,以降低误报成本 -- 必须把 `directional_shadow_scene`、`backpack_lit_scene` 一起纳入回归验证 - -### 验收标准 - -- `multi_light_scene` 三后端全绿 -- `spot_light_scene` 三后端全绿 -- 现有 lighting 相关 integration 不回退 - -## 8.5 Phase E:文档与阶段收口 - -### 目标 - -让本阶段结束后,rendering 主线边界重新清晰。 - -### 具体工作 - -- 更新 `tests/TEST_SPEC.md` -- 更新 renderer 相关 API / guide 文档 -- 把本阶段已完成的旧计划归档到 `docs/used` -- 输出“多光源阶段收口说明” - -### 验收标准 - -- 文档、测试矩阵、实现三者口径一致 -- 下一阶段可以自然承接 skybox / environment / post-process - -## 9. 测试策略 - -本阶段测试必须分三层执行: - -### 9.1 Unit - -- `RenderSceneExtractor` multi-light 提取 -- `BuiltinForwardPipeline` lighting resource layout / descriptor binding -- light attenuation 数学辅助函数 - -### 9.2 Integration - -- `multi_light_scene` -- `spot_light_scene` -- 回归: - - `directional_shadow_scene` - - `backpack_lit_scene` - - `camera_stack_scene` - - `transparent_material_scene` - - `cull_material_scene` - - `depth_sort_scene` - - `material_state_scene` - - `offscreen_scene` - -### 9.3 Editor Runtime Smoke - -- SceneView 光照结果与 GameView 主路径一致 -- object-id picking 不因 lighting contract 变更而回退 -- editor overlay 不污染 runtime lighting - -## 10. 风险与控制 - -### 风险 1:把 multi-light 直接塞进 `PerObject` - -后果: - -- descriptor update 开销恶化 -- 语义继续混乱 -- 后续 skybox / environment / post-process 继续难收口 - -控制策略: - -- 本阶段第一步就强制建立 `Lighting` 常量资源 - -### 风险 2:additional lights 排序不稳定导致 GT 抖动 - -后果: - -- 三后端图像对比不稳定 -- 回归测试容易出现“偶发错图” - -控制策略: - -- 排序规则必须写死 -- 相同影响力要有稳定 tie-breaker - -### 风险 3:spot light 参数扩散到组件/UI/序列化全链路 - -后果: - -- 本阶段失焦 -- runtime 主线再次拖长 - -控制策略: - -- 首版先用单 `spotAngle` + 固定 inner ratio - -### 风险 4:三后端 shader 细节分叉 - -后果: - -- D3D12 / OpenGL / Vulkan 结果不一致 - -控制策略: - -- 高层资源 contract 只保留一份 canonical 声明 -- integration 以三后端同时跑为硬约束 - -## 11. 阶段完成判定 - -满足以下条件时,本阶段可以认为收口: - -1. `RenderLightingData` 已正式支持 additional lights -2. `BuiltinForwardPipeline` 已正式消费 bounded additional lights -3. `multi_light_scene` 与 `spot_light_scene` 三后端通过 -4. `directional_shadow_scene` 与 `backpack_lit_scene` 无回退 -5. Scene/Game View 继续通过统一 runtime renderer 显示一致光照结果 -6. 文档与测试矩阵已同步 - -## 12. 本阶段之后的正确下一步 - -多光源阶段收口后,renderer 的下一阶段才应该进入: - -1. skybox / environment seam -2. post-process 正式入口 -3. 更完整的 renderer frame composition - -而不是立即跳去: - -- `render graph` -- deferred -- editor-only 视觉堆叠 - -## 13. 与旧计划的关系 - -`2026-04-04` 的《Renderer 下一阶段:方向光阴影与光照闭环计划》已经完成了其中大部分更高优先级内容: - -- 自动方向光阴影 -- Scene/Game runtime renderer 接入 -- object-id 正式化 -- 当前 forward runtime 主链稳定化 - -因此它已经不适合作为当前执行入口,现已归档。 -本文件从 `2026-04-05` 起接管 rendering 主线的正式下一阶段。 diff --git a/docs/used/Renderer下一阶段_方向光阴影与光照闭环计划_完成归档_2026-04-05.md b/docs/used/Renderer下一阶段_方向光阴影与光照闭环计划_完成归档_2026-04-05.md deleted file mode 100644 index d9bb7d4e..00000000 --- a/docs/used/Renderer下一阶段_方向光阴影与光照闭环计划_完成归档_2026-04-05.md +++ /dev/null @@ -1,462 +0,0 @@ -# Renderer 下一阶段:方向光阴影与光照闭环计划 - -日期:`2026-04-04` - -## 1. 阶段背景 - -当前 `shader / material / rendering` 主线已经完成了这一阶段应有的基础闭环: - -- `Shader` 已具备 `properties / passes / resources / backend variants` 运行时契约 -- `Material` 已具备 schema 驱动的属性、纹理、常量布局与 pass resource binding plan -- `SceneRenderer / CameraRenderer / BuiltinForwardPipeline` 已经打通 -- `ObjectId / DepthOnly / ShadowCaster` 已具备独立 pass 与 request 骨架 -- 三后端 `D3D12 / OpenGL / Vulkan` 的当前渲染集成测试已全绿 - -但这仍然只是“能稳定出图的基础 forward renderer”,还不是“能承担引擎默认场景渲染”的完整运行时。 -下一阶段不应该优先做 `render graph`,也不应该先追求更复杂的编辑器功能,而应该把当前 renderer 推进成一个真正可用的默认前向渲染器。 - -这一阶段的主线目标只有一句话: - -**把当前 renderer 从“无阴影的基础出图器”推进到“具备方向光、阴影、基础多光源闭环,并可稳定承接 Scene/Game 视图”的运行时渲染器。** - -## 2. 为什么下一步是这个 - -当前代码结构已经说明了下一阶段的最短主路径: - -- `RenderSceneExtractor` 里已经有 `RenderLightingData.mainDirectionalLight` -- `BuiltinForwardPipeline` 已经在 per-object constants 中消费主方向光方向和颜色 -- `CameraRenderRequest` 已经有 `shadowCaster / depthOnly / objectId` request -- `BuiltinShadowCasterPass / BuiltinDepthOnlyPass` 已经存在 - -也就是说: - -1. 光照数据通路已经有最小入口 -2. 阴影 pass 的执行骨架已经有了 -3. 缺的是把这些骨架真正接成 frame 级闭环 - -如果这时跳去做 `render graph`,只会把尚未收紧的运行时逻辑包上一层更复杂的调度壳。 -如果这时去做更复杂的 editor 特效,也是在建立在“runtime lighting 还不完整”的地基上。 - -所以本阶段应当严格沿着 Unity 风格的自然演进路径推进: - -1. 先把默认前向运行时补完整 -2. 再谈更高层调度与优化 - -## 3. 与 Unity 式架构的对齐原则 - -本阶段继续遵循现有总设计,不偏离 `RHI -> Renderer -> Editor/Runtime` 这条主分层: - -- `RHI` 只负责统一 GPU 抽象,不承载场景渲染逻辑 -- `Renderer` 负责场景提取、光照数据组织、shadow/depth/object-id/forward 等 pass 执行 -- `Editor` 只是 renderer 的宿主与附加 overlay/pass 使用方,不拥有独立的一套 runtime 渲染逻辑 - -与 Unity 对齐时,要注意以下边界: - -- 阴影图、主光照、相机渲染请求,这些属于 runtime renderer 的正式能力 -- grid、outline、gizmo、icon,这些属于 editor 专属叠加能力 -- editor 需要复用 renderer,但不能反向污染 runtime 主链 - -因此,这一阶段做的方向光阴影、基础多光源、GameView/SceneView 统一接入,都是正式主线。 -`GPU picking`、editor outline、gizmo 美术化这些,都不应抢主线优先级。 - -## 4. 当前真实现状 - -从当前代码看,renderer 已具备但尚未闭环的点如下。 - -### 4.1 已经具备的能力 - -- `SceneRenderer` 已支持多 camera request、camera stack、surface render area -- `CameraRenderer` 已支持 `pre -> shadowCaster -> depthOnly -> main pipeline -> objectId -> post` -- `BuiltinForwardPipeline` 已支持 `ForwardLit / Unlit` 的统一 shader/material contract -- `BuiltinObjectIdPass` 已正式接入 object-id 渲染路径 -- `BuiltinDepthOnlyPass / BuiltinShadowCasterPass` 已经具备 pass 级执行能力 -- `RenderSceneExtractor` 已能提取主方向光 - -### 4.2 尚未闭环的能力 - -- 阴影图尚未由 `SceneRenderer/CameraRenderer` 正式规划、分配、执行与回收 -- forward 主通道尚未消费 shadow map 结果 -- 光照仍停留在“单主方向光最小数据”,没有正式多光源提交模型 -- shadow request 目前更像通用 hook,而不是 renderer 自动生成的正式帧请求 -- 缺少以“阴影正确性”为目标的正式集成测试场景 -- 缺少以“多光源正确性”为目标的正式集成测试场景 - -### 4.3 这意味着什么 - -当前 renderer 的问题已经不再是“架构没有”,而是: - -**架构有了,但 runtime lighting/shadow 这条主业务链尚未贯通。** - -## 5. 本阶段总体目标 - -本阶段拆成四个连续目标: - -1. 建立正式的方向光阴影闭环 -2. 建立正式的前向多光源数据通路 -3. 统一 SceneView / GameView 对 runtime renderer 的使用方式 -4. 为下一阶段的 post-process 与 renderer 扩展收紧边界 - -其中优先级严格如下: - -1. `Directional Light + Shadow Map` -2. `Forward 多光源` -3. `Scene/Game 视图统一使用正式 renderer 能力` -4. `补文档、补测试、清理旧临时路径` - -## 6. 分阶段实施计划 - -## 6.1 Phase A:方向光阴影闭环 - -### 目标 - -让场景中的主方向光真正生成 shadow map,并在主 forward pass 中被采样,形成跨三后端稳定一致的阴影结果。 - -### 具体工作 - -#### A1. 正式定义 shadow frame 数据模型 - -新增或收紧 renderer 内部数据结构,至少明确: - -- 主方向光是否需要阴影 -- shadow map 尺寸与格式 -- light-space view/projection 矩阵 -- shadow caster 渲染 surface -- forward receiver 采样所需的阴影参数 - -这里的目标不是先做复杂 cascades,而是先做单张 directional shadow map MVP。 - -#### A2. 由 renderer 自动生成 shadow caster request - -不能继续依赖测试或上层调用者手工拼 `shadowCaster` request。 -应由 `SceneRenderer` 或 `CameraRenderer` 在主 camera 渲染前自动生成: - -- 主方向光对应的 shadow camera data -- shadow surface -- clear flags -- shadow pass 执行顺序 - -也就是说,要把“有 shadow pass 骨架”提升成“runtime renderer 正式调度 shadow pass”。 - -#### A3. 收紧 `BuiltinShadowCasterPass` - -确认并补齐以下行为: - -- 只渲染 `castShadows = true` 的可见物体 -- 正确处理 section/material pass 选择 -- 只依赖 shadow caster 所需最小资源 -- 在三后端下都使用统一的深度输出语义 - -#### A4. 在 `BuiltinForwardPipeline` 中消费 shadow map - -forward pass 至少补齐: - -- shadow map SRV 绑定 -- light-space position 计算 -- shadow compare -- 基础 bias -- 基础 PCF 或最小稳定采样 - -这一阶段不追求高级阴影质量,但必须追求: - -- 没有明显自阴影灾难 -- 没有跨后端严重不一致 -- 测试图可稳定固化 GT - -#### A5. 增加阴影集成测试 - -新增至少一个正式场景: - -- `tests/Rendering/integration/directional_shadow_scene` - -场景要求: - -- 至少包含一盏方向光 -- 至少包含一个会投影的物体 -- 至少包含一个接收阴影的地面或大平面 -- 阴影边界、方向、遮挡关系都足够稳定,适合 GT 比对 - -### 验收标准 - -- 三后端都能稳定生成方向光阴影 -- forward pass 正式消费 shadow map -- `directional_shadow_scene` 三后端 GT 全绿 -- 不破坏现有全部 rendering integration - -## 6.2 Phase B:前向多光源闭环 - -### 目标 - -把当前只支持主方向光的 lighting 数据模型,推进为可正式承接多个灯光的前向运行时。 - -### 具体工作 - -#### B1. 扩展 `RenderLightingData` - -从当前的: - -- `mainDirectionalLight` - -扩展到至少可描述: - -- `main directional light` -- `additional directional lights` -- `point lights` -- `spot lights` - -注意这里不一定一步到位做完整 Unity 灯光体系,但要保证数据模型不会很快被推翻。 - -#### B2. 明确本阶段多光源策略 - -当前阶段建议使用: - -- 单主方向光 -- 有上限的 additional lights -- CPU 侧整理一份稳定 light list -- GPU 侧通过常量缓冲或结构化数据提交 - -本阶段**不做**: - -- clustered lighting -- tiled lighting -- deferred lighting - -这是为了保证先把可验证的前向路径收紧。 - -#### B3. 扩展 forward shader/material contract - -补齐多光源所需的 shader 输入: - -- additional light count -- light position / direction / color / range / spot angle -- shadowed main light 与 non-shadowed additional lights 的职责边界 - -要求仍沿用当前的 shader/material/pass contract,不要回退成硬编码散乱常量。 - -#### B4. 新增多光源集成测试 - -新增至少两个场景: - -- `multi_light_scene` -- `spot_light_scene` - -场景目标: - -- 验证 point/spot 的衰减与照明范围 -- 验证 additional lights 会真实影响画面 -- 验证三后端输出保持一致 - -### 验收标准 - -- `RenderSceneExtractor` 能提取正式多光源数据 -- `BuiltinForwardPipeline` 能消费多光源 -- 新增多光源场景三后端 GT 全绿 -- 方向光阴影能力不被破坏 - -## 6.3 Phase C:SceneView / GameView runtime 接入收紧 - -### 目标 - -让 editor 的场景与游戏视图都建立在同一套 runtime renderer 能力上,而不是继续沿着临时 editor 路径分叉。 - -### 具体工作 - -#### C1. 明确 runtime pass 与 editor overlay pass 的边界 - -正式定义: - -- runtime 正式 pass:shadow caster、depth only、forward、object-id、offscreen copy 等 -- editor 附加 pass:grid、outline、icon、gizmo 等 - -要求: - -- runtime pass 可脱离 editor 独立工作 -- editor pass 只能叠加,不能挟持 runtime 主流程 - -#### C2. GameView 走正式相机渲染请求 - -GameView 必须通过标准 `CameraRenderRequest` 驱动 renderer,不能再依赖 editor 特殊逻辑直接拼接。 - -#### C3. SceneView 继续复用 renderer - -SceneView 应: - -- 复用正式相机渲染链 -- 在其上叠加 editor overlay -- 允许 object-id / outline / grid 等继续作为 editor 增量能力存在 - -### 验收标准 - -- SceneView 与 GameView 都走正式 renderer 主链 -- editor 专属 overlay 不污染 runtime pass -- 不出现“editor 正常、runtime 不正常”或反之的分叉 - -## 6.4 Phase D:收口与稳定性整理 - -### 目标 - -在新能力落地后,把这一阶段的测试、文档、边界彻底收紧。 - -### 具体工作 - -#### D1. 补齐测试矩阵 - -至少确保以下持续可跑: - -- `shader_tests` -- `material_tests` -- `mesh_tests` -- `rendering_unit_tests` -- 全部 `tests/Rendering/integration` - -新增场景至少包括: - -- `directional_shadow_scene` -- `multi_light_scene` -- `spot_light_scene` - -#### D2. 清理临时路径 - -逐项检查并收紧: - -- 是否仍有测试手工拼 shadow request -- 是否仍有 runtime/editor 职责混用 -- 是否仍有与 shader/material contract 相冲突的旧 lighting 常量路径 - -#### D3. 文档归档 - -这一阶段结束后,应补一份阶段收口说明,并把过期计划归档到 `docs/plan/used/`。 - -### 验收标准 - -- 阴影、多光源、Scene/Game 接入均通过测试 -- 当前计划中的临时方案被收紧到清晰边界内 -- 下一阶段可以自然承接 skybox/post-process,而不是继续补地基 - -## 7. 测试策略 - -这一阶段的测试必须比前一阶段更严格,因为它开始影响真正的场景表现。 - -### 7.1 单元测试 - -重点补以下测试: - -- `RenderSceneExtractor` 的多光源提取 -- shadow request 自动生成逻辑 -- shadow matrix / light camera 参数构建 -- forward shader resource binding 对 shadow map 的消费 -- additional lights 排序与裁剪规则 - -### 7.2 集成测试 - -必须新增: - -- `directional_shadow_scene` -- `multi_light_scene` -- `spot_light_scene` - -保底回归集: - -- `textured_quad_scene` -- `unlit_scene` -- `object_id_scene` -- `backpack_scene` -- `backpack_lit_scene` -- `camera_stack_scene` -- `transparent_material_scene` -- `cull_material_scene` -- `depth_sort_scene` -- `material_state_scene` -- `offscreen_scene` - -### 7.3 三后端要求 - -本阶段所有新增集成测试都必须同时覆盖: - -- `D3D12` -- `OpenGL` -- `Vulkan` - -如果某一步只在单后端通过,不算完成。 -这一阶段绝不接受“先在一个后端跑通,另外两个后面再补”的收口标准。 - -## 8. 明确不做 - -为了避免主线失控,这一阶段明确不做下面这些: - -- `render graph` -- `deferred renderer` -- `clustered/tiled lighting` -- `cascaded shadow maps` -- `PCSS / VSM / EVSM` 等高级阴影方案 -- `post-process` 大框架 -- `shader graph` -- editor gizmo 的进一步美术化与交互打磨 - -这些都应该建立在“方向光阴影 + 基础多光源 + runtime renderer 稳定闭环”完成之后。 - -## 9. 风险点与处理策略 - -### 9.1 风险:阴影路径把 pass/resource contract 搞散 - -处理策略: - -- 阴影采样与 shadow caster 仍必须走正式 shader/material/pass contract -- 不允许为了赶进度,在 pipeline 内重新堆一套散乱硬编码绑定 - -### 9.2 风险:editor 需求重新污染 runtime 主线 - -处理策略: - -- SceneView 只复用 renderer -- grid/outline/gizmo 始终作为 editor overlay -- runtime 主链以 GameView/真实场景渲染为准 - -### 9.3 风险:三后端阴影精度差异导致 GT 不稳定 - -处理策略: - -- 第一版 shadow scene 场景构图应保守 -- 阈值控制应严格,但允许合理的小误差 -- 优先追求稳定一致,而不是追求复杂阴影表现 - -### 9.4 风险:多光源一步做太大 - -处理策略: - -- 先做“有限 additional lights 的前向提交” -- 不提前引入 forward+ 或 deferred -- 以可验证场景为主,不以理论最优为目标 - -## 10. 提交与执行节奏 - -这一阶段继续按“每一步可验证、每一步可提交”的节奏推进: - -1. 先做 `Phase A` 的数据模型与 shadow request 自动生成 -2. 测试通过后提交 -3. 再做 `ShadowCaster -> Forward` 的 shadow map 消费 -4. 测试通过后提交 -5. 再做 `Phase B` 的多光源数据模型与 shader 消费 -6. 测试通过后提交 -7. 最后做 `Phase C / D` 的接入收口与文档归档 - -每一步的“通过”都必须包含: - -- 至少相关 unit tests 通过 -- 至少相关 integration tests 通过 -- 如影响主线,必须补跑 rendering regression - -## 11. 成功标准 - -本阶段完成时,应满足以下判断: - -- renderer 能正式生成并消费方向光阴影 -- renderer 能正式消费基础多光源 -- SceneView 与 GameView 都建立在统一 runtime renderer 主链上 -- editor overlay 与 runtime pass 边界清晰 -- rendering 测试体系在三后端下持续稳定 - -## 12. 一句话总结 - -下一阶段的核心不是“做更多花哨渲染功能”,而是: - -**把当前已经具备架构基础的 renderer,推进成一个真正能承担默认场景渲染的 Unity 风格前向运行时。** diff --git a/docs/used/Renderer剩余收口与体积渲染多后端正式化计划_完成归档_2026-04-10.md b/docs/used/Renderer剩余收口与体积渲染多后端正式化计划_完成归档_2026-04-10.md deleted file mode 100644 index f4fb2ab5..00000000 --- a/docs/used/Renderer剩余收口与体积渲染多后端正式化计划_完成归档_2026-04-10.md +++ /dev/null @@ -1,198 +0,0 @@ -# Renderer 剩余收口与体积渲染多后端正式化计划 - -日期:2026-04-10 - -## 1. 文档定位 - -旧的 `Renderer当前阶段正式收口计划_2026-04-09` 与 `NanoVDB稀疏体积渲染后续正式化计划_2026-04-09` -已归档。 - -当前这份计划只覆盖 Rendering 主线里仍然没有真正收口的剩余问题,不再重复记录已经完成的阶段成果。 - -本轮目标有且只有四类: - -1. 把 NanoVDB 体积渲染从 D3D12 单后端正式能力推进到 Vulkan / OpenGL 正式能力。 -2. 把 `FinalColor` 与 final-output 的契约收死,消除“接口已存在但运行时半接线”的状态。 -3. 把 `ObjectId` 从“低 32 位截断可用方案”推进到清晰稳定的正式渲染 ID 契约。 -4. 把阴影规划与前向管线剩余的硬编码策略、monolith 结构继续拆干净。 - ---- - -## 2. 当前状态判断 - -当前 Rendering 已经具备稳定运行基础,但还不能称为“完全收口”,核心原因是: - -1. `volume_scene / volume_occlusion_scene / volume_transform_scene` 目前仍只在 D3D12 上做了正式实例化与 GT 验证。 -2. `FinalColorSettings` 当前真正落地的是 pipeline defaults + camera overrides;此前残留的 volume override 分支只是头文件级 API,并未进入运行时。 -3. `ObjectId` 仍基于 runtime object id 低 32 位编码,不是长期正式方案。 -4. `SceneRenderRequestPlanner` 里仍保留方向光阴影尺寸与 focus 策略的硬编码。 -5. `BuiltinForwardPipeline` 功能上可用,但资源布局、PSO、descriptor、skybox、draw path 仍聚集在同一条大实现链里。 - -这五点不继续收掉,后面不管做 editor 深化还是做更高层 renderer / SRP,都会反复返工。 - ---- - -## 3. 本轮明确不做什么 - -1. 不引入 `RenderGraph`。 -2. 不提前启动 `SRP` 正式实现。 -3. 不改写现有 Shader/Material 语法主线。 -4. 不扩展新的大特性,例如 deferred、reflection probe、完整 post-processing stack。 -5. 不为了追求“结构完美”重写整个 builtin renderer。 - -本轮只做剩余收口,不做新能力扩张。 - ---- - -## 4. 执行阶段 - -### Phase 1:NanoVDB Vulkan 正式点亮 - -目标: - -把当前 D3D12 已验证的体积渲染链路推进到 Vulkan 正式支持。 - -任务: - -1. 审查 `BuiltinVolumetricPass` 在 Vulkan 下的 StructuredBuffer / storage buffer 绑定路径。 -2. 核对体积 shader 在 Vulkan 下的资源布局、反射、descriptor set 绑定。 -3. 让 `volume_scene`、`volume_occlusion_scene`、`volume_transform_scene` 在 Vulkan 下正式实例化并通过。 -4. 固化 Vulkan 失败日志与 capability 行为,禁止 silent fallback。 - -验收标准: - -1. Vulkan 路线能稳定出图。 -2. 三个体积集成测试全部通过。 -3. 失败时日志能直接定位到 capability / shader / 绑定问题。 - -### Phase 2:NanoVDB OpenGL 正式点亮 - -目标: - -把体积渲染推进到 OpenGL 正式支持,并确认 SSBO/GLSL 转译边界。 - -任务: - -1. 审查 StructuredBuffer -> OpenGL SSBO 的运行时绑定链。 -2. 解决体积 shader 在 OpenGL 下的 layout / binding / 对齐问题。 -3. 让三组体积集成测试在 OpenGL 下正式实例化并通过。 -4. 明确 OpenGL 不支持场景下的禁用与报错策略。 - -验收标准: - -1. OpenGL 路线能稳定出图。 -2. D3D12 / Vulkan / OpenGL 三后端都具备正式验证。 -3. 不再存在“体积渲染只是 D3D12 正式能力”的状态。 - -### Phase 3:FinalColor 契约彻底收死 - -目标: - -让 `FinalColor` 的 API 契约、运行时行为、测试覆盖完全一致。 - -任务: - -1. 审查 `FinalColorSettings`、camera overrides、final-output 的设计边界。 -2. 将未落地的 volume override 分支从现阶段 API 中正式收掉,不再保留半接线接口。 -3. 补齐对应单测与场景回归测试。 -4. 明确 post-process / final-output 的职责关系文档。 - -验收标准: - -1. 不再存在“接口支持但执行层没接”的不自洽状态。 -2. `FinalColor` 行为能用测试稳定验证。 - -### Phase 4:ObjectId 正式渲染 ID 契约 - -目标: - -把 picking / outline / selection 依赖的 ID 方案从阶段实现推进到正式契约。 - -任务: - -1. 明确长期方案是稳定 32-bit render id,还是 runtime mapping table。 -2. 将 `ObjectIdCodec` 与 `GameObject::ID` 的关系从“低 32 位截断”调整为正式约束。 -3. 让 picking / outline / editor selection 共用同一份正式契约。 -4. 补齐边界测试与错误防护。 - -验收标准: - -1. `ObjectId` 不再依赖模糊约定。 -2. 相关 editor 能力建立在稳定渲染 ID 契约上。 - -### Phase 5:阴影规划与前向管线剩余结构收口 - -目标: - -继续把 planner / pipeline 里剩余的硬编码和 monolith 结构压缩到合理边界。 - -任务: - -1. 把方向光阴影尺寸、focus/depth/padding 策略抽成正式配置点。 -2. 收紧 `SceneRenderRequestPlanner` 的职责,避免继续膨胀。 -3. 拆分 `BuiltinForwardPipeline` 剩余的大块职责: - - pipeline state resolve - - descriptor/layout cache - - skybox path - - draw submission -4. 保持功能不回退的前提下优化代码结构与目录边界。 - -验收标准: - -1. 阴影规划不再写死关键策略参数。 -2. `BuiltinForwardPipeline` 不再继续向单体类膨胀。 - -### Phase 6:测试、文档、归档 - -目标: - -让本轮真正可交接、可回归、可归档。 - -任务: - -1. 复跑关键 Rendering unit / integration。 -2. 输出阶段总结。 -3. 将本轮完成计划归档到 `docs/used`。 - -验收标准: - -1. 关键测试全绿。 -2. 活跃 plan 只保留真正未完成事项。 - ---- - -## 5. 执行顺序 - -严格按下面顺序推进: - -1. `Phase 1` -2. `Phase 2` -3. `Phase 3` -4. `Phase 4` -5. `Phase 5` -6. `Phase 6` - -原因: - -1. 体积渲染多后端 rollout 还没完成,当前还不能宣称 renderer 全面正式支持。 -2. `FinalColor` 与 `ObjectId` 都是契约问题,必须在继续推进更高层系统前收死。 -3. planner / pipeline 结构整理必须建立在前面能力边界稳定之后。 - ---- - -## 6. 本轮完成标志 - -当下面条件同时成立时,本轮才算真正完成: - -1. 三个体积场景已在 D3D12 / Vulkan / OpenGL 三后端正式验证。 -2. `FinalColor` 契约与执行层完全一致。 -3. `ObjectId` 已切换到正式渲染 ID 契约。 -4. 阴影规划关键策略点已正式化。 -5. `BuiltinForwardPipeline` 剩余的结构性膨胀得到明显收口。 -6. 文档与测试全部同步完成。 - ---- - -## 7. 一句话结论 - -当前 Rendering 不是“功能没做完”,而是“剩余收口项已经缩小到多后端体积渲染、运行时契约、自身结构整理三类问题”;本轮做完之后,才可以严肃地说它进入了真正可长期维护的正式状态。 diff --git a/docs/used/Renderer当前主线下一阶段_PostProcess与FinalOutput收口计划_完成归档_2026-04-06.md b/docs/used/Renderer当前主线下一阶段_PostProcess与FinalOutput收口计划_完成归档_2026-04-06.md deleted file mode 100644 index ae646fca..00000000 --- a/docs/used/Renderer当前主线下一阶段_PostProcess与FinalOutput收口计划_完成归档_2026-04-06.md +++ /dev/null @@ -1,296 +0,0 @@ -# Renderer 当前主线下一阶段:PostProcess 与 FinalOutput 收口计划 - -日期:`2026-04-06` - -## 1. 阶段结论 - -当前 Rendering 主线的下一个阶段,不应该跳去做: - -- `render graph` -- deferred / clustered -- GPU picking 替换 -- 更复杂的 editor-only 特效 - -现在真正还没收口的,是 **runtime 相机帧合成链路**。 - -更具体地说,就是把当前 renderer 从: - -`Scene -> 直接打到最终 surface` - -收口成: - -`Scene Color -> 可选 PostProcess -> Final Output` - -只有这一步补齐之后,现阶段的 renderer 才算真正具备了稳定的 runtime frame composition 能力,也才适合继续往 Unity 风格的 renderer feature / C# SRP 承接点推进。 - -## 2. 为什么现在必须先做这个 - -当前仓库已经具备: - -- 正式的 forward runtime scene renderer -- directional / point / spot 多光源 -- main directional shadow -- skybox -- object id -- editor overlay / gizmo / grid - -但真正缺的仍然是下面这条 runtime 正式主链: - -`Opaque -> Skybox -> Transparent -> PostProcess -> Final Output` - -现在的主要缺口是: - -- `CameraRenderRequest` 还没有正式的 post-process / final-output contract -- `CameraRenderer` 仍然以 `request.surface` 作为主场景直接输出目标 -- 没有 renderer 级别的 fullscreen pass 基础设施 -- 没有“只有需要时才分配 intermediate color target”的正式策略 -- 也没有一个专门验证 runtime 后处理闭环的 `post_process_scene` - -如果这个阶段不先收口,后续无论做 tone mapping、color grading、exposure、更多环境特性,都会继续以临时拼接的方式往上堆,架构会重新变脏。 - -## 3. 目标 - -本阶段只做一件事: - -**把 runtime 相机输出链路正式化。** - -完成后应达到: - -1. renderer 具备正式的 post-process 请求与执行入口。 -2. renderer 具备 backend-neutral 的 fullscreen pass 基础设施。 -3. 相机默认仍可直接渲染到最终目标,只有在确实需要后处理或最终合成时才切换 intermediate color target。 -4. final output 的 copy / blit / resolve 规则明确,不再隐式散落在具体 pass 中。 -5. object id 与 editor overlay 明确保持在 runtime final-color 主链之外。 -6. 增加 `post_process_scene` 集成测试,验证三后端一致性。 - -## 4. 非目标 - -本阶段明确不做: - -- `render graph` -- HDR 全量管线 -- bloom / SSAO / DOF / motion blur 等完整后处理套件 -- cubemap IBL / reflection probe -- GPU picking 替换 CPU picking -- editor SceneView 的额外视觉增强 -- 新一轮 renderer 大重构 - -## 5. 设计原则 - -### 5.1 继续遵循当前分层 - -- `RHI` 只提供资源、render target、pipeline、draw/dispatch 抽象。 -- `Renderer` 负责相机帧规划、runtime pass 调度、final output 规则。 -- `Editor` 仍然只是宿主与 overlay 使用方,不接管 runtime 后处理。 - -### 5.2 runtime 主链与 editor 辅助链继续隔离 - -下列内容不进入 runtime final-color 主链: - -- `ObjectId` -- grid -- gizmo -- selection outline -- scene icon - -它们仍然属于 editor 专用辅助链路。 - -### 5.3 默认直出,按需离屏 - -默认情况: - -- 没有 post-process -- 没有额外 final composition 需求 - -则继续允许相机直接渲染到最终 `surface`。 - -只有满足下面条件之一时,才启用 intermediate color target: - -- 存在 runtime post-process -- final output 需要一次 fullscreen copy / resolve / color transform -- 后续扩展的 runtime frame composition 明确要求 scene color 先落到中间目标 - -这个判断必须由 `CameraRenderer` 或 request planning 统一控制,不能散落到单个 pass 内部。 - -## 6. 核心方案 - -### 6.1 补齐 CameraRenderRequest 的正式 contract - -当前 `CameraRenderRequest` 已有: - -- `PreScenePasses` -- `ShadowCaster` -- `DepthOnly` -- `MainScene` -- `ObjectId` -- `PostScenePasses` -- `OverlayPasses` - -下一步要补的是“runtime final-color 链”的明确语义,而不是继续把 `PostScenePasses` 当一个模糊兜底桶。 - -建议演进方向: - -- 保留现有 stage 枚举兼容当前结构 -- 在 request 数据层新增更明确的 runtime 输出描述 -- 显式区分: - - scene color source - - post-process chain - - final output target - -换句话说,本阶段的重点不是改名字,而是把数据契约补完整。 - -### 6.2 增加 Fullscreen Pass 基础设施 - -需要新增 backend-neutral 的 fullscreen pass helper,用于: - -- 绑定 source color texture -- 绑定 sampler / descriptor set -- 设置 fullscreen triangle 或等价路径 -- 输出到指定 color target - -首版要求: - -- 三后端可复用同一套 renderer 侧调用方式 -- shader 输入输出约束简单、稳定、可测试 -- 不依赖 editor 私有绘制路径 - -### 6.3 Final Output 明确化 - -当前 main scene 直接打到 `request.surface` 的方式,需要升级成可判断的策略: - -1. 如果本帧不需要 post-process / final copy,则 direct-to-surface。 -2. 如果本帧需要 post-process,则: - - main scene 输出到 intermediate color target - - post-process 读取 intermediate - - final pass 输出到 `request.surface` - -这里要把以下规则写死: - -- intermediate 的格式、尺寸与生命周期 -- final blit / copy / fullscreen draw 由谁负责 -- MSAA / resolve 如果暂时没有正式支持,要在 contract 里先写清当前限制 - -### 6.4 第一阶段验证效果只做最小闭环 - -本阶段不追求复杂后处理效果。 - -第一阶段 builtin effect 建议只做一种确定性极强、三后端最容易对齐的效果: - -- `ColorScale` - -例如对 scene color 统一乘一个常量因子。 - -这样做的原因: - -- 易于写 GT -- 不依赖复杂数学或 LUT -- 不容易受不同后端精度差异影响 -- 能直接证明 post-process infrastructure 已闭环 - -## 7. 测试策略 - -### 7.1 新增集成测试 - -新增: - -- `tests/Rendering/integration/post_process_scene` - -建议测试场景: - -- 结构尽量简单 -- 使用确定性颜色块或单个模型 -- 主场景颜色在经过 `ColorScale` 后能明显与未处理版本区分 -- 三后端都输出 `*_d3d12.ppm` / `*_opengl.ppm` / `*_vulkan.ppm` -- 统一对比单张 `GT.ppm` - -### 7.2 必跑回归 - -本阶段至少回归: - -- `rendering_integration_skybox_scene` -- `rendering_integration_offscreen_scene` -- `rendering_integration_transparent_material_scene` -- `rendering_integration_camera_stack_scene` -- `rendering_integration_object_id_scene` - -必要时再补: - -- `material_state_scene` -- `depth_sort_scene` - -### 7.3 单测方向 - -建议补或增强的 unit coverage: - -- 何时启用 intermediate color target -- post-process chain 为空时的 direct path -- final output routing 决策 -- request contract 的默认行为与 fallback - -## 8. 分阶段执行 - -### Phase A:补齐 contract - -目标: - -- 明确 runtime post-process / final-output 所需的数据结构与决策入口 - -完成标准: - -- `CameraRenderRequest` / planner / `CameraRenderer` 层的数据语义清晰 -- 现有场景渲染不回退 - -### Phase B:引入 fullscreen pass 与 intermediate 策略 - -目标: - -- 跑通 scene color -> fullscreen pass -> final surface - -完成标准: - -- 三后端都能稳定执行最小 fullscreen pass -- direct path 仍然保留 - -### Phase C:接入最小 builtin post-process - -目标: - -- 用 `ColorScale` 验证 runtime 后处理闭环 - -完成标准: - -- `post_process_scene` GT 通过 -- `skybox_scene` / `offscreen_scene` / `transparent_material_scene` 不回退 - -### Phase D:文档与测试收口 - -目标: - -- 把当前阶段的 contract、测试矩阵、限制条件写回文档 - -完成标准: - -- `tests/TEST_SPEC.md` 如有必要同步更新 -- 当前计划可归档到 `docs/used` - -## 9. 收口判定 - -满足以下条件时,本阶段可以视为完成: - -1. runtime renderer 已具备正式 post-process 入口。 -2. final output 路径不再依赖隐式直写逻辑。 -3. intermediate color target 为按需启用,而不是一刀切常驻。 -4. `post_process_scene` 在 D3D12 / OpenGL / Vulkan 三后端通过。 -5. `skybox_scene`、`offscreen_scene`、`camera_stack_scene`、`transparent_material_scene` 不回退。 -6. object id 与 editor overlay 没有被错误卷入 runtime final-color 链。 - -## 10. 这一阶段之后再做什么 - -等这一阶段收口之后,renderer 主线再继续往下走,顺序才是合理的: - -1. 更正式的 shader / material runtime pass contract -2. 更成熟的 renderer feature / Unity 风格可扩展点 -3. GPU object id / picking 正式化 -4. 更复杂的 environment、tone mapping 与后处理能力 - -也就是说,**现在下一步不是做更多“效果”,而是先把相机输出主链做完整。** diff --git a/docs/used/Renderer当前阶段正式收口计划_阶段归档_2026-04-10.md b/docs/used/Renderer当前阶段正式收口计划_阶段归档_2026-04-10.md deleted file mode 100644 index d1247b96..00000000 --- a/docs/used/Renderer当前阶段正式收口计划_阶段归档_2026-04-10.md +++ /dev/null @@ -1,228 +0,0 @@ -# Renderer 当前阶段正式收口计划 - -日期:2026-04-09 - -## 1. 文档定位 - -这份计划只处理当前 `Rendering` 主线里“已经能跑,但还没有完全正式化”的问题。 - -它不是下一阶段功能规划,也不是 `SRP / RenderGraph / 后处理体系扩展` 计划。 -本轮目标很明确: - -1. 把当前内建渲染链路收口到可长期维护的正式状态。 -2. 清掉仍然明显带有阶段性、硬编码、单后端假设的实现。 -3. 在不破坏现有 Editor/Game/Integration Test 行为的前提下,把当前渲染层的边界整理干净。 - ---- - -## 2. 当前阶段判断 - -当前 `Rendering` 已经完成了这些关键闭环: - -1. 内建前向主链路已经能稳定驱动 `Scene / Game` 视图。 -2. 多光源、方向光阴影、天空盒、体积渲染都已经进入正式运行链路。 -3. Shader/Material 资产链路已经从旧散装方案收束到当前统一体系。 -4. Picking、Outline、Grid、Gizmo 等编辑器侧能力已经具备基本可用性。 -5. 集成测试体系已经覆盖了一批真实渲染场景,并能做 GT 对比。 - -但现在还剩下一批很典型的“阶段尾巴”: - -1. 前向管线对颜色目标格式、深度格式、PSO 组合键仍有硬编码假设。 -2. Editor 相关 pass 仍夹带单后端假设和格式写死问题。 -3. 阴影规划层仍偏固定策略,缺少正式配置入口和更清晰的职责边界。 -4. ObjectId 方案仍是阶段版实现,和 `GameObject::ID` 的长期契约没有彻底收束。 -5. `FinalColor` 与体积渲染之间还有“接口预留已存在,但运行时接线未完全正式化”的问题。 - -这些问题现在不处理,后面做 `SRP`、Renderer 扩展、Editor 深化时会持续反噬。 - ---- - -## 3. 本轮明确不做什么 - -为了避免计划失控,本轮明确不做下面这些事: - -1. 不引入 `RenderGraph`。 -2. 不启动 `SRP` 正式实现。 -3. 不重写现有多 pass 框架。 -4. 不扩展新的大特性,例如延迟渲染、反射探针、后处理栈。 -5. 不再重开一轮 Shader/Material 语法设计。 - -本轮只做“把当前链路做正式”。 - ---- - -## 4. 收口目标 - -本轮收口完成后,当前 `Renderer` 至少应满足下面这些条件: - -1. 同一套内建前向链路不再偷偷依赖固定颜色格式或固定深度格式。 -2. PSO 缓存键和 pass 描述能完整反映真正影响渲染状态的维度。 -3. Editor overlay 类 pass 不再用 D3D12 风格思维写死输入输出资源契约。 -4. 阴影规划和 ObjectId 编码拥有清晰、稳定、可扩展的正式边界。 -5. `FinalColor`、体积渲染、场景颜色输出之间的职责关系明确且可测试。 -6. 对应单测与集成测试补齐,保证本轮收口不会把现有功能打坏。 - ---- - -## 5. 分阶段执行 - -### Phase 1:前向管线资源契约正式化 - -目标: - -把当前 `BuiltinForwardPipeline` 从“能用”整理成“目标格式、深度格式、状态组合都明确受描述驱动”。 - -要做的事: - -1. 审查并收口前向主链路里所有颜色目标格式、深度格式、MSAA、load/store、viewport/scissor 的隐式假设。 -2. 补全 PSO cache key,让真正影响管线状态的维度全部进入键值,而不是靠调用点默认一致。 -3. 把 render target/depth format 的来源统一到正式 request/context,而不是散落在 pass 内部硬编码。 -4. 检查 `Scene / Game / Editor overlay` 是否共用了不该共用的隐式默认值,逐一拆开。 - -优先关注: - -1. `engine/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp` -2. `engine/src/Rendering/Pipelines/BuiltinForwardPipelineResources.cpp` -3. 与 camera request / pass context / pipeline key 相关的描述结构 - -验收标准: - -1. 同一前向主链路可在不同颜色格式/深度格式输入下保持行为正确。 -2. 不再存在“只因为默认值碰巧一致所以能跑”的路径。 -3. 相关单测补齐并通过。 - -### Phase 2:Editor Overlay Pass 正式化 - -目标: - -把 `Grid / Outline / ObjectId` 这批 Editor 侧 pass 从阶段性实现整理成正式 pass。 - -要做的事: - -1. 审查 `BuiltinInfiniteGridPass`、`BuiltinSelectionOutlinePass`、`BuiltinObjectIdOutlinePass` 对资源格式和后端行为的假设。 -2. 把这些 pass 对 scene color/depth/object-id/mask 的输入输出契约整理成统一描述,而不是各自单独偷拿默认格式。 -3. 去掉明显的 D3D12-only 思维残留,至少保证代码层面不再把后端差异写死在 pass 逻辑里。 -4. 统一调试路径和正式路径,避免同一个 pass 同时保留多套阶段性资源接法。 - -优先关注: - -1. `engine/src/Rendering/Passes/BuiltinInfiniteGridPass.cpp` -2. `engine/src/Rendering/Passes/BuiltinSelectionOutlinePass.cpp` -3. `engine/src/Rendering/Passes/BuiltinObjectIdOutlinePass.cpp` -4. Editor 侧 Scene/Game viewport render target 管理层 - -验收标准: - -1. Grid、Outline、Picking 对资源输入输出的依赖关系清晰可读。 -2. Editor 相关 pass 不再依赖单后端硬编码格式。 -3. 现有 Scene/Game 视图行为不回退。 - -### Phase 3:Shadow Planning 与 ObjectId 正式边界收口 - -目标: - -把当前“能工作但还是阶段实现”的阴影规划与 ObjectId 方案整理为正式边界。 - -要做的事: - -1. 审查方向光阴影规划层,把固定 `1024` 阴影图尺寸、全场景粗扫描等阶段策略抽成正式配置/策略点。 -2. 明确 shadow request/planning/execution 的职责边界,避免 planner 继续膨胀成杂物层。 -3. 审查 `ObjectIdCodec` 与 `GameObject::ID` 关系。 -4. 明确长期方案到底是稳定 32-bit 渲染 ID,还是正式的 runtime 映射表,不再让 64-bit 对象 ID 与 32-bit object-id target 处于半耦合状态。 -5. 为 picking、outline、editor selection 使用同一份正式 ID 契约。 - -优先关注: - -1. `engine/src/Rendering/Planning/SceneRenderRequestPlanner.cpp` -2. `engine/include/XCEngine/Rendering/Picking/ObjectIdCodec.h` -3. `engine/include/XCEngine/Components/GameObject.h` - -验收标准: - -1. 阴影规划不再带有明显的固定阶段参数硬编码。 -2. ObjectId 编码边界清晰且有测试覆盖。 -3. Picking/Outline 不再建立在模糊 ID 约定之上。 - -### Phase 4:FinalColor 与体积渲染接线正式化 - -目标: - -把当前 `FinalColor`、场景颜色输出、体积渲染之间的关系做实,避免继续停留在“API 预留了,但接线没完全正式化”的状态。 - -要做的事: - -1. 复查 `FinalColorSettings`、camera request、scene renderer 执行链路中的实际生效路径。 -2. 确认 volume override / final color override 的职责边界,是正式运行时能力还是仅作为未来扩展保留。 -3. 把现在已经真实存在的行为和仍只是预留的行为区分清楚。 -4. 如果保留预留接口,就必须让默认行为、空行为、调试行为全部明确;如果决定正式启用,就把运行时接线补齐。 - -优先关注: - -1. `engine/include/XCEngine/Rendering/Planning/FinalColorSettings.h` -2. `engine/src/Rendering/Execution/SceneRenderer.cpp` -3. 与 camera scene renderer / frame composition 相关的执行层 - -验收标准: - -1. `FinalColor` 不再停留在“名字像正式能力,实际只是半接线状态”。 -2. 体积渲染与最终颜色输出关系清晰、行为稳定。 -3. 对应单测或场景回归测试补齐。 - -### Phase 5:测试、文档、归档收口 - -目标: - -让这一轮不是“代码上看起来做完”,而是真正可交接、可回归、可归档。 - -要做的事: - -1. 为本轮所有正式化改动补对应 unit/integration coverage。 -2. 至少复跑当前关键 Rendering 集成场景: - - 基础 forward 场景 - - directional shadow 场景 - - skybox 场景 - - volume 场景 -3. 更新必要文档,把本轮完成的阶段计划归档到 `docs/used`。 -4. 输出一份阶段总结,明确当前 Renderer 已正式化到什么程度,下一阶段才该进入什么主题。 - -验收标准: - -1. 关键测试全绿。 -2. 没有新增“只在某个后端偶然能过”的脆弱实现。 -3. 本轮计划可以被完整归档。 - ---- - -## 6. 执行顺序要求 - -必须按下面顺序推进: - -1. 先做 `Phase 1` -2. 再做 `Phase 2` -3. 然后做 `Phase 3` -4. 再处理 `Phase 4` -5. 最后统一做 `Phase 5` 收口 - -原因很简单: - -1. 前向主链路资源契约不稳,后面的 Editor pass 正式化会一直返工。 -2. ObjectId 和 shadow planning 的正式边界不先理顺,Editor 相关能力会持续建立在临时假设上。 -3. `FinalColor` 的运行时接线要建立在前面这些边界已经收紧之后。 - ---- - -## 7. 本轮完成标志 - -当下面条件同时成立时,这一轮 Renderer 收口才算真正完成: - -1. 当前内建前向链路已摆脱明显格式硬编码和状态隐式约定。 -2. Editor overlay 类 pass 已完成正式资源契约整理。 -3. Shadow planning 与 ObjectId 契约已从阶段版实现收口为正式边界。 -4. `FinalColor` 与体积渲染关系已经明确并落地到代码与测试。 -5. 所有本轮阶段性计划文档已经归档,`docs/plan` 中不再残留已经完成的旧专项计划。 - ---- - -## 8. 一句话结论 - -当前 Renderer 已经具备“继续承接更高层系统”的能力,但在进入下一阶段之前,必须先把资源契约、Editor pass、阴影规划、ObjectId、FinalColor 这五块彻底做正式;这轮收口做完,后面再接 `SRP` 或更复杂渲染能力才不会反复返工。 diff --git a/docs/used/Renderer模块_EditorViewport缺少RenderSurface接入层3.27.md b/docs/used/Renderer模块_EditorViewport缺少RenderSurface接入层3.27.md deleted file mode 100644 index d365743e..00000000 --- a/docs/used/Renderer模块_EditorViewport缺少RenderSurface接入层3.27.md +++ /dev/null @@ -1,120 +0,0 @@ -# Renderer模块 EditorViewport缺少RenderSurface接入层 - -## 1. 问题定义 - -当前 editor 已经有: - -- `GameViewPanel` -- `SceneViewPanel` - -但二者目前仍然是空面板,尚未形成真正的 viewport 渲染宿主。 - -这意味着 Renderer 即使在 runtime 侧建立起来,editor 侧也还没有标准方式去承接: - -- 离屏 color/depth 输出 -- viewport resize -- 渲染结果贴到 ImGui 面板 - ---- - -## 2. 当前现状 - -当前 `GameViewPanel::Render()` 与 `SceneViewPanel::Render()` 仅创建面板窗口,本身没有: - -- 离屏渲染目标 -- 渲染尺寸管理 -- RHI / Renderer 级渲染输出对象 -- 纹理到 ImGui 的桥接 - -也就是说,editor 当前只有“面板外壳”,还没有“渲染宿主层”。 - ---- - -## 3. 为什么这不应该反向污染 Renderer 设计 - -这个问题很容易走偏成下面这种错误路线: - -- 先把 renderer 直接写进 editor 面板逻辑 -- `SceneView` 一套渲染逻辑 -- `GameView` 一套渲染逻辑 -- runtime 又一套渲染逻辑 - -这会导致: - -- 渲染逻辑重复 -- editor 与 runtime 耦合 -- 后续 C# SRP 难以统一接管 - -因此正确思路不是“先让面板自己画”,而是: - -1. Renderer 模块先支持统一的 `RenderSurface` -2. editor viewport 只作为 `RenderSurface` 的宿主与展示层 - ---- - -## 4. 建议方案 - -### 4.1 先在 Renderer 模块建立 `RenderSurface` - -`RenderSurface` 应统一描述: - -- swapchain 输出 -- 离屏 color / depth 目标 -- viewport 尺寸 -- resize 行为 - -### 4.2 editor 侧建立 viewport host 层 - -editor 侧后续应新增一层专门承接: - -- 面板尺寸变化 -- 请求 Renderer 重建或 resize `RenderSurface` -- 把结果纹理显示到 ImGui - -而不是把渲染实现直接塞进面板类。 - -### 4.3 `SceneView` 与 `GameView` 应共享同一套 Renderer 输出机制 - -区别只在于: - -- `GameView` 使用 runtime camera -- `SceneView` 使用 editor 自有 camera / gizmo / overlay - -但底层渲染输出机制应一致。 - ---- - -## 5. 与未来 Unity 风格演进的关系 - -未来如果要做接近 Unity 的 editor 与 SRP 体系,这一层非常关键。 - -因为 Unity 的: - -- Game 视图 -- Scene 视图 -- Camera 预览 - -本质上都不是复制多套渲染器,而是在同一渲染体系上挂不同宿主与附加绘制。 - -XCEngine 也应遵循同样思路。 - ---- - -## 6. 验收标准 - -完成后至少应满足: - -1. Renderer 能输出到离屏 `RenderSurface` -2. `GameView` 能显示 renderer 的离屏结果 -3. `SceneView` 能显示 renderer 的离屏结果 -4. viewport resize 不需要复制新的渲染逻辑 -5. runtime 与 editor 共用同一套渲染宿主接口 - ---- - -## 7. 优先级 - -中到高。 - -它不是 Renderer v0 的第一步实现内容,但必须在 renderer 基础链路稳定后尽快接上,否则 editor 渲染体系会被迫走临时方案。 - diff --git a/docs/used/Renderer模块_Material与Shader资产模型暂不满足SRP演进需求.md b/docs/used/Renderer模块_Material与Shader资产模型暂不满足SRP演进需求.md deleted file mode 100644 index d4d40812..00000000 --- a/docs/used/Renderer模块_Material与Shader资产模型暂不满足SRP演进需求.md +++ /dev/null @@ -1,146 +0,0 @@ -# Renderer模块 Material与Shader资产模型暂不满足SRP演进需求 - -## 1. 问题定义 - -当前资源层已经具备: - -- `Material` -- `Shader` -- `Texture` - -但现有模型仍然偏“最小资源容器”,尚不足以直接承接未来 Unity 风格 Renderer / SRP 的完整需求。 - -当前最典型的问题是: - -- `Shader` 更像“单个 stage shader 资源” -- `Material` 更像“shader 引用 + 属性包” -- 资产层缺少 pass / tag / render state / render queue 语义 - ---- - -## 2. 当前现状 - -从现有实现看: - -### `Shader` - -当前 `Shader` 资源主要描述: - -- shader type -- shader language -- source / compiled binary -- uniforms / attributes - -这更接近“单个 shader stage 对象”,而不是面向渲染管线选择的 shader asset。 - -### `Material` - -当前 `Material` 资源主要描述: - -- 一个 `Shader` 引用 -- 属性表 -- 纹理绑定表 - -当前 `MaterialLoader` 也只解析最基础的 `"shader"` 字段。 - ---- - -## 3. 为什么这会影响未来 Renderer / SRP - -在未来的 Unity 风格渲染体系中,Renderer / SRP 至少需要下列能力: - -- 根据 shader pass/tag 选择绘制路径 -- 区分 opaque / transparent / shadow caster / depth only 等 pass -- 用 render queue 控制排序层级 -- 用 render state 描述 cull / blend / ztest / zwrite -- 为后续 keyword / variant 留空间 - -如果材质 / shader 资产模型长期停留在当前结构,会导致: - -- 内建 forward 渲染可以临时跑起来 -- 但未来一旦上 SRP,就必须大规模重构资源资产模型 - -这会影响: - -- Renderer -- Material 资源格式 -- Shader 导入格式 -- editor 材质面板 -- 未来 C# SRP 的 pass 选择逻辑 - ---- - -## 4. 建议方向 - -### 4.1 不要让 `Shader` 永远停留在“单 stage 资源” - -后续建议演进到更面向渲染管线的概念,例如: - -- `ShaderAsset` -- `ShaderPass` -- 每个 pass 包含 vertex / fragment 等 stage 组合 -- 每个 pass 具备 tag 与 render state 描述 - -### 4.2 `Material` 应逐步获得 renderer 需要的元数据 - -后续建议补充: - -- render queue -- pass 相关 tag / override -- render state override -- shader keyword / variant 预留位 - -### 4.3 第一阶段可以先不一次性做全 - -但即使第一阶段只做 `UnlitTexture` / `SimpleLit`,也建议: - -- 不把格式彻底写死成只有 `"shader"` 这一个入口 -- 为 future pass/tag 结构预留升级路径 - -换句话说: - -- 当前阶段可以简化实现 -- 但不能封死演进路线 - ---- - -## 5. 与 Unity 风格的关系 - -未来如果要在 C# 层做接近 Unity 的 SRP,shader pass 选择是核心能力之一。 - -例如: - -- `ShaderTagId` -- `DrawingSettings` -- `FilteringSettings` -- renderer list / pass filtering - -这些能力都要求材质与 shader 资产层至少能表达“pass/tag/state”。 - -因此,这个问题虽然不一定是 Renderer v0 第一周就要全量解决,但必须在设计阶段明确记录。 - ---- - -## 6. 建议验收标准 - -进入下一阶段时,至少应达到以下其中一档: - -### 档位 A:最小可演进 - -- 明确存在“shader pass”概念 -- 材质资产格式有升级空间 -- 内建 pipeline 能按 pass 选择绘制 - -### 档位 B:更接近 SRP - -- 具备 `pass + tag + render state + queue` -- 为 future keyword / variant 留接口 - ---- - -## 7. 优先级 - -中到高。 - -它不一定阻塞 Renderer v0 的第一步落地,但如果完全忽略,未来做 C# SRP 时会出现明显返工。 - diff --git a/docs/used/Renderer模块_Scene层缺少MeshFilter与MeshRenderer抽象3.27.md b/docs/used/Renderer模块_Scene层缺少MeshFilter与MeshRenderer抽象3.27.md deleted file mode 100644 index 8e15d817..00000000 --- a/docs/used/Renderer模块_Scene层缺少MeshFilter与MeshRenderer抽象3.27.md +++ /dev/null @@ -1,129 +0,0 @@ -# Renderer模块 Scene层缺少MeshFilter与MeshRenderer抽象 - -## 1. 问题定义 - -当前工程已经具备: - -- `GameObject` -- `Component` -- `Scene` -- `CameraComponent` -- `LightComponent` - -但仍然缺少最关键的可渲染组件抽象: - -- `MeshFilterComponent` -- `MeshRendererComponent` - -这会导致 Renderer 模块无法以“正式场景组件”的方式承接渲染对象。 - ---- - -## 2. 当前现状 - -从当前代码看: - -- `GameObject` 已经支持通用组件挂载 -- `Scene` 的序列化 / 反序列化已经依赖 `ComponentFactoryRegistry` -- `ComponentFactoryRegistry` 当前只注册了 `Camera`、`Light`、`AudioSource`、`AudioListener` - -也就是说,场景层已经具备“扩展组件”的框架,但还没有真正承接渲染对象的组件类型。 - ---- - -## 3. 为什么这是 Renderer 阶段必须先补的缺口 - -如果没有正式的可渲染组件,Renderer 会被迫依赖以下临时做法: - -- 在测试里手工拼 mesh/material/transform -- 在 Renderer 内部写死资源路径或对象查找逻辑 -- 把“场景对象”和“渲染对象”分裂成两套平行系统 - -这会直接破坏后续目标: - -- 不利于 editor Inspector 展示 -- 不利于场景序列化 -- 不利于未来 C# API 对齐 Unity -- 不利于未来引入 `SkinnedMeshRenderer`、`SpriteRenderer` 等扩展 - ---- - -## 4. 与 Unity 风格的关系 - -如果未来要模仿 Unity 做 C# SRP,那么场景组件模型最好一开始就尽量贴近 Unity。 - -因此建议不是只加一个大而全的 `MeshRendererComponent`,而是直接采用: - -- `MeshFilterComponent`:持有 mesh -- `MeshRendererComponent`:持有材质和渲染状态 - -这样更容易在未来映射到: - -- C# `MeshFilter` -- C# `MeshRenderer` -- Inspector 上的分离展示 -- 脚本对 mesh / material 的分别访问 - ---- - -## 5. 建议方案 - -### 5.1 新增组件 - -#### `MeshFilterComponent` - -最小建议字段: - -- `ResourceHandle m_mesh` - -#### `MeshRendererComponent` - -最小建议字段: - -- 材质数组 -- `enabled` -- `castShadows` -- `receiveShadows` -- `renderLayer` - -### 5.2 注册到 `ComponentFactoryRegistry` - -这样场景文件才能: - -- 正确反序列化 -- 在 editor 中按组件类型恢复 -- 为未来脚本与 Inspector 留入口 - -### 5.3 Renderer 只从组件层抽取渲染对象 - -Renderer 不应自行维护另一套“逻辑对象 -> 渲染对象”的临时注册表作为长期方案。 - -正确方式应是: - -- `Scene` -- `GameObject` -- `MeshFilterComponent` -- `MeshRendererComponent` - -共同构成 Renderer 的输入源。 - ---- - -## 6. 验收标准 - -完成后至少应满足: - -1. 场景可正式挂载 mesh 渲染对象 -2. `Scene` 序列化 / 反序列化可恢复对应组件 -3. Renderer 可以直接从场景中提取可渲染对象 -4. editor 后续可以直接复用同一套组件数据 -5. 后续 C# API 可以平滑映射到 Unity 风格组件模型 - ---- - -## 7. 优先级 - -高。 - -这是 Renderer 模块正式落地前必须解决的前置缺口。 - diff --git a/docs/used/Renderer模块设计与实现.md b/docs/used/Renderer模块设计与实现.md deleted file mode 100644 index a7adea43..00000000 --- a/docs/used/Renderer模块设计与实现.md +++ /dev/null @@ -1,556 +0,0 @@ -# Renderer模块设计与实现 - -## 1. 背景 - -XCEngine 当前已经完成了较为可用的 RHI 抽象层,且已经具备: - -- `Scene + GameObject + Component` 基础场景模型 -- `CameraComponent` / `LightComponent` 等基础组件 -- `Mesh` / `Material` / `Texture` / `Shader` 等资源类型 -- D3D12 / OpenGL 双后端 RHI 抽象与测试体系 - -下一阶段不应该继续封闭式打磨 RHI,而应该在 RHI 之上正式建立 **Renderer 模块**。 - -这里的 Renderer 模块不是“最终形态的 SRP”,而是: - -- 先建立一层 **原生渲染运行时** -- 先让场景对象能够以正式渲染链路被绘制 -- 同时在设计上预留未来 **C# Scriptable Render Pipeline(SRP)** 的接入点 - -也就是说,当前阶段的正确目标不是直接实现 Unity 的 URP/HDRP,而是先建立一套 **与 Unity 渲染架构方向一致的原生基础层**,后续让 C# SRP 驱动它。 - ---- - -## 2. 设计目标 - -Renderer 模块的目标分为两层: - -### 2.1 当前阶段目标 - -先完成一套最小但完整的原生渲染链路: - -- 从 `Scene` 中提取可渲染对象 -- 通过 `Camera` 构建视图与投影数据 -- 通过 `Material` / `Mesh` / `Texture` 构建 GPU 绘制数据 -- 在 RHI 之上完成正式的 frame 渲染 -- 支持 swapchain 输出与离屏输出 -- 建立独立于 editor 的渲染宿主模型 - -### 2.2 面向未来 C# SRP 的目标 - -当前阶段的实现必须为后续演进预留稳定边界: - -- 未来允许用 C# 定义 `RenderPipelineAsset` / `RenderPipeline` -- 未来允许用 C# 组织 render pass -- 未来允许 editor `SceneView` / `GameView` 通过同一套 renderer 输出 -- 未来允许 C# 脚本控制 camera 渲染、pass 排序、目标输出与 command buffer - -因此,当前阶段的原生 Renderer 不能做成一个写死的“大一统内建渲染函数”,而应该一开始就具备“可被上层 pipeline 驱动”的结构。 - ---- - -## 3. 与 Unity 渲染架构的对应关系 - -当前建议的路线与 Unity 的总体方向是对齐的,但要注意分层位置。 - -### 3.1 推荐分层 - -```text -Scene / Components / Resources - ↓ -Renderer 模块(原生渲染运行时) - ↓ -未来 C# SRP 层(脚本化渲染管线) - ↓ -RHI 抽象层 - ↓ -D3D12 / OpenGL / Vulkan 后端 -``` - -### 3.2 各层职责 - -#### Scene / Components / Resources - -负责描述“要渲染什么”,例如: - -- 场景对象 -- 相机 -- 灯光 -- 网格 -- 材质 -- 贴图 - -这一层不应该直接持有后端 API 对象。 - -#### Renderer 模块(本阶段核心) - -负责描述“如何从场景变成 draw call”,例如: - -- 渲染对象抽取 -- 可见性裁剪 -- GPU 资源缓存 -- camera frame 数据组织 -- render target / depth target 管理 -- render pass 调度 -- 内建前向管线 - -这一层是未来 C# SRP 的原生支撑层。 - -#### 未来 C# SRP 层 - -负责描述“以脚本方式控制渲染流程”,例如: - -- `RenderPipelineAsset` -- `RenderPipeline` -- `ScriptableRenderContext` -- `CommandBuffer` -- `RenderPassEvent` -- pass 注入与重排 - -这一层不应该直接绕过 Renderer 模块去操作后端 API。 - -#### RHI 抽象层 - -负责统一 GPU 接口与资源对象,是渲染系统的执行后端,而不是场景渲染逻辑本身。 - -### 3.3 与 Unity 的概念映射 - -| Unity 概念 | XCEngine 当前/规划对应 | -|---|---| -| `Camera` | `CameraComponent` | -| `Light` | `LightComponent` | -| `MeshFilter` | 计划新增 `MeshFilterComponent` | -| `MeshRenderer` | 计划新增 `MeshRendererComponent` | -| `RenderPipelineAsset` | 未来 Renderer 模块上的 pipeline asset 抽象 | -| `RenderPipeline` | 未来 Renderer 模块上的 pipeline 实例抽象 | -| `ScriptableRenderContext` | 未来 Renderer 模块对脚本暴露的原生 render context | -| `CommandBuffer` | 未来 Renderer 模块对脚本暴露的命令缓冲抽象 | -| `GraphicsDevice` / native render backend | 当前 RHI + 后端实现 | - -结论是: - -- **方向上符合 Unity 渲染架构** -- **当前阶段实现的应是 Unity 渲染体系中的原生底座** -- **而不是直接跳到最终的脚本化 SRP** - ---- - -## 4. 核心设计原则 - -### 4.1 先建立原生渲染运行时,再开放脚本化管线 - -如果现在直接做 C# SRP,而原生 Renderer 边界还不存在,后续会出现: - -- C# API 直接耦合 RHI -- editor viewport 与 runtime camera 逻辑混杂 -- 资源对象与 GPU 对象生命周期混乱 - -因此必须先收敛原生 Renderer 模块。 - -### 4.2 Scene 层只描述逻辑对象,不持有后端对象 - -`GameObject`、`Component`、`Mesh`、`Material` 等对象只能描述逻辑与资源,不应该直接持有 D3D12/OpenGL 私有对象。 - -GPU 对象应由 Renderer 内部缓存层负责创建和复用。 - -### 4.3 editor 只是渲染宿主,不是渲染逻辑本体 - -`GameView` / `SceneView` 最终只是 Renderer 的输出宿主。 - -Renderer 本身必须先支持: - -- 输出到 swapchain -- 输出到离屏纹理 - -然后 editor 再把离屏纹理接进 ImGui 面板。 - -### 4.4 为未来 SRP 预留 pipeline 抽象 - -即使第一阶段先做内建前向渲染,也不应该把逻辑写死成单一 `SceneRenderer::DrawEverything()`。 - -应该从一开始就保留: - -- `RenderPipeline` -- `RenderPipelineAsset` -- `RenderContext` -- camera 列表驱动 -- pass 分阶段执行 - -这样未来 C# 只是在这个原生结构上做绑定,而不是重做一遍架构。 - -### 4.5 测试体系与渲染层分离 - -`tests/RHI/` 继续只验证 RHI。 - -Renderer 模块应建立自己的测试体系: - -- `tests/Rendering/unit/` -- `tests/Rendering/integration/` - -这样职责边界才清晰。 - ---- - -## 5. 模块划分建议 - -建议新增 `Rendering` 模块,作为场景与 RHI 之间的正式中间层。 - -### 5.1 推荐目录结构 - -```text -engine/ -├── include/XCEngine/Rendering/ -│ ├── RenderSurface.h -│ ├── RenderContext.h -│ ├── RenderPipeline.h -│ ├── RenderPipelineAsset.h -│ ├── SceneRenderer.h -│ ├── RenderSceneExtractor.h -│ ├── RenderCameraData.h -│ ├── VisibleRenderObject.h -│ ├── RenderResourceCache.h -│ └── Pipelines/ -│ └── BuiltinForwardPipeline.h -└── src/Rendering/ - ├── RenderSurface.cpp - ├── SceneRenderer.cpp - ├── RenderSceneExtractor.cpp - ├── RenderResourceCache.cpp - └── Pipelines/ - └── BuiltinForwardPipeline.cpp -``` - -### 5.2 组件层建议 - -为了尽可能对齐 Unity,而不是做一个临时过渡方案,建议直接采用: - -- `MeshFilterComponent` -- `MeshRendererComponent` - -其中: - -#### `MeshFilterComponent` - -负责“这个对象使用哪一个 mesh”: - -- `ResourceHandle` - -#### `MeshRendererComponent` - -负责“这个对象如何被渲染”: - -- 材质数组 -- cast shadow / receive shadow -- render queue / layer / enable 状态 -- 未来可扩展 light probe / motion vector / static batching 标记 - -这样做的好处是: - -- 更贴近 Unity 的对象模型 -- 更容易映射未来 C# API -- 更容易在 editor Inspector 中呈现 -- 更容易为 `SkinnedMeshRenderer`、`SpriteRenderer` 等后续组件扩展留位置 - -### 5.3 Renderer 内部运行时对象 - -#### `RenderSurface` - -统一表示渲染输出目标: - -- 交换链输出 -- 离屏 color/depth 输出 -- editor viewport 输出 - -#### `RenderSceneExtractor` - -负责从 `Scene` 中提取本帧可渲染对象: - -- mesh -- material -- transform -- bounds -- render state - -#### `RenderResourceCache` - -负责把资源模块对象转成 GPU 可用对象: - -- mesh -> vertex/index buffer -- texture -> RHI texture / resource view -- material -> descriptor set / uniform buffer / pipeline key -- shader pass -> pipeline state - -#### `RenderContext` - -作为原生渲染执行上下文,未来用于承接脚本化 pipeline 的调度。 - -它应封装: - -- 当前 frame 的 command list -- render target 设置 -- clear / draw / submit -- camera 相关渲染上下文 - -#### `RenderPipeline` - -用于抽象具体渲染流程。 - -第一阶段只有一个原生内建实现: - -- `BuiltinForwardPipeline` - -未来再开放: - -- native 可切换 pipeline -- C# 绑定的 scriptable pipeline - ---- - -## 6. 第一阶段实现边界 - -第一阶段只做最小可用链路,不做“大而全”。 - -### 6.1 第一阶段要做 - -- `Rendering` 模块骨架 -- `MeshFilterComponent` / `MeshRendererComponent` -- `RenderSurface` -- `RenderSceneExtractor` -- `RenderResourceCache` -- `SceneRenderer` -- `BuiltinForwardPipeline` -- 单 camera -- 单方向的 opaque forward 渲染 -- 深度测试与深度写入 -- 交换链输出 -- 离屏输出 - -### 6.2 第一阶段先不做 - -- 阴影 -- 后处理 -- 延迟渲染 -- 完整 PBR -- render graph -- C# SRP 真正落地 -- editor viewport 完整交互 -- 多 camera 叠加 - -### 6.3 第一阶段材质能力建议 - -建议先支持两档: - -1. `UnlitTexture` -2. `SimpleLit` - -其中: - -- `UnlitTexture` 用于先打通最小链路 -- `SimpleLit` 用于验证灯光、法线与材质基础通路 - ---- - -## 7. 面向未来 C# SRP 的预留设计 - -虽然第一阶段先做原生内建渲染,但必须提前约束下面这些方向。 - -### 7.1 先定义 pipeline 边界,再定义内建实现 - -正确顺序应当是: - -1. 先定义 `RenderPipeline` 抽象 -2. 再实现 `BuiltinForwardPipeline` -3. 后续 C# SRP 只是在这个边界上做脚本绑定 - -而不是: - -1. 先写死一个 `SceneRenderer` -2. 以后再强行拆成 pipeline - -第二种方式后续返工会很大。 - -### 7.2 Renderer 模块应向未来脚本层暴露的概念 - -当前阶段不一定全部实现,但结构上要留位置: - -- `RenderPipelineAsset` -- `RenderPipeline` -- `RenderContext` -- `CullingResults` -- `DrawingSettings` -- `FilteringSettings` -- `ShaderTag` -- `CommandBuffer` -- `RendererList` - -这些概念不一定要立刻与 Unity 一字不差,但应该在职责上能对应上。 - -### 7.3 材质与 Shader 资产模型不能停留在“单 shader 文件 + 属性包” - -未来做 SRP 时,shader pass 选择、render queue、tag、render state 都是必要能力。 - -因此当前阶段即使先不全量实现,也不能把资产模型彻底锁死在过于简单的结构上。 - -这一点单独列为 issue。 - ---- - -## 8. 分阶段推进建议 - -### 阶段 A:Renderer v0 骨架 - -目标: - -- 建立 `Rendering` 模块 -- 建立 `MeshFilterComponent` / `MeshRendererComponent` -- 建立 `RenderSurface` -- 建立 `BuiltinForwardPipeline` - -验收: - -- 可以通过 Renderer 正式绘制一个 textured quad 场景 -- 输出到 swapchain -- 输出到离屏 RT - -### 阶段 B:真实资源场景接入 - -目标: - -- 接入 mesh / texture / material 资源模块 -- 跑通 `backpack` 这样的真实模型场景 - -验收: - -- 真实 obj 资源经资源模块导入后可通过 Renderer 正式绘制 -- D3D12 / OpenGL 双后端结果一致 - -### 阶段 C:基础光照 - -目标: - -- 接入 `LightComponent` -- 跑通最基础的单方向光前向渲染 - -验收: - -- `sphere` / `backpack` 存在正确基础明暗 -- 材质参数与法线链路可验证 - -### 阶段 D:pipeline 抽象显式化 - -目标: - -- 把内建前向渲染切到 `RenderPipeline` 抽象之下 -- 支持 camera 列表驱动 -- 为未来 C# SRP 绑定准备原生接口 - -验收: - -- 原生内建 pipeline 通过统一接口驱动 -- renderer 不再依赖单一路径写死执行 - -### 阶段 E:editor viewport 接入 - -目标: - -- `SceneView` / `GameView` 使用 Renderer 的离屏输出 - -验收: - -- editor 面板只是渲染宿主 -- 不额外复制一套渲染逻辑 - -### 阶段 F:C# SRP 桥接 - -目标: - -- 在既有 Renderer 模块基础上绑定脚本化 pipeline - -验收: - -- C# 可以控制 camera 渲染流程 -- 原生 Renderer 继续负责底层资源、上下文与执行 - ---- - -## 9. 测试体系建议 - -Renderer 模块需要独立测试体系。 - -### 9.1 单元测试 - -建议放在: - -- `tests/Rendering/unit/` - -测试内容: - -- render object 抽取 -- material 参数打包 -- pipeline key 构建 -- GPU cache 命中与失效 -- render surface 创建与 resize - -### 9.2 集成测试 - -建议放在: - -- `tests/Rendering/integration/` - -建议场景: - -1. `textured_quad_scene` -2. `backpack_scene` -3. `lit_sphere_scene` - -仍然维持当前 RHI 抽象测试的好习惯: - -- 一场景一张 `GT.ppm` -- D3D12 / OpenGL 都与同一张 GT 比对 - -### 9.3 与 RHI 测试的关系 - -`tests/RHI/` 继续用于验证: - -- API 抽象正确性 -- 后端行为一致性 -- 资源 / 命令 /格式映射等底层问题 - -`tests/Rendering/` 则验证: - -- 场景渲染链路 -- 组件与资源到渲染结果的闭环 - ---- - -## 10. 当前已识别的不适配问题 - -以下问题不适合直接塞进本设计文档正文实现里,而应该独立跟踪: - -1. `Scene / Components` 层还没有 `MeshFilter / MeshRenderer` 抽象 -2. `Editor` 还没有 viewport 的离屏渲染宿主接入层 -3. `Material / Shader` 资产模型还不足以支撑未来 SRP 的 pass/tag 语义 - -对应 issue: - -- `docs/issues/Renderer模块_Scene层缺少MeshFilter与MeshRenderer抽象.md` -- `docs/issues/Renderer模块_EditorViewport缺少RenderSurface接入层.md` -- `docs/issues/Renderer模块_Material与Shader资产模型暂不满足SRP演进需求.md` - ---- - -## 11. 结论 - -当前 Renderer 阶段的正确方向是: - -- 在 RHI 之上建立 **原生渲染运行时** -- 用它先承接基础前向渲染 -- 同时提前为未来 **C# SRP** 留出清晰接口 - -因此,下一阶段的 Renderer 规划如果按本文执行,是与 Unity 渲染架构方向相容的,而且比“先做一个临时内建 renderer,后面再拆”更稳。 - -一句话概括: - -- **现在做的是 Unity 式渲染体系的原生底座** -- **以后在这个底座之上接 C# SRP** - diff --git a/docs/used/Renderer结构收口与代码正式化计划_完成归档_2026-04-05.md b/docs/used/Renderer结构收口与代码正式化计划_完成归档_2026-04-05.md deleted file mode 100644 index 5a94b922..00000000 --- a/docs/used/Renderer结构收口与代码正式化计划_完成归档_2026-04-05.md +++ /dev/null @@ -1,415 +0,0 @@ -# Renderer 结构收口与代码正式化计划 -日期:`2026-04-05` - -## 1. 阶段定位 - -当前 Rendering 主线在功能上已经完成了相当多闭环: - -- 三后端统一的 runtime renderer 主链已经建立 -- directional shadow、multi-light、object-id、editor overlay 等能力都已接入 -- SceneView / GameView 基本共用了同一条 runtime 渲染路径 - -但从代码结构和职责边界上看,这一阶段还没有真正收口。现在的问题已经不再是“某个功能没接上”,而是: - -**renderer 的核心模块里仍然混有明显的阶段性写法、特殊分支、职责堆叠和 editor/runtime 边界不清的问题。** - -如果此时直接继续往上叠 skybox、环境、后处理、更多 renderer feature,后面会越来越难拆,最终重新把已经相对稳定的 renderer 主链拖回到“能跑但很难维护”的状态。 - -因此,本阶段的唯一目标不是加新功能,而是: - -**把当前 renderer 这一阶段真正做成可持续演进的正式结构,为后续 Skybox / Environment / PostProcess / 更正式的 SRP 承接清掉结构债。** - -## 2. 为什么现在必须先做结构收口 - -这不是“目录看着乱一点”的表面问题,而是已经影响后续演进的实质性架构问题。 - -### 2.1 `CameraRenderer` 仍然存在特殊通道 - -当前 `CameraRenderer` 虽然已经具备请求规划与多阶段执行能力,但 `object-id` 仍然是单独的一套特殊路径,而不是正式 frame composition 里的统一 pass 节点。 - -这带来的问题: - -- 相机级执行顺序不是单一模型,而是“主链 + 特判” -- 后续 skybox / post-process / capture / debug target 更难正规接入 -- 单元测试里被迫维护特殊 mock pass 类型,而不是统一的 pass contract - -关键文件: - -- `engine/include/XCEngine/Rendering/ObjectIdPass.h` -- `engine/include/XCEngine/Rendering/CameraRenderer.h` -- `engine/include/XCEngine/Rendering/CameraRenderRequest.h` -- `engine/src/Rendering/CameraRenderer.cpp` - -### 2.2 `BuiltinForwardPipeline.cpp` 已经是典型 god file - -这个文件里当前同时混着: - -- pass wrapper -- shader pass resolve -- graphics pipeline 创建 -- descriptor set layout 规划 -- descriptor set 资源写入 -- lighting 常量打包 -- material fallback -- draw submission - -这已经不是“文件有点长”,而是职责拆分失败。后续任何修改都会把高层语义、资源绑定、RHI 细节、draw 级逻辑一起牵动,测试也只能做大颗粒回归,无法精准保护。 - -关键文件: - -- `engine/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp` - -### 2.3 `RenderMaterialUtility.h` 混合了契约、兼容层和运行时解析 - -当前这个头里至少混了五类职责: - -- builtin pass contract 定义 -- shader/property/binding 查询 -- descriptor layout 规划 -- legacy/material fallback 兼容 -- runtime material resolve 与绑定辅助 - -这会导致: - -- “正式 contract” 与 “过渡兼容逻辑” 难以分开演进 -- 很多 renderer 代码只能依赖一个超大工具头 -- 头文件膨胀,职责不可读,接口边界不清 - -关键文件: - -- `engine/include/XCEngine/Rendering/RenderMaterialUtility.h` - -### 2.4 editor / debug pass 仍然混在 runtime renderer 核心层里 - -grid、outline 这些能力现在已经走到了比较正式的 runtime host path,但它们在 engine 里的组织方式仍然更接近“把 editor 需求塞进 renderer 核心”。 - -风险在于: - -- runtime 核心会继续被 editor 语义污染 -- 后续 scene/game/editor 三条宿主路径边界会再次变模糊 -- 玩家运行时和编辑器专用渲染能力的依赖关系难以长期维护 - -关键文件: - -- `engine/include/XCEngine/Rendering/Passes/BuiltinInfiniteGridPass.h` -- `engine/include/XCEngine/Rendering/Passes/BuiltinObjectIdOutlinePass.h` - -### 2.5 文件拆分层次仍然不干净 - -例如 `BuiltinDepthStylePassBase.cpp` 的底部还直接放着 `BuiltinDepthOnlyPass`、`BuiltinShadowCasterPass` 具体实现,这说明“抽象基类”和“具体 pass”仍然没有彻底分层。 - -关键文件: - -- `engine/src/Rendering/Passes/BuiltinDepthStylePassBase.cpp` - -### 2.6 少量稳定性问题仍然暴露出“临时写法” - -比如 scene extractor 里 visible item 的稳定排序仍然用 raw pointer 作为 tie-breaker,而 additional light 已经升级成了稳定的 `GameObject::ID` 语义。 - -这类问题虽然不大,但非常能说明当前代码里仍混有阶段性临时写法,必须顺手清理干净。 - -关键文件: - -- `engine/src/Rendering/RenderSceneExtractor.cpp` - -## 3. 本阶段的核心设计原则 - -本阶段继续严格遵循当前工程既定设计理念,并与 `RHI模块总览` 中的核心原则保持一致: - -1. `RHI` 只负责 GPU 抽象,不感知 object-id、grid、outline、skybox、post-process 等高层语义。 -2. `Renderer` 负责 scene extraction、frame composition、material/shader contract、runtime pass orchestration。 -3. `Editor` 只作为 renderer 的宿主和扩展使用方,不把 editor 语义反向污染 RHI。 -4. 兼容层和正式 contract 必须拆开,不能继续把“临时兜底”混在正式主链里。 -5. 不引入 `render graph`。本阶段先把现有 renderer 结构做正式化,不跳级优化。 -6. 不做“修修补补式”文件搬家,必须同时修职责边界、执行路径和测试结构。 - -## 4. 阶段总目标 - -本阶段收口完成后,应达到以下状态: - -1. `CameraRenderer` 形成单一、明确、可测试的 frame composition 模型。 -2. runtime pass 与 object-id / editor-debug pass 的边界清晰,接入点正式化。 -3. `BuiltinForwardPipeline` 不再由一个 god file 承担所有责任。 -4. `RenderMaterialUtility` 被拆分为“正式 contract 层”和“兼容/运行时辅助层”。 -5. renderer 文件结构与代码结构一致,抽象基类、具体 pass、绑定辅助、compat helper 各归其位。 -6. 当前所有 rendering / editor 相关测试继续通过,不破坏已闭环功能。 - -## 5. 非目标 - -本阶段明确不做: - -- `render graph` -- deferred / clustered / tiled lighting -- 新一轮 editor 视觉特效堆叠 -- 大规模 shader authoring 体系重写 -- point / spot shadow -- 重新设计 RHI - -## 6. 分阶段执行方案 - -## 6.1 Phase A:Camera Frame Composition 正式化 - -### 目标 - -消灭 `CameraRenderer` 里 object-id 的特殊执行通道,把相机级执行模型统一成正式 frame composition。 - -### 要解决的根因 - -- 现在相机渲染顺序不是单一 contract -- `ObjectIdPass` 是旁路抽象,不利于后续继续扩展 composition -- 测试里存在针对 object-id 的特殊 mock pass 体系 - -### 具体工作 - -1. 重新审视 `CameraRenderRequest` 的阶段描述,明确: - - pre-scene - - shadow/depth - - scene pipeline - - auxiliary offscreen passes - - post-scene - - overlay -2. 去掉 `ObjectIdPass` 作为并行特例抽象的地位。 -3. 把 object-id 统一纳入正式 pass 执行序列,必要时通过 pass category / target intent 标识语义,而不是再保留独立虚函数族。 -4. 简化 `CameraRenderer` 执行逻辑,让失败传播、目标准备、pass 顺序只走一套主链。 -5. 同步收敛相关单元测试,让测试验证“阶段顺序”和“失败传播”,而不是验证某个特判分支。 - -### 验收标准 - -- `CameraRenderer` 不再对 object-id 走特判主逻辑 -- `test_camera_scene_renderer` 等单测仍覆盖 object-id 顺序与失败传播 -- editor viewport object-id picking 不回退 - -### 计划提交点 - -这一阶段完成后立即提交推送一次。 - -## 6.2 Phase B:BuiltinForwardPipeline 职责拆分 - -### 目标 - -把 `BuiltinForwardPipeline.cpp` 从 god file 拆成正式的职责层次,但不改变现有 forward runtime 的对外行为。 - -### 要解决的根因 - -- pipeline resolve、resource layout、descriptor write、material resolve、lighting packing、draw submission 全部耦合 -- 任何小修改都会波及整个文件 -- 难以为 skybox / post-process / future pipeline 承接建立稳定接口 - -### 具体工作 - -1. 先按职责切出独立模块,优先拆成以下几层: - - shader/pass resolve - - pipeline cache/build - - resource binding layout / descriptor planning - - frame-scoped lighting / pass constants upload - - draw item submission -2. 让 `BuiltinForwardPipeline` 保留 orchestration 职责,而不是继续承载全部细节。 -3. 清理与 `RenderMaterialUtility` 的交叉依赖,为下一阶段拆 contract 做准备。 -4. 保证 unlit / lit / object-id / depth-only / shadow-caster 的绑定逻辑不被混淆。 - -### 验收标准 - -- `BuiltinForwardPipeline.cpp` 明显缩小,核心职责清晰 -- 新拆出的模块命名与职责稳定,不是单纯“工具类化” -- forward 相关单测、集成测试全部不回退 - -### 计划提交点 - -这一阶段完成后立即提交推送一次。 - -## 6.3 Phase C:RenderMaterialUtility 正式拆层 - -### 目标 - -把 shader/material/pass 的正式 contract 与 legacy/compat/runtime helper 拆开。 - -### 要解决的根因 - -- 正式接口和过渡逻辑混在一起 -- 任何依赖 `RenderMaterialUtility.h` 的代码都会被迫包含大量不相干能力 -- 后续 shader/material 演进会被兼容逻辑长期绑死 - -### 具体工作 - -1. 明确拆成三层语义: - - `contract`:builtin pass 名称、标准 binding 名称、正式解析规则 - - `runtime resolve`:材质/着色器运行时查询、pass 选择、绑定规划 - - `compat`:legacy property 名称、历史 fallback、过渡适配 -2. 避免再把大段实现继续塞在头文件里,能下沉到 `.cpp` 的尽量下沉。 -3. 对外只暴露最小且稳定的正式接口。 -4. 给 compat 层加清晰边界,避免以后继续被当作默认主路径使用。 - -### 验收标准 - -- `RenderMaterialUtility.h` 体量显著下降,职责单一 -- renderer 主链依赖的是正式 contract / runtime resolve,而不是 compat 大杂烩 -- 现有 shader/material 行为与测试结果保持一致 - -### 计划提交点 - -这一阶段完成后立即提交推送一次。 - -## 6.4 Phase D:Runtime Pass 与 Editor/Debug Pass 边界重整 - -### 目标 - -明确 engine runtime rendering core 与 editor/debug-oriented rendering extension 的边界。 - -### 要解决的根因 - -- grid、outline 等语义虽然已经可用,但组织上仍偏临时 -- engine 核心层里混有 editor 专用概念 -- 后续 camera frame composition 扩展容易再次被 editor 需求污染 - -### 具体工作 - -1. 明确哪些是 runtime 正式能力,哪些是 editor/debug extension。 -2. 把 editor/debug pass 的注册与宿主接入方式整理成正式 extension seam。 -3. 保持 SceneView / GameView 继续复用 runtime renderer 主链,但 editor overlay / outline / grid 不侵入 runtime scene composition。 -4. 补足必要文档,说明 engine、renderer、editor 三者的责任边界。 - -### 验收标准 - -- editor/debug pass 不再作为 runtime renderer 核心概念扩散 -- SceneView / GameView 显示、grid、outline、gizmo 宿主路径不回退 -- 新增代码结构能自然承接后续 icon/light gizmo/camera gizmo 等扩展 - -### 计划提交点 - -这一阶段完成后立即提交推送一次。 - -## 6.5 Phase E:稳定性清扫、文件收口与文档归档 - -### 目标 - -清掉这一阶段剩余的临时写法,让实现、测试、文档口径再次一致。 - -### 具体工作 - -1. 修正 `RenderSceneExtractor` 里仍然使用 raw pointer 的稳定排序 tie-breaker。 -2. 把 `BuiltinDepthStylePassBase.cpp` 中具体 pass 实现拆出到独立文件。 -3. 全面复查 renderer 相关文件命名、目录结构、头源分布是否仍有明显反模式。 -4. 更新 `tests/TEST_SPEC.md` 与相关 renderer / editor guide。 -5. 阶段完成后,把已过期 plan 归档到 `docs/used`。 - -### 验收标准 - -- renderer 核心目录结构与职责边界基本一致 -- 没有明显残留的阶段性临时代码入口 -- 文档、测试矩阵、实现状态三者一致 - -### 计划提交点 - -这一阶段完成后立即提交推送一次。 - -## 7. 测试策略 - -本阶段的测试必须是“每阶段落地即验证”,不能到最后一次性回归。 - -### 7.1 Unit - -重点保护: - -- `CameraRenderer` 阶段顺序与失败传播 -- `RenderSceneExtractor` 的稳定输出 -- `BuiltinForwardPipeline` 的绑定与材质解析 -- material/shader contract 拆层后的接口行为 - -优先关注: - -- `tests/Rendering/unit/test_camera_scene_renderer.cpp` -- `tests/Rendering/unit/test_builtin_forward_pipeline.cpp` -- 与 material utility / scene extractor 相关的 unit tests - -### 7.2 Editor / Runtime Integration - -重点回归: - -- object-id picking -- SceneView / GameView runtime 渲染链 -- overlay / outline / grid -- backpack / shadow / multi-light / camera stack / offscreen 等现有场景 - -至少覆盖: - -- `tests/editor/test_viewport_render_flow_utils.cpp` -- `tests/editor/test_scene_viewport_overlay_renderer.cpp` -- `tests/editor/test_viewport_object_id_picker.cpp` -- 现有 rendering integration matrix 中与 lighting、object-id、camera flow 相关的场景 - -### 7.3 编译与宿主验证 - -每一阶段至少执行: - -1. 相关 test target 编译 -2. 相关 unit / integration tests -3. 必要时编译 `XCEditor` -4. 对 editor 中 SceneView / GameView 做 smoke 验证 - -## 8. 风险与控制 - -### 风险 1:把“结构重构”做成单纯的文件搬家 - -后果: - -- 文件名变了,职责没变 -- 代码仍然继续跨层互相依赖 - -控制策略: - -- 每次拆分都要同时调整接口边界和测试保护点 - -### 风险 2:为了图省事继续保留 object-id 特判 - -后果: - -- Camera frame composition 永远无法正式化 -- 后续 skybox / post-process 会继续引入更多特判 - -控制策略: - -- 第一阶段必须先砍掉这类特殊旁路 - -### 风险 3:compat 逻辑继续侵入正式 contract - -后果: - -- shader/material 体系长期混乱 -- 之后 Unity 风格 shader authoring 很难落地 - -控制策略: - -- compat 层必须显式命名、显式隔离、显式限定使用场景 - -### 风险 4:editor/debug pass 重整时破坏现有 editor 体验 - -后果: - -- 影响当前 SceneView 主线 -- 把结构收口又变成功能回退 - -控制策略: - -- 每一阶段都要做 editor smoke 和既有测试回归 - -## 9. 阶段完成判定 - -满足以下条件时,本阶段才算真正收口: - -1. `CameraRenderer` 已统一成正式 frame composition 执行模型。 -2. `BuiltinForwardPipeline` 与 `RenderMaterialUtility` 已完成职责拆分,核心 god file 问题消除。 -3. runtime pass 与 editor/debug pass 边界清晰,不再混成一团。 -4. 现有 rendering / editor tests 继续稳定通过。 -5. `docs/plan` 与 `docs/used` 的 plan 入口重新清晰,不再保留已过期的执行入口。 - -## 10. 与当前主线的关系 - -这份计划不是替代“Skybox 环境与 Frame Composition 正式化”方向,而是它的前置收口。 - -顺序必须是: - -1. 先做 renderer 结构收口与代码正式化 -2. 再做 skybox / environment / post-process 的正式接入 -3. 最后才考虑更高阶的 renderer feature 与未来 SRP 承接 - -否则就是在结构债未清的情况下继续加层,后面只会越收越难。 diff --git a/docs/used/Renderer阶段收口_旧兼容路径清理与正式化计划_完成归档_2026-04-08.md b/docs/used/Renderer阶段收口_旧兼容路径清理与正式化计划_完成归档_2026-04-08.md deleted file mode 100644 index 1551701d..00000000 --- a/docs/used/Renderer阶段收口_旧兼容路径清理与正式化计划_完成归档_2026-04-08.md +++ /dev/null @@ -1,523 +0,0 @@ -# Renderer阶段收口:旧兼容路径清理与正式化计划 - -日期:`2026-04-08` - -## 1. 背景 - -当前 `Rendering` 模块的主执行架构已经基本成型: - -- `RenderSceneExtractor` -- `SceneRenderRequestPlanner` -- `SceneRenderer / CameraRenderer` -- built-in forward / shadow / object-id / outline / final-color / skybox - -这些主链路已经能稳定支撑: - -- runtime 场景渲染 -- editor scene/game viewport -- 多光源、阴影、object-id、outline、skybox 等现有能力 - -因此,当前 Rendering 的主要问题已经不再是“能不能画出来”,而是: - -- 还残留一些旧路线兼容代码 -- 一些 built-in 运行契约仍然依赖隐式推断 -- 少量路径仍然带有明显的过渡期实现痕迹 - -如果这些问题不在当前阶段彻底收口,后续继续推进: - -- Renderer 模块扩展 -- Material / Shader editor -- Unity 风格 SRP 底层承接 - -就会持续建立在一层“虽然能跑,但不是正式规则”的兼容逻辑之上。 - -这不符合当前阶段的目标。 - -当前阶段的正确方向不是新增更多渲染功能,而是: - -- 清理旧兼容路径 -- 去掉运行时语义猜测 -- 把 built-in shader / material / pass contract 进一步正式化 - ---- - -## 2. 当前已确认的问题 - -基于本轮对 `engine/include/XCEngine/Rendering`、`engine/src/Rendering`、`engine/src/Resources/Shader`、`engine/src/Resources/Mesh` 的代码审查,当前确认存在以下问题。 - -### 2.1 Mesh 导入仍可生成“无 shader / 无 schema”的旧材质路线 - -当前 `MeshLoader` 导入子材质时,仍然直接写入: - -- `baseColor` -- `baseColorTexture` -- `opacity` -- `twoSided` - -而不是直接落到正式 shader schema 对应的属性名与纹理槽位。 - -这导致 runtime 渲染阶段仍然需要兜底兼容这些旧名字。 - -典型位置: - -- `engine/src/Resources/Mesh/MeshLoader.cpp` -- `engine/include/XCEngine/Rendering/Materials/RenderMaterialResolve.h` - -### 2.2 Rendering 仍通过属性别名表推断 built-in 材质语义 - -当前 `RenderMaterialResolve.h` 中,仍然保留了大量 builtin 属性/纹理别名表,例如: - -- `baseColor` -- `_BaseColor` -- `color` -- `_Color` -- `baseColorTexture` -- `_BaseColorTexture` -- `_MainTex` -- `texture` - -这意味着 runtime 当前并不是“按 shader schema 正式解析”,而是: - -- 优先找 semantic -- 找不到就继续按一批旧属性名字猜 - -这属于典型过渡兼容逻辑,不应成为正式长期实现。 - -### 2.3 BuiltinForward / Depth / Shadow 仍存在 per-material fallback constant 路线 - -当前如果材质没有正式 schema constant layout,管线仍会临时构造: - -- `FallbackPerMaterialConstants` - -并继续提交 draw。 - -这说明 runtime 仍允许“非正式材质实例”继续进入正式绘制链路。 - -这条路径虽然提高了兼容性,但本质上绕开了已经建立的 shader/material 正式模型。 - -### 2.4 Built-in pass resource binding 仍依赖隐式硬编码 - -当前 builtin shader pass 如果未显式声明 `resources`,运行时仍会通过: - -- `TryBuildImplicitBuiltinPassResourceBindings` - -自动补一套绑定布局。 - -这意味着资源绑定契约并不完全存在于 shader 资产中,而是仍有一部分硬编码在 C++ 中。 - -这会带来两个问题: - -1. shader 资产与 runtime 存在双份真相 -2. 后续继续演进 shader/material/editor 时,容易再次产生隐式规则 - -### 2.5 HLSL register 重写仍保留 legacy alias - -当前 `ShaderVariantUtils.h` 仍保留: - -- `ResolveLegacyHlslBindingDeclarationAlias` - -以及基于 `gBaseColorTexture` / `gLinearSampler` 一类旧命名的重写逻辑。 - -这说明 shader runtime 编译阶段仍在兼容旧命名风格。 - -这属于典型“过渡兼容层”,应在 built-in shader 显式资源契约完成后清掉。 - -### 2.6 Built-in pass 选择仍存在隐式默认规则 - -当前如果 shader 没有显式 builtin metadata,`MatchesBuiltinPass(...)` 仍会把它默认当成: - -- `ForwardLit` - -这意味着 shader 即使没有明确声明自己属于哪个 built-in pass,也有可能继续进入主几何管线。 - -这不利于长期正式化。 - -### 2.7 Shader artifact 仍兼容多代旧 schema - -当前 shader artifact loader 仍兼容: - -- `XCSHD01` -- `XCSHD02` -- `XCSHD03` -- `XCSHD04` -- 当前 schema - -但 shader artifact 本质上是 `Library` 中的可重建中间产物,不属于必须长期 runtime 兼容的用户资产格式。 - -如果继续保留多代 schema 分支,会让 shader 资源链路长期背着历史包袱。 - ---- - -## 3. 本阶段设计原则 - -本计划执行时,必须严格遵守以下原则。 - -### 3.1 正式路径只能有一条 - -对 built-in shader / material / pass 来说,正式路径必须是: - -`导入/authoring -> shader schema -> material instance -> explicit pass contract -> render pipeline` - -不能继续允许 runtime 依赖旧命名、旧别名、旧格式去自动猜测。 - -### 3.2 兼容应尽量前移到导入/重建阶段,而不是留在 runtime - -如果确实存在历史资产问题,应优先采用: - -- 重新导入 -- 重新生成 artifact -- 一次性迁移 - -而不是继续在 runtime loader / renderer 中保留长期兼容分支。 - -### 3.3 Built-in shader 契约必须显式写进 shader 资产 - -以下内容必须属于 shader/pass 资产本身,而不是 runtime 猜出来: - -- pass 类型 -- pass metadata -- resource binding -- property semantic - -### 3.4 Rendering 不再为“无正式 shader/schema 的材质”兜底渲染 - -当前阶段的目标是“收口”,不是“继续最大化兼容”。 - -因此: - -- 非正式材质应尽快在导入层修正 -- runtime 应逐步拒绝无 schema 的正式绘制路径 - -### 3.5 每一步都必须可验证 - -每个阶段完成后必须配套: - -- unit test -- 必要的 integration test -- editor 编译/回归 - -不能只凭画面“看起来没问题”判断完成。 - ---- - -## 4. 本阶段目标 - -本阶段完成后,Rendering 模块应达到以下状态: - -1. Mesh 导入出来的材质直接走正式 shader/material 体系 -2. runtime 不再依赖 `baseColor` / `_MainTex` 等别名表去维持 built-in 主链 -3. built-in pass resource binding 由 shader 资产显式声明,不再依赖隐式硬编码补全 -4. built-in pass 分类必须显式声明,不再存在“默认 ForwardLit” -5. shader artifact runtime loader 不再长期兼容多代旧 schema -6. 对应测试体系同步升级,保证收口后功能不回退 - ---- - -## 5. 明确不在本阶段处理的内容 - -以下内容不属于本阶段目标: - -- render graph -- deferred renderer -- 新一轮后处理功能扩展 -- C# SRP 脚本侧 API -- ShaderGraph -- 高级材质编辑器功能扩展 - -这些方向都依赖本阶段先把底层 contract 收紧。 - ---- - -## 6. 分阶段执行计划 - -## Phase 1:建立基线与目标测试 - -### 目标 - -先把当前遗留兼容路径的行为边界用测试钉住,并同步写出“目标行为”的新测试。 - -### 任务 - -- 审查并整理当前覆盖以下行为的测试: - - `RenderMaterialResolve` - - builtin forward pipeline resource binding - - mesh material import - - shader artifact load -- 新增/调整测试,使其明确区分: - - 当前历史兼容行为 - - 本阶段目标正式行为 -- 对以下目标先写失败测试或待切换测试: - - imported mesh material 必须绑定正式 builtin shader - - imported material property 必须落到正式 schema 名称 - - builtin pass 若无显式 metadata,不得进入主 pipeline - - builtin shader 若无显式 resources,不得依赖隐式 binding 补全 - -### 验收标准 - -- 能清楚列出哪些测试在保护旧行为,哪些测试在保护目标行为 -- 后续每个阶段都能基于这些测试判断是否真正收口 - ---- - -## Phase 2:收口 Mesh 导入材质到正式 shader/material 路径 - -### 目标 - -彻底去掉 imported mesh material 的“无 shader / 裸属性名”旧路线。 - -### 任务 - -- 调整 `MeshLoader` 导入逻辑: - - imported material 直接绑定正式 builtin shader - - 默认按现有主线落到 builtin lit/forward 合同 -- 导入属性与纹理时,直接写正式 property name / texture slot: - - 例如 `_BaseColor` - - `_MainTex` - - `_Cutoff` - - 其他已正式声明的 builtin 属性 -- 不再向 imported material 写入仅靠 runtime 别名识别的裸字段: - - `baseColor` - - `baseColorTexture` - - `color` - - `texture` -- 更新 mesh import 相关测试、render extractor 测试、相关 integration 资源测试 - -### 验收标准 - -- mesh import 结果中的材质都带有正式 shader 引用 -- mesh import 结果中的属性/纹理绑定名称与 shader schema 对齐 -- 不再需要 runtime 靠旧别名才能让导入材质正常渲染 - ---- - -## Phase 3:移除 runtime builtin 材质语义别名与 fallback 常量路径 - -### 目标 - -让 built-in pipeline 只吃正式 schema 材质,不再继续兼容旧材质命名。 - -### 任务 - -- 清理 `RenderMaterialResolve.h` 中的旧别名解析表: - - base color property alias - - base texture alias - - skybox texture alias - - alpha cutoff alias -- 保留并强化基于 `shader property semantic` 的正式解析路径 -- 移除 `FallbackPerMaterialConstants` 路线 -- 当材质未携带正式 schema constant layout 时: - - 显式报错 / 记录诊断 - - 拒绝进入需要正式材质常量的绘制路径 -- 调整 forward / depth / shadow / skybox 相关单测 - -### 验收标准 - -- builtin pipeline 不再依赖属性别名表维持主链 -- builtin pipeline 不再手工构造 per-material fallback constant 继续绘制 -- runtime 只接受正式 shader/material 契约 - ---- - -## Phase 4:显式化 builtin pass resource binding contract - -### 目标 - -让 builtin shader pass 的资源绑定契约完全存在于 shader 资产中,而不是藏在 runtime 硬编码里。 - -### 任务 - -- 为所有 builtin shader pass 补齐显式 `resources` 描述 -- 覆盖至少以下 shader: - - `forward-lit.shader` - - `depth-only.shader` - - `shadow-caster.shader` - - `object-id.shader` - - `skybox.shader` - - `final-color.shader` - - 其他当前仍在主链中的 builtin shader -- 清理 `TryBuildImplicitBuiltinPassResourceBindings` -- 清理 `ShaderVariantUtils.h` 中围绕 implicit/legacy binding 的兼容逻辑: - - legacy alias register rewrite - - 依赖 `gXxx` 名称重写的分支 -- 调整 shader loader / rendering pipeline / builtin pass 单测 - -### 验收标准 - -- builtin shader pass 缺少显式资源绑定时,构建或运行应明确失败 -- runtime 不再替 shader 资产自动补 binding layout -- HLSL runtime 编译不再依赖 legacy alias register 重写 - ---- - -## Phase 5:显式化 builtin pass metadata 与 pass 选择规则 - -### 目标 - -去掉“默认 ForwardLit”一类隐式 pass 归类规则。 - -### 任务 - -- 收紧 `BuiltinPassMetadataUtils`: - - built-in pass 匹配必须依赖显式 pass name / tag - - 删除“无 metadata 默认归 ForwardLit”的逻辑 -- 审查并统一 builtin shader 的 pass metadata: - - `Name` - - `LightMode` - - 其它当前正式要求的 tag -- 对进入 builtin 主线的 shader 建立硬约束: - - 没有显式 builtin metadata 的 shader,不得继续被当作主几何 shader 使用 -- 更新 pass 匹配测试和 shader authoring 测试 - -### 验收标准 - -- builtin pass 选择全部基于显式 metadata -- 不存在 runtime 默认猜一个 pass 类型的行为 - ---- - -## Phase 6:清理旧 shader artifact schema 兼容 - -### 目标 - -让 shader artifact runtime loader 与 material artifact 一样,收口到 current schema。 - -### 任务 - -- 清理 `ShaderArtifactLoader.cpp` 中对旧 schema 的分支兼容: - - `XCSHD01` - - `XCSHD02` - - `XCSHD03` - - `XCSHD04` -- 将旧 `Library` artifact 的处理方式改为: - - 识别为过期 - - 触发重新导入 / 重新生成 - - 或直接报错要求重建 `Library` -- 更新 asset database / shader load 相关测试 -- 明确记录此阶段会带来的影响: - - 旧 `Library` 无法直接沿用 - - 需要一次性刷新或重建 - -### 验收标准 - -- shader artifact loader 只接受 current schema -- 对旧 artifact 的处理边界清晰且可测试 - ---- - -## Phase 7:全量验证与阶段收口 - -### 目标 - -确认 Rendering 在去掉旧兼容层之后没有破坏现有功能。 - -### 任务 - -- 编译并运行: - - `material_tests` - - `rendering_unit_tests` - - `asset_tests` - - `editor_tests` - - 受影响的 mesh/shader 资源测试 -- 重新编译 `XCEditor` -- 重点回归: - - scene viewport - - game viewport - - object-id picking - - selection outline - - skybox - - 阴影 - - 多光源 - - backpack / sphere / quad 等 integration scene -- 形成阶段收口报告 - -### 验收标准 - -- 所有直接相关测试通过 -- editor 编译通过 -- 关键 integration scene 渲染行为不回退 -- 能明确宣告 runtime 旧兼容路径已移除 - ---- - -## 7. 风险与注意事项 - -### 7.1 这是一次“切正式路径”的收口,不是小修小补 - -本计划一旦执行,就会主动删除一部分兼容逻辑。 - -因此不能以“尽量少改代码”为目标,而应以: - -- 正式路径唯一 -- contract 清晰 -- 后续 SRP 可承接 - -为目标。 - -### 7.2 `Library` 重建属于预期影响 - -一旦收掉旧 shader artifact schema 兼容,旧 `Library` 里的 shader artifact 失效是正常现象。 - -这不应被视为回归,而应被视为阶段性收口的合理代价。 - -### 7.3 必须避免引入新的“临时兼容层” - -执行过程中需要特别警惕以下错误做法: - -- 新加一层 alias 表,试图“先兼容一下” -- 把 runtime fallback 换个名字继续保留 -- 在 editor 或 import 层再次引入一套过渡数据模型 - -如果遇到结构性问题,正确做法是: - -- 直接改到正式模型 -- 同步补测试 - -而不是再加一层短期兜底。 - ---- - -## 8. 建议执行顺序 - -建议严格按以下顺序推进: - -1. `Phase 1` 测试基线整理 -2. `Phase 2` mesh 导入材质正式化 -3. `Phase 3` runtime 材质别名与 fallback 常量清理 -4. `Phase 4` builtin pass 显式资源绑定 -5. `Phase 5` builtin pass metadata 显式化 -6. `Phase 6` shader artifact schema 收口 -7. `Phase 7` 全量验证 - -原因是: - -- 如果不先把 imported material 拉回正式路径 -- 后面的 runtime alias / fallback 清理就一定会打断现有资源链路 - ---- - -## 9. 本阶段完成后的预期状态 - -本计划完成后,Rendering 模块应达到以下状态: - -1. built-in shader/material/pass contract 全部走正式显式路径 -2. runtime 不再依赖旧命名猜测材质语义 -3. runtime 不再替非正式材质拼接 fallback 常量布局 -4. builtin shader 资源绑定契约完全由 shader 资产声明 -5. builtin pass 类型选择完全依赖显式 metadata -6. shader artifact runtime loader 不再背负旧 schema 包袱 -7. 整个 Rendering 模块更适合作为后续 Unity 风格 SRP 的底层承接 - ---- - -## 10. 一句话总结 - -当前 Rendering 真正需要的不是继续加功能,而是把残留的旧兼容路径彻底拔干净。 - -这一阶段的本质,是把: - -- imported material -- built-in shader binding -- pass metadata -- shader artifact - -全部拉回到同一套正式 contract 上,为后续 Renderer / Material / Shader / SRP 的继续推进打地基。 diff --git a/docs/used/Renderer阶段收口补充_ObjectIdPicking正式化.md b/docs/used/Renderer阶段收口补充_ObjectIdPicking正式化.md deleted file mode 100644 index abce5b74..00000000 --- a/docs/used/Renderer阶段收口补充_ObjectIdPicking正式化.md +++ /dev/null @@ -1,71 +0,0 @@ -# Renderer 阶段收口补充:Object ID Picking 正式化 - -日期:`2026-04-02` - -## 1. 这次补充收口解决什么 - -本次补充只收一件事: - -- `SceneView` 选中主链路正式切到 `GPU object-id` - -本次明确不做: - -- render graph -- renderer 内更完整的多 pass 调度 -- game/runtime 通用 picking 服务 - -原因很简单:这些属于下一阶段架构演进,不应该继续污染当前阶段的收口边界。 - -## 2. 本次收口后的正式行为 - -当前 `SceneView` 选中行为统一定义为: - -1. 场景渲染时生成 `object-id` 纹理 -2. 鼠标点击时读取对应像素 -3. 颜色解码为实体 ID -4. `0` 视为“未选中任何对象”,但这仍然是一次成功的 GPU 采样 - -关键变化: - -- editor 不再把 `CPU ray picking` 作为 `SceneView` 点击选中的静默回退主链路 -- `CPU ray picking` 继续保留为独立几何工具能力,不再承担当前正式选中流程 -- `object-id` 读取失败会被显式标记为 readback failure,而不是与“没有有效帧”混在一起 - -## 3. 为什么这才算收口 - -之前的问题不是没有 `object-id pass`,而是“主路径”和“兜底路径”的语义不够硬: - -- 成功采样 -- 无有效 object-id 帧 -- GPU 读回失败 - -这三种状态以前没有被清晰区分。 - -现在已经收紧为显式结果类型: - -- `Unavailable` -- `Success` -- `ReadbackFailed` - -这意味着: - -- renderer/editor 的 `object-id` 交互已经形成可测试契约 -- `0 id` 与“采样失败”不再混淆 -- 后续若要继续升级成异步 readback、共享 picking 服务,也有稳定边界可接 - -## 4. 当前阶段完成后的边界 - -到这里,当前阶段可以正式视为完成: - -- editor viewport 宿主链路已打通 -- renderer 的 builtin post-process 已形成稳定接口 -- `SceneView` 选中正式以 GPU object-id 为主链路 -- 回归测试已覆盖 object-id 读回状态语义 - -下一阶段真正该做的是: - -- renderer 内正式 render graph / pass graph -- 更完整的 renderer-owned picking 服务 -- editor / runtime shared picking contract - -而不是继续在这个阶段里反复修补 viewport host。 diff --git a/docs/used/Renderer阶段收口说明.md b/docs/used/Renderer阶段收口说明.md deleted file mode 100644 index 44fdd05a..00000000 --- a/docs/used/Renderer阶段收口说明.md +++ /dev/null @@ -1,164 +0,0 @@ -# Renderer阶段收口说明 - -## 1. 目标 - -本文用于正式收口当前 Renderer 阶段,明确: - -- 本阶段已经完成什么 -- 哪些能力已经进入稳定边界 -- 哪些事项明确延期到下一阶段 -- 后续开发不应再继续把新功能塞回本阶段 - -当前收口日期:`2026-04-02` - ---- - -## 2. 本阶段已完成能力 - -### 2.1 Renderer 主体边界 - -当前已经形成稳定分层: - -- `RHI` 负责后端抽象与资源/命令执行 -- `Rendering` 负责场景提取、camera request、pipeline、builtin pass -- `Editor` 负责 viewport 宿主、输入、overlay、编辑态请求装配 - -关键点: - -- `CameraRenderer` 已经承担统一 camera 渲染执行职责 -- `SceneRenderer` 已经承担 scene -> camera request 的组织职责 -- editor scene viewport 不再自己拼装 renderer 执行逻辑 - -### 2.2 内建后处理边界 - -本阶段内建编辑态后处理已经收敛为 renderer 自己的通用请求能力: - -- `BuiltinPostProcessRequest` -- `BuiltinPostProcessPassPlan` -- `BuiltinPostProcessPassSequenceBuilder` - -这意味着: - -- renderer 公共接口不再暴露 `SceneView` 专有命名 -- grid / selection outline / debug mask 已归入 renderer 侧 builtin post-process 能力 -- editor 只负责“是否启用、传什么数据、把哪些 render target 绑定进 request” - -### 2.3 Editor Scene Viewport 接入 - -当前 editor scene viewport 已具备: - -- renderer 离屏输出接入 -- object-id 帧输出接入 -- CPU picking 回退链路 -- selection outline -- infinite grid -- built-in post-process 请求装配 - -其中: - -- grid 和 outline 仍然服务于 editor scene viewport -- 但执行入口已经下沉到 renderer -- editor 只保留宿主与编辑器语义 - -### 2.4 自动化测试体系 - -当前已经具备稳定回归闸门: - -- `tests/Rendering/unit` -- `tests/Rendering/integration` -- `tests/Editor` -- `rendering_phase_regression` - -当前阶段收口依赖的关键验证包括: - -- renderer unit tests -- editor tests -- 全 rendering integration 场景 -- `XCEditor` smoke launch - ---- - -## 3. 本阶段稳定边界 - -以下内容从现在开始视为本阶段稳定边界: - -1. renderer 公共请求以 `CameraRenderRequest` 为核心,而不是 editor 自定义执行入口。 -2. editor scene viewport 的内建后处理数据由 editor 组装,但 pass 执行由 renderer 负责。 -3. builtin post-process 的公共语义是 renderer 语义,不是 `SceneView` 语义。 -4. rendering regression 失败时,优先视为阶段回归,而不是“可接受的小问题”。 - ---- - -## 4. 本阶段明确延期项 - -以下事项明确不再继续塞入本阶段,转入下一阶段: - -### 4.1 真正的多 pass / render graph 框架 - -当前已有 pass sequence 与 builtin post-process,但这还不是完整的 renderer 多 pass 架构。 - -延期内容: - -- renderer 级 render graph -- 更正式的 pass phase / event 模型 -- 更通用的资源读写依赖管理 - -### 4.2 GPU Object ID 正式方案 - -当前 editor selection 相关链路已经能工作,但还不是最终方案。 - -延期内容: - -- renderer 内正式 object-id pass/attachment 规范化 -- editor picking 从 CPU fallback 继续向 GPU object-id 正式方案收敛 -- editor/game shared picking contract - -### 4.3 Gizmo 最终渲染体系 - -当前 gizmo 与 scene viewport 已经能工作,但不属于本阶段 renderer 收口范围。 - -延期内容: - -- 更成熟的 gizmo 渲染架构 -- 更统一的 gizmo draw pass / picking / overlay 体系 -- 与后续 renderer 多 pass 的正式对接 - -### 4.4 C# SRP 对接 - -当前 renderer 的职责边界已经为 SRP 预留好了方向,但本阶段不做真正脚本化 pipeline 落地。 - -延期内容: - -- `RenderPipelineAsset` / `RenderPipeline` 脚本绑定 -- `ScriptableRenderContext` -- `CommandBuffer` -- renderer 与脚本侧的正式桥接层 - ---- - -## 5. 阶段退出标准 - -当前阶段只有在以下条件全部满足时才视为完成: - -1. renderer/editor 边界中不再存在新的 `SceneView` 语义向 renderer 公共接口泄漏。 -2. scene viewport 的 builtin post-process 组合链路具备稳定自动化回归覆盖。 -3. `rendering_phase_regression` 保持通过。 -4. 新功能开发转入下一阶段,不再回头污染本阶段边界。 - -截至本文落地时,这些退出标准已经满足。 - ---- - -## 6. 下一阶段入口 - -Renderer 下一阶段应当正式转向: - -- renderer 内更完整的多 pass / phase 模型 -- editor/game shared render feature 契约 -- object-id 正式化 -- 为后续 C# SRP 搭建真正可扩展的 renderer 接口 - -一句话总结: - -- 当前阶段已经把“Renderer 从 RHI 之上独立出来,并接通 editor scene viewport”这件事做完 -- 下一阶段不该继续修补这一层,而应开始建设更正式的 renderer 扩展框架 diff --git a/docs/used/SRP_AssetRuntime失效与重建接缝计划_完成归档_2026-04-20.md b/docs/used/SRP_AssetRuntime失效与重建接缝计划_完成归档_2026-04-20.md deleted file mode 100644 index bffcae6e..00000000 --- a/docs/used/SRP_AssetRuntime失效与重建接缝计划_完成归档_2026-04-20.md +++ /dev/null @@ -1,152 +0,0 @@ -# SRP AssetRuntime 失效与重建接缝计划 2026-04-20 - -## 1. 阶段目标 - -上一阶段已经补上了: - -`ScriptableRendererData -> ScriptableRenderer` - -这一层的 invalidation seam。 - -但当前还缺更上面的一刀: - -`ScriptableRenderPipelineAsset -> ManagedRenderPipelineAssetRuntime` - -也就是同一个 managed pipeline asset 还活着、同一个 native asset runtime 也还活着时,如果 asset 自己发生变化,native 侧现在还会继续拿旧缓存工作。 - -这一阶段的目标就是把这条接缝补齐。 - ---- - -## 2. 当前问题 - -### 2.1 asset 本身没有正式 dirty/version seam - -当前 `ScriptableRenderPipelineAsset` 只有: - -1. `CreatePipeline` -2. `ConfigureCameraRenderRequest` -3. `ConfigureCameraFramePlan` -4. `GetPipelineRendererAssetKey` -5. `ReleaseRuntimeResources` - -但没有像 Unity `RenderPipelineAsset` 那样的资源失效入口。 - -### 2.2 native runtime 会缓存旧 pipeline 和旧 renderer asset - -`MonoManagedRenderPipelineAssetRuntime` 当前会缓存: - -1. `m_pipelineHandle` -2. `m_pipelineRendererAssetResolved` -3. `m_pipelineRendererAsset` - -这些缓存现在只会在更大的生命周期边界释放: - -1. asset runtime 销毁 -2. bridge generation 变化 - -如果同一个 managed asset 在运行时修改自身配置,native 仍可能继续使用旧 pipeline。 - -### 2.3 现有测试还没锁住“同 runtime 内 asset dirty 后重建” - -上一阶段锁住的是: - -1. renderer data dirty 后 renderer rebuild -2. asset runtime release 时缓存释放 - -但还没有锁住: - -1. 同一个 asset runtime -2. 同一个 recorder -3. asset 调用 `SetDirty()` -4. native 自动丢弃旧 pipeline 并重建 - ---- - -## 3. 实施方案 - -### 3.1 给 `ScriptableRenderPipelineAsset` 增加正式 asset invalidation seam - -新增: - -1. `SetDirty()` -2. `GetRuntimeResourceVersionInstance()` - -其中: - -1. `SetDirty()` 负责释放 asset runtime resources -2. 同时 bump asset runtime resource version -3. native 通过 version 变化判断缓存是否失效 - -### 3.2 让 native runtime 基于 asset version 自动失效 - -`MonoManagedRenderPipelineAssetRuntime` 在访问下面两类缓存前先同步 version: - -1. `AcquireManagedPipelineHandle()` -2. `GetPipelineRendererAsset()` - -如果 version 变化,则: - -1. dispose 旧 managed pipeline -2. 清掉 renderer asset cache -3. 下次按新 asset 状态重建 - -### 3.3 让 stage recorder 感知 pipeline 重建 - -同一个 recorder 继续复用时,需要在 pipeline handle 变化后清掉旧 method cache,避免还抓着旧 managed pipeline 的方法解析结果。 - -### 3.4 用纯 asset 级 probe 锁住行为 - -不走 URP renderer data,直接增加: - -1. 一个 `ScriptableRenderPipelineAsset` probe -2. 一个按创建时配置决定 `SupportsStageRenderGraph` 的 pipeline probe -3. 一个运行时触发 `SetDirty()` 的观察脚本 - -验证点: - -1. dirty 前只支持 `MainScene` -2. dirty 后同一个 recorder 改为支持 `PostProcess` -3. create/dispose/invalidate 计数正确 - ---- - -## 4. 实施步骤 - -### Step 1:补 managed core seam - -1. 修改 `managed/XCEngine.ScriptCore/Rendering/Core/ScriptableRenderPipelineAsset.cs` -2. 增加 asset dirty/version 机制 - -### Step 2:补 native runtime invalidation - -1. 修改 `engine/src/Scripting/Mono/MonoScriptRuntime.cpp` -2. 在 runtime 侧检测 asset version -3. 失效旧 pipeline 和 renderer asset cache - -### Step 3:补 probe 与脚本测试 - -1. 修改 `managed/GameScripts/RenderPipelineApiProbe.cs` -2. 修改 `managed/GameScripts/ScriptableRenderContextApiSurfaceProbe.cs` -3. 修改 `tests/scripting/test_mono_script_runtime.cpp` - -### Step 4:验证与收口 - -1. 编译 `XCEditor` -2. 运行 `rendering_unit_tests` -3. 运行 `scripting_tests` -4. 运行旧 editor 10s 冒烟并检查 `SceneReady` -5. 归档 plan -6. 提交推送 - ---- - -## 5. 验收标准 - -完成后应满足: - -1. `ScriptableRenderPipelineAsset` 有正式 dirty/version seam -2. 同一个 native asset runtime 能检测 managed asset 变化 -3. 同一个 recorder 下 asset dirty 后会丢弃旧 pipeline 并重建 -4. API probe 与 scripting test 全部锁住这条行为 -5. `XCEditor`、单测、冒烟全部通过 diff --git a/docs/used/SRP_GenericNativePolicyKeySeamNarrowingPlan_完成归档_2026-04-21.md b/docs/used/SRP_GenericNativePolicyKeySeamNarrowingPlan_完成归档_2026-04-21.md deleted file mode 100644 index 5055c178..00000000 --- a/docs/used/SRP_GenericNativePolicyKeySeamNarrowingPlan_完成归档_2026-04-21.md +++ /dev/null @@ -1,60 +0,0 @@ -# SRP Generic Native Policy Key Seam Narrowing Plan 2026-04-21 - -## Goal - -Remove the remaining native policy-key seams from the generic `ScriptableRenderPipelineAsset` API surface and narrow them to renderer-backed pipelines. - -## Why This Stage - -After the previous cleanup stages, the generic managed SRP base still exposes several string-key seams that are not Unity-style SRP-facing concepts: - -1. `GetRenderSceneSetupPolicyAssetKey*` -2. `GetCameraFrameStandalonePassAssetKey*` -3. `GetDirectionalShadowPlanningPolicyAssetKey` -4. `GetDirectionalShadowExecutionPolicyAssetKey*` - -Repo usage shows these seams are effectively renderer-backed only: - -1. renderer-data-level overrides live in `ScriptableRendererData` / `UniversalRendererData`; -2. asset-level directional-shadow planning override lives in `UniversalRenderPipelineAsset`; -3. no plain `ScriptableRenderPipelineAsset` subclass currently overrides these methods. - -So the generic base is still carrying URP/native binding details that should sit lower in the stack. - -## Scope - -Included: - -1. remove the remaining native policy-key methods from generic `ScriptableRenderPipelineAsset`; -2. re-home asset-level fallback hooks onto `RendererBackedRenderPipelineAsset`; -3. keep optional native Mono bridge resolution so renderer-backed/URP behavior still works; -4. rebuild `XCEditor` and run old editor smoke. - -Not included: - -1. removing policy-key based native resolution entirely; -2. changing renderer-data-level native key APIs; -3. redesigning native scene renderer binding itself; -4. deferred renderer work. - -## Acceptance - -This stage is complete when: - -1. generic `ScriptableRenderPipelineAsset` no longer exposes render-scene / standalone-pass / directional-shadow native key seams; -2. `RendererBackedRenderPipelineAsset` still provides the required fallback hooks for URP/native binding; -3. `XCEditor` builds successfully; -4. old editor smoke passes and `editor.log` contains a fresh `SceneReady`. - -## Validation - -Validated on 2026-04-21: - -1. built old editor target with `cmake --build . --config Debug --target XCEditor` from `build/`; -2. launched `editor/bin/Debug/XCEngine.exe`; -3. let it run for about 12 seconds; -4. verified a fresh `SceneReady` entry in `editor/bin/Debug/editor.log`. - -Observed log line: - -`[2026-04-21 14:08:38] [INFO] [General] [SceneLoadTrace] SceneReady elapsed_ms=6527 first_frame_ms=806 peak_pending_async=9` diff --git a/docs/used/SRP_GenericPipelineRendererKeySeamNarrowingPlan_完成归档_2026-04-21.md b/docs/used/SRP_GenericPipelineRendererKeySeamNarrowingPlan_完成归档_2026-04-21.md deleted file mode 100644 index fcab0cff..00000000 --- a/docs/used/SRP_GenericPipelineRendererKeySeamNarrowingPlan_完成归档_2026-04-21.md +++ /dev/null @@ -1,58 +0,0 @@ -# SRP Generic Pipeline Renderer Key Seam Narrowing Plan 2026-04-21 - -## Goal - -Remove native scene renderer key selection from the generic `ScriptableRenderPipelineAsset` public surface and narrow that seam to renderer-backed pipelines only. - -## Why This Stage - -The current managed SRP API still exposes `GetPipelineRendererAssetKey*` on the generic pipeline asset base class. That is not a Unity-style SRP-facing concept. It is an internal native binding seam used to resolve a native scene renderer backend for renderer-backed pipelines. - -Keeping it on the generic SRP asset base has two problems: - -1. it leaks native backend selection into the public API shape of all managed pipelines; -2. it makes custom SRP assets look like they are supposed to choose native renderer assets directly, which is not the long-term ownership model. - -At the same time, the repo scan shows the active usage is renderer-backed only: - -1. `RendererBackedRenderPipelineAsset` provides the asset-level contextual key resolution; -2. `ScriptableRendererData` provides the renderer-data-level backend key; -3. no plain `ScriptableRenderPipelineAsset` subclass currently overrides this seam. - -## Scope - -Included: - -1. remove `GetPipelineRendererAssetKey*` from generic managed `ScriptableRenderPipelineAsset`; -2. keep the optional native bridge support so renderer-backed assets can still expose the method by name; -3. define the pipeline-renderer-key seam directly on `RendererBackedRenderPipelineAsset`; -4. rebuild `XCEditor` and run old editor smoke. - -Not included: - -1. deleting native pipeline renderer asset resolution from the runtime bridge; -2. renaming renderer-data backend key APIs; -3. removing other native key seams such as scene setup / shadow execution; -4. deferred renderer work. - -## Acceptance - -This stage is complete when: - -1. generic `ScriptableRenderPipelineAsset` no longer exposes `GetPipelineRendererAssetKey*`; -2. renderer-backed assets still resolve native scene renderer binding correctly; -3. `XCEditor` builds successfully; -4. old editor smoke passes and `editor.log` contains a fresh `SceneReady`. - -## Validation - -Validated on 2026-04-21: - -1. built old editor target with `cmake --build . --config Debug --target XCEditor` from `build/`; -2. launched `editor/bin/Debug/XCEngine.exe`; -3. let it run for about 12 seconds; -4. verified a fresh `SceneReady` entry in `editor/bin/Debug/editor.log`. - -Observed log line: - -`[2026-04-21 14:04:19] [INFO] [General] [SceneLoadTrace] SceneReady elapsed_ms=5688 first_frame_ms=600 peak_pending_async=9` diff --git a/docs/used/SRP_MainScene录制入口收口与ContextCore化计划_完成归档_2026-04-20.md b/docs/used/SRP_MainScene录制入口收口与ContextCore化计划_完成归档_2026-04-20.md deleted file mode 100644 index 39784f14..00000000 --- a/docs/used/SRP_MainScene录制入口收口与ContextCore化计划_完成归档_2026-04-20.md +++ /dev/null @@ -1,183 +0,0 @@ -# SRP Main Scene 录制入口收口与 Context Core 化计划 2026-04-20 - -## 1. 阶段目标 - -上一阶段已经完成: - -1. managed fullscreen pass 统一走 `RenderGraphRasterPassBuilder` -2. `Rendering_ScriptableRenderContext_RecordFullscreenPass` 旧桥删除 - -但主场景录制仍然没有完全收口: - -1. `ScriptableRenderPass` 还内置 `RecordScenePhase / RecordSceneInjectionPoint / RecordScene` -2. Universal 的主场景 pass 仍然是通过这些 pass 基类 helper 间接转发到 native -3. `RecordScene` 仍然保留着“一键录制默认 forward 场景顺序”的旧 internal call - -这一阶段的目标是: - -1. 把 main scene 录制入口提升到 `ScriptableRenderContext` -2. 让 `ScriptableRenderPass` 从 builtin-forward scene helper 中退出 -3. 删除 native `RecordScene` 旧桥 - ---- - -## 2. 当前问题 - -### 2.1 scene 录制入口放错层 - -当前 fullscreen 已经变成: - -1. `ScriptableRenderContext` -2. `RenderGraphTextureHandle` -3. `RenderGraphRasterPassBuilder` - -这一套 core API 在负责 authoring。 - -但 main scene 仍然是: - -1. `ScriptableRenderPass.RecordOpaqueScenePhase(...)` -2. `ScriptableRenderPass.RecordBeforeOpaqueInjection(...)` -3. `ScriptableRenderPass.RecordScene(...)` - -也就是 scene 录制能力还挂在首方 Universal pass 基类上。 - -这会导致: - -1. core/context 没有完整 scene authoring 语义 -2. Universal package 仍然在偷偷拥有一套 core 级能力 -3. 后续想做更像 Unity 的 SRP/URP API 时,会继续被 pass helper 绑住 - -### 2.2 `RecordScene` 是旧时代残留 - -`RecordScene` 本质上是在 native 侧执行一段默认 builtin forward 场景顺序: - -1. before opaque -2. opaque -3. after opaque -4. before skybox -5. skybox -6. after skybox -7. before transparent -8. transparent -9. after transparent - -这条能力如果继续保留在 native internal call: - -1. 会鼓励 managed 侧继续依赖默认顺序黑盒 -2. 不利于以后把场景组织完全收回 SRP/URP 包层 - ---- - -## 3. 本阶段范围 - -本阶段只做下面几件事: - -1. 归档上一阶段 fullscreen plan -2. 在 core 层补 scene phase / injection API -3. 删除 `RecordScene` native internal call -4. 重构 Universal 与 probe 调用面 -5. 编译旧 editor 并冒烟 - -本阶段明确不做: - -1. culling API -2. renderer list / draw settings / filtering settings -3. deferred renderer -4. HDRP 风格分支 - ---- - -## 4. 实施步骤 - -### Step 1:core 层补 scene 录制枚举与 context API - -目标: - -在 `XCEngine.Rendering` core 中建立: - -1. `SceneRenderPhase` -2. `SceneRenderInjectionPoint` -3. `ScriptableRenderContext.RecordScenePhase(...)` -4. `ScriptableRenderContext.RecordSceneInjectionPoint(...)` - -并按需要补 convenience 方法: - -1. `RecordOpaqueScenePhase()` -2. `RecordBeforeOpaqueInjection()` -3. 以及一个纯 managed 组合版 `RecordScene()` - -完成标准: - -1. scene authoring 能力回到 context/core -2. core API 不再需要 Universal pass 基类代持 scene helper - -### Step 2:删除 native `RecordScene` 旧桥 - -目标: - -删除: - -1. `InternalCalls.Rendering_ScriptableRenderContext_RecordScene` -2. native 对应实现 -3. native 对应注册 - -并由 managed `ScriptableRenderContext.RecordScene()` 用显式 scene step 顺序自行组合。 - -完成标准: - -1. 全仓搜索不再出现 `Rendering_ScriptableRenderContext_RecordScene` -2. 默认主场景顺序不再是 native 黑盒 - -### Step 3:重构 Universal 与 probes - -目标: - -1. `UniversalRenderer` 改用 `ScriptableRenderContext` scene API -2. `ScriptableRenderPass` 删除 builtin-forward scene helper -3. `GameScripts` probes 改用 context scene API - -完成标准: - -1. Universal 主场景组织不再依赖 pass 基类里的 scene helper -2. probe 仍能覆盖主场景阶段行为 - -### Step 4:编译与冒烟 - -固定验证: - -1. 编译旧 `editor` -2. 启动 `editor/bin/Debug/XCEngine.exe` -3. 冒烟至少 10 秒 -4. 检查 `editor.log` 中新的 `SceneReady` - ---- - -## 5. 验收标准 - -这一阶段收口后应满足: - -1. scene 录制入口正式进入 `ScriptableRenderContext` -2. `ScriptableRenderPass` 不再持有 builtin-forward 专属 scene helper -3. native `RecordScene` 旧桥删除 -4. Universal main scene 仍可稳定录制 -5. 旧 editor 编译通过,冒烟通过 - ---- - -## 6. 阶段意义 - -这一阶段完成以后,SRP 主线会从: - -`fullscreen 已 graph 化,但 main scene 仍绑在 pass helper 上` - -推进到: - -`fullscreen + main scene 的基础 authoring 入口都回到 core/context` - -这样下一步继续做更像 Unity 的: - -1. `DrawObjectsPass` -2. `DrawSkyboxPass` -3. 更正式的 renderer feature / pass 体系 - -时,主语就会是 `context` 和 `SRP package`,而不是一层历史 helper。 diff --git a/docs/used/SRP_Mainline_过期归档_2026-04-19.md b/docs/used/SRP_Mainline_过期归档_2026-04-19.md deleted file mode 100644 index 6cec63ec..00000000 --- a/docs/used/SRP_Mainline_过期归档_2026-04-19.md +++ /dev/null @@ -1,374 +0,0 @@ -# SRP Mainline 2026-04-16 - -## 1. 结论 - -现在可以正式切到 `SRP` 主线。 - -但这里的“切到 SRP 主线”不是直接开做 `URP` 包层,也不是马上把所有渲染效果搬到 C#。 - -当前最正确的主线是: - -`Native RenderGraph / Planning / Execution` -`-> Managed SRP Runtime` -`-> Managed Forward Pipeline v1` -`-> URP-like 官方包层` - -也就是说,下一阶段的真正目标不是“做一个叫 SRP 的壳”,而是先把: - -1. `managed pipeline asset` 真正实例化出来 -2. `managed pipeline` 真正被 native 持有并回调 -3. `C#` 真正能组织主场景 render graph 录制 - -这三件事做成闭环。 - -在这之前,`URP-like package` 还不能开工。 - -## 2. 当前状态判断 - -### 2.1 已经具备的东西 - -当前 native 底座已经够你正式进入 SRP 主线: - -1. `RenderGraph` 已经不是空壳,builder / compiler / executor 都在工作。 -2. `ScriptableRenderPipelineHost` 已经能把 “stage recorder + fallback renderer” 组合起来。 -3. `BuiltinForwardPipeline` 已经支持按 `MainScene` stage 录制 render graph。 -4. 默认 pipeline 工厂已经能从 “managed or default asset” 入口走。 - -这说明 native 侧已经有了 SRP runtime 可以依附的承接点。 - -### 2.2 还没具备的东西 - -当前 managed 侧还远远没有形成真正的 SRP runtime: - -1. `GraphicsSettings` 现在只是在 native 里记录了一个 pipeline asset 类型描述,不是 asset 实例。 -2. `ManagedScriptableRenderPipelineAsset` 现在只是 “fallback host + optional stage recorder bridge”。 -3. 真正的 `ManagedRenderPipelineBridge` 实现根本还没接进运行时,当前只有测试里在 mock。 -4. `ScriptableRenderPipelineAsset` 只有 `CreatePipeline()` 占位,没有 frame/request planning 能力。 -5. `ScriptableRenderPipeline` 只有 `SupportsStageRenderGraph/RecordStageRenderGraph` 两个最小占位方法,而且没有上下文对象。 -6. C# 层没有 `ScriptableRenderContext`、没有 `CommandBuffer`、没有 `RendererFeature`、没有 `Renderer` 抽象。 - -所以当前状态不是 “SRP 已经差不多了”,而是: - -`native SRP host seam 已经有了` - -但 - -`managed SRP runtime 还没开始真正落地` - -## 3. 当前最核心的架构根因 - -### 3.1 现在的 managed 入口只是“选择器”,不是“运行时” - -当前 `GraphicsSettings.renderPipelineAssetType` 的语义本质上是: - -`把一个 C# 类型名写回 native` - -它没有解决下面这些真正关键的问题: - -1. asset 谁实例化 -2. pipeline 谁实例化 -3. 生命周期谁管理 -4. native 如何回调到具体 pipeline 对象 -5. pipeline 如何持有自己的配置 - -这意味着现在的 API 形态本身还不是未来可长期保留的最终形态。 - -### 3.2 当前 bridge 只允许“补一个 stage recorder”,这还不是 SRP - -现在 `ManagedScriptableRenderPipelineAsset` 的工作方式是: - -1. 先创建 native fallback host -2. 再给 host 塞一个可选 `stage recorder` - -这条路只能得到: - -`C# 向 builtin forward 注入一个 graph 录制钩子` - -而不是: - -`C# 自己拥有一条 render pipeline` - -如果这条边界不改,后面做出来的只会是“脚本包装版 builtin forward”,不是 Unity 意义上的 `SRP`。 - -### 3.3 当前 pipeline API 颗粒度太粗,无法支撑未来 SRP/URP - -现在 managed pipeline 只有: - -1. `SupportsStageRenderGraph(CameraFrameStage)` -2. `RecordStageRenderGraph(CameraFrameStage)` - -问题是它没有: - -1. frame context -2. camera context -3. render graph builder/context wrapper -4. scene draw / fullscreen / blit / renderer feature 注入接口 -5. request/frame planning hook - -这意味着即使把 bridge 打通,C# 也拿不到足够的组织能力。 - -### 3.4 当前 `BuiltinForwardPipeline` 仍然太像“完整管线”,还不像“可被 SRP 调度的 renderer” - -现在 builtin forward 内部已经有: - -1. `SceneRenderFeatureHost` -2. scene phase -3. stage graph builder - -但这些能力大部分仍然锁在 `BuiltinForwardPipeline` 这个整管线对象内部。 - -对未来 SRP 来说,更正确的方向应该是: - -1. `BuiltinForward` 退化为 native 默认 renderer 实现 -2. 把可复用的 scene rendering contract 抽成稳定 native renderer API -3. 让 managed pipeline 调用这个 renderer API 组织主场景,而不是直接操纵 `BuiltinForwardPipeline` 私有实现 - -### 3.5 当前 asset API 太弱,无法承担 Unity 风格 pipeline asset 角色 - -Unity 里的 pipeline asset 至少要承担: - -1. 创建 pipeline 实例 -2. 保存 renderer 配置 -3. 保存阴影/后处理/renderer data 等默认策略 -4. 驱动 camera/frame 规划策略 - -而现在的 managed asset 还没有这些入口。 - -这会直接阻塞未来: - -1. `URP Asset` -2. `RendererData` -3. `RendererFeature` -4. shadow/post-process 默认策略迁移 - -## 4. 正确目标架构 - -正确的长期结构应该是: - -`RHI` -`-> Native Render Kernel` -`-> Native RenderGraph` -`-> Native Renderer Contract` -`-> Managed SRP Runtime` -`-> Managed Universal Pipeline Package` -`-> 用户自定义 Pipeline / Feature / Pass` - -其中边界必须明确: - -### 4.1 留在 C++ 的东西 - -1. `RHI` -2. `RenderGraph` -3. planning / execution 主框架 -4. scene extraction / culling / resource lifetime -5. native draw/fullscreen/feature primitive -6. builtin renderer 的底层实现 - -### 4.2 去到 managed 的东西 - -1. `ScriptableRenderPipelineAsset` -2. `ScriptableRenderPipeline` -3. `ScriptableRenderContext` -4. `UniversalRenderPipelineAsset` -5. `UniversalRenderer / RendererFeature / RenderPass` -6. 用户自定义管线逻辑 - -### 4.3 不应该长期停留在 C++ builtin pipeline 里的东西 - -这些能力最终应该逐步上移到 `URP-like package` 的组织层: - -1. 阴影默认策略 -2. post-process 组合策略 -3. gaussian / volumetric / custom effect 的注入时机 -4. renderer feature 的启用/排序/注入逻辑 - -但注意: - -“上移”指的是组织和调度上移,不是把底层 draw code 全部改成 C#。 - -## 5. 执行顺序 - -### Step 1: 先做通用 managed object runtime - -这是第一刀,必须先做。 - -目标: - -1. 在 `MonoScriptRuntime` 里补通用 managed object 创建/持有/释放能力 -2. 不再只支持 `ScriptComponent` 实例和 `GameObject/Component` wrapper -3. 能按类描述创建任意 managed 对象 -4. 能持有 GC handle -5. 能反射调用实例方法 -6. 能稳定记录异常并向 native 返回失败 - -这一步是后面所有 SRP runtime 的硬前提。 - -没有这个,asset/pipeline 实例生命周期根本没法成立。 - -### Step 2: 把 managed pipeline asset 从“类型描述”提升成“真实 runtime 对象” - -目标: - -1. native 不再只拿到 `assembly/namespace/class` 描述 -2. native 能真正创建一个 managed asset 实例 -3. managed asset 能产出 managed pipeline 实例 -4. runtime 能持有 asset/pipeline 的生命周期 - -这一阶段里,`GraphicsSettings.renderPipelineAssetType` 可以临时继续作为 bootstrap 入口, -但 native 内部不能再停留在 descriptor-only 语义。 - -后面如果你要彻底对齐 Unity,再把 public API 从 `Type` 收敛到 asset instance。 - -### Step 3: 实现真正的 managed render pipeline bridge - -目标: - -1. 由 `MonoScriptRuntime` 提供真实的 `ManagedRenderPipelineBridge` -2. `ManagedScriptableRenderPipelineAsset` 不再只接测试 mock -3. bridge 负责创建绑定到具体 managed pipeline 实例的 recorder/runtime object -4. native `ScriptableRenderPipelineHost` 能稳定回调到对应 managed pipeline - -注意: - -这一步不能继续维持“只有一个全局空 bridge”的临时状态。 - -### Step 4: 扩展 managed asset / pipeline API - -当前 API 太弱,必须补接口。 - -至少要补到: - -1. asset 侧有 request/frame planning 钩子 -2. pipeline 侧有真正的 record context -3. pipeline 侧能拿到 camera/stage/frame 语义 -4. pipeline 侧不再只有裸 `CameraFrameStage` - -推荐的第一版方向: - -1. `ScriptableRenderPipelineAsset` - - `CreatePipeline()` - - `ConfigureCameraRenderRequest(...)` - - `ConfigureCameraFramePlan(...)` - - `GetDefaultFinalColorSettings()` 或等价设置入口 -2. `ScriptableRenderPipeline` - - `SupportsStageRenderGraph(...)` - - `RecordStageRenderGraph(...)` - - 参数升级为真正 context,而不是只有 stage enum - -### Step 5: 建立 `ScriptableRenderContext v1` - -这是第二个根问题。 - -如果没有 `ScriptableRenderContext`,C# 根本无法真正组织管线。 - -第一版不要直接暴露 RHI,要暴露“受控的 native renderer contract”: - -1. record graph pass -2. 调用 builtin scene renderer 绘制 opaque / skybox / transparent -3. 执行 fullscreen / blit -4. 访问必要的 frame blackboard / source/target -5. 注入 renderer feature/pass - -原则: - -1. 不把 native 内部 planning 细节直接暴露给 C# -2. 不把 `RenderGraphTextureHandle`、`RenderSurface` 全量裸暴露成最终 API -3. 先做受控 wrapper,再决定哪些能力继续上放 - -### Step 6: 把 builtin forward 拆成可复用的 native renderer contract - -这一刀非常关键。 - -目标: - -1. 不让 managed pipeline 直接依赖 `BuiltinForwardPipeline` 私有实现 -2. 把主场景绘制能力抽成稳定 native renderer API -3. 让 builtin forward 退化为默认实现,而不是未来所有 SRP 的硬编码中心 - -正确方向类似: - -`BuiltinForwardSceneRenderer` - -或等价的 - -`UniversalRendererNativeBackend` - -它至少要能被 managed 调度: - -1. 主场景 phase 绘制 -2. feature injection point -3. shadow sampling 相关约束 -4. scene setup / pass begin/end - -### Step 7: 做一条最小可用 managed forward pipeline - -到这一步,才算真正开始“跑 SRP 主线”。 - -验收标准不是有类名,而是: - -1. C# managed pipeline 可以被创建 -2. managed pipeline 可以稳定回调 -3. managed pipeline 可以录 `MainScene` graph -4. 这条 managed pipeline 可以替代当前 builtin forward 跑出主场景 - -这个阶段不要急着搬阴影/体积/高斯的全部组织权。 - -先把主链跑通。 - -### Step 8: 再开 URP-like package - -只有当 Step 7 完成,才适合正式开: - -`UniversalRenderPipelineAsset` -`UniversalRendererData` -`UniversalRenderer` -`RendererFeature` -`RenderPassEvent` - -到这时才开始逐步把: - -1. 阴影策略 -2. post-process 组合 -3. 体积/高斯等特性注入 - -从当前 native builtin 组织层往 managed package 迁。 - -## 6. 本阶段明确不做的事 - -现在不应该做: - -1. 直接开 `URP` 包 -2. 直接做 deferred pipeline -3. 直接把所有阴影/体积/高斯逻辑搬上 C# -4. 直接暴露一堆 raw native internal type 给脚本 - -这些都会把架构做歪。 - -## 7. 第一阶段收口标准 - -下面这些条件成立,才算 `SRP runtime v1` 收口: - -1. 运行时存在真实的 managed bridge,而不是测试 mock -2. native 能创建并持有 managed asset 与 managed pipeline 实例 -3. managed pipeline 能稳定参与 camera frame graph 录制 -4. managed forward pipeline 能替代 builtin forward 跑通主场景 -5. builtin forward 仍保留 native fallback 路径 -6. editor / runtime 渲染不会因为切到 managed pipeline 而失稳 - -## 8. 下一步从哪里下刀 - -下一步不要先碰 `URP`,也不要先改渲染效果组织。 - -第一刀就应该是: - -`MonoScriptRuntime -> 通用 managed object runtime -> 真实 ManagedRenderPipelineBridge` - -原因很简单: - -当前最根的缺口不是某个 pass,不是某个 renderer feature,也不是 shader。 - -而是: - -`managed pipeline 在运行时根本还不存在` - -这个根因不先解决,后面所有“SRP API”“URP 包”“阴影搬迁”都会变成空中楼阁。 diff --git a/docs/used/SRP_ManagedAsset原生后端选择收口计划_完成归档_2026-04-19.md b/docs/used/SRP_ManagedAsset原生后端选择收口计划_完成归档_2026-04-19.md deleted file mode 100644 index 81620d81..00000000 --- a/docs/used/SRP_ManagedAsset原生后端选择收口计划_完成归档_2026-04-19.md +++ /dev/null @@ -1,144 +0,0 @@ -# SRP ManagedAsset 原生后端选择收口计划 2026-04-19 - -## 1. 阶段目标 - -上一阶段已经把 `Universal` 包里的录制组合边界收紧了,但 native 侧还有一个关键 ownership 没有收干净: - -`ManagedScriptableRenderPipelineAsset` --> `ScriptableRenderPipelineHostAsset` --> 默认 `BuiltinForwardPipelineAsset` - -这条链目前是硬编码的。也就是说,只要是 managed pipeline asset,native 执行后端就默认落到同一个 `BuiltinForwardPipeline`。 - -这在当前阶段能工作,但它会直接卡住后续两件事: - -1. first-party `Universal` 包对 native backend 的正式接管 -2. 未来不同 managed pipeline 对不同 native backend 的选择语义 - -本阶段不新增渲染特性,只把这条“managed asset 如何选择 native backend”的 seam 正式化。 - ---- - -## 2. 当前问题 - -### 2.1 `ManagedScriptableRenderPipelineAsset` 现在把执行后端写死了 - -当前 `ManagedScriptableRenderPipelineAsset` 内部持有固定的: - -1. `ScriptableRenderPipelineHostAsset m_executionHostAsset` - -而这个 host asset 默认再去创建: - -1. `BuiltinForwardPipelineAsset` -2. `BuiltinForwardPipeline` - -这意味着 managed runtime 当前只能决定: - -1. stage recorder -2. camera request policy -3. final color defaults - -但**不能决定它自己要挂在哪个 native renderer backend 上**。 - -### 2.2 Mono stage recorder 也在默认依赖 builtin native scene renderer - -当前 `MonoManagedRenderPipelineStageRecorder` 在 scene recording fallback 里,也会默认拿: - -1. `CreateDefaultNativeSceneRenderer()` - -这说明“managed pipeline 默认使用 builtin native scene renderer”已经不是单点实现,而是正在成为一条隐式固定约束。 - -### 2.3 这不符合后续 SRP/URP 的 ownership 方向 - -我们现在要的不是立刻把所有场景绘制搬上 C#,而是先把 ownership 变成清晰的: - -`Managed pipeline asset runtime` --> 选择 native backend asset --> `ScriptableRenderPipelineHost` -负责组合 backend + recorder - -而不是: - -`Managed pipeline asset` --> 固定 builtin forward --> 后续再到处拆硬编码 - ---- - -## 3. 本阶段方案 - -本阶段采用的方向: - -1. 在 `ManagedRenderPipelineAssetRuntime` 上增加“可选提供 native pipeline renderer asset”的 seam -2. 让 `ManagedScriptableRenderPipelineAsset` 在创建 host 和取默认 final color 时,优先使用 runtime 提供的 renderer asset -3. 如果 runtime 没提供,则保持现有 fallback,不改当前行为 -4. 先把 seam 在 native 层立住,不在这一阶段新增新的 managed C# 公共 API - -这样做的目的很明确: - -1. 先去掉 managed asset 对 builtin forward 的硬编码依赖 -2. 保持现有行为不回退 -3. 为后续 first-party `Universal` 显式选择 backend 铺路 - ---- - -## 4. 实施步骤 - -### Step 1:扩展 managed runtime contract - -目标: - -1. 在 `ManagedRenderPipelineAssetRuntime` 增加可选的 renderer asset 提供接口 -2. 保持默认实现返回空,不影响现有 bridge - -### Step 2:重构 `ManagedScriptableRenderPipelineAsset` 的执行 host 组合方式 - -目标: - -1. 不再把默认 execution host asset 固定成成员常量 -2. 改为按 runtime 解析结果构建 `ScriptableRenderPipelineHostAsset` -3. `CreatePipeline` 与 `GetDefaultFinalColorSettings` 走同一条 backend asset 解析逻辑 - -### Step 3:补测试,锁死新 seam - -目标: - -1. 新增 rendering unit test -2. 验证 runtime 提供 renderer asset 时,host 确实使用该 asset -3. 验证 runtime 未提供时,仍然走现有 fallback -4. 验证默认 final color fallback 会从 runtime 提供的 renderer asset 读取 - -### Step 4:完整验证 - -目标: - -1. 编译 `XCEditor` -2. 运行相关 `rendering_unit_tests` -3. 运行相关 `scripting_tests` -4. 运行相关 `editor_tests` -5. 旧版 `editor/bin/Debug/XCEngine.exe` 10s 冒烟 - ---- - -## 5. 验收标准 - -本阶段完成后应满足: - -1. `ManagedScriptableRenderPipelineAsset` 不再把 builtin forward 当成唯一固定 execution backend -2. managed runtime 可以可选提供 native renderer asset -3. 未提供 runtime renderer asset 时,现有行为不回退 -4. 相关 tests、编译、冒烟全部通过 - ---- - -## 6. 本阶段不做的内容 - -这一阶段明确不做: - -1. 不新增新的 C# public SRP API -2. 不直接把阴影、高斯、体积、后处理 ownership 迁到 managed -3. 不做 deferred -4. 不做 lightmap / baking -5. 不做 editor renderer asset 工作流扩展 - -这一刀只收 `ManagedScriptableRenderPipelineAsset -> native backend asset` 的组合边界。 diff --git a/docs/used/SRP_ManagedCameraFramePolicyKeyRemovalPlan_完成归档_2026-04-21.md b/docs/used/SRP_ManagedCameraFramePolicyKeyRemovalPlan_完成归档_2026-04-21.md deleted file mode 100644 index 0d34d142..00000000 --- a/docs/used/SRP_ManagedCameraFramePolicyKeyRemovalPlan_完成归档_2026-04-21.md +++ /dev/null @@ -1,55 +0,0 @@ -# SRP Managed Camera Frame Policy Key Removal Plan 2026-04-21 - -## Goal - -Remove the now-redundant managed camera-frame plan policy-key bridge from the generic SRP asset path so managed pipelines only integrate with native planning through explicit capability signals and direct managed frame planning hooks. - -## Why This Stage - -The previous stage already changed renderer-backed managed pipelines to request native camera-frame baseline through `UsesNativeCameraFramePlanBaseline*`. After that change, the old `GetCameraFramePlanPolicyAssetKey*` path became dead architectural baggage: - -1. managed `ScriptableRenderPipelineAsset` still exposes a string-based camera-frame plan policy hook; -2. native Mono runtime still resolves and calls that hook; -3. native managed asset host still keeps a fallback branch for it; -4. current managed URP no longer overrides or needs it. - -Keeping that bridge in the core SRP asset surface weakens ownership boundaries and encourages native policy selection to leak back into managed URP authoring. - -## Scope - -Included: - -1. remove camera-frame policy-key methods from managed `ScriptableRenderPipelineAsset`; -2. remove the corresponding runtime virtuals and Mono bridge resolution/invocation code; -3. simplify `ManagedScriptableRenderPipelineAsset::ConfigureCameraFramePlan` so it only does native baseline application when explicitly requested, then hands control to managed planning; -4. rebuild `XCEditor` and run old editor smoke. - -Not included: - -1. deleting native camera-frame policy-key support used by non-managed pipelines; -2. changing standalone pass / render-scene / shadow policy-key surfaces; -3. SRP renderer selection redesign; -4. deferred renderer work. - -## Acceptance - -This stage is complete when: - -1. generic managed SRP asset API no longer exposes `GetCameraFramePlanPolicyAssetKey*`; -2. native managed runtime bridge no longer resolves or invokes camera-frame policy-key methods; -3. managed camera-frame planning host path only keeps native baseline capability plus managed `ConfigureCameraFramePlan(...)`; -4. `XCEditor` builds successfully; -5. old editor smoke passes and `editor.log` contains a fresh `SceneReady`. - -## Validation - -Validated on 2026-04-21: - -1. built old editor target with `cmake --build . --config Debug --target XCEditor` from `build/`; -2. launched `editor/bin/Debug/XCEngine.exe`; -3. let it run for about 12 seconds; -4. verified a fresh `SceneReady` entry in `editor/bin/Debug/editor.log`. - -Observed log line: - -`[2026-04-21 13:54:49] [INFO] [General] [SceneLoadTrace] SceneReady elapsed_ms=5610 first_frame_ms=607 peak_pending_async=9` diff --git a/docs/used/SRP_ManagedFramePlan接缝与Universal规划层计划_2026-04-20.md b/docs/used/SRP_ManagedFramePlan接缝与Universal规划层计划_2026-04-20.md deleted file mode 100644 index 4986c74e..00000000 --- a/docs/used/SRP_ManagedFramePlan接缝与Universal规划层计划_2026-04-20.md +++ /dev/null @@ -1,165 +0,0 @@ -# SRP Managed Frame Plan 接缝与 Universal 规划层计划 2026-04-20 - -## 1. 阶段目标 - -当前 SRP 主线已经有了两条 managed 接缝: - -1. `ConfigureCameraRenderRequest` -2. `RecordStageRenderGraph` - -但真正夹在中间、决定“一帧到底要不要有 post process / final output,以及这些 stage 怎么串”的 -`CameraFramePlan` 仍然完全停留在 native 默认策略里。 - -这一阶段的目标,就是把这条缺口补上: - -1. 给 managed SRP Core 增加正式的 frame-plan planning context -2. 让 `ScriptableRenderPipelineAsset` 能参与 `CameraFramePlan` 配置 -3. 让 Universal 的 `ScriptableRendererData / ScriptableRendererFeature` 能继续把这层规划往下分发 -4. 保持 Render Graph 仍然留在 C++,只把“规划策略”暴露给 C# - ---- - -## 2. 当前问题 - -### 2.1 managed 现在只能改 request,不能改 frame plan - -现在 managed 资产可以在 request 阶段做: - -1. 清理 directional shadow -2. 选择 native backend key -3. 提供 final color 默认值 - -但它还不能做: - -1. 显式请求 post-process stage -2. 显式请求 final-output stage -3. 决定 stage color source -4. 决定 graph-managed output color 的串联关系 - -这导致 managed 管线如果想表达“我需要这两个阶段”,目前只能绕到 -`SupportsStageRenderGraph` 这条被动推断路径。 - -### 2.2 Universal 还没有正式的“规划层”下发通道 - -当前 Universal 已经有: - -`Asset -> RendererData -> Renderer -> Feature -> Pass` - -但只有: - -1. request 配置通道 -2. recording 通道 - -缺少: - -1. plan 配置通道 - -所以 `RendererData` 和 `RendererFeature` 还没法正式参与 stage planning。 - -### 2.3 现在的 fullscreen stage 主要靠 native heuristic 推断 - -当前 `CameraFramePlanBuilder` 里还有一层: - -`ConfigureFullscreenStagesFromPipeline(...)` - -它会根据 pipeline 对 stage 的支持情况补出 fullscreen stage。 - -这条 heuristic 对当前主线有用,但它不是未来 SRP/URP 的最终 authoring 方式。 -真正成熟的方向应该是: - -1. native 负责执行与兜底 -2. managed asset / renderer data / feature 负责更明确的规划策略 - ---- - -## 3. 本阶段方案 - -### 3.1 Core:新增 planning context - -在 managed core 增加: - -1. `ScriptableRenderPipelinePlanningContext` -2. `ScriptableRenderPipelineAsset.ConfigureCameraFramePlan(...)` - -planning context 先只暴露这阶段真正需要的最小能力: - -1. 查询 stage 是否已请求 -2. 查询 stage color source -3. 查询 stage 是否使用 graph-managed output color -4. 请求 fullscreen stage -5. 清理 fullscreen stage - -### 3.2 Native bridge:把 plan 接缝接进 Mono runtime - -在 native/Mono bridge 增加: - -1. planning context state registry -2. managed planning context object 构造 -3. `ManagedRenderPipelineAssetRuntime::ConfigureCameraFramePlan(...)` -4. 对应 internal call - -### 3.3 Universal:补上 renderer data / feature 规划层 - -给 Universal 包增加: - -1. `ScriptableRendererData.ConfigureCameraFramePlanInstance(...)` -2. `ScriptableRendererFeature.ConfigureCameraFramePlan(...)` -3. `UniversalRenderPipelineAsset` 把 planning context 下发到默认 renderer data - -这样 Universal 的 request / plan / record 三层链路就对齐了。 - ---- - -## 4. 实施步骤 - -### Step 1:补 managed core planning API - -目标: - -1. 新增 `ScriptableRenderPipelinePlanningContext` -2. 新增 `ScriptableRenderPipelineAsset.ConfigureCameraFramePlan` -3. 补 internal call 声明 - -### Step 2:补 native planning bridge - -目标: - -1. 扩展 `ManagedRenderPipelineAssetRuntime` -2. `ManagedScriptableRenderPipelineAsset` 调用 managed `ConfigureCameraFramePlan` -3. Mono runtime 能创建并驱动 managed planning context - -### Step 3:把 Universal 接到 planning seam - -目标: - -1. `ScriptableRendererData` 下发 plan 配置 -2. `ScriptableRendererFeature` 能参与 plan 配置 -3. `UniversalRenderPipelineAsset` 使用默认 renderer data 承担这层逻辑 - -### Step 4:补 probe 与回归测试 - -目标: - -1. 更新 API surface probe -2. 新增/更新 managed probe asset,验证 managed plan hook 真正生效 -3. 锁住 Universal feature 的 planning seam - -### Step 5:完整验证与收口 - -目标: - -1. 编译 `rendering_unit_tests`、`scripting_tests`、`XCEditor` -2. 运行相关测试 -3. 运行旧版 `editor/bin/Debug/XCEngine.exe` 至少 10 秒并检查新的 `SceneReady` - ---- - -## 5. 验收标准 - -完成后应满足: - -1. managed `ScriptableRenderPipelineAsset` 可以正式参与 `CameraFramePlan` 配置 -2. `UniversalRenderPipelineAsset -> ScriptableRendererData -> ScriptableRendererFeature` 有完整的 planning 分发链 -3. fullscreen stage 不再只能依赖“pipeline support heuristic”这一条隐式路径 -4. API surface probe 与脚本回归测试锁住新的 public / protected seam -5. 编译、测试、旧版 editor 冒烟全部通过 diff --git a/docs/used/SRP_ManagedFullscreen旧桥清理与Builder统一计划_阶段归档_2026-04-20.md b/docs/used/SRP_ManagedFullscreen旧桥清理与Builder统一计划_阶段归档_2026-04-20.md deleted file mode 100644 index 6d5d5c8e..00000000 --- a/docs/used/SRP_ManagedFullscreen旧桥清理与Builder统一计划_阶段归档_2026-04-20.md +++ /dev/null @@ -1,167 +0,0 @@ -# SRP Managed Fullscreen 旧桥清理与 Builder 统一计划 2026-04-20 - -## 1. 阶段目标 - -上一阶段已经完成两件关键工作: - -1. `ScriptCore` 与 `XCEngine.RenderPipelines.Universal` 的包边界拆开 -2. managed 侧最小 `RenderGraphRasterPassBuilder` 打通,并且首个 Universal feature 已经开始走 builder - -当前还剩最后一条明显的旧接缝没有收干净: - -1. `ScriptableRenderPass` 里无 handle 的 fullscreen 录制重载仍然在走旧的 `RecordFullscreenPass` internal call -2. C# probe 仍然能间接命中这条旧路径 -3. native 侧仍然保留了专门给这条旧路径服务的 internal call 注册和实现 - -这一阶段的目标很明确: - -1. Universal fullscreen pass 录制统一走 managed raster builder -2. 删除 `Rendering_ScriptableRenderContext_RecordFullscreenPass` 这条废弃桥 -3. 保持现有 `editor` 主链可编译、可启动、可冒烟 - ---- - -## 2. 当前问题 - -### 2.1 `ScriptableRenderPass` 仍然保留双轨制 - -当前 `ScriptableRenderPass` 已经有基于: - -1. `RenderGraphTextureHandle` -2. `RenderGraphRasterPassBuilder` - -的正式录制接口。 - -但无 handle 的两个重载: - -1. `RecordColorScaleFullscreenPass(context, Vector4)` -2. `RecordShaderVectorFullscreenPass(context, string, Vector4, string)` - -仍然通过旧 internal call 直接把“fullscreen pass 描述”扔回 native。 - -这会导致: - -1. Universal pass API 表面上已经 graph 化,内部却仍然夹着旧录制桥 -2. probe 和样例代码继续依赖旧路径,阻碍后续 SRP API 收口 -3. native 侧保留多余桥接逻辑,增加维护成本 - -### 2.2 这条旧桥已经不再合理 - -现在 `ScriptableRenderContext` 已经直接暴露: - -1. `sourceColorTexture` -2. `primaryColorTarget` -3. `AddRasterPass(...)` - -也就是说,fullscreen pass 已经具备从 managed 侧完整声明: - -1. 输入纹理 -2. 输出 attachment -3. 执行类型 - -的最小能力。继续保留旧桥,只会让 SRP host 看起来“像 Unity”,但实现上仍然是半旧半新。 - ---- - -## 3. 本阶段范围 - -本阶段只做下面四件事: - -1. 归档上一阶段 `ManagedRenderGraph v1` 计划 -2. 新增本阶段计划并开始执行 -3. 清理 Universal fullscreen 旧录制桥 -4. 重编 `editor` 并完成至少 10 秒冒烟验证 - -本阶段明确不做: - -1. 新的 SRP API 扩展 -2. compute pass 接口扩展 -3. renderer feature 编辑器适配 -4. shadow / volume / deferred 内容迁移 - ---- - -## 4. 实施步骤 - -### Step 1:统一 `ScriptableRenderPass` fullscreen 录制入口 - -目标: - -让无 handle 的 fullscreen 重载内部自动解析: - -1. `context.sourceColorTexture` -2. `context.primaryColorTarget` - -然后直接复用现有 builder 版本的正式录制路径。 - -完成标准: - -1. `ScriptableRenderPass` 不再出现 `RecordedFullscreenPassType` -2. `RecordFullscreenPassInternal(...)` 被删除 -3. fullscreen pass 的所有 managed 入口都统一汇入 builder - -### Step 2:移除废弃 internal call - -目标: - -删除已经不再需要的: - -1. `InternalCalls.Rendering_ScriptableRenderContext_RecordFullscreenPass` -2. native 对应实现 -3. native 对应注册 - -完成标准: - -1. 全仓搜索不再出现 `Rendering_ScriptableRenderContext_RecordFullscreenPass` -2. managed/native 不再保留 fullscreen 旧桥专用代码 - -### Step 3:校正 probe 与调用面 - -目标: - -确认现有 probe 通过无 handle 重载时,已经自动命中 builder 路径,而不是继续依赖旧桥。 - -完成标准: - -1. probe 不需要特殊兼容逻辑 -2. API surface 仍然满足现有验证脚本使用方式 - -### Step 4:编译与冒烟 - -固定验证: - -1. 编译旧版 `editor` -2. 启动 `editor/bin/Debug/XCEngine.exe` -3. 冒烟运行至少 10 秒 -4. 检查 `editor/bin/Debug/editor.log` 中新的 `SceneReady` - ---- - -## 5. 验收标准 - -这一阶段收口后应满足: - -1. Universal fullscreen pass 录制完全统一到 managed builder -2. managed 到 native 的 fullscreen 旧桥彻底删除 -3. 现有 probe / Universal feature 不需要回退到旧 API -4. `editor` 编译通过,旧 editor 冒烟通过 - ---- - -## 6. 阶段意义 - -这一步做完以后,SRP 主线会从: - -`已有 RenderGraph builder,但 fullscreen 仍残留旧录制桥` - -推进到: - -`Universal 已经开始真正以 managed graph producer 的方式录制 pass` - -这样下一阶段继续扩: - -1. 更多 Universal 官方式 pass -2. 更清晰的 renderer feature authoring -3. 更接近 Unity 风格的 SRP/URP API 面 - -时,就不需要再回头清理历史接缝。 diff --git a/docs/used/SRP_ManagedPipeline实例归属统一计划_完成归档_2026-04-20.md b/docs/used/SRP_ManagedPipeline实例归属统一计划_完成归档_2026-04-20.md deleted file mode 100644 index bfeb9b6c..00000000 --- a/docs/used/SRP_ManagedPipeline实例归属统一计划_完成归档_2026-04-20.md +++ /dev/null @@ -1,137 +0,0 @@ -# SRP ManagedPipeline实例归属统一计划 2026-04-20 - -## 1. 阶段目标 - -当前 Universal/managed SRP 主线里还有一个关键 ownership 没收干净: - -1. `ManagedRenderPipelineAssetRuntime` 表示的是“一个 active managed pipeline asset” -2. 但真正的 `ScriptableRenderPipeline` managed instance 仍然是每个 stage recorder 自己去创建 -3. 这不符合 Unity 风格的 `RenderPipelineAsset -> one active RenderPipeline instance` - -本阶段目标就是把这个 ownership 统一回来: - -`MonoManagedRenderPipelineAssetRuntime` - -负责缓存并持有 managed pipeline instance, - -`MonoManagedRenderPipelineStageRecorder` - -只借用,不再各自创建和销毁。 - ---- - -## 2. 当前问题 - -### 2.1 pipeline instance 被 recorder 私有化了 - -现在 recorder 内部自己维护: - -1. `m_pipelineHandle` -2. `m_pipelineCreationAttempted` -3. `ReleaseManagedObjects()` 时销毁 pipeline object - -这意味着: - -1. 同一个 asset runtime 可以派生多个 managed pipeline instance -2. `CreatePipeline()` 调用次数取决于 recorder 数量,而不是 asset 生命周期 -3. pipeline lifecycle 没有真正挂在 asset/runtime 上 - -### 2.2 Unity 风格的 active pipeline 语义还没成立 - -Unity 的思路更接近: - -1. `RenderPipelineAsset` 决定并创建 pipeline -2. active pipeline 生命周期挂在 asset/runtime 这一侧 -3. 录制器、上下文、渲染阶段都围着这一个 pipeline 工作 - -我们现在虽然有 `ManagedRenderPipelineAssetRuntime`,但它还没真正拥有 pipeline instance。 - -### 2.3 当前 create count 还可能被 recorder 数量放大 - -前几步已经收了: - -1. backend ownership -2. renderer data ownership -3. renderer instance cache - -如果 pipeline instance ownership 不收,后面依然会留下: - -`asset -> runtime -> recorder -> pipeline` - -这条倒挂关系。 - ---- - -## 3. 本阶段方案 - -### 方案核心 - -把 managed pipeline instance cache 从: - -`MonoManagedRenderPipelineStageRecorder` - -上移到: - -`MonoManagedRenderPipelineAssetRuntime` - -形成: - -1. runtime 负责确保 pipeline instance 存在 -2. runtime 负责释放 pipeline instance -3. recorder 只查询/借用 runtime 持有的 pipeline object - -### 预期结构 - -1. `MonoManagedRenderPipelineAssetRuntime` - - 持有 shared managed pipeline handle - - 负责首次创建和最终释放 -2. `MonoManagedRenderPipelineStageRecorder` - - 不再拥有/销毁 pipeline handle - - 只缓存 method 指针和录制期状态 - ---- - -## 4. 实施步骤 - -### Step 1:把 pipeline handle/cache 上移到 asset runtime - -目标: - -1. runtime 增加 shared pipeline handle/cache -2. `CreateManagedPipeline()` 改成 shared semantics -3. `ReleaseManagedAsset()` 一并释放 shared pipeline - -### Step 2:清理 recorder 自己的 pipeline ownership - -目标: - -1. 移除 recorder 的 pipeline handle owner 角色 -2. `Shutdown()` 不再销毁 shared pipeline -3. `SupportsStageRenderGraph()` / `RecordStageRenderGraph()` 通过 runtime 借用 pipeline object - -### Step 3:补回归测试 - -目标: - -1. 同一个 managed asset runtime 创建多个 recorder 时,`CreatePipeline()` 只调用一次 -2. 原有 main scene / post process / fallback 语义不回归 - -### Step 4:验证与收口 - -目标: - -1. 编译 `rendering_unit_tests`、`scripting_tests`、`XCEditor` -2. 运行相关测试 -3. 运行旧版 `XCEditor` 10 秒冒烟并验证新 `SceneReady` -4. 归档 plan、提交、推送 - ---- - -## 5. 验收标准 - -本阶段完成后应满足: - -1. managed pipeline instance ownership 归位到 asset runtime -2. recorder 不再各自创建/销毁 pipeline object -3. 同 asset runtime 多 recorder 不会放大 `CreatePipeline()` 调用次数 -4. 当前 SRP 结构更接近 Unity 的 active pipeline 生命周期模型 diff --git a/docs/used/SRP_ManagedSceneDrawAPI计划_完成归档_2026-04-21.md b/docs/used/SRP_ManagedSceneDrawAPI计划_完成归档_2026-04-21.md deleted file mode 100644 index d8c47732..00000000 --- a/docs/used/SRP_ManagedSceneDrawAPI计划_完成归档_2026-04-21.md +++ /dev/null @@ -1,127 +0,0 @@ -# SRP Managed Scene Draw API 计划 2026-04-20 - -## 1. 阶段目标 - -当前 SRP 主线已经完成: -1. `ScriptableRenderContext` 拥有 scene phase / injection 录制入口 -2. Universal 默认 main-scene builtin passes 回到 `UniversalRenderer` 自己管理 -3. managed fullscreen authoring 已经 builder 化 - -但现在的 main-scene draw 仍然是黑盒: -1. managed 侧只能 `RecordScenePhase(SceneRenderPhase.*)` -2. URP 包里的 `DrawObjectsPass` 还不是真正的 draw-objects authoring -3. 用户无法像 Unity SRP 一样控制 renderer list / filtering / sorting - -这一阶段的目标是: -1. 把 native 已有的 scene draw primitives 正式抬到 managed API -2. 让 URP 包后续可以从“phase 黑盒”走向真正的 `DrawObjectsPass` -3. 为未来 C# 自定义渲染管线打下第一批 scene draw authoring 基元 - -## 2. 现状分析 - -### 2.1 native 侧已经有 draw 原语,但 managed 侧拿不到 - -当前 native 已存在: -1. `RendererListType` -2. `FilteringSettings` -3. `SortingSettings` -4. `RendererListDesc` -5. `DrawSettings` - -而 builtin forward 也已经按这些原语工作: -1. opaque -> `RendererListType::Opaque` -2. transparent -> `RendererListType::Transparent` -3. 最终由 `VisitRendererListVisibleItems(...)` 完成可见物体遍历 - -所以问题不是底层没有能力,而是: -1. 这些能力仍被 builtin forward 内部私有化 -2. managed SRP/URP 还只能调用 phase 黑盒 - -### 2.2 当前 `SceneRenderPhase` 只能做“整段默认绘制” - -这对于现阶段收口很有价值,但它的上限明显不够: -1. 无法表达只绘制 opaque / transparent 的一个子集 -2. 无法表达自定义 filtering / sorting -3. 无法自然演进到 Unity 风格 `DrawObjectsPass` - -## 3. 本阶段范围 - -本阶段优先做: -1. 梳理 native scene draw 原语与 builtin forward 的耦合点 -2. 确定第一批暴露到 managed 的最小 API 集 -3. 先打通最小可用 scene draw 调用链 -4. 用它替换 Universal 默认对象绘制 pass 的黑盒 phase 依赖 -5. `XCEditor` 重编译与旧 editor 冒烟 - -本阶段暂不做: -1. 全量照搬 Unity 所有 drawing/filtering 状态 -2. layer mask / render queue range / shader tag id 的完整体系 -3. renderer list 缓存优化 -4. deferred renderer - -## 4. 建议切口 - -### Step 1:先暴露最小可用 scene draw 描述 - -建议先暴露: -1. `RendererListType` -2. `RendererSortMode` -3. `FilteringSettings` -4. `SortingSettings` -5. `RendererListDesc` - -原因: -1. 这些类型 native 已经存在 -2. 数据结构简单,不需要先把整套 Unity 复杂状态一次性搬完 -3. 足够支撑第一个 managed `DrawObjectsPass` - -### Step 2:在 `ScriptableRenderContext` 增加显式 scene draw 入口 - -不要继续扩展 `RecordScenePhase(...)` 黑盒。 -建议新增类似: -1. `CreateRendererList(...)` -2. `DrawRendererList(...)` - -或者第一阶段更务实一点: -1. `DrawRenderers(RendererListDesc desc, SceneRenderPhase phase)` - -原则: -1. API 命名要朝 Unity 靠拢 -2. 但实现可以先走引擎现有 `RendererListType` 体系 - -### Step 3:让 Universal 默认对象 pass 开始消费新 API - -目标不是一次性推翻全部逻辑,而是先替换: -1. `UniversalDrawObjectsPass(opaque)` -2. `UniversalDrawObjectsPass(transparent)` - -让它们不再直接 `context.RecordScenePhase(...)` -而是基于新的 scene draw API 录制对象绘制。 - -Skybox 可以后续单独处理,因为它不是 renderer list 驱动的对象遍历。 - -## 5. 验收标准 - -这一阶段收口后应满足: -1. managed 侧首次拥有正式的 scene draw primitives -2. Universal 的对象绘制 pass 不再完全依赖 phase 黑盒 -3. 后续继续实现真正 Unity 风格 `DrawObjectsPass` 时,不需要再推翻这一层 -4. `XCEditor` 编译通过 -5. 旧 editor 冒烟通过 - -## 6. 阶段意义 - -这一步完成后,SRP 主线会从: - -`managed 可以组织主场景顺序,但对象绘制仍是 native 黑盒` - -推进到: - -`managed 开始拥有可组合的 scene draw primitives` - -这才是后面继续做: -1. Unity 风格 `DrawObjectsPass` -2. renderer feature 自定义对象绘制 -3. 更细的 filtering / sorting / override 行为 - -的真正起点。 diff --git a/docs/used/SRP_Managed生命周期清理接缝计划_完成归档_2026-04-20.md b/docs/used/SRP_Managed生命周期清理接缝计划_完成归档_2026-04-20.md deleted file mode 100644 index 78e1afa5..00000000 --- a/docs/used/SRP_Managed生命周期清理接缝计划_完成归档_2026-04-20.md +++ /dev/null @@ -1,132 +0,0 @@ -# SRP Managed生命周期清理接缝计划 2026-04-20 - -## 1. 阶段目标 - -当前 managed SRP/URP 主线里,`RenderPipelineAsset -> active pipeline instance` 的 ownership 已经成立,但生命周期还缺最后一环: - -1. managed `ScriptableRenderPipeline` 没有正式的 dispose/cleanup 接缝 -2. `ScriptableRenderer` / `ScriptableRendererFeature` 也没有 cleanup 语义 -3. `ScriptableRendererData` 虽然缓存了 renderer instance,但没有正式释放路径 -4. native 在释放 managed pipeline handle 时,只是销毁 GC handle,没有先走 managed cleanup - -这一阶段的目标,就是把这条生命周期链补齐,避免后续做资源化 renderer、更多 renderer feature、甚至真正的 SRP 资源对象时继续堆积隐式状态。 - ---- - -## 2. 当前问题 - -### 2.1 active pipeline 能建立,但不能正式销毁 - -现在 `MonoManagedRenderPipelineAssetRuntime` 会缓存并持有 shared managed pipeline instance。 - -但 release 时只是: - -1. native 侧直接销毁 external managed object handle -2. managed pipeline 本身没有机会执行 cleanup - -这意味着“创建”已经像 Unity 了,“释放”还没有。 - -### 2.2 renderer/feature 没有正式生命周期 - -现在 `ScriptableRenderer` 负责: - -1. 持有 feature 列表 -2. 构建 active pass queue -3. 执行 stage record - -但它没有: - -1. renderer cleanup hook -2. feature cleanup hook -3. renderer data 缓存失效/释放 hook - -后面一旦 renderer 或 feature 开始持有真正的资源对象,这里就会成为脏状态积累点。 - -### 2.3 Universal 现在缺少“pipeline dispose -> renderer cache release”这一跳 - -当前 `UniversalRenderPipelineAsset` 已经正式持有: - -1. renderer data 列表 -2. 默认 renderer 选择 - -`ScriptableRendererData` 也已经正式缓存 renderer instance。 - -那就意味着 pipeline 销毁时,应该有一条明确链路把对应 renderer instance cache 收掉;否则 asset/runtime 生命周期和 renderer cache 生命周期仍然不闭合。 - ---- - -## 3. 本阶段方案 - -### 方案核心 - -建立一条完整的 managed 生命周期链: - -1. native release managed pipeline 前,先调用 pipeline dispose -2. `ScriptableRenderPipeline` 提供正式 dispose seam -3. `UniversalRenderPipeline` 在 dispose 时回收 asset 上缓存的 renderer instance -4. `ScriptableRendererData` 提供释放 renderer cache 的内部入口 -5. `ScriptableRenderer` 负责释放 feature 和自身队列状态 -6. `ScriptableRendererFeature` 提供正式 dispose seam - -### 预期结果 - -阶段完成后,生命周期关系会变成: - -`ManagedAssetRuntime -> managed pipeline dispose -> pipeline asset cleanup -> renderer data release -> renderer dispose -> feature dispose` - ---- - -## 4. 实施步骤 - -### Step 1:补 managed core 生命周期 API - -目标: - -1. 给 `ScriptableRenderPipeline` 增加内部 dispose 入口和受保护 cleanup seam -2. 给 `ScriptableRenderer` 增加内部 dispose 入口和 feature release -3. 给 `ScriptableRendererFeature` 增加受保护 dispose seam -4. 给 `ScriptableRendererData` 增加 renderer cache release 入口 - -### Step 2:把 Universal 链接到 cleanup - -目标: - -1. 让 `ScriptableRenderPipelineAsset` 提供内部 pipeline-resource release seam -2. 让 `UniversalRenderPipelineAsset` override 回收 rendererDataList 中的 renderer caches -3. 让 `UniversalRenderPipeline` 在 dispose 时调用 asset cleanup - -### Step 3:native release 前调用 managed dispose - -目标: - -1. `MonoManagedRenderPipelineAssetRuntime` 在释放 shared pipeline handle 前先调用 managed pipeline dispose -2. 保持 runtime ownership 不回退、不重新散乱到 recorder - -### Step 4:补生命周期回归测试 - -目标: - -1. 验证 runtime release 时 managed pipeline dispose 被调用 -2. 验证 Universal renderer/feature cleanup 会沿 dispose 链执行 -3. 保持现有 SRP/URP 创建、复用、录制能力不回归 - -### Step 5:完整验证与收口 - -目标: - -1. 编译 `rendering_unit_tests`、`scripting_tests`、`XCEditor` -2. 运行测试 -3. 运行旧版 `XCEditor` 10 秒冒烟并验证新 `SceneReady` -4. 归档 plan,提交并推送 - ---- - -## 5. 验收标准 - -本阶段完成后应满足: - -1. managed pipeline release 前会先执行正式 dispose -2. `ScriptableRenderer` / `ScriptableRendererFeature` 不再只有创建,没有 cleanup -3. `ScriptableRendererData` 缓存的 renderer instance 有明确释放路径 -4. `UniversalRenderPipeline` 生命周期能闭合到 renderer cache -5. 现有 SRP/URP 主线测试与 editor 冒烟全部通过 diff --git a/docs/used/SRP_Mono原生后端选择统一收口计划_完成归档_2026-04-19.md b/docs/used/SRP_Mono原生后端选择统一收口计划_完成归档_2026-04-19.md deleted file mode 100644 index b45062a1..00000000 --- a/docs/used/SRP_Mono原生后端选择统一收口计划_完成归档_2026-04-19.md +++ /dev/null @@ -1,185 +0,0 @@ -# SRP Mono 原生后端选择统一收口计划 2026-04-19 - -## 1. 阶段目标 - -上一阶段已经把: - -`ManagedScriptableRenderPipelineAsset` --> `ManagedRenderPipelineAssetRuntime` --> `GetPipelineRendererAsset()` --> `ScriptableRenderPipelineHostAsset` - -这条 host 组合链路收出了一个正式 seam。 - -但 Mono 这条 managed SRP 执行链还没有完全跟上: - -`MonoManagedRenderPipelineStageRecorder` -在主场景录制时仍然会直接: - -`CreateDefaultNativeSceneRenderer()` - -这意味着当前仍然存在第二条隐藏 ownership: - -1. host 组合用的是 runtime 提供的 backend asset -2. scene recording fallback 却自己偷偷硬编码 builtin backend - -这会导致“managed pipeline 到底选择哪个 native backend”这件事没有真正收口。 - -本阶段目标就是把 Mono runtime 的 scene recording 也并到同一条 backend asset seam 上。 - ---- - -## 2. 当前问题 - -### 2.1 同一个 managed pipeline,host 和 stage recorder 现在可能不是同一后端语义 - -现在 `ManagedScriptableRenderPipelineAsset` 创建 host 时,会优先问: - -`ManagedRenderPipelineAssetRuntime::GetPipelineRendererAsset()` - -但 `MonoManagedRenderPipelineStageRecorder` 在主场景录制时,并没有复用这份 asset, -而是单独 new 一个默认 native scene renderer。 - -这在当前 builtin forward 只有一种 backend 时暂时不炸,但架构语义是错的: - -1. backend 选择被拆成了两处 -2. 后续 Universal / 自定义 SRP 想切 native backend 时会出现双写点 -3. 未来如果 host renderer 和 recorder fallback 不是同一种 renderer,行为会漂移 - -### 2.2 Mono runtime 还没有显式承担“我选择哪个 native backend asset”的责任 - -虽然基类已经有: - -`GetPipelineRendererAsset()` - -但 Mono runtime 当前并没有正式 override 这件事。 - -结果就是: - -1. 上一阶段刚建好的 seam 在 Mono 这里并没有真正落地 -2. 当前 builtin forward 仍然只是“默认兜底行为”,而不是“runtime 明确选择” - -### 2.3 缺少针对这条 seam 的 Mono 测试 - -当前 scripting tests 覆盖了: - -1. managed bridge 能创建 runtime -2. managed stage recorder 能录主场景和后处理 - -但还没有明确锁死: - -1. Mono runtime 会暴露一个确定的 pipeline renderer asset -2. Mono scene recording 会沿这份 asset 去创建 native scene renderer - ---- - -## 3. 本阶段方案 - -### 方案核心 - -把 Mono managed SRP 的 native backend ownership 统一成: - -`MonoManagedRenderPipelineAssetRuntime` --> 解析/提供 native pipeline renderer asset --> host 组合使用它 --> stage recorder 录 scene 也使用它 - -而不是继续保留: - -host 一条链 -recorder 一条链 - -### 本阶段具体做法 - -1. 在 native factory 层补一个“从 renderer asset 创建 native scene renderer”的帮助函数 -2. 让 `MonoManagedRenderPipelineAssetRuntime` 正式 override `GetPipelineRendererAsset()` -3. 让 `MonoManagedRenderPipelineStageRecorder` 不再直接 `CreateDefaultNativeSceneRenderer()` -4. 改为优先通过 runtime 提供的 renderer asset 创建 scene renderer -5. 如果 asset 不可用或不能产出 `NativeSceneRenderer`,再统一回默认 fallback - -### 当前阶段的明确边界 - -这一刀只做 ownership 统一,不做下面这些内容: - -1. 不新增新的 C# public SRP API -2. 不引入 renderer data 到 native asset 的公开序列化桥 -3. 不做 deferred -4. 不做 lightmap / baking -5. 不做 Universal 包功能扩张 - ---- - -## 4. 实施步骤 - -### Step 1:补 native scene renderer asset 工厂 seam - -目标: - -1. 新增一个从 `RenderPipelineAsset` 创建 `NativeSceneRenderer` 的统一入口 -2. 如果 asset 对应 pipeline 不是 native scene renderer,则安全 fallback -3. 避免 Mono runtime 自己重复写 asset->pipeline->native cast 逻辑 - -### Step 2:让 Mono runtime 显式提供 backend asset - -目标: - -1. `MonoManagedRenderPipelineAssetRuntime` override `GetPipelineRendererAsset()` -2. 当前阶段先显式返回 builtin forward asset -3. 把“Mono managed SRP 当前使用 builtin forward native backend”从隐式默认改成显式选择 - -### Step 3:改 Mono stage recorder 复用 runtime backend asset - -目标: - -1. 删除 recorder 内部对 `CreateDefaultNativeSceneRenderer()` 的直接硬编码依赖 -2. 改为通过 runtime backend asset 解析 native scene renderer -3. 保证 scene recording 与 host 组合落到同一 backend 语义 - -### Step 4:补测试并锁行为 - -目标: - -1. 增加 scripting test,验证 Mono runtime 能提供 renderer asset -2. 验证该 asset 能创建 native scene renderer -3. 保留主场景/后处理 stage recording 回归测试,确保行为不退 - -### Step 5:完整验证并收口 - -目标: - -1. 编译 `XCEditor` -2. 运行相关 `rendering_unit_tests` -3. 运行相关 `scripting_tests` -4. 旧版 `editor/bin/Debug/XCEngine.exe` 10s 冒烟 -5. 归档 plan -6. 按规范提交并推送 - ---- - -## 5. 验收标准 - -本阶段完成后应满足: - -1. Mono managed SRP 的 host backend 选择和 scene recording backend 选择走同一条 seam -2. `MonoManagedRenderPipelineAssetRuntime` 对 native backend asset 有显式选择责任 -3. `MonoManagedRenderPipelineStageRecorder` 不再直接硬编码默认 builtin scene renderer 创建 -4. 相关 tests、编译、冒烟全部通过 - ---- - -## 6. 对后续 SRP 主线的意义 - -这一步完成后,managed SRP 的 backend ownership 会进一步收成: - -`Managed asset runtime` --> backend asset --> host renderer --> scene recorder fallback - -这样下一阶段如果要继续向 Unity 风格推进: - -1. 给 Universal 包引入更明确的 backend asset 语义 -2. 拆分不同 renderer data 对不同 backend 的映射 -3. 再往 renderer feature / renderer data / asset workflow 扩 - -都不会再被 Mono recorder 里的隐式 builtin fallback 卡住。 diff --git a/docs/used/SRP_NativeBackendTerminologyCleanupPlan_2026-04-21_完成归档.md b/docs/used/SRP_NativeBackendTerminologyCleanupPlan_2026-04-21_完成归档.md deleted file mode 100644 index 44a69f24..00000000 --- a/docs/used/SRP_NativeBackendTerminologyCleanupPlan_2026-04-21_完成归档.md +++ /dev/null @@ -1,57 +0,0 @@ -# SRP Native Backend Terminology Cleanup Plan - -日期:2026-04-21 - -## 背景 - -上一阶段已经把 native scene draw substrate 从 `NativeSceneRenderer` 收口为 `SceneDrawBackend`,并把默认场景顺序从 builtin forward 私有实现中拆了出来。 - -但在 `ScriptableRenderPipelineHost`、`RenderPipeline`、`ManagedRenderPipelineAssetRuntime`、`MonoScriptRuntime` 这条链上,仍然大量使用: - -- `RenderPipelineRenderer` -- `pipelineRenderer` -- `pipelineRendererAsset` -- `GetPipelineRendererAsset` - -这和 Unity/URP 语境里的 `ScriptableRenderer` / `ScriptableRendererData` 高度冲突。 - -## 本阶段目标 - -把 C++ host/runtime bridge 里当前表示“native pipeline backend”的命名统一改成 backend 语义,避免再和 Unity 的 renderer 概念撞名: - -- `RenderPipelineRenderer` -> `RenderPipelineBackend` -- `pipelineRenderer` -> `pipelineBackend` -- `pipelineRendererAsset` -> `pipelineBackendAsset` -- `GetPipelineRendererAsset` -> `GetPipelineBackendAsset` - -## 范围 - -仅做 native backend terminology cleanup,不改渲染行为,不加新功能,不动 managed C# 的 `ScriptableRenderer` / `ScriptableRendererData` 设计。 - -主要涉及: - -- `engine/include/XCEngine/Rendering/RenderPipeline.h` -- `engine/include/XCEngine/Rendering/Pipelines/ScriptableRenderPipelineHost.h` -- `engine/include/XCEngine/Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.h` -- `engine/src/Rendering/Pipelines/ScriptableRenderPipelineHost.cpp` -- `engine/src/Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.cpp` -- `engine/src/Scripting/Mono/MonoScriptRuntime.cpp` -- `engine/src/Rendering/Execution/Internal/CameraFrameGraph/StageContract.*` - -## 为什么现在做 - -如果这里不先收口,后面继续按 Unity 的 SRP + URP 路线推进时会持续出现两层 `renderer`: - -1. C# `ScriptableRenderer` -2. C++ native backend - -这会让接口、文档、调试日志和后续 plan 全部变得容易误读。 - -## 验证标准 - -- `cmake --build . --config Debug --target XCEditor` 成功 -- 运行 `editor/bin/Debug/XCEngine.exe` -- 冒烟至少 10 秒 -- `editor/bin/Debug/editor.log` 出现新的 `SceneReady` -- 仅提交 SRP/rendering 主线相关文件与本计划文档 - diff --git a/docs/used/SRP_NativeSceneDrawSubstrateRefactorPlan_2026-04-21_完成归档.md b/docs/used/SRP_NativeSceneDrawSubstrateRefactorPlan_2026-04-21_完成归档.md deleted file mode 100644 index 423b248f..00000000 --- a/docs/used/SRP_NativeSceneDrawSubstrateRefactorPlan_2026-04-21_完成归档.md +++ /dev/null @@ -1,84 +0,0 @@ -# SRP Native Scene Draw Substrate Refactor Plan - -日期:2026-04-21 - -## 背景 - -当前 managed SRP/URP 侧已经基本使用 `ScriptableRenderContext.DrawRenderers(...)`、`RecordScenePhase(...)`、`RecordSceneInjectionPoint(...)` 这套抽象驱动场景绘制,方向是对的。 - -但 native 桥接层仍然把这套能力挂在 `NativeSceneRenderer` 上,接口命名和职责仍然带有明显的“内建 forward pipeline 执行器”语义: - -- `Initialize` -- `PrepareScene` -- `BeginScenePass` -- `EndScenePass` -- `ExecuteScenePhase` -- `ExecuteSceneDrawSettings` - -这会导致两个问题: - -1. managed SRP 表面上已经在驱动 renderer/passes,但底层依然像是在偷偷调用一个 builtin pipeline。 -2. 后续要继续按 Unity 的 SRP + URP 路线推进时,native 侧缺少一个稳定的“场景绘制 substrate”概念,URP 很容易继续耦合到 builtin forward 的实现细节。 - -## 本阶段目标 - -在不改动现有渲染行为的前提下,把 native scene recording 依赖的底层 contract 明确为“scene draw substrate”,而不是“scene renderer / hidden pipeline”: - -- 把接口命名调整为更贴近底层 scene draw substrate 的语义 -- 让 `NativeSceneRecorder` 明确依赖 scene draw substrate,而不是 pipeline 名义对象 -- 同步清理 `MonoScriptRuntime` 与 `RenderPipelineFactory` 上的调用链命名 -- 保持现有 managed SRP / URP 录制路径、RenderGraph 行为与 editor 运行结果不变 - -## 为什么现在做 - -现在继续往前堆 renderer、deferred、shadow 拆分都不是优先级最高的事。 - -如果 scene draw substrate 这一层不先收干净,后面做: - -- 多 renderer 支持 -- URP renderer data / renderer instance -- C# 自定义 scene pass -- 更完整的 RenderGraph ownership - -都会继续建立在一个命名混乱、职责暧昧的 native bridge 上,之后再返工代价更高。 - -## 重构范围 - -本阶段只处理 C++ native scene draw substrate 的 contract 收口,不扩功能,不改 editor,不碰 new_editor,不处理 deferred。 - -涉及方向: - -- `engine/include/XCEngine/Rendering/NativeSceneRenderer.h` -- `engine/include/XCEngine/Rendering/Pipelines/NativeSceneRecorder.h` -- `engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h` -- `engine/src/Rendering/Pipelines/NativeSceneRecorder.cpp` -- `engine/src/Rendering/Internal/RenderPipelineFactory.*` -- `engine/src/Scripting/Mono/MonoScriptRuntime.cpp` - -## 预期结果 - -重构后,native 侧应该表达成下面这个结构: - -- managed `ScriptableRenderer` 负责决定何时画什么 -- `ScriptableRenderContext` 负责把 managed 请求传到 native -- `NativeSceneRecorder` 负责把这些请求录进 RenderGraph -- native scene draw substrate 负责执行稳定的场景绘制原语 -- `BuiltinForwardPipeline` 只是当前默认的一种 native scene draw substrate 实现 - -## 实施步骤 - -1. 重命名并收窄 native scene draw contract,去掉 `NativeSceneRenderer` 的误导性语义。 -2. 清理 `NativeSceneRecorder` 内部调用与错误信息,使其围绕 scene draw substrate 组织。 -3. 清理 `MonoScriptRuntime` 中 scene renderer 的解析和持有逻辑命名。 -4. 清理 `RenderPipelineFactory` 中相关工厂命名,让默认 builtin forward 只表现为默认 substrate 实现。 -5. 编译 `XCEditor` 并运行旧版 editor 冒烟,确认 `SceneReady`。 -6. 归档本计划,提交并推送。 - -## 验证标准 - -- `cmake --build . --config Debug --target XCEditor` 成功 -- 运行 `editor/bin/Debug/XCEngine.exe` -- 冒烟至少 10 秒 -- `editor/bin/Debug/editor.log` 出现新的 `SceneReady` -- 本阶段仅包含 SRP/rendering 相关代码与本计划文档 - diff --git a/docs/used/SRP_NativeSeamVisibilityTighteningPlan_完成归档_2026-04-21.md b/docs/used/SRP_NativeSeamVisibilityTighteningPlan_完成归档_2026-04-21.md deleted file mode 100644 index cca0a0ad..00000000 --- a/docs/used/SRP_NativeSeamVisibilityTighteningPlan_完成归档_2026-04-21.md +++ /dev/null @@ -1,60 +0,0 @@ -# SRP Native Seam Visibility Tightening Plan 2026-04-21 - -## Goal - -Tighten the visibility of the remaining native-binding seams that are still exposed on public SRP/URP managed base types, while preserving the current first-party URP behavior. - -## Why This Stage - -The previous cleanup stages removed or narrowed most generic native key seams, but several native-only hooks are still exposed as normal overridable managed API: - -1. `UsesNativeCameraFramePlanBaseline*` on `ScriptableRenderPipelineAsset` -2. render-scene / standalone-pass / directional-shadow policy hooks on `RendererBackedRenderPipelineAsset` -3. renderer-data-level scene-setup / standalone-pass / directional-shadow execution hooks on `ScriptableRendererData` - -Current repo usage shows these hooks are already first-party only: - -1. `UsesNativeCameraFramePlanBaselineContextual` is only overridden by `RendererBackedRenderPipelineAsset` -2. renderer-backed asset-level native policy fallback is only overridden by `UniversalRenderPipelineAsset` -3. renderer-data-level native policy keys are only overridden by `UniversalRendererData` - -So they should stop looking like public customization points for external C# users. - -## Scope - -Included: - -1. tighten `UsesNativeCameraFramePlanBaseline*` visibility so it is no longer an external customization point; -2. tighten non-pipeline-key native hooks on `RendererBackedRenderPipelineAsset`; -3. tighten renderer-data-level non-pipeline-key native hooks on `ScriptableRendererData`; -4. keep method names and bridge behavior intact so native runtime lookup still works; -5. rebuild `XCEditor` and run old editor smoke. - -Not included: - -1. changing `GetPipelineRendererAssetKey*` visibility on renderer-backed / renderer-data types; -2. replacing native key resolution with a different binding mechanism; -3. Mono bridge method-name refactors; -4. deferred renderer work. - -## Acceptance - -This stage is complete when: - -1. external managed pipelines no longer see non-pipeline-key native seams as normal protected override points; -2. first-party URP types still override the required hooks inside the URP assembly; -3. `XCEditor` builds successfully; -4. old editor smoke passes and `editor.log` contains a fresh `SceneReady`. - -## Validation - -Validated on 2026-04-21: - -1. built old editor target with `cmake --build . --config Debug --target XCEditor` from `build/`; -2. launched `editor/bin/Debug/XCEngine.exe`; -3. let it run for about 12 seconds; -4. verified a fresh `SceneReady` entry in `editor/bin/Debug/editor.log`. - -Observed log line: - -`[2026-04-21 14:16:09] [INFO] [General] [SceneLoadTrace] SceneReady elapsed_ms=5470 first_frame_ms=656 peak_pending_async=9` diff --git a/docs/used/SRP_RenderStateBlock计划_完成归档_2026-04-21.md b/docs/used/SRP_RenderStateBlock计划_完成归档_2026-04-21.md deleted file mode 100644 index 5115c6d0..00000000 --- a/docs/used/SRP_RenderStateBlock计划_完成归档_2026-04-21.md +++ /dev/null @@ -1,144 +0,0 @@ -# SRP RenderStateBlock 计划 2026-04-20 - -## 1. 阶段目标 - -把当前 SRP scene draw authoring 的最后一块关键能力补齐: - -1. managed 侧可以像 Unity `RenderObjects` 一样为 scene draw 指定 `RenderStateBlock` -2. native scene draw 真正消费这些覆盖状态,而不是继续只能靠材质自身状态 -3. `RenderObjectsRendererFeature` 形成最小闭环: - - filtering / sorting - - override material - - shader pass - - render state override - -这一阶段收口后,当前这条 “scene draw primitive” 主线就基本完整,可以转去做更高层的 SRP/URP 组织。 - -## 2. 现状问题 - -当前已经有: - -1. `RendererListDesc` -2. `DrawingSettings` -3. `overrideMaterial` -4. `shaderPassName` -5. `DrawSkybox / DrawOpaqueRenderers / DrawTransparentRenderers` - -但还缺: - -1. `RenderStateBlock` -2. depth/stencil override 的 managed authoring 面 -3. native scene draw 对 override render state 的正式消费 - -结果就是: - -1. 现在能决定 “画谁” -2. 现在能决定 “用什么材质 / 什么 pass 画” -3. 但还不能决定 “以什么覆盖状态画” - -这会让很多 URP 本来应该在包层做的行为,继续被逼回材质或 C++ 特判。 - -## 3. 本阶段范围 - -本阶段只做最小可用的 Unity 风格 `RenderStateBlock`: - -1. `RenderStateMask` -2. `CompareFunction` -3. `StencilOp` -4. `DepthState` -5. `StencilFaceState` -6. `StencilState` -7. `RenderStateBlock` - -优先支持: - -1. depth write -2. depth compare function -3. stencil enable / read-write mask / reference -4. stencil front/back compare + op - -本阶段暂不做: - -1. blend state override -2. raster state override -3. color write mask override -4. 更高层 renderer preset / inspector authoring - -## 4. 实施步骤 - -### Step 1:补齐 managed API 面 - -新增 core rendering 类型: - -1. `CompareFunction` -2. `StencilOp` -3. `RenderStateMask` -4. `DepthState` -5. `StencilFaceState` -6. `StencilState` -7. `RenderStateBlock` - -并把它们挂进 `DrawingSettings`。 - -### Step 2:打通 Mono bridge - -让 `ScriptableRenderContext.DrawRenderers(...)` 可以把 `RenderStateBlock` 从 managed 传到 native。 - -要求: - -1. 结构体布局稳定 -2. 枚举值与 native 对齐 -3. 默认值不改现有行为 - -### Step 3:native scene draw 消费 override render state - -在 `BuiltinForwardPipeline` scene draw 路径里: - -1. 先解析材质 / shader pass 自身 render state -2. 再叠加 `RenderStateBlock` -3. pipeline key 与动态 stencil ref 都基于最终 resolved state - -这一步要保证: - -1. 不影响未设置 override 的现有行为 -2. stencil reference 仍然走动态状态,不污染 pipeline key - -### Step 4:接入 URP `RenderObjectsRendererFeature` - -让 URP 包层真的能 author 这套状态,而不是只有 API 没有 feature 消费方。 - -### Step 5:编译 / 冒烟 / 阶段提交 - -每个阶段完成后都执行: - -1. 编译 `XCEditor` -2. 运行旧版 `editor/bin/Debug/XCEngine.exe` 至少 10 秒 -3. 检查 `editor.log` -4. 规范提交并推送 - -## 5. 验收标准 - -本阶段收口后应满足: - -1. `DrawingSettings` 可以表达最小可用 `RenderStateBlock` -2. `RenderObjectsRendererFeature` 可以 author render state override -3. native scene draw 真正按 override state 绘制 -4. 默认场景绘制行为不回退 -5. `XCEditor` 编译通过 -6. 旧 editor 冒烟通过 - -## 6. 阶段完成后的意义 - -这一步完成后,当前 SRP 主线就从: - -`只能控制 draw list + material/pass` - -推进到: - -`已经具备最小完整的 scene draw authoring contract` - -这样下一阶段就可以更自然地进入: - -1. 更高层的 URP renderer data 组织 -2. shadow / post process / special effects 逐步包层化 -3. scene draw 与 render graph 的更正式结合 diff --git a/docs/used/SRP_RendererBackedBackendSeamClosurePlan_完成归档_2026-04-21.md b/docs/used/SRP_RendererBackedBackendSeamClosurePlan_完成归档_2026-04-21.md deleted file mode 100644 index 89767868..00000000 --- a/docs/used/SRP_RendererBackedBackendSeamClosurePlan_完成归档_2026-04-21.md +++ /dev/null @@ -1,46 +0,0 @@ -# SRP RendererBacked Backend Seam Closure Plan - -日期:2026-04-21 - -## 背景 - -当前 SRP 主线已经把大部分 native backend 选择接缝从 generic `ScriptableRenderPipelineAsset` 和公共 SRP API 中移走,剩余最后一个公开口子仍然存在于 `RendererBackedRenderPipelineAsset`: - -- `GetPipelineRendererAssetKeyContextual(int rendererIndex)` - -这个设计的问题很明确: - -1. 它暗示项目侧或 generic SRP 资产可以直接决定 native backend key。 -2. 这和 Unity 风格分层不一致。`SRP` 负责抽象与调度,首方管线包才负责首方实现细节。 -3. 当前生产 native registry 实际只注册了 `BuiltinForward`,项目/探针层继续暴露这个 override 只会制造错误扩展面。 - -## 本阶段目标 - -1. 关闭 `RendererBackedRenderPipelineAsset` 上最后一个公开 backend key customization seam。 -2. 让 renderer-backed backend key 回到资产内部固定解析,不再允许项目层 override。 -3. 把仍依赖这个 seam 的项目探针/脚本探针迁移到 `UniversalRenderPipelineAsset` / `UniversalRendererData` 归属下。 -4. 清掉不再符合主线方向的 probe-only backend override 代码。 - -## 具体执行 - -1. 修改 `RendererBackedRenderPipelineAsset` - - 将 backend key 解析实现收为资产内部固定逻辑。 - - 不再保留供外部资产 override 的公开/受保护接缝。 - -2. 调整探针与项目脚本 - - 让需要 builtin forward backend 的自定义 renderer data 归位到 `UniversalRendererData` 分支。 - - 让依赖 renderer-backed 但本质属于 URP 范式的 probe asset 继承 `UniversalRenderPipelineAsset`。 - - 删除仅用于模拟 asset-level backend key override 的旧 probe 代码。 - -3. 验证 - - `cmake --build . --config Debug --target XCEditor` - - 运行旧版 `editor/bin/Debug/XCEngine.exe` - - 冒烟至少 10 秒 - - 检查 `editor/bin/Debug/editor.log` 中新的 `SceneReady` - -## 完成标准 - -1. 项目层和 probe 层不再 override renderer-backed backend key。 -2. `RendererBackedRenderPipelineAsset` 不再暴露该扩展缝。 -3. 旧版 `XCEditor` Debug 编译通过。 -4. 冒烟日志出现新的 `SceneReady`。 diff --git a/docs/used/SRP_RendererBackend注册与多Key接缝计划_完成归档_2026-04-20.md b/docs/used/SRP_RendererBackend注册与多Key接缝计划_完成归档_2026-04-20.md deleted file mode 100644 index b61f97d2..00000000 --- a/docs/used/SRP_RendererBackend注册与多Key接缝计划_完成归档_2026-04-20.md +++ /dev/null @@ -1,119 +0,0 @@ -# SRP RendererBackend 注册与多 Key 接缝计划 2026-04-20 - -## 1. 阶段目标 - -上一阶段已经把: - -`ScriptableRenderPipelineAsset -> ManagedRenderPipelineAssetRuntime` - -这一层的失效与重建接缝补齐了。 - -当前新的主矛盾不在生命周期,而在 backend ownership: - -1. managed SRP/Universal 已经能声明 backend key -2. native factory 仍然基本停留在单 key 硬编码 -3. future SRP/URP 想挂更多 native backend 时,没有正式 registry - -这一阶段的目标,就是把: - -`managed backend key -> native renderer backend asset` - -从临时硬编码改成正式 registry seam。 - ---- - -## 2. 当前问题 - -### 2.1 native factory 仍是单点硬编码 - -当前 `CreatePipelineRendererAssetByKey()` 仍然是: - -1. 只认 `BuiltinForward` -2. 直接 `if` 分支返回 - -这意味着后续每多一个 backend,都要继续往 factory 里堆硬编码。 - -### 2.2 多 key 语义还没有真正被锁住 - -managed 侧虽然已经允许不同 asset / renderer data 返回不同 key, -但 native 侧并没有一套正式的“注册、查询、失败回退”模型。 - -### 2.3 现有测试只锁住单 key 与 unknown fallback - -当前已经锁住: - -1. `BuiltinForward` 能解析 -2. missing key 会 fallback - -但还没有锁住: - -1. 可以注册额外 key -2. managed asset 返回额外 key 之后,native runtime 仍能正常解析 backend asset - ---- - -## 3. 实施方案 - -### 3.1 在 native factory 引入正式 backend registry - -新增 registry 职责: - -1. 默认注册 builtin backend -2. 支持额外 key 注册 -3. 支持通过 key 查询 backend asset factory -4. unknown key 保持安全失败 - -### 3.2 builtin forward 改为 registry 默认项 - -`BuiltinForward` 不再作为 `if` 特判逻辑散落在 factory 主流程里, -而是作为 registry 初始化时的默认注册项。 - -### 3.3 用测试 alias key 锁住多 key seam - -本阶段不强行引入第二套真实 renderer backend, -而是通过测试注册一个 alias key,映射到 builtin forward asset, -用来验证: - -1. registry 真的支持额外 key -2. managed runtime 真的沿 key seam 解析 native backend - ---- - -## 4. 实施步骤 - -### Step 1:补 native registry API - -1. 修改 `engine/src/Rendering/Internal/RenderPipelineFactory.h/.cpp` -2. 引入 backend asset factory registry -3. builtin forward 作为默认注册项 - -### Step 2:补 unit tests - -1. 修改 `tests/Rendering/unit/test_camera_scene_renderer.cpp` -2. 锁住 custom alias key 的注册、解析、native scene renderer 创建 - -### Step 3:补 scripting probe 与 runtime test - -1. 修改 `managed/GameScripts/RenderPipelineApiProbe.cs` -2. 修改 `tests/scripting/test_mono_script_runtime.cpp` -3. 锁住 managed asset 返回 alias key 后,runtime 仍能解析 builtin forward backend - -### Step 4:验证与收口 - -1. 编译 `XCEditor` -2. 运行 `rendering_unit_tests` -3. 运行 `scripting_tests` -4. 旧 editor 冒烟 10s -5. 阶段完成后归档 plan、提交推送 - ---- - -## 5. 验收标准 - -完成后应满足: - -1. native backend key 解析走正式 registry,而不是单点 `if` -2. `BuiltinForward` 作为默认注册项保留 -3. 可以额外注册 alias key 并解析出 backend asset -4. managed asset 返回 alias key 时,native runtime 仍能正常工作 -5. 编译、单测、冒烟全部通过 diff --git a/docs/used/SRP_RendererDataPipelineBackendSeamNarrowingPlan_完成归档_2026-04-21.md b/docs/used/SRP_RendererDataPipelineBackendSeamNarrowingPlan_完成归档_2026-04-21.md deleted file mode 100644 index 1fcc1ad3..00000000 --- a/docs/used/SRP_RendererDataPipelineBackendSeamNarrowingPlan_完成归档_2026-04-21.md +++ /dev/null @@ -1,59 +0,0 @@ -# SRP RendererData Pipeline Backend Seam Narrowing Plan 2026-04-21 - -## Goal - -Remove the last public native-backend seam from `ScriptableRendererData` by hiding renderer-data-level pipeline renderer key selection and moving project probe-only backend overrides to the renderer-backed asset layer. - -## Why This Stage - -After the previous SRP cleanup stages, the main remaining user-visible native seam is: - -1. `ScriptableRendererData.GetPipelineRendererAssetKey()` - -This is still the wrong layer for external customization: - -1. ordinary URP renderer-data authors should not choose native scene renderer backend keys directly; -2. the backend family should be a first-party URP / engine concern; -3. current external usage is mostly project probe code in `managed/GameScripts`. - -At the same time, most builtin-forward probe renderer data already derive from `UniversalRendererData` through `ProbeRendererData`, so they can inherit first-party backend binding behavior without overriding the seam directly. - -## Scope - -Included: - -1. tighten `ScriptableRendererData.GetPipelineRendererAssetKey()` visibility to package-private override scope; -2. tighten `UniversalRendererData`'s override accordingly; -3. remove project probe renderer-data overrides of that seam where first-party inheritance is sufficient; -4. move project probe-only special backend choices such as `MissingBackend` / alias cases to asset-level contextual key overrides; -5. rebuild `XCEditor` and run old editor smoke. - -Not included: - -1. hiding `RendererBackedRenderPipelineAsset.GetPipelineRendererAssetKeyContextual(...)` yet; -2. replacing backend key resolution with a non-string registry; -3. Mono bridge method-name changes; -4. deferred renderer work. - -## Acceptance - -This stage is complete when: - -1. external renderer-data subclasses no longer see `GetPipelineRendererAssetKey()` as a normal override point; -2. first-party `UniversalRendererData` still provides builtin forward backend binding; -3. project probe assets still express their special backend selection behavior through asset-level overrides where needed; -4. `XCEditor` builds successfully; -5. old editor smoke passes and `editor.log` contains a fresh `SceneReady`. - -## Validation - -Validated on 2026-04-21: - -1. built old editor target with `cmake --build . --config Debug --target XCEditor` from `build/`; -2. launched `editor/bin/Debug/XCEngine.exe`; -3. let it run for about 12 seconds; -4. verified a fresh `SceneReady` entry in `editor/bin/Debug/editor.log`. - -Observed log line: - -`[2026-04-21 14:25:41] [INFO] [General] [SceneLoadTrace] SceneReady elapsed_ms=5084 first_frame_ms=568 peak_pending_async=9` diff --git a/docs/used/SRP_RendererDataSetup接缝计划_2026-04-20.md b/docs/used/SRP_RendererDataSetup接缝计划_2026-04-20.md deleted file mode 100644 index b2736435..00000000 --- a/docs/used/SRP_RendererDataSetup接缝计划_2026-04-20.md +++ /dev/null @@ -1,128 +0,0 @@ -# SRP RendererData Setup 接缝计划 2026-04-20 - -## 1. 阶段目标 - -上一阶段已经把 Universal 包里的 renderer 执行入口收成了 `context-first`,但 `ScriptableRendererData -> ScriptableRenderer` 之间还缺一条正式的 setup 接缝。 - -当前 renderer data 明明已经负责: - -1. renderer instance cache -2. renderer feature cache -3. request / frame plan 配置 -4. backend key - -但 renderer 的 feature 装配仍然没有正式归位,仍然存在两种不够干净的路径: - -1. `UniversalRenderer` 构造函数主动回头去拉 `rendererData.CreateRendererFeaturesInstance()` -2. 某些 probe renderer 直接通过构造函数接收 feature 数组 - -这会让 ownership 继续反着走,不利于后面做: - -1. renderer setup 语义固定化 -2. renderer data 驱动的 renderer 重建 / 失效 -3. 更接近 Unity 的 `RendererData owns renderer setup` - -这一阶段的目标就是把这条 setup seam 正式补上。 - ---- - -## 2. 当前问题 - -### 2.1 renderer data 持有 feature cache,却没有正式 setup seam - -现在 `ScriptableRendererData` 已经缓存了 `m_rendererFeatures`,但没有一个明确的: - -`CreateRenderer -> SetupRenderer -> ReuseRenderer` - -流程。 - -### 2.2 renderer 仍在反向拉数据 - -`UniversalRenderer` 当前直接从 `rendererData` 拉 feature。 - -这会导致 renderer 本身知道太多 data 侧装配细节,后面如果要引入 renderer invalidation / reconfiguration,会继续把脏逻辑堆进 renderer 构造过程。 - -### 2.3 probe / 生命周期测试仍依赖构造函数注入 feature - -这类写法能工作,但它锁不住“renderer data 正式负责 setup”的结构,只锁住了“某些 renderer 恰好在 ctor 里做了装配”。 - ---- - -## 3. 实施方案 - -### 3.1 给 `ScriptableRendererData` 增加正式 setup seam - -新增: - -1. `SetupRenderer(ScriptableRenderer renderer)` 受保护虚方法 -2. `AddRendererFeature(...)` / `AddRendererFeatures(...)` 辅助方法 -3. renderer 首次创建后,由 data 侧统一触发 setup - -默认实现负责把 `CreateRendererFeatures()` 产出的 feature cache 装到 renderer 上。 - -### 3.2 给 `ScriptableRenderer` 增加 data setup 适配入口 - -新增非 public 的内部入口,允许 `ScriptableRendererData` 正式向 renderer 注册 feature,而不是继续让 renderer 自己反向摸 data。 - -### 3.3 收掉现有的临时 feature 装配路径 - -本阶段至少收掉两类: - -1. `UniversalRenderer` 构造函数里的 data feature 拉取 -2. 生命周期 probe renderer 的构造函数 feature 注入 - -### 3.4 用脚本 probe / 测试锁住新接缝 - -至少锁住: - -1. `ScriptableRendererData` 存在非 public 的 `SetupRenderer` -2. renderer reuse 场景下 setup 不会重复乱跑 -3. 生命周期释放链路下 feature create / dispose 行为不回退 - ---- - -## 4. 实施步骤 - -### Step 1:补 core setup seam - -目标: - -1. 修改 `ScriptableRendererData` -2. 修改 `ScriptableRenderer` -3. 让 renderer 首次创建时走正式 setup - -### Step 2:迁移 Universal 与 probe - -目标: - -1. 收掉 `UniversalRenderer` 对 data feature cache 的反向拉取 -2. 让 probe renderer / probe renderer data 改走 setup seam - -### Step 3:补测试与 API probe - -目标: - -1. 更新 `ScriptableRenderContextApiSurfaceProbe.cs` -2. 更新 `test_mono_script_runtime.cpp` -3. 必要时补 setup call count 观测 - -### Step 4:验证与收口 - -目标: - -1. 编译 `rendering_unit_tests`、`scripting_tests`、`XCEditor` -2. 跑测试 -3. 跑旧版 `editor/bin/Debug/XCEngine.exe` 10s 冒烟并检查新的 `SceneReady` -4. 归档 plan,提交并推送 - ---- - -## 5. 验收标准 - -完成后应满足: - -1. `ScriptableRendererData` 正式拥有 renderer setup seam -2. renderer feature 装配不再主要依赖 renderer 构造函数反向拉数据 -3. Universal 与现有 probe 行为不回退 -4. renderer reuse / 生命周期相关测试继续通过 -5. old editor 编译、测试、冒烟全部通过 diff --git a/docs/used/SRP_RendererData主链正式化计划_2026-04-20.md b/docs/used/SRP_RendererData主链正式化计划_2026-04-20.md deleted file mode 100644 index 88dac08e..00000000 --- a/docs/used/SRP_RendererData主链正式化计划_2026-04-20.md +++ /dev/null @@ -1,233 +0,0 @@ -# SRP RendererData 主链正式化计划 2026-04-20 - -## 1. 阶段目标 - -上一阶段已经把: - -`ManagedRenderPipelineAssetRuntime -> native renderer backend registry` - -这条接缝补齐了,`managed backend key -> native backend asset` 不再是单点硬编码。 - -下一阶段不再补零散 seam,而是正式收紧 Unity 风格的 managed SRP 主链: - -`RenderPipelineAsset -> RendererData -> Renderer -> RendererFeature -> RenderPass` - -这一阶段的目标,是把当前已经存在但还不够“主线化”的 Universal/SRP 结构,收成一个稳定、清晰、可继续往 URP 演进的正式骨架。 - ---- - -## 2. 当前状态 - -当前已经具备的能力: - -1. C++ 侧已经有 `ManagedScriptableRenderPipelineAsset`,可以通过 Mono bridge 创建 managed asset runtime。 -2. managed 侧已经有: - - `ScriptableRenderPipelineAsset` - - `RendererBackedRenderPipelineAsset` - - `ScriptableRendererData` - - `ScriptableRenderer` - - `ScriptableRendererFeature` - - `ScriptableRenderPass` - - `UniversalRenderPipelineAsset` - - `UniversalRendererData` - - `UniversalRenderer` -3. 默认 Universal 路径已经能驱动 main scene / post process / final output 这些录制。 -4. renderer backend key 已经能通过 managed asset / renderer data 交给 native factory 解析。 - -也就是说,骨架已经不是从零开始了,问题不在“有没有”,而在“主链边界还没有彻底收紧”。 - ---- - -## 3. 当前主要问题 - -### 3.1 `RendererBackedRenderPipelineAsset` 的主链职责虽然存在,但还没有完全锁死 - -当前 asset 已经负责: - -1. 创建 pipeline -2. 解析 default renderer data / renderer -3. 转发 camera request 配置 -4. 转发 camera frame plan 配置 -5. 提供 backend key -6. 释放 renderer data runtime resources - -但是这条链的“正式契约”还不够清楚,很多行为只是现在恰好能跑,不代表后续扩展 renderer list / renderer selection / 多种 renderer data 时仍然稳定。 - -### 3.2 `ScriptableRendererData` 的 dirty / rebuild 语义还不够完整 - -当前 `ScriptableRendererData` 已经有: - -1. renderer instance cache -2. renderer feature cache -3. `SetDirty()` -4. runtime resource release - -但它的 dirty 更多还是“本地缓存失效”语义,尚未完全锁死: - -1. renderer data 失效后,asset 的 runtime resource version 是否必然变化 -2. native runtime 是否会稳定感知并重建 -3. renderer data / renderer / feature 的 release 顺序是否完全可预测 - -这一步如果不收紧,后面做真正的 URP renderer asset、feature authoring、甚至多 renderer 切换时,会不断出现“改了配置但 native 还拿着旧缓存”的问题。 - -### 3.3 default renderer 选择与 fallback 规则还不够正式 - -当前已经有: - -1. `rendererDataList` -2. `defaultRendererIndex` -3. invalid index fallback - -但还缺少更完整的主线约束: - -1. renderer data 为空时如何补默认项 -2. 多个 slot 指向同一 renderer data 时如何释放 -3. request / frame plan / backend key / renderer execution 是否始终走同一套 renderer selection - -### 3.4 builtin renderer 行为和自定义 feature 行为需要进一步统一 - -当前 `UniversalRenderer` 内部已经挂了 builtin scene feature,同时 `UniversalRendererData` 也能提供自定义 `rendererFeatures`。 - -这说明主线已经成型,但还需要进一步锁死: - -1. builtin feature 和用户 feature 的装配顺序 -2. pass queue 的稳定顺序 -3. invalidation 后 builtin/custom feature 是否都被正确重建 - ---- - -## 4. 本阶段要解决什么 - -这一阶段只做 SRP/URP 主链正式化,不做下面这些内容: - -1. 不做 deferred renderer -2. 不做新的 native renderer backend -3. 不做 editor 侧 renderer asset 检视器 -4. 不做新的大型图形效果 - -这一阶段只做一件事: - -把现有 `RendererBackedRenderPipelineAsset / ScriptableRendererData / UniversalRenderer` -这条链,收成一个后续可以稳定承接“真正 URP 包层演进”的正式主线。 - -`Render Graph` 仍然继续留在 C++ 层,这一阶段不改这个大方向。 - ---- - -## 5. 实施方案 - -### Step 1:正式化 asset 与 renderer data 的失效传播 - -目标: - -让 renderer data 的 dirty 不再只是 managed 本地缓存行为,而是正式影响 asset runtime version / native runtime rebuild 判定。 - -预期改动点: - -1. `managed/XCEngine.ScriptCore/Rendering/Core/ScriptableRenderPipelineAsset.cs` -2. `managed/XCEngine.ScriptCore/Rendering/Universal/RendererBackedRenderPipelineAsset.cs` -3. `managed/XCEngine.ScriptCore/Rendering/Universal/ScriptableRendererData.cs` -4. 必要时补 `engine/src/Scripting/Mono/MonoScriptRuntime.cpp` 对应验证 - -要锁住的结果: - -1. 改 renderer data 配置会触发 asset 级 runtime resource version 变化 -2. native runtime 能稳定重建 renderer / pipeline 相关缓存 - -### Step 2:正式化 renderer selection 主链 - -目标: - -把“选哪个 renderer data / renderer”变成单一主链,不允许 request、frame plan、backend key、execution 各走各的。 - -预期改动点: - -1. `managed/XCEngine.ScriptCore/Rendering/Universal/RendererBackedRenderPipelineAsset.cs` -2. `managed/XCEngine.ScriptCore/Rendering/Universal/RendererBackedRenderPipeline.cs` -3. `managed/XCEngine.ScriptCore/Rendering/Universal/UniversalRenderPipelineAsset.cs` - -要锁住的结果: - -1. default renderer resolve 规则唯一 -2. invalid index fallback 行为唯一 -3. `ConfigureCameraRenderRequest / ConfigureCameraFramePlan / GetPipelineRendererAssetKey / ResolveRenderer` - 始终基于同一 renderer selection - -### Step 3:正式化 renderer / feature / pass 的装配与释放边界 - -目标: - -让 `ScriptableRendererData -> ScriptableRenderer -> ScriptableRendererFeature -> ScriptableRenderPass` -这条运行时装配链可预测、可重建、可释放。 - -预期改动点: - -1. `managed/XCEngine.ScriptCore/Rendering/Universal/ScriptableRendererData.cs` -2. `managed/XCEngine.ScriptCore/Rendering/Universal/ScriptableRenderer.cs` -3. `managed/XCEngine.ScriptCore/Rendering/Universal/ScriptableRendererFeature.cs` -4. `managed/XCEngine.ScriptCore/Rendering/Universal/UniversalRenderer.cs` -5. 必要时相关 probe / test script - -要锁住的结果: - -1. builtin feature 与 custom feature 的创建顺序稳定 -2. pass queue 顺序稳定 -3. dirty/release 后 renderer、feature、pass 不残留旧实例 - -### Step 4:补齐测试,把主链锁死 - -需要重点补的测试方向: - -1. renderer data dirty 会触发 managed asset runtime rebuild -2. renderer selection 在 request / plan / execution / backend key 上保持一致 -3. duplicated renderer data slot 的 release 不重复 -4. builtin feature + custom feature 的装配顺序与 pass 顺序稳定 -5. runtime release 后再次访问会得到新实例而不是旧缓存 - -优先测试文件: - -1. `tests/scripting/test_mono_script_runtime.cpp` -2. `tests/Rendering/unit/test_camera_scene_renderer.cpp` -3. 如有必要补 managed probe: - `managed/GameScripts/RenderPipelineApiProbe.cs` - -### Step 5:阶段验证与收口 - -本阶段完成后,按固定流程验证: - -1. 编译 `XCEditor` -2. 运行 `rendering_unit_tests` -3. 运行 `scripting_tests` -4. 运行旧版 `editor/bin/Debug/XCEngine.exe` 冒烟至少 10s -5. 检查新的 `editor/bin/Debug/editor.log` 中出现 `SceneReady` -6. plan 归档到 `docs/used` -7. 使用规范 commit message 提交并推送 - ---- - -## 6. 验收标准 - -完成后应满足: - -1. `RendererBackedRenderPipelineAsset` 成为明确的 renderer selection 主入口 -2. `ScriptableRendererData` 的 dirty 能稳定传导到 asset/runtime rebuild -3. renderer、feature、pass 的创建/释放/重建边界清楚且有测试锁定 -4. Universal 默认路径和自定义 feature 路径都走同一套正式主链 -5. 后续再往上做真正的 URP 包层功能时,不需要继续补大量“临时接缝” - ---- - -## 7. 阶段完成后的意义 - -这一阶段完成之后,SRP 就不再只是“managed 侧能录几段 graph”的试运行状态,而会进入: - -`SRP core 已成型,URP 可以沿着正式主链继续搭` - -届时再往后做: - -1. 更完整的 Universal renderer data -2. 更正式的 renderer feature authoring -3. 真正的 URP 包层功能扩展 -4. 更进一步的项目级渲染管线资源化 - -都会顺很多。 diff --git a/docs/used/SRP_RendererData失效与重建接缝计划_2026-04-20.md b/docs/used/SRP_RendererData失效与重建接缝计划_2026-04-20.md deleted file mode 100644 index bbba795f..00000000 --- a/docs/used/SRP_RendererData失效与重建接缝计划_2026-04-20.md +++ /dev/null @@ -1,139 +0,0 @@ -# SRP RendererData 失效与重建接缝计划 2026-04-20 - -## 1. 阶段目标 - -上一阶段已经把 renderer feature setup 正式归位到: - -`ScriptableRendererData -> ScriptableRenderer` - -但现在还缺最后一条关键闭环: - -当同一个 `ScriptableRendererData` 被修改后,旧的 renderer cache 该怎么失效,以及下一次使用时怎么重建。 - -当前系统只在更大的生命周期边界清缓存: - -1. asset runtime 释放 -2. bridge generation 变化 -3. managed pipeline dispose - -这意味着“同一个 asset 还活着,但 renderer data 已经变了”的情况,还没有正式 contract。 - -这一阶段的目标就是补上 Unity 风格的 renderer data invalidation seam。 - ---- - -## 2. 当前问题 - -### 2.1 renderer cache 只能在 runtime release 时整体释放 - -现在 `ScriptableRendererData` 已经缓存: - -1. renderer instance -2. renderer feature cache - -但没有一个正式的“我变脏了,请把这套 renderer setup 丢掉”的入口。 - -### 2.2 同 asset、同 runtime 内部缺少 renderer rebuild 语义 - -对于后续编辑器和 SRP/URP 包来说,很常见的场景是: - -1. 还在用同一个 pipeline asset -2. 只是改了 renderer data -3. 希望下一次录制时重建 renderer - -这一层现在还只能靠 asset/runtime 整体重建绕过去。 - -### 2.3 测试还没锁住“同一个 runtime 内触发 renderer rebuild” - -现有测试已经锁了: - -1. renderer reuse -2. 生命周期 release -3. setup seam - -但还没锁“同一个 asset runtime 内,dirty 之后旧 renderer dispose,新 renderer rebuild”。 - ---- - -## 3. 实施方案 - -### 3.1 给 `ScriptableRendererData` 增加正式 invalidation seam - -新增非 public / protected 语义: - -1. `SetDirty()` -2. `isInvalidated` - -`SetDirty()` 负责: - -1. 释放当前 renderer cache -2. 释放当前 feature cache -3. 标记 data 已失效 - -### 3.2 renderer 下一次解析时自动重建 - -保持调用面不变: - -`GetRendererInstance()` - -但在 dirty 之后,下一次解析 renderer 时应: - -1. 重新创建 renderer -2. 重新 setup renderer -3. 清掉 invalidated 状态 - -### 3.3 用 probe 锁住同 asset runtime 内的 rebuild 行为 - -新增一个专用 probe: - -1. 第一次 `SupportsStageRenderGraph` 创建 renderer -2. 运行时通过同一个 managed asset 调用 renderer data invalidation -3. 第二次 `SupportsStageRenderGraph` 在同一个 asset runtime 里重建 renderer - ---- - -## 4. 实施步骤 - -### Step 1:补 core invalidation seam - -目标: - -1. 修改 `ScriptableRendererData` -2. 引入 renderer/feature cache 的单独释放辅助逻辑 -3. 增加 `SetDirty()` 与 `isInvalidated` - -### Step 2:补 invalidation probe - -目标: - -1. 在 `RenderPipelineApiProbe.cs` 增加 renderer invalidation 专用 asset / renderer data / renderer / feature / 观察脚本 -2. 用同一个 managed asset handle 触发 invalidation - -### Step 3:补 API probe 与脚本测试 - -目标: - -1. 更新 `ScriptableRenderContextApiSurfaceProbe.cs` -2. 更新 `test_mono_script_runtime.cpp` -3. 锁住 dirty 之后的 create/setup/dispose 计数 - -### Step 4:验证与收口 - -目标: - -1. 编译 `rendering_unit_tests`、`scripting_tests`、`XCEditor` -2. 跑测试 -3. 跑旧版 `editor/bin/Debug/XCEngine.exe` 10s 冒烟并检查新的 `SceneReady` -4. 归档 plan,提交并推送 - ---- - -## 5. 验收标准 - -完成后应满足: - -1. `ScriptableRendererData` 存在正式 dirty / invalidated seam -2. dirty 之后旧 renderer cache 和 feature cache 会被释放 -3. 同一个 asset runtime 内下一次解析 renderer 时会自动重建 -4. probe / scripting test 锁住重建行为 -5. old editor 编译、测试、冒烟全部通过 diff --git a/docs/used/SRP_RendererDrivenPipeline执行接缝计划_2026-04-20.md b/docs/used/SRP_RendererDrivenPipeline执行接缝计划_2026-04-20.md deleted file mode 100644 index 6646b473..00000000 --- a/docs/used/SRP_RendererDrivenPipeline执行接缝计划_2026-04-20.md +++ /dev/null @@ -1,161 +0,0 @@ -# SRP Renderer-Driven Pipeline 执行接缝计划 2026-04-20 - -## 1. 阶段目标 - -上一阶段已经把 `CameraFramePlan` 的 managed 规划接缝接进来了: - -1. `ScriptableRenderPipelineAsset -> ConfigureCameraFramePlan` -2. `UniversalRenderPipelineAsset -> ScriptableRendererData -> ScriptableRendererFeature` - -但执行层还有一个明显没收干净的问题: - -1. `UniversalRenderPipelineAsset` 仍然自己持有一套 rendererData/defaultRenderer/runtime cleanup 逻辑 -2. `UniversalRenderPipeline` 仍然自己做一层很薄的 renderer forwarding -3. managed renderer 虽然已经存在,但还没有被提炼成正式的 renderer-driven pipeline 骨架 - -这一阶段的目标就是把这层骨架正式抽出来: - -1. 增加通用的 `RendererDrivenRenderPipeline` -2. 增加通用的 `RendererBackedRenderPipelineAsset` -3. 增加 `RendererBackedRenderPipeline` -4. 给 Universal 正式迁移到这套骨架 -5. 补上 renderer execution 侧的上下文类型,给后续 SRP/URP 继续扩张留出稳定接口 - ---- - -## 2. 当前问题 - -### 2.1 Universal 还在重复持有“通用 renderer 资产”逻辑 - -当前 `UniversalRenderPipelineAsset` 里同时承担了: - -1. `rendererDataList/defaultRendererIndex` -2. `GetRendererData/GetRenderer` -3. `ConfigureCameraRenderRequest` -4. `ConfigureCameraFramePlan` -5. `GetPipelineRendererAssetKey` -6. `ReleaseRendererDataRuntimeResources` - -这些其实已经不是 `Universal` 特有逻辑,而是“renderer-backed pipeline asset”的通用职责。 - -### 2.2 Pipeline 执行骨架还没有提升成可复用抽象 - -当前 `UniversalRenderPipeline` 本质上只是: - -1. 拿默认 renderer -2. 转发 `SupportsStageRenderGraph` -3. 转发 `RecordStageRenderGraph` - -这层 forwarding 逻辑应该收成通用的 renderer-driven pipeline 基类,而不是继续散落在首方包里。 - -### 2.3 缺少 renderer execution 层自己的正式上下文对象 - -当前 renderer 执行时主要直接吃: - -1. `ScriptableRenderContext` -2. `RenderingData` - -这能工作,但在 API 组织上还缺一个更明确的“renderer 正在做执行决策”的上下文层。 - -这一阶段先补最小集合: - -1. `RendererRecordingContext` -2. `RendererCameraRequestContext` - -不扩 raw native API,只做 managed 组织骨架。 - ---- - -## 3. 实施方案 - -### 3.1 Universal 包新增通用 renderer-driven 基类 - -新增: - -1. `RendererDrivenRenderPipeline` -2. `RendererBackedRenderPipeline` -3. `RendererBackedRenderPipelineAsset` - -职责边界: - -1. `RendererDrivenRenderPipeline` 负责 pipeline -> renderer execution delegation -2. `RendererBackedRenderPipeline` 负责从 asset 解析默认 renderer -3. `RendererBackedRenderPipelineAsset` 负责 rendererData 列表、默认选择、request/plan/backend key/runtime cleanup - -### 3.2 Universal 迁移到通用骨架 - -重构: - -1. `UniversalRenderPipelineAsset : RendererBackedRenderPipelineAsset` -2. `UniversalRenderPipeline : RendererBackedRenderPipeline` -3. `UniversalRenderPipelineAsset` 只保留 Universal 自己的默认 rendererData 初始化 - -### 3.3 补 execution context 类型 - -新增: - -1. `RendererRecordingContext` -2. `RendererCameraRequestContext` - -这一步先做到: - -1. 类型正式存在 -2. pipeline / renderer 内部可以开始用它们组织 execution seam -3. 不强行扩大 public raw recording surface - -### 3.4 用测试锁住新骨架 - -验证点: - -1. API probe 能看到新类型 -2. Universal 行为不回退 -3. 通用 renderer-backed 骨架至少被现有 Universal 主链真实经过 - ---- - -## 4. 实施步骤 - -### Step 1:新增 renderer-driven 基类 - -目标: - -1. 新增 `RendererDrivenRenderPipeline` -2. 新增 `RendererBackedRenderPipeline` -3. 新增 `RendererBackedRenderPipelineAsset` - -### Step 2:迁移 Universal - -目标: - -1. `UniversalRenderPipelineAsset` 下放通用逻辑 -2. `UniversalRenderPipeline` 改成继承通用基类 -3. 保持现有 runtime / cleanup / default renderer 行为一致 - -### Step 3:补 execution context 类型 - -目标: - -1. 新增 `RendererRecordingContext` -2. 新增 `RendererCameraRequestContext` -3. 在 renderer-driven 骨架里用起来 - -### Step 4:补测试与验证 - -目标: - -1. 更新 `ScriptableRenderContextApiSurfaceProbe` -2. 更新 `test_mono_script_runtime.cpp` -3. 编译 `rendering_unit_tests`、`scripting_tests`、`XCEditor` -4. 运行 old editor 10s 冒烟并检查新的 `SceneReady` - ---- - -## 5. 验收标准 - -完成后应满足: - -1. `Universal` 不再自己持有整套通用 renderer-backed asset 逻辑 -2. 存在正式的 `RendererDrivenRenderPipeline / RendererBackedRenderPipeline / RendererBackedRenderPipelineAsset` -3. 存在正式的 `RendererRecordingContext / RendererCameraRequestContext` -4. `Universal` 现有 request / plan / record 行为不回退 -5. `scripting_tests`、`rendering_unit_tests`、`XCEditor`、old editor 冒烟全部通过 diff --git a/docs/used/SRP_RendererSetup与ContextFirst执行接缝计划_2026-04-20.md b/docs/used/SRP_RendererSetup与ContextFirst执行接缝计划_2026-04-20.md deleted file mode 100644 index c3b174d8..00000000 --- a/docs/used/SRP_RendererSetup与ContextFirst执行接缝计划_2026-04-20.md +++ /dev/null @@ -1,133 +0,0 @@ -# SRP Renderer Setup 与 Context-First 执行接缝计划 2026-04-20 - -## 1. 阶段目标 - -上一阶段已经把 renderer-driven 主骨架立起来了: - -1. `RendererDrivenRenderPipeline` -2. `RendererBackedRenderPipeline` -3. `RendererBackedRenderPipelineAsset` -4. `RendererRecordingContext / RendererCameraRequestContext` - -但当前 `RendererRecordingContext` 还没有真正成为 renderer 的正式 override 面。 -现在 `ScriptableRenderer` 里仍然主要靠: - -1. `SupportsStageRenderGraph(CameraFrameStage)` -2. `RecordStageRenderGraph(ScriptableRenderContext)` - -而 `RendererRecordingContext` 只是被外层包了一层。 - -这一阶段的目标就是把 Universal 包内的 renderer 执行接缝正式切成 context-first: - -1. 让 `ScriptableRenderer` 以 `RendererRecordingContext` 为正式支持/录制入口 -2. 让 `RendererDrivenRenderPipeline` 直接走这条正式 seam -3. 让 probe / 测试改成覆盖这条新 seam -4. 锁住“新 seam 存在,但不额外扩 public surface” - ---- - -## 2. 当前问题 - -### 2.1 `RendererRecordingContext` 还只是包装,不是正式 contract - -当前 `RendererDrivenRenderPipeline` 虽然已经把 stage/context 转成了 -`RendererRecordingContext`,但 `ScriptableRenderer` 仍然是: - -1. `SupportsRendererRecording(context)` 内部转回 `SupportsStageRenderGraph(stage)` -2. `RecordRenderer(context)` 内部转回 `RecordStageRenderGraph(context.renderContext)` - -这说明真正的 override 面还没变。 - -### 2.2 support path 和 record path 还没真正共享同一组上下文入口 - -现在 support 和 record 的主入口签名不同: - -1. support:`CameraFrameStage` -2. record:`ScriptableRenderContext` - -虽然外层能桥接,但 renderer 内部并没有一个统一的 context-first contract。 - -### 2.3 后续想扩 renderer setup 数据会继续卡在旧签名 - -未来如果要把更多 setup 数据挂进 renderer 执行面: - -1. stage planning 结果 -2. camera-specific flags -3. renderer-local setup state - -那就应该沿着 `RendererRecordingContext` 往里加,而不是继续扩旧签名。 - ---- - -## 3. 实施方案 - -### 3.1 让 `ScriptableRenderer` 正式采用 context-first seam - -调整为: - -1. `SupportsRendererRecording(RendererRecordingContext)` 成为主 support seam -2. `RecordRenderer(RendererRecordingContext)` 成为主 record seam -3. 旧的 `SupportsStageRenderGraph/RecordStageRenderGraph` 只保留为 native bridge 适配入口 - -### 3.2 probe renderers 迁移到新 seam - -把目前那些会观察 support/record 调用次数的 probe renderer: - -1. `ManagedRenderPipelineProbe` -2. `ManagedUniversalRenderPipelineProbe` -3. `ManagedLifecycleProbeRenderer` - -改成 override 新 seam。 - -### 3.3 用脚本 probe 锁住 seam - -新增/更新 API probe,确认: - -1. `RendererRecordingContext` 类型存在 -2. `ScriptableRenderer` 存在非 public 的 context-first 方法 -3. 旧的 stage-based renderer 方法仍然不是 public - ---- - -## 4. 实施步骤 - -### Step 1:重构 `ScriptableRenderer` - -目标: - -1. 把 context-first 方法变成正式 virtual seam -2. 旧入口退回到适配层 - -### Step 2:迁移 probe renderers - -目标: - -1. 改写 probe renderer overrides -2. 保持现有行为和计数不回退 - -### Step 3:补 probe 与测试 - -目标: - -1. 更新 `ScriptableRenderContextApiSurfaceProbe.cs` -2. 更新 `test_mono_script_runtime.cpp` - -### Step 4:完整验证 - -目标: - -1. 编译 `rendering_unit_tests`、`scripting_tests`、`XCEditor` -2. 跑测试 -3. old editor 10s 冒烟并检查最新 `SceneReady` - ---- - -## 5. 验收标准 - -完成后应满足: - -1. `ScriptableRenderer` 的正式 support/record seam 是 context-first -2. `RendererDrivenRenderPipeline` 直接走正式 seam -3. probe renderers 与脚本测试已经迁移并锁住新结构 -4. 不扩大 public renderer 录制表面 -5. 编译、测试、old editor 冒烟全部通过 diff --git a/docs/used/SRP_Runtime_v1_阶段归档_2026-04-18.md b/docs/used/SRP_Runtime_v1_阶段归档_2026-04-18.md deleted file mode 100644 index 91e7e7f4..00000000 --- a/docs/used/SRP_Runtime_v1_阶段归档_2026-04-18.md +++ /dev/null @@ -1,306 +0,0 @@ -# SRP Runtime v1 实施计划 2026-04-17 - -## 1. 结论 - -现在**可以正式进入 SRP 主线**了。 - -但这里的“开始搭 SRP”,准确含义不是: - -1. 直接开做 `URP` 包层。 -2. 直接做延迟渲染。 -3. 直接把阴影、高斯、体积、后处理全部搬到 C#。 - -本阶段正确目标是: - -`Native RenderGraph / Planning / Execution` -`-> Managed SRP Runtime v1` -`-> Managed Forward Pipeline v1` -`-> 之后再开 URP-like Package` - -也就是说,下一阶段确实是搭 SRP,但搭的是 **SRP Runtime v1**,不是直接搭“最终版 URP”。 - ---- - -## 2. 为什么现在可以开始做 SRP - -当前 native 渲染层已经具备进入 SRP 主线的前提: - -1. `RenderGraph` 已经可用,不是空壳。 -2. `CameraFramePlan` 已经把每相机渲染组织稳定下来。 -3. `CameraFrameStage` 和 stage dispatch 已经把主链拆清楚了。 -4. `BuiltinForwardPipeline` 已经能把 `MainScene` 录进 render graph。 -5. `ScriptableRenderPipelineHost` 已经提供了 `stage recorder + fallback renderer` 的宿主接缝。 -6. 默认 pipeline 工厂已经能从 `managed or default asset` 入口创建渲染管线。 - -所以当前真正缺的已经不是 native 渲染骨架,而是: - -1. managed pipeline 在 runtime 里真实存在。 -2. native 能持有并调用 managed pipeline。 -3. managed 层拿到最小可用的渲染上下文。 - -换句话说,**native 这一段已经够了,下一刀就该切 SRP runtime**。 - ---- - -## 3. 本阶段目标 - -本阶段收口目标只有四个: - -1. native 能创建并持有真实 managed `ScriptableRenderPipelineAsset` / `ScriptableRenderPipeline` 实例。 -2. `ScriptableRenderPipelineHost` 能稳定回调到 managed pipeline 参与 `MainScene` stage graph 录制。 -3. managed 拿到一个最小可用的 `ScriptableRenderContext v1`。 -4. 跑通一条最小 managed forward pipeline 主链,同时保留 builtin forward fallback。 - -如果这四件事没有形成闭环,就不算进入 SRP 主线。 - ---- - -## 4. 本阶段明确不做的事 - -这一阶段**明确不做**下面这些内容: - -1. 不开 `URP-like package`。 -2. 不做 deferred pipeline。 -3. 不把阴影、后处理、高斯、体积的组织权全部搬到 C#。 -4. 不直接对外暴露原始 `RHI` 或原始 native internal type。 -5. 不追求第一版就完全对齐 Unity 的 `ScriptableRenderContext` 和 `CommandBuffer`。 - -原因很简单: - -当前根问题不是“缺某个 renderer feature”,而是“managed pipeline 运行时还没真正成立”。 - ---- - -## 5. 本阶段的正确边界 - -### 留在 C++ 的 - -1. `RHI` -2. `RenderGraph` -3. frame planning / stage dispatch / graph compile / execute -4. scene extraction / culling / frame data -5. builtin renderer 的底层执行实现 - -### 开始上移到 managed 的 - -1. pipeline asset 的实例化和生命周期 -2. pipeline 的主场景录制组织 -3. 受控的 render context 封装 -4. 后续 `URP-like` 包层需要依赖的 SRP runtime 接缝 - -本阶段的核心原则是: - -> C++ 继续负责渲染内核,C# 开始接管渲染组织。 - ---- - -## 6. 分阶段实施顺序 - -### Phase A: 建立通用 managed object runtime - -目标: - -1. 在 `MonoScriptRuntime` 中补齐“任意 managed 对象”的创建、持有、释放能力。 -2. 不再只围绕 `ScriptComponent`、`GameObject`、`Component` wrapper 打转。 -3. 能按类型描述创建 asset 实例和 pipeline 实例。 -4. 能稳定保存 `GCHandle` 或等价持有关系。 -5. 能稳定回调实例方法并正确处理异常。 - -这一阶段是整个 SRP runtime 的硬前提。 - -如果这一步不成立,后面一切 SRP API 都只是空壳。 - -### Phase B: 把 render pipeline asset 从“类型描述”升级成“真实 runtime 对象” - -当前 `GraphicsSettings.renderPipelineAssetType` 的语义还是: - -`把一个 C# 类型名写给 native` - -这一阶段需要升级成: - -1. native 能根据类型创建真实 managed asset 实例。 -2. managed asset 能创建真实 managed pipeline 实例。 -3. native 能持有 asset / pipeline 生命周期。 -4. `ManagedScriptableRenderPipelineAsset` 不再只是 descriptor-only 包装。 - -第一版允许继续用 `Type` 作为 bootstrap 入口,但 native 内部不能再停留在 descriptor 模型。 - -### Phase C: 实现真实的 managed render pipeline bridge - -目标: - -1. `ManagedRenderPipelineBridge` 由 `MonoScriptRuntime` 提供真实实现。 -2. `ManagedScriptableRenderPipelineAsset` 创建的 host 能绑定到真实 managed pipeline。 -3. `ScriptableRenderPipelineHost` 能调用 managed pipeline 的 stage 录制方法。 -4. bridge 不再只是测试用 mock seam。 - -这一阶段完成后,managed pipeline 才算真正“活过来”。 - -### Phase D: 定义 `ScriptableRenderContext v1` - -第一版不要追求大而全。 - -只需要提供最小可用能力: - -1. 查询当前 camera frame / stage 基本信息。 -2. 参与 `MainScene` render graph 录制。 -3. 调用 native scene renderer 绘制 `Opaque / Skybox / Transparent`。 -4. 访问最基础的 source / target / blackboard 语义。 -5. 预留 fullscreen / blit 接口,但不强求一步到位。 - -这一版不直接暴露 raw `RenderGraphTextureHandle` 和 raw `RenderSurface` 给用户脚本。 - -### Phase E: 跑通最小 managed forward pipeline v1 - -目标: - -1. 用 C# 创建一个 managed pipeline。 -2. managed pipeline 能声明支持 `MainScene` stage graph。 -3. managed pipeline 能在 `MainScene` 调 native forward scene renderer。 -4. editor / runtime 在切到 managed pipeline 后仍能正常渲染。 -5. builtin forward 仍可作为 fallback 路径保留。 - -这一阶段完成后,才算正式进入 “SRP 已经开始工作” 的状态。 - ---- - -## 7. 第一批要改的文件 - -### Native 侧核心落点 - -1. `engine/src/Scripting/Mono/MonoScriptRuntime.cpp` -2. `engine/include/XCEngine/Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.h` -3. `engine/src/Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.cpp` -4. `engine/include/XCEngine/Rendering/Pipelines/ScriptableRenderPipelineHost.h` -5. `engine/src/Rendering/Pipelines/ScriptableRenderPipelineHost.cpp` -6. `engine/src/Rendering/Internal/RenderPipelineFactory.cpp` - -### Managed 侧核心落点 - -1. `managed/XCEngine.ScriptCore/InternalCalls.cs` -2. `managed/XCEngine.ScriptCore/GraphicsSettings.cs` -3. `managed/XCEngine.ScriptCore/ScriptableRenderPipelineAsset.cs` -4. `managed/XCEngine.ScriptCore/ScriptableRenderPipeline.cs` -5. 新增 `managed/XCEngine.ScriptCore/ScriptableRenderContext.cs` -6. 可能新增最小 `managed/XCEngine.ScriptCore/Rendering/*` 辅助类型 - -### 测试与验证 - -1. `managed/GameScripts/RenderPipelineApiProbe.cs` -2. 新增 native / managed bridge smoke tests -3. editor 冒烟场景验证 - ---- - -## 8. 代码层面的实施拆分 - -建议按下面的提交粒度推进,而不是一口气乱改: - -### Commit 1: managed object runtime 基础设施 - -完成后应该能: - -1. 按类型创建普通 managed 对象。 -2. 持有并释放对象句柄。 -3. 回调实例方法。 - -### Commit 2: managed pipeline asset / pipeline 生命周期 - -完成后应该能: - -1. 从 `GraphicsSettings.renderPipelineAssetType` 生成真实 asset 实例。 -2. 从 asset 生成真实 pipeline 实例。 -3. 由 native 负责持有这两个对象。 - -### Commit 3: host 与 managed bridge 接通 - -完成后应该能: - -1. `ScriptableRenderPipelineHost` 绑定真实 managed stage recorder。 -2. `MainScene` 录制可回调到 managed pipeline。 - -### Commit 4: `ScriptableRenderContext v1` - -完成后应该能: - -1. managed 侧拿到最小上下文。 -2. managed 能调用 native scene renderer 画主场景阶段。 - -### Commit 5: managed forward pipeline v1 smoke path - -完成后应该能: - -1. 用 managed pipeline 跑通主场景。 -2. 与 builtin forward fallback 共存。 - ---- - -## 9. 关键风险 - -### 风险 1: Mono 生命周期和 GC handle 泄漏 - -这是第一风险。 - -如果 object runtime 持有策略不稳,后面渲染管线切换、domain reload、editor 热切换都会出问题。 - -### 风险 2: 把 host 做成“全局单例桥” - -如果 bridge 继续停留在粗糙全局状态,后面多实例、多场景、重建 pipeline 都会很难收口。 - -### 风险 3: 过早暴露原始 native 类型 - -如果第一版就把 raw graph/resource handle 暴露给 managed,API 很快会固化,后面很难收拾。 - -### 风险 4: builtin forward 与 managed forward 边界不清 - -如果 managed 直接依赖 `BuiltinForwardPipeline` 私有实现细节,后面就不是 SRP,而是“脚本包一层 builtin forward”。 - ---- - -## 10. 阶段验收标准 - -本阶段收口时,必须同时满足下面这些条件: - -1. native 里存在真实的 managed pipeline bridge,不再只是 mock seam。 -2. `GraphicsSettings` 能驱动创建真实 managed asset / pipeline 实例。 -3. `ScriptableRenderPipelineHost` 能稳定回调 managed pipeline 的 `MainScene` 录制。 -4. managed 侧存在最小 `ScriptableRenderContext v1`。 -5. 一条最小 managed forward pipeline 能真实跑出主场景。 -6. builtin forward fallback 路径仍然可用。 -7. editor 编译通过,基础冒烟渲染不崩。 - -只要这 7 条有一条不成立,这一阶段就不能算收口。 - ---- - -## 11. 本阶段完成后,下一阶段才做什么 - -等 `SRP Runtime v1` 收口之后,下一阶段才适合开始: - -1. `URP-like asset` -2. `RendererData` -3. `RendererFeature` -4. `RenderPassEvent` -5. 阴影 / 后处理 / 高斯 / 体积 等组织权逐步上移 - -也就是说: - -**现在可以开始搭 SRP。** - -但准确说法应该是: - -**现在开始搭的是 `SRP Runtime v1`,不是直接搭最终版 URP。** - ---- - -## 12. 下一刀从哪里下 - -下一步第一刀就应该下在: - -`MonoScriptRuntime` -`-> 通用 managed object runtime` -`-> 真实 ManagedRenderPipelineBridge` - -这是整个 SRP 主线最根上的卡点。 - -如果这里不先解决,后面无论做多少 `ScriptableRenderPipelineAsset`、`ScriptableRenderContext`、`RendererFeature`,都只是外壳。 diff --git a/docs/used/SRP_Runtime_v2_过期归档_2026-04-19.md b/docs/used/SRP_Runtime_v2_过期归档_2026-04-19.md deleted file mode 100644 index f1a1a366..00000000 --- a/docs/used/SRP_Runtime_v2_过期归档_2026-04-19.md +++ /dev/null @@ -1,348 +0,0 @@ -# SRP Runtime v2 计划 2026-04-18 - -## 1. 结论 - -当前阶段的下一步,不是继续做 editor 对接,也不是直接开做 deferred,更不是马上把阴影、体积、高斯这些效果全部搬到 C#。 - -下一阶段的正确主线是: - -`Native RenderGraph / Native Scene Renderer` -`-> Managed SRP Runtime v2` -`-> Managed Renderer / Feature / Pass 组织层` -`-> 之后再开正式的 URP-like package` - -换句话说,`SRP runtime v1` 已经把“managed pipeline 能创建、能被 native 持有、能参与 stage record”这条链路打通了; -`SRP runtime v2` 要解决的是另一件更关键的事: - -**让 managed 侧不再只是“在 ScriptableRenderPipeline 里手写 stage”,而是正式进入 Unity/URP 风格的 `Renderer / Feature / Pass` 组织模型。** - ---- - -## 2. 当前状态判断 - -从现有代码看,下面这些能力已经成立: - -1. `RenderGraph`、`CameraFramePlan`、stage dispatch、compile、execute 已经是工作状态。 -2. `ScriptableRenderPipelineHost` 已经能作为 native 宿主,组合 `stage recorder + fallback renderer`。 -3. `ManagedScriptableRenderPipelineAsset` 已经不只是 descriptor 空壳,能够通过 bridge 创建 managed asset runtime。 -4. `MonoScriptRuntime` 已经提供真实的 `ManagedRenderPipelineBridge`,不再只是测试 seam。 -5. `ScriptableRenderContext` 已经有最小可用能力,能: - - `RecordScene()` - - `RecordScenePhase(...)` - - `RecordSceneInjectionPoint(...)` - - `RecordFullscreenPass(...)` -6. managed pipeline 已经能通过 `ScriptableRenderContext` 驱动 native `NativeSceneRecorder` 和 fullscreen pass sequence。 -7. `ScriptableRenderPipelineAsset` 已经可以参与: - - `ConfigureCameraRenderRequest(...)` - - `ConfigureCameraFramePlan(...)` - - `GetDefaultFinalColorSettings()` - -也就是说,**SRP runtime 的“宿主打通”问题已经不是主矛盾了。** - -当前真正的主矛盾变成了: - -1. managed 侧还没有 `ScriptableRenderer` -2. managed 侧还没有 `ScriptableRendererFeature` -3. managed 侧还没有 `ScriptableRenderPass` -4. managed 侧还没有 `RenderPassEvent` -5. pipeline 仍然直接在自己内部录制 stage,缺少可组合的 renderer 层 - -这意味着你现在虽然已经能“用 C# 自定义渲染”,但它还不是 Unity/URP 那种成熟形态。 -现在的形态更接近: - -`Pipeline 直接写 RenderGraph 录制逻辑` - -而不是: - -`Pipeline -> Renderer -> Feature -> Pass` - ---- - -## 3. 为什么下一步必须先做这个 - -如果这一层不先补起来,后面会出现三个结构性问题: - -### 3.1 自定义能力会卡死在 `ScriptableRenderPipeline` - -当前用户要改渲染逻辑,主要方式还是: - -1. 继承 `ScriptableRenderPipeline` -2. 覆盖 `SupportsStageRenderGraph(...)` -3. 覆盖 `RecordStageRenderGraph(...)` -4. 直接手写 `RecordScenePhase / RecordSceneInjectionPoint / RecordFullscreenPass` - -这条路能做 demo,但不能支撑成熟工程。 - -因为它缺少: - -1. renderer 内部 pass 队列 -2. feature 注入点 -3. renderer data / pipeline data -4. 面向 camera/frame 的稳定数据载体 - -继续沿这条路堆功能,最后只会变成“能工作的脚本版 builtin forward”,而不是 Unity 意义上的 SRP/URP。 - -### 3.2 未来的阴影、后处理、体积、高斯没地方上移 - -你之前关心的那些东西,长期来看确实应该逐步从 C++ builtin 组织层往更高层移动,但它们要上移的不是裸 `Pipeline`,而是: - -1. `Renderer` -2. `RendererFeature` -3. `RenderPass` -4. `RendererData` - -如果这层不存在,那后面谈“把 shadow 组织权搬到 URP 层”“把 volumetric/gassian 搬到包层”,都会没有落点。 - -### 3.3 未来正式做 URP-like package 会缺承接面 - -真正的 URP-like package 不是只多几个 C# 类名,而是至少要有: - -1. `PipelineAsset` -2. `RendererData` -3. `Renderer` -4. `RendererFeature` -5. `RenderPassEvent` -6. `RenderPass` - -这几个层次如果不先在 SRP runtime 里长出来,后面直接开做 URP-like package 只会是壳子工程。 - ---- - -## 4. 本阶段目标 - -本阶段只做一件事: - -**把当前“managed pipeline 直接录 stage”的模型,升级成“managed pipeline 驱动 managed renderer,renderer 再组织 feature/pass”的模型。** - -收口后应该形成这样的关系: - -`ScriptableRenderPipelineAsset` -`-> ScriptableRenderPipeline` -`-> ScriptableRenderer` -`-> ScriptableRendererFeature` -`-> ScriptableRenderPass` -`-> ScriptableRenderContext` -`-> Native RenderGraph / NativeSceneRenderer` - -这一步完成后,你这个引擎才算真正进入“能继续往 Unity SRP/URP 方向长”的状态。 - ---- - -## 5. 本阶段明确不做的内容 - -这一阶段不做下面这些事: - -1. 不做 deferred pipeline。 -2. 不做 lightmap / baking。 -3. 不做 editor 侧 inspector、资源面板、renderer asset 可视化编辑。 -4. 不把阴影、体积、高斯、后处理全部迁到 C#。 -5. 不直接暴露 raw RHI、raw RenderGraph handle 给脚本层。 -6. 不为了兼容临时方案而长期保留双轨 API。 - -注意: - -这不是说这些东西以后不做,而是说**现在最值钱的一刀不是它们**。 -现在最值钱的是先把 renderer 组织层做对。 - ---- - -## 6. 目标架构 - -### 6.1 继续留在 C++ 的部分 - -1. `RHI` -2. `RenderGraph` -3. frame planning / stage dispatch / graph compile / execute -4. scene extraction / culling / frame data -5. native scene renderer 执行内核 -6. shadow / fullscreen / scene phase 的底层执行 primitive - -### 6.2 开始在 managed 侧稳定下来的部分 - -1. `ScriptableRenderPipelineAsset` -2. `ScriptableRenderPipeline` -3. `ScriptableRenderer` -4. `ScriptableRendererFeature` -5. `ScriptableRenderPass` -6. `RenderPassEvent` -7. `RenderingData / CameraData` 这类组织层数据 - -### 6.3 这一阶段的核心原则 - -1. C++ 负责执行内核,不负责未来用户级管线组织。 -2. managed 负责 renderer 组织,不直接持有底层 RHI 细节。 -3. `BuiltinForwardPipeline` 继续做默认 native backend,但不再是未来自定义渲染的最终承载层。 -4. shadow / volumetric / gaussian 这类东西后续上移时,上移的是“组织权”,不是把所有 draw code 都改成 C#。 - ---- - -## 7. 分阶段实施 - -### Phase A: 建立 managed renderer 基础抽象 - -先把 managed 侧最基础的几块搭出来: - -1. `ScriptableRenderer` -2. `ScriptableRendererFeature` -3. `ScriptableRenderPass` -4. `RenderPassEvent` -5. `RenderingData v1` -6. `CameraData v1` - -第一版不要追求大而全,只解决最小闭环: - -1. pass 能声明执行时机 -2. renderer 能收集 pass -3. feature 能往 renderer 注入 pass -4. pipeline 能把 camera/frame 数据传给 renderer - -### Phase B: 建立 renderer 的执行骨架 - -这一阶段要解决的是: - -1. `ScriptableRenderer.EnqueuePass(...)` -2. pass 按 `RenderPassEvent` 排序 -3. renderer 根据 event 把 pass 映射到当前已有的 native 能力: - - `RecordScenePhase(...)` - - `RecordSceneInjectionPoint(...)` - - `RecordFullscreenPass(...)` -4. renderer 能组织默认的主场景录制顺序 - -这里的关键不是“把所有 pass 都写复杂”,而是先把**组织模型**做出来。 - -### Phase C: 做一个最小前向 renderer - -在 managed 侧做第一条真正能跑的 renderer 主链: - -1. 默认 opaque pass -2. 默认 skybox pass -3. 默认 transparent pass -4. 默认 post-process/fullscreen pass -5. 允许 feature 在标准 injection point 前后插入 pass - -注意这里的“前向 renderer”还不是最终版 URP,只是: - -**一个 SRP runtime v2 上的 first-party renderer 验证样板。** - -### Phase D: 把 pipeline asset 和 renderer data 关系收清楚 - -这一阶段要让 `ScriptableRenderPipelineAsset` 不再只是“创建 pipeline”,还要能稳定持有 renderer 配置关系。 - -第一版建议先做到: - -1. asset 持有一个默认 renderer data -2. pipeline 从 asset 创建 renderer -3. renderer feature 由 renderer data 决定启用顺序 -4. camera/frame 规划仍然通过现有 planning context 接入 - -也就是说,要先有: - -`PipelineAsset -> RendererData -> Renderer` - -这条关系。 - -### Phase E: 替换 probe 级实现,形成正式 smoke path - -这一阶段不再满足于 probe/demo,而是要形成正式可回归的主链: - -1. 用 renderer/pass 模型重做当前 managed forward probe -2. 验证主场景 + fullscreen 仍然能工作 -3. 验证 pipeline 切换时生命周期正确 -4. 验证 native fallback 路径仍然可用 - -这一步收口后,才算本阶段真正完成。 - ---- - -## 8. 第一批要改的文件 - -### Managed 侧 - -优先会落在这些位置: - -1. `managed/XCEngine.ScriptCore/ScriptableRenderPipelineAsset.cs` -2. `managed/XCEngine.ScriptCore/ScriptableRenderPipeline.cs` -3. `managed/XCEngine.ScriptCore/ScriptableRenderContext.cs` -4. 新增 `managed/XCEngine.ScriptCore/Rendering/*` 或等价目录 -5. `managed/GameScripts/RenderPipelineApiProbe.cs` - -第一批大概率会新增这些类型: - -1. `ScriptableRenderer` -2. `ScriptableRendererFeature` -3. `ScriptableRenderPass` -4. `RenderPassEvent` -5. `RenderingData` -6. `CameraData` - -### Native 侧 - -native 这一阶段不是主战场,但仍可能需要补桥接: - -1. `engine/src/Scripting/Mono/MonoScriptRuntime.cpp` -2. `engine/include/XCEngine/Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.h` -3. `engine/src/Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.cpp` - -只有当 managed renderer 需要额外上下文时,才增加新的 internal call / bridge 数据。 - -原则是: - -**能用现有 `ScriptableRenderContext` 表达的,就不继续扩 raw native API。** - ---- - -## 9. 验收标准 - -这一阶段收口时,至少要同时满足下面这些条件: - -1. managed 侧存在正式的 `Renderer / Feature / Pass` 基础抽象。 -2. `ScriptableRenderPipeline` 不再是唯一的直接录制组织层。 -3. 一个 managed forward renderer 能通过 pass 队列稳定跑通主场景和 fullscreen。 -4. feature 可以在标准 injection point 上插入 pass。 -5. pipeline asset 能稳定持有 renderer 配置关系。 -6. native `RenderGraph`、`NativeSceneRenderer`、fallback renderer 路径不被破坏。 -7. editor 编译通过,并完成基础 smoke test。 - -只要上面任意一条不成立,这一阶段都不能算收口。 - ---- - -## 10. 完成本阶段之后,下一步才是什么 - -等这一阶段收口之后,下一步才适合正式进入更像 URP 的层级: - -1. `UniversalRenderPipelineAsset` -2. `UniversalRendererData` -3. `UniversalRenderer` -4. 官方 first-party `RendererFeature` -5. 更成熟的 `RenderPassEvent` -6. 逐步把 shadow / volumetric / gaussian / post-process 的组织权上移 - -也就是说: - -**不是现在不能往 Unity 的 SRP/URP 方向走,而是现在要先把“SRP runtime 的 renderer 层”补齐。** - -这一步做对了,后面你再做: - -1. URP-like package -2. deferred -3. lightmap -4. renderer asset 资源化 -5. editor 可视化配置 - -都会顺很多。 - ---- - -## 11. 下一刀从哪里下 - -下一步建议直接从 managed 侧开刀,顺序如下: - -1. 先加 `ScriptableRenderer / ScriptableRenderPass / RenderPassEvent` 基础类型。 -2. 再把当前 probe 级 `ManagedForwardRenderPipelineProbe` 改造成 renderer 驱动。 -3. 确认 renderer/pass 模型能复用现有 `ScriptableRenderContext`。 -4. 只有在 managed 侧明确缺上下文时,再补 native internal call。 - -简单说,下一刀不是继续修 C++ 渲染内核,而是: - -**开始正式搭 managed renderer 组织层。** diff --git a/docs/used/SRP_SharedNativeBackendSubstratePlan_2026-04-21_完成归档.md b/docs/used/SRP_SharedNativeBackendSubstratePlan_2026-04-21_完成归档.md deleted file mode 100644 index 7d3e477c..00000000 --- a/docs/used/SRP_SharedNativeBackendSubstratePlan_2026-04-21_完成归档.md +++ /dev/null @@ -1,90 +0,0 @@ -# SRP Shared Native Backend Substrate Plan - -日期:2026-04-21 - -## 背景 - -前两个小阶段已经完成两件事: - -1. native scene draw contract 从 `NativeSceneRenderer` 收口成了 `SceneDrawBackend` -2. native host/runtime bridge 中代表底层执行体的命名,从 `renderer` 收口成了 `backend` - -但当前还有一个更深的结构问题没有彻底收干净: - -- managed `rendererIndex` 已经在按 Unity/URP 思路选择 `ScriptableRendererData` / `ScriptableRenderer` -- native bridge 仍然保留了 `GetPipelineBackendAsset(int rendererIndex)` 这条链路 -- `MonoManagedRenderPipelineStageRecorder::ResolveSceneDrawBackend(...)` 也还在按 `rendererIndex` 去尝试解析 contextual backend asset - -这会留下一个错误暗示: - -- 好像不同 `ScriptableRenderer` 需要切换不同 native backend - -这和当前目标路线不一致。按 Unity 的 SRP + URP 思路,这一阶段应该明确成: - -- managed `ScriptableRenderer` 负责选择 pass / stage 组织 -- native C++ 提供共享的 backend substrate / scene draw substrate -- `rendererIndex` 只影响 managed renderer 选择,不负责切 native backend - -## 本阶段目标 - -把当前桥接模型彻底收口成 “shared native backend substrate”: - -- managed asset runtime 对外只暴露共享 native backend asset -- native stage recorder 不再按 `rendererIndex` 解析 backend asset -- `rendererIndex` 保留给 managed renderer 选择和 planning/recording 上下文 -- host/runtime/managed 代码中的接口语义与实现保持一致,不再留下 future per-renderer backend 的假入口 - -## 为什么现在做 - -如果这层不先收口,后面继续做: - -- 更完整的 `ScriptableRendererFeature` -- renderer data 资源生命周期 -- SRP asset / renderer data 序列化 -- 最终的 C# 可定制渲染管线 - -都会建立在一条含混的桥接语义上。到时候用户会天然误解成: - -- “renderer slot = native backend slot” - -这会直接污染后续 API、文档、调试认知和资源生命周期设计。 - -## 改动范围 - -只做 shared native backend substrate 收口,不新增渲染功能,不碰 deferred,不碰 new editor。 - -主要涉及: - -- `engine/include/XCEngine/Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.h` -- `engine/src/Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.cpp` -- `engine/src/Scripting/Mono/MonoScriptRuntime.cpp` -- `engine/include/XCEngine/Rendering/Pipelines/ScriptableRenderPipelineHost.h` -- `engine/src/Rendering/Pipelines/ScriptableRenderPipelineHost.cpp` - -如有必要,少量同步到 managed C# 命名或注释,但不改当前 URP 行为。 - -## 预期结果 - -重构后,当前 SRP 主链应当表达成: - -- `RenderPipelineAsset` / managed SRP asset 决定使用哪个共享 native backend substrate -- `ScriptableRendererData` / `ScriptableRenderer` 决定每个 camera request 用哪个 renderer,以及该 renderer 录哪些 pass -- `ScriptableRenderPipelineHost` 绑定一个共享 native backend,并把它提供给 stage recorder -- `MonoManagedRenderPipelineStageRecorder` 只复用这个共享 backend,不再偷偷实现 per-renderer backend 切换 - -## 实施步骤 - -1. 收口 `ManagedRenderPipelineAssetRuntime` 接口,去掉基于 `rendererIndex` 的 backend asset 解析语义 -2. 收口 `MonoManagedRenderPipelineStageRecorder` 的 backend 解析逻辑,只保留共享 backend 绑定/兜底创建 -3. 检查 `ScriptableRenderPipelineHost` 与 `ManagedScriptableRenderPipelineAsset` 调用链,确保语义一致 -4. 编译 `XCEditor` -5. 运行旧版 `editor/bin/Debug/XCEngine.exe` 冒烟至少 10 秒,并确认新的 `SceneReady` -6. 归档计划,提交并推送 - -## 验证标准 - -- `cmake --build . --config Debug --target XCEditor` 成功 -- 运行 `editor/bin/Debug/XCEngine.exe` -- 冒烟至少 10 秒 -- `editor/bin/Debug/editor.log` 出现新的 `SceneReady` -- 本阶段提交只包含 SRP/rendering 主线相关代码与计划文档 diff --git a/docs/used/SRP_URP_CameraDepthPrepassPlanningPlan_完成归档_2026-04-21.md b/docs/used/SRP_URP_CameraDepthPrepassPlanningPlan_完成归档_2026-04-21.md deleted file mode 100644 index 007df6fa..00000000 --- a/docs/used/SRP_URP_CameraDepthPrepassPlanningPlan_完成归档_2026-04-21.md +++ /dev/null @@ -1,69 +0,0 @@ -# SRP URP Camera Depth Prepass Planning Plan - -Date: 2026-04-21 - -## Goal - -Make the managed URP `DepthOnly` block fully usable instead of only structurally present. - -After the previous stage: - -- `UniversalRenderer` owns the default depth-prepass block -- managed passes can record into the `DepthOnly` stage - -But the managed side still cannot create the underlying `DepthOnlyRenderRequest` from the camera surface, so enabling the block alone does not produce an executable depth prepass. - -This stage closes that gap. - -## Current Problems - -1. `ScriptableRenderPipelinePlanningContext.RequestDepthOnlyStage()` only toggles stage existence based on an already-populated native request. -2. Managed URP has no planning API for "use the camera surface depth attachment as a default depth-prepass target". -3. `UniversalRenderer.depthPrepass` can be enabled in data, but that does not yet guarantee a valid `DepthOnly` request exists. - -## Scope - -### 1. Add a native planning helper for default camera depth prepass requests - -Required work: - -- derive a depth-only `RenderSurface` from the camera request surface -- preserve the camera surface dimensions, render area, sample description, depth attachment, and transition settings -- remove color attachments so the request becomes a true depth-only target -- fail cleanly if the camera surface cannot support a depth prepass - -### 2. Expose the helper through the managed planning context - -Required work: - -- add a managed planning API that requests a default `DepthOnly` stage from the camera surface -- keep the older boolean-only request API for compatibility - -### 3. Make `UniversalRenderer` use the new helper - -Required work: - -- when the renderer-owned depth-prepass block is enabled, request the default camera depth prepass through the new API -- clear the stage explicitly when the block is disabled - -## Out Of Scope - -- depth-normal prepass -- automatic depth-prepass requirement analysis -- depth texture consumer tracking -- deferred renderer - -## Done Criteria - -1. Managed planning can create a valid default `DepthOnlyRenderRequest` from the camera surface. -2. Enabling the renderer-owned depth-prepass block produces a real executable `DepthOnly` stage. -3. Old `XCEditor` builds in Debug. -4. Old editor smoke passes for at least about 10 seconds and a fresh `SceneReady` appears in `editor/bin/Debug/editor.log`. - -## Follow-Up - -Once this closes, the next SRP work can move to higher-value renderer composition again: - -- renderer variants / multiple renderer strategies -- richer prepass policies -- managed shadow block expansion diff --git a/docs/used/SRP_URP_DefaultNativeRendererBindingPlan_完成归档_2026-04-21.md b/docs/used/SRP_URP_DefaultNativeRendererBindingPlan_完成归档_2026-04-21.md deleted file mode 100644 index 4c1e646c..00000000 --- a/docs/used/SRP_URP_DefaultNativeRendererBindingPlan_完成归档_2026-04-21.md +++ /dev/null @@ -1,63 +0,0 @@ -# SRP URP Default Native Renderer Binding Plan - -Date: 2026-04-21 - -## Goal - -Remove the remaining managed backend-key declaration path from current URP -renderer data and make native scene-renderer binding fall back to an internal -default native renderer asset instead of asking managed C# for a string key. - -This keeps the native renderer substrate in C++, but stops routing current URP -through a Unity-unlike `"BuiltinForward"` key callback. - -## Why This Stage - -After the previous SRP cleanup: - -1. managed standalone-pass asset keys are gone; -2. managed URP owns camera request, frame planning, render-scene setup, - shadow/depth stage composition, and stage recording; -3. the remaining managed/native backend seam is - `GetPipelineRendererAssetKey*()`. - -Current repo state shows: - -1. `UniversalRendererData` still returns `"BuiltinForward"` from managed C#; -2. `MonoManagedRenderPipelineAssetRuntime` still resolves that method name and - converts the string key back into a native asset; -3. the only active builtin backend is still the default forward renderer - substrate. - -So the current C# backend-key hop is not buying real flexibility. It is mostly -an internal detour that keeps Unity-unlike ownership in the current URP path. - -## Scope - -Included: - -1. add an internal helper that resolves the default native pipeline renderer - asset in C++; -2. remove `GetPipelineRendererAssetKey*()` from current managed URP C# types; -3. remove the corresponding Mono method-resolution / invocation bridge; -4. make `MonoManagedRenderPipelineAssetRuntime::GetPipelineRendererAsset(...)` - use the internal default native renderer asset; -5. rebuild `XCEditor` and run old editor smoke. - -Not included: - -1. redesigning `NativeSceneRecorder` itself; -2. introducing deferred native scene renderer backends; -3. changing public SRP authoring APIs; -4. moving scene draw implementation out of C++. - -## Done Criteria - -1. current URP managed code no longer returns native backend keys; -2. Mono runtime no longer resolves `GetPipelineRendererAssetKey*()` from - managed assets; -3. managed SRP host / stage recorder still binds a valid native scene renderer - substrate through internal default binding; -4. `cmake --build . --config Debug --target XCEditor` passes; -5. old editor smoke passes for at least about 10 seconds and a fresh - `SceneReady` appears in `editor/bin/Debug/editor.log`. diff --git a/docs/used/SRP_URP_DefaultRendererAssetCompositionPlan_2026-04-22_完成归档.md b/docs/used/SRP_URP_DefaultRendererAssetCompositionPlan_2026-04-22_完成归档.md deleted file mode 100644 index 16cdedc5..00000000 --- a/docs/used/SRP_URP_DefaultRendererAssetCompositionPlan_2026-04-22_完成归档.md +++ /dev/null @@ -1,67 +0,0 @@ -# SRP / URP Default Renderer Asset Composition Plan - -时间:2026-04-22 - -## 背景 - -当前 `RendererBackedRenderPipelineAsset` 已经把 renderer data collection 的运行时管理收口了, -但 `UniversalRenderPipelineAsset` 的默认 renderer 资产组合仍然是隐式的: - -1. 新建 asset 时,`rendererDataList` 默认还是空 -2. 默认 renderer data 主要靠 runtime resolve/fallback 时再补出来 -3. 这会让 asset authoring 和 runtime bootstrap 混在一起 - -这不够像 Unity 风格。Unity 的 pipeline asset 虽然也有 default renderer/fallback 语义, -但“这个 asset 默认有哪些 renderer data”本身应该是 asset 侧明确表达的,而不是运行时临时补洞。 - -## 目标 - -把默认 renderer data list 正式收成 asset 默认组合。 - -阶段完成后要达到: - -1. `RendererBackedRenderPipelineAsset` 拥有明确的默认 renderer data list 重置入口 -2. `UniversalRenderPipelineAsset` 通过专门工厂表达默认 renderer 组合 -3. 新建 `UniversalRenderPipelineAsset` 时就有正式的默认 renderer data 列表 -4. runtime fallback 只负责容错,不再承担默认 authoring 组合表达 - -## 范围 - -本阶段只处理默认 renderer asset 组合表达,不做: - -- deferred renderer -- renderer inspector / editor UI -- public API 大改 -- 阴影与后处理能力扩展 - -## 实施步骤 - -### 1. 给 RendererBackedRenderPipelineAsset 增加默认重置入口 - -职责: - -- 允许 asset 明确重置到默认 renderer data list -- 把默认 list 构造和 runtime fallback 区分开 - -### 2. 抽出 Universal 默认 renderer data 工厂 - -职责: - -- 创建默认 `UniversalRendererData` -- 返回默认 renderer data list - -### 3. 让 UniversalRenderPipelineAsset 显式初始化默认组合 - -目标: - -- asset 构造时就有明确默认 renderer data -- 为未来 renderer variant / preset 演进保留稳定落点 - -## 完成标准 - -满足以下条件才算本阶段收口: - -1. `UniversalRenderPipelineAsset` 默认 renderer 组合不再依赖 runtime 隐式补全 -2. 默认 renderer data list 有专门工厂落点 -3. `XCEditor` 编译通过 -4. old editor 冒烟至少 10 秒通过 diff --git a/docs/used/SRP_URP_DefaultRendererCompositionContractPlan_2026-04-22_完成归档.md b/docs/used/SRP_URP_DefaultRendererCompositionContractPlan_2026-04-22_完成归档.md deleted file mode 100644 index 037f16e2..00000000 --- a/docs/used/SRP_URP_DefaultRendererCompositionContractPlan_2026-04-22_完成归档.md +++ /dev/null @@ -1,66 +0,0 @@ -# SRP / URP Default Renderer Composition Contract Plan - -时间:2026-04-22 - -## 背景 - -上一阶段已经让 `UniversalRenderPipelineAsset` 在构造时显式创建默认 renderer data list, -但当前默认组合 contract 仍然不完整: - -1. 默认组合仍然被拆成 “rendererDataList + 固定 `defaultRendererIndex = 0`” -2. `ResetRendererDataToDefault()` 隐式假定默认 renderer 永远在 0 号槽位 -3. 这会限制后续多 renderer variant / renderer preset 的正式表达 - -如果未来默认组合需要同时带出多个 renderer data,这两个信息本来就应该一起返回。 - -## 目标 - -把默认 renderer 资产组合正式收成 composition contract。 - -阶段完成后要达到: - -1. 默认 renderer data list 和默认 renderer index 一起表达 -2. `RendererBackedRenderPipelineAsset` 不再写死默认 index 为 0 -3. `UniversalRenderPipelineAsset` 的默认组合通过正式 composition 工厂返回 -4. 为未来多 renderer variant 的默认组合提供稳定接缝 - -## 范围 - -本阶段只处理默认 renderer composition contract,不做: - -- deferred renderer -- editor UI -- 运行时 renderer selection 语义变更 -- public API 大改 - -## 实施步骤 - -### 1. 新增 ScriptableRendererDataComposition - -职责: - -- 表达默认 renderer data list -- 表达默认 renderer index - -### 2. 接入 RendererBackedRenderPipelineAsset - -迁移内容: - -- 默认组合创建 -- `ResetRendererDataToDefault()` - -### 3. 更新 Universal 默认工厂 - -目标: - -- 明确返回默认 renderer composition -- 保持当前行为不变,但去掉固定 0 号假设 - -## 完成标准 - -满足以下条件才算本阶段收口: - -1. 默认 renderer 组合 contract 包含 list + default index -2. `RendererBackedRenderPipelineAsset` 不再在默认重置时硬编码 `defaultRendererIndex = 0` -3. `XCEditor` 编译通过 -4. old editor 冒烟至少 10 秒通过 diff --git a/docs/used/SRP_URP_ExplicitSceneSetupPolicyPlan_2026-04-21_完成归档.md b/docs/used/SRP_URP_ExplicitSceneSetupPolicyPlan_2026-04-21_完成归档.md deleted file mode 100644 index e62e397a..00000000 --- a/docs/used/SRP_URP_ExplicitSceneSetupPolicyPlan_2026-04-21_完成归档.md +++ /dev/null @@ -1,144 +0,0 @@ -# SRP / URP Explicit Scene Setup Policy Plan - -## 背景 - -上一阶段已经把 request-level 的一部分策略从 native default 挪到了 managed URP: - -- `Camera.clearMode` -- `Camera.stackType` -- `CameraRenderRequestContext.clearFlags` -- `UniversalRenderPipelineAsset` 中的默认 clear / shadow request policy - -但 `RenderSceneSetup` 这一层还没有真正收口。 -当前 `UniversalRenderer.ConfigureRenderSceneSetup(...)` 仍然只是调用: - -- `UseDefaultEnvironment()` -- `UseDefaultGlobalShaderKeywords()` - -这意味着: - -- URP 还没有显式决定“这帧环境到底是什么” -- URP 还没有显式决定“全局 shader keyword 到底开哪些” -- native default 仍然在替 URP 做 scene setup policy - -这和目标中的 Unity 风格 `SRP substrate + URP package policy` 不一致。 - -## 本阶段目标 - -把 scene setup 从“调用 native 默认策略”收成“managed URP 显式配置策略”。 - -本阶段只做 seam 收口,不做 deferred,不改 Render Graph 主执行骨架。 - -### 目标 1 - -扩展 `RenderSceneSetupContext`,让 managed 层拿到显式决策所需的最小上下文: - -- `clearFlags` -- `hasMainSceneDepthAttachment` -- `hasMainDirectionalShadow` - -### 目标 2 - -扩展 managed `Camera` 只读能力,让 URP 可以显式决定 skybox policy: - -- `projectionType` -- `skyboxEnabled` -- `hasSkyboxMaterial` -- `skyboxTopColor` -- `skyboxHorizonColor` -- `skyboxBottomColor` - -### 目标 3 - -扩展 `RenderSceneSetupContext` 的显式写入口: - -- `SetEnvironmentNone()` -- `UseCameraSkyboxMaterial()` -- `SetProceduralSkybox(...)` -- `SetGlobalShaderKeyword(string keyword, bool enabled)` - -### 目标 4 - -把 `UniversalRenderer.ConfigureRenderSceneSetup(...)` 改成显式逻辑,复刻当前 native default 行为: - -- 先清空 environment / global keywords -- 再根据 camera + request + main scene surface 条件显式设置 skybox -- 再根据 `hasMainDirectionalShadow` 显式设置 `XC_MAIN_LIGHT_SHADOWS` - -## 实施步骤 - -### Step 1. 补齐 managed / native scene setup bridge - -在 `RenderSceneSetupContext` 与 `MonoScriptRuntime` 之间增加新的只读属性和显式写接口。 - -要求: - -- 不引入兼容层 -- 不保留“旧 API + 新 API 并存但无人使用”的半弃用状态 -- scene setup 的显式接口命名要直接表达意图 - -### Step 2. 补齐 Camera 的只读 skybox / projection seam - -通过 internal call 暴露 managed 决策所需的 camera 状态。 - -要求: - -- 只补当前 scene setup 确实需要的读取能力 -- 不顺手扩一堆暂时无用的 Camera API - -### Step 3. 切换 UniversalRenderer 的 scene setup 实现 - -把当前两句 `UseDefault...` 换成显式策略代码。 - -要求: - -- 行为尽量与当前默认行为一致 -- 不再依赖 native default 来决定 keyword / environment -- feature 后续仍可以在 `ConfigureRenderSceneSetup(...)` 链路里继续覆写 - -## 收口标准 - -- `UniversalRenderer.ConfigureRenderSceneSetup(...)` 不再调用 native default environment / keyword policy -- URP 可以显式配置: - - environment none - - camera material skybox - - procedural skybox - - global shader keyword on/off -- `XC_MAIN_LIGHT_SHADOWS` 由 managed scene setup 显式决定 -- `XCEditor` Debug 编译通过 -- 旧版 editor 冒烟通过 - -## 非目标 - -本阶段不做以下内容: - -- deferred rendering -- shadow 算法本体迁移 -- gaussian / volumetric pass 迁移 -- Render Graph 结构大改 -- editor / new_editor 相关重构 - -## 完成情况 - -- 已补齐 managed `Camera` 的 scene setup 只读 seam: - - `projectionType` - - `skyboxEnabled` - - `hasSkyboxMaterial` - - `skyboxTopColor` - - `skyboxHorizonColor` - - `skyboxBottomColor` -- 已补齐 `RenderSceneSetupContext` 的显式 scene setup seam: - - `clearFlags` - - `hasMainSceneDepthAttachment` - - `hasMainDirectionalShadow` - - `SetEnvironmentNone()` - - `UseCameraSkyboxMaterial()` - - `SetProceduralSkybox(...)` - - `SetGlobalShaderKeyword(string, bool)` -- 已删除不再使用的 managed scene setup default / 清空兼容 API,避免双轨状态继续存在 -- `UniversalRenderer.ConfigureRenderSceneSetup(...)` 已切换为显式策略: - - 显式决定 skybox environment 是否启用 - - 显式决定材质天空盒 / procedural skybox - - 显式决定 `XC_MAIN_LIGHT_SHADOWS` -- `XCEditor` Debug 编译通过 -- 旧版 editor 冒烟通过 diff --git a/docs/used/SRP_URP_FeatureRuntimeControllerSplitPlan_2026-04-22_完成归档.md b/docs/used/SRP_URP_FeatureRuntimeControllerSplitPlan_2026-04-22_完成归档.md deleted file mode 100644 index cbff8a41..00000000 --- a/docs/used/SRP_URP_FeatureRuntimeControllerSplitPlan_2026-04-22_完成归档.md +++ /dev/null @@ -1,68 +0,0 @@ -# SRP / URP Feature Runtime Controller Split Plan - -时间:2026-04-22 - -## 背景 - -当前 renderer block 和 feature injection 骨架已经基本成形,但 feature 层还有最后一块不够整齐: - -1. `BuiltinGaussianSplatRendererFeature` / `BuiltinVolumetricRendererFeature` 已经走了 runtime controller 模式。 -2. `ColorScalePostProcessRendererFeature` 仍然直接持有 runtime pass,并且 pass 反向引用 feature。 -3. `RenderObjectsRendererFeature` 也仍然把配置解析、runtime pass 构建、enqueue 逻辑都混在 feature 里。 - -这导致 feature 层的边界还不统一: - -- 有的 feature 是“配置数据 + controller” -- 有的 feature 还是“配置数据 + runtime pass 直接混写” - -## 本阶段目标 - -把 `ColorScalePostProcessRendererFeature` 和 `RenderObjectsRendererFeature` 也收成统一的“配置数据 + runtime controller”模式。 - -目标结果: - -1. `ColorScalePostProcessPass` 不再反向持有 feature 资产引用。 -2. `RenderObjectsRendererFeature` 的配置解析与 runtime pass 持有分离。 -3. 当前内建 feature 的 runtime 边界风格统一。 - -## 范围 - -本阶段只处理 feature runtime 边界,不做: - -- public renderer feature API 改造 -- deferred rendering -- C++ host / RenderGraph 改造 - -## 实施步骤 - -### 1. 拆出 feature settings / controller - -新增: - -- `ColorScalePostProcessSettings` -- `UniversalColorScalePostProcessController` -- `RenderObjectsFeatureSettings` -- `UniversalRenderObjectsFeatureController` - -### 2. 接入现有 feature - -接入: - -- `ColorScalePostProcessRendererFeature` -- `RenderObjectsRendererFeature` - -### 3. 验证 - -要求: - -1. `XCEditor` Debug 构建通过 -2. old editor 冒烟至少 10 秒 -3. `editor.log` 出现 `SceneReady` - -## 完成标准 - -满足以下条件才算本阶段收口: - -1. `ColorScale` 与 `RenderObjects` 都改成配置数据 + runtime controller。 -2. 不再存在 runtime pass 直接反向持有 feature 资产的耦合。 -3. `XCEditor` 编译与 old editor 冒烟通过。 diff --git a/docs/used/SRP_URP_MainSceneFeatureInjectionPlan_2026-04-22_完成归档.md b/docs/used/SRP_URP_MainSceneFeatureInjectionPlan_2026-04-22_完成归档.md deleted file mode 100644 index 57ba6150..00000000 --- a/docs/used/SRP_URP_MainSceneFeatureInjectionPlan_2026-04-22_完成归档.md +++ /dev/null @@ -1,73 +0,0 @@ -# SRP / URP Main Scene Feature Injection Plan - -时间:2026-04-22 - -## 背景 - -当前 renderer block 主线已经基本清晰: - -- `ShadowCaster` -- `DepthPrepass` -- `MainScene` -- `PostProcess` -- `FinalOutput` - -`UniversalRenderer` 也已经拆出了对应 block owner。 - -但 `main scene` 这一面的 renderer feature 注入还不够干净: - -1. `RenderObjectsRendererFeature` 自己手写 scene-stage 与 pass-stage 判断。 -2. `BuiltinGaussianSplatRendererFeature` / `BuiltinVolumetricRendererFeature` 各自重复一套 native scene feature pass 注入逻辑。 -3. 缺少统一的 main-scene feature 注入骨架,后续继续加 scene feature 时还会复制这些判断和样板。 - -## 本阶段目标 - -补齐 `main scene` feature 注入的统一 utility / controller,把当前散落在 feature 里的 main-scene 注入规则收口。 - -目标结果: - -1. `RenderObjectsRendererFeature` 走统一的 main-scene pass 注入 helper。 -2. Gaussian / Volumetric 共用同一套 native scene feature controller。 -3. 后续再加 main-scene renderer feature 时,有稳定的落点可复用。 - -## 范围 - -本阶段只处理 main-scene feature 注入骨架,不做: - -- public renderer feature API 改造 -- deferred rendering -- C++ host / RenderGraph 改造 - -## 实施步骤 - -### 1. 新增 main-scene feature 注入 utility - -职责: - -- 统一判断当前是否处于 main scene stage -- 统一判断 pass 是否属于当前 stage -- 统一执行 enqueue - -### 2. 新增 native scene feature controller - -职责: - -- 持有 `NativeSceneFeaturePass` -- 统一 create / configure / enqueue -- 供 Gaussian / Volumetric 复用 - -### 3. 接入现有 feature - -接入: - -- `RenderObjectsRendererFeature` -- `BuiltinGaussianSplatRendererFeature` -- `BuiltinVolumetricRendererFeature` - -## 完成标准 - -满足以下条件才算本阶段收口: - -1. main-scene feature 注入规则不再散落在多个 feature 里重复实现。 -2. Gaussian / Volumetric 共用统一 native scene feature controller。 -3. `XCEditor` 编译通过,old editor 冒烟通过。 diff --git a/docs/used/SRP_URP_ManagedDirectionalShadowPlanningPlan_完成归档_2026-04-21.md b/docs/used/SRP_URP_ManagedDirectionalShadowPlanningPlan_完成归档_2026-04-21.md deleted file mode 100644 index 05f63939..00000000 --- a/docs/used/SRP_URP_ManagedDirectionalShadowPlanningPlan_完成归档_2026-04-21.md +++ /dev/null @@ -1,92 +0,0 @@ -# SRP/URP Managed Directional Shadow Planning Plan - -日期:2026-04-21 - -## 背景 - -当前主方向光阴影的 planning 仍然残留一条旧的 native policy-key 接缝: - -- `UniversalRenderPipelineAsset` - 通过 `GetDirectionalShadowPlanningPolicyAssetKey()` - 返回 `BuiltinDirectionalShadowPlanning` -- `ManagedScriptableRenderPipelineAsset` - 和 `MonoManagedRenderPipelineAssetRuntime` - 通过字符串 key 把 planning 再路由回 native -- managed `ConfigureCameraRenderRequest(...)` - 实际上只是修改 planning settings, - 真正的默认规划仍然依赖后置 policy-key 分发 - -这条接缝的问题和前面收掉的 scene setup / shadow execution 一样: - -- 默认所有权不够清晰 -- managed SRP/URP 只是“提供参数”,不是明确拥有规划行为 -- native policy-key registry 形成了不必要的中间层 - -## 目标 - -本阶段把主方向光阴影 planning 改成: - -- managed `ConfigureCameraRenderRequest(...)` - 负责最终 shadow settings / suppress 决策 -- native 默认阴影规划函数 - `ApplyDefaultRenderPipelineAssetCameraRenderRequestPolicy(...)` - 直接消费 managed 最终状态 -- 移除 `BuiltinDirectionalShadowPlanning` - 和 `GetDirectionalShadowPlanningPolicyAssetKey(...)` - 整条接缝 - -也就是说: - -- C++ 继续保留默认阴影规划实现 -- 但不再通过 policy-key 回跳 -- managed 变成默认输入所有者 - -## 实施步骤 - -1. 清理 managed API - -- 删除 `RendererBackedRenderPipelineAsset` - 上的 `GetDirectionalShadowPlanningPolicyAssetKey()` -- 删除 `UniversalRenderPipelineAsset` - 对 `BuiltinDirectionalShadowPlanning` 的覆写 -- 删除 native runtime 对这个 managed 方法的解析缓存 - -2. 清理 native registry - -- 删除 `RenderPipelineFactory` - 中的 `DirectionalShadowPlanningPolicy` registry -- 删除 builtin key `BuiltinDirectionalShadowPlanning` -- 删除 `ApplyDirectionalShadowPlanningPolicyByKey(...)` - 及相关注册/反注册 API - -3. 重构 camera request 流程 - -- `ManagedScriptableRenderPipelineAsset::ConfigureCameraRenderRequest(...)` - 不再预先调用 policy-key -- `MonoManagedRenderPipelineAssetRuntime::ConfigureCameraRenderRequest(...)` - 在 managed 回调结束后: - - 若明确 suppress,则清空 directional shadow - - 否则直接用最终 `DirectionalShadowPlanningSettings` - 调用默认 native 规划函数 - -这样 `ConfigureCameraRenderRequest(...)` -就成为阴影规划的真正 managed 所有权入口。 - -## 验收标准 - -- 仓库中不再出现: - - `BuiltinDirectionalShadowPlanning` - - `GetDirectionalShadowPlanningPolicyAssetKey` - - `DirectionalShadowPlanningPolicy` registry -- `UniversalRenderPipelineAsset` - 仍然能正确配置主方向光阴影 -- `XCEditor` 编译通过 -- 旧 editor 冒烟通过,并出现新的 `SceneReady` - -## 不在本阶段处理 - -- standalone pass asset key -- `BuiltinForward` native renderer substrate -- deferred / renderer graph capability 扩展 - -这些属于后续阶段,不和这一步混在一起。 diff --git a/docs/used/SRP_URP_ManagedFinalColorRequestPolicyPlan_2026-04-21_完成归档.md b/docs/used/SRP_URP_ManagedFinalColorRequestPolicyPlan_2026-04-21_完成归档.md deleted file mode 100644 index b4c7ed9b..00000000 --- a/docs/used/SRP_URP_ManagedFinalColorRequestPolicyPlan_2026-04-21_完成归档.md +++ /dev/null @@ -1,63 +0,0 @@ -# SRP / URP Managed Final Color Request Policy Plan - -## Background - -The previous SRP cleanup already moved several policies out of native defaults and into managed URP request/planning seams. -The remaining native baseline dependency was `final color policy`: - -- pipeline default final color settings -- camera final color overrides -- `requiresProcessing` source data - -That policy was still injected from native camera-frame baseline into `CameraFramePlan.finalColorPolicy`, which meant renderer-backed URP was not yet the real owner of final color policy. - -## Goal - -Move final color policy ownership from native camera-frame baseline into the managed request seam. - -After this phase: - -- URP resolves final color policy in `ConfigureRendererCameraRequest(...)` -- `CameraRenderRequest.finalColorPolicy` becomes the source of truth -- `CameraFramePlan` simply inherits the resolved request policy -- the native `UsesNativeCameraFramePlanBaseline*` bridge is removed - -## Design - -### 1. Camera read seam - -Expose managed read access for camera final color overrides: - -- `Camera.hasFinalColorOverrides` -- `Camera.GetFinalColorOverrideSettings()` - -### 2. Request write seam - -Expose managed write access for resolved request policy: - -- `CameraRenderRequestContext.SetResolvedFinalColorPolicy(...)` -- `CameraRenderRequestContext.ClearFinalColorPolicy()` - -### 3. URP policy ownership - -`UniversalRenderPipelineAsset.ConfigureRendererCameraRequest(...)` should: - -- read pipeline default final color settings -- read camera overrides -- resolve the final settings -- write the resolved policy back into request context - -## Completion - -- Status: completed on 2026-04-21 -- Added managed `FinalColorOverrideSettings` -- Added camera final color override read seam -- Added request-context final color policy write seam -- Moved URP final color resolution into managed request configuration -- Removed `UsesNativeCameraFramePlanBaseline*` from managed/native SRP bridge - -## Verification - -- `cmake --build . --config Debug --target XCEditor` -- old editor smoke passed -- `SceneReady elapsed_ms=6129 first_frame_ms=719` diff --git a/docs/used/SRP_URP_ManagedFullscreenAuxTextureBindingPlan_2026-04-22_完成归档.md b/docs/used/SRP_URP_ManagedFullscreenAuxTextureBindingPlan_2026-04-22_完成归档.md deleted file mode 100644 index 75f3ae08..00000000 --- a/docs/used/SRP_URP_ManagedFullscreenAuxTextureBindingPlan_2026-04-22_完成归档.md +++ /dev/null @@ -1,107 +0,0 @@ -# SRP / URP Managed Fullscreen Auxiliary Texture Binding Plan - -时间:2026-04-22 - -## 背景 - -上一阶段已经补上了 managed raster pass 对 depth 读依赖和 depth attachment 的声明能力,但这条链只走到了 render graph 依赖记录层,并没有真正把额外输入纹理绑定到 fullscreen/custom pass 的 shader 资源槽位。 - -当前实际问题: - -1. `RenderGraphRasterPassBuilder.UseTexture(...)` / `UseDepthTexture(...)` 只声明读依赖,不会形成 shader 资源绑定。 -2. `BuiltinVectorFullscreenPass` 仍然是手写单 `SourceColorTexture` + 单 sampler 的 descriptor layout。 -3. render graph callback 在执行 `RenderPassContext` 时只携带 `sourceColorView`,没有携带额外输入纹理的 SRV。 -4. render graph 对 transient depth 只建了 DSV,没有建可采样的 SRV,导致 graph 内部深度纹理即使声明为 read depth,也不能被 fullscreen shader 真正采样。 - -这会导致 managed SRP / URP 侧写出来的 fullscreen/custom pass 只能做“单输入 source color”效果,无法形成真正可用的自定义后处理输入面。 - -## 本阶段目标 - -把 managed fullscreen/custom pass 的“声明输入纹理”与“运行时真实绑定纹理”收成闭环,并保持架构方向继续对齐 Unity 风格: - -- C++ core 继续拥有 render graph、资源生命周期和执行调度。 -- managed SRP / URP 继续拥有 pass authoring 和 shader 输入声明。 -- fullscreen pass 不再手写固定单纹理布局,而是接到现有 shader 资源绑定计划能力上。 - -## 范围 - -本阶段只做 fullscreen/custom raster pass 输入绑定闭环,不做: - -- deferred pipeline -- shadow 系统迁移 -- surface/imported texture 多视图系统大改 -- renderer 多实现切换 - -## 实施步骤 - -### 1. Managed API 补齐“按 shader 资源名绑定输入纹理” - -在 `RenderGraphRasterPassBuilder` 增加显式输入绑定 API: - -- `BindTexture(string shaderResourceName, RenderGraphTextureHandle texture)` -- `BindDepthTexture(string shaderResourceName, RenderGraphTextureHandle texture)` - -这两个 API 需要同时完成两件事: - -1. 自动登记 graph read dependency -2. 自动登记 runtime shader resource binding request - -这样 managed 侧不会再出现“声明读了,但执行没绑上”的双轨状态。 - -### 2. Mono bridge 保留纹理绑定描述 - -在 `ManagedScriptableRenderContextState::RasterPassRecordRequest` 中新增 fullscreen 输入绑定记录,至少包含: - -- shader resource name -- render graph texture handle -- texture aspect(color / depth) - -并增加 internal call,把 managed builder 的输入绑定请求带到 native。 - -### 3. RenderGraph callback 执行上下文补额外 SRV - -扩展 `RenderPassContext`,让 callback/fullscreen pass 在执行时能拿到: - -- `sourceColorView` -- 额外输入纹理对应的已解析 SRV 列表 - -执行阶段需要按 handle 解析实际 view: - -- `sourceColorTexture` 特殊走现有 `sourceColorView` -- 其他 graph-managed 纹理走 `ResolveTextureView(..., ShaderResource)` -- 如果拿不到 shader-readable view,则 pass 执行失败 - -### 4. 补 transient depth SRV - -render graph runtime 目前对 transient depth 只创建 DSV,不创建 SRV。 - -需要修改 runtime texture allocation 逻辑,让 transient depth 也能生成 `shaderResourceView`,从而支持 fullscreen shader 读取 graph 内部 depth texture。 - -### 5. BuiltinVectorFullscreenPass 接入 shader 资源绑定计划 - -把 `BuiltinVectorFullscreenPass` 从“手写 3 个 descriptor set”改成: - -- 解析 shader pass 的资源绑定计划 -- 基于 binding plan 自动创建 set layout / pipeline layout -- 自动填充 `PassConstants` -- 自动填充 `SourceColorTexture` -- 自动填充 `LinearClampSampler` -- 对其余 `Texture2D` 资源按 resource name 从 runtime 输入绑定表里解析 - -也就是说,fullscreen vector pass 要变成一个真正的“受控小型 material/fullscreen pass”,而不是只支持 `SourceColorTexture` 的专用壳。 - -## 阶段完成标准 - -满足以下条件才算本阶段收口: - -1. managed fullscreen/custom pass 可以声明并真正绑定额外 color/depth 输入纹理。 -2. `BuiltinVectorFullscreenPass` 不再硬编码单纹理 descriptor layout。 -3. graph 内部 transient depth 能生成并提供可采样 SRV。 -4. `XCEditor` Debug 构建通过。 -5. old editor 冒烟至少 10-15 秒通过,日志出现 `SceneReady`。 - -## 已知边界 - -本阶段不主动扩 surface/imported texture 的多视图系统。 - -也就是说,若某些 imported texture 当前只有 RTV / DSV、没有独立 SRV 视图,这部分仍然不是完整方案;后续如果要让任意 imported color/depth 都可被 graph 中任意 pass 采样,需要单独做 imported resource multi-view / surface-view ownership 收口计划。 diff --git a/docs/used/SRP_URP_ManagedRenderGraphFullscreenDepthSurfacePlan_2026-04-21_完成归档.md b/docs/used/SRP_URP_ManagedRenderGraphFullscreenDepthSurfacePlan_2026-04-21_完成归档.md deleted file mode 100644 index d84eb1b0..00000000 --- a/docs/used/SRP_URP_ManagedRenderGraphFullscreenDepthSurfacePlan_2026-04-21_完成归档.md +++ /dev/null @@ -1,107 +0,0 @@ -# SRP / URP Managed Render Graph Fullscreen Depth Surface Plan - -Date: 2026-04-21 - -## Background - -The current SRP / URP managed render-graph surface is usable, but still too -thin in one important place: - -- managed fullscreen raster passes can read source color -- managed fullscreen raster passes can write color targets -- managed fullscreen raster passes cannot explicitly declare depth-texture reads -- managed fullscreen raster passes cannot explicitly keep or override a depth - attachment - -That is an architectural gap, not just a convenience issue. - -If a C# renderer feature wants to implement a depth-aware fullscreen effect -(for example edge detection, fog composition, SSAO-style composition, depth -conditioned blur, or custom outline logic), it currently has no correct managed -API surface to describe that dependency to the native render graph. - -So even though the native render graph already supports depth texture access, -the managed SRP surface still under-expresses the dependency graph. - -## Goal - -Close the first managed render-graph execution-surface gap by making managed -fullscreen raster recording depth-aware. - -After this stage: - -- managed C# fullscreen raster passes can declare depth texture reads -- managed C# fullscreen raster passes can explicitly set a depth attachment when - needed -- the Mono bridge preserves those declarations into the native render graph -- native graph recording uses depth-as-depth semantics instead of treating every - read texture as color - -## Scope - -Included: - -- `managed/XCEngine.ScriptCore/Rendering/Core/ScriptableRenderContext.cs` -- `managed/XCEngine.ScriptCore/Rendering/Graph/RenderGraphRasterPassBuilder.cs` -- `managed/XCEngine.ScriptCore/InternalCalls.cs` -- `engine/src/Scripting/Mono/MonoScriptRuntime.cpp` -- `engine/include/XCEngine/Rendering/RenderPassGraphContract.h` -- `engine/src/Rendering/RenderPassGraphContract.cpp` - -Not included: - -- deferred renderer work -- moving shadow / gaussian / volumetric runtime ownership in this phase -- generic managed compute-pass authoring in this phase -- editor / new_editor work - -## Implementation Plan - -1. Extend managed raster-pass builder with depth read / depth attachment APIs -2. Extend Mono internal calls and pending managed fullscreen-pass request state -3. Preserve depth reads separately from color reads through native bridge flush -4. Teach native raster-pass graph recording to issue depth reads with correct - render-graph aspect semantics -5. Rebuild old `XCEditor` -6. Run old editor smoke for at least 10 seconds and verify `SceneReady` -7. Archive the plan, commit, and push - -## Exit Criteria - -- managed code can call APIs equivalent to: - - `UseDepthTexture(...)` - - `SetDepthAttachment(...)` -- native bridge stores depth reads independently from color reads -- native render-graph recording emits `ReadDepthTexture(...)` for managed depth - inputs -- old `XCEditor` Debug build succeeds -- old editor smoke succeeds - -## Why This Stage First - -This is the right next step because it improves the managed SRP execution -surface without prematurely expanding into deferred rendering or feature -migration. - -It makes the current managed URP layer materially more useful for real custom -renderer features, while keeping the change scoped and architecture-driven. - -## Result - -Completed on 2026-04-21. - -- extended managed fullscreen raster-pass authoring with: - - `UseDepthTexture(...)` - - `SetDepthAttachment(...)` -- extended Mono internal-call bridge to preserve fullscreen managed depth reads - and optional depth attachment overrides -- extended native raster-pass graph recording to emit depth reads with - `ReadDepthTexture(...)` instead of treating all managed read dependencies as - color -- preserved existing fullscreen source-color path and final-output behavior - -## Verification - -- `cmake --build . --config Debug --target XCEditor` -- old editor smoke passed -- `SceneReady elapsed_ms=5621 first_frame_ms=602` diff --git a/docs/used/SRP_URP_ManagedRenderSceneSetupPlan_完成归档_2026-04-21.md b/docs/used/SRP_URP_ManagedRenderSceneSetupPlan_完成归档_2026-04-21.md deleted file mode 100644 index c11bb5fe..00000000 --- a/docs/used/SRP_URP_ManagedRenderSceneSetupPlan_完成归档_2026-04-21.md +++ /dev/null @@ -1,97 +0,0 @@ -# SRP/URP Managed RenderSceneSetup Plan - -日期:2026-04-21 - -## 背景 - -当前 `RenderSceneData` 的 scene setup 仍然主要通过 native policy key -`BuiltinDefaultSceneSetup` 组织: - -- `ScriptableRenderPipelineHost::ConfigureRenderSceneData(...)` - 先向 managed 资产查询字符串 key。 -- `UniversalRendererData` 仍然通过 - `GetRenderSceneSetupPolicyAssetKey()` 返回 - `BuiltinDefaultSceneSetup`。 -- 真正的默认行为仍然落在 native - `ApplyDefaultRenderPipelineSceneSetupPolicy(...)`。 - -这条路径的问题不是功能不对,而是所有权不对: - -- `URP` 默认行为没有显式挂在 managed `renderer` 上。 -- host 仍然依赖字符串分发,而不是显式回调。 -- 这和前面已经完成的 shadow/depth-prepass 接缝上提方向不一致。 - -## 目标 - -本阶段把 scene setup 的默认所有权从 native policy key -切换到 managed `SRP/URP` 显式回调,方向和前面的 -`DirectionalShadowExecutionContext` 一致: - -- C++ 保留默认 scene setup 实现,作为 substrate/fallback。 -- managed 新增 `RenderSceneSetupContext`。 -- `ScriptableRenderPipelineHost` 优先调用 managed 显式配置。 -- `RendererBackedRenderPipelineAsset -> ScriptableRendererData -> UniversalRenderer` - 形成明确的默认 scene setup 所有权链路。 -- 清理 managed 侧 `BuiltinDefaultSceneSetup` 字符串接缝。 - -## 实施步骤 - -1. 新增 native/managed `RenderSceneSetupContext` - -- native 维护 context state registry。 -- 暴露最小必要 API: - - `rendererIndex` - - `isConfigured` - - `UseDefaultSceneSetup()` - - `UseDefaultEnvironment()` - - `UseDefaultGlobalShaderKeywords()` - - `ClearEnvironment()` - - `ClearGlobalShaderKeywords()` - - `ClearSceneSetup()` - -2. 改造 `ManagedRenderPipelineAssetRuntime` - -- 新增 `ConfigureRenderSceneSetup(...)` 显式回调。 -- `ScriptableRenderPipelineHost::ConfigureRenderSceneData(...)` - 改为: - - 先尝试 managed explicit setup - - 未配置时再回退 native 默认实现 - -3. 改造 managed SRP/URP 链路 - -- `ScriptableRenderPipelineAsset` - 增加 `ConfigureRenderSceneSetup(...)` hook。 -- `RendererBackedRenderPipelineAsset` - 将请求分派到当前 `ScriptableRendererData`。 -- `ScriptableRendererData` / `ScriptableRenderer` - 增加对应 hook。 -- `UniversalRenderer` - 显式调用 `context.UseDefaultSceneSetup()`, - 让 URP 默认 scene setup 所有权落到 renderer。 - -4. 清理旧接缝 - -- 删除 managed 侧: - - `GetRenderSceneSetupPolicyAssetKey()` - - `GetRenderSceneSetupPolicyAssetKeyContextual()` -- 删除 host 对 `BuiltinDefaultSceneSetup` 的主路径依赖。 -- 若 native registry 无剩余使用点,则同步清理 - `RenderSceneSetupPolicy` registry。 - -## 验收标准 - -- `UniversalRendererData` 不再暴露 - `BuiltinDefaultSceneSetup` 字符串接缝。 -- `ScriptableRenderPipelineHost` 的 scene setup - 主路径为 managed explicit callback。 -- 默认场景渲染行为不回归。 -- `cmake --build . --config Debug --target XCEditor` - 通过。 -- 旧 `editor/bin/Debug/XCEngine.exe` - 冒烟至少 10 秒并在 `editor.log` 中出现新的 `SceneReady`。 - -## 完成后下一步 - -scene setup 接缝上提完成后,`SRP/URP` 这条主线会继续收紧 -managed 对 camera/light/environment 默认组织的所有权, -再进入下一批更贴近 Unity `URP` 的 renderer capability 收口。 diff --git a/docs/used/SRP_URP_ManagedShadowExecutionPlan_完成归档_2026-04-21.md b/docs/used/SRP_URP_ManagedShadowExecutionPlan_完成归档_2026-04-21.md deleted file mode 100644 index 7816e1a0..00000000 --- a/docs/used/SRP_URP_ManagedShadowExecutionPlan_完成归档_2026-04-21.md +++ /dev/null @@ -1,72 +0,0 @@ -# SRP URP Managed Shadow Execution Plan - -Date: 2026-04-21 - -## Goal - -Continue moving URP renderer ownership upward by replacing the URP-specific native directional-shadow execution policy key with an explicit managed shadow execution callback. - -After the previous stages: - -- `UniversalRenderer` owns default shadow-stage planning -- `UniversalRenderer` owns default shadow-caster pass composition -- renderer slots now behave like authored renderer entries - -But the bridge between the planned shadow stage and the final runtime shadow state is still routed through the native `"BuiltinDirectionalShadow"` execution policy key. This stage removes that URP-specific indirection. - -## Current Problems - -1. `UniversalRendererData` still exposes directional-shadow execution through a native asset-key fallback instead of a managed renderer callback. -2. `ScriptableRenderPipelineHost` can only ask managed URP for a directional-shadow execution policy key, not for an explicit renderer-owned shadow execution decision. -3. The current design leaves shadow execution ownership split awkwardly: - - managed URP decides stage existence - - native policy key decides how the planned main-light shadow becomes executable runtime state - -## Scope - -### 1. Add an explicit managed directional shadow execution context - -Required work: - -- add a native-to-managed context wrapper for directional shadow execution -- let managed code opt into the default native shadow execution wiring without exposing raw native resource internals -- allow managed code to explicitly clear shadow execution state when needed - -### 2. Add managed shadow execution hooks across the SRP stack - -Required work: - -- add a directional-shadow execution hook to `ScriptableRenderPipelineAsset` -- route renderer-backed assets through `ScriptableRendererData` -- let `ScriptableRenderer` and renderer features participate, mirroring existing camera-request / frame-plan ownership layering where useful - -### 3. Make URP use the new callback instead of the native execution policy key - -Required work: - -- make `UniversalRenderer` explicitly request the default main directional shadow execution when its shadow block is enabled -- remove the URP reliance on `"BuiltinDirectionalShadow"` as the primary renderer ownership seam -- keep native fallback behavior only as generic substrate, not as the URP default ownership path - -## Out Of Scope - -- cascaded shadows -- soft-shadow algorithm changes -- deferred renderer -- moving all lighting/shadow sampling shader setup into managed code -- editor-side URP renderer UI - -## Done Criteria - -1. Managed URP can explicitly configure directional shadow execution through a callback context. -2. `UniversalRendererData` no longer depends on a native directional-shadow execution policy key as its primary default path. -3. The old `XCEditor` builds in Debug. -4. The old editor smoke passes for at least about 10 seconds and a fresh `SceneReady` appears in `editor/bin/Debug/editor.log`. - -## Follow-Up - -Once this stage closes, the remaining renderer-core seams become smaller and cleaner: - -- render scene setup ownership -- richer renderer variants -- eventual managed SRP package composition above the current C++ render-graph substrate diff --git a/docs/used/SRP_URP_ManagedStandaloneStageFallbackPlan_完成归档_2026-04-21.md b/docs/used/SRP_URP_ManagedStandaloneStageFallbackPlan_完成归档_2026-04-21.md deleted file mode 100644 index bcc76c0d..00000000 --- a/docs/used/SRP_URP_ManagedStandaloneStageFallbackPlan_完成归档_2026-04-21.md +++ /dev/null @@ -1,70 +0,0 @@ -# SRP URP Managed Standalone Stage Fallback Plan - -Date: 2026-04-21 - -## Goal - -Remove the now-dead managed standalone-pass asset-key seam and make -`ShadowCaster` / `DepthOnly` native standalone fallback depend on actual -managed stage-recording capability instead of being silently hardwired. - -This keeps native fallback available for pipelines that still need it, while -allowing Unity-style URP renderer-owned shadow/depth passes to become the real -primary path. - -## Why This Stage - -After the previous SRP stages: - -1. renderer selection is already owned by managed URP; -2. render-scene setup is already managed-owned; -3. directional-shadow planning is already driven by managed camera request - configuration; -4. directional-shadow execution is already managed-configurable through direct - execution context callbacks; -5. `ScriptableRenderer` already knows how to record `ShadowCaster` and - `DepthOnly` through managed pass queues. - -But one architectural inconsistency remains: - -1. native host still exposes `GetCameraFrameStandalonePassAssetKey(...)`; -2. Mono runtime still resolves that method even though no current managed URP - code overrides it anymore; -3. host still carries contextual standalone-pass cache machinery for that seam; -4. native default `DepthOnly` standalone pass can still preempt managed URP - renderer recording if it stays installed. - -That means the shadow/depth path is not fully aligned with Unity-style -`ScriptableRenderer` ownership yet. - -## Scope - -Included: - -1. remove `GetCameraFrameStandalonePassAssetKey*` from managed runtime C++ / - Mono / C# surfaces; -2. delete native contextual standalone-pass cache / factory resolution path - that only existed for that seam; -3. make host-managed `ShadowCaster` / `DepthOnly` fallback installation depend - on whether the current stage recorder actually supports those stages; -4. keep `ObjectId` native standalone pass intact; -5. rebuild `XCEditor` and run old editor smoke. - -Not included: - -1. deferred renderer / GBuffer work; -2. moving object-id rendering into managed SRP; -3. moving native shadow raster implementation into C#; -4. editor-side renderer asset UX changes. - -## Done Criteria - -1. managed SRP runtime no longer exposes `GetCameraFrameStandalonePassAssetKey*`; -2. native host no longer keeps contextual standalone-pass resolution state for - managed pipelines; -3. `ShadowCaster` / `DepthOnly` native standalone fallback is retained only - when managed stage recording does not support those stages; -4. current URP renderer-owned shadow/depth passes become the active path; -5. `cmake --build . --config Debug --target XCEditor` passes; -6. old editor smoke passes for at least about 10 seconds and a fresh - `SceneReady` appears in `editor/bin/Debug/editor.log`. diff --git a/docs/used/SRP_URP_NativeSceneFeatureIdBridgePlan_2026-04-21_完成归档.md b/docs/used/SRP_URP_NativeSceneFeatureIdBridgePlan_2026-04-21_完成归档.md deleted file mode 100644 index cb9427fa..00000000 --- a/docs/used/SRP_URP_NativeSceneFeatureIdBridgePlan_2026-04-21_完成归档.md +++ /dev/null @@ -1,74 +0,0 @@ -# SRP / URP Native Scene Feature Id Bridge Plan - -## Background - -Current managed URP feature wrappers for gaussian splat and volumetric rendering -already live in the URP package, but the bridge into native runtime still uses -string names: - -- managed `NativeSceneFeaturePass("BuiltinGaussianSplatPass", ...)` -- managed `NativeSceneFeaturePass("BuiltinVolumetricPass", ...)` -- native host resolves feature passes by string comparison - -This is a transitional seam, not a stable long-term SRP/URP backbone. - -## Goal - -Replace string-based native scene feature dispatch with an explicit feature id bridge. - -After this phase: - -- managed URP records native scene features by enum/id -- native scene feature host resolves by stable feature id -- feature pass names remain only for logging / graph labeling -- future native-backed URP features can reuse the same bridge cleanly - -## Scope - -### Step 1. Add explicit native scene feature id type - -- add native enum for scene feature ids -- add managed enum mirror -- keep values explicit and stable - -### Step 2. Move recorder bridge from string to id - -- `ScriptableRenderContext.RecordNativeSceneFeaturePass(...)` -- Mono internal call bridge -- `NativeSceneRecorder::RecordFeaturePass(...)` -- `SceneRenderFeatureHost::RecordFeaturePass(...)` - -### Step 3. Bind builtin native features to explicit ids - -- `BuiltinGaussianSplatPass` -- `BuiltinVolumetricPass` -- managed `NativeSceneFeaturePass` -- managed builtin renderer features - -## Non-goals - -- changing gaussian splat runtime logic -- changing volumetric runtime logic -- moving shadow runtime in this phase -- deferred renderer work - -## Exit Criteria - -- no managed/native string-based native feature dispatch remains -- old editor `XCEditor` Debug build passes -- old editor smoke reaches `SceneReady` - -## Result - -Completed on 2026-04-21. - -- added explicit native/managed `NativeSceneFeaturePassId` enums -- replaced string-based native scene feature pass recording with id-based dispatch -- bound builtin gaussian splat / volumetric native passes to stable ids -- kept pass names only for logging and render-graph labeling - -## Verification - -- `cmake --build . --config Debug --target XCEditor` -- old editor smoke passed -- `SceneReady elapsed_ms=6277 first_frame_ms=637` diff --git a/docs/used/SRP_URP_NativeSceneFeatureRegistrationOwnershipPlan_2026-04-21_完成归档.md b/docs/used/SRP_URP_NativeSceneFeatureRegistrationOwnershipPlan_2026-04-21_完成归档.md deleted file mode 100644 index 29a422ad..00000000 --- a/docs/used/SRP_URP_NativeSceneFeatureRegistrationOwnershipPlan_2026-04-21_完成归档.md +++ /dev/null @@ -1,86 +0,0 @@ -# SRP / URP Native Scene Feature Registration Ownership Plan - -## Background - -Current native gaussian splat / volumetric scene feature passes have already -been lifted into a managed URP-facing wrapper layer: - -- `UniversalRendererData` owns the default feature list -- managed URP records native scene features explicitly -- native dispatch now uses stable `NativeSceneFeaturePassId` - -But one C++ ownership seam is still dirty: - -- `BuiltinForwardPipeline` constructor silently registers default native scene - feature passes -- that means pipeline instantiation itself still decides which first-party - native scene features exist -- the default backend asset / scene draw backend factory do not own that policy - -This keeps an avoidable hidden side effect inside the pipeline runtime object. - -## Goal - -Move default native scene feature registration out of -`BuiltinForwardPipeline` construction and into explicit backend -configuration/factory ownership. - -After this phase: - -- `BuiltinForwardPipeline` only owns execution/runtime state -- default native scene feature availability is configured by - backend asset/factory code -- default backend asset creation and default scene draw backend creation share - the same setup path - -## Scope - -### Step 1. Remove constructor-side feature registration - -- stop registering default scene features inside - `BuiltinForwardPipeline::BuiltinForwardPipeline()` - -### Step 2. Centralize configured builtin forward creation - -- introduce one explicit setup path for default builtin forward scene features -- use that path from `BuiltinForwardPipelineAsset` -- use that path from default scene draw backend creation / fallback creation - -### Step 3. Verify no behavior regression - -- rebuild old `XCEditor` -- run old editor smoke until `SceneReady` - -## Non-goals - -- changing gaussian splat runtime logic -- changing volumetric runtime logic -- moving shadow runtime in this phase -- deferred renderer work -- editor / new_editor work - -## Exit Criteria - -- `BuiltinForwardPipeline` constructor no longer performs implicit native scene - feature registration -- default builtin forward backend creation still exposes gaussian splat / - volumetric native features through the explicit factory path -- old editor `XCEditor` Debug build passes -- old editor smoke reaches `SceneReady` - -## Result - -Completed on 2026-04-21. - -- removed constructor-side default native scene feature registration from - `BuiltinForwardPipeline` -- centralized configured builtin forward creation in - `BuiltinForwardSceneSetup` -- routed builtin forward asset creation, default scene draw backend creation, - and host fallback creation through the same configured pipeline path - -## Verification - -- `cmake --build . --config Debug --target XCEditor` -- old editor smoke passed -- `SceneReady elapsed_ms=5493 first_frame_ms=614` diff --git a/docs/used/SRP_URP_ObjectIdToolingBoundaryPlan_2026-04-21_完成归档.md b/docs/used/SRP_URP_ObjectIdToolingBoundaryPlan_2026-04-21_完成归档.md deleted file mode 100644 index c698fe9b..00000000 --- a/docs/used/SRP_URP_ObjectIdToolingBoundaryPlan_2026-04-21_完成归档.md +++ /dev/null @@ -1,40 +0,0 @@ -# SRP/URP ObjectId Tooling Boundary Plan - -日期:2026-04-21 - -## 背景 - -`ObjectId` 这条链路当前主要用于 editor 视口拾取和选中描边,不属于 runtime SRP/URP 主渲染能力。 - -上一阶段把 standalone stage fallback ownership 从 host 往 backend 收口时,`ObjectId` 也一起落到了 builtin forward backend setup 中。这个方向不对: - -1. `ShadowCaster` / `DepthOnly` 属于 scene rendering stage,适合归到 backend 或 renderer 规划边界。 -2. `ObjectId` 更像 tooling render request,只在 editor / picking / selection 这条链路上使用。 -3. 如果把 `ObjectId` 继续和 shadow/depth 混在一起,会误导后续 SRP/URP 分层,让它看起来像 URP runtime feature。 - -## 目标 - -1. 把 `ObjectId` 从 builtin forward backend ownership 中移出。 -2. 把 `ObjectId` 重新挂到顶层 `CameraRenderer` 执行入口,作为通用 tooling pass 能力。 -3. 保持 editor 视口 object-id picking 行为不变。 - -## 实施步骤 - -1. 从 `BuiltinForwardSceneSetup` 中移除 `BuiltinObjectIdPass` 注册。 -2. 在 `CameraRenderer` 顶层 pipeline 绑定路径中补上 object-id tooling pass 注入。 -3. 重新编译旧版 `XCEditor`,运行旧版 editor 冒烟并确认 `SceneReady`。 - -## 完成判定 - -1. builtin forward backend 不再拥有 `ObjectId` fallback 注册。 -2. `CameraRenderer` 创建或切换顶层 pipeline 后,会显式提供 `ObjectId` tooling pass。 -3. 旧版 editor 编译通过并冒烟通过。 - -## 结果 - -1. `BuiltinForwardSceneSetup` 已移除 `BuiltinObjectIdPass` 注册,builtin forward backend 现在只保留 scene-stage 相关 fallback。 -2. `CameraRenderer` 在顶层 pipeline 绑定路径中统一注入 `ObjectId` standalone pass,语义上归为 tooling/editor request。 -3. 验证结果: - `cmake --build . --config Debug --target XCEditor` 通过。 - 旧版 editor 冒烟 15 秒通过。 - 日志结果:`SceneReady elapsed_ms=5621 first_frame_ms=602`。 diff --git a/docs/used/SRP_URP_ProbeScriptableObjectAuthoringCleanupPlan_2026-04-21_完成归档.md b/docs/used/SRP_URP_ProbeScriptableObjectAuthoringCleanupPlan_2026-04-21_完成归档.md deleted file mode 100644 index dd5e8595..00000000 --- a/docs/used/SRP_URP_ProbeScriptableObjectAuthoringCleanupPlan_2026-04-21_完成归档.md +++ /dev/null @@ -1,28 +0,0 @@ -# SRP/URP Probe ScriptableObject Authoring Cleanup Plan - -日期:2026-04-21 - -## 背景 - -当前生产代码里的 `RenderPipelineAsset`、`ScriptableRendererData`、`ScriptableRendererFeature` 已经切到 `ScriptableObject` 基底,并且默认创建路径开始优先走 `ScriptableObject.CreateInstance(...)`。 - -但 `managed/GameScripts/RenderPipelineApiProbe.cs` 里仍然残留一批直接 `new` 出 render asset、renderer data、renderer feature 的路径。这会让 probe 的 authoring 语义落后于当前 SRP/URP 主线,也会让后续继续按 Unity 风格推进时,测试/探针层和产品层出现分叉。 - -## 目标 - -1. 把 probe 中由脚本直接 author 的 render asset / renderer data / renderer feature 创建路径统一到 `ScriptableObject.CreateInstance(...)`。 -2. 对少数仍依赖带参构造的 probe `ScriptableObject` 子类,改成 Unity 风格的“无参创建 + 字段配置”。 -3. 不改 native backend,不改 `new_editor`,只收口 probe authoring 语义。 - -## 实施步骤 - -1. 在 `RenderPipelineApiProbe.cs` 中补一个轻量的 ScriptableObject probe 工具,统一创建和空值过滤。 -2. 迁移 probe asset 构造、runtime selection probe、renderer data 默认 feature 配置,移除直接 `new` 出 `ScriptableObject` 派生类型的路径。 -3. 处理少数带参构造的 probe feature / renderer data,使其可通过 `CreateInstance(...)` 创建后再配置。 -4. 重新编译 `XCEditor`,运行旧版 `editor/bin/Debug/XCEngine.exe` 冒烟至少 10 秒,确认 `SceneReady`。 - -## 完成判定 - -1. `RenderPipelineApiProbe.cs` 中 probe authoring 路径不再直接 `new` 出 render asset / renderer data / renderer feature。 -2. `cmake --build build --config Debug --target XCEditor` 成功。 -3. 旧版 editor 冒烟通过,日志出现新的 `SceneReady`。 diff --git a/docs/used/SRP_URP_RenderGraphImportedSurfaceMultiViewPlan_2026-04-22_完成归档.md b/docs/used/SRP_URP_RenderGraphImportedSurfaceMultiViewPlan_2026-04-22_完成归档.md deleted file mode 100644 index 379b5226..00000000 --- a/docs/used/SRP_URP_RenderGraphImportedSurfaceMultiViewPlan_2026-04-22_完成归档.md +++ /dev/null @@ -1,93 +0,0 @@ -# SRP / URP Render Graph Imported Surface Multi-View Plan - -时间:2026-04-22 - -## 背景 - -上一阶段已经打通了 managed fullscreen/custom pass 的额外纹理绑定链路,也补上了 transient depth 的 SRV 创建。 - -但 render graph 对 imported texture 的建模仍然是不完整的: - -1. `RenderGraph::TextureResource` 只保存了一个 `importedView` -2. `RenderGraphRuntimeResources::ResolveTextureView(...)` 对 imported texture 不区分请求的 view type,直接返回这一份 view -3. imported color / depth 一旦进入 graph,就无法稳定地在不同阶段之间切换 `RTV / DSV / SRV` -4. graph-managed imported surface 在 fullscreen/custom pass 中采样 imported depth / color 时,仍然可能拿到错误类型的 view - -这会导致 render graph 表面上已经接管 imported surface 的生命周期与状态调度,但在真正执行时还没有形成“纹理资源 + 多视图解析”的闭环。 - -## 本阶段目标 - -把 imported texture 从“只有一个盲视图指针”升级成“保留底层纹理资源,并能按需解析不同 view type”的运行时模型。 - -阶段完成后应满足: - -1. imported texture 能从 `RHIResourceView` 反查到底层 `RHITexture` -2. render graph 能为 imported texture 按请求解析 `RenderTarget / DepthStencil / ShaderResource / UnorderedAccess` -3. 对 graph-owned imported transitions,不再依赖导入时碰巧传进来的那一个 view 类型 -4. managed fullscreen/custom pass 采样 imported surface 时,color / depth 都能走统一的 `ResolveTextureView(..., ShaderResource)` 路径 -5. `XCEditor` Debug 构建通过,old editor 冒烟至少 10-15 秒并出现 `SceneReady` - -## 范围 - -本阶段只收口 imported texture multi-view 运行时能力,不做: - -- deferred pipeline -- shadow 系统迁移 -- editor 侧功能扩展 -- imported buffer multi-view 建模 -- RenderSurface 大规模重做 - -## 实施步骤 - -### 1. 抬升 texture-backed view 抽象 - -在 `RHIResourceView` 抽象层增加统一接口,让上层可以判断一个 view 是否绑定到某个 `RHITexture`。 - -要求: - -- `RHIResourceView` 暴露 `GetTextureResource()` -- Vulkan / OpenGL 返回各自已经持有的 texture 指针 -- D3D12 纹理视图补齐 `D3D12Texture*` 存储,并通过该接口返回 -- buffer view 继续返回空指针 - -### 2. 扩展 render graph imported texture 元数据 - -在 render graph builder / compiler / runtime 之间,把 imported texture 的“底层纹理资源”一起传下去,而不是只传一份 primary view。 - -要求: - -- `RenderGraph::TextureResource` 记录 imported texture 指针 -- `CompiledRenderGraph::CompiledTexture` 同步保存该信息 -- compiler 对 graph-owned imported transitions 增加更严格校验: - `graphOwnsTransitions == true` 时必须导入 texture-backed view - -### 3. 重做 imported texture runtime view 解析 - -把 imported texture 的运行时解析从“固定返回 importedView”改成: - -1. 如果 primary imported view 类型正好匹配请求,则直接返回 primary view -2. 如果类型不匹配,但有底层 imported texture,则按需创建目标 view -3. 运行时缓存这些按需创建的派生 view,并在 graph 执行结束后释放 - -要求: - -- `ResolveTextureView(handle, viewType)` 对 imported / transient 路径统一 -- 派生 view 创建走 `RHIDevice`,不允许在 render graph 里做后端特化 cast -- depth texture 不创建 RTV,color texture 不创建 DSV -- 失败时返回空指针,由上层执行链路报错 - -### 4. 完成验证与归档 - -执行以下验证闭环: - -1. `cmake --build . --config Debug --target XCEditor` -2. 运行 old editor 冒烟 15 秒 -3. 检查 `editor/bin/Debug/editor.log` 出现 `SceneReady` -4. plan 归档到 `docs/used` -5. 使用规范 Conventional Commit 提交并推送 - -## 风险与边界 - -1. swapchain backbuffer 是否支持 SRV,取决于后端和资源创建方式;本阶段不为此额外改造 swapchain 资源描述 -2. 如果某个 imported view 不是 texture-backed view,本阶段不会伪造多视图能力 -3. 本阶段先解决“可正确建模并解析 imported texture view”的根问题,不扩展更高层 surface API diff --git a/docs/used/SRP_URP_RendererBlockFormalizationPlan_2026-04-22_完成归档.md b/docs/used/SRP_URP_RendererBlockFormalizationPlan_2026-04-22_完成归档.md deleted file mode 100644 index f3b825e4..00000000 --- a/docs/used/SRP_URP_RendererBlockFormalizationPlan_2026-04-22_完成归档.md +++ /dev/null @@ -1,90 +0,0 @@ -# SRP / URP Renderer Block Formalization Plan - -时间:2026-04-22 - -## 背景 - -当前渲染主线已经完成了两件更底层的事情: - -1. C++ 侧的 RenderGraph / runtime resource / stage contract 已经能稳定承接 managed SRP。 -2. managed 侧也已经有 `ScriptableRenderer / RendererFeature / RenderPass` 这套骨架,并且 post-process / final output 的 graph 绑定已经打通。 - -但 renderer 这一层还有一个明显的问题没有收口: - -- `ScriptableRenderer` 现在还是把所有 pass 丢进一个总队列,然后在录制时按 `CameraFrameStage` 逐个扫描、过滤。 -- 这意味着 renderer 的“组织单位”依然是 stage,而不是 renderer 自己拥有的 block。 -- 对后续把阴影、体积、Gaussian、更多 scene feature 逐步迁到 URP 层不够友好,因为缺少稳定的 block 落点。 - -这一步的目标不是继续扩 RenderGraph 功能,也不是开始 deferred,而是先把 managed renderer 的组织方式收紧。 - -## 本阶段目标 - -把 managed `ScriptableRenderer` 从“单队列 + stage 过滤”重构成“显式 renderer block 录制”,并保持当前 C++ stage 边界不变。 - -目标结果: - -1. `ScriptableRenderer` 内部显式识别 `ShadowCaster / DepthPrepass / MainOpaque / MainSkybox / MainTransparent / PostProcess / FinalOutput`。 -2. stage 仍然只是 native 执行边界,renderer 内部真正按 block 组织与录制。 -3. `UniversalRenderer` 后续迁移阴影、体积、Gaussian 等逻辑时,有稳定的 block 入口可以承接。 -4. 不引入新的兼容层,不保留“旧逻辑先留着”的临时路线。 - -## 范围 - -本阶段只做 managed SRP / URP 组织重构,不做: - -- deferred rendering -- shadow 算法升级 -- editor 侧面板与资源工作流 -- ObjectId 这类 editor-only 特性迁移 - -## 实施步骤 - -### 1. 补齐 renderer block 基础类型 - -新增 renderer block 枚举与辅助工具,统一维护: - -- pass event -> renderer block -- renderer block -> camera frame stage -- renderer block 的运行时范围描述 - -### 2. 重构 ScriptableRenderer 的录制入口 - -保留 active pass queue 作为排序输入,但录制逻辑改为: - -1. 先按 `RenderPassEvent` 排序构建 active pass queue -2. 再根据 event range 构建 renderer block range -3. 按当前 stage 对应的 block 顺序执行 - -也就是说: - -- `MainScene` 不再是“扫完整队列找属于 MainScene 的 pass” -- 而是显式录制 `MainOpaque -> MainSkybox -> MainTransparent` - -### 3. 清理 stage-driven 痕迹 - -重点清理: - -- `ScriptableRenderer` 内部 `renderPass.SupportsStage(...)` 式的全队列过滤 -- 把 `SupportsRendererRecording / RecordRenderer` 改成基于 block 判断 - -保留: - -- native C++ 仍然通过 `CameraFrameStage` 调用 managed -- `ScriptableRenderPass.SupportsStage(...)` 作为公共 API 兼容入口,但内部改为基于 block 推导 - -### 4. 验证主线不回退 - -要求: - -1. `XCEditor` Debug 构建通过 -2. old editor 冒烟运行至少 10 秒 -3. `editor.log` 出现 `SceneReady` - -## 完成标准 - -满足以下条件才算本阶段收口: - -1. `ScriptableRenderer` 不再通过“单队列 + stage 过滤”驱动录制。 -2. renderer block 成为 managed renderer 内部的正式组织单位。 -3. `UniversalRenderer` 当前默认路径行为不回退。 -4. `XCEditor` 构建与 old editor 冒烟通过。 diff --git a/docs/used/SRP_URP_RendererCoreShadowPrepassPlan_完成归档_2026-04-21.md b/docs/used/SRP_URP_RendererCoreShadowPrepassPlan_完成归档_2026-04-21.md deleted file mode 100644 index c518f22a..00000000 --- a/docs/used/SRP_URP_RendererCoreShadowPrepassPlan_完成归档_2026-04-21.md +++ /dev/null @@ -1,75 +0,0 @@ -# SRP URP Renderer Core Shadow Prepass Plan - -Date: 2026-04-21 - -## Goal - -Continue aligning the current managed rendering stack with Unity-style SRP + URP ownership: - -- `UniversalRenderer` owns the default shadow and depth-prepass core blocks -- `ScriptableRendererFeature` injects or explicitly overrides stage existence through planning hooks -- standalone stage fallback remains secondary instead of being the primary owner of URP core blocks - -This stage does not add deferred rendering. - -## Current Problems - -The current code still has three architectural gaps: - -1. `UniversalRenderer` explicitly owns final output, but shadow and depth-prepass core blocks are still not renderer-owned managed defaults. -2. `ScriptableRenderer.FinalizeCameraFramePlan()` infers standalone stages from the queued pass list without respecting explicit shadow/depth planning decisions, so a feature-level `ClearShadowCasterStage()` can be silently undone later. -3. `UniversalRendererData` still exposes native standalone pass keys for `ShadowCaster` and `DepthOnly`, so the managed renderer core is not yet the obvious primary ownership seam. - -## Scope - -### 1. Make shadow / depth stage planning explicit - -Required work: - -- expose explicit standalone-stage planning state to managed code -- skip inferred shadow/depth stage requests when the renderer or a feature has already configured those stages explicitly -- preserve the existing inferred path only for renderers that still rely on implicit standalone-stage discovery - -### 2. Move default shadow / depth core passes into `UniversalRenderer` - -Required work: - -- add default managed `DrawObjectsPass` instances for: - - `ShadowCaster` - - `DepthOnly` -- make `UniversalRenderer` decide whether those blocks exist -- keep shadow stage tied to actual shadow-plan availability -- keep depth prepass opt-in instead of enabling it unconditionally - -### 3. Reduce native fallback ownership in URP data - -Required work: - -- stop treating native standalone asset keys as the primary URP shadow/depth implementation path -- make `UniversalRendererData` describe the renderer-owned default blocks instead -- keep fallback compatibility only where it is still useful during transition - -## Out Of Scope - -- deferred renderer -- GBuffer -- editor-side renderer configuration UX -- moving every shadow implementation detail out of native code -- multiple URP renderer variants in this stage - -## Done Criteria - -1. `UniversalRenderer` owns default managed shadow and depth-prepass block composition. -2. Feature-level explicit shadow/depth stage planning is no longer overwritten by generic queue inference. -3. URP shadow/depth defaults no longer depend on `UniversalRendererData.GetCameraFrameStandalonePassAssetKey(...)` as the main ownership seam. -4. Old `XCEditor` builds in Debug. -5. Old editor smoke passes for at least about 10 seconds and a fresh `SceneReady` appears in `editor/bin/Debug/editor.log`. - -## Follow-Up - -After this stage closes, the next SRP stage can move higher toward Unity-style renderer composition: - -- renderer variants / multiple renderer assets -- managed shadow block enrichment -- managed prepass expansion -- eventually, SRP-facing C# pipeline composition with clearer URP package layering diff --git a/docs/used/SRP_URP_RendererDataCollectionPlan_2026-04-22_完成归档.md b/docs/used/SRP_URP_RendererDataCollectionPlan_2026-04-22_完成归档.md deleted file mode 100644 index adbeecb6..00000000 --- a/docs/used/SRP_URP_RendererDataCollectionPlan_2026-04-22_完成归档.md +++ /dev/null @@ -1,64 +0,0 @@ -# SRP / URP Renderer Data Collection Plan - -时间:2026-04-22 - -## 背景 - -上一阶段已经把 `ScriptableRendererData` 里的 `rendererFeatures` 生命周期收成了 -`ScriptableRendererFeatureCollection`,但 pipeline asset 这一层还有同类问题: - -1. `RendererBackedRenderPipelineAsset` 直接持有并管理 `rendererDataList/defaultRendererIndex` -2. renderer data 的默认创建、fallback 解析、重复引用释放、runtime hash 都散落在 asset 类里 -3. 这会让 asset 同时承担 authoring 表达和 runtime collection 管理两类职责 - -这和上一阶段收 `rendererFeatures` 之前的状态很像,也不利于后续继续往 Unity 风格的 -多个 renderer variant 演进。 - -## 目标 - -把 asset 层的 renderer data 管理正式收成一个 collection 边界。 - -阶段完成后要达到: - -1. `RendererBackedRenderPipelineAsset` 不再散写 renderer data 列表生命周期细节 -2. 默认 renderer data 创建、index fallback、runtime hash、release 都有统一落点 -3. 不改变现有外部 authoring 入口,仍然保留 `rendererDataList/defaultRendererIndex` -4. 为后续继续做 Unity 风格的 renderer variant / renderer profile 留出干净接缝 - -## 范围 - -本阶段只处理 renderer data collection 收口,不做: - -- deferred renderer -- editor inspector / 面板 -- public API 大改 -- 阴影或 post-process 功能扩展 - -## 实施步骤 - -### 1. 新增 ScriptableRendererDataCollection - -职责: - -- 统一解析 `rendererDataList` -- 统一保证默认 renderer data 存在 -- 统一处理 `defaultRendererIndex` fallback -- 统一计算 collection runtime hash -- 统一释放 renderer data runtime 资源 - -### 2. 接入 RendererBackedRenderPipelineAsset - -迁移内容: - -- renderer data 解析 -- renderer 选择 -- runtime version 同步 -- release 流程 - -## 完成标准 - -满足以下条件才算本阶段收口: - -1. `RendererBackedRenderPipelineAsset` 不再自己散写 renderer data collection 细节 -2. `XCEditor` 编译通过 -3. old editor 冒烟至少 10 秒通过 diff --git a/docs/used/SRP_URP_RendererExplicitStagePlanningPlan_2026-04-22_完成归档.md b/docs/used/SRP_URP_RendererExplicitStagePlanningPlan_2026-04-22_完成归档.md deleted file mode 100644 index 09dec203..00000000 --- a/docs/used/SRP_URP_RendererExplicitStagePlanningPlan_2026-04-22_完成归档.md +++ /dev/null @@ -1,82 +0,0 @@ -# SRP / URP Renderer Explicit Stage Planning Plan - -时间:2026-04-22 - -## 背景 - -当前 managed `ScriptableRenderer / ScriptableRendererFeature / ScriptableRenderPass` 骨架已经可用, -但 renderer 这一层还有一个不干净的旧接缝: - -1. `ScriptableRenderer.FinalizeCameraFramePlan(...)` 仍然会根据 active pass queue 自动推断 - `ShadowCaster / DepthOnly / PostProcess / FinalOutput` stage -2. 这个推断依赖一份“planning 阶段伪造的 `RenderingData(MainScene)`”,并不是真正的 renderer planning contract -3. 结果是 renderer / feature 看起来已经在控制管线,但 stage ownership 仍然有一部分藏在基类启发式里 - -这不符合要对齐 Unity SRP / URP 的目标。 - -Unity 风格应该是: - -- renderer / feature 显式决定本帧需要哪些阶段 -- native C++ 只负责 stage 执行、RenderGraph、scene draw substrate -- 不能再让基类偷偷扫描 pass queue 替上层“猜阶段” - -## 本阶段目标 - -把 managed renderer 的 stage planning 从“隐式推断”收成“显式声明”: - -1. 去掉 `ScriptableRenderer` 基类里的 stage 推断逻辑 -2. 让真正拥有 pass 的 renderer / feature 自己在 planning hook 中申请所需 stage -3. 保证现有 Universal 默认路径和 probe/sample 路径仍然能工作 -4. 完成后,renderer-driven SRP 的组织权更清晰,后续再继续往 renderer pass / renderer feature 主线推进 - -## 范围 - -本阶段只处理 managed renderer 的显式 stage planning,不做: - -- deferred pipeline -- shadow 资源系统迁移 -- RenderGraph 新功能扩展 -- editor 逻辑 - -## 实施步骤 - -### 1. 清理 `ScriptableRenderer` 基类隐式推断 - -删除以下旧逻辑: - -- `CreatePlanningRenderingData(...)` -- `ApplyInferredFullscreenStageRequests(...)` -- `ApplyInferredStandaloneStageRequests(...)` -- `HasQueuedPassForStage(...)` - -`FinalizeCameraFramePlan(...)` 保持为空默认实现,不再隐式改 plan。 - -### 2. 把 stage planning 放回真正拥有 pass 的类型 - -至少补齐: - -- `ColorScalePostProcessRendererFeature` -- GameScripts 里的 fullscreen probe feature / renderer - -原则: - -- post-process feature 显式请求 `PostProcess` -- 如需把结果交给 `FinalOutput`,则显式申请 graph-managed output color -- 如有 final-output pass,则显式请求 `FinalOutput` - -### 3. 验证当前主线不回退 - -要求: - -1. `XCEditor` Debug 构建通过 -2. old editor 冒烟 15 秒通过 -3. `editor.log` 出现 `SceneReady` - -## 完成标准 - -满足以下条件才算收口: - -1. managed renderer 的 stage planning 不再依赖基类扫描 pass queue 的启发式 -2. 现有 Universal 默认 renderer 仍可运行 -3. probe/sample fullscreen renderer 走显式 planning 后仍能编译通过 -4. `XCEditor` 编译和 old editor 冒烟通过 diff --git a/docs/used/SRP_URP_RendererFeatureAuthoringModelPlan_2026-04-21_完成归档.md b/docs/used/SRP_URP_RendererFeatureAuthoringModelPlan_2026-04-21_完成归档.md deleted file mode 100644 index 8855058d..00000000 --- a/docs/used/SRP_URP_RendererFeatureAuthoringModelPlan_2026-04-21_完成归档.md +++ /dev/null @@ -1,103 +0,0 @@ -# SRP URP Renderer Feature Authoring Model Plan - -Date: 2026-04-21 - -## Background - -The last few SRP stages already cleaned up the following seams: - -- shared native backend substrate ownership -- renderer feature lifecycle invalidation -- renderer feature runtime-state synchronization - -However, the managed URP side still has one architecture mismatch against the -Unity-style direction: - -- `ScriptableRendererData` still exposes two feature ownership paths: - - persistent configured field: `rendererFeatures` - - runtime factory seam: `CreateRendererFeatures()` - -That dual model makes the current design harder to reason about: - -- the renderer-data asset is not the single source of truth for feature config -- feature lifetime semantics are blurred between "configured object" and - "runtime-created helper" -- project probes and managed probes still encode a non-Unity authoring style -- future editor integration and SRP asset editing will inherit this ambiguity - -If the engine is meant to converge toward a Unity-style `SRP + URP` model, -`ScriptableRendererData` should own a stable set of feature configuration -objects, and runtime invalidation should rebuild renderer runtime state from -that configured list rather than from an alternate feature factory seam. - -## Goal - -Make the managed URP renderer feature model closer to Unity: - -- `rendererFeatures` becomes the single configured source of truth -- `ScriptableRendererData` no longer creates an alternate feature collection via - `CreateRendererFeatures()` -- renderer runtime rebuild continues to work by snapshotting and releasing the - configured feature instances correctly -- `ScriptableRendererFeature` is modeled as a managed engine object rather than - a plain helper class -- managed probes and project probes are migrated to explicit feature - configuration instead of runtime feature factory overrides - -## Why Now - -This is the right point to do it because: - -- the lifecycle and dirty/version chain is already stable enough -- the next SRP stages need a clearer asset/config model, not more invalidation - patches -- future renderer authoring, editor exposure, and custom URP feature workflows - all depend on a clean ownership model - -If this seam stays open, later SRP work will keep mixing: - -- asset configuration logic -- runtime cache rebuild logic -- probe-only factory patterns - -That would move the codebase away from the Unity-style architecture the project -is aiming for. - -## Scope - -This stage stays focused on managed SRP/URP ownership cleanup. - -Included: - -- `managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRendererData.cs` -- `managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRendererFeature.cs` -- affected managed probes under `managed/GameScripts/RenderPipelineApiProbe.cs` -- affected project probes under `project/Assets/Scripts/ProjectRenderPipelineProbe.cs` - -Not included: - -- deferred rendering -- native render backend changes -- new editor work - -## Implementation Plan - -1. Collapse renderer feature ownership onto `rendererFeatures` -2. Remove the `CreateRendererFeatures()` seam from `ScriptableRendererData` -3. Keep runtime rebuild safe by retaining the last runtime-bound feature - snapshot for disposal -4. Make `ScriptableRendererFeature` inherit from managed engine `Object` -5. Migrate probes to constructor/config-field based feature assignment -6. Rebuild `XCEditor` -7. Run old editor smoke for at least 10 seconds and verify a fresh - `SceneReady` -8. Archive the plan, commit, and push - -## Expected Result - -After this stage: - -- renderer data is the authoritative owner of renderer feature configuration -- runtime invalidation semantics remain intact but are simpler to reason about -- custom URP-style feature authoring is closer to the Unity mental model -- the next SRP stages can build on a cleaner managed asset/config substrate diff --git a/docs/used/SRP_URP_RendererFeatureCollectionPlan_2026-04-22_完成归档.md b/docs/used/SRP_URP_RendererFeatureCollectionPlan_2026-04-22_完成归档.md deleted file mode 100644 index eb1aafbe..00000000 --- a/docs/used/SRP_URP_RendererFeatureCollectionPlan_2026-04-22_完成归档.md +++ /dev/null @@ -1,70 +0,0 @@ -# SRP / URP Renderer Feature Collection Plan - -时间:2026-04-22 - -## 背景 - -当前 feature 层的 runtime boundary 已经基本收好,但 `renderer data` 这一层还有明显的结构问题: - -1. `ScriptableRendererData` 直接持有并操作 `rendererFeatures` 数组。 -2. feature owner bind、active 遍历、runtime state hash、runtime release 这些逻辑在 `ScriptableRendererData` 里分散重复。 -3. `UniversalRendererData` 的默认 feature 组合也还是直接在 `CreateDefaultRendererFeatures()` 里硬编码构造。 - -这会导致: - -- `renderer data` 既在做 renderer asset 表达,又在做 feature collection 运行时管理。 -- 默认 feature 组合没有正式落点,后续扩展不同 renderer profile 时不利于演进。 - -## 本阶段目标 - -把 `rendererFeatures` 这层正式收成 collection / factory 结构。 - -目标结果: - -1. `ScriptableRendererFeatureCollection` 统一管理 feature 数组的 bind、active 遍历、hash、release。 -2. `ScriptableRendererData` 不再散写 feature 集合生命周期。 -3. `UniversalRendererData` 的默认 feature 组合改走明确工厂。 - -## 范围 - -本阶段只处理 renderer feature collection,不做: - -- public renderer feature API 改造 -- deferred rendering -- editor feature 分类面板 -- C++ host / RenderGraph 改造 - -## 实施步骤 - -### 1. 新增 ScriptableRendererFeatureCollection - -职责: - -- 持有 feature 数组引用 -- 统一 bind owner -- 统一 active 遍历 -- 统一计算 collection hash -- 统一 runtime release - -### 2. 接入 ScriptableRendererData - -迁移: - -- `ConfigureCameraRenderRequestInstance` -- `ConfigureCameraFramePlanInstance` -- `ConfigureRenderSceneSetupInstance` -- `ConfigureDirectionalShadowExecutionStateInstance` -- `ReleaseRendererSetupCache` -- renderer feature collection state sync - -### 3. 抽离 Universal 默认 feature 工厂 - -新增 `UniversalDefaultRendererFeatureFactory`,负责默认 builtin feature 组合。 - -## 完成标准 - -满足以下条件才算本阶段收口: - -1. `ScriptableRendererData` 不再自己散写 feature collection 生命周期细节。 -2. `UniversalRendererData` 默认 feature 组合走明确工厂。 -3. `XCEditor` 编译通过,old editor 冒烟通过。 diff --git a/docs/used/SRP_URP_RendererFeatureOwnershipLifecyclePlan_2026-04-21_完成归档.md b/docs/used/SRP_URP_RendererFeatureOwnershipLifecyclePlan_2026-04-21_完成归档.md deleted file mode 100644 index 44d535a4..00000000 --- a/docs/used/SRP_URP_RendererFeatureOwnershipLifecyclePlan_2026-04-21_完成归档.md +++ /dev/null @@ -1,78 +0,0 @@ -# SRP URP Renderer Feature Ownership Lifecycle Plan - -日期:2026-04-21 - -## 背景 - -上一阶段已经把 native 侧收口成了共享 backend substrate: - -- managed `rendererIndex` 负责选择 `ScriptableRenderer` -- native C++ 负责共享 backend / scene draw substrate - -接下来最关键的问题不在 native,而在 managed URP 这一层: - -- `ScriptableRendererFeature` 目前没有 owner 概念 -- feature 自身没有 dirty/version 语义 -- `ScriptableRendererData` 只能在自己显式 `SetDirty()` 时失效 -- `rendererFeatures` 列表替换、feature 激活状态变化、feature 自身 runtime 失效,都没有被完整建模 - -这意味着当前虽然已经能写 `ScriptableRendererFeature`,但它还不算一个真正稳定、可扩展、可长期维护的 Unity 风格定制点。 - -## 本阶段目标 - -把 managed `ScriptableRendererFeature` / `ScriptableRendererData` 收口成更接近 Unity URP 的生命周期语义: - -- `ScriptableRendererFeature` 知道自己属于哪个 `ScriptableRendererData` -- feature 可以显式 `SetDirty()`,并向 owner 传播 invalidation -- feature 具备基本 runtime state version 语义 -- `ScriptableRendererData` 能检测 `rendererFeatures` 集合变化并使 renderer cache 失效 -- 内置 feature 不再绕开 `CreateInstance()` 的生命周期入口 - -## 为什么现在做 - -如果这层不先收口,后面继续做: - -- 用户自定义 `ScriptableRendererFeature` -- renderer data 序列化/编辑器配置 -- 更复杂的 URP renderer feature 组合 -- 最终的 C# 自定义渲染管线体验 - -都会建立在一个“feature 能写,但生命周期和失效语义不完整”的地基上。这样继续堆功能,后面只会越来越难收拾。 - -## 改动范围 - -本阶段只处理 managed URP 的 feature/data 生命周期,不碰 new editor,不改 deferred,不重新打开 native backend 选择问题。 - -主要涉及: - -- `managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRendererFeature.cs` -- `managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRendererData.cs` -- `managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/BuiltinGaussianSplatRendererFeature.cs` -- `managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/BuiltinVolumetricRendererFeature.cs` -- `managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/RenderObjectsRendererFeature.cs` - -## 预期结果 - -收口后,这一层的关系应当变成: - -- `ScriptableRendererData` 拥有 renderer feature 集合,并负责 renderer cache 失效 -- `ScriptableRendererFeature` 是 renderer data 的受管对象,而不是游离对象 -- feature 的激活状态和显式 dirty,都能正确影响 renderer/runtime 生命周期 -- 内置 feature 的创建和释放都走统一生命周期入口 - -## 实施步骤 - -1. 给 `ScriptableRendererFeature` 增加 owner / dirty / runtime state version 语义 -2. 给 `ScriptableRendererData` 增加 feature 集合同步与 invalidation 收口逻辑 -3. 修正内置 feature 的 `Create()` 回退路径,统一走 `CreateInstance()` -4. 编译 `XCEditor` -5. 运行旧版 `editor/bin/Debug/XCEngine.exe` 冒烟至少 10 秒,并确认新的 `SceneReady` -6. 归档计划,提交并推送 - -## 验证标准 - -- `cmake --build . --config Debug --target XCEditor` 成功 -- 运行 `editor/bin/Debug/XCEngine.exe` -- 冒烟至少 10 秒 -- `editor/bin/Debug/editor.log` 出现新的 `SceneReady` -- 本阶段提交只包含 SRP/rendering 主线代码与计划文档 diff --git a/docs/used/SRP_URP_RendererFeatureRuntimeStateSyncPlan_2026-04-21_完成归档.md b/docs/used/SRP_URP_RendererFeatureRuntimeStateSyncPlan_2026-04-21_完成归档.md deleted file mode 100644 index 4bf65bbf..00000000 --- a/docs/used/SRP_URP_RendererFeatureRuntimeStateSyncPlan_2026-04-21_完成归档.md +++ /dev/null @@ -1,85 +0,0 @@ -# SRP URP Renderer Feature Runtime State Sync Plan - -日期:2026-04-21 - -## 背景 - -上一阶段已经把 `ScriptableRendererFeature` / `ScriptableRendererData` 的 owner、dirty、lifecycle 收口了一轮: - -- feature 的激活状态变化会触发 invalidation -- feature 可以向 owner 传播 dirty -- renderer feature 集合变化能使 renderer cache 失效 - -但当前还有一个关键缺口: - -- feature 的普通配置字段变化,系统本身还不会主动感知 - -例如: - -- `RenderObjectsRendererFeature.passEvent` -- `RenderObjectsRendererFeature.overrideMaterialPath` -- `ColorScalePostProcessRendererFeature.colorScale` -- 内置 feature 的 `passEvent` - -这些值如果被代码改掉,当前多数场景只是“碰巧还能工作”,而不是被明确建模成 runtime-state 变化。 - -## 本阶段目标 - -给 `ScriptableRendererFeature` 增加统一的 runtime-state 同步入口,并把当前内置 feature 的配置接到这条链路上: - -- `ScriptableRendererFeature` 自身能同步 runtime state version -- `ScriptableRendererData` 读取 feature version 时会拿到同步后的值 -- 内置 feature 的关键配置字段参与 runtime-state 哈希 -- feature 配置变化会走统一 invalidation 语义,而不是散落在各处的偶然行为 - -## 为什么现在做 - -如果这层不做,后面继续做: - -- 更多 URP feature -- 用户自定义 renderer feature -- editor/序列化 对 feature 字段的编辑 - -都会建立在一个“字段变了,但系统不一定知道这算一次 runtime state 变化”的前提上。 - -这会让后面调试、缓存失效、资源重建的行为越来越难预测。 - -## 改动范围 - -本阶段只处理 managed URP feature 的 runtime-state 同步,不碰 new editor,不改 deferred,不回头扩 native。 - -主要涉及: - -- `managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRendererFeature.cs` -- `managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/RenderObjectsRendererFeature.cs` -- `managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ColorScalePostProcessRendererFeature.cs` -- `managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/BuiltinGaussianSplatRendererFeature.cs` -- `managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/BuiltinVolumetricRendererFeature.cs` - -如有必要,可加一个小型哈希辅助实现,但保持范围只在 managed URP。 - -## 预期结果 - -收口后: - -- `ScriptableRendererFeature` 会像 `ScriptableRenderPipelineAsset` / `ScriptableRendererData` 一样具备同步 runtime-state 的能力 -- feature 配置字段变化会转化为明确的 version 变化 -- `ScriptableRendererData` 不再只知道 “feature 对象还是不是同一个”,也知道 “同一个 feature 的配置有没有变” -- 后面继续做用户自定义 URP feature 时,失效语义有稳定扩展点 - -## 实施步骤 - -1. 给 `ScriptableRendererFeature` 增加 runtime-state 同步入口 -2. 为当前内置 feature 接入配置哈希/同步逻辑 -3. 保持和上一阶段的 owner/dirty/lifecycle 逻辑兼容 -4. 编译 `XCEditor` -5. 运行旧版 `editor/bin/Debug/XCEngine.exe` 冒烟至少 10 秒,并确认新的 `SceneReady` -6. 归档计划,提交并推送 - -## 验证标准 - -- `cmake --build . --config Debug --target XCEditor` 成功 -- 运行 `editor/bin/Debug/XCEngine.exe` -- 冒烟至少 10 秒 -- `editor/bin/Debug/editor.log` 出现新的 `SceneReady` -- 本阶段提交只包含 SRP/rendering 主线代码与计划文档 diff --git a/docs/used/SRP_URP_RendererSlotSemanticsPlan_完成归档_2026-04-21.md b/docs/used/SRP_URP_RendererSlotSemanticsPlan_完成归档_2026-04-21.md deleted file mode 100644 index 8d432641..00000000 --- a/docs/used/SRP_URP_RendererSlotSemanticsPlan_完成归档_2026-04-21.md +++ /dev/null @@ -1,69 +0,0 @@ -# SRP URP Renderer Slot Semantics Plan - -Date: 2026-04-21 - -## Goal - -Continue aligning the current managed SRP + URP stack with Unity-style renderer asset semantics before moving on to larger SRP features. - -This stage focuses on two structural gaps: - -- renderer slot resolution should treat configured renderer entries as real authored slots instead of silently synthesizing new renderer data for arbitrary null entries -- shared renderer feature ownership should live on `ScriptableRendererData`, not only on `UniversalRendererData` - -The point of this stage is to make the current renderer list architecture trustworthy, so later SRP work can build on it cleanly. - -## Current Problems - -1. `RendererBackedRenderPipelineAsset` currently materializes a new default renderer data into any resolved null slot. That makes renderer list behavior implicit and does not match Unity-style authored renderer slots. -2. Only `UniversalRendererData` publicly owns `rendererFeatures`, which means future renderer-data variants would immediately duplicate the same feature-list plumbing. -3. The current asset bootstrap path and the current slot fallback path are mixed together, so "create the default renderer list for an empty asset" and "resolve a camera-selected renderer entry" are not clearly separated. - -## Scope - -### 1. Clean up renderer slot fallback semantics - -Required work: - -- keep automatic default renderer creation only for empty renderer lists or a missing effective default renderer -- when a camera-selected renderer index is invalid or points to a null slot, fall back to the effective default renderer instead of silently creating a new renderer entry in that slot -- preserve the existing camera override model: asset chooses the renderer list, camera chooses an index override - -### 2. Move shared renderer feature ownership into `ScriptableRendererData` - -Required work: - -- move the public `rendererFeatures` storage and its default-initialization path into `ScriptableRendererData` -- keep `UniversalRendererData` focused on renderer-specific core block configuration -- preserve current default built-in feature composition for the universal renderer - -### 3. Keep the next renderer-variant stage easy to add - -Required work: - -- leave the renderer-data list and renderer-index flow structurally ready for additional renderer-data types later -- avoid introducing fake renderer families or placeholder deferred code in this stage - -## Out Of Scope - -- deferred renderer -- 2D renderer -- editor-side renderer asset UI -- moving all native shadow execution into managed URP -- render graph expansion - -## Done Criteria - -1. Renderer slot resolution no longer creates ad-hoc renderer data for non-default null entries. -2. Empty assets and broken default slots still recover to a usable default renderer. -3. `rendererFeatures` is owned by `ScriptableRendererData` instead of `UniversalRendererData`. -4. Old `XCEditor` builds in Debug. -5. Old editor smoke passes for at least about 10 seconds and a fresh `SceneReady` appears in `editor/bin/Debug/editor.log`. - -## Follow-Up - -Once this stage closes, the next SRP work can move up one level again: - -- richer Unity-style renderer variants -- more managed URP ownership for renderer core blocks -- eventually, higher-level SRP package composition on top of the current C++ render-graph substrate diff --git a/docs/used/SRP_URP_RequestPolicyAndSceneSetupSeamPlan_2026-04-21_完成归档.md b/docs/used/SRP_URP_RequestPolicyAndSceneSetupSeamPlan_2026-04-21_完成归档.md deleted file mode 100644 index c585b55a..00000000 --- a/docs/used/SRP_URP_RequestPolicyAndSceneSetupSeamPlan_2026-04-21_完成归档.md +++ /dev/null @@ -1,100 +0,0 @@ -# SRP / URP Request Policy And Scene Setup Seam Plan - -## 背景 - -当前渲染模块已经基本形成了如下分层: - -- C++ 层负责 Render Graph、frame execution、scene extraction、native scene draw backend、shadow runtime、fullscreen substrate 等执行底座 -- managed SRP / URP 层负责 renderer 选择、pass 组织、feature 组织、camera/frame planning 策略 - -但当前仍有两类关键策略口没有完全收干净: - -1. `CameraRenderRequest` 阶段仍有部分默认策略固定在 C++ 层 -2. `RenderSceneSetup` 阶段 managed 层还缺少足够的配置能力,当前主要仍在调用 native default - -这会导致 SRP / URP 虽然已经能组织 pass,但还没有完全接管“为什么这么渲染”的策略层。 - -## 本阶段目标 - -本阶段不继续搬运 native draw backend,不做 deferred,不扩张底层执行复杂度,只收策略边界。 - -### 目标一 - -把 request seam 做实,让 managed URP 可以直接接管以下 request-level 策略: - -- camera stack / clear mode 读取 -- request clear flags 覆盖 -- directional shadow request 抑制与规划参数调整 -- renderer 选择仍保持由 managed 决定 - -### 目标二 - -为后续 scene setup seam 做准备,明确下一阶段 managed 需要接管的 scene setup 内容: - -- environment policy -- skybox policy -- global shader keywords policy - -## 本阶段实施步骤 - -### Step 1. 扩展 request context 与 camera managed API - -补齐 managed 侧 request policy 所需的最小上下文: - -- `Camera.clearMode` -- `Camera.stackType` -- `CameraRenderRequestContext.clearFlags` - -要求: - -- 通过 Mono internal call 打通 -- 不引入兼容层、废弃层或双轨 API -- 保持 request 仍由 C++ 创建,但允许 managed 在 request 阶段覆盖策略结果 - -### Step 2. 把默认 request clear / shadow policy 收进 URP - -在 `UniversalRenderPipelineAsset` 中接管默认 request policy: - -- 依据 camera clear mode 和 stack type 解析 request clear flags -- 继续在 managed 层控制 main light shadow request 是否保留 -- 继续在 managed 层控制 shadow planning settings - -要求: - -- 默认行为与当前引擎已有表现保持一致 -- native default policy 退化为底座 fallback,而不是 URP 主路径 - -### Step 3. 预留 scene setup seam 下一阶段入口 - -本阶段先不大改 scene setup,但要确认下一阶段的直接入口: - -- `RenderSceneSetupContext` 需要扩展哪些显式设置能力 -- `UniversalRenderer.ConfigureRenderSceneSetup(...)` 之后如何从“调用 default”过渡为“显式配置环境与关键字” - -## 收口标准 - -- managed 层可以读取 camera stack / clear mode -- managed 层可以覆盖 request clear flags -- URP 默认 clear / shadow request 策略由 managed 决定 -- `XCEditor` Debug 编译通过 -- 旧版 editor 冒烟通过 - -## 非目标 - -本阶段不做以下事项: - -- deferred rendering -- render graph 大改 -- native scene draw backend 搬迁 -- 阴影算法本体迁移到 managed -- gaussian / volumetric native 执行实现迁移 - -## 完成情况 - -- 已补齐 managed request seam 所需的 `Camera.clearMode`、`Camera.stackType`、`CameraRenderRequestContext.clearFlags` -- 已将 URP 默认 request clear policy 收进 managed `UniversalRenderPipelineAsset` -- 已将 `RenderClearFlags` 收敛到 rendering core,移除 URP 包内重复定义 -- 已让 `UniversalRenderer.ConfigureRenderSceneSetup(...)` 从 bundled `UseDefaultSceneSetup()` 过渡为显式组合 environment / global keywords -- 已补齐 `RenderSceneSetupContext.camera` 入口,便于下一阶段继续接管 scene setup policy -- `XCEditor` Debug 编译通过 -- 旧版 editor 冒烟通过 diff --git a/docs/used/SRP_URP_ScriptableObjectRenderAssetInstantiationPlan_2026-04-21_完成归档.md b/docs/used/SRP_URP_ScriptableObjectRenderAssetInstantiationPlan_2026-04-21_完成归档.md deleted file mode 100644 index c1ad3898..00000000 --- a/docs/used/SRP_URP_ScriptableObjectRenderAssetInstantiationPlan_2026-04-21_完成归档.md +++ /dev/null @@ -1,68 +0,0 @@ -# SRP URP ScriptableObject Render Asset Instantiation Plan - -Date: 2026-04-21 - -## Background - -The previous stage introduced a minimal managed `ScriptableObject` substrate and -already moved the SRP/URP render asset types onto it: - -- `RenderPipelineAsset` -- `ScriptableRendererData` -- `ScriptableRendererFeature` - -That fixed the inheritance model, but the creation model is still mixed: - -- several production code paths still instantiate render assets with `new` -- user-facing probes still demonstrate `new UniversalRenderPipelineAsset()` and - `new UniversalRendererData()` - -For a Unity-style SRP/URP direction, the next cleanup is to move the common -creation paths onto `ScriptableObject.CreateInstance(...)` semantics. - -## Goal - -Make the managed SRP/URP render asset creation model more Unity-like: - -- default renderer data creation uses `ScriptableObject.CreateInstance` -- default builtin renderer feature creation uses `ScriptableObject.CreateInstance` -- key user-facing probes demonstrate the same model - -This stage does not try to ban every direct `new` immediately. It focuses on -the primary engine paths and the API examples users are most likely to follow. - -## Scope - -Included: - -- `managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRenderPipelineAsset.cs` -- `managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRendererData.cs` -- `managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRenderer.cs` -- selected user-facing probes in `managed/GameScripts/RenderPipelineApiProbe.cs` - -Not included: - -- constructor access restriction across every custom probe type -- editor serialization work -- native asset persistence work - -## Implementation Plan - -1. Switch default renderer data creation to `ScriptableObject.CreateInstance` -2. Switch builtin renderer feature creation to `ScriptableObject.CreateInstance` -3. Switch the main fallback `UniversalRenderer` path to the same substrate -4. Update key SRP/URP selection probes to use `CreateInstance` -5. Rebuild `XCEditor` -6. Run old editor smoke for at least 10 seconds and verify a fresh - `SceneReady` -7. Archive the plan, commit, and push - -## Expected Result - -After this stage: - -- engine-side SRP/URP default creation paths match the new ScriptableObject - substrate -- user-facing probe code demonstrates the intended Unity-style creation model -- the codebase takes another step away from plain-object semantics for render - assets diff --git a/docs/used/SRP_URP_ScriptableObjectRenderAssetPlan_2026-04-21_完成归档.md b/docs/used/SRP_URP_ScriptableObjectRenderAssetPlan_2026-04-21_完成归档.md deleted file mode 100644 index 0aec5aed..00000000 --- a/docs/used/SRP_URP_ScriptableObjectRenderAssetPlan_2026-04-21_完成归档.md +++ /dev/null @@ -1,95 +0,0 @@ -# SRP URP ScriptableObject Render Asset Plan - -Date: 2026-04-21 - -## Background - -The recent SRP stages already moved the managed URP side closer to Unity: - -- native backend responsibilities were narrowed -- renderer feature lifecycle invalidation was stabilized -- renderer feature runtime-state synchronization was added -- renderer feature ownership was collapsed onto configured - `rendererFeatures` - -The next architectural mismatch is now more fundamental: - -- render pipeline assets, renderer data, and renderer features still sit - directly on the generic managed `Object` base -- there is no `ScriptableObject` substrate for non-scene managed authoring - objects - -That means the SRP/URP API shape still lacks the exact kind of managed asset -identity Unity uses for: - -- `RenderPipelineAsset` -- `ScriptableRendererData` -- `ScriptableRendererFeature` - -If the engine is meant to converge toward Unity-style SRP/URP authoring in C#, -this substrate needs to exist before the next layer of asset/editor work. - -## Goal - -Introduce a minimal managed `ScriptableObject` base and move the SRP/URP render -asset types onto it: - -- add `ScriptableObject : Object` -- make `RenderPipelineAsset` inherit from `ScriptableObject` -- make `ScriptableRendererData` inherit from `ScriptableObject` -- make `ScriptableRendererFeature` inherit from `ScriptableObject` - -The scope of this stage is intentionally small: - -- provide the inheritance substrate -- keep runtime behavior unchanged -- avoid pulling in editor serialization or native asset authoring work yet - -## Why Now - -Without this step, the managed SRP model keeps carrying a structural mismatch: - -- render assets look like plain objects instead of authored managed assets -- future editor integration would need to retrofit the base type later -- Unity-style mental model remains fuzzy for users extending URP in C# - -Doing this now keeps the next SRP stages building on the right object model -instead of stacking more rendering behavior on top of a temporary substrate. - -## Scope - -Included: - -- `managed/XCEngine.ScriptCore/ScriptableObject.cs` -- `managed/XCEngine.ScriptCore/Rendering/Core/RenderPipelineAsset.cs` -- `managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRendererData.cs` -- `managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRendererFeature.cs` -- managed build manifests / source lists if needed -- lightweight API probe updates if needed - -Not included: - -- editor inspector integration -- asset database / serialization work -- native object persistence changes -- deferred rendering or renderer feature behavior changes - -## Implementation Plan - -1. Add a minimal managed `ScriptableObject` base -2. Move render asset types to inherit from it -3. Add `CreateInstance()` style helper only if it is needed cleanly now -4. Update managed source registration if required -5. Rebuild `XCEditor` -6. Run old editor smoke for at least 10 seconds and verify a fresh - `SceneReady` -7. Archive the plan, commit, and push - -## Expected Result - -After this stage: - -- SRP/URP render assets sit on a Unity-like managed asset base type -- the managed rendering API shape is closer to Unity's authoring model -- future renderer-data / feature authoring work can continue on a cleaner - substrate diff --git a/docs/used/SRP_URP_ShadowBoundaryClosurePlan_2026-04-21_完成归档.md b/docs/used/SRP_URP_ShadowBoundaryClosurePlan_2026-04-21_完成归档.md deleted file mode 100644 index f11c4f76..00000000 --- a/docs/used/SRP_URP_ShadowBoundaryClosurePlan_2026-04-21_完成归档.md +++ /dev/null @@ -1,31 +0,0 @@ -# SRP/URP Shadow Boundary Closure Plan - -日期:2026-04-21 - -## 背景 - -当前 SRP/URP 骨架已经基本成型,但阴影这条链路在 managed 与 native 之间还有一个关键边界没收干净: - -1. `UniversalRenderPipelineAsset` / `UniversalRenderer` 已经在 managed 层表达阴影开关、stage 规划与 renderer 级别控制。 -2. native 侧仍保留阴影图分配与默认执行策略,这本身是当前阶段合理的 substrate。 -3. 但当 managed renderer 明确不想执行阴影时,如果只是不配置 execution state,host 仍可能回落到 native 默认阴影执行,导致 managed 决策失效。 - -这会让 shadow 的“策略在 managed,执行底座在 native”的边界重新变脏。 - -## 目标 - -1. 收紧 shadow execution 边界,确保 managed renderer 明确关闭阴影时,不会再落回 native 默认执行。 -2. 保持当前 native 阴影 runtime / surface allocation / default execution substrate 不变,不扩功能,不碰 deferred。 -3. 通过一次编译和旧版 editor 冒烟验证,尽快把这一个骨架口子收掉。 - -## 实施步骤 - -1. 修正 `UniversalRenderer.ConfigureDirectionalShadowExecutionState(...)`,让 shadow caster block 禁用或无阴影计划时显式清空 execution state,而不是隐式留给 host fallback。 -2. 复查这条路径的 host fallback 行为,确认 managed 明确配置后不会再重回 native 默认阴影执行。 -3. 重新编译 `XCEditor`,运行旧版 `editor/bin/Debug/XCEngine.exe` 冒烟至少 10 秒,确认 `SceneReady`。 - -## 完成判定 - -1. managed renderer 明确关闭阴影时,shadow execution state 会被显式清空。 -2. `cmake --build build --config Debug --target XCEditor` 成功。 -3. 旧版 editor 冒烟通过,日志出现新的 `SceneReady`。 diff --git a/docs/used/SRP_URP_StageScopedPassCollectionPlan_2026-04-22_完成归档.md b/docs/used/SRP_URP_StageScopedPassCollectionPlan_2026-04-22_完成归档.md deleted file mode 100644 index caaff87b..00000000 --- a/docs/used/SRP_URP_StageScopedPassCollectionPlan_2026-04-22_完成归档.md +++ /dev/null @@ -1,85 +0,0 @@ -# SRP / URP Stage-Scoped Pass Collection Plan - -时间:2026-04-22 - -## 背景 - -上一阶段已经把 managed renderer 的执行组织收成了 renderer block: - -- `ShadowCaster` -- `DepthPrepass` -- `MainOpaque` -- `MainSkybox` -- `MainTransparent` -- `PostProcess` -- `FinalOutput` - -但当前还有一个没有完全收口的问题: - -1. native C++ 这一层仍然按 `CameraFrameStage` 分阶段调用 managed renderer。 -2. `ScriptableRenderer.BuildPassQueue(...)` 也是每个 stage 都会执行一次。 -3. 可现在 renderer / feature 仍然可能在这个阶段里把所有 stage 的 pass 都先塞进总队列,再交给后面的 block 过滤。 - -这意味着: - -- 组织方式虽然已经比之前清晰,但 pass collection 仍然混着跨 stage 的内容。 -- `UniversalRenderer` 自己对 block 的 ownership 也还不够直接。 -- 后续把 shadow / volumetric / gaussian 等能力继续往 URP 层迁时,边界会继续发虚。 - -## 本阶段目标 - -把 managed renderer 的 pass collection 也收成 stage-scoped,并同步把 `UniversalRenderer` 的内建 pass 入口拆成更清晰的 block-owned 结构。 - -目标结果: - -1. 当前 stage 构建 pass queue 时,不再混入明显属于其他 stage 的 pass。 -2. `UniversalRenderer` 明确区分 shadow / depth / main scene / final output 这些 block-owned 入口。 -3. 继续保留现在的 `RenderPassEvent + RenderBlocks` 路线,不引入额外兼容层。 - -## 范围 - -本阶段只处理 managed SRP / URP 的 pass collection 与 block ownership,不做: - -- deferred rendering -- 阴影算法迁移 -- editor-only 渲染能力迁移 -- C++ RenderGraph 功能扩展 - -## 实施步骤 - -### 1. ScriptableRenderer pass collection stage-scoped 化 - -重构 `ScriptableRenderer`: - -- 在 build active pass queue 时记录当前 `RenderingData.stage` -- `EnqueuePass(...)` 在 pass queue build 期间只接受与当前 stage 匹配的 pass -- 非 build 期间保持现有安全行为 - -这样可以让当前 stage 的 queue 从源头上变干净,而不是先混入、后过滤。 - -### 2. UniversalRenderer 显式按 block 组织内建 pass - -重构 `UniversalRenderer.AddRenderPasses(...)`: - -- `ShadowCaster` 只处理 shadow block -- `DepthOnly` 只处理 depth prepass block -- `MainScene` 只处理 opaque / skybox / transparent -- `FinalOutput` 只处理 final output block - -必要时同步清理内建 feature 的 stage 边界判断,避免继续依赖下游过滤。 - -### 3. 验证主线不回退 - -要求: - -1. `XCEditor` Debug 构建通过 -2. old editor 冒烟至少 10 秒 -3. `editor.log` 出现 `SceneReady` - -## 完成标准 - -满足以下条件才算本阶段收口: - -1. active pass queue 的 collection 已经是 stage-scoped。 -2. `UniversalRenderer` 内建 pass ownership 更直接,不再在单个入口里混着所有 stage。 -3. `XCEditor` 编译与 old editor 冒烟通过。 diff --git a/docs/used/SRP_URP_StandaloneStageOwnershipBoundaryPlan_2026-04-21_完成归档.md b/docs/used/SRP_URP_StandaloneStageOwnershipBoundaryPlan_2026-04-21_完成归档.md deleted file mode 100644 index 7a9a9c84..00000000 --- a/docs/used/SRP_URP_StandaloneStageOwnershipBoundaryPlan_2026-04-21_完成归档.md +++ /dev/null @@ -1,48 +0,0 @@ -# SRP/URP Standalone Stage Ownership Boundary Plan - -日期:2026-04-21 - -## 背景 - -当前 SRP/URP 主线已经有了基本骨架,但 `ScriptableRenderPipelineHostAsset` 里仍然直接注册了三类 native standalone pass: - -1. `BuiltinShadowCasterPass` -2. `BuiltinDepthOnlyPass` -3. `BuiltinObjectIdPass` - -这会带来两个结构问题: - -1. 通用 SRP host 和 builtin forward fallback 耦合在一起。`ScriptableRenderPipelineHost` 本应只负责 managed/native 桥接、stage recorder 绑定和 fallback 分发,不应该自己偷偷决定 builtin standalone stage 的具体实现。 -2. ownership 不清晰。managed URP 已经在 C# 层负责 shadow/depth 的 stage 规划和 render-graph 录制,但 native fallback 仍然挂在 host asset 上,导致“谁拥有这些 fallback pass”这个问题没有收口。 - -这一步不是做新功能,而是把这条边界收干净,让后续继续按 Unity 风格往 SRP + URP 分层推进。 - -## 目标 - -1. 让 `ScriptableRenderPipelineHost` 退回到真正的 host 角色,不再在 asset 层硬编码 builtin standalone pass。 -2. 让 builtin forward backend 显式拥有自己的 native standalone fallback pass。 -3. 保持当前运行结果不变:managed renderer 录制 stage 时优先走 managed;需要 native fallback 时,由 backend 明确提供。 - -## 实施步骤 - -1. 在 `BuiltinForward...Setup` 路径中显式配置 builtin forward pipeline 的 standalone fallback pass,而不是在 host asset 中配置。 -2. 调整 `ScriptableRenderPipelineHost::GetCameraFrameStandalonePass(...)`,让 host 在自身未持有 pass 时,从 backend 查询 standalone pass。 -3. 清理 `ScriptableRenderPipelineHostAsset` 中已过期的 builtin pass 注入逻辑。 -4. 重新编译旧版 `XCEditor`,运行旧版 editor 冒烟至少 10 秒,确认没有把 fallback 链路改坏。 - -## 完成判定 - -1. `ScriptableRenderPipelineHostAsset` 不再直接注册 builtin shadow/depth/object-id standalone pass。 -2. builtin forward backend 仍可为 fallback stage 提供 native standalone pass。 -3. `cmake --build . --config Debug --target XCEditor` 成功。 -4. 旧版 `editor/bin/Debug/XCEngine.exe` 冒烟通过,并出现新的 `SceneReady` 记录。 - -## 结果 - -1. `ScriptableRenderPipelineHostAsset` 已移除 builtin standalone pass 注入逻辑。 -2. builtin forward backend 现在在自身 setup 中显式注册 `ObjectId`、`DepthOnly`、`ShadowCaster` fallback pass。 -3. `ScriptableRenderPipelineHost` 现在会在自身未持有 standalone pass 时,向 backend 查询 fallback pass。 -4. 验证结果: - `cmake --build . --config Debug --target XCEditor` 通过。 - 旧版 editor 冒烟 15 秒通过。 - 日志结果:`SceneReady elapsed_ms=5505 first_frame_ms=619`。 diff --git a/docs/used/SRP_URP_UniversalPostProcessBlockPlan_2026-04-22_完成归档.md b/docs/used/SRP_URP_UniversalPostProcessBlockPlan_2026-04-22_完成归档.md deleted file mode 100644 index c8db5e04..00000000 --- a/docs/used/SRP_URP_UniversalPostProcessBlockPlan_2026-04-22_完成归档.md +++ /dev/null @@ -1,69 +0,0 @@ -# SRP / URP UniversalPostProcessBlock Plan - -时间:2026-04-22 - -## 背景 - -当前 `UniversalRenderer` 已经把这些 block 拆出来了: - -- `UniversalShadowCasterBlock` -- `UniversalDepthPrepassBlock` -- `UniversalMainSceneBlock` -- `UniversalFinalOutputBlock` - -但 `post-process` 这一面还没有自己的正式落点: - -1. fullscreen post-process stage planning 还散在具体 feature 里。 -2. `UniversalFinalOutputBlock` 又需要读取并调整 post-process stage 的 graph-managed output 状态。 -3. 结果是 post-process / final output 的协作语义分散在多个类里,不利于后续继续接更多 post-process feature。 - -## 本阶段目标 - -补齐 `UniversalPostProcessBlock`,把 post-process 共享 planning 语义收口到统一 helper。 - -目标结果: - -1. `ColorScalePostProcessRendererFeature` 不再内联维护 fullscreen post-process planning 逻辑。 -2. `UniversalFinalOutputBlock` 不再直接手写 post-process stage promotion 逻辑。 -3. 后续新增更多 post-process feature 时,可以复用 `UniversalPostProcessBlock` 的共享行为。 - -## 范围 - -本阶段只处理 post-process / final output 共享 planning,不做: - -- 新的 post-process feature API 设计 -- deferred rendering -- C++ host / RenderGraph 改造 - -## 实施步骤 - -### 1. 新增 UniversalPostProcessBlock - -职责: - -- 统一请求 post-process fullscreen stage -- 统一处理 graph-managed output color 的 promotion -- 为 final output 提供 post-process color source 解析 - -### 2. 接入现有使用点 - -接入: - -- `ColorScalePostProcessRendererFeature` -- `UniversalFinalOutputBlock` - -### 3. 验证 - -要求: - -1. `XCEditor` Debug 构建通过 -2. old editor 冒烟至少 10 秒 -3. `editor.log` 出现 `SceneReady` - -## 完成标准 - -满足以下条件才算本阶段收口: - -1. post-process shared planning 已经集中到 `UniversalPostProcessBlock`。 -2. `ColorScalePostProcessRendererFeature` 与 `UniversalFinalOutputBlock` 都走统一 helper。 -3. `XCEditor` 编译与 old editor 冒烟通过。 diff --git a/docs/used/SRP_URP_UniversalRendererBlockOwnershipPlan_2026-04-22_完成归档.md b/docs/used/SRP_URP_UniversalRendererBlockOwnershipPlan_2026-04-22_完成归档.md deleted file mode 100644 index 2b70f0b4..00000000 --- a/docs/used/SRP_URP_UniversalRendererBlockOwnershipPlan_2026-04-22_完成归档.md +++ /dev/null @@ -1,80 +0,0 @@ -# SRP / URP UniversalRenderer Block Ownership Plan - -时间:2026-04-22 - -## 背景 - -当前主线已经做到: - -1. managed renderer 的执行已经按 renderer block 组织。 -2. pass collection 也已经收成 stage-scoped。 -3. `UniversalRenderer` 现在会按 `ShadowCaster / DepthOnly / MainScene / FinalOutput` 分入口添加内建 pass。 - -但还有一个明显的结构问题: - -- `UniversalRenderer` 自己仍然同时承担了 block planning、scene setup、shadow execution state、builtin pass setup 这些职责。 -- 虽然逻辑已经按 block 分段,但仍然都堆在一个类里。 - -这会直接影响下一阶段把更多能力往 URP 层迁: - -- 没有稳定的 block owner 落点。 -- 以后 shadow / lighting / volumetric / gaussian / post-process 继续增强时,`UniversalRenderer` 会再次膨胀。 - -## 本阶段目标 - -把 `UniversalRenderer` 的内联 block 逻辑正式拆成 block controller / owner 类,让 renderer 自己退化成编排者。 - -目标结果: - -1. `ShadowCaster` block 自己负责 stage planning、shadow execution state、builtin pass setup。 -2. `DepthPrepass` block 自己负责 planning 和 builtin pass setup。 -3. `MainScene` block 自己负责 scene pass setup,并承接 scene environment/setup 相关逻辑。 -4. `FinalOutput` block 自己负责 final output planning 与 builtin final color pass setup。 -5. `UniversalRenderer` 自己只负责拿 data、协调这些 block、暴露 renderer 级入口。 - -## 范围 - -本阶段只做 `UniversalRenderer` 内部 block ownership 拆分,不做: - -- deferred rendering -- 新的 renderer feature API -- 阴影算法功能升级 -- C++ host / RenderGraph 扩展 - -## 实施步骤 - -### 1. 提取 block controller 类 - -新增内部 block controller: - -- `UniversalShadowCasterBlock` -- `UniversalDepthPrepassBlock` -- `UniversalMainSceneBlock` -- `UniversalFinalOutputBlock` - -### 2. 迁移 block 职责 - -迁移内容: - -- shadow stage planning -- depth stage planning -- final output planning -- directional shadow execution state -- render scene setup 中属于 main scene block 的内容 -- builtin pass configure / enqueue - -### 3. 收紧 UniversalRenderer - -`UniversalRenderer` 只保留: - -- renderer data 获取 -- 调用各 block controller -- renderer 级生命周期协调 - -## 完成标准 - -满足以下条件才算本阶段收口: - -1. `UniversalRenderer` 不再内联维护大段 block-specific 逻辑。 -2. 四个核心 block 都有自己的 owner/controller 落点。 -3. `XCEditor` 编译通过,old editor 冒烟通过。 diff --git a/docs/used/SRP_UnityStyleURPCoreRendererOwnershipPlan_完成归档_2026-04-21.md b/docs/used/SRP_UnityStyleURPCoreRendererOwnershipPlan_完成归档_2026-04-21.md deleted file mode 100644 index 449daa38..00000000 --- a/docs/used/SRP_UnityStyleURPCoreRendererOwnershipPlan_完成归档_2026-04-21.md +++ /dev/null @@ -1,83 +0,0 @@ -# SRP Unity-Style URP Core Renderer Ownership Plan - -日期:2026-04-21 - -## 阶段目标 - -在已经完成 `ShadowCaster` stage ownership 的基础上,继续把 URP 的核心职责收回到 `UniversalRenderer` 本体,向 Unity 的 `ScriptableRenderer` 组织方式靠拢。 - -这一阶段先不碰 deferred,也不碰大范围的 RenderPassEvent 扩表。先把最明显、最不符合 Unity 的地方收干净: - -- `FinalColor / FinalOutput` 不再由默认 `RendererFeature` 伪装承载 -- `UniversalRenderer` 显式拥有核心收尾 stage 的默认规划与录制 -- `RendererFeature` 只保留扩展/注入职责,不再混入明显属于 renderer core 的默认 pass - -## 当前问题 - -当前 managed 层虽然已经有了 `RendererData -> Renderer -> RendererFeature -> Pass` 结构,但还存在一个关键偏差: - -1. `BuiltinFinalColorRendererFeature` 仍然承担 `FinalOutput` stage 的默认规划 -2. `BuiltinFinalColorRendererFeature` 仍然承担核心 final color pass 的录制 -3. `UniversalRendererData.rendererFeatures` 默认列表里混入了 renderer core pass - -这跟 Unity 的真实边界不一致。Unity 里: - -- `ScriptableRenderer` 负责核心 pass 的组织 -- `ScriptableRendererFeature` 负责附加、覆写、注入 -- `RendererData` 主要是 renderer 配置资产,而不是“默认核心 pass 容器” - -如果继续把 core pass 塞在默认 feature 里,后面会直接影响: - -- renderer 自己的阶段所有权 -- future renderer variant(不同 renderer)的拆分 -- 后续 post-process block / shadow block / prepass block 的继续归位 - -## 本阶段实施内容 - -### 1. 收回 FinalColor 核心职责 - -把 `BuiltinFinalColor` 的默认规划与录制从默认 `RendererFeature` 收回到 `UniversalRenderer`: - -1. `UniversalRenderer.ConfigureCameraFramePlan(...)` 显式拥有 `FinalOutput` 的默认规划 -2. `UniversalRenderer.AddRenderPasses(...)` 显式拥有内建 final color pass 的入队 -3. 后续 feature 仍然可以在 renderer 之后覆写/补充 - -### 2. 清理默认 RendererFeature 列表 - -调整 `UniversalRendererData` 的默认 feature 列表,只保留扩展性质的 feature: - -1. `BuiltinFinalColorRendererFeature` 退出默认列表 -2. `rendererFeatures` 不再承担 renderer core 职责 -3. 默认 feature 列表表达“扩展点”,而不是“核心管线本体” - -### 3. 保持现有行为不回退 - -这一步不是改效果,而是改职责边界: - -1. 有 final color processing 时,`FinalOutput` 仍会被正确请求 -2. 若 `PostProcess` 已占用 graph-managed color,`FinalOutput` 仍沿用正确 source -3. 旧版 `XCEditor` 冒烟结果不能回退 - -## 本阶段不做的事情 - -1. 不做 deferred renderer -2. 不做 GBuffer -3. 不做完整 post-process block 体系 -4. 不做阴影 pass managed 化 -5. 不一口气重写所有 `RendererFeature` - -## 完成标准 - -1. `FinalOutput` 的默认规划位于 `UniversalRenderer` -2. 内建 final color pass 的默认录制位于 `UniversalRenderer` -3. `UniversalRendererData` 默认 feature 列表不再包含 core final color feature -4. 旧版 `XCEditor` Debug 编译通过 -5. 旧版编辑器冒烟 10 秒以上,并在新的 `editor.log` 中出现 `SceneReady` - -## 下一阶段前置条件 - -只有这一阶段收口后,再进入下一个更像 Unity 的阶段: - -- `PostProcess / Core Block Ownership` - -也就是继续把 renderer core 的 block 组织,从 feature 侧收回到 `UniversalRenderer`,为后续更完整的 Unity 风格 `RenderPassEvent` 与 renderer variant 演进做准备。 diff --git a/docs/used/SRP_UnityStyleURPFullscreenStageInferencePlan_完成归档_2026-04-21.md b/docs/used/SRP_UnityStyleURPFullscreenStageInferencePlan_完成归档_2026-04-21.md deleted file mode 100644 index 8c2f1c3b..00000000 --- a/docs/used/SRP_UnityStyleURPFullscreenStageInferencePlan_完成归档_2026-04-21.md +++ /dev/null @@ -1,96 +0,0 @@ -# SRP Unity-Style URP Fullscreen Stage Inference Plan - -日期:2026-04-21 - -## 阶段目标 - -继续沿着 Unity 的 `ScriptableRenderer + RendererFeature + RenderPassEvent` 模型收敛,解决当前 fullscreen stage 仍然需要 feature 手写 `ConfigureCameraFramePlan(...)` 的问题。 - -本阶段的核心目标是: - -- 让 `ScriptableRenderer` 能基于 pass queue 自动推导 `PostProcess / FinalOutput` 这类 fullscreen stage 需求 -- 让 `UniversalRenderer` 在 feature 参与之后再完成 final output 的依赖收口 -- 减少 feature 作者同时维护 “AddRenderPasses + ConfigureCameraFramePlan” 两套逻辑的重复负担 - -## 当前问题 - -当前结构里,feature 如果要往 `PostProcess` 塞一个 pass,通常必须同时做两件事: - -1. 在 `AddRenderPasses(...)` 里 enqueue pass -2. 在 `ConfigureCameraFramePlan(...)` 里手动请求 `PostProcess` - -这不符合 Unity 的主思路。Unity 里用户主要写的是: - -- `RendererFeature` -- `ScriptableRenderPass` -- `renderPassEvent` - -而不是再额外手工参与底层 stage existence planning。 - -当前这套方式还有一个实际问题: - -1. `UniversalRenderer` 现在已经接管了 `FinalOutput` -2. 但 feature 对 `PostProcess` 的请求发生在 renderer 默认规划之后 -3. 这会让 `FinalOutput` 难以及时感知 feature 注入出来的 `PostProcess` 依赖 - -## 本阶段实施内容 - -### 1. 增加 renderer 规划收尾阶段 - -在 renderer 规划流程中补一个 finalize/fixup 阶段: - -1. renderer 先做 core default planning -2. feature 再做显式 override -3. renderer 最后基于 pass queue 和当前 stage 状态做依赖收口 - -这样 renderer 才能在 feature 参与后,看到最终的 fullscreen stage 需求。 - -### 2. 基于 pass queue 自动推导 fullscreen stage - -让 `ScriptableRenderer` 使用已经 enqueue 的 pass 队列自动推导: - -1. 是否需要 `PostProcess` -2. 是否需要 `FinalOutput` - -优先先做 fullscreen stage,不一口气泛化到所有 stage。 - -### 3. 清理 feature 侧重复请求 - -拿 `ColorScalePostProcessRendererFeature` 作为第一批清理对象: - -1. 保留 `AddRenderPasses(...)` -2. 去掉手写的 `ConfigureCameraFramePlan(...)` -3. 改由 renderer 自动根据 pass queue 推导 `PostProcess` - -### 4. 让 UniversalRenderer 在 finalize 阶段收 final output - -`UniversalRenderer` 的 `FinalOutput` 依赖不再在 feature 之前就定死,而是在 fullscreen stage 自动推导之后再做: - -1. 若 feature 导致 `PostProcess` 存在 -2. 且 pipeline 需要 final color processing -3. 则 `FinalOutput` 应该正确依赖 `PostProcessColor` - -## 本阶段不做的事情 - -1. 不做 deferred renderer -2. 不做 GBuffer -3. 不做 shadow pass managed 化 -4. 不重写完整的 Unity RenderPassEvent 大表 -5. 不一口气移除所有 `ConfigureCameraFramePlan(...)` - -## 完成标准 - -1. renderer 规划流程具备 finalize/fixup 阶段 -2. `ScriptableRenderer` 能基于 pass queue 自动推导 `PostProcess / FinalOutput` -3. `ColorScalePostProcessRendererFeature` 不再手写请求 `PostProcess` -4. `UniversalRenderer` 在 finalize 阶段正确收口 final output 依赖 -5. 旧版 `XCEditor` Debug 编译通过 -6. 旧版编辑器冒烟 10 秒以上,并在新的 `editor.log` 中出现 `SceneReady` - -## 下一阶段前置条件 - -只有这一阶段收口后,再进入下一个更接近 Unity URP 的阶段: - -- `Renderer Block / Event Semantics Expansion` - -也就是进一步把 renderer 的 block 边界和 `RenderPassEvent` 语义扩全,为未来的不同 renderer variant 和更完整的 URP 风格扩展点做准备。 diff --git a/docs/used/SRP_UnityStyleURPShadowStageOwnershipPlan_完成归档_2026-04-21.md b/docs/used/SRP_UnityStyleURPShadowStageOwnershipPlan_完成归档_2026-04-21.md deleted file mode 100644 index 7be6e765..00000000 --- a/docs/used/SRP_UnityStyleURPShadowStageOwnershipPlan_完成归档_2026-04-21.md +++ /dev/null @@ -1,99 +0,0 @@ -# SRP Unity-Style URP Shadow Stage Ownership Plan - -日期:2026-04-21 - -## 阶段目标 - -本阶段不做 deferred。本阶段只做一件更关键的事: - -- 按 Unity 的 `SRP -> URP Asset -> RendererData -> Renderer -> RendererFeature/Pass` 分层, - 把 `ShadowCaster` 阶段的组织权从 native 默认宿主继续收回到 URP 包层。 - -当前主线已经完成了 SRP generic seam 收口,但 `ShadowCaster` 仍主要由 native 默认流程决定: - -1. native request/build-plan 默认逻辑决定阴影阶段是否存在。 -2. URP 目前只能改阴影 planning settings 和 execution policy key,不能显式组织阴影阶段。 -3. 这会直接阻塞后续 renderer model 和 deferred 演进。 - -## 为什么这一阶段优先级最高 - -如果现在直接做 deferred,会把以下职责继续错误地压在 C++ 宿主层: - -1. 阶段存在性判定 -2. ShadowCaster 与 MainScene 的耦合关系 -3. Forward/Deferred 的 renderer path 组织 - -最终会得到一个“有 C# 外壳的 C++ 内建管线”,而不是 Unity 风格的 `SRP + URP`。 - -## 对齐 Unity 的实现原则 - -这一阶段严格按下面的边界推进: - -1. `ScriptableRenderPipelineAsset` - - 只保留 SRP 抽象宿主职责。 - -2. `UniversalRenderPipelineAsset` - - 负责首方 URP 级别的全局设置与默认 renderer 组织。 - -3. `UniversalRendererData` - - 负责 renderer 级配置,不再只做 key 映射容器。 - -4. `UniversalRenderer` - - 负责主场景与阴影等核心 renderer stage 的组织。 - -5. `ScriptableRendererFeature` - - 负责注入和覆写,不负责继续依赖 native 默认猜测阶段。 - -## 本阶段具体工作 - -### 1. ShadowCaster 显式规划 API - -给 managed planning context 增加 `ShadowCaster` 显式请求/清理能力: - -1. native `CameraFramePlan` 记录 `ShadowCaster` 显式配置状态。 -2. managed `ScriptableRenderPipelinePlanningContext` 暴露对应 API。 -3. `DirectionalShadowRuntime` 尊重显式关闭,不再无条件按 native 默认继续推导。 - -### 2. URP 默认阴影阶段归位 - -让 `UniversalRenderPipelineAsset` 在 planning 阶段显式声明: - -1. 允许阴影时,请求 `ShadowCaster` -2. 关闭阴影时,清理 `ShadowCaster` - -这一步的意义不是改变最终效果,而是把“阶段组织权”显式放回 URP。 - -### 3. Feature 覆写路径归位 - -让类似 `DisableDirectionalShadowRendererFeature` 这类 feature: - -1. 不再只靠 request 阶段清掉 directional shadow 数据 -2. 同时在 plan 阶段显式清掉 `ShadowCaster` - -这样以后 renderer feature 覆写路径才符合 Unity 的思路。 - -## 本阶段不做的事 - -1. 不做 deferred renderer -2. 不做 GBuffer -3. 不重写整个 shadow runtime -4. 不一次性泛化所有 standalone stage - -## 完成标准 - -1. managed planning context 可以显式控制 `ShadowCaster` -2. `UniversalRenderPipelineAsset` 显式拥有默认阴影阶段组织权 -3. feature 可以显式覆写 `ShadowCaster` -4. 旧版 `XCEditor` Debug 编译通过 -5. 旧版编辑器冒烟 10 秒以上并出现新的 `SceneReady` - -## 下一阶段前置条件 - -只有本阶段完成,才进入下一阶段: - -- `URP Renderer Model Formalization` - -也就是: - -1. 把 `UniversalRendererData` 从“forward 默认配置袋”演进成真正的 renderer data 模型 -2. 为未来 `ForwardRenderer / DeferredRenderer` 拆分做好结构准备 diff --git a/docs/used/SRP_UnityStyleURPStandaloneStageBlockSemanticsPlan_完成归档_2026-04-21.md b/docs/used/SRP_UnityStyleURPStandaloneStageBlockSemanticsPlan_完成归档_2026-04-21.md deleted file mode 100644 index c71f2f69..00000000 --- a/docs/used/SRP_UnityStyleURPStandaloneStageBlockSemanticsPlan_完成归档_2026-04-21.md +++ /dev/null @@ -1,93 +0,0 @@ -# SRP Unity-Style URP Standalone Stage Block Semantics Plan - -Date: 2026-04-21 - -## Goal - -Continue aligning the managed rendering stack with Unity's SRP/URP split: - -- `ScriptableRenderer` owns stage/block organization -- `RendererFeature` and `ScriptableRenderPass` inject work by `RenderPassEvent` -- standalone blocks such as shadow casting and depth prepass are valid managed recording targets - -This stage does not add deferred rendering. The purpose is to make `ShadowCaster` and `DepthOnly` first-class managed stage blocks, so later SRP work is built on a real execution seam instead of placeholder enums. - -## Current Problems - -The current code still has four structural gaps: - -1. Managed stage recording only supports `MainScene` and fullscreen stages. -2. `ScriptableRenderContext.DrawRenderersByDesc(...)` hard-blocks all non-`MainScene` stages. -3. `NativeSceneRecorder` rejects `ShadowCaster` and `DepthOnly`, so managed standalone stages cannot record meaningful scene draw work. -4. Managed `RenderPassEvent` still cannot express Unity-style shadow/prepass blocks. - -Because of this, adding `BeforeRenderingShadows` or `BeforeRenderingPrePasses` right now would only create API decoration without real execution support. - -## Scope - -### 1. Enable managed recording for standalone scene-pass stages - -Make the managed render-pipeline bridge treat `ShadowCaster` and `DepthOnly` as valid recordable stages. - -Required work: - -- relax managed stage-recording support checks -- allow `ScriptableRenderContext.DrawRenderersByDesc(...)` inside standalone scene-pass stages -- allow `NativeSceneRecorder` to build raster work for standalone scene-pass stages - -### 2. Make native scene drawing usable inside standalone stages - -The native scene path must be able to execute depth-only and shadow-caster material passes when recording through the managed bridge. - -Required work: - -- let `BuiltinForwardPipeline` render into depth-only surfaces -- extend scene-draw pass resolution so `ShadowCaster` and `DepthOnly` shader pass names are supported -- provide stage-aware defaults where needed so managed passes can target standalone stages without custom native-only code - -### 3. Add explicit planning controls for standalone stage ownership - -Match the earlier `ShadowCaster` stage-ownership cleanup and remove hidden coupling. - -Required work: - -- add explicit `DepthOnly` plan state beside `ShadowCaster` -- expose managed planning bridge methods for requesting and clearing `DepthOnly` -- keep `UniversalRenderer` / future renderer code responsible for stage existence instead of scattering it into random features - -### 4. Expand Unity-style block events - -After execution support exists, expand managed URP event semantics. - -Required work: - -- add `BeforeRenderingShadows` / `AfterRenderingShadows` -- add `BeforeRenderingPrePasses` / `AfterRenderingPrePasses` -- map those events to `CameraFrameStage.ShadowCaster` and `CameraFrameStage.DepthOnly` -- infer standalone stage requests from the queued pass list where appropriate - -## Out Of Scope - -- deferred renderer -- GBuffer -- moving all native shadow logic out of C++ in this stage -- full Unity parity for every URP event value -- editor-side UX work - -## Done Criteria - -1. Managed renderer/pass code can target `ShadowCaster` and `DepthOnly` as real render-graph stages. -2. `DrawRenderersByDesc(...)` can record scene draw work inside those standalone stages. -3. Managed `RenderPassEvent` includes Unity-style shadow/prepass block events. -4. `ScriptableRenderer` can infer standalone stage presence from its pass queue, instead of relying only on manual plan code. -5. Old `XCEditor` builds in Debug. -6. Old editor smoke passes for at least about 10 seconds and a fresh `SceneReady` appears in `editor/bin/Debug/editor.log`. - -## Follow-Up After This Stage - -Once this stage is closed, the next SRP work can safely move higher in the stack: - -- renderer-level block composition -- renderer variants / multiple renderer strategies -- managed shadow/depth passes replacing more native fallback ownership -- eventually, a cleaner path to Unity-style SRP + URP package layering diff --git a/docs/used/SRP_UniversalBuiltinSceneFeaturesManagedOwnership计划_完成归档_2026-04-21.md b/docs/used/SRP_UniversalBuiltinSceneFeaturesManagedOwnership计划_完成归档_2026-04-21.md deleted file mode 100644 index 51be7ad3..00000000 --- a/docs/used/SRP_UniversalBuiltinSceneFeaturesManagedOwnership计划_完成归档_2026-04-21.md +++ /dev/null @@ -1,100 +0,0 @@ -# SRP Universal Builtin Scene Features Managed Ownership 计划 2026-04-21 - -## 1. 阶段目标 - -把当前 `GaussianSplat` / `Volumetric` 这类 main-scene 高层效果从 -“`BuiltinForwardPipeline` 在 C++ 注入点里自动持有并自动执行”的状态, -收成更接近 Unity URP 的 ownership: - -1. `UniversalRenderer` 不再依赖 generic native injection pass 来偷偷执行这些高层效果 -2. 这些效果改成由 managed `ScriptableRendererFeature` 显式挂到 `UniversalRendererData` -3. C++ 仍然保留底层 native pass 实现,但调度权回到 URP feature 层 - -## 2. 当前问题 - -当前最不 Unity-like 的点不是 `DrawRenderers` 主链,而是 builtin scene feature 的 ownership: - -1. `BuiltinForwardSceneSetup` 会直接把 `BuiltinGaussianSplatPass` 和 `BuiltinVolumetricPass` - 注册到 C++ `SceneRenderFeatureHost` -2. `UniversalRenderer` 里仍然有 `UniversalSceneInjectionPass` - 去调用 `RecordSceneInjectionPoint(BeforeTransparent)` -3. 结果是: - - `GaussianSplat` / `Volumetric` 虽然表现上跟着 URP 主线走 - - 但真正的 feature ownership 仍然在 C++ automatic injection,而不是 C# renderer feature - -这会带来三个问题: - -1. 用户无法像 Unity 一样从 renderer feature 层显式打开/关闭这些效果 -2. `UniversalRenderer` 仍然混着“显式 pass 调度”和“隐式 C++ 注入”两套模型 -3. 后续继续把阴影、更多高层特性收进 URP 时,会越来越难收口 - -## 3. 本阶段原则 - -这一步只解决 ownership,不急着把底层 GPU 实现也搬到 C#: - -1. 调度权在 managed `ScriptableRendererFeature` -2. native builtin pass 先保留为 backend implementation -3. `ScriptableRenderContext` 提供显式录制 named native scene feature pass 的桥 -4. `UniversalRenderer` 回到 Unity 风格: - - builtin scene draw pass 显式排队 - - feature pass 由 renderer feature 显式排队 - - 不再靠 generic injection pass 偷偷执行高层效果 - -## 4. 本阶段范围 - -包括: - -1. `SceneRenderFeatureHost` 增加按名字录制单个 feature pass 的入口 -2. `NativeSceneRecorder` / `ScriptableRenderContext` 打通对应 managed/native bridge -3. 新增 managed `NativeSceneFeaturePass` -4. 新增 `BuiltinGaussianSplatRendererFeature` -5. 新增 `BuiltinVolumetricRendererFeature` -6. `UniversalRenderer` 移除 generic injection pass 依赖 -7. `UniversalRendererData` 默认挂上上述 builtin managed features -8. 重新编译 `XCEditor` -9. 运行旧版 `editor/bin/Debug/XCEngine.exe` 冒烟至少 10 秒 - -不包括: - -1. 把 `GaussianSplat` / `Volumetric` 的底层 GPU 实现本身改写成纯 C# -2. 阴影系统迁移到 managed feature -3. deferred renderer -4. renderer inspector/editor UI - -## 5. 实施步骤 - -### Step 1:给 managed 侧一个显式录制 native scene feature 的桥 - -做这几件事: - -1. `SceneRenderFeatureHost` 支持按 feature pass 名称录制单个 pass -2. `NativeSceneRecorder` 暴露对应入口 -3. `ScriptableRenderContext` 增加 `RecordNativeSceneFeaturePass(string featurePassName)` - -### Step 2:把 UniversalRenderer 的 implicit injection 改成 explicit managed feature - -做这几件事: - -1. 去掉 `UniversalRenderer` 里当前为了 C++ feature host 服务的 generic injection pass -2. 新增 managed `NativeSceneFeaturePass` -3. 用 `BuiltinGaussianSplatRendererFeature` - / `BuiltinVolumetricRendererFeature` - 显式把对应 native builtin pass 挂到 `BeforeRenderingTransparents` - -### Step 3:把默认 ownership 切到 UniversalRendererData - -要求: - -1. 默认行为不能比当前少功能 -2. 用户应该可以像 Unity 一样从 `rendererFeatures` 层决定是否保留这些效果 -3. 效果排序由 `RenderPassEvent` 决定,而不是由 C++ implicit injection 决定 - -## 6. 验收标准 - -本阶段收口后必须满足: - -1. `UniversalRenderer` 不再依赖 generic `RecordSceneInjectionPoint` 来执行 `GaussianSplat` / `Volumetric` -2. `GaussianSplat` / `Volumetric` 默认通过 managed renderer feature 挂载 -3. native builtin pass 仍然可以正常执行,不丢当前功能 -4. `XCEditor` 编译通过 -5. 旧版 editor 冒烟通过 diff --git a/docs/used/SRP_UniversalCameraFramePlanPolicyManagedOwnershipPlan_完成归档_2026-04-21.md b/docs/used/SRP_UniversalCameraFramePlanPolicyManagedOwnershipPlan_完成归档_2026-04-21.md deleted file mode 100644 index 7279c0d4..00000000 --- a/docs/used/SRP_UniversalCameraFramePlanPolicyManagedOwnershipPlan_完成归档_2026-04-21.md +++ /dev/null @@ -1,66 +0,0 @@ -# SRP Universal Camera Frame Plan Policy Managed Ownership Plan 2026-04-21 - -## Goal - -Move default camera-frame planning policy selection out of native `RenderPipelineAsset` hardcoded flow and into managed URP ownership. - -This stage still keeps the actual frame-plan mutation native. -It only makes managed URP explicitly choose which native camera-frame plan policy asset key should be used. - -## Why This Stage - -The current SRP split already moved: - -1. shadow planning policy into managed URP asset ownership; -2. shadow execution policy into managed URP renderer-data ownership; -3. render-scene setup policy into managed URP renderer-data ownership; -4. shadow-caster standalone stage selection into managed URP renderer-data ownership. - -But `RenderPipelineAsset::ConfigureCameraFramePlan()` still directly hardcodes: - -1. default final-color policy resolution; -2. default fullscreen stage planning for post-process and final-output. - -Managed URP can currently override pieces afterward, but the native layer still decides the default path first. -That is not the final SRP ownership shape. - -## Scope - -Included: - -1. add a native registry for camera-frame plan policies; -2. add managed asset / renderer-data API for camera-frame plan policy asset keys; -3. let Mono runtime resolve those keys from managed pipeline assets; -4. let `ManagedScriptableRenderPipelineAsset` use managed-selected frame-plan policy keys before invoking managed frame-plan configuration; -5. make `UniversalRendererData` explicitly own the builtin camera-frame plan policy key; -6. rebuild `XCEditor` and run old editor smoke. - -Not included: - -1. exposing raw `ResolvedFinalColorPolicy` editing directly to C#; -2. moving fullscreen-stage planning logic into managed code; -3. moving camera-frame validation into managed code; -4. post-process pass authoring UI. - -## Acceptance - -This stage is complete when: - -1. managed URP explicitly owns camera-frame default policy selection; -2. native default camera-frame plan policy is no longer blindly hardcoded for managed pipeline assets; -3. builtin URP still gets the same default final-color and fullscreen-stage behavior; -4. `XCEditor` build and old editor smoke both pass. - -## Result - -Completed. - -Managed URP renderer data now selects the native camera-frame plan policy key through the same `managed key -> native registry -> builtin policy` ownership path already used by the earlier SRP stages. - -`UniversalRendererData` explicitly selects builtin key `"BuiltinDefaultCameraFramePlan"`, while native still owns the concrete default frame-plan mutation logic for final-color resolution and fullscreen-stage defaults. - -## Validation - -1. `cmake --build . --config Debug --target XCEditor` -2. launched `editor/bin/Debug/XCEngine.exe` for about 12 seconds -3. verified `editor/bin/Debug/editor.log` contains new `SceneReady` at `2026-04-21 12:20:32` diff --git a/docs/used/SRP_UniversalDirectionalShadowExecutionPolicyManagedOwnershipPlan_完成归档_2026-04-21.md b/docs/used/SRP_UniversalDirectionalShadowExecutionPolicyManagedOwnershipPlan_完成归档_2026-04-21.md deleted file mode 100644 index d3e380fa..00000000 --- a/docs/used/SRP_UniversalDirectionalShadowExecutionPolicyManagedOwnershipPlan_完成归档_2026-04-21.md +++ /dev/null @@ -1,65 +0,0 @@ -# SRP Universal Directional Shadow Execution Policy Managed Ownership Plan 2026-04-21 - -## Goal - -Move directional shadow execution policy selection out of native host default logic and into managed URP ownership. - -This stage still keeps native shadow rendering and native surface allocation. -It only makes managed URP explicitly choose which native directional-shadow execution policy asset key should be used. - -## Why This Stage - -The current SRP/URP split already moved: - -1. renderer selection into managed URP; -2. builtin scene feature ownership into managed renderer features; -3. shadow planning defaults into managed asset settings; -4. shadow-caster standalone pass selection into managed URP. - -The remaining hardcoded gap is the conversion from: - -1. `DirectionalShadowRenderPlan` - -to - -2. `DirectionalShadowExecutionState` (`shadowCasterRequest + shadowData`). - -That conversion is still owned by native host fallback logic through -`RenderPipeline::ConfigureDirectionalShadowExecutionState()`. - -## Scope - -Included: - -1. add a native registry for directional shadow execution policies; -2. add managed asset / renderer-data API for directional shadow execution policy asset keys; -3. let Mono runtime resolve those keys from managed pipeline assets; -4. let `ScriptableRenderPipelineHost` use managed-selected directional shadow execution policy keys; -5. make `UniversalRendererData` explicitly own the builtin directional shadow execution policy key; -6. rebuild `XCEditor` and run old editor smoke. - -Not included: - -1. moving shadow rasterization into C#; -2. cascades; -3. atlas packing; -4. custom managed shadow execution policy authoring UI. - -## Acceptance - -This stage is complete when: - -1. managed URP explicitly owns directional shadow execution policy selection; -2. native host no longer blindly applies the default directional shadow execution policy for managed pipelines; -3. builtin fallback rendering still works; -4. `XCEditor` build and old editor smoke both pass. - -## Result - -Completed on 2026-04-21. - -Validation: - -1. `cmake --build . --config Debug --target XCEditor` passed; -2. old editor smoke passed with `editor/bin/Debug/XCEngine.exe`; -3. `editor/bin/Debug/editor.log` recorded `SceneReady` at `2026-04-21 02:47:28`. diff --git a/docs/used/SRP_UniversalDirectionalShadowPlanningPolicyManagedOwnershipPlan_完成归档_2026-04-21.md b/docs/used/SRP_UniversalDirectionalShadowPlanningPolicyManagedOwnershipPlan_完成归档_2026-04-21.md deleted file mode 100644 index 99dec791..00000000 --- a/docs/used/SRP_UniversalDirectionalShadowPlanningPolicyManagedOwnershipPlan_完成归档_2026-04-21.md +++ /dev/null @@ -1,63 +0,0 @@ -# SRP Universal Directional Shadow Planning Policy Managed Ownership Plan 2026-04-21 - -## Goal - -Move directional shadow planning policy selection out of native `RenderPipelineAsset` default flow and into managed URP asset ownership. - -This stage still keeps the actual shadow-plan construction native. -It only makes managed URP explicitly choose which native directional-shadow planning policy asset key should be used. - -## Why This Stage - -The current SRP split already moved: - -1. renderer selection into managed URP; -2. builtin scene feature ownership into managed renderer features; -3. shadow planning settings into managed URP asset settings; -4. shadow-caster stage pass selection into managed URP renderer data; -5. directional shadow execution policy selection into managed URP. - -The remaining shadow entry gap is earlier in the pipeline: - -1. `RenderPipelineAsset::ConfigureCameraRenderRequest()` still blindly applies the native default directional-shadow planning policy; -2. managed URP can only tweak planning settings afterward or clear the request entirely. - -That means the managed package still does not explicitly own which shadow-planning path gets executed. - -## Scope - -Included: - -1. add a native registry for directional shadow planning policies; -2. add managed asset API for directional shadow planning policy asset keys; -3. let Mono runtime resolve those keys from managed pipeline assets; -4. let `ManagedScriptableRenderPipelineAsset` use managed-selected planning policy keys before invoking managed request configuration; -5. let managed request re-planning after settings changes use the same managed-selected policy key; -6. make `UniversalRenderPipelineAsset` explicitly own the builtin directional shadow planning policy key; -7. rebuild `XCEditor` and run old editor smoke. - -Not included: - -1. moving directional shadow plan construction into C#; -2. exposing raw light-selection logic to managed code; -3. renderer-specific shadow planning overrides; -4. shadow atlas / cascade authoring. - -## Acceptance - -This stage is complete when: - -1. managed URP explicitly owns directional shadow planning policy selection; -2. native default planning is no longer blindly hardcoded for managed pipeline assets; -3. managed re-planning after shadow-setting changes uses the same managed-owned planning policy; -4. `XCEditor` build and old editor smoke both pass. - -## Result - -Completed on 2026-04-21. - -Validation: - -1. `cmake --build . --config Debug --target XCEditor` passed; -2. old editor smoke passed with `editor/bin/Debug/XCEngine.exe`; -3. `editor/bin/Debug/editor.log` recorded `SceneReady` at `2026-04-21 03:00:17`. diff --git a/docs/used/SRP_UniversalExplicitFullscreenStagePlanningPlan_完成归档_2026-04-21.md b/docs/used/SRP_UniversalExplicitFullscreenStagePlanningPlan_完成归档_2026-04-21.md deleted file mode 100644 index b6dbd4b8..00000000 --- a/docs/used/SRP_UniversalExplicitFullscreenStagePlanningPlan_完成归档_2026-04-21.md +++ /dev/null @@ -1,52 +0,0 @@ -# SRP Universal Explicit Fullscreen Stage Planning Plan 2026-04-21 - -## Goal - -Remove renderer-level fullscreen stage request heuristics and make managed URP fullscreen stage ownership explicit through renderer features. - -## Why This Stage - -Current managed URP already owns more of the camera-frame plan, but one important seam is still implicit: - -1. `ScriptableRenderer.ConfigureCameraFramePlan(...)` scans queued render passes; -2. it infers whether `PostProcess` or `FinalOutput` should exist from `RenderPassEvent`; -3. fullscreen stage planning therefore still depends on hidden pass-event heuristics instead of explicit managed planning code. - -That is weak architecture for a Unity-style SRP/URP direction. A renderer feature that needs a fullscreen stage should explicitly request it. - -## Scope - -Included: - -1. remove fullscreen-stage inference from `ScriptableRenderer`; -2. make first-party post-process-style feature planning explicit; -3. keep builtin final-color feature on explicit planning path; -4. rebuild `XCEditor` and run old editor smoke. - -Not included: - -1. moving all native fullscreen request legacy paths out of the engine; -2. deleting native final-color or vector fullscreen pass implementations; -3. deferred renderer work; -4. editor UI work. - -## Acceptance - -This stage is complete when: - -1. `ScriptableRenderer` no longer auto-requests fullscreen stages from queued pass events; -2. first-party managed fullscreen features explicitly request the stages they need; -3. `XCEditor` build passes; -4. old editor smoke passes and `editor.log` contains a fresh `SceneReady`. - -## Validation - -Validated on 2026-04-21: - -1. built old editor target with `cmake --build . --config Debug --target XCEditor` from `build/`; -2. launched `editor/bin/Debug/XCEngine.exe` for about 12 seconds; -3. verified fresh `editor/bin/Debug/editor.log` contains `SceneReady`. - -Observed log line: - -`[2026-04-21 13:06:17] [INFO] [General] [SceneLoadTrace] SceneReady elapsed_ms=5372 first_frame_ms=536 peak_pending_async=9` diff --git a/docs/used/SRP_UniversalFinalColorDefaultsManagedOwnershipPlan_完成归档_2026-04-21.md b/docs/used/SRP_UniversalFinalColorDefaultsManagedOwnershipPlan_完成归档_2026-04-21.md deleted file mode 100644 index dea5c118..00000000 --- a/docs/used/SRP_UniversalFinalColorDefaultsManagedOwnershipPlan_完成归档_2026-04-21.md +++ /dev/null @@ -1,53 +0,0 @@ -# SRP Universal Final Color Defaults Managed Ownership Plan 2026-04-21 - -## Goal - -Move default final-color settings ownership into managed URP asset data so `UniversalRenderPipelineAsset` explicitly supplies the pipeline-level `FinalColorSettings` used by native frame-plan policy resolution. - -## Why This Stage - -The native side already asks managed pipeline assets for `GetDefaultFinalColorSettings()`. -But current URP package still leaves that path at the base default, which means: - -1. URP asset does not explicitly own pipeline-level final-color defaults yet; -2. final-color runtime data is visible to managed renderer code, but its asset authoring surface is missing; -3. later migration of final-output behavior into managed URP would still lack a proper asset-owned source of truth. - -## Scope - -Included: - -1. add managed URP data container for default final-color settings; -2. make `UniversalRenderPipelineAsset` own and return those settings; -3. keep the native final-color execution path unchanged; -4. rebuild `XCEditor` and run old editor smoke. - -Not included: - -1. moving final-output pass execution into managed URP; -2. exposing raw `ResolvedFinalColorPolicy` mutation to C#; -3. editor inspector authoring UI; -4. post-process stack migration. - -## Acceptance - -This stage is complete when: - -1. `UniversalRenderPipelineAsset` explicitly owns default `FinalColorSettings`; -2. managed URP no longer relies on base-class implicit defaults for pipeline final-color settings; -3. existing editor build and smoke still pass. - -## Result - -Completed. - -Managed URP now has an explicit `UniversalFinalColorSettings` asset data container, and `UniversalRenderPipelineAsset` returns that data through `GetDefaultFinalColorSettings()`. - -The native execution path for final-color resolution remains unchanged in this stage. -Only the pipeline-level data ownership moved into the managed URP package. - -## Validation - -1. `cmake --build . --config Debug --target XCEditor` -2. launched `editor/bin/Debug/XCEngine.exe` for about 12 seconds -3. verified `editor/bin/Debug/editor.log` contains new `SceneReady` at `2026-04-21 12:42:26` diff --git a/docs/used/SRP_UniversalFinalColorExecutionManagedOwnershipPlan_完成归档_2026-04-21.md b/docs/used/SRP_UniversalFinalColorExecutionManagedOwnershipPlan_完成归档_2026-04-21.md deleted file mode 100644 index dedc8954..00000000 --- a/docs/used/SRP_UniversalFinalColorExecutionManagedOwnershipPlan_完成归档_2026-04-21.md +++ /dev/null @@ -1,57 +0,0 @@ -# SRP Universal Final Color Execution Managed Ownership Plan 2026-04-21 - -## Goal - -Move first-party final-color final-output execution from native fullscreen pass-sequence construction into managed URP renderer-feature ownership. - -## Why This Stage - -Current state already moved several SRP ownership seams into managed URP: - -1. renderer selection; -2. shadow planning and execution policy selection; -3. render-scene setup policy selection; -4. camera-frame plan policy selection; -5. URP asset-owned final-color default settings. - -But the actual first-party final-color pass is still a native builtin sequence created by the default camera-frame plan policy. - -That means URP still does not own its own final-output pass authoring surface, even though managed render-graph fullscreen recording already exists. - -## Scope - -Included: - -1. add a managed raster-pass execution primitive for final-color fullscreen passes; -2. expose planning-context visibility for whether resolved final color requires processing; -3. add a builtin URP final-color renderer feature and pass; -4. make managed URP explicitly claim the `FinalOutput` stage for final-color processing; -5. rebuild `XCEditor` and run old editor smoke. - -Not included: - -1. deleting native final-color pass implementation; -2. moving raw final-color policy resolution out of native camera-frame planning; -3. custom editor UI for URP final-color authoring; -4. deferred renderer work. - -## Acceptance - -This stage is complete when: - -1. managed URP can record a final-color fullscreen pass through the render-graph builder; -2. builtin URP provides a first-party final-color renderer feature; -3. final-color driven `FinalOutput` stage ownership no longer depends on native fullscreen sequence execution for managed URP; -4. `XCEditor` build and old editor smoke both pass. - -## Validation - -Validated on 2026-04-21: - -1. built old editor target with `cmake --build . --config Debug --target XCEditor` from `build/`; -2. launched `editor/bin/Debug/XCEngine.exe` for about 12 seconds; -3. verified fresh `editor/bin/Debug/editor.log` contains `SceneReady`. - -Observed log line: - -`[2026-04-21 12:58:06] [INFO] [General] [SceneLoadTrace] SceneReady elapsed_ms=4891 first_frame_ms=537 peak_pending_async=9` diff --git a/docs/used/SRP_UniversalFullscreenStageHeuristicCleanupPlan_完成归档_2026-04-21.md b/docs/used/SRP_UniversalFullscreenStageHeuristicCleanupPlan_完成归档_2026-04-21.md deleted file mode 100644 index 21b41b3b..00000000 --- a/docs/used/SRP_UniversalFullscreenStageHeuristicCleanupPlan_完成归档_2026-04-21.md +++ /dev/null @@ -1,58 +0,0 @@ -# SRP Universal Fullscreen Stage Heuristic Cleanup Plan 2026-04-21 - -## Goal - -Remove the native fullscreen-stage support heuristic from `CameraFramePlanBuilder` so managed URP stage planning no longer depends on hidden native inference from pipeline render-graph capability. - -## Why This Stage - -The current SRP split already gives managed URP explicit fullscreen-stage planning seams: - -1. `ScriptableRenderPipelineAsset.ConfigureCameraFramePlan(...)` -2. `ScriptableRendererData.ConfigureCameraFramePlan(...)` -3. `ScriptableRenderer.ConfigureCameraFramePlan(...)` -4. `ScriptableRendererFeature.ConfigureCameraFramePlan(...)` - -But `CameraFramePlanBuilder` still probes `pipeline->SupportsStageRenderGraph(...)` and silently upgrades that into requested `PostProcess` / `FinalOutput` stages. - -That keeps one native hidden decision path alive after managed URP already became capable of requesting those stages itself. - -## Scope - -Included: - -1. remove pipeline-support probing from `CameraFramePlanBuilder`; -2. keep legacy fullscreen pass-sequence requests working; -3. simplify `CameraFramePlanBuilder` interface so it no longer depends on `RenderPipeline`; -4. rebuild `XCEditor` and run old editor smoke. - -Not included: - -1. changing managed URP pass authoring APIs; -2. moving native fullscreen pass-sequence construction into C#; -3. deferred rendering work; -4. renderer inspector/editor integration. - -## Acceptance - -This stage is complete when: - -1. fullscreen stage requests are no longer inferred from `SupportsStageRenderGraph(...)` inside `CameraFramePlanBuilder`; -2. managed URP remains the explicit owner of renderer-driven fullscreen stages; -3. legacy native fullscreen pass sequences still request their stages correctly; -4. `XCEditor` build and old editor smoke both pass. - -## Result - -Completed. - -`CameraFramePlanBuilder` no longer probes pipeline stage support to infer `PostProcess` or `FinalOutput`. -It now only bridges already-authored legacy fullscreen pass-sequence requests into runtime stage requests, while managed URP keeps explicit ownership of renderer-driven fullscreen stage planning. - -The builder interface was also narrowed so it no longer takes a `RenderPipeline*`, which makes the class boundary reflect its actual responsibility. - -## Validation - -1. `cmake --build . --config Debug --target XCEditor` -2. launched `editor/bin/Debug/XCEngine.exe` for about 12 seconds -3. verified `editor/bin/Debug/editor.log` contains new `SceneReady` at `2026-04-21 12:35:36` diff --git a/docs/used/SRP_UniversalFullscreenStage归位计划_完成归档_2026-04-21.md b/docs/used/SRP_UniversalFullscreenStage归位计划_完成归档_2026-04-21.md deleted file mode 100644 index ae43f06c..00000000 --- a/docs/used/SRP_UniversalFullscreenStage归位计划_完成归档_2026-04-21.md +++ /dev/null @@ -1,99 +0,0 @@ -# SRP Universal Fullscreen Stage 归位计划 2026-04-21 - -## 1. 阶段目标 - -把 `PostProcess` / `FinalOutput` 这两个 fullscreen stage 的默认规划逻辑,从零散的 feature 手工 `ConfigureCameraFramePlan()` 请求,收回到 renderer/pass 队列本身。 - -这一阶段完成后,要达到: - -1. renderer pass event 本身就能表达 pass 属于 `MainScene` / `PostProcess` / `FinalOutput` -2. `ScriptableRenderer` 可以基于已构建的 pass 队列自动请求 fullscreen stage -3. Universal 内建 renderer 和首方 feature 不再依赖手工点亮默认 stage -4. feature 继续负责扩展 pass,而不是承担默认 stage orchestration - -## 2. 当前问题 - -目前的主问题不是 `mainScene`,而是 fullscreen stage 规划还不够像 Unity: - -1. `ColorScalePostProcessRendererFeature` 这类 feature 还要自己实现 `ConfigureCameraFramePlan()` -2. `AddRenderPasses()` 里仍然存在按当前 stage 判断是否 enqueue 的逻辑 -3. stage 是否存在,并不是由 renderer 的 pass 拓扑自然推导出来 - -这会导致: - -1. stage planning 逻辑散落在 feature 层 -2. renderer 不能单独说明“我这一帧会组织哪些 stage” -3. 越往后做 fullscreen/post-process,越容易继续堆临时逻辑 - -## 3. 设计原则 - -这一步按 Unity URP 的职责划分来做: - -1. `ScriptableRenderPass.renderPassEvent` 决定 pass 所属 stage -2. `ScriptableRenderer` 负责组织 pass 队列并从中推导 stage 需求 -3. `ScriptableRendererFeature` 负责提供 pass -4. `ConfigureCameraFramePlan()` 只保留给特殊场景的显式覆盖,不再承担默认主链职责 - -## 4. 本阶段范围 - -包括: - -1. 给 `ScriptableRenderer` 增加基于 pass 队列的 fullscreen stage 自动规划 -2. 去掉 Universal 首方 renderer/feature 中按当前 stage 才 enqueue pass 的硬判断 -3. 让 `ColorScalePostProcessRendererFeature` 回到“只提供 post-process pass”的职责 -4. 保持现有 main-scene / post-process 行为不回退 -5. 重编译 `XCEditor` -6. 运行旧版 `editor/bin/Debug/XCEngine.exe` 冒烟至少 10 秒 - -不包括: - -1. 多 renderer 选择 -2. deferred renderer -3. renderer inspector UI -4. final color policy 从 pipeline asset 挪到 renderer data - -## 5. 实施步骤 - -### Step 1:让 renderer 能基于 pass 队列规划 fullscreen stage - -新增 renderer planning 入口: - -1. 构建当前 renderer pass 队列 -2. 扫描是否存在 `PostProcess` pass -3. 扫描是否存在 `FinalOutput` pass -4. 自动请求对应 fullscreen stage - -### Step 2:清理 Universal 首方 stage-gated enqueue - -把这些逻辑从“当前 stage 才 enqueue”改成“统一 enqueue,由 pass event 决定在哪个 stage 生效”: - -1. `UniversalRenderer` -2. `RenderObjectsRendererFeature` -3. `ColorScalePostProcessRendererFeature` - -### Step 3:验证旧 feature 扩展仍然可用 - -确保: - -1. main-scene pass 仍只在 main-scene record -2. post-process pass 仍只在 post-process record -3. stage 规划由 renderer 主链自动完成 - -### Step 4:编译与冒烟 - -固定流程: - -1. `cmake --build . --config Debug --target XCEditor` -2. 启动 `editor/bin/Debug/XCEngine.exe` -3. 冒烟至少 10 秒 -4. 检查 `editor/bin/Debug/editor.log` - -## 6. 验收标准 - -本阶段收口后必须满足: - -1. Universal 首方 post-process feature 不再依赖手工 `ConfigureCameraFramePlan()` 请求默认 stage -2. renderer 可以从 pass queue 自动推导 `PostProcess` / `FinalOutput` -3. `AddRenderPasses()` 不再大量依赖当前 stage 判定才 enqueue -4. `XCEditor` 编译通过 -5. 旧 editor 冒烟通过 diff --git a/docs/used/SRP_UniversalManagedCameraFrameBaselineSignalPlan_完成归档_2026-04-21.md b/docs/used/SRP_UniversalManagedCameraFrameBaselineSignalPlan_完成归档_2026-04-21.md deleted file mode 100644 index 76fb7e51..00000000 --- a/docs/used/SRP_UniversalManagedCameraFrameBaselineSignalPlan_完成归档_2026-04-21.md +++ /dev/null @@ -1,58 +0,0 @@ -# SRP Universal Managed Camera Frame Baseline Signal Plan 2026-04-21 - -## Goal - -Stop renderer-backed managed pipelines from acquiring native camera-frame baseline behavior through camera-frame plan policy keys, and replace that path with an explicit managed capability signal. - -## Why This Stage - -The previous stage split a native managed baseline policy from the old default camera-frame plan policy, but renderer-backed managed pipelines still reach that baseline indirectly: - -1. managed asset runtime asks for a camera-frame plan policy key; -2. renderer-backed managed asset selects a builtin baseline key; -3. native host resolves that key back into baseline behavior. - -That is still string-driven indirection in the core SRP path. The cleaner ownership model is: - -1. managed asset says whether it needs native camera-frame baseline preparation; -2. native host applies that baseline directly; -3. managed renderer data / renderer / features continue the actual frame planning. - -## Scope - -Included: - -1. add a managed asset runtime capability signal for native camera-frame baseline usage; -2. make `ManagedScriptableRenderPipelineAsset` apply native baseline directly from that signal; -3. remove renderer-backed managed reliance on camera-frame plan policy key selection for this path; -4. remove renderer-data-level camera-frame plan policy key plumbing if it is no longer used; -5. rebuild `XCEditor` and run old editor smoke. - -Not included: - -1. deleting generic camera-frame plan policy key support for all possible managed assets; -2. moving final-color policy resolution fully into managed code; -3. deleting the legacy native default frame-plan policy for non-managed pipelines; -4. deferred renderer work. - -## Acceptance - -This stage is complete when: - -1. renderer-backed managed pipelines request native baseline through an explicit capability signal instead of a camera-frame plan policy key; -2. `ManagedScriptableRenderPipelineAsset` applies baseline directly for that path; -3. renderer-data-level camera-frame policy key plumbing is removed if unused; -4. `XCEditor` build passes; -5. old editor smoke passes and `editor.log` contains a fresh `SceneReady`. - -## Validation - -Validated on 2026-04-21: - -1. built old editor target with `cmake --build . --config Debug --target XCEditor` from `build/`; -2. launched `editor/bin/Debug/XCEngine.exe` for about 12 seconds; -3. verified fresh `editor/bin/Debug/editor.log` contains `SceneReady`. - -Observed log line: - -`[2026-04-21 13:34:54] [INFO] [General] [SceneLoadTrace] SceneReady elapsed_ms=6190 first_frame_ms=746 peak_pending_async=9` diff --git a/docs/used/SRP_UniversalManagedCameraFramePlanBaselineOwnershipPlan_完成归档_2026-04-21.md b/docs/used/SRP_UniversalManagedCameraFramePlanBaselineOwnershipPlan_完成归档_2026-04-21.md deleted file mode 100644 index 0bba6262..00000000 --- a/docs/used/SRP_UniversalManagedCameraFramePlanBaselineOwnershipPlan_完成归档_2026-04-21.md +++ /dev/null @@ -1,55 +0,0 @@ -# SRP Universal Managed Camera Frame Plan Baseline Ownership Plan 2026-04-21 - -## Goal - -Detach managed URP from the native `BuiltinDefaultCameraFramePlan` policy and replace it with a minimal native baseline policy that only resolves the data still required before managed planning runs. - -## Why This Stage - -Current SRP work already moved fullscreen stage ownership into managed URP features, but managed URP still references the old native default camera-frame plan policy. - -That default policy still bundles two responsibilities: - -1. resolve final-color policy from pipeline defaults and camera overrides; -2. build native fullscreen stage state and owned fullscreen pass sequences. - -For renderer-backed managed pipelines, responsibility `2` is no longer the right owner. We already clear that legacy state before managed renderer planning runs, so the remaining native need is only the baseline final-color policy resolution. - -## Scope - -Included: - -1. split native camera-frame plan baseline policy from the old default fullscreen-planning policy; -2. register a builtin managed baseline camera-frame policy key; -3. make renderer-backed managed pipelines default to that baseline policy when they own fullscreen stage planning explicitly; -4. remove the explicit `BuiltinDefaultCameraFramePlan` dependency from URP renderer data; -5. rebuild `XCEditor` and run old editor smoke. - -Not included: - -1. deleting the old `BuiltinDefaultCameraFramePlan` policy for non-managed paths; -2. moving final-color policy resolution fully into managed code; -3. deleting native fullscreen pass implementations; -4. deferred renderer work. - -## Acceptance - -This stage is complete when: - -1. managed URP no longer selects `BuiltinDefaultCameraFramePlan`; -2. a dedicated builtin managed baseline policy resolves the native data that managed planning still needs; -3. renderer-backed managed pipelines use that baseline by default when explicit fullscreen stage planning is enabled; -4. `XCEditor` build passes; -5. old editor smoke passes and `editor.log` contains a fresh `SceneReady`. - -## Validation - -Validated on 2026-04-21: - -1. built old editor target with `cmake --build . --config Debug --target XCEditor` from `build/`; -2. launched `editor/bin/Debug/XCEngine.exe` for about 12 seconds; -3. verified fresh `editor/bin/Debug/editor.log` contains `SceneReady`. - -Observed log line: - -`[2026-04-21 13:21:42] [INFO] [General] [SceneLoadTrace] SceneReady elapsed_ms=5834 first_frame_ms=593 peak_pending_async=9` diff --git a/docs/used/SRP_UniversalManagedFullscreenPlanningOwnershipPlan_完成归档_2026-04-21.md b/docs/used/SRP_UniversalManagedFullscreenPlanningOwnershipPlan_完成归档_2026-04-21.md deleted file mode 100644 index 6fd46159..00000000 --- a/docs/used/SRP_UniversalManagedFullscreenPlanningOwnershipPlan_完成归档_2026-04-21.md +++ /dev/null @@ -1,51 +0,0 @@ -# SRP Universal Managed Fullscreen Planning Ownership Plan 2026-04-21 - -## Goal - -Make renderer-backed managed pipelines explicitly own fullscreen stage planning by clearing native legacy fullscreen stage state before managed renderer planning runs. - -## Why This Stage - -The previous stage removed renderer-level fullscreen heuristics, but one legacy seam is still present: - -1. native camera-frame policy can prebuild post-process and final-output fullscreen state; -2. managed URP then reshapes or replaces that state later; -3. ownership is therefore still mixed during plan construction. - -For a Unity-style SRP/URP direction, renderer-backed managed assets should start from a clean fullscreen-stage slate and let managed renderer data/features re-declare the stages they need. - -## Scope - -Included: - -1. clear legacy `PostProcess` and `FinalOutput` stage state at the start of renderer-backed managed planning; -2. keep this behavior overridable in the renderer-backed asset base; -3. rebuild `XCEditor` and run old editor smoke. - -Not included: - -1. removing native final-color policy resolution; -2. deleting native fullscreen pass implementations; -3. deleting legacy fullscreen planning for non-managed pipelines; -4. deferred renderer work. - -## Acceptance - -This stage is complete when: - -1. renderer-backed managed pipelines begin planning with cleared legacy fullscreen stage state; -2. managed URP still reconstructs the required fullscreen stages through renderer features; -3. `XCEditor` build passes; -4. old editor smoke passes and `editor.log` contains a fresh `SceneReady`. - -## Validation - -Validated on 2026-04-21: - -1. built old editor target with `cmake --build . --config Debug --target XCEditor` from `build/`; -2. launched `editor/bin/Debug/XCEngine.exe` for about 12 seconds; -3. verified fresh `editor/bin/Debug/editor.log` contains `SceneReady`. - -Observed log line: - -`[2026-04-21 13:11:41] [INFO] [General] [SceneLoadTrace] SceneReady elapsed_ms=6597 first_frame_ms=757 peak_pending_async=9` diff --git a/docs/used/SRP_UniversalNativeRendererBinding计划_完成归档_2026-04-21.md b/docs/used/SRP_UniversalNativeRendererBinding计划_完成归档_2026-04-21.md deleted file mode 100644 index e233a4ab..00000000 --- a/docs/used/SRP_UniversalNativeRendererBinding计划_完成归档_2026-04-21.md +++ /dev/null @@ -1,106 +0,0 @@ -# SRP Universal Native Renderer Binding 计划 2026-04-21 - -## 1. 阶段目标 - -把当前 managed SRP/Universal 宿主里“native pipeline renderer 只按全局默认 key 绑定一次”的模型收口成: - -1. `stage recorder` 在录制时按当前 `rendererIndex` 解析需要的 native scene renderer -2. `ScriptableRenderPipelineHost` 在有 managed `stage recorder` 时,以它为 authoritative path -3. `renderer selection` 主链从 request / plan / execution / capability,继续延伸到 native renderer binding - -## 2. 当前问题 - -当前真实模型是: - -1. `MonoManagedRenderPipelineAssetRuntime::GetPipelineRendererAsset()` - 只会为整个 managed asset runtime 解析一次 global native renderer asset -2. `ScriptableRenderPipelineHost` - 也只持有一个 `m_pipelineRenderer` -3. `MonoManagedRenderPipelineStageRecorder` - 在 main-scene 录制时优先复用 host 里那个 global renderer - -这会带来两个结构问题: - -1. 即使前面已经有 `rendererIndex` 主链,main-scene native 录制仍然可能被 default renderer 污染 -2. 如果 stage recorder 已经支持当前 stage,但由于 native renderer binding 不对导致录制失败, - host 还会回退到全局 renderer,继续把错误掩盖掉 - -## 3. 设计原则 - -这一步按 Unity 风格职责划分来收: - -1. `RendererData / rendererIndex` - 决定当前相机选中的 renderer 配置 -2. managed pipeline asset - 提供“按 rendererIndex 解析 native renderer family key”的 seam -3. Mono stage recorder - 负责在录制时按当前 `rendererIndex` 解析 native scene renderer -4. host - 只做 orchestration,不再用 global default renderer 抢 stage recorder 的最终决定权 - -也就是说: - -`camera -> rendererIndex -> renderer data -> native renderer asset key -> native scene renderer` - -## 4. 本阶段范围 - -包括: - -1. `ScriptableRenderPipelineAsset` 增加 contextual native renderer key seam -2. `RendererBackedRenderPipelineAsset` 按 rendererIndex 解析对应 renderer data 的 key -3. native `ManagedRenderPipelineAssetRuntime` 增加 contextual renderer asset resolve 接口 -4. `MonoManagedRenderPipelineStageRecorder` 按 `context.rendererIndex` 解析 native scene renderer -5. `ScriptableRenderPipelineHost` 在有 stage recorder 时,不再把 global renderer 当成 authoritative support / record fallback -6. 重新编译 `XCEditor` -7. 运行旧版 `editor/bin/Debug/XCEngine.exe` 冒烟至少 10 秒 - -不包括: - -1. 一个 pipeline asset 内混挂多种完全不同 native backend family 的完整产品化支持 -2. deferred renderer -3. renderer inspector UI -4. camera stack 完整系统 - -## 5. 实施步骤 - -### Step 1:补 managed contextual native renderer key seam - -做这几件事: - -1. `ScriptableRenderPipelineAsset` - 增加 `GetPipelineRendererAssetKeyContextual(int rendererIndex)` 回退 seam -2. `RendererBackedRenderPipelineAsset` - 用 resolved renderer data 返回对应 key -3. native runtime - 增加 `GetPipelineRendererAsset(int rendererIndex)` 接口 - -### Step 2:让 Mono stage recorder 按 context.rendererIndex 解析 native scene renderer - -要求: - -1. 不能再盲信 host 里那个全局默认 renderer -2. 当前 renderer key 如果无效,录制要显式失败,而不是偷偷回到 default renderer -3. 允许对同一 backend family 做轻量缓存,不必每次重新建 renderer - -### Step 3:让 host 把 stage recorder 视为 authoritative path - -要改: - -1. contextual `SupportsStageRenderGraph` -2. `RecordStageRenderGraph` - -规则: - -1. 有 stage recorder 时,先问它支不支持 -2. 它说支持,就由它负责录制结果 -3. 只有它不支持当前 stage/context 时,才允许走 host 自己的 native renderer - -## 6. 验收标准 - -本阶段收口后必须满足: - -1. main-scene native 录制按当前 `rendererIndex` 解析 renderer asset -2. 有 managed stage recorder 时,host 不再被 global default renderer 污染 -3. renderer selection 主链继续向 native renderer binding 延伸 -4. `XCEditor` 编译通过 -5. 旧版 editor 冒烟通过 diff --git a/docs/used/SRP_UniversalRenderSceneSetupPolicyManagedOwnershipPlan_完成归档_2026-04-21.md b/docs/used/SRP_UniversalRenderSceneSetupPolicyManagedOwnershipPlan_完成归档_2026-04-21.md deleted file mode 100644 index 1e42277d..00000000 --- a/docs/used/SRP_UniversalRenderSceneSetupPolicyManagedOwnershipPlan_完成归档_2026-04-21.md +++ /dev/null @@ -1,62 +0,0 @@ -# SRP Universal Render Scene Setup Policy Managed Ownership Plan 2026-04-21 - -## Goal - -Move render-scene setup policy selection out of native `RenderPipeline` hardcoded defaults and into managed URP renderer-data ownership. - -This stage still keeps the actual `RenderSceneData` population native. -It only makes managed URP explicitly choose which native scene-setup policy asset key should be used. - -## Why This Stage - -The current SRP split already moved: - -1. renderer selection into managed URP; -2. builtin scene feature ownership into managed renderer features; -3. shadow planning policy into managed URP asset ownership; -4. shadow execution policy into managed URP renderer-data ownership; -5. shadow-caster standalone pass selection into managed URP renderer-data ownership. - -But `RenderPipeline::ConfigureRenderSceneData()` still directly hardcodes: - -1. global shader keyword setup; -2. environment / skybox setup. - -That means managed URP still cannot explicitly own how extracted scene data is finalized before the renderer consumes it. - -## Scope - -Included: - -1. add a native registry for render-scene setup policies; -2. add managed asset / renderer-data API for render-scene setup policy asset keys; -3. let Mono runtime resolve those keys from managed pipeline assets; -4. let `ScriptableRenderPipelineHost` use managed-selected render-scene setup policy keys; -5. make `UniversalRendererData` explicitly own the builtin render-scene setup policy key; -6. rebuild `XCEditor` and run old editor smoke. - -Not included: - -1. exposing mutable `RenderSceneData` editing directly to C#; -2. custom managed skybox authoring APIs; -3. custom global shader keyword authoring APIs; -4. changing render-scene extraction itself. - -## Acceptance - -This stage is complete when: - -1. managed URP explicitly owns render-scene setup policy selection; -2. native host no longer blindly applies the default render-scene setup for managed pipelines; -3. builtin scene setup still works for the default URP renderer; -4. `XCEditor` build and old editor smoke both pass. - -## Result - -Completed on 2026-04-21. - -Validation: - -1. `cmake --build . --config Debug --target XCEditor` passed; -2. old editor smoke passed with `editor/bin/Debug/XCEngine.exe`; -3. `editor/bin/Debug/editor.log` recorded `SceneReady` at `2026-04-21 03:07:29`. diff --git a/docs/used/SRP_UniversalRendererData配置层计划_完成归档_2026-04-21.md b/docs/used/SRP_UniversalRendererData配置层计划_完成归档_2026-04-21.md deleted file mode 100644 index 02d0b0d9..00000000 --- a/docs/used/SRP_UniversalRendererData配置层计划_完成归档_2026-04-21.md +++ /dev/null @@ -1,109 +0,0 @@ -# SRP UniversalRendererData 配置层计划 2026-04-20 - -## 1. 阶段目标 - -把 `UniversalRendererData` 从“直接暴露裸 `defaultScenePasses` 数组”的状态,收成更接近 Unity URP 的 renderer settings 形态。 - -这一阶段完成后,要达到: - -1. `UniversalRendererData` 对外暴露的是高层配置,而不是原始 pass 序列 -2. 项目侧不需要自己拼 `BeforeOpaque / AfterOpaque` 这类内建 injection pass -3. `UniversalRenderer` 负责执行固定的内建主场景顺序 -4. renderer data 负责描述 opaque / skybox / transparent 这些默认段的开关和绘制设置 - -## 2. 当前问题 - -上一阶段虽然把默认主场景 pass 从 renderer 字段堆里抽成了 data 驱动,但当前形态仍然不够像 Unity: - -1. `UniversalRendererData` 直接暴露 `defaultScenePasses` -2. 项目脚本要自己 author injection pass 和 builtin draw pass 组合 -3. `UniversalScenePassData` 暴露的是运行时拓扑细节,不是 renderer asset 配置语义 - -这会导致: - -1. renderer data API 过低层 -2. 用户会在错误层级上改内建 topology -3. 未来做 renderer preset / renderer variant 时,外层 authoring 会继续失控 - -## 3. 目标结构 - -本阶段改成: - -1. `UniversalRendererData` - - `mainScene` - - `rendererFeatures` -2. `UniversalMainSceneData` - - `renderOpaque` - - `opaquePassEvent` - - `opaqueRendererListDesc` - - `opaqueDrawingSettings` - - `renderSkybox` - - `skyboxPassEvent` - - `renderTransparent` - - `transparentPassEvent` - - `transparentRendererListDesc` - - `transparentDrawingSettings` -3. `UniversalRenderer` - - 固定拥有 builtin opaque / skybox / transparent pass 组 - - 每帧根据 `mainScene` 配置这些 builtin pass - -## 4. 本阶段范围 - -包括: - -1. 新增 `UniversalMainSceneData` -2. 移除 `UniversalRendererData.defaultScenePasses` -3. 收掉 `UniversalScenePassData` 这层对外 authoring 面 -4. 更新项目 probe 和脚本侧调用 -5. 重编译 `XCEditor` -6. 运行旧版 `editor/bin/Debug/XCEngine.exe` 冒烟至少 10 秒 - -不包括: - -1. renderer inspector/editor UI -2. deferred renderer -3. shadow/post-process 迁移到更完整的 renderer preset -4. C# 端用户自定义 `ScriptableRenderer` 体系扩展 - -## 5. 设计原则 - -这一步明确回到 Unity 风格: - -1. 内建 topology 由 renderer 负责 -2. renderer data 负责设置 -3. feature 负责扩展 -4. 用户不直接 author 内建 injection pass 序列 - -## 6. 实施步骤 - -### Step 1:新增高层 main-scene 配置对象 - -提供默认、仅 opaque、空主场景等工厂方法,降低项目侧 authoring 成本。 - -### Step 2:收掉裸 pass 数组接口 - -让 `UniversalRendererData` 改为暴露 `mainScene`,不再让上层直接构造内建主场景 pass 列表。 - -### Step 3:让 UniversalRenderer 回到 builtin pass 执行器角色 - -renderer 固定维护内建 pass 实例,但从 data 读取每段的设置来配置它们。 - -### Step 4:编译与冒烟 - -固定流程: - -1. `cmake --build . --config Debug --target XCEditor` -2. 启动 `editor/bin/Debug/XCEngine.exe` -3. 冒烟至少 10 秒 -4. 检查 `editor/bin/Debug/editor.log` - -## 7. 验收标准 - -本阶段收口后必须满足: - -1. `UniversalRendererData` 不再暴露裸 `defaultScenePasses` -2. `UniversalScenePassData` 不再是项目 authoring 入口 -3. `UniversalRenderer` 以内建 opaque / skybox / transparent 段来组织默认主场景 -4. 项目侧可以通过高层 `mainScene` 配置实现“仅 opaque”或“无默认主场景” -5. `XCEditor` 编译通过 -6. 旧 editor 冒烟通过 diff --git a/docs/used/SRP_UniversalRendererSelection主链计划_完成归档_2026-04-21.md b/docs/used/SRP_UniversalRendererSelection主链计划_完成归档_2026-04-21.md deleted file mode 100644 index f8f28b5c..00000000 --- a/docs/used/SRP_UniversalRendererSelection主链计划_完成归档_2026-04-21.md +++ /dev/null @@ -1,124 +0,0 @@ -# SRP Universal Renderer Selection 主链计划 2026-04-21 - -## 1. 阶段目标 - -把当前 Universal 的 renderer list / default renderer index 从“资产层有配置,但运行时基本只吃默认 renderer”的状态,收成真正可执行的 Unity 风格 renderer selection 主链。 - -本阶段完成后,要达到: - -1. `RendererBackedRenderPipelineAsset` 不再在 request / plan / execution 三处各自偷偷取默认 renderer -2. 相机级 renderer 选择能够在 request 阶段被正式解析,并沿着 frame plan 一路传到 execution -3. Universal 能像 Unity URP 一样,先支持“相机覆盖 renderer index”这条最关键的链路 -4. 后续继续做 renderer inspector、多 renderer、deferred renderer 时,不需要再返工主链 - -## 2. 当前问题 - -当前已经有: - -1. `rendererDataList` -2. `defaultRendererIndex` -3. `GetRenderer(index)` / `GetRendererData(index)` - -但是主链仍然不完整: - -1. `ConfigureCameraRenderRequest()` 默认只下发到 default renderer data -2. `ConfigureCameraFramePlan()` 默认只下发到 default renderer data -3. `RendererBackedRenderPipeline.ResolveRenderer(...)` 录制阶段也只拿 default renderer -4. 没有正式“当前 camera 这一帧最终选中了哪个 renderer index”的 request / plan / context 数据 - -这意味着: - -1. renderer list 现在更多只是配置壳,不是运行时 contract -2. 以后想做 Unity 风格不同 renderer(Forward Renderer / 2D Renderer / 自定义 Renderer)时,主链会重新散掉 -3. 即便后面加 `UniversalAdditionalCameraData`,也没有稳定的数据路径把选择结果带到 execution - -## 3. 设计原则 - -这一阶段按 Unity 的职责分层来收: - -1. camera 侧决定是否覆盖 renderer index -2. pipeline asset 负责解析“这一帧这个 camera 应该用哪个 renderer” -3. request / frame plan / render context 负责携带“已解析好的 renderer index” -4. renderer data / renderer / feature 只吃解析结果,不自己再猜 - -也就是: - -`camera override -> request.rendererIndex -> plan.rendererIndex -> execution resolve renderer` - -## 4. 本阶段范围 - -包括: - -1. 给 native `CameraRenderRequest` 增加正式 `rendererIndex` -2. 给 managed request / planning / render context 暴露当前 renderer index -3. 让 `RendererBackedRenderPipelineAsset` 统一通过“已解析 renderer index”选择 renderer data / renderer -4. 给 Universal 增加最小版 `UniversalAdditionalCameraData` -5. 让 `UniversalRenderPipelineAsset` 支持相机覆盖 renderer index -6. 重编译 `XCEditor` -7. 运行旧版 `editor/bin/Debug/XCEngine.exe` 冒烟至少 10 秒 - -不包括: - -1. 多 backend renderer 混挂 -2. renderer inspector UI -3. deferred renderer -4. camera stack / overlay renderer 体系 -5. 完整 Unity `CameraData.renderer` / `cameraData.camera` API 还原 - -## 5. 实施步骤 - -### Step 1:把 renderer selection 结果正式写进 request / plan / context - -做这几件事: - -1. native `CameraRenderRequest` 增加 `rendererIndex` -2. managed `CameraRenderRequestContext` 增加 `camera` 和 `rendererIndex` -3. managed planning / render context 增加只读 `rendererIndex` - -目标是让 selection 结果不再是执行时临时猜出来,而是 request 阶段就正式落下。 - -### Step 2:统一 RendererBackedRenderPipelineAsset 的 renderer resolve 入口 - -把现在这些默认 renderer 路径统一起来: - -1. `ConfigureCameraRenderRequest` -2. `ConfigureCameraFramePlan` -3. `ResolveRenderer` - -要求: - -1. request 阶段先解析 renderer index -2. 后续 plan / execution 全部吃这个结果 -3. default fallback 规则只有一套 - -### Step 3:补 Universal 的 camera override seam - -新增: - -1. `UniversalAdditionalCameraData` -2. `UniversalRenderPipelineAsset` 对相机 `rendererIndex` 覆盖的解析逻辑 - -先只做最小可用版: - -1. `rendererIndex = -1` 表示使用 pipeline default renderer -2. `rendererIndex >= 0` 表示请求指定 renderer slot -3. 越界仍然回退 default renderer - -### Step 4:编译与冒烟 - -固定流程: - -1. `cmake --build . --config Debug --target XCEditor` -2. 启动 `editor/bin/Debug/XCEngine.exe` -3. 冒烟至少 10 秒 -4. 检查 `editor/bin/Debug/editor.log` - -## 6. 验收标准 - -本阶段收口后必须满足: - -1. Universal renderer list 不再只是配置壳,而是正式进入 request / plan / execution 主链 -2. `RendererBackedRenderPipelineAsset` 的 renderer 选择规则唯一 -3. camera 可以正式覆盖 renderer index,并稳定回退 default renderer -4. `XCEditor` 编译通过 -5. 旧 editor 冒烟通过 diff --git a/docs/used/SRP_UniversalRendererStageCapability计划_完成归档_2026-04-21.md b/docs/used/SRP_UniversalRendererStageCapability计划_完成归档_2026-04-21.md deleted file mode 100644 index b92bc6ee..00000000 --- a/docs/used/SRP_UniversalRendererStageCapability计划_完成归档_2026-04-21.md +++ /dev/null @@ -1,138 +0,0 @@ -# SRP Universal Renderer Stage Capability 计划 2026-04-21 - -## 1. 阶段目标 - -把当前 `SupportsStageRenderGraph` 这条能力查询,从“默认 renderer 的全局能力判断”收成“当前 camera / plan 已解析 renderer 的正式能力判断”。 - -本阶段完成后,必须满足: - -1. `rendererIndex` 不只进入 request / plan / execution,还真正参与 stage support 查询 -2. fullscreen stage 的自动推导和执行前校验,不再被 default renderer 污染 -3. Universal 不同 renderer 以后即使 stage 支持不一致,plan 也能稳定按选中 renderer 走 -4. 旧的项目脚本如果只重写 `SupportsStageRenderGraph(stage)`,仍然能通过回退逻辑继续工作 - -## 2. 当前问题 - -上一阶段虽然已经打通了: - -`camera override -> request.rendererIndex -> plan.rendererIndex -> execution resolve renderer` - -但还有一个口没收干净: - -1. `CameraFramePlanBuilder::ConfigureFullscreenStagesFromPipeline(...)` - 还是通过 `pipeline->SupportsStageRenderGraph(stage)` 做全局判断 -2. `CameraRenderer::Render(...)` - 在执行前做 stage support 校验时,也还是只问“这个 pipeline 支不支持” -3. `PassRecorder.cpp` - 录制 pipeline stage render graph 前,同样还是无上下文查询 -4. `RendererDrivenRenderPipeline` - 默认 support 查询路径仍然走 `rendererIndex = -1` - -这意味着一旦出现: - -1. default renderer 支持 post-process,但相机选中的 renderer 不支持 -2. default renderer 不支持 final output,但相机选中的 renderer 支持 - -当前计划推导和执行前校验都会出现错误判断。 - -## 3. 设计原则 - -这一阶段按下面的责任分层处理: - -1. native `CameraFramePlan` 负责携带当前相机已经解析好的 `rendererIndex` -2. native `RenderPipeline` / `RenderPipelineRenderer` / `RenderPipelineStageRecorder` - 新增 contextual support query seam -3. managed `ScriptableRenderPipeline` - 新增“带 `rendererIndex` 的 stage support 查询”入口 -4. `RendererDrivenRenderPipeline` - 通过 contextual query 按选中的 renderer 构造 `RendererRecordingContext` -5. 旧 API 不直接删,保留回退,避免把当前项目脚本主线打崩 - -核心目标是把: - -`stage support` - -从: - -`pipeline global capability` - -改成: - -`resolved renderer capability for this request` - -## 4. 本阶段范围 - -包括: - -1. C++ 新增 stage support contextual contract -2. 让 plan builder / render-time validation / pass recorder 都使用 contextual query -3. managed `ScriptableRenderPipeline` 新增 contextual support seam -4. Mono stage recorder 优先调用新 seam,找不到时回退旧 seam -5. Universal renderer-driven pipeline 按 `rendererIndex` 做 support 判断 -6. 重新编译 `XCEditor` -7. 运行旧版 `editor/bin/Debug/XCEngine.exe` 冒烟至少 10 秒 - -不包括: - -1. deferred renderer -2. multi-backend renderer family 切换 -3. inspector / editor UI -4. camera stack 完整还原 -5. `UniversalAdditionalCameraData` 更多字段扩张 - -## 5. 实施步骤 - -### Step 1:新增 native contextual support query seam - -做这几件事: - -1. 给 `RenderPipeline` / `RenderPipelineRenderer` / `RenderPipelineStageRecorder` - 增加携带 `stage + rendererIndex` 的 support query context -2. 默认实现继续回退到旧的 `SupportsStageRenderGraph(stage)` -3. 只改调用链,不把现有 builtin pipeline 行为改乱 - -### Step 2:把 plan / render / pass recorder 全部改成 contextual query - -重点改: - -1. `CameraFramePlanBuilder` -2. `CameraRenderer` -3. `CameraFrameGraph/PassRecorder` -4. `ScriptableRenderPipelineHost` - -要求是: - -1. 只要已经有 `plan.request.rendererIndex`,后续所有 support 判断都必须吃这个值 -2. 不再出现“计划按 A renderer,校验却按 default renderer”的双轨行为 - -### Step 3:补齐 managed contextual seam - -做这几件事: - -1. `ScriptableRenderPipeline` 增加新的 contextual support 虚函数 -2. 默认实现回退到旧的 `SupportsStageRenderGraph(stage)` -3. `RendererDrivenRenderPipeline` 覆盖新的 contextual seam -4. `RendererRecordingContext` 能按 `stage + rendererIndex` 构造 -5. Mono stage recorder 优先找新方法,找不到再回退旧方法 - -### Step 4:验证与收口 - -固定流程: - -1. `cmake --build . --config Debug --target XCEditor` -2. 启动 `editor/bin/Debug/XCEngine.exe` -3. 冒烟至少 10 秒 -4. 检查 `editor/bin/Debug/editor.log` -5. 计划归档 -6. 提交并推送 - -## 6. 验收标准 - -本阶段收口后必须满足: - -1. renderer-specific stage capability 成为正式 contract -2. fullscreen stage 推导和执行前校验不再依赖 default renderer 的隐式能力 -3. Universal renderer selection 主链从 request / plan / execution 延伸到 capability query -4. 旧 `SupportsStageRenderGraph(stage)` override 仍然可回退 -5. `XCEditor` 编译通过 -6. 旧版 editor 冒烟通过 diff --git a/docs/used/SRP_UniversalShadowCasterStageManagedOwnershipPlan_完成归档_2026-04-21.md b/docs/used/SRP_UniversalShadowCasterStageManagedOwnershipPlan_完成归档_2026-04-21.md deleted file mode 100644 index 615ae318..00000000 --- a/docs/used/SRP_UniversalShadowCasterStageManagedOwnershipPlan_完成归档_2026-04-21.md +++ /dev/null @@ -1,47 +0,0 @@ -# SRP Universal Shadow Caster Stage Managed Ownership Plan 2026-04-21 - -## Goal - -Move `CameraFrameStage::ShadowCaster` pass selection out of the native host hardcode and into managed URP ownership. - -This stage does **not** move shadow rasterization itself into C#. -It only makes managed URP explicitly declare which native shadow-caster pass asset it wants to use. - -## Why This Stage - -The current managed URP already owns: - -1. renderer selection; -2. main-scene builtin feature ownership; -3. shadow enable / planning defaults. - -But the actual `ShadowCaster` standalone stage is still installed in native host code through a hardcoded `BuiltinShadowCasterPass`. - -That means shadow stage ownership is still not aligned with the SRP/URP boundary. - -## Scope - -Included: - -1. add a native registry/factory for camera-frame standalone pass assets; -2. let managed `ScriptableRenderPipelineAsset` expose standalone pass asset keys by stage; -3. let `RendererBackedRenderPipelineAsset` route that decision through selected renderer data; -4. make `UniversalRendererData` explicitly declare the builtin shadow-caster pass asset key; -5. let `ScriptableRenderPipelineHost` resolve contextual standalone passes from managed asset runtime instead of hardcoding shadow caster; -6. rebuild `XCEditor` and run old editor smoke. - -Not included: - -1. managed shadow raster pass recording; -2. cascades; -3. shadow atlas packing; -4. editor UI for standalone pass selection. - -## Acceptance - -This stage is complete when: - -1. managed URP explicitly owns the `ShadowCaster` standalone pass selection; -2. native host no longer hardcodes shadow caster for managed pipelines; -3. builtin fallback pipeline still keeps depth-only / object-id / shadow-caster working; -4. `XCEditor` build and old editor smoke both pass. diff --git a/docs/used/SRP_UniversalShadowOwnershipAndBuildHygienePlan_完成归档_2026-04-21.md b/docs/used/SRP_UniversalShadowOwnershipAndBuildHygienePlan_完成归档_2026-04-21.md deleted file mode 100644 index 070a60fc..00000000 --- a/docs/used/SRP_UniversalShadowOwnershipAndBuildHygienePlan_完成归档_2026-04-21.md +++ /dev/null @@ -1,64 +0,0 @@ -# SRP Universal Shadow Ownership And Build Hygiene Plan 2026-04-21 - -## Goal - -Close two issues that are now blocking a clean SRP mainline: - -1. Rendering/managed source files already used by the build are still missing from git tracking. -2. Main directional shadow enablement is still primarily owned by native default policy instead of the managed URP asset layer. - -This stage does not rewrite shadow rendering implementation. It only moves the first ownership decision to the managed URP asset and fixes repository hygiene for rendering-related source files that are already part of the build. - -## Current Problems - -1. `managed/CMakeLists.txt` already references `XCEngine.ScriptCore/Rendering/Graph/*`, but those files are still untracked. -2. Native rendering code includes `Rendering/Internal/RenderSurfacePipelineUtils.h` and `Rendering/Internal/ShaderVariantUtils.h`, but the corresponding public/internal headers are still untracked. -3. `RenderPipelineAsset::ConfigureCameraRenderRequest(...)` still applies default directional shadow planning first. -4. Managed URP can disable shadows only indirectly through a renderer feature, which is weaker than Unity-like asset ownership. - -## Scope - -Included: - -1. Track the missing managed render graph source files used by the current build. -2. Track the missing rendering utility headers used by native rendering code. -3. Add a managed URP asset-level shadow settings object with a first setting for main light shadow enablement. -4. Apply that managed setting during camera request configuration. -5. Rebuild `XCEditor`. -6. Run old editor smoke for at least 10 seconds. - -Not included: - -1. Rewriting shadow GPU passes in C#. -2. Adding new shadow map quality/resolution/cascade controls. -3. Deferred renderer. -4. Editor UI for these new asset settings. - -## Implementation Steps - -### Step 1 - -Track missing rendering/scriptcore source files that are already build dependencies: - -1. `managed/XCEngine.ScriptCore/Rendering/Graph/*` -2. `engine/include/XCEngine/Rendering/RenderSurfacePipelineUtils.h` -3. `engine/include/XCEngine/Rendering/ShaderVariantUtils.h` -4. `engine/include/XCEngine/Rendering/Internal/*` - -### Step 2 - -Add managed request control for shadow ownership: - -1. Expose directional shadow request state on `CameraRenderRequestContext`. -2. Expose the same control on `RendererCameraRequestContext`. -3. Add `UniversalShadowSettings`. -4. Let `UniversalRenderPipelineAsset` clear the default main directional shadow request when the managed asset disables it. - -## Acceptance - -This stage is complete when: - -1. Clean checkout no longer depends on untracked rendering/scriptcore source files. -2. `UniversalRenderPipelineAsset` owns the first managed decision for whether main directional shadows stay enabled. -3. `XCEditor` builds successfully. -4. Old editor smoke reaches scene-ready logs without regression. diff --git a/docs/used/SRP_UniversalShadowPlanningSettingsBridgePlan_完成归档_2026-04-21.md b/docs/used/SRP_UniversalShadowPlanningSettingsBridgePlan_完成归档_2026-04-21.md deleted file mode 100644 index 4469bc58..00000000 --- a/docs/used/SRP_UniversalShadowPlanningSettingsBridgePlan_完成归档_2026-04-21.md +++ /dev/null @@ -1,42 +0,0 @@ -# SRP Universal Shadow Planning Settings Bridge Plan 2026-04-21 - -## Goal - -Extend the current managed shadow ownership from a simple enable/disable switch to a real planning-settings bridge: - -1. managed can read the current directional shadow planning settings; -2. managed can override those settings; -3. native recomputes the shadow request after managed overrides, without changing the existing call order. - -## Why This Stage - -The previous stage moved the first shadow ownership decision into `UniversalRenderPipelineAsset`, but it still only controlled whether shadows stay enabled. - -The remaining gap is that native already receives `DirectionalShadowPlanningSettings`, while the managed runtime currently discards them. Until that bridge exists, URP cannot meaningfully own shadow planning defaults. - -## Scope - -Included: - -1. Add managed mirror structs for directional shadow planning settings. -2. Expose those settings on `CameraRenderRequestContext` and `RendererCameraRequestContext`. -3. Extend the Mono bridge state to carry a mutable settings copy and a dirty flag. -4. Re-run native default shadow planning after managed request configuration when settings changed. -5. Let `UniversalShadowSettings` push its planning defaults into the request context. -6. Rebuild `XCEditor` and run old editor smoke. - -Not included: - -1. Shadow cascades. -2. Shadow atlas / multiple lights. -3. C# shadow map raster pass implementation. -4. Editor UI for shadow settings. - -## Acceptance - -This stage is complete when: - -1. managed URP can override planner-owned main directional shadow defaults; -2. existing `ClearDirectionalShadow()` behavior still works; -3. the native shadow request is recomputed only when managed changed planning settings; -4. `XCEditor` build and old editor smoke both pass. diff --git a/docs/used/SRP_Universal_RendererData归位计划_完成归档_2026-04-19.md b/docs/used/SRP_Universal_RendererData归位计划_完成归档_2026-04-19.md deleted file mode 100644 index 2f2db3e5..00000000 --- a/docs/used/SRP_Universal_RendererData归位计划_完成归档_2026-04-19.md +++ /dev/null @@ -1,168 +0,0 @@ -# SRP Universal RendererData归位计划 2026-04-19 - -## 1. 阶段目标 - -上一阶段已经建立了: - -`managed asset -> backend key -> native RenderPipelineAsset` - -这条正式接缝,但当前 backend ownership 仍然挂在: - -`UniversalRenderPipelineAsset` - -这和 Unity/URP 的真实职责边界还差半步。Unity 里真正决定 renderer 组织方式的不是 pipeline asset 自己,而是它选中的: - -`ScriptableRendererData` - -本阶段目标就是把这半步补完: - -1. 让 native backend key 的声明责任从 `UniversalRenderPipelineAsset` 下放到 `ScriptableRendererData` -2. 让 `UniversalRenderPipelineAsset` 改成通过“默认 renderer 选择”来驱动 -3. 把当前单个 `rendererData` 形态整理成更接近 Unity 的 renderer data 列表 + 默认索引 -4. 保持 C++ bridge 仍然只面向 `RenderPipelineAsset`,不把 Unity 风格 API 细节直接污染到 native 层 - ---- - -## 2. 当前问题 - -### 2.1 ownership 位置仍然偏高 - -现在的结构是: - -`UniversalRenderPipelineAsset.GetPipelineRendererAssetKey() -> "BuiltinForward"` - -这意味着: - -1. backend ownership 仍然是 pipeline asset 在声明 -2. renderer data 虽然已经负责 renderer/pass/features 组合,但还没有负责 native renderer backend 语义 -3. 未来如果一个 pipeline asset 下面要切多个 renderer data,就会开始和 Unity 的职责边界不一致 - -### 2.2 Universal 目前还是“单 rendererData” - -当前 `UniversalRenderPipelineAsset` 只有: - -`rendererData` - -而 Unity/URP 的主流组织方式是: - -1. renderer data 列表 -2. 默认 renderer 索引 -3. pipeline asset 通过默认 renderer 解析当前使用的 renderer data - -现在如果不先把这个模型摆正,后面继续往 SRP/URP 方向扩时,很多测试、序列化、编辑器 UI 都会继续围着单字段长。 - -### 2.3 unknown backend 的探针位置也偏了 - -目前 unknown backend 探针还是通过: - -`ManagedUnknownBackendRenderPipelineProbeAsset` - -override `GetPipelineRendererAssetKey()` - -来模拟错误 backend。 - -如果本阶段要把 ownership 归位到 renderer data,这个探针也应该一起下沉,否则测试覆盖的是旧职责边界,不是新结构。 - ---- - -## 3. 本阶段方案 - -### 方案核心 - -把 backend ownership 下沉为: - -`ScriptableRendererData -> backend key` - -然后由: - -`UniversalRenderPipelineAsset -> ResolveDefaultRendererData() -> rendererData.GetPipelineRendererAssetKeyInstance()` - -这样做之后: - -1. `UniversalRenderPipelineAsset` 只负责“选哪个 renderer data” -2. `ScriptableRendererData` 负责“这个 renderer data 需要哪个 native backend” -3. Mono runtime / native factory 保持现有桥接方式,不需要再扩散 Unity 细节 - -### 数据形态 - -`UniversalRenderPipelineAsset` - -改成: - -1. `ScriptableRendererData[] rendererDataList` -2. `int defaultRendererIndex` -3. 内部统一通过 `ResolveDefaultRendererData()` 取当前默认 renderer data - -本阶段不做完整编辑器序列化工作流,只把运行时模型和测试模型摆正。 - ---- - -## 4. 实施步骤 - -### Step 1:给 ScriptableRendererData 增加 backend key seam - -目标: - -1. 在 `ScriptableRendererData` 增加受保护虚方法 -2. 默认返回空 key -3. `UniversalRendererData` override 返回 `BuiltinForward` - -### Step 2:把 UniversalRenderPipelineAsset 改成默认 renderer 选择模型 - -目标: - -1. 删除单 `rendererData` 字段 -2. 增加 `rendererDataList` 和 `defaultRendererIndex` -3. `CreatePipeline()`、`ConfigureCameraRenderRequest()`、`GetPipelineRendererAssetKey()` 都通过默认 renderer data 解析 -4. 默认空/越界/null 情况下仍然收敛到一个可用的 `UniversalRendererData` - -### Step 3:把 probe 与测试一起归位 - -目标: - -1. 所有 probe asset 改成使用 `rendererDataList` -2. unknown backend 探针改成 custom renderer data 返回错误 key -3. 保证现有 managed stage recorder / host fallback 语义不回退 - -### Step 4:补测试 - -目标: - -1. scripting tests 覆盖 default renderer data -> builtin forward backend -2. scripting tests 覆盖 unknown backend key 来源于 renderer data 时,host/recorder 仍能本地 fallback -3. 如果需要,再补 rendererDataList/defaultRendererIndex 的选择回归 - -### Step 5:阶段验证与收口 - -目标: - -1. 编译 `rendering_unit_tests`、`scripting_tests`、`XCEditor` -2. 运行相关单测 -3. 跑旧版 `XCEditor` 10 秒冒烟并验证新 `SceneReady` -4. 归档 plan、提交、推送 - ---- - -## 5. 验收标准 - -本阶段完成后应满足: - -1. Universal 的 native backend ownership 从 pipeline asset 下放到 renderer data -2. Universal pipeline asset 的职责变成“选择默认 renderer” -3. 探针和测试不再依赖旧的 asset-level backend override -4. C++ bridge 不需要理解 Unity 风格 renderer data 细节,仍然保持桥接层定位 -5. 当前结构继续朝 Unity 风格 `SRP Asset -> RendererData -> Renderer/Features/Passes` 演进 - ---- - -## 6. 本阶段不做的事 - -本阶段明确不做: - -1. renderer data 的完整工程资产化 UI -2. 多 renderer 的编辑器切换体验 -3. deferred renderer -4. lightmap / baking -5. 用户自定义 backend registry 的公开 API - -本阶段只做职责归位和运行时模型整理。 diff --git a/docs/used/SRP_Universal_Renderer实例与默认选择收口计划_完成归档_2026-04-20.md b/docs/used/SRP_Universal_Renderer实例与默认选择收口计划_完成归档_2026-04-20.md deleted file mode 100644 index c85064ff..00000000 --- a/docs/used/SRP_Universal_Renderer实例与默认选择收口计划_完成归档_2026-04-20.md +++ /dev/null @@ -1,148 +0,0 @@ -# SRP Universal Renderer实例与默认选择收口计划 2026-04-20 - -## 1. 阶段目标 - -上一阶段已经把: - -`native backend ownership` - -从 `UniversalRenderPipelineAsset` 下放到了: - -`ScriptableRendererData` - -但当前 Universal 运行时还有一个明显没收好的点: - -1. `UniversalRenderPipelineAsset` 只有 renderer data 列表和默认索引 -2. 真正的 renderer 实例却仍然藏在 `UniversalRenderPipeline` 私有字段里 -3. renderer 的默认选择、索引回退、实例缓存没有形成一套统一语义 - -本阶段目标就是把这层收口成更接近 Unity 的形态: - -`UniversalRenderPipelineAsset -> Resolve/GetRenderer(index) -> ScriptableRendererData -> ScriptableRenderer` - ---- - -## 2. 当前问题 - -### 2.1 renderer 实例 ownership 还在 pipeline 对象里 - -现在 `UniversalRenderPipeline` 内部自己持有: - -1. `ScriptableRendererData` -2. `ScriptableRenderer m_renderer` - -这意味着 renderer 的生命周期和 pipeline 对象绑得过紧,不利于后续: - -1. 默认 renderer 选择正式化 -2. 多 renderer data 扩展 -3. 资产级 renderer cache -4. 更像 Unity 的 `GetRenderer(index)` 模型 - -### 2.2 默认 renderer 选择还只是“私有 helper” - -现在 `UniversalRenderPipelineAsset` 只是内部有一个: - -`ResolveDefaultRendererData()` - -但还没有形成: - -1. `ResolveRendererIndex` -2. `GetRendererData(index)` -3. `GetRenderer(index)` - -这导致默认选择语义存在,但没有成为一等运行时接口。 - -### 2.3 renderer cache 还没有落到正确层级 - -如果未来相同 asset 产生多个 pipeline 对象,renderer 实例现在无法通过 asset/data 层复用。 - -Unity 风格下更合理的职责是: - -1. asset 决定当前用哪个 renderer data -2. renderer data 负责产出或缓存 renderer -3. pipeline 只消费“选好的 renderer” - ---- - -## 3. 本阶段方案 - -### 方案核心 - -把 renderer lifecycle 正式归位到: - -`ScriptableRendererData` - -并让: - -`UniversalRenderPipelineAsset` - -提供统一的 renderer 解析入口。 - -### 目标结构 - -1. `ScriptableRendererData` - - 缓存 renderer instance - - 提供 `GetRendererInstance()` -2. `UniversalRenderPipelineAsset` - - 统一处理 renderer index 解析和 fallback - - 提供 default renderer / 指定 index renderer 获取 -3. `UniversalRenderPipeline` - - 不再拥有 renderer data / renderer cache - - 仅通过 asset 解析当前 renderer - ---- - -## 4. 实施步骤 - -### Step 1:把 renderer 实例缓存下放到 ScriptableRendererData - -目标: - -1. 给 `ScriptableRendererData` 增加 renderer instance cache -2. `CreateRenderer()` 只在首次需要时触发 -3. renderer features 的初始化与 renderer instance 生命周期对齐 - -### Step 2:给 UniversalRenderPipelineAsset 增加正式的 renderer 解析接口 - -目标: - -1. 增加 renderer index 解析与 fallback -2. 增加 default renderer data / renderer 获取接口 -3. 把 `CreatePipeline()` 改成传 asset,而不是直接传 renderer data - -### Step 3:收掉 UniversalRenderPipeline 的私有 renderer ownership - -目标: - -1. 移除 pipeline 内部的私有 renderer cache -2. 改为通过 asset 取 default renderer -3. 保证主场景与后处理录制仍然正常 - -### Step 4:补回归测试 - -目标: - -1. 默认 renderer index 选择继续决定 native backend asset -2. 默认 renderer index 越界时 fallback 到 index 0 -3. 同一 managed asset 派生多个 pipeline/recorder 时,renderer 实例不会重复构建 - -### Step 5:验证与收口 - -目标: - -1. 编译 `rendering_unit_tests`、`scripting_tests`、`XCEditor` -2. 跑相关单测 -3. 跑旧版 `XCEditor` 10 秒冒烟并验证新 `SceneReady` -4. 归档 plan、提交、推送 - ---- - -## 5. 验收标准 - -本阶段完成后应满足: - -1. renderer 默认选择语义在 `UniversalRenderPipelineAsset` 层正式成立 -2. renderer 实例 cache 在 `ScriptableRendererData` 层成立 -3. `UniversalRenderPipeline` 退回到轻量 orchestration 角色 -4. 默认索引 fallback 和 renderer reuse 都有测试锁住 -5. 结构继续向 Unity 风格 `Asset -> RendererData -> Renderer` 逼近 diff --git a/docs/used/SRP_Universal包边界与ManagedRenderGraph_v1计划_阶段归档_2026-04-20.md b/docs/used/SRP_Universal包边界与ManagedRenderGraph_v1计划_阶段归档_2026-04-20.md deleted file mode 100644 index 38014cc1..00000000 --- a/docs/used/SRP_Universal包边界与ManagedRenderGraph_v1计划_阶段归档_2026-04-20.md +++ /dev/null @@ -1,216 +0,0 @@ -# SRP Universal 包边界与 Managed RenderGraph v1 计划 2026-04-20 - -## 1. 阶段目标 - -上一阶段已经完成了: - -1. `ScriptableRenderPipelineAsset -> RendererBackedRenderPipelineAsset -> ScriptableRendererData -> ScriptableRenderer -> Feature -> Pass` -2. project 侧自定义 SRP/URP 风格资源、feature、pass 的主链打通 -3. renderer data / asset invalidation、runtime rebuild、runtime release 契约锁定 - -当前下一阶段不再继续补 lifecycle 接缝,而是正式进入两个面向长期演进的基础工作: - -1. 把首方 Universal 包的物理边界收干净 -2. 给 managed 层补上最小可用的 RenderGraph API v1 - -这一步是后续继续做 Unity 风格 `SRP + URP` 的真正起点。 - ---- - -## 2. 当前问题 - -### 2.1 包边界逻辑正确,但物理结构不干净 - -虽然 `XCEngine.RenderPipelines.Universal.dll` 已经是独立 assembly, -但是源码物理上仍然放在: - -`managed/XCEngine.ScriptCore/Rendering/Universal/*` - -这会造成几个问题: - -1. 目录结构误导,难以分辨哪些属于 ScriptCore,哪些属于首方管线包 -2. 后续继续扩 Universal 内容时,容易把 package code 和 core API 混写 -3. 不利于未来继续拆 `Core / Universal / HDRP-like / user package` - -### 2.2 现在的 Universal 还只是“薄骨架” - -当前 Universal 主要完成了: - -1. renderer data / renderer / feature / pass 的 managed 组织 -2. main scene / post process / final output 的阶段录制入口 -3. 少量 feature 级能力验证 - -但它还没有真正进入“官方默认渲染管线内容层”的状态。 - -### 2.3 Managed 侧还没有真正的 RenderGraph API - -当前 managed pass 主要还是依赖: - -1. `RecordScene` -2. `RecordScenePhase` -3. `RecordSceneInjectionPoint` -4. `RecordFullscreenPass` - -这意味着: - -1. C# 侧还不能明确声明纹理资源依赖 -2. C# 侧还不能真正创建 transient 资源 -3. C# 侧还不能录制通用 raster / compute pass - -如果继续在这个基础上堆功能,最终会变成“可脚本化的 builtin pipeline”,而不是真正的 SRP/URP。 - ---- - -## 3. 本阶段范围 - -本阶段只做下面两件事: - -1. `Universal 包边界正式化` -2. `Managed RenderGraph API v1` - -本阶段明确不做: - -1. deferred renderer -2. lightmap / GI / probe -3. HDRP 风格分支 -4. 编辑器 inspector/资源面板适配 - ---- - -## 4. 实施步骤 - -### Step 1:Universal 包物理拆层 - -目标: - -把当前 `managed/XCEngine.ScriptCore/Rendering/Universal/*` -迁移到独立目录,例如: - -`managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/*` - -同时修正构建脚本,让 assembly 名、目录名、源码归属三者一致。 - -完成标准: - -1. Universal 源码不再物理挂在 ScriptCore 目录下 -2. `managed/CMakeLists.txt` 仍然稳定产出 `XCEngine.RenderPipelines.Universal.dll` -3. 所有现有 scripting/rendering/editor 验证通过 - -### Step 2:梳理 managed SRP core 和 Universal package 的边界 - -目标: - -明确哪些类型应该留在 core,哪些应该留在 Universal: - -保留在 core: - -1. `RenderPipelineAsset` -2. `ScriptableRenderPipelineAsset` -3. `ScriptableRenderPipeline` -4. `ScriptableRenderContext` -5. `ScriptableRenderPipelinePlanningContext` -6. `CameraRenderRequestContext` - -保留在 Universal: - -1. `RendererBackedRenderPipelineAsset` -2. `RendererDrivenRenderPipeline` -3. `ScriptableRendererData` -4. `ScriptableRenderer` -5. `ScriptableRendererFeature` -6. `ScriptableRenderPass` -7. `RenderingData*` -8. `UniversalRenderPipelineAsset / UniversalRendererData / UniversalRenderer` - -完成标准: - -1. 不再出现 core/package 边界混乱的命名与落位 -2. C# API 表意清晰,后续用户可以理解“引擎 core”和“首方管线包”的区别 - -### Step 3:设计 Managed RenderGraph API v1 - -目标: - -在不推翻当前 native RenderGraph 的前提下,给 managed 层补最小可用 graph API。 - -v1 只做最小闭环: - -1. graph texture handle -2. 导入主场景/后处理阶段颜色输入 -3. 创建 transient color/depth texture -4. 添加 raster pass -5. 添加 compute pass -6. 在 pass setup 中声明 read/write 依赖 - -v1 暂不做: - -1. 完整 builder 泛型体系 -2. 复杂 blackboard typed API -3. 跨帧历史资源 -4. async compute / pass culling / alias 可视化 - -完成标准: - -1. managed pass 能不依赖固定 helper,直接声明 graph 资源和 pass -2. native graph compile/execute 仍由 C++ 层负责 -3. API 能支撑后续做首方 URP feature - -### Step 4:用 v1 API 改造 Universal 的官方默认内容层 - -目标: - -先挑最小路径做首个正式化样板: - -1. 保留主场景几何后端仍走 native builtin forward -2. 把 managed Universal 的 post process / final output 录制逐步改到 graph API -3. 保证 feature/pass 是真正的 graph producer,而不是固定 internal call 包装 - -完成标准: - -1. Universal 包不再只是“阶段调用壳” -2. feature/pass 能开始承载真实官方默认管线内容 - -### Step 5:阶段验证与收口 - -固定验证流程: - -1. 编译 `rendering_unit_tests` -2. 编译 `scripting_tests` -3. 编译 `XCEditor` -4. 运行 `rendering_unit_tests` -5. 运行 `scripting_tests` -6. 启动旧版 `editor/bin/Debug/XCEngine.exe` 至少 10 秒 -7. 检查新鲜 `editor/bin/Debug/editor.log` 中出现 `SceneReady` -8. 阶段完成后归档 plan -9. 使用规范 conventional commit 提交并推送 - ---- - -## 5. 验收标准 - -本阶段收口后应满足: - -1. `ScriptCore` 和 `Universal package` 的边界在目录、构建、职责三层一致 -2. managed 层拥有最小可用的 graph 录制能力 -3. Universal 开始从“验证骨架”进入“官方默认管线内容层” -4. 下一阶段可以继续做更像 URP 的 renderer feature / renderer data / 官方 pass 体系,而不是继续补接缝 - ---- - -## 6. 阶段意义 - -这一步做完后,渲染架构会从: - -`SRP bridge 已打通,但 Universal 还是薄封装` - -推进到: - -`SRP core + 首方 Universal package + managed graph authoring 基础成立` - -只有到这一步,后面继续做: - -1. Unity 风格官方 URP renderer feature -2. 更复杂的官方 post process -3. deferred / lightmap / volume 等后续内容 - -才不会再回头重写主结构。 diff --git a/docs/used/SRP_Universal原生后端Key接缝计划_完成归档_2026-04-19.md b/docs/used/SRP_Universal原生后端Key接缝计划_完成归档_2026-04-19.md deleted file mode 100644 index ab3648c9..00000000 --- a/docs/used/SRP_Universal原生后端Key接缝计划_完成归档_2026-04-19.md +++ /dev/null @@ -1,202 +0,0 @@ -# SRP Universal 原生后端 Key 接缝计划 2026-04-19 - -## 1. 阶段目标 - -上一阶段已经把 Mono managed SRP 的: - -1. host renderer -2. stage recorder - -收到了同一条 native backend ownership 上。 - -但当前仍然有一个关键问题没有解决: - -`MonoManagedRenderPipelineAssetRuntime` -现在还是统一硬编码返回: - -`BuiltinForwardPipelineAsset` - -这意味着: - -1. managed asset 自己并没有显式声明“我要哪个 native backend” -2. first-party `UniversalRenderPipelineAsset` 和普通 `ScriptableRenderPipelineAsset` - 在 native backend 选择上没有语义区别 -3. 未来想让不同 SRP/renderer data 走不同 native backend 时, - 还会继续卡在 Mono runtime 的类型外部硬编码 - -本阶段目标就是把这件事改成: - -`ScriptableRenderPipelineAsset` --> 返回 native backend key --> Mono runtime 解析 key --> native factory 映射到 `RenderPipelineAsset` - -先把“谁声明 backend”这件事正式落到 managed asset 自己身上。 - ---- - -## 2. 当前问题 - -### 2.1 Universal 虽然已经有 `rendererData`,但还没有 native backend 声明权 - -当前 `UniversalRenderPipelineAsset` 已经拥有: - -1. `rendererData` -2. `CreatePipeline()` -3. `ConfigureCameraRenderRequest()` - -这些都说明 managed package 侧已经在承担“渲染管线组织”责任。 - -但 native backend 选择仍然没有经过它,而是 Mono runtime 统一硬编码。 - -这不符合我们要的方向: - -1. first-party Universal 应该先成为第一个显式声明 backend 的包 -2. Mono runtime 只负责桥接,不负责替 asset 做产品决策 - -### 2.2 当前 `CreateNativeSceneRendererFromAsset(nullptr)` 语义不干净 - -上一阶段新补的: - -`CreateNativeSceneRendererFromAsset(...)` - -内部现在仍然会走: - -`ResolveRenderPipelineAssetOrDefault(...)` - -这对于“从一个明确 backend asset 创建 native scene renderer”这件事来说语义过重了。 - -如果传入 `nullptr`,它不该再去全局查询 configured render pipeline asset, -更不该重新把当前 managed pipeline asset 自己绕回来。 - -这个点如果不修,后面 backend key seam 加上去以后, -scene recorder 的 fallback 仍然有可能偷偷回到全局 configured asset, -会把 ownership 再次搞脏。 - -### 2.3 当前还缺一组“unknown backend key 也不会崩”的测试 - -我们不仅要验证: - -1. Universal 显式声明 builtin forward key 能被解析 - -还要验证: - -1. key 不存在时 runtime 返回空 asset -2. recorder 仍然能本地 fallback 到默认 native scene renderer -3. 不会因为 unknown key 让 managed stage graph 录制回归 - ---- - -## 3. 本阶段方案 - -### 方案核心 - -新增一条受控的 managed seam: - -`ScriptableRenderPipelineAsset` --> `GetPipelineRendererAssetKey()` --> Mono runtime --> native factory key mapping --> `RenderPipelineAsset` - -### 第一阶段只落 first-party Universal - -本阶段只让: - -`UniversalRenderPipelineAsset` - -显式返回: - -`BuiltinForward` - -也就是说: - -1. Universal 成为第一个正式声明 native backend 的 managed package -2. 普通 `ScriptableRenderPipelineAsset` 先保持默认不声明 -3. Mono runtime 对“未声明 key”的 asset 返回 `nullptr` -4. host / recorder 再按本地 fallback 兜底 - -### 这样做的原因 - -这样能同时保证两件事: - -1. 我们正式建立了面向未来的 backend key seam -2. 又不会一次性把所有 custom SRP 公共 API 扩太大 - ---- - -## 4. 实施步骤 - -### Step 1:给 managed asset 加 backend key seam - -目标: - -1. 在 `ScriptableRenderPipelineAsset` 新增受保护虚方法 -2. 默认返回空 key -3. `UniversalRenderPipelineAsset` override 返回 `BuiltinForward` - -### Step 2:补 native key -> asset 工厂映射 - -目标: - -1. 在 native factory 层新增统一的 key 解析入口 -2. 当前先支持 `BuiltinForward` -3. 保证未来增加更多 native backend 时,不需要把 if/switch 散落到 Mono runtime - -### Step 3:重构 Mono runtime 的 backend asset 解析 - -目标: - -1. `MonoManagedRenderPipelineAssetRuntime::GetPipelineRendererAsset()` - 不再硬编码 builtin forward -2. 改为调用 managed asset 的 backend key 方法 -3. 按 key 去 native factory 解析 asset -4. 未声明或未知 key 时返回空 asset - -### Step 4:修正 recorder fallback 语义 - -目标: - -1. `CreateNativeSceneRendererFromAsset(nullptr)` 不再回查全局 configured pipeline asset -2. recorder fallback 只在“本地 asset 解析失败”后回到默认 native scene renderer -3. 避免 native backend 选择再次绕回全局 configured managed asset - -### Step 5:补测试并完整验证 - -目标: - -1. 增加 scripting tests,验证 Universal backend key -> builtin forward asset -2. 增加 unknown key fallback 回归测试 -3. 编译 `rendering_unit_tests`、`scripting_tests`、`XCEditor` -4. 旧版 editor 10s 冒烟 -5. 归档 plan、提交、推送 - ---- - -## 5. 验收标准 - -本阶段完成后应满足: - -1. native backend 的声明责任开始落到 managed asset 自己 -2. first-party Universal 是第一个显式声明 native backend 的包 -3. Mono runtime 不再统一硬编码 builtin forward backend -4. recorder fallback 不再回查全局 configured managed asset -5. unknown backend key 不会导致 managed stage recording 崩坏 - ---- - -## 6. 本阶段不做的事 - -本阶段明确不做: - -1. 不开放完整用户自定义 backend key 注册系统 -2. 不做 deferred -3. 不做 lightmap / baking -4. 不做 renderer data 到 native renderer asset 的复杂序列化桥 -5. 不做 editor workflow 扩展 - -这一刀只建立: - -`managed asset -> backend key -> native asset` - -这条正式接缝。 diff --git a/docs/used/SRP_Universal录制契约收口计划_阶段归档_2026-04-19.md b/docs/used/SRP_Universal录制契约收口计划_阶段归档_2026-04-19.md deleted file mode 100644 index 6a03bd30..00000000 --- a/docs/used/SRP_Universal录制契约收口计划_阶段归档_2026-04-19.md +++ /dev/null @@ -1,116 +0,0 @@ -# SRP Universal 录制契约收口计划 2026-04-19 - -## 1. 阶段目标 - -本阶段不扩展新的渲染特性,重点收口当前 `Universal` 包层里仍然直接暴露给外部脚本的底层录制协议。 - -目标是把: - -1. `ScriptableRenderContextExtensions` -2. `CameraRenderRequestContextExtensions` - -这类偏 native recording contract 的 public extension surface, -收回到 `Universal` 包自己的 `ScriptableRenderPass` / `ScriptableRendererFeature` 基类中。 - -这样做完后,结构应当变成: - -`Core Context` --> `Universal Pass/Feature Base` --> `UniversalRenderer / 用户自定义 Universal Pass` - -而不是: - -`Core Context` --> `public extension method` --> `所有外部脚本都可直接访问 native recording contract` - ---- - -## 2. 当前问题 - -### 2.1 包层语义和底层协议还没有分开 - -当前 `XCEngine.RenderPipelines.Universal` 仍然公开暴露了: - -1. `RecordScene` -2. `RecordOpaqueScenePhase` -3. `RecordBeforeOpaqueInjection` -4. `RecordShaderVectorFullscreenPass` -5. `HasDirectionalShadow` -6. `ClearDirectionalShadow` - -这些接口本质上不是“稳定的首方包层 authoring API”, -而是当前 native host 的直接录制协议。 - -### 2.2 当前 public surface 会把 prototype 固化 - -如果继续让这些 public extension 留在包层: - -1. probe/tests 会继续把它们视为正式 API -2. 外部脚本会绕过 `Pass/Feature` 基类直接依赖 raw contract -3. 后续要把 `Universal` 真正做成 Unity 风格首方包时,会更难收口 - -### 2.3 当前更合理的 ownership - -这些能力更适合由: - -1. `ScriptableRenderPass` 以 protected helper 形式承接录制 -2. `ScriptableRendererFeature` 以 protected helper 形式承接 request policy - -也就是: - -1. 自定义 pass 通过 pass 基类调度 recording -2. 自定义 feature 通过 feature 基类配置 request -3. 外部脚本不再直接拿到 context extension helper - ---- - -## 3. 本阶段实施内容 - -### Step 1:把录制 helper 收回到 `ScriptableRenderPass` - -计划: - -1. 在 `ScriptableRenderPass` 增加 protected recording helper -2. 覆盖 scene / injection / fullscreen 这几类当前已存在能力 -3. 删除 `ScriptableRenderContextExtensions.cs` -4. 更新 `UniversalRenderer` 和 probe passes 的调用方式 - -### Step 2:把 request helper 收回到 `ScriptableRendererFeature` - -计划: - -1. 在 `ScriptableRendererFeature` 增加 protected request helper -2. 覆盖方向光阴影查询与清理 -3. 删除 `CameraRenderRequestContextExtensions.cs` -4. 更新 `DisableDirectionalShadowRendererFeature` 的调用方式 - -### Step 3:更新 API surface probe 与脚本测试 - -计划: - -1. 修改 `ScriptableRenderContextApiSurfaceProbe` -2. 不再把上述 extension type 视为应存在的 public surface -3. 更新相关 scripting tests,保证收口后的 API surface 被固定下来 - ---- - -## 4. 验收标准 - -完成后应满足: - -1. `XCEngine.RenderPipelines.Universal` 不再公开包含 `ScriptableRenderContextExtensions` -2. `XCEngine.RenderPipelines.Universal` 不再公开包含 `CameraRenderRequestContextExtensions` -3. `UniversalRenderer`、内建 feature、probe 自定义 pass 仍然能正常录制 -4. `scripting_tests`、`rendering_unit_tests`、`editor_tests` 通过 -5. `XCEditor` 编译通过并完成至少 10s 冒烟验证 - ---- - -## 5. 阶段后位置 - -这一刀做完后,下一小阶段再继续处理: - -1. `ScriptableRenderer.SupportsStageRenderGraph / RecordStageRenderGraph` 的公开边界 -2. `Universal` 内部 scene/fullscreen recording 的正式 package-local 组织 -3. 是否需要进一步把 renderer recording seam 变成更明确的首方包内契约 diff --git a/docs/used/SRP_Universal阶段录制组合边界收口计划_完成归档_2026-04-19.md b/docs/used/SRP_Universal阶段录制组合边界收口计划_完成归档_2026-04-19.md deleted file mode 100644 index a187fd0c..00000000 --- a/docs/used/SRP_Universal阶段录制组合边界收口计划_完成归档_2026-04-19.md +++ /dev/null @@ -1,139 +0,0 @@ -# SRP Universal 阶段录制组合边界收口计划 2026-04-19 - -## 1. 阶段目标 - -上一阶段已经完成: - -1. `Universal` 的 raw context/request extension surface 收口 -2. 录制 helper 回收到 `ScriptableRenderPass` / `ScriptableRendererFeature` / `ScriptableRendererData` -3. `XCEditor`、脚本测试、渲染测试、编辑器测试和 10s 冒烟通过 - -这一阶段不新增渲染特性,继续收口 `Universal` 包里的 stage recording seam。 - -本阶段目标是把当前这条链: - -`ScriptableRenderPipeline -> ScriptableRenderer -> PassQueue` - -从“外部也能直接拿来拼装”的状态, -收成更明确的“包层内部组合点”。 - ---- - -## 2. 当前问题 - -### 2.1 `UniversalRenderPipeline` 目前只是一个很薄的 wrapper - -当前 `UniversalRenderPipeline` 的核心只是: - -1. 创建 `ScriptableRenderer` -2. 把 `SupportsStageRenderGraph` -3. 把 `RecordStageRenderGraph` - -直接转发到 renderer。 - -这能工作,但包层组合点还不够正式。 - -### 2.2 `ScriptableRenderer` 目前暴露的是“可直接录制 stage graph” - -当前 `ScriptableRenderer` 直接公开: - -1. `SupportsStageRenderGraph` -2. `RecordStageRenderGraph` - -这意味着: - -1. 外部脚本可以直接把 renderer 当成 stage recorder 来拼 -2. probe 也在复制 `UniversalRenderPipeline` 的这层包装逻辑 -3. `ScriptableRenderer` 更像“native host 需要的录制协议对象”,而不只是首方包里的 renderer authoring 抽象 - -### 2.3 这条 seam 还没有真正变成 package-local contract - -Unity 风格里,用户面对的应该是: - -1. pipeline asset -2. pipeline -3. renderer / feature / pass - -而不是把“stage recorder protocol”直接作为一层显式对外表面长期固化。 - ---- - -## 3. 收口方向 - -本阶段采用的方向是: - -1. 保持 `ScriptableRenderPipeline` core 继续薄 -2. 保持 `UniversalRenderPipelineAsset -> UniversalRenderPipeline` 这条首方包层链 -3. 把 `ScriptableRenderer` 的 stage recording 组合点收成更明确的包内/受保护 API -4. 避免外部 probe 再复制一套“renderer-backed pipeline wrapper”形态 - -原则: - -1. 不回退到旧 wrapper 体系 -2. 不重新引入兼容层 -3. 不把新的 prototype seam 继续公开给用户脚本 - ---- - -## 4. 实施步骤 - -### Step 1:整理 seam 的真实职责 - -目标: - -1. 明确 `ScriptableRenderer` 负责的是 pass queue build + stage record -2. 明确 `UniversalRenderPipeline` 负责的是 renderer lifetime 和 pipeline-level delegation -3. 明确哪些方法应当保留 public,哪些应当降到 `internal/protected` - -### Step 2:重构 `UniversalRenderPipeline` 与 `ScriptableRenderer` 组合边界 - -目标: - -1. 避免外部脚本直接把 renderer 当成 stage recorder 使用 -2. 让 `UniversalRenderPipeline` 通过更明确的 package-local seam 驱动 renderer -3. 保持现有 probe/test 行为不回退 - -### Step 3:收 probe 和 API surface - -目标: - -1. 修改 `RenderPipelineApiProbe.cs` -2. 修改 `ScriptableRenderContextApiSurfaceProbe.cs` -3. 修改 `test_mono_script_runtime.cpp` -4. 固定“用户能看到什么,不能看到什么” - -### Step 4:完整验证 - -目标: - -1. 编译 `XCEditor` -2. 运行相关 `scripting_tests` -3. 运行相关 `rendering_unit_tests` -4. 运行相关 `editor_tests` -5. 做 `editor/bin/Debug/XCEngine.exe` 10s 冒烟并检查 `editor.log` 的 `SceneReady` - ---- - -## 5. 验收标准 - -本阶段完成后应满足: - -1. `Universal` 不再保留“外部可直接复用的 stage recorder seam” -2. `UniversalRenderPipeline` 和 `ScriptableRenderer` 的职责更清楚 -3. probe 不再复制一套长期存在的 renderer-backed pipeline 形态 -4. 现有 SRP/Universal 主链行为不回退 -5. 编译、测试、冒烟全部通过 - ---- - -## 6. 本阶段不做的内容 - -这一阶段明确不做: - -1. deferred pipeline -2. lightmap / baking -3. 阴影/体积/高斯 ownership 迁移 -4. RenderGraph 对用户脚本直接暴露 -5. editor 侧 renderer asset 工作流 - -这一步只处理 `Universal` 的录制组合边界。 diff --git a/docs/used/SRP_Universal默认Pass显式化计划_完成归档_2026-04-20.md b/docs/used/SRP_Universal默认Pass显式化计划_完成归档_2026-04-20.md deleted file mode 100644 index 3d97a87d..00000000 --- a/docs/used/SRP_Universal默认Pass显式化计划_完成归档_2026-04-20.md +++ /dev/null @@ -1,114 +0,0 @@ -# SRP Universal 默认 Pass 显式化计划 2026-04-20 - -## 1. 阶段目标 - -上一阶段已经完成: -1. main-scene 录制入口回收到 `ScriptableRenderContext` -2. native `RecordScene` 旧桥删除 -3. fullscreen 与 main-scene 的基础 authoring 都进入 core/context - -但 Universal 默认主场景组织仍然不够像 Unity: -1. 默认场景拓扑还挂在 `UniversalSceneFeature` -2. renderer 自己没有显式拥有 builtin scene passes -3. opaque / skybox / transparent 仍然表现为通用 phase pass,而不是明确的默认渲染 pass - -这一阶段的目标是: -1. 让 `UniversalRenderer` 自己拥有并组织默认主场景 builtin passes -2. 保留 `ScriptableRendererFeature` 只作为扩展点,而不是默认管线本体 -3. 把默认场景 pass 语义显式化,为后续 SRP/URP 化继续铺路 - -## 2. 当前问题 - -### 2.1 默认主场景拓扑被伪装成 feature - -当前 `UniversalRenderer` 通过 `AddFeature(new UniversalSceneFeature(...))` -把默认 scene 序列塞进 feature 系统。 - -这会导致: -1. builtin pipeline 和 user feature 的职责边界不清 -2. renderer 本身不像 Unity URP 那样显式拥有默认 passes -3. 后续继续做 renderer feature / renderer pass 体系时,默认拓扑会继续漂在扩展层 - -### 2.2 pass 语义仍然偏“占位符” - -当前 main-scene 默认 pass 仍是: -1. injection pass -2. scene phase pass - -这比上一阶段更干净,但还不是最清晰的语义。 -下一步至少要把 renderer 默认 pass 明确为 renderer 自带的 builtin passes, -而不是一个通用 feature 的队列拼装结果。 - -## 3. 本阶段范围 - -本阶段只做: -1. 归档上一阶段 plan -2. 重构 `UniversalRenderer`,让 builtin main-scene pass 由 renderer 自己管理 -3. 清理 `UniversalSceneFeature` 这一层默认拓扑桥接 -4. 保持 project probes / Universal 包行为不回退 -5. 重编译 `XCEditor` -6. 旧 editor 冒烟至少 10 秒 - -本阶段暂不做: -1. culling / drawing settings / filtering settings 的 managed 暴露 -2. deferred renderer -3. HDRP 分支 -4. 真正的 Unity 风格 renderer-list/draw-call authoring - -## 4. 实施步骤 - -### Step 1:收回 builtin scene topology ownership - -目标: -1. `UniversalRenderer` 自己持有 builtin scene passes -2. 通过 `AddRenderPasses(RenderingData)` 组织默认主场景 pass -3. feature 系统只负责附加扩展 passes - -完成标准: -1. 默认 main-scene 拓扑不再依赖 `UniversalSceneFeature` -2. builtin scene pass 的所有权回到 renderer - -### Step 2:显式化默认 scene pass 语义 - -目标: -1. 保留必要 injection 边界 -2. 让 opaque / skybox / transparent 的默认行为成为 renderer builtin passes -3. 为后续继续演进到更像 Unity 的 `DrawObjectsPass` / `DrawSkyboxPass` 打底 - -完成标准: -1. `UniversalRenderer.cs` 不再是“feature 包一层默认拓扑” -2. 默认 pass 结构读起来能直接看出主场景顺序 - -### Step 3:编译与冒烟 - -固定验证: -1. `cmake --build build --config Debug --target XCEditor` -2. 启动 `editor/bin/Debug/XCEngine.exe` -3. 冒烟至少 10 秒 -4. 检查 `editor/bin/Debug/editor.log` 新的 `SceneReady` - -## 5. 验收标准 - -本阶段收口后应满足: -1. `UniversalRenderer` 显式拥有默认主场景 builtin passes -2. `ScriptableRendererFeature` 只承担扩展职责 -3. Universal 默认主场景拓扑边界更接近 Unity 的 renderer-owned 组织方式 -4. `XCEditor` 重编译通过 -5. 旧 editor 冒烟通过 - -## 6. 阶段意义 - -这一阶段完成后,SRP 主线会从: - -`context/core 入口已经收口,但 Universal 默认拓扑仍然漂在 feature 包装层` - -推进到: - -`context/core 负责底层录制入口,Universal renderer 自己负责默认主场景拓扑` - -这样下一小阶段再继续做: -1. 更明确的默认 scene pass 命名与拆分 -2. 更像 Unity 的 renderer pass 体系 -3. 用户侧 feature/pass 与 builtin pass 的职责分界 - -就不会再被“默认逻辑伪装成 feature”这一层历史包袱卡住。 diff --git a/docs/used/SRP_Universal默认ScenePass数据化计划_完成归档_2026-04-21.md b/docs/used/SRP_Universal默认ScenePass数据化计划_完成归档_2026-04-21.md deleted file mode 100644 index ad1997aa..00000000 --- a/docs/used/SRP_Universal默认ScenePass数据化计划_完成归档_2026-04-21.md +++ /dev/null @@ -1,125 +0,0 @@ -# SRP Universal 默认 Scene Pass 数据化计划 2026-04-20 - -## 1. 阶段目标 - -把 `UniversalRenderer` 里写死的默认主场景 pass 顺序抽到 `UniversalRendererData` 一侧,让这一层开始具备真正的 renderer-data 驱动组织能力。 - -这一阶段完成后,要达到的状态是: - -1. `UniversalRenderer` 不再直接硬编码 opaque / skybox / transparent 全套默认 pass 成员 -2. `UniversalRendererData` 不再只是三个布尔开关,而是能描述默认 scene pass 序列 -3. renderer 负责“解释并执行 data”,而不是继续兼任“存放默认拓扑定义” -4. 后续继续做 Unity 风格的 renderer preset、renderer variant、SRP 包层扩展时,有稳定落点 - -## 2. 当前问题 - -现在的主要问题不是 draw primitive 不够,而是默认 renderer 组织仍然过于硬编码: - -1. `UniversalRenderer` 直接持有 before/after opaque、skybox、transparent 这些 pass 实例 -2. `UniversalRendererData` 只有 `renderOpaque / renderSkybox / renderTransparent` -3. 默认主场景拓扑并没有正式进入 renderer data 层 - -这会带来几个后果: - -1. renderer data 无法真正承载“这个 renderer 默认长什么样” -2. 后续做 renderer preset、多个 renderer variant 时,会继续把逻辑堆在 renderer 类本身 -3. Unity 风格的 `RendererData -> Renderer` 关系虽然已经搭起来了,但默认 pass 组织还没真正归位 - -## 3. 本阶段范围 - -本阶段只做默认主场景 pass 的数据化,不做新的高级渲染能力扩展。 - -包括: - -1. 新增一个最小可用的 `UniversalScenePassData` -2. 让 `UniversalRendererData` 用默认 scene pass 数组表达主场景默认序列 -3. 让 `UniversalRenderer` 按 data 创建/配置/执行默认 pass -4. 保持现有默认行为不变,确保旧场景不回退 -5. 重编译 `XCEditor` -6. 运行旧版 `editor/bin/Debug/XCEngine.exe` 冒烟至少 10 秒 - -不包括: - -1. deferred renderer -2. shadow 包层迁移 -3. post-process renderer preset 扩展 -4. 用户级 renderer inspector/asset 编辑器 - -## 4. 设计原则 - -这一步不做一套脱离 Unity 的“任意图编辑器”。 - -采用的原则是: - -1. 只把默认 scene pass 的 ownership 从 renderer 类搬到 renderer data -2. 保持 pass 类型仍然是 Unity 式的 injection / draw renderers / draw skybox -3. 保留现有 `RendererFeature` 作为扩展层,不让 feature 重新承担默认拓扑 -4. renderer 中只保留运行时 pass cache 和执行逻辑 - -## 5. 实施步骤 - -### Step 1:建立默认 Scene Pass 数据模型 - -新增一个短名类型,至少表达: - -1. pass 类型 -2. `RenderPassEvent` -3. injection point -4. scene draw phase -5. `RendererListDesc` -6. `DrawingSettings` -7. 是否启用 - -### Step 2:让 UniversalRendererData 持有默认 Scene Pass 序列 - -把原先三个布尔开关替换成明确的默认序列,默认仍然是: - -1. BeforeOpaque injection -2. Opaque draw -3. AfterOpaque injection -4. BeforeSkybox injection -5. Skybox draw -6. AfterSkybox injection -7. BeforeTransparent injection -8. Transparent draw -9. AfterTransparent injection - -### Step 3:把 UniversalRenderer 改成 data 解释器 - -renderer 需要: - -1. 从 `UniversalRendererData` 读取默认 scene pass 数组 -2. 维护每个 slot 对应的 runtime pass 实例 -3. 根据 data 重新配置 draw/injection/skybox pass -4. 把启用的 pass enqueue 进当前帧 - -### Step 4:编译与冒烟验证 - -固定验证流程: - -1. `cmake --build . --config Debug --target XCEditor` -2. 启动 `editor/bin/Debug/XCEngine.exe` -3. 冒烟至少 10 秒 -4. 检查 `editor/bin/Debug/editor.log` 最新时间与 `SceneReady` - -## 6. 验收标准 - -本阶段收口后必须满足: - -1. `UniversalRenderer` 默认主场景 pass 不再硬编码为字段阵列 -2. `UniversalRendererData` 可以完整表达默认主场景 pass 顺序 -3. 现有 opaque / skybox / transparent 默认行为保持一致 -4. `XCEditor` 编译通过 -5. 旧 editor 冒烟通过 - -## 7. 阶段意义 - -这一步收口之后,SRP 主线会从: - -`Asset -> RendererData -> Renderer` 结构已经存在,但默认主场景拓扑还黏在 renderer 类里 - -推进到: - -`Asset -> RendererData(默认拓扑) -> Renderer(执行器)` - -这才是真正可以继续往 Unity 风格 SRP/URP 走的下一跳。 diff --git a/docs/used/SRP_工程级配置与资源化计划_阶段归档_2026-04-18.md b/docs/used/SRP_工程级配置与资源化计划_阶段归档_2026-04-18.md deleted file mode 100644 index 33b9ee8c..00000000 --- a/docs/used/SRP_工程级配置与资源化计划_阶段归档_2026-04-18.md +++ /dev/null @@ -1,150 +0,0 @@ -# SRP 工程级配置与资源化计划 2026-04-18 - -## 1. 阶段目标 - -`SRP Runtime v1` 已经收口,但当前 `SRP` 仍然主要停留在“运行时已打通”的状态,还不是“工程级正式系统”。 - -这一阶段的目标不是继续堆 `RendererFeature`,也不是直接开 `URP-like` 包,而是先把下面三件事做实: - -1. `GraphicsSettings / RenderPipelineAsset` 不再依赖临时全局状态。 -2. `SRP Asset` 能进入项目级配置、持久化与编辑器工作流。 -3. `BuiltinForwardPipeline` 继续退化为 native renderer backend,而不是未来所有 SRP 逻辑的硬编码中心。 - -本阶段收口后,才适合正式往 `SRP/URP` 的工程化主线推进。 - -## 2. 当前根问题 - -### 2.1 图形设置仍然是临时运行时状态 - -虽然 managed 侧已经有: - -`GraphicsSettings.renderPipelineAsset` - -但 native 内部目前仍然主要靠: - -1. 全局 descriptor -2. 全局 bridge -3. generation 计数 - -来驱动渲染管线切换。 - -这套机制适合验证 `SRP Runtime v1`,但不适合继续承接: - -1. 项目级图形设置 -2. 资源引用与持久化 -3. 编辑器切换与恢复 -4. 后续 renderer asset / quality tier / default policy 扩展 - -### 2.2 工程级配置入口还不存在 - -项目里目前没有正式的: - -1. `ProjectSettings/GraphicsSettings` 文件 -2. `RenderPipelineAsset` 资源引用落点 -3. 编辑器图形设置面板 -4. 项目打开时的图形设置加载逻辑 - -### 2.3 BuiltinForward 仍然承担过多组织责任 - -当前 `ScriptableRenderPipelineHost` 已经成立,但主场景绘制能力仍然大量锁在 `BuiltinForwardPipeline` 内部。 - -这会阻塞下一阶段: - -1. `managed SRP` 调用稳定 native renderer contract -2. `URP-like renderer data / feature / pass` -3. 阴影、后处理、体积、高斯等组织权的逐步上移 - -## 3. 本阶段边界 - -### 本阶段要做 - -1. 把 render pipeline 选择状态从临时全局静态中抽离,落成正式 native `GraphicsSettingsState` -2. 为后续项目级 `GraphicsSettings` 持久化预留稳定 native 承接点 -3. 开始梳理 `BuiltinForward -> Native Renderer Contract` 的边界 -4. 继续扩 `ScriptableRenderContext` 所需的最小正式能力 - -### 本阶段不做 - -1. 不直接开 `URP-like package` -2. 不直接做 deferred pipeline -3. 不把阴影 / 高斯 / 体积 / 后处理的组织权一次性搬到 C# -4. 不急着补 Unity 全量 `CommandBuffer` - -## 4. 分阶段执行 - -### Phase A: GraphicsSettings native 状态正式化 - -目标: - -1. 新建明确的 native `GraphicsSettingsState` -2. render pipeline 选择、bridge、generation 不再散落在 `ManagedScriptableRenderPipelineAsset.cpp` 的局部静态里 -3. `MonoScriptRuntime`、`CameraRenderer`、pipeline factory 统一依赖同一个状态入口 - -验收: - -1. 原有 SRP runtime 测试通过 -2. `XCEditor` 编译通过 -3. 旧版 editor 冒烟通过 - -### Phase B: 项目级 GraphicsSettings 配置文件 - -目标: - -1. 为项目建立正式 `GraphicsSettings` 配置文件 -2. 保存当前 `RenderPipelineAsset` 选择 -3. 项目打开时恢复配置 - -### Phase C: 编辑器图形设置接入 - -目标: - -1. 编辑器能查看/切换项目当前 `RenderPipelineAsset` -2. 切换后能写回项目级配置 -3. 运行时与编辑态对同一份设置状态达成一致 - -### Phase D: BuiltinForward 收口为 native renderer contract - -目标: - -1. 把主场景绘制能力从“完整 builtin pipeline”里继续拆出来 -2. 让 managed SRP 调用稳定 native renderer API -3. 为未来 `RendererData / RendererFeature / RenderPass` 铺路 - -### Phase E: SRP Runtime v2 入口 - -目标: - -1. 在新的工程级配置与 native renderer contract 上继续扩展 `ScriptableRenderContext` -2. 进入 `URP-like` 前置阶段 - -## 5. 第一刀的具体落点 - -先做 `Phase A`。 - -第一刀只改 native 承接层,不碰 `URP-like` 组织层: - -1. 新增 `Rendering/GraphicsSettingsState` -2. 把 managed render pipeline descriptor / bridge / generation 收进状态对象 -3. 让关键调用链改走统一状态入口 - -如果这一刀不先做,后面无论是项目配置文件还是编辑器图形设置面板,都会继续建立在临时状态之上。 - -## 6. 阶段验收标准 - -本计划收口前,至少满足: - -1. render pipeline 选择状态不再依赖临时散落静态 -2. native 存在明确 `GraphicsSettingsState` -3. `SRP Runtime v1` 既有行为不回退 -4. `XCEditor` 编译通过 -5. 旧版 `editor/bin/Debug/XCEngine.exe` 冒烟通过 - -## 7. 收口后的下一步 - -这一阶段完成后,下一步就正式进入: - -`Project GraphicsSettings` -`-> Editor Graphics Settings UI` -`-> Native Renderer Contract` -`-> SRP Runtime v2` -`-> URP-like` diff --git a/docs/used/SRP_核心层与首方渲染管线拆层计划_完成归档_2026-04-19.md b/docs/used/SRP_核心层与首方渲染管线拆层计划_完成归档_2026-04-19.md deleted file mode 100644 index ba8dcd46..00000000 --- a/docs/used/SRP_核心层与首方渲染管线拆层计划_完成归档_2026-04-19.md +++ /dev/null @@ -1,420 +0,0 @@ -# SRP 核心层与首方渲染管线拆层计划 2026-04-19 - -## 1. 结论 - -`docs/plan/SRP_Mainline_2026-04-16.md` 和 -`docs/plan/SRP_Runtime_v2_2026-04-18.md` -都应该归档。 - -原因不是“SRP 已经全部做完”,而是这两份计划对应的目标阶段, -已经被当前代码状态实质性越过了。 - -当前代码已经具备: - -1. 可运行的 native RenderGraph / planning / execution 主链 -2. 可运行的 managed SRP runtime -3. `Renderer / Feature / Pass` 这一层的基础模型 -4. 一条能跑起来的 first-party forward renderer 骨架 - -所以下一阶段的真正主线,不再是“继续搭 SRP runtime”,而是: - -**把当前混在 `managed/XCEngine.ScriptCore` 里的 SRP Core 与首方渲染管线层拆开, -把边界做干净,为后续真正的 URP-like 包层和用户自定义渲染管线做准备。** - -另外,`RenderGraph` 仍然应该留在 C++ 层。 -它属于渲染内核,不属于未来的 URP 包层。 - ---- - -## 2. 当前状态判断 - -### 2.1 已经成立的部分 - -从当前代码看,下面这些能力已经不是计划,而是已经存在: - -#### Native 侧 - -1. `engine/src/Rendering/Graph/RenderGraph.cpp` -2. `engine/src/Rendering/Graph/RenderGraphCompiler.cpp` -3. `engine/src/Rendering/Graph/RenderGraphExecutor.cpp` -4. `engine/src/Rendering/Pipelines/ScriptableRenderPipelineHost.cpp` -5. `engine/src/Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.cpp` -6. `engine/src/Rendering/GraphicsSettingsState.cpp` - -这说明 native 侧已经具备: - -1. RenderGraph 录制、编译、执行 -2. pipeline host -3. managed pipeline bridge -4. graphics settings 状态承接 - -#### Managed 侧 SRP Core / Runtime - -1. `managed/XCEngine.ScriptCore/RenderPipelineAsset.cs` -2. `managed/XCEngine.ScriptCore/ScriptableRenderPipelineAsset.cs` -3. `managed/XCEngine.ScriptCore/ScriptableRenderPipeline.cs` -4. `managed/XCEngine.ScriptCore/GraphicsSettings.cs` -5. `managed/XCEngine.ScriptCore/ScriptableRenderContext.cs` -6. `managed/XCEngine.ScriptCore/ScriptableRenderPipelineCameraRequestContext.cs` -7. `managed/XCEngine.ScriptCore/ScriptableRenderPipelinePlanningContext.cs` - -这说明 managed 侧已经不是只有一个“空壳入口”,而是已经有真正的 -pipeline asset、pipeline、context、graphics settings 运行时接口。 - -#### Managed 侧 Renderer / Feature / Pass 层 - -1. `managed/XCEngine.ScriptCore/ScriptableRendererData.cs` -2. `managed/XCEngine.ScriptCore/ScriptableRenderer.cs` -3. `managed/XCEngine.ScriptCore/ScriptableRendererFeature.cs` -4. `managed/XCEngine.ScriptCore/ScriptableRenderPass.cs` -5. `managed/XCEngine.ScriptCore/RenderPassEvent.cs` -6. `managed/XCEngine.ScriptCore/RenderingData.cs` -7. `managed/XCEngine.ScriptCore/CameraData.cs` - -这意味着旧计划里“下一步先把 Renderer / Feature / Pass 搭出来” -这件事,现在也已经不是待做事项了。 - -#### 当前首方 forward 骨架 - -1. `managed/XCEngine.ScriptCore/Rendering/Universal/UniversalRenderPipelineAsset.cs` -2. `managed/XCEngine.ScriptCore/Rendering/Universal/UniversalRendererData.cs` -3. `managed/XCEngine.ScriptCore/Rendering/Universal/UniversalRenderer.cs` -4. `managed/XCEngine.ScriptCore/ColorScalePostProcessRendererFeature.cs` -5. `managed/XCEngine.ScriptCore/DisableDirectionalShadowRendererFeature.cs` -6. `managed/GameScripts/RenderPipelineApiProbe.cs` - -这说明当前已经不仅有 SRP runtime, -而且已经有一条“首方 renderer + feature + pass”的工作链路。 - -### 2.2 当前真正的问题 - -现在的主矛盾已经不是“SRP 能不能跑起来”,而是下面这些结构问题: - -1. `managed/XCEngine.ScriptCore` 同时塞了基础脚本 API、SRP Core、首方 forward renderer、示例 feature -2. `ScriptableRenderer / ScriptableRenderPass / RenderPassEvent / RenderingData` - 这些更像 URP-like 层的东西,仍然和 SRP Core 混在一起 -3. `UniversalRenderer*`、`ColorScalePostProcessRendererFeature`、 - `DisableDirectionalShadowRendererFeature` 这种明显属于首方管线层的实现, - 还在“ScriptCore”里直接暴露 -4. `ScriptableRenderContext` 目前仍暴露了 - `ScenePhase`、`SceneRenderInjectionPoint`、`FullscreenPassDescriptor` - 这类偏 prototype / native contract 的接口 -5. `GraphicsSettingsState` 虽然已经比以前正规很多,但项目级配置语义仍然偏 - “descriptor/bootstrap”,还不是最终的资源化资产语义 -6. `managed/CMakeLists.txt` 目前只假设有 - `XCEngine.ScriptCore.dll + GameScripts.dll` - 两层 managed 程序集,说明还不适合直接粗暴切成多个 engine 程序集 - ---- - -## 3. 根因分析 - -### 3.1 旧计划解决的是“能不能跑” - -前两轮 SRP 计划的核心任务是: - -1. 让 managed pipeline 真正存在 -2. 让 native 能持有并回调 managed pipeline -3. 让 C# 能参与 RenderGraph / stage 录制 -4. 让 `Renderer / Feature / Pass` 这一层先长出来 - -这些事情现在已经基本成立。 - -### 3.2 当前阶段要解决的是“边界是否稳定” - -如果现在不先做拆层,后面继续往上堆功能,就会出现三个后果: - -1. `XCEngine.ScriptCore` 会继续膨胀成一个“什么都往里塞”的程序集 -2. 未来 URP-like 包层会直接继承今天这些 prototype API, - 把临时接口永久化 -3. 用户以后做自定义渲染管线时,拿到的不是干净的 SRP Core, - 而是一坨混合了引擎内核、首方 renderer 和试验性 feature 的 API - -所以当前最值钱的一刀不是继续加 pass, -而是先把层级边界做对。 - ---- - -## 4. 下一阶段的正确目标 - -下一阶段的目标不是“立刻做完整 URP”,而是先把下面这个结构稳定下来: - -`Native RHI / Native RenderGraph / Native Renderer Contract` -`-> Managed Base Script API` -`-> Managed SRP Core` -`-> Managed First-Party Renderer Layer` -`-> GameScripts / 用户自定义渲染管线与 Feature` - -这里最关键的是: - -1. C++ 继续负责渲染执行内核 -2. SRP Core 只保留真正稳定、长期保留的管线抽象 -3. `Renderer / Feature / Pass` 和当前 `UniversalRenderer*` - 视为首方渲染层,不再和 SRP Core 混放 -4. 真正的 URP-like 包层,要建立在这条边界已经清晰之后 - ---- - -## 5. 目标边界 - -### 5.1 应该留在 SRP Core 的内容 - -当前阶段建议保留在 SRP Core 的主要内容: - -1. `RenderPipelineAsset` -2. `ScriptableRenderPipelineAsset` -3. `ScriptableRenderPipeline` -4. `ScriptableRenderContext` -5. `GraphicsSettings` -6. `CameraFrameStage` -7. `CameraFrameColorSource` -8. `ScriptableRenderPipelineCameraRequestContext` -9. `ScriptableRenderPipelinePlanningContext` -10. 和 pipeline asset / frame planning 直接相关的最小数据契约 - -### 5.2 应该归入首方渲染层的内容 - -当前阶段建议划到首方渲染层的内容: - -1. `ScriptableRendererData` -2. `ScriptableRenderer` -3. `ScriptableRendererFeature` -4. `ScriptableRenderPass` -5. `RenderPassEvent` -6. `RenderingData` -7. `CameraData` -8. `LightingData` -9. `ShadowData` -10. `EnvironmentData` -11. `FinalColorData` -12. `StageColorData` -13. `UniversalRenderPipelineAsset` -14. `UniversalRendererData` -15. `UniversalRenderer` -16. `ColorScalePostProcessRendererFeature` -17. `DisableDirectionalShadowRendererFeature` - -理由很简单: - -这些类型描述的不是“最底层 SRP 抽象”, -而是“一个首方 renderer 如何组织 pass 和 feature”。 - -这在 Unity 语义里更接近 URP 包层,而不是 SRP Core。 - -### 5.3 应该收紧或下沉的 prototype 接口 - -下面这些东西不适合长期作为 public SRP Core API 暴露: - -1. `ScenePhase` -2. `SceneRenderInjectionPoint` -3. `FullscreenPassDescriptor` - -它们当前可以存在, -但后续应该逐步: - -1. 移入首方渲染层 -2. 或改成更中性的 context helper / renderer backend contract -3. 而不是长期挂在用户直接面对的 Core 表面 - ---- - -## 6. 执行策略 - -### 原则 1:先拆层,再拆程序集 - -虽然长期目标是把 SRP Core 和首方渲染层真正拆到不同程序集, -但当前 `managed/CMakeLists.txt` 还只支持: - -1. `XCEngine.ScriptCore.dll` -2. `GameScripts.dll` - -所以不能一上来同时做: - -1. 大量 API 重命名 -2. 多程序集构建 -3. 运行时装载链改造 -4. probe / tests 全量迁移 - -这样风险太高。 - -更正确的顺序是: - -1. 先在现有单程序集内把文件结构、命名空间、责任边界拆清楚 -2. 再补多程序集构建与装载 -3. 最后再把首方渲染层独立成真正的 URP-like 包层 - -### 原则 2:不长期保留双轨兼容 - -这一阶段不建议为了“兼容旧 API”长期保留两套命名或两套入口。 - -可以有极短暂的迁移提交, -但目标必须是: - -1. core 是 core -2. first-party renderer 是 first-party renderer -3. 旧的混放路径尽快清掉 - -### 原则 3:先整理 ownership,再继续加功能 - -在拆层完成之前,不应该继续往现有 `ScriptCore` 里堆: - -1. 更多 post-process feature -2. 更多 shadow 策略脚本封装 -3. deferred -4. lightmap -5. 大量 renderer asset 编辑器工作流 - -否则只会把错的边界继续固化。 - ---- - -## 7. 分阶段实施 - -### Phase A: 单程序集内先完成目录与命名空间拆层 - -目标: - -1. 在 `managed/XCEngine.ScriptCore` 内先把 - `Base / SRP Core / Universal Rendering` - 至少分目录 -2. 能拆命名空间的地方先拆命名空间 -3. 更新 `managed/CMakeLists.txt`,不再把 SRP 与首方 forward 实现混成一坨平铺文件 -4. 不改变 native 行为,只做结构重组与边界收口 - -建议的第一版目录方向: - -1. `managed/XCEngine.ScriptCore/Rendering/Core/*` -2. `managed/XCEngine.ScriptCore/Rendering/Universal/*` -3. `managed/XCEngine.ScriptCore/Rendering/Internal/*` - -验收: - -1. `XCEngine.ScriptCore.dll` 继续可编译 -2. 旧 editor 能继续加载脚本程序集 -3. SRP 现有 probe 不回退 - -### Phase B: 把首方 renderer 契约从 Core 里拔出去 - -目标: - -1. 让 `ScriptableRenderer* / ScriptableRenderPass / RenderPassEvent` - 从“像 Core”变成“明确属于首方渲染层” -2. `UniversalRenderer*` 不再伪装成“底层核心的一部分” -3. 现有示例 feature 一并迁入首方层 - -这一阶段完成后,`ScriptableRenderPipeline` 本身只负责: - -1. pipeline 生命周期 -2. 选择和驱动 renderer -3. 不再自己承担首方 renderer 的组织细节 - -### Phase C: 收紧 `ScriptableRenderContext` 的原型泄漏 - -目标: - -1. 盘点 `ScenePhase / SceneRenderInjectionPoint / FullscreenPassDescriptor` - 这些接口是否应该长期 public -2. 把明显属于首方 renderer backend 的能力收口到更合适的位置 -3. 如果需要新增中性 helper,则以最小包装形式补上 - -这一阶段的原则是: - -**不是马上做成 Unity 全量 `ScriptableRenderContext`,** -**而是先把今天暴露错层的接口收回正确层。** - -### Phase D: GraphicsSettings 和 pipeline asset 语义正式化 - -目标: - -1. 保留当前 `GraphicsSettings.renderPipelineAsset` - 作为 managed 入口 -2. 继续弱化 native 侧“descriptor-only”的语义 -3. 为未来项目级资源引用、资产持久化和默认管线恢复打地基 - -注意: - -这一步不是优先去做 editor UI, -而是先把 runtime 语义收干净。 - -### Phase E: 多程序集 / 包层化前置改造 - -当 Phase A-D 收口后,再进入真正的多程序集前置: - -1. 扩展 `managed/CMakeLists.txt` -2. 让 engine managed assemblies 不再只有 `XCEngine.ScriptCore.dll` -3. 让 `GameScripts.dll` 能引用: - - base script core - - SRP core - - first-party pipeline assembly -4. 让 runtime 装载链支持多个 engine managed assemblies - -只有到这里,后面再开: - -1. `XCEngine.RenderPipelines.Core.dll` -2. `XCEngine.RenderPipelines.Universal.dll` - -才是顺的。 - ---- - -## 8. 第一刀应该落在哪里 - -下一步最正确的第一刀是: - -**先做 Phase A,先把 `managed/XCEngine.ScriptCore` 内部的 SRP Core 与首方渲染层拆目录、拆责任、拆命名空间。** - -原因: - -1. 这是当前最根上的结构问题 -2. 这一步不需要先动 editor -3. 这一步不需要先动 deferred / lightmap / shadow 迁移 -4. 这一步做对了,后面的 URP-like 包层才不会建立在混乱边界上 - ---- - -## 9. 本阶段明确不做的内容 - -这一阶段明确不做: - -1. deferred pipeline -2. lightmap / baking -3. 大规模 editor 面板工作流 -4. 把阴影、体积、高斯全部立刻搬到 C# -5. 直接把 RenderGraph 暴露给用户脚本 -6. 直接把所有 managed 渲染代码拆成多个程序集并同时改运行时装载链 - -这些都不是现在最值钱的一刀。 - ---- - -## 10. 阶段验收标准 - -这一阶段收口时,至少要满足: - -1. `docs/plan` 和代码主线一致,不再把已经完成的 SRP runtime 阶段继续挂在当前计划里 -2. `managed/XCEngine.ScriptCore` 内部已经能明确区分: - - Base Script API - - SRP Core - - First-Party Rendering -3. 首方 forward renderer 与示例 feature 不再伪装成“core” -4. `ScriptableRenderContext` 上明显错层的 public surface 已被盘点并开始收口 -5. `GraphicsSettings` 与 pipeline asset 的后续正式化方向已经明确 -6. editor 与现有 SRP probe 不回退 - -只要上面这些条件不成立,这一阶段就不能算收口。 - ---- - -## 11. 收口后的下一阶段 - -等这一阶段收口后,下一阶段才适合正式切到: - -`SRP Core 正式独立` -`-> First-Party Universal/Forward Pipeline 包层` -`-> 用户自定义 RendererFeature / Pass` -`-> 再往后才是 deferred / lightmap / 更完整的 renderer asset 工作流` - -也就是说: - -**现在不是“继续补 SRP runtime”,** -**而是“把已经长出来的 SRP runtime 和首方渲染层做一次真正的结构定型”。** diff --git a/docs/used/SRP_项目侧自定义渲染管线打通计划_完成归档_2026-04-20.md b/docs/used/SRP_项目侧自定义渲染管线打通计划_完成归档_2026-04-20.md deleted file mode 100644 index 8cc98eca..00000000 --- a/docs/used/SRP_项目侧自定义渲染管线打通计划_完成归档_2026-04-20.md +++ /dev/null @@ -1,147 +0,0 @@ -# SRP 项目侧自定义渲染管线打通计划 2026-04-20 - -## 1. 阶段目标 - -上一阶段已经把这条 managed 主链正式化了: - -`ScriptableRenderPipelineAsset -> RendererData -> Renderer -> Feature -> Pass` - -并且已经锁住了: - -1. `renderer data dirty -> asset runtime version -> native runtime rebuild` -2. `request / frame plan / backend key / execution` 统一 renderer selection -3. feature/pass 生命周期与 builtin/custom feature 顺序契约 - -下一阶段不再继续整理内部边界,而是正式把“用户在项目脚本里自定义 SRP/URP”这条外部能力打通。 - -目标不是先做编辑器,而是先证明: - -1. `project/Assets` 里的 C# 脚本可以直接继承公开 SRP API -2. 项目脚本定义的 `RenderPipelineAsset / RendererData / RendererFeature / RenderPass` 能真正跑进 native runtime -3. 这条路径不是 probe 特例,而是后续做 Unity 风格 SRP/URP 的正式入口 - ---- - -## 2. 当前缺口 - -当前引擎虽然已经有: - -1. `XCEngine.ScriptCore.dll` -2. `XCEngine.RenderPipelines.Universal.dll` -3. managed render pipeline bridge -4. 项目脚本程序集 `project/Assets -> GameScripts.dll` - -但是目前 SRP 能力主要还是在 `managed/GameScripts/RenderPipelineApiProbe.cs` 里被验证,离“项目作者真正使用”还差一层正式化: - -1. 缺少项目侧自定义 renderer feature/pass 的正式验证 -2. 缺少项目侧自定义 renderer data / pipeline asset 的正式验证 -3. 缺少项目侧路径下的 invalidation / rebuild / release 契约验证 -4. 还没有把“引擎内置 probe”与“项目作者可直接照着写”的能力明确区分开 - -也就是说,现在 SRP core 已经成型,但“用户可用”这一步还没有打通。 - ---- - -## 3. 本阶段范围 - -本阶段只做“项目侧自定义 SRP 能力打通”,不做下面这些内容: - -1. 不做 editor inspector / asset 面板 -2. 不做新的渲染特性大项,比如 deferred / lightmap / SSAO -3. 不改 RenderGraph 的 C++ 层方向 -4. 不做 HDRP 风格的复杂内容体系 - -本阶段只做一件事: - -把 `project/Assets` 里的 C# 脚本真正变成可运行、可验证、可扩展的 SRP 入口。 - ---- - -## 4. 实施步骤 - -### Step 1:打通项目侧 custom renderer feature / pass - -目标: - -让项目脚本可以直接定义自己的 `ScriptableRendererFeature` 和 `ScriptableRenderPass`,并通过公开 API 参与主场景 / 后处理阶段录制。 - -预期改动点: - -1. `project/Assets/Scripts/*` -2. `tests/scripting/test_mono_script_runtime.cpp` -3. 如有必要,微调 `managed/XCEngine.ScriptCore/Rendering/*` 的公开面 - -需要锁住的结果: - -1. 项目脚本定义的 custom feature/pass 能被发现并运行 -2. 录制顺序与 stage 支持判断符合正式 SRP 链路 -3. 不依赖 probe 内部 helper 才能工作 - -### Step 2:打通项目侧 custom renderer data / pipeline asset - -目标: - -让项目脚本直接定义自己的 `RendererBackedRenderPipelineAsset` / `ScriptableRendererData` / `ScriptableRenderer`,并由 `GraphicsSettings.renderPipelineAsset` 正式选中。 - -预期改动点: - -1. `project/Assets/Scripts/*` -2. `tests/scripting/test_mono_script_runtime.cpp` -3. 如有必要,补 `managed/XCEngine.ScriptCore/Rendering/Universal/*` - -需要锁住的结果: - -1. 项目侧 asset 能经由 managed bridge materialize 成 native runtime -2. backend key / request / plan / execution 继续走统一主链 -3. 项目侧 renderer 不需要 engine 内部特判 - -### Step 3:锁住项目侧 invalidation / rebuild / release 契约 - -目标: - -把“项目作者改了 pipeline asset / renderer data / feature 配置之后,native runtime 能稳定重建”这件事正式化。 - -需要锁住的结果: - -1. project-side asset invalidation 会传播到 runtime version -2. project-side renderer data invalidation 会触发 renderer/pipeline rebuild -3. release 后不会残留旧 feature/pass/runtime cache - -### Step 4:阶段验证与收口 - -固定验证流程: - -1. 编译 `XCEditor` -2. 运行 `rendering_unit_tests` -3. 运行 `scripting_tests` -4. 运行旧版 `editor/bin/Debug/XCEngine.exe` 至少 10s -5. 检查新的 `editor/bin/Debug/editor.log` 中出现 `SceneReady` -6. plan 归档到 `docs/used` -7. 使用规范 commit message 提交并推送 - ---- - -## 5. 验收标准 - -完成后应满足: - -1. 项目脚本可以直接定义并运行自定义 SRP 组件 -2. engine package 提供的是正式 API,而不是只能靠测试 probe 才能走通 -3. 用户未来写自己的“轻量 URP 包”时,不需要再先改 C++ 主链 -4. 下一阶段可以继续往真正的 URP 内容层扩展,而不是继续补基础接缝 - ---- - -## 6. 阶段意义 - -这一步做完之后,引擎就不只是“内部已经有 SRP 骨架”,而是会进入: - -`SRP core 已可被项目脚本直接消费` - -这才是后面继续做 Unity 风格: - -1. 官方 URP 包层 -2. 用户自定义 RendererFeature -3. 更复杂的通用渲染特性 - -的真正起点。 diff --git a/docs/used/SceneViewport_Overlay_Gizmo_Rework_Checkpoint_完成归档_2026-04-05.md b/docs/used/SceneViewport_Overlay_Gizmo_Rework_Checkpoint_完成归档_2026-04-05.md deleted file mode 100644 index 885aba72..00000000 --- a/docs/used/SceneViewport_Overlay_Gizmo_Rework_Checkpoint_完成归档_2026-04-05.md +++ /dev/null @@ -1,223 +0,0 @@ -# SceneViewport Overlay/Gizmo Rework Checkpoint - -## Update 2026-04-04 Phase 5G - -### Chrome/Focus/Presentation Shell Formalization Completed - -- Added `SceneViewportChrome.{h,cpp}` to take over the remaining scene viewport chrome responsibilities: - - top toolbar rendering - - in-viewport tool overlay rendering - - unified tool-change command building and execution -- Extended `SceneViewportInteractionFrame.h` with explicit helpers for: - - post-interaction viewport focus decisions - - final scene viewport presentation refresh and HUD draw -- `SceneViewPanel` no longer owns inline scene toolbar/tool overlay rendering, ad-hoc tool switching glue, focus heuristics, or the final gizmo refresh/HUD presentation tail path. -- Added focused editor tests covering tool-command composition/execution and the new focus/presentation helpers. - -### Verification - -- `cmake --build build --config Debug --target editor_tests -- /p:BuildProjectReferences=false` -- `build/tests/Editor/Debug/editor_tests.exe --gtest_filter=SceneViewportChromeTest.*:SceneViewportNavigationTest.*:SceneViewportInteractionFrameTest.*:SceneViewportInteractionActionsTest.*:SceneViewportInteractionResolverTest.*:SceneViewportTransformGizmoCoordinatorTest.*:SceneViewportOverlayRenderer_Test.*:SceneViewportOverlayProviderRegistryTest.*:ViewportRenderFlowUtilsTest.*` -- `cmake --build build --config Debug --target XCEditor` - -All commands completed successfully in `Debug`. - -## Update 2026-04-04 Phase 5F - -### Interaction Frame/Request Glue Formalization Completed - -- Added `SceneViewportInteractionFrame.h` to formalize: - - scene viewport tool visibility/state derivation - - frame geometry derivation from viewport rect and mouse position - - per-frame interaction/gizmo context assembly - - interaction resolve request construction -- `SceneViewPanel` no longer assembles tool visibility booleans, local mouse coordinates, overlay frame references, or interaction resolve requests inline. -- The panel now consumes `SceneViewportToolState`, `SceneViewportFrameGeometry`, and `SceneViewportInteractionFrameState` instead of stitching together those frame-level inputs itself. -- Added focused editor tests covering tool-state mapping, frame geometry, interaction frame state, and resolve request assembly. - -### Verification - -- `cmake --build build --config Debug --target editor_tests -- /p:BuildProjectReferences=false` -- `build/tests/Editor/Debug/editor_tests.exe --gtest_filter=SceneViewportNavigationTest.*:SceneViewportInteractionFrameTest.*:SceneViewportInteractionActionsTest.*:SceneViewportInteractionResolverTest.*:SceneViewportTransformGizmoCoordinatorTest.*:SceneViewportOverlayRenderer_Test.*:SceneViewportOverlayProviderRegistryTest.*:ViewportRenderFlowUtilsTest.*` -- `cmake --build build --config Debug --target XCEditor` - -All commands completed successfully in `Debug`. - -## Update 2026-04-04 Phase 5E - -### Navigation/Input State Formalization Completed - -- Added `SceneViewportEditorModes.h` to move shared scene viewport editor mode enums out of `SceneViewPanel.h`. -- Added `SceneViewportNavigation.h` as the formal navigation/input helper layer for: - - tool shortcut resolution - - look/pan drag state transitions - - mouse/keyboard capture requests - - `SceneViewportInput` assembly - - interaction-resolution gating -- `SceneViewPanel` now owns one `SceneViewportNavigationState` instead of multiple raw drag booleans and no longer assembles scene viewport input inline. -- Removed the dead `m_loggedLookDelta` / `m_loggedPanDelta` panel state that no longer influenced runtime behavior. -- Added focused editor tests for shortcut mapping, drag transitions, capture flags, input assembly, and interaction gating. - -### Verification - -- `cmake --build build --config Debug --target editor_tests -- /p:BuildProjectReferences=false` -- `build/tests/Editor/Debug/editor_tests.exe --gtest_filter=SceneViewportNavigationTest.*:SceneViewportInteractionActionsTest.*:SceneViewportInteractionResolverTest.*:SceneViewportTransformGizmoCoordinatorTest.*:SceneViewportOverlayRenderer_Test.*:SceneViewportOverlayProviderRegistryTest.*:ViewportRenderFlowUtilsTest.*` -- `cmake --build build --config Debug --target XCEditor` - -All commands completed successfully in `Debug`. - -## Update 2026-04-04 Phase 5D - -### Stage-Oriented Gizmo Frame API Completed - -- Extended `SceneViewportTransformGizmoCoordinator.{h,cpp}` with formal frame options, refresh requests, frame updates, and refresh-and-submit helpers. -- `SceneViewPanel` no longer directly assembles the large `RefreshSceneViewportTransformGizmos(...)` argument list for interaction and draw stages. -- The required dual-stage gizmo timing remains intact, but the stage orchestration now flows through `BuildSceneViewportTransformGizmoRefreshRequest(...)` and `RefreshAndSubmitSceneViewportTransformGizmoFrame(...)`. -- Added coordinator tests covering frame option/request construction and refresh-to-submit flow. - -### Verification - -- `cmake --build build --config Debug --target editor_tests -- /p:BuildProjectReferences=false` -- `build/tests/Editor/Debug/editor_tests.exe --gtest_filter=SceneViewportInteractionActionsTest.*:SceneViewportInteractionResolverTest.*:SceneViewportTransformGizmoCoordinatorTest.*:SceneViewportOverlayRenderer_Test.*:SceneViewportOverlayProviderRegistryTest.*:ViewportRenderFlowUtilsTest.*` -- `cmake --build build --config Debug --target XCEditor` - -All commands completed successfully in `Debug`. - -## Update 2026-04-04 Phase 5C - -### Transform Gizmo Coordinator Completed - -- Added `SceneViewportTransformGizmoCoordinator.{h,cpp}` to formalize transform gizmo overlay submission and drag lifecycle dispatch. -- `SceneViewPanel` no longer assembles transform gizmo overlay state inline or manually switches between `TryBeginDrag(...)`, `UpdateDrag(...)`, and `EndDrag(...)`. -- Overlay submission now flows through `BuildSceneViewportTransformGizmoOverlaySubmission(...)` and `SubmitSceneViewportTransformGizmoOverlaySubmission(...)`. -- Drag lifecycle intent now flows through explicit lifecycle commands, keeping the existing interaction timing unchanged while shrinking panel-owned orchestration. - -### Verification - -- `cmake --build build --config Debug --target editor_tests -- /p:BuildProjectReferences=false` -- `build/tests/Editor/Debug/editor_tests.exe --gtest_filter=SceneViewportInteractionActionsTest.*:SceneViewportInteractionResolverTest.*:SceneViewportTransformGizmoCoordinatorTest.*:SceneViewportOverlayRenderer_Test.*:SceneViewportOverlayProviderRegistryTest.*:ViewportRenderFlowUtilsTest.*` -- `cmake --build build --config Debug --target XCEditor` - -All commands completed successfully in `Debug`. - -## Update 2026-04-04 Phase 5B - -### Interaction Actions Completed - -- Added `SceneViewportInteractionActions.{h,cpp}` to formalize viewport-side hover state application and click action derivation. -- `SceneViewPanel` no longer assembles orientation click, scene icon click, scene pick fallback, or hovered gizmo handle state inline. -- Selection and orientation side effects now flow through `DispatchSceneViewportInteractionActions(...)`. -- The panel keeps navigation and active gizmo drag ownership; interaction semantics are now authored in the viewport module. - -### Verification - -- `cmake --build build --config Debug --target editor_tests -- /p:BuildProjectReferences=false` -- `build/tests/Editor/Debug/editor_tests.exe --gtest_filter=SceneViewportInteractionActionsTest.*:SceneViewportInteractionResolverTest.*:SceneViewportOverlayRenderer_Test.*:SceneViewportOverlayProviderRegistryTest.*:ViewportRenderFlowUtilsTest.*` -- `cmake --build build --config Debug --target XCEditor` - -All commands completed successfully in `Debug`. - -## Update 2026-04-03 Phase 5A - -### Interaction Resolver Completed - -- Added `SceneViewportInteractionResolver.{h,cpp}` as the formal viewport-side interaction arbitration module. -- `SceneViewPanel` no longer owns overlay handle priority rules or HUD/world interaction winner selection. -- Overlay handle hit testing and HUD hit testing are now composed behind one resolver entry point. -- The panel now consumes a resolved interaction result instead of stitching together multiple hit systems inline. - -### Verification - -- `cmake --build build --config Debug --target editor_tests -- /p:BuildProjectReferences=false` -- `build/tests/Editor/Debug/editor_tests.exe --gtest_filter=SceneViewportInteractionResolverTest.*:SceneViewportOverlayRenderer_Test.*:SceneViewportOverlayProviderRegistryTest.*:ViewportRenderFlowUtilsTest.*` -- `cmake --build build --config Debug --target XCEditor` - -All commands completed successfully in `Debug`. - -## Update 2026-04-03 - -### Phase 4 Completed - -- Formalized a dedicated HUD overlay API via `SceneViewportHudOverlay.{h,cpp}`. -- `SceneViewPanel` no longer calls the orientation gizmo draw/hit helpers directly. -- HUD hit testing now flows through `HitTestSceneViewportHudOverlay(...)`. -- World overlay remains on the formal overlay/pass path; HUD stays on the ImGui/UI path. -- Removed the legacy `SceneViewportOverlayRenderer.*` shim. - -### Verification - -- `cmake --build build --config Debug --target editor_tests -- /p:BuildProjectReferences=false` -- `build/tests/Editor/Debug/editor_tests.exe --gtest_filter=SceneViewportOverlayRenderer_Test.*:SceneViewportOverlayProviderRegistryTest.*:ViewportRenderFlowUtilsTest.*` -- `cmake --build build --config Debug --target XCEditor` - -All commands completed successfully in `Debug`. - -### Next Step - -- Continue Phase 5 cleanup: keep shrinking residual viewport/UI glue in `SceneViewPanel` and formalize remaining editor-only overlay/resource entry points behind dedicated services/providers. - -日期:`2026-04-02` - -## 本轮完成 - -- `ViewportHostService` 已持有并缓存 scene viewport 的 canonical `SceneViewportOverlayFrameData` -- `SceneViewPanel` 不再自己重复构建 scene editor overlay frame data,而是统一从 host service 取 -- `scene icon` 已接入 `handleRecords` -- 新增 `SceneViewportOverlayHitTester.h`,scene icon 的 hover/click 已改为基于 canonical overlay handle 命中 -- `SceneViewportEditorOverlayData.h` 已扩展为通用 handle record 结构,支持优先级与多种命中形状 -- 新增 `SceneViewportOverlayHandleBuilder.h`,开始把 move/rotate/scale gizmo 的 draw data 转成 canonical handle records -- `SceneViewPanel.cpp` 已改为通过统一 `HitTestSceneViewportOverlayHandles(...)` 解析 transform gizmo 的 hover / click-begin -- `SceneViewportEditorOverlayPass` 已新增 screen-space triangle primitive 支持,可直接绘制 transform gizmo 的屏幕空间几何 -- `ViewportHostService` 已开始在 host 侧构建 transient transform gizmo overlay frame data,`SceneViewPanel.cpp` 只提交 overlay 与 gizmo handle build inputs -- transform gizmo 的 handle build inputs 组装 helper 已从 `SceneViewPanel.cpp` 挪到 `SceneViewportOverlayHandleBuilder.h` -- `transform gizmo` 的 selection/context/refresh/cancel helper 已从 `SceneViewPanel.cpp` 挪到 `SceneViewportTransformGizmoFrameBuilder.h` -- move / rotate / scale gizmo 已不再直接依赖 `DrawSceneViewportOverlay()` 的 ImGui world draw 分支出图 -- `SceneViewportOverlayRenderer.cpp` 中旧的 gizmo / scene icon / scene line ImGui 绘制逻辑已删除,当前只保留 HUD 类 overlay -- `SceneViewPanel.cpp` 中旧的 scene icon 临时命中逻辑已删除 -- `SceneViewPanel.cpp` 中旧的 gizmo 直接 hit test 入口已从 hover / click-begin 仲裁链上移除 -- `SceneViewPanel.cpp` 中已经失效的 camera/light world overlay 组装辅助函数已删除,避免 panel 继续承担 world overlay 构建职责 -- `SceneViewPanel.cpp` 中交互前命中与交互后绘制的 transform gizmo 刷新链路已收敛到同一套 helper,减少重复的 context/update/submit 逻辑 -- `GetSceneViewInteractionOverlayFrameData(...)` 已改为由 host 按传入的 transform gizmo inputs 现场组合交互 frame,`SceneViewPanel` 不再为命中阶段先写入 transient overlay 缓存 -- `ViewportHostService` 已改为缓存 transient transform gizmo 的原始 overlay + inputs,并在 render 阶段现场构建 frame data;panel 不再驱动这部分 frame data 的即时生成 - -## 当前状态 - -- `camera frustum` -- `directional light gizmo` -- `camera/light scene icon` - -上述内容的绘制数据已经在 overlay builder / overlay frame data 路径上。 - -`scene icon` 与 `transform gizmo` 的 hover / click-begin 已经开始走统一 handle 数据。 - -目前 `transform gizmo` 尚未完全收口的部分主要是: - -- move gizmo -- rotate gizmo -- scale gizmo - -它们的 draw data / drag solver 仍然各自保留,且 draw data 仍然由 `SceneViewPanel` 驱动生成。 - -另外,`SceneViewPanel` 目前仍要控制 transform gizmo 的最终绘制提交时机;这说明绘制与命中都已经开始收口,但 transform gizmo 还没有完全并入单帧单份的 canonical overlay 数据,也还没有把 draw-state 提交流程完全下沉到 host。 - -## 本轮验证 - -以下翻译单元已在 `Debug` 下单独编译通过: - -- `editor/src/panels/SceneViewPanel.cpp` -- `editor/src/Viewport/SceneViewportTransformGizmoFrameBuilder.h` -- `editor/src/Viewport/SceneViewportOverlayBuilder.cpp` -- `editor/src/Viewport/Passes/SceneViewportEditorOverlayPass.cpp` -- `editor/src/Application.cpp` - -完整 `XCEditor` 整体编译当前仍被与本轮任务无关的 `engine/src/Resources/Shader/ShaderLoader.cpp` 缺失阻塞,因此本轮没有做整包通过确认。 - -## 下一步建议 - -下一小步应继续沿着统一 handle 数据收口,但先不要动 gizmo drag solver: - -1. 继续把 transform gizmo 的最终绘制提交时机从 `SceneViewPanel` 往 host / canonical builder 收口,减少 panel 对 render-frame orchestration 的直接控制 -2. 在不改 drag solver 的前提下,把 transform gizmo 的最终 draw-state 提交流程也继续从 `SceneViewPanel` 往 host 下沉,让 panel 只消费统一命中结果和少量 gizmo state -3. 等 transform gizmo 的提交/组装职责继续下沉后,再收最后一层 drag begin 之后的求解入口 - -目标是先统一“点中了谁”,再统一“如何拖动”。 diff --git a/docs/used/SceneViewport_Overlay_Gizmo_Rework_Plan_完成归档_2026-04-04.md b/docs/used/SceneViewport_Overlay_Gizmo_Rework_Plan_完成归档_2026-04-04.md deleted file mode 100644 index 2c1124a0..00000000 --- a/docs/used/SceneViewport_Overlay_Gizmo_Rework_Plan_完成归档_2026-04-04.md +++ /dev/null @@ -1,631 +0,0 @@ -# Scene Viewport Overlay 与 Gizmo 正规化重构方案 - -日期:`2026-04-02` - -## 0. 当前进度 Checkpoint - -截至 `2026-04-02`,本方案已有以下落地结果: - -- `Phase 1` 已完成: - - `CameraRenderRequest` 已新增 `overlayPasses` - - `CameraRenderer` 已在 builtin postprocess 之后执行 `overlayPasses` - - `ViewportHostService` 已接入 editor overlay pass sequence -- `Phase 2` 已完成首批迁移: - - `camera frustum` - - `directional light gizmo` - - `camera/light scene icon` - - 上述内容已不再走 ImGui world draw,而是走 renderer overlay pass -- `scene icon` 的命中数据已开始收口: - - `SceneViewPanel` 不再自己扫描 scene 构建 icon draw data - - icon hit test 已改为消费 `SceneViewportOverlayBuilder::Build()` 产出的同类 frame data -- `transform gizmo` 的统一命中已开始接线: - - `SceneViewportEditorOverlayData.h` 已扩展为通用 `handleRecords` - - `SceneViewportOverlayHandleBuilder.h` 已可把 move/rotate/scale gizmo draw data 转为 canonical handle records - - `SceneViewPanel` 中 gizmo 的 hover / click-begin 已开始走统一 `HitTestSceneViewportOverlayHandles(...)` -- `transform gizmo` 的绘制迁移已开始接线: - - `SceneViewportEditorOverlayPass` 已支持 screen-space triangle primitive - - `ViewportHostService` 已可在 host 侧根据 `SceneViewPanel` 提交的 overlay 与 gizmo handle build inputs 构建 transient transform overlay frame data - - transform gizmo 的 handle build inputs 组装 helper 已开始从 `SceneViewPanel` 向 `SceneViewportOverlayHandleBuilder.h` 收口 - - transform gizmo 的 selection/context/refresh/cancel helper 已开始从 `SceneViewPanel` 向 `SceneViewportTransformGizmoFrameBuilder.h` 收口 - - move / rotate / scale gizmo 已不再直接依赖 `DrawSceneViewportOverlay()` 的 ImGui gizmo 绘制分支出图 - - `SceneViewportOverlayRenderer.cpp` 已收缩回 HUD/orientation 责任,不再承担 transform gizmo / scene icon / scene line 的 ImGui world draw - - `SceneViewPanel` 内部交互前命中与交互后绘制的 gizmo 刷新链路已开始复用同一套 helper,重复的 context/update/submit 逻辑已明显收缩 - - interaction overlay frame 已改为 host 按传入的 transform gizmo inputs 现场组合,`SceneViewPanel` 不再为 hit test 预先写入 transient overlay 缓存 - - render 阶段使用的 transient transform gizmo frame data 也已改为 host 基于缓存的原始 overlay + inputs 现场构建 - -当前仍未完成的关键点: - -- `transform gizmo` 的 drag solver / 变换求解仍然保留在各自 gizmo 类中 -- `SceneViewPanel` 里仍保留 transform gizmo 的 draw data 生成、交互仲裁与 transient overlay 提交逻辑 -- `SceneViewPanel` 仍直接控制 transform gizmo 的最终绘制 overlay 提交时机,host 尚未完全接管这一层 frame orchestration -- `ViewportHostService` 的 canonical overlay frame data 仍未直接承载 transform gizmo,尚未收敛到单帧单份 canonical overlay 数据 - -当前阶段结论: - -**方案方向已经验证正确,下一步不应该回头继续扩写 ImGui world overlay,而应该继续推进 canonical overlay data 与统一命中系统。** - -## 1. 方案结论 - -当前 `Scene Viewport` 的问题,不是某一个 `Directional Light Gizmo` 画丑了,而是整条 editor overlay 链路本身没有收口: - -- `grid` 已经是正规 renderer pass -- `transform gizmo / camera icon / light icon / camera frustum / directional light gizmo` 仍然是 ImGui overlay -- 输入命中和绘制几何不是同一份数据 -- `SceneViewPanel.cpp` 同时承担 panel UI、输入调度、世界转屏幕、overlay 构建、命中仲裁,职责已经失控 - -结论只有一个: - -**不能再继续往 `SceneViewPanel.cpp` 和 ImGui world overlay 上堆功能。必须把场景中的 editor 可视化正式收口成一套 renderer 级 overlay pass 和统一 handle 数据。** - ---- - -## 2. 当前链路梳理 - -### 2.1 正规链路:Grid - -当前 `grid` 的路径是正规的 renderer pass: - -`ViewportHostService -> SceneRenderer -> CameraRenderer -> BuiltinPostProcessPassSequenceBuilder -> BuiltinInfiniteGridPass` - -关键文件: - -- `editor/src/Viewport/ViewportHostService.h` -- `editor/src/Viewport/ViewportHostRenderFlowUtils.h` -- `engine/src/Rendering/CameraRenderer.cpp` -- `engine/src/Rendering/Passes/BuiltinPostProcessPassSequenceBuilder.cpp` -- `engine/src/Rendering/Passes/BuiltinInfiniteGridPass.cpp` - -这条链路的特征是: - -- 在 scene 几何渲染完成后,由 GPU pass 正式叠加 -- 有明确的 render request 输入 -- 有独立 pass 边界 -- 不依赖 ImGui draw list - -### 2.2 非正规链路:Editor World Overlay - -当前绝大多数 editor 可视化不是 renderer pass,而是: - -1. `ViewportHostService` 渲出 scene viewport 纹理 -2. `RenderViewportPanelContent()` 在 ImGui 面板中显示这张纹理 -3. `SceneViewPanel.cpp` 在这张纹理之上继续用 ImGui draw list 手搓 world overlay - -关键文件: - -- `editor/src/panels/ViewportPanelContent.h` -- `editor/src/panels/SceneViewPanel.cpp` -- `editor/src/Viewport/SceneViewportOverlayRenderer.cpp` - -当前放在这条链上的内容包括: - -- move gizmo -- rotate gizmo -- scale gizmo -- camera icon / light icon -- camera frustum -- directional light gizmo -- orientation gizmo - -其中 `orientation gizmo` 本质上是固定在右上角的 HUD,放在 ImGui 问题不大。真正失控的是那些锚定在世界空间中的 overlay。 - ---- - -## 3. 当前架构的核心问题 - -### 3.1 绘制职责放错层 - -`SceneViewPanel.cpp` 不应该知道: - -- camera frustum 怎么构造几何 -- directional light gizmo 怎么构造几何 -- icon 如何转屏幕矩形 -- 各种 gizmo 如何排序、如何遮挡 - -这些本质上都属于 viewport overlay 系统,不属于 panel UI。 - -### 3.2 命中和绘制分裂 - -当前很多交互是: - -- 一套代码负责画 -- 另一套代码负责 hover / click / drag - -这会导致: - -- 看见的和能点的不是同一个东西 -- 改样式时经常忘改命中 -- 优先级只能靠条件链硬拼 - -### 3.3 世界空间对象被当成 2D UI 处理 - -camera/light icon、frustum、light gizmo、transform gizmo 本质上都是世界空间 editor overlay。 - -但它们现在被塞进 ImGui draw list 后,就天然失去: - -- 正规的渲染顺序语义 -- 稳定的深度/遮挡策略 -- 统一的 primitive 渲染方式 -- GPU 级别的扩展能力 - -### 3.4 `SceneViewPanel.cpp` 已经过胖 - -当前它同时负责: - -- tools/top bar UI -- tool mode 切换 -- gizmo context 组装 -- gizmo hover/click 仲裁 -- icon hit test -- overlay 世界几何构建 -- scene picking -- scene camera 输入 - -这已经不再是“面板”,而是一个巨型调度器加半个渲染系统。 - -### 3.5 视觉风格无法稳定收敛 - -Directional Light 这次暴露得最明显: - -- 需求是“圆形底盘上的光线分布” -- 当前实现却是在 panel 里临时拼几根线 - -这不是调几个参数能根治的问题,而是底层 primitive 表达和系统边界不对。 - ---- - -## 4. 重构目标 - -这次重构的目标不是“顺手把几个 gizmo 再修漂亮一点”,而是把 Scene Viewport overlay 彻底正规化。 - -最终目标如下: - -### 4.1 分离两类 overlay - -#### A. HUD 类 overlay - -固定在面板坐标系的内容继续留在 ImGui: - -- 顶部工具栏 -- 左侧 tools 按钮 -- 右上角 orientation gizmo -- 状态提示文字 - -#### B. World Anchored Overlay - -锚定在世界空间里的 editor 可视化统一进入 renderer overlay pass: - -- move / rotate / scale gizmo -- camera / light scene icon -- camera frustum -- directional light gizmo -- 后续 collider bounds / helper shapes / volume gizmo - -### 4.2 统一绘制数据与命中数据 - -所有可交互 gizmo handle 必须来自同一份 canonical data: - -- 画什么 -- 颜色是什么 -- 层级优先级是什么 -- handle id 是什么 -- 哪里可以点 - -都不能再分散在不同类里各算一套。 - -### 4.3 建立 renderer 级 editor overlay pass - -目标顺序应为: - -`Scene Geometry -> ObjectId -> Builtin Post Process(Grid/Outline) -> Editor Overlay Pass -> ImGui HUD` - -也就是说,editor 世界 overlay 必须成为正式 render stage,而不是纹理上的二次手绘。 - -### 4.4 让 gizmo 类回归“控制器/求解器”角色 - -`Move / Rotate / Scale Gizmo` 类应主要负责: - -- drag 状态机 -- 轴向约束 -- plane 约束 -- 变换求解 -- 交互反馈求解 - -不再继续兼任: - -- 实际几何绘制器 -- 实际命中主仲裁器 - ---- - -## 5. 推荐的目标架构 - -## 5.1 Render Request 层新增 `overlayPasses` - -当前 `CameraRenderRequest` 里有: - -- `preScenePasses` -- `postScenePasses` -- `builtinPostProcess` - -但没有真正适合 editor world overlay 的最后一层。 - -建议新增: - -- `overlayPasses` - -执行顺序调整为: - -1. `preScenePasses` -2. scene geometry -3. object id -4. `postScenePasses` -5. `builtinPostProcess` -6. `overlayPasses` - -这样 world overlay 才能稳定压在 grid 和 outline 之上,再由 ImGui 负责最后的 HUD。 - -### 5.2 新建 `SceneViewportOverlayFrameData` - -建议新增一个独立的 frame data 结构,承载这一帧 Scene Viewport 的 editor overlay 数据。 - -建议字段: - -- `linePrimitives` -- `trianglePrimitives` -- `billboardSprites` -- `handleRecords` -- `renderLayer` -- `depthMode` -- `screenSpaceThickness` - -其中: - -- primitive 用于绘制 -- handle record 用于命中 -- 两者共享相同的 `handleId` - -### 5.3 新建 `SceneViewportOverlayBuilder` - -职责: - -- 接收 scene overlay context -- 收集 selected objects -- 构建 camera frustum -- 构建 directional light 圆形底盘 gizmo -- 构建 scene icons -- 构建 transform gizmo handles -- 输出统一的 `SceneViewportOverlayFrameData` - -建议位置: - -- `editor/src/Viewport/SceneViewportOverlayBuilder.h` -- `editor/src/Viewport/SceneViewportOverlayBuilder.cpp` - -### 5.4 新建 `SceneViewportOverlayHitTester` - -职责: - -- 基于 `SceneViewportOverlayFrameData::handleRecords` 做统一命中 -- 输出唯一的 hovered handle -- 同一份数据同时服务 hover / click / drag begin - -建议位置: - -- `editor/src/Viewport/SceneViewportOverlayHitTester.h` -- `editor/src/Viewport/SceneViewportOverlayHitTester.cpp` - -### 5.5 新建 `SceneViewportEditorOverlayPass` - -职责: - -- 读取 `SceneViewportOverlayFrameData` -- 用 GPU 绘制 line / fill / billboard -- 负责世界空间 editor overlay 的正式渲染 - -建议位置: - -- `editor/src/Viewport/Passes/SceneViewportEditorOverlayPass.h` -- `editor/src/Viewport/Passes/SceneViewportEditorOverlayPass.cpp` - -### 5.6 `SceneViewPanel` 的目标职责 - -重构后 `SceneViewPanel` 只负责: - -- 顶部栏和 tools UI -- tool mode / pivot / local-global 状态 -- 输入汇总 -- 与 viewport host service 交互 -- 显示固定 HUD - -不再负责: - -- world overlay 几何构建 -- icon 转屏幕 -- frustum / light gizmo 线框拼装 -- world overlay 主渲染 - ---- - -## 6. 模块职责重新划分 - -### 6.1 `SceneViewPanel` - -保留: - -- 面板 UI -- 工具模式切换 -- 快捷键 -- 鼠标/键盘输入汇总 -- orientation gizmo - -移出: - -- world overlay primitive 构建 -- scene icon world/screen 几何生成 -- camera/light 线框绘制逻辑 - -### 6.2 `ViewportHostService` - -新增职责: - -- 组装 `SceneViewportOverlayFrameData` -- 把 overlay pass 接到 render request - -保留职责: - -- scene view camera -- viewport render target 管理 -- scene render request 提交 -- object id picking - -### 6.3 Gizmo 求解器 - -`SceneViewportMoveGizmo / RotateGizmo / ScaleGizmo` 保留: - -- 拖拽求解 -- 激活态 -- 交互反馈数据 - -逐步移除: - -- 直接操作 ImGui draw data 的职责 -- 各自内部封闭的 hit test 决策权 - -### 6.4 Overlay Pass - -只负责 GPU 绘制,不处理业务判断。 - -输入必须已经是“可直接画”的 primitive 数据,避免 pass 内部再知道 camera/light/gizmo 业务语义。 - ---- - -## 7. 推荐执行阶段 - -这次重构不能一次性大爆炸改完,但必须每一步都朝最终架构收敛,不能做过渡性屎层。 - -### Phase 0:冻结错误扩展方向 - -目标: - -- 停止继续向 `SceneViewPanel.cpp` 添加新的 world overlay 绘制逻辑 -- 停止继续扩写 `SceneViewportOverlayRenderer.cpp` 为 ImGui world renderer - -产出: - -- 文档确认 -- 后续新增 world gizmo 一律走 overlay builder / pass 方向 - -### Phase 1:打通 `overlayPasses` 通道 - -目标: - -- 给 `CameraRenderRequest` 新增 `overlayPasses` -- 在 `CameraRenderer` 中调整执行顺序 -- 在 `ViewportHostService` 中接入 editor overlay pass sequence - -产出: - -- renderer 支持 scene 之后再画 editor overlay -- 这一步允许先画空 pass,不做功能迁移 - -验收: - -- 不影响现有 scene / grid / selection outline -- `overlayPasses` 具备独立初始化、执行、释放边界 - -### Phase 2:迁移纯显示型 world overlay - -优先迁移: - -- camera frustum -- camera/light scene icon -- directional light gizmo - -原因: - -- 这些内容交互复杂度低 -- 最容易先把 `SceneViewPanel.cpp` 中世界几何拼装代码减掉 - -Directional Light 本阶段的目标形态: - -- 圆形底盘 -- 光线分布在圆环或圆盘采样点上 -- 明确的世界朝向 -- 稳定屏幕线宽 - -验收: - -- 这些 overlay 不再由 ImGui draw list 直接绘制 -- `SceneViewPanel.cpp` 不再负责生成对应线框 - -### Phase 3:迁移 Transform Gizmo 的绘制 - -目标: - -- move / rotate / scale gizmo 的显示改为 overlay pass primitive -- gizmo 类转为 handle builder + drag solver - -当前进度: - -- transform gizmo 已开始转成 transient overlay frame data -- overlay pass 已可消费一批 screen-space triangle primitive 来绘制 gizmo 屏幕几何 -- 但 transient gizmo overlay 仍然由 `SceneViewPanel` 在帧末提交,尚未完全收口到 host / builder 的 canonical 路径 - -说明: - -- 这一阶段只迁移“怎么画” -- 可以暂时保留现有 drag 求解逻辑 - -验收: - -- transform gizmo 已不依赖 ImGui world draw -- gizmo 视觉反馈仍然可用 - -### Phase 4:统一命中系统 - -目标: - -- 所有 world overlay 的 hover / click / drag begin 统一使用 `handleRecords` -- 彻底删除 panel 里的多套 hit test 拼接逻辑 - -当前进度: - -- `scene icon` 已完成 -- `transform gizmo` 的 hover / click-begin 已经开始接入统一 `handleRecords` -- `drag begin` 之后的求解与 active 状态仍然保留在 gizmo 类与 `SceneViewPanel` 现有链路中 - -涵盖对象: - -- transform gizmo -- scene icon -- 后续 camera/light/world helper handles - -验收: - -- 命中结果只由一份 canonical handle 数据决定 -- 不再依赖大量互相屏蔽的布尔条件 - -### Phase 5:删除旧世界 overlay 路径 - -目标: - -- 删除 `SceneViewPanel.cpp` 中遗留的 world overlay 构建逻辑 -- 缩减 `SceneViewportOverlayRenderer.cpp` 到只保留 HUD 类渲染或直接拆分 - -最终保留: - -- ImGui HUD -- renderer world overlay - -最终移除: - -- ImGui world overlay - ---- - -## 8. 第一阶段建议改动范围 - -如果按最小风险起步,第一轮只做下面这些: - -- `engine/include/XCEngine/Rendering/CameraRenderRequest.h` -- `engine/src/Rendering/CameraRenderer.cpp` -- `editor/src/Viewport/ViewportHostService.h` -- `editor/src/Viewport/ViewportHostRenderFlowUtils.h` -- 新增 `editor/src/Viewport/Passes/SceneViewportEditorOverlayPass.*` - -第一轮不碰: - -- move/rotate/scale 的求解逻辑 -- Scene icon 的交互优先级逻辑 -- SceneViewPanel 的大面积交互重写 - -这样可以先把 renderer 通道立住,再分批迁移业务内容。 - ---- - -## 9. 方案边界 - -这次重构明确不做的内容: - -- 不顺手做 local mode 新行为 -- 不顺手做 runtime 通用 debug draw 系统 -- 不顺手把所有 editor widget 一次性改成 pass -- 不在本轮引入 render graph - -这次只做一件事: - -**把 Scene Viewport 中锚定世界空间的 editor overlay,从 ImGui 手搓模式收口为正式 renderer overlay 系统。** - ---- - -## 10. 风险与控制 - -### 10.1 风险:一次性迁移过大 - -控制方式: - -- 先打通 pass 通道 -- 再迁静态 overlay -- 最后迁 transform gizmo 与命中 - -### 10.2 风险:命中系统重写影响交互稳定性 - -控制方式: - -- handle record 和旧逻辑短期并存 -- 先让新系统服务 hover -- 再切 click / drag begin - -### 10.3 风险:D3D12-only pass 与后端扩展 - -控制方式: - -- 第一阶段允许 editor overlay pass 先只支持 D3D12 -- 但数据结构和 pass 边界必须 backend-neutral - -### 10.4 风险:继续出现“画的是一个,点的是另一个” - -控制方式: - -- 明确规定 overlay primitive 和 hit proxy 必须同源 -- 不能再允许绘制与命中分别各算一套几何 - ---- - -## 11. 最终验收标准 - -当以下条件全部满足时,说明收口完成: - -- `SceneViewPanel.cpp` 不再承担 world overlay 几何构建职责 -- world anchored editor overlay 全部进入 renderer pass -- transform gizmo / scene icon / camera frustum / light gizmo 使用统一 overlay frame data -- hover / click / drag begin 使用统一 handle record -- ImGui 只负责 HUD,不再负责世界空间 gizmo 主绘制 -- 新增一种 world gizmo 时,不需要再把世界转屏幕逻辑写回 panel - ---- - -## 12. 对本次 Directional Light Gizmo 的直接指导 - -在该重构方案下,Directional Light Gizmo 的正确实现方式应是: - -- 它属于 `World Anchored Overlay` -- 它的几何由 `SceneViewportOverlayBuilder` 负责生成 -- 它的线段/圆环由 `SceneViewportEditorOverlayPass` 负责绘制 -- 它的样式使用“圆形底盘 + 圆周分布光线”,不再在 panel 中临时拼矩形列线 - -也就是说,Directional Light Gizmo 不应该被当成一个局部修补任务继续留在 `SceneViewPanel.cpp`。 - ---- - -## 13. 本文后的执行原则 - -在本方案审核通过之前: - -- 允许修 bug -- 不建议继续扩写新的 ImGui world overlay 逻辑 - -在本方案审核通过之后: - -- 新增 world overlay 功能,优先接入 overlay builder / overlay pass -- `SceneViewPanel.cpp` 只减不增 diff --git a/docs/used/Scene选中描边彻底修复计划_2026-04-08.md b/docs/used/Scene选中描边彻底修复计划_2026-04-08.md deleted file mode 100644 index 6d28102c..00000000 --- a/docs/used/Scene选中描边彻底修复计划_2026-04-08.md +++ /dev/null @@ -1,291 +0,0 @@ -# Scene 选中描边彻底修复计划 - -日期:2026-04-08 - -## 1. 文档定位 - -本文档只解决一个问题: - -- `Scene` 面板里,选中对象的描边效果需要达到稳定、可扩展、接近商业编辑器的质量。 - -本文档不讨论: - -- 层级面板右键菜单 -- 灯光 gizmo 图标 -- 运行时高亮 -- 游戏内选中反馈 - -## 2. 当前问题结论 - -当前实现不是“可见外轮廓提取”,而是“基于 object-id 的屏幕空间膨胀”。 - -现状链路如下: - -1. 主场景正常渲染。 -2. `BuiltinObjectIdPass` 把屏幕上最前面的物体 id 写入 `objectIdTexture`。 -3. `BuiltinObjectIdOutlinePass` 在全屏 shader 中检查当前像素周围是否存在“选中对象的 object-id”。 -4. 只要邻域中存在选中 object-id,当前像素就会被涂成 outline。 - -这个算法的根本缺陷是: - -- 它只知道“邻域里有没有选中对象”。 -- 它不知道“当前像素前面是不是别的物体挡住了选中对象”。 -- 它也不知道“这条边是可见外轮廓,还是选中大平面与前景遮挡物之间的内部接触边界”。 - -所以当选中一个大平面时,平面上方的 cube、sphere、capsule 这些前景物体边缘也会被错误描边。 - -这不是数据污染,也不是拾取错误,而是算法层面的必然结果。 - -## 3. 根因 - -根因可以归纳为两条: - -### 3.1 描边输入选错了 - -当前描边 pass 直接消费 `objectIdTexture`。 - -但 `objectIdTexture` 的职责本质上是: - -- 告诉编辑器“当前屏幕像素最前面的对象是谁”,用于 picking。 - -它不适合作为“选中描边”的唯一依据,因为它不包含: - -- 选中对象的可见 mask -- 选中对象自己的深度 -- 当前场景深度与选中对象深度之间的遮挡关系 - -### 3.2 描边算法缺少深度裁决 - -当前 outline shader 的判断规则是: - -- 当前像素不是选中对象 -- 但周围像素里存在选中对象 - -满足这两个条件就画 outline。 - -这套规则没有使用场景深度,也没有使用选中对象的可见性信息,因此无法区分: - -- 真正应该画的外轮廓 -- 不应该画的前景遮挡边界 - -## 4. 修复目标 - -本次彻底修复后的目标是: - -1. 选中大平面时,前景物体边缘不再被错误染成 outline。 -2. 选中普通 mesh 时,outline 仍然能稳定出现在可见外轮廓处。 -3. 被遮挡区域不画“伪外轮廓”。 -4. picking 与 selection outline 两套能力彻底解耦。 -5. 后续如果要做 Unity 风格的 x-ray 选中、被遮挡高亮、hover outline,可以在当前结构上继续扩展,而不是推倒重来。 - -## 5. 非目标 - -这次修复不做以下内容: - -1. 不做运行时游戏内高亮系统。 -2. 不做完整的“透视遮挡时仍显示淡色轮廓”的 x-ray 选中风格。 -3. 不借这次机会重写整个 editor render pipeline。 -4. 不继续在现有 `object-id-outline.shader` 上做只针对单个 case 的临时补丁。 - -## 6. 总体方案 - -正确方案是把“拾取”和“描边”拆开,改成: - -- `object id for picking` -- `visible selection mask for outline` -- `depth-aware outline composite` - -### 6.1 picking 继续走 object-id - -现有 `objectIdTexture` 继续只用于: - -- 场景点击选中 -- object-id readback - -它不再直接决定 outline 的视觉结果。 - -### 6.2 新增 selection mask pass - -新增一张仅服务于 outline 的 `selectionMaskTexture`。 - -它的规则是: - -1. 只渲染当前选中的对象。 -2. 渲染时绑定主场景已有的深度缓冲。 -3. 依赖深度测试,只把“真正可见的 selected fragment”写进 `selectionMaskTexture`。 - -这样得到的不是 picking id 图,而是“选中对象可见区域 mask”。 - -### 6.3 scene viewport 暴露深度 SRV - -为了做真正的 depth-aware composite,`Scene` viewport 需要同时持有: - -1. `depthView` -2. `depthShaderView` - -也就是说,Scene viewport 的深度纹理既要能作为 DSV 使用,也要能在全屏 composite shader 中作为 SRV 读取。 - -底层 RHI 已经具备这条能力,阴影缓存已有相同用法,因此这不是架构冒险,而是 viewport 资源层尚未接线。 - -### 6.4 重写 outline composite pass - -新的 outline shader 不再直接从 `objectIdTexture` 做邻域膨胀,而是读取: - -1. `selectionMaskTexture` -2. `sceneDepthTexture` - -必要时预留: - -3. `selectionDepthTexture` - -新的裁决规则应为: - -1. 中心像素若属于 selected mask,本像素不直接着色。 -2. 若邻域存在 selected mask,则该像素可能位于 selected 的边界附近。 -3. 只有在深度关系证明“当前像素不是更近的前景遮挡物”时,才允许输出 outline。 - -这一步的本质是: - -- 只画可见 selected 外轮廓。 -- 不把前景遮挡物边界误判为 selected outline。 - -## 7. 具体实施步骤 - -### Phase 1:补齐 viewport 资源 - -目标: - -- Scene viewport 持有可采样深度。 -- Scene viewport 持有单独的 selection mask render target。 - -需要修改: - -1. `editor/src/Viewport/ViewportHostRenderTargets.h` -2. 相关 viewport resource reuse / destroy / create 流程 - -新增资源建议: - -1. `depthShaderView` -2. `selectionMaskTexture` -3. `selectionMaskView` -4. `selectionMaskShaderView` -5. `selectionMaskState` - -验收标准: - -- Scene viewport 创建/销毁/复用逻辑能完整覆盖新资源。 -- 编译通过。 -- 不影响现有 Scene 视图展示。 - -### Phase 2:新增 selection mask pass - -目标: - -- 只把当前选中对象的可见区域写入 mask。 - -实现建议: - -1. 新建 editor 专用 `SelectionMaskPass`。 -2. 输入为选中对象 id 列表。 -3. 输出到 `selectionMaskTexture`。 -4. 渲染时绑定主场景同一套深度。 - -实现关键点: - -1. 这个 pass 的语义不是 picking。 -2. 它只关心“选中对象哪些像素当前可见”。 -3. 它不需要写 object id,只需要写统一 mask 值或选中组 id。 - -验收标准: - -- 选中对象时可以输出稳定的可见 mask。 -- 未选中时 pass 可被跳过。 - -### Phase 3:重写 outline composite - -目标: - -- outline 只出现在选中对象的可见外轮廓。 - -实现建议: - -1. 保留全屏 pass 形式。 -2. 输入从 `objectIdTexture` 改为 `selectionMaskTexture + depthShaderView`。 -3. 邻域检查继续保留,但结果必须经过深度裁决。 - -最低正确规则: - -1. 中心像素若是别的前景几何,且深度显著近于 selected 边界,不画 outline。 -2. 中心像素若是背景或更远表面,可以画 outline。 - -验收标准: - -1. 选中大平面时,前景 cube/sphere/capsule 边缘不再被错误描边。 -2. 选中单个 mesh 时,外轮廓仍连续稳定。 - -### Phase 4:移除旧耦合 - -目标: - -- 彻底消除 “outline 直接依赖 object-id picking 纹理” 这一旧逻辑。 - -需要完成: - -1. 旧 `object-id-outline.shader` 逻辑下线或重命名。 -2. `SceneViewportSelectionOutlinePass` 的输入契约改为新资源。 -3. 保证 `objectIdTexture` 仅服务于 picking。 - -验收标准: - -- 代码层不再存在“outline 直接读 object id 邻域并膨胀”的核心路径。 - -## 8. 测试计划 - -至少补以下回归验证: - -### 8.1 功能场景 - -1. 选中大平面,前面摆放 cube / sphere / capsule: - - 前景物体边缘不能被错误描边。 -2. 选中单个 cube,背景是地面: - - outline 能稳定显示在 cube 可见外轮廓上。 -3. 选中被部分遮挡的物体: - - 只允许可见区域出现 outline。 -4. 多选相邻物体: - - outline 不串边,不污染第三方前景物体。 - -### 8.2 回归类型 - -1. shader 资源加载测试 -2. render plan 接线测试 -3. pass 构建与资源存在性测试 -4. 视口级人工验证截图 - -## 9. 风险与边界 - -### 9.1 风险 - -1. 仅使用 `sceneDepth + selectionMask` 可能仍有少数边界 case 需要 `selectionDepth` 才能更稳。 -2. Scene viewport 新增 render target 后,资源管理和状态切换复杂度会上升。 -3. 若后续要兼容 Vulkan / OpenGL,需要同步确认深度 SRV 路径与资源状态语义。 - -### 9.2 边界 - -本计划的第一目标不是“做最炫的 outline”,而是: - -- 先把语义做对 -- 再把视觉做稳 - -只要还在复用 picking 的 `objectIdTexture` 直接做 outline,问题就不可能彻底解决。 - -## 10. 最终结论 - -这次 bug 不能靠继续修补当前 `object-id-outline.shader` 解决。 - -彻底方案必须满足三点: - -1. picking 与 outline 解耦 -2. 选中对象生成独立可见 mask -3. outline composite 引入深度裁决 - -只有这样,Scene 视图选中描边才会从“屏幕空间膨胀特效”升级为“真正可用的编辑器轮廓系统”。 diff --git a/docs/used/Shader与Material下一阶段_ShaderLab正式化与BuiltinShader资产布局计划_完成归档_2026-04-07.md b/docs/used/Shader与Material下一阶段_ShaderLab正式化与BuiltinShader资产布局计划_完成归档_2026-04-07.md deleted file mode 100644 index 878965d6..00000000 --- a/docs/used/Shader与Material下一阶段_ShaderLab正式化与BuiltinShader资产布局计划_完成归档_2026-04-07.md +++ /dev/null @@ -1,247 +0,0 @@ -# Shader 与 Material 下一阶段:ShaderLab 正式化与 Builtin Shader 资产布局计划 -日期:`2026-04-07` - -## 1. 阶段结论 - -上一阶段已经完成了最关键的主线收口: - -- `.shader` 现在只表示 authoring shader,不再兼容 JSON manifest。 -- 旧的 manifest 主路径已经从运行时主流程移除。 -- shader / material / editor 相关测试已经回归通过。 -- 当前 shader 主线终于从“过渡态”进入了“可以继续正规化”的状态。 - -这意味着接下来不应该再围绕“兼容旧双路径”打补丁,而应该开始正式推进: - -- `ShaderLab authoring` -- `Material contract` -- `builtin shader asset layout` -- `runtime pass contract` - -一起向 Unity 风格继续收口。 - ---- - -## 2. 当前真实状态 - -当前已经成立的事实: - -1. `.shader` 主语义已经统一。 -2. authoring parser 已经能承载基础的 `Shader / Properties / SubShader / Pass / Tags / HLSLPROGRAM / pragma / 基础状态`。 -3. runtime 已经有 `Shader -> Pass -> Variant` 数据模型。 -4. builtin shader 已经基本都迁入 authoring 主路径。 - -但当前还没有完全完成的部分也很明确: - -1. 语法还只是 Unity ShaderLab 的一个子集。 -2. builtin shader 资产目录结构还带有明显的过渡痕迹。 -3. material 与 shader 的契约虽然比之前干净,但还没完全达到 Unity 式心智模型。 -4. renderer 对 pass contract 的消费还可以继续正规化。 - ---- - -## 3. Builtin Shader 资产目录结论 - -这里先把结论写死: - -- `engine/assets/builtin/shaders` 下的**主入口 shader 文件**,不需要再一 shader 一子文件夹。 -- 既然当前主入口已经都是单个 `.shader` 文件,那么更合理的结构应该是: - -```text -engine/assets/builtin/shaders/ - forward-lit.shader - unlit.shader - depth-only.shader - shadow-caster.shader - object-id.shader - object-id-outline.shader - skybox.shader - color-scale-post-process.shader - final-color.shader -``` - -- 需要保留子目录的,不是 builtin shader 入口文件本身,而是**公用 include / shader library**。 -- 因此公共代码应该独立成类似: - -```text -engine/assets/shaderlib/ - Core.hlsl - Common.hlsl - Lighting.hlsl - SpaceTransforms.hlsl - Shadow.hlsl - MaterialInput.hlsl -``` - -也就是说: - -- `builtin/shaders/`:放“逻辑 shader 资产入口” -- `shaderlib/`:放“共享 include 库” - -而不是继续维持“每个 shader 一个文件夹,文件名还重复一遍”的结构。 - -这更符合现在的真实资产形态,也更利于后续继续扩展 builtin shader 数量。 - ---- - -## 4. 下一阶段目标 - -下一阶段的目标不是做更多渲染效果,而是把 shader/material 底座继续正规化,达到可以稳定承接后续 SRP 的程度。 - -这一阶段的目标分成四件事: - -1. 收口 builtin shader 资产布局。 -2. 扩展 ShaderLab authoring 子集,继续向 Unity 靠拢。 -3. 继续压缩 material 中残留的临时职责。 -4. 让 renderer 对 pass / keyword / variant 的消费边界更正式。 - ---- - -## 5. 分阶段执行 - -### Phase A:Builtin Shader 资产布局拍平 - -目标: - -- 把 `engine/assets/builtin/shaders/*/* .shader` 收口成单层 `.shader` 文件布局。 -- 同步清理 `BuiltinResources` 中的路径映射和命名。 - -工作项: - -1. 拍平 builtin shader 文件路径。 -2. 更新 `BuiltinResources.cpp/.h` 中的 builtin shader relative path 表。 -3. 回归所有 builtin shader loader 测试。 -4. 回归 editor / renderer 中依赖 builtin shader path 的测试。 - -完成标准: - -- `engine/assets/builtin/shaders/` 单层化。 -- 所有 builtin shader 路径 helper 仍稳定工作。 - -### Phase B:扩展 ShaderLab authoring 子集 - -目标: - -- 把当前 authoring 语法从“基础可用”推进到“真正像 Unity ShaderLab 的工程可用子集”。 - -优先支持: - -- `BlendOp` -- `Offset` -- `Stencil` -- `UsePass` -- 更完整的 `Tags` -- 更稳的 `SubShader` 级状态继承与覆盖规则 - -工作项: - -1. 扩 parser / IR。 -2. 为新增语法补单测。 -3. 为 builtin shader 和后续材质/渲染用例补回归用例。 - -完成标准: - -- 这些语法不再停留在计划里,而是进入实际 parser / runtime contract。 - -### Phase C:Material 契约继续收口 - -目标: - -- 让 material 更接近 Unity 的角色定位:只管理 shader、properties、textures、keywords、必要的 render state override。 - -工作项: - -1. 继续压缩 legacy `shaderPass` 的主路径影响范围。 -2. 梳理 property 到 constant buffer 的稳定映射。 -3. 梳理 keyword 集与 variant lookup 的契约边界。 -4. 审查 material loader 中是否还存在对旧 shader 体系的隐式兼容假设。 - -完成标准: - -- material 不再承担本应属于 renderer 的 pass 选择职责。 - -### Phase D:Renderer 消费链继续正规化 - -目标: - -- 让 renderer 更正式地围绕 `LightMode / pass contract / keywords / backend` 选用 pass 与 variant。 - -工作项: - -1. 审查 builtin pipeline 中的 pass 选择逻辑。 -2. 审查 keyword 参与 variant 选择与 pipeline cache 的路径。 -3. 清理仍然依赖旧约定字符串兜底的代码。 - -完成标准: - -- shader/material/runtime 三者的职责边界更加稳定,不再互相越界兜底。 - ---- - -## 6. 测试要求 - -这一阶段必须保持“每做一层就立刻回归”,不能再先堆实现后补测试。 - -至少要持续回归: - -- `shader_tests` -- `material_tests` -- `editor_tests` 中 shader path / viewport render flow / overlay 相关用例 -- `XCEditor` 编译 -- 受影响的 rendering 单测与关键集成测试 - -如果 builtin shader 资产布局拍平,必须额外回归: - -- builtin shader loader -- object-id / outline / grid / final-color / skybox 相关路径与运行时消费 - ---- - -## 7. 风险与控制 - -### 风险 1:目录结构重构把路径引用打坏 - -控制策略: - -- 先只拍平 builtin shader 主入口文件。 -- `shaderlib` 不混入 `builtin/shaders` 同层迁移,避免一次改太多。 -- 先跑 loader / editor path / renderer path 回归,再考虑下一层。 - -### 风险 2:语法扩展过快,把当前稳定 authoring 再次打回过渡态 - -控制策略: - -- 只扩“明确服务于 Unity 风格主线”的语法。 -- 不引入新的双路径兼容层。 -- 任何新语法都必须先建测试,再接运行时。 - -### 风险 3:material 与 renderer 职责再次混淆 - -控制策略: - -- 每做一项都用“这件事在 Unity 里是谁负责”来校验职责归属。 - ---- - -## 8. 收口判定 - -满足下面条件时,这一阶段可以认为完成: - -1. builtin shader 资产目录已经拍平到单层主入口结构。 -2. `BuiltinResources` 与相关路径 helper 已同步完成。 -3. ShaderLab authoring 子集新增一轮关键语法支持并有测试覆盖。 -4. material 对旧路径的临时兼容进一步缩小。 -5. renderer 的 pass / variant 消费逻辑进一步正规化。 -6. shader / material / editor / XCEditor 回归全部稳定。 - ---- - -## 9. 下一步执行顺序 - -建议的实际执行顺序如下: - -1. 先做 builtin shader 资产布局拍平。 -2. 立刻回归 shader/material/editor 路径相关测试。 -3. 再开始扩 ShaderLab 子集。 -4. 然后收 material 与 renderer 的契约边界。 - -也就是说,**下一步最应该先做的不是再加语法,而是先把 builtin shader 资产布局收干净。** diff --git a/docs/used/Shader与Material系统下一阶段计划_完成归档_2026-04-04.md b/docs/used/Shader与Material系统下一阶段计划_完成归档_2026-04-04.md deleted file mode 100644 index 1ec4dfe2..00000000 --- a/docs/used/Shader与Material系统下一阶段计划_完成归档_2026-04-04.md +++ /dev/null @@ -1,506 +0,0 @@ -# Shader 与 Material 系统下一阶段计划 - -日期:`2026-04-03` - -## 1. 阶段结论 - -当前 Renderer 这一轮主线已经完成收口,但完成的是“基础运行时闭环”,不是“最终的 Unity 风格 shader/material 体系”。 - -已经完成并应视为当前基线的内容: - -- `Shader` 运行时契约已具备 `properties / passes / resources / backend variants` -- builtin shader 资源已经外置,运行时不再依赖 C++ 内嵌 fallback -- `Material` 对 builtin forward 的基础属性解析已经优先走 shader semantic -- `BuiltinForwardPipeline` 已按 shader pass `resources` 合约生成 pipeline layout 与 descriptor binding -- `Shader` 已接入 `AssetDatabase / Library` 流程,shader import 会生成 `main.xcshader` -- `ShaderLoader` 已支持加载 `.xcshader` artifact -- shader manifest 依赖与 `Material -> Shader` 依赖已进入资产追踪链 -- `rendering_unit_tests` -- `rendering_integration_textured_quad_scene` -- `rendering_integration_backpack_lit_scene` -- `shader_tests` -- `material_tests` - -三后端验证当前是稳定的: - -- D3D12:通过 -- OpenGL:通过 -- Vulkan:通过 - -但这仍然只是“Shader / Material Runtime 的第一层骨架”。 - -当前真正还没有完成的是: - -- Unity 风格的 shader authoring 入口 -- 正式的 material schema / instance contract -- 从 shader property layout 到 GPU material layout 的通用映射 -- 从 builtin forward 扩展到更通用 pass 的正式执行模型 - -## 2. 旧计划归档说明 - -以下两份计划文档已经完成历史使命,应归入 `docs/used/`: - -- `Renderer模块设计与实现.md` -- `Renderer下一阶段_ShaderMaterial与Pass体系设计.md` - -原因不是它们“写错了”,而是: - -- 第一份解决的是 Renderer 模块从无到有的问题,当前骨架已经落地 -- 第二份解决的是“为什么下一阶段先做 shader/material/pass contract,而不是 render graph”的阶段判断;这份判断已经部分兑现,整体已过期 - -本文件接手它们之后的主线,只保留对当前 checkout 仍然有效的目标。 - -## 3. 当前真实问题 - -### 3.1 Shader 运行时有了,但 authoring 还没有正式化 - -现在的运行时已经能消费: - -- shader property -- pass tag -- pass resource binding -- backend variant - -但作者侧仍然不是最终形态。 - -当前还缺: - -- 面向用户的 Unity 风格 `.shader` 语法入口 -- import 阶段把 authoring 语法转换为 runtime contract 的正式流程 -- shader 资产与 Library artifact 的稳定产物边界 - -### 3.2 Material 仍偏“资源容器”,还不是正式材质实例系统 - -当前 `Material` 已有: - -- shader 引用 -- render state -- property / texture 覆盖 -- tag / queue - -但它还缺少真正用于 Renderer 执行的正式约束: - -- 基于 shader property schema 的类型验证 -- property 默认值与 override 的统一解析 -- per-pass material constant layout -- texture / sampler / buffer 到 pass resource 的正式映射 -- renderer 侧可缓存、可失效、可复用的 material binding plan - -### 3.3 现有 pass contract 仍偏 builtin-forward 视角 - -当前 forward 主链已经能跑,但完整的 pass contract 还没有正式化为可扩展系统。 - -后续至少要能稳定承接: - -- `ForwardLit` -- `Unlit` -- `DepthOnly` -- `ShadowCaster` -- `ObjectId` - -否则后面一旦开始做阴影、深度预通道、更多 editor/runtime helper pass,就会重新退化回 pipeline 内部的条件分支拼装。 - -### 3.4 三后端问题的本质不是“语法不同”,而是“资产如何统一” - -当前真正要解决的,不是简单回答“到底用 GLSL 还是 HLSL”,而是明确三层边界: - -1. 对外 authoring 语法是什么 -2. import 后的内部运行时资产是什么 -3. 每个 backend 最终执行的 variant 是什么 - -这三层不分开,后面一定会把 authoring、runtime、backend 编译链搅在一起。 - -## 4. 下一阶段的目标 - -下一阶段只做一件事:把 `Shader` 和 `Material` 从“能支撑当前 builtin forward 的运行时拼装”升级为“能长期承接 Unity 风格渲染架构的正式系统”。 - -### 4.1 对外 authoring 语法目标:严格向 Unity 对齐 - -最终对外公开的 shader 语法目标,必须与 Unity 的使用方式保持一致。 - -目标形态应当是: - -- 单个 `.shader` 文件作为逻辑 shader 入口 -- `Shader / Properties / SubShader / Pass` 的层级结构 -- pass 内通过 `HLSLPROGRAM ... ENDHLSL` 或等价块组织代码 -- 通过 `#pragma vertex` / `#pragma fragment` 指定 stage 入口 - -也就是说: - -- 对外 authoring 视角应当是“Unity 风格的一体化 shader 文件” -- 不是要求作者直接去维护一堆 runtime JSON manifest -- 也不是让上层逻辑直接感知 D3D12/OpenGL/Vulkan 各自的底层差异 - -### 4.2 对内 runtime 资产目标:继续保留 contract 模型 - -虽然 authoring 目标要严格向 Unity 靠拢,但 runtime 不应直接拿 authoring AST 当执行数据。 - -运行时仍应落到清晰的 contract: - -```text -Shader Asset - -> Properties - -> Passes - -> Tags - -> Resource Bindings - -> Backend Variants -``` - -原因很直接: - -- Renderer 需要稳定、扁平、可缓存的数据结构 -- 三后端最终执行的仍然是 backend variant -- material schema 与 pass binding 都需要基于 import 结果,而不是原始文本 - -结论是: - -- 外部写法要像 Unity -- 内部执行模型继续使用现在这套 runtime contract,并进一步完善 - -### 4.3 Material 目标:从资源对象升级为正式材质实例 - -Material 下一阶段的核心不是“多支持几个 SetFloat”,而是建立正式实例语义。 - -Material 至少要明确: - -- 引用哪个 shader -- 使用哪个 pass 或 pass 策略 -- 每个 property 的默认值、覆盖值、类型与序列化规则 -- 每个 pass 对应的 material constant block 如何布局 -- 每个 texture / sampler / buffer 如何映射到 pass resource - -Renderer 侧则必须能把这些内容稳定编译成: - -- material binding key -- material constant payload -- descriptor update plan -- pipeline compatibility key - -## 5. 推荐架构 - -### 5.1 分成三层,不混写 - -推荐明确拆成三层: - -```text -Unity-like Shader Authoring (.shader) - -> Shader Importer - -> Runtime Shader Contract - -> Backend Variants / Material Binding Plan -``` - -三层职责分别是: - -- authoring 层:给人写、给 editor 看 -- importer 层:把 authoring 语法转成稳定运行时资产 -- runtime 层:给 renderer 执行、缓存、绑定、测试 - -### 5.2 Shader 的 public contract 与 backend contract 分离 - -下一阶段不建议把“Unity 风格语法”直接等同于“单源码自动跨平台编译已完全成熟”。 - -更务实的路线是: - -- public contract 先统一成 Unity 风格 `.shader` -- importer 先产出统一 runtime contract -- backend variant 暂时仍允许按 backend 持有各自的编译输入或中间产物 - -这意味着: - -- 作者看到的是统一 shader -- Renderer 消费的是统一 runtime contract -- backend 最终执行的仍然可以是不同 variant - -这个分层既不违背 Unity 风格目标,也不会过早把工程拖进复杂的全平台 shader 编译链泥潭。 - -### 5.3 Vertex / Fragment 的外部写法按 Unity 组织,内部可拆分 - -对外语义上,vertex / fragment 应当属于同一个 pass。 - -也就是说,public authoring 角度要符合 Unity: - -- 一个 shader -- 一个或多个 subshader -- 一个或多个 pass -- 每个 pass 里通过 pragma 指定 vertex / fragment - -但内部 import/runtime 完全可以把它们拆成: - -- pass descriptor -- vertex stage variant -- fragment stage variant - -外部合一,内部拆开,这是最稳妥的做法。 - -## 6. 下一阶段实施顺序 - -### 阶段 D0:先打通 Shader Import / Artifact 基础设施(已完成) - -这是当前 checkout 在 `2026-04-03` 已经完成的新增里程碑。 - -完成内容: - -- `ShaderImporter` 已接管 `.shader / .hlsl / .glsl / .vert / .frag / .geom / .comp` -- shader import 结果会写入 `Library/Artifacts/.../main.xcshader` -- `ShaderLoader` 已支持直接读取 `.xcshader` -- shader manifest 中声明的 stage 源文件会进入依赖追踪 -- `Material` 对引用 shader 的直接依赖也已进入依赖追踪 -- `shader_tests` 与 `material_tests` 已覆盖 shader artifact 生成、加载与重导场景 - -这一步的意义不是“最终方案已完成”,而是先把 shader 纳入和 texture/material/mesh 一致的资产闭环。 - -没有这一步,后续不管做 Unity 风格 frontend,还是做 material schema,都会一直建立在“运行时临时解析源码”的不稳定基础上。 - -### 阶段 0:当前基线确认 - -这部分已经完成,不再作为待办: - -- runtime shader contract 已建立 -- builtin forward 已按 pass resources 驱动 -- 三后端渲染回归通过 - -### 阶段 A:建立 Unity 风格 Shader Authoring Frontend - -目标: - -- 新增 Unity 风格 `.shader` authoring 入口 -- importer 能解析最小闭环子集 - -第一批建议支持的子集: - -- `Shader` -- `Properties` -- `SubShader` -- `Tags` -- `Pass` -- `HLSLPROGRAM / ENDHLSL` -- `#pragma vertex` -- `#pragma fragment` - -交付标准: - -- builtin `forward-lit` -- builtin `unlit` -- builtin `object-id` -- builtin `infinite-grid` - -至少迁移其中一条主线并成功跑通三后端测试。 - -### 阶段 B:建立正式的 Material Schema 与 Instance Contract - -目标: - -- shader importer 输出可供 material 消费的 property schema -- material 对 property override 做类型校验与默认值回退 -- 为 pass 生成 material constant layout 与 resource mapping - -交付标准: - -- material 不再只靠 builtin alias 名字兜底 -- shader property semantic 变成正式主路径,而不是兼容性补丁 -- renderer 能从 shader schema 生成 material binding payload - -当前建议把这一阶段作为下一步主线。 - -原因: - -- shader artifact 与依赖追踪已经到位,shader 现在可以作为稳定 schema 来源 -- material 仍然缺少基于 shader property 的正式类型校验、默认值回退和资源映射 -- renderer 目前虽然能消费 pass resources,但 material binding 仍偏 builtin-forward 特判 - -当前进展(`2026-04-03`): - -- 已完成:shader schema 驱动的 property 类型校验与默认值回退 -- 已完成:source `.material` 的 `properties` authoring 入口 -- 已完成:material constant layout runtime contract - - `Material` 现在会生成正式的 constant layout 元数据 - - layout 字段包含 `name / type / offset / size / alignedSize` - - renderer 读取的已不再只是裸字节 payload,而是 `layout + payload` 组合 -- 已完成:builtin pass resource mapping / material binding plan runtime contract - - `RenderMaterialUtility` 现在统一提供 `PassResourceBindingLocation / BuiltinPassResourceBindingPlan` - - 显式 shader pass `resources` 与 legacy builtin forward fallback 已走同一套解析与校验路径 - - `BuiltinForwardPipeline` 已改为消费通用 binding plan,而不是继续内联 forward 特判绑定逻辑 -- 已验证:`rendering_unit_tests` 57/57,`material_tests` 51/51 -- 下一步:把这套 pass binding plan 继续推到 `Unlit / ObjectId` 等 pass,收敛成真正共享的 shader/material 执行边界 - -### 阶段 C:把 Pass Binding 扩展为正式材质执行链路 - -目标: - -- 不只是 builtin forward 能吃 pass resources -- `Unlit`、`ObjectId` 也逐步切到同一套 shader/material contract -- pipeline state key 与 descriptor binding plan 从“按功能写逻辑”升级到“按 shader pass contract 解析” - -交付标准: - -- 至少 `ForwardLit + Unlit + ObjectId` 共用同一套 shader/material 执行边界 -- 新增 pass 不再默认要求先写一套新的硬编码 binding 路径 - -当前进展(`2026-04-04`): - -- 已完成:builtin `ObjectId` pass 接入通用 pass binding plan - - builtin object-id shader 已显式声明 `PerObject` 资源合约 - - `BuiltinObjectIdPass` 已改为消费通用 binding plan,不再硬编码 `set0/binding0` 常量布局 - - 显式 shader `resources` 与 legacy object-id fallback 现在走同一套解析与校验路径 -- 已完成:builtin `Unlit` shader / pipeline 主线接入共享执行边界 - - 新增 builtin `unlit` shader 资产与 `BuiltinResources` 入口 - - `BuiltinForwardPipeline` 现在会在 `ForwardLit + Unlit` 之间按 material/shader metadata 解析目标 pass - - `Unlit` 与 `ForwardLit` 现在共用同一套 input layout、material schema、binding plan 与 descriptor 组装路径 -- 已完成:抽出 `ForwardLit / Unlit / ObjectId` 共用的 pass layout / descriptor set 组装骨架 - - `RenderMaterialUtility` 现在统一提供 `BuiltinPassSetLayoutMetadata` 与 `TryBuildBuiltinPassSetLayouts(...)` - - `BuiltinForwardPipeline` 与 `BuiltinObjectIdPass` 现在都复用同一套 `binding plan -> set layout -> pipeline layout` 组装路径 - - forward 侧只保留 set0 compatibility fallback 与 draw/update 逻辑;object-id 侧只保留自身约束校验与 per-object 常量写入 -- 已完成:builtin `DepthOnly / ShadowCaster` shader 与独立 pass skeleton 落地 - - 新增 builtin `depth-only` 与 `shadow-caster` shader 资产,以及 `BuiltinResources` 对应入口 - - 新增 `BuiltinDepthOnlyPass / BuiltinShadowCasterPass`,作为独立 `RenderPass` 复用同一套 shared pass-layout skeleton - - 两个 pass 当前先收敛到 `PerObject` 常量路径;opaque 物体走 builtin fallback,`ShadowCaster` 额外尊重 `MeshRenderer.castShadows` - - 这一步解决的是“pass contract 与执行骨架缺失”,还没有把 shadow map / light-space request flow 一次性做完 -- 已完成:`CameraRenderRequest + CameraRenderer` 正式接入 `DepthOnly / ShadowCaster` request 流程 - - 新增 `depthOnly / shadowCaster` request,支持独立 surface、clear flags / clear color,以及可选 `RenderCameraData` override - - `CameraRenderer` 现在按 `ShadowCaster -> DepthOnly -> Forward -> ObjectId` 的顺序执行这些请求,`BuiltinDepthStylePassBase` 也会按 request clear flags 清理目标 -- 已验证:`rendering_unit_tests` 73/73 -- 已验证:`rendering_integration_textured_quad_scene` 3/3(D3D12 / OpenGL / Vulkan) -- 已验证:`rendering_integration_unlit_scene` 3/3(D3D12 / OpenGL / Vulkan) -- 已验证:`rendering_integration_object_id_scene` 3/3(D3D12 / OpenGL / Vulkan) -- 下一步:如果继续沿 renderer 主线收口,优先补 `DepthOnly / ShadowCaster` 的真实 cross-backend integration coverage,并决定是否把 shadow-map 结果继续接回 forward lighting 消费链;如果先控制范围,request 流程这一块已经收口 - -### 阶段 D:扩展 AssetDatabase / Library Artifact 能力 - -目标: - -- 在已完成的 shader artifact 基础上,继续扩展 import 产物边界 -- backend variant 的编译输入、中间产物或缓存策略进入 `Library/Artifacts` -- 为后续 Unity 风格 `.shader` frontend 预留稳定 importer 输出层 - -交付标准: - -- 资源改动能够稳定触发 shader/material 重新导入 -- editor/runtime 读取的是 import 后资产,不是源码临时解析 -- shader importer 不再只服务当前 JSON manifest 兼容路径,也能承接下一步 authoring frontend - -### 阶段 E:测试与回归收口 - -目标: - -- 给 shader importer、material schema、binding plan 增加 unit tests -- 对 forward/unlit/object-id 增加 integration coverage -- 保持 D3D12 / OpenGL / Vulkan 一致回归 - -最低验证集: - -- `shader_tests` -- `material_tests` -- `rendering_unit_tests` -- `rendering_integration_textured_quad_scene` -- `rendering_integration_backpack_lit_scene` - -必要时新增: - -- `rendering_integration_unlit_scene` -- `rendering_integration_object_id_scene` - -当前进展(`2026-04-04`): - -- 已完成:`rendering_integration_unlit_scene` - - 显式使用 builtin `unlit` shader + `Unlit` pass,覆盖 `BuiltinForwardPipeline` 的 `ForwardLit / Unlit` pass 选择路径 - - 复用 `textured_quad_scene` 构图做最小差异回归,验证 shader/material 新 contract 不改变既有画面输出 -- 已验证:`rendering_integration_unlit_scene` - - D3D12:通过 - - OpenGL:通过 - - Vulkan:通过 -- 已完成:`rendering_integration_object_id_scene` - - 新增独立的 object-id integration scene,直接验证 object-id 输出采样点,而不是再维护一张新的大尺寸 GT 图片 - - 覆盖 `CameraRenderer + BuiltinObjectIdPass` 路径,以及 `Forward -> ObjectId -> Copy/Screenshot` 的跨 pass 回归 - - 修正了 `BuiltinObjectIdPass` 在 Vulkan 下的顶点输入步长问题,并让 object-id pass 自己清理/写入 depth,避免依赖前一 pass 的 depth 复用状态 -- 已验证:`rendering_integration_object_id_scene` - - D3D12:通过 - - OpenGL:通过 - - Vulkan:通过 -- 阶段 E 当前状态:`unlit_scene` 与 `object_id_scene` 均已完成并通过三后端验证,本阶段可以收口 - -## 7. 当前阶段明确不做 - -下一阶段不应把范围扩散到下面这些方向: - -- render graph -- shader graph -- 全平台单源码自动转译一次性做完 -- 完整 SRP scripting API -- 大规模后处理框架 - -这些都依赖 shader/material 正式体系先稳定下来。 - -## 8. 成功标准 - -这个阶段完成时,应该满足下面几个判断: - -- 作者侧已经可以写 Unity 风格的 `.shader` -- runtime 已不再依赖手写 JSON manifest 才能描述 pass contract -- material 能基于 shader schema 做正式绑定,而不是 builtin 特判兜底 -- 至少 `ForwardLit / Unlit / ObjectId` 三类 pass 共用统一 shader/material 执行边界 -- 三后端回归测试仍稳定通过 - -## 9. 一句话总结 - -下一阶段不是继续给 builtin forward 打补丁,而是把 `Shader` 和 `Material` 正式提升为 Unity 风格渲染架构中的稳定中层资产与执行契约。 - -## 10. 快速收口策略(`2026-04-04`) - -目标收窄为只处理 `shader / material` 核心主线,不继续扩散到完整阴影功能、render graph、shader graph 或 editor 外围能力。 - -按下面顺序收口: - -1. 先完成 Unity-like `.shader` authoring MVP importer - - 允许最小子集:`Shader / Properties / SubShader / Tags / Pass / HLSLPROGRAM / #pragma vertex / #pragma fragment` - - importer 输出继续复用当前 runtime shader contract:`properties / passes / resources / backend variants` - - 这一阶段不追求完整 Unity ShaderLab,只做 builtin 与主线材质系统需要的最小闭环 - -2. 再迁移 builtin shader 到新 authoring 入口 - - 优先 `ForwardLit / Unlit / ObjectId / DepthOnly / ShadowCaster` - - 要求 importer 产出的 runtime contract 与当前 renderer 消费路径保持一致 - -3. 然后收紧 material 主路径 - - 以 imported shader schema 为主路径完成 property 类型校验、默认值回退、constant layout 与 resource mapping - - builtin alias / canonical-name fallback 只保留兼容兜底,不再作为主执行路径 - -4. 最后做最小回归集收口 - - `shader_tests` - - `material_tests` - - `rendering_unit_tests` - - 必要的 rendering integration smoke - -当前进展(`2026-04-04`): - -- 已完成:Step 1 `Unity-like .shader authoring MVP importer` - - `ShaderLoader` 新增 Unity-like `.shader` authoring 识别与解析入口,但保留现有 JSON manifest `.shader` 兼容路径不动 - - importer 继续落到现有 runtime shader contract:`properties / passes / resources / backend variants` - - `CollectSourceDependencies` 已覆盖新 authoring 路径,`AssetDatabase` 会继续追踪各 backend stage 文件依赖并参与重导入 -- 已完成:Step 2 `builtin shader 迁到新 authoring 入口` - - `forward-lit / unlit / object-id / depth-only / shadow-caster` 五个 builtin `.shader` 入口已全部切到 Unity-like authoring - - stage 源文件、builtin shader 路径与 renderer 消费 contract 保持不变,迁移只发生在 authoring 入口层 - - 补充 `DepthOnly / ShadowCaster` builtin shader loader 覆盖,确保五类 builtin pass 都经过新 authoring 路径验证 -- 已完成:Step 3 `material 主路径收紧到 imported shader schema` - - `MaterialLoader` 在存在 shader schema 时,`properties / textures` 中的旧 key 会先解析到 shader property semantic,再落到 shader 正式属性名 - - 旧的 `baseColor / baseColorTexture` 等兼容 key 仍可导入,但只作为兼容输入存在,不再直接污染 material 主执行路径 - - 当 material 绑定 shader schema 后,未知 texture binding 现在会被显式拒绝,不再静默忽略 -- 已验证:`shader_tests` 中新增 authoring 直载与 artifact/reimport 覆盖 -- 已验证:`shader_tests` 31/31 通过,builtin `ForwardLit / Unlit / ObjectId / DepthOnly / ShadowCaster` 全部通过加载与 backend variant 覆盖 -- 已验证:`material_tests` 53/53 通过,schema 驱动的 property/texture 映射与未知 binding 拒绝路径都已覆盖 -- 已完成:Step 4 `最小回归集收口` - - `rendering_unit_tests` 73/73 通过,builtin pass contract 与 renderer 侧消费路径在当前重构后保持稳定 - - 本轮最小回归集结果:`shader_tests` 31/31,`material_tests` 53/53,`rendering_unit_tests` 73/73 - - 以当前目标范围看,`shader / material` 核心主线已经可以阶段性收口 -- 补充验收(`2026-04-04`): - - 补跑 integration smoke 时发现并修复了一处真实回归:Unity-like `.shader` authoring 将 HLSL 的 `MainVS / MainPS` 入口错误套到了 GLSL/Vulkan variant,上层表现为 Vulkan `BuiltinForwardPipeline` 建 pipeline 失败 - - 修复后补跑 `rendering_integration_textured_quad_scene` 与 `rendering_integration_unlit_scene`、`rendering_integration_object_id_scene`,三者在 `D3D12 / OpenGL / Vulkan` 下均通过 - - 额外补跑 `rendering_integration_backpack_lit_scene` 时,暴露出一条相邻但不同范围的问题:direct `MeshLoader` 导入的 mesh/material/texture 生命周期路径存在异常退出/不干净收尾,已超出本次 `shader / material contract` 收口范围 - - 因此本计划按“核心主线已收口,附带一条相邻 residual risk”归档;若后续继续补强,优先单独开一轮处理 `MeshLoader + imported subresource lifetime` -- 下一步:从当前目标范围出发,本计划可归档;后续如继续推进,请以新计划承接 `backpack_lit_scene` 这条相邻问题 - -当前阶段明确不做: - -- 完整阴影贴图消费链 -- render graph -- shader graph -- 完整 Unity ShaderLab 全语法 -- 与 shader/material 主线无关的 editor/ui 扩展 diff --git a/docs/used/Shader统一预编译缓存并入Library正式方案_完成归档_2026-04-11.md b/docs/used/Shader统一预编译缓存并入Library正式方案_完成归档_2026-04-11.md deleted file mode 100644 index d18e5849..00000000 --- a/docs/used/Shader统一预编译缓存并入Library正式方案_完成归档_2026-04-11.md +++ /dev/null @@ -1,181 +0,0 @@ -# Shader 统一预编译缓存并入 Library 正式方案 - -文档日期:2026-04-11 - -## 1. 目标 - -把 shader 的预编译结果正式并入现有 `Library/Artifacts` 体系,不再允许出现: - -1. project shader 走 `Library`,builtin shader 走裸加载。 -2. 运行时每次开编辑器都重新现场编译重 shader。 -3. 只修 D3D12,一到 Vulkan / OpenGL 又退回运行时临时编译。 - -本轮正式目标: - -1. builtin shader 与 project shader 统一走同一个 artifact 产物格式。 -2. `ShaderStageVariant` 保持现有 authoring / runtime 变体模型不变,不扩成“每后端一个 variant”。 -3. 每个 variant 可以携带“按后端区分的已编译 payload”。 -4. D3D12 / Vulkan / OpenGL 运行时优先消费 artifact 中的 payload,只有 miss 时才 fallback 到现场编译。 - -## 2. 架构原则 - -### 2.1 唯一正式缓存位置 - -唯一正式 shader 预编译缓存位置是: - -`project/Library/Artifacts/.../main.xcshader` - -不新增独立 runtime cache 目录,不做旁路缓存。 - -### 2.2 builtin shader 也必须纳入 Library - -外部标识仍然保持: - -`builtin://shaders/...` - -但在有 project root 时,真实加载路径应优先变成: - -`Library/Artifacts/.../main.xcshader` - -这样 builtin shader 与普通 `Assets/*.shader` 在缓存语义上完全一致。 - -### 2.3 不改变现有 variant 选择逻辑 - -现有很多代码和测试都依赖: - -1. authoring variant 可以是 `Generic` -2. 运行时按 backend 查找时先找精确 backend,再回退 `Generic` - -因此不把一个 `Generic` variant 展开成三份后端 variant,而是让一个 variant 内部携带: - -1. 旧字段 `compiledBinary` -2. 新字段 `backendCompiledBinaries` - -## 3. 数据模型 - -### 3.1 运行时 compile desc - -`RHI::ShaderCompileDesc` 新增: - -1. `compiledBinaryBackend` -2. `compiledBinary` - -含义: - -1. source / fileName 仍然描述“可回退到现场编译的输入” -2. compiledBinary 描述“当前后端可直接消费的已编译 payload” - -### 3.2 shader variant - -`Resources::ShaderStageVariant` 支持: - -1. `GetCompiledBinaryForBackend(...)` -2. `SetCompiledBinaryForBackend(...)` - -语义: - -1. backend-specific variant 可以继续把本后端 payload 放在旧 `compiledBinary` -2. `Generic` variant 的 D3D12 / Vulkan / OpenGL payload 放在 `backendCompiledBinaries` - -### 3.3 artifact schema - -shader artifact schema 升级为 `6`,`ShaderVariantArtifactHeader` 新增: - -1. `backendCompiledBinaryCount` - -并追加 `ShaderBackendCompiledBinaryArtifactHeader + payload` 序列。 - -## 4. 导入阶段 - -导入 `.shader` 时: - -1. 先用 `ShaderLoader` 生成现有 authoring/runtime variant。 -2. 对每个 pass / variant 生成运行时 compile desc。 -3. 按目标后端尝试预编译: - - D3D12:生成 DXBC - - Vulkan:生成 Vulkan SPIR-V - - OpenGL:生成 OpenGL-target SPIR-V -4. 把结果写回 variant。 -5. 最终统一写入 `main.xcshader` artifact。 - -后端目标规则: - -1. `Generic` variant 预编译到 D3D12 / Vulkan / OpenGL -2. backend-specific variant 只预编译自己的 backend - -失败策略: - -1. 预编译失败不阻断 import -2. artifact 仍然照常生成 -3. 运行时保持 fallback 能力 -4. 日志明确记录 pass / stage / backend / reason - -## 5. builtin shader 接入方式 - -### 5.1 ResourceManager - -`ResourceManager::LoadResource(...)` 在 shader + builtin path 情况下: - -1. 解析到真实 builtin shader 资产路径 -2. 在当前 project 的 `Library` 中确保对应 artifact -3. 真正加载 artifact -4. 资源对外 path 仍然恢复成 `builtin://...` - -### 5.2 AssetDatabase - -由于 builtin shader 资产位于 project root 外部: - -1. 允许绝对路径解析成相对 project root 的 `../engine/...` -2. 这些外部 source record 不写 `.meta` -3. 使用 synthetic GUID / synthetic meta hash -4. `Refresh()` 时显式扫描 builtin shader 资产,避免下次启动把 builtin artifact 当 orphan 清掉 - -## 6. 运行时消费 - -### 6.1 D3D12 - -1. `D3D12Shader` 增加 `InitializeFromBytecode(...)` -2. `CompileD3D12Shader(...)` 优先命中 `compiledBinaryBackend == D3D12` -3. miss 时才走 `D3DCompile(...)` -4. `CreateShader(...)` 统一复用这条逻辑 - -### 6.2 Vulkan - -1. `CompileSpirvShader(...)` 支持直接消费 `compiledBinaryBackend == Vulkan` -2. `VulkanDevice / VulkanPipelineState` 通过既有 `CompileVulkanShader(...)` 自动命中缓存 payload - -### 6.3 OpenGL - -1. 若命中 `compiledBinaryBackend == OpenGL` -2. 则把该 payload 作为 SPIR-V 输入 -3. 继续走现有 `SPIR-V -> GLSL` 转译路径 -4. 原始 HLSL source 仍保留给 sampler/texture 绑定推导逻辑使用 - -## 7. 验收标准 - -### 7.1 工程标准 - -1. `shader_tests` 全绿 -2. `XCEditor` 能完整编译通过 - -### 7.2 行为标准 - -1. shader artifact roundtrip 后不丢失 backend payload -2. builtin shader 与 project shader 都能命中 `Library/Artifacts` -3. D3D12 / Vulkan / OpenGL 运行时在 payload 存在时不再重复现场编译 - -### 7.3 性能标准 - -1. 影响 NanoVDB 首帧的 volumetric shader 不再在每次启动时重编 -2. editor 打开主场景时,shader stall 不再成为 `SceneReady` 的主瓶颈 - -## 8. 后续扩展 - -后续若继续做: - -1. import-time completeness 标记 -2. 运行时回写 artifact -3. PSO cache blob -4. 更完整的 shader compile telemetry - -都必须建立在本方案之上,不允许再引入平行缓存体系。 diff --git a/docs/used/Shader预编译缓存与D3D12字节码正式化计划_完成归档_2026-04-10.md b/docs/used/Shader预编译缓存与D3D12字节码正式化计划_完成归档_2026-04-10.md deleted file mode 100644 index 231dcfb1..00000000 --- a/docs/used/Shader预编译缓存与D3D12字节码正式化计划_完成归档_2026-04-10.md +++ /dev/null @@ -1,287 +0,0 @@ -# Shader 预编译缓存与 D3D12 字节码正式化计划 - -文档日期:2026-04-10 - -适用范围:`XCEngine` 当前 `Resources / Asset / Rendering / RHI / Editor` 主线,目标问题为编辑器与运行时虽然已经具备 shader artifact 与 variant 系统,但 D3D12 路径仍普遍在运行时现场 `D3DCompile(...)`,缺少真正可复用的编译字节码缓存链路。 - -关联归档: -[NanoVDB体积云加载阻塞与Runtime上传修复计划_完成归档_2026-04-10.md](/D:/Xuanchi/Main/XCEngine/docs/used/NanoVDB体积云加载阻塞与Runtime上传修复计划_完成归档_2026-04-10.md) - ---- - -## 1. 当前结论 - -本轮 `NanoVDB` 卡顿排查已经把“资源读入/上传”与“shader 编译”分开钉死: - -1. `main.xcvol` artifact 载入已经压到亚秒级。 -2. `cloud.nvdb` 对应 payload 的 CPU 读入和 GPU 上传不再是主瓶颈。 -3. 当前剩余大头是 D3D12 图形管线创建时的运行时 shader 编译。 - -最新 editor 实测: - -- `VolumeFieldLoader Artifact total_ms = 674` -- `AsyncVolumeLoadCompleted elapsed_ms = 909` -- `UploadVolumeField total_ms = 736` -- `D3D12 shader compile MainPS total_ms = 6205` -- `SceneReady elapsed_ms = 8500` - -这说明当前主线问题已经从“体积资源路径错误”切换成“D3D12 shader 运行时现场编译没有被缓存机制覆盖”。 - ---- - -## 2. 根本问题 - -当前工程里虽然有 shader artifact,也预留了 `compiledBinary` 字段,但整条链路没有真正闭合。 - -### 2.1 已有但未闭环的部分 - -1. `ShaderStageVariant` 有 `compiledBinary` 字段。 -2. shader artifact 文件格式会序列化/反序列化 `compiledBinary`。 -3. shader runtime build 也会统计 `compiledBinary` 的内存占用。 - -### 2.2 真正缺失的部分 - -1. 没有任何正式步骤把 D3D12 编译结果写入 `variant.compiledBinary`。 -2. 没有任何 D3D12 运行时路径优先消费 `compiledBinary`。 -3. `D3D12Device::CreatePipelineState(...)` 仍然直接对 `ShaderCompileDesc` 走 `D3DCompile(...)`。 -4. shader artifact 现在缓存的是源码展开结果和变体描述,不是可直接用于 D3D12 的 DXBC/DXIL。 - -因此当前的真实状态不是“缓存偶尔失效”,而是: - -`D3D12 shader 预编译缓存机制整体没有真正落地。` - -`NanoVDB` 只是把这个系统性缺口最先炸出来,因为它的 `MainPS` 足够重。 - ---- - -## 3. 目标 - -## 3.1 一级目标 - -让 D3D12 路径对同一 shader variant 不再反复运行时现场编译。 - -要求: - -1. 首次编译后能够落盘保存 D3D12 可复用字节码。 -2. 再次打开 editor / scene / project 时优先命中缓存。 -3. `CreatePipelineState(...)` 在缓存命中时不再进入 `D3DCompile(...)`。 - -## 3.2 二级目标 - -把这套机制做成通用能力,而不是只给 `volumetric.shader` 打补丁。 - -要求: - -1. 面向所有 D3D12 shader variant。 -2. 与现有 shader artifact/variant 系统兼容。 -3. 缓存失效规则明确,可随源码、profile、macro、backend、entry point 正确失效。 - -## 3.3 三级目标 - -为后续 Vulkan / OpenGL / DXC 路径保留统一设计空间。 - -本轮不要求一次做完多后端,但数据模型和接口命名不能把后续扩展堵死。 - ---- - -## 4. 非目标 - -本轮不做以下内容: - -1. 不重写整个 shader authoring 系统。 -2. 不直接切 DXC 全量替换 FXC。 -3. 不先做跨后端统一离线编译工具链。 -4. 不先做完整 PSO cache blob 体系。 -5. 不先解决所有 shader 首次编译耗时,只先解决“重复现场编译”这个系统性缺口。 - ---- - -## 5. 正式实施方向 - -## 5.1 方向一:明确 D3D12 预编译产物的数据模型 - -要先统一“什么叫缓存命中”。 - -建议把 D3D12 预编译缓存键固定为以下信息的稳定组合: - -1. shader 资源路径 -2. pass name -3. stage -4. backend -5. source language -6. entry point -7. profile -8. macro 集合 -9. 变体关键字集合 -10. 源码内容哈希 - -产物: - -1. 对应 stage 的已编译 D3D12 字节码 -2. 可选的编译日志/调试信息版本标识 -3. 可选的 shader reflection 派生数据版本号 - -这一步的目的不是立即提速,而是先避免后面做出“缓存看起来有,实际上命不中或误命中”的半成品。 - -## 5.2 方向二:把 `compiledBinary` 从占位字段变成真实产物 - -当前 `compiledBinary` 只是格式字段,不是真正能力。 - -本阶段要做: - -1. D3D12 编译成功后把字节码写回 `ShaderStageVariant::compiledBinary` -2. shader artifact 写出时携带该字节码 -3. 重新载入 shader artifact 时恢复该字节码 - -要求: - -1. 同一 variant 的 artifact 可以直接携带 D3D12 字节码 -2. 非 D3D12 后端不会被这套数据污染 -3. 缓存不存在时依然允许 fallback 到现场编译 - -## 5.3 方向三:D3D12 runtime 优先消费已编译字节码 - -这是运行时闭环的关键。 - -需要调整的不是 asset 层,而是 D3D12 runtime 创建 shader / pipeline 的入口: - -1. `CreateShader(...)` -2. `CreatePipelineState(...)` -3. 任何内部 `CompileD3D12Shader(...)` 的调用链 - -优先级顺序应为: - -1. 命中 `compiledBinary`,直接创建 shader bytecode -2. 未命中时才走 `D3DCompile(...)` -3. 首次现场编译成功后可回写缓存 - -验收标准: - -1. 对同一项目二次启动时,`MainPS` 不再重新编译 -2. 日志里能明确区分 `cache_hit` / `cache_miss` / `runtime_compile` - -## 5.4 方向四:把缓存失效规则正式化 - -没有失效规则,缓存就会变成隐患。 - -必须纳入失效判定的至少包括: - -1. shader 源文件内容变化 -2. `#include` 展开结果变化 -3. entry point 变化 -4. profile 变化 -5. macro 变化 -6. backend 变化 -7. variant keyword 变化 -8. shader artifact schema version 变化 - -这一阶段必须输出一套明确规则,避免后面出现“改了 shader 却继续吃旧字节码”的错误。 - -## 5.5 方向五:引入可验证的日志与测试 - -这次 `NanoVDB` 能钉死问题,靠的是可计时日志,不是猜。 - -shader 预编译缓存正式化之后,也必须保留最小必要日志: - -1. cache key -2. cache hit/miss -3. runtime compile elapsed -4. binary load elapsed -5. fallback reason - -测试层至少要覆盖: - -1. 首次冷启动:miss + compile + write -2. 第二次热启动:hit + no compile -3. 改 shader 源码后:缓存失效 + 重新编译 -4. 切换 profile / macro / keyword 后:缓存失效 - ---- - -## 6. 分阶段执行计划 - -## Phase 0:基线与接口盘点 - -任务: - -1. 梳理 `ShaderStageVariant::compiledBinary` 的现状用途 -2. 盘点 D3D12 shader / pipeline 创建的全部编译入口 -3. 定义统一缓存键和版本策略 -4. 确认 artifact 与 runtime 的责任边界 - -交付: - -1. 一份固定缓存键规则 -2. 一份 D3D12 编译调用链清单 - -## Phase 1:让 artifact 真正带上 D3D12 编译产物 - -任务: - -1. 为 D3D12 variant 生成并保存编译字节码 -2. artifact 写入与读取完整覆盖 `compiledBinary` -3. 为编译产物附加必要版本信息 - -交付: - -1. D3D12 shader artifact 中存在真实二进制 payload -2. 可验证 artifact 前后字节码一致 - -## Phase 2:让 D3D12 runtime 优先吃缓存 - -任务: - -1. 调整 `CompileD3D12Shader(...)` 调用链 -2. 优先从 `compiledBinary` 构造 shader bytecode -3. 未命中时 fallback 到 `D3DCompile(...)` -4. 命中/失效/回写日志打通 - -交付: - -1. 二次打开 editor 时不再重新编译体积云 `MainPS` -2. 其他 D3D12 shader 也具备相同缓存能力 - -## Phase 3:引入自动验证与冷启动对比 - -任务: - -1. 加入 shader cache 命中测试 -2. 加入变体失效测试 -3. 对 editor 冷启动做两轮连续对比 - -目标数字: - -1. 二次启动 `MainPS compile total_ms` 应接近 `0` -2. `SceneReady` 应继续从当前 `~8.5s` 下探 - ---- - -## 7. 当前建议的提交边界 - -在新计划开始实施前,建议把已经完成的修复与后续 shader cache 工作拆成两批提交: - -### 提交一:NanoVDB 路径修复与编译热点降压 - -应包含: - -1. volume artifact / payload 路径修复 -2. `ResizeUninitialized` 与大 payload 默认构造开销修复 -3. `volumetric.shader` 去除高风险 `[unroll]` -4. HLSL profile 对齐到 `5_1` -5. 当前保留的计时日志 - -### 提交二:Shader 预编译缓存正式化 - -暂不在本提交里混入,避免把“已验证修复”和“下一阶段系统改造”搅在一起。 - ---- - -## 8. 验收标准 - -本计划完成时,至少满足: - -1. D3D12 对同一 shader variant 的二次启动不再现场编译。 -2. 日志能明确证明缓存命中。 -3. 修改 shader 后缓存会正确失效。 -4. `NanoVDB` 体积云场景的二次启动 `SceneReady` 不再被 `MainPS` 编译拖慢。 - diff --git a/docs/used/Subplan-01_XCUI-Core-Tree-State.md b/docs/used/Subplan-01_XCUI-Core-Tree-State.md deleted file mode 100644 index 48531028..00000000 --- a/docs/used/Subplan-01_XCUI-Core-Tree-State.md +++ /dev/null @@ -1,48 +0,0 @@ -# Subplan 01:XCUI Core Tree / State / Invalidation - -目标: - -- 搭出 XCUI 的 retained-mode 核心骨架。 -- 明确 `ElementTree`、`NodeId`、`View`、`ViewModel`、`dirty flag`、`rebuild`、`lifecycle` 的最小闭环。 - -负责人边界: - -- 负责 `engine/include/XCEngine/UI/` 与 `engine/src/UI/Core/` 的核心树模型。 -- 不负责具体布局算法。 -- 不负责 ImGui 适配绘制。 - -建议目录: - -- `engine/include/XCEngine/UI/Core/` -- `engine/src/UI/Core/` -- `tests` 中对应 XCUI core 测试文件 - -前置依赖: - -- 依赖主线完成 `Phase 0` 的基础类型和 UI 生命周期边界清理。 - -现在就可以先做的内容: - -- 设计 `UIElementId` / `UIElement` / `UIContext` / `UIBuildContext` -- 设计 dirty 标记与增量重建规则 -- 设计 ViewModel 读写边界和 command 回调入口 -- 写最小 tree rebuild 测试 - -明确不做: - -- 不接入 `.xcui` 文件 -- 不接入 editor 面板 -- 不写具体 widget 大库 - -交付物: - -- XCUI core 基础类与生命周期定义 -- tree rebuild / invalidation / state propagation 单元测试 -- 一个最小 demo:代码构建 UI tree 并触发一次增量更新 - -验收标准: - -- 可以构建一棵稳定的 UI tree -- 局部状态变化时只标脏必要节点 -- 重建逻辑与布局/渲染解耦 -- 其他 subplan 可以基于该模块定义控件树和状态更新 diff --git a/docs/used/Subplan-03_XCUI-Style-Theme-Token.md b/docs/used/Subplan-03_XCUI-Style-Theme-Token.md deleted file mode 100644 index 5217fdaa..00000000 --- a/docs/used/Subplan-03_XCUI-Style-Theme-Token.md +++ /dev/null @@ -1,46 +0,0 @@ -# Subplan 03:XCUI Style / Theme / Token - -目标: - -- 建立 XCUI 的样式模型、token 体系与主题覆盖规则。 -- 让控件视觉不再散落在代码硬编码常量里。 - -负责人边界: - -- 负责 `Style` / `Theme` / `Token` 数据模型和解析规则。 -- 不负责 `.xcui` 导入器。 -- 不负责最终 renderer 具体绘制。 - -建议目录: - -- `engine/include/XCEngine/UI/Style/` -- `engine/src/UI/Style/` -- `tests` 中 style/theme 测试 - -前置依赖: - -- 依赖 `Subplan 01` 的节点属性注入点。 - -现在就可以先做的内容: - -- 设计 style 层级:默认样式、类型样式、命名样式、局部覆盖 -- 设计 token:颜色、圆角、边距、字号、线宽 -- 设计主题切换与 token 查询接口 -- 写冲突优先级测试 - -明确不做: - -- 不做具体面板视觉重构 -- 不做字体资源导入 - -交付物: - -- 统一样式查询入口 -- `.xctheme` 对应的数据结构 -- 样式优先级与 token 解析测试 - -验收标准: - -- 样式优先级可预测 -- 主题替换不需要改控件逻辑 -- 其他 subplan 能通过统一 API 获取视觉参数 diff --git a/docs/used/Subplan-06_XCUI-Markup-Import-HotReload.md b/docs/used/Subplan-06_XCUI-Markup-Import-HotReload.md deleted file mode 100644 index 52716d03..00000000 --- a/docs/used/Subplan-06_XCUI-Markup-Import-HotReload.md +++ /dev/null @@ -1,46 +0,0 @@ -# Subplan 06:XCUI Markup / Import / Hot Reload - -目标: - -- 把 `.xcui` / `.xctheme` / `.xcschema` 拉进资源系统。 -- 建立导入、编译产物、热重载、诊断输出的第一版链路。 - -负责人边界: - -- 负责资源类型、导入器、artifact、诊断日志。 -- 不负责 widget 运行时逻辑本身。 - -建议目录: - -- `engine/include/XCEngine/Resources/UI/` -- `engine/src/Resources/UI/` -- `editor/src` 中与导入面板、诊断输出相关的接入口 - -前置依赖: - -- 需要主计划中的资源类型命名拍板。 -- 与 `Subplan 03`、`Subplan 07` 协调格式字段。 - -现在就可以先做的内容: - -- 定义三类资源描述结构 -- 设计导入错误诊断格式 -- 设计热重载触发和缓存失效策略 -- 先做一个最小 parser,可以把简单 `.xcui` 编成中间结构 - -明确不做: - -- 不做完整 markup 语法大全 -- 不做 inspector 的最终渲染 - -交付物: - -- UI 资源类型定义 -- 导入器与 artifact 结构 -- 热重载与错误输出最小闭环 - -验收标准: - -- UI 资源可被 ResourceManager 识别 -- 导入失败时有可读诊断 -- 改动文件后可触发重新加载 diff --git a/docs/used/TESTING.md b/docs/used/TESTING.md deleted file mode 100644 index 27223a40..00000000 --- a/docs/used/TESTING.md +++ /dev/null @@ -1,285 +0,0 @@ -# XCEngine 测试体系文档 - -> **版本**: 1.0 -> **日期**: 2026-03-13 - ---- - -## 1. 测试架构 - -``` -tests/ -├── CMakeLists.txt # 测试构建配置 -├── run_tests.cmake # 测试运行脚本 -├── fixtures/ # 测试夹具 -│ └── MathFixtures.h -├── math/ # Math 单元测试 -│ ├── CMakeLists.txt -│ ├── test_vector.cpp -│ ├── test_matrix.cpp -│ ├── test_quaternion.cpp -│ └── test_geometry.cpp -├── core/ # Core 测试 -├── threading/ # 线程测试 -├── memory/ # 内存测试 -├── containers/ # 容器测试 -└── rendering/ # 渲染测试 - ├── unit/ # 单元测试 - ├── integration/ # 集成测试 - └── screenshots/ # 参考图 -``` - ---- - -## 2. 测试分类 - -| 类型 | 目录 | 目的 | 运行频率 | -|------|------|------|---------| -| **Unit Test** | `tests/*/` | 验证单个函数/类 | 每次提交 | -| **Integration Test** | `tests/rendering/integration/` | 验证多模块协作 | 每次提交 | -| **Benchmark** | `tests/benchmark/` | 性能回归检测 | 每日/每周 | -| **Screenshot Test** | `tests/rendering/screenshots/` | 渲染正确性 | 每次提交 | - ---- - -## 3. 测试命名规范 - -```cpp -// 格式: test_<模块>_<功能>_<场景> -TEST(Math_Vector3, Dot_TwoVectors_ReturnsCorrectValue) { } -TEST(Math_Vector3, Normalize_ZeroVector_ReturnsZeroVector) { } -TEST(Math_Matrix4, Inverse_Identity_ReturnsIdentity) { } -TEST(Math_Matrix4, TRS_Decompose_RecoversOriginalValues) { } -TEST(Math_Quaternion, Slerp_ShortestPath_InterpolatesCorrectly) { } - -// 边界情况 -TEST(Math_Vector3, Normalize_ZeroVector_DoesNotCrash) { } -TEST(Math_Matrix4, Inverse_SingularMatrix_ReturnsIdentity) { } -``` - ---- - -## 4. 断言规范 - -### 4.1 浮点数比较 - -```cpp -// 必须使用容差 -EXPECT_NEAR(actual, expected, 1e-5f); -ASSERT_FLOAT_EQ(actual, expected); // gtest 内部有容差 - -// 数组比较 -for (int i = 0; i < 4; i++) { - EXPECT_NEAR(actual.m[i], expected.m[i], 1e-5f); -} -``` - -### 4.2 常用断言 - -```cpp -EXPECT_TRUE(condition); -EXPECT_FALSE(condition); -EXPECT_EQ(actual, expected); -EXPECT_NE(actual, expected); -EXPECT_STREQ(actual, expected); -EXPECT_THROW(expression, exception_type); -``` - ---- - -## 5. 测试夹具 (Fixture) - -```cpp -class MathFixture : public ::testing::Test { -protected: - void SetUp() override { - v1 = Vector3(1, 0, 0); - v2 = Vector3(0, 1, 0); - v3 = Vector3(1, 1, 1); - - m1 = Matrix4x4::Identity(); - m2 = Matrix4x4::Translation(Vector3(1, 2, 3)); - } - - Vector3 v1, v2, v3; - Matrix4x4 m1, m2; - const float epsilon = 1e-5f; -}; - -TEST_F(MathFixture, Dot_OrthogonalVectors_ReturnsZero) { - EXPECT_FLOAT_EQ(Vector3::Dot(v1, v2), 0.0f); -} -``` - ---- - -## 6. 参数化测试 - -```cpp -class MatrixInverseTest : public ::testing::TestWithParam {}; - -INSTANTIATE_TEST_SUITE_P( - InverseCases, - MatrixInverseTest, - testing::Values( - Matrix4x4::Identity(), - Matrix4x4::Translation(Vector3(1,2,3)), - Matrix4x4::Scale(Vector3(2,2,2)) - ) -); - -TEST_P(MatrixInverseTest, InverseOfInverse_EqualsOriginal) { - Matrix4x4 original = GetParam(); - Matrix4x4 inverted = original.Inverse(); - Matrix4x4 recovered = inverted.Inverse(); - - for (int i = 0; i < 4; i++) { - for (int j = 0; j < 4; j++) { - EXPECT_NEAR(original.m[i][j], recovered.m[i][j], 1e-4f); - } - } -} -``` - ---- - -## 7. Math 测试用例 - -### 7.1 Vector2/3/4 测试 - -| 测试类别 | 测试用例 | -|---------|---------| -| **构造** | 默认构造、参数构造、从 Vector3 构造 | -| **运算** | 加、减、乘、除、点积、叉积 | -| **归一化** | Normalize、Normalized、Magnitude、SqrMagnitude | -| **插值** | Lerp、MoveTowards | -| **投影** | Project、ProjectOnPlane | -| **角度** | Angle、Reflect | - -### 7.2 Matrix 测试 - -| 测试类别 | 测试用例 | -|---------|---------| -| **构造** | Identity、Zero | -| **变换** | Translation、Rotation、Scale、TRS | -| **相机** | LookAt、Perspective、Orthographic | -| **运算** | 乘法、点乘、叉乘 | -| **分解** | Inverse、Transpose、Determinant、Decompose | - -### 7.3 Quaternion 测试 - -| 测试类别 | 测试用例 | -|---------|---------| -| **构造** | Identity、FromAxisAngle、FromEulerAngles | -| **转换** | ToEulerAngles、ToMatrix4x4、FromRotationMatrix | -| **插值** | Slerp | -| **运算** | 乘法和逆 | - -### 7.4 几何测试 - -| 测试类型 | 测试用例 | -|---------|---------| -| **Ray** | GetPoint、Intersects(Sphere/Box/Plane) | -| **Sphere** | Contains、Intersects | -| **Box** | Contains、Intersects | -| **Plane** | FromPoints、GetDistanceToPoint、Intersects | -| **Frustum** | Contains(Point/Sphere/Bounds)、Intersects | -| **Bounds** | GetMinMax、Intersects、Contains、Encapsulate | - ---- - -## 8. 构建与运行 - -### 8.1 构建测试 - -```bash -# 创建构建目录 -mkdir build && cd build - -# 配置 CMake -cmake .. -G "Visual Studio 17 2022" -A x64 - -# 构建测试 -cmake --build . --config Debug --target xcengine_math_tests -``` - -### 8.2 运行测试 - -```bash -# 运行所有测试 -ctest --output-on-failure - -# 运行 Math 测试 -./tests/xcengine_math_tests.exe - -# 运行特定测试 -./tests/xcengine_math_tests.exe --gtest_filter=Math_Vector3.* - -# 运行测试并显示详细信息 -./tests/xcengine_math_tests.exe --gtest_also_run_disabled_tests --gtest_print_time=1 -``` - -### 8.3 测试过滤器 - -```bash -# 运行所有 Vector3 测试 ---gtest_filter=Math_Vector3.* - -# 运行除某测试外的所有测试 ---gtest_filter=-Math_Matrix4.SingularMatrix* - -# 运行多个测试 ---gtest_filter=Math_Vector3.*:Math_Matrix4.* -``` - ---- - -## 9. 覆盖率要求 - -| 模块 | 最低覆盖率 | 关键测试 | -|------|-----------|---------| -| Math | 90% | 所有公开 API | -| Core | 80% | 智能指针、Event | -| Containers | 85% | 边界、迭代器 | -| Memory | 90% | 分配/泄漏 | -| Threading | 70% | 基本功能 | - ---- - -## 10. 持续集成 - -```yaml -# .github/workflows/test.yml -name: Test - -on: [push, pull_request] - -jobs: - test: - runs-on: windows-latest - - steps: - - uses: actions/checkout@v3 - - - name: Configure - run: cmake -B build -DENABLE_COVERAGE=ON - - - name: Build - run: cmake --build build --config Debug - - - name: Test - run: ctest --test-dir build --output-on-failure - - - name: Coverage - run: cmake --build build --target coverage -``` - ---- - -## 11. 注意事项 - -1. **浮点数比较** - 必须使用容差 (通常 1e-5 或 1e-6) -2. **边界条件** - 必须测试零向量、奇异矩阵等 -3. **随机性** - 如需固定 seed 保证确定性 -4. **线程安全** - 线程测试需设置超时 -5. **内存泄漏** - 使用 Valgrind 或 CRT 检测 diff --git a/docs/used/UI-Editor-GameObject缺口分析.md b/docs/used/UI-Editor-GameObject缺口分析.md deleted file mode 100644 index ef3f8a86..00000000 --- a/docs/used/UI-Editor-GameObject缺口分析.md +++ /dev/null @@ -1,550 +0,0 @@ -# UI-Editor GameObject 缺口分析 - -> **基于**:XCEngine渲染引擎架构设计.md -> **目标**:识别 UI 编辑器 GameObject.h 相对于 Engine 架构设计文档的缺失功能 -> **日期**:2026-03-20 - ---- - -## 架构参考 - -XCEngine 组件系统参考 **Unity 传统架构 (GameObject-Component Pattern)**: - -- **GameObject**:场景中的实体(UI Editor 中称 `Entity`) -- **Component**:挂载到 GameObject 的功能模块(变换、渲染器等) -- **TransformComponent**:每个 GameObject 都有的变换组件,管理位置/旋转/缩放 -- **Scene/SceneManager**:场景管理和场景切换 - ---- - -## Engine 模块现状 - -| 模块 | 状态 | UI Editor 对齐情况 | -|-----|------|------------------| -| Core (Event, Types) | ✅ 已实现 | UI 编辑器已复用 `XCEngine::Core::Event` | -| Debug (Logger, LogLevel, LogEntry) | ✅ 已实现 | UI 编辑器已复用 `XCEngine::Debug::LogLevel` | -| **Math (Vector3, Quaternion, Matrix4x4, Transform, Color)** | ✅ **已实现** | **可直接复用 Engine Math 类型** | -| Memory | ✅ 已实现 | | -| Threading | ✅ 已实现 | | -| Containers | ✅ 已实现 | | -| RHI / D3D12 | ✅ 已实现 | UI 编辑器自行实现(与 Engine 技术栈一致) | -| Resources | ✅ 已实现 | | -| **Components (GameObject, Component, System)** | ❌ **缺失** | UI 编辑器自行实现,待 Engine 实现后迁移 | -| **Scene (Scene, SceneManager, SceneLoader, SceneSerializer)** | ❌ **缺失** | UI 编辑器自行实现,待 Engine 实现后迁移 | - ---- - -## 一、Component 基类缺口 - -| 架构设计接口 | 函数签名 | 状态 | 说明 | -|------------|---------|------|------| -| `transform()` | `TransformComponent& transform() const` | ❌ 缺失 | 返回所在实体的 TransformComponent 引用 | -| `GetScene()` | `Scene* GetScene() const` | ❌ 缺失 | 返回所属 Scene 指针 | -| `FixedUpdate()` | `virtual void FixedUpdate()` | ❌ 缺失 | 物理更新(每固定帧) | -| `LateUpdate()` | `virtual void LateUpdate(float deltaTime)` | ❌ 缺失 | 晚于 Update 执行 | -| `OnEnable()` | `virtual void OnEnable()` | ❌ 缺失 | 组件启用时调用 | -| `OnDisable()` | `virtual void OnDisable()` | ❌ 缺失 | 组件禁用时调用 | -| `friend class` | `friend class GameObject` | ⚠️ 差异 | UI Editor 是 `friend class Entity` | - -**补充说明**: -- UI Editor 的 `Component` 需要增加 `TransformComponent*` 获取方法以支持 Inspector 面板的变换编辑 -- Engine 的 `m_gameObject` 在 UI Editor 中对应 `m_entity` - ---- - -## 二、TransformComponent 缺口 - -### 2.1 World 空间方法 - -| 架构设计接口 | 函数签名 | 状态 | 说明 | -|------------|---------|------|------| -| `GetPosition()` | `XCEngine::Math::Vector3 GetPosition() const` | ❌ 缺失 | 获取世界坐标 | -| `SetPosition()` | `void SetPosition(const XCEngine::Math::Vector3& position)` | ❌ 缺失 | 设置世界坐标 | -| `GetRotation()` | `XCEngine::Math::Quaternion GetRotation() const` | ❌ 缺失 | 获取世界旋转 | -| `SetRotation()` | `void SetRotation(const XCEngine::Math::Quaternion& rotation)` | ❌ 缺失 | 设置世界旋转 | -| `GetScale()` | `XCEngine::Math::Vector3 GetScale() const` | ❌ 缺失 | 获取世界缩放 | -| `SetScale()` | `void SetScale(const XCEngine::Math::Vector3& scale)` | ❌ 缺失 | 设置世界缩放 | - -### 2.2 方向向量 - -| 架构设计接口 | 函数签名 | 状态 | 说明 | -|------------|---------|------|------| -| `GetForward()` | `XCEngine::Math::Vector3 GetForward() const` | ❌ 缺失 | 获取前向向量 | -| `GetRight()` | `XCEngine::Math::Vector3 GetRight() const` | ❌ 缺失 | 获取右向量 | -| `GetUp()` | `XCEngine::Math::Vector3 GetUp() const` | ❌ 缺失 | 获取上向量 | - -### 2.3 矩阵变换 - -| 架构设计接口 | 函数签名 | 状态 | 说明 | -|------------|---------|------|------| -| `GetLocalToWorldMatrix()` | `const XCEngine::Math::Matrix4x4& GetLocalToWorldMatrix() const` | ❌ 缺失 | 本地到世界矩阵(含缓存) | -| `GetWorldToLocalMatrix()` | `XCEngine::Math::Matrix4x4 GetWorldToLocalMatrix() const` | ❌ 缺失 | 世界到本地矩阵 | - -### 2.4 父子层级 - -| 架构设计接口 | 函数签名 | 状态 | 说明 | -|------------|---------|------|------| -| `GetParent()` | `TransformComponent* GetParent() const` | ❌ 缺失 | 获取父变换 | -| `SetParent()` | `void SetParent(TransformComponent* parent, bool worldPositionStays = true)` | ❌ 缺失 | 设置父变换 | -| `GetChild()` | `TransformComponent* GetChild(int index) const` | ❌ 缺失 | 按索引获取子变换 | -| `Find()` | `TransformComponent* Find(const String& name) const` | ❌ 缺失 | 按名称查找子变换 | -| `DetachChildren()` | `void DetachChildren()` | ❌ 缺失 | 断开所有子节点 | -| `SetAsFirstSibling()` | `void SetAsFirstSibling()` | ❌ 缺失 | 设为第一个同级 | -| `SetAsLastSibling()` | `void SetAsLastSibling()` | ❌ 缺失 | 设为最后一个同级 | -| `GetSiblingIndex()` | `int GetSiblingIndex() const` | ❌ 缺失 | 获取同级索引 | -| `SetSiblingIndex()` | `void SetSiblingIndex(int index)` | ❌ 缺失 | 设置同级索引 | - -### 2.5 变换操作 - -| 架构设计接口 | 函数签名 | 状态 | 说明 | -|------------|---------|------|------| -| `LookAt(target)` | `void LookAt(const XCEngine::Math::Vector3& target)` | ❌ 缺失 | 朝向目标点 | -| `LookAt(target, up)` | `void LookAt(const XCEngine::Math::Vector3& target, const XCEngine::Math::Vector3& up)` | ❌ 缺失 | 朝向目标点(指定上向量) | -| `Rotate(eulers)` | `void Rotate(const XCEngine::Math::Vector3& eulers)` | ❌ 缺失 | 欧拉角旋转 | -| `Rotate(axis, angle)` | `void Rotate(const XCEngine::Math::Vector3& axis, float angle)` | ❌ 缺失 | 轴角旋转 | -| `Translate(translation)` | `void Translate(const XCEngine::Math::Vector3& translation)` | ❌ 缺失 | 平移(世界空间) | -| `Translate(translation, relativeTo)` | `void Translate(const XCEngine::Math::Vector3& translation, Space relativeTo)` | ❌ 缺失 | 平移(指定空间) | - -### 2.6 点/方向变换 - -| 架构设计接口 | 函数签名 | 状态 | 说明 | -|------------|---------|------|------| -| `TransformPoint()` | `XCEngine::Math::Vector3 TransformPoint(const XCEngine::Math::Vector3& point) const` | ❌ 缺失 | 变换点(含平移旋转) | -| `InverseTransformPoint()` | `XCEngine::Math::Vector3 InverseTransformPoint(const XCEngine::Math::Vector3& point) const` | ❌ 缺失 | 逆变换点 | -| `TransformDirection()` | `XCEngine::Math::Vector3 TransformDirection(const XCEngine::Math::Vector3& direction) const` | ❌ 缺失 | 变换方向(仅旋转) | -| `InverseTransformDirection()` | `XCEngine::Math::Vector3 InverseTransformDirection(const XCEngine::Math::Vector3& direction) const` | ❌ 缺失 | 逆变换方向 | - -### 2.7 脏标记与缓存 - -| 架构设计接口 | 函数签名 | 状态 | 说明 | -|------------|---------|------|------| -| `SetDirty()` | `void SetDirty()` | ⚠️ 局部 | UI Editor 只有简单 flag,缺少完整缓存机制 | -| 缓存成员 | `mutable XCEngine::Math::Matrix4x4 m_localToWorldMatrix` 等 | ❌ 缺失 | 矩阵缓存 | -| 更新方法 | `void UpdateWorldTransform() const` | ❌ 缺失 | 缓存失效时重新计算 | - -**当前 UI Editor 实现**: -```cpp -class TransformComponent : public Component { -public: - float position[3] = {0.0f, 0.0f, 0.0f}; // 仅有本地坐标 - float rotation[3] = {0.0f, 0.0f, 0.0f}; // 欧拉角 - float scale[3] = {1.0f, 1.0f, 1.0f}; // 仅有本地缩放 - // 缺少世界空间方法、矩阵缓存、父子指针等 -}; -``` - -**架构设计要求**(应使用 Engine Math 类型): -```cpp -class TransformComponent : public Component { -public: - // 使用 Engine Math 类型 - XCEngine::Math::Vector3 GetLocalPosition() const { return m_localPosition; } - void SetLocalPosition(const XCEngine::Math::Vector3& pos) { m_localPosition = pos; SetDirty(); } - - XCEngine::Math::Quaternion GetLocalRotation() const { return m_localRotation; } - void SetLocalRotation(const XCEngine::Math::Quaternion& rot) { m_localRotation = rot; SetDirty(); } - - XCEngine::Math::Vector3 GetLocalScale() const { return m_localScale; } - void SetLocalScale(const XCEngine::Math::Vector3& sc) { m_localScale = sc; SetDirty(); } - -private: - XCEngine::Math::Vector3 m_localPosition = XCEngine::Math::Vector3::Zero(); - XCEngine::Math::Quaternion m_localRotation = XCEngine::Math::Quaternion::Identity(); - XCEngine::Math::Vector3 m_localScale = XCEngine::Math::Vector3::One(); - - TransformComponent* m_parent = nullptr; - std::vector m_children; - - mutable XCEngine::Math::Matrix4x4 m_localToWorldMatrix; - mutable XCEngine::Math::Matrix4x4 m_worldToLocalMatrix; - mutable XCEngine::Math::Vector3 m_worldPosition; - mutable XCEngine::Math::Quaternion m_worldRotation; - mutable XCEngine::Math::Vector3 m_worldScale; - mutable bool m_dirty = true; - - void UpdateWorldTransform() const; -}; -``` - ---- - -## 三、GameObject缺口 - -### 3.1 基础方法 - -| 架构设计接口 | 函数签名 | 状态 | 说明 | -|------------|---------|------|------| -| `GetTransform()` | `TransformComponent& GetTransform()` | ❌ 缺失 | 获取变换组件引用 | -| `GetScene()` | `Scene* GetScene() const` | ❌ 缺失 | 获取所属场景 | - -### 3.2 组件查找 - -| 架构设计接口 | 函数签名 | 状态 | 说明 | -|------------|---------|------|------| -| `GetComponentInChildren()` | `T* GetComponentInChildren() const` | ❌ 缺失 | 在子实体中查找组件 | -| `GetComponentsInChildren()` | `std::vector GetComponentsInChildren() const` | ❌ 缺失 | 获取所有子实体中的组件 | -| `GetComponentInParent()` | `T* GetComponentInParent() const` | ❌ 缺失 | 在父实体中查找组件 | - -### 3.3 父子层级 - -| 架构设计接口 | 函数签名 | 状态 | 说明 | -|------------|---------|------|------| -| `SetParent(parent)` | `void SetParent(Entity* parent)` | ❌ 缺失 | 2参数版本(含 worldPositionStays) | -| `GetChildren()` | `const std::vector& GetChildren() const` | ❌ 缺失 | 获取子实体列表 | -| `GetChild()` | `Entity* GetChild(int index) const` | ❌ 缺失 | 按索引获取子实体 | - -### 3.4 激活状态 - -| 架构设计接口 | 函数签名 | 状态 | 说明 | -|------------|---------|------|------| -| `active` 成员 | `bool active = true` | ❌ 缺失 | 实体激活状态 | -| `SetActive()` | `void SetActive(bool active)` | ❌ 缺失 | 设置激活状态 | -| `IsActive()` | `bool IsActive() const` | ❌ 缺失 | 检查激活状态 | -| `IsActiveInHierarchy()` | `bool IsActiveInHierarchy() const` | ❌ 缺失 | 检查层级中的激活状态 | - -### 3.5 静态查找 - -| 架构设计接口 | 函数签名 | 状态 | 说明 | -|------------|---------|------|------| -| `Find()` | `static Entity* Find(const String& name)` | ❌ 缺失 | 按名称查找实体 | -| `FindObjectsOfType()` | `static std::vector FindObjectsOfType()` | ❌ 缺失 | 查找所有实体 | -| `FindGameObjectsWithTag()` | `static std::vector FindGameObjectsWithTag(const String& tag)` | ❌ 缺失 | 按标签查找 | - -### 3.6 销毁 - -| 架构设计接口 | 函数签名 | 状态 | 说明 | -|------------|---------|------|------| -| `Destroy()` | `void Destroy()` | ❌ 缺失 | 实例方法销毁(通过 Scene) | - ---- - -## 四、Scene / SceneManager 缺口 - -### 4.1 Scene 类 - -| 架构设计接口 | 函数签名 | 状态 | 说明 | -|------------|---------|------|------| -| `GetName()` | `const String& GetName() const` | ❌ 缺失 | 获取场景名称 | -| `SetName()` | `void SetName(const String& name)` | ❌ 缺失 | 设置场景名称 | -| `CreateGameObject(name, parent)` | `Entity* CreateGameObject(const String& name, Entity* parent)` | ❌ 缺失 | 带父实体创建 | -| `GetRootGameObjects()` | `std::vector GetRootGameObjects() const` | ❌ 缺失 | 获取根实体列表 | -| `Find()` | `Entity* Find(const String& name) const` | ❌ 缺失 | 查找实体 | -| `FindGameObjectWithTag()` | `Entity* FindGameObjectWithTag(const String& tag) const` | ❌ 缺失 | 按标签查找 | -| `FindObjectsOfType()` | `std::vector FindObjectsOfType() const` | ❌ 缺失 | 模板查找 | -| `FindObjectOfType()` | `T* FindObjectOfType() const` | ❌ 缺失 | 查找单个 | -| `IsActive()` | `bool IsActive() const` | ❌ 缺失 | 场景激活状态 | -| `SetActive()` | `void SetActive(bool active)` | ❌ 缺失 | 设置激活状态 | -| `Load()` | `void Load(const String& filePath)` | ❌ 缺失 | 加载场景 | -| `Save()` | `void Save(const String& filePath)` | ❌ 缺失 | 保存场景 | -| `Update()` | `void Update(float deltaTime)` | ❌ 缺失 | 场景更新 | -| `FixedUpdate()` | `void FixedUpdate(float fixedDeltaTime)` | ❌ 缺失 | 物理更新 | -| `LateUpdate()` | `void LateUpdate(float deltaTime)` | ❌ 缺失 | 晚更新 | - -**当前 UI Editor 缺失 Scene 概念**,所有实体都在 SceneManager 全局管理。 - -### 4.2 SceneManager 类 - -| 架构设计接口 | 函数签名 | 状态 | 说明 | -|------------|---------|------|------| -| `CreateScene()` | `Scene* CreateScene(const String& name)` | ❌ 缺失 | 创建场景 | -| `LoadScene()` | `void LoadScene(const String& filePath)` | ❌ 缺失 | 同步加载 | -| `LoadSceneAsync()` | `void LoadSceneAsync(const String& filePath, std::function callback)` | ❌ 缺失 | 异步加载 | -| `UnloadScene(scene)` | `void UnloadScene(Scene* scene)` | ❌ 缺失 | 卸载场景 | -| `UnloadScene(name)` | `void UnloadScene(const String& sceneName)` | ❌ 缺失 | 按名称卸载 | -| `SetActiveScene(scene)` | `void SetActiveScene(Scene* scene)` | ❌ 缺失 | 设置激活场景 | -| `SetActiveScene(name)` | `void SetActiveScene(const String& sceneName)` | ❌ 缺失 | 按名称设置 | -| `GetActiveScene()` | `Scene* GetActiveScene() const` | ❌ 缺失 | 获取激活场景 | -| `GetScene(name)` | `Scene* GetScene(const String& name) const` | ❌ 缺失 | 按名称获取场景 | -| `GetAllScenes()` | `std::vector GetAllScenes() const` | ❌ 缺失 | 获取所有场景 | -| `OnSceneLoaded` | `Event` | ⚠️ 差异 | UI Editor 是 `OnSceneChanged` | -| `OnSceneUnloaded` | `Event` | ❌ 缺失 | 场景卸载事件 | -| `OnActiveSceneChanged` | `Event` | ❌ 缺失 | 激活场景变更事件 | - -### 4.3 GameObjectBuilder 类 - -| 架构设计接口 | 函数签名 | 状态 | 说明 | -|------------|---------|------|------| -| 完整类 | `class GameObjectBuilder` | ❌ 缺失 | 实体创建辅助类 | - ---- - -## 五、实现优先级 - -### P0 - 核心缺失(必须实现) - -| 模块 | 原因 | 涉及文件 | -|-----|------|---------| -| TransformComponent 世界空间方法 | Editor 变换编辑基础 | GameObject.h | -| Entity `GetTransform()` | Inspector 面板需要 | GameObject.h | -| Component `transform()` | 组件访问变换 | GameObject.h | - -**依赖**:Engine Math 模块 ✅ 已实现,可直接使用 `XCEngine::Math::Vector3` / `Quaternion` / `Matrix4x4`。 - -### P1 - 功能完整(应该实现) - -| 模块 | 原因 | 涉及文件 | -|-----|------|---------| -| TransformComponent 矩阵缓存 | 性能优化 | GameObject.h | -| TransformComponent 父子指针 | 层级操作 | GameObject.h | -| Entity 激活状态 | 运行时状态 | GameObject.h | -| Entity 父子层级操作 | 层级编辑 | GameObject.h | -| Scene 概念 | 多场景支持 | SceneManager.h | -| SceneManager 多场景管理 | 场景隔离 | SceneManager.h | - -### P2 - 高级功能(可选实现) - -| 模块 | 原因 | 涉及文件 | -|-----|------|---------| -| Scene 序列化/反序列化 | 持久化 | SceneManager.h | -| TransformComponent 点/方向变换 | Gizmos 操作 | GameObject.h | -| SceneManager 异步加载 | 体验优化 | SceneManager.h | -| Entity 静态查找 | Editor 操作 | GameObject.h | - ---- - -## 六、数据结构对齐表 - -### 6.1 当前 UI Editor vs 架构设计 - -| 项目 | UI Editor 当前 | 架构设计 | 对齐建议 | -|-----|--------------|---------|---------| -| 实体内组件存储 | `vector>` | `vector>` | ✅ 已对齐 | -| 变换类型 | `float[3]` 数组 | `XCEngine::Math::Vector3` / `Quaternion` | ⚠️ **应改用 Engine Math** | -| 父子关系 | `EntityID` (uint64) | `Entity*` 指针 | ⚠️ UI 用 ID,Engine 用指针 | -| 场景管理 | 全局单例 SceneManager | 多 Scene + SceneManager | ❌ 需要重构 | - -### 6.2 命名差异 - -| UI Editor | Engine 架构设计 | 说明 | -|-----------|---------------|------| -| `Entity` | `GameObject` | UI Editor 使用 Entity 避免与 std 冲突 | -| `EntityID` | `uint64_t` | UI Editor 自定义类型别名 | -| `INVALID_ENTITY_ID` | N/A | UI Editor 自定义常量 | -| `ComponentRegistry` | `ComponentTypeRegistry` | 功能相似,命名不同 | - -### 6.3 类型复用情况 - -| Engine 类型 | UI Editor 现状 | 建议 | -|-----------|--------------|------| -| `XCEngine::Math::Vector3` | UI Editor 用 `float[3]` | **直接复用** | -| `XCEngine::Math::Quaternion` | UI Editor 用 `float[3]` (欧拉角) | **直接复用**,但欧拉角存储可保留 | -| `XCEngine::Math::Matrix4x4` | 无 | **直接复用** | -| `XCEngine::Core::Event` | ✅ 已复用 | 保持 | -| `XCEngine::Debug::LogLevel` | ✅ 已复用 | 保持 | - ---- - -## 七、依赖关系分析 - -``` -实现优先级 - 依赖链: - -P0: TransformComponent 世界空间 - │ - ├── 需要: XCEngine::Math::Vector3 ✅ 已实现 - ├── 需要: XCEngine::Math::Quaternion ✅ 已实现 - ├── 需要: XCEngine::Math::Matrix4x4 ✅ 已实现 - └── 影响: Entity::GetTransform() - -P0: Component::transform() - │ - └── 依赖: Entity::GetTransform() - -P1: TransformComponent 父子指针 - │ - ├── 需要: TransformComponent* parent - ├── 需要: vector children - └── 影响: Entity 父子操作 - -P1: Entity 激活状态 - │ - ├── 需要: active 成员 - └── 影响: IsActiveInHierarchy() - -P1: Scene 多场景 - │ - ├── 需要: Scene 类 - ├── 需要: Scene::CreateGameObject() - └── 影响: SceneManager 重构 -``` - ---- - -## 八、与 Unity 架构的对照 - -### Unity 传统架构 (GameObject-Component) - -``` -Scene - └── GameObject ("MyEntity") - ├── Transform - ├── MeshRenderer - └── (其他组件...) - -GameObject.GetComponent() → Entity.GetComponent() -GameObject.transform → Entity.GetTransform() -GameObject.SetActive(bool) → 缺失 -GameObject.Find("name") → 缺失 -Scene.GetRootGameObjects() → 缺失 -SceneManager.GetActiveScene() → 缺失 -``` - -### UI Editor 当前实现 - -``` -SceneManager (全局单例) - └── m_entities (unordered_map) - └── Entity ("MyEntity") - ├── id, name, parent, children - ├── components (vector>) - └── selected - -Entity.AddComponent() → ✅ 已实现 -Entity.GetComponent() → ✅ 已实现 -Entity.GetTransform() → ❌ 缺失 -TransformComponent 世界空间方法 → ❌ 缺失 -``` - ---- - -## 九、附录:当前 GameObject.h 完整内容 - -```cpp -// ui_editor/src/Core/GameObject.h 当前实现 - -#pragma once - -#include -#include -#include -#include -#include -#include - -#include - -namespace UI { - -using EntityID = uint64_t; -constexpr EntityID INVALID_ENTITY_ID = 0; - -class Component { -public: - virtual ~Component() = default; - virtual std::string GetName() const = 0; - - virtual void Awake() {} - virtual void Start() {} - virtual void Update(float deltaTime) {} - virtual void OnDestroy() {} - - class Entity* GetEntity() const { return m_entity; } - bool IsEnabled() const { return m_enabled; } - void SetEnabled(bool enabled) { m_enabled = enabled; } - -protected: - class Entity* m_entity = nullptr; - bool m_enabled = true; - - friend class Entity; -}; - -class TransformComponent : public Component { -public: - float position[3] = {0.0f, 0.0f, 0.0f}; - float rotation[3] = {0.0f, 0.0f, 0.0f}; - float scale[3] = {1.0f, 1.0f, 1.0f}; - - std::string GetName() const override { return "Transform"; } -}; - -class MeshRendererComponent : public Component { -public: - std::string materialName = "Default-Material"; - std::string meshName = ""; - - std::string GetName() const override { return "Mesh Renderer"; } -}; - -class Entity { -public: - EntityID id = INVALID_ENTITY_ID; - std::string name; - EntityID parent = INVALID_ENTITY_ID; - std::vector children; - std::vector> components; - bool selected = false; - - template - T* AddComponent(Args&&... args) { - auto comp = std::make_unique(std::forward(args)...); - comp->m_entity = this; - T* ptr = comp.get(); - components.push_back(std::move(comp)); - return ptr; - } - - template - T* GetComponent() { - for (auto& comp : components) { - if (auto casted = dynamic_cast(comp.get())) { - return casted; - } - } - return nullptr; - } - - template - std::vector GetComponents() { - std::vector result; - for (auto& comp : components) { - if (auto casted = dynamic_cast(comp.get())) { - result.push_back(casted); - } - } - return result; - } -}; - -using ComponentInspectorFn = std::function; - -struct ComponentInspectorInfo { - std::string name; - ComponentInspectorFn renderFn; -}; - -class ComponentRegistry { -public: - static ComponentRegistry& Get() { - static ComponentRegistry instance; - return instance; - } - - template - void RegisterComponent(const std::string& name, ComponentInspectorFn inspectorFn) { - m_inspectors[name] = {name, inspectorFn}; - m_factories[name] = []() -> std::unique_ptr { - return std::make_unique(); - }; - } - - ComponentInspectorInfo* GetInspector(const std::string& name) { - auto it = m_inspectors.find(name); - if (it != m_inspectors.end()) { - return &it->second; - } - return nullptr; - } - -private: - ComponentRegistry() = default; - std::unordered_map m_inspectors; - std::unordered_map()>> m_factories; -}; - -} // namespace UI -``` - ---- - -**文档结束** diff --git a/docs/used/UI-Editor设计与实现.md b/docs/used/UI-Editor设计与实现.md deleted file mode 100644 index 80d7c71a..00000000 --- a/docs/used/UI-Editor设计与实现.md +++ /dev/null @@ -1,1055 +0,0 @@ -# UI-Editor 设计与实现文档 - -> **版本**: 1.0 -> **日期**: 2026-03-20 -> **目标**: 为 XCEngine 游戏引擎搭建 UI 编辑器框架 - ---- - -## 第一章 概述 - -### 1.1 UI编辑器目标与定位 - -UI 编辑器是 XCEngine 游戏引擎的编辑器前端,用于可视化编辑游戏场景、场景层级、检视组件属性、控制台输出等核心功能。 - -**核心目标**: -1. 提供独立的 UI 编辑器可执行程序(基于 ImGui + D3D12) -2. UI 编辑器的数据结构与引擎核心类型对齐 -3. 引擎核心已有的类型直接复用,引擎核心未实现的则在 UI 编辑器中临时实现但保持接口一致 -4. 支持场景层级管理、实体创建删除、组件编辑等基础功能 - -**定位**: -- UI 编辑器作为引擎的独立模块,不直接耦合引擎渲染管线 -- 通过 Engine 核心的抽象接口与引擎通信 -- 当前阶段以独立开发为主,待引擎核心完善后进行对接 - -### 1.2 与引擎核心的对齐策略 - -| Engine 类型 | 状态 | UI 编辑器策略 | -|------------|------|-------------| -| `XCEngine::Core::Event` | ✅ 已实现 | 直接复用 | -| `XCEngine::Core::uint64` | ✅ 已实现 | 直接复用 | -| `XCEngine::Debug::Logger` | ✅ 已实现 | 直接复用 | -| `XCEngine::Debug::LogLevel` | ✅ 已实现 | 直接复用 | -| `XCEngine::Debug::LogEntry` | ✅ 已实现 | 直接复用 | -| `XCEngine::Math::Vector3` 等 | 仅有设计文档 | UI 编辑器临时实现但接口对齐 | -| `Component` | 仅有设计文档 | UI 编辑器自行实现 `UI::Component` | -| `GameObject` | 仅有设计文档 | UI 编辑器自行实现 `UI::Entity` | -| `TransformComponent` | 仅有设计文档 | UI 编辑器自行实现 `UI::TransformComponent` | -| `Scene` / `SceneManager` | 仅有设计文档 | UI 编辑器自行实现 `UI::SceneManager` | - -**对齐原则**: -- Engine 已有类型:直接 `#include ` 并使用 -- Engine 暂无类型:在 UI 编辑器中实现,但类名、成员变量、成员函数命名与设计文档保持一致 -- Engine 实现后:UI 编辑器可平滑迁移到 Engine 类型 - -### 1.3 技术选型 - -**渲染后端:D3D12** -- 与引擎核心渲染管线技术栈一致 -- 支持多窗口、多渲染目标 -- 提供高性能的 GPU 命令提交 - -**UI 框架:Dear ImGui** -- 轻量级、可扩展的即时模式 UI -- 支持主题定制 -- 成熟的 D3D12 集成示例 -- 高效的 UI 渲染管线 - -**架构模式:模块化 Manager + Panel** -- Manager:负责数据管理和业务逻辑 -- Panel:负责 UI 展示和用户交互 -- 清晰的职责分离便于维护和扩展 - ---- - -## 第二章 架构设计 - -### 2.1 整体架构图 - -``` -┌─────────────────────────────────────────────────────────────────────────────────────┐ -│ UI 编辑器架构 │ -└─────────────────────────────────────────────────────────────────────────────────────┘ - - ┌─────────────────┐ - │ Application │ 应用程序入口 - │ (D3D12 + ImGui)│ - └────────┬────────┘ - │ - ┌─────────────────────────────────┼─────────────────────────────────┐ - │ │ │ - ▼ ▼ ▼ -┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ -│ Managers │ │ Panels │ │ Core Types │ -│ │ │ │ │ │ -│ ┌─────────────┐ │ │ ┌─────────────┐ │ │ ┌─────────────┐ │ -│ │SceneManager │ │ │ │HierarchyPanel│ │ │ │ Entity │ │ -│ └─────────────┘ │ │ └─────────────┘ │ │ └─────────────┘ │ -│ ┌─────────────┐ │ │ ┌─────────────┐ │ │ ┌─────────────┐ │ -│ │SelectionMgr │ │◀──────────────│ │InspectorPanel│ │ │ │ Component │ │ -│ └─────────────┘ │ │ └─────────────┘ │ │ └─────────────┘ │ -│ ┌─────────────┐ │ │ ┌─────────────┐ │ │ ┌─────────────┐ │ -│ │ LogSystem │ │ │ │SceneViewPanel│ │ │ │ Theme │ │ -│ └─────────────┘ │ │ └─────────────┘ │ │ └─────────────┘ │ -│ ┌─────────────┐ │ │ ┌─────────────┐ │ │ │ -│ │ProjectManager│ │ │ │GameViewPanel │ │ │ │ -│ └─────────────┘ │ │ └─────────────┘ │ │ │ -│ ┌─────────────┐ │ │ ┌─────────────┐ │ │ │ -│ │ AssetItem │ │ │ │ConsolePanel │ │ │ │ -│ └─────────────┘ │ │ └─────────────┘ │ │ │ -│ │ │ ┌─────────────┐ │ │ │ -│ │ │ │ ProjectPanel│ │ │ │ -│ │ │ └─────────────┘ │ │ │ -│ │ │ ┌─────────────┐ │ │ │ -│ │ │ │ MenuBar │ │ │ │ -│ │ │ └─────────────┘ │ │ │ -└─────────────────┘ └─────────────────┘ └─────────────────┘ - │ │ - │ ┌───────────────────────┘ - │ │ - ▼ ▼ -┌─────────────────────────────────────────────────────────────────────────────────────┐ -│ Engine Core (XCEngine) │ -│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ -│ │Event │ │Logger │ │LogLevel │ │LogEntry │ │ -│ │(已实现复用) │ │(已实现复用) │ │(已实现复用) │ │(已实现复用) │ │ -│ └──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘ │ -│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ -│ │Math Types │ │Component │ │GameObject │ │Scene │ │ -│ │(设计文档) │ │(设计文档) │ │(设计文档) │ │(设计文档) │ │ -│ └──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘ │ -└─────────────────────────────────────────────────────────────────────────────────────┘ -``` - -### 2.2 目录结构 - -``` -ui_editor/ -├── CMakeLists.txt # 构建配置 -├── bin/ # 编译输出 -│ └── Release/ -│ └── XCVolumeRendererUI2.exe -├── build/ # 中间文件 -└── src/ - ├── main.cpp # 程序入口 - ├── Application.h/cpp # 应用程序类(D3D12 + ImGui) - │ - ├── Core/ # 核心数据类型 - │ ├── GameObject.h # Entity、Component、TransformComponent - │ ├── LogEntry.h # 日志条目(复用 Engine LogLevel) - │ └── AssetItem.h # 资产条目 - │ - ├── Managers/ # 管理器系统 - │ ├── SceneManager.h/cpp # 场景管理 - │ ├── SelectionManager.h # 选择管理 - │ ├── LogSystem.h/cpp # 日志系统 - │ └── ProjectManager.h/cpp # 项目管理 - │ - ├── panels/ # UI 面板 - │ ├── Panel.h/cpp # 面板基类 - │ ├── MenuBar.h/cpp # 菜单栏 - │ ├── HierarchyPanel.h/cpp # 层级面板 - │ ├── InspectorPanel.h/cpp # 检视面板 - │ ├── SceneViewPanel.h/cpp # 场景视图 - │ ├── GameViewPanel.h/cpp # 游戏视图 - │ ├── ConsolePanel.h/cpp # 控制台面板 - │ └── ProjectPanel.h/cpp # 项目面板 - │ - └── Theme.h/cpp # 主题样式 -``` - -### 2.3 核心类型与引擎核心的对齐映射表 - -| UI 编辑器类型 | 文件位置 | 对齐的 Engine 类型 | 说明 | -|-------------|---------|------------------|------| -| `UI::Entity` | Core/GameObject.h | `XCEngine::GameObject` | 实体,包含 ID、名称、层级关系、组件 | -| `UI::Component` | Core/GameObject.h | `XCEngine::Component` | 组件基类 | -| `UI::TransformComponent` | Core/GameObject.h | `XCEngine::TransformComponent` | 变换组件 | -| `UI::MeshRendererComponent` | Core/GameObject.h | `XCEngine::RenderMeshComponent` | 网格渲染组件 | -| `UI::EntityID` | Core/GameObject.h | `uint64_t` | 实体唯一标识符 | -| `UI::INVALID_ENTITY_ID` | Core/GameObject.h | N/A | 无效实体 ID 常量 | -| `UI::SceneManager` | Managers/SceneManager.h | `XCEngine::SceneManager` | 场景管理器 | -| `UI::LogSystem` | Managers/LogSystem.h | `XCEngine::Debug::Logger` | 日志系统 | -| `UI::LogEntry` | Core/LogEntry.h | `XCEngine::Debug::LogEntry` | 日志条目 | -| `XCEngine::Core::Event` | Engine/include/... | N/A | 直接复用引擎事件系统 | - ---- - -## 第三章 核心数据类型 - -### 3.1 Entity(对齐 Engine GameObject) - -`Entity` 是 UI 编辑器中的核心对象类型,对齐 Engine 的 `GameObject` 设计。 - -**设计文件**:`ui_editor/src/Core/GameObject.h` - -```cpp -namespace UI { - -using EntityID = uint64_t; -constexpr EntityID INVALID_ENTITY_ID = 0; - -class Entity { -public: - EntityID id = INVALID_ENTITY_ID; - std::string name; - EntityID parent = INVALID_ENTITY_ID; // 父实体 ID - std::vector children; // 子实体 ID 列表 - std::vector> components; - bool selected = false; - - template - T* AddComponent(Args&&... args) { - auto comp = std::make_unique(std::forward(args)...); - comp->m_entity = this; - T* ptr = comp.get(); - components.push_back(std::move(comp)); - return ptr; - } - - template - T* GetComponent() { - for (auto& comp : components) { - if (auto casted = dynamic_cast(comp.get())) { - return casted; - } - } - return nullptr; - } - - template - std::vector GetComponents() { - std::vector result; - for (auto& comp : components) { - if (auto casted = dynamic_cast(comp.get())) { - result.push_back(casted); - } - } - return result; - } -}; - -} // namespace UI -``` - -**关键设计决策**: - -1. **EntityID 类型**:使用 `uint64_t` 作为实体唯一标识,与 Engine 的 `uint64_t` 一致 -2. **父子关系**:通过 `parent` ID 和 `children` ID 列表维护层级关系,避免循环引用 -3. **组件存储**:使用 `std::vector>` 存储组件,支持多态 -4. **选择状态**:`selected` 成员用于追踪实体是否被选中 - -**与 Engine GameObject 的接口对齐**: - -| UI::Entity 接口 | Engine::GameObject 接口 | 对齐状态 | -|---------------|------------------------|---------| -| `id` | (无直接对应,Engine 用指针) | 接口不同但功能一致 | -| `name` | `name` | ✅ 完全对齐 | -| `parent` | `GetParent()/SetParent()` | ✅ 功能对齐 | -| `children` | `GetChildren()` | ✅ 功能对齐 | -| `AddComponent()` | `AddComponent()` | ✅ 接口对齐 | -| `GetComponent()` | `GetComponent()` | ✅ 接口对齐 | - -### 3.2 Component(对齐 Engine Component) - -**设计文件**:`ui_editor/src/Core/GameObject.h` - -```cpp -class Component { -public: - virtual ~Component() = default; - virtual std::string GetName() const = 0; - - virtual void Awake() {} - virtual void Start() {} - virtual void Update(float deltaTime) {} - virtual void OnDestroy() {} - - class Entity* GetEntity() const { return m_entity; } - bool IsEnabled() const { return m_enabled; } - void SetEnabled(bool enabled) { m_enabled = enabled; } - -protected: - class Entity* m_entity = nullptr; - bool m_enabled = true; - - friend class Entity; -}; -``` - -**生命周期函数**:对齐 Engine Component 的设计,包含 `Awake()`、`Start()`、`Update()`、`OnDestroy()` 等。 - -**TransformComponent 实现**: - -```cpp -class TransformComponent : public Component { -public: - float position[3] = {0.0f, 0.0f, 0.0f}; - float rotation[3] = {0.0f, 0.0f, 0.0f}; - float scale[3] = {1.0f, 1.0f, 1.0f}; - - std::string GetName() const override { return "Transform"; } -}; -``` - -**MeshRendererComponent 实现**: - -```cpp -class MeshRendererComponent : public Component { -public: - std::string materialName = "Default-Material"; - std::string meshName = ""; - - std::string GetName() const override { return "Mesh Renderer"; } -}; -``` - -### 3.3 ComponentRegistry(组件注册表) - -组件注册表用于将组件类型与对应的 ImGui 检视函数关联。 - -```cpp -using ComponentInspectorFn = std::function; - -struct ComponentInspectorInfo { - std::string name; - ComponentInspectorFn renderFn; -}; - -class ComponentRegistry { -public: - static ComponentRegistry& Get() { - static ComponentRegistry instance; - return instance; - } - - template - void RegisterComponent(const std::string& name, ComponentInspectorFn inspectorFn) { - m_inspectors[name] = {name, inspectorFn}; - m_factories[name] = []() -> std::unique_ptr { - return std::make_unique(); - }; - } - - ComponentInspectorInfo* GetInspector(const std::string& name) { - auto it = m_inspectors.find(name); - if (it != m_inspectors.end()) { - return &it->second; - } - return nullptr; - } - -private: - ComponentRegistry() = default; - std::unordered_map m_inspectors; - std::unordered_map()>> m_factories; -}; -``` - -**注册示例**(在 `InspectorPanel.cpp` 中): - -```cpp -ComponentRegistry::Get().RegisterComponent( - "Transform", - [](Component* comp) { - auto* transform = dynamic_cast(comp); - ImGui::InputFloat3("Position", transform->position); - ImGui::InputFloat3("Rotation", transform->rotation); - ImGui::InputFloat3("Scale", transform->scale); - } -); -``` - -### 3.4 SceneManager(对齐 Engine SceneManager) - -**设计文件**:`ui_editor/src/Managers/SceneManager.h` - -```cpp -class SceneManager { -public: - static SceneManager& Get() { - static SceneManager instance; - return instance; - } - - EntityID CreateEntity(const std::string& name, EntityID parent = INVALID_ENTITY_ID); - - Entity* GetEntity(EntityID id) { - auto it = m_entities.find(id); - if (it != m_entities.end()) { - return &it->second; - } - return nullptr; - } - - const std::vector& GetRootEntities() const { - return m_rootEntities; - } - - void DeleteEntity(EntityID id); - void RenameEntity(EntityID id, const std::string& newName); - void CopyEntity(EntityID id); - EntityID PasteEntity(EntityID parent = INVALID_ENTITY_ID); - EntityID DuplicateEntity(EntityID id); - void MoveEntity(EntityID id, EntityID newParent); - void CreateDemoScene(); - - XCEngine::Core::Event OnEntityCreated; - XCEngine::Core::Event OnEntityDeleted; - XCEngine::Core::Event OnEntityChanged; - XCEngine::Core::Event<> OnSceneChanged; - -private: - EntityID m_nextEntityId = 1; - std::unordered_map m_entities; - std::vector m_rootEntities; - std::optional m_clipboard; -}; -``` - -**关键设计决策**: - -1. **Singleton 模式**:通过 `static SceneManager& Get()` 提供全局访问点 -2. **实体存储**:`std::unordered_map` 提供 O(1) 的实体查找 -3. **根实体列表**:`m_rootEntities` 维护所有顶层实体,用于层级面板展示 -4. **剪贴板支持**:`std::optional` 支持复制/粘贴操作 -5. **事件系统**:复用 Engine 的 `Event` 实现观察者模式 - -**事件类型说明**: - -| 事件 | 签名 | 触发时机 | -|-----|------|---------| -| `OnEntityCreated` | `Event` | 创建新实体时 | -| `OnEntityDeleted` | `Event` | 删除实体时 | -| `OnEntityChanged` | `Event` | 实体属性变化时(重命名、父子关系变化) | -| `OnSceneChanged` | `Event<>` | 场景整体变化时 | - ---- - -## 第四章 面板系统 - -### 4.1 Panel 基类 - -所有面板继承自 `Panel` 基类,提供统一的接口。 - -**设计文件**:`ui_editor/src/panels/Panel.h` - -```cpp -class Panel { -public: - virtual ~Panel() = default; - - virtual void Initialize() {} - virtual void Render() = 0; - virtual void OnSceneChanged() {} - - const char* GetName() const { return m_name; } - bool IsVisible() const { return m_visible; } - void SetVisible(bool visible) { m_visible = visible; } - -protected: - const char* m_name = "Panel"; - bool m_visible = true; -}; -``` - -**ImGui 窗口包装示例**: - -```cpp -void HierarchyPanel::Render() { - if (!m_visible) return; - - ImGui::PushID(GetName()); - if (ImGui::Begin(GetName(), &m_visible, ImGuiWindowFlags_NoCollapse)) { - RenderContent(); - } - ImGui::End(); - ImGui::PopID(); -} -``` - -### 4.2 HierarchyPanel(层级面板) - -**职责**:展示场景层级结构,支持实体的创建、删除、拖拽排序、右键菜单。 - -**设计文件**:`ui_editor/src/panels/HierarchyPanel.h` - -```cpp -class HierarchyPanel : public Panel { -public: - void Initialize() override; - void OnSceneChanged() override; - -private: - void RenderContent(); - void RenderEntity(EntityID entity, int& nodeIndex); - void RenderContextMenu(EntityID entity); - void RenderCreateMenu(); -}; -``` - -**核心功能**: - -1. **树形结构展示**:递归渲染实体及其子实体 -2. **选中高亮**:选中的实体以高亮显示 -3. **拖拽排序**:支持通过拖拽改变实体顺序和父子关系 -4. **右键菜单**:创建、删除、复制、粘贴、重命名等操作 -5. **搜索过滤**:支持按名称搜索实体 - -**节点渲染流程**: - -``` -RenderEntity(EntityID id) - │ - ├── 获取实体指针 GetEntity(id) - │ - ├── 构建节点标签 [图标] [名称] [子节点数量] - │ - ├── 选中状态处理 - │ └── 点击时调用 SelectionManager::Select(id) - │ - ├── 展开/折叠状态 - │ - ├── 渲染子节点(递归) - │ └── for child in entity.children: RenderEntity(child) - │ - └── 渲染右键菜单(如果右键) -``` - -### 4.3 InspectorPanel(检视面板) - -**职责**:展示选中实体的所有组件及其属性,支持编辑。 - -**设计文件**:`ui_editor/src/panels/InspectorPanel.h` - -```cpp -class InspectorPanel : public Panel { -public: - void Initialize() override; - void OnSelectionChanged(); - -private: - void RenderContent(); - void RenderEntityHeader(Entity* entity); - void RenderComponent(Component* comp); - void RenderAddComponentMenu(); -}; -``` - -**核心功能**: - -1. **实体头信息**:显示实体名称,支持直接编辑 -2. **组件列表**:遍历实体的所有组件 -3. **组件检视**:调用 `ComponentRegistry` 获取检视函数并渲染 -4. **添加组件**:显示可添加的组件类型列表 - -**组件检视函数注册示例**: - -```cpp -void InspectorPanel::Initialize() { - ComponentRegistry::Get().RegisterComponent( - "Transform", - [](Component* comp) { - auto* transform = dynamic_cast(comp); - ImGui::InputFloat3("Position", transform->position); - if (ImGui::InputFloat3("Rotation", transform->rotation)) { - SceneManager::Get().GetEntity(transform->GetEntity()->id) - ->name = transform->GetEntity()->name; // 触发变更事件 - } - ImGui::InputFloat3("Scale", transform->scale, 1.0f); - } - ); - - ComponentRegistry::Get().RegisterComponent( - "Mesh Renderer", - [](Component* comp) { - auto* mesh = dynamic_cast(comp); - ImGui::InputText("Mesh", &mesh->meshName); - ImGui::InputText("Material", &mesh->materialName); - } - ); -} -``` - -### 4.4 SceneViewPanel(场景视图) - -**职责**:渲染场景视图窗口,提供视口控制。 - -**设计文件**:`ui_editor/src/panels/SceneViewPanel.h` - -**当前实现状态**:基础框架已搭建,待扩展。 - -**计划功能**: -- 视口渲染(通过 Engine 渲染管线) -- 相机控制(平移、旋转、缩放) -- Gizmos 渲染(变换、旋转、缩放工具) -- 网格和辅助线显示 - -### 4.5 GameViewPanel(游戏视图) - -**职责**:显示游戏运行时的画面。 - -**设计文件**:`ui_editor/src/panels/GameViewPanel.h` - -**当前实现状态**:基础框架已搭建,待与 Engine 渲染管线对接。 - -### 4.6 ConsolePanel(控制台) - -**职责**:显示日志系统收集的日志消息。 - -**设计文件**:`ui_editor/src/panels/ConsolePanel.h/cpp` - -```cpp -class ConsolePanel : public Panel { -public: - void Initialize() override; - void Clear(); - void OnLogMessage(const LogEntry& entry); - -private: - void RenderContent(); - void RenderLogEntry(const LogEntry& entry); - void RenderFilterBar(); - - std::vector m_logs; - std::vector m_filteredLogs; - bool m_autoScroll = true; - bool m_wordWrap = false; - XCEngine::Debug::LogLevel m_minLevel = XCEngine::Debug::LogLevel::Verbose; -}; -``` - -**核心功能**: - -1. **日志显示**:显示所有收集的日志条目 -2. **级别过滤**:通过下拉菜单筛选日志级别 -3. **自动滚动**:新日志自动滚动到底部 -4. **时间戳显示**:显示每条日志的时间戳 -5. **双击跳转**:双击日志可定位到相关实体(预留) - -### 4.7 ProjectPanel(项目面板) - -**职责**:浏览项目资源文件。 - -**设计文件**:`ui_editor/src/panels/ProjectPanel.h/cpp` - -**当前实现状态**:基础框架已搭建,待扩展资源导入功能。 - ---- - -## 第五章 管理器系统 - -### 5.1 SelectionManager(选择管理器) - -**设计文件**:`ui_editor/src/Managers/SelectionManager.h` - -```cpp -class SelectionManager { -public: - static SelectionManager& Get() { - static SelectionManager instance; - return instance; - } - - void Select(EntityID entity); - void Deselect(EntityID entity); - void ToggleSelection(EntityID entity); - void ClearSelection(); - void SetSelection(const std::vector& entities); - - bool IsSelected(EntityID entity) const; - const std::vector& GetSelection() const { return m_selection; } - size_t GetSelectionCount() const { return m_selection.size(); } - - XCEngine::Core::Event OnSelected; - XCEngine::Core::Event OnDeselected; - XCEngine::Core::Event<> OnSelectionCleared; - -private: - SelectionManager() = default; - std::vector m_selection; - std::unordered_set m_selectionSet; -}; -``` - -**关键设计决策**: - -1. **双向存储**:`m_selection` 保持顺序,`m_selectionSet` 提供 O(1) 查询 -2. **事件系统**:选择变化时触发对应事件 -3. **多选支持**:通过 `SetSelection` 支持批量选择 - -### 5.2 LogSystem(日志系统) - -**设计文件**:`ui_editor/src/Managers/LogSystem.h/cpp` - -```cpp -class LogSystem { -public: - static LogSystem& Get() { - static LogSystem instance; - return instance; - } - - void Initialize(); - void Shutdown(); - - void Log(XCEngine::Debug::LogLevel level, const std::string& message, - const std::string& file = "", int line = 0); - - void Info(const std::string& message) { Log(LogLevel::Info, message); } - void Warning(const std::string& message) { Log(LogLevel::Warning, message); } - void Error(const std::string& message) { Log(LogLevel::Error, message); } - - XCEngine::Core::Event OnLog; - -private: - LogSystem() = default; - std::vector m_logs; -}; -``` - -**日志流向**: - -``` -XCEngine::Debug::Logger (Engine) - │ - ▼ (通过自定义 LogSink 拦截) -UI::LogSystem::OnLog - │ - ▼ -ConsolePanel::OnLogMessage - │ - ▼ -显示到 ConsolePanel -``` - -### 5.3 ProjectManager(项目管理器) - -**设计文件**:`ui_editor/src/Managers/ProjectManager.h/cpp` - -**职责**: -- 管理项目根目录 -- 扫描和索引资源文件 -- 提供资源查找接口 - ---- - -## 第六章 事件系统 - -### 6.1 引擎 Event 的复用 - -UI 编辑器直接复用 Engine 的事件系统: - -```cpp -#include - -// 使用方式 -XCEngine::Core::Event OnEntityCreated; -XCEngine::Core::Event OnEntityDeleted; -XCEngine::Core::Event OnEntityChanged; -XCEngine::Core::Event<> OnSceneChanged; -``` - -**订阅示例**: - -```cpp -// 在 Panel 初始化时订阅 -SceneManager::Get().OnEntityCreated.AddLambda([this](EntityID id) { - // 处理实体创建 -}); -``` - -### 6.2 UI 编辑器事件类型 - -| 事件源 | 事件名 | 签名 | 说明 | -|-------|-------|------|------| -| `SceneManager` | `OnEntityCreated` | `Event` | 实体创建 | -| `SceneManager` | `OnEntityDeleted` | `Event` | 实体删除 | -| `SceneManager` | `OnEntityChanged` | `Event` | 实体属性变化 | -| `SceneManager` | `OnSceneChanged` | `Event<>` | 场景变化 | -| `SelectionManager` | `OnSelected` | `Event` | 实体被选中 | -| `SelectionManager` | `OnDeselected` | `Event` | 实体取消选中 | -| `SelectionManager` | `OnSelectionCleared` | `Event<>` | 选择被清空 | -| `LogSystem` | `OnLog` | `Event` | 日志消息 | - -### 6.3 事件处理时机 - -**同一帧内的事件处理**: - -```cpp -void Application::Render() { - // 1. 处理用户输入 - // 2. 更新 Managers - SceneManager::Get().Update(deltaTime); - - // 3. 清空pending列表(ProcessUnsubscribes) - // 4. 渲染 ImGui UI - RenderUI(); - - // 5. 渲染 D3D12 - RenderD3D12(); -} -``` - -**注意**:`Event::ProcessUnsubscribes()` 应在每帧适当位置调用,以确保安全的订阅取消。 - ---- - -## 第七章 渲染集成 - -### 7.1 D3D12 初始化流程 - -**设计文件**:`ui_editor/src/Application.cpp` - -``` -Initialize(HWND hwnd) - │ - ├── CreateDevice() - │ ├── D3D12CreateDevice() - │ ├── CreateCommandQueue() - │ ├── CreateCommandAllocator() - │ └── CreateCommandList() - │ - ├── CreateRenderTarget() - │ ├── CreateDescriptorHeap (RTV) - │ ├── CreateDescriptorHeap (SRV) - │ └── CreateRenderTargets (3个双缓冲) - │ - └── CreateFence() -``` - -### 7.2 ImGui 渲染管线 - -**初始化**: - -```cpp -ImGui_ImplWin32_Init(hwnd); -ImGui_ImplDX12_Init( - m_device, - 3, // 双缓冲帧数 - DXGI_FORMAT_R8G8B8A8_UNORM, // 格式 - m_srvHeap, - m_srvHeap->GetCPUDescriptorHandleForHeapStart(), - m_srvHeap->GetGPUDescriptorHandleForHeapStart() -); -``` - -**渲染**: - -```cpp -ImGui_ImplDX12_NewFrame(); -ImGui_ImplWin32_NewFrame(); -ImGui::NewFrame(); - -// 渲染 ImGui UI -RenderUI(); - -ImGui::EndFrame(); -ImGui::Render(); -ImGui_ImplDX12_RenderDrawData(ImGui::GetDrawData(), m_commandList); -``` - -### 7.3 渲染目标与交换链 - -**配置**: - -```cpp -swapChainDesc.Width = width; -swapChainDesc.Height = height; -swapChainDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; -swapChainDesc.BufferCount = 3; // 三重缓冲 -swapChainDesc.SampleDesc.Count = 1; -``` - -**_present 流程**: - -```cpp -m_swapChain->Present(1, 0); -m_fenceValue++; -m_commandQueue->Signal(m_fence, m_fenceValue); -``` - ---- - -## 第八章 主题与样式 - -### 8.1 Theme 系统 - -**设计文件**:`ui_editor/src/Theme.h/cpp` - -```cpp -struct Theme { - ImVec4 colors[ImGuiCol_COUNT]; - - void Apply(); - void LoadDefault(); - void LoadDark(); - void LoadLight(); -}; - -extern Theme g_theme; -``` - -**默认深色主题配色**: - -| 用途 | 颜色 (RGB) | -|-----|-----------| -| 背景 | (0.1, 0.1, 0.1) | -| 面板背景 | (0.15, 0.15, 0.15) | -| 边框 | (0.3, 0.3, 0.3) | -| 文字 | (0.9, 0.9, 0.9) | -| 选中 | (0.2, 0.4, 0.8) | -| 警告 | (0.9, 0.6, 0.2) | -| 错误 | (0.9, 0.2, 0.2) | - ---- - -## 第九章 未来扩展计划 - -### 9.1 序列化/反序列化 - -**计划实现**: - -```cpp -class SceneSerializer { -public: - bool Serialize(Scene* scene, const String& filePath); - bool Deserialize(Scene* scene, const String& filePath); -}; -``` - -**文件格式**:JSON 或二进制(待定) - -### 9.2 撤销/重做系统 - -**计划实现**: - -```cpp -class UndoSystem { -public: - void PushAction(std::unique_ptr action); - void Undo(); - void Redo(); - bool CanUndo() const; - bool CanRedo() const; - -private: - std::vector> m_undoStack; - std::vector> m_redoStack; -}; -``` - -**支持的操作**: -- 实体创建/删除 -- 实体重命名 -- 实体移动(父子关系变更) -- 组件添加/删除 -- 属性修改 - -### 9.3 资源导入管线 - -**计划实现**: - -1. 资源文件监控(文件变化自动检测) -2. 资源导入器(Mesh、Texture、Material 等) -3. 资源依赖解析 -4. 资源热重载 - -### 9.4 预制体系统 - -**计划实现**: - -```cpp -class Prefab { -public: - String name; - std::vector> components; - std::vector children; -}; -``` - ---- - -## 附录 A:核心类型对齐详细对照表 - -| UI 编辑器类型/接口 | Engine 类型/接口 | 文件位置 | 对齐状态 | -|------------------|----------------|---------|---------| -| `UI::Entity` | `XCEngine::GameObject` | GameObject.h / GameObject.h | ✅ 接口对齐 | -| `UI::EntityID` | `uint64_t` | GameObject.h / Types.h | ✅ 完全一致 | -| `UI::INVALID_ENTITY_ID = 0` | N/A | GameObject.h | ✅ 自定义常量 | -| `UI::Component` | `XCEngine::Component` | GameObject.h / Component.h | ✅ 接口对齐 | -| `UI::Component::GetEntity()` | `Component::gameObject()` | GameObject.h / Component.h | ✅ 功能一致 | -| `UI::TransformComponent` | `XCEngine::TransformComponent` | GameObject.h / TransformComponent.h | ✅ 接口对齐 | -| `UI::TransformComponent::position` | `TransformComponent::GetPosition()` | GameObject.h / TransformComponent.h | ✅ 成员/方法对齐 | -| `UI::SceneManager` | `XCEngine::SceneManager` | SceneManager.h / SceneManager.h | ✅ 接口对齐 | -| `UI::SceneManager::CreateEntity()` | `SceneManager::CreateScene()` (不同) | SceneManager.h | ⚠️ 接口有差异 | -| `UI::LogSystem` | `XCEngine::Debug::Logger` | LogSystem.h / Logger.h | ✅ 功能复用 | -| `UI::LogEntry` | `XCEngine::Debug::LogEntry` | LogEntry.h / LogEntry.h | ✅ 复用 Engine | -| `XCEngine::Core::Event` | N/A | Event.h | ✅ 直接复用 | -| `XCEngine::Debug::LogLevel` | N/A | LogLevel.h | ✅ 直接复用 | - ---- - -## 附录 B:事件类型清单 - -### SceneManager 事件 - -| 事件名 | 类型 | 参数 | 触发时机 | -|-------|------|------|---------| -| `OnEntityCreated` | `Event` | 新实体的 ID | `CreateEntity()` 成功后 | -| `OnEntityDeleted` | `Event` | 被删实体的 ID | `DeleteEntity()` 成功后 | -| `OnEntityChanged` | `Event` | 变化实体的 ID | `RenameEntity()`、`MoveEntity()` 等 | -| `OnSceneChanged` | `Event<>` | 无 | 场景整体变化时 | - -### SelectionManager 事件 - -| 事件名 | 类型 | 参数 | 触发时机 | -|-------|------|------|---------| -| `OnSelected` | `Event` | 被选实体的 ID | `Select()` 时 | -| `OnDeselected` | `Event` | 取消选中的实体 ID | `Deselect()` 时 | -| `OnSelectionCleared` | `Event<>` | 无 | `ClearSelection()` 时 | - -### LogSystem 事件 - -| 事件名 | 类型 | 参数 | 触发时机 | -|-------|------|------|---------| -| `OnLog` | `Event` | 日志条目引用 | 任意日志产生时 | - ---- - -## 附录 C:面板布局配置 - -**默认布局**(通过 ImGui Dockspace 实现): - -``` -┌────────────────────────────────────────────────────────────────────┐ -│ MenuBar │ -├──────────────┬─────────────────────────────────┬─────────────────┤ -│ │ │ │ -│ Hierarchy │ SceneView │ Inspector │ -│ Panel │ Panel │ Panel │ -│ │ │ │ -│ (200px) │ (flex) │ (250px) │ -│ │ │ │ -├──────────────┼─────────────────────────────────┼─────────────────┤ -│ │ │ │ -│ Project │ GameView │ Console │ -│ Panel │ Panel │ Panel │ -│ │ │ │ -│ (200px) │ (flex) │ (150px) │ -│ │ │ │ -└──────────────┴─────────────────────────────────┴─────────────────┘ -``` - -**Dock 优先级**: -1. HierarchyPanel - 左上 -2. SceneViewPanel - 中上 -3. InspectorPanel - 右上 -4. ProjectPanel - 左下 -5. GameViewPanel - 中下 -6. ConsolePanel - 右下 - ---- - -**文档结束** diff --git a/docs/used/Unity式Library资产导入与缓存系统重构方案.md b/docs/used/Unity式Library资产导入与缓存系统重构方案.md deleted file mode 100644 index f9182898..00000000 --- a/docs/used/Unity式Library资产导入与缓存系统重构方案.md +++ /dev/null @@ -1,1601 +0,0 @@ -# Unity式 Library 资产导入与缓存系统重构方案 - -## 0. 当前实施进度(2026-04-02) - -本节用于记录方案的实际落地状态,避免文档只停留在设计层。 - -### 0.1 阶段进度表 - -| 阶段 | 状态 | 当前结果 | -| --- | --- | --- | -| 阶段 0:基础类型与工程边界收口 | 已完成 | 已引入 `AssetGUID`、`AssetRef`,开始把运行时 `ResourceGUID` 与编辑器资产身份拆开。 | -| 阶段 1:`.meta` 与 SourceAssetDB | 已完成 | 已支持自动生成 `.meta`、扫描 `Assets`、建立 `guid/path/importer` 索引,并支持路径到 GUID/AssetRef 的反查。 | -| 阶段 2:Artifact Store 与 ArtifactDB | 已完成 | 已落地 `Library/SourceAssetDB`、`Library/ArtifactDB`、`Library/Artifacts`,支持 artifact key、导入失效判断与产物目录管理。 | -| 阶段 3:TextureImporter | 已完成 | 已支持纹理导入为 `xctex`,运行时可直接从 artifact 读取,不再总是解码原始图片。 | -| 阶段 4:ModelImporter | 已完成(初版) | 已支持模型导入为 `xcmesh`,并缓存模型关联纹理的 `xctex`;`ResourceManager` 会优先走 artifact。 | -| 阶段 5:Material 系统 lazy 引用化 | 部分完成 | 已补齐材质序列化所需的 tag/property/texture binding 访问接口,但仍是 eager 贴图加载,未完成真正的延迟纹理解析。 | -| 阶段 6:Scene / Component 引用迁移 | 部分完成 | `MeshFilterComponent` 与 `MeshRendererComponent` 已开始双写路径和 `AssetRef`,并已在 editor 场景打开路径接入延迟恢复。Scene 全量格式迁移尚未完成。 | -| 阶段 7:异步导入与异步加载 | 部分完成 | `AsyncLoader` 已具备真实 worker 线程与主线程 completion pump;editor 打开 scene 时已改为 deferred restore,但通用导入队列、placeholder/UI、全资源类型异步化尚未完成。 | -| 阶段 8:清理、GC、工具与可视化 | 未开始 | 还没有 Reimport All、依赖图查看、orphan artifact 清理、导入面板等工具。 | - -### 0.2 本轮已经落地的具体内容 - -- 新增 `AssetGUID`、`AssetRef`、`ArtifactFormats`、`AssetDatabase`,正式建立 Unity 式资产身份与导入缓存基础设施。 -- `ResourceManager` 已接入 `AssetDatabase`,资源加载时会先尝试 `EnsureArtifact(...)`,命中后走 `Library` 产物。 -- `TextureLoader` 已支持 `xctex`,`MeshLoader` 已支持 `xcmesh`,从而把“导入”和“运行时加载”从代码路径上拆开。 -- `ProjectManager` 已支持忽略 `.meta` 文件显示,并在资源删除、移动、重命名时携带 `.meta` sidecar。 -- `MeshFilterComponent` 与 `MeshRendererComponent` 已开始从纯路径引用迁到 `AssetRef`,目前是“旧路径兼容 + 新引用双写”模式。 -- `AsyncLoader` 已从空壳改为真实后台加载器,支持 worker thread、完成队列与主线程回调泵。 -- editor 的 `SceneManager::LoadScene(...)` / `RestoreSceneSnapshot(...)` 已接入 deferred scene load scope,scene 打开时不再在组件反序列化阶段同步恢复 mesh/material。 -- `EditorWorkspace::Update(...)` 已开始逐帧 pump async completion,`MeshFilterComponent` / `MeshRendererComponent` 现在会在资源真正完成后再接管 handle。 -- 已为纹理 artifact、模型 artifact、组件 `AssetRef` 序列化补充回归测试。 - -### 0.3 已验证结果 - -- `Debug` 下 `texture_tests.exe` 已通过,覆盖 `.meta` 自动生成、`Library` 产物生成、二次 `EnsureArtifact` 不重复导入、按 `AssetRef` 再加载纹理。 -- `Debug` 下 `components_tests.exe` 已通过,覆盖 `MeshRendererComponent` 的 `AssetRef` 双写序列化/反序列化,以及项目内材质按 `AssetRef` 的 deferred async 恢复。 -- `Release` 下 `mesh_tests.exe` 已通过,覆盖 `obj + mtl + 贴图` 导入为 `xcmesh + xctex`、二次导入命中缓存、按 `AssetRef` 再加载模型。 -- `Release` 下 `XCEditor` 已构建通过。 - -### 0.4 当前版本仍然存在的限制 - -- editor 场景打开阶段虽然已经不再在组件反序列化里同步恢复大资源,但首次真正需要该资源时,若 artifact 缺失或失效,后台任务仍可能较重,且缺少显式进度/UI。 -- 即使已经命中 artifact,当前模型 artifact 读取后仍会立即把关联贴图一起加载到内存,尚未做到材质纹理 lazy load。 -- 首帧渲染阶段仍会发生 GPU 资源创建与上传,这部分也会带来一次性卡顿。 -- `MaterialImporter` 还没有形成完整的独立 artifact 格式,材质系统仍处于过渡状态。 -- Scene 级别仍是“组件局部迁移”,还不是一套完整的新序列化协议。 - -## 1. 文档目的 - -本文档用于给 XCEngine 当前资源系统做一次正式的、可实施的重构设计,目标是把当前“打开场景时直接同步读取原始资源”的模式,重构为一套接近 Unity 的: - -- `Assets` 作为源资源目录 -- `.meta` 作为稳定资产身份与导入配置 -- `Library` 作为导入产物与索引缓存 -- 场景、组件、材质、渲染运行时全部通过稳定资产引用访问缓存产物 - -本文档不是一句“加个缓存”式的小修,而是资产身份、导入、缓存、运行时加载、组件引用、编辑器流程的系统级重构方案。 - ---- - -## 2. 这次重构要解决什么问题 - -### 2.1 当前最直接的问题 - -当前 editor 打开一个引用了 `obj` 模型的 scene 时,慢的不是 scene 文件本身,而是 scene 打开过程中同步触发了原始资源导入。 - -以当前 `project/Assets/Scenes/Backpack.xc` 为例,scene 文件本身很小,但里面的 `MeshFilter` 直接引用了: - -- `Assets/Models/backpack/backpack.obj` - -当前调用链是: - -1. SceneManager 打开 scene -2. Scene 反序列化 GameObject 与 Component -3. MeshFilterComponent 反序列化时,立刻按路径同步 `Load()` -4. MeshLoader 直接用 Assimp 读取原始 `obj` -5. MeshLoader 顺手把模型材质和贴图一起导入 -6. TextureLoader 直接读取并解码原始 `png/jpg` -7. 首帧渲染再把 CPU 资源上传到 GPU - -这意味着当前“打开 scene”其实包含了“首次导入资源”。 - -### 2.2 当前系统的结构性问题 - -当前系统慢,不是某一个函数写得慢,而是系统边界本身就不对。问题包括: - -- 资产身份不稳定。当前 `ResourceGUID` 本质上是路径 hash,不是独立资产 ID。文件改名、移动、目录调整都会导致身份变化。 -- Scene/Component 引用层错误。当前 scene 和组件直接存路径字符串,而不是稳定资产引用。 -- 资源导入和运行时加载混在一起。`IResourceLoader` 现在既承担“从源文件导入”,又承担“运行时拿到资源对象”。 -- 没有持久化导入缓存。当前没有等价于 Unity `Library/Artifacts` 的正式产物目录。 -- 没有源资源数据库。当前没有等价于 Unity `SourceAssetDB` 的资产身份与路径索引层。 -- 没有 artifact 数据库。当前没有等价于 Unity `ArtifactDB` 的导入结果索引层。 -- 没有依赖图。模型依赖哪些贴图、材质、侧车文件,目前没有正式记录。 -- 组件反序列化会立即加载资源。Scene 打开直接卡在主线程。 -- 异步加载器目前基本是空壳,不能承担真正的后台导入与后台加载。 -- 渲染时还有 editor 额外开销,例如 Scene View object-id pass 的首帧资源上传,但这属于次要问题。 - -### 2.3 为什么 Unity 看起来没这么卡 - -Unity 在打开 scene 时,通常不是重新解析 `obj/png/jpg` 源文件,而是: - -- 通过 `.meta` 的 `guid` 识别资产 -- 通过 `Library` 中已经存在的导入产物恢复资源 -- 只有源文件、导入设置、依赖变化时,才重新导入 - -也就是说: - -- Unity 把“导入”提前做了 -- 你当前的 editor 把“导入”塞进了“打开场景” - -这就是 Unity 里大场景不一定很卡,而你这里小 scene 也会卡的根本原因。 - ---- - -## 3. 本地 Unity 项目观察结果 - -本次方案参考了本地 Unity 项目: - -- `D:\Xuanchi\Main\Editor` - -从该项目可以明确观察到以下结构: - -### 3.1 `.meta` 文件 - -Unity 的每个源资源旁边都有 `.meta` 文件,最核心字段是: - -- `guid` -- importer 类型 -- importer 设置 - -例如 `backpack.obj.meta` 中可以看到: - -- `guid` -- `ModelImporter` -- 材质导入模式 -- mesh 导入配置 -- tangent/normal 配置 - -这说明 Unity 的源资源身份和导入配置是放在源资源旁的,而不是临时内存状态。 - -### 3.2 `Library` 的职责分层 - -在本地 Unity 项目里,`Library` 下可以看到: - -- `SourceAssetDB` -- `ArtifactDB` -- `Artifacts` -- `ShaderCache` -- `ScriptAssemblies` -- `StateCache` - -这说明 Unity 的 `Library` 不是一个单独的缓存目录,而是多个子系统缓存的集合。 - -### 3.3 本次只模仿哪一部分 - -本次 XCEngine 初版只模仿 Unity 的“资产导入与缓存”这一部分,不复制以下内容: - -- 脚本编译缓存 -- 包管理缓存 -- build 缓存 -- editor 状态缓存 -- shader 编译缓存 - -本次重点只放在: - -- `Assets` -- `.meta` -- `SourceAssetDB` -- `ArtifactDB` -- `Artifacts` - ---- - -## 4. 重构目标 - -### 4.1 总目标 - -把当前资源系统重构为: - -`源文件系统`、`资产身份系统`、`导入系统`、`artifact 缓存系统`、`运行时资源加载系统`、`场景资产引用系统` - -六层明确分离的正式资产管线。 - -### 4.2 直接目标 - -- 打开 scene 时不再直接读取原始 `obj/png/jpg` -- 首次导入后,后续打开 scene 优先命中 `Library` artifact -- 文件改名/移动后,scene 引用不失效 -- 模型、材质、贴图引用不再依赖路径字符串 -- 模型导入不再把所有外部贴图同步塞进 `Mesh` 对象里 -- 资源导入的失效条件可控、可追踪、可重建 - -### 4.3 长期目标 - -- 为后续 prefab、动画、shader、音频、打包系统留出统一入口 -- 为后续后台导入、导入进度 UI、资产依赖分析、项目迁移工具提供基础设施 -- 为后续 player 构建系统提供统一的“已导入资源来源” - ---- - -## 5. 非目标 - -这次重构的第一版不追求: - -- 100% 复制 Unity 内部数据库格式 -- 立即实现所有资源类型的 importer -- 一步到位做完脚本编译缓存与 ShaderCache -- 一次性改完整个 editor 的所有资产面板功能 -- 一开始就做压缩纹理格式转码、平台分包、远程缓存 - -第一版必须先把“资产导入缓存”这条主链路做对。 - ---- - -## 6. 设计原则 - -### 6.1 资产身份必须独立于路径 - -路径只是定位信息,不应该承担身份。 - -新的系统必须引入独立 `AssetGUID`,并把它落盘到 `.meta`。改名和移动只影响路径索引,不影响资产身份。 - -### 6.2 导入和运行时加载必须分离 - -导入器负责: - -- 读取原始源文件 -- 生成规范化中间产物 -- 记录依赖 -- 生成 artifact - -运行时加载器负责: - -- 读取 artifact -- 创建运行时资源对象 -- 加入内存 cache - -不能再让一个 `Load(path)` 同时承担两件事。 - -### 6.3 Scene 反序列化不能立即触发重型资源导入 - -Scene 打开时应该只恢复引用关系和组件数据,不应在反序列化过程中同步读取大模型和大贴图源文件。 - -### 6.4 运行时资源对象不能再“顺手拥有所有关联资源” - -当前最大的结构性浪费之一是: - -- `Mesh` 导入时直接把材质和贴图都挂进去 -- `Material` 运行时对象也直接持有纹理 handle - -这样会导致: - -- 只需要 mesh 时,材质和贴图也跟着进来 -- 只渲染 base color 时,normal/specular/roughness/ao 也同步加载 - -重构后应改成: - -- `Mesh` 只管 mesh 数据和默认材质引用 -- `Material` 只管材质属性和纹理引用 -- 纹理运行时加载按需触发 - -### 6.5 缓存键必须可重复计算 - -artifact 是否有效,必须由可重复计算的输入决定,而不是靠“我觉得没变”。 - -artifact key 必须包含: - -- 源文件内容 hash -- 导入设置 hash -- importer version -- 依赖文件/依赖资产 hash -- 平台或目标格式 - ---- - -## 7. 新系统的核心概念 - -### 7.1 AssetGUID - -新增稳定资产 ID。 - -定义建议: - -- 128-bit -- 文本形式为 32 位小写十六进制 -- 在 `.meta` 首次生成时随机创建 -- 之后不再因为路径变化而变化 - -注意: - -- 当前 `ResourceGUID` 不是这个概念 -- 当前 `ResourceGUID` 可以暂时保留给运行时 cache 用 -- `AssetGUID` 用于编辑器和资产引用层 - -### 7.2 LocalID - -一个源资产导入后,可能产生多个子资源。 - -例如一个 `obj` 模型可能产生: - -- 主模型资产 -- 一个或多个 `Mesh` -- 一个或多个默认 `Material` -- 内嵌纹理子资产 - -因此需要 `LocalID` 表示“某个资产内部的某个子对象”。 - -定义建议: - -- 64-bit -- 在 importer 内根据“子资源逻辑路径”稳定生成 -- 不依赖内存地址 -- 尽量在重导入后保持稳定 - -### 7.3 AssetRef - -新的统一资产引用结构: - -```text -AssetRef = { assetGuid, localID, resourceType } -``` - -所有 scene、prefab、material、component 最终都应该依赖这个结构,而不是路径字符串。 - -### 7.4 SourceAssetDB - -源资产数据库,用来记录: - -- `AssetGUID -> 源路径` -- `源路径 -> AssetGUID` -- `.meta` 内容 -- importer 类型 -- importer 设置 hash -- 源文件指纹 -- 是否为文件/文件夹 -- 上次导入状态 - -### 7.5 ArtifactKey - -导入产物的内容键。 - -它不是资产 GUID,而是本次“导入结果”的键。 - -同一个 `AssetGUID` 在不同 importer 设置或不同依赖版本下,会产生不同 `ArtifactKey`。 - -### 7.6 ArtifactDB - -导入结果数据库,用来记录: - -- `(AssetGUID, importer signature) -> ArtifactKey` -- `ArtifactKey -> 产物清单` -- `ArtifactKey -> 依赖集合` -- 主子资源映射 -- sub-asset `LocalID -> artifact object` - -### 7.7 Artifact Store - -真正落盘的导入产物目录,对应 Unity 的 `Library/Artifacts`。 - -### 7.8 Importer - -新的导入器不再等于运行时 loader,而是只负责: - -- 从源资源生成 artifact -- 声明依赖 -- 产出 sub-asset 映射 - -### 7.9 Runtime Artifact Loader - -新的运行时资源加载器负责: - -- 从 artifact 读取 `xcmesh/xctex/xcmat/...` -- 创建 `Mesh/Texture/Material` 等运行时对象 -- 写入 `ResourceManager` - ---- - -## 8. 总体架构 - -### 8.1 目标数据流 - -```text -Assets + .meta - -> -Project Asset Scan - -> -SourceAssetDB - -> -Importer Registry - -> -ArtifactDB + Artifact Store - -> -Runtime Artifact Loader - -> -ResourceManager - -> -Scene / Component / Renderer -``` - -### 8.2 目标职责边界 - -#### 源文件层 - -只负责: - -- `Assets` 下的真实源文件 -- 同路径 `.meta` - -#### 编辑器资产数据库层 - -只负责: - -- 维护 `AssetGUID` -- 管理路径与 meta -- 决定是否需要 reimport -- 维护依赖索引 - -#### 导入层 - -只负责: - -- 从源文件读取 -- 生成 artifact -- 写入 `Library` - -#### 运行时资源层 - -只负责: - -- 读取 artifact -- 构造运行时资源对象 -- 做内存 cache - -#### Scene/Component 层 - -只负责: - -- 保存 `AssetRef` -- 请求资源 -- 不直接操作源文件 - ---- - -## 9. 推荐的目录结构 - -建议项目目录最终如下: - -```text -project/ -├── Assets/ -│ ├── Scenes/ -│ ├── Models/ -│ ├── Textures/ -│ ├── Materials/ -│ └── ... + 对应 .meta -├── Library/ -│ ├── SourceAssetDB/ -│ │ ├── assets.json -│ │ ├── guid_index.json -│ │ ├── path_index.json -│ │ └── folders.json -│ ├── ArtifactDB/ -│ │ ├── artifacts.json -│ │ ├── asset_to_artifact.json -│ │ ├── dependency_index.json -│ │ └── subasset_index.json -│ ├── Artifacts/ -│ │ ├── 00/ -│ │ ├── 01/ -│ │ ├── ... -│ │ └── ff/ -│ ├── ImportLogs/ -│ └── CacheVersion.json -├── .xceditor/ -│ └── imgui_layout.ini -└── Project.xcproject -``` - -### 9.1 为什么初版不直接做成 Unity 那种单文件 DB - -Unity 的 `SourceAssetDB` 与 `ArtifactDB` 在本地看起来更像单文件数据库。 - -但 XCEngine 初版建议先做成: - -- 同名目录 -- 多个版本化 JSON 索引文件 - -理由: - -- 易实现 -- 易调试 -- 易人工修复 -- 易写测试 -- 后续可以在不改外部结构的前提下替换为二进制 DB 或 SQLite - -也就是说: - -- 逻辑结构模仿 Unity -- 存储后端先选工程上可落地的版本 - ---- - -## 10. `.meta` 系统设计 - -### 10.1 总体目标 - -每个 `Assets` 内的文件和文件夹都应该有 `.meta`。 - -`.meta` 要承担: - -- 稳定 `AssetGUID` -- importer 类型 -- importer 设置 -- importer 版本 -- 用户数据与扩展字段 - -### 10.2 初版格式建议 - -为了最大程度贴近 Unity,推荐采用“Unity 风格文本 + 严格子集解析器”的方式,而不是纯 JSON。 - -示例: - -```yaml -fileFormatVersion: 1 -guid: 07215647f41c2504abc024d70182ba63 -folderAsset: false -importer: ModelImporter -importerVersion: 1 -userData: -labels: [] -ModelImporter: - materials: - importMode: ImportAsSubAssets - searchMode: LocalDirectory - meshes: - globalScale: 1.0 - generateNormals: FromSource - generateTangents: IfNeeded - importCameras: false - importLights: false - weldVertices: true -``` - -### 10.3 为什么不直接上完整 YAML - -完整 YAML 解析超出当前工程必要范围。 - -初版建议: - -- 文本表现模仿 Unity -- 解析器只支持我们定义的固定字段子集 -- 统一由 editor 写回,尽量不手写任意结构 - -### 10.4 `.meta` 的生成规则 - -- `Assets` 下扫描到新文件/文件夹时,若缺少 `.meta`,自动生成 -- 新生成 `.meta` 时写入随机 `AssetGUID` -- 重复 GUID 检测到时,自动再生并记录错误日志 -- 文件重命名、移动时 `.meta` 跟随文件一起移动 -- 文件夹同样有 `.meta` - ---- - -## 11. 资产数据库设计 - -### 11.1 SourceAssetDB 记录结构 - -建议的 `SourceAssetRecord`: - -```text -guid -relativePath -metaPath -isFolder -importerName -importerVersion -metaHash -sourceHash -sourceFileSize -sourceWriteTime -importSettingsHash -lastKnownArtifactKey -state -``` - -其中: - -- `sourceHash` 用于真正判断内容是否变化 -- `sourceFileSize + sourceWriteTime` 用作快速短路 -- `metaHash` 用于判断 importer 设置变化 - -### 11.2 ArtifactDB 记录结构 - -建议的 `ArtifactRecord`: - -```text -artifactKey -assetGuid -importerName -importerVersion -targetProfile -inputSignatureHash -artifactPath -mainObjectLocalID -subAssets[] -dependencies[] -createdAt -``` - -### 11.3 依赖记录结构 - -依赖应支持两种: - -- 源文件依赖 -- 资产依赖 - -建议字段: - -```text -dependencyKind = SourceFile | Asset -sourcePath / assetGuid -expectedHash -``` - ---- - -## 12. 哈希与键设计 - -### 12.1 AssetGUID - -用途: - -- 标识“这个资产是谁” - -生成方式: - -- 首次生成时随机 UUID 风格 128-bit - -### 12.2 ContentHash - -用途: - -- 标识“这个文件内容是什么” - -建议实现: - -- 引入 `xxHash` 的 `XXH3_128` -- 用于源文件内容 hash、meta 文本 hash、artifact 输入签名 hash - -### 12.3 ArtifactKey - -用途: - -- 标识“这次导入结果是什么” - -建议组成: - -```text -ArtifactKey = Hash128( - importerName, - importerVersion, - assetGuid, - sourceContentHash, - importSettingsHash, - dependencySignatureHash, - targetProfile, - artifactSchemaVersion -) -``` - -### 12.4 Runtime Resource GUID - -当前 `ResourceGUID` 不应再直接由源路径生成。 - -重构后建议: - -- `ResourceGUID` 用于运行时对象 cache -- 由 `artifactKey + localID + resourceType` 生成 - -这样: - -- 资产改名不会改变运行时对象身份 -- 同一 artifact 的 sub-asset 可以稳定寻址 - ---- - -## 13. Artifact Store 设计 - -### 13.1 落盘组织方式 - -建议采用哈希分片目录: - -```text -Library/Artifacts/07/07ab23cd.../ -``` - -目录名是完整 `artifactKey`,前两位做分片。 - -### 13.2 每个 artifact 包内的内容 - -建议不要做成一个不可读 blob,而是一个小型包目录: - -```text -Library/Artifacts/07/07ab23cd.../ -├── artifact.json -├── main.xcasset -├── mesh_1001.xcmesh -├── material_2001.xcmat -├── material_2002.xcmat -└── embedded_texture_3001.xctex -``` - -这样做的原因: - -- 易调试 -- 易增量扩展 -- 易人工清理 -- 适合初版实现 - -### 13.3 artifact.json 内容 - -建议至少包含: - -- `artifactKey` -- `assetGuid` -- `importer` -- `importerVersion` -- `schemaVersion` -- `mainObject` -- `subAssets` -- `dependencies` -- `producedFiles` - ---- - -## 14. Importer 体系重构 - -### 14.1 现有问题 - -当前 `MeshLoader`、`TextureLoader` 直接从源文件创建运行时对象。 - -这导致: - -- 每次首次访问都重复导入 -- 无法形成 Library artifact -- 无法精确 reimport - -### 14.2 新的职责拆分 - -建议把当前体系拆成两类接口: - -#### IAssetImporter - -负责: - -- 从源文件导入到 artifact -- 生成 sub-asset -- 记录依赖 - -#### IArtifactLoader - -负责: - -- 从 artifact 还原为运行时资源对象 - -### 14.3 推荐接口草案 - -```text -IAssetImporter -- GetImporterName() -- GetImporterVersion() -- CanImport(sourcePath, meta) -- Import(request, output) -- GatherDependencies(request, outDeps) -- BuildDefaultMeta() - -IArtifactLoader -- GetResourceType() -- CanLoad(artifactObject) -- LoadFromArtifact(artifactObject, loadContext) -``` - -### 14.4 对现有类的映射 - -建议演进为: - -- `TextureLoader` 拆为 `TextureImporter + TextureArtifactLoader` -- `MeshLoader` 拆为 `ModelImporter + MeshArtifactLoader` -- `MaterialLoader` 拆为 `MaterialImporter + MaterialArtifactLoader` - ---- - -## 15. 各资产类型的初版实现细节 - -## 15.1 Texture 资产 - -### 15.1.1 输入 - -- `.png` -- `.jpg` -- `.jpeg` -- 后续再扩展其它格式 - -### 15.1.2 输出 - -建议输出: - -- `xctex` 二进制文件 -- 可选 `artifact.json` - -### 15.1.3 初版 `xctex` 内容 - -- magic -- schemaVersion -- textureFormat -- width -- height -- mipCount -- colorSpace -- importFlags -- payloadOffset -- payloadSize -- pixel/compressed data - -### 15.1.4 初版策略 - -初版先不做 GPU 压缩纹理缓存,先做: - -- RGBA8_UNORM -- 可选 sRGB 标记 -- 可选 mipmap 预生成 - -先把“只导一次、只读 artifact”做对,再谈 BC 压缩。 - -## 15.2 Material 资产 - -### 15.2.1 当前问题 - -当前 `Material` 运行时对象直接持有纹理 handle,容易在加载材质时把所有纹理一并拉进来。 - -### 15.2.2 重构目标 - -`Material` artifact 应该存: - -- shader 标识 -- render state -- float/int/vector 属性 -- 纹理槽位对应的 `AssetRef` - -而不是直接保存已加载的纹理对象。 - -### 15.2.3 运行时行为 - -运行时 `Material` 应支持: - -- 保存纹理引用 -- 延迟请求实际 `Texture` -- 由渲染管线按需解析纹理 - -这样可以避免打开 scene 时把所有材质贴图全同步加载。 - -## 15.3 Model 资产 - -### 15.3.1 当前问题 - -当前 `MeshLoader` 会: - -- 读取 `obj` -- 生成 mesh -- 导入材质 -- 导入贴图 -- 把材质和贴图一起挂到 `Mesh` - -这会造成过度加载。 - -### 15.3.2 新设计 - -`ModelImporter` 应该把模型导入成一个 artifact 包,内部可包含多个 sub-asset: - -- 主模型对象 -- 一个或多个 `Mesh` sub-asset -- 零个或多个默认 `Material` sub-asset -- 零个或多个内嵌纹理 sub-asset - -外部贴图不应再被复制进入 mesh runtime 对象。 - -### 15.3.3 外部贴图处理 - -如果 `obj/mtl` 引用的是外部贴图文件: - -- 先通过相对路径解析到 `Assets` 内真实资源 -- 找到该贴图的 `AssetGUID` -- 在导出的 `Material` sub-asset 中写入 `AssetRef` -- 不在 model artifact 中嵌入外部贴图像素数据 - -只有模型文件内嵌的纹理,才作为 model 的 sub-asset 输出。 - -### 15.3.4 `xcmesh` 内容建议 - -- header -- vertex buffer layout -- vertex count -- index count -- bounds -- sections -- 默认材质 `AssetRef` -- vertex data blob -- index data blob - -### 15.3.5 mesh runtime 对象要求 - -新的 `Mesh` 运行时对象应该: - -- 持有顶点、索引、section、bounds -- 可选保存默认材质引用 -- 不再持有 `Texture*` -- 不再默认拥有一堆已创建好的 `Material*` - -## 15.4 Scene 资产 - -### 15.4.1 这次不把 Scene 打成必须的二进制 artifact - -初版不要求 scene 一定导成二进制 artifact。 - -原因: - -- 当前性能瓶颈不在 scene 文本解析 -- 瓶颈在 scene 打开时触发原始模型和贴图导入 - -所以初版 scene 可以继续保留 `.xc` 文本格式,但必须改引用方式。 - -### 15.4.2 Scene 引用重构 - -scene 中不应再保存: - -- `mesh=Assets/Models/backpack/backpack.obj` - -而应改成: - -- `meshGuid=` -- `meshLocalID=` - -或者后续再统一为通用 `AssetRef` 文本格式。 - -### 15.4.3 兼容期 - -初版建议保留一段兼容窗口: - -- 读取优先使用 `guid + localID` -- 如果没有,再回退读取旧路径字段 -- 迁移完成后,写出只写新格式 - ---- - -## 16. 组件层重构要求 - -### 16.1 MeshFilterComponent - -当前问题: - -- 反序列化时立刻同步 `Load()` - -新要求: - -- 反序列化只恢复 `AssetRef` -- 不立即导入或加载 -- `GetMesh()` 可做 lazy resolve -- editor 可根据需要同步或异步请求资源 - -### 16.2 MeshRendererComponent - -当前问题: - -- 材质路径字符串 + 反序列化时直接同步加载 - -新要求: - -- 保存 `AssetRef` 列表 -- 反序列化只恢复引用,不立即创建资源 -- 渲染时按需 resolve - -### 16.3 为什么这一步必须做 - -如果不改组件层,即使有 `Library`: - -- scene 打开时仍会同步加载所有资源 -- 只是“从 artifact 同步加载”,但主线程仍会卡 - -所以这次重构不只是 cache 落盘,还必须把组件反序列化改成纯数据恢复。 - ---- - -## 17. Runtime 资源加载重构 - -### 17.1 ResourceManager 的新职责 - -重构后 `ResourceManager` 应只负责: - -- 运行时对象 cache -- artifact object 到 runtime object 的映射 -- 引用计数 -- 卸载策略 - -不再直接负责: - -- 解析 `.obj` -- 解码 `.png/.jpg` -- 决定 asset 是否需要 reimport - -### 17.2 AssetDatabase 与 ResourceManager 的关系 - -建议关系: - -- `AssetDatabase` 负责编辑器世界的资产身份与 artifact 定位 -- `ResourceManager` 负责运行时对象生命周期 - -流程: - -1. Component 持有 `AssetRef` -2. 请求资源时交给 `AssetDatabase` -3. `AssetDatabase` 找到当前有效 artifact object -4. `ResourceManager` 用 `IArtifactLoader` 加载 artifact -5. 缓存 runtime object - -### 17.3 渲染时的按需纹理解析 - -例如 forward pipeline 只需要 `baseColorTexture` 时: - -- 只解析该槽位对应的 `AssetRef` -- 不应自动加载 normal/specular/occlusion - -这一步是解决“模型材质导入过重”的关键。 - ---- - -## 18. 依赖系统设计 - -### 18.1 为什么一定要有依赖系统 - -没有依赖系统,就无法正确回答: - -- `obj` 改了要不要重导? -- `mtl` 改了要不要重导? -- 外部贴图改了要不要重导? -- importer 设置改了要不要重导? -- 导入器版本升级了要不要重导? - -### 18.2 依赖类型 - -初版至少支持: - -- 主源文件依赖 -- 侧车文件依赖 -- 资产依赖 -- importer 版本依赖 - -### 18.3 以 OBJ 为例 - -一个 `backpack.obj` 的导入依赖可能包含: - -- `backpack.obj` -- `backpack.mtl` -- 由 `.mtl` 解析出的外部贴图路径 -- `ModelImporter` 的导入设置 hash -- `ModelImporter` 版本号 - -### 18.4 失效规则 - -以下任一变化应触发 reimport: - -- 源文件内容 hash 变化 -- `.meta` 设置变化 -- importer version 变化 -- 依赖文件内容变化 -- 依赖资产引用变化 -- artifact 文件缺失 -- artifact schema version 不匹配 - ---- - -## 19. 异步导入与异步加载设计 - -### 19.1 当前问题 - -当前 `AsyncLoader` 不是真正的后台导入系统。 - -### 19.2 新的任务模型 - -建议分成两类任务: - -#### Import Task - -负责: - -- 检查 artifact 是否失效 -- 读取源文件 -- 执行 importer -- 写 Library - -#### Runtime Load Task - -负责: - -- 从 artifact 读取 -- 创建运行时对象 - -### 19.3 初版落地策略 - -建议分两阶段: - -#### 第一阶段 - -- 先做正式持久化 Library -- artifact miss 时允许同步 reimport -- 目标是第二次打开同资源时显著加速 - -#### 第二阶段 - -- 引入后台 import 队列 -- scene 打开时只恢复引用并排队资源 -- 用 placeholder 和进度状态代替主线程长阻塞 - -这样风险更低,也更容易调试。 - ---- - -## 20. Editor 侧流程重构 - -### 20.1 打开项目 - -打开项目时应执行: - -1. 确保 `Assets/` 存在 -2. 确保 `Library/` 存在 -3. 扫描 `Assets/` -4. 自动补齐缺失 `.meta` -5. 构建或修复 `SourceAssetDB` -6. 校验 `ArtifactDB` -7. 清理明显损坏或孤立的 artifact 记录 - -### 20.2 新增/导入文件 - -新文件出现时: - -- 自动生成 `.meta` -- 分配 `AssetGUID` -- 进入 `SourceAssetDB` -- 标记为 `NeedsImport` - -### 20.3 重命名/移动文件 - -在 editor 内部重命名/移动时: - -- 文件与 `.meta` 一起移动 -- `AssetGUID` 保持不变 -- 只更新路径索引 -- scene 不需要改引用 - -这是新系统最核心的收益之一。 - -### 20.4 删除文件 - -删除文件时: - -- 从 `SourceAssetDB` 标记删除 -- `ArtifactDB` 中对应 artifact 标记为 orphan -- 后台或手动 GC 清理 artifact 文件 - -### 20.5 Reimport - -需要支持: - -- Reimport 当前资产 -- Reimport All -- 查看导入日志 -- 查看当前 artifact key -- 查看依赖列表 - ---- - -## 21. 场景与序列化迁移方案 - -### 21.1 目标 - -把 scene 与 prefab 从路径引用迁移到 `AssetRef`。 - -### 21.2 迁移策略 - -推荐采用三阶段迁移: - -#### 阶段 A:双读 - -- 读新格式 -- 读不到则回退旧路径格式 - -#### 阶段 B:双写 - -- 新保存写 `guid + localID` -- 可选保留 `pathFallback` - -#### 阶段 C:单写 - -- 正式只写新格式 -- 旧格式仅保留读兼容 - -### 21.3 迁移工具 - -需要提供一个项目级迁移工具: - -- 扫描所有 scene / prefab / material -- 尝试把路径解析成 `AssetGUID` -- 找不到则报错并生成报告 -- 成功后回写新引用 - ---- - -## 22. 对当前 Backpack 问题的直接收益 - -以 `backpack.obj` 为例,重构后流程将变为: - -### 第一次导入 - -- `ModelImporter` 读取 `backpack.obj` -- 解析 `backpack.mtl` -- 为模型生成 `Mesh` artifact -- 为默认材质生成 `Material` sub-asset -- 外部贴图只写成 `AssetRef` -- 贴图由各自的 `TextureImporter` 生成单独 artifact -- 导入结果写入 `Library/Artifacts` - -### 之后打开 scene - -- scene 只恢复 `AssetRef` -- editor 查 `ArtifactDB` -- 直接从 `xcmesh` 读取 mesh -- 材质只恢复引用 -- forward pipeline 只在需要时解析 `baseColorTexture` -- 不再重新解析原始 `obj` -- 不再重新解码所有原始 `jpg/png` - -也就是说: - -- 首次导入仍然可能慢 -- 但第二次及以后,scene 打开不应再把 OBJ/贴图导入当场重做 - ---- - -## 23. 推荐的模块拆分 - -### 23.1 Engine 层新增/调整 - -建议新增或调整以下模块: - -```text -engine/include/XCEngine/Core/Asset/ -├── AssetGUID.h -├── AssetRef.h -├── ArtifactKey.h -├── ArtifactManifest.h -├── IArtifactLoader.h -└── RuntimeAssetResolver.h -``` - -```text -engine/src/Core/Asset/ -├── AssetGUID.cpp -├── AssetRef.cpp -├── ArtifactKey.cpp -├── ArtifactManifest.cpp -└── RuntimeAssetResolver.cpp -``` - -### 23.2 Editor 层新增模块 - -建议新增: - -```text -editor/src/AssetPipeline/ -├── AssetMetaSerializer.h/.cpp -├── SourceAssetDatabase.h/.cpp -├── ArtifactDatabase.h/.cpp -├── ArtifactStore.h/.cpp -├── AssetImporterRegistry.h/.cpp -├── TextureImporter.h/.cpp -├── ModelImporter.h/.cpp -├── MaterialImporter.h/.cpp -├── AssetDependencyScanner.h/.cpp -├── ImportScheduler.h/.cpp -├── ImportLogger.h/.cpp -└── ProjectAssetScanner.h/.cpp -``` - -### 23.3 对现有模块的调整 - -- `ResourceManager` 从“源文件加载器入口”改为“artifact runtime cache” -- `MeshLoader` 拆分 -- `TextureLoader` 拆分 -- `MaterialLoader` 拆分 -- `MeshFilterComponent` 改为存 `AssetRef` -- `MeshRendererComponent` 改为存 `AssetRef` -- `ProjectManager` 扫描时支持 `.meta` -- `Scene` 序列化升级版本 - ---- - -## 24. 分阶段实施计划 - -## 阶段 0:基础类型与工程边界收口 - -目标: - -- 引入 `AssetGUID` -- 引入 `AssetRef` -- 把“路径 hash 充当资产身份”的设计正式停止 - -产出: - -- `AssetGUID` 类型 -- `AssetRef` 类型 -- `ResourceGUID` 与 `AssetGUID` 的职责拆分 - -当前状态: - -- 已完成。 -- 已新增 `AssetGUID` / `AssetRef` 基础类型,并开始接入运行时加载链路。 - -## 阶段 1:`.meta` 与 SourceAssetDB - -目标: - -- 为 `Assets` 中所有文件/文件夹补齐 `.meta` -- 建立 `guid/path/meta/importer` 索引 - -产出: - -- `.meta` 读写器 -- 资产扫描器 -- `SourceAssetDB` -- 资产移动/重命名时 GUID 保持不变 - -当前状态: - -- 已完成。 -- 已实现 `.meta` 自动生成、读取、写回与资产扫描。 -- 已在 `ProjectManager` 中处理 `.meta` sidecar 的删除、移动、重命名与隐藏显示。 - -## 阶段 2:Artifact Store 与 ArtifactDB - -目标: - -- 把导入结果正式落盘进 `Library/Artifacts` -- 用 `ArtifactDB` 记录导入产物和依赖 - -产出: - -- `ArtifactKey` -- `ArtifactDB` -- `ArtifactStore` -- 依赖与失效判断 - -当前状态: - -- 已完成。 -- 已落地 `Library/SourceAssetDB`、`Library/ArtifactDB`、`Library/Artifacts` 三层结构。 -- 已实现基于 `sourceHash/metaHash/importerVersion/size/writeTime` 的失效判断。 - -## 阶段 3:TextureImporter - -目标: - -- 纹理先走通从源文件到 artifact 的闭环 - -产出: - -- `TextureImporter` -- `TextureArtifactLoader` -- `xctex` 格式 - -当前状态: - -- 已完成。 -- 当前纹理资源首次导入时会生成 `xctex`,后续优先从 artifact 读取。 - -## 阶段 4:ModelImporter - -目标: - -- 模型导入改为 artifact 生成,不再 runtime 直接读源 `obj` - -产出: - -- `ModelImporter` -- `xcmesh` 格式 -- 默认材质 sub-asset -- 外部贴图改成 `AssetRef` - -当前状态: - -- 已完成初版。 -- 当前模型首次导入时会生成 `main.xcmesh` 与关联 `texture_N.xctex`。 -- 仍未完成“外部贴图全局去重 + 材质独立 artifact + 真正按引用延迟加载”的最终形态,因此这里只能算阶段 4 初版完成。 - -## 阶段 5:Material 系统 lazy 引用化 - -目标: - -- 材质不再一加载就拉起所有纹理 - -产出: - -- `xcmat` -- 纹理引用延迟解析 -- `Material` runtime 结构调整 - -当前状态: - -- 部分完成。 -- 已补齐材质 artifact 读写所需的 tag/property/texture binding 访问接口。 -- `xcmat` 尚未落地,贴图仍是 eager load,不是最终目标状态。 - -## 阶段 6:Scene / Component 引用迁移 - -目标: - -- 路径引用改成 `guid + localID` - -产出: - -- 新 scene 序列化格式 -- 旧路径兼容读取 -- 迁移工具 - -当前状态: - -- 部分完成。 -- `MeshFilterComponent` 与 `MeshRendererComponent` 已双写 `path + AssetRef`,并保留旧数据兼容。 -- Scene 全量序列化升级、统一迁移工具、其他组件资产引用迁移尚未完成。 - -## 阶段 7:异步导入与异步加载 - -目标: - -- 打开 scene 主线程只恢复引用,不做大规模阻塞导入 - -产出: - -- import task 队列 -- runtime load task 队列 -- placeholder 与状态提示 - -当前状态: - -- 部分完成。 -- `AsyncLoader` 已具备真实 worker thread、pending/completed queue、主线程 completion callback pump。 -- editor `SceneManager` 已在 `LoadScene(...)` 与 `RestoreSceneSnapshot(...)` 路径启用 deferred scene load scope。 -- `MeshFilterComponent` 与 `MeshRendererComponent` 已支持“反序列化只恢复引用并提交异步请求,真正 handle 接管延后到完成回调之后”。 -- 仍未完成通用 import job 系统、统一 placeholder 状态、批量队列调度、更多资源类型的异步恢复,以及失败/进度 UI。 - -## 阶段 8:清理、GC、工具与可视化 - -目标: - -- 系统可维护、可调试、可清理 - -产出: - -- orphan artifact 清理 -- Reimport All -- 依赖图查看 -- 导入日志面板 - -当前状态: - -- 未开始。 - ---- - -## 25. 测试与验证方案 - -### 25.1 单元测试 - -必须补这些测试: - -- `.meta` 缺失时自动生成 GUID -- 重命名/移动后 GUID 保持不变 -- 同一资产不同 importer 设置产生不同 artifact key -- source 改变时 artifact 失效 -- 依赖贴图变化时 material 或 model 重新判定 -- sub-asset localID 稳定性 -- scene 新旧格式兼容读取 - -### 25.2 集成测试 - -必须补这些流程测试: - -- 首次打开 backpack scene 触发导入 -- 第二次打开 backpack scene 不再读取原始 `obj` -- 仅改 `diffuse.jpg` 时,不重新导入无关资产 -- 移动模型文件后 scene 仍能打开 -- 删除 artifact 后系统能够自动重建 - -### 25.3 性能验证 - -必须记录的指标: - -- 首次导入耗时 -- warm 打开 scene 耗时 -- 主线程阻塞时间 -- texture 解码时间 -- model import 时间 -- GPU 首帧上传时间 - -建议加入正式 instrumentation: - -- `Scene open` -- `Asset resolve` -- `Import task` -- `Artifact load` -- `GPU upload` - ---- - -## 26. 风险与注意事项 - -### 26.1 不能再把路径当 GUID - -这是本次重构的底线。如果不先解决这个问题,后面所有缓存都会在重命名/移动后变脆。 - -### 26.2 sub-asset LocalID 稳定性很重要 - -如果 `obj` 重导后 mesh localID 改了,即使 `AssetGUID` 不变,scene 里的引用仍然会断。 - -因此 localID 的生成规则必须: - -- 基于稳定逻辑路径 -- 不依赖运行时顺序 -- 在 importer 内版本化 - -### 26.3 初版不要急着做复杂压缩 - -真正需要优先解决的是: - -- 导入和加载分离 -- Library 持久化 -- scene 引用改成 GUID - -纹理压缩和高级优化可以后做。 - -### 26.4 当前 Preview 缓存可以暂不并入新 Library - -当前 editor 已经有 `.xceditor/thumbs` 预览缓存。 - -初版资产导入缓存系统可以先不动它,后续再考虑统一到 `Library/PreviewCache`。 - -### 26.5 需要预留 schema/version - -以下内容都必须版本化: - -- `.meta` format version -- artifact manifest version -- `xcmesh/xctex/xcmat` format version -- importer version -- project Library cache version - -否则后续升级会非常痛苦。 - ---- - -## 27. 最终预期结果 - -这次重构完成后,系统应达到以下效果: - -- 打开 scene 时,不再直接解析源 `obj/png/jpg` -- 资产改名或移动后,scene 引用仍然稳定 -- 首次导入慢,但只慢一次 -- 后续打开 scene 优先走 `Library` -- `Mesh`、`Material`、`Texture` 的职责边界清晰 -- editor 可以正式支持 Reimport、依赖跟踪、缓存清理 -- 后续扩展 prefab、动画、打包、后台导入时,不需要推倒重来 - -一句话总结: - -这次要做的不是“给现在的 `ResourceManager` 加缓存”,而是把 XCEngine 的资产系统重构成: - -```text -Assets + .meta - -> -SourceAssetDB - -> -Importer - -> -ArtifactDB + Artifacts - -> -Artifact Loader - -> -ResourceManager - -> -Scene / Renderer -``` - -只有这样,当前“打开 scene 时同步导入 obj 模型导致卡顿”的问题,才会被系统性解决,而不是被局部绕开。 diff --git a/docs/used/Unity式SceneView_Gizmo系统完整审查与正式化重构方案_完成归档_2026-04-06.md b/docs/used/Unity式SceneView_Gizmo系统完整审查与正式化重构方案_完成归档_2026-04-06.md deleted file mode 100644 index f4831d16..00000000 --- a/docs/used/Unity式SceneView_Gizmo系统完整审查与正式化重构方案_完成归档_2026-04-06.md +++ /dev/null @@ -1,1048 +0,0 @@ -# Unity式 SceneView Gizmo 系统完整审查与正式化重构方案 - -日期:`2026-04-03` - -相关旧文档: -- `docs/plan/SceneViewport_Overlay_Gizmo_Rework_Plan.md` -- `docs/plan/SceneViewport_Overlay_Gizmo_Rework_Checkpoint_2026-04-02.md` -- `docs/plan/Unity SRP API参考文档.md` - -本文档以当前代码真实状态为准,覆盖: -- `editor` 中 SceneView gizmo / grid / outline / overlay 的现状审查 -- `engine` 与 `editor` 的职责边界重划 -- 一套更接近 Unity SceneView / Gizmos / Handles 思路的正式化重构方案 -- 可分阶段落地、可测试、可验收的迁移计划 - -## 0. 执行结论 - -当前 SceneView 可视化链路已经完成了第一轮“从 ImGui 临时绘制向正式渲染 pass 迁移”的关键基础设施建设,但还没有真正收口。 - -当前最重要的结论有四条: - -1. `engine` 里现有的通用渲染扩展点是对的,应该保留。 - - `RenderPass` - - `RenderPassSequence` - - `CameraRenderRequest::preScenePasses` - - `CameraRenderRequest::postScenePasses` - - `CameraRenderRequest::overlayPasses` - -2. `editor` 里现有的 `SceneViewportEditorOverlayData` 和 `SceneViewportEditorOverlayPass` 方向是对的,应该继续扩展,而不是回退到 ImGui world draw。 - -3. 目前真正的架构问题,不是“某个 gizmo 画得还不够像 Unity”,而是 **SceneView 的 editor 语义仍然泄漏进了 engine 的 builtin postprocess**。 - - `infinite grid` - - `selection outline` - - `outline debug mask` - - 与之绑定的 shader / resource 注册 - -4. 最终正确边界应当是: - - `engine/runtime` 负责“能画什么、怎样插 pass、怎样跑后端” - - `editor/SceneView` 负责“为什么要画、何时画、画哪些 gizmo、哪些对象参与、交互规则是什么” - -换句话说,**Unity-like 的正确做法不是把所有 gizmo 都塞进 runtime engine,而是 runtime 提供绘制能力,editor 拥有 SceneView gizmo 的语义和调度权。** - ---- - -## 1. 当前实现完整审查 - -## 1.1 已经正式化、应保留的部分 - -### A. CameraRenderer 的可扩展 pass 时序已经成立 - -当前 `CameraRenderer` 的执行顺序已经具备正规的扩展缝: - -```text -preScenePasses --> scene geometry --> object id --> postScenePasses --> builtin postprocess --> overlayPasses -``` - -这条缝本身是正确的,说明现在的 renderer 已经不是一个只能硬编码单条流水线的结构,后续 SceneView 的正式化工作不需要推翻它。 - -### B. Editor world overlay 已经有 canonical frame data - -`SceneViewportEditorOverlayData.h` 已经提供了一份比较正确的中间表示: -- `worldLines` -- `worldSprites` -- `screenTriangles` -- `handleRecords` - -这意味着当前系统已经不必再依赖“某个 gizmo 组件自己画、自己 hit test、自己决定屏幕图元”的散乱做法,而是可以朝一份单帧 canonical overlay 数据前进。 - -### C. Editor overlay 已经具备独立 GPU pass - -`SceneViewportEditorOverlayPass` 已经能够在 renderer pass 中绘制: -- world line -- sprite billboard -- screen-space triangle - -这一步非常关键。它说明 camera/light icon、camera frustum、light shape、transform gizmo 等内容已经不需要再走 ImGui world draw 这条临时路径。 - -### D. Scene object picking 已经走 object-id GPU 路线 - -当前对象选中本身已经不是纯 CPU 射线猜测,而是建立在 object id buffer 之上的正式读回流程。这个方向是对的,后续无需推翻。 - -结论: -**当前系统最有价值的资产不是若干单独功能,而是已经出现了一套可继续扩张的“SceneView render extension seam + canonical overlay data + editor overlay pass”。** - ---- - -## 1.2 已经进入正式化,但还没有彻底收口的部分 - -### A. Camera / Light icon、Camera frustum、Directional Light gizmo - -这部分现在已经通过 `SceneViewportOverlayBuilder -> SceneViewportEditorOverlayPass` 进入 GPU overlay 链路,已经不再是纯 ImGui hack。 - -这是正确方向,但当前还存在几个明显问题: -- `SceneViewportOverlayBuilder` 仍然是一个偏“堆逻辑”的单体 builder -- gizmo provider 还没有独立注册体系 -- 灯光 gizmo 的覆盖范围还不完整 -- `PointLight` / `SpotLight` 还没有形成完整正式方案 - -### B. Transform gizmo 的绘制链路已经正规化,但来源还偏临时 - -当前 transform gizmo 的 solver、状态机、屏幕几何、handle records 已经大量进入 canonical overlay 路线,这是好的。 - -但当前仍然有两个收口问题: -- `SceneViewPanel` 仍然参与了 transform gizmo 输入组装与 transient overlay 提交 -- `ViewportHostService` 仍然通过 `SetSceneViewTransientTransformGizmoOverlayData(...)` 注入一份“临时 overlay” - -这说明 transform gizmo 还没有真正变成一套由 SceneView gizmo system 自己统一调度的 provider,而是“正规链路 + 临时桥接层”并存。 - -### C. Orientation gizmo 仍然走 ImGui / HUD 路线 - -这本身不算错误,因为 orientation gizmo 本质上就是 HUD,而不是 world overlay。 - -真正的问题在于: -- 当前 HUD overlay 与 world overlay 的系统边界还没有被正式命名 -- `SceneViewportOverlayRenderer.cpp` 还承担着一个“历史遗留收容层”的角色 - -结论: -**不是所有 gizmo 都必须迁入 GPU world pass。SceneView 右上角 orientation gizmo 属于 HUD,可以继续保留在 editor UI 层,但应该被正式归类为 HUD overlay,而不是继续以“杂项 overlay”存在。** - ---- - -## 1.3 当前最核心的架构问题:SceneView 语义泄漏进了 engine - -### A. grid / outline 不应该继续作为 engine 的 builtin scene semantics 存在 - -当前 `CameraRenderRequest::BuiltinPostProcessRequest` 直接携带: -- `InfiniteGridPassData` -- `selectedObjectIds` -- `ObjectIdOutlineStyle` - -这在工程上能跑,但从架构边界看是不对的。原因很简单: - -- SceneView infinite grid 是编辑器语义,不是 runtime camera 的通用语义 -- Scene selection outline 是编辑器语义,不是 runtime scene 的通用语义 -- `debugSelectionMask` 更是纯 editor 调试语义 - -如果这些字段挂在 engine 的公共 camera request 上,等价于让 runtime camera API 默认理解 SceneView 的编辑器概念。 - -这与 Unity 风格不一致。 - -### B. 具体 editor 效果被注册成 engine builtin shader / builtin resource - -当前 engine builtin resource 中已经出现: -- `builtin://shaders/object-id-outline` -- `builtin://shaders/infinite-grid` - -这意味着: -- runtime 资源表里已经掺入 editor SceneView 专用效果 -- 后续 player/package 很容易继续被这些 editor-only 资源污染 -- engine API 会逐步被 SceneView 需求反向牵着走 - -### C. SceneView 的 post scene policy 目前仍然依赖 engine 内建的固定流程 - -现在 `SceneView` 的 grid / outline 虽然是从 editor 发起,但执行上还是通过 engine builtin postprocess builder 被硬编码规划: -- `SceneInfiniteGrid` -- `SceneSelectionOutline` -- `SceneSelectionMaskDebug` - -这相当于 SceneView 的一部分渲染语义并不真正属于 editor,而是“借用 engine builtin 实现”。 - -短期可用,长期会越来越难维护。 - -结论: -**当前系统最大的结构性缺陷,不是 gizmo 样式,而是 editor 语义与 runtime 语义没有彻底分层。** - ---- - -## 1.4 仍然偏临时的实现点 - -### A. icon 资源加载仍然是 ad hoc 文件路径解析 - -`SceneViewportEditorOverlayPass` 当前直接解析: -- `editor/resources/Icons/camera_gizmo.png` -- `editor/resources/Icons/main_light_gizmo.png` - -问题在于: -- 没有 editor 内建资源注册表 -- 没有生命周期与缓存抽象 -- 没有 icon atlas / versioning / fallback 策略 -- 这条链仍然像“功能先跑起来”的工程写法 - -### B. gizmo provider 没有正式注册点 - -当前 camera/light gizmo 是 `SceneViewportOverlayBuilder` 直接扫描 scene 后构建。 - -这在功能上可行,但有明显扩展隐患: -- 新增 gizmo 类型时会继续堆进单个 builder -- provider 的职责无法单测 -- 组件与 gizmo 的映射关系不清晰 -- 后续 collider / reflection probe / audio source / particle / custom component gizmo 很难优雅接入 - -### C. 视觉图元与交互图元虽然已经接近统一,但规范还不完整 - -现在 `handleRecords` 已经是一个很好的方向,但还缺少更明确的约束: -- 哪些图元负责可见渲染 -- 哪些记录负责 hit test -- 同一 handle 是否允许多份可视 primitive -- 优先级、遮挡、深度模式、拾取扩张半径等是否有统一规则 - -当前这些规则大多“隐含在代码里”,还没有真正上升为系统规范。 - ---- - -## 1.5 测试体系现状 - -当前测试并不差,但仍然没有覆盖到最关键的“正式化边界”。 - -已有较好覆盖: -- grid 数学与相机投影相关测试 -- render flow utils 测试 -- object id picker 测试 -- move / rotate / scale gizmo solver 测试 - -当前缺口: -- `SceneViewportOverlayBuilder` 的 contract 测试不足 -- `SceneViewportEditorOverlayPass` 的资源契约与 primitive 分发测试不足 -- camera / light gizmo provider 行为测试不足 -- SceneView pass 顺序与装配测试不足 -- “editor-only 资源不污染 runtime” 的构建层测试缺失 - -结论: -**当前最大测试缺口不是数学公式,而是架构边界、provider 契约、pass 组合关系。** - ---- - -## 2. Unity 风格的目标架构 - -## 2.1 总体原则 - -参考 Unity 的思路,应当把 SceneView 可视化拆成三层: - -### Layer A:Runtime Render Capability(属于 `engine`) - -负责“能画什么”: -- scene geometry 渲染 -- object id 渲染 -- render pass 扩展点 -- 通用 full-screen / line / sprite / mesh / debug primitive 渲染能力 -- 后端抽象、资源绑定、shader 编译与执行 - -### Layer B:SceneView Gizmo Orchestration(属于 `editor`) - -负责“为什么画、何时画、画哪些”: -- SceneView grid -- selection outline -- camera / light / helper gizmo -- transform gizmo -- Scene icon -- gizmo subset 调度 -- 交互 handle 组织与命中规则 - -### Layer C:HUD Overlay(属于 `editor UI`) - -负责固定在面板屏幕空间的内容: -- orientation gizmo -- toolbar -- 状态提示 -- 调试开关 -- 2D 操作提示与 hover 标签 - -这三层之间的关系应该是: - -```text -engine 提供绘制能力 -editor SceneView 决定 world gizmo / editor pass 计划 -editor UI 决定 HUD 展示 -``` - ---- - -## 2.2 Unity-like 的关键不是“都放 engine”,而是“runtime 与 editor 的语义边界清楚” - -Unity 的本质做法并不是让 player runtime 永远背着 SceneView gizmo 语义。 - -更接近本项目的正确映射应当是: - -- `engine` 对应 Unity 中底层 render context / renderer capability -- `editor` 的 SceneView 系统对应 Unity 中 SceneView 对 gizmos / wire overlay / handles 的调度 -- `HUD` 对应 SceneView 窗口自己的 UI overlay - -因此本项目的最终方案应当遵循: - -1. `engine` 暴露“generic render extension seam” -2. `editor` 组装 SceneView 专属 render plan -3. SceneView 的 gizmo/grid/outline 作为 editor-only feature 实现 -4. runtime/player 构建不再默认携带 SceneView 语义资产 - ---- - -## 2.3 正式目标流水线 - -推荐的 SceneView 渲染时序如下: - -```text -PreScenePasses --> Scene Geometry --> ObjectId Pass --> SceneView Editor PostScene Passes - - Grid - - Selection Outline - - Future: bounds / selection mask visualize / editor-only debug fullscreen pass --> SceneView World Overlay Pass - - Scene icons - - Camera frustum - - Light gizmos - - Transform gizmos - - Future: collider / audio / volume / probe gizmos --> SceneView HUD Overlay - - Orientation gizmo - - Toolbar - - Labels / hints / debug text -``` - -这里面最关键的一点是: - -- `grid` 和 `selection outline` 仍然属于 world-space / scene-view editor pass -- 但它们不再属于 engine 的 builtin camera semantics -- 它们应该变成 editor 自己构建的 post-scene pass chain - ---- - -## 2.4 Gizmo 系统内部应再拆三类数据 - -最终系统不应只有“画什么”,还要清楚地区分三种数据: - -### A. Visual Primitive Data - -纯视觉数据: -- line -- billboard sprite -- mesh / wire mesh -- screen triangle - -### B. Handle Interaction Data - -纯交互数据: -- handle id -- entity id -- pick shape -- priority -- depth / sort rule -- hit thickness / pick expansion - -### C. Gizmo Provider Output - -语义层输出: -- Camera gizmo provider 输出 camera icon + frustum + camera specific handles -- Light gizmo provider 输出 light icon + light shape -- Transform gizmo provider 输出 axis / plane / ring / center handles - -这样做的好处是: -- 视觉可以演进 -- 交互可以演进 -- provider 可以独立测试 -- 不会再把 SceneViewPanel 变成绘制器 + 命中器 + 业务调度器的混合体 - ---- - -## 3. 最终职责边界 - -## 3.1 `engine` 应保留的内容 - -以下内容应明确保留在 `engine`: - -- `RenderPass` / `RenderPassSequence` -- `CameraRenderer` 的 pass 执行骨架 -- scene geometry 渲染 -- object id pass -- render target / state transition / descriptor / backend shader compilation -- 如果未来可复用,则保留“无 editor 语义”的底层 helper - - full-screen quad/blit helper - - line primitive renderer helper - - sprite billboard renderer helper - - debug mesh renderer helper - -核心要求: -**这些能力必须是 generic 的,不能带 SceneView / Gizmo / Editor 的具体业务语义。** - ---- - -## 3.2 `editor` 应拥有的内容 - -以下内容应明确归 `editor`: - -- SceneView grid -- selection outline 及其颜色、宽度、debug mask 策略 -- camera / light scene icon -- camera frustum -- directional / point / spot light gizmo -- transform gizmo -- orientation gizmo -- 组件到 gizmo provider 的注册关系 -- editor-only gizmo shader / icon / material / texture 资源 - -换句话说: -**凡是只有 SceneView 才关心的“显示语义”,最终都必须回收到 `editor`。** - ---- - -## 3.3 可以下沉到 `engine` 的只有“无语义 helper”,不是 SceneView 语义本身 - -例如: -- 如果 full-screen outline 的底层执行流程是通用的,可以提炼成 helper -- 如果 world line / billboard / wire mesh 的 GPU 提交代码是通用的,可以提炼成 helper - -但是不能继续暴露成这样的 engine 公共语义: -- `SceneInfiniteGrid` -- `SceneSelectionOutline` -- `debugSelectionMask` - -原则非常简单: - -**实现可以共享,语义不能泄漏。** - ---- - -## 4. 正式重构方案 - -## 4.1 Phase 1:先把 SceneView pass 规划权完全收回 editor - -### 目标 - -让 `editor` 自己决定 SceneView 需要哪些 pass,而不是继续把 grid / outline 塞进 `engine::BuiltinPostProcessRequest`。 - -### 方案 - -新增 editor 侧的 SceneView render planning 层,例如: -- `SceneViewportRenderPlan` -- `SceneViewportPassChainBuilder` - -由它负责: -- 生成 SceneView 的 `postScenePasses` -- 生成 SceneView 的 `overlayPasses` -- 规划 object id 是否需要 -- 规划 grid / outline / gizmo 的具体顺序 - -### 结果 - -`CameraRenderRequest` 仍然保留: -- `preScenePasses` -- `postScenePasses` -- `overlayPasses` - -但 `BuiltinPostProcessRequest` 不再继续承载 SceneView 专属语义。 - -### 验收标准 - -- `SceneView` 渲染路径不再依赖 `BuiltinPostProcessRequest` 中的 grid / outline 字段 -- SceneView pass 顺序完全由 editor 侧 builder 决定 -- engine 只负责执行 pass,不再理解 SceneView 概念 - ---- - -## 4.2 Phase 2:把 Grid 与 Selection Outline 从 engine builtin 中迁出 - -### 目标 - -完成这次重构中最关键的一刀:**切断 SceneView editor 语义对 engine builtin postprocess 的依赖。** - -### 方案 - -在 `editor/src/Viewport/Passes` 新建 editor-owned passes: -- `SceneViewportGridPass` -- `SceneViewportSelectionOutlinePass` - -如果当前实现代码中有通用部分,可以这样拆: - -- `engine` - - 保留无语义的 GPU helper -- `editor` - - 维护 `SceneViewportGridPassData` - - 维护 `SceneViewportSelectionOutlineSettings` - - 维护 SceneView 下的调用时机与参数策略 - -### 需要同步迁移的内容 - -- `InfiniteGridPassData` 从 engine 公共 camera request 中移除 -- `ObjectIdOutlineStyle` 不再作为 engine camera request 的 editor 语义字段暴露 -- `builtin://shaders/infinite-grid` -- `builtin://shaders/object-id-outline` - -上述资源应迁移为 editor-owned builtin 资源,或至少从注册语义上变为 editor 专属。 - -### 验收标准 - -- runtime build 不再默认依赖 SceneView grid / outline shader -- `CameraRenderRequest` 不再携带 SceneView grid / outline 语义 -- SceneView grid / outline 仍能正常工作,并维持现有视觉效果 - ---- - -## 4.3 Phase 3:建立正式的 Gizmo Provider Registry - -### 目标 - -把当前“一个大 builder 扫全场”的模式,升级为“SceneView 系统调度多个 provider”。 - -### 建议接口 - -```text -ISceneViewportGizmoProvider - Gather(const SceneViewportGizmoBuildContext&, SceneViewportOverlayFrameData&) -``` - -### 建议首批 provider - -- `SceneCameraGizmoProvider` -- `SceneLightGizmoProvider` -- `SceneTransformHandleProvider` -- `SceneSelectionHelperProvider` - -后续可继续扩展: -- collider gizmo provider -- audio source gizmo provider -- reflection probe gizmo provider -- custom editor component gizmo provider - -### 为什么必须这么做 - -因为 Unity-like 的 SceneView 不是“一个巨大 builder 拼所有形状”,而是“editor 根据对象类型和选择状态,调度多种 gizmo provider / handle provider / wire provider”。 - -### 验收标准 - -- `SceneViewportOverlayBuilder` 从单体 builder 退化为 orchestration layer -- 新增 gizmo 类型时,不需要继续往一个 cpp 文件里堆逻辑 -- provider 可以独立单测 - ---- - -## 4.4 Phase 4:把 World Overlay 与 HUD Overlay 正式分家 - -### 目标 - -让系统命名和责任与实际表现一致。 - -### 最终划分 - -#### World Overlay - -使用 renderer pass: -- scene icons -- frustum -- light shapes -- transform gizmos -- future helper volumes - -#### HUD Overlay - -使用 editor UI / ImGui: -- orientation gizmo -- toolbar -- screen label -- 操作提示 - -### 注意 - -这一步不是要求 orientation gizmo 也上 GPU。 - -正确目标是: -- world-space 的内容不再走 ImGui -- HUD 的内容明确归 UI - -### 验收标准 - -- `SceneViewportOverlayRenderer.cpp` 不再承担历史兼容杂项职责 -- 所有 world-anchored overlay 都从统一 world overlay frame 进入 -- HUD overlay 有独立职责命名 - ---- - -## 4.5 Phase 5:建立 editor gizmo 资源体系 - -### 目标 - -消除当前 ad hoc 图标与 shader 资源加载方式。 - -### 方案 - -新增 editor 资源注册层,例如: -- `EditorBuiltinResourceRegistry` -- `EditorGizmoResourceRegistry` - -负责: -- icon texture 注册 -- gizmo shader 注册 -- gizmo material / pipeline preset 注册 -- future atlas / hot reload / fallback - -### 具体要求 - -- 不再在 pass 内部直接写文件路径解析作为长期方案 -- camera / light / future icons 统一纳入 editor builtin 资源 -- gizmo pass 通过资源 key 查询,而不是硬编码本地 png 路径 - -### 验收标准 - -- gizmo 资源加载路径统一 -- 资源生命周期与缓存不再散落在单个 pass 里 -- future 新增 icon / shader 不需要继续复制粘贴路径解析代码 - ---- - -## 4.6 Phase 6:测试体系重构 - -### 目标 - -让测试覆盖系统边界,而不只是覆盖若干数学细节。 - -### 建议补齐的测试类别 - -#### A. Provider contract tests - -例如: -- camera provider 在 camera enabled 时输出 icon + frustum -- disabled camera 不输出 gizmo -- directional / point / spot light 分别输出正确图元组合 - -#### B. Overlay builder orchestration tests - -例如: -- 多 provider 输出能正确合并到单帧 overlay data -- selected / unselected subset 行为正确 -- provider 顺序不影响 hit test 规则 - -#### C. Pass assembly tests - -例如: -- SceneView render plan 能生成正确的 `postScenePasses` -- SceneView world overlay pass 顺序正确 -- object id / outline / gizmo 的依赖关系正确 - -#### D. Editor resource contract tests - -例如: -- icon key 能解析到合法资源 -- 缺失资源时能给出可诊断 fallback - -#### E. Packaging / build boundary tests - -例如: -- player runtime target 不依赖 editor gizmo shader / icon 资源 -- editor target 才链接或注册 SceneView gizmo 资源 - -### 验收标准 - -- 新增 gizmo 类型时,至少有 provider contract test -- 修改 SceneView pass 顺序时,至少有 render plan test 可以兜底 -- editor/runtime 边界错误能通过构建或测试尽早暴露 - ---- - -## 5. 关于 Picking 与 Handle 命中的最终建议 - -## 5.1 Scene object picking - -当前对象选择已经基于 object-id buffer,这条路线应保留。 - -原因: -- 这是正规的 GPU picking -- 它与场景真实渲染结果一致性更好 -- 更适合 editor 里对 mesh / model / future submesh 的对象级选中 - -## 5.2 Gizmo handle picking - -当前 gizmo handle 的命中是基于 canonical `handleRecords` 的 CPU hit test。 - -这不是临时方案,反而是合理的正式方案之一。 - -原因: -- gizmo handle 本身就是高度人工设计的 screen-space / mixed-space 交互图元 -- CPU 命中更容易做 priority、命中扩张、前后景优先级、hover 宽容度 -- 没必要为了“全 GPU 化”把 handles 也做成 object-id pass - -最终建议: -- Scene object picking:继续 GPU object-id -- Gizmo handle picking:继续 CPU hit test,但必须建立在统一 canonical handle 数据之上 - -这与 Unity 的使用体验和工程组织都更接近。 - ---- - -## 6. 对当前代码的具体重构建议 - -## 6.1 立即保留、不要动错的部分 - -- 保留 `CameraRenderer` 的 pass 时序骨架 -- 保留 `overlayPasses` -- 保留 `SceneViewportEditorOverlayData` -- 保留 `SceneViewportEditorOverlayPass` -- 保留 object-id picking - -这些是已经验证正确的基础设施,不应推翻重做。 - -## 6.2 应优先迁出的部分 - -- `BuiltinPostProcessRequest` 中的 SceneView 语义字段 -- `BuiltinInfiniteGridPass` -- `BuiltinObjectIdOutlinePass` -- SceneView 专属 shader 的 engine builtin 注册 - -## 6.3 应逐步拆分的部分 - -- `SceneViewportOverlayBuilder` -- `ViewportHostService` 中 SceneView overlay / transient gizmo 的编排逻辑 -- `SceneViewPanel` 中仍然残留的 gizmo orchestration 代码 - ---- - -## 7. 推荐落地顺序 - -为避免大爆炸式重写,建议严格按下面顺序推进: - -1. 先做 Phase 1 - - editor 拿回 SceneView pass 规划权 - - 不要先拆 gizmo provider - -2. 再做 Phase 2 - - grid / outline 脱离 engine builtin - - 先保证边界正确 - -3. 再做 Phase 3 - - provider registry - - camera / light / transform 分 provider - -4. 再做 Phase 4 - - 正式命名 HUD 与 world overlay 的分层 - -5. 然后做 Phase 5 - - 资源体系 - -6. 最后做 Phase 6 - - 测试与 packaging 收口 - -核心原则: -**不要先推翻正在工作的 overlay pass;应当先纠正语义归属,再做 provider 化和资源化。** - ---- - -## 8. 非目标 - -本轮重构不应顺手扩展成以下主题: - -- render graph 改造 -- gameplay runtime debug draw 系统 -- 把所有 ImGui HUD 都迁到 GPU -- 把 gizmo handle picking 也改成 GPU object-id -- 重写整个 editor UI 框架 - -这些都不是本轮的主目标。 - -本轮的唯一主目标是: -**把 SceneView gizmo / grid / outline 的系统边界、调度方式、资源归属、测试契约一次性做正规。** - ---- - -## 9. 完成态判定标准 - -当以下条件全部满足时,可以认为本轮正式化重构收口: - -1. `engine` 不再公开 SceneView grid / outline 的具体 camera 语义 -2. `editor` 自己构建 SceneView post-scene passes 与 world overlay passes -3. camera / light / transform gizmo 由正式 provider 输出 -4. orientation gizmo 被正式归类为 HUD overlay -5. gizmo 图标和 shader 拥有 editor 侧资源注册体系 -6. player runtime 构建不再依赖 SceneView gizmo 资源 -7. 测试覆盖 provider、render plan、resource boundary 三类关键契约 - -达到这一步后,当前 SceneView gizmo 系统才算真正进入“可持续扩展”的阶段。 - ---- - -## 10. 本文档对应的直接行动建议 - -下一阶段的实施建议如下: - -1. 先开一个“SceneView pass ownership 回收”阶段 - - 目标只做 Phase 1 + Phase 2 - - 不在同一阶段内继续大规模重写 gizmo 样式 - -2. 该阶段完成后,再开“Gizmo Provider Registry”阶段 - - 先把 camera / light provider 正式拆出 - - transform gizmo provider 最后并入 - -3. 再开“Editor Gizmo Resources + Tests 收口”阶段 - - 把图标、shader、测试边界一次性清干净 - -这会比“看到哪改哪、边做边塞进现有 builder”稳定得多。 - ---- - -## 11. 进度更新 2026-04-03 - -本轮已完成“SceneView pass ownership 回收”的最后一个收口步骤: - -1. `engine` 不再注册 SceneView 专属的 `object-id-outline` / `infinite-grid` builtin shader -2. `BuiltinInfiniteGridPass` 与 `BuiltinObjectIdOutlinePass` 改为由调用方注入 shader 路径 -3. SceneView grid / outline shader 已迁移到 `editor/resources/shaders/scene-viewport` -4. `editor` 通过自己的 shader 路径入口组装这两个 pass -5. 已补齐 editor 侧 shader 路径与加载测试,并回归验证 runtime builtin shader 测试 - -对应结论: - -- Phase 1:已完成 -- Phase 2:已完成并收口 -- Phase 3 及后续阶段:仍待继续推进 - ---- - -## 12. 下一阶段执行计划 2026-04-03 - -基于当前真实代码状态,下一阶段不应继续直接堆叠新 gizmo 功能,而应优先完成 `Phase 3: Gizmo Provider Registry`。当前最主要的结构性问题有三处: - -1. `SceneViewportOverlayBuilder` 仍是大单体 - - camera gizmo - - light gizmo - - scene icon / frustum / helper 生成逻辑仍堆在同一处 - -2. `SceneViewPanel` 仍参与 transform gizmo 的 transient overlay 注入 - - 仍通过 `SetSceneViewTransientTransformGizmoOverlayData(...)` 把临时 frame data 注入宿主 - - 这说明 transform gizmo 还没有真正并入正式 provider 体系 - -3. `OrientationGizmo` 仍属于 HUD/ImGui 历史链路 - - 现在功能上可用 - - 但尚未在架构层被正式命名为 HUD overlay - -因此,下一阶段推荐拆成如下 4 个可执行小阶段。 - -### 12.1 Phase 3A:Overlay Provider Registry 落地 - -目标: -- 把“谁负责生成 SceneView world overlay”从 `SceneViewportOverlayBuilder` 中拆出 -- 让 `builder` 退化成聚合器,而不是所有 gizmo 规则的实现者 - -本阶段应新增: -- `ISceneViewportOverlayProvider` -- `SceneViewportOverlayProviderRegistry` -- `SceneViewportOverlayBuildContext` - -本阶段应先拆出的 provider: -- `SceneViewportCameraOverlayProvider` -- `SceneViewportLightOverlayProvider` - -本阶段不应做: -- 不修改 gizmo 样式 -- 不动 transform gizmo 交互算法 -- 不改 orientation gizmo - -完成标志: -- `SceneViewportOverlayBuilder` 不再直接内嵌 camera/light 具体生成逻辑 -- provider 可以按顺序注册并聚合输出 `SceneViewportOverlayFrameData` -- SceneView 现有 camera/light 图标、frustum、directional light helper 视觉结果保持不变 - -建议测试: -- provider registry 装配顺序测试 -- camera provider contract 测试 -- light provider contract 测试 -- 现有 overlay render flow 测试继续通过 - -### 12.2 Phase 3B:Transform Gizmo 并入正式 Provider 体系 - -目标: -- 去掉当前的 transient gizmo overlay 桥接层 -- 让 move / rotate / scale gizmo 和其他 SceneView overlay 一样,成为正式 provider 输出 - -本阶段应处理: -- `SceneViewPanel` 中 gizmo frame 组织逻辑 -- `ViewportHostService` 中的 transient overlay 存储字段 -- `SceneViewportTransformGizmoFrameBuilder` - -建议方向: -- 引入 `SceneViewportTransformGizmoOverlayProvider` -- provider 输入来自统一的 gizmo state / selection state / camera state -- 输出仍然是统一的 `worldLines / screenTriangles / handleRecords` - -本阶段完成后,应删除或废弃: -- `SetSceneViewTransientTransformGizmoOverlayData(...)` -- `m_sceneViewTransientTransformGizmoOverlay` -- `m_sceneViewTransientTransformGizmoInputs` - -完成标志: -- `SceneViewPanel` 不再向宿主注入临时 gizmo overlay 数据 -- `ViewportHostService` 只消费正式 overlay provider 输出 -- move / rotate / scale gizmo 交互行为不回退 - -建议测试: -- move/rotate/scale gizmo provider 输出测试 -- overlay handle hit test 回归测试 -- SceneView render plan 下 gizmo 仍能正常显示与拾取 - -### 12.3 Phase 4:HUD Overlay 与 World Overlay 正式分层 - -目标: -- 把当前“能工作但还没正式命名”的 HUD 链路正规化 -- 明确区分 `world overlay` 与 `HUD overlay` - -本阶段应做: -- 保留 `SceneViewportEditorOverlayPass` 作为 world overlay GPU pass -- 将 `SceneViewportOverlayRenderer` 明确归类为 HUD overlay renderer -- 将 `OrientationGizmo` 明确纳入 HUD overlay 体系 - -本阶段之后的职责边界应为: -- `world overlay` - - scene icon - - camera frustum - - light helper - - transform gizmo -- `HUD overlay` - - orientation gizmo - - Scene toolbar - - 后续 label / hint / debug text - -完成标志: -- world/HUD 两条链路命名清楚 -- orientation gizmo 不再被视为历史遗留杂项 -- 后续 HUD 元素可持续接入,而不再向 world overlay 污染 - -建议测试: -- HUD overlay 独立渲染入口测试 -- SceneView panel 中 HUD/world 顺序与命中关系测试 - -### 12.4 Phase 5:Editor Gizmo 资源体系继续收口 - -目标: -- 继 shader 之后,把 gizmo 相关 editor 资源定位逻辑继续集中 - -本阶段建议新增: -- `SceneViewportResourcePaths` 或等价 editor resource locator - -本阶段应纳入统一入口的内容: -- gizmo shader 路径 -- camera/light icon 路径 -- 后续如果引入 mesh/wire helper 资源,也走同一入口 - -本阶段不要求: -- 不必立刻做 atlas -- 不必引入复杂资源注册中心 - -完成标志: -- panel/pass 内不再到处手写 `exeDir/../../resources/...` -- editor 资源定位逻辑集中,便于后续 packaging 与迁移 - -建议测试: -- 资源路径解析测试 -- working directory 改变后的 editor 资源加载测试 - -### 12.5 Phase 6:测试与打包边界收口 - -目标: -- 把当前已经成型的边界真正测住 - -需要补齐的关键测试类别: - -1. provider contract - - provider 输入什么 - - 必须输出什么 - - 禁止输出什么 - -2. render assembly contract - - SceneView render plan 如何装配 post-scene / overlay / HUD - -3. resource boundary - - editor-only shader / icon 不污染 runtime builtin - - player/runtime 构建不依赖 SceneView 专属资源 - -4. interaction regression - - gizmo handle hit test - - scene icon picking - - HUD 与 world overlay 的点击优先级 - -完成标志: -- 当前 plan 第 9 节中的 7 条完成态判定标准全部可被验证 - -### 12.6 推荐执行顺序 - -严格建议按下面顺序推进,不要跳阶段: - -1. Phase 3A -2. Phase 3B -3. Phase 4 -4. Phase 5 -5. Phase 6 - -原因: -- 先做 provider 化,才能消除 `builder` 与 `SceneViewPanel` 的持续膨胀 -- 先做 transform gizmo provider 化,后面 HUD/world 分层才不会再被临时桥接逻辑牵制 -- 资源与测试应在结构稳定后统一收口,而不是提前做成半成品 - -### 12.7 下一次会话的直接开工点 - -下一次正式实施时,优先执行 `Phase 3A`,并以如下拆分作为第一批提交目标: - -1. 新建 `ISceneViewportOverlayProvider` -2. 新建 `SceneViewportOverlayProviderRegistry` -3. 抽离 `SceneViewportCameraOverlayProvider` -4. 抽离 `SceneViewportLightOverlayProvider` -5. 让 `SceneViewportOverlayBuilder` 改为只负责: - - 构建上下文 - - 调用 provider registry - - 合并 `SceneViewportOverlayFrameData` - -这一步完成后,当前 SceneView gizmo 系统才算真正进入“可持续扩展”的第二阶段。 - ---- - -## 13. 进度更新 2026-04-03 Phase 3A 已完成 - -本次已完成 `Phase 3A: Overlay Provider Registry 落地`,且已经通过代码、测试与 editor 编译验证。 - -已落地内容: - -1. 新增 `SceneViewportOverlayBuildContext` -2. 新增 `ISceneViewportOverlayProvider` -3. 新增 `SceneViewportOverlayProviderRegistry` -4. 新增默认 provider: - - `SceneViewportCameraOverlayProvider` - - `SceneViewportLightOverlayProvider` -5. `SceneViewportOverlayBuilder` 已从大单体逻辑改为 provider 聚合器 -6. `ViewportHostService` 已改为持有 `SceneViewportOverlayBuilder` 实例,而不是依赖静态构建入口 - -本阶段行为保持不变的部分: - -- `BuildSceneViewEditorOverlayContentSignature(...)` 未改 -- `SceneViewPanel` 的 transient transform gizmo 注入链路未改 -- orientation gizmo / HUD 分层未改 -- grid / outline pass 未改 - -新增验证: - -1. `SceneViewportOverlayProviderRegistryTest.AppendsProvidersInRegistrationOrder` -2. `SceneViewportOverlayProviderRegistryTest.CameraProviderBuildsSceneIconAndSelectedFrustum` -3. `SceneViewportOverlayProviderRegistryTest.LightProviderBuildsSceneIconAndSelectedDirectionalHelper` -4. `SceneViewportOverlayProviderRegistryTest.OverlayBuilderUsesDefaultRegistryToAggregateCameraAndLightProviders` - -已执行验证: - -- `cmake --build build --config Debug --target editor_tests -- /p:BuildProjectReferences=false` -- `build/tests/Editor/Debug/editor_tests.exe --gtest_filter=SceneViewportOverlayProviderRegistryTest.*:ViewportRenderFlowUtilsTest.*` -- `cmake --build build --config Debug --target XCEditor` - -阶段结论: - -- Phase 3A 已完成并可作为后续 `Phase 3B: Transform Gizmo 并入正式 Provider 体系` 的稳定起点 -- 当前 `camera/light overlay` 的职责边界已经从单体 builder 中抽离,但 `transform gizmo` 仍然保留在 transient 注入链路,需要下一阶段继续收口 diff --git a/docs/used/Unity式Tick系统与PlayMode运行时方案-阶段进展.md b/docs/used/Unity式Tick系统与PlayMode运行时方案-阶段进展.md deleted file mode 100644 index 29da2573..00000000 --- a/docs/used/Unity式Tick系统与PlayMode运行时方案-阶段进展.md +++ /dev/null @@ -1,62 +0,0 @@ -# Unity式 Tick 系统与 Play Mode 运行时方案阶段进展 - -日期:2026-04-02 - -## 已完成 - -### 阶段 A - -- 已接入 `RuntimeLoop`,统一承载 `FixedUpdate / Update / LateUpdate` -- 已接入 `PlaySessionController` -- 已实现 `Play / Stop` -- Play 时运行 runtime scene clone -- Stop 时恢复 editor scene snapshot -- `Run` 菜单与 `F5` 已可切换 `Play / Stop` - -### 阶段 B 当前收口 - -- 明确区分“文档级编辑”和“运行态场景对象编辑” -- `New/Open/Save Scene` 与 `New/Open/Save Project` 仍只允许在 `Edit` 下执行 -- `Play / Paused` 下允许对 runtime scene 进行对象级编辑与 `Undo / Redo` -- runtime scene 的对象改动默认不再污染场景文档 dirty 状态 - -### 阶段 C 当前收口 - -- 已补全 `Pause / Resume / Step` 的完整请求与状态切换 -- `Run` 菜单现在区分 `Play/Stop`、`Pause/Resume`、`Step` -- `Error Pause` 已接入正式 Pause 请求通道 -- `Paused` 下维持 runtime world,不回退到 editor scene -- `Step` 现在只在 `Paused` 下有效,并保持 `Paused` 状态不变 - -### 阶段 D 当前收口 - -- 已在软件顶部增加独立运行栏 -- 运行栏已接入 `Play / Pause / Step` 三个图标按钮 -- 顶部按钮直接复用现有 PlayMode 请求通道,不额外分叉状态机 -- `Play` 按钮在运行中会保持高亮,再次点击即 `Stop` -- `Pause` 在 `Play / Paused` 下可用,并沿用现有 `F6` -- `Step` 仍只在 `Paused` 下可用 -- 顶部栏已改为参与 dockspace 布局,不再覆盖 Scene / Hierarchy / Inspector 面板标题区 - -## 本轮验证 - -- 已重新执行 `cmake -S . -B build` -- 已通过 `cmake --build build --config Debug --target scene_tests` -- 已通过 `cmake --build build --config Debug --target editor_tests -- /m:1 /v:minimal` -- 已通过 `cmake --build build --config Debug --target XCEditor -- /m:1 /v:minimal` -- 已通过聚焦测试: - `ctest --test-dir build -C Debug --output-on-failure -j1 -R "RuntimeLoopTest|PlaySessionControllerTest|EditorActionRoutingTest.*PlayMode|EditorActionRoutingTest.*MainMenuRouterRequestsPlayPauseResumeAndStepEvents"` - -## 当前语义 - -- `editor tick` 负责托管运行时会话 -- `engine tick` 负责推进 runtime world -- Play 时 `Hierarchy / Inspector / SceneView / GameView` 面对的是同一份 runtime world -- Play 中对对象的改动默认是临时运行态改动,Stop 后回滚 -- Play 中禁止的是文档切换与文档保存,不是禁止观察或编辑 runtime clone - -## 下一阶段建议 - -- 明确 `Paused` 下的 `Undo / Redo / Gizmo / Inspector` 更细粒度交互边界 -- 将 GameView 输入正式接入 runtime input 通道 -- 继续补 `Simulate` 与更完整的 Time 语义 diff --git a/docs/used/Unity式Tick系统与PlayMode运行时方案.md b/docs/used/Unity式Tick系统与PlayMode运行时方案.md deleted file mode 100644 index 1b9d42d8..00000000 --- a/docs/used/Unity式Tick系统与PlayMode运行时方案.md +++ /dev/null @@ -1,1054 +0,0 @@ -# Unity 式 Tick 系统与 Play Mode 运行时方案 - -日期:4.2 - -## 1. 背景与问题定义 - -当前仓库已经具备一批运行时生命周期原语: - -- `Application` 已经能稳定提供 editor 每帧的 `deltaTime` -- `SceneRuntime` 已经具备 `Start / Stop / FixedUpdate / Update / LateUpdate` -- `Scene` 与 `GameObject` 已经能向原生组件继续分发生命周期 -- `ScriptEngine` 已经能把脚本生命周期接到运行时场景上 -- `SceneManager` 已经具备场景序列化、快照恢复与启动场景加载能力 - -因此,当前缺的并不是“有没有 `Update(float dt)` 这个函数”,而是: - -1. editor 模式下,谁来决定什么时候进入运行时 -2. Play 时跑的是哪一份场景 -3. Pause / Step / Stop 的边界由谁维护 -4. editor 自己的帧循环与 engine 运行时循环如何嵌套 -5. 运行时对 scene / input / audio / scripting / viewport 的驱动顺序如何统一 - -如果只是在 editor 每帧里直接调用 `Scene::Update(dt)`,短期看像是“加了 tick”,但长期会立刻引出几个错误方向: - -- 编辑场景被运行时修改污染 -- Stop 后无法恢复编辑状态 -- Hierarchy / Inspector / Undo / Dirty 状态混杂 -- GameView 和 SceneView 看到的不是同一个 runtime world -- Pause / Step 无法建立稳定语义 -- 后续独立 player 无法复用 editor 中临时写死的调度逻辑 - -所以这里必须把“tick”提升为一套 **Unity 式运行时主循环方案**,而不是零散的每帧调用。 - ---- - -## 2. 目标 - -本方案的目标是建立一套与 Unity 语义对齐的运行时驱动体系。 - -### 2.1 功能目标 - -- 明确区分 `editor tick` 与 `engine tick` -- 在 editor 内支持 `Edit / Play / Pause / Step / Simulate` -- Play 时运行的是 **runtime scene 副本**,而不是编辑场景本体 -- 统一驱动 `FixedUpdate / Update / LateUpdate` -- 让 SceneView / GameView / Inspector / Hierarchy 在 Play 中观察同一份 runtime world -- 为未来独立 player 复用同一套 runtime loop 打基础 - -### 2.2 架构目标 - -- 真正的 runtime loop 逻辑应放在 `engine/`,而不是散在 `editor/` -- editor 只负责“托管运行时会话”,不复制第二套引擎 -- scene 文档管理和 runtime 执行管理要严格分层 -- Play/Stop 的数据边界必须显式,不允许隐式回写编辑态 - -### 2.3 非目标 - -本方案第一阶段不解决以下问题: - -- 完整物理系统 -- 完整 Time API,如 `fixedDeltaTime`、`timeScale`、`unscaledDeltaTime` -- 全量输入系统 editor 宿主接线 -- 全量音频系统 editor 生命周期接线 -- Hot Reload / Script Recompile During Play -- Prefab / Domain Reload / Enter Play Mode Options - -这些能力可以在 Tick 体系稳定后继续扩展,但不能反过来阻塞 Tick 方案本身。 - ---- - -## 3. 核心概念定义 - -为了避免后续讨论混淆,本方案先固定术语。 - -### 3.1 editor tick - -`editor tick` 指编辑器自己的每帧更新。 - -它始终存在,只要 editor 窗口还在运行,就会继续执行。它负责: - -- 面板更新 -- 输入焦点与 UI 交互 -- 资源异步刷新 -- viewport 请求与宿主渲染 -- PlaySession 的状态推进 - -它不等价于 gameplay simulation。 - -### 3.2 engine tick - -`engine tick` 指引擎运行时自己的每帧逻辑循环,也就是 Unity 语义里的 PlayerLoop。 - -它只应在以下场景出现: - -- editor 的 `Play` -- editor 的 `Simulate` -- 将来的独立 runtime/player 程序 - -它负责: - -- 固定步长逻辑推进 -- 每帧逻辑推进 -- 脚本生命周期分发 -- 原生组件生命周期分发 -- 与运行时相关的系统刷新 - -### 3.3 runtime scene - -`runtime scene` 指进入 Play/Simulate 后真正被 engine tick 驱动的场景副本。 - -它具备以下特征: - -- 从编辑场景复制而来 -- 允许运行时自由修改 -- Stop 后整体丢弃 -- 不能直接污染编辑场景 - -### 3.4 editor scene - -`editor scene` 指当前用户正在编辑的场景文档。 - -它具备以下特征: - -- 持久化来源是 scene 文件 -- 与 Undo / Dirty / Save 强绑定 -- 在 Edit 模式中由 Inspector / Hierarchy / SceneView 操作 -- Play 期间不应被 runtime 直接修改 - -### 3.5 Play Session - -`Play Session` 指 editor 内一次完整的运行时会话。 - -它应至少覆盖: - -- 进入 Play 前快照编辑态 -- 构造 runtime scene -- 启动 runtime -- 推进 Pause / Step / Stop 状态 -- 退出时恢复编辑态 - ---- - -## 4. Unity 语义下 editor tick 与 engine tick 的关系 - -这一点必须讲清楚,因为这是整个方案的基础。 - -### 4.1 它们不是同一个东西 - -如果它们是同一个东西,那么游戏 Pause 时 editor 也应该跟着停掉。 - -但 Unity 不是这样: - -- Pause 游戏后,Hierarchy 还能选对象 -- Inspector 还能看 runtime 状态 -- SceneView 还能绕相机 -- Console 还能操作 -- 菜单栏和工具栏仍然活着 - -因此,暂停的是 gameplay loop,不是 editor host loop。 - -同理,在本引擎里也应该遵守相同原则: - -- editor tick 是宿主 -- engine tick 是被宿主管理的一段运行时循环 - -### 4.2 正确的嵌套关系 - -正确关系应为: - -`Win32 Loop -> Application::Render -> EditorWorkspace::Update(dt) -> PlaySession::Tick(dt) -> RuntimeLoop::Tick(dt)` - -也就是说: - -- 最外层永远是 editor -- editor 每帧判断当前是不是 Play/Simulate -- 若不是,则不推进 runtime loop -- 若是,则由 editor 在本帧内部托管调用 runtime loop - -### 4.3 三种模式下的关系 - -#### Edit - -- 有 editor tick -- 没有 engine tick -- 当前 active scene 是 editor scene - -#### Play - -- 有 editor tick -- 有 engine tick -- 当前 active scene 是 runtime scene -- SceneView 与 GameView 都观察 runtime world - -#### Simulate - -- 有 editor tick -- 有 engine tick -- 当前 active scene 仍是 runtime scene -- 与 Play 的主要区别不在“场景是否运行”,而在“使用哪种观察与交互语义” - ---- - -## 5. 当前代码状态与缺口归纳 - -### 5.1 当前已经具备的条件 - -#### editor 外层帧循环 - -当前 editor 已有稳定主循环: - -- `Win32EditorHost` 持续调用 `Application::Render` -- `Application::Render` 已计算每帧 `deltaTime` -- `EditorLayer -> EditorWorkspace` 已能接收 `dt` - -这说明 **editor host tick 已存在**。 - -#### 生命周期分发原语 - -当前 engine 已具备: - -- `SceneRuntime::Start / Stop` -- `SceneRuntime::FixedUpdate / Update / LateUpdate` -- `Scene::Update / FixedUpdate / LateUpdate` -- `GameObject::Start / Update / FixedUpdate / LateUpdate` - -这说明 **engine tick 的底层生命周期原语已存在**。 - -#### 场景复制与恢复基础 - -当前 `SceneManager` 已具备: - -- 场景序列化到字符串 -- 基于字符串恢复场景 -- `SceneSnapshot` 抓取与恢复 - -这说明 **runtime scene 副本机制已经有足够基础,不需要从零发明**。 - -### 5.2 当前明确缺失的部分 - -#### 缺失 1:运行时会话控制器 - -当前还没有一个对象负责统一维护: - -- 当前 editor mode -- PlaySession 生命周期 -- runtime scene 的创建与销毁 -- Pause / Step / Stop 状态机 -- fixed timestep accumulator - -#### 缺失 2:runtime scene 与 editor scene 的显式所有权边界 - -当前大多数 editor UI 都通过 `SceneManager::GetScene()` 读取场景。 - -这意味着如果 Play 期间仍然让 `GetScene()` 指向编辑场景,就会直接污染编辑态。 - -#### 缺失 3:真正统一的 runtime tick 调度入口 - -当前 `EditorWorkspace::Update(dt)` 只做: - -- `ResourceManager::UpdateAsyncLoads()` -- `m_panels.UpdateAll(dt)` - -还没有推进: - -- `SceneRuntime` -- fixed-step accumulator -- runtime-specific system flush - -#### 缺失 4:Play/Pause/Stop 事件只有定义,没有闭环 - -当前已有: - -- `PlayModeStartedEvent` -- `PlayModeStoppedEvent` -- `PlayModePausedEvent` - -但缺: - -- 谁发布 Started/Stopped -- 谁在 Pause 后阻止 runtime 更新 -- 谁在 Step 时只推进一帧或一次 fixed step - -#### 缺失 5:脚本后端在 editor 中未完成正式接线 - -当前 `MonoScriptRuntime` 实现已存在,但 editor 初始化路径中没有形成明确的 runtime 安装流程。 - -这意味着即使补上 PlaySession,如果不同时补脚本后端接线,Play 下的脚本生命周期仍可能只落到 `NullScriptRuntime`。 - -#### 缺失 6:输入 / 音频 / 任务系统接线不完整 - -当前代码中虽然有: - -- `InputManager::Update` -- `AudioSystem::Update` -- `TaskSystem::Update` - -但 editor 生命周期中还没有完整初始化和宿主消息接线,因此第一版 Tick 方案必须区分: - -- 哪些系统是架构上应该接入的 -- 哪些系统是当前阶段先预留挂点 - ---- - -## 6. 总体架构方案 - -### 6.1 分层原则 - -本方案采用两层控制: - -1. `engine/` 内的 **RuntimeLoop** -2. `editor/` 内的 **PlaySessionController** - -其职责必须严格区分。 - -### 6.2 engine 层:RuntimeLoop - -建议新增一个 runtime loop 控制器,放在 `engine/Scene` 或 `engine/Runtime` 相关目录下。 - -它负责纯运行时行为,不依赖任何 editor 类型。 - -#### 职责 - -- 持有 `SceneRuntime` -- 持有 `running / paused` 状态 -- 持有 fixed timestep accumulator -- 接受宿主传入的 `dt` -- 统一调度 `FixedUpdate / Update / LateUpdate` -- 提供 `Tick(dt)`、`Pause()`、`Resume()`、`StepOnce()` - -#### 不负责 - -- 场景文件保存 -- editor Undo/Dirty -- 面板 UI -- GameView 工具栏状态 -- 项目切换 - -#### 建议接口 - -```cpp -class RuntimeLoop { -public: - struct Settings { - float fixedDeltaTime = 1.0f / 50.0f; - float maxFrameDeltaTime = 0.1f; - uint32_t maxFixedStepsPerFrame = 4; - }; - - void Start(Components::Scene* scene); - void Stop(); - - void Tick(float deltaTime); - void Pause(); - void Resume(); - void StepFrame(); - - bool IsRunning() const; - bool IsPaused() const; - Components::Scene* GetScene() const; -}; -``` - -### 6.3 editor 层:PlaySessionController - -建议新增 editor 侧运行时会话控制器,例如: - -- `editor/src/Core/PlaySessionController.h` -- 或 `editor/src/Core/EditorRuntimeController.h` - -它负责 editor 与 runtime 的边界管理。 - -#### 职责 - -- 维护 `Edit / Play / Pause / Simulate` 模式 -- 抓取编辑场景快照 -- 创建 runtime scene 副本 -- 切换 `SceneManager` 当前 active scene -- 调用 `RuntimeLoop::Start / Stop / Tick` -- 发布 Play/Pause/Stop 事件 -- Stop 时恢复编辑场景与 dirty 状态 - -#### 不负责 - -- 不实现具体生命周期分发 -- 不直接遍历 `GameObject` -- 不替代 `SceneManager` - -### 6.4 SceneManager 的角色 - -`SceneManager` 仍然是场景文档与当前 active scene 的管理入口。 - -本方案不建议新增第二个并行 scene manager。 - -建议保留单一 active scene 语义,但增加“当前 scene 处于 editor 还是 runtime 模式”的管理逻辑。 - -这样可以保证: - -- Hierarchy 读取的是当前正在展示的 scene -- Inspector 读取的是当前正在运行或编辑的对象 -- SceneView / GameView 都基于同一份 scene - -### 6.5 为什么不让 GameView 单独持有 runtime scene - -这种设计看起来“侵入更小”,但实际会把 editor 撕成两套世界: - -- GameView 看 runtime scene -- Hierarchy / Inspector / SceneView 还看 editor scene - -这与 Unity Play Mode 的真实使用习惯相反,也会带来以下问题: - -- 运行中对象在 GameView 里变了,但 Hierarchy 看不到 -- 选中对象后 Inspector 读到的是编辑态,不是运行态 -- SceneView 无法自然观察 runtime 世界 - -因此第一版正确路线应是: - -**Play 时让 editor 当前 active scene 切到 runtime clone。** - ---- - -## 7. 场景所有权与数据流方案 - -### 7.1 编辑态与运行态必须双轨 - -在任何时刻,editor 都必须知道自己面对的是哪一类 scene: - -- `editor scene` -- `runtime scene` - -运行态和编辑态不能混存于同一份对象图中。 - -### 7.2 进入 Play 的标准流程 - -建议流程如下: - -1. 确认当前有 active scene -2. 抓取 editor scene 快照 -3. 基于快照数据构造新的 runtime scene -4. 暂存当前 selection、dirty、scene path 等 editor 上下文 -5. 将 `SceneManager` 当前 active scene 切到 runtime scene -6. 启动 `RuntimeLoop` -7. 发布 `PlayModeStartedEvent` - -### 7.3 停止 Play 的标准流程 - -建议流程如下: - -1. 调用 `RuntimeLoop::Stop()` -2. 销毁 runtime scene -3. 用 Play 前保存的快照恢复 editor scene -4. 恢复 dirty 状态、scene path、scene name -5. 清理 runtime-only 选择状态 -6. 发布 `PlayModeStoppedEvent` -7. 回到 `Edit` 模式 - -### 7.4 为什么快照恢复是第一版最合适方案 - -当前仓库已有: - -- `SerializeToString` -- `DeserializeFromString` -- `SceneSnapshot` - -这意味着第一版根本不需要发明复杂的“对象级镜像同步层”。 - -对于 Unity 式 Play 行为来说,最符合直觉的方式就是: - -- Play 前冻结编辑态 -- Play 中任 runtime 随便改 -- Stop 后整份 runtime world 丢弃 -- 恢复编辑态 - -这比尝试在 Stop 时“回滚运行时改动”更稳,也更接近 Unity。 - -### 7.5 Dirty 与 Save 语义 - -Play 期间的 runtime 改动不应自动标记编辑场景 dirty。 - -需要明确以下规则: - -- 进入 Play 前,dirty 状态是多少,就记录多少 -- Play 期间 runtime world 的变化不回写 editor dirty -- Stop 后恢复 Play 前 dirty 状态 - -这样可以避免: - -- 只是运行一次游戏,却把场景误判成已修改 -- runtime 中脚本动态生成对象导致 editor scene 被误保存 - ---- - -## 8. 模式状态机设计 - -### 8.1 状态定义 - -建议 editor 模式至少定义如下枚举: - -```cpp -enum class EditorRuntimeMode { - Edit, - Play, - Paused, - Simulate -}; -``` - -第一阶段即使只正式实现 `Edit / Play / Paused`,也建议把 `Simulate` 预留到状态机层,而不是后面另起一套逻辑。 - -### 8.2 状态行为语义 - -#### Edit - -- 当前 active scene 为 editor scene -- 不推进 runtime loop -- 允许完整编辑 - -#### Play - -- 当前 active scene 为 runtime scene -- 推进 runtime loop -- 对高风险编辑行为进行限制 - -#### Paused - -- 当前 active scene 仍为 runtime scene -- 不自动推进 runtime loop -- 允许观察运行时状态 -- 可通过 Step 手动推进 - -#### Simulate - -- 当前 active scene 为 runtime scene -- 推进 runtime loop -- 主要差异体现在观察相机和工具行为 - -### 8.3 状态转换 - -建议只允许以下显式转换: - -- `Edit -> Play` -- `Play -> Paused` -- `Paused -> Play` -- `Play -> Edit` -- `Paused -> Edit` -- `Edit -> Simulate` -- `Simulate -> Edit` - -不建议存在: - -- 隐式自动恢复 -- 运行时错误直接把模式切回 Edit - -运行时错误更合理的做法是: - -- 触发 Pause -- 在 Console 标记错误 -- 保留 runtime world 供观察 - -这与 Unity 的调试体验更接近。 - ---- - -## 9. 主循环与 Tick 顺序方案 - -### 9.1 顶层顺序 - -editor 每帧顺序建议为: - -1. 计算 host `deltaTime` -2. 更新 editor 级输入与宿主状态 -3. 推进 PlaySession -4. 更新面板逻辑 -5. 渲染 SceneView / GameView -6. 提交 ImGui 与窗口交换链 - -其中第 3 步内部若处于 `Play / Simulate`,再调用 runtime loop。 - -### 9.2 RuntimeLoop 的单帧顺序 - -建议 `RuntimeLoop::Tick(dt)` 顺序如下: - -1. 对 `dt` 做上限钳制 -2. 把 `dt` 累加到 fixed accumulator -3. 循环执行若干次 `FixedUpdate(fixedDeltaTime)` -4. 执行一次 `Update(dt)` -5. 执行一次 `LateUpdate(dt)` -6. 运行帧尾系统刷新 - -伪代码如下: - -```cpp -void RuntimeLoop::Tick(float dt) { - if (!m_running || m_scene == nullptr) { - return; - } - - if (m_paused) { - if (!m_stepRequested) { - return; - } - } - - dt = Clamp(dt, 0.0f, m_settings.maxFrameDeltaTime); - m_fixedAccumulator += dt; - - uint32_t fixedSteps = 0; - while (m_fixedAccumulator >= m_settings.fixedDeltaTime && - fixedSteps < m_settings.maxFixedStepsPerFrame) { - m_sceneRuntime.FixedUpdate(m_settings.fixedDeltaTime); - m_fixedAccumulator -= m_settings.fixedDeltaTime; - ++fixedSteps; - } - - m_sceneRuntime.Update(dt); - m_sceneRuntime.LateUpdate(dt); - - m_stepRequested = false; -} -``` - -### 9.3 为什么必须有 fixed accumulator - -如果没有它: - -- `FixedUpdate` 就会退化成“和 `Update` 一样每帧一次” -- Pause / Step 没法建立稳定语义 -- 未来物理系统没有可靠接入点 -- 帧率波动会直接改变 fixed 逻辑行为 - -这与 Unity 模型不一致。 - -### 9.4 为什么要限制每帧最大 fixed 步数 - -这是为了避免“螺旋死亡”: - -- 某帧卡顿导致 `dt` 很大 -- 一次性补太多 `FixedUpdate` -- 补固定步本身又导致更卡 -- 进入恶性循环 - -因此必须有: - -- `maxFrameDeltaTime` -- `maxFixedStepsPerFrame` - -### 9.5 Start 与 Update 的顺序 - -当前 `SceneRuntime::Update` 内部会先进入脚本更新,再进入 scene/gameobject 的 `Start` 与原生 `Update` 分发。 - -这一行为当前已有测试兜底,但后续在形成正式 runtime loop 后,仍建议把 **生命周期顺序作为公开契约** 固化下来。 - -对于第一阶段方案,本文件接受当前行为,后续若要进一步对齐 Unity 的更细语义,可以单独再做生命周期整理,不阻塞 Tick 方案落地。 - ---- - -## 10. 系统接入顺序 - -### 10.1 第一阶段必须接入 - -第一阶段真正必须接入 runtime tick 的系统只有: - -- `SceneRuntime` -- `ScriptEngine`(通过 `SceneRuntime` 间接接入) -- `Scene / GameObject / Component` 生命周期 - -这是形成最小 Unity 式 Play 语义闭环的核心。 - -### 10.2 第一阶段建议预留但不强制打通 - -以下系统建议在 RuntimeLoop 中预留挂点,但可以先不完整接通: - -- `InputManager` -- `AudioSystem` -- `TaskSystem` - -原因不是它们不重要,而是它们当前在 editor 生命周期中还未完整初始化。 - -因此第一阶段合理策略是: - -- 架构上预留顺序和位置 -- 实现上只接已初始化的系统 -- 未初始化时安全跳过 - -### 10.3 建议顺序 - -如果这些系统后续要正式进入 runtime tick,建议顺序为: - -1. 宿主输入消息先进入输入模块 -2. runtime loop 读取上一帧积累的输入状态 -3. 运行 `FixedUpdate` -4. 运行 `Update` -5. 运行 `LateUpdate` -6. 刷新音频 -7. 执行主线程任务队列 -8. 清理本帧输入瞬时态 - -第一阶段若尚未把 editor 消息正式接到引擎输入模块,则不要伪造“已接通输入”。 - ---- - -## 11. Play、Pause、Step 的精确定义 - -### 11.1 Play - -`Play` 的语义不是“开始渲染 GameView”,而是: - -- 构造 runtime scene -- 启动 runtime loop -- 开始推进生命周期 - -GameView 只是 runtime world 的一个观察窗口。 - -### 11.2 Pause - -`Pause` 的语义不是“冻结 editor”,而是: - -- 停止自动推进 runtime loop -- 保留 runtime scene 当前状态 -- 允许用户继续观察运行态对象图 - -这点与 Unity 保持一致。 - -### 11.3 Step - -`Step` 的语义建议定义为: - -- 在 `Paused` 状态下推进 **一帧完整 runtime tick** -- 即允许本次执行固定步补偿、一次 `Update`、一次 `LateUpdate` - -而不是只推进一次 `Update`。 - -原因如下: - -- 更符合 Unity 使用者对 Step 的直觉 -- 对脚本与未来物理更一致 -- 避免“Step 看起来动了,但 FixedUpdate 没跑”的语义分裂 - -### 11.4 Error Pause - -Console 中已有 `Error Pause` 语义雏形。 - -建议正式方案中把它收敛为: - -- 监听运行时错误 -- 当处于 Play 且开启 `Error Pause` 时,自动切换到 `Paused` -- 不自动 Stop - -这更便于排查 runtime 状态。 - ---- - -## 12. 对 editor 各模块的影响 - -### 12.1 SceneManager - -需要增强但不重写。 - -建议新增能力: - -- 进入 runtime scene 的接口 -- 恢复 editor scene 的接口 -- 查询当前 scene 是否为 runtime scene - -不建议让多个模块自行持有 scene 指针并绕开 `SceneManager`。 - -### 12.2 Hierarchy - -Play 时 Hierarchy 应展示 runtime scene 中的对象树。 - -这样才能做到: - -- 运行时动态生成对象可见 -- 运行时销毁对象可见 -- 选中对象后 Inspector 读到的是运行态 - -### 12.3 Inspector - -Play 时 Inspector 应直接观察 runtime object/component 状态。 - -同时需要明确编辑策略: - -- 第一阶段允许查看与轻量编辑 runtime 值 -- 但这些修改默认只作用于 runtime scene -- Stop 后不自动回写 editor scene - -### 12.4 SceneView - -Play 时 SceneView 不应继续盯着 editor scene。 - -它应观察 runtime world,但仍然使用 editor camera 语义。 - -这正是 Unity 中 SceneView 在 Play 时的常见行为。 - -### 12.5 GameView - -GameView 在 Play 时应渲染 runtime scene 中的运行时 camera。 - -这一点当前 viewport host 的基本结构已具备,关键在于它取到的 scene 必须是 runtime clone,而不是 editor scene。 - -### 12.6 Undo / Dirty - -Play 时 runtime 修改默认不进入 editor undo 历史。 - -原因: - -- Unity 风格下,Play 期间改的是 runtime world -- runtime world 本身是临时态 -- 把 runtime 改动混进 editor undo 会破坏编辑语义 - -因此第一阶段建议: - -- `Edit` 模式下:正常 Undo/Dirty -- `Play / Pause / Simulate` 下:对 editor 文档型命令做限制或隔离 - ---- - -## 13. 实现方案细化 - -### 13.1 建议新增类型 - -#### engine 层 - -- `RuntimeLoop` - -#### editor 层 - -- `PlaySessionController` -- `EditorRuntimeMode` -- `PlaySessionState` - -### 13.2 PlaySessionState 建议字段 - -```cpp -struct PlaySessionState { - EditorRuntimeMode mode = EditorRuntimeMode::Edit; - - SceneSnapshot editorSnapshot; - std::unique_ptr runtimeScene; - - bool hasSession = false; - bool stepRequested = false; - - std::string sourceScenePath; - std::string sourceSceneName; - bool sourceSceneDirty = false; -}; -``` - -### 13.3 RuntimeLoop 建议字段 - -```cpp -class RuntimeLoop { -private: - SceneRuntime m_sceneRuntime; - Components::Scene* m_scene = nullptr; - - Settings m_settings = {}; - float m_fixedAccumulator = 0.0f; - - bool m_running = false; - bool m_paused = false; - bool m_stepRequested = false; -}; -``` - -### 13.4 EditorWorkspace 中的挂载点 - -建议在 `EditorWorkspace` 内持有 `PlaySessionController`,而不是在各个 panel 分散维护。 - -理由: - -- `EditorWorkspace` 已是 editor panel 与 context 的调度层 -- 它比单个 panel 更适合承接全局运行态 -- 它比 `Application` 更贴近 editor 业务层,但又不直接陷入 UI 细节 - -建议每帧顺序变为: - -1. 资源异步刷新 -2. `PlaySessionController.Update(dt)` -3. `m_panels.UpdateAll(dt)` - -### 13.5 Application 层的责任变化 - -`Application` 不应直接拥有 runtime scene 或 runtime loop。 - -它只应继续负责: - -- 计算 host `dt` -- 调用 layer / workspace update -- 驱动 viewport 渲染 - -这样未来独立 runtime 程序仍然可以重用 engine runtime loop,而无需依赖 editor 程序对象。 - ---- - -## 14. 分阶段实施建议 - -### 阶段 A:建立 runtime loop 与最小 PlaySession - -目标: - -- 新增 `RuntimeLoop` -- 新增 `PlaySessionController` -- 支持 `Play` 与 `Stop` -- Play 时切到 runtime scene -- Stop 时恢复 editor snapshot - -这一阶段先不要求: - -- Pause / Step UI 完整 -- Simulate 正式可用 -- 输入/音频全接通 - -### 阶段 B:补 Pause / Step / 状态机闭环 - -目标: - -- 正式接 `Paused` -- 正式实现 `Step` -- 完整发布 Started / Paused / Stopped 事件 -- Console 的 `Error Pause` 改为连接正式状态机 - -### 阶段 C:补脚本后端 editor 接线 - -目标: - -- editor 启动时根据配置安装 `MonoScriptRuntime` -- Play 时脚本生命周期真实运行 -- 运行时错误进入 Console - -### 阶段 D:补输入与 GameView 可操作性 - -目标: - -- editor 宿主窗口消息接入引擎输入模块 -- runtime tick 可读取真实输入 -- 基础 `Time.deltaTime` 与输入行为在 Play 中可用 - -### 阶段 E:扩展 Simulate 与更完整 Time 语义 - -目标: - -- `Simulate` 正式与 `Play` 分化 -- 增加 `Time.fixedDeltaTime` -- 增加 `timeScale` -- 为物理系统预留更明确挂点 - ---- - -## 15. 风险与边界 - -### 15.1 最大风险:把 runtime 改动污染 editor scene - -这是整个方案最需要防的风险。 - -只要 runtime scene 与 editor scene 边界不清,就会导致: - -- Stop 后场景数据错乱 -- Undo/Dirty 语义崩塌 -- 用户无法信任 Play 行为 - -因此第一原则必须是: - -**Play 永远运行副本,不直接跑编辑场景。** - -### 15.2 第二风险:在 editor 里复制第二套 runtime 逻辑 - -如果把生命周期调度、输入推进、viewport runtime 判断等逻辑分散写在: - -- GameViewPanel -- SceneViewPanel -- ConsolePanel -- Application - -最终会得到一套 editor 私有 runtime。 - -这会严重偏离“editor 是宿主,不是第二个引擎”的原则。 - -因此真正的 runtime 调度逻辑必须尽可能收敛到 `engine/RuntimeLoop`。 - -### 15.3 第三风险:第一阶段试图一次打通所有系统 - -如果把以下内容强行绑成一个任务: - -- PlaySession -- Mono 正式接线 -- 输入宿主化 -- 音频接线 -- Step -- Simulate -- Time API 扩展 - -实现风险和调试复杂度都会迅速失控。 - -因此应坚持: - -- 第一阶段先形成 `Play/Stop + runtime clone + SceneRuntime tick` 闭环 -- 其他系统按阶段接入 - ---- - -## 16. 测试建议 - -### 16.1 engine 层单测 - -新增或扩展以下测试: - -- `RuntimeLoop` 在 running / paused / step 下的推进行为 -- fixed accumulator 在不同 `dt` 下的补偿行为 -- `maxFixedStepsPerFrame` 的限制行为 -- Stop 后不再继续分发生命周期 - -### 16.2 editor 层单测 - -新增或扩展以下测试: - -- EnterPlay 会创建 runtime scene 副本 -- Stop 会恢复 editor snapshot -- Play 期间 active scene 指向 runtime scene -- Play 期间 runtime 改动不污染 editor snapshot -- Pause 后不自动推进 -- Step 在 Pause 下能推进一帧 - -### 16.3 手工验证清单 - -第一阶段至少手工验证: - -1. 进入 Play 后,脚本或原生组件的运行时移动只影响 Play 中对象 -2. Stop 后场景恢复到 Play 前状态 -3. Play 中动态创建的对象只在 runtime world 中存在 -4. Hierarchy / Inspector / SceneView / GameView 看到的是同一份 runtime world -5. Play 后保存场景不会把 runtime 临时对象保存进去 - ---- - -## 17. 最终建议与结论 - -对于当前仓库,最合理的 `tick` 建设路线不是“在 editor 每帧里直接调一次 `Scene::Update`”,而是: - -1. 在 `engine/` 建立独立的 `RuntimeLoop` -2. 在 `editor/` 建立 `PlaySessionController` -3. Play 时把 active scene 切到 runtime scene 副本 -4. 用 `RuntimeLoop` 统一推进 `FixedUpdate / Update / LateUpdate` -5. Stop 时恢复 editor snapshot - -这条路线的核心收益有三点: - -- 它与 Unity 的 Editor/Player 语义一致 -- 它能保证 editor scene 与 runtime scene 的边界清晰 -- 它为未来独立 runtime/player 复用同一套主循环打下基础 - -简化成一句话就是: - -**editor tick 是宿主循环,engine tick 是运行时循环;在 Unity 式架构下,前者在 Play 时托管后者,但两者绝不能被当成同一个东西。** diff --git a/docs/used/Unity风格模型导入与Model资产架构重构计划_2026-04-10.md b/docs/used/Unity风格模型导入与Model资产架构重构计划_2026-04-10.md deleted file mode 100644 index 8d59635f..00000000 --- a/docs/used/Unity风格模型导入与Model资产架构重构计划_2026-04-10.md +++ /dev/null @@ -1,779 +0,0 @@ -# Unity 风格模型导入与 Model 资产架构重构计划 - -日期:2026-04-10 - -## 1. 文档定位 - -这份计划覆盖 XCEngine 现阶段外部模型资源导入链路的结构性重构,目标不是“再给 `MeshLoader` 多加几个扩展名”,而是把模型资源正式提升为接近 Unity 的 `ModelImporter + ModelAsset` 工作流。 - -本计划默认遵循以下战略判断: - -1. 当前工程已经具备基于 `Assimp` 的基础静态模型读取能力,`.obj/.fbx/.gltf/.glb/.dae/.stl` 都能进入导入链。 -2. 当前主资源仍然是 `Mesh`,这导致导入时会把大量上层语义压扁到静态网格层。 -3. 如果要走 Unity 风格,后续继续在 `Mesh` 主资产上堆功能会越来越别扭,必须先补一层真正的 `Model` 资产。 -4. 骨骼动画不是本轮主目标,但本轮的数据结构和 artifact 设计必须为它预留正式扩展位。 - -本轮计划的本质是: - -1. 把“source model import”和“runtime mesh load”拆开。 -2. 把“外部模型文件的主资产类型”从 `Mesh` 提升到 `Model`。 -3. 把 `.obj/.fbx/...` 的导入体验统一到一条 `ModelImporter` 主链上。 - ---- - -## 2. 当前状态判断 - -基于当前代码结构,可以确认已有能力与缺口如下。 - -### 2.1 已有能力 - -1. `MeshLoader` 已通过 `Assimp` 支持 `.fbx/.obj/.gltf/.glb/.dae/.stl/.xcmesh`。 -2. `AssetDatabase` 已将 `.obj/.fbx/.gltf/.glb/.dae/.stl` 识别为 `ModelImporter`。 -3. 编辑器 `MeshFilter` 的资源选择 UI 已允许 `.fbx/.obj/.gltf/.glb`。 -4. 工程已接入 `assimp-vc143-mt.dll`,说明构建链和基础运行依赖已经建立。 -5. 当前 artifact 管线已经具备 `xcmesh/xcmat/xctex` 的写入与回读能力。 - -### 2.2 当前核心问题 - -1. `ModelImporter` 的主资源类型实际上仍然是 `Mesh`,并不是真正的 `Model`。 -2. 导入时递归遍历 Assimp node,但会把 node transform 烘进顶点,原始层级、局部变换、pivot 语义丢失。 -3. `Mesh` 顶点结构当前仍是静态网格视角,只覆盖 `position/normal/tangent/bitangent/uv0`。 -4. `MeshImportSettings` 中虽然已经出现 `ImportSkinning / ImportAnimations / ImportCameras / ImportLights` 等标志,但没有形成对应的正式数据通路。 -5. `.meta` 当前只写 importer 名称和版本,没有形成 Unity 风格的模型导入设置持久化。 -6. 测试主要覆盖 `obj` 路径,针对真实 `fbx` fixture 的链路验证明显不足。 -7. 拖模型进场景时,现有工作流更接近“给一个 `GameObject` 绑定单个 `Mesh`”,而不是实例化一棵模型层级。 - -### 2.3 当前阶段最重要的判断 - -当前问题不在于“FBX 能不能读进来”,而在于“读进来以后是被当成 `Mesh` 还是被当成 `Model`”。 - -如果继续让外部模型文件直接产出主 `Mesh`: - -1. `OBJ` 还能勉强成立。 -2. `FBX` 会不断丢失层级、pivot、子节点、多部件组织等上层语义。 -3. 后续无论做子资产展开、模型 prefab 化、骨骼动画还是稳定 reimport,都会越来越难。 - ---- - -## 3. 目标架构决策 - -### 3.1 总体决策 - -长期目标不是: - -1. `OBJ -> Mesh` -2. `FBX -> Model` - -而是统一到: - -1. 外部模型格式统一进入 `ModelImporter` -2. `ModelImporter` 的主产物统一是 `Model` -3. `Model` 内部再引用一个或多个 `Mesh/Material/Texture` - -即: - -1. `.obj -> 简化 Model` -2. `.fbx -> 完整 Model` -3. `.gltf/.glb/... -> 同样走 Model 主链` - -这样做的好处是: - -1. 资源系统主线统一。 -2. 编辑器工作流统一。 -3. 子资产稳定引用机制统一。 -4. 骨骼、动画、blend shape、嵌入纹理等后续扩展都有正式落点。 - -### 3.2 `Mesh` 与 `Model` 的职责边界 - -`Mesh` 只回答一件事: - -1. 这个表面怎么画。 - -它应该承载: - -1. 顶点数据 -2. 索引数据 -3. section -4. bounds -5. 材质槽位数量与 section-material 映射 - -`Model` 回答另一件事: - -1. 这个资源整体怎么组织。 - -它应该承载: - -1. 节点层级 -2. 节点局部 TRS -3. 节点名字与路径 -4. 节点绑定的 mesh 引用 -5. 每个 mesh 节点的材质槽绑定 -6. 根节点信息 -7. 导入元数据 -8. 未来的 skeleton / animation / blend shape 扩展位 - -### 3.3 运行时链路的长期形态 - -长期上应当形成三层: - -1. `ModelImporter` - 负责 source file -> artifact graph -2. `ModelLoader` - 负责读取 `xcmodel` -3. `MeshLoader` - 负责读取 `xcmesh` - -其中: - -1. `MeshLoader` 不再承担完整 source scene import 的主责任。 -2. `MeshLoader` 更适合退化成“运行时 mesh artifact 加载器 + builtin mesh loader”。 -3. 所有 source model 的正式导入都应该通过 `ModelImporter` 的专用实现完成。 - ---- - -## 4. 本轮范围与明确不做的内容 - -### 4.1 本轮必须完成 - -1. `Model` 资源类型与 `xcmodel` artifact。 -2. 统一的 `ModelImporter` 主链。 -3. `.meta` 中的 Model Import Settings 持久化。 -4. 保留层级的静态模型导入。 -5. 场景中实例化模型层级的工作流。 -6. 子资产与稳定 `LocalID` 规则。 -7. 编辑器中的模型导入设置 Inspector。 -8. `OBJ/FBX` 静态模型的正式测试闭环。 - -### 4.2 本轮暂不做 - -1. `SkinnedMeshRenderer` 正式渲染链。 -2. 骨骼矩阵上传与 GPU skinning。 -3. 动画 clip runtime 播放系统。 -4. blend shape runtime。 -5. 完整相机/灯光从 `FBX` 到 scene 的自动生成。 -6. 模型子资产在 Project 面板中的完整可展开 UI。 - -### 4.3 本轮只预留不落地的内容 - -1. `Skeleton` 资源类型接口 -2. `AnimationClip` 资源类型接口 -3. `Model` 中 skeleton/animation 的 artifact 扩展位 -4. skinning/blend shape 所需的稳定 source 标识规则 - ---- - -## 5. 目标数据结构设计 - -### 5.1 新增资源类型 - -建议在 `ResourceType` 中正式新增: - -1. `Model` - -同时保留已有: - -1. `Mesh` -2. `Material` -3. `Texture` -4. `AnimationClip` -5. `Skeleton` - -即使 `AnimationClip/Skeleton` 本轮不真正导出,也应在设计上与 `Model` 并列存在。 - -### 5.2 `Model` 资源结构建议 - -`Model` 至少包含以下结构: - -1. `ModelNode` - - `name` - - `parentIndex` - - `firstChildIndex / childCount` 或 child index array - - `localPosition` - - `localRotation` - - `localScale` - - `meshBindingStart` - - `meshBindingCount` -2. `ModelMeshBinding` - - `meshLocalID` - - `materialBindingStart` - - `materialBindingCount` -3. `ModelMaterialBinding` - - `slotIndex` - - `materialLocalID` -4. `ModelImportMetadata` - - importer version - - source file signature - - import settings snapshot - -### 5.3 `Mesh` 资源职责调整 - -`Mesh` 继续作为低层渲染资源保留,但职责要收紧: - -1. 不再把 source model file 当成它的长期主输入。 -2. 主要负责: - - `xcmesh` - - builtin mesh -3. source format 直接读取只作为过渡能力,后续应弱化。 - -### 5.4 `Model` artifact 文件布局 - -建议 artifact 目录布局统一为: - -1. `main.xcmodel` -2. `mesh_0.xcmesh` -3. `mesh_1.xcmesh` -4. `material_0.xcmat` -5. `material_1.xcmat` -6. `texture_0.xctex` -7. `texture_1.xctex` -8. 未来可扩展: - - `skeleton_0.xcskel` - - `anim_0.xcanim` - -### 5.5 `LocalID` 稳定规则 - -这是整个 Unity 风格方案里最关键的一部分。 - -不能简单使用“导入顺序下标”作为长期稳定标识。应当引入基于源语义的稳定 `LocalID` 生成策略: - -1. Node:基于节点路径生成 -2. Mesh:基于节点路径 + mesh name + source mesh index 生成 -3. Material:基于 material name + source material index 生成 -4. Texture:基于 source texture path 或 embedded texture key 生成 -5. 后续骨骼/动画:基于骨骼名、clip 名、source index 生成 - -目标是: - -1. 同一个模型在普通 reimport 后,子资产 `LocalID` 不变。 -2. 场景/prefab/材质槽引用不因为 artifact 目录变化而失效。 - ---- - -## 6. 导入链路重构方案 - -### 6.1 引入专用 source importer - -建议新增专用模块,例如: - -1. `AssimpModelImporter` - -它的职责是: - -1. 读取 `.obj/.fbx/.gltf/.glb/...` -2. 产出统一中间结构 `ImportedModel` -3. 再由 artifact writer 写出 `xcmodel/xcmesh/xcmat/xctex` - -不建议继续把这部分主逻辑堆在 `MeshLoader` 内部。 - -### 6.2 导入中间结构 - -建议先建立 importer 内部中间结构: - -1. `ImportedModel` -2. `ImportedNode` -3. `ImportedMesh` -4. `ImportedMaterial` -5. `ImportedTexture` - -这样可以把: - -1. Assimp scene 读取 -2. 引擎内部资源表示 -3. artifact 写出 - -三者解耦。 - -### 6.3 节点与变换处理原则 - -这是与当前实现最大的差异之一。 - -当前做法是把 `node` 变换乘到顶点上,再把所有几何收进同一个 `Mesh`。本轮要改成: - -1. Mesh 顶点保持在 mesh local space -2. Node local transform 单独存进 `Model` -3. Scene 实例化时再把 node local transform 还原到 `GameObject` - -只有这样才能保留: - -1. 层级 -2. pivot -3. 本地 TRS -4. 后续动画骨骼的基础结构 - -### 6.4 轴系与缩放的处理原则 - -建议明确以下规则: - -1. 轴系转换属于导入标准化步骤,但不能以“摧毁原始层级”为代价。 -2. 全局 import scale 应优先体现在 root 侧或 mesh 侧的单一标准化策略中,不要 mesh 与 node 两边重复施加。 -3. 一旦规则确定,要在 `ModelImportSettings` 中固定,并进入 `metaHash`。 - -本轮必须把“坐标转换在哪里做、缩放在哪里做”写成正式约束,而不是散落在 loader 里。 - -### 6.5 `OBJ` 的导入策略 - -`OBJ` 仍然统一走 `ModelImporter`,但导入结果通常是一个简化模型: - -1. 一个 root node -2. 一个或少量 mesh node -3. 少量 material bindings - -即: - -1. `OBJ` 不是继续作为架构特例保留在 `Mesh` 主链外面 -2. 它只是“语义很简单的 model source” - -### 6.6 `FBX` 的导入策略 - -`FBX` 则要保留完整静态结构: - -1. node hierarchy -2. local transform -3. mesh attachments -4. material slots -5. embedded/external textures -6. 后续可扩展 skeleton/animation metadata - -即使本轮不做动画,也不应该再把 `FBX` 烘成单个主 `Mesh`。 - ---- - -## 7. AssetDatabase 与 ArtifactDatabase 改造计划 - -### 7.1 主资产类型调整 - -当前 `ModelImporter` 的主资产仍是 `Mesh`。本轮要改成: - -1. `ModelImporter` 主资产类型为 `Model` -2. `mainLocalID` 指向 `Model` 主资产 -3. `Mesh/Material/Texture` 成为其子资产 - -### 7.2 `EnsureArtifact` 语义调整 - -对 `Assets/Models/robot.fbx` 执行 `EnsureArtifact(..., ResourceType::Model)` 时: - -1. 返回主 `xcmodel` -2. 内部子资产可通过 `AssetRef(assetGuid, localID, resourceType)` 访问 - -若后续有需要,`MeshFilter` 等组件可直接引用某个 `Mesh` 子资产,而不是引用主 `Model`。 - -### 7.3 依赖追踪调整 - -当前依赖追踪对 `obj -> mtl -> texture` 已有基础覆盖,但本轮要升级成通用模型依赖追踪: - -1. source model file -2. 外部纹理 -3. embedded texture 的派生 key -4. 导入设置 meta -5. future:外部 animation source / skeleton source - -目标是: - -1. 改 texture 会触发模型 reimport -2. 改 importer settings 会触发模型 reimport -3. 子资产 artifact 会稳定重建 - -### 7.4 资源查询接口调整 - -建议新增或调整: - -1. `TryGetAssetRef(path, ResourceType::Model, ...)` -2. `TryGetSubAssetRef(path, subAssetKey or localID, ResourceType::Mesh, ...)` -3. `TryGetPrimaryAssetPath(guid, ...)` 对 `Model` 继续保持主资产语义 - -同时要保证: - -1. 主 `Model` 与子 `Mesh` 的引用路径语义清晰 -2. 编辑器与场景序列化知道自己引用的是主资产还是子资产 - ---- - -## 8. `.meta` 与导入设置计划 - -### 8.1 当前问题 - -当前 `.meta` 只有: - -1. `guid` -2. `folderAsset` -3. `importer` -4. `importerVersion` - -这不足以支撑 Unity 风格模型工作流。 - -### 8.2 新增 `ModelImportSettings` - -建议正式引入或升级为: - -1. `ModelImportSettings` - -至少包括: - -1. `globalScale` -2. `axisConversion` -3. `flipUVs` -4. `flipWindingOrder` -5. `generateNormals` -6. `generateTangents` -7. `importMaterials` -8. `extractEmbeddedTextures` -9. `preserveHierarchy` -10. `mergeStaticMeshes` -11. 预留: - - `importSkinning` - - `importAnimations` - - `importCameras` - - `importLights` - -### 8.3 `.meta` 持久化规则 - -应当把上述设置正式写入 `.meta`,并参与 `metaHash`。 - -目标是: - -1. 切换 importer setting 后 artifact key 会变化 -2. `Reimport` 有明确语义 -3. Inspector 改完设置能稳定反映到导入结果 - -### 8.4 版本策略 - -建议: - -1. `ModelImporter` 单独维护 importer version -2. 当 `xcmodel` schema 或子资产布局变化时,显式提升版本号 -3. 不再使用“整个 AssetDatabase 共用一个笼统版本号”来覆盖全部导入器变化 - ---- - -## 9. 编辑器工作流计划 - -### 9.1 Project 面板语义 - -`.obj/.fbx/.gltf/.glb/...` 在 Project 面板中应统一显示为: - -1. `Model` - -而不是继续让用户默认把这些资源理解成“单个 mesh 文件”。 - -### 9.2 Model Importer Inspector - -选中模型资源时,Inspector 需要出现真正的导入设置面板: - -1. Import Settings -2. Apply -3. Revert -4. Reimport - -首批显示的设置至少包括: - -1. Scale -2. Axis Conversion -3. Preserve Hierarchy -4. Import Materials -5. Generate Normals -6. Generate Tangents -7. Flip UVs - -### 9.3 场景拖拽行为 - -把模型资源拖进场景时,不应只创建一个空物体加一个 `MeshFilter`。 - -推荐行为: - -1. 读取 `Model` -2. 生成对应 `GameObject` 树 -3. 按节点局部变换恢复 TRS -4. 对带 mesh 的节点挂 `MeshFilter + MeshRenderer` -5. 材质槽按 `Model` 内的绑定还原 - -这才接近 Unity 的“拖入模型得到层级实例”。 - -### 9.4 子资产访问 - -本轮不强制要求 Project 面板完整展开子资产树,但至少应保证: - -1. `MeshFilter` 能引用模型产出的某个 `Mesh` 子资产 -2. 场景序列化后引用稳定 -3. 后续补 Project 子资产展开 UI 时不需要推倒重来 - ---- - -## 10. 场景实例化与运行时路径 - -### 10.1 不建议增加 `ModelComponent` - -为了贴近 Unity,建议不要把“整个模型实例”抽成一个新的 `ModelComponent` 挂在单个对象上。 - -更合理的做法是: - -1. `Model` 是资源 -2. “实例化模型”是一个 utility / service -3. 实例化结果是 `GameObject` 层级 - -### 10.2 建议新增实例化工具层 - -可以新增例如: - -1. `ModelInstantiationUtility` -2. `InstantiateModelAsset(...)` - -职责: - -1. 根据 `Model` 创建场景层级 -2. 为每个节点恢复局部 TRS -3. 绑定子 `Mesh` -4. 绑定默认材质或导入材质 - -### 10.3 与现有渲染链的兼容策略 - -现有渲染链以 `MeshFilter + MeshRenderer` 为中心,这一层本轮不应推翻。 - -本轮应当: - -1. 保持 runtime 渲染主链稳定 -2. 通过新的 `Model -> GameObject hierarchy` 实例化路径,把 `Model` 翻译回现有组件体系 - -这样风险最小,且后续加 `SkinnedMeshRenderer` 时也有自然落点。 - ---- - -## 11. 测试与验证计划 - -### 11.1 Fixture 规划 - -必须新增真实模型 fixture,不再只停留在字符串层面的 `CanLoad("test.fbx")`。 - -建议至少准备: - -1. `single_mesh_static.obj` -2. `single_mesh_static.fbx` -3. `multi_node_static.fbx` -4. `multi_material_static.fbx` -5. `embedded_texture_static.fbx` -6. `external_texture_static.fbx` - -### 11.2 Unit Test - -建议新增或扩展: - -1. `ModelLoader` 基础加载测试 -2. `ModelImportSettings` 持久化测试 -3. `ModelArtifact` 写入/回读测试 -4. `LocalID` 稳定性测试 -5. `AssetDatabase` 主资产/子资产引用测试 - -### 11.3 Integration Test - -建议新增: - -1. `FBX -> xcmodel -> scene instantiate` 集成测试 -2. reimport 后 `AssetRef` 稳定性测试 -3. 多节点模型实例化后层级与局部变换恢复测试 -4. 多材质槽绑定测试 - -### 11.4 Editor Test - -建议补: - -1. Project 面板模型类型识别 -2. Model Importer Inspector 设置编辑 -3. Apply/Reimport 行为 -4. 拖模型到场景生成层级 - -### 11.5 验收原则 - -本轮不能以“能读进一个 FBX 文件”作为完成标准。 - -必须同时满足: - -1. artifact 正确 -2. hierarchy 正确 -3. reimport 稳定 -4. editor 工作流成立 - ---- - -## 12. 分阶段执行计划 - -### Phase 1:`Model` 资源与 artifact 基础设施落地 - -目标: - -1. 新增 `ResourceType::Model` -2. 新增 `Model` 类与 `ModelLoader` -3. 新增 `xcmodel` artifact 格式 - -任务: - -1. 设计 `ModelNode / ModelMeshBinding / ModelMaterialBinding` -2. 在 `ArtifactFormats` 中定义 `ModelArtifactHeader` -3. 实现 `WriteModelArtifactFile / LoadModelArtifact` -4. 让 `ResourceManager` 注册 `ModelLoader` - -验收标准: - -1. `xcmodel` 能写出与读回 -2. `Model` 可被 `ResourceManager` 正式加载 - -### Phase 2:source import 主链从 `Mesh` 迁移到 `Model` - -目标: - -1. 建立 `AssimpModelImporter` -2. 将 `.obj/.fbx/...` 的正式导入主链切换到 `Model` - -任务: - -1. 建立 `ImportedModel` 中间结构 -2. 从 Assimp scene 提取节点层级、局部 TRS、mesh、material、texture -3. 停止在导入主链中把 node transform 烘平到单个主 mesh -4. 输出 `main.xcmodel + sub assets` - -验收标准: - -1. `OBJ` 被导入成简化 `Model` -2. `FBX` 被导入成保留 hierarchy 的 `Model` - -### Phase 3:`AssetDatabase` 子资产与稳定 `LocalID` - -目标: - -1. 完成主资产/子资产语义 -2. 建立稳定 `LocalID` 规则 - -任务: - -1. 修改 `ModelImporter` 的主资源类型 -2. 实现模型子资产 `AssetRef` -3. 让 `EnsureArtifact`、`TryGetAssetRef`、序列化链路理解 `Model` 主资产与子 `Mesh` -4. 建立 reimport 稳定性测试 - -验收标准: - -1. reimport 后子资产引用不漂移 -2. 场景中的 mesh 引用可稳定恢复 - -### Phase 4:`.meta` 与 Model Import Settings 正式化 - -目标: - -1. 让模型导入参数进入正式工作流 - -任务: - -1. 定义 `ModelImportSettings` -2. 把 settings 写入 `.meta` -3. 调整 `metaHash` -4. 保证 settings 变化触发 reimport - -验收标准: - -1. 改 scale/axis/material 等设置会稳定影响导入结果 -2. artifact key 与导入设置一致变化 - -### Phase 5:编辑器 Inspector 与场景实例化工作流 - -目标: - -1. 建立 Unity 风格的模型资源使用体验 - -任务: - -1. Project 面板统一把模型文件识别为 `Model` -2. 新增 Model Importer Inspector -3. 实现 `Apply/Reimport` -4. 实现拖模型到场景生成 `GameObject` 层级 - -验收标准: - -1. 从 editor 可完整配置模型导入 -2. 拖入场景后层级与局部变换正确 - -### Phase 6:清理过渡路径与补齐文档测试 - -目标: - -1. 收紧旧路径,避免双轨架构长期并存 - -任务: - -1. 评估并逐步弱化“source file 直接由 `MeshLoader` 作为主入口”的旧路径 -2. 清理命名、文档、注释、测试目录结构 -3. 输出阶段总结 - -验收标准: - -1. 主链路清晰,旧路径只保留必要兼容 -2. 文档与测试同步完成 - ---- - -## 13. 关键风险与应对 - -### 13.1 最大风险:`LocalID` 不稳定 - -风险: - -1. reimport 后子资产重排 -2. 场景与 prefab 引用失效 - -应对: - -1. 在 Phase 1 之前先写清 `LocalID` 规则 -2. 在 Phase 3 前就建立稳定性测试 - -### 13.2 第二风险:轴系与缩放规则前后不一致 - -风险: - -1. 同一模型在 direct load、artifact load、scene instantiate 三条路径下结果不一致 - -应对: - -1. 明确标准化规则只允许一个真值来源 -2. 写入 importer settings 并进入测试 - -### 13.3 第三风险:编辑器体验与底层数据不同步 - -风险: - -1. Inspector 改了设置但 reimport 行为不稳定 -2. Project 面板显示的是 `Model`,内部实际仍按 `Mesh` 走 - -应对: - -1. 先完成 Resource/AssetDatabase 语义,再接 editor UI -2. 不允许 editor 先行伪装完成 - -### 13.4 第四风险:过渡期双轨逻辑长期共存 - -风险: - -1. `MeshLoader` source import 与 `ModelImporter` source import 两套路径并行,长期维护成本失控 - -应对: - -1. 在 Phase 6 明确收紧旧路径 -2. 把 source import 主入口统一到 `ModelImporter` - ---- - -## 14. 本轮完成标志 - -当以下条件同时成立时,本轮才算真正完成: - -1. `.obj/.fbx/.gltf/.glb/...` 的主资产统一为 `Model` -2. `Model` artifact 已正式落地并进入 `ResourceManager` -3. `OBJ` 能导入成简化 `Model` -4. `FBX` 能导入成保留 hierarchy 的静态 `Model` -5. 模型导入设置已写入 `.meta` 并进入 reimport 逻辑 -6. editor 中已具备 Model Importer Inspector -7. 拖模型到场景时会生成 `GameObject` 层级,而非单个烘平 mesh -8. 子资产 `LocalID` 在普通 reimport 下稳定 -9. 测试已覆盖真实 `FBX` fixture 的关键路径 - ---- - -## 15. 一句话结论 - -这一轮不是“给 FBX 补支持”,而是把 XCEngine 的外部模型资源体系从“以 `Mesh` 为主资产的静态导入器”升级成“以 `Model` 为主资产、以子资产和稳定 reimport 为基础、可持续扩展到骨骼动画的 Unity 风格模型工作流”。 diff --git a/docs/used/XCEditor_Dock统一与Tab拖拽停靠收口计划_2026-04-10.md b/docs/used/XCEditor_Dock统一与Tab拖拽停靠收口计划_2026-04-10.md deleted file mode 100644 index 7f468bc1..00000000 --- a/docs/used/XCEditor_Dock统一与Tab拖拽停靠收口计划_2026-04-10.md +++ /dev/null @@ -1,364 +0,0 @@ -# XCEditor Dock统一与Tab拖拽停靠收口计划 - -日期:`2026-04-10` - -## 1. 文档定位 - -这份计划只解决 `XCEditor` 当前 dock 基础层的两个核心问题: - -1. `Hierarchy / Inspector` 与 `Scene / Game / Console / Project` 顶部 chrome 不是同一套。 -2. tab 不能像旧 `editor/` 的 ImGui docking 那样拖拽重排与调整停靠位置。 - -这两个问题本质上是同一个根问题:当前 `DockHost` 的叶子语义和渲染路径没有统一,导致视觉、命中、交互、布局变更都被拆成两套,后续再堆业务面板只会继续在错误基础上加复杂度。 - -本计划属于 `Editor` 基础层收口,不属于业务面板重建计划。 -在这份计划完成前,不继续推进 `new_editor` 的 `Project / Hierarchy / Inspector / Console` 业务重建。 - -## 2. 当前问题与根因 - -## 2.1 当前现象 - -- `Hierarchy / Inspector` 的 header 仍然走 standalone panel 外壳。 -- `Scene / Game / Console / Project` 走 tab stack 外壳。 -- 用户看到的直接结果是:tab 高度、内边距、标题区结构、可交互区域都不一致。 -- 用户尝试拖 tab 时,没有任何重排、合并、分裂停靠反馈。 - -## 2.2 当前真实根因 - -当前实现里同时存在两种叶子形态: - -- `Panel` -- `TabStack` - -这使得 `DockHost` 的整条链路都被拆成两套: - -- 布局输出分叉 -- 绘制分叉 -- hit test 分叉 -- 交互分叉 -- 未来的 docking mutation 也无从统一落点 - -当前代码里的直接证据: - -- `new_editor/app/Shell/ProductShellAsset.cpp` -- `new_editor/include/XCEditor/Shell/UIEditorDockHost.h` -- `new_editor/src/Shell/UIEditorDockHost.cpp` -- `new_editor/include/XCEditor/Shell/UIEditorDockHostInteraction.h` -- `new_editor/src/Shell/UIEditorDockHostInteraction.cpp` -- `new_editor/include/XCEditor/Collections/UIEditorTabStripInteraction.h` -- `new_editor/src/Collections/UIEditorTabStripInteraction.cpp` -- `new_editor/include/XCEditor/Shell/UIEditorWorkspaceController.h` -- `new_editor/src/Shell/UIEditorWorkspaceController.cpp` -- `new_editor/src/Shell/UIEditorWorkspaceLayoutPersistence.cpp` - -当前控制器层只具备: - -- `OpenPanel` -- `ClosePanel` -- `ShowPanel` -- `HidePanel` -- `ActivatePanel` -- `ResetWorkspace` -- `SetSplitRatio` - -还没有真正支撑 docking 的命令与树变更能力: - -- `ReorderTab` -- `MoveTabToStack` -- `DockTabRelative` -- `ExtractTab` -- `MergeTabStack` - -## 3. 改造目标 - -本轮收口后的目标状态如下: - -1. 运行态叶子节点只保留一种语义:`TabStack`。 -2. 单面板也被视为“只有一个 tab 的 tab stack”。 -3. `DockHost` 只保留一套统一的 `DockHeader / TabWell / ContentBody` 视觉结构。 -4. `Hierarchy / Inspector` 与 `Scene / Game / Console / Project` 必须走同一套 header/tab primitive。 -5. tab 至少支持: - - 同 stack 重排 - - 跨 stack 合并 - - 向目标 leaf 的 `left/right/top/bottom/center` 停靠 -6. 拖拽过程中必须有明确 preview,不允许“拖了但没有反馈”。 -7. workspace 布局持久化必须覆盖新结构,并兼容旧布局升级。 -8. 在 `tests/UI/Editor` 中补齐单元测试与集成测试后,才允许继续推进业务面板。 - -## 4. 范围边界 - -## 4.1 本轮必须做 - -- `XCEditor` 的 dock 叶子语义统一 -- dock chrome 统一 -- tab 拖拽状态机 -- docking drop target 与 preview overlay -- workspace tree surgery -- layout persistence 升级 -- `tests/UI/Editor` 的回归补齐 - -## 4.2 本轮明确不做 - -- 多原生窗口 detached docking -- 浮动窗口系统 -- 业务面板内部复杂逻辑重建 -- `Runtime UI` 相关能力 -- 为 `Editor` 再做一套资源化主题体系 - -`Editor` 当前继续采用固定代码样式,不走 UI 资源那套。 - -## 5. 目标结构 - -## 5.1 统一后的 workspace 叶子模型 - -运行态只保留: - -- `SplitNode` -- `TabStackNode` - -其中 `TabStackNode` 内部包含: - -- `tabs` -- `selectedTabId` -- `activePanelId` - -不再保留“裸 `Panel` 叶子节点”这种视觉语义。 - -## 5.2 统一后的 dock 布局输出 - -`DockHost` 最终应面向统一 leaf 输出: - -- `tabWellRect` -- `contentBodyRect` -- `selectedPanelId` -- `tabItems` -- `dropPreview` - -不再分成: - -- `panelLayouts` -- `tabStackLayouts` - -两套平行分支。 - -## 5.3 统一后的交互分层 - -交互链路收口为: - -1. `UIEditorTabStripInteraction` - - 负责 tab press / armed / drag / release / cancel -2. `UIEditorDockHostInteraction` - - 负责把 tab 拖拽映射为 docking preview 与 docking command -3. `UIEditorWorkspaceController` - - 负责真正修改 workspace tree -4. `UIEditorWorkspaceLayoutPersistence` - - 负责新旧布局读写与升级 - -## 6. 分阶段执行计划 - -## Phase A:统一叶子语义 - -### 目标 - -把 workspace 运行态的叶子语义统一到 `TabStackNode`,彻底消除 standalone panel 叶子。 - -### 任务 - -- 调整 `UIEditorWorkspaceModel`,明确叶子节点只允许 `TabStackNode`。 -- 调整 `ProductShellAsset` 与默认 workspace 构建逻辑,让单面板默认也生成单-tab stack。 -- 调整 `UIEditorWorkspaceSession`,保证 active/selected 状态都围绕 tab stack 工作。 -- 调整 `UIEditorWorkspaceLayoutPersistence`,读旧格式时自动升级为统一 leaf 结构。 -- 增加 degenerate tree cleanup: - - 空 stack 删除 - - 单子 split 折叠 - - 非法 selectedTab 修正 - -### 完成标准 - -- 运行态 workspace 中不再出现 standalone panel 叶子。 -- 旧布局能被自动升级并正常显示。 - -## Phase B:统一 dock chrome 与布局输出 - -### 目标 - -让所有 panel 顶部都走同一套 dock header / tab well primitive。 - -### 任务 - -- 重构 `UIEditorDockHost` 的 layout 输出结构,改为统一 leaf 布局。 -- 删除 standalone panel 与 tab stack 的渲染分叉。 -- `UIEditorPanelFrame` 缩回“边框与 body 外壳”职责,不再自带独立 header 风格。 -- `UIEditorTabStrip` 成为唯一 tab/header 渲染入口。 -- `UIEditorPanelContentHost` 只根据统一 leaf layout 定位 panel body。 -- 统一 hit target 语义,保证 header/tab/body 命中都来自同一套结构。 - -### 完成标准 - -- `Hierarchy / Inspector / Scene / Game / Console / Project` 顶部结构完全统一。 -- tab 高度、padding、命中区域、激活态样式只由一套 metric/palette 控制。 - -## Phase C:同 stack tab 拖拽重排 - -### 目标 - -先把最小闭环做稳:同一个 stack 内 tab 可拖拽重排。 - -### 任务 - -- 在 `UIEditorTabStripInteraction` 增加拖拽状态机: - - `pressed` - - `armed` - - `dragging` - - `cancelled` - - `committed` -- 增加 drag threshold、pointer capture、Esc cancel、release commit。 -- 在同 stack 内计算插入位置与 preview insertion marker。 -- 在 `UIEditorWorkspaceController` 增加 `ReorderTab(...)`。 -- 保证 selected/active/focus 状态在重排后仍然正确。 - -### 完成标准 - -- 同一 tab strip 内可拖拽重排。 -- 有明确插入线 preview。 -- 无闪烁、无丢焦、无错误激活。 - -## Phase D:跨 stack 合并与 split docking - -### 目标 - -把 tab 从“只能在本组重排”扩展到“可跨组移动并形成新停靠布局”。 - -### 任务 - -- 在 `DockHostInteraction` 中引入 drop target 解析: - - `center` - - `left` - - `right` - - `top` - - `bottom` - - `root empty area` -- 增加 preview overlay: - - center merge 预览 - - edge split 高亮预览 - - 同 stack reorder 线性预览 -- 在 `WorkspaceController` 中增加: - - `MoveTabToStack(...)` - - `DockTabRelative(...)` - - `ExtractTab(...)` - - `MergeTabStack(...)` -- 增加 tree surgery: - - 从源 stack 抽 tab - - 插入目标 stack - - 围绕目标 leaf 生成 split - - 空 stack 清理 - - 单子 split 折叠 - -### 完成标准 - -- tab 可从一个 stack 拖到另一个 stack。 -- 可通过 `left/right/top/bottom/center` 形成新布局。 -- 交互有实时 preview。 - -## Phase E:持久化、回归与测试体系补齐 - -### 目标 - -把新 docking 结构正式纳入 `tests/UI/Editor` 的标准回归。 - -### 任务 - -- 更新 layout serialization,稳定写回统一 leaf 结构。 -- 旧布局读取时自动升级。 -- 补齐 `tests/UI/Editor/unit`: - - 旧布局升级 - - 同 stack reorder - - 跨 stack merge - - split docking - - cleanup collapse - - active/selected 同步 - - persistence round-trip -- 补齐 `tests/UI/Editor/integration`: - - `shell/dock_header_unified` - - `shell/dock_tab_reorder_same_stack` - - `shell/dock_tab_move_between_stacks` - - `shell/dock_tab_split_targets` - - `state/dock_layout_persistence` -- 每个集成测试 exe 顶部写清楚当前验证目标,不再只写模糊状态文本。 - -### 完成标准 - -- `tests/UI/Editor` 对新 docking 具备完整回归入口。 -- 后续业务面板接入时,不需要再倒回头修基础 docking 架构。 - -## 7. 代码落点 - -本轮预计主要改动路径: - -- `new_editor/include/XCEditor/Shell/UIEditorWorkspaceModel.h` -- `new_editor/src/Shell/UIEditorWorkspaceModel.cpp` -- `new_editor/include/XCEditor/Shell/UIEditorWorkspaceController.h` -- `new_editor/src/Shell/UIEditorWorkspaceController.cpp` -- `new_editor/include/XCEditor/Shell/UIEditorWorkspaceLayoutPersistence.h` -- `new_editor/src/Shell/UIEditorWorkspaceLayoutPersistence.cpp` -- `new_editor/include/XCEditor/Shell/UIEditorWorkspaceSession.h` -- `new_editor/src/Shell/UIEditorWorkspaceSession.cpp` -- `new_editor/include/XCEditor/Shell/UIEditorDockHost.h` -- `new_editor/src/Shell/UIEditorDockHost.cpp` -- `new_editor/include/XCEditor/Shell/UIEditorDockHostInteraction.h` -- `new_editor/src/Shell/UIEditorDockHostInteraction.cpp` -- `new_editor/include/XCEditor/Collections/UIEditorTabStripInteraction.h` -- `new_editor/src/Collections/UIEditorTabStripInteraction.cpp` -- `new_editor/include/XCEditor/Shell/UIEditorPanelFrame.h` -- `new_editor/src/Shell/UIEditorPanelFrame.cpp` -- `new_editor/include/XCEditor/Shell/UIEditorPanelContentHost.h` -- `new_editor/src/Shell/UIEditorPanelContentHost.cpp` -- `new_editor/app/Shell/ProductShellAsset.cpp` -- `tests/UI/Editor/unit/*dock*` -- `tests/UI/Editor/unit/*workspace*` -- `tests/UI/Editor/integration/shell/*dock*` -- `tests/UI/Editor/integration/state/layout_persistence/*` - -## 8. 并行拆分建议 - -这轮改造可以拆成 4 条可并行子线,但必须按主从顺序集成: - -1. `WorkspaceModel / Controller / Persistence` -2. `DockHost layout / render / hit-test 统一` -3. `TabStripInteraction / DockHostInteraction` 拖拽状态机 -4. `tests/UI/Editor` 单元与集成回归补齐 - -并行原则: - -- 先以 `Phase A` 的统一 leaf 语义为总前提。 -- `Phase B` 与 `Phase C` 可以在统一模型落定后并行推进。 -- `Phase E` 可以在 `Phase C / D` 功能接口稳定后并行补齐。 - -## 9. 风险与控制 - -## 9.1 最大风险 - -- 旧布局兼容被打断 -- 统一叶子后现有 panel body 定位错位 -- 拖拽状态机与 focus/capture 冲突 -- split tree surgery 产生退化树或悬空 active panel - -## 9.2 控制方式 - -- 先做模型统一,再做交互,不反过来堆补丁 -- 每个 mutation 先补 `unit` 再补 `integration` -- 任何旧布局兼容问题都在 persistence 层处理,不在渲染层写兼容分叉 -- 任何单面板特殊视觉都回收为单-tab stack,不再恢复 standalone panel 路径 - -## 10. 收口判定 - -只有同时满足以下条件,才算这轮 dock 基础层真正收口: - -1. 所有 dock 叶子统一为 `TabStackNode`。 -2. `Hierarchy / Inspector` 与 `Scene / Game / Console / Project` 头部视觉完全统一。 -3. tab 可同组重排、跨组合并、左右上下停靠。 -4. 拖拽过程有稳定 preview,释放后结果正确。 -5. 布局可持久化,旧布局可升级。 -6. `tests/UI/Editor/unit + integration` 已覆盖关键回归。 - -在这 6 条完成前,不进入下一轮业务面板重建。 diff --git a/docs/used/XCEngine渲染引擎架构设计.md b/docs/used/XCEngine渲染引擎架构设计.md deleted file mode 100644 index faff46f4..00000000 --- a/docs/used/XCEngine渲染引擎架构设计.md +++ /dev/null @@ -1,4564 +0,0 @@ -# XCEngine - 渲染引擎架构设计文档 - -> **借鉴 Unity 渲染架构概念设计** - ---- - -## 渲染流程图 - -``` -┌─────────────────────────────────────────────────────────────────────────────────────┐ -│ 渲染流程 │ -└─────────────────────────────────────────────────────────────────────────────────────┘ - -┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ -│ Scene │────▶│ Culling │────▶│ RenderQueue │────▶│ Renderer │ -│ 场景数据 │ │ System │ │ 渲染队列 │ │ 渲染器 │ -└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ - │ │ │ - ▼ │ ▼ - ┌─────────────┐ │ ┌─────────────┐ - │CullingResults│◀──────────┘ │ CommandList │ - │ 剔除结果 │ PrepareRender │ 命令列表 │ - └─────────────┘ QueueEntries └─────────────┘ - │ - ▼ -┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ -│ Camera │────▶│ LightManager│────▶│ Pass │────▶│ GPU │ -│ 相机 │ │ 光照管理 │ │ 渲染通道 │ │ GPU执行 │ -└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ - │ │ - ▼ ▼ - ┌─────────────┐ ┌─────────────┐ - │ ShadowAtlas │ │ Material │ - │ 阴影图集 │ │ 材质 │ - └─────────────┘ └─────────────┘ - -流程说明: -1. Scene 提供场景数据(GameObjects, Components) -2. CullingSystem 执行视锥剔除,产出 CullingResults -3. RenderQueue 根据 CullingResults 准备渲染项(Renderer::PrepareRenderQueueEntries) -4. Renderer 遍历 RenderQueue,通过 CommandList 提交绘制命令 -5. LightManager/ShadowAtlas 处理光照和阴影 -6. 最终 GPU 执行渲染命令 -``` - -``` -┌─────────────────────────────────────────────────────────────────────────────────────┐ -│ RHI 抽象层架构 │ -└─────────────────────────────────────────────────────────────────────────────────────┘ - - ┌─────────────────┐ - │ RHISystem │ 渲染系统入口 - └────────┬────────┘ - │ - ┌───────────────────────┼───────────────────────┐ - │ │ │ - ┌──────▼──────┐ ┌──────▼──────┐ ┌──────▼──────┐ - │ D3D12Device │ │ D3D11Device │ │ VulkanDevice │ - └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ - │ │ │ - └───────────────────────┼───────────────────────┘ - │ - ┌────────▼────────┐ - │ IRHIDevice │ 抽象设备接口 - └────────┬────────┘ - │ - ┌──────────────────────────────────┼──────────────────────────────────┐ - │ │ │ │ │ │ │ - ┌────▼────┐ ┌───▼────┐ ┌────▼┐ ┌──▼────┐ ┌───▼───┐ ┌───▼────┐ ┌──▼────┐ - │CommandQueue│ │Descriptor│ │Fence│ │SwapChain│ │RootSig│ │PSO │ │Texture│ - │ 命令队列 │ │Heap │ │围栏 │ │交换链 │ │根签名 │ │管线状态│ │纹理 │ - └──────────┘ └────────┘ └─────┘ └────────┘ └───────┘ └───────┘ └───────┘ -``` - -> **重要声明**:本架构借鉴 Unity 渲染系统的核心概念与设计模式,包括: -> - `CullingResults` / `CullingSystem` - 剔除系统(概念来自Unity SRP API) -> - `RenderPipeline` / `RenderPipelineManager` - 渲染管线 -> - `RenderQueue` / `RenderQueueEntry` - 渲染队列 -> - `Renderer` - 渲染器核心 -> - `ScriptableRenderContext` - 渲染上下文 -> - `Shader` / `Pass` / `Material` - 着色器与材质 -> - `LightManager` / `ShadowAtlas` - 光照与阴影管理 -> -> **注**:Unity底层C++渲染架构未公开,本设计基于公开API概念与通用渲染引擎模式实现。 -> -> 版本: 1.0 -> 日期: 2026-03-13 -> 目标: 构建专业级实时体积渲染引擎 - ---- - -## 第一章 核心基础层 - -### 1.1 数学库 (Math Library) - -```cpp -namespace XCEngine { -namespace Math { - -struct Vector2 { - float x, y; - - static Vector2 Zero() { return Vector2{0, 0}; } - static Vector2 One() { return Vector2{1, 1}; } - static Vector2 Up() { return Vector2{0, 1}; } - static Vector2 Down() { return Vector2{0, -1}; } - static Vector2 Right() { return Vector2{1, 0}; } - static Vector2 Left() { return Vector2{-1, 0}; } - - static float Dot(const Vector2& a, const Vector2& b); - static float Cross(const Vector2& a, const Vector2& b); - static Vector2 Normalize(const Vector2& v); - static float Magnitude(const Vector2& v); - static Vector2 Lerp(const Vector2& a, const Vector2& b, float t); -}; - -struct Vector3 { - float x, y, z; - - static Vector3 Zero() { return Vector3{0, 0, 0}; } - static Vector3 One() { return Vector3{1, 1, 1}; } - static Vector3 Forward() { return Vector3{0, 0, 1}; } - static Vector3 Back() { return Vector3{0, 0, -1}; } - static Vector3 Up() { return Vector3{0, 1, 0}; } - static Vector3 Down() { return Vector3{0, -1, 0}; } - static Vector3 Right() { return Vector3{1, 0, 0}; } - static Vector3 Left() { return Vector3{-1, 0, 0}; } - - static Vector3 Cross(const Vector3& a, const Vector3& b); - static float Dot(const Vector3& a, const Vector3& b); - static Vector3 Normalize(const Vector3& v); - static float Magnitude(const Vector3& v); - static float SqrMagnitude(const Vector3& v); - static Vector3 Lerp(const Vector3& a, const Vector3& b, float t); - static Vector3 MoveTowards(const Vector3& current, const Vector3& target, float maxDistance); -}; - -struct Vector4 { float x, y, z, w; }; - -struct Matrix3x3 { - float m[3][3]; - - static Matrix3x3 Identity() { - Matrix3x3 result{}; - result.m[0][0] = 1.0f; result.m[1][1] = 1.0f; result.m[2][2] = 1.0f; - return result; - } - - static Matrix3x3 Zero() { - Matrix3x3 result{}; - return result; - } - - Matrix3x3 operator*(const Matrix3x3& other) const; - Vector3 operator*(const Vector3& v) const; - Matrix3x3 Transpose() const; - Matrix3x3 Inverse() const; - float Determinant() const; -}; - -struct Matrix4x4 { - float m[4][4]; - - static Matrix4x4 Identity() { - Matrix4x4 result{}; - result.m[0][0] = 1.0f; result.m[1][1] = 1.0f; - result.m[2][2] = 1.0f; result.m[3][3] = 1.0f; - return result; - } - - static Matrix4x4 Zero() { - Matrix4x4 result{}; - return result; - } - - static Matrix4x4 Translation(const Vector3& v); - static Matrix4x4 Rotation(const Quaternion& q); - static Matrix4x4 Scale(const Vector3& v); - static Matrix4x4 TRS(const Vector3& translation, const Quaternion& rotation, const Vector3& scale); - static Matrix4x4 LookAt(const Vector3& eye, const Vector3& target, const Vector3& up); - static Matrix4x4 Perspective(float fov, float aspect, float near, float far); - static Matrix4x4 Orthographic(float left, float right, float bottom, float top, float near, float far); - - Matrix4x4 operator*(const Matrix4x4& other) const; - Vector4 operator*(const Vector4& v) const; - Vector3 MultiplyPoint(const Vector3& v) const; - Vector3 MultiplyVector(const Vector3& v) const; - - Matrix4x4 Transpose() const; - Matrix4x4 Inverse() const; - float Determinant() const; - - Vector3 GetTranslation() const; - Quaternion GetRotation() const; - Vector3 GetScale() const; - - void Decompose(Vector3& translation, Quaternion& rotation, Vector3& scale) const; -}; - -struct Quaternion { - float x, y, z, w; - - static Quaternion Identity() { return Quaternion{0, 0, 0, 1}; } - - static Quaternion FromAxisAngle(const Vector3& axis, float radians); - static Quaternion FromEulerAngles(float pitch, float yaw, float roll); - static Quaternion FromRotationMatrix(const Matrix4x4& matrix); - static Quaternion Slerp(const Quaternion& a, const Quaternion& b, float t); - static Quaternion LookRotation(const Vector3& forward, const Vector3& up = Vector3::Up()); - - Vector3 ToEulerAngles() const; - Matrix4x4 ToMatrix4x4() const; - Vector3 operator*(const Vector3& v) const; - - Quaternion operator*(const Quaternion& other) const; - Quaternion Inverse() const; - float Dot(const Quaternion& other) const; -}; - -struct Transform { - Vector3 position = Vector3::Zero(); - Quaternion rotation = Quaternion::Identity(); - Vector3 scale = Vector3::One(); - - Matrix4x4 ToMatrix() const; - Transform Inverse() const; - Transform operator*(const Transform& other) const; - Vector3 TransformPoint(const Vector3& point) const; - Vector3 TransformDirection(const Vector3& direction) const; -}; - -enum class Space { - Self, - World -}; - -struct Color { - float r, g, b, a; - - static Color White() { return Color{1, 1, 1, 1}; } - static Color Black() { return Color{0, 0, 0, 1}; } - static Color Red() { return Color{1, 0, 0, 1}; } - static Color Green() { return Color{0, 1, 0, 1}; } - static Color Blue() { return Color{0, 0, 1, 1}; } - static Color Yellow() { return Color{1, 1, 0, 1}; } - static Color Cyan() { return Color{0, 1, 1, 1}; } - static Color Magenta() { return Color{1, 0, 1, 1}; } - static Color Clear() { return Color{0, 0, 0, 0}; } - - static Color Lerp(const Color& a, const Color& b, float t); -}; - -struct Rect { - float x, y, width, height; - - Rect() : x(0), y(0), width(0), height(0) {} - Rect(float x, float y, float w, float h) : x(x), y(y), width(w), height(h) {} - - float GetLeft() const { return x; } - float GetRight() const { return x + width; } - float GetTop() const { return y; } - float GetBottom() const { return y + height; } - - Vector2 GetPosition() const { return Vector2{x, y}; } - Vector2 GetSize() const { return Vector2{width, height}; } - - bool Contains(float px, float py) const { - return px >= x && px < x + width && py >= y && py < y + height; - } -}; - -struct RectInt { - int32_t x, y, width, height; - - RectInt() : x(0), y(0), width(0), height(0) {} - RectInt(int32_t x, int32_t y, int32_t w, int32_t h) : x(x), y(y), width(w), height(h) {} -}; - -struct Viewport { - float x, y, width, height; - float minDepth = 0.0f; - float maxDepth = 1.0f; - - Viewport() : x(0), y(0), width(0), height(0) {} - Viewport(float x, float y, float w, float h) : x(x), y(y), width(w), height(h) {} -}; - -struct Ray { - Vector3 origin; - Vector3 direction; - - Vector3 GetPoint(float t) const; - bool Intersects(const Sphere& sphere, float& t) const; - bool Intersects(const Box& box, float& t) const; - bool Intersects(const Plane& plane, float& t) const; -}; - -struct Sphere { Vector3 center; float radius; }; -struct Box { Vector3 center; Vector3 extents; Matrix4x4 transform; }; -struct Plane { Vector3 normal; float distance; }; -struct Frustum; -struct OBB; -struct AABB; - -} // namespace Math -} // namespace XCEngine -``` - -### 1.2 内存管理 (Memory Management) - -```cpp -namespace XCEngine { -namespace Memory { - -class IAllocator { -public: - virtual ~IAllocator() = default; - - virtual void* Allocate(size_t size, size_t alignment = 0) = 0; - virtual void Free(void* ptr) = 0; - virtual void* Reallocate(void* ptr, size_t newSize) = 0; - - virtual size_t GetTotalAllocated() const = 0; - virtual size_t GetTotalFreed() const = 0; - virtual size_t GetPeakAllocated() const = 0; - virtual size_t GetAllocationCount() const = 0; - - virtual const char* GetName() const = 0; -}; - -class LinearAllocator : public IAllocator { -public: - explicit LinearAllocator(size_t size, IAllocator* parent = nullptr); - ~LinearAllocator(); - - void* Allocate(size_t size, size_t alignment = 8) override; - void Free(void* ptr) override; - void Clear(); - - void* GetMarker() const; - void SetMarker(void* marker); - -private: - byte* m_buffer = nullptr; - size_t m_capacity = 0; - size_t m_offset = 0; - IAllocator* m_parent = nullptr; -}; - -class PoolAllocator : public IAllocator { -public: - PoolAllocator(size_t blockSize, size_t poolSize, size_t alignment = 8); - ~PoolAllocator(); - - void* Allocate(size_t size, size_t alignment = 0) override; - void Free(void* ptr) override; - - bool Contains(void* ptr) const; - size_t GetBlockSize() const { return m_blockSize; } - size_t GetFreeBlockCount() const; - -private: - struct FreeNode { - FreeNode* next; - }; - - size_t m_blockSize = 0; - size_t m_alignment = 0; - void* m_memory = nullptr; - FreeNode* m_freeList = nullptr; - size_t m_totalBlocks = 0; - size_t m_freeBlocks = 0; -}; - -class ProxyAllocator : public IAllocator { -public: - ProxyAllocator(IAllocator* underlying, const char* name); - - void* Allocate(size_t size, size_t alignment = 0) override; - void Free(void* ptr) override; - void* Reallocate(void* ptr, size_t newSize) override; - - struct Stats { - size_t totalAllocated; - size_t totalFreed; - size_t peakAllocated; - size_t allocationCount; - size_t memoryOverhead; - }; - const Stats& GetStats() const; - -private: - IAllocator* m_underlying; - const char* m_name; - Stats m_stats; - Mutex m_mutex; -}; - -class MemoryManager { -public: - static MemoryManager& Get(); - - void Initialize(); - void Shutdown(); - - IAllocator* GetSystemAllocator(); - - std::unique_ptr CreateLinearAllocator(size_t size); - std::unique_ptr CreatePoolAllocator(size_t blockSize, size_t count); - std::unique_ptr CreateProxyAllocator(const char* name); - - void SetTrackAllocations(bool track); - void DumpMemoryLeaks(); - void GenerateMemoryReport(); -}; - -#define XE_ALLOC(allocator, size, ...) allocator->Allocate(size, ##__VA_ARGS__) -#define XE_FREE(allocator, ptr) allocator->Free(ptr) - -} // namespace Memory -} // namespace XCEngine -``` - -### 1.3 容器库 (Containers) - -```cpp -namespace XCEngine { -namespace Containers { - -template -class Array { -public: - using Iterator = T*; - using ConstIterator = const T*; - - Array() = default; - explicit Array(size_t capacity); - Array(size_t count, const T& value); - Array(std::initializer_list init); - ~Array(); - - Array(const Array& other); - Array(Array&& other) noexcept; - Array& operator=(const Array& other); - Array& operator=(Array&& other) noexcept; - - T& operator[](size_t index); - const T& operator[](size_t index) const; - - T* Data() { return m_data; } - const T* Data() const { return m_data; } - - size_t Size() const { return m_size; } - size_t Capacity() const { return m_capacity; } - - void Clear(); - void PushBack(const T& value); - void PushBack(T&& value); - template - T& EmplaceBack(Args&&... args); - void PopBack(); - -private: - T* m_data = nullptr; - size_t m_size = 0; - size_t m_capacity = 0; - IAllocator* m_allocator = nullptr; -}; - -class String { -public: - String(); - String(const char* str); - String(const char* str, size_t len); - ~String(); - - String& operator+=(const String& other); - String& operator+=(const char* str); - - String Substring(size_t pos, size_t len = npos) const; - String Trim() const; - String ToLower() const; - String ToUpper() const; - - size_t Find(const char* str, size_t pos = 0) const; - bool StartsWith(const String& prefix) const; - bool EndsWith(const String& suffix) const; - - const char* CStr() const { return m_data; } - size_t Length() const { return m_length; } - -private: - char* m_data = nullptr; - size_t m_length = 0; - size_t m_capacity = 0; -}; - -template -class HashMap { -public: - struct Pair { - Key first; - Value second; - }; - - HashMap() = default; - explicit HashMap(size_t bucketCount, IAllocator* allocator = nullptr); - - Value& operator[](const Key& key); - Value* Find(const Key& key); - const Value* Find(const Key& key) const; - bool Contains(const Key& key) const; - - bool Insert(const Key& key, const Value& value); - bool Erase(const Key& key); - void Clear(); - - size_t Size() const { return m_size; } - -private: - size_t GetBucketIndex(const Key& key) const; - void Resize(); - - struct Bucket { - Array pairs; - }; - Array m_buckets; - size_t m_bucketCount = 0; - size_t m_size = 0; - float m_loadFactor = 0.75f; - IAllocator* m_allocator = nullptr; -}; - -} // namespace Containers -} // namespace XCEngine -``` - -### 1.4 线程系统 (Threading) - -```cpp -namespace XCEngine { -namespace Threading { - -enum class TaskPriority : uint8_t { - Critical = 0, - High = 1, - Normal = 2, - Low = 3, - Idle = 4 -}; - -enum class TaskStatus : uint8_t { - Pending, - Scheduled, - Running, - Completed, - Failed, - Canceled -}; - -template -using Func = std::function; - -class ITask { -public: - virtual ~ITask() = default; - - virtual void Execute() = 0; - virtual void OnComplete() {} - virtual void OnCancel() {} - - TaskPriority GetPriority() const { return m_priority; } - TaskStatus GetStatus() const { return m_status; } - uint64 GetId() const { return m_id; } - -protected: - TaskPriority m_priority = TaskPriority::Normal; - TaskStatus m_status = TaskStatus::Pending; - uint64 m_id = 0; - std::atomic m_refCount{1}; -}; - -template -class LambdaTask : public ITask { -public: - explicit LambdaTask(Func&& func, TaskPriority priority = TaskPriority::Normal) - : m_func(std::move(func)), m_priority(priority) {} - - void Execute() override { - m_func(); - } - -private: - Func m_func; -}; - -class TaskGroup { -public: - using Callback = std::function; - - TaskGroup(); - ~TaskGroup(); - - uint64 AddTask(std::unique_ptr task); - uint64 AddTask(Func&& func, TaskPriority priority = TaskPriority::Normal); - - void AddDependency(uint64 taskId, uint64 dependsOn); - void Wait(); - bool WaitFor(std::chrono::milliseconds timeout); - - void SetCompleteCallback(Callback&& callback); - bool IsComplete() const; - float GetProgress() const; -}; - -class TaskSystem { -public: - static TaskSystem& Get(); - - void Initialize(const TaskSystemConfig& config); - void Shutdown(); - - uint64 Submit(std::unique_ptr task); - uint64 Submit(Func&& func, TaskPriority priority = TaskPriority::Normal); - - TaskGroup* CreateTaskGroup(); - void DestroyTaskGroup(TaskGroup* group); - - void Wait(uint64 taskId); - uint32 GetWorkerThreadCount() const; - - void Update(); - - template - void ParallelFor(int32 start, int32 end, Func&& func); - - void RunOnMainThread(Func&& func); -}; - -class Mutex { -public: - Mutex() = default; - ~Mutex() = default; - - void Lock(); - void Unlock(); - bool TryLock(); - -private: - std::mutex m_mutex; -}; - -class SpinLock { -public: - void Lock(); - void Unlock(); - bool TryLock(); - -private: - std::atomic_flag m_flag = ATOMIC_FLAG_INIT; -}; - -class ReadWriteLock { -public: - void ReadLock(); - void ReadUnlock(); - void WriteLock(); - void WriteUnlock(); - -private: - std::mutex m_mutex; - std::condition_variable m_readCondition; - std::condition_variable m_writeCondition; - int32_t m_readers = 0; - int32_t m_writersWaiting = 0; - bool m_writerActive = false; -}; - -class Thread { -public: - using Id = uint64_t; - - Thread(); - ~Thread(); - - template - void Start(Func&& func, const String& name = "Thread"); - void Join(); - void Detach(); - - Id GetId() const { return m_id; } - const String& GetName() const { return m_name; } - - static Id GetCurrentId(); - static void Sleep(uint32_t milliseconds); - static void Yield(); - -private: - Id m_id = 0; - String m_name; - std::thread m_thread; -}; - -} // namespace Threading -} // namespace XCEngine -``` - -### 1.5 日志与调试系统 - -```cpp -namespace XCEngine { -namespace Core { - -template -class Event { -public: - using Callback = std::function; - using Listener = std::pair; - using Iterator = typename std::vector::iterator; - - uint64_t Subscribe(Callback callback) { - std::lock_guard lock(m_mutex); - uint64_t id = ++m_nextId; - m_listeners.emplace_back(id, std::move(callback)); - return id; - } - - void Unsubscribe(uint64_t id) { - std::lock_guard lock(m_mutex); - m_pendingUnsubscribes.push_back(id); - } - - void ProcessUnsubscribes() { - std::lock_guard lock(m_mutex); - for (uint64_t id : m_pendingUnsubscribes) { - m_listeners.erase( - std::remove_if(m_listeners.begin(), m_listeners.end(), - [id](const auto& pair) { return pair.first == id; }), - m_listeners.end() - ); - } - m_pendingUnsubscribes.clear(); - } - - void Invoke(Args... args) const { - std::vector listenersCopy; - { - std::lock_guard lock(m_mutex); - if (!m_pendingUnsubscribes.empty()) { - for (uint64_t id : m_pendingUnsubscribes) { - m_listeners.erase( - std::remove_if(m_listeners.begin(), m_listeners.end(), - [id](const auto& pair) { return pair.first == id; }), - m_listeners.end() - ); - } - m_pendingUnsubscribes.clear(); - } - listenersCopy = m_listeners; - } - for (const auto& [id, callback] : listenersCopy) { - callback(args...); - } - } - - void Clear() { - std::lock_guard lock(m_mutex); - m_listeners.clear(); - } - - Iterator begin() { return m_listeners.begin(); } - Iterator end() { return m_listeners.end(); } - -private: - mutable std::mutex m_mutex; - std::vector m_listeners; - std::vector m_pendingUnsubscribes; - uint64_t m_nextId = 0; -}; - -using int8 = int8_t; -using int16 = int16_t; -using int32 = int32_t; -using int64 = int64_t; -using uint8 = uint8_t; -using uint16 = uint16_t; -using uint32 = uint32_t; -using uint64 = uint64_t; -using byte = uint8_t; - -class RefCounted { -public: - RefCounted() : m_refCount(1) {} - virtual ~RefCounted() = default; - - void AddRef() { ++m_refCount; } - void Release() { - if (--m_refCount == 0) { - delete this; - } - } - - uint32_t GetRefCount() const { return m_refCount.load(); } - -protected: - std::atomic m_refCount; -}; - -template -using Ref = std::shared_ptr; - -template -using UniqueRef = std::unique_ptr; - -} // namespace Core -} // namespace XCEngine - -namespace XCEngine { -namespace Debug { - -enum class LogLevel : uint8_t { - Verbose = 0, - Debug = 1, - Info = 2, - Warning = 3, - Error = 4, - Fatal = 5 -}; - -enum class LogCategory { - General, - Rendering, - Physics, - Audio, - Scripting, - Network, - Memory, - Threading, - FileSystem, - Custom -}; - -struct LogEntry { - LogLevel level; - LogCategory category; - String message; - String file; - int32 line; - String function; - uint64 timestamp; - uint32 threadId; -}; - -class ILogSink { -public: - virtual ~ILogSink() = default; - virtual void Log(const LogEntry& entry) = 0; - virtual void Flush() = 0; -}; - -class ConsoleLogSink : public ILogSink { -public: - void Log(const LogEntry& entry) override; - void Flush() override; - void SetColorOutput(bool enable); - void SetMinimumLevel(LogLevel level); -}; - -class FileLogSink : public ILogSink { -public: - explicit FileLogSink(const String& filePath); - ~FileLogSink(); - void Log(const LogEntry& entry) override; - void Flush() override; -private: - String m_filePath; - FileWriter m_writer; -}; - -class Logger { -public: - static Logger& Get(); - - void Initialize(); - void Shutdown(); - - void AddSink(std::unique_ptr sink); - void RemoveSink(ILogSink* sink); - - void Log(LogLevel level, LogCategory category, - const String& message, const char* file = nullptr, - int32 line = 0, const char* function = nullptr); - - void Verbose(LogCategory category, const String& message); - void Debug(LogCategory category, const String& message); - void Info(LogCategory category, const String& message); - void Warning(LogCategory category, const String& message); - void Error(LogCategory category, const String& message); - void Fatal(LogCategory category, const String& message); - - void SetMinimumLevel(LogLevel level); - void SetCategoryEnabled(LogCategory category, bool enabled); -}; - -#define XE_LOG(category, level, message) \ - XCEngine::Debug::Logger::Get().Log(level, category, message, __FILE__, __LINE__, __FUNCTION__) - -#define XE_ASSERT(condition, message) \ - if (!(condition)) { \ - XCEngine::Debug::Logger::Get().Fatal(XCEngine::Debug::LogCategory::General, message); \ - __debugbreak(); \ - } - -class Profiler { -public: - static Profiler& Get(); - - void Initialize(); - void Shutdown(); - - void BeginProfile(const char* name); - void EndProfile(); - - void BeginFrame(); - void EndFrame(); - - void MarkEvent(const char* name, uint64_t timestamp, uint32_t threadId); - void SetMarker(const char* name, uint32_t color); - - void ExportChromeTracing(const String& filePath); -}; - -#define XE_PROFILE_BEGIN(name) XCEngine::Debug::Profiler::Get().BeginProfile(name) -#define XE_PROFILE_END() XCEngine::Debug::Profiler::Get().EndProfile() -#define XE_PROFILE_FUNCTION() XE_PROFILE_BEGIN(__FUNCTION__) - -} // namespace Debug -} // namespace XCEngine -``` - ---- - -## 第二章 组件系统 - -### 2.1 组件基类 - -```cpp -namespace XCEngine { - -class Scene; -class GameObject; -class TransformComponent; - -// 组件类型注册(用于运行时类型识别) -class ComponentTypeRegistry { -public: - static ComponentTypeRegistry& Get(); - - template - static uint32_t GetTypeId() { - static uint32_t id = Get().Register(typeid(T).name(), static_cast(-1)); - return id; - } - - template - static uint32_t GetTypeId(const char* typeName) { - static uint32_t id = Get().Register(typeName, static_cast(-1)); - return id; - } - - static uint32_t GetTypeIdFromName(const char* typeName) { - return Get().GetIdByName(typeName); - } - - static const char* GetTypeName(uint32_t typeId) { - return Get().GetNameById(typeId); - } - -private: - uint32_t Register(const char* typeName, uint32_t suggestedId); - uint32_t GetIdByName(const char* typeName) const; - const char* GetNameById(uint32_t typeId) const; - - std::atomic m_nextTypeId{0}; - std::unordered_map m_idToName; - std::unordered_map m_nameToId; - Mutex m_mutex; -}; - -// 组件基类(类Unity MonoBehaviour) -class Component { -public: - Component(); - virtual ~Component(); - - GameObject* gameObject() const { return m_gameObject; } - TransformComponent& transform() const { return m_gameObject->GetTransform(); } - - bool IsEnabled() const { return m_enabled; } - void SetEnabled(bool enabled) { m_enabled = enabled; } - - Scene* GetScene() const; - - template - T* GetComponent() const { return m_gameObject->GetComponent(); } - - template - std::vector GetComponents() const { return m_gameObject->GetComponents(); } - - virtual void Awake() {} - virtual void Start() {} - virtual void Update(float deltaTime) {} - virtual void FixedUpdate() {} - virtual void LateUpdate(float deltaTime) {} - virtual void OnDestroy() {} - virtual void OnEnable() {} - virtual void OnDisable() {} - -protected: - GameObject* m_gameObject = nullptr; - bool m_enabled = true; - - friend class GameObject; -}; - -} // namespace XCEngine -``` - -### 2.2 Transform组件 - -```cpp -namespace XCEngine { - -class TransformComponent : public Component { -public: - Vector3 GetLocalPosition() const { return m_localPosition; } - void SetLocalPosition(const Vector3& position) { m_localPosition = position; SetDirty(); } - - Quaternion GetLocalRotation() const { return m_localRotation; } - void SetLocalRotation(const Quaternion& rotation) { m_localRotation = rotation; SetDirty(); } - - Vector3 GetLocalScale() const { return m_localScale; } - void SetLocalScale(const Vector3& scale) { m_localScale = scale; SetDirty(); } - - Vector3 GetPosition() const; - void SetPosition(const Vector3& position); - - Quaternion GetRotation() const; - void SetRotation(const Quaternion& rotation); - - Vector3 GetScale() const; - void SetScale(const Vector3& scale); - - Vector3 GetForward() const { return GetRotation() * Vector3::Forward(); } - Vector3 GetRight() const { return GetRotation() * Vector3::Right(); } - Vector3 GetUp() const { return GetRotation() * Vector3::Up(); } - - const Matrix4x4& GetLocalToWorldMatrix() const; - Matrix4x4 GetWorldToLocalMatrix() const; - - TransformComponent* GetParent() const { return m_parent; } - void SetParent(TransformComponent* parent, bool worldPositionStays = true); - - int GetChildCount() const { return static_cast(m_children.size()); } - TransformComponent* GetChild(int index) const; - TransformComponent* Find(const String& name) const; - - void DetachChildren(); - void SetAsFirstSibling(); - void SetAsLastSibling(); - void SetSiblingIndex(int index); - int GetSiblingIndex() const; - - void LookAt(const Vector3& target); - void LookAt(const Vector3& target, const Vector3& up); - void Rotate(const Vector3& eulers); - void Rotate(const Vector3& axis, float angle); - void Translate(const Vector3& translation); - void Translate(const Vector3& translation, Math::Space relativeTo); - - Vector3 TransformPoint(const Vector3& point) const; - Vector3 InverseTransformPoint(const Vector3& point) const; - Vector3 TransformDirection(const Vector3& direction) const; - Vector3 InverseTransformDirection(const Vector3& direction) const; - - void SetDirty() { m_dirty = true; } - -private: - Vector3 m_localPosition = Vector3::Zero(); - Quaternion m_localRotation = Quaternion::Identity(); - Vector3 m_localScale = Vector3::One(); - - TransformComponent* m_parent = nullptr; - std::vector m_children; - - mutable Matrix4x4 m_localToWorldMatrix; - mutable Matrix4x4 m_worldToLocalMatrix; - mutable Vector3 m_worldPosition; - mutable Quaternion m_worldRotation; - mutable Vector3 m_worldScale; - mutable bool m_dirty = true; - - void UpdateWorldTransform() const; - - friend class GameObject; -}; - -} // namespace XCEngine -``` - -### 2.3 GameObject - -```cpp -namespace XCEngine { - -class Scene; - -class GameObject { -public: - GameObject(); - ~GameObject(); - - struct ConstructParams { - String name = "GameObject"; - GameObject* parent = nullptr; - bool active = true; - }; - - static GameObject* Create(const ConstructParams& params = {}); - static void Destroy(GameObject* obj); - - String name; - bool active = true; - - TransformComponent& GetTransform() { return *m_transform; } - const TransformComponent& GetTransform() const { return *m_transform; } - - Scene* GetScene() const { return m_scene; } - - template - T* AddComponent(); - - template - void RemoveComponent(); - - template - T* GetComponent() const; - - template - std::vector GetComponents() const; - - template - T* GetComponentInChildren() const; - - template - std::vector GetComponentsInChildren() const; - - template - T* GetComponentInParent() const; - - GameObject* GetParent() const; - void SetParent(GameObject* parent); - void SetParent(GameObject* parent, bool worldPositionStays); - const std::vector& GetChildren() const; - GameObject* GetChild(int index) const; - int GetChildCount() const; - int GetSiblingIndex() const; - void SetSiblingIndex(int index); - - void SetActive(bool active); - bool IsActive() const; - bool IsActiveInHierarchy() const; - - static GameObject* Find(const String& name); - static std::vector FindObjectsOfType(); - static std::vector FindGameObjectsWithTag(const String& tag); - - void Destroy(); - -private: - void AddComponentInternal(Component* component); - void RemoveComponentInternal(Component* component); - - std::vector> m_components; - std::unordered_map m_componentTypeIndex; - - std::vector m_children; - GameObject* m_parent = nullptr; - Scene* m_scene = nullptr; - - std::unique_ptr m_transform; - - void Initialize(const ConstructParams& params); - - friend class Scene; - friend class TransformComponent; -}; - -inline GameObject::GameObject() { -} - -inline GameObject::~GameObject() { - for (auto* child : m_children) { - delete child; - } -} - -inline GameObject* GameObject::Create(const ConstructParams& params) { - GameObject* obj = new GameObject(); - obj->Initialize(params); - return obj; -} - -inline void GameObject::Destroy(GameObject* obj) { - delete obj; -} - -inline void GameObject::Initialize(const ConstructParams& params) { - name = params.name; - active = params.active; - - m_transform = std::make_unique(); - m_transform->m_gameObject = this; - - if (params.parent) { - params.parent->m_children.push_back(this); - m_parent = params.parent; - } -} - -inline void GameObject::Destroy() { - if (m_scene) { - m_scene->DestroyGameObject(this); - } else { - delete this; - } -} - -inline GameObject* GameObject::Find(const String& name) { - return SceneManager::Get().GetActiveScene()->Find(name); -} - -inline std::vector GameObject::FindObjectsOfType() { - return SceneManager::Get().GetActiveScene()->FindObjectsOfType(); -} - -inline std::vector GameObject::FindGameObjectsWithTag(const String& tag) { - return SceneManager::Get().GetActiveScene()->FindGameObjectsWithTag(tag); -} - -} // namespace XCEngine -``` - ---- - -## 第三章 场景系统 - -### 3.1 Scene - -```cpp -namespace XCEngine { - -class Scene { -public: - Scene(); - ~Scene(); - - const String& GetName() const { return m_name; } - void SetName(const String& name) { m_name = name; } - - GameObject* CreateGameObject(const String& name = "GameObject") { - GameObject::ConstructParams params; - params.name = name; - params.parent = nullptr; - params.active = true; - - GameObject* obj = GameObject::Create(params); - obj->m_scene = this; - AddGameObject(obj); - return obj; - } - - GameObject* CreateGameObject(const String& name, GameObject* parent) { - GameObject::ConstructParams params; - params.name = name; - params.parent = parent; - params.active = true; - - GameObject* obj = GameObject::Create(params); - obj->m_scene = this; - AddGameObject(obj); - return obj; - } - - void DestroyGameObject(GameObject* obj); - - std::vector GetRootGameObjects() const { - std::vector roots; - roots.reserve(m_gameObjects.size() / 2); - for (auto& obj : m_gameObjects) { - if (obj->GetParent() == nullptr) { - roots.push_back(obj.get()); - } - } - return roots; - } - - GameObject* Find(const String& name) const; - GameObject* FindGameObjectWithTag(const String& tag) const; - std::vector FindGameObjectsWithTag(const String& tag) const; - template - std::vector FindObjectsOfType() const; - template - T* FindObjectOfType() const; - - bool IsActive() const { return m_isActive; } - void SetActive(bool active); - - void Load(const String& filePath); - void LoadAsync(const String& filePath, std::function callback); - void Save(const String& filePath); - - void Update(float deltaTime); - void FixedUpdate(float fixedDeltaTime); - void LateUpdate(float deltaTime); - - void DebugDraw(); - int GetObjectCount() const { return m_gameObjects.size(); } - -private: - void AddGameObject(GameObject* obj); - void RemoveGameObject(GameObject* obj); - - String m_name; - std::vector> m_gameObjects; - bool m_isActive = true; - - friend class GameObject; - friend class SceneManager; -}; - -class SceneManager { -public: - static SceneManager& Get(); - - void Initialize(); - void Shutdown(); - - Scene* CreateScene(const String& name); - void LoadScene(const String& filePath); - void LoadSceneAsync(const String& filePath, std::function callback); - void UnloadScene(Scene* scene); - void UnloadScene(const String& sceneName); - - void SetActiveScene(Scene* scene); - void SetActiveScene(const String& sceneName); - Scene* GetActiveScene() const; - - Scene* GetScene(const String& name) const; - std::vector GetAllScenes() const; - - void Update(float deltaTime); - void FixedUpdate(float fixedDeltaTime); - void LateUpdate(float deltaTime); - - Event OnSceneLoaded; - Event OnSceneUnloaded; - Event OnActiveSceneChanged; - -private: - SceneManager() = default; - - std::vector> m_scenes; - Scene* m_activeScene = nullptr; - Scene* m_loadingScene = nullptr; - - std::unordered_map m_sceneNameMap; - std::function m_loadCallback; - bool m_loading = false; -}; - -// GameObject创建辅助类 -class GameObjectBuilder { -public: - explicit GameObjectBuilder(const String& name = "GameObject"); - ~GameObjectBuilder() = default; - - template - GameObjectBuilder& AddComponent(Args&&... args); - - GameObject* Build(); - -private: - String m_name; - std::vector> m_components; -}; - -} // namespace XCEngine -``` - ---- - -## 第四章 渲染系统 - -> **借鉴 Unity 渲染架构设计** - -### 4.0 公共类型定义 - -```cpp -namespace XCEngine { -namespace Rendering { - -// ============================================ -// 前置类型定义 - 资源基类 -// ============================================ - -enum class ResourceLoadState { - Undefined, - Unloaded, - Loading, - Loaded, - Unloading, - Failed -}; - -enum class ResourceType { - Mesh, - Texture, - Shader, - Material, - ComputeBuffer, - GraphicsBuffer, - Sampler, - RenderTarget, - DepthStencil, - VertexBuffer, - IndexBuffer, - Unknown -}; - -class IResource { -public: - virtual ~IResource() = default; - - virtual void SetName(const std::string& name) { m_name = name; } - virtual const std::string& GetName() const { return m_name; } - - virtual ResourceType GetType() const = 0; - virtual ResourceLoadState GetLoadState() const { return m_loadState; } - - virtual bool IsValid() const { return m_loadState == ResourceLoadState::Loaded; } - virtual void Load() = 0; - virtual void Unload() = 0; - - virtual int GetRefCount() const { return m_refCount.load(); } - virtual void AddRef() { m_refCount.fetch_add(1); } - virtual void Release() { - if (m_refCount.fetch_sub(1) == 1) { - delete this; - } - } - -protected: - IResource() : m_loadState(ResourceLoadState::Unloaded), m_memoryUsage(0) {} - - std::string m_name; - ResourceLoadState m_loadState; - uint64_t m_memoryUsage; - std::atomic m_refCount{1}; -}; - -// ============================================ -// 缓冲区类型定义 -// ============================================ - -class VertexBuffer : public IResource { -public: - ResourceType GetType() const override { return ResourceType::VertexBuffer; } - - void Load() override {} - void Unload() override {} - - uint32_t GetVertexCount() const { return m_vertexCount; } - uint32_t GetStride() const { return m_stride; } - uint64_t GetSize() const { return m_size; } - - void SetData(const void* data, uint64_t size); - void* Map(); - void Unmap(); - -private: - uint32_t m_vertexCount = 0; - uint32_t m_stride = 0; - uint64_t m_size = 0; - void* m_nativeHandle = nullptr; -}; - -class IndexBuffer : public IResource { -public: - ResourceType GetType() const override { return ResourceType::IndexBuffer; } - - void Load() override {} - void Unload() override {} - - uint32_t GetIndexCount() const { return m_indexCount; } - bool Is32Bit() const { return m_is32Bit; } - uint64_t GetSize() const { return m_size; } - - void SetData(const void* data, uint64_t size); - void* Map(); - void Unmap(); - -private: - uint32_t m_indexCount = 0; - bool m_is32Bit = false; - uint64_t m_size = 0; - void* m_nativeHandle = nullptr; -}; - -// ============================================ -// 渲染目标类型定义 -// ============================================ - -struct RenderTargetDesc { - uint32_t width = 0; - uint32_t height = 0; - Format format = Format::R8G8B8A8_UNorm; - SampleCount sampleCount = SampleCount::Count1; - uint32_t mipLevels = 1; - uint32_t arraySize = 1; - bool enableUAV = false; - String name; -}; - -class RenderTarget : public IResource { -public: - ResourceType GetType() const override { return ResourceType::RenderTarget; } - - void Load() override {} - void Unload() override {} - - uint32_t GetWidth() const { return m_width; } - uint32_t GetHeight() const { return m_height; } - Format GetFormat() const { return m_format; } - - void* GetNativeHandle() const { return m_nativeHandle; } - - IShaderResourceView* GetSRV() { return m_srv.get(); } - IRenderTargetView* GetRTV() { return m_rtv.get(); } - -private: - uint32_t m_width = 0; - uint32_t m_height = 0; - Format m_format; - void* m_nativeHandle = nullptr; - std::unique_ptr m_srv; - std::unique_ptr m_rtv; -}; - -struct DepthStencilDesc { - uint32_t width = 0; - uint32_t height = 0; - Format format = Format::D24_UNorm_S8_UInt; - SampleCount sampleCount = SampleCount::Count1; - bool bindAsShaderResource = false; - String name; -}; - -class DepthStencil : public IResource { -public: - ResourceType GetType() const override { return ResourceType::DepthStencil; } - - void Load() override {} - void Unload() override {} - - uint32_t GetWidth() const { return m_width; } - uint32_t GetHeight() const { return m_height; } - Format GetFormat() const { return m_format; } - - void* GetNativeHandle() const { return m_nativeHandle; } - - IDepthStencilView* GetDSV() { return m_dsv.get(); } - IShaderResourceView* GetSRV() { return m_srv.get(); } - -private: - uint32_t m_width = 0; - uint32_t m_height = 0; - Format m_format; - void* m_nativeHandle = nullptr; - std::unique_ptr m_dsv; - std::unique_ptr m_srv; -}; - -// ============================================ -// 渲染通道描述 -// ============================================ - -struct RenderPassAttachmentDesc { - RenderTarget* renderTarget = nullptr; - DepthStencil* depthStencil = nullptr; - LoadAction loadAction = LoadAction::Clear; - StoreAction storeAction = StoreAction::Store; - Color clearColor = Color::Black(); - float clearDepth = 1.0f; - uint8_t clearStencil = 0; -}; - -struct RenderPassDesc { - RenderPassAttachmentDesc* colorAttachments = nullptr; - uint32_t colorAttachmentCount = 0; - RenderPassAttachmentDesc* depthStencilAttachment = nullptr; - Rect viewport; - Rect scissor; -}; - -// ============================================ -// 基础枚举类型 -// ============================================ - -enum class Format { - Unknown = 0, - R8_UNorm = 61, - R8G8_UNorm = 49, - R8G8B8A8_UNorm = 28, - R16G16B16A16_Float = 10, - R32G32B32A32_Float = 2, - R16_Float = 54, - R32_Float = 41, - D16_UNorm = 55, - D24_UNorm_S8_UInt = 45, - D32_Float = 40, - BC1_UNorm = 71, - BC2_UNorm = 74, - BC3_UNorm = 77, - BC4_UNorm = 80, - BC5_UNorm = 83, - BC6H_UF16 = 95, - BC7_UNorm = 98 -}; - -enum class SampleCount : uint32_t { - Count1 = 1, - Count2 = 2, - Count4 = 4, - Count8 = 8 -}; - -enum class CubemapFace : uint8_t { - PositiveX = 0, - NegativeX = 1, - PositiveY = 2, - NegativeY = 3, - PositiveZ = 4, - NegativeZ = 5 -}; - -struct Frustum { - Plane planes[6]; - - bool Contains(const Vector3& point) const; - bool Contains(const Sphere& sphere) const; - bool Contains(const Bounds& bounds) const; - bool Intersects(const Bounds& bounds) const; -}; - -enum class FilterMode { - Point = 0, - Bilinear = 1, - Trilinear = 2 -}; - -enum class TextureWrapMode { - Repeat = 0, - Clamp = 1, - Mirror = 2, - MirrorOnce = 3 -}; - -enum class MeshTopology { - Points, - Lines, - LineStrip, - Triangles, - TriangleStrip, - Quads -}; - -struct Bounds { - Vector3 center; - Vector3 extents; - Vector3 min; - Vector3 max; - - Bounds() : center(Vector3::Zero()), extents(Vector3::Zero()) {} - Bounds(const Vector3& center, const Vector3& size) - : center(center), extents(size * 0.5f) { - min = center - extents; - max = center + extents; - } - - void SetMinMax(const Vector3& min, const Vector3& max) { - this->min = min; - this->max = max; - center = (min + max) * 0.5f; - extents = (max - min) * 0.5f; - } - - bool Intersects(const Bounds& other) const; - bool Contains(const Vector3& point) const; - - void Encapsulate(const Vector3& point); - void Encapsulate(const Bounds& bounds); - - void Expand(float amount); - void Expand(const Vector3& amount); - - Vector3 GetClosestPoint(const Vector3& point) const; -}; - -struct QualityLevel { - enum { - Fastest = 0, - Fast = 1, - Simple = 2, - Good = 3, - High = 4, - Ultra = 5 - }; -}; - -class StringView { -public: - StringView() : m_data(nullptr), m_length(0) {} - StringView(const char* str); - StringView(const char* str, size_t len); - StringView(const String& str); - - const char* Data() const { return m_data; } - size_t Length() const { return m_length; } - - bool Empty() const { return m_length == 0; } - - char operator[](size_t index) const { return m_data[index]; } - - int Compare(const StringView& other) const; - bool operator==(const StringView& other) const; - bool operator!=(const StringView& other) const; - -private: - const char* m_data; - size_t m_length; -}; - -class Texture { -public: - virtual ~Texture() = default; - - uint32_t GetWidth() const { return m_width; } - uint32_t GetHeight() const { return m_height; } - uint32_t GetDepth() const { return m_depth; } - uint32_t GetMipLevels() const { return m_mipLevels; } - Format GetFormat() const { return m_format; } - - FilterMode GetFilterMode() const { return m_filterMode; } - void SetFilterMode(FilterMode mode) { m_filterMode = mode; } - - TextureWrapMode GetWrapMode() const { return m_wrapMode; } - void SetWrapMode(TextureWrapMode mode) { m_wrapMode = mode; } - - void* GetNativeHandle() const { return m_nativeHandle; } - -protected: - uint32_t m_width = 0; - uint32_t m_height = 0; - uint32_t m_depth = 1; - uint32_t m_mipLevels = 1; - Format m_format = Format::R8G8B8A8_UNorm; - FilterMode m_filterMode = FilterMode::Bilinear; - TextureWrapMode m_wrapMode = TextureWrapMode::Clamp; - void* m_nativeHandle = nullptr; -}; - -class RenderTargetIdentifier { -public: - RenderTargetIdentifier(); - RenderTargetIdentifier(int nameID); - RenderTargetIdentifier(const String& name); - RenderTargetIdentifier(Texture* texture); - - bool IsValid() const { return m_type != Buffer || m_nameID != -1; } - bool operator==(const RenderTargetIdentifier& other) const; - bool operator!=(const RenderTargetIdentifier& other) const { return !(*this == other); } - - int GetType() const { return m_type; } - int GetNameID() const { return m_nameID; } - Texture* GetTexture() const { return m_texture; } - -private: - int m_type = Buffer; - int m_nameID = -1; - Texture* m_texture = nullptr; - - enum { Buffer, Texture2D, RenderTarget }; -}; - -class CommandBuffer { -public: - CommandBuffer(); - ~CommandBuffer(); - - void Clear(); - void BeginSample(const char* name); - void EndSample(const char* name); - void BeginDebugGroup(const char* name); - void EndDebugGroup(); - - void SetRenderTarget( - RenderTargetIdentifier colorTarget, - RenderTargetIdentifier depthTarget, - int mipLevel = 0, - CubemapFace cubemapFace = CubemapFace::PositiveX, - bool depthIsWritable = true - ); - - void SetRenderTarget( - const std::vector& colorTargets, - RenderTargetIdentifier depthTarget, - int mipLevel = 0, - CubemapFace cubemapFace = CubemapFace::PositiveX, - bool depthIsWritable = true - ); - - void SetViewport(const Rect& rect); - void SetScissor(const Rect& rect); - - void SetViewMatrix(const Matrix4x4& matrix); - void SetProjectionMatrix(const Matrix4x4& matrix); - - void DrawMeshInstanced( - const Mesh& mesh, - int submeshIndex, - Material* material, - int shaderPass, - const std::vector& matrices - ); - - void DrawMeshInstancedIndirect( - const Mesh& mesh, - int submeshIndex, - Material* material, - int shaderPass, - const ComputeBuffer& argsBuffer, - int argsOffset - ); - - void DrawProcedural( - Material* material, - int shaderPass, - MeshTopology topology, - int vertexCount, - int instanceCount - ); - - void Blit( - Material* material, - int pass, - RenderTargetIdentifier source, - RenderTargetIdentifier dest - ); - - void CopyTexture( - RenderTargetIdentifier source, - RenderTargetIdentifier dest - ); - - void GetTemporaryRT( - const String& name, - int width, - int height, - Format format = Format::R8G8B8A8_UNorm, - FilterMode filter = FilterMode::Bilinear, - TextureWrapMode wrap = TextureWrapMode::Clamp, - int depthBufferBits = 0 - ); - - void ReleaseTemporaryRT(const String& name); - - void GetCommandBufferPtr(void** ptr); - -private: - std::vector m_data; - std::vector m_sampleNames; - int m_sampleDepth = 0; -}; - -class Mesh : public IResource { -public: - Mesh(); - ~Mesh(); - - void SetVertices(const std::vector& vertices); - void SetNormals(const std::vector& normals); - void SetTangents(const std::vector& tangents); - void SetUVs(int channel, const std::vector& uvs); - void SetUVs(int channel, const std::vector& uvs); - void SetTriangles(const std::vector& triangles, int submesh); - void SetIndices(const std::vector& indices, int submesh); - void SetIndices(const std::vector& indices, int submesh); - - const std::vector& GetVertices() const { return m_vertices; } - const std::vector& GetNormals() const { return m_normals; } - const std::vector& GetTangents() const { return m_tangents; } - - int GetSubmeshCount() const { return m_submeshes.size(); } - const struct SubmeshDescriptor& GetSubmesh(int index) const; - - int GetVertexCount() const { return static_cast(m_vertices.size()); } - int GetIndexCount(int submesh) const; - - MeshTopology GetTopology(int submesh) const; - void SetTopology(MeshTopology topology, int submesh); - - void RecalculateNormals(); - void RecalculateTangents(); - void RecalculateBounds(); - - void Optimize(); - - bool IsUse32BitIndexBuffer() const { return m_use32BitIndex; } - - struct MeshData { - std::vector vertices; - std::vector normals; - std::vector tangents; - std::vector uvs[8]; - std::vector indices32; - std::vector indices16; - }; - void GetMeshData(MeshData& data) const; - -private: - std::vector m_vertices; - std::vector m_normals; - std::vector m_tangents; - std::vector m_uvs[8]; - - struct SubmeshDescriptor { - int indexStart; - int indexCount; - int baseVertex; - MeshTopology topology; - }; - std::vector m_submeshes; - - Bounds m_bounds; - bool m_use32BitIndex = false; - bool m_hasVertices = false; - bool m_hasNormals = false; - bool m_hasTangents = false; - - friend class MeshCollider; - friend class SkinnedMeshRenderer; -}; - -class ComputeBuffer : public IResource { -public: - enum class Type { - Default, - Raw, - Append, - Counter, - Structured, - ByteAddress - }; - - ComputeBuffer(Type type, int count, int stride); - ~ComputeBuffer(); - - int GetCount() const { return m_count; } - int GetStride() const { return m_stride; } - Type GetType() const { return m_type; } - - void SetData(const void* data); - void GetData(void* data) const; - - void SetCounterValue(uint32_t value); - uint32_t GetCounterValue() const; - -private: - Type m_type; - int m_count; - int m_stride; - void* m_buffer; -}; - -class GraphicsBuffer : public IResource { -public: - enum class Target { - Constant, - IndexBuffer, - VertexBuffer, - Structured, - Append, - Counter, - ReadWrite - }; - - GraphicsBuffer(Target target, uint32_t count, uint32_t stride); - virtual ~GraphicsBuffer(); - - Target GetTarget() const { return m_target; } - uint32_t GetCount() const { return m_count; } - uint32_t GetStride() const { return m_stride; } - uint64_t GetSize() const { return m_size; } - - void SetData(const void* data); - void GetData(void* data) const; - - void* Map(); - void Unmap(); - - void SetCounterValue(uint32_t value); - uint32_t GetCounterValue() const; - -private: - Target m_target; - uint32_t m_count = 0; - uint32_t m_stride = 0; - uint64_t m_size = 0; - void* m_buffer; -}; - -struct ShaderProgramDesc { - enum class Stage { - Vertex, - Fragment, - Compute, - Geometry, - Hull, - Domain - }; - - Stage stage; - String source; - String entryPoint; - - bool debug = false; - bool optimize = true; -}; - -} // namespace Rendering -} // namespace XCEngine -``` - -### 4.1 RHI 抽象层接口 - -```cpp -namespace XCEngine { -namespace RHI { - -// ============================================ -// 跨平台枚举类型 - 不暴露平台特定类型 -// ============================================ - -enum class GraphicsAPI { - Unknown, - Direct3D11, - Direct3D12, - Vulkan, - Metal, - OpenGL -}; - -enum class CommandListType { - Direct, - Compute, - Copy, - Bundle -}; - -enum class ShaderVisibility { - All = 0, - Vertex = 1, - Hull = 2, - Domain = 3, - Geometry = 4, - Pixel = 5, - Amplification = 6, - Mesh = 7 -}; - -enum class ResourceStateFlag : uint32_t { - Common = 0, - VertexBuffer = 1 << 0, - ConstantBuffer = 1 << 1, - IndexBuffer = 1 << 2, - RenderTarget = 1 << 3, - UnorderedAccess = 1 << 4, - DepthWrite = 1 << 5, - DepthRead = 1 << 6, - ShaderResource = 1 << 7, - IndirectArgument = 1 << 8, - CopyDest = 1 << 9, - CopySource = 1 << 10, - ResolveDest = 1 << 11, - ResolveSource = 1 << 12, - Present = 1 << 13, - RaytracingAccelerationStructure = 1 << 14, - ShadingRateSource = 1 << 15 -}; - -inline ResourceStateFlag operator|(ResourceStateFlag a, ResourceStateFlag b) { - return static_cast(static_cast(a) | static_cast(b)); -} - -inline ResourceStateFlag operator&(ResourceStateFlag a, ResourceStateFlag b) { - return static_cast(static_cast(a) & static_cast(b)); -} - -enum class DescriptorHeapType { - CBV_SRV_UAV, - Sampler, - RTV, - DSV -}; - -enum class QueryType { - Occlusion, - Timestamp, - PipelineStatistics -}; - -// ============================================ -// 描述结构体 -// ============================================ - -struct CommandQueueDesc { - CommandListType type = CommandListType::Direct; - int priority = 0; - const char* name = nullptr; -}; - -struct DescriptorHeapDesc { - DescriptorHeapType type; - uint32_t count; - bool shaderVisible; - const char* name; -}; - -struct QueryHeapDesc { - QueryType type; - uint32_t count; - const char* name; -}; - -struct SwapChainDesc { - uint32_t width; - uint32_t height; - Format format; - uint32_t bufferCount; - bool vsync; - bool fullscreen; -}; - -class ICommandQueue { -public: - virtual ~ICommandQueue() = default; - - virtual void ExecuteCommandLists(ICommandList** lists, uint32_t count) = 0; - virtual void Signal(IFence* fence, uint64_t value) = 0; - virtual void Wait(IFence* fence, uint64_t value) = 0; - - virtual uint64_t GetTimestampFrequency() const = 0; -}; - -class ICommandAllocator { -public: - virtual ~ICommandAllocator() = default; - virtual void Reset() = 0; -}; - -class IFence { -public: - virtual ~IFence() = default; - - virtual uint64_t GetCompletedValue() const = 0; - virtual void Signal(uint64_t value) = 0; - virtual void Wait(uint64_t value) = 0; - virtual void Wait(uint64_t value, uint64_t timeoutMs) = 0; -}; - -class IDescriptorHeap { -public: - virtual ~IDescriptorHeap() = default; - - virtual DescriptorHeapType GetType() const = 0; - virtual uint32_t GetDescriptorCount() const = 0; - - virtual void* GetCPUDescriptorHandle(uint32_t index) const = 0; - virtual uint64_t GetGPUDescriptorHandle(uint32_t index) const = 0; - - virtual void SetName(const String& name) = 0; -}; - -class IQueryHeap { -public: - virtual ~IQueryHeap() = default; - - virtual QueryType GetType() const = 0; - virtual uint32_t GetCount() const = 0; - - virtual void Begin(ICommandList* cmdList, uint32_t index) = 0; - virtual void End(ICommandList* cmdList, uint32_t index) = 0; - virtual void GetData(uint32_t index, void* data, uint32_t dataSize) = 0; -}; - -class ISwapChain { -public: - virtual ~ISwapChain() = default; - - virtual bool Initialize(const SwapChainDesc& desc, void* windowHandle) = 0; - virtual void Shutdown() = 0; - - virtual bool Present() = 0; - virtual bool Resize(uint32_t width, uint32_t height) = 0; - - virtual uint32_t GetCurrentBufferIndex() const = 0; - virtual RenderTexture* GetBuffer(uint32_t index) = 0; - - virtual void SetFullscreen(bool fullscreen) = 0; - virtual bool IsFullscreen() const = 0; -}; - -struct RootParameter { - enum class Type { DescriptorTable, Constants, CBV, SRV, UAV, Sampler }; - Type type; - uint32_t shaderRegister; - uint32_t registerSpace; - ShaderVisibility visibility = ShaderVisibility::All; -}; - -struct RootSignatureDesc { - RootParameter* parameters; - uint32_t parameterCount; - uint32_t flags; -}; - -class RootSignature { -public: - virtual ~RootSignature() = default; - virtual bool Initialize(const RootSignatureDesc& desc) = 0; - virtual void SetName(const String& name) = 0; -}; - -struct PipelineDesc { - RootSignature* rootSignature; - Shader* vertexShader; - Shader* pixelShader; - Shader* computeShader; - RenderState renderState; - uint32_t numRenderTargets; - Format rtvFormats[8]; - Format dsvFormat; -}; - -class PipelineStateObject { -public: - virtual ~PipelineStateObject() = default; - virtual void SetName(const String& name) = 0; -}; - -class ICommandList { -public: - virtual ~ICommandList() = default; - - virtual void Reset(ICommandAllocator* allocator) = 0; - virtual void Close() = 0; - - virtual void BeginRenderPass(const RenderPassDesc& desc) = 0; - virtual void EndRenderPass() = 0; - - virtual void SetPipelineState(PipelineStateObject* pso) = 0; - virtual void SetRootSignature(RootSignature* signature) = 0; - - virtual void SetVertexBuffer(uint32_t slot, VertexBuffer* buffer, uint32_t offset = 0) = 0; - virtual void SetIndexBuffer(IndexBuffer* buffer, uint32_t offset = 0) = 0; - - virtual void SetDescriptorHeap(IDescriptorHeap* heap) = 0; - virtual void SetGraphicsDescriptorTable(uint32_t rootParameterIndex, uint64_t baseDescriptor) = 0; - virtual void SetComputeDescriptorTable(uint32_t rootParameterIndex, uint64_t baseDescriptor) = 0; - - virtual void DrawInstanced(uint32_t vertexCountPerInstance, uint32_t instanceCount, - uint32_t startVertex, uint32_t startInstance) = 0; - virtual void DrawIndexedInstanced(uint32_t indexCountPerInstance, uint32_t instanceCount, - uint32_t startIndex, int32_t baseVertex, uint32_t startInstance) = 0; - virtual void Dispatch(uint32_t x, uint32_t y, uint32_t z) = 0; - - virtual void SetViewports(const Viewport* viewports, uint32_t count) = 0; - virtual void SetScissorRects(const Rect* rects, uint32_t count) = 0; - virtual void SetRenderTargets(const RenderTarget* const* targets, uint32_t count, - const DepthStencil* depthStencil) = 0; - - virtual void ClearRenderTargetView(const RenderTarget* target, const float color[4]) = 0; - virtual void ClearDepthStencilView(const DepthStencil* depth, float depthValue, uint8_t stencil) = 0; - - virtual void CopyResource(IResource* dst, const IResource* src) = 0; - virtual void CopyBuffer(IResource* dst, uint64_t dstOffset, const IResource* src, - uint64_t srcOffset, uint64_t size) = 0; - - virtual void ResourceBarrier(IResource* resource, ResourceStateFlag before, - ResourceStateFlag after) = 0; -}; - -class IShaderResourceView { -public: - virtual ~IShaderResourceView() = default; -}; - -class IRenderTargetView { -public: - virtual ~IRenderTargetView() = default; -}; - -class IDepthStencilView { -public: - virtual ~IDepthStencilView() = default; -}; - -class IRHIDevice { -public: - virtual ~IRHIDevice() = default; - - virtual GraphicsAPI GetAPI() const = 0; - virtual const char* GetAPIName() const = 0; - - virtual bool Initialize() = 0; - virtual void Shutdown() = 0; - - virtual bool CreateCommandQueue(ICommandQueue** queue, const CommandQueueDesc& desc) = 0; - virtual bool CreateCommandAllocator(ICommandAllocator** allocator) = 0; - virtual bool CreateCommandList(ICommandList** list, ICommandAllocator* allocator) = 0; - virtual bool CreateFence(IFence** fence) = 0; - - virtual bool CreateDescriptorHeap(IDescriptorHeap** heap, const DescriptorHeapDesc& desc) = 0; - virtual bool CreateQueryHeap(IQueryHeap** heap, const QueryHeapDesc& desc) = 0; - - virtual bool CreateRootSignature(RootSignature** signature, const RootSignatureDesc& desc) = 0; - virtual bool CreatePipelineState(PipelineStateObject** pso, const PipelineDesc& desc) = 0; - - virtual bool CreateSwapChain(ISwapChain** swapChain, const SwapChainDesc& desc, void* windowHandle) = 0; - virtual bool CreateRenderTarget(RenderTarget** target, const RenderTargetDesc& desc) = 0; - virtual bool CreateDepthStencil(DepthStencil** depthStencil, const DepthStencilDesc& desc) = 0; - - virtual void* GetNativeDevice() const = 0; -}; - -} // namespace RHI -} // namespace XCEngine -``` - - - -### 4.3 Direct3D 12 特有的 RHI 实现 - -```cpp -// ============================================ -// D3D12 前向声明 -// ============================================ -struct ID3D12Device; -struct ID3D12CommandQueue; -struct ID3D12CommandAllocator; -struct ID3D12GraphicsCommandList; -struct ID3D12DescriptorHeap; -struct ID3D12RootSignature; -struct ID3D12PipelineState; -struct ID3D12Fence; -struct ID3D12QueryHeap; -struct IDXGISwapChain3; - -template -class ComPtr; - -namespace XCEngine { -namespace RHI { -namespace D3D12 { - -enum class D3D12_COMMAND_LIST_TYPE { - DIRECT = 0, - BUNDLE = 1, - COMPUTE = 2, - COPY = 3 -}; - -enum class D3D12_DESCRIPTOR_HEAP_TYPE { - CBV_SRV_UAV = 0, - SAMPLER = 1, - RTV = 2, - DSV = 3, - NUM_TYPES = 4 -}; - -enum class D3D12_DESCRIPTOR_HEAP_FLAGS { - NONE = 0, - SHADER_VISIBLE = 1 -}; - -enum class D3D12_RESOURCE_STATES { - COMMON = 0, - VERTEX_AND_CONSTANT_BUFFER = 0x1, - INDEX_BUFFER = 0x2, - RENDER_TARGET = 0x4, - UNORDERED_ACCESS = 0x8, - DEPTH_WRITE = 0x10, - DEPTH_READ = 0x20, - NON_PIXEL_SHADER_RESOURCE = 0x40, - PIXEL_SHADER_RESOURCE = 0x80, - STREAM_OUT = 0x100, - INDIRECT_ARGUMENT = 0x200, - COPY_DEST = 0x400, - COPY_SOURCE = 0x800, - RESOLVE_DEST = 0x1000, - RESOLVE_SOURCE = 0x2000, - RAYTRACING_ACCELERATION_STRUCTURE = 0x4000, - SHADING_RATE_COARSE = 0x8000, - SHADING_RATE_FINE = 0x10000, - PRESENT = 0x0 -}; - -enum class D3D12_SRV_DIMENSION { - UNKNOWN = 0, - BUFFER = 1, - TEXTURE1D = 2, - TEXTURE1DARRAY = 3, - TEXTURE2D = 4, - TEXTURE2DARRAY = 5, - TEXTURE2DMS = 6, - TEXTURE2DMSARRAY = 7, - TEXTURE3D = 8, - TEXTURECUBE = 9, - TEXTURECUBEARRAY = 10 -}; - -enum class D3D12_UAV_DIMENSION { - UNKNOWN = 0, - BUFFER = 1, - TEXTURE1D = 2, - TEXTURE1DARRAY = 3, - TEXTURE2D = 4, - TEXTURE2DARRAY = 5, - TEXTURE3D = 8 -}; - -enum class D3D12_RTV_DIMENSION { - UNKNOWN = 0, - BUFFER = 1, - TEXTURE1D = 2, - TEXTURE1DARRAY = 3, - TEXTURE2D = 4, - TEXTURE2DARRAY = 5, - TEXTURE2DMS = 6, - TEXTURE2DMSARRAY = 7, - TEXTURE3D = 8 -}; - -enum class D3D12_DSV_DIMENSION { - UNKNOWN = 0, - TEXTURE1D = 1, - TEXTURE1DARRAY = 2, - TEXTURE2D = 3, - TEXTURE2DARRAY = 4, - TEXTURE2DMS = 5, - TEXTURE2DMSARRAY = 6 -}; - -enum class D3D12_ROOT_SIGNATURE_FLAGS { - NONE = 0, - ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT = 0x1, - DENY_VERTEX_SHADER_ROOT_ACCESS = 0x2, - DENY_HULL_SHADER_ROOT_ACCESS = 0x4, - DENY_DOMAIN_SHADER_ROOT_ACCESS = 0x8, - DENY_GEOMETRY_SHADER_ROOT_ACCESS = 0x10, - DENY_PIXEL_SHADER_ROOT_ACCESS = 0x20, - ALLOW_STREAM_PIPELINE = 0x40, - LOCAL_ROOT_SIGNATURE = 0x80 -}; - -enum class D3D12_ROOT_PARAMETER_TYPE { - DESCRIPTOR_TABLE = 0, - _32BIT_CONSTANTS = 1, - CBV = 2, - SRV = 3, - UAV = 4, - SAMPLER = 5 -}; - -enum class D3D12_SHADER_VISIBILITY { - ALL = 0, - VERTEX = 1, - HULL = 2, - DOMAIN = 3, - GEOMETRY = 4, - PIXEL = 5 -}; - -enum class D3D12_FILTER { - MIN_MAG_MIP_POINT = 0, - MIN_MAG_POINT_MIP_LINEAR = 0x1, - MIN_POINT_MAG_LINEAR_MIP_POINT = 0x2, - MIN_POINT_MAG_MIP_LINEAR = 0x3, - MIN_LINEAR_MAG_MIP_POINT = 0x4, - MIN_LINEAR_MAG_POINT_MIP_LINEAR = 0x5, - MIN_MAG_LINEAR_MIP_POINT = 0x6, - MIN_MAG_MIP_LINEAR = 0x7, - ANISOTROPIC = 0x15, - COMPARISON_MIN_MAG_MIP_POINT = 0x80, - COMPARISON_MIN_MAG_POINT_MIP_LINEAR = 0x81, - COMPARISON_MIN_POINT_MAG_LINEAR_MIP_POINT = 0x82, - COMPARISON_MIN_POINT_MAG_MIP_LINEAR = 0x83, - COMPARISON_MIN_LINEAR_MAG_MIP_POINT = 0x84, - COMPARISON_MIN_LINEAR_MAG_POINT_MIP_LINEAR = 0x85, - COMPARISON_MIN_MAG_LINEAR_MIP_POINT = 0x86, - COMPARISON_MIN_MAG_MIP_LINEAR = 0x87, - COMPARISON_ANISOTROPIC = 0x95 -}; - -enum class D3D12_TEXTURE_ADDRESS_MODE { - WRAP = 1, - MIRROR = 2, - CLAMP = 3, - BORDER = 4, - MIRROR_ONCE = 5 -}; - -enum class D3D12_COMPARISON_FUNC { - NEVER = 1, - LESS = 2, - EQUAL = 3, - LESS_EQUAL = 4, - GREATER = 5, - NOT_EQUAL = 6, - GREATER_EQUAL = 7, - ALWAYS = 8 -}; - -enum class D3D12_BLEND { - ZERO = 1, - ONE = 2, - SRC_COLOR = 3, - INV_SRC_COLOR = 4, - SRC_ALPHA = 5, - INV_SRC_ALPHA = 6, - DST_ALPHA = 7, - INV_DST_ALPHA = 8, - DST_COLOR = 9, - INV_DST_COLOR = 10, - SRC_ALPHA_SAT = 11, - BLEND_FACTOR = 14, - INV_BLEND_FACTOR = 15, - SRC1_COLOR = 16, - INV_SRC1_COLOR = 17, - SRC1_ALPHA = 18, - INV_SRC1_ALPHA = 19 -}; - -enum class D3D12_BLEND_OP { - ADD = 1, - SUBTRACT = 2, - REV_SUBTRACT = 3, - MIN = 4, - MAX = 5 -}; - -enum class D3D12_COLOR_WRITE_ENABLE { - RED = 1, - GREEN = 2, - BLUE = 4, - ALPHA = 8, - ALL = 15 -}; - -enum class D3D12_CULL_MODE { - NONE = 1, - FRONT = 2, - BACK = 3 -}; - -enum class D3D12_FILL_MODE { - WIREFRAME = 2, - SOLID = 3 -}; - -enum class D3D12_PRIMITIVE_TOPOLOGY_TYPE { - UNDEFINED = 0, - POINT = 1, - LINE = 2, - TRIANGLE = 3, - PATCH = 4 -}; - -enum class D3D12_PRIMITIVE_TOPOLOGY { - UNDEFINED = 0, - POINTLIST = 1, - LINELIST = 2, - LINESTRIP = 3, - TRIANGLELIST = 4, - TRIANGLESTRIP = 5, - TRIANGLEFAN = 6, - LINELIST_ADJ = 10, - LINESTRIP_ADJ = 11, - TRIANGLELIST_ADJ = 12, - TRIANGLESTRIP_ADJ = 13, - PATCHLIST_1 = 33, - PATCHLIST_2 = 34, - PATCHLIST_3 = 35, - PATCHLIST_4 = 36, - PATCHLIST_5 = 37, - PATCHLIST_6 = 38, - PATCHLIST_7 = 39, - PATCHLIST_8 = 40, - PATCHLIST_9 = 41, - PATCHLIST_10 = 42, - PATCHLIST_11 = 43, - PATCHLIST_12 = 44, - PATCHLIST_13 = 45, - PATCHLIST_14 = 46, - PATCHLIST_15 = 47, - PATCHLIST_16 = 48, - PATCHLIST_17 = 49, - PATCHLIST_18 = 50, - PATCHLIST_19 = 51, - PATCHLIST_20 = 52, - PATCHLIST_21 = 53, - PATCHLIST_22 = 54, - PATCHLIST_23 = 55, - PATCHLIST_24 = 56, - PATCHLIST_25 = 57, - PATCHLIST_26 = 58, - PATCHLIST_27 = 59, - PATCHLIST_28 = 60, - PATCHLIST_29 = 61, - PATCHLIST_30 = 62, - PATCHLIST_31 = 63, - PATCHLIST_32 = 64 -}; - -enum class D3D12_QUERY_TYPE { - OCCLUSION = 0, - TIMESTAMP = 1, - PIPELINE_STATISTICS = 2, - DEPTH_STENCIL_CLIP_CUT = 3 -}; - -enum class D3D12_QUERY_HEAP_TYPE { - OCCLUSION = 0, - TIMESTAMP = 1, - PIPELINE_STATISTICS = 2, - SO_STATISTICS = 3, - VIDEO_DECODE_STATISTICS = 4, - COPY_QUEUE_TIMESTAMP = 5, - TIME_STAMP = 6 -}; - -struct D3D12_CPU_DESCRIPTOR_HANDLE { - void* ptr; -}; - -struct D3D12_GPU_DESCRIPTOR_HANDLE { - uint64_t ptr; -}; - -struct D3D12_RESOURCE_BARRIER { - enum class Type { - TRANSITION, - ALIASING, - UDV - }; - - Type Type; - - union { - struct { - IResource* pResource; - int32_t Subresource; - D3D12_RESOURCE_STATES StateBefore; - D3D12_RESOURCE_STATES StateAfter; - } Transition; - - struct { - IResource* pResourceBefore; - IResource* pResourceAfter; - } Aliasing; - - struct { - IResource* pResource; - uint32_t NumRoadmapEntries; - uint32_t* pRoadmap; - D3D12_RESOURCE_STATES StateBefore; - D3D12_RESOURCE_STATES StateAfter; - } UAV; - }; -}; - -struct D3D12_VIEWPORT { - float TopLeftX; - float TopLeftY; - float Width; - float Height; - float MinDepth; - float MaxDepth; -}; - -struct D3D12_RECT { - int32_t left; - int32_t top; - int32_t right; - int32_t bottom; -}; - -struct D3D12_INPUT_ELEMENT_DESC { - const char* SemanticName; - uint32_t SemanticIndex; - Format Format; - uint32_t InputSlot; - uint32_t AlignedByteOffset; - D3D12_INPUT_CLASSIFICATION InputSlotClass; - uint32_t InstanceDataStepRate; -}; - -enum class D3D12_INPUT_CLASSIFICATION { - PER_VERTEX_DATA = 0, - PER_INSTANCE_DATA = 1 -}; - -struct D3D12_SO_DECLARATION_ENTRY { - const char* SemanticName; - uint32_t SemanticIndex; - uint8_t StartComponent; - uint8_t ComponentCount; - uint8_t OutputSlot; -}; - -struct D3D12_SHADER_RESOURCE_VIEW_DESC { - D3D12_SRV_DIMENSION ViewDimension; - Format Format; - uint32_t Shader4ComponentMapping; - - union { - struct { - uint32_t MostDetailedMip; - uint32_t MipLevels; - uint32_t PlaneSlice; - float ResourceMinLODClamp; - } Texture2D; - - struct { - uint32_t MostDetailedMip; - uint32_t MipLevels; - uint32_t FirstArraySlice; - uint32_t ArraySize; - uint32_t PlaneSlice; - float ResourceMinLODClamp; - } Texture2DArray; - - struct { - uint32_t FirstWSlice; - uint32_t WSize; - uint32_t MostDetailedMip; - uint32_t MipLevels; - float ResourceMinLODClamp; - } Texture3D; - - struct { - uint32_t MostDetailedMip; - uint32_t MipLevels; - uint32_t FirstArraySlice; - uint32_t ArraySize; - uint32_t PlaneSlice; - float ResourceMinLODClamp; - } TextureCube; - }; -}; - -struct D3D12_UNORDERED_ACCESS_VIEW_DESC { - D3D12_UAV_DIMENSION ViewDimension; - Format Format; - uint32_t Shader4ComponentMapping; - - union { - struct { - uint32_t MipSlice; - } Buffer; - - struct { - uint32_t MipSlice; - uint32_t FirstArraySlice; - uint32_t ArraySize; - uint32_t PlaneSlice; - } Texture2D; - - struct { - uint32_t MipSlice; - uint32_t FirstWSlice; - uint32_t WSize; - } Texture3D; - }; -}; - -struct D3D12_RENDER_TARGET_VIEW_DESC { - D3D12_RTV_DIMENSION ViewDimension; - Format Format; - - union { - struct { - uint32_t MipSlice; - uint32_t FirstArraySlice; - uint32_t ArraySize; - } Texture2D; - - struct { - uint32_t MipSlice; - uint32_t FirstArraySlice; - uint32_t ArraySize; - uint32_t PlaneSlice; - } Texture2DMS; - }; -}; - -struct D3D12_DEPTH_STENCIL_VIEW_DESC { - D3D12_DSV_DIMENSION ViewDimension; - Format Format; - uint32_t MipSlice; - uint32_t FirstArraySlice; - uint32_t ArraySize; - uint32_t PlaneSlice; -}; - -struct D3D12_CONSTANT_BUFFER_VIEW_DESC { - uint64_t BufferLocation; - uint32_t SizeInBytes; -}; - -struct D3D12_SAMPLER_DESC { - D3D12_FILTER Filter; - D3D12_TEXTURE_ADDRESS_MODE AddressU; - D3D12_TEXTURE_ADDRESS_MODE AddressV; - D3D12_TEXTURE_ADDRESS_MODE AddressW; - float MipLODBias; - uint32_t MaxAnisotropy; - D3D12_COMPARISON_FUNC ComparisonFunc; - float BorderColor[4]; - float MinLOD; - float MaxLOD; -}; - -struct InputLayout { - D3D12_INPUT_ELEMENT_DESC* pInputElementDescs = nullptr; - uint32_t NumInputElementDescs = 0; -}; - -struct StreamOutput { - D3D12_SO_DECLARATION_ENTRY* pSODeclaration = nullptr; - uint32_t NumSODeclarationEntries = 0; - uint64_t* pBufferStrides = nullptr; - uint32_t NumBufferStrides = 0; - uint32_t RasterizedStream = 0; -}; - -struct BlendState { - bool AlphaToCoverageEnable = false; - bool IndependentBlendEnable = false; - - struct RenderTargetBlendDesc { - bool BlendEnable = false; - D3D12_BLEND SrcBlend = D3D12_BLEND::ONE; - D3D12_BLEND DestBlend = D3D12_BLEND::ZERO; - D3D12_BLEND_OP BlendOp = D3D12_BLEND_OP::ADD; - D3D12_BLEND SrcBlendAlpha = D3D12_BLEND::ONE; - D3D12_BLEND DestBlendAlpha = D3D12_BLEND::ZERO; - D3D12_BLEND_OP BlendOpAlpha = D3D12_BLEND_OP::ADD; - D3D12_COLOR_WRITE_ENABLE RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE::ALL; - }; - RenderTargetBlendDesc RenderTarget[8]; -}; - -struct RasterizerState { - D3D12_FILL_MODE FillMode = D3D12_FILL_MODE::SOLID; - D3D12_CULL_MODE CullMode = D3D12_CULL_MODE::BACK; - bool FrontCounterClockwise = false; - int32_t DepthBias = 0; - float DepthBiasClamp = 0.0f; - float SlopeScaledDepthBias = 0.0f; - bool DepthClipEnable = true; - bool MultisampleEnable = false; - bool AntialiasedLineEnable = false; - uint32_t ForcedSampleCount = 0; - bool ConservativeRaster = false; -}; - -struct DepthStencilState { - bool DepthEnable = true; - D3D12_DEPTH_WRITE_MASK DepthWriteMask = D3D12_DEPTH_WRITE_MASK::ALL; - D3D12_COMPARISON_FUNC DepthFunc = D3D12_COMPARISON_FUNC::LESS; - bool StencilEnable = false; - uint8_t StencilReadMask = 0xFF; - uint8_t StencilWriteMask = 0xFF; - - struct DepthStencilOpDesc { - D3D12_STENCIL_OP StencilFailOp = D3D12_STENCIL_OP::KEEP; - D3D12_STENCIL_OP StencilDepthFailOp = D3D12_STENCIL_OP::KEEP; - D3D12_STENCIL_OP StencilPassOp = D3D12_STENCIL_OP::KEEP; - D3D12_COMPARISON_FUNC StencilFunc = D3D12_COMPARISON_FUNC::ALWAYS; - }; - DepthStencilOpDesc FrontFace; - DepthStencilOpDesc BackFace; -}; - -enum class D3D12_DEPTH_WRITE_MASK { - ZERO = 0, - ALL = 1 -}; - -enum class D3D12_STENCIL_OP { - KEEP = 1, - ZERO = 2, - REPLACE = 3, - INCR_SAT = 4, - DECR_SAT = 5, - INVERT = 6, - INCR = 7, - DECR = 8 -}; - -struct DXGI_SAMPLE_DESC { - uint32_t Count = 1; - uint32_t Quality = 0; -}; - -struct CachedPSO { - void* pCachedBlob = nullptr; - size_t CachedBlobSizeInBytes = 0; -}; - -enum class D3D12_PIPELINE_STATE_FLAGS { - NONE = 0, - TOOL_DEBUG = 1 -}; - -struct D3D12_GRAPHICS_PIPELINE_STATE_DESC { - InputLayout* InputLayout; - RootSignature* pRootSignature; - Shader* VS; - Shader* HS; - Shader* DS; - Shader* GS; - Shader* PS; - StreamOutput* StreamOutput; - BlendState* BlendState; - uint32_t SampleMask; - RasterizerState* RasterizerState; - DepthStencilState* DepthStencilState; - D3D12_PRIMITIVE_TOPOLOGY_TYPE PrimitiveTopologyType; - uint32_t NumRenderTargets; - Format RTVFormats[8]; - Format DSVFormat; - DXGI_SAMPLE_DESC SampleDesc; - uint32_t NodeMask; - CachedPSO* cachedPSO; - D3D12_PIPELINE_STATE_FLAGS Flags; -}; - -struct D3D12_COMPUTE_PIPELINE_STATE_DESC { - RootSignature* pRootSignature; - Shader* CS; - uint32_t NodeMask; - CachedPSO* cachedPSO; - D3D12_PIPELINE_STATE_FLAGS Flags; -}; - -class D3D12Device : public IRHIDevice { -public: - D3D12Device(); - virtual ~D3D12Device(); - - GraphicsAPI GetAPI() const override { return GraphicsAPI::Direct3D12; } - const char* GetAPIName() const override { return "Direct3D 12"; } - - bool Initialize() override; - void Shutdown() override; - - ID3D12Device* GetD3D12Device() { return m_device.Get(); } - ID3D12CommandQueue* GetCommandQueue(D3D12_COMMAND_LIST_TYPE type); - - bool CreateCommandQueue(ICommandQueue** queue, const CommandQueueDesc& desc) override; - bool CreateCommandAllocator(ICommandAllocator** allocator) override; - bool CreateCommandList(ICommandList** list, ICommandAllocator* allocator) override; - bool CreateFence(IFence** fence) override; - - bool CreateDescriptorHeap(IDescriptorHeap** heap, const DescriptorHeapDesc& desc) override; - bool CreateQueryHeap(IQueryHeap** heap, const QueryHeapDesc& desc) override; - - bool CreateRootSignature(RootSignature** signature, const RootSignatureDesc& desc) override; - bool CreatePipelineState(PipelineStateObject** pso, const PipelineDesc& desc) override; - - bool CreateSwapChain(ISwapChain** swapChain, const SwapChainDesc& desc, void* windowHandle) override; - - bool CreateRenderTarget(RenderTarget** target, const RenderTargetDesc& desc) override; - bool CreateDepthStencil(DepthStencil** depthStencil, const DepthStencilDesc& desc) override; - - void* GetNativeDevice() const override { return m_device.Get(); } - -private: - ComPtr m_device; - ComPtr m_directQueue; - ComPtr m_computeQueue; - ComPtr m_copyQueue; - - D3D_FEATURE_LEVEL m_featureLevel; - bool m_raytracingSupported; - bool m_meshShadersSupported; -}; - -class D3D12CommandList : public ICommandList { -public: - D3D12CommandList(ID3D12CommandAllocator* allocator); - virtual ~D3D12CommandList(); - - void Reset(ICommandAllocator* allocator) override; - void Close() override; - - void BeginRenderPass(const RenderPassDesc& desc) override; - void EndRenderPass() override; - - void SetPipelineState(PipelineStateObject* pso) override; - void SetRootSignature(RootSignature* signature) override; - - void SetVertexBuffer(uint32_t slot, VertexBuffer* buffer, uint32_t offset = 0) override; - void SetIndexBuffer(IndexBuffer* buffer, uint32_t offset = 0) override; - - void SetDescriptorHeap(IDescriptorHeap* heap) override; - void SetGraphicsDescriptorTable(uint32_t rootParameterIndex, uint64_t baseDescriptor) override; - void SetComputeDescriptorTable(uint32_t rootParameterIndex, uint64_t baseDescriptor) override; - - void DrawInstanced( - uint32_t vertexCountPerInstance, - uint32_t instanceCount, - uint32_t startVertex, - uint32_t startInstance - ) override; - - void DrawIndexedInstanced( - uint32_t indexCountPerInstance, - uint32_t instanceCount, - uint32_t startIndex, - int32_t baseVertex, - uint32_t startInstance - ) override; - - void Dispatch(uint32_t threadGroupCountX, uint32_t threadGroupCountY, uint32_t threadGroupCountZ) override; - - void SetViewports(const Viewport* viewports, uint32_t count) override; - void SetScissorRects(const Rect* rects, uint32_t count) override; - - void SetRenderTargets( - const RenderTarget* const* renderTargets, - uint32_t renderTargetCount, - const DepthStencil* depthStencil - ) override; - - void ClearRenderTargetView(const RenderTarget* target, const float color[4]) override; - void ClearDepthStencilView(const DepthStencil* depthStencil, float depth, uint8_t stencil) override; - - void CopyResource(IResource* dst, const IResource* src) override; - void CopyBuffer(IResource* dst, uint64_t dstOffset, const IResource* src, uint64_t srcOffset, uint64_t size) override; - - void ResourceBarrier( - IResource* resource, - ResourceStateFlag stateBefore, - ResourceStateFlag stateAfter - ); - - ID3D12GraphicsCommandList* GetD3D12CommandList() { return m_commandList.Get(); } - -private: - ComPtr m_commandList; - D3D12_PRIMITIVE_TOPOLOGY m_topology = D3D12_PRIMITIVE_TOPOLOGY::UNDEFINED; -}; - -class D3D12DescriptorHeap : public IDescriptorHeap { -public: - D3D12DescriptorHeap(ID3D12DescriptorHeap* heap, DescriptorHeapType type, uint32_t count); - virtual ~D3D12DescriptorHeap(); - - DescriptorHeapType GetType() const override { return m_type; } - uint32_t GetDescriptorCount() const override { return m_descriptorCount; } - - void* GetCPUDescriptorHandle(uint32_t index) const override; - uint64_t GetGPUDescriptorHandle(uint32_t index) const override; - - void SetName(const String& name) override; - - ID3D12DescriptorHeap* GetD3D12DescriptorHeap() { return m_heap.Get(); } - -private: - ComPtr m_heap; - DescriptorHeapType m_type; - uint32_t m_descriptorCount; - uint32_t m_descriptorSize; -}; - -class D3D12RootSignature : public RootSignature { -public: - D3D12RootSignature(); - virtual ~D3D12RootSignature(); - - bool Initialize(const RootSignatureDesc& desc) override; - void SetName(const String& name) override; - - ID3D12RootSignature* GetD3D12RootSignature() { return m_rootSignature.Get(); } - -private: - bool SerializeRootSignature( - const RootSignatureDesc& desc, - std::vector& serializedData - ); - - ComPtr m_rootSignature; -}; - -class D3D12PipelineState : public PipelineStateObject { -public: - D3D12PipelineState(); - virtual ~D3D12PipelineState(); - - bool Initialize(const PipelineDesc& desc); - void SetName(const String& name); - - ID3D12PipelineState* GetD3D12PipelineState() { return m_pipelineState.Get(); } - -private: - ComPtr m_pipelineState; -}; - -class D3D12Fence : public IFence { -public: - D3D12Fence(ID3D12Fence* fence); - virtual ~D3D12Fence(); - - uint64_t GetCompletedValue() const override; - void Signal(uint64_t value) override; - void Wait(uint64_t value) override; - void Wait(uint64_t value, uint64_t timeout) override; - - ID3D12Fence* GetD3D12Fence() { return m_fence.Get(); } - - HANDLE GetEventHandle() { return m_event; } - -private: - ComPtr m_fence; - HANDLE m_event; -}; - -class D3D12QueryHeap : public IQueryHeap { -public: - D3D12QueryHeap(ID3D12QueryHeap* heap, QueryType type, uint32_t count); - virtual ~D3D12QueryHeap(); - - QueryType GetType() const override { return m_type; } - uint32_t GetCount() const override { return m_count; } - - void Begin(ICommandList* cmdList, uint32_t index) override; - void End(ICommandList* cmdList, uint32_t index) override; - - void GetData(uint32_t index, void* data, uint32_t dataSize) override; - - ID3D12QueryHeap* GetD3D12QueryHeap() { return m_queryHeap.Get(); } - -private: - ComPtr m_queryHeap; - QueryType m_type; - uint32_t m_count; -}; - -class D3D12SwapChain : public ISwapChain { -public: - D3D12SwapChain(IDXGISwapChain3* swapChain); - virtual ~D3D12SwapChain(); - - bool Initialize(const SwapChainDesc& desc, void* windowHandle) override; - void Shutdown() override; - - bool Present() override; - bool Resize(uint32_t width, uint32_t height) override; - - uint32_t GetCurrentBufferIndex() const override; - RenderTexture* GetBuffer(uint32_t index) override; - - void SetFullscreen(bool fullscreen) override; - bool IsFullscreen() const override; - - IDXGISwapChain3* GetD3D12SwapChain() { return m_swapChain.Get(); } - -private: - ComPtr m_swapChain; - std::vector m_buffers; - uint32_t m_bufferCount; - bool m_fullscreen; -}; - -} // namespace D3D12 -} // namespace RHI -} // namespace XCEngine - -namespace XCEngine { -namespace Rendering { - -using ICommandList = RHI::ICommandList; -using IBuffer = RHI::IResource; - -enum class BufferUsage { - Default, - Immutable, - Dynamic, - Staging -}; - -enum class MemoryUsage { - Default, - GPU_Only, - CPU_Only, - CPU_GPU -}; - -enum class PassType { - Graphics, - Compute, - Copy, - Raytracing -}; - -enum class LoadAction { - Load, - Clear, - DontCare -}; - -enum class StoreAction { - Store, - Resolve, - StoreAndResolve, - DontCare -}; - -enum class ResolveMode { - None, - Min, - Average, - Sample0, - Sample1, - Sample2, - Sample3, - Sample4, - Sample5, - Sample6, - Sample7 -}; - -enum class ComparisonFunc { - Never, - Less, - Equal, - LessEqual, - Greater, - NotEqual, - GreaterEqual, - Always -}; - -enum class BorderColor { - TransparentBlack, - OpaqueBlack, - OpaqueWhite -}; - -struct IndexType { - static constexpr uint8_t Uint16 = 0; - static constexpr uint8_t Uint32 = 1; -}; - -enum class ResourceState { - Undefined = 0, - Common = 1, - VertexBuffer = 2, - ConstantBuffer = 3, - IndexBuffer = 4, - RenderTarget = 5, - UnorderedAccess = 6, - DepthWrite = 7, - DepthRead = 8, - ShaderResource = 9, - IndirectArgument = 10, - CopyDest = 11, - CopySource = 12, - ResolveDest = 13, - ResolveSource = 14, - Present = 15, - GenericRead = 16 -}; - -struct ResourceDesc { - ResourceType type; - uint32_t width = 1; - uint32_t height = 1; - uint32_t depth = 1; - uint32_t mipLevels = 1; - uint32_t arraySize = 1; - Format format = Format::Unknown; - SampleCount sampleCount = SampleCount::Count1; - ResourceState initialState = ResourceState::Undefined; - bool cpuAccessible = false; - bool randomAccess = false; - - MemoryUsage memoryUsage = MemoryUsage::Default; - - String name; -}; - -class RenderContext { -public: - class GBufferPass; - class LightingPass; - class ShadowPass; - class PostProcessPass; - - static RenderContext* Create(const RenderContextDesc& desc); - static void Destroy(RenderContext* context); - - static RenderContext* GetMain(); - - void Initialize(const RenderContextDesc& desc); - void Shutdown(); - - RHI::IRHIDevice* GetDevice() { return m_device.get(); } - - void BeginFrame(); - void EndFrame(); - - ICommandList* GetMainCommandList() { return m_mainCommandList.get(); } - - void SetRenderTarget(const RenderTarget& target); - void SetViewport(const Viewport& viewport); - void SetScissorRect(const Rect& rect); - - void RenderScene(const SceneRenderDesc& desc); - void ApplyPostProcessing(const PostProcessDesc& desc); - - void CaptureFrame(const String& filePath); - void ToggleDebugView(); - -private: - std::unique_ptr m_device; - std::unique_ptr m_mainCommandList; - std::unique_ptr m_swapChain; - - std::unique_ptr m_gBufferPass; - std::unique_ptr m_lightingPass; - std::unique_ptr m_shadowPass; - std::unique_ptr m_postProcessPass; - - uint32_t m_frameIndex = 0; - uint64_t m_frameCount = 0; - - static RenderContext* s_mainContext; -}; - -struct ResourceGUID { - static constexpr uint64_t INVALID = 0; - - uint64_t value = INVALID; - - bool IsValid() const { return value != INVALID; } - bool operator==(const ResourceGUID& other) const { return value == other.value; } - bool operator!=(const ResourceGUID& other) const { return value != other.value; } - - String ToString() const; - static ResourceGUID FromString(const String& str); - - struct Hash { - size_t operator()(const ResourceGUID& guid) const { - return std::hash{}(guid.value); - } - }; -}; - -class RenderTexture { -public: - uint32_t GetWidth() const { return m_width; } - uint32_t GetHeight() const { return m_height; } - uint32_t GetMipLevels() const { return m_mipLevels; } - Format GetFormat() const { return m_format; } - - void* GetNativeHandle() const { return m_nativeHandle; } - - IShaderResourceView* GetSRV() { return m_srv.get(); } - IRenderTargetView* GetRTV() { return m_rtv.get(); } - IDepthStencilView* GetDSV() { return m_dsv.get(); } - - void* Map(); - void Unmap(); - -private: - uint32_t m_width, m_height; - uint32_t m_mipLevels; - Format m_format; - void* m_nativeHandle = nullptr; - - std::unique_ptr m_srv; - std::unique_ptr m_rtv; - std::unique_ptr m_dsv; -}; - -class RenderBuffer { -public: - uint64_t GetSize() const { return m_size; } - BufferUsage GetUsage() const { return m_usage; } - - void* GetNativeHandle() const { return m_nativeHandle; } - - void SetData(const void* data, uint64_t size, uint64_t offset = 0); - void* Map(); - void Unmap(); - -private: - uint64_t m_size; - BufferUsage m_usage; - void* m_nativeHandle = nullptr; -}; - -} // namespace Rendering -} // namespace XCEngine -``` - -### 4.4 相机组件 - -```cpp -namespace XCEngine { - -class CameraComponent : public Component { -public: - void Awake() override; - void Start() override; - void Update(float deltaTime) override; - - enum class ProjectionType : uint8_t { - Perspective, - Orthographic - }; - - void SetProjectionType(ProjectionType type) { m_projectionType = type; } - ProjectionType GetProjectionType() const { return m_projectionType; } - - void SetFieldOfView(float fov) { m_fieldOfView = fov; } - float GetFieldOfView() const { return m_fieldOfView; } - - void SetOrthographicSize(float size) { m_orthographicSize = size; } - float GetOrthographicSize() const { return m_orthographicSize; } - - void SetNearClipPlane(float near) { m_nearPlane = near; } - float GetNearClipPlane() const { return m_nearPlane; } - - void SetFarClipPlane(float far) { m_farPlane = far; } - float GetFarClipPlane() const { return m_farPlane; } - - void SetViewport(const Rect& rect) { m_viewportRect = rect; } - const Rect& GetViewport() const { return m_viewportRect; } - - void SetCullingMask(int32_t mask) { m_cullingMask = mask; } - int32_t GetCullingMask() const { return m_cullingMask; } - - void SetDepth(float depth) { m_depth = depth; } - float GetDepth() const { return m_depth; } - - Matrix4x4 GetViewMatrix() const; - Matrix4x4 GetProjectionMatrix() const; - Matrix4x4 GetViewProjectionMatrix() const; - - Vector3 ScreenToWorldPoint(const Vector3& screenPoint) const; - Vector3 WorldToScreenPoint(const Vector3& worldPoint) const; - Ray ScreenPointToRay(const Vector2& screenPoint) const; - - Frustum& GetFrustum() { return m_frustum; } - -private: - ProjectionType m_projectionType = ProjectionType::Perspective; - float m_fieldOfView = 60.0f; - float m_orthographicSize = 5.0f; - float m_nearPlane = 0.1f; - float m_farPlane = 1000.0f; - float m_aspectRatio = 16.0f / 9.0f; - Rect m_viewportRect = Rect(0, 0, 1, 1); - float m_depth = 0.0f; - int32_t m_cullingMask = -1; - Frustum m_frustum; -}; - -} // namespace XCEngine -``` - -### 4.5 光照组件 - -```cpp -namespace XCEngine { - -class LightComponent : public Component { -public: - void Awake() override; - void Update(float deltaTime) override; - - enum class LightType : uint8_t { - Directional, - Point, - Spot, - Area - }; - - void SetLightType(LightType type) { m_type = type; } - LightType GetLightType() const { return m_type; } - - void SetColor(const Vector3& color) { m_color = color; } - Vector3 GetColor() const { return m_color; } - - void SetIntensity(float intensity) { m_intensity = intensity; } - float GetIntensity() const { return m_intensity; } - - void SetRange(float range) { m_range = range; } - float GetRange() const { return m_range; } - - void SetSpotAngle(float angle) { m_spotAngle = angle; } - float GetSpotAngle() const { return m_spotAngle; } - - void SetCastShadows(bool cast) { m_castShadows = cast; } - bool GetCastShadows() const { return m_castShadows; } - - void SetShadowResolution(uint32_t resolution) { m_shadowResolution = resolution; } - uint32_t GetShadowResolution() const { return m_shadowResolution; } - - void SetCullingMask(int32_t mask) { m_cullingMask = mask; } - int32_t GetCullingMask() const { return m_cullingMask; } - -private: - LightType m_type = LightType::Point; - Vector3 m_color = Vector3::One(); - float m_intensity = 1.0f; - float m_range = 10.0f; - float m_spotAngle = 30.0f; - float m_penumbraAngle = 5.0f; - bool m_castShadows = true; - uint32_t m_shadowResolution = 1024; - float m_shadowBias = 0.005f; - float m_normalOffsetBias = 0.001f; - float m_nearPlane = 0.1f; - int32_t m_cullingMask = -1; - float m_intensityVariation = 0.0f; - ResourceGUID m_cookieTextureGuid; - float m_cookieSize = 5.0f; -}; - -} // namespace XCEngine -``` - -### 4.6 渲染网格组件 - -```cpp -namespace XCEngine { - -class RenderMeshComponent : public Component { -public: - void Awake() override; - void Start() override; - void Update(float deltaTime) override; - - ResourceGUID GetMesh() const { return m_meshGuid; } - void SetMesh(const ResourceGUID& guid); - - ResourceGUID GetMaterial() const { return m_materialGuid; } - void SetMaterial(const ResourceGUID& guid); - - bool GetCastShadows() const { return m_castShadows; } - void SetCastShadows(bool cast) { m_castShadows = cast; } - - bool GetReceiveShadows() const { return m_receiveShadows; } - void SetReceiveShadows(bool receive) { m_receiveShadows = receive; } - -private: - ResourceGUID m_meshGuid; - ResourceGUID m_materialGuid; - bool m_castShadows = true; - bool m_receiveShadows = true; -}; - -} // namespace XCEngine -``` - -### 4.7 材质系统 - -```cpp -namespace XCEngine { -namespace Rendering { - -enum class ShaderType { - Vertex, - Fragment, - Compute, - Geometry, - Hull, - Domain -}; - -enum class UniformType { - None, - Float, - Float2, - Float3, - Float4, - Int, - Int2, - Int3, - Int4, - Bool, - Sampler2D, - Sampler3D, - SamplerCube, - Sampler2DArray, - Matrix3x3, - Matrix4x4 -}; - -struct UniformDesc { - String name; - UniformType type; - uint32_t arraySize = 1; - uint32_t offset = 0; -}; - -struct ShaderUniformBlock { - String name; - uint32_t bindingSlot = 0; - uint32_t size = 0; - std::vector uniforms; -}; - -class Shader : public IResource { -public: - const String& GetSource() const { return m_source; } - ShaderType GetType() const { return m_type; } - - const std::vector& GetUniformBlocks() const { return m_uniformBlocks; } - const std::vector& GetResources() const { return m_resources; } - - bool HasVariant(const String& variantName) const; - Shader* GetVariant(const HashMap& defines); - - bool IsCompiled() const { return m_compiled; } - const String& GetCompileErrors() const { return m_compileErrors; } - -private: - String m_source; - ShaderType m_type; - bool m_compiled = false; - String m_compileErrors; - - std::vector m_uniformBlocks; - std::vector m_resources; - HashMap> m_variants; -}; - -enum class BlendMode { - Opaque, - Transparent, - Additive, - Multiply, - AlphaTest -}; - -enum class CullMode { - None, - Front, - Back -}; - -enum class ZWriteMode { - On, - Off -}; - -enum class DepthTest { - Never, - Less, - Equal, - LessEqual, - Greater, - NotEqual, - GreaterEqual, - Always -}; - -struct RenderState { - BlendMode blendMode = BlendMode::Opaque; - CullMode cullMode = CullMode::Back; - ZWriteMode zWriteMode = ZWriteMode::On; - DepthTest depthTest = DepthTest::LessEqual; - bool depthClip = true; - bool scissorTest = false; - bool stencilTest = false; - - uint8_t stencilReadMask = 0xFF; - uint8_t stencilWriteMask = 0xFF; - int32_t stencilRef = 0; - - uint32_t colorWriteMask = 0xF; - - bool operator==(const RenderState& other) const; -}; - -struct MaterialPropBlock { - HashMap floatProps; - HashMap intProps; - HashMap matrixProps; - HashMap textureProps; - - void SetFloat(const String& name, float value); - void SetFloat(const String& name, const Vector4& value); - void SetInt(const String& name, int32_t value); - void SetMatrix(const String& name, const Matrix4x4& value); - void SetTexture(const String& name, const ResourceGUID& guid); - - float GetFloat(const String& name, float defaultValue = 0.0f) const; - Vector4 GetFloat4(const String& name, const Vector4& defaultValue = Vector4::Zero()) const; - int32_t GetInt(const String& name, int32_t defaultValue = 0) const; - Matrix4x4 GetMatrix(const String& name, const Matrix4x4& defaultValue = Matrix4x4::Identity()) const; - ResourceGUID GetTexture(const String& name, ResourceGUID defaultValue = ResourceGUID::INVALID) const; -}; - -class Material : public IResource { -public: - Material(); - explicit Material(Shader* shader); - - Shader* GetShader() const { return m_shader; } - void SetShader(Shader* shader); - - const RenderState& GetRenderState() const { return m_renderState; } - void SetRenderState(const RenderState& state) { m_renderState = state; } - - MaterialPropBlock& GetPropBlock() { return m_props; } - const MaterialPropBlock& GetPropBlock() const { return m_props; } - - int32_t GetRenderQueue() const { return m_renderQueue; } - void SetRenderQueue(int32_t queue) { m_renderQueue = queue; } - - void SetFloat(const String& name, float value); - void SetFloat(const String& name, const Vector4& value); - void SetInt(const String& name, int32_t value); - void SetTexture(const String& name, const ResourceGUID& guid); - - float GetFloat(const String& name, float defaultValue = 0.0f) const; - int32_t GetInt(const String& name, int32_t defaultValue = 0) const; - ResourceGUID GetTexture(const String& name) const; - - bool IsInstanced() const { return m_instanced; } - void SetInstanced(bool instanced) { m_instanced = instanced; } - -private: - Shader* m_shader = nullptr; - RenderState m_renderState; - MaterialPropBlock m_props; - int32_t m_renderQueue = 0; - bool m_instanced = false; -}; - -class MaterialManager { -public: - static MaterialManager& Get(); - - void Initialize(); - void Shutdown(); - - Material* CreateMaterial(Shader* shader); - Material* GetMaterial(const ResourceGUID& guid) const; - void DestroyMaterial(Material* material); - - Material* FindMaterialByName(const String& name) const; - -private: - HashMap> m_materials; - uint64_t m_nextId = 1; -}; - -class MaterialPropertyBlock { -public: - MaterialPropertyBlock(); - ~MaterialPropertyBlock(); - - void Clear(); - - void SetFloat(const String& name, float value); - void SetVector(const String& name, const Vector4& value); - void SetColor(const String& name, const Color& value); - void SetMatrix(const String& name, const Matrix4x4& matrix); - void SetTexture(const String& name, Texture* texture); - - bool HasProperty(const String& name) const; - float GetFloat(const String& name, float defaultValue = 0.0f) const; - Vector4 GetVector(const String& name, const Vector4& defaultValue = Vector4::Zero()) const; - Color GetColor(const String& name, const Color& defaultValue = Color::White()) const; - Matrix4x4 GetMatrix(const String& name, const Matrix4x4& defaultValue = Matrix4x4::Identity()) const; - Texture* GetTexture(const String& name) const; - - bool IsEmpty() const { return m_properties.empty(); } - size_t GetPropertyCount() const { return m_properties.size(); } - - void ApplyToMaterial(Material* material) const; - void CopyFrom(const MaterialPropertyBlock& other); - -private: - struct PropertyValue { - enum class Type { Float, Vector, Matrix, Texture }; - Type type; - union { - float floatValue; - Vector4 vectorValue; - Matrix4x4 matrixValue; - Texture* textureValue; - }; - }; - HashMap m_properties; -}; - -class RenderSettings { -public: - static RenderSettings& Get(); - - void Load(); - void Save(); - - enum class AmbientMode { - Skybox, - Trilight, - Flat, - Custom - }; - - AmbientMode GetAmbientMode() const { return m_ambientMode; } - void SetAmbientMode(AmbientMode mode) { m_ambientMode = mode; } - - Color GetAmbientSkyColor() const { return m_ambientSkyColor; } - void SetAmbientSkyColor(const Color& color) { m_ambientSkyColor = color; } - - Color GetAmbientEquatorColor() const { return m_ambientEquatorColor; } - void SetAmbientEquatorColor(const Color& color) { m_ambientEquatorColor = color; } - - Color GetAmbientGroundColor() const { return m_ambientGroundColor; } - void SetAmbientGroundColor(const Color& color) { m_ambientGroundColor = color; } - - float GetAmbientIntensity() const { return m_ambientIntensity; } - void SetAmbientIntensity(float intensity) { m_ambientIntensity = intensity; } - - bool GetFogEnabled() const { return m_fogEnabled; } - void SetFogEnabled(bool enabled) { m_fogEnabled = enabled; } - - enum class FogMode { - Linear, - Exponential, - ExponentialSquared - }; - - FogMode GetFogMode() const { return m_fogMode; } - void SetFogMode(FogMode mode) { m_fogMode = mode; } - - Color GetFogColor() const { return m_fogColor; } - void SetFogColor(const Color& color) { m_fogColor = color; } - - float GetFogDensity() const { return m_fogDensity; } - void SetFogDensity(float density) { m_fogDensity = density; } - - float GetFogStartDistance() const { return m_fogStartDistance; } - void SetFogStartDistance(float distance) { m_fogStartDistance = distance; } - - float GetFogEndDistance() const { return m_fogEndDistance; } - void SetFogEndDistance(float distance) { m_fogEndDistance = distance; } - - float GetShadowDistance() const { return m_shadowDistance; } - void SetShadowDistance(float distance) { m_shadowDistance = distance; } - - uint32_t GetShadowCascadeCount() const { return m_shadowCascadeCount; } - void SetShadowCascadeCount(uint32_t count) { m_shadowCascadeCount = count; } - - float GetShadowResolution() const { return m_shadowResolution; } - void SetShadowResolution(float resolution) { m_shadowResolution = resolution; } - - Texture* GetSkybox() const { return m_skybox; } - void SetSkybox(Texture* skybox) { m_skybox = skybox; } - -private: - RenderSettings() = default; - - AmbientMode m_ambientMode = AmbientMode::Skybox; - Color m_ambientSkyColor = Color{0.5f, 0.5f, 0.5f, 1.0f}; - Color m_ambientEquatorColor = Color{0.2f, 0.2f, 0.2f, 1.0f}; - Color m_ambientGroundColor = Color{0.1f, 0.1f, 0.1f, 1.0f}; - float m_ambientIntensity = 1.0f; - - bool m_fogEnabled = false; - FogMode m_fogMode = FogMode::Linear; - Color m_fogColor = Color{0.5f, 0.5f, 0.5f, 1.0f}; - float m_fogDensity = 0.01f; - float m_fogStartDistance = 10.0f; - float m_fogEndDistance = 100.0f; - - float m_shadowDistance = 150.0f; - uint32_t m_shadowCascadeCount = 4; - float m_shadowResolution = 1024.0f; - - Texture* m_skybox = nullptr; -}; - -class GraphicsSettings { -public: - static GraphicsSettings& Get(); - - void Load(); - void Save(); - - enum class ColorSpace { - Gamma, - Linear - }; - - ColorSpace GetColorSpace() const { return m_colorSpace; } - void SetColorSpace(ColorSpace space) { m_colorSpace = space; } - - enum class HDRSetting { - Off, - On, - Auto - }; - - HDRSetting GetHDR() const { return m_hdr; } - void SetHDR(HDRSetting hdr) { m_hdr = hdr; } - - uint32_t GetMSAASamples() const { return m_msaaSamples; } - void SetMSAASamples(uint32_t samples) { m_msaaSamples = samples; } - - bool GetRealtimeReflectionProbes() const { return m_realtimeReflectionProbes; } - void SetRealtimeReflectionProbes(bool enabled) { m_realtimeReflectionProbes = enabled; } - - float GetLODBias() const { return m_lodBias; } - void SetLODBias(float bias) { m_lodBias = bias; } - - int GetMaximumLODLevel() const { return m_maximumLODLevel; } - void SetMaximumLODLevel(int level) { m_maximumLODLevel = level; } - - enum class ShaderQuality { - Low, - Medium, - High - }; - - ShaderQuality GetShaderQuality() const { return m_shaderQuality; } - void SetShaderQuality(ShaderQuality quality) { m_shaderQuality = quality; } - -private: - GraphicsSettings() = default; - - ColorSpace m_colorSpace = ColorSpace::Linear; - HDRSetting m_hdr = HDRSetting::Auto; - uint32_t m_msaaSamples = 4; - bool m_realtimeReflectionProbes = true; - float m_lodBias = 1.0f; - int m_maximumLODLevel = -1; - ShaderQuality m_shaderQuality = ShaderQuality::High; -}; - -} // namespace Rendering -} // namespace XCEngine -``` - -### 4.8 渲染管线 - -```cpp -namespace XCEngine { -namespace Rendering { - -enum class RenderPipelineType { - Forward, - Deferred, - Hybrid, - Custom -}; - -struct RenderPipelineDesc { - RenderPipelineType type = RenderPipelineType::Forward; - uint32_t maxLights = 16; - bool shadowEnabled = true; - uint32_t shadowMapSize = 2048; - bool ssaoEnabled = false; - bool bloomEnabled = false; - bool toneMappingEnabled = true; - String toneMappingCurve = "ACES"; -}; - -struct RenderItem { - ResourceGUID meshGuid; - Material* material = nullptr; - Matrix4x4 worldMatrix; - uint32_t instanceId = 0; - int32_t renderQueue = 0; - uint32_t subMeshIndex = 0; - uint32_t lightingHash = 0; - - float GetDistanceSq(const Vector3& cameraPos) const; - bool operator<(const RenderItem& other) const; -}; - -struct CullingParams { - Vector3 position; - float sphereCulling; - - Matrix4x4 viewMatrix; - Matrix4x4 projectionMatrix; - - float nearClipPlane; - float farClipPlane; - - Rect viewport; - - int32_t cullingPlaneFlags; - int32_t cullingMask; - int32_t ortho; - - float shadowDistance; - float shadowNearPlaneDistance; - - bool isOrthographic; - bool cullDynamicObjects; - int32_t LODStripping; - int32_t forceCullingMode; - int32_t maximumLODLevel; - int32_t minimumLODLevel; -}; - -struct CullingResults { - struct VisibleObject { - uint64_t instanceID; - int32_t referenceID; - - Matrix4x4 localToWorldMatrix; - Matrix4x4 localToWorldMatrixPrevious; - - uint64_t staticBatchRootID; - - uint32_t subMeshIndex; - - Vector4 lightmapScaleOffset; - uint32_t lightmapIndex; - - bool staticShadowCaster; - bool motionVectors; - }; - - std::vector visibleRenderers; - - struct LightData { - uint64_t lightID; - int32_t uniqueID; - - Vector3 position; - Vector3 color; - Vector3 forward; - - float range; - float intensity; - float spotAngle; - - int32_t lightType; - int32_t renderMode; - - bool enabled; - bool shadowsEnabled; - - int32_t cullingMask; - - Matrix4x4 viewMatrix; - Matrix4x4 projectionMatrix; - }; - - std::vector visibleLights; - - Frustum frustumPlanes[2]; -}; - -class CullingSystem { -public: - static CullingSystem& Get(); - - void Initialize(); - void Shutdown(); - - bool PerformCulling( - const CullingParams& params, - CullingResults& results, - Scene* scene - ); - - void ComputeFrustumPlanes( - const Matrix4x4& viewMatrix, - const Matrix4x4& projectionMatrix, - Frustum& frustum - ); - -private: - std::vector m_visibleRenderers; - Frustum m_frustum; - std::vector m_cullingPlanes; -}; - -class ScriptableRenderContext { -public: - ScriptableRenderContext(); - ~ScriptableRenderContext(); - - void SetRenderTarget( - RenderTargetIdentifier colorTarget, - RenderTargetIdentifier depthTarget - ); - - void PushDebugGroup(const char* name); - void PopDebugGroup(); - - void BeginSample(const char* name); - void EndSample(const char* name); - - void DrawRenderer( - RenderItem* renderers, - size_t count, - Material* material, - int passIndex - ); - - void ExecuteAndPresent(); - - void Submit(); - -private: - CommandBuffer* m_commandBuffer; - RenderTargetIdentifier m_activeColorTarget; - RenderTargetIdentifier m_activeDepthTarget; -}; - -class LightManager { -public: - static LightManager& Get(); - - void Initialize(); - void Shutdown(); - - void UpdateLights( - Scene* scene, - CameraComponent* camera, - CullingResults& cullingResults - ); - - void GetMainLight(LightData& light); - void GetVisibleLights(std::vector& lights); - - int GetVisibleLightCount() const { return m_visibleLights.size(); } - bool HasShadows() const { return m_shadowsEnabled; } - - void SetShadowDistance(float distance) { m_shadowDistance = distance; } - float GetShadowDistance() const { return m_shadowDistance; } - -private: - std::vector m_visibleLights; - int m_mainLightIndex = -1; - - float m_shadowDistance = 50.0f; - bool m_shadowsEnabled = true; -}; - -class ShadowAtlas { -public: - ShadowAtlas(); - ~ShadowAtlas(); - - void Initialize(int width, int height); - void Shutdown(); - - void Update( - const std::vector& lights, - CullingResults& cullingResults - ); - - RenderTexture* GetTexture() const { return m_texture.get(); } - -private: - struct ShadowSlice { - int x, y; - int width, height; - bool allocated; - LightData* light; - }; - - std::unique_ptr m_texture; - int m_width = 0; - int m_height = 0; - - std::vector m_slices; -}; - -class Renderer { -public: - Renderer(); - ~Renderer(); - - void Initialize(); - void Shutdown(); - - void Render( - ScriptableRenderContext& context, - CameraComponent* camera, - CullingResults& cullingResults, - RenderQueue& renderQueue - ); - - void RenderOpaque( - ScriptableRenderContext& context, - CullingResults& cullingResults, - RenderQueue& renderQueue - ); - - void RenderTransparent( - ScriptableRenderContext& context, - CullingResults& cullingResults, - RenderQueue& renderQueue - ); - - void RenderShadowCasters( - ScriptableRenderContext& context, - CullingResults& cullingResults - ); - - void SortRenderers(RenderQueue& renderQueue); - -private: - void PrepareRenderQueueEntries( - RenderQueue& renderQueue, - CullingResults& cullingResults - ); - - Material* m_defaultMaterial = nullptr; -}; - -class RenderQueue { -public: - void Clear(); - void Sort(); - - void Add(const RenderItem& item); - void AddRange(const RenderItem* items, size_t count); - - const std::vector& GetOpaque() const { return m_opaque; } - const std::vector& GetTransparent() const { return m_transparent; } - const std::vector& GetAlphaTest() const { return m_alphaTest; } - - size_t GetOpaqueCount() const { return m_opaque.size(); } - size_t GetTransparentCount() const { return m_transparent.size(); } - size_t GetAlphaTestCount() const { return m_alphaTest.size(); } - -private: - std::vector m_opaque; - std::vector m_transparent; - std::vector m_alphaTest; -}; - -struct CameraData { - Matrix4x4 viewMatrix; - Matrix4x4 projectionMatrix; - Matrix4x4 viewProjectionMatrix; - Vector3 position; - Vector3 forward; - Vector3 up; - Vector3 right; - float nearPlane; - float farPlane; - float fov; - float aspectRatio; - Rect viewport; - bool isOrthographic; -}; - -struct RenderContextData { - CameraData camera; - std::vector lights; - std::vector visibleLights; - float deltaTime; - float time; - uint32_t frameIndex; - uint64_t frameCount; -}; - -class RenderPipeline { -public: - virtual ~RenderPipeline() = default; - - virtual void Initialize(const RenderPipelineDesc& desc); - virtual void Shutdown(); - - virtual void Prepare(RenderContextData& context); - virtual void Execute(RHI::ICommandList* cmdList, RenderContextData& context); - virtual void Present(); - - RenderQueue& GetRenderQueue() { return m_renderQueue; } - const RenderQueue& GetRenderQueue() const { return m_renderQueue; } - - RenderTexture* GetColorTarget() const { return m_colorTarget.get(); } - RenderTexture* GetDepthTarget() const { return m_depthTarget.get(); } - - virtual void Resize(uint32_t width, uint32_t height); - -protected: - virtual void CollectRenderItems(Scene* scene, CameraComponent* camera); - virtual void SortRenderItems(); - virtual void SetupLights(Scene* scene, CameraComponent* camera); - - RenderPipelineDesc m_desc; - RenderQueue m_renderQueue; - - std::unique_ptr m_colorTarget; - std::unique_ptr m_depthTarget; - - std::vector m_mainLightData; - std::vector m_additionalLightData; -}; - -class ForwardPipeline : public RenderPipeline { -public: - void Initialize(const RenderPipelineDesc& desc) override; - void Execute(RHI::ICommandList* cmdList, RenderContextData& context) override; - -private: - void RenderOpaque(RHI::ICommandList* cmdList, RenderContextData& context); - void RenderTransparent(RHI::ICommandList* cmdList, RenderContextData& context); - void RenderAlphaTest(RHI::ICommandList* cmdList, RenderContextData& context); - void RenderShadowMaps(RHI::ICommandList* cmdList); - void RenderLighting(RHI::ICommandList* cmdList, RenderContextData& context); -}; - -class DeferredPipeline : public RenderPipeline { -public: - void Initialize(const RenderPipelineDesc& desc) override; - void Execute(RHI::ICommandList* cmdList, RenderContextData& context) override; - -private: - void RenderGBuffer(RHI::ICommandList* cmdList); - void RenderDeferredLighting(RHI::ICommandList* cmdList); - void RenderDeferredShading(RHI::ICommandList* cmdList); - - std::unique_ptr m_gBufferAlbedo; - std::unique_ptr m_gBufferNormal; - std::unique_ptr m_gBufferMetallicRoughness; - std::unique_ptr m_gBufferDepth; -}; - -class RenderPipelineManager { -public: - static RenderPipelineManager& Get(); - - void Initialize(const RenderPipelineDesc& desc); - void Shutdown(); - - RenderPipeline* GetPipeline() const { return m_pipeline.get(); } - - void SetPipeline(std::unique_ptr pipeline); - void CreatePipeline(RenderPipelineType type); - - void Resize(uint32_t width, uint32_t height); - -private: - std::unique_ptr m_pipeline; - RenderPipelineDesc m_desc; -}; - -} // namespace Rendering -} // namespace XCEngine -``` - ---- - -## 第五章 目录结构(与 Unity 引擎目录结构一致) - -``` -XCVolumeRenderer/ -├── engine/ # 引擎核心库(静态库) -│ ├── CMakeLists.txt -│ ├── include/ -│ │ └── XCEngine/ -│ │ ├── Core/ # 核心基础 -│ │ │ ├── Assert.h -│ │ │ ├── Event.h -│ │ │ ├── TypeTraits.h -│ │ │ └── UniquePtr.h -│ │ ├── Math/ # 数学库(与 Unity Mathf 对应) -│ │ │ ├── Vector2.h -│ │ │ ├── Vector3.h -│ │ │ ├── Vector4.h -│ │ │ ├── Matrix3.h -│ │ │ ├── Matrix4.h -│ │ │ ├── Quaternion.h -│ │ │ ├── Transform.h -│ │ │ ├── Color.h -│ │ │ ├── Ray.h -│ │ │ ├── Plane.h -│ │ │ ├── Sphere.h -│ │ │ ├── Box.h -│ │ │ ├── Bounds.h -│ │ │ └── Frustum.h -│ │ ├── Containers/ # 容器(与 Unity Collections 对应) -│ │ │ ├── Array.h -│ │ │ ├── String.h -│ │ │ ├── StringView.h -│ │ │ └── HashMap.h -│ │ ├── Memory/ # 内存管理 -│ │ │ ├── Allocator.h -│ │ │ ├── LinearAllocator.h -│ │ │ ├── PoolAllocator.h -│ │ │ └── ProxyAllocator.h -│ │ ├── Threading/ # 线程系统 -│ │ │ ├── Thread.h -│ │ │ ├── Mutex.h -│ │ │ ├── SpinLock.h -│ │ │ ├── ReadWriteLock.h -│ │ │ ├── TaskSystem.h -│ │ │ └── Atomic.h -│ │ ├── Debug/ # 调试系统 -│ │ │ ├── Logger.h -│ │ │ ├── Profiler.h -│ │ │ └── LogSink.h -│ │ ├── Components/ # 组件系统(与 Unity Component 对应) -│ │ │ ├── GameObject.h -│ │ │ ├── Component.h -│ │ │ ├── ComponentTypeRegistry.h -│ │ │ ├── TransformComponent.h -│ │ │ ├── RenderMeshComponent.h -│ │ │ ├── LightComponent.h -│ │ │ └── CameraComponent.h -│ │ ├── Scene/ # 场景系统(与 Unity SceneManager 对应) -│ │ │ ├── Scene.h -│ │ │ ├── SceneManager.h -│ │ │ └── GameObjectBuilder.h -│ │ ├── Renderer/ # 渲染系统(核心,与 Unity Graphics/Shader 对应) -│ │ │ ├── Device.h # 设备抽象 -│ │ │ ├── Context.h # 渲染上下文 -│ │ │ ├── SwapChain.h # 交换链 -│ │ │ ├── Buffer.h # 缓冲区 -│ │ │ ├── Texture.h # 纹理 -│ │ │ ├── Shader.h # Shader -│ │ │ ├── Material.h # 材质 -│ │ │ ├── Pipeline.h # 渲染管线 -│ │ │ ├── RenderPass.h # 渲染通道 -│ │ │ ├── CommandList.h # 命令列表 -│ │ │ ├── RenderQueue.h # 渲染队列 -│ │ │ ├── RenderPipeline.h # 渲染管线基类 -│ │ │ ├── CullingSystem.h # 剔除系统 -│ │ │ ├── Renderer.h # 渲染器 -│ │ │ ├── LightManager.h # 光照管理器 -│ │ │ ├── ShadowAtlas.h # 阴影图集 -│ │ │ └── RenderModule.h # 渲染模块 -│ │ ├── RHI/ # 渲染硬件抽象层(与 Unity GraphicsDevice 对应) -│ │ │ ├── RHISystem.h # RHI 系统入口 -│ │ │ ├── RHIDevice.h # 抽象设备接口 -│ │ │ ├── CommandQueue.h # 命令队列 -│ │ │ ├── CommandList.h # 命令列表 -│ │ │ ├── CommandAllocator.h # 命令分配器 -│ │ │ ├── Fence.h # 同步围栏 -│ │ │ ├── DescriptorHeap.h # 描述符堆 -│ │ │ ├── QueryHeap.h # 查询堆 -│ │ │ ├── RootSignature.h # 根签名 -│ │ │ ├── PipelineState.h # 管线状态 -│ │ │ ├── Sampler.h # 采样器 -│ │ │ └── SwapChain.h # 交换链 -│ │ ├── RHI/D3D12/ # D3D12 后端实现 -│ │ │ ├── D3D12Device.h -│ │ │ ├── D3D12CommandList.h -│ │ │ ├── D3D12CommandQueue.h -│ │ │ ├── D3D12DescriptorHeap.h -│ │ │ ├── D3D12RootSignature.h -│ │ │ ├── D3D12PipelineState.h -│ │ │ ├── D3D12Fence.h -│ │ │ ├── D3D12QueryHeap.h -│ │ │ ├── D3D12SwapChain.h -│ │ │ ├── D3D12Texture.h -│ │ │ └── D3D12Buffer.h -│ │ └── XCEngine.h # 主头文件 -│ ├── src/ -│ │ ├── Core/ -│ │ ├── Math/ -│ │ ├── Components/ -│ │ ├── Scene/ -│ │ ├── Renderer/ -│ │ ├── RHI/ -│ │ │ ├── CMakeLists.txt -│ │ │ └── D3D12/ -│ │ └── ... -│ └── third_party/ -│ ├── DirectX/ # DirectX SDK -│ ├── stb/ # stb 图像库 -│ ├── json/ # json 解析库 -│ └── NanoVDB/ # NanoVDB 体积渲染库 -│ -├── runtime/ # 游戏运行时(独立可执行文件) -│ ├── CMakeLists.txt -│ ├── src/ -│ │ ├── GameMain.cpp -│ │ ├── EntryPoint.cpp -│ │ └── Application.cpp -│ └── resources/ -│ ├── Shaders/ -│ ├── Models/ -│ ├── Textures/ -│ └── Volumes/ -│ -├── tools/ # 工具链 -│ ├── ShaderCompiler/ # 着色器编译器 -│ │ ├── ShaderCompiler.h -│ │ └── ShaderCompileOptions.h -│ ├── BuildTool/ # 构建打包工具 -│ └── AssetProcessor/ # 资源处理工具 -│ -├── content/ # 资源内容 -│ ├── Assets/ # 资源文件夹 -│ ├── Scenes/ # 场景文件 -│ └── Packages/ # 资源包 -│ -└── projects/ # 项目文件夹 - └── .. - -# Unity 引擎目录结构对照 -# Unity/Editor/Data/Resources/ -# Unity/Editor/Data/Modules/ -# Unity/Editor/Data/Tools/ -``` - ---- - -## 附录:待扩展功能 - -以下功能将在后续迭代中添加: - -1. **物理系统** - RigidBodyComponent, ColliderComponent, PhysicsWorld -2. **音频系统** - AudioSourceComponent, AudioListenerComponent, AudioEngine -3. **动画系统** - AnimatorComponent, AnimationClip, Skeleton -4. **粒子系统** - ParticleSystemComponent, GPUParticles -5. **UI系统** - CanvasComponent, ImageComponent, TextComponent, ButtonComponent -6. **网络系统** - NetworkIdentityComponent, NetworkTransformComponent -7. **完整编辑器** - HierarchyPanel, InspectorPanel, SceneViewPanel diff --git a/docs/used/XCEngine输入系统设计.md b/docs/used/XCEngine输入系统设计.md deleted file mode 100644 index b9be6b48..00000000 --- a/docs/used/XCEngine输入系统设计.md +++ /dev/null @@ -1,546 +0,0 @@ -# XCEngine 输入系统设计与实现 - -> **文档信息** -> - **版本**: 1.0 -> - **日期**: 2026-03-22 -> - **状态**: 设计文档 -> - **目标**: 设计引擎级输入系统 - ---- - -## 1. 概述 - -### 1.1 设计目标 - -XCEngine 输入系统需要提供: -1. **统一的跨平台输入抽象** - 支持键盘、鼠标、手柄、触摸 -2. **与引擎架构无缝集成** - 使用现有的 `Core::Event` 系统 -3. **轮询 + 事件混合模式** - 既支持 `IsKeyDown()` 轮询,也支持事件回调 -4. **UI 系统支持** - 为 UI 组件 (Button, Slider 等) 提供指针事件 - -### 1.2 当前状态分析 - -| 模块 | 状态 | 说明 | -|-----|------|------| -| Core::Event | ✅ 完备 | 线程安全,Subscribe/Unsubscribe 模式 | -| RHI::RHISwapChain | ⚠️ PollEvents 空实现 | 需要填充 Windows 消息泵 | -| 现有 Input (mvs) | ❌ 耦合 Windows | 直接处理 HWND 消息,不适合引擎架构 | -| Platform/Window | ❌ 不存在 | 需要新建 | - ---- - -## 2. 架构设计 - -### 2.1 模块结构 - -``` -engine/include/XCEngine/ -├── Core/ -│ └── Event.h # 已有,复用 -├── Input/ -│ ├── InputTypes.h # 枚举和结构体定义 -│ ├── InputEvent.h # 输入事件结构体 -│ ├── InputManager.h # 输入管理器(单例) -│ └── InputModule.h # 平台相关输入处理模块接口 -└── Platform/ - ├── PlatformTypes.h # 平台类型抽象 - ├── Window.h # 窗口抽象接口 - └── Windows/ - ├── WindowsPlatform.h # Windows 平台实现 - └── WindowsInputModule.h # Windows 输入模块实现 -``` - -### 2.2 核心设计原则 - -1. **事件驱动 + 状态轮询双模式** - - 事件:用于 UI 交互、一次性按键响应 - - 轮询:用于连续输入检测(角色移动、视角控制) - -2. **平台抽象层** - - `InputModule` 接口:抽象平台特定的输入处理 - - `Window` 接口:抽象平台特定的窗口管理 - -3. **与现有引擎组件集成** - - 使用 `Core::Event` 作为事件系统 - - 使用 `Math::Vector2` 作为 2D 坐标类型 - ---- - -## 3. 详细设计 - -### 3.1 输入类型定义 (`InputTypes.h`) - -```cpp -#pragma once -#include "Core/Types.h" -#include "Math/Vector2.h" - -namespace XCEngine { -namespace Input { - -enum class KeyCode : uint8 { - None = 0, - A = 4, B = 5, C = 6, D = 7, E = 8, F = 9, G = 10, - H = 11, I = 12, J = 13, K = 14, L = 15, M = 16, N = 17, - O = 18, P = 19, Q = 20, R = 21, S = 22, T = 23, U = 24, - V = 25, W = 26, X = 27, Y = 28, Z = 29, - F1 = 58, F2 = 59, F3 = 60, F4 = 61, F5 = 62, F6 = 63, - F7 = 64, F8 = 65, F9 = 66, F10 = 67, F11 = 68, F12 = 69, - Space = 49, Tab = 48, Enter = 36, Escape = 53, - LeftShift = 56, RightShift = 60, LeftCtrl = 59, RightCtrl = 62, - LeftAlt = 58, RightAlt = 61, - Up = 126, Down = 125, Left = 123, Right = 124, - Home = 115, End = 119, PageUp = 116, PageDown = 121, - Delete = 51, Backspace = 51 -}; - -enum class MouseButton : uint8 { - Left = 0, - Right = 1, - Middle = 2, - Button4 = 3, - Button5 = 4 -}; - -enum class JoystickButton : uint8 { - Button0 = 0, - Button1 = 1, - Button2 = 2, - // ... Xbox/PlayStation 标准按钮 -}; - -enum class JoystickAxis : uint8 { - LeftX = 0, - LeftY = 1, - RightX = 2, - RightY = 3, - LeftTrigger = 4, - RightTrigger = 5 -}; - -} // namespace Input -} // namespace XCEngine -``` - -### 3.2 输入事件结构体 (`InputEvent.h`) - -```cpp -#pragma once -#include "InputTypes.h" -#include "Math/Vector2.h" -#include "Containers/String.h" - -namespace XCEngine { -namespace Input { - -struct KeyEvent { - KeyCode keyCode; - bool alt; - bool ctrl; - bool shift; - bool meta; - enum Type { Down, Up, Repeat } type; -}; - -struct MouseButtonEvent { - MouseButton button; - Math::Vector2 position; - enum Type { Pressed, Released } type; -}; - -struct MouseMoveEvent { - Math::Vector2 position; - Math::Vector2 delta; // 相对上一帧的移动量 -}; - -struct MouseWheelEvent { - Math::Vector2 position; - float delta; // 滚轮滚动量 -}; - -struct TextInputEvent { - char character; - Containers::String text; // 完整输入文本 -}; - -struct TouchState { - int touchId; - Math::Vector2 position; - Math::Vector2 deltaPosition; - float deltaTime; - int tapCount; - enum Phase { Began, Moved, Stationary, Ended, Canceled } phase; -}; - -} // namespace Input -} // namespace XCEngine -``` - -### 3.3 输入管理器 (`InputManager.h`) - -```cpp -#pragma once -#include "Core/Event.h" -#include "InputTypes.h" -#include "InputEvent.h" -#include "Math/Vector2.h" - -namespace XCEngine { -namespace Input { - -class InputManager { -public: - static InputManager& Get(); - - void Initialize(void* platformWindowHandle); - void Shutdown(); - void Update(); // 每帧调用,更新输入状态 - - // ============ 轮询接口 ============ - - // 键盘 - bool IsKeyDown(KeyCode key) const; - bool IsKeyUp(KeyCode key) const; - bool IsKeyPressed(KeyCode key) const; // 当前帧按下 - - // 鼠标 - Math::Vector2 GetMousePosition() const; - Math::Vector2 GetMouseDelta() const; - float GetMouseScrollDelta() const; - bool IsMouseButtonDown(MouseButton button) const; - bool IsMouseButtonUp(MouseButton button) const; - bool IsMouseButtonClicked(MouseButton button) const; // 当前帧点击 - - // 手柄 - int GetJoystickCount() const; - bool IsJoystickConnected(int joystick) const; - Math::Vector2 GetJoystickAxis(int joystick, JoystickAxis axis) const; - bool IsJoystickButtonDown(int joystick, JoystickButton button) const; - - // ============ 事件接口 ============ - - Core::Event& OnKeyEvent() { return m_onKeyEvent; } - Core::Event& OnMouseButton() { return m_onMouseButton; } - Core::Event& OnMouseMove() { return m_onMouseMove; } - Core::Event& OnMouseWheel() { return m_onMouseWheel; } - Core::Event& OnTextInput() { return m_onTextInput; } - - // 内部方法(供 PlatformInputModule 调用) - void ProcessKeyDown(KeyCode key, bool repeat); - void ProcessKeyUp(KeyCode key); - void ProcessMouseMove(int x, int y, int deltaX, int deltaY); - void ProcessMouseButton(MouseButton button, bool pressed, int x, int y); - void ProcessMouseWheel(float delta, int x, int y); - void ProcessTextInput(char c); - -private: - InputManager() = default; - ~InputManager() = default; - - void* m_platformWindowHandle = nullptr; - - // 键盘状态 - std::vector m_keyDownThisFrame; - std::vector m_keyDownLastFrame; - std::vector m_keyDown; - - // 鼠标状态 - Math::Vector2 m_mousePosition; - Math::Vector2 m_mouseDelta; - float m_mouseScrollDelta = 0.0f; - std::vector m_mouseButtonDownThisFrame; - std::vector m_mouseButtonDownLastFrame; - std::vector m_mouseButtonDown; - - // 事件 - Core::Event m_onKeyEvent; - Core::Event m_onMouseButton; - Core::Event m_onMouseMove; - Core::Event m_onMouseWheel; - Core::Event m_onTextInput; -}; - -} // namespace Input -} // namespace XCEngine -``` - -### 3.4 平台输入模块接口 (`InputModule.h`) - -```cpp -#pragma once - -namespace XCEngine { -namespace Input { - -class InputModule { -public: - virtual ~InputModule() = default; - - virtual void Initialize(void* windowHandle) = 0; - virtual void Shutdown() = 0; - virtual void PumpEvents() = 0; // 抽取并处理平台事件 - -protected: - InputModule() = default; -}; - -} // namespace Input -} // namespace XCEngine -``` - -### 3.5 Windows 输入模块实现 (`WindowsInputModule.h`) - -```cpp -#pragma once -#include "InputModule.h" -#include - -namespace XCEngine { -namespace Input { -namespace Platform { - -class WindowsInputModule : public InputModule { -public: - void Initialize(void* windowHandle) override; - void Shutdown() override; - void PumpEvents() override; - - // 供 Window 调用的消息处理 - void HandleMessage(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); - -private: - void ProcessKeyDown(WPARAM wParam, LPARAM lParam); - void ProcessKeyUp(WPARAM wParam, LPARAM lParam); - void ProcessMouseMove(WPARAM wParam, LPARAM lParam); - void ProcessMouseButton(WPARAM wParam, LPARAM lParam, bool pressed); - void ProcessMouseWheel(WPARAM wParam, LPARAM lParam); - void ProcessCharInput(WPARAM wParam, LPARAM lParam); - - HWND m_hwnd = nullptr; - bool m_captureMouse = false; -}; - -} // namespace Platform -} // namespace Input -} // namespace XCEngine -``` - ---- - -## 4. 与引擎其他模块的集成 - -### 4.1 与 RHI/SwapChain 的集成 - -`RHISwapChain::PollEvents()` 需要调用 `InputManager::Update()` 和平台输入模块的 `PumpEvents()`: - -```cpp -// D3D12SwapChain.cpp -void D3D12SwapChain::PollEvents() { - // 抽取 Windows 消息 - if (m_inputModule) { - m_inputModule->PumpEvents(); - } - - // 更新输入管理器状态 - Input::InputManager::Get().Update(); - - // 处理关闭请求 - MSG msg; - if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) { - if (msg.message == WM_QUIT) { - m_shouldClose = true; - } - TranslateMessage(&msg); - DispatchMessage(&msg); - } -} -``` - -### 4.2 与 UI 系统的集成 - -UI 组件 (Button, Slider 等) 通过订阅 `InputManager` 的事件来响应用户输入: - -```cpp -// ButtonComponent.cpp -void ButtonComponent::Update(float deltaTime) { - if (!IsEnabled()) return; - - auto& input = Input::InputManager::Get(); - Vector2 mousePos = input.GetMousePosition(); - - // 射线检测是否悬停在按钮上 - if (IsPointInRect(mousePos, m_rect)) { - if (input.IsMouseButtonClicked(Input::MouseButton::Left)) { - OnClick.Invoke(); - } - } -} -``` - -### 4.3 与场景生命周期的集成 - -```cpp -// Scene.cpp -void Scene::Awake() { - // 初始化输入系统 - Input::InputManager::Get().Initialize(m_windowHandle); -} - -void Scene::OnDestroy() { - Input::InputManager::Get().Shutdown(); -} -``` - ---- - -## 5. Windows 消息到引擎事件的映射 - -| Windows Message | Engine Event | -|----------------|---------------| -| WM_KEYDOWN | `InputManager::ProcessKeyDown` | -| WM_KEYUP | `InputManager::ProcessKeyUp` | -| WM_CHAR | `InputManager::ProcessTextInput` | -| WM_MOUSEMOVE | `InputManager::ProcessMouseMove` | -| WM_LBUTTONDOWN/RBUTTONDOWN/MBUTTONDOWN | `InputManager::ProcessMouseButton` | -| WM_LBUTTONUP/RBUTTONUP/MBUTTONUP | `InputManager::ProcessMouseButton` | -| WM_MOUSEWHEEL | `InputManager::ProcessMouseWheel` | - -### 5.1 Windows VK 码到 KeyCode 的映射 - -```cpp -// WindowsVKToKeyCode 映射表 -static const KeyCode VK_TO_KEYCODE[] = { - // VK 0-29 对应 KeyCode A-Z - KeyCode::A, KeyCode::B, KeyCode::C, KeyCode::D, KeyCode::E, - KeyCode::F, KeyCode::G, KeyCode::H, KeyCode::I, KeyCode::J, - KeyCode::K, KeyCode::L, KeyCode::M, KeyCode::N, KeyCode::O, - KeyCode::P, KeyCode::Q, KeyCode::R, KeyCode::S, KeyCode::T, - KeyCode::U, KeyCode::V, KeyCode::W, KeyCode::X, KeyCode::Y, - KeyCode::Z, - // ... 其他映射 -}; -``` - ---- - -## 6. 实现计划 - -### Phase 1: 核心输入系统 - -| 任务 | 文件 | 说明 | -|-----|------|------| -| 创建 Input 模块目录 | `engine/include/XCEngine/Input/` | | -| 实现 InputTypes.h | KeyCode, MouseButton 等枚举 | | -| 实现 InputEvent.h | 事件结构体 | | -| 实现 InputManager.h/cpp | 单例管理器 | | -| 实现 WindowsInputModule.h/cpp | Windows 平台实现 | | - -### Phase 2: 与 RHI 集成 - -| 任务 | 文件 | 说明 | -|-----|------|------| -| 修改 RHISwapChain | 添加 InputModule 成员 | | -| 实现 D3D12SwapChain::PollEvents | 填充消息泵逻辑 | | -| 实现 OpenGLSwapChain::PollEvents | GL 消息处理 | | - -### Phase 3: UI 输入支持 - -| 任务 | 说明 | -|-----|------| -| 实现 Canvas 组件的射线检测 | 将屏幕坐标转换为 UI 元素 | -| Button/Slider 事件集成 | 使用 InputManager 事件 | - ---- - -## 7. 使用示例 - -### 7.1 轮询模式(角色移动) - -```cpp -void PlayerController::Update(float deltaTime) { - auto& input = Input::InputManager::Get(); - - if (input.IsKeyDown(Input::KeyCode::W)) { - m_velocity.z = 1.0f; - } else if (input.IsKeyDown(Input::KeyCode::S)) { - m_velocity.z = -1.0f; - } - - if (input.IsKeyDown(Input::KeyCode::A)) { - m_velocity.x = -1.0f; - } else if (input.IsKeyDown(Input::KeyCode::D)) { - m_velocity.x = 1.0f; - } -} -``` - -### 7.2 事件模式(UI 交互) - -```cpp -class MyUIButton : public Component { - uint64_t m_clickHandlerId = 0; - - void Awake() override { - m_clickHandlerId = Input::InputManager::Get().OnMouseButton().Subscribe( - [this](const Input::MouseButtonEvent& event) { - if (event.type == Input::MouseButtonEvent::Pressed && - event.button == Input::MouseButton::Left && - IsPointInButton(event.position)) { - OnClicked(); - } - } - ); - } - - void OnDestroy() override { - Input::InputManager::Get().OnMouseButton().Unsubscribe(m_clickHandlerId); - } -}; -``` - -### 7.3 文本输入 - -```cpp -void InputFieldComponent::Update(float deltaTime) { - auto& input = Input::InputManager::Get(); - - uint64_t textHandlerId = input.OnTextInput().Subscribe( - [this](const Input::TextInputEvent& event) { - if (m_isFocused) { - m_text += event.character; - OnValueChanged.Invoke(m_text); - } - } - ); - - // 清理 - input.OnTextInput().Unsubscribe(textHandlerId); -} -``` - ---- - -## 8. 待讨论问题 - -1. **多窗口支持**: 引擎是否需要支持多窗口?如果是,输入系统如何路由事件到正确的窗口? - -2. **手柄/游戏手柄支持**: 是否需要实现 XInput 支持?这会增加复杂性。 - -3. **原始输入 vs 输入模式**: Unity 有 "Input.GetAxis" vs "Input.GetButtonDown"。是否需要实现类似的轴概念? - -4. **IME/输入法支持**: 对于中文、日文输入,是否需要特殊处理? - -5. **平台模块位置**: `InputModule` 放在 `Input/` 还是 `Platform/` 命名空间下? - ---- - -## 9. 结论 - -XCEngine 输入系统设计遵循以下核心原则: - -1. **复用现有 Core::Event** - 不重复造轮子 -2. **平台抽象** - 通过 `InputModule` 接口隔离平台差异 -3. **双模式支持** - 轮询 + 事件,兼顾性能和响应性 -4. **与 UI 架构协同** - 为 Canvas/Button 提供必要的事件支持 - -该设计参考了 Unity 的 Input 系统架构,同时适配了 XCEngine 现有的组件模式和事件系统。 diff --git a/docs/used/XCEngine音频模块架构设计.md b/docs/used/XCEngine音频模块架构设计.md deleted file mode 100644 index fbddf3c9..00000000 --- a/docs/used/XCEngine音频模块架构设计.md +++ /dev/null @@ -1,1640 +0,0 @@ -# XCEngine - 音频模块架构设计文档 - -> **借鉴 Unity 音频架构概念设计** - ---- - -## 音频流程图 - -``` -┌─────────────────────────────────────────────────────────────────────────────────────┐ -│ 音频流程 │ -└─────────────────────────────────────────────────────────────────────────────────────┘ - -┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ -│ AudioSource │────▶│ Spatialize │────▶│ EffectChain │────▶│ Output │ -│ 声源组件 │ │ 3D空间化 │ │ 效果链 │ │ 音频输出 │ -└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ - │ │ │ │ - ▼ ▼ ▼ ▼ -┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ -│ AudioClip │ │ HRTF │ │ FFT │ │ AudioBackend│ -│ 音频资源 │ │ 头部相关传输 │ │ 傅里叶变换 │ │ 音频后端 │ -└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ - │ │ - ▼ ▼ - ┌─────────────┐ ┌─────────────┐ - │ Reverbation │ │ WASAPI │ - │ 混响效果 │ │ OpenAL │ - └─────────────┘ │ CoreAudio │ - └─────────────┘ - -流程说明: -1. AudioSourceComponent 负责播放 AudioClip,管理播放状态 -2. AudioListenerComponent 接收声音,计算3D空间化参数 -3. Spatialize 根据 Listener 位置计算 panning、distance attenuation、doppler -4. EffectChain 处理 DSP 效果链(FFT、Reverb、EQ等) -5. AudioBackend 抽象层负责与具体音频API交互 -6. 最终输出到音频设备 -``` - -``` -┌─────────────────────────────────────────────────────────────────────────────────────┐ -│ AudioBackend 抽象层架构 │ -└─────────────────────────────────────────────────────────────────────────────────────┘ - - ┌─────────────────┐ - │ AudioSystem │ 音频系统入口 - └────────┬────────┘ - │ - ┌───────────────────────┼───────────────────────┐ - │ │ │ - ┌──────▼──────┐ ┌──────▼──────┐ ┌──────▼──────┐ - │ WASAPIBackend│ │OpenALBackend │ │CoreAudioBackend│ - └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ - │ │ │ - └───────────────────────┼───────────────────────┘ - │ - ┌────────▼────────┐ - │ IAudioBackend │ 抽象音频后端接口 - └────────┬────────┘ - │ - ┌──────────────────────────────────┼──────────────────────────────────┐ - │ │ │ │ │ │ │ - ┌────▼────┐ ┌───▼────┐ ┌────▼┐ ┌──▼────┐ ┌───▼───┐ ┌───▼────┐ ┌──▼────┐ - │AudioBuffer│ │AudioBus │ │Fence│ │Mixer │ │Effect │ │HRTF │ │Resampler│ - │ 音频缓冲 │ │ 音频总线 │ │围栏 │ │ 混音器 │ │ 效果 │ │ 空间化 │ │ 重采样 │ - └──────────┘ └────────┘ └─────┘ └────────┘ └───────┘ └────────┘ └────────┘ -``` - -> **重要声明**:本架构借鉴 Unity 音频系统的核心概念与设计模式,包括: -> - `AudioSourceComponent` / `AudioListenerComponent` - 声源与监听器组件 -> - `AudioClip` / `AudioMixer` - 音频资源与混音器 -> - `EffectChain` - 效果器链 -> - `Spatializer` - 3D空间化处理器 -> - `AudioBackend` - 音频后端抽象 -> -> **注**:Unity底层C++音频架构未公开,本设计基于公开API概念与通用音频引擎模式实现。 -> -> 版本: 1.0 -> 日期: 2026-03-20 -> 目标: 构建专业级游戏引擎音频系统 - ---- - -## 第一章 核心类型定义 - -### 1.1 音频基础类型 - -```cpp -namespace XCEngine { -namespace Audio { - -// ============================================ -// 前置类型定义 -// ============================================ - -enum class AudioResourceType { - AudioClip, - AudioMixer, - AudioBank -}; - -enum class AudioLoadState { - Unloaded, - Loading, - Loaded, - Failed -}; - -enum class AudioFormat { - Unknown, - WAV, - OGG, - MP3, - FLAC, - AAC -}; - -enum class SpeakerMode { - Mono, - Stereo, - Surround51, - Surround71, - Surround51_2, - Surround71_2 -}; - -enum class AudioChannel { - FrontLeft, - FrontRight, - FrontCenter, - LFE, - BackLeft, - BackRight, - SideLeft, - SideRight -}; - -struct AudioConfig { - uint32_t sampleRate = 48000; - uint16_t channels = 2; - uint16_t bitsPerSample = 16; - SpeakerMode speakerMode = SpeakerMode::Stereo; - uint32_t bufferSize = 8192; - uint32_t bufferCount = 2; -}; - -// ============================================ -// 3D 音频参数 -// ============================================ - -struct Audio3DParams { - float dopplerLevel = 1.0f; - float speedOfSound = 343.0f; - float minDistance = 1.0f; - float maxDistance = 500.0f; - float panLevel = 1.0f; - float spread = 0.0f; - float reverbZoneMix = 1.0f; -}; - -enum class PanMode { - Pan3D, - Pan2D -}; - -enum class VolumeSource { - Direct, - Path Occlusion, - Transmission, - Obstruction -}; - -// ============================================ -// 音频缓冲区描述 -// ============================================ - -struct AudioBufferDesc { - uint32_t size = 0; - uint32_t channels = 2; - uint32_t sampleRate = 48000; - uint16_t bitsPerSample = 16; - bool isFloat = false; - bool isCompressed = false; - AudioFormat format = AudioFormat::Unknown; -}; - -// ============================================ -// 播放状态 -// ============================================ - -enum class PlayState { - Stopped, - Playing, - Paused -}; - -enum class StopMode { - Immediate, - AllowFadeOut -}; - -// ============================================ -// 空间化参数 -// ============================================ - -struct SpatializerParams { - float azimuth = 0.0f; - float elevation = 0.0f; - float distance = 0.0f; - float volumeDb = 0.0f; - float panCartesianX = 0.0f; - float panCartesianY = 0.0f; - bool isOccluded = false; - bool isObstructed = false; -}; - -} // namespace Audio -} // namespace XCEngine -``` - -### 1.2 IAudioResource 基类 - -```cpp -namespace XCEngine { -namespace Audio { - -class IAudioResource { -public: - virtual ~IAudioResource() = default; - - virtual void SetName(const String& name) { m_name = name; } - virtual const String& GetName() const { return m_name; } - - virtual AudioResourceType GetType() const = 0; - virtual AudioLoadState GetLoadState() const { return m_loadState; } - - virtual bool IsValid() const { return m_loadState == AudioLoadState::Loaded; } - virtual void Load() = 0; - virtual void Unload() = 0; - - virtual int GetRefCount() const { return m_refCount.load(); } - virtual void AddRef() { m_refCount.fetch_add(1); } - virtual void Release() { - if (m_refCount.fetch_sub(1) == 1) { - delete this; - } - } - -protected: - IAudioResource() : m_loadState(AudioLoadState::Unloaded), m_memoryUsage(0) {} - - String m_name; - AudioLoadState m_loadState; - uint64_t m_memoryUsage; - std::atomic m_refCount{1}; -}; - -} // namespace Audio -} // namespace XCEngine -``` - ---- - -## 第二章 音频资源 - -### 2.1 AudioClip - -```cpp -namespace XCEngine { -namespace Audio { - -class AudioClip : public IAudioResource { -public: - AudioClip(); - AudioClip(const String& filePath); - ~AudioClip(); - - AudioResourceType GetType() const override { return AudioResourceType::AudioClip; } - - void Load() override; - void Unload() override; - - // 音频数据访问 - const int16_t* GetData() const { return m_data.data(); } - int16_t* GetData() { return m_data.data(); } - - const float* GetDataFloat() const { return m_dataFloat.data(); } - float* GetDataFloat() { return m_dataFloat.data(); } - - uint32_t GetDataSize() const { return m_dataSize; } - uint32_t GetSampleCount() const { return m_sampleCount; } - uint32_t GetChannelCount() const { return m_channels; } - uint32_t GetSampleRate() const { return m_sampleRate; } - uint16_t GetBitsPerSample() const { return m_bitsPerSample; } - float GetDuration() const { return m_duration; } - - AudioFormat GetFormat() const { return m_format; } - bool IsCompressed() const { return m_isCompressed; } - bool IsStreamed() const { return m_isStreamed; } - - // 频谱数据查询 - bool HasFrequencyData() const { return m_hasFrequencyData; } - const float* GetFrequencyData() const { return m_frequencyData.data(); } - uint32_t GetFrequencyDataSize() const { return m_frequencyData.size(); } - - // 资源路径 - void SetResourcePath(const String& path) { m_resourcePath = path; } - const String& GetResourcePath() const { return m_resourcePath; } - -private: - void ProcessWavFile(); - void DecodeCompressedData(); - void GenerateFrequencyData(); - void ConvertToFloat(); - -private: - std::vector m_data; - std::vector m_dataFloat; - std::vector m_frequencyData; - - uint32_t m_dataSize = 0; - uint32_t m_sampleCount = 0; - uint16_t m_channels = 2; - uint32_t m_sampleRate = 48000; - uint16_t m_bitsPerSample = 16; - float m_duration = 0.0f; - - AudioFormat m_format = AudioFormat::Unknown; - bool m_isCompressed = false; - bool m_isStreamed = false; - bool m_hasFrequencyData = false; - - String m_resourcePath; -}; - -} // namespace Audio -} // namespace XCEngine -``` - -### 2.2 AudioMixer - -```cpp -namespace XCEngine { -namespace Audio { - -class AudioMixer : public IAudioResource { -public: - AudioMixer(); - ~AudioMixer(); - - AudioResourceType GetType() const override { return AudioResourceType::AudioMixer; } - - void Load() override; - void Unload() override; - - // 混音器参数 - float GetVolume() const { return m_volume; } - void SetVolume(float volume, float fadeTime = 0.0f); - - bool IsMuted() const { return m_isMuted; } - void SetMuted(bool muted) { m_isMuted = muted; } - - // 子混音器组 - class MixerGroup* GetGroup(const String& name); - class MixerGroup* CreateGroup(const String& name); - void RemoveGroup(const String& name); - - const std::map>& GetGroups() const { return m_groups; } - - // 输出混音器 - void SetOutputMixer(AudioMixer* mixer); - AudioMixer* GetOutputMixer() const { return m_outputMixer; } - -private: - float m_volume = 1.0f; - bool m_isMuted = false; - - std::map> m_groups; - AudioMixer* m_outputMixer = nullptr; -}; - -class MixerGroup { -public: - MixerGroup(const String& name); - ~MixerGroup(); - - const String& GetName() const { return m_name; } - - float GetVolume() const { return m_volume; } - void SetVolume(float volume, float fadeTime = 0.0f); - - bool IsMuted() const { return m_isMuted; } - void SetMuted(bool muted) { m_isMuted = muted; } - - bool IsSoloed() const { return m_isSoloed; } - void SetSoloed(bool soloed) { m_isSoloed = soloed; } - - // 效果器 - void AddEffect(IAudioEffect* effect, size_t index = SIZE_MAX); - void RemoveEffect(IAudioEffect* effect); - void SetEffectEnabled(IAudioEffect* effect, bool enabled); - - const std::vector& GetEffects() const { return m_effects; } - - // 音频处理 - void Process(float* buffer, uint32_t sampleCount, uint32_t channels); - -private: - String m_name; - float m_volume = 1.0f; - bool m_isMuted = false; - bool m_isSoloed = false; - - std::vector m_effects; -}; - -} // namespace Audio -} // namespace XCEngine -``` - -### 2.3 AudioBank (用于分段加载) - -```cpp -namespace XCEngine { -namespace Audio { - -class AudioBank : public IAudioResource { -public: - AudioBank(); - ~AudioBank(); - - AudioResourceType GetType() const override { return AudioResourceType::AudioBank; } - - void Load() override; - void Unload() override; - - // 事件触发 - void TriggerEvent(const String& eventName); - void TriggerEvent(int eventId); - - // 获取嵌入的音频片段 - AudioClip* GetClip(const String& name); - AudioClip* GetClip(int id); - - const std::map& GetAllClips() const { return m_clips; } - -private: - std::map m_clips; - std::map m_eventMap; -}; - -} // namespace Audio -} // namespace XCEngine -``` - ---- - -## 第三章 DSP 效果系统 - -### 3.1 IAudioEffect 接口 - -```cpp -namespace XCEngine { -namespace Audio { - -class IAudioEffect { -public: - virtual ~IAudioEffect() = default; - - virtual const char* GetName() const = 0; - virtual void Process(float* buffer, uint32_t sampleCount, uint32_t channels) = 0; - - virtual void SetEnabled(bool enabled) { m_enabled = enabled; } - virtual bool IsEnabled() const { return m_enabled; } - - virtual void SetParameter(const char* name, float value) = 0; - virtual float GetParameter(const char* name) const = 0; - - virtual size_t GetParameterCount() const = 0; - virtual const char* GetParameterName(size_t index) const = 0; - -protected: - bool m_enabled = true; -}; - -} // namespace Audio -} // namespace XCEngine -``` - -### 3.2 FFTFilter (频谱分析) - -```cpp -namespace XCEngine { -namespace Audio { - -class FFTFilter : public IAudioEffect { -public: - FFTFilter(size_t fftSize = 1024); - ~FFTFilter(); - - const char* GetName() const override { return "FFTFilter"; } - void Process(float* buffer, uint32_t sampleCount, uint32_t channels) override; - - void SetEnabled(bool enabled) override; - - void SetFFTSize(size_t size); - size_t GetFFTSize() const { return m_fftSize; } - - void SetParameter(const char* name, float value) override; - float GetParameter(const char* name) const override; - size_t GetParameterCount() const override; - const char* GetParameterName(size_t index) const override; - - // 频谱数据访问 - const float* GetMagnitudeSpectrum() const { return m_magnitudeSpectrum.data(); } - const float* GetPhaseSpectrum() const { return m_phaseSpectrum.data(); } - size_t GetSpectrumSize() const { return m_spectrumSize; } - - // 设置时域核(用于滤波) - void SetTimeDomainKernel(const std::vector& kernel); - void AddTimeDomainKernel(const std::vector& kernel); - - // 频域核 - void SetFreqDomainKernel(const std::vector& kernel); - void AddFreqDomainKernel(const std::vector& kernel); - -private: - void Init(); - void ComplexVectorProduct(const std::vector& inputA, - const std::vector& inputB, - std::vector* result); - void InverseFFTScaling(std::vector* signal); - -private: - size_t m_fftSize; - size_t m_spectrumSize; - - bool m_analysisMode = true; - - std::vector m_magnitudeSpectrum; - std::vector m_phaseSpectrum; - - std::vector m_filterState; - std::vector m_window; - - bool m_kernelDefined = false; - std::vector m_kernelTimeDomainBuffer; - std::vector m_kernelFreqDomainBuffer; - - int m_bufferSelector = 0; - std::vector> m_signalTimeDomainBuffer; - std::vector> m_signalFreqDomainBuffer; - std::vector m_filteredFreqDomainBuffer; - - kiss_fftr_cfg m_forwardFFT = nullptr; - kiss_fftr_cfg m_inverseFFT = nullptr; -}; - -} // namespace Audio -} // namespace XCEngine -``` - -### 3.3 Reverbation (混响) - -```cpp -namespace XCEngine { -namespace Audio { - -class Reverbation : public IAudioEffect { -public: - Reverbation(int blockSize = 1024, int samplingRate = 48000, float reverbTime = 2.0f); - ~Reverbation(); - - const char* GetName() const override { return "Reverbation"; } - void Process(float* buffer, uint32_t sampleCount, uint32_t channels) override; - - void SetParameter(const char* name, float value) override; - float GetParameter(const char* name) const override; - size_t GetParameterCount() const override; - const char* GetParameterName(size_t index) const override; - - // 混响参数 - void SetReverbTime(float seconds); - float GetReverbTime() const { return m_reverbTime; } - - void SetDecay(float decay) { m_decay = decay; } - float GetDecay() const { return m_decay; } - - void SetPreDelay(float ms); - float GetPreDelay() const { return m_preDelayMs; } - - void SetHighFreqDamping(float ratio); - float GetHighFreqDamping() const { return m_highFreqDamping; } - - // 脉冲响应 - const std::vector& GetImpulseResponseLeft() const { return m_impulseResponseLeft; } - const std::vector& GetImpulseResponseRight() const { return m_impulseResponseRight; } - - float GetQuietPeriod() const { return m_quietPeriodSec; } - -private: - void RenderImpulseResponse(int blockSize, int samplingRate, float reverbTime); - static float FloatRand(); - -private: - int m_blockSize; - float m_reverbTime = 2.0f; - float m_decay = 0.7f; - float m_preDelayMs = 20.0f; - float m_highFreqDamping = 0.5f; - - std::vector m_impulseResponseLeft; - std::vector m_impulseResponseRight; - float m_quietPeriodSec; - - std::unique_ptr m_leftReverbFilter; - std::unique_ptr m_rightReverbFilter; - - std::vector m_reverbInput; - std::vector m_reverbOutputLeft; - std::vector m_reverbOutputRight; - int m_reverbOutputReadPos = 0; -}; - -} // namespace Audio -} // namespace XCEngine -``` - -### 3.4 EQ (均衡器) - -```cpp -namespace XCEngine { -namespace Audio { - -class Equalizer : public IAudioEffect { -public: - Equalizer(); - ~Equalizer(); - - const char* GetName() const override { return "Equalizer"; } - void Process(float* buffer, uint32_t sampleCount, uint32_t channels) override; - - void SetParameter(const char* name, float value) override; - float GetParameter(const char* name) const override; - size_t GetParameterCount() const override; - const char* GetParameterName(size_t index) const override; - - // 频段控制 - enum class Band { - LowPass, - HighPass, - BandPass, - Notch, - LowShelf, - HighShelf, - Peaking - }; - - void SetBand(size_t index, Band type, float frequency, float gainDb, float Q); - void SetBandEnabled(size_t index, bool enabled); - - struct BandParams { - Band type = Band::Peaking; - float frequency = 1000.0f; - float gainDb = 0.0f; - float Q = 1.0f; - bool enabled = true; - }; - - const BandParams& GetBand(size_t index) const { return m_bands[index]; } - -private: - void UpdateCoefficients(size_t index); - void ProcessBand(float* buffer, uint32_t sampleCount, size_t bandIndex); - -private: - static constexpr size_t MaxBands = 10; - std::array m_bands; - std::array, MaxBands> m_coefficients; - - std::vector m_x1, m_x2, m_y1, m_y2; -}; - -} // namespace Audio -} // namespace XCEngine -``` - -### 3.5 Dynamics (动态效果器) - -```cpp -namespace XCEngine { -namespace Audio { - -class Compressor : public IAudioEffect { -public: - Compressor(); - ~Compressor(); - - const char* GetName() const override { return "Compressor"; } - void Process(float* buffer, uint32_t sampleCount, uint32_t channels) override; - - void SetParameter(const char* name, float value) override; - float GetParameter(const char* name) const override; - size_t GetParameterCount() const override; - const char* GetParameterName(size_t index) const override; - - void SetThreshold(float db); - float GetThreshold() const { return m_thresholdDb; } - - void SetRatio(float ratio); - float GetRatio() const { return m_ratio; } - - void SetAttack(float ms); - float GetAttack() const { return m_attackMs; } - - void SetRelease(float ms); - float GetRelease() const { return m_releaseMs; } - - float GetGainReduction() const { return m_gainReduction; } - -private: - float m_thresholdDb = -20.0f; - float m_ratio = 4.0f; - float m_attackMs = 10.0f; - float m_releaseMs = 100.0f; - - float m_gainReduction = 0.0f; - float m_envelope = 0.0f; -}; - -class Limiter : public IAudioEffect { -public: - Limiter(); - ~Limiter(); - - const char* GetName() const override { return "Limiter"; } - void Process(float* buffer, uint32_t sampleCount, uint32_t channels) override; - - void SetParameter(const char* name, float value) override; - float GetParameter(const char* name) const override; - size_t GetParameterCount() const override; - const char* GetParameterName(size_t index) const override; - - void SetCeiling(float db); - float GetCeiling() const { return m_ceilingDb; } - - void SetRelease(float ms); - float GetRelease() const { return m_releaseMs; } - -private: - float m_ceilingDb = -0.3f; - float m_releaseMs = 50.0f; - float m_envelope = 0.0f; -}; - -} // namespace Audio -} // namespace XCEngine -``` - ---- - -## 第四章 3D 空间音频 - -### 4.1 SpatializerParams 空间化参数 - -```cpp -namespace XCEngine { -namespace Audio { - -struct SpatializerParams { - Vector3 listenerPosition; - Quaternion listenerRotation; - Vector3 listenerVelocity; - - Vector3 sourcePosition; - Vector3 sourceVelocity; - Quaternion sourceRotation; - - Audio3DParams params; - - float GetDistanceAttenuation() const; - float GetPan() const; - float GetDopplerShift() const; -}; -``` - -### 4.2 HRTF 空间化 - -```cpp -namespace XCEngine { -namespace Audio { - -class HRTF { -public: - HRTF(); - ~HRTF(); - - // 方向设置 - void SetDirection(int elevation, int azimuth); - void GetDirection(int& elevation, int& azimuth); - - // HRTF 脉冲响应 - void GetLeftEarTimeHRTF(std::vector& data); - void GetLeftEarFreqHRTF(std::vector& data); - void GetRightEarTimeHRTF(std::vector& data); - void GetRightEarFreqHRTF(std::vector& data); - - // 空间化处理 - void Process(const float* input, float* outputLeft, float* outputRight, - size_t sampleCount, size_t channels); - - // 淡入淡出处理 - void ApplyFadeWindow(const std::vector& blockLast, - const std::vector& blockNext, - std::vector* fadeResult); - - // 重采样 - void Resample(const float* input, float* output, size_t inputSize, - size_t outputSize, size_t channels); - -private: - void BuildReacherTree(); - int SearchNearestIndex(int elevation, int azimuth); - double CalculateSphereDistance(int elev1, int azim1, int elev2, int azim2); - -private: - int m_realAzimuth = 0; - int m_realElevation = 0; - int m_azimuth = 0; - int m_elevation = 0; - bool m_isSwapLeftRight = false; - int m_directionIndex = 0; - - using ResampledHRTFT = std::vector; - using ResampledHRTFPairT = std::pair; - - std::vector m_hrtfResampledTimeDomain; - std::vector m_hrtfResampledFreqDomain; - - std::map, std::vector>> m_hrtfDirections; - - std::unique_ptr m_filter; - std::unique_ptr m_hrtfFilterLeft; - std::unique_ptr m_hrtfFilterRight; - - std::vector m_fadeWindow; -}; - -} // namespace Audio -} // namespace XCEngine -``` - -### 4.3 DopplerEffect 多普勒效应 - -```cpp -namespace XCEngine { -namespace Audio { - -class DopplerEffect { -public: - DopplerEffect(); - ~DopplerEffect(); - - void SetSpeedOfSound(float metersPerSecond); - float GetSpeedOfSound() const { return m_speedOfSound; } - - void SetDopplerLevel(float level); - float GetDopplerLevel() const { return m_dopplerLevel; } - - float CalculatePitchShift(const Vector3& sourceVelocity, - const Vector3& listenerVelocity, - const Vector3& sourceToListener); - -private: - float m_speedOfSound = 343.0f; - float m_dopplerLevel = 1.0f; -}; - -} // namespace Audio -} // namespace XCEngine -``` - -### 4.4 OcclusionSystem 遮蔽系统 - -```cpp -namespace XCEngine { -namespace Audio { - -class OcclusionSystem { -public: - OcclusionSystem(); - ~OcclusionSystem(); - - // 遮蔽计算 - float CalculateOcclusion(const Vector3& sourcePos, const Vector3& listenerPos, - const std::vector& obstaclePositions); - - float CalculateObstruction(const Vector3& sourcePos, const Vector3& listenerPos, - const Vector3& obstaclePos); - - // 遮蔽参数 - void SetOcclusionHighFreqAbsorption(float value); - float GetOcclusionHighFreqAbsorption() const { return m_occlusionHighFreqAbsorb; } - - void SetObstructionHighFreqDamping(float value); - float GetObstructionHighFreqDamping() const { return m_obstructionHighFreqDamp; } - -private: - float m_occlusionHighFreqAbsorb = 0.5f; - float m_obstructionHighFreqDamp = 0.5f; -}; - -} // namespace Audio -} // namespace XCEngine -``` - ---- - -## 第五章 音频后端抽象层 - -### 5.1 IAudioBackend 接口 - -```cpp -namespace XCEngine { -namespace Audio { - -class IAudioBackend { -public: - virtual ~IAudioBackend() = default; - - // 初始化与销毁 - virtual bool Initialize(const AudioConfig& config) = 0; - virtual void Shutdown() = 0; - - // 设备查询 - virtual String GetDeviceName() const = 0; - virtual void GetAvailableDevices(std::vector& devices) = 0; - virtual bool SetDevice(const String& deviceName) = 0; - - // 混音器控制 - virtual float GetMasterVolume() const = 0; - virtual void SetMasterVolume(float volume) = 0; - - virtual bool IsMuted() const = 0; - virtual void SetMuted(bool muted) = 0; - - // 播放控制 - virtual void Start() = 0; - virtual void Stop() = 0; - virtual void Suspend() = 0; - virtual void Resume() = 0; - - // 音频处理 - virtual void ProcessAudio(float* buffer, uint32_t bufferSize, - uint32_t channels, uint32_t sampleRate) = 0; - - // 状态查询 - virtual bool IsRunning() const = 0; - virtual AudioConfig GetConfig() const = 0; -}; - -} // namespace Audio -} // namespace XCEngine -``` - -### 5.2 WASAPI 后端 - -```cpp -namespace XCEngine { -namespace Audio { -namespace WASAPI { - -class WASAPIBackend : public IAudioBackend { -public: - WASAPIBackend(); - ~WASAPIBackend() override; - - bool Initialize(const AudioConfig& config) override; - void Shutdown() override; - - String GetDeviceName() const override; - void GetAvailableDevices(std::vector& devices) override; - bool SetDevice(const String& deviceName) override; - - float GetMasterVolume() const override; - void SetMasterVolume(float volume) override; - - bool IsMuted() const override; - void SetMuted(bool muted) override; - - void Start() override; - void Stop() override; - void Suspend() override; - void Resume() override; - - void ProcessAudio(float* buffer, uint32_t bufferSize, - uint32_t channels, uint32_t sampleRate) override; - - bool IsRunning() const override { return m_isRunning; } - AudioConfig GetConfig() const override { return m_config; } - -private: - MMRESULT InitDevice(); - MMRESULT InitClient(); - MMRESULT InitBuffer(); - - static DWORD WINAPI AudioThreadProc(LPVOID lpParameter); - void AudioThread(); - - void OnAudioCallback(HWAVEOUT hwo, UINT uMsg, DWORD_PTR dwInstance, - DWORD_PTR dwParam1, DWORD_PTR dwParam2); - static void CALLBACK StaticAudioCallback(HWAVEOUT hwo, UINT uMsg, - DWORD_PTR dwInstance, - DWORD_PTR dwParam1, DWORD_PTR dwParam2); - -private: - bool m_isRunning = false; - std::thread m_audioThread; - - AudioConfig m_config; - - WAVEFORMATEX m_waveFormat = {}; - HWAVEOUT m_hWaveOut = nullptr; - HANDLE m_hEvent = nullptr; - - std::vector m_mixBuffer; - uint32_t m_mixBufferSize = 0; - - std::vector m_waveOutCaps; - String m_deviceName; - - IAudioEffect* m_masterEffect = nullptr; -}; - -} // namespace WASAPI -} // namespace Audio -} // namespace XCEngine -``` - -### 5.3 OpenAL 后端 - -```cpp -namespace XCEngine { -namespace Audio { -namespace OpenAL { - -class OpenALBackend : public IAudioBackend { -public: - OpenALBackend(); - ~OpenALBackend() override; - - bool Initialize(const AudioConfig& config) override; - void Shutdown() override; - - String GetDeviceName() const override; - void GetAvailableDevices(std::vector& devices) override; - bool SetDevice(const String& deviceName) override; - - float GetMasterVolume() const override; - void SetMasterVolume(float volume) override; - - bool IsMuted() const override; - void SetMuted(bool muted) override; - - void Start() override; - void Stop() override; - void Suspend() override; - void Resume() override; - - void ProcessAudio(float* buffer, uint32_t bufferSize, - uint32_t channels, uint32_t sampleRate) override; - - bool IsRunning() const override { return m_isRunning; } - AudioConfig GetConfig() const override { return m_config; } - -private: - bool m_isRunning = false; - AudioConfig m_config; - - ALCdevice* m_device = nullptr; - ALCcontext* m_context = nullptr; - ALuint m_source = 0; - ALuint m_buffers[2]; - - std::vector m_mixBuffer; -}; - -} // namespace OpenAL -} // namespace Audio -} // namespace XCEngine -``` - ---- - -## 第六章 音频系统核心 - -### 6.1 AudioSystem - -```cpp -namespace XCEngine { -namespace Audio { - -class AudioSystem { -public: - static AudioSystem& Get(); - - void Initialize(const AudioConfig& config); - void Shutdown(); - - void Update(float deltaTime); - - // 后端控制 - void SetBackend(std::unique_ptr backend); - IAudioBackend* GetBackend() const { return m_backend.get(); } - - // 设备控制 - String GetCurrentDevice() const; - void SetDevice(const String& deviceName); - void GetAvailableDevices(std::vector& devices); - - // 全局混音控制 - float GetMasterVolume() const; - void SetMasterVolume(float volume); - - bool IsMuted() const; - void SetMuted(bool muted); - - // 音频处理 - void ProcessAudio(float* buffer, uint32_t sampleCount, uint32_t channels); - - // 资源管理 - AudioClip* LoadAudioClip(const String& filePath); - void UnloadAudioClip(AudioClip* clip); - - AudioMixer* CreateMixer(const String& name); - void DestroyMixer(AudioMixer* mixer); - - // 空间化 - void SetListenerTransform(const Vector3& position, const Quaternion& rotation); - void SetListenerVelocity(const Vector3& velocity); - - const Vector3& GetListenerPosition() const { return m_listenerPosition; } - const Quaternion& GetListenerRotation() const { return m_listenerRotation; } - const Vector3& GetListenerVelocity() const { return m_listenerVelocity; } - - // 统计信息 - struct Stats { - uint32_t activeSources; - uint32_t totalSources; - uint64_t memoryUsage; - float cpuUsage; - }; - const Stats& GetStats() const { return m_stats; } - -private: - AudioSystem() = default; - ~AudioSystem() = default; - - AudioSystem(const AudioSystem&) = delete; - AudioSystem& operator=(const AudioSystem&) = delete; - - void ProcessSource(AudioSourceComponent* source); - void ApplyGlobalEffects(float* buffer, uint32_t sampleCount, uint32_t channels); - -private: - std::unique_ptr m_backend; - - Vector3 m_listenerPosition = Vector3::Zero(); - Quaternion m_listenerRotation = Quaternion::Identity(); - Vector3 m_listenerVelocity = Vector3::Zero(); - - std::vector> m_loadedClips; - std::map> m_mixers; - - std::vector m_activeSources; - - Stats m_stats = {}; - float m_deltaTime = 0.0f; -}; - -} // namespace Audio -} // namespace XCEngine -``` - -### 6.2 AudioSourceComponent - -```cpp -namespace XCEngine { -namespace Components { - -class AudioSourceComponent : public Component { -public: - AudioSourceComponent(); - ~AudioSourceComponent(); - - // 播放控制 - void Play(); - void Pause(); - void Stop(StopMode mode = StopMode::Immediate); - bool IsPlaying() const { return m_playState == Audio::PlayState::Playing; } - bool IsPaused() const { return m_playState == Audio::PlayState::Paused; } - - // 音频剪辑 - void SetClip(Audio::AudioClip* clip); - Audio::AudioClip* GetClip() const { return m_clip; } - - // 播放参数 - void SetVolume(float volume); - float GetVolume() const { return m_volume; } - - void SetPitch(float pitch); - float GetPitch() const { return m_pitch; } - - void SetPan(float pan); - float GetPan() const { return m_pan; } - - // 循环 - void SetLooping(bool loop); - bool IsLooping() const { return m_isLooping; } - - // 3D 空间化 - void SetSpatialize(bool spatialize); - bool IsSpatialize() const { return m_spatialize; } - - void Set3DParams(const Audio::Audio3DParams& params); - const Audio::Audio3DParams& Get3DParams() const { return m_3DParams; } - - void SetDopplerLevel(float level); - float GetDopplerLevel() const { return m_3DParams.dopplerLevel; } - - void SetSpread(float spread); - float GetSpread() const { return m_3DParams.spread; } - - void SetReverbZoneMix(float mix); - float GetReverbZoneMix() const { return m_3DParams.reverbZoneMix; } - - // 混音器 - void SetOutputMixer(Audio::AudioMixer* mixer); - Audio::AudioMixer* GetOutputMixer() const { return m_outputMixer; } - - // 播放位置 - void SetTime(float seconds); - float GetTime() const; - float GetDuration() const; - - // 能量检测 - float GetEnergy() const { return m_energy; } - void StartEnergyDetect(); - void StopEnergyDetect(); - bool IsEnergyDetecting() const { return m_isEnergyDetecting; } - - // Component 虚函数 - void Update(float deltaTime) override; - void OnEnable() override; - void OnDisable() override; - void OnDestroy() override; - -private: - void Apply3DAttenuation(); - void ProcessEffects(float* buffer, uint32_t sampleCount); - void UpdateEnergy(const float* buffer, uint32_t sampleCount); - -private: - Audio::AudioClip* m_clip = nullptr; - Audio::AudioMixer* m_outputMixer = nullptr; - - PlayState m_playState = PlayState::Stopped; - bool m_isLooping = false; - - float m_volume = 1.0f; - float m_pitch = 1.0f; - float m_pan = 0.0f; - bool m_spatialize = true; - - Audio::Audio3DParams m_3DParams; - - // 播放状态 - uint64_t m_samplePosition = 0; - double m_lastingTime = 0.0; - - // 能量检测 - bool m_isEnergyDetecting = false; - float m_energy = 0.0f; - float m_maxEnergy = 5.0f; - std::deque m_energyHistory; - - // 效果链 - std::vector m_effects; - std::unique_ptr m_hrtf; - std::unique_ptr m_doppler; - - // 输出缓冲 - static constexpr size_t BufferSize = 8192; - std::vector m_outputBuffer; -}; - -} // namespace Components -} // namespace XCEngine -``` - -### 6.3 AudioListenerComponent - -```cpp -namespace XCEngine { -namespace Components { - -class AudioListenerComponent : public Component { -public: - AudioListenerComponent(); - ~AudioListenerComponent(); - - // 能量检测 - float GetEnergy() const { return m_energy; } - const float* GetFrequencyData() const { return m_frequencyData.data(); } - size_t GetFrequencyDataSize() const { return m_frequencyData.size(); } - - // 全局参数 - void SetMasterVolume(float volume); - float GetMasterVolume() const { return m_masterVolume; } - - void SetMute(bool mute); - bool IsMute() const { return m_mute; } - - // 空间化参数 - void SetDopplerLevel(float level); - float GetDopplerLevel() const { return m_dopplerLevel; } - - void SetSpeedOfSound(float metersPerSecond); - float GetSpeedOfSound() const { return m_speedOfSound; } - - // 混响 - void SetReverbLevel(float level); - float GetReverbLevel() const { return m_reverbLevel; } - - void SetReverb(Audio::AudioMixer* reverb); - Audio::AudioMixer* GetReverb() const { return m_reverb; } - - // Component 虚函数 - void Update(float deltaTime) override; - -private: - float m_masterVolume = 1.0f; - bool m_mute = false; - float m_dopplerLevel = 1.0f; - float m_speedOfSound = 343.0f; - float m_reverbLevel = 1.0f; - - Audio::AudioMixer* m_reverb = nullptr; - - float m_energy = 0.0f; - std::vector m_frequencyData; -}; - -} // namespace Components -} // namespace XCEngine -``` - ---- - -## 第七章 事件系统 - -### 7.1 AudioEvent - -```cpp -namespace XCEngine { -namespace Audio { - -class AudioEvent { -public: - AudioEvent() = default; - AudioEvent(const String& name) : m_name(name) {} - - using Callback = std::function; - using FloatCallback = std::function; - - uint64_t Subscribe(Callback callback); - uint64_t Subscribe(FloatCallback callback, float param); - - void Unsubscribe(uint64_t id); - void ProcessUnsubscribes(); - - void Invoke() const; - void Invoke(float param) const; - - void Clear(); - - const String& GetName() const { return m_name; } - void SetName(const String& name) { m_name = name; } - -private: - struct ListenerBase { - virtual ~ListenerBase() = default; - virtual void Call() = 0; - virtual void CallFloat(float) = 0; - }; - - template - struct Listener : ListenerBase { - Listener(Func&& f) : func(std::move(f)) {} - void Call() override { func(); } - void CallFloat(float) override { - if constexpr (std::is_invocable_v) { - func(0.0f); - } else { - func(); - } - } - Func func; - }; - - String m_name; - std::vector>> m_listeners; - std::vector m_pendingUnsubscribes; - uint64_t m_nextId = 0; -}; - -} // namespace Audio -} // namespace XCEngine -``` - -### 7.2 AudioEventSystem - -```cpp -namespace XCEngine { -namespace Audio { - -class AudioEventSystem { -public: - static AudioEventSystem& Get(); - - void Initialize(); - void Shutdown(); - - // 事件创建 - AudioEvent* CreateEvent(const String& name); - AudioEvent* GetEvent(const String& name); - AudioEvent* GetEvent(uint64_t id); - - void DestroyEvent(const String& name); - void DestroyEvent(uint64_t id); - - // 触发事件 - void TriggerEvent(const String& name); - void TriggerEvent(uint64_t id); - void TriggerEvent(const String& name, float param); - void TriggerEvent(uint64_t id, float param); - - // 订阅 - uint64_t Subscribe(const String& eventName, AudioEvent::Callback callback); - uint64_t Subscribe(const String& eventName, AudioEvent::FloatCallback callback, float param); - - // 状态 - bool HasEvent(const String& name) const; - bool HasEvent(uint64_t id) const; - size_t GetEventCount() const { return m_events.size(); } - -private: - AudioEventSystem() = default; - ~AudioEventSystem() = default; - - std::map> m_events; - std::map m_idToEvent; - uint64_t m_nextEventId = 0; -}; - -} // namespace Audio -} // namespace XCEngine -``` - ---- - -## 第八章 资源管理集成 - -### 8.1 AudioClipLoader - -```cpp -namespace XCEngine { -namespace Resources { - -class AudioClipLoader : public IResourceLoader { -public: - AudioClipLoader(); - ~AudioClipLoader() override; - - bool CanLoad(const String& extension) const override; - IResource* Load(const String& filePath) override; - bool LoadAsync(const String& filePath, std::function callback) override; - void Unload(IResource* resource) override; - - // 获取支持的文件格式 - static const std::vector& GetSupportedExtensions(); - -private: - static std::vector s_supportedExtensions; -}; - -} // namespace Resources -} // namespace XCEngine -``` - -### 8.2 ResourceManager 集成 - -```cpp -namespace XCEngine { -namespace Resources { - -// 在 ResourceManager 中添加音频相关接口 - -template<> -class ResourceHandle { -public: - ResourceHandle() = default; - explicit ResourceHandle(Audio::AudioClip* resource); - - Audio::AudioClip* Get() const { return m_resource; } - Audio::AudioClip* operator->() const { return m_resource; } - - explicit operator bool() const { return m_resource != nullptr; } - -private: - Audio::AudioClip* m_resource = nullptr; -}; - -} // namespace Resources -} // namespace XCEngine -``` - ---- - -## 第九章 目录结构 - -``` -XCEngine/ -├── engine/ # 引擎核心库 -│ ├── include/XCEngine/ -│ │ ├── Audio/ # 音频模块 -│ │ │ ├── AudioSystem.h # 音频系统入口 -│ │ │ ├── AudioConfig.h # 配置结构 -│ │ │ ├── AudioTypes.h # 基础类型定义 -│ │ │ ├── IAudioBackend.h # 后端抽象接口 -│ │ │ ├── AudioBackendFactory.h # 后端工厂 -│ │ │ │ -│ │ │ ├── AudioClip.h # 音频剪辑资源 -│ │ │ ├── AudioMixer.h # 混音器 -│ │ │ ├── AudioBank.h # 音频银行 -│ │ │ │ -│ │ │ ├── IAudioEffect.h # 效果器接口 -│ │ │ ├── FFTFilter.h # FFT频谱分析 -│ │ │ ├── Reverbation.h # 混响效果 -│ │ │ ├── Equalizer.h # 均衡器 -│ │ │ ├── Compressor.h # 压缩器 -│ │ │ ├── Limiter.h # 限制器 -│ │ │ ├── DopplerEffect.h # 多普勒效应 -│ │ │ ├── OcclusionSystem.h # 遮蔽系统 -│ │ │ │ -│ │ │ ├── Spatializer.h # 空间化处理 -│ │ │ ├── HRTF.h # HRTF实现 -│ │ │ │ -│ │ │ ├── AudioEvent.h # 音频事件 -│ │ │ ├── AudioEventSystem.h # 事件系统 -│ │ │ │ -│ │ │ ├── WASAPI/ -│ │ │ │ └── WASAPIBackend.h # WASAPI后端 -│ │ │ └── OpenAL/ -│ │ │ └── OpenALBackend.h # OpenAL后端 -│ │ │ │ -│ │ │ └── XCEngineAudio.h # 主头文件 -│ │ │ -│ │ └── Components/ -│ │ ├── AudioSourceComponent.h # 声源组件 -│ │ └── AudioListenerComponent.h # 监听器组件 -│ │ -│ └── src/Audio/ -│ ├── AudioSystem.cpp -│ ├── AudioClip.cpp -│ ├── AudioMixer.cpp -│ ├── FFTFilter.cpp -│ ├── Reverbation.cpp -│ ├── Equalizer.cpp -│ ├── HRTF.cpp -│ ├── DopplerEffect.cpp -│ ├── OcclusionSystem.cpp -│ ├── AudioEventSystem.cpp -│ ├── WASAPI/ -│ │ └── WASAPIBackend.cpp -│ └── OpenAL/ -│ └── OpenALBackend.cpp -│ -├── mvs/ # 示例应用 -│ └── Music fluctuations/ # 音频示例(参考) -│ ├── source/ -│ │ ├── audio/ # 基础音频 -│ │ ├── audio3d/ # 3D音频 -│ │ ├── kissfft/ # FFT库 -│ │ └── libsamplerate/ # 重采样库 -│ └── res/ # 音频资源 -│ -└── tests/Audio/ # 音频模块测试 - ├── AudioSystemTest.cpp - ├── AudioClipTest.cpp - ├── SpatializerTest.cpp - └── HRTFTest.cpp -``` - ---- - -## 附录:实现优先级 - -### 第一阶段 - 基础音频 -1. AudioSystem 核心框架 -2. AudioClip 资源加载(WAV格式) -3. AudioSourceComponent 播放控制 -4. AudioListenerComponent 监听器 -5. WASAPI 后端实现 - -### 第二阶段 - 3D 空间音频 -1. HRTF 空间化 -2. DopplerEffect 多普勒效应 -3. OcclusionSystem 遮蔽系统 -4. 距离衰减 - -### 第三阶段 - 效果系统 -1. FFTFilter 频谱分析 -2. Reverbation 混响 -3. Equalizer 均衡器 -4. Compressor/Limiter 动态效果 -5. 效果链系统 - -### 第四阶段 - 混音与事件 -1. AudioMixer 混音器 -2. AudioEvent 事件系统 -3. AudioBank 分段加载 -4. OpenAL 后端实现 - -### 第五阶段 - 优化与扩展 -1. 资源异步加载 -2. 性能优化 -3. profiler 集成 -4. 更多音频格式支持 diff --git a/docs/used/XCGameEngine架构设计.md b/docs/used/XCGameEngine架构设计.md deleted file mode 100644 index b505f2c4..00000000 --- a/docs/used/XCGameEngine架构设计.md +++ /dev/null @@ -1,6025 +0,0 @@ -# XCEngine - 仿Unity引擎架构设计文档 - -> **⚠️ 重要声明(严禁修改!)** -> 下文出现矛盾时永远以开头声明为准!!!!!!!! -> 本架构**1:1参考传统Unity引擎架构**(GameObject + MonoBehaviour模式),**不是Unity DOTS**! -> -> - 组件是**类**(引用类型),包含数据和行为 -> - 实体是GameObject容器 -> - 系统是OOP风格的类 -> - 不是纯数据组件 + 纯函数系统的DOTS模式 -> - 不是SoA内存布局(**除粒子系统性能关键路径外**) -> - 不要被"ECS"这个词误导,这里是传统Unity的Component模式 -> - TransformComponent继承自Component,与Unity的GetComponent\() API行为一致 - ---- - -## 文档信息 - -- **版本**: 1.0 -- **日期**: 2026-03-12 -- **状态**: 架构设计文档 -- **目标**: 构建类Unity的专业游戏引擎 - ---- - -## 第一章 整体架构概览 - -### 1.1 架构设计原则 - -本引擎采用**分层架构**结合**模块化设计**,遵循以下核心原则: - -1. **关注点分离 (Separation of Concerns)**: 各系统独立演化,通过明确定义的接口通信 -2. **依赖倒置 (Dependency Inversion)**: 上层模块依赖抽象接口,不依赖具体实现 -3. **开闭原则 (Open-Closed)**: 对扩展开放,对修改封闭 -4. **单一职责 (Single Responsibility)**: 每个模块只负责一项职责 -5. **数据驱动 (Data-Driven)**: 引擎行为由数据(组件、资源、配置)驱动 -6. **组件化架构**: 采用类Unity的组件模式,组件挂载在实体上 -7. **渲染抽象**: 跨 API 的渲染接口,便于多平台支持 - -### 1.2 整体架构图 - -``` -┌─────────────────────────────────────────────────────────────────────────────┐ -│ XCGameEngine │ -├─────────────────────────────────────────────────────────────────────────────┤ -│ │ -│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────────────┐│ -│ │ Editor │ │ Runtime │ │ Toolchain ││ -│ │ (编辑器) │ │ (运行时) │ │ (工具链) ││ -│ ├─────────────────┤ ├─────────────────┤ ├─────────────────────────┤│ -│ │ • UI Framework │ │ • Game Loop │ │ • Asset Processor ││ -│ │ • Scene View │ │ • Component Update │ │ • Shader Compiler ││ -│ │ • Inspector │ │ • Physics Sim │ │ • Mesh Optimizer ││ -│ │ • Hierarchy │ │ • Audio Engine │ │ • Texture Compressor ││ -│ │ • Project │ │ • Script VM │ │ • Build Pipeline ││ -│ │ • Console │ │ • Networking │ │ • Profiler ││ -│ └────────┬────────┘ └────────┬────────┘ └────────────┬────────────┘│ -│ │ │ │ │ -│ └───────────────────────┼──────────────────────────┘ │ -│ │ │ -│ ┌────────────────────────────────▼───────────────────────────────────────┐ │ -│ │ Engine Core │ │ -│ ├────────────────────────────────────────────────────────────────────────┤ │ -│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐│ │ -│ │ │ Renderer │ │ Scene │ │ Resource │ │ Components ││ │ -│ │ │ (渲染) │ │ (场景) │ │ (资源) │ │ (组件系统) ││ │ -│ │ └──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘│ │ -│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐│ │ -│ │ │ Physics │ │ Audio │ │ Script │ │ Math ││ │ -│ │ │ (物理) │ │ (音频) │ │ (脚本) │ │ (数学) ││ │ -│ │ └──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘│ │ -│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐│ │ -│ │ │ Memory │ │ Thread │ │ Network │ │ File ││ │ -│ │ │ (内存) │ │ (线程) │ │ (网络) │ │ (文件) ││ │ -│ │ └──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘│ │ -│ └───────────────────────────────────────────────────────────────────────┘ │ -│ │ │ -│ ┌────────────────────────────────▼───────────────────────────────────────┐ │ -│ │ Platform Layer │ │ -│ ├────────────────────────────────────────────────────────────────────────┤ │ -│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────────────┐ │ │ -│ │ │ D3D12 │ │ Vulkan │ │ Metal │ │ OpenGL │ │ │ -│ │ └─────────┘ └─────────┘ └─────────┘ └─────────────────┘ │ │ -│ └───────────────────────────────────────────────────────────────────────┘ │ -│ │ -└─────────────────────────────────────────────────────────────────────────────┘ -``` - -### 1.3 目录结构设计 - -``` -XCVolumeRenderer/ -├── engine/ # 引擎核心库(静态库) -│ ├── CMakeLists.txt -│ ├── include/ -│ │ └── XCGameEngine/ -│ │ ├── Core/ # 核心基础 -│ │ │ ├── Assert.h -│ │ │ ├── Enum.h -│ │ │ ├── Flags.h -│ │ │ ├── TypeTraits.h -│ │ │ ├── Variant.h -│ │ │ └── UniquePtr.h -│ │ ├── Math/ # 数学库 -│ │ │ ├── Vector2.h -│ │ │ ├── Vector3.h -│ │ │ ├── Vector4.h -│ │ │ ├── Matrix2.h -│ │ │ ├── Matrix3.h -│ │ │ ├── Matrix4.h -│ │ │ ├── Quaternion.h -│ │ │ ├── Transform.h -│ │ │ ├── Color.h -│ │ │ ├── MathUtils.h -│ │ │ ├── Ray.h -│ │ │ ├── Plane.h -│ │ │ ├── Sphere.h -│ │ │ ├── Box.h -│ │ │ └── Frustum.h -│ │ ├── Containers/ # 容器 -│ │ │ ├── Array.h -│ │ │ ├── FixedArray.h -│ │ │ ├── Vector.h -│ │ │ ├── Map.h -│ │ │ ├── Set.h -│ │ │ ├── HashMap.h -│ │ │ ├── String.h -│ │ │ └── RingBuffer.h -│ │ ├── Memory/ # 内存管理 -│ │ │ ├── Allocator.h -│ │ │ ├── StackAllocator.h -│ │ │ ├── PoolAllocator.h -│ │ │ ├── FreeListAllocator.h -│ │ │ ├── ProxyAllocator.h -│ │ │ └── MemoryUtils.h -│ │ ├── Threading/ # 线程 -│ │ │ ├── Thread.h -│ │ │ ├── Mutex.h -│ │ │ ├── RWLock.h -│ │ │ ├── Semaphore.h -│ │ │ ├── ThreadPool.h -│ │ │ ├── TaskSystem.h -│ │ │ ├── SpinLock.h -│ │ │ └── Atomic.h -│ │ ├── IO/ # 文件IO -│ │ │ ├── FileSystem.h -│ │ │ ├── Path.h -│ │ │ ├── Stream.h -│ │ │ ├── FileReader.h -│ │ │ ├── FileWriter.h -│ │ │ ├── Archive.h -│ │ │ └── Compression.h -│ │ ├── Debug/ # 调试 -│ │ │ ├── Logger.h -│ │ │ ├── Profiler.h -│ │ │ ├── DebugDraw.h -│ │ │ ├── Assertion.h -│ │ │ └── Traces.h -│ │ ├── Components/ # 组件系统(类Unity GameObject模式) -│ │ │ ├── GameObject.h # 游戏对象 -│ │ │ ├── Component.h # 组件基类 -│ │ │ └── System.h # 系统基类 -│ │ ├── Scene/ # 场景系统 -│ │ │ ├── Scene.h -│ │ │ ├── SceneLoader.h -│ │ │ ├── SceneSerializer.h -│ │ │ ├── SubScene.h -│ │ │ └── SceneManager.h -│ │ ├── Renderer/ # 渲染系统 -│ │ │ ├── ForwardDeclarations.h -│ │ │ ├── Device.h -│ │ │ ├── Context.h -│ │ │ ├── SwapChain.h -│ │ │ ├── Buffer.h -│ │ │ ├── Texture.h -│ │ │ ├── Shader.h -│ │ │ ├── Pipeline.h -│ │ │ ├── RenderPass.h -│ │ │ ├── RenderTarget.h -│ │ │ ├── CommandList.h -│ │ │ ├── CommandQueue.h -│ │ │ ├── Descriptor.h -│ │ │ ├── Fence.h -│ │ │ ├── QueryHeap.h -│ │ │ ├── GpuResource.h -│ │ │ ├── GpuAllocator.h -│ │ │ ├── GpuEvent.h -│ │ │ ├── RenderModule.h -│ │ │ ├── RenderGraph.h -│ │ │ ├── Light.h -│ │ │ ├── Camera.h -│ │ │ ├── Material.h -│ │ │ ├── Mesh.h -│ │ │ ├── Skybox.h -│ │ │ ├── DebugRenderer.h -│ │ │ └── PostProcess.h -│ │ ├── Resources/ # 资源系统 -│ │ │ ├── Resource.h -│ │ │ ├── ResourceManager.h -│ │ │ ├── ResourceLoader.h -│ │ │ ├── ResourceCache.h -│ │ │ ├── AssetDatabase.h -│ │ │ ├── ImportSettings.h -│ │ │ ├── ResourceCooker.h -│ │ │ └── Bundle.h -│ │ ├── Physics/ # 物理系统 -│ │ │ ├── PhysicsWorld.h -│ │ │ ├── RigidBody.h -│ │ │ ├── Collider.h -│ │ │ ├── Joint.h -│ │ │ ├── CharacterController.h -│ │ │ ├── Trigger.h -│ │ │ ├── Raycast.h -│ │ │ ├── ForceField.h -│ │ │ └── PhysicsMaterial.h -│ │ ├── Audio/ # 音频系统 -│ │ │ ├── AudioEngine.h -│ │ │ ├── AudioSource.h -│ │ │ ├── AudioListener.h -│ │ │ ├── AudioClip.h -│ │ │ ├── AudioMixer.h -│ │ │ ├── AudioEffect.h -│ │ │ └── AudioBanks.h -│ │ ├── Scripting/ # 脚本系统 -│ │ │ ├── ScriptEngine.h -│ │ │ ├── ScriptClass.h -│ │ │ ├── ScriptObject.h -│ │ │ ├── ScriptMethod.h -│ │ │ ├── MonoBridge.h -│ │ │ ├── LuaBridge.h -│ │ │ └── AngelScript.h -│ │ ├── Animation/ # 动画系统 -│ │ │ ├── Animator.h -│ │ │ ├── AnimationClip.h -│ │ │ ├── AnimationCurve.h -│ │ │ ├── AnimationState.h -│ │ │ ├── AnimationBlendTree.h -│ │ │ ├── Skeleton.h -│ │ │ ├── Bone.h -│ │ │ ├── Skinning.h -│ │ │ └── BlendShape.h -│ │ ├── Particles/ # 粒子系统 -│ │ │ ├── ParticleSystem.h -│ │ │ ├── ParticleEmitter.h -│ │ │ ├── ParticleRenderer.h -│ │ │ ├── ParticleForce.h -│ │ │ ├── GPUParticles.h -│ │ │ └── VFXGraph.h -│ │ ├── Networking/ # 网络系统 -│ │ │ ├── NetworkManager.h -│ │ │ ├── NetworkClient.h -│ │ │ ├── NetworkServer.h -│ │ │ ├── NetworkPeer.h -│ │ │ ├── Packet.h -│ │ │ ├── Serializer.h -│ │ │ ├── RPC.h -│ │ │ ├── NetworkTransform.h -│ │ │ └── NetcodeProtocol.h -│ │ ├── UI/ # UI系统 -│ │ │ ├── Canvas.h -│ │ │ ├── Element.h -│ │ │ ├── Image.h -│ │ │ ├── Text.h -│ │ │ ├── Button.h -│ │ │ ├── ScrollView.h -│ │ │ ├── LayoutGroup.h -│ │ │ ├── UIRenderer.h -│ │ │ └── Font.h -│ │ ├── Navigation/ # 导航系统 -│ │ │ ├── NavMesh.h -│ │ │ ├── NavMeshAgent.h -│ │ │ ├── NavMeshQuery.h -│ │ │ ├── Obstacle.h -│ │ │ └── CrowdSystem.h -│ │ ├── Platforms/ # 平台层 -│ │ │ ├── Platform.h -│ │ │ ├── Windows.h -│ │ │ ├── Linux.h -│ │ │ ├── MacOS.h -│ │ │ ├── Android.h -│ │ │ ├── IOS.h -│ │ │ └── Web.h -│ │ └── XCGameEngine.h # 主头文件 -│ ├── src/ -│ │ ├── Core/ -│ │ ├── Math/ -│ │ ├── Components/ # 组件系统 -│ │ ├── Scene/ -│ │ ├── Renderer/ -│ │ ├── Resources/ -│ │ ├── Physics/ -│ │ ├── Audio/ -│ │ ├── Scripting/ -│ │ └── ... -│ └── third_party/ # 第三方库 -│ ├── stb/ -│ ├── json/ -│ ├── lua/ -│ ├── physx/ -│ └── etc. -│ -├── editor/ # 编辑器项目(你的UI) -│ ├── CMakeLists.txt -│ ├── src/ -│ │ ├── Application.h/cpp -│ │ ├── main.cpp -│ │ ├── Theme.h/cpp -│ │ ├── Core/ -│ │ │ ├── Event.h -│ │ │ ├── AssetItem.h -│ │ │ └── LogEntry.h -│ │ ├── Managers/ -│ │ │ ├── SelectionManager.h -│ │ │ ├── LogSystem.h/cpp -│ │ │ ├── ProjectManager.h/cpp -│ │ │ └── EditorSceneManager.h/cpp -│ │ ├── Panels/ -│ │ │ ├── Panel.h/cpp -│ │ │ ├── MenuBar.h/cpp -│ │ │ ├── HierarchyPanel.h/cpp -│ │ │ ├── InspectorPanel.h/cpp -│ │ │ ├── SceneViewPanel.h/cpp -│ │ │ ├── GameViewPanel.h/cpp -│ │ │ ├── ProjectPanel.h/cpp -│ │ │ ├── ConsolePanel.h/cpp -│ │ │ ├── AssetsBrowserPanel.h/cpp -│ │ │ ├── AnimationPanel.h/cpp -│ │ │ ├── ProfilerPanel.h/cpp -│ │ │ └── SettingsPanel.h/cpp -│ │ ├── Windows/ -│ │ │ ├── AssetImporter.h/cpp -│ │ │ ├── SceneHierarchyWindow.h/cpp -│ │ │ ├── ShaderEditor.h/cpp -│ │ │ ├── MaterialEditor.h/cpp -│ │ │ ├── AnimationEditor.h/cpp -│ │ │ └── PreferenceWindow.h/cpp -│ │ ├── Tools/ -│ │ │ ├── Gizmo.h/cpp -│ │ │ ├── SelectionTool.h/cpp -│ │ │ ├── MoveTool.h/cpp -│ │ │ ├── RotateTool.h/cpp -│ │ │ ├── ScaleTool.h/cpp -│ │ │ └── EditorCamera.h/cpp -│ │ ├── Editor/ -│ │ │ ├── EditorEngineLink.h/cpp -│ │ │ ├── EditorRenderView.h/cpp -│ │ │ ├── EditorModes.h -│ │ │ └── UndoRedo.h/cpp -│ │ └── ImGui/ -│ │ ├── ImGuiRenderer.h/cpp -│ │ ├── ImGuiTheme.h/cpp -│ │ └── ImGuiWidgets.h/cpp -│ └── assets/ -│ ├── fonts/ -│ ├── icons/ -│ └── themes/ -│ -├── runtime/ # 游戏运行时(独立可执行文件) -│ ├── CMakeLists.txt -│ ├── src/ -│ │ ├── GameMain.cpp -│ │ ├── RuntimeConfig.h -│ │ └── EntryPoint.cpp -│ └── resources/ -│ └── (烘焙后的场景和资源) -│ -├── tools/ # 工具链 -│ ├── AssetProcessor/ # 资源处理器 -│ ├── ShaderCompiler/ # 着色器编译器 -│ ├── SceneCooker/ # 场景烘焙工具 -│ ├── TextureConverter/ # 纹理转换工具 -│ ├── MeshOptimizer/ # 网格优化工具 -│ ├── AudioImporter/ # 音频导入工具 -│ ├── BuildTool/ # 构建打包工具 -│ └── ShaderDebugger/ # 着色器调试工具 -│ -├── content/ # 资源内容 -│ ├── Assets/ -│ │ ├── Scenes/ -│ │ ├── Materials/ -│ │ ├── Meshes/ -│ │ ├── Textures/ -│ │ ├── Audio/ -│ │ ├── Scripts/ -│ │ ├── Shaders/ -│ │ ├── Animations/ -│ │ ├── Prefabs/ -│ │ └── UI/ -│ ├── Packages/ # 包管理 -│ └── Library/ # 引擎库缓存 -│ ├── ShaderLibrary/ -│ ├── ShaderCache/ -│ ├── AssetDatabase/ -│ ├── SourceAssets/ -│ └── ImportCache/ -│ -└── docs/ # 文档 - ├── Architecture.md - ├── ComponentReference.md - ├── ShaderGuide.md - └── APIReference.md -``` - ---- - -## 第二章 核心层 (Core Layer) - -核心层提供引擎所有其他部分依赖的基础功能,是整个引擎的基石。 - -### 2.1 数学库 (Math Library) - -#### 2.1.1 设计目标 - -- **高性能**: SIMD 优化,支持 SSE/AVX/NEON -- **精确性**: 满足游戏精度需求,避免浮点误差 -- **完整性**: 覆盖游戏开发所需的所有数学类型 -- **可读性**: 简洁清晰的 API 设计 - -#### 2.1.2 核心类型 - -```cpp -namespace XCEngine { -namespace Math { - -// 向量类型 -struct Vector2 { float x, y; }; -struct Vector3 { float x, y, z; }; -struct Vector4 { float x, y, z, w; }; - -// 矩阵类型 -struct Matrix3x3; // 3D变换 -struct Matrix4x4; // 4D变换 -struct Matrix2x2; // 2D变换 - -// 四元数 - 避免欧拉角万向节锁 -struct Quaternion { - float x, y, z, w; - - // 旋转变换 - static Quaternion FromAxisAngle(const Vector3& axis, float radians); - static Quaternion FromEulerAngles(float pitch, float yaw, float roll); - static Quaternion FromRotationMatrix(const Matrix4x4& matrix); - static Quaternion Slerp(const Quaternion& a, const Quaternion& b, float t); - - Vector3 ToEulerAngles() const; - Matrix4x4 ToMatrix4x4() const; - Vector3 operator*(const Vector3& v) const; // 旋转向量 -}; - -// 变换 - 组合位置、旋转、缩放 -struct Transform { - Vector3 position = Vector3::Zero(); - Quaternion rotation = Quaternion::Identity(); - Vector3 scale = Vector3::One(); - - Matrix4x4 ToMatrix() const; - Transform Inverse() const; - Transform operator*(const Transform& other) const; // 组合变换 - Vector3 TransformPoint(const Vector3& point) const; - Vector3 TransformDirection(const Vector3& direction) const; -}; - -// 空间参照 -enum class Space { - Self, // 自身局部空间 - World // 世界空间 -}; - -// 颜色 -struct Color { float r, g, b, a; }; -struct LinearColor; // 线性空间颜色 -struct HDRColor; // HDR颜色 - -// 射线 - 用于射线检测和选择 -struct Ray { - Vector3 origin; - Vector3 direction; - - Vector3 GetPoint(float t) const; - bool Intersects(const Sphere& sphere, float& t) const; - bool Intersects(const Box& box, float& t) const; - bool Intersects(const Plane& plane, float& t) const; -}; - -// 包围体 -struct Sphere { Vector3 center; float radius; }; -struct Box { Vector3 center; Vector3 extents; Matrix4x4 transform; }; -struct Frustum; // 视锥体 -struct OBB; // 定向包围盒 -struct AABB; // 轴对齐包围盒 - -} // namespace Math -} // namespace XCEngine -``` - -#### 2.1.3 SIMD 优化示例 - -```cpp -// 使用 SIMD 进行向量运算 -struct alignas(16) Vector3_SIMD { - union { - __m128 simd; - struct { float x, y, z, w; }; - }; - - Vector3_SIMD operator+(const Vector3_SIMD& other) const { - Vector3_SIMD result; - result.simd = _mm_add_ps(simd, other.simd); - return result; - } - - float Dot(const Vector3_SIMD& other) const { - __m128 dot = _mm_mul_ps(simd, other.simd); - __m128 temp = _mm_movehl_ps(dot, dot); - dot = _mm_add_ss(dot, temp); - return _mm_cvtss_f32(dot); - } - - Vector3_SIMD Cross(const Vector3_SIMD& other) const { - Vector3_SIMD result; - __m128 a = _mm_shuffle_ps(simd, simd, _MM_SHUFFLE(3,0,2,1)); - __m128 b = _mm_shuffle_ps(other.simd, other.simd, _MM_SHUFFLE(3,1,0,2)); - result.simd = _mm_sub_ps(_mm_mul_ps(simd, a), _mm_mul_ps(other.simd, b)); - result.simd = _mm_shuffle_ps(result.simd, result.simd, _MM_SHUFFLE(3,0,2,1)); - return result; - } -}; -``` - -### 2.2 内存管理 (Memory Management) - -```cpp -namespace XCEngine { -namespace Memory { - -// 分配器基类 -class IAllocator { -public: - virtual ~IAllocator() = default; - - virtual void* Allocate(size_t size, size_t alignment = 0) = 0; - virtual void Free(void* ptr) = 0; - virtual void* Reallocate(void* ptr, size_t newSize) = 0; - - virtual size_t GetTotalAllocated() const = 0; - virtual size_t GetTotalFreed() const = 0; - virtual size_t GetPeakAllocated() const = 0; - virtual size_t GetAllocationCount() const = 0; - - virtual const char* GetName() const = 0; -}; - -// 线性分配器 - 快速分配和批量释放 -class LinearAllocator : public IAllocator { -public: - explicit LinearAllocator(size_t size, IAllocator* parent = nullptr); - ~LinearAllocator(); - - void* Allocate(size_t size, size_t alignment = 8) override; - void Free(void* ptr) override; // 只重置,不真正释放 - void Clear(); // 清空所有内存 - - void* GetMarker() const; - void SetMarker(void* marker); - -private: - byte* m_buffer = nullptr; - size_t m_capacity = 0; - size_t m_offset = 0; - IAllocator* m_parent = nullptr; -}; - -// 池分配器 - 高频小对象分配 -class PoolAllocator : public IAllocator { -public: - PoolAllocator(size_t blockSize, size_t poolSize, size_t alignment = 8); - ~PoolAllocator(); - - void* Allocate(size_t size, size_t alignment = 0) override; - void Free(void* ptr) override; - - bool Contains(void* ptr) const; - size_t GetBlockSize() const { return m_blockSize; } - size_t GetFreeBlockCount() const; - -private: - struct FreeNode { - FreeNode* next; - }; - - size_t m_blockSize = 0; - size_t m_alignment = 0; - void* m_memory = nullptr; - FreeNode* m_freeList = nullptr; - size_t m_totalBlocks = 0; - size_t m_freeBlocks = 0; -}; - -// 代理分配器 - 用于追踪和调试 -class ProxyAllocator : public IAllocator { -public: - ProxyAllocator(IAllocator* underlying, const char* name); - - void* Allocate(size_t size, size_t alignment = 0) override; - void Free(void* ptr) override; - void* Reallocate(void* ptr, size_t newSize) override; - - struct Stats { - size_t totalAllocated; - size_t totalFreed; - size_t peakAllocated; - size_t allocationCount; - size_t memoryOverhead; - }; - const Stats& GetStats() const; - -private: - IAllocator* m_underlying; - const char* m_name; - Stats m_stats; - Mutex m_mutex; -}; - -// 全局内存管理器 -class MemoryManager { -public: - static MemoryManager& Get(); - - void Initialize(); - void Shutdown(); - - IAllocator* GetSystemAllocator(); - IAllocator* GetPhysicsAllocator(); - IAllocator* GetRenderingAllocator(); - IAllocator* GetAudioAllocator(); - - std::unique_ptr CreateLinearAllocator(size_t size); - std::unique_ptr CreatePoolAllocator(size_t blockSize, size_t count); - std::unique_ptr CreateProxyAllocator(const char* name); - - void SetTrackAllocations(bool track); - void DumpMemoryLeaks(); - void GenerateMemoryReport(); -}; - -#define XE_ALLOC(allocator, size, ...) allocator->Allocate(size, ##__VA_ARGS__) -#define XE_FREE(allocator, ptr) allocator->Free(ptr) - -} -} -``` - -### 2.3 线程系统 (Threading) - -```cpp -namespace XCEngine { -namespace Threading { - -enum class TaskPriority : uint8_t { - Critical = 0, - High = 1, - Normal = 2, - Low = 3, - Idle = 4 -}; - -enum class TaskStatus : uint8_t { - Pending, - Scheduled, - Running, - Completed, - Failed, - Canceled -}; - -template -using Func = std::function; - -// 任务接口 -class ITask { -public: - virtual ~ITask() = default; - - virtual void Execute() = 0; - virtual void OnComplete() {} - virtual void OnCancel() {} - - TaskPriority GetPriority() const { return m_priority; } - TaskStatus GetStatus() const { return m_status; } - uint64 GetId() const { return m_id; } - -protected: - TaskPriority m_priority = TaskPriority::Normal; - TaskStatus m_status = TaskStatus::Pending; - uint64 m_id = 0; - std::atomic m_refCount{1}; -}; - -// Lambda 任务包装器 -template -class LambdaTask : public ITask { -public: - explicit LambdaTask(Func&& func, TaskPriority priority = TaskPriority::Normal) - : m_func(std::move(func)), m_priority(priority) {} - - void Execute() override { - m_func(); - } - -private: - Func m_func; -}; - -// 任务组 - 用于批量管理任务 -class TaskGroup { -public: - using Callback = std::function; - - TaskGroup(); - ~TaskGroup(); - - uint64 AddTask(std::unique_ptr task); - uint64 AddTask(Func&& func, TaskPriority priority = TaskPriority::Normal); - - void AddDependency(uint64 taskId, uint64 dependsOn); - void Wait(); - bool WaitFor(std::chrono::milliseconds timeout); - - void SetCompleteCallback(Callback&& callback); - bool IsComplete() const; - float GetProgress() const; -}; - -// 任务系统主类 -class TaskSystem { -public: - static TaskSystem& Get(); - - void Initialize(const TaskSystemConfig& config); - void Shutdown(); - - uint64 Submit(std::unique_ptr task); - uint64 Submit(Func&& func, TaskPriority priority = TaskPriority::Normal); - - TaskGroup* CreateTaskGroup(); - void DestroyTaskGroup(TaskGroup* group); - - void Wait(uint64 taskId); - uint32 GetWorkerThreadCount() const; - - void Update(); - - // 并行 For 循环 - template - void ParallelFor(int32 start, int32 end, Func&& func); - - void RunOnMainThread(Func&& func); -}; - -} -} -``` - -### 2.4 容器库 (Containers) - -```cpp -namespace XCEngine { -namespace Containers { - -// 动态数组 -template -class Array { -public: - using Iterator = T*; - using ConstIterator = const T*; - - Array() = default; - explicit Array(size_t capacity); - Array(size_t count, const T& value); - Array(std::initializer_list init); - ~Array(); - - Array(const Array& other); - Array(Array&& other) noexcept; - Array& operator=(const Array& other); - Array& operator=(Array&& other) noexcept; - - T& operator[](size_t index); - const T& operator[](size_t index) const; - - T* Data() { return m_data; } - const T* Data() const { return m_data; } - - size_t Size() const { return m_size; } - size_t Capacity() const { return m_capacity; } - - void Clear(); - void PushBack(const T& value); - void PushBack(T&& value); - template - T& EmplaceBack(Args&&... args); - void PopBack(); - -private: - T* m_data = nullptr; - size_t m_size = 0; - size_t m_capacity = 0; - IAllocator* m_allocator = nullptr; -}; - -// 字符串 -class String { -public: - String(); - String(const char* str); - String(const char* str, size_t len); - ~String(); - - String& operator+=(const String& other); - String& operator+=(const char* str); - - String Substring(size_t pos, size_t len = npos) const; - String Trim() const; - String ToLower() const; - String ToUpper() const; - - size_t Find(const char* str, size_t pos = 0) const; - bool StartsWith(const String& prefix) const; - bool EndsWith(const String& suffix) const; - - const char* CStr() const { return m_data; } - size_t Length() const { return m_length; } - -private: - char* m_data = nullptr; - size_t m_length = 0; - size_t m_capacity = 0; -}; - -// 哈希映射 -template -class HashMap { -public: - struct Pair { - Key first; - Value second; - }; - - HashMap() = default; - explicit HashMap(size_t bucketCount, IAllocator* allocator = nullptr); - - Value& operator[](const Key& key); - Value* Find(const Key& key); - const Value* Find(const Key& key) const; - bool Contains(const Key& key) const; - - bool Insert(const Key& key, const Value& value); - bool Erase(const Key& key); - void Clear(); - - size_t Size() const { return m_size; } - -private: - size_t GetBucketIndex(const Key& key) const; - void Resize(); - - struct Bucket { - Array pairs; - }; - Array m_buckets; - size_t m_bucketCount = 0; - size_t m_size = 0; - float m_loadFactor = 0.75f; - IAllocator* m_allocator = nullptr; -}; - -} // namespace Containers -} // namespace XCEngine -``` - -### 2.5 日志与调试系统 - -```cpp -namespace XCEngine { -namespace Core { - -// 事件系统 - 基础事件系统(线程安全版本) -template -class Event { -public: - using Callback = std::function; - using Listener = std::pair; - using Iterator = typename std::vector::iterator; - - uint64_t Subscribe(Callback callback) { - std::lock_guard lock(m_mutex); - uint64_t id = ++m_nextId; - m_listeners.emplace_back(id, std::move(callback)); - return id; - } - - void Unsubscribe(uint64_t id) { - std::lock_guard lock(m_mutex); - m_pendingUnsubscribes.push_back(id); - } - - void ProcessUnsubscribes() { - std::lock_guard lock(m_mutex); - for (uint64_t id : m_pendingUnsubscribes) { - m_listeners.erase( - std::remove_if(m_listeners.begin(), m_listeners.end(), - [id](const auto& pair) { return pair.first == id; }), - m_listeners.end() - ); - } - m_pendingUnsubscribes.clear(); - } - - void Invoke(Args... args) const { - // 注意:这里不加锁以避免回调中调用Unsubscribe导致的死锁 - // 实际使用时应在调用前确保没有并发的Unsubscribe - // 或者使用副本进行调用 - - if (!m_pendingUnsubscribes.empty()) { - std::vector listenersCopy; - { - std::lock_guard lock(m_mutex); - listenersCopy = m_listeners; - m_pendingUnsubscribes.clear(); - } - for (const auto& [id, callback] : listenersCopy) { - callback(args...); - } - } else { - for (const auto& [id, callback] : m_listeners) { - callback(args...); - } - } - } - - void Clear() { - std::lock_guard lock(m_mutex); - m_listeners.clear(); - } - - Iterator begin() { return m_listeners.begin(); } - Iterator end() { return m_listeners.end(); } - -private: - mutable std::mutex m_mutex; - std::vector m_listeners; - std::vector m_pendingUnsubscribes; - uint64_t m_nextId = 0; -}; - -// 基础类型定义 -// 注意:String 定义在 Containers 命名空间中,避免与 Core::String 冲突 -using int8 = int8_t; -using int16 = int16_t; -using int32 = int32_t; -using int64 = int64_t; -using uint8 = uint8_t; -using uint16 = uint16_t; -using uint32 = uint32_t; -using uint64 = uint64_t; -using byte = uint8_t; - -// 引用计数基类 -class RefCounted { -public: - RefCounted() : m_refCount(1) {} - virtual ~RefCounted() = default; - - void AddRef() { ++m_refCount; } - void Release() { - if (--m_refCount == 0) { - delete this; - } - } - - uint32_t GetRefCount() const { return m_refCount.load(); } - -protected: - std::atomic m_refCount; -}; - -// 智能指针别名 -template -using Ref = std::shared_ptr; - -template -using UniqueRef = std::unique_ptr; - -} // namespace Core -} - -namespace XCEngine { -namespace Debug { - -enum class LogLevel : uint8_t { - Verbose = 0, - Debug = 1, - Info = 2, - Warning = 3, - Error = 4, - Fatal = 5 -}; - -enum class LogCategory { - General, - Rendering, - Physics, - Audio, - Scripting, - Network, - Memory, - Threading, - FileSystem, - Custom -}; - -struct LogEntry { - LogLevel level; - LogCategory category; - String message; - String file; - int32 line; - String function; - uint64 timestamp; - uint32 threadId; -}; - -class ILogSink { -public: - virtual ~ILogSink() = default; - virtual void Log(const LogEntry& entry) = 0; - virtual void Flush() = 0; -}; - -class ConsoleLogSink : public ILogSink { -public: - void Log(const LogEntry& entry) override; - void Flush() override; - void SetColorOutput(bool enable); - void SetMinimumLevel(LogLevel level); -}; - -class FileLogSink : public ILogSink { -public: - explicit FileLogSink(const String& filePath); - ~FileLogSink(); - void Log(const LogEntry& entry) override; - void Flush() override; -private: - String m_filePath; - FileWriter m_writer; -}; - -class Logger { -public: - static Logger& Get(); - - void Initialize(); - void Shutdown(); - - void AddSink(std::unique_ptr sink); - void RemoveSink(ILogSink* sink); - - void Log(LogLevel level, LogCategory category, - const String& message, const char* file = nullptr, - int32 line = 0, const char* function = nullptr); - - void Verbose(LogCategory category, const String& message); - void Debug(LogCategory category, const String& message); - void Info(LogCategory category, const String& message); - void Warning(LogCategory category, const String& message); - void Error(LogCategory category, const String& message); - void Fatal(LogCategory category, const String& message); - - void SetMinimumLevel(LogLevel level); - void SetCategoryEnabled(LogCategory category, bool enabled); -}; - -#define XE_LOG(category, level, message) \ - XCEngine::Debug::Logger::Get().Log(level, category, message, __FILE__, __LINE__, __FUNCTION__) - -#define XE_ASSERT(condition, message) \ - if (!(condition)) { \ - XCEngine::Debug::Logger::Get().Fatal(XCEngine::Debug::LogCategory::General, message); \ - __debugbreak(); \ - } - -// 性能分析器 -class Profiler { -public: - static Profiler& Get(); - - void Initialize(); - void Shutdown(); - - void BeginProfile(const char* name); - void EndProfile(); - - void BeginFrame(); - void EndFrame(); - - void MarkEvent(const char* name, uint64_t timestamp, uint32_t threadId); - void SetMarker(const char* name, uint32_t color); - - void ExportChromeTracing(const String& filePath); -}; - -#define XE_PROFILE_BEGIN(name) XCEngine::Debug::Profiler::Get().BeginProfile(name) -#define XE_PROFILE_END() XCEngine::Debug::Profiler::Get().EndProfile() -#define XE_PROFILE_FUNCTION() XE_PROFILE_BEGIN(__FUNCTION__) - -} -} -``` - ---- - -## 第三章 组件系统 (GameObject + Component) - -> 本架构参考**传统Unity的GameObject + Component模式**。 -> - GameObject是实体容器,包含Transform等内置组件 -> - Component是挂载在GameObject上的class(引用类型),包含数据和行为 -> - Component有完整的生命周期(Awake, Start, Update, OnDestroy等) -> - Scene直接管理GameObject列表,没有World中间层 - -### 3.1 组件系统架构 - -``` -┌─────────────────────────────────────────────────────────────────────────────┐ -│ GameObject + Component Architecture │ -├─────────────────────────────────────────────────────────────────────────────┤ -│ │ -│ ┌───────────────────────────────────────────────────────────────────────┐ │ -│ │ Scene (场景) │ │ -│ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ -│ │ │ Scene │ │ -│ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ -│ │ │ │ GameObject List │ │ │ -│ │ │ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │ │ -│ │ │ │ │ GameObject │ │ GameObject │ │ GameObject │ │ │ │ -│ │ │ │ │ - name │ │ - name │ │ - name │ │ │ │ -│ │ │ │ │ - transform│ │ - transform│ │ - transform│ │ │ │ -│ │ │ │ │ - components│ │ - components│ │ - components│ │ │ │ -│ │ │ │ │ └ Renderer│ │ └ Camera │ │ └ Script │ │ │ │ -│ │ │ │ └────────────┘ └────────────┘ └────────────┘ │ │ │ -│ │ │ └─────────────────────────────────────────────────────────┘ │ │ -│ └───────────────────────────────────────────────────────────────────────┘ │ -│ │ -└─────────────────────────────────────────────────────────────────────────────┘ -``` - -### 3.2 游戏对象 (GameObject) - -```cpp -namespace XCEngine { - -class Scene; -class Transform; -class Component; - -// GameObject: 游戏对象(类Unity GameObject) -// 传统Unity风格的GameObject,包含内置组件和用户组件 -// 生命周期由 Scene 的 unique_ptr 管理,不需要引用计数 -class GameObject { -public: - GameObject(); - ~GameObject(); - - // 内部构造 - 用于Scene创建GameObject时设置初始属性 - struct ConstructParams { - String name = "GameObject"; - GameObject* parent = nullptr; - bool active = true; - }; - - // 工厂方法(替代公有构造函数) - static GameObject* Create(const ConstructParams& params = {}); - static void Destroy(GameObject* obj); - - String name; // 对象名称 - bool active = true; // 激活状态 - - TransformComponent& GetTransform() { return m_transform; } - const TransformComponent& GetTransform() const { return m_transform; } - - Scene* GetScene() const { return m_scene; } - - // 组件管理 - template - T* AddComponent(); - - template - void RemoveComponent(); - - template - T* GetComponent() const; - - template - std::vector GetComponents() const; - - template - T* GetComponentInChildren() const; - - template - std::vector GetComponentsInChildren() const; - - template - T* GetComponentInParent() const; - - // 层级管理 - GameObject* GetParent() const; - void SetParent(GameObject* parent); - void SetParent(GameObject* parent, bool worldPositionStays); - const std::vector& GetChildren() const; - GameObject* GetChild(int index) const; - int GetChildCount() const; - int GetSiblingIndex() const; - void SetSiblingIndex(int index); - - // 激活状态 - void SetActive(bool active); - bool IsActive() const; - bool IsActiveInHierarchy() const; - - // 场景查找(静态方法,内部使用SceneManager::GetActiveScene) - static GameObject* Find(const String& name); - static std::vector FindObjectsOfType(); - static std::vector FindGameObjectsWithTag(const String& tag); - - // 生命周期 - void Destroy(); - -private: - void AddComponentInternal(Component* component); - void RemoveComponentInternal(Component* component); - - std::vector> m_components; - - // 组件类型索引 - 用于O(1)查询(类型ID -> 组件索引) - // 每个 AddComponent/RemoveComponent 时更新 - std::unordered_map m_componentTypeIndex; - - std::vector m_children; - GameObject* m_parent = nullptr; - Scene* m_scene = nullptr; - - // TransformComponent是GameObject的内置组件 - // 注意:TransformComponent继承自Component,需要在构造后设置m_gameObject - TransformComponent m_transform; - - void Initialize(const ConstructParams& params); - - friend class Scene; - friend class TransformComponent; -}; - -// GameObject实现 -inline GameObject::GameObject() { - // m_transform需要在构造后初始化m_gameObject -} - -inline GameObject::~GameObject() { - // 销毁所有子对象 - for (auto* child : m_children) { - delete child; - } -} - -inline GameObject* GameObject::Create(const ConstructParams& params) { - GameObject* obj = new GameObject(); - obj->Initialize(params); - return obj; -} - -inline void GameObject::Destroy(GameObject* obj) { - delete obj; -} - -inline void GameObject::Initialize(const ConstructParams& params) { - name = params.name; - active = params.active; - - // 设置TransformComponent的m_gameObject指针 - m_transform.m_gameObject = this; - - // 设置父子关系 - if (params.parent) { - params.parent->m_children.push_back(this); - m_parent = params.parent; - } -} - -inline void GameObject::Destroy() { - if (m_scene) { - m_scene->DestroyGameObject(this); - } else { - delete this; - } -} - -// 静态方法实现 -inline GameObject* GameObject::Find(const String& name) { - return SceneManager::Get().GetActiveScene()->Find(name); -} - -inline std::vector GameObject::FindObjectsOfType() { - return SceneManager::Get().GetActiveScene()->FindObjectsOfType(); -} - -inline std::vector GameObject::FindGameObjectsWithTag(const String& tag) { - return SceneManager::Get().GetActiveScene()->FindGameObjectsWithTag(tag); -} - -// GameObject创建辅助类 -class GameObjectBuilder { -public: - explicit GameObjectBuilder(const String& name = "GameObject"); - ~GameObjectBuilder() = default; - - template - GameObjectBuilder& AddComponent(Args&&... args); - - GameObject* Build(); - -private: - String m_name; - std::vector> m_components; -}; - -} -``` - -### 3.3 组件 (Component) - -```cpp -namespace XCEngine { - -class GameObject; - -// 组件基类(类Unity MonoBehaviour) -// 包含完整的生命周期方法,用户组件继承此类 -// 注意:组件由GameObject拥有和管理生命周期,不需要引用计数 -class Component { -public: - Component(); - virtual ~Component(); - - // 所属GameObject - GameObject* gameObject() const { return m_gameObject; } - TransformComponent& transform() const { return m_gameObject->GetTransform(); } - - // 激活状态 - bool IsEnabled() const { return m_enabled; } - void SetEnabled(bool enabled) { m_enabled = enabled; } - - // 场景引用 - Scene* GetScene() const; - - // 组件查找 - template - T* GetComponent() const { return m_gameObject->GetComponent(); } - - template - std::vector GetComponents() const { return m_gameObject->GetComponents(); } - - // ======================================== - // 生命周期(类Unity MonoBehaviour) - // ======================================== - - // 创建时调用(即使组件被禁用也会调用) - virtual void Awake() {} - - // 首次启用时调用(当对象和组件都被启用时) - virtual void Start() {} - - // 每帧调用(当组件启用时) - virtual void Update(float deltaTime) {} - - // 物理更新(固定时间间隔) - virtual void FixedUpdate() {} - - // 在Update之后每帧调用 - virtual void LateUpdate(float deltaTime) {} - - // 销毁时调用 - virtual void OnDestroy() {} - - // 当组件被启用时调用 - virtual void OnEnable() {} - - // 当组件被禁用时调用 - virtual void OnDisable() {} - - // ======================================== - // 物理回调(类Unity) - // ======================================== - - virtual void OnCollisionEnter(const Collision& collision) {} - virtual void OnCollisionStay(const Collision& collision) {} - virtual void OnCollisionExit(const Collision& collision) {} - - virtual void OnTriggerEnter(Collider* other) {} - virtual void OnTriggerStay(Collider* other) {} - virtual void OnTriggerExit(Collider* other) {} - - virtual void OnMouseDown() {} - virtual void OnMouseDrag() {} - virtual void OnMouseEnter() {} - virtual void OnMouseExit() {} - virtual void OnMouseOver() {} - virtual void OnMouseUp() {} - virtual void OnMouseUpAsButton() {} - - virtual void OnTriggerEnter2D(Collider2D* other) {} - virtual void OnTriggerExit2D(Collider2D* other) {} - virtual void OnCollisionEnter2D(Collision2D* collision) {} - virtual void OnCollisionExit2D(Collision2D* collision) {} - - // ======================================== - // 消息发送 - // ======================================== - - // 发送消息到同GameObject上的其他组件 - void SendMessage(const String& methodName, const void* value = nullptr); - void SendMessageUpwards(const String& methodName, const void* value = nullptr); - void BroadcastMessage(const String& methodName, const void* value = nullptr); - -protected: - GameObject* m_gameObject = nullptr; - bool m_enabled = true; - - friend class GameObject; -}; - -// 组件类型注册(用于运行时类型识别) -class ComponentTypeRegistry { -public: - static ComponentTypeRegistry& Get(); - - template - static uint32_t GetTypeId() { - static uint32_t id = Get().Register(typeid(T).name(), static_cast(-1)); - return id; - } - - template - static uint32_t GetTypeId(const char* typeName) { - static uint32_t id = Get().Register(typeName, static_cast(-1)); - return id; - } - - static uint32_t GetTypeIdFromName(const char* typeName) { - return Get().GetIdByName(typeName); - } - - static const char* GetTypeName(uint32_t typeId) { - return Get().GetNameById(typeId); - } - -private: - uint32_t Register(const char* typeName, uint32_t suggestedId); - uint32_t GetIdByName(const char* typeName) const; - const char* GetNameById(uint32_t typeId) const; - - std::atomic m_nextTypeId{0}; - std::unordered_map m_idToName; - std::unordered_map m_nameToId; - Mutex m_mutex; -}; - -// ============================================================================ -// ============================================================================ -// TransformComponent - GameObject的内置组件(类Unity Transform) -// 继承自 Component,包含父子层级关系 -// ============================================================================ - -class TransformComponent : public Component { -public: - // 局部变换 - Vector3 GetLocalPosition() const { return m_localPosition; } - void SetLocalPosition(const Vector3& position) { m_localPosition = position; SetDirty(); } - - Quaternion GetLocalRotation() const { return m_localRotation; } - void SetLocalRotation(const Quaternion& rotation) { m_localRotation = rotation; SetDirty(); } - - Vector3 GetLocalScale() const { return m_localScale; } - void SetLocalScale(const Vector3& scale) { m_localScale = scale; SetDirty(); } - - // 世界变换(通过计算得出) - Vector3 GetPosition() const; - void SetPosition(const Vector3& position); - - Quaternion GetRotation() const; - void SetRotation(const Quaternion& rotation); - - Vector3 GetScale() const; - void SetScale(const Vector3& scale); - - // 方向向量 - Vector3 GetForward() const { return GetRotation() * Vector3::Forward(); } - Vector3 GetRight() const { return GetRotation() * Vector3::Right(); } - Vector3 GetUp() const { return GetRotation() * Vector3::Up(); } - - // 矩阵 - const Matrix4x4& GetLocalToWorldMatrix() const; - Matrix4x4 GetWorldToLocalMatrix() const; - - // 层级操作 - TransformComponent* GetParent() const { return m_parent; } - void SetParent(TransformComponent* parent, bool worldPositionStays = true); - - int GetChildCount() const { return static_cast(m_children.size()); } - TransformComponent* GetChild(int index) const; - TransformComponent* Find(const String& name) const; - - void DetachChildren(); - void SetAsFirstSibling(); - void SetAsLastSibling(); - void SetSiblingIndex(int index); - int GetSiblingIndex() const; - - // 变换操作 - void LookAt(const Vector3& target); - void LookAt(const Vector3& target, const Vector3& up); - void Rotate(const Vector3& eulers); - void Rotate(const Vector3& axis, float angle); - void Translate(const Vector3& translation); - void Translate(const Vector3& translation, Math::Space relativeTo); - - // 点变换 - Vector3 TransformPoint(const Vector3& point) const; - Vector3 InverseTransformPoint(const Vector3& point) const; - Vector3 TransformDirection(const Vector3& direction) const; - Vector3 InverseTransformDirection(const Vector3& direction) const; - - // 设为脏(需要重新计算世界变换) - void SetDirty() { m_dirty = true; } - -private: - Vector3 m_localPosition = Vector3::Zero(); - Quaternion m_localRotation = Quaternion::Identity(); - Vector3 m_localScale = Vector3::One(); - - TransformComponent* m_parent = nullptr; - std::vector m_children; - - mutable Matrix4x4 m_localToWorldMatrix; - mutable Matrix4x4 m_worldToLocalMatrix; - mutable Vector3 m_worldPosition; - mutable Quaternion m_worldRotation; - mutable Vector3 m_worldScale; - mutable bool m_dirty = true; - - void UpdateWorldTransform() const; - - friend class GameObject; -}; - -// 渲染组件(继承Component) -class RenderMeshComponent : public Component { -public: - void Awake() override; - void Start() override; - void Update(float deltaTime) override; - - ResourceGUID GetMesh() const { return m_meshGuid; } - void SetMesh(const ResourceGUID& guid); - - ResourceGUID GetMaterial() const { return m_materialGuid; } - void SetMaterial(const ResourceGUID& guid); - - bool GetCastShadows() const { return m_castShadows; } - void SetCastShadows(bool cast) { m_castShadows = cast; } - - bool GetReceiveShadows() const { return m_receiveShadows; } - void SetReceiveShadows(bool receive) { m_receiveShadows = receive; } - -private: - ResourceGUID m_meshGuid; - ResourceGUID m_materialGuid; - bool m_castShadows = true; - bool m_receiveShadows = true; -}; - -// 光源组件 - 继承Component(类Unity Light) -class RenderLightComponent : public Component { -public: - void Awake() override; - void Update(float deltaTime) override; - - enum class LightType : uint8_t { - Directional, - Point, - Spot, - Area - }; - - void SetLightType(LightType type) { m_type = type; } - LightType GetLightType() const { return m_type; } - - void SetColor(const Vector3& color) { m_color = color; } - Vector3 GetColor() const { return m_color; } - - void SetIntensity(float intensity) { m_intensity = intensity; } - float GetIntensity() const { return m_intensity; } - - void SetRange(float range) { m_range = range; } - float GetRange() const { return m_range; } - - void SetSpotAngle(float angle) { m_spotAngle = angle; } - float GetSpotAngle() const { return m_spotAngle; } - - void SetCastShadows(bool cast) { m_castShadows = cast; } - bool GetCastShadows() const { return m_castShadows; } - - void SetShadowResolution(uint32_t resolution) { m_shadowResolution = resolution; } - uint32_t GetShadowResolution() const { return m_shadowResolution; } - - void SetCullingMask(int32_t mask) { m_cullingMask = mask; } - int32_t GetCullingMask() const { return m_cullingMask; } - -private: - LightType m_type = LightType::Point; - Vector3 m_color = Vector3::One(); - float m_intensity = 1.0f; - float m_range = 10.0f; - float m_spotAngle = 30.0f; - float m_penumbraAngle = 5.0f; - bool m_castShadows = true; - uint32_t m_shadowResolution = 1024; - float m_shadowBias = 0.005f; - float m_normalOffsetBias = 0.001f; - float m_nearPlane = 0.1f; - int32_t m_cullingMask = -1; - float m_intensityVariation = 0.0f; - ResourceGUID m_cookieTextureGuid = ResourceGUID::Invalid; - float m_cookieSize = 5.0f; -}; - -// 相机组件 - 继承Component(类Unity Camera) -class CameraComponent : public Component { -public: - void Awake() override; - void Start() override; - void Update(float deltaTime) override; - - enum class ProjectionType : uint8_t { - Perspective, - Orthographic - }; - - void SetProjectionType(ProjectionType type) { m_projectionType = type; } - ProjectionType GetProjectionType() const { return m_projectionType; } - - void SetFieldOfView(float fov) { m_fieldOfView = fov; } - float GetFieldOfView() const { return m_fieldOfView; } - - void SetOrthographicSize(float size) { m_orthographicSize = size; } - float GetOrthographicSize() const { return m_orthographicSize; } - - void SetNearClipPlane(float near) { m_nearPlane = near; } - float GetNearClipPlane() const { return m_nearPlane; } - - void SetFarClipPlane(float far) { m_farPlane = far; } - float GetFarClipPlane() const { return m_farPlane; } - - void SetViewport(const Rect& rect) { m_viewportRect = rect; } - const Rect& GetViewport() const { return m_viewportRect; } - - void SetCullingMask(int32_t mask) { m_cullingMask = mask; } - int32_t GetCullingMask() const { return m_cullingMask; } - - void SetDepth(float depth) { m_depth = depth; } - float GetDepth() const { return m_depth; } - - Matrix4x4 GetViewMatrix() const; - Matrix4x4 GetProjectionMatrix() const; - Matrix4x4 GetViewProjectionMatrix() const; - - Vector3 ScreenToWorldPoint(const Vector3& screenPoint) const; - Vector3 WorldToScreenPoint(const Vector3& worldPoint) const; - Ray ScreenPointToRay(const Vector2& screenPoint) const; - - Frustum& GetFrustum() { return m_frustum; } - -private: - ProjectionType m_projectionType = ProjectionType::Perspective; - float m_fieldOfView = 60.0f; - float m_orthographicSize = 5.0f; - float m_nearPlane = 0.1f; - float m_farPlane = 1000.0f; - float m_aspectRatio = 16.0f / 9.0f; - Rect m_viewportRect = Rect(0, 0, 1, 1); - float m_depth = 0.0f; - int32_t m_cullingMask = -1; - Frustum m_frustum; -}; - -// 物理组件 - 继承Component(类Unity Rigidbody) -class RigidBodyComponent : public Component { -public: - void Awake() override; - void Start() override; - void FixedUpdate() override; - void OnEnable() override; - void OnDisable() override; - void OnDestroy() override; - - // 物理回调(类Unity) - void OnCollisionEnter(const Collision& collision) override; - void OnCollisionStay(const Collision& collision) override; - void OnCollisionExit(const Collision& collision) override; - void OnTriggerEnter(Collider* other) override; - void OnTriggerStay(Collider* other) override; - void OnTriggerExit(Collider* other) override; - - enum class BodyType : uint8_t { - Dynamic, - Kinematic, - Static - }; - - void SetBodyType(BodyType type) { m_bodyType = type; } - BodyType GetBodyType() const { return m_bodyType; } - - void SetMass(float mass) { m_mass = mass; } - float GetMass() const { return m_mass; } - - void SetLinearDamping(float damping) { m_linearDamping = damping; } - float GetLinearDamping() const { return m_linearDamping; } - - void SetAngularDamping(float damping) { m_angularDamping = damping; } - float GetAngularDamping() const { return m_angularDamping; } - - void AddForce(const Vector3& force, ForceMode mode = ForceMode::Force); - void AddForceAtPosition(const Vector3& force, const Vector3& position, ForceMode mode = ForceMode::Force); - void AddTorque(const Vector3& torque, ForceMode mode = ForceMode::Force); - - void SetVelocity(const Vector3& velocity); - Vector3 GetVelocity() const; - - void SetAngularVelocity(const Vector3& velocity); - Vector3 GetAngularVelocity() const; - - void SetUseGravity(bool use) { m_useGravity = use; } - bool IsUsingGravity() const { return m_useGravity; } - - void SetIsKinematic(bool kinematic); - bool IsKinematic() const; - - enum class InterpolateMode : uint8_t { - None, - Interpolate, - Extrapolate - }; - - void SetInterpolate(InterpolateMode mode); - InterpolateMode GetInterpolate() const { return m_interpolateMode; } - - // 获取物理引擎内部句柄(用于 PhysicsWorld 调用,避免 GameObject* 悬空指针) - void* GetPhysicsHandle() const { return m_physicsHandle; } - void SetPhysicsHandle(void* handle) { m_physicsHandle = handle; } - -private: - BodyType m_bodyType = BodyType::Dynamic; - float m_mass = 1.0f; - float m_linearDamping = 0.0f; - float m_angularDamping = 0.05f; - Vector3 m_velocity; - Vector3 m_angularVelocity; - bool m_useGravity = true; - InterpolateMode m_interpolateMode = InterpolateMode::None; - - // 物理引擎内部句柄(由 PhysicsWorld 设置) - void* m_physicsHandle = nullptr; -}; - -// 碰撞组件 - 继承Component(类Unity Collider) -class ColliderComponent : public Component { -public: - void Awake() override; - void Start() override; - - enum class ShapeType : uint8_t { - Box, - Sphere, - Capsule, - Mesh, - ConvexHull - }; - - void SetShapeType(ShapeType type) { m_shapeType = type; } - ShapeType GetShapeType() const { return m_shapeType; } - - void SetCenter(const Vector3& center) { m_center = center; } - Vector3 GetCenter() const { return m_center; } - - void SetSize(const Vector3& size) { m_boxSize = size; } - Vector3 GetSize() const { return m_boxSize; } - - void SetRadius(float radius) { m_sphereRadius = radius; } - float GetRadius() const { return m_sphereRadius; } - - void SetIsTrigger(bool trigger) { m_isTrigger = trigger; } - bool IsTrigger() const { return m_isTrigger; } - - void SetMaterial(const ResourceGUID& guid); - ResourceGUID GetMaterial() const { return m_materialGuid; } - -private: - ShapeType m_shapeType = ShapeType::Box; - Vector3 m_boxSize = Vector3::One(); - float m_sphereRadius = 0.5f; - float m_capsuleRadius = 0.5f; - float m_capsuleHeight = 2.0f; - - Vector3 m_center = Vector3::Zero(); - Quaternion m_rotation = Quaternion::Identity(); - - int32_t m_layer = 0; - int32_t m_mask = -1; - - float m_friction = 0.5f; - float m_restitution = 0.0f; - - bool m_isTrigger = false; - bool m_convex = true; - - ResourceGUID m_materialGuid = ResourceGUID::Invalid; - ResourceGUID m_meshGuid = ResourceGUID::Invalid; - - // 物理引擎内部句柄(由 PhysicsWorld 设置) - void* m_physicsHandle = nullptr; -}; - -// 关节组件 - 继承Component(类Unity Joint) -class JointComponent : public Component { -public: - void Awake() override; - void OnDestroy() override; - - enum class Type : uint8_t { - Hinge, // 铰链关节 - Slider, // 滑块关节 - Spring, // 弹簧关节 - Fixed, // 固定关节 - Configurable, // 可配置关节 - Character // 角色关节 - }; - - enum class Axis { - X, Y, Z - }; - - void SetJointType(Type type) { m_jointType = type; } - Type GetJointType() const { return m_jointType; } - - void SetConnectedBody(GameObject* body) { m_connectedBody = body; } - GameObject* GetConnectedBody() const { return m_connectedBody; } - - void SetAnchor(const Vector3& anchor) { m_anchor = anchor; } - Vector3 GetAnchor() const { return m_anchor; } - - void SetAxis(const Vector3& axis) { m_axis = axis; } - Vector3 GetAxis() const { return m_axis; } - - void SetBreakForce(float force) { m_breakForce = force; } - float GetBreakForce() const { return m_breakForce; } - - void SetBreakTorque(float torque) { m_breakTorque = torque; } - float GetBreakTorque() const { return m_breakTorque; } - - void SetEnableCollision(bool enable) { m_enableCollision = enable; } - bool GetEnableCollision() const { return m_enableCollision; } - - bool IsBroken() const { return m_broken; } - -private: - Type m_jointType = Type::Fixed; - GameObject* m_connectedBody = nullptr; - - Vector3 m_anchor = Vector3::Zero(); - Vector3 m_axis = Vector3::Up(); - Vector3 m_secondaryAxis = Vector3::Right(); - - float m_breakForce = INFINITY; - float m_breakTorque = INFINITY; - bool m_enableCollision = false; - bool m_broken = false; - - void* m_physicsJoint = nullptr; -}; - -// 音频源组件(继承Component) -class AudioSourceComponent : public Component { -public: - void Awake() override; - void Start() override; - void Update(float deltaTime) override; - void OnEnable() override; - void OnDisable() override; - void OnDestroy() override; - - void Play(); - void Stop(); - void Pause(); - - bool IsPlaying() const; - bool IsPaused() const; - - void SetClip(const ResourceGUID& guid); - ResourceGUID GetClip() const { return m_clipGuid; } - - void SetLoop(bool loop) { m_loop = loop; } - bool GetLoop() const { return m_loop; } - - void SetVolume(float volume) { m_volume = volume; } - float GetVolume() const { return m_volume; } - - void SetPitch(float pitch) { m_pitch = pitch; } - float GetPitch() const { return m_pitch; } - - void SetSpatialBlend(float blend) { m_spatialBlend = blend; } - float GetSpatialBlend() const { return m_spatialBlend; } - -private: - ResourceGUID m_clipGuid; - bool m_playOnAwake = false; - bool m_loop = false; - bool m_isPlaying = false; - bool m_isPaused = false; - float m_volume = 1.0f; - float m_pitch = 1.0f; - float m_spatialBlend = 1.0f; - float m_minDistance = 1.0f; - float m_maxDistance = 500.0f; -}; - -// 音频监听器组件(继承Component) -class AudioListenerComponent : public Component { -public: - void Awake() override; - - void SetDopplerFactor(float factor) { m_dopplerFactor = factor; } - float GetDopplerFactor() const { return m_dopplerFactor; } - - void SetMasterVolume(float volume) { m_masterVolume = volume; } - float GetMasterVolume() const { return m_masterVolume; } - -private: - float m_dopplerFactor = 1.0f; - float m_masterVolume = 1.0f; -}; - -} // namespace XCEngine -``` - -### 3.5 场景 (Scene) - -> 说明:传统Unity的Scene直接管理GameObject列表,没有World中间层。 - -```cpp -namespace XCEngine { - -// 场景(类Unity Scene) -// 直接管理GameObject列表,不需要World -class Scene { -public: - Scene(); - ~Scene(); - - // 场景名称 - const String& GetName() const { return m_name; } - void SetName(const String& name) { m_name = name; } - - // GameObject管理(类Unity Scene API) - GameObject* CreateGameObject(const String& name = "GameObject") { - GameObject::ConstructParams params; - params.name = name; - params.parent = nullptr; - params.active = true; - - GameObject* obj = GameObject::Create(params); - obj->m_scene = this; - AddGameObject(obj); - return obj; - } - - GameObject* CreateGameObject(const String& name, GameObject* parent) { - GameObject::ConstructParams params; - params.name = name; - params.parent = parent; - params.active = true; - - GameObject* obj = GameObject::Create(params); - obj->m_scene = this; - AddGameObject(obj); - return obj; - } - - void DestroyGameObject(GameObject* obj); - - // 根GameObject(没有父对象的顶层GameObject,从所有GameObject中筛选) - // 注意:此方法为O(n)复杂度,与Unity的Scene.GetRootGameObjects行为一致 - // Unity也不缓存根对象,因为GameObject层级变化频繁,缓存维护成本可能更高 - std::vector GetRootGameObjects() const { - std::vector roots; - roots.reserve(m_gameObjects.size() / 2); // 预分配合理容量 - for (auto& obj : m_gameObjects) { - if (obj->GetParent() == nullptr) { - roots.push_back(obj.get()); - } - } - return roots; - } - - // 查找 - GameObject* Find(const String& name) const; - GameObject* FindGameObjectWithTag(const String& tag) const; - std::vector FindGameObjectsWithTag(const String& tag) const; - template - std::vector FindObjectsOfType() const; - T* FindObjectOfType() const; - - // 激活状态 - bool IsActive() const { return m_isActive; } - void SetActive(bool active); - - // 生命周期 - void Load(const String& filePath); - void LoadAsync(const String& filePath, std::function callback); - void Save(const String& filePath); - - // 更新(内部调用) - void Update(float deltaTime); - void FixedUpdate(float fixedDeltaTime); - void LateUpdate(float deltaTime); - - // 调试 - void DebugDraw(); - int GetObjectCount() const { return m_gameObjects.size(); } - -private: - void AddGameObject(GameObject* obj); - void RemoveGameObject(GameObject* obj); - - String m_name; - std::vector> m_gameObjects; - bool m_isActive = true; - - friend class GameObject; - friend class SceneManager; -}; - -// 场景管理器 -class SceneManager { -public: - static SceneManager& Get(); - - void Initialize(); - void Shutdown(); - - // 场景操作 - Scene* CreateScene(const String& name); - void LoadScene(const String& filePath); - void LoadSceneAsync(const String& filePath, std::function callback); - void UnloadScene(Scene* scene); - void UnloadScene(const String& sceneName); - - // 活动场景 - void SetActiveScene(Scene* scene); - void SetActiveScene(const String& sceneName); - Scene* GetActiveScene() const; - - // 查找 - Scene* GetScene(const String& name) const; - std::vector GetAllScenes() const; - - // 更新 - void Update(float deltaTime); - void FixedUpdate(float fixedDeltaTime); - void LateUpdate(float deltaTime); - - // 事件 - Event OnSceneLoaded; - Event OnSceneUnloaded; - Event OnActiveSceneChanged; - -private: - SceneManager() = default; - - std::vector> m_scenes; - Scene* m_activeScene = nullptr; - Scene* m_loadingScene = nullptr; - - std::unordered_map m_sceneNameMap; - std::function m_loadCallback; - bool m_loading = false; -}; - -} // namespace XCEngine -``` - ---- - -## 第四章 渲染系统 (Renderer) - -> **架构说明**: -> - **RenderGraph** - 高级渲染管线抽象,定义 Pass 之间的依赖关系和资源流 -> - **RenderContext** - 低级渲染上下文,管理 GPU 资源和执行具体的渲染命令 -> - 关系:RenderGraph 是声明式的渲染描述,RenderContext 是执行层 - -### 4.1 渲染系统架构 - -``` -┌─────────────────────────────────────────────────────────────────────────────┐ -│ Rendering Pipeline │ -├─────────────────────────────────────────────────────────────────────────────┤ -│ │ -│ ┌───────────────────────────────────────────────────────────────────────┐ │ -│ │ Render Pipeline Manager │ │ -│ └───────────────────────────────────────────────────────────────────────┘ │ -│ │ │ -│ ┌────────────────────────────────▼───────────────────────────────────────┐ │ -│ │ Render Graph Builder │ │ -│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ -│ │ │ Pass 1 │ │ Pass 2 │ │ Pass 3 │ │ Pass N │ │ │ -│ │ │ G-Buffer │ │ Lighting │ │ Shadow │ │ Post │ │ │ -│ │ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │ │ -│ └───────────────────────────────────────────────────────────────────────┘ │ -│ │ │ -│ ┌────────────────────────────────▼───────────────────────────────────────┐ │ -│ │ Render Passes │ │ -│ │ │ │ -│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ -│ │ │ GBuffer │→ │ Shadow │→ │ Deferred │→ │ Forward │ │ │ -│ │ │ Pass │ │ Pass │ │ Lighting │ │ Pass │ │ │ -│ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │ -│ │ ↓ ↓ ↓ ↓ │ │ -│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ -│ │ │ Depth │ │ Shadow │ │ SSAO │ │ Alpha │ │ │ -│ │ │ Pre-Pass │ │ Update │ │ Pass │ │ Blend │ │ │ -│ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │ -│ │ ↓ │ │ -│ │ ┌────────────────────────────────────────────────────────────────┐ │ │ -│ │ │ Post-Processing │ │ │ -│ │ │ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │ │ │ -│ │ │ │ SSAO │ │ SSR │ │ Bloom │ │ Tone │ │ FXAA │ │ │ │ -│ │ │ └────────┘ └────────┘ └────────┘ └────────┘ └────────┘ │ │ │ -│ │ └────────────────────────────────────────────────────────────────┘ │ │ -│ └───────────────────────────────────────────────────────────────────────┘ │ -│ │ │ -│ ┌────────────────────────────────▼───────────────────────────────────────┐ │ -│ │ GPU Resources │ │ -│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ -│ │ │ Vertex │ │ Index │ │ Uniform │ │ Texture │ │ Render │ │ │ -│ │ │ Buffer │ │ Buffer │ │ Buffer │ │ Sampler │ │ Target │ │ │ -│ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │ -│ └───────────────────────────────────────────────────────────────────────┘ │ -│ │ -└─────────────────────────────────────────────────────────────────────────────┘ -``` - -### 4.2 渲染抽象层 - -```cpp -namespace XCEngine { -namespace Rendering { - -enum class ResourceState { - Undefined, - RenderTarget, - DepthStencil, - ShaderResource, - UnorderedAccess, - CopySrc, - CopyDst, - Present -}; - -struct ResourceDesc { - ResourceType type; - uint32_t width = 1; - uint32_t height = 1; - uint32_t depth = 1; - uint32_t mipLevels = 1; - uint32_t arraySize = 1; - Format format = Format::Unknown; - SampleCount sampleCount = SampleCount::Count1; - ResourceState initialState = ResourceState::Undefined; - bool cpuAccessible = false; - bool randomAccess = false; - - MemoryUsage memoryUsage = MemoryUsage::Default; - - String name; -}; - -class IRenderDevice { -public: - virtual ~IRenderDevice() = default; - - virtual const char* GetApiName() const = 0; - virtual const char* GetDriverVersion() const = 0; - virtual uint64_t GetDeviceId() const = 0; - virtual uint64_t GetVendorId() const = 0; - - virtual bool SupportsRaytracing() const = 0; - virtual bool SupportsMeshShaders() const = 0; - virtual bool SupportsVariableRateShading() const = 0; - virtual bool SupportsSamplerFeedback() const = 0; - virtual uint32_t GetMaxTextureSize() const = 0; - virtual uint32_t GetMaxUBOSize() const = 0; - virtual uint32_t GetMaxMSAA() const = 0; - - virtual std::unique_ptr CreateTexture(const ResourceDesc& desc) = 0; - virtual std::unique_ptr CreateBuffer(const ResourceDesc& desc, const void* initialData = nullptr) = 0; - virtual std::unique_ptr CreateShader(const ShaderDesc& desc) = 0; - virtual std::unique_ptr CreatePipeline(const PipelineDesc& desc) = 0; - virtual std::unique_ptr CreateRenderPass(const RenderPassDesc& desc) = 0; - - virtual ICommandQueue* GetGraphicsQueue() = 0; - virtual ICommandQueue* GetComputeQueue() = 0; - virtual ICommandQueue* GetCopyQueue() = 0; - - virtual std::unique_ptr CreateFence() = 0; - virtual IDescriptorHeap* CreateDescriptorHeap(const DescriptorHeapDesc& desc) = 0; - virtual std::unique_ptr CreateQueryHeap(const QueryHeapDesc& desc) = 0; -}; - -class ICommandList { -public: - virtual ~ICommandList() = default; - - virtual void TransitionBarrier(const IResource* resource, ResourceState newState) = 0; - virtual void UAVBarrier(const IResource* resource) = 0; - virtual void FlushBarriers() = 0; - - virtual void SetPipeline(const IPipeline* pipeline) = 0; - virtual void SetVertexBuffer(uint32_t slot, const IBuffer* buffer, uint64_t offset = 0) = 0; - virtual void SetIndexBuffer(const IBuffer* buffer, IndexType indexType, uint64_t offset = 0) = 0; - virtual void SetConstantBuffer(uint32_t slot, const IBuffer* buffer, uint64_t offset = 0) = 0; - virtual void SetShaderResource(uint32_t slot, const IResource* resource) = 0; - virtual void SetSampler(uint32_t slot, const ISampler* sampler) = 0; - - virtual void Draw(uint32_t vertexCount, uint32_t firstVertex = 0) = 0; - virtual void DrawIndexed(uint32_t indexCount, uint32_t firstIndex = 0, int32_t baseVertex = 0) = 0; - virtual void DrawInstanced(uint32_t vertexCountPerInstance, uint32_t instanceCount, - uint32_t firstVertex = 0, uint32_t firstInstance = 0) = 0; - virtual void DrawIndexedInstanced(uint32_t indexCountPerInstance, uint32_t instanceCount, - uint32_t firstIndex = 0, int32_t baseVertex = 0, uint32_t firstInstance = 0) = 0; - - virtual void Dispatch(uint32_t threadGroupCountX, uint32_t threadGroupCountY, uint32_t threadGroupCountZ) = 0; - - virtual void BuildAccelerationStructure(const RaytracingBuildDesc& desc) = 0; - virtual void SetRaytracingPipeline(const IPipeline* pipeline) = 0; - virtual void DispatchRays(const DispatchRaysDesc& desc) = 0; - - virtual void BeginRenderPass(const RenderPassBeginDesc& desc) = 0; - virtual void EndRenderPass() = 0; - - virtual void CopyResource(IResource* dst, const IResource* src) = 0; - virtual void CopyBuffer(IBuffer* dst, uint64_t dstOffset, const IBuffer* src, uint64_t srcOffset, uint64_t size) = 0; - - virtual void BeginQuery(IQueryHeap* heap, uint32_t index) = 0; - virtual void EndQuery(IQueryHeap* heap, uint32_t index) = 0; - - virtual void SetMarker(const char* name, uint32_t color) = 0; - virtual void BeginEvent(const char* name, uint32_t color) = 0; - virtual void EndEvent() = 0; -}; - -class RenderContext { -public: - // 前向声明 - class GBufferPass; - class LightingPass; - class ShadowPass; - class PostProcessPass; - - // 创建和销毁(支持多实例) - static RenderContext* Create(const RenderContextDesc& desc); - static void Destroy(RenderContext* context); - - // 获取主上下文(默认上下文,用于单窗口场景) - static RenderContext* GetMain(); - - void Initialize(const RenderContextDesc& desc); - void Shutdown(); - - IRenderDevice* GetDevice() { return m_device.get(); } - - void BeginFrame(); - void EndFrame(); - - ICommandList* GetMainCommandList() { return m_mainCommandList.get(); } - - void SetRenderTarget(const RenderTarget& target); - void SetViewport(const Viewport& viewport); - void SetScissorRect(const Rect& rect); - - void RenderScene(const SceneRenderDesc& desc); - void ApplyPostProcessing(const PostProcessDesc& desc); - - void CaptureFrame(const String& filePath); - void ToggleDebugView(); - -private: - std::unique_ptr m_device; - std::unique_ptr m_mainCommandList; - std::unique_ptr m_swapChain; - - std::unique_ptr m_gBufferPass; - std::unique_ptr m_lightingPass; - std::unique_ptr m_shadowPass; - std::unique_ptr m_postProcessPass; - - uint32_t m_frameIndex = 0; - uint64_t m_frameCount = 0; - - // 静态主上下文(用于 GetMain()) - static RenderContext* s_mainContext; -}; - -class RenderTexture : public IResource { -public: - uint32_t GetWidth() const { return m_width; } - uint32_t GetHeight() const { return m_height; } - uint32_t GetMipLevels() const { return m_mipLevels; } - Format GetFormat() const { return m_format; } - - void* GetNativeHandle() const { return m_nativeHandle; } - - IShaderResourceView* GetSRV() { return m_srv.get(); } - IRenderTargetView* GetRTV() { return m_rtv.get(); } - IDepthStencilView* GetDSV() { return m_dsv.get(); } - - void* Map(); - void Unmap(); - -private: - uint32_t m_width, m_height; - uint32_t m_mipLevels; - Format m_format; - void* m_nativeHandle = nullptr; - - std::unique_ptr m_srv; - std::unique_ptr m_rtv; - std::unique_ptr m_dsv; -}; - -class RenderBuffer : public IResource { -public: - uint64_t GetSize() const { return m_size; } - BufferUsage GetUsage() const { return m_usage; } - - void* GetNativeHandle() const { return m_nativeHandle; } - - void SetData(const void* data, uint64_t size, uint64_t offset = 0); - void* Map(); - void Unmap(); - -private: - uint64_t m_size; - BufferUsage m_usage; - void* m_nativeHandle = nullptr; -}; - -} -} -``` - -### 4.3 渲染图 (Render Graph) - -```cpp -namespace XCEngine { -namespace Rendering { - -class RenderGraphResource { -public: - RenderGraphResource() = default; - RenderGraphResource(const String& name); - - const String& GetName() const { return m_name; } - uint32_t GetIndex() const { return m_index; } - - bool IsValid() const { return m_index != UINT32_MAX; } - operator bool() const { return IsValid(); } - -private: - String m_name; - uint32_t m_index = UINT32_MAX; - friend class RenderGraph; -}; - -class RenderGraphPass { -public: - using ExecuteFn = std::function; - - RenderGraphPass(const String& name, PassType type); - - RenderGraphResource Read(const String& resourceName); - RenderGraphResource Write(const String& resourceName); - RenderGraphResource ReadWrite(const String& resourceName, ResourceState initialState, ResourceState finalState); - - void Execute(ExecuteFn&& fn); - void Execute() const; - - void SetDebugColor(const Color& color); - - const String& GetName() const { return m_name; } - PassType GetType() const { return m_type; } - const std::vector& GetReads() const { return m_reads; } - const std::vector& GetWrites() const { return m_writes; } - -private: - String m_name; - PassType m_type; - std::vector m_reads; - std::vector m_writes; - ExecuteFn m_executeFn; - Color m_debugColor; - - friend class RenderGraph; -}; - -class RenderGraph { -public: - RenderGraph(); - ~RenderGraph(); - - RenderGraphResource CreateTexture(const String& name, const TextureDesc& desc); - RenderGraphResource CreateBuffer(const String& name, const BufferDesc& desc); - - RenderGraphPass& AddPass(const String& name, PassType type); - - void Build(); - void Execute(ICommandList* commandList); - - void SetName(const String& name); - void Print() const; - void ExportToDot(const String& filePath) const; - - void Clear(); - -private: - void Compile(); - void Validate(); - void SortPasses(); - void AllocateResources(); - - struct ResourceInfo { - String name; - ResourceDesc desc; - ResourceState currentState; - uint32_t firstPassWrite = UINT32_MAX; - uint32_t lastPassRead = UINT32_MAX; - bool imported = false; - }; - - struct PassInfo { - String name; - PassType type; - std::vector reads; - std::vector writes; - std::function executeFn; - }; - - std::vector m_resources; - std::vector m_passes; - std::unordered_map m_resourceIndex; - - bool m_built = false; -}; - -} -} -``` - ---- - -## 第五章 资源系统 (Resource System) - -### 5.1 资源管理架构 - -``` -┌─────────────────────────────────────────────────────────────────────────────┐ -│ Resource Management │ -├─────────────────────────────────────────────────────────────────────────────┤ -│ │ -│ ┌───────────────────────────────────────────────────────────────────────┐ │ -│ │ Asset Database │ │ -│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ -│ │ │ GUID │ │ Path │ │ Meta │ │ │ -│ │ │ 映射表 │ │ 索引 │ │ 数据 │ │ │ -│ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │ -│ └───────────────────────────────────────────────────────────────────────┘ │ -│ │ │ -│ ┌────────────────────────────────▼───────────────────────────────────────┐ │ -│ │ Resource Manager │ │ -│ │ ┌───────────────────────────────────────────────────────────────┐ │ │ -│ │ │ Resource Loader Registry │ │ │ -│ │ │ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │ │ │ -│ │ │ │ Mesh │ │ Texture│ │ Audio │ │ Material│ │ Shader │ │ │ │ -│ │ │ │ Loader │ │ Loader │ │ Loader │ │ Loader │ │ Loader │ │ │ │ -│ │ │ └────────┘ └────────┘ └────────┘ └────────┘ └────────┘ │ │ │ -│ │ └───────────────────────────────────────────────────────────────┘ │ │ -│ │ │ │ │ -│ │ ┌──────────────────────────▼────────────────────────────────────┐ │ │ -│ │ │ Resource Cache │ │ │ -│ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ -│ │ │ │ LRU Cache │ │ │ │ -│ │ │ │ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │ │ │ │ -│ │ │ │ │ Resource│ │ Resource│ │ Resource│ │ Resource│ │ │ │ │ -│ │ │ │ │ 1 │ │ 2 │ │ 3 │ │ N │ │ │ │ │ -│ │ │ │ └────────┘ └────────┘ └────────┘ └────────┘ │ │ │ │ -│ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ │ -│ │ └───────────────────────────────────────────────────────────────┘ │ │ -│ └───────────────────────────────────────────────────────────────────────┘ │ -│ │ │ -│ ┌────────────────────────────────▼───────────────────────────────────────┐ │ -│ │ Import Pipeline │ │ -│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ -│ │ │ Import │→ │ Process │→ │ Cook │→ │ Package │→ │ Index │ │ │ -│ │ │ 导入 │ │ 处理 │ │ 烘焙 │ │ 打包 │ │ 索引 │ │ │ -│ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │ -│ └───────────────────────────────────────────────────────────────────────┘ │ -│ │ -└─────────────────────────────────────────────────────────────────────────────┘ -``` - -### 5.2 资源系统实现 - -```cpp -namespace XCEngine { -namespace Resources { - -enum class ResourceType { - Unknown, - Mesh, - Texture, - Material, - Shader, - Audio, - Animation, - Skeleton, - Font, - Prefab, - Scene, - Script, - PhysicsMaterial, - NavMesh, - Video, - Custom -}; - -struct ResourceGUID { - static constexpr uint64_t INVALID = 0; - - uint64_t value = INVALID; - - bool IsValid() const { return value != INVALID; } - bool operator==(const ResourceGUID& other) const { return value == other.value; } - bool operator!=(const ResourceGUID& other) const { return value != other.value; } - - String ToString() const; - static ResourceGUID FromString(const String& str); - - struct Hash { - size_t operator()(const ResourceGUID& guid) const { - return std::hash{}(guid.value); - } - }; -}; - -class IResource { -public: - virtual ~IResource() = default; - - virtual ResourceType GetType() const = 0; - virtual const String& GetName() const = 0; - virtual ResourceGUID GetGUID() const = 0; - - virtual bool IsLoaded() const = 0; - virtual bool IsLoading() const = 0; - virtual float GetLoadingProgress() const = 0; - - virtual void AddRef() = 0; - virtual void Release() = 0; - virtual uint32_t GetRefCount() const = 0; - - virtual void Unload() = 0; -}; - -struct LoadRequest { - ResourceGUID guid; - ResourceType type; - std::function callback; - std::function progressCallback; - bool async = true; - uint32_t priority = 0; -}; - -class IResourceLoader { -public: - virtual ~IResourceLoader() = default; - - virtual ResourceType GetResourceType() const = 0; - virtual std::vector GetSupportedExtensions() const = 0; - - virtual bool CanLoad(const String& filePath) const = 0; - virtual bool Load(const String& filePath, IResource* resource) = 0; - virtual bool Save(const String& filePath, const IResource* resource) = 0; - - virtual std::unique_ptr CreateResource() = 0; -}; - -class ResourceRepository { -public: - void Initialize(const String& basePath); - void Shutdown(); - - std::unique_ptr LoadRaw(const String& filePath, size_t& outSize); - std::unique_ptr OpenStream(const String& filePath); - - bool FileExists(const String& filePath); - bool DirectoryExists(const String& dirPath); - std::vector GetFiles(const String& dirPath, const String& extension = ""); - - String GetFullPath(const String& relativePath); - String GetCachePath(); - String GetSourcePath(); - -private: - String m_basePath; - String m_cachePath; - String m_sourcePath; -}; - -class ResourceCache { -public: - explicit ResourceCache(size_t maxMemory); - ~ResourceCache(); - - void Initialize(); - void Shutdown(); - - void Add(const ResourceGUID& guid, IResource* resource); - IResource* Find(const ResourceGUID& guid); - void Remove(const ResourceGUID& guid); - void Clear(); - - void SetMaxMemory(size_t bytes); - size_t GetCurrentMemory() const; - size_t GetMaxMemory() const; - - void Touch(const ResourceGUID& guid); - void Evict(size_t bytesToFree); - - struct Stats { - uint32_t hitCount; - uint32_t missCount; - size_t memoryUsed; - size_t memoryReserved; - }; - const Stats& GetStats() const; - -private: - struct CacheEntry { - IResource* resource; - size_t memorySize; - uint64_t lastAccess; - }; - - std::unordered_map m_cache; - size_t m_maxMemory; - size_t m_currentMemory; - Stats m_stats; -}; - -class ResourceManager { -public: - static ResourceManager& Get(); - - void Initialize(const ResourceManagerConfig& config); - void Shutdown(); - - void RegisterLoader(std::unique_ptr loader); - IResourceLoader* GetLoader(ResourceType type) const; - IResourceLoader* GetLoaderForFile(const String& filePath) const; - - ResourceGUID Load(const String& filePath); - ResourceGUID LoadAsync(const String& filePath, const std::function& callback); - - template - std::shared_ptr Get(const ResourceGUID& guid); - - template - std::shared_ptr Get(const String& filePath); - - void AddRef(const ResourceGUID& guid); - void Release(const ResourceGUID& guid); - - ResourceGUID FindByPath(const String& path) const; - String GetPath(const ResourceGUID& guid) const; - - bool Reload(const ResourceGUID& guid); - void Unload(const ResourceGUID& guid); - void UnloadUnused(); - - void PrintStatistics(); - - void RebuildDatabase(); - void ScanDirectory(const String& dirPath); - -private: - ResourceManager() = default; - - std::unordered_map> m_loaders; - std::unordered_map, ResourceGUID::Hash> m_resources; - std::unordered_map m_pathToGUID; - std::unordered_map m_guidToPath; - - std::unique_ptr m_cache; - std::unique_ptr m_repository; - - std::unique_ptr m_assetDatabase; - - std::atomic m_nextGuid{1}; -}; - -template -class ResourcePtr { -public: - ResourcePtr() = default; - - explicit ResourcePtr(const ResourceGUID& guid) - : m_guid(guid), m_resource(ResourceManager::Get().Get(guid)) {} - - ResourcePtr(const ResourcePtr& other) - : m_guid(other.m_guid), m_resource(other.m_resource) {} - - ~ResourcePtr() { - if (m_guid.IsValid()) { - ResourceManager::Get().Release(m_guid); - } - } - - T* Get() const { return m_resource.get(); } - T* operator->() const { return m_resource.get(); } - T& operator*() const { return *m_resource; } - - bool IsValid() const { return m_resource != nullptr; } - explicit operator bool() const { return IsValid(); } - - const ResourceGUID& GetGUID() const { return m_guid; } - -private: - ResourceGUID m_guid; - std::shared_ptr m_resource; -}; - -} -} -``` - ---- - -## 第六章 场景系统 (Scene System) - -> **架构说明**: Scene是Unity风格的场景,直接管理GameObject列表(没有World中间层),负责场景生命周期管理、资源加载和序列化。 - -### 6.1 场景管理 - -```cpp -namespace XCEngine { -namespace Scene { - -// 注意:GameObject和Transform定义在Components命名空间中 -// Transform是GameObject的内置组件,包含父子层级关系 - -enum class SceneLoadFlags { - None = 0, - Async = 1 << 0, - Additive = 1 << 1, - Single = 1 << 2, - DontLoadAudio = 1 << 3, - DontCreatePhysics = 1 << 4, - SubScene = 1 << 5 -}; - -inline SceneLoadFlags operator|(SceneLoadFlags a, SceneLoadFlags b) { - return static_cast(static_cast(a) | static_cast(b)); -} - -inline SceneLoadFlags& operator|=(SceneLoadFlags& a, SceneLoadFlags b) { - a = a | b; - return a; -} - -inline SceneLoadFlags operator&(SceneLoadFlags a, SceneLoadFlags b) { - return static_cast(static_cast(a) & static_cast(b)); -} - -inline SceneLoadFlags& operator&=(SceneLoadFlags& a, SceneLoadFlags b) { - a = a & b; - return a; -} - -inline bool HasFlag(SceneLoadFlags flags, SceneLoadFlags flag) { - return (static_cast(flags) & static_cast(flag)) != 0; -} - -// 注意:SceneManager 定义在 XCEngine 命名空间中(第三章 3.5 节) -// 此处只保留场景相关的辅助类型和序列化器 - -// 场景序列化器 -// 支持 JSON 和 Binary 两种格式 -class SceneSerializer { -public: - // 序列化格式 - enum class Format { - JSON, - Binary - }; - - // 序列化到文件 - static void Serialize(const Scene& scene, const String& filePath, Format format = Format::JSON); - static std::unique_ptr Deserialize(const String& filePath); - - // JSON 序列化 (使用已存在的 Core 命名空间中的 JSON 库) - static void SerializeToJson(const Scene& scene, Core::JsonDocument& doc); - static void DeserializeFromJson(Scene& scene, const Core::JsonDocument& doc); - - // Binary 序列化 (使用 Core::IStream) - static void SerializeToBinary(const Scene& scene, Core::IStream& stream); - static std::unique_ptr DeserializeFromBinary(Core::IStream& stream); - -private: - static void SerializeGameObject(const GameObject& gameObject, Core::JsonValue& parent); - static void DeserializeGameObject(GameObject& gameObject, const Core::JsonValue& data); - - static void SerializeComponent(uint32_t componentTypeId, const void* component, Core::JsonValue& parent); - static void DeserializeComponent(uint32_t componentTypeId, const GameObject& gameObject, const Core::JsonValue& data); -}; - -// ============================================================================ -// 序列化辅助类型 (需要在 Core 命名空间中定义) -// ============================================================================ - -namespace Core { - -// 流接口 -class IStream { -public: - virtual ~IStream() = default; - - virtual bool IsValid() const = 0; - virtual size_t GetPosition() const = 0; - virtual size_t GetSize() const = 0; - - virtual size_t Read(void* buffer, size_t size) = 0; - virtual size_t Write(const void* buffer, size_t size) = 0; - - virtual void Seek(size_t position) = 0; - virtual void Skip(size_t bytes) = 0; - virtual void Flush() = 0; - - virtual bool IsReading() const = 0; - virtual bool IsWriting() const = 0; -}; - -// JSON 文档 -class JsonDocument { -public: - JsonDocument(); - ~JsonDocument(); - - void Parse(const String& json); - void Parse(const char* json, size_t length); - - JsonValue& GetRoot(); - const JsonValue& GetRoot() const; - - String ToString() const; - - bool HasParseError() const; - String GetParseError() const; -}; - -// JSON 值 -class JsonValue { -public: - enum class Type { - Null, - Bool, - Int, - Float, - String, - Array, - Object - }; - - JsonValue(); - JsonValue(bool value); - JsonValue(int32_t value); - JsonValue(int64_t value); - JsonValue(float value); - JsonValue(double value); - JsonValue(const String& value); - JsonValue(const char* value); - ~JsonValue(); - - Type GetType() const { return m_type; } - - // 值的获取 - bool GetBool() const; - int32_t GetInt() const; - int64_t GetInt64() const; - float GetFloat() const; - double GetDouble() const; - const String& GetString() const; - - // 数组/对象访问 - size_t GetSize() const; - JsonValue& operator[](size_t index); - const JsonValue& operator[](size_t index) const; - JsonValue& operator[](const String& key); - const JsonValue& operator[](const String& key) const; - - bool Has(const String& key) const; - - // 修改 - JsonValue& SetBool(bool value); - JsonValue& SetInt(int32_t value); - JsonValue& SetFloat(float value); - JsonValue& SetString(const String& value); - - JsonValue& AddArrayElement(); - JsonValue& AddObjectMember(const String& key); - - void Clear(); - -private: - Type m_type = Type::Null; - // ... 具体成员 -}; - -} // namespace Core - -} -} -``` - ---- - -## 第七章 物理系统 (Physics) - -### 7.1 物理系统 - -```cpp -namespace XCEngine { -namespace Physics { - -// 碰撞检测查询标志 -enum class QueryFlags : uint32_t { - None = 0, - Colliders = 1 << 0, - Rigidodies = 1 << 1, - Transforms = 1 << 2, - All = Colliders | Rigidodies | Transforms -}; - -// 力学模式 (类Unity ForceMode) -enum class ForceMode : uint8_t { - Force = 0, // 持续力 (质量单位) - Acceleration = 1, // 加速度 (无质量单位) - Impulse = 2, // 瞬时冲量 (质量单位) - VelocityChange = 3 // 速度变化 (无质量单位) -}; - -enum class CollisionShapeType { - Sphere, - Box, - Capsule, - ConvexMesh, - ConcaveMesh, - Heightfield, - Compound -}; - -struct CollisionLayer { - static constexpr uint32_t MaxLayers = 32; - static constexpr uint32_t Default = 0; - - uint32_t id; - String name; - uint32_t mask; -}; - -struct PhysicsMaterial { - float friction = 0.5f; - float restitution = 0.0f; - float frictionCombine = 0; - float restitutionCombine = 0; - bool isTrigger = false; - - float staticFriction = 0.5f; - float dynamicFriction = 0.5f; -}; - -struct RaycastHit { - GameObject* gameObject; - Vector3 point; - Vector3 normal; - float distance; - Vector2 uv; - int32_t faceIndex; - float fraction; - - ColliderComponent* collider = nullptr; -}; - -// 碰撞信息 (类Unity Collision) -struct Collision { - GameObject* gameObject = nullptr; - ColliderComponent* collider = nullptr; - RigidBodyComponent* rigidBody = nullptr; - - Vector3 relativeVelocity; - Vector3 contactPoint; - Vector3 contactNormal; - - std::vector contacts; - std::vector normals; -}; - -// 2D碰撞信息 (类Unity Collision2D) -struct Collision2D { - GameObject* gameObject = nullptr; - Collider2DComponent* collider = nullptr; - RigidBody2DComponent* rigidBody = nullptr; - - Vector2 relativeVelocity; - Vector2 contactPoint; - Vector2 contactNormal; - - std::vector contacts; - std::vector normals; - - float fraction = 0.0f; - int32_t faceID = 0; -}; - -// 2D碰撞体组件 (类Unity Collider2D) -class Collider2DComponent : public Component { -public: - void Awake() override; - void Start() override; - - enum class ShapeType : uint8_t { - Box, - Circle, - Capsule, - Polygon, - Edge - }; - - void SetShapeType(ShapeType type) { m_shapeType = type; } - ShapeType GetShapeType() const { return m_shapeType; } - - void SetOffset(const Vector2& offset) { m_offset = offset; } - Vector2 GetOffset() const { return m_offset; } - - void SetDensity(float density) { m_density = density; } - float GetDensity() const { return m_density; } - - void SetIsTrigger(bool trigger) { m_isTrigger = trigger; } - bool IsTrigger() const { return m_isTrigger; } - - void SetUsedByEffector(bool use) { m_usedByEffector = use; } - bool IsUsedByEffector() const { return m_usedByEffector; } - -private: - ShapeType m_shapeType = ShapeType::Box; - Vector2 m_offset = Vector2::Zero(); - float m_density = 1.0f; - bool m_isTrigger = false; - bool m_usedByEffector = false; - - void* m_physicsHandle = nullptr; -}; - -// 2D刚体组件 (类Unity Rigidbody2D) -class RigidBody2DComponent : public Component { -public: - void Awake() override; - void Start() override; - void FixedUpdate() override; - - enum class BodyType : uint8_t { - Dynamic, - Kinematic, - Static - }; - - enum class InterpolateMode : uint8_t { - None, - Interpolate, - Extrapolate - }; - - void SetBodyType(BodyType type) { m_bodyType = type; } - BodyType GetBodyType() const { return m_bodyType; } - - void SetMass(float mass) { m_mass = mass; } - float GetMass() const { return m_mass; } - - void SetLinearVelocity(const Vector2& velocity); - Vector2 GetLinearVelocity() const; - - void SetAngularVelocity(float velocity); - float GetAngularVelocity() const; - - void AddForce(const Vector2& force, ForceMode mode = ForceMode::Force); - void AddForceAtPosition(const Vector2& force, const Vector2& position, ForceMode mode = ForceMode::Force); - - void SetGravityScale(float scale) { m_gravityScale = scale; } - float GetGravityScale() const { return m_gravityScale; } - - void SetIsKinematic(bool kinematic) { m_kinematic = kinematic; } - bool IsKinematic() const { return m_kinematic; } - - void SetInterpolateMode(InterpolateMode mode) { m_interpolateMode = mode; } - InterpolateMode GetInterpolateMode() const { return m_interpolateMode; } - - void* GetPhysicsHandle() const { return m_physicsHandle; } - void SetPhysicsHandle(void* handle) { m_physicsHandle = handle; } - -private: - BodyType m_bodyType = BodyType::Dynamic; - float m_mass = 1.0f; - Vector2 m_linearVelocity; - float m_angularVelocity = 0.0f; - float m_gravityScale = 1.0f; - bool m_kinematic = false; - InterpolateMode m_interpolateMode = InterpolateMode::None; - - void* m_physicsHandle = nullptr; -}; - -struct CollisionEvent { - GameObject* gameObjectA; - GameObject* gameObjectB; - Vector3 point; - Vector3 normal; - Vector3 relativeVelocity; - - enum class Type { - Enter, - Stay, - Exit - }; - Type type; -}; - -class PhysicsWorld { -public: - PhysicsWorld(); - ~PhysicsWorld(); - - void Initialize(const PhysicsWorldDesc& desc); - void Shutdown(); - - void Simulate(float deltaTime); - void SetGravity(const Vector3& gravity); - Vector3 GetGravity() const { return m_gravity; } - - // 物理组件注册(替代直接 SyncWithScene,通过组件系统同步) - void RegisterRigidBody(RigidBodyComponent* component); - void UnregisterRigidBody(RigidBodyComponent* component); - void RegisterCollider(ColliderComponent* component); - void UnregisterCollider(ColliderComponent* component); - - bool Raycast(const Ray& ray, float maxDistance, RaycastHit& hit, - uint32_t layerMask = 0xFFFFFFFF, QueryFlags flags = QueryFlags::None); - bool RaycastMultiple(const Ray& ray, float maxDistance, std::vector& hits, - uint32_t layerMask = 0xFFFFFFFF, QueryFlags flags = QueryFlags::None); - - bool CheckSphere(const Sphere& sphere, uint32_t layerMask = 0xFFFFFFFF); - bool CheckBox(const Box& box, uint32_t layerMask = 0xFFFFFFFF); - bool CheckCapsule(const Vector3& start, const Vector3& end, float radius, - uint32_t layerMask = 0xFFFFFFFF); - - bool SphereCast(const Sphere& sphere, const Vector3& direction, float maxDistance, - RaycastHit& hit, uint32_t layerMask = 0xFFFFFFFF); - - std::vector OverlapSphere(const Sphere& sphere, uint32_t layerMask = 0xFFFFFFFF); - std::vector OverlapBox(const Box& box, uint32_t layerMask = 0xFFFFFFFF); - - // 使用 RigidBodyComponent* 代替 GameObject*,通过组件内部的句柄操作,避免悬空指针 - void AddForce(RigidBodyComponent* component, const Vector3& force, ForceMode mode = ForceMode::Force); - void AddForceAtPosition(RigidBodyComponent* component, const Vector3& force, const Vector3& position, - ForceMode mode = ForceMode::Force); - void AddTorque(RigidBodyComponent* component, const Vector3& torque, ForceMode mode = ForceMode::Force); - - void SetLinearVelocity(RigidBodyComponent* component, const Vector3& velocity); - void SetAngularVelocity(RigidBodyComponent* component, const Vector3& velocity); - Vector3 GetLinearVelocity(RigidBodyComponent* component) const; - Vector3 GetAngularVelocity(RigidBodyComponent* component) const; - - void SetKinematic(RigidBodyComponent* component, bool kinematic); - - // Joint 由 JointComponent 组件管理,不再由 PhysicsWorld 直接创建 - - Event OnCollisionEnter; - Event OnCollisionStay; - Event OnCollisionExit; - Event OnTriggerEnter; - Event OnTriggerStay; - Event OnTriggerExit; - - void DebugDraw(); - void SetDebugDrawEnabled(bool enabled); - -private: - void* m_physicsBackend = nullptr; - - Vector3 m_gravity = Vector3(0, -9.81f, 0); - - std::vector m_collisionEvents; - std::vector m_triggerEvents; - - bool m_debugDrawEnabled = false; -}; - -class CharacterControllerComponent : public Component { -public: - void Awake() override; - void Update(float deltaTime) override; - void OnDestroy() override; - - void Initialize(const CharacterControllerDesc& desc); - - void Move(const Vector3& displacement); - void SetVelocity(const Vector3& velocity); - Vector3 GetVelocity() const { return m_velocity; } - - bool IsGrounded() const { return m_grounded; } - Vector3 GetGroundNormal() const { return m_groundNormal; } - GameObject* GetGroundObject() const { return m_groundObject; } - - void Jump(const Vector3& jumpForce); - void SetJumpEnabled(bool enabled); - - void SetSlopeLimit(float angle); - void SetStepOffset(float height); - void SetSkinWidth(float width); - - bool DetectCollision(const Vector3& position, const Vector3& direction, float distance); - -private: - void UpdateGroundStatus(); - void HandleCollision(); - - Vector3 m_velocity = Vector3::Zero(); - Vector3 m_pendingForces = Vector3::Zero(); - - float m_slopeLimit = 45.0f; - float m_stepOffset = 0.5f; - float m_skinWidth = 0.02f; - float m_minDistance = 0.1f; - - bool m_grounded = false; - bool m_jumpEnabled = true; - Vector3 m_groundNormal = Vector3::Up(); - GameObject* m_groundObject = nullptr; - - uint32_t m_layer = 0; - uint32_t m_collisionMask = 0xFFFFFFFF; -}; - -} -} -``` - ---- - -## 第八章 音频系统 (Audio) - -> **架构说明**: Unity风格的音频系统。音频源和监听器都是挂载在GameObject上的组件。 -> AudioEngine是底层实现,提供音频解码、混音、空间化等功能。 - -### 8.1 音频系统 - -```cpp -namespace XCEngine { -namespace Audio { - -// 音频格式 -enum class AudioFormat { - Unknown, - WAV, - OGG, - MP3, - FLAC, - AAC -}; - -// 音频片段 (资源) -class AudioClip : public IResource { -public: - AudioClip(); - ~AudioClip(); - - const String& GetName() const override { return m_name; } - ResourceType GetType() const override { return ResourceType::Audio; } - ResourceGUID GetGUID() const override { return m_guid; } - - bool IsLoaded() const override { return m_loaded; } - bool IsLoading() const override { return m_loading; } - float GetLoadingProgress() const override { return m_loadProgress; } - - const uint8_t* GetData() const { return m_data.get(); } - size_t GetDataSize() const { return m_dataSize; } - uint32_t GetSampleRate() const { return m_sampleRate; } - uint32_t GetChannels() const { return m_channels; } - uint32_t GetBitsPerSample() const { return m_bitsPerSample; } - float GetDuration() const { return m_duration; } - - AudioFormat GetFormat() const { return m_format; } - -private: - String m_name; - ResourceGUID m_guid; - - std::unique_ptr m_data; - size_t m_dataSize = 0; - - uint32_t m_sampleRate = 44100; - uint32_t m_channels = 2; - uint32_t m_bitsPerSample = 16; - float m_duration = 0.0f; - - AudioFormat m_format = AudioFormat::Unknown; - - bool m_loaded = false; - bool m_loading = false; - float m_loadProgress = 0.0f; - - friend class AudioEngine; -}; - -// 音频总线 (混音器) -class AudioBus { -public: - AudioBus(const String& name, AudioBus* parent = nullptr); - - void SetVolume(float volume); - float GetVolume() const { return m_volume; } - - void SetMute(bool mute); - bool IsMuted() const { return m_muted; } - - void SetBypassEffects(bool bypass); - bool IsBypassingEffects() const { return m_bypassEffects; } - - void AddEffect(AudioEffect* effect); - void RemoveEffect(AudioEffect* effect); - -private: - String m_name; - AudioBus* m_parent = nullptr; - std::vector m_children; - std::vector> m_effects; - - float m_volume = 1.0f; - bool m_muted = false; - bool m_bypassEffects = false; -}; - -// 音频引擎 - 底层实现 (不对外直接使用) -class AudioEngine { -public: - static AudioEngine& Get(); - - void Initialize(const AudioEngineConfig& config); - void Shutdown(); - - const char* GetDeviceName() const; - uint32_t GetSpeakerCount() const; - - void Update(float deltaTime); - - // 资源管理 - std::shared_ptr LoadClip(const String& filePath); - void UnloadClip(AudioClip* clip); - - // 混音器 - AudioBus* GetMasterBus() { return m_masterBus.get(); } - AudioBus* GetBus(const String& path); - AudioBus* CreateBus(const String& path); - - // 内部音频源管理 (由 AudioSystem 使用) - struct InternalSource { - uint64_t id = 0; - void* handle = nullptr; // 底层音频引擎句柄 - }; - - InternalSource CreateInternalSource(); - void DestroyInternalSource(InternalSource source); - void SetSourceClip(InternalSource source, AudioClip* clip); - void PlaySource(InternalSource source); - void StopSource(InternalSource source); - void PauseSource(InternalSource source); - void SetSourceVolume(InternalSource source, float volume); - void SetSourcePitch(InternalSource source, float pitch); - void SetSourcePosition(InternalSource source, const Vector3& position); - void SetSourceLoop(InternalSource source, bool loop); - bool IsSourcePlaying(InternalSource source); - - // 监听器管理 - Unity 限制只能有一个 AudioListener - void SetListenerComponent(AudioListenerComponent* listener); - AudioListenerComponent* GetListenerComponent() const { return m_listenerComponent; } - - void SetListenerPosition(const Vector3& position); - void SetListenerVelocity(const Vector3& velocity); - void SetListenerOrientation(const Vector3& forward, const Vector3& up); - void SetListenerDopplerFactor(float factor); - - struct Stats { - uint32_t activeVoices; - uint32_t totalVoices; - uint32_t playingSources; - uint32_t cpuUsage; - size_t memoryUsage; - }; - const Stats& GetStats() const { return m_stats; } - -private: - AudioEngine() = default; - - std::unique_ptr m_masterBus; - - std::unordered_map> m_loadedClips; - std::vector m_activeSources; - uint64_t m_nextSourceId = 1; - - // 监听器状态 - Vector3 m_listenerPosition; - Vector3 m_listenerVelocity; - Vector3 m_listenerForward = Vector3::Forward(); - Vector3 m_listenerUp = Vector3::Up(); - float m_listenerDopplerFactor = 1.0f; - - // 限制只能有一个 AudioListener - AudioListenerComponent* m_listenerComponent = nullptr; - - Stats m_stats; - - void* m_backend = nullptr; // FMOD / Wwise / OpenAL -}; - -} // namespace Audio -} // namespace XCEngine -``` - ---- - -## 第十章 动画系统 (Animation) - -### 10.1 动画系统架构 - -```cpp -namespace XCEngine { -namespace Animation { - -// 动画曲线 -struct AnimationCurve { - std::vector> floatKeys; - std::vector> vector3Keys; - std::vector> quaternionKeys; - std::vector> colorKeys; - - // 具体类型的求值方法 - float EvaluateFloat(float time, float defaultValue = 0.0f) const; - Vector3 EvaluateVector3(float time, const Vector3& defaultValue = Vector3::Zero()) const; - Quaternion EvaluateQuaternion(float time, const Quaternion& defaultValue = Quaternion::Identity()) const; - Color EvaluateColor(float time, const Color& defaultValue = Color::White) const; - - // 具体类型的 Key 操作 - void AddKeyFloat(float time, float value); - void AddKeyVector3(float time, const Vector3& value); - void AddKeyQuaternion(float time, const Quaternion& value); - void AddKeyColor(float time, const Color& value); - - void RemoveKeyFloat(float time); - void RemoveKeyVector3(float time); - void RemoveKeyQuaternion(float time); - void RemoveKeyColor(float time); -}; - -// 动画片段 -struct AnimationClip : public IResource { - float duration = 0.0f; - float sampleRate = 30.0f; - bool loop = false; - - // 轨道 - struct Track { - String path; // 目标路径 e.g., "Root/Child/Arm" - enum Type { Position, Rotation, Scale, Float, Int, Bool, Trigger } type; - AnimationCurve curve; - }; - - std::vector tracks; - - // 事件 - struct Event { - float time; - String functionName; - String stringParameter; - float floatParameter; - int intParameter; - }; - std::vector events; -}; - -// 骨骼 -struct Bone { - String name; - int32 parentIndex = -1; - Matrix4x4 localMatrix; - Matrix4x4 worldMatrix; - Vector3 position; - Quaternion rotation; - Vector3 scale; -}; - -// 骨架 -struct Skeleton : public IResource { - std::vector bones; - int32 rootBoneIndex = -1; - - int32 FindBone(const String& name) const; - const Bone& GetBone(int32 index) const; - Matrix4x4 GetBoneMatrix(int32 index) const; -}; - -// 蒙皮数据 -struct SkinningData { - std::vector bindPoses; - std::vector boneIndices; - std::vector boneWeights; -}; - -// 动画状态机 -class AnimatorStateMachine { -public: - struct State { - String name; - AnimationClip* clip = nullptr; - float speed = 1.0f; - float cycleOffset = 0.0f; - - std::vector transitions; - }; - - struct Transition { - String targetState; - float exitTime = 0.0f; - float duration = 0.0f; - - enum class ConditionMode { - If, - IfNot, - Greater, - Less, - Equals - }; - - struct Condition { - String parameter; - ConditionMode mode; - float threshold; - }; - std::vector conditions; - }; - - struct Parameter { - enum Type { Float, Int, Bool, Trigger } type; - String name; - }; - - // 状态管理 - void AddState(const String& name); - void AddTransition(const String& from, const String& to, const Transition& transition); - void AddParameter(const Parameter& param); - - // 播放控制 - void Play(const String& stateName); - void CrossFade(const String& stateName, float duration); - void Stop(); - - // 参数 - void SetFloat(const String& name, float value); - void SetInt(const String& name, int32 value); - void SetBool(const String& name, bool value); - void SetTrigger(const String& name); - - float GetFloat(const String& name) const; - int32 GetInt(const String& name) const; - bool GetBool(const String& name) const; - - // 动画层级 - void AddLayer(const String& name, float weight = 1.0f); - void SetLayerWeight(const String& name, float weight); - -private: - std::vector m_states; - std::vector m_parameters; - std::unordered_map m_stateIndices; - - String m_currentState; - String m_previousState; - float m_currentTime = 0.0f; -}; - -// 动画器组件 - 继承Component(类Unity Animator) -class AnimatorComponent : public Component { -public: - void Awake() override; - void Start() override; - void Update(float deltaTime) override; - void OnDestroy() override; - - void SetSkeleton(Skeleton* skeleton); - Skeleton* GetSkeleton() const { return m_skeleton; } - - // 播放控制 - void Play(const String& stateName); - void CrossFade(const String& stateName, float duration); - void PlayInFixedTime(const String& stateName, float normalizedTime); - - bool IsPlaying() const; - float GetCurrentAnimationTime() const; - float GetCurrentAnimationLength() const; - - // 层 - void AddLayer(const String& name, float weight); - void SetLayerWeight(const String& name, float weight); - - // 参数 - void SetFloat(const String& name, float value); - void SetInteger(const String& name, int32 value); - void SetBool(const String& name, bool value); - void SetTrigger(const String& name); - - // 根运动 - void SetApplyRootMotion(bool apply) { m_applyRootMotion = apply; } - bool GetApplyRootMotion() const { return m_applyRootMotion; } - - Vector3 GetRootPosition() const { return m_rootPosition; } - Quaternion GetRootRotation() const { return m_rootRotation; } - -private: - Skeleton* m_skeleton = nullptr; - std::unique_ptr m_stateMachine; - bool m_useGPUAnimation = false; - Buffer* m_boneMatricesBuffer = nullptr; - bool m_applyRootMotion = false; - Vector3 m_rootPosition; - Quaternion m_rootRotation; -}; - -// 动画系统管理器 (处理GPU蒙皮、事件等) -class AnimationSystem { -public: - static AnimationSystem& Get(); - - void Initialize(); - void Shutdown(); - - void Update(float deltaTime); - - void UpdateGPUBuffers(AnimatorComponent* animator); - void ProcessAnimationEvents(AnimatorComponent* animator, float prevTime, float currentTime); - -private: - void EvaluateState(AnimatorComponent* animator, float time, - std::vector& boneMatrices); - void BlendStates(AnimatorComponent* animator, float blendWeight, - const std::vector& from, - const std::vector& to, - std::vector& output); -}; - - // GPU 蒙皮 - void UpdateGPUBuffers(AnimatorComponent* animator); - - // 事件触发 - void ProcessAnimationEvents(AnimatorComponent* animator, float prevTime, float currentTime); - -private: - void EvaluateState(AnimatorComponent* animator, float time, - std::vector& boneMatrices); - void BlendStates(AnimatorComponent* animator, float blendWeight, - const std::vector& from, - const std::vector& to, - std::vector& output); -}; - -// Blend Shape -struct BlendShape { - String name; - std::vector vertices; - std::vector normals; - std::vector tangents; -}; - -struct BlendShapeFrame { - float weight = 0.0f; - std::vector deltaVertices; - std::vector deltaNormals; - std::vector deltaTangents; -}; - -struct BlendShapeData { - std::vector shapes; - std::vector frames; -}; - -// 程序化动画 -namespace ProceduralAnimation { - -class IKChain { -public: - void SetJoints(const std::vector& joints); - void Solve(const Vector3& target); - - void SetPoleVector(const Vector3& pole); - void SetIterations(int32 iterations); - -private: - std::vector m_joints; - Vector3 m_poleVector; - int32_t m_iterations = 10; -}; - -class CCDIK : public IKChain { -public: - void Solve(const Vector3& target) override; -}; - -class FABRIK : public IKChain { -public: - void Solve(const Vector3& target) override; - -private: - void ForwardReach(const Vector3& target); - void BackwardReach(const Vector3& base); - void SolveConstraints(); -}; - -} // namespace ProceduralAnimation -} // namespace Animation -} // namespace XCEngine -``` - ---- - -## 第十一章 粒子系统 (Particle System) - -### 11.1 粒子系统架构 - -```cpp -namespace XCEngine { -namespace Particles { - -// 粒子数据 - 粒子系统使用SoA布局以优化SIMD性能(这是性能关键路径的例外) -struct Particle { - Vector3 position; - Vector3 velocity; - Vector3 acceleration; - - Quaternion rotation; - float rotationSpeed; - - float size; - float lifetime; - float age; - - Color color; - float alpha; - - uint32_t seed; - bool alive; -}; - -// 粒子发射器 -class ParticleEmitter { -public: - // 发射模式 - enum class EmitMode { - Continuous, - Burst, - Distance - }; - - // 形状 - enum class ShapeType { - Sphere, - Box, - Cone, - Circle, - Edge, - MeshSurface, - MeshVolume - }; - - // 发射参数 - float rate = 10.0f; - int maxParticles = 1000; - float lifetime = 5.0f; - - // 初始速度 - Vector3 velocityMin = Vector3(-1, 1, -1); - Vector3 velocityMax = Vector3(1, 1, 1); - float speedMin = 1.0f; - float speedMax = 5.0f; - - // 初始变换 - float startSizeMin = 0.1f; - float startSizeMax = 0.5f; - float startRotationMin = 0.0f; - float startRotationMax = 360.0f; - Color startColor = Color::White; - - // 形状 - ShapeType shape = ShapeType::Sphere; - float shapeRadius = 1.0f; - float shapeAngle = 25.0f; - Vector3 shapeBoxSize = Vector3::One(); - - // 发射 - EmitMode emitMode = EmitMode::Continuous; - int burstCount = 10; - float burstInterval = 1.0f; -}; - -// 粒子系统组件 - 继承Component(类Unity ParticleSystem) -class ParticleSystemComponent : public Component { -public: - void Awake() override; - void Start() override; - void Update(float deltaTime) override; - void OnDestroy() override; - - // 播放控制(Unity 风格) - void Play(); - void Stop(); - void Pause(); - void Resume(); - void Clear(); - - bool IsPlaying() const { return m_isPlaying && !m_isPaused; } - bool IsPaused() const { return m_isPaused; } - bool IsStopped() const { return !m_isPlaying; } - - void SetLoop(bool loop) { m_loop = loop; } - bool GetLoop() const { return m_loop; } - - void SetEmitRate(float rate) { m_emitter.rate = rate; } - float GetEmitRate() const { return m_emitter.rate; } - - void SetLifetime(float lifetime) { m_emitter.lifetime = lifetime; } - float GetLifetime() const { return m_emitter.lifetime; } - - void SetStartSpeed(float speed) { m_emitter.speedMin = m_emitter.speedMax = speed; } - float GetStartSpeed() const { return m_emitter.speedMin; } - - void SetStartSize(float size) { m_emitter.startSizeMin = m_emitter.startSizeMax = size; } - float GetStartSize() const { return m_emitter.startSizeMin; } - - void SetStartColor(const Color& color) { m_emitter.startColor = color; } - Color GetStartColor() const { return m_emitter.startColor; } - -private: - ParticleEmitter m_emitter; - bool m_isPlaying = false; - bool m_isPaused = false; - bool m_loop = true; - float m_simulationSpeed = 1.0f; - float m_startDelay = 0.0f; - uint32_t m_randomSeed = 0; -}; - -// GPU 粒子系统 -class GPUParticleSystem { -public: - void Initialize(uint32_t maxParticles); - void Shutdown(); - - void Update(float deltaTime, const ParticleSystemComponent& config); - void Render(RenderContext& context); - - // GPU 缓冲区 - Buffer* GetPositionBuffer() { return m_positionBuffer.get(); } - Buffer* GetVelocityBuffer() { return m_velocityBuffer.get(); } - Buffer* GetColorBuffer() { return m_colorBuffer.get(); } - -private: - struct GPUParticle { - Vector4 position; // w = size - Vector4 velocity; // w = lifetime - Vector4 color; // w = rotation - }; - - uint32_t m_maxParticles = 0; - - std::unique_ptr m_positionBuffer; - std::unique_ptr m_velocityBuffer; - std::unique_ptr m_colorBuffer; - std::unique_ptr m_indexBuffer; - - ComputePipeline m_updatePipeline; -}; - -// 粒子系统管理器 -class ParticleSystemManager { -public: - static ParticleSystemManager& Get(); - - void Initialize(); - void Shutdown(); - - void Update(float deltaTime); - void Render(RenderContext& context); - - // 发射器 - ParticleEmitter* CreateEmitter(); - void DestroyEmitter(ParticleEmitter* emitter); - - // GPU 粒子 - GPUParticleSystem* GetGPUParticleSystem() { return &m_gpuSystem; } - -private: - std::vector> m_emitters; - GPUParticleSystem m_gpuSystem; -}; - -// VFX Graph 集成 -namespace VFXGraph { - -class VFXGraphAsset : public IResource { -public: - // VFX Graph 描述 -}; - -class VFXGraphComponent : public Component { -public: - void Update(float deltaTime) override; - - VFXGraphAsset* asset = nullptr; - - std::unordered_map floatParameters; - std::unordered_map vector3Parameters; - std::unordered_map colorParameters; - std::unordered_map boolParameters; - - void SetFloat(const String& name, float value); - void SetVector3(const String& name, const Vector3& value); - void SetColor(const String& name, const Color& value); - void SetBool(const String& name, bool value); - - void Play(); - void Stop(); - void Pause(); - void SetSeed(uint32_t seed); -}; - -} // namespace VFXGraph -} // namespace Particles -} // namespace XCEngine -``` - ---- - -## 第十二章 网络系统 (Networking) - -### 12.1 网络系统架构 - -```cpp -namespace XCEngine { -namespace Networking { - -// 网络模式 -enum class NetworkMode { - Offline, - Host, - Client, - Server -}; - -// 传输层 -enum class TransportProtocol { - UDP, - TCP, - WebSocket -}; - -// 网络消息 -struct NetworkMessage { - uint32_t type; - uint32_t channel; - uint32_t senderId; - uint64_t sequence; - uint64_t timestamp; - std::vector data; -}; - -// 消息类型 -enum class MessageType : uint32_t { - // 内置 - Connect = 1, - Disconnect = 2, - Heartbeat = 3, - Ack = 4, - - // 游戏 - Spawn = 100, - Despawn = 101, - TransformUpdate = 102, - ComponentUpdate = 103, - RPC = 104, - SceneChange = 105, -}; - -// 网络通道 -enum class NetworkChannel { - Reliable, - Unreliable, - Voice, - Raw -}; - -// 网络连接 -class NetworkConnection { -public: - uint32_t GetId() const { return m_id; } - const String& GetAddress() const { return m_address; } - float GetRoundTripTime() const { return m_rtt; } - - bool IsConnected() const { return m_connected; } - - void Send(uint32_t type, const void* data, size_t size, NetworkChannel channel); - void Disconnect(); - -private: - uint32_t m_id = 0; - String m_address; - bool m_connected = false; - float m_rtt = 0.0f; -}; - -// 网络玩家 -struct NetworkPlayer { - uint32_t id; - String name; - NetworkConnection* connection = nullptr; - GameObject* avatar = nullptr; - bool isLocal = false; - bool isReady = false; -}; - -// 网络管理器 -class NetworkManager { -public: - static NetworkManager& Get(); - - void Initialize(const NetworkConfig& config); - void Shutdown(); - - // 模式 - NetworkMode GetMode() const { return m_mode; } - - // 主机 - bool StartHost(uint16_t port); - void StopHost(); - - // 客户端 - bool Connect(const String& address, uint16_t port); - void Disconnect(); - - // 玩家 - uint32_t GetLocalPlayerId() const { return m_localPlayerId; } - NetworkPlayer* GetPlayer(uint32_t id); - const std::vector& GetPlayers(); - - // 消息 - void Send(uint32_t playerId, uint32_t type, const void* data, size_t size, - NetworkChannel channel = NetworkChannel::Reliable); - void SendToAll(uint32_t type, const void* data, size_t size, - NetworkChannel channel = NetworkChannel::Reliable); - - // RPC - template - void RegisterRPC(const String& name, T* obj, void (T::*func)(NetworkReader&)); - - // 场景同步 - void SetNetworkedScene(Scene* scene); - void AddNetworkedObject(GameObject* gameObject); - void RemoveNetworkedObject(GameObject* gameObject); - - // 同步 - void Update(float deltaTime); - - // 事件 - Event OnPlayerConnected; - Event OnPlayerDisconnected; - Event OnMessageReceived; - -private: - NetworkMode m_mode = NetworkMode::Offline; - uint32_t m_localPlayerId = 0; - std::vector m_players; - - void* m_transport = nullptr; // ENet / LiteNetLib / custom -}; - -// 网络组件 -class NetworkIdentityComponent : public Component { -public: - void Update(float deltaTime) override; - - uint32_t networkId = 0; - bool isLocalPlayer = false; - bool isOwnedByClient = false; - bool canSpawn = true; - bool syncTransform = true; - bool syncComponents = true; -}; - -class NetworkTransformComponent : public Component { -public: - void Update(float deltaTime) override; - - enum class SyncMode { - None, - SyncTransform, - SyncPosition, - SyncPositionAndRotation, - SyncPositionRotationScale - }; - - SyncMode syncMode = SyncMode::SyncPositionRotationScale; - float snapThreshold = 0.5f; - float interpolationDelay = 0.1f; - - float syncInterval = 0.05f; - - bool enablePrediction = true; - float predictionTime = 0.1f; - - bool quantizePosition = true; - int positionPrecision = 0.01f; - bool quantizeRotation = true; - int rotationPrecision = 1; -}; - -// 远程过程调用 -namespace RPC { - -// RPC 属性 -enum class RpcTarget { - Server, - Others, - All, - Specific -}; - -enum class RpcMode { - Normal, - Buffered, - Unbuffered -}; - -// RPC 调用 -struct RpcCall { - String methodName; - std::vector args; - RpcTarget target; - uint32_t targetPlayer; - float timestamp; -}; - -// 注册 RPC -#define XE_RPC(methodName, target) \ - void methodName(NetworkReader& reader); \ - static void _RPC_##methodName(NetworkIdentityComponent* identity, NetworkReader& reader) { \ - auto* comp = identity->GetComponent(); \ - if (comp && comp->isOwnedByClient) { \ - auto* obj = static_cast(identity); \ - obj->methodName(reader); \ - } \ - } - -// 服务器/客户端 RPC -class RpcHandler { -public: - void Register(uint32_t hash, std::function handler); - void Call(NetworkIdentityComponent* identity, uint32_t methodHash, NetworkWriter& writer, - RpcTarget target, uint32_t targetPlayer = 0); - void Handle(NetworkMessage& msg); -}; - -} // namespace RPC - -// 网络序列化 -class NetworkSerializer { -public: - void Serialize(NetworkWriter& writer, const GameObject& gameObject); - void Deserialize(NetworkReader& reader, GameObject& gameObject); - - void SerializeTransform(NetworkWriter& writer, const TransformComponent& transform); - void DeserializeTransform(NetworkReader& reader, TransformComponent& transform); - - // 组件同步 - void SerializeComponent(NetworkWriter& writer, uint32_t componentTypeId, const void* component); - void* DeserializeComponent(NetworkReader& reader, uint32_t componentTypeId, GameObject* gameObject); -}; - -// 网络预测 -class ClientPrediction { -public: - struct PredictedState { - Vector3 position; - Quaternion rotation; - Vector3 velocity; - float timestamp; - }; - - void StorePredictedState(const Vector3& position, const Quaternion& rotation, - const Vector3& velocity); - void ApplyServerCorrection(const Vector3& serverPosition, const Quaternion& serverRotation); - - bool HasPredictionError() const; - void Reconcile(); - -private: - std::deque m_predictedStates; - std::deque m_unconfirmedStates; - - Vector3 m_lastServerPosition; - Quaternion m_lastServerRotation; -}; - -// 延迟补偿 -class LagCompensation { -public: - struct ServerFrame { - float timestamp; - std::vector entities; - }; - - void RecordFrame(const std::vector& entities); - EntitySnapshot GetInterpolatedSnapshot(float timestamp); - - bool Raycast(Ray& ray, float maxDistance, RaycastHit& hit); - -private: - std::deque m_frames; - float m_recordDuration = 1.0f; // 记录1秒 -}; - -} // namespace Networking -} // namespace XCEngine -``` - ---- - -## 第十三章 UI 系统 (In-Game UI / UGUI) - -> **架构说明**: Unity UGUI风格的UI系统。UI元素作为组件挂载在GameObject上。 -> 每个UI元素GameObject必须挂载RectTransform组件。 - -### 13.1 UI 系统架构 - -```cpp -namespace XCEngine { -namespace UI { - -// ============================================================================ -// UI 组件 (Unity UGUI风格) -// ============================================================================ - -// 矩形变换组件 (Unity RectTransform,继承 Transform) -class RectTransformComponent : public TransformComponent { -public: - void Update(float deltaTime) override; - - // 锚点 - Vector2 anchorMin = Vector2(0, 0); - Vector2 anchorMax = Vector2(0, 0); - Vector2 pivot = Vector2(0.5f, 0.5f); - - // 位置和大小 - Vector2 anchoredPosition = Vector2::Zero(); - Vector2 sizeDelta = Vector2(100, 100); - - // 偏移 (相对于锚点) - Vector2 offsetMin = Vector2::Zero(); - Vector2 offsetMax = Vector2::Zero(); - - // 计算属性 - Vector3 GetWorldPosition() const; - Vector2 GetWorldSize() const; - void SetSize(const Vector2& size); - -private: - // 父子层级由基类 TransformComponent 管理 - mutable Matrix4x4 m_worldMatrix; - mutable bool m_dirty = true; -}; - -// 图像组件 (Unity Image) -class ImageComponent : public Component { -public: - void Update(float deltaTime) override; - - ResourceGUID spriteGuid; - - enum class Type { - Simple, - Sliced, - Tiled, - Filled - }; - Type type = Type::Simple; - - float fillAmount = 1.0f; - enum class FillMethod { Horizontal, Vertical, Radial90, Radial180, Radial360 }; - FillMethod fillMethod = FillMethod::Horizontal; - bool fillClockwise = true; - int fillOrigin = 0; - - Color color = Color::White; - bool raycastTarget = true; - - Event<> OnSpriteChanged; -}; - -// 文本组件 (Unity Text / TextMeshPro) -class TextComponent : public Component { -public: - void Update(float deltaTime) override; - - String text; - ResourceGUID fontGuid; - - int fontSize = 14; - enum class FontStyle { Normal, Bold, Italic, BoldAndItalic }; - FontStyle fontStyle = FontStyle::Normal; - - Color color = Color::White; - Color outlineColor = Color::Black; - float outlineWidth = 0.0f; - - enum class Alignment { - UpperLeft, UpperCenter, UpperRight, - MiddleLeft, MiddleCenter, MiddleRight, - LowerLeft, LowerCenter, LowerRight - }; - Alignment alignment = Alignment::MiddleCenter; - - float lineSpacing = 1.0f; - bool richText = true; - bool horizontalOverflow = false; - bool verticalOverflow = true; - bool raycastTarget = true; -}; - -// 按钮组件 (Unity Button) -class ButtonComponent : public Component { -public: - void Update(float deltaTime) override; - - enum class Transition { - None, - ColorTint, - SpriteSwap, - Animation - }; - Transition transition = Transition::ColorTint; - - // 颜色状态 - Color normalColor = Color::White; - Color highlightedColor = Color(1, 1, 1, 0.8f); - Color pressedColor = Color(1, 1, 1, 0.6f); - Color disabledColor = Color(1, 1, 1, 0.5f); - - // 精灵状态 - ResourceGUID normalSpriteGuid; - ResourceGUID highlightedSpriteGuid; - ResourceGUID pressedSpriteGuid; - ResourceGUID disabledSpriteGuid; - - // 事件 - Event<> OnClick; - Event<> OnPointerDown; - Event<> OnPointerUp; - Event<> OnPointerEnter; - Event<> OnPointerExit; -}; - -// 输入字段组件 (Unity InputField / TMP_InputField) -class InputFieldComponent : public Component { -public: - void Update(float deltaTime) override; - - String text; - String placeholder; - - int characterLimit = 0; - bool multiLine = false; - bool isPassword = false; - char passwordChar = '*'; - - enum class ContentType { - Standard, - Autocorrected, - DecimalNumber, - IntegerNumber, - Name, - EmailAddress, - Password, - PIN, - MobileTelephoneNumber, - TelephoneNumber, - URL, - Alphabet - }; - ContentType contentType = ContentType::Standard; - - Color color = Color::White; - bool raycastTarget = true; - - // 事件 - Event OnValueChanged; - Event OnEndEdit; - Event<> OnSubmit; -}; - -// 滑动条组件 (Unity Slider) -class SliderComponent : public Component { -public: - void Update(float deltaTime) override; - - float value = 0.5f; - float minValue = 0.0f; - float maxValue = 1.0f; - bool wholeNumbers = false; - - enum class Direction { - LeftToRight, - RightToLeft, - BottomToTop, - TopToBottom - }; - Direction direction = Direction::LeftToRight; - - Color color = Color::White; - bool raycastTarget = true; - - // 事件 - Event OnValueChanged; - Event OnPointerDown; - Event OnPointerUp; -}; - -// 滚动视图组件 (Unity ScrollRect) -class ScrollViewComponent : public Component { -public: - void Update(float deltaTime) override; - - GameObject* contentGameObject = nullptr; - - enum class MovementType { - Elastic, - Clamped, - Unrestricted - }; - MovementType movementType = MovementType::Elastic; - - float elasticity = 0.1f; - float decelerationRate = 0.135f; - - GameObject* horizontalScrollbar = nullptr; - GameObject* verticalScrollbar = nullptr; - - bool enabled = true; - bool raycastTarget = true; -}; - -// 画布组件 (Unity Canvas) -class CanvasComponent : public Component { -public: - void Update(float deltaTime) override; - - enum class RenderMode { - ScreenSpaceOverlay, - ScreenSpaceCamera, - WorldSpace - }; - RenderMode renderMode = RenderMode::ScreenSpaceOverlay; - - GameObject* targetCamera = nullptr; - float planeDistance = 100.0f; - - float scaleFactor = 1.0f; - - int32_t renderOrder = 0; -}; - -// 画布缩放器组件 (Unity CanvasScaler) -class CanvasScalerComponent : public Component { -public: - void Update(float deltaTime) override; - - enum class ScaleMode { - ConstantPixelSize, - ScaleWithScreenSize, - ConstantPhysicalSize - }; - ScaleMode uiScaleMode = ScaleMode::ScaleWithScreenSize; - - Vector2 referenceResolution = Vector2(800, 600); - float referencePixelsPerUnit = 100.0f; - - enum class ScreenMatchMode { - Width, - Height, - Expand, - Shrink - }; - ScreenMatchMode screenMatchMode = ScreenMatchMode::Expand; - - float matchWidthOrHeight = 0.0f; -}; - -// 布局元素组件 (用于布局组) -class LayoutElementComponent : public Component { -public: - void Update(float deltaTime) override; - - float minWidth = 0.0f; - float minHeight = 0.0f; - float preferredWidth = 0.0f; - float preferredHeight = 0.0f; - float flexibleWidth = 0.0f; - float flexibleHeight = 0.0f; - int32_t layoutPriority = 0; -}; - -// 水平布局组组件 (Unity HorizontalLayoutGroupComponent) -class HorizontalLayoutGroupComponent : public Component { -public: - void Update(float deltaTime) override; - - float spacing = 0.0f; - bool childForceExpandWidth = true; - bool childForceExpandHeight = false; - bool childControlWidth = true; - bool childControlHeight = true; - int32_t childAlignment = 4; -}; - -// 垂直布局组组件 (Unity VerticalLayoutGroupComponent) -class VerticalLayoutGroupComponent : public Component { -public: - void Update(float deltaTime) override; - - float spacing = 0.0f; - bool childForceExpandWidth = false; - bool childForceExpandHeight = true; - bool childControlWidth = true; - bool childControlHeight = true; - int32_t childAlignment = 4; -}; - -// 网格布局组组件 (Unity GridLayoutGroupComponent) -class GridLayoutGroupComponent : public Component { -public: - void Update(float deltaTime) override; - - Vector2 cellSize = Vector2(100, 100); - Vector2 spacing = Vector2::Zero(); - int32_t startCorner = 0; - int32_t startAxis = 0; - int32_t constraint = 0; - int32_t constraintCount = 2; -}; - -// ============================================================================ -// UI 系统 (处理布局、事件等 - 传统Unity风格) -// ============================================================================ - -class UISystem { -public: - void Initialize(); - void Shutdown(); - void Update(float deltaTime); - - // 射线检测 - GameObject* Raycast(const Vector2& screenPosition); - std::vector RaycastAll(const Vector2& screenPosition); - - // 焦点管理 - void SetFocus(GameObject* element); - GameObject* GetFocusedElement(); - void ClearFocus(); - -private: - void ProcessLayoutGroups(); - void ProcessRaycasts(); - void ProcessEvents(); - - GameObject* m_focusedElement = nullptr; - GameObject* m_hoveredElement = nullptr; -}; - -// ============================================================================ -// UI 渲染器 (底层实现) -// ============================================================================ - -class UIRenderer { -public: - void Initialize(); - void Shutdown(); - - void Render(Scene& scene); - - // 批处理 - void BeginBatch(); - void AddToBatch(GameObject* gameObject, const Matrix4x4& transform, const Mesh* mesh, const Material* material); - void EndBatch(); - - // 默认材质 - Material* defaultMaterial = nullptr; - Material* defaultSpriteMaterial = nullptr; -}; - -// ============================================================================ -// 画布渲染器组件 (挂载在 Canvas 上) -class CanvasRendererComponent : public Component { -public: - void Update(float deltaTime) override; - - bool renderMode = true; - uint32_t vertexCount = 0; - uint32_t indexCount = 0; - bool hasPopulateMesh = false; - - std::function onPopulateMesh; -}; - -// ============================================================================ -// UI 事件系统 -// ============================================================================ - -class UIEventSystem { -public: - static UIEventSystem& Get(); - - // 指针事件 - void OnPointerDown(PointerEvent& event); - void OnPointerUp(PointerEvent& event); - void OnPointerMove(PointerEvent& event); - void OnPointerClick(PointerEvent& event); - - // 键盘事件 - void OnKeyDown(KeyEvent& event); - void OnKeyUp(KeyEvent& event); - void OnTextInput(TextInputEvent& event); - - // 拖放事件 - void OnDragStart(DragEvent& event); - void OnDrag(DragEvent& event); - void OnDragEnd(DragEvent& event); - -private: - GameObject* m_currentPointerOver = nullptr; - GameObject* m_draggedElement = nullptr; - GameObject* m_focusedElement = nullptr; -}; - -} // namespace UI -} // namespace XCEngine -``` - ---- - -## 第十四章 编辑器架构 (Editor) - -### 14.1 编辑器整体架构 - -```cpp -namespace XCEngine { -namespace Editor { - -// 编辑器应用 -class EditorApplication { -public: - static EditorApplication& Get(); - - void Initialize(); - void Shutdown(); - void Run(); - - // 主循环 - void Update(); - void Render(); - - // 项目 - Project* GetCurrentProject() { return m_currentProject.get(); } - void NewProject(const String& path); - void OpenProject(const String& path); - void SaveProject(); - void CloseProject(); - - // 编辑器状态 - enum class Mode { - Stopped, - Playing, - Paused, - Step - }; - Mode GetMode() const { return m_mode; } - void Play(); - void Pause(); - void Stop(); - void Step(); - - // 场景操作 - void NewScene(); - void OpenScene(const String& path); - void SaveScene(); - void SaveSceneAs(const String& path); - - // 窗口 - void RegisterWindow(EditorWindow* window); - void UnregisterWindow(EditorWindow* window); - -private: - std::unique_ptr m_currentProject; - Mode m_mode = Mode::Stopped; - std::vector> m_windows; -}; - -// 编辑器窗口基类 -class EditorWindow { -public: - virtual ~EditorWindow() = default; - - virtual void OnOpen() {} - virtual void OnClose() {} - virtual void Update(float deltaTime) {} - virtual void OnGUI() {} - - virtual bool IsOpen() const { return m_open; } - void SetOpen(bool open) { m_open = open; } - - virtual ImVec2 GetSize() const { return ImVec2(400, 300); } - virtual ImVec2 GetPosition() const { return ImVec2(0, 0); } - -protected: - String m_title; - bool m_open = true; -}; - -// 项目 -class Project { -public: - String name; - String path; - String assemblyName; - - // 设置 - ProjectSettings settings; - - // 资源(使用编辑器专用的资源数据库) - EditorAssetDatabase* GetAssetDatabase() { return m_assetDatabase.get(); } - - // 场景 - std::vector GetScenes(); - void AddScene(const String& scenePath); - void RemoveScene(const String& scenePath); - - // 构建 - void Build(const BuildSettings& settings); - -private: - std::unique_ptr m_assetDatabase; - std::vector m_scenes; -}; - -// 编辑器资源数据库(继承自 Resources::AssetDatabase) -class EditorAssetDatabase { -public: - void Initialize(const String& projectPath); - void Shutdown(); - - // 资源查找 - Resources::ResourceGUID FindAsset(const String& guid) const; - Resources::ResourceGUID FindAssetByPath(const String& path) const; - String GetAssetPath(const Resources::ResourceGUID& guid) const; - - // 资源操作 - void ImportAsset(const String& sourcePath); - void ReimportAsset(const Resources::ResourceGUID& guid); - void DeleteAsset(const Resources::ResourceGUID& guid); - - // 刷新 - void Refresh(); - - // GUID 生成 - Resources::ResourceGUID GenerateGUID(); - - // 编辑器特定功能 - void SelectAsset(const Resources::ResourceGUID& guid); - std::vector GetSelectedAssets() const; - void OpenInExternalEditor(const Resources::ResourceGUID& guid); - - // 资源变换监听 - Core::Event OnAssetRenamed; - Core::Event OnAssetDeleted; - -private: - void ScanDirectory(const String& dirPath); - void ImportAssetInternal(const String& sourcePath, const String& destPath); - - struct AssetMetadata { - Resources::ResourceGUID guid; - String path; - String extension; - int64_t fileSize; - int64_t lastModified; - String importerType; - JsonObject metadata; - }; - - std::unordered_map m_guidToAsset; - std::unordered_map m_pathToGuid; - - std::vector m_selectedAssets; -}; - -// 资源导入器 -class AssetImporter { -public: - virtual ~AssetImporter() = default; - - virtual bool CanImport(const String& extension) const = 0; - virtual void Import(const String& sourcePath, const ResourceGUID& guid, - const ImportSettings& settings) = 0; - virtual ImportSettings* GetDefaultSettings() const = 0; - -protected: - String GetOutputPath(const String& sourcePath, const ResourceGUID& guid); -}; - -// 具体导入器 -class TextureImporter : public AssetImporter { -public: - bool CanImport(const String& extension) const override; - void Import(const String& sourcePath, const ResourceGUID& guid, - const ImportSettings& settings) override; - - struct Settings : ImportSettings { - TextureType textureType = TextureType::Texture2D; - int maxSize = 2048; - TextureFormat format = TextureFormat::RGBA; - bool generateMipmaps = true; - bool sRGB = true; - FilterMode filterMode = FilterMode::Bilinear; - WrapMode wrapMode = WrapMode::Repeat; - }; -}; - -class MeshImporter : public AssetImporter { -public: - bool CanImport(const String& extension) const override; - void Import(const String& sourcePath, const ResourceGUID& guid, - const ImportSettings& settings) override; -}; - -class MaterialImporter : public AssetImporter { -public: - bool CanImport(const String& extension) const override; - void Import(const String& sourcePath, const ResourceGUID& guid, - const ImportSettings& settings) override; -}; - -// 编辑器场景管理 (Unity Editor 风格) -// -// 编辑器/运行时模式切换机制: -// - Stop (编辑模式): 编辑器直接操作 m_editingScene -// - Play (运行模式): -// 1. 深拷贝 m_editingScene -> m_runtimeScene (包括所有Entity和Component) -// 2. 运行 m_runtimeScene -// 3. 用户在运行时对Scene的修改都在 m_runtimeScene 中 -// - Stop -> Play 循环: -// 1. 销毁旧的 m_runtimeScene (如果存在) -// 2. 深拷贝 m_editingScene -> m_runtimeScene -// - Play -> Stop: -// 1. 销毁 m_runtimeScene -// 2. 如果用户选择了 "Apply Changes",将 runtime 改动的 GameObject 同步回 editing -// 3. 恢复编辑模式 -class EditorSceneManager { -public: - static EditorSceneManager& Get(); - - // 编辑场景 (编辑模式下使用) - Scene* GetEditingScene() { return m_editingScene.get(); } - - // 运行时场景 (Play 模式下使用) - // 注意:运行时场景是编辑场景的副本,不是同一个对象 - Scene* GetRuntimeScene() { return m_runtimeScene.get(); } - - // Play/Stop 控制 - enum class Mode { - Stopped, // 编辑模式 - Playing, // 播放模式 - Paused // 暂停模式 - }; - - void Play(); // 开始播放:从 editing 拷贝到 runtime - void Pause(); // 暂停 - void Unpause(); // 继续 - void Stop(); // 停止播放:销毁 runtime,可选 Apply Changes - void Step(); // 单步执行一帧 - - Mode GetMode() const { return m_mode; } - - // Apply Changes (Play -> Stop 时可选) - // 将 runtime 场景中的运行时数据同步回 editing 场景 - enum class ApplyChangesMode { - None, // 不应用任何更改 - All, // 应用所有更改 - Selected // 只应用选中的GameObject - }; - void SetApplyChangesMode(ApplyChangesMode mode); - void ApplyRuntimeChanges(); - - // 场景操作 - void CreateNewScene(); - void OpenScene(const String& path); - void SaveScene(const String& path); - - // 撤销/重做 - void Undo(); - void Redo(); - bool CanUndo() const; - bool CanRedo() const; - - // GameObject操作 - void DuplicateGameObject(GameObject* gameObject); - void DeleteGameObject(GameObject* gameObject); - void PasteGameObject(); - void CopyGameObject(GameObject* gameObject); - - // 预制件 - void CreatePrefab(GameObject* gameObject, const String& path); - GameObject* InstantiatePrefab(const String& path); - void UnpackPrefab(GameObject* gameObject); - -private: - void CopySceneToRuntime(); // 深拷贝编辑场景到运行时场景 - void SyncRuntimeToEditing(); // 同步运行时更改回编辑场景 - - std::unique_ptr m_editingScene; - std::unique_ptr m_runtimeScene; - - Mode m_mode = Mode::Stopped; - ApplyChangesMode m_applyChangesMode = ApplyChangesMode::None; - - std::deque m_undoStack; - std::deque m_redoStack; -}; - -// 撤销/重做 -class UndoRedoSystem { -public: - void PushAction(std::unique_ptr action); - void Undo(); - void Redo(); - - bool CanUndo() const { return !m_undoStack.empty(); } - bool CanRedo() const { return !m_redoStack.empty(); } - - void Clear(); - -private: - std::deque> m_undoStack; - std::deque> m_redoStack; - size_t m_maxStackSize = 100; -}; - -// 编辑器工具 -namespace Tools { - -class EditorTool { -public: - virtual ~EditorTool() = default; - virtual void OnActivate() {} - virtual void OnDeactivate() {} - virtual void Update(float deltaTime) {} - virtual void OnSceneGUI() {} - virtual void OnObjectGUI(GameObject* gameObject) {} - - virtual const char* GetName() const = 0; - virtual const char* GetIcon() const = 0; - -protected: - bool m_active = false; -}; - -class SelectionTool : public EditorTool { -public: - const char* GetName() const override { return "Select"; } - void Update(float deltaTime) override; - void OnSceneGUI() override; -}; - -class MoveTool : public EditorTool { -public: - const char* GetName() const override { return "Move"; } - void Update(float deltaTime) override; - void OnSceneGUI() override; -}; - -class RotateTool : public EditorTool { -public: - const char* GetName() const override { return "Rotate"; } - void Update(float deltaTime) override; - void OnSceneGUI() override; -}; - -class ScaleTool : public EditorTool { -public: - const char* GetName() const override { return "Scale"; } - void Update(float deltaTime) override; - void OnSceneGUI() override; -}; - -class Gizmo { -public: - enum class Mode { - Translate, - Rotate, - Scale, - Rect, - Transform - }; - - enum class Space { - World, - Local - }; - - enum class PivotMode { - Center, - Pivot - }; - - void SetMode(Mode mode); - void SetSpace(Space space); - void SetPivot(PivotMode pivot); - - void BeginManipulation(GameObject* gameObject); - void Draw(GameObject* gameObject); - void EndManipulation(); - - bool IsManipulating() const { return m_manipulating; } - Matrix4x4 GetManipulationDelta() const; - -private: - Mode m_mode = Mode::Translate; - Space m_space = Space::World; - PivotMode m_pivot = PivotMode::Center; - bool m_manipulating = false; - GameObject* m_selectedGameObject = nullptr; - Matrix4x4 m_startMatrix; -}; - -// 编辑器相机 -class EditorCamera { -public: - void Initialize(); - void Shutdown(); - - void Update(float deltaTime); - - void SetProjection(float fov, float aspect, float near, float far); - void SetOrthographic(float size, float near, float far); - - Matrix4x4 GetViewMatrix() const; - Matrix4x4 GetProjectionMatrix() const; - - // 控制 - void Orbit(const Vector2& delta); - void Pan(const Vector2& delta); - void Zoom(float delta); - - void FocusOn(GameObject* gameObject); - void FocusOn(const BoundingBox& bounds); - - // 状态 - float GetDistance() const { return m_distance; } - void SetDistance(float distance); - - Vector3 GetPosition() const; - Vector3 GetForward() const; - Vector3 GetTarget() const { return m_target; } - -private: - Vector3 m_target = Vector3::Zero(); - float m_distance = 10.0f; - float m_pitch = 0.0f; - float m_yaw = 0.0f; - - float m_fov = 60.0f; - float m_near = 0.1f; - float m_far = 1000.0f; - bool m_orthographic = false; - - // 交互 - bool m_isOrbiting = false; - bool m_isPanning = false; -}; - -} // namespace Tools - -// 编辑器面板 -namespace Panels { - -class HierarchyPanel { -public: - void OnGUI(); - - void SetSearchFilter(const String& filter); - void DrawGameObjectTree(GameObject* parent, int& idCounter); - -private: - String m_searchFilter; - GameObject* m_contextMenuGameObject = nullptr; -}; - -class InspectorPanel { -public: - void OnGUI(); - - void DrawGameObject(GameObject* gameObject); - void DrawComponent(uint32_t componentTypeId, void* component); - void DrawAddComponentMenu(); - -private: - GameObject* m_selectedGameObject = nullptr; -}; - -class SceneViewPanel { -public: - void OnGUI(); - void Render(); - - Tools::EditorCamera& GetCamera() { return m_camera; } - -private: - Tools::EditorCamera m_camera; - bool m_gridVisible = true; - bool m_gizmosVisible = true; -}; - -class GameViewPanel { -public: - void OnGUI(); - void Render(); - - void SetResolution(int width, int height); - void SetFullscreen(bool fullscreen); - -private: - int m_width = 1280; - int m_height = 720; - bool m_fullscreen = false; - bool m_vsync = true; -}; - -class ProjectPanel { -public: - void OnGUI(); - - void DrawAssetBrowser(); - void DrawFolderTree(); - - void OnAssetSelected(ResourceGUID guid); - -private: - String m_currentPath; - std::vector m_selectedAssets; - ViewMode m_viewMode = ViewMode::Grid; -}; - -class ConsolePanel { -public: - void OnGUI(); - - void Log(const String& message, LogLevel level); - void Clear(); - void Filter(LogLevel minLevel); - -private: - struct LogEntry { - String message; - LogLevel level; - String source; - int64_t timestamp; - }; - - std::vector m_logs; - LogLevel m_filterLevel = LogLevel::Info; -}; - -// 动画曲线编辑器 -class AnimationCurveEditor { -public: - void OnGUI(); - - void SetCurve(AnimationCurve* curve); - void AddKey(float time, float value); - void RemoveKey(int index); - -private: - AnimationCurve* m_curve = nullptr; - int m_selectedKey = -1; -}; - -// 着色器编辑器 -class ShaderEditor { -public: - void OnGUI(); - - void OpenShader(const String& path); - void SaveShader(); - void CompileShader(); - - void SetPreviewMesh(Mesh* mesh); - void SetPreviewRotation(const Vector3& rotation); - -private: - String m_shaderPath; - bool m_compileErrors = false; - String m_errorMessage; -}; - -// 材质编辑器 -class MaterialEditor { -public: - void OnGUI(); - - void OpenMaterial(Material* material); - void SaveMaterial(); - - void DrawProperty(MaterialProperty* property); - -private: - Material* m_material = nullptr; -}; - -// 偏好设置 -class PreferencesWindow : public EditorWindow { -public: - void OnGUI() override; - -private: - void DrawGeneralSettings(); - void DrawThemeSettings(); - void DrawInputSettings(); - void DrawShortcutSettings(); -}; - -} // namespace Panels - -// 编辑器渲染 -class EditorRenderView { -public: - void Initialize(); - void Shutdown(); - - void RenderSceneView(Panels::SceneViewPanel* panel); - void RenderGameView(Panels::GameViewPanel* panel); - - void SetRenderTarget(RenderTexture* target); - -private: - void SetupCamera(EditorCamera& camera); - void RenderGizmos(); - - RenderTexture* m_renderTarget = nullptr; -}; - -} // namespace Editor -} // namespace XCEngine -``` - ---- - -## 第十五章 工具链 (Toolchain) - -### 15.1 资源处理管道 - -```cpp -namespace XCEngine { -namespace Tools { - -// 资源处理器 -class AssetProcessor { -public: - static AssetProcessor& Get(); - - void Initialize(const String& projectPath); - void Shutdown(); - - // 导入 - void ImportAsset(const String& sourcePath); - void ImportAllAssets(); - - // 烘焙 - void CookScene(Scene* scene, const String& outputPath); - void CookMaterial(Material* material, const String& outputPath); - void CookTexture(Texture* texture, const String& outputPath); - - // 构建 - void BuildProject(const BuildSettings& settings); - -private: - void ProcessImportQueue(); - void WriteCookedData(const String& path, const void* data, size_t size); - - std::queue m_importQueue; -}; - -// 着色器编译器 -class ShaderCompiler { -public: - void Initialize(); - void Shutdown(); - - // 编译 - CompiledShader Compile(const ShaderSource& source, const ShaderCompileOptions& options); - bool CompileFile(const String& inputPath, const String& outputPath, - const ShaderCompileOptions& options); - - // 工具 - void GenerateShaderIncludes(); - void StripDebugInfo(CompiledShader& shader); - - // 工具链 - enum class ShaderProfile { - DXIL, // DirectX 12 - SPIRV, // Vulkan - MSL, // Metal - GLSL, // OpenGL - WGSL // WebGPU - }; - - void SetProfile(ShaderProfile profile); - void AddPreprocessorDefine(const String& define); - -private: - struct CompilerConfig { - ShaderProfile profile; - std::vector defines; - bool generateDebugInfo = false; - bool optimize = true; - int optimizationLevel = 3; - }; - - CompilerConfig m_config; -}; - -// 场景烘焙器 -class SceneCooker { -public: - void Cook(Scene* scene, const CookSettings& settings); - - // 灯光烘焙 - void BakeLightmaps(Scene* scene); - void BakeReflectionProbes(Scene* scene); - void BakeAmbientOcclusion(Scene* scene); - - // 导航烘焙 - void BakeNavigationMesh(Scene* scene); - - // 遮挡剔除 - void BakeOcclusionCulling(Scene* scene); - - // 静态批处理 - void BakeStaticBatches(Scene* scene); - -private: - void CookGameObject(GameObject* gameObject); - void WriteCookedScene(const String& path, const CookedScene& data); -}; - -// 构建工具 -class BuildTool { -public: - struct BuildSettings { - String outputPath; - String projectName; - - // 平台 - BuildPlatform platform; - BuildConfiguration config = BuildConfiguration::Release; - - // 脚本 - bool compileScripts = true; - bool generateDebugSymbols = false; - - // 资源 - bool compressAssets = true; - bool stripUnusedResources = true; - - // 目标 - bool buildExe = true; - bool buildDLC = false; - bool bundleResources = true; - }; - - void Build(const BuildSettings& settings); - void BuildPlayer(const BuildSettings& settings); - void BuildAssetBundle(const BuildSettings& settings); - -private: - void PrepareBuildDirectory(const BuildSettings& settings); - void CompileScripts(const BuildSettings& settings); - void CookResources(const BuildSettings& settings); - void CopyEngineFiles(const BuildSettings& settings); - void LinkExecutable(const BuildSettings& settings); -}; - -// 性能分析器工具 -class ProfilerCapture { -public: - void StartCapture(); - void StopCapture(); - - void SaveCapture(const String& path); - void LoadCapture(const String& path); - - // 分析 - void GenerateReport(); - float GetTotalFrameTime() const; - float GetCPUFrameTime() const; - float GetGPUFrameTime() const; - -private: - struct FrameData { - uint64_t timestamp; - std::vector events; - }; - - std::vector m_frames; - bool m_capturing = false; -}; - -} // namespace Tools -} // namespace XCEngine -``` - ---- - -## 第十六章 平台层 (Platform) - -### 16.1 跨平台抽象 - -```cpp -namespace XCEngine { -namespace Platform { - -// 平台类型 -enum class PlatformType { - Windows, - Linux, - macOS, - Android, - iOS, - Web, - PlayStation, - Xbox, - Nintendo -}; - -// 键码 -enum class KeyCode { - None = 0, - A = 4, B = 5, C = 6, D = 7, E = 8, F = 9, G = 10, - H = 11, I = 12, J = 13, K = 14, L = 15, M = 16, N = 17, - O = 18, P = 19, Q = 20, R = 21, S = 22, T = 23, U = 24, - V = 25, W = 26, X = 27, Y = 28, Z = 29, - F1 = 122, F2 = 120, F3 = 99, F4 = 118, F5 = 96, F6 = 97, - F7 = 98, F8 = 100, F9 = 101, F10 = 109, F11 = 103, F12 = 111, - Space = 49, Tab = 48, Enter = 36, Escape = 53, - LeftShift = 56, RightShift = 60, LeftCtrl = 59, RightCtrl = 62, - LeftAlt = 58, RightAlt = 61, - Up = 126, Down = 125, Left = 123, Right = 124, - Home = 115, End = 119, PageUp = 116, PageDown = 121, - Delete = 51, Backspace = 51 -}; - -// 鼠标按钮 -enum class MouseButton { - Left = 0, - Right = 1, - Middle = 2, - Button4 = 3, - Button5 = 4 -}; - -// 触摸状态 -struct TouchState { - int touchId; - Vector2 position; - Vector2 deltaPosition; - float deltaTime; - int tapCount; - enum Phase { Began, Moved, Stationary, Ended, Canceled } phase; -}; - -// 指针事件 -struct PointerEvent { - int pointerId; - Vector2 position; - Vector2 deltaPosition; - float pressure; - MouseButton button; - enum Type { Pressed, Released, Moved, Wheel, Hover } type; -}; - -// 按键事件 -struct KeyEvent { - KeyCode keyCode; - bool alt; - bool ctrl; - bool shift; - bool meta; - enum Type { Down, Up, Repeat } type; -}; - -// 文本输入事件 -struct TextInputEvent { - char character; - String text; -}; - -// 拖放事件 -struct DragEvent { - Vector2 position; - Vector2 delta; - int pointerId; -}; - -// 文件变化事件 -struct FileChangeEvent { - String path; - enum Type { Created, Modified, Deleted, Renamed } type; -}; - -// 手势事件 -struct GestureEvent { - Vector2 position; - float deltaScale; - float deltaRotation; - int tapCount; - enum Type { None, Tap, DoubleTap, Pinch, Rotate, Pan } type; -}; - -// 窗口 -class Window { -public: - virtual ~Window() = default; - - virtual void Create(const WindowDesc& desc) = 0; - virtual void Destroy() = 0; - - virtual void Show() = 0; - virtual void Hide() = 0; - virtual void Minimize() = 0; - virtual void Maximize() = 0; - virtual void Restore() = 0; - virtual void SetFullscreen(bool fullscreen) = 0; - - virtual void SetTitle(const String& title) = 0; - virtual void SetSize(int width, int height) = 0; - virtual void SetPosition(int x, int y) = 0; - - virtual bool IsClosed() const = 0; - virtual bool IsFocused() const = 0; - virtual bool IsMinimized() const = 0; - virtual bool IsMaximized() const = 0; - virtual bool IsFullscreen() const = 0; - - virtual void* GetNativeHandle() const = 0; - - // 事件 - Event OnResize; - Event OnMove; - Event<> OnClose; - Event<> OnFocus; - Event<> OnBlur; - Event<> OnMinimize; - Event<> OnMaximize; - Event<> OnRestore; -}; - -// 输入 -class InputSystem { -public: - // 键盘 - bool IsKeyDown(KeyCode key) const; - bool IsKeyUp(KeyCode key) const; - bool IsKeyPressed(KeyCode key) const; - - // 鼠标 - Vector2 GetMousePosition() const; - Vector2 GetMouseDelta() const; - float GetMouseScrollDelta() const; - bool IsMouseButtonDown(MouseButton button) const; - bool IsMouseButtonUp(MouseButton button) const; - bool IsMouseButtonClicked(MouseButton button) const; - - // 手柄 - int GetJoystickCount() const; - bool IsJoystickConnected(int joystick) const; - Vector2 GetJoystickAxis(int joystick, int axis) const; - bool IsJoystickButtonDown(int joystick, int button) const; - - // 触摸 - int GetTouchCount() const; - TouchState GetTouch(int index) const; - - // 手势 - Event OnGesture; -}; - -// 注意:FileSystem 定义在 Platform 命名空间中 -// Core::IO 只保留流抽象 (IStream, FileReader, FileWriter) -} - -namespace XCEngine { -namespace Platform { - -// 文件系统 - 统一的跨平台文件系统抽象 -class FileSystem { -public: - static FileSystem& Get(); - - // 路径操作 - String GetWorkingDirectory(); - String GetExecutablePath(); - String GetUserDirectory(); - String GetAppDataDirectory(); - String GetTempDirectory(); - - // 文件操作 - bool FileExists(const String& path); - bool DirectoryExists(const String& path); - bool CreateDirectory(const String& path); - bool DeleteFile(const String& path); - bool DeleteDirectory(const String& path); - - // 读写 - std::unique_ptr OpenRead(const String& path); - std::unique_ptr OpenWrite(const String& path); - std::unique_ptr OpenAppend(const String& path); - - // 遍历 - void EnumerateFiles(const String& directory, const String& pattern, - std::vector& results); - void EnumerateDirectories(const String& directory, - std::vector& results); - - // 观察 - void WatchDirectory(const String& directory, - std::function callback); -}; - -// 时间 - -// 时间 -class Time { -public: - static float GetTime(); - static float GetUnscaledTime(); - static float GetDeltaTime(); - static float GetUnscaledDeltaTime(); - - static void SetTimeScale(float scale); - static float GetTimeScale() { return m_timeScale; } - - static uint64_t GetFrameCount() { return m_frameCount; } - static int GetFrameRate() { return m_frameRate; } - - static void SetTargetFrameRate(int rate); - static void BeginFrame(); - static void EndFrame(); - -private: - static float m_timeScale; - static uint64_t m_frameCount; - static int m_frameRate; -}; - -// 内存映射文件 -class MemoryMappedFile { -public: - bool Open(const String& path); - void Close(); - - void* GetData() const { return m_data; } - size_t GetSize() const { return m_size; } - -private: - void* m_handle = nullptr; - void* m_data = nullptr; - size_t m_size = 0; -}; - -// 进程 -class Process { -public: - bool Launch(const String& command, const std::vector& args); - void Terminate(); - bool IsRunning() const; - - int GetExitCode() const; - int WaitForExit(); - - void Write(const void* data, size_t size); - String ReadOutput(); - String ReadError(); - -private: - void* m_handle = nullptr; -}; - -// 动态库 -class DynamicLibrary { -public: - bool Load(const String& path); - void Unload(); - - void* GetFunction(const String& name); - bool IsLoaded() const; - -private: - void* m_handle = nullptr; -}; - -// 系统信息 -class SystemInfo { -public: - static String GetOSName(); - static String GetOSVersion(); - static String GetMachineName(); - static String GetUserName(); - - static int GetProcessorCount(); - static int GetProcessorCoreCount(); - static int GetThreadCount(); - - static uint64_t GetTotalMemory(); - static uint64_t GetAvailableMemory(); - - static String GetGPUName(); - static uint64_t GetGPUMemory(); - static int GetGPUMonitorCount(); - - static bool HasNvidiaGPU(); - static bool HasAmdGPU(); - static bool HasIntelGPU(); -}; - -} // namespace Platform -} // namespace XCEngine -``` - ---- - -## 第十七章 构建与部署 - -### 17.1 CMake 项目结构 - -``` -XCVolumeRenderer/ -├── CMakeLists.txt # 根 CMake 配置 -├── cmake/ -│ ├── FindXXX.cmake # 第三方库查找 -│ ├── XCEngineDefaults.cmake # 默认设置 -│ └── Utils.cmake # 工具宏 -│ -├── engine/ # 引擎核心 -│ ├── CMakeLists.txt -│ ├── include/ -│ └── src/ -│ -├── editor/ # 编辑器 -│ ├── CMakeLists.txt -│ └── src/ -│ -├── runtime/ # 运行时 -│ ├── CMakeLists.txt -│ └── src/ -│ -├── tools/ # 工具链 -│ ├── AssetProcessor/ -│ ├── ShaderCompiler/ -│ └── SceneCooker/ -│ -└── third_party/ # 第三方库 - ├── CMakeLists.txt - ├── imgui/ - ├── stb/ - └── ... -``` - -### 17.2 CMake 配置示例 - -```cmake -# 根 CMakeLists.txt -cmake_minimum_required(VERSION 3.20) -project(XCGameEngine VERSION 1.0.0) - -# 选项 -option(BUILD_EDITOR "Build editor" ON) -option(BUILD_RUNTIME "Build runtime" ON) -option(BUILD_TOOLS "Build tools" ON) -option(BUILD_SHADERS "Build shaders" ON) - -# 加载工具 -list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") -include(XCEngineDefaults) - -# 依赖 -find_package(Threads REQUIRED) -find_package(D3D12 REQUIRED) -find_package(Vulkan REQUIRED) - -# 子目录 -add_subdirectory(third_party) -add_subdirectory(engine) - -if(BUILD_EDITOR) - add_subdirectory(editor) -endif() - -if(BUILD_RUNTIME) - add_subdirectory(runtime) -endif() - -if(BUILD_TOOLS) - add_subdirectory(tools) -endif() -``` - -### 17.3 构建流程 - -``` -┌─────────────────────────────────────────────────────────────────────────────┐ -│ Build Pipeline │ -├─────────────────────────────────────────────────────────────────────────────┤ -│ │ -│ Source Code ──► Compile ──► Link ──► Engine Library │ -│ │ │ -│ │ │ -│ ▼ │ -│ Assets ──► Import ──► Cook ──► Pack ──► Resource Bundle │ -│ │ -│ │ │ -│ │ │ -│ ▼ │ -│ Shaders ──► Compile ──► Pack ──► Shader Cache │ -│ │ -│ │ │ -│ │ │ -│ ▼ │ -│ ┌─────────────────────────────────────────────────────────────────────┐ │ -│ │ Final Output │ │ -│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────────┐ │ │ -│ │ │ Executable │ │ Resources │ │ Engine Libraries │ │ │ -│ │ │ (EXE/DLL) │ │ (.pak) │ │ (.lib/.dll) │ │ │ -│ │ └──────────────┘ └──────────────┘ └──────────────────────────┘ │ │ -│ └─────────────────────────────────────────────────────────────────────┘ │ -│ │ -└─────────────────────────────────────────────────────────────────────────────┘ -``` - ---- - -## 第十八章 总结与实现建议 - -### 18.1 架构核心要点 - -1. **分层设计**: Platform → Core → Engine → Editor/Runtime -2. **组件系统**: 类Unity的GameObject+Component模式 -3. **渲染抽象**: 跨 API 的渲染接口,便于扩展 -4. **资源管理**: 完整的导入→处理→缓存→加载管道 -5. **工具链**: 从源代码到最终产品的完整构建系统 - -### 18.2 实现优先级建议 - -#### 第一阶段 (基础框架) -- [ ] 数学库 (Vector, Matrix, Quaternion, Transform) -- [ ] 基础容器 (Array, String, HashMap) -- [ ] 内存管理 (Allocator, Pool) -- [ ] 线程系统 (Thread, Mutex, TaskSystem) -- [ ] 日志系统 -- [ ] 文件系统抽象 - -#### 第二阶段 (组件系统 + 渲染) -- [ ] 组件系统核心 (GameObject, Component) -- [ ] 基础组件 (Transform, RenderMesh) -- [ ] 渲染抽象层 (Device, CommandList) -- [ ] D3D12/Vulkan 后端 -- [ ] 基础渲染管线 (GBuffer, Lighting) - -#### 第三阶段 (功能系统) -- [ ] 资源系统 (ResourceManager, AssetDatabase) -- [ ] 场景系统 (Scene, SceneManager) -- [ ] 物理系统 (基础碰撞检测) -- [ ] 输入系统 -- [ ] UI 框架 - -#### 第四阶段 (编辑器) -- [ ] 编辑器 UI (ImGui) -- [ ] 场景视图/游戏视图 -- [ ] 层级面板/检视面板 -- [ ] 资源浏览器 -- [ ] 工具 (Gizmo, 编辑器相机) - -#### 第五阶段 (高级功能) -- [ ] 动画系统 -- [ ] 粒子系统 -- [ ] 音频系统 -- [ ] 网络系统 -- [ ] 脚本系统 - -### 18.3 代码组织建议 - -``` -engine/src/ -├── Core/ # 1-2周 -│ ├── Math/ -│ ├── Containers/ -│ ├── Memory/ -│ ├── Threading/ -│ └── Debug/ -├── Components/ # 2-3周 -│ ├── GameObject.cpp -│ ├── Component.cpp -│ └── System.cpp -├── Renderer/ # 3-4周 -│ ├── Device.cpp -│ ├── CommandList.cpp -│ ├── Pipeline.cpp -│ └── Passes/ -├── Resources/ # 2周 -│ ├── ResourceManager.cpp -│ ├── AssetDatabase.cpp -│ └── Loaders/ -├── Scene/ # 1周 -├── Physics/ # 2-3周 -├── Scripting/ # 3-4周 (可选) -└── Platform/ # 1周 - ├── Windows.cpp - └── ... - -editor/src/ -├── Application.cpp # 1周 -├── Panels/ # 2周 -├── Tools/ # 2周 -├── Windows/ # 2周 -└── ImGui/ # 1周 -``` - -### 18.4 技术选型建议 - -| 子系统 | 推荐方案 | 备选 | -|--------|----------|------| -| 图形 API | D3D12 + Vulkan | Metal, OpenGL | -| 物理 | PhysX | Bullet, Box2D | -| 脚本 | C# (Mono/.NET) | Lua, AngelScript | -| UI (编辑器) | ImGui | Qt, Custom | -| UI (运行时) | 自研 Canvas | DearImgui, RmlUI | -| 资源格式 | 自研 Binary + JSON | glTF, USD | -| 压缩 | LZ4 + ZSTD | LZMA, LZHAM | -| 网络 | 自研 / ENet | LiteNetLib | - -### 18.5 性能目标 - -- 60 FPS 稳定运行 (1080p, 中等画质) -- 10000+ 实体同屏 -- 加载时间 < 3秒 (典型场景) -- 编辑器响应 < 16ms - ---- - -*文档版本: 1.15* -*最后更新: 2026-03-13* diff --git a/docs/used/XCUI_Hierarchy真实场景绑定计划_2026-04-17.md b/docs/used/XCUI_Hierarchy真实场景绑定计划_2026-04-17.md deleted file mode 100644 index 7b06ab8e..00000000 --- a/docs/used/XCUI_Hierarchy真实场景绑定计划_2026-04-17.md +++ /dev/null @@ -1,80 +0,0 @@ -# XCUI Hierarchy 真实场景绑定计划 -日期: `2026-04-17` - -## 1. 问题定义 - -当前 `new_editor` 的 `Hierarchy` 面板存在根因级问题: - -- `HierarchyPanel` 自己持有一份 `HierarchyModel::BuildDefault()` 生成的假树,UI 树不是场景真相来源。 -- `delete / duplicate / reparent` 等命令直接改 `HierarchyModel`,没有改真实场景。 -- `new_editor` 启动链没有建立 startup scene,`Hierarchy` 就算切到真实场景也可能拿到空 scene。 -- 引擎层 `XCEngine::Components::SceneManager::LoadScene()` 只负责加载,不负责切换 `active scene`,如果不额外处理,`Hierarchy` 仍然会显示为空。 - -这会导致 `Hierarchy`、`Inspector`、后续 `SceneView` gizmo、选择状态长期分叉,继续往上做功能只会越堆越歪。 - -## 2. 本次目标 - -本次只收主线第 `1` 项: 把 `Hierarchy` 从假数据切到真实场景数据。 - -完成标准: - -- `Hierarchy` 的树结构直接由当前 active scene 投影生成。 -- `Hierarchy` 不再把 `HierarchyModel` 当事实来源,只把它当 UI 投影缓存。 -- `delete / duplicate / reparent / move-to-root` 直接修改真实 scene,再由 scene 反推 UI。 -- `new_editor` 启动时保证存在可用的 active scene。 -- 补最小必要测试,避免这条链路再次回退成假树。 - -本次明确不做: - -- 不把旧版 `editor` 的 `SceneManager / SelectionManager / EventBus` 整套硬搬进 `new_editor`。 -- 不顺手扩散到 `Inspector` 真编辑、`Scene/Game` 真渲染、undo/redo 全量闭环。 -- 不做表面补丁,例如“启动后把假树替换一次但后续编辑仍然改本地模型”。 - -## 3. 方案 - -### 3.1 启动链补齐真实 scene - -- 在 `new_editor` 启动初始化阶段设置 `project` 资源根。 -- 加一个轻量的 scene bridge: - - 负责加载 `project/Assets/Scenes/Main.xc` - - 负责在加载后显式设置 active scene - - 如果 startup scene 不存在,则创建一个空的 `Main` scene 作为当前 active scene - -### 3.2 Hierarchy 改为 scene-backed projection - -- 为 `HierarchyModel` 增加从 `XCEngine::Components::Scene` 构建树的能力。 -- `itemId` 改为真实 `GameObject::ID` 的稳定字符串表达,不再使用 demo id。 -- `HierarchyPanel` 每帧从 active scene 同步投影模型。 -- 保留 `selection / expansion / tree interaction` 在 UI 层,但它们只消费 scene 投影,不拥有场景数据。 - -### 3.3 编辑命令直接落到真实 scene - -- `delete` 直接调用真实 `Scene::DestroyGameObject` -- `duplicate` 复用旧版 editor 的思路: - - 递归序列化组件 payload - - 在同级 parent 下递归 paste 出新对象 -- `reparent / move-to-root` 直接调用真实 `GameObject::SetParent` -- UI 侧命令执行完成后,重新从 active scene 投影 `HierarchyModel` - -### 3.4 验证 - -- 增加 `editor_app_feature_tests` 级别单测,覆盖: - - 从真实 scene 构建 hierarchy model - - duplicate 后层级结构正确 - - reparent / move-to-root 后 hierarchy 投影正确 -- 构建 `editor_app_feature_tests` -- 视情况构建 `XCUIEditorApp`,确认编译链没断 - -## 4. 风险与约束 - -- `new_editor` 当前还没有旧版 editor 的 scene dirty / undo / selection manager 体系,本次不扩散补齐。 -- `SceneManager::LoadScene()` 的 active scene 行为在引擎层本身不完整,本次在 `new_editor` bridge 层兜住,不直接改全局引擎语义。 -- 如果工作树里已有并行改动触到同一批文件,需要只做最小交叉修改,不回退别人代码。 - -## 5. 执行顺序 - -1. 加 scene bridge,补 startup scene 初始化和 active scene 建立。 -2. 给 `HierarchyModel` 增加真实 scene 投影能力。 -3. 改 `HierarchyPanel`,去掉 `BuildDefault()` 和对本地模型的事实写入。 -4. 把 delete / duplicate / reparent / move-to-root 切到真实 scene。 -5. 增加单测并编译验证。 diff --git a/docs/used/XCUI_Input_Focus_Shortcut_Subplan_完成归档_2026-04-04.md b/docs/used/XCUI_Input_Focus_Shortcut_Subplan_完成归档_2026-04-04.md deleted file mode 100644 index 5ae37630..00000000 --- a/docs/used/XCUI_Input_Focus_Shortcut_Subplan_完成归档_2026-04-04.md +++ /dev/null @@ -1,140 +0,0 @@ -# XCUI Input / Focus / Shortcut Subplan 完成归档 - -日期:`2026-04-04` - -## 1. 归档结论 - -`Subplan-04` 在其原始定义边界内已经完成,可以归档。 - -这里的“完成”指的是: - -- 已建立 XCUI 输入事件基础模型 -- 已建立焦点、激活路径、指针捕获路径三套状态管理 -- 已建立 capture / target / bubble 三阶段输入路由 -- 已建立 shortcut scope 与命令匹配机制 -- 已建立统一的 `UIInputDispatcher` -- 已补齐最小单元测试并完成通过验证 - -这里的“完成”不包括: - -- Win32 原生消息采集 -- ImGui / Editor 侧的输入桥接 -- 文本输入控件级别的 IME 细节 - -这些内容本来就不在 `Subplan-04` 的负责边界里,后续应由 `Subplan-05`、`Subplan-08`、`Subplan-09` 等继续接手。 - -## 2. 本次落地内容 - -### 2.1 输入事件模型 - -已扩展 `UIInputEvent`: - -- `PointerEnter` -- `PointerLeave` -- `pointerId` -- `timestampNanoseconds` -- `repeat` -- `synthetic` - -相关文件: - -- `engine/include/XCEngine/UI/Types.h` - -### 2.2 焦点与路径模型 - -已补齐: - -- `UIElementId` -- `UIInputPath` -- `UIFocusController` -- `UIFocusChange` - -相关文件: - -- `engine/include/XCEngine/UI/Input/UIInputPath.h` -- `engine/include/XCEngine/UI/Input/UIFocusController.h` -- `engine/src/UI/Input/UIInputPath.cpp` -- `engine/src/UI/Input/UIFocusController.cpp` - -### 2.3 Shortcut 系统 - -已补齐: - -- `UIShortcutScope` -- `UIShortcutChord` -- `UIShortcutBinding` -- `UIShortcutRegistry` -- shortcut scope 优先级匹配规则 - -相关文件: - -- `engine/include/XCEngine/UI/Input/UIShortcutRegistry.h` -- `engine/src/UI/Input/UIShortcutRegistry.cpp` - -### 2.4 输入路由与统一分发器 - -已补齐: - -- `UIInputRouter` -- `UIInputRoutingPlan` -- `UIInputRoutingStep` -- `UIInputDispatcher` - -相关文件: - -- `engine/include/XCEngine/UI/Input/UIInputRouter.h` -- `engine/include/XCEngine/UI/Input/UIInputDispatcher.h` -- `engine/src/UI/Input/UIInputRouter.cpp` -- `engine/src/UI/Input/UIInputDispatcher.cpp` - -## 3. 测试与验证 - -新增测试: - -- `tests/Input/test_xcui_input_dispatcher.cpp` - -已完成验证: - -- `cmake --build build --config Debug --target input_tests` -- `ctest -C Debug -R "XCUI.*" --output-on-failure` - -验证结果: - -- `6 / 6` 通过 - -覆盖点包括: - -- focus path 切换 -- capture path 优先级 -- keyboard routed path 顺序 -- shortcut scope 匹配优先级 -- pointer down / pointer up 对 active path 的影响 -- shortcut 命中后优先消费、跳过普通 routing - -## 4. 对后续 subplan 的可复用输出 - -当前已经可以被后续直接依赖的稳定入口: - -- `XCEngine::UI::UIInputPath` -- `XCEngine::UI::UIFocusController` -- `XCEngine::UI::UIShortcutRegistry` -- `XCEngine::UI::UIInputRouter` -- `XCEngine::UI::UIInputDispatcher` - -后续建议对接方式: - -- `Subplan-05`:负责把 ImGui/平台输入桥接进这套 dispatcher -- `Subplan-08`:负责把 menu / dock / panel shell 的 shortcut scope 接进 registry -- `Subplan-09`:负责把 viewport shell 的 pointer / focus / capture 接进 routing - -## 5. 原 subplan 文件 - -原始 subplan 文件已从活跃 `xcui-subplans` 目录移出。 - -当前这份文件就是 `Subplan-04` 的最终归档版本。 - -其状态应视为: - -- 已完成 -- 已归档 -- 不再作为活跃开发计划继续扩写 diff --git a/docs/used/XCUI_NewEditor主线重建计划_2026-04-07.md b/docs/used/XCUI_NewEditor主线重建计划_2026-04-07.md deleted file mode 100644 index a927728b..00000000 --- a/docs/used/XCUI_NewEditor主线重建计划_2026-04-07.md +++ /dev/null @@ -1,489 +0,0 @@ -# XCUI NewEditor主线重建计划 - -日期:2026-04-09 - -## 1. 文档定位 - -本文档用于收口 `XCUI / XCEditor / new_editor` 当前阶段的真实执行主线。 - -它不是重复定义 XCUI 全部架构,而是把“接下来到底怎么做”重新拍板,并覆盖之前已经不再成立的执行假设。 - -本文档与现有文档的关系如下: - -- `docs/plan/XCUI完整架构设计与执行计划.md` - - 继续作为 XCUI 总体架构与三层设计的总纲参考。 -- 本文档 - - 作为 `new_editor` 主线重建的最新执行计划。 - - 优先级高于旧的阶段状态快照与旧执行顺序说明。 - ---- - -## 2. 当前拍板结论 - -当前必须明确以下结论,并作为后续开发硬约束: - -### 2.1 旧 `editor/` 不再作为 XCUI 替换目标 - -- `editor/` 当前 ImGui 版本继续保留。 -- 它是参考实现、行为对照和视觉基线来源。 -- 当前阶段不再把主要精力放在“把 `XCEditor` 回填进旧 `editor/` 并替掉 ImGui”这条路线。 - -### 2.2 `new_editor/` 是未来正式编辑器主线 - -- `new_editor/` 不再只是临时名字上的沙盒。 -- 它应被视为未来正式编辑器的新主线工作区。 -- 后续真正重建编辑器时,应以 `new_editor/` 为宿主和产品主线,而不是回头改旧 `editor/`。 -- 但它不是当前测试体系入口;当前阶段不承担基础层验证职责。 - -### 2.3 `tests/UI` 仍然是基础层唯一实验场 - -- `tests/UI/Core` 只验证 Core 共享能力。 -- `tests/UI/Editor` 只验证 Editor 基础层能力。 -- `tests/UI/Runtime` 只验证 Runtime 层能力。 -- 所有基础层验证、交互试验、截图检查,都优先放在 `tests/UI`。 -- `tests/UI` 同时也是当前 XCUI 的唯一正式验证入口。 - -### 2.4 `new_editor/` 当前不承担“实验面板堆场” - -- `new_editor/` 当前只承载: - - `XCEditor` 基础层库 - - `new_editor` 的真实宿主与产品装配代码 -- 在 Editor 基础层收口前,禁止把 `new_editor/` 继续做成杂乱的试验场或验证面板集合。 -- 需要人工操作检查的内容,仍然通过 `tests/UI/*/integration` 提供。 - -### 2.5 Core/Runtime 可资源化,Editor 固定代码样式 - -- `Core / Runtime` 仍可继续推进资源化、热重载与资源驱动验证。 -- `Editor` 当前默认样式、palette、metrics 与视觉语义固定在代码层,不再把 Editor 主题解析作为主线。 -- workspace、panel session、状态机、命令派发、业务装配与控制器仍保留在代码层。 -- 在 `Core / Editor` 基础层没有收口前,不提前在 `new_editor/` 推进业务面板。 - -### 2.6 当前主线优先级是 `Editor`,不是 `Runtime` - -- `Runtime` 继续按三层设计保留,但不是当前最高优先级。 -- 当前最高优先级是先把 `UI Core + UI Editor` 做成熟,并让其具备支撑 `new_editor` 正式重建的能力。 - -### 2.7 Editor 视觉基线以旧 `editor/` 为准 - -- `XCEditor` 当前默认视觉还没有达到旧 `editor/` 的程度。 -- 后续 `Editor UI` 的默认主题、控件密度、边框、分隔线、状态反馈强度,都要以旧 `editor/` 现有风格为基线。 -- 目标不是做一套“看起来差不多的深色 UI”,而是做出可以承载当前编辑器产品感的正式 Editor 风格。 - ---- - -## 3. 三层边界再次确认 - -### 3.1 Core - -`Core` 负责: - -- retained-mode UI 运行机制 -- layout / input / focus / scroll / popup / text / render contract -- theme / token / style resolve 的通用机制 -- 与 Editor / Runtime 都共享的基础能力 - -`Core` 不负责: - -- Editor 风格语义 -- Runtime screen/player 宿主策略 -- 业务面板 - -### 3.2 Runtime - -`Runtime` 负责: - -- game UI 的 screen / layer / player / system -- runtime 输入路由、阻塞、导航、menu/hud/modal 语义 -- 面向游戏开发者的运行时宿主层 - -`Runtime` 不负责: - -- Editor 专用控件 -- Editor 默认风格 -- 新编辑器重建主线 - -### 3.3 Editor - -`Editor` 负责: - -- editor-only widget -- editor-only shell / dock / workspace / panel session / viewport shell -- editor 风格语义 -- 固定代码样式下的默认视觉常量与结构语义 - -`Editor` 不负责: - -- 旧 `editor/` 的就地替换 -- 当前测试体系入口 -- Editor 主题解析主线 -- 直接承担游戏 Runtime UI - ---- - -## 4. 当前状态评估 - -## 4.1 已有基础 - -当前 `XCEditor` 已经具备以下基础: - -- shell 级模型与状态骨架: - - workspace - - dock host - - panel registry - - panel lifecycle - - layout persistence - - shortcut / command 基础路径 -- editor 常用基础件: - - menu bar / popup / context menu - - tab strip - - tree view / list view - - scroll view - - property grid - - viewport shell / viewport slot - - bool / number / enum field -- `tests/UI/Editor` 已形成 `unit + integration` 的基础验证体系。 - -结论: - -- `XCEditor` 已经不是 demo 级玩具。 -- 它已经具备继续作为 `new_editor` 底座推进的资格。 - -## 4.2 当前最突出的缺口 - -当前离“可正式支撑 `new_editor` 重建”的差距主要在以下几点: - -### 4.2.1 Editor 固定样式常量仍未收口 - -- 当前大量 Editor palette / metrics / font size / rounding / inset 仍分散在不同控件实现里。 -- 问题不在于 `.xctheme` 不够厚,而在于固定代码样式尚未统一收口。 -- 当前很多控件仍停留在通用深色皮肤,不是正式 Editor 风格。 - -### 4.2.3 视觉基线还未对齐旧 editor - -- 当前 widget 默认密度偏松。 -- 圆角偏大。 -- 行高偏高。 -- 分隔线与层级关系不够清晰。 -- 菜单栏、面板框架、属性行、tab 等视觉结构还没有贴近旧 editor。 - -### 4.2.4 仍缺少若干 Editor 通用字段件与高级能力 - -- `TextField` -- `Vector2Field` -- `Vector3Field` -- `Vector4Field` -- `ColorField` -- 后续可扩展的 `AssetField / ObjectField` - -### 4.2.5 collection / shell 高级能力还未收口 - -- multi-selection -- inline rename -- drag/drop contract -- virtualization -- icon / glyph 统一语义 -- 更完整的 toolbar / status / shell chrome 体系 - ---- - -## 5. 当前阶段硬规则 - -### 5.1 先做基础层,不提前做业务面板 - -在 Editor 基础层完成收口前: - -- 不开始复刻 Hierarchy / Inspector / Console / Project 这些完整业务面板。 -- 只允许做承载这些面板将来必需的通用 Editor 基础件。 - -### 5.2 Core 能力缺口必须先回补 Core - -凡是发现缺的是: - -- layout -- input -- focus -- popup -- text -- render contract -- shared widget primitive - -都必须优先回补到 `Core` 或 shared 层,而不是在 `Editor` 层写临时绕过实现。 - -### 5.3 Editor 样式固定在代码层,资源与代码边界固定 - -- 样式机制归 `Core` -- Editor 默认风格语义归 `Editor` -- `Core / Runtime` 可以继续使用资源化样式与热重载 -- `Editor` 默认颜色 / spacing / radius / border / density / typography 固定在代码层 -- 控件行为、状态机、workspace/panel session、命令派发与业务装配仍由 `Editor` 代码层控制 - -### 5.4 `new_editor/` 只做正式产品装配,不做测试堆场 - -- 当前正式验证入口继续固定在 `tests/UI` -- `new_editor/` 只保留未来正式编辑器真正需要的宿主、装配、资源与业务层结构 -- `new_editor/` 只做宿主装配与后续业务承载,不承担当前基础层验证入口职责 - ---- - -## 6. Editor基础层完成标准 - -只有当以下条件基本满足后,才允许进入 `new_editor` 业务面板重建阶段: - -### 6.1 视觉与样式层 - -- 已建立统一的 Editor 固定样式常量出口 -- widget 的 palette / metrics 不再分散硬编码,而是通过统一代码入口收口 -- menu / popup / tab / panel / property / list / tree / status / scrollbar / splitter 都已切到同一套 Editor 固定样式语义 -- 默认样式已稳定逼近旧 editor 的视觉基线 - -### 6.2 字段件层 - -- `BoolField / NumberField / EnumField / TextField / Vector2/3/4Field / ColorField` 可稳定使用 -- `PropertyGrid` 能复用这些字段件,而不是自己硬画假控件 - -### 6.3 collection / shell 层 - -- tree / list / tab / popup / scroll / dock / workspace 都具备稳定基础交互 -- keyboard / focus / shortcut / popup / panel session 契约稳定 -- 关键状态机已通过 `unit` - -### 6.4 验证层 - -- 每个重要基础件都有对应 `unit` -- 每个重要交互点都有对应 `integration exe` -- 截图检查链路可用 -- 中文操作指示和检查重点明确 - ---- - -## 7. 分阶段执行计划 - -## Phase A:Editor固定样式系统收口 - -### 目标 - -把 `Editor` 当前分散的硬编码视觉常量,收口为统一的固定代码样式体系。 - -### 任务 - -- 定义统一的 Editor 样式常量与 helper 命名规范。 -- 收口以下语义槽位: - - workspace - - panel - - header - - footer - - menu bar - - popup - - tab - - property row - - field label/value - - status bar - - splitter - - scrollbar - - selection / hover / active / focus -- 把现有 widget 中分散的默认 palette / metrics 汇总到统一代码出口。 -- 建立“旧 editor 风格对齐”的基准场景与截图检查。 - -### 完成标准 - -- Editor 默认视觉不再依赖主题解析测试。 -- 调整 Editor 默认样式时,改动集中在统一代码出口,而不是散落在各控件里。 - -## Phase B:字段件体系补齐 - -### 目标 - -补齐 Editor 通用字段件,为后续 Inspector / 面板表单重建打基础。 - -### 任务 - -- 正式完成 `TextField` -- 完成 `Vector2Field` -- 完成 `Vector3Field` -- 完成 `Vector4Field` -- 完成 `ColorField` -- 视需要评估 `AssetField / ObjectField` 的基础契约 -- 让 `PropertyGrid` 正式承接这些字段件 - -### 完成标准 - -- `PropertyGrid` 不再停留在基础标量字段件阶段。 -- 复合字段件已有完整 `unit + integration`。 - -### 当前检查点(2026-04-08) - -- `TextField` 已完成正式接入: - - `XCEditor` 已提供 `UIEditorTextField` 与 `UIEditorTextFieldInteraction` - - 已补齐 `TextField` 的 hosted builder 与固定样式装配入口 - - `PropertyGrid` 的 `Text` 行已切到正式 `TextField` 复用链路 -- `Vector2Field` 已完成第一批复合字段件接入: - - `XCEditor` 已提供 `UIEditorVector2Field` 与 `UIEditorVector2FieldInteraction` - - 已补齐 `Vector2Field` 的 hosted builder 与固定样式装配入口 - - `tests/UI/Editor` 已补齐 `Vector2Field` 的 layout / hit-test / interaction 单测 - - `editor_ui_vector2_field_basic_validation` 已可编译、运行、自动截图,并已接入 `editor_ui_integration_tests` -- 当前 `tests/UI/Editor` 中,这两批字段件都遵守同一条验证规范: - - 顶部必须明确写“这个测试验证什么功能” - - 试验面板只放当前批次要检查的控件 - - 保持 Unity 向黑白灰风格,不把业务面板塞进 `new_editor` -- 因此 `Phase B` 当前已正式完成前两项: - 1. `TextField` - 2. `Vector2Field` -- 下一批主线顺序固定为: - 1. `Vector3Field` - 2. `Vector4Field` - 3. `ColorField` - -## Phase C:Collection与交互高级能力收口 - -### 目标 - -让 tree/list/tab/menu/property 进入可长期复用的 Editor 基础件水平。 - -### 任务 - -- multi-selection -- inline rename/edit session -- drag/drop contract -- virtualization 基础设计与第一轮实现 -- icon / glyph / disclosure / dropdown indicator 统一语义 -- collection 与 keyboard navigation/focus 的进一步收口 - -### 完成标准 - -- collection 控件不再只是演示级原型。 -- 已具备支撑真实 editor 面板的核心交互能力。 - -## Phase D:Shell与Workspace正式收口 - -### 目标 - -把 shell 基础层从“能演示”推进到“可作为新编辑器外壳底座”。 - -### 任务 - -- menu bar / popup / context menu 视觉与交互对齐旧 editor 基线 -- panel frame / status bar / tab strip / dock host 进一步主题化和结构收口 -- workspace session / panel lifecycle / shortcut / command / layout persistence 继续补齐 -- viewport shell / viewport input bridge 与 Editor shell 契约继续稳定 - -### 完成标准 - -- shell 基础层达到可承载空业务编辑器外壳的程度。 - -## Phase E:`new_editor` 空壳正式接管 - -### 目标 - -在不引入具体业务面板的前提下,让 `new_editor` 正式使用 `XCEditor` 搭建空编辑器壳层。 - -### 任务 - -- `new_editor` 中装配: - - 主菜单壳层 - - 工具栏占位 - - workspace / dock 壳层 - - status bar - - viewport shell 占位 -- 保持业务内容为空或极简占位,不提前混入具体面板逻辑。 - -### 完成标准 - -- `new_editor` 具备“正式产品空壳”形态。 -- 旧 `editor/` 继续只承担参考作用。 - -## Phase F:业务面板分批重建 - -### 前置条件 - -只有在 Phase A 到 Phase E 基本完成后,才进入本阶段。 - -### 执行原则 - -- 以旧 `editor/` 为行为和视觉参考 -- 以 `new_editor/` 为正式宿主 -- 业务面板按独立垂直切片逐个迁入 - -### 候选顺序 - -- Inspector 基础表单链路 -- Hierarchy -- Console -- Project -- Scene/Game 相关工具壳层 - ---- - -## 8. 测试与验收规则 - -### 8.1 Core 能力进 `tests/UI/Core` - -凡是共享能力,一律在 `tests/UI/Core` 验证: - -- popup overlay primitive -- scroll / focus / keyboard navigation -- text input / style resolve / layout - -### 8.2 Editor-only 能力进 `tests/UI/Editor` - -凡是 editor-only,一律在 `tests/UI/Editor` 验证: - -- field widgets -- property grid -- menu / popup / tab / dock / workspace -- shell state / panel lifecycle / viewport shell - -### 8.3 `new_editor` 不承担测试职责 - -- `new_editor` 只做产品集成冒烟检查 -- 不把验证场景继续塞到 `new_editor` 里 - -### 8.4 每批次收口要求 - -每推进一个 Editor 基础件批次,至少必须完成: - -- 对应 `unit` -- 对应 `integration exe` -- 人工截图检查 -- 风格与交互结论记录 - ---- - -## 9. 与其它计划的关系 - -### 9.1 继续有效的文档 - -- `docs/plan/XCUI完整架构设计与执行计划.md` - - 继续作为 XCUI 总体架构总纲 -- `docs/plan/Material Inspector与Shader属性面板收口计划_2026-04-07.md` - - 继续作为未来业务层计划保留 - - 但执行前提是本计划中的 Editor 基础层先收口 - -### 9.2 已被覆盖的旧结论 - -本节即当前归档说明。仓库当前未单独维护 `docs/plan/used` 目录时,凡被本节点名覆盖的旧口径,一律视为已归档、不再执行。 - -以下旧结论不再作为当前主线执行依据: - -- “后续主要目标是把 XCUI 直接嵌回旧 `editor/` 并替掉 ImGui” -- “`new_editor/` 长期只作为临时沙盒存在” -- “可以在 `new_editor/` 中继续堆各类试验面板作为主验证入口” -- “可以在 `Core / Editor` 基础层未完成前,直接进入 `new_editor` 业务面板开发” - -## 10. 当前结论 - -当前最重要的不是立刻复刻旧 editor 的具体业务面板,而是先把: - -- `UI Core` -- `UI Editor` -- `new_editor` 正式宿主边界 - -这三者之间的职责彻底做对。 - -正确路径是: - -1. 继续在 `tests/UI` 中把基础层做成熟 -2. 以旧 `editor/` 为风格与行为参考 -3. 以 `new_editor/` 为未来正式编辑器主线 -4. 待基础层达标后,再进入业务面板分批重建 - -这条路线比“直接替换旧 editor 中的 ImGui”风险更低,也更符合当前工程实际。 diff --git a/docs/used/XCUI_NewEditor收口重构计划_2026-04-17.md b/docs/used/XCUI_NewEditor收口重构计划_2026-04-17.md deleted file mode 100644 index bf651fbc..00000000 --- a/docs/used/XCUI_NewEditor收口重构计划_2026-04-17.md +++ /dev/null @@ -1,855 +0,0 @@ -# XCUI NewEditor 收口重构计划 -日期: `2026-04-17` - -## 当前执行状态(更新于 `2026-04-17`) - -### 总体判断 - -如果按这份文档最初定义的“全量重构计划”来判断,当前计划**仍未全部完成**;但如果按本轮 `new_editor` 主线稳定性收口目标来判断,主链路已经**基本完成**,剩余更多属于后续演进 backlog。 - -- 当前主线收口:基本完成 -- `Phase 0`:基本完成 -- `Phase 1`:部分完成 -- `Phase 2`:部分完成 -- `Phase 3`:部分完成 -- `Phase 4`:部分完成 -- `Phase 5`:基本完成 -- `Phase 6`:基本完成 -- `Phase 7`:未完成 - -### 已完成事项 - -1. `workspace` 关键校验已补齐一项核心缺口: - - 已补 `duplicate nodeId` 校验 - - 已补对应单元测试 - -2. 历史测试残留已清理: - - `tests/NewEditor` 已删除 - - 失效的 `XCNewEditorLib` 测试入口已退出工作树 - -3. 测试边界已有明显改善: - - `tests/UI/Editor` 不再直接编译 `new_editor/app` 私有 `.cpp` - - `EditorHostCommandBridge / EditorSession / EditorEditCommandRoute` 已提升为正式公共 app-level API - -4. shell 语义命名已收口: - - foundation shell 与 application shell 已分开命名 - - 旧的 `default shell` 歧义已明显降低 - -5. panel 单一事实源已有第一步落地: - - 核心 panel id 已集中到统一常量入口 - - `workspace / menu / command / route / viewport` 的一部分散落字符串已收敛 - -6. `PanelRegistry` 已从纯元数据表前进到“带装配信息的注册表”: - - 已承载 viewport 默认 shell 配置 - - `PanelContentHost` 已直接从 registry 派生外部 host 装配,不再依赖额外 bindings 列表 - -7. target graph 已完成一轮新的收口: - - `XCUIEditorAppLib` 已从 `XCUIEditorApp` 可执行目标中拆出 - - app 业务层不再全部内联在 exe target 中 - - `XCUIEditorLib` 已改为 `PRIVATE` 链接 `XCEngine` - - 当前 consumer 改为通过显式公开的 engine include root 获取编译所需头文件,而不再继承整个 engine link interface - -8. `window-workspace` 全局约束已再补一刀: - - 已补跨窗口 `panelId` 唯一性校验 - - 已补对应窗口工作区单元测试 - - 已补 detached single-panel transfer 的 source root / open-visible 约束,避免脏 source 请求绕过可见性校验 - - 已补同窗口 `move/dock` 的 controller 级结果校验与回滚,避免 mutation 回归直接污染 window-set 状态 - - 已补 detached window 起始 global-tab-drag 的源请求校验,避免 stale `nodeId/panelId` 直接进入跨窗口拖拽状态机 - - 已补 live window-set 构造时对 destroyed/no-`HWND` 窗口的过滤,避免 `primary/activeWindowId` 指向不存在的运行时窗口 - - 已补 window-sync 失败时的已有窗口/新建窗口回滚,避免同步中途失败留下半更新窗口集 - - 已补 closing-window 过滤,避免已发出 `WM_CLOSE` 但尚未销毁的 detached window 继续参与 live window-set 与命中测试 - - 已补 closing-window 对 render/transfer/global-tab-drag 的排除,并在主窗口级联关闭 detached windows 前先标记 `closing` - -9. integration 测试构建模板已继续收口: - - 已把 `tests/UI/Editor/integration` 叶子目标收敛到 shared helper - - 重复的 include / compile definition / link 模板已明显减少 - - helper 内已统一 `/utf-8`、`/FS` 与 `MSVC_RUNTIME_LIBRARY` 配置 - - `editor_ui_integration_tests` 已恢复全量可构建 - -### 当前仍未完成的后续演进项 - -1. `Milestone A` 未完成: - - `XCUIEditorLib` 虽已不再 `PUBLIC` 依赖整个 `XCEngine`,但 public header 仍直接暴露部分 engine 头文件 - - UI `Core / Runtime / Editor / App` 还未真正拆成清晰 target graph,当前只是先收紧了 transitive link boundary - -2. `Milestone B` 未完成: - - `PanelRegistry` 仍未成为完整的 runtime factory / lifecycle 入口 - - `EditorShellRuntime` 仍手工持有具体 panel runtime 实例 - - `window-workspace / cross-window / transfer` 仍有剩余一致性约束未补齐,当前已补到跨窗口 `panelId` 唯一性与 detached single-panel transfer 源约束 - -3. `Milestone C` 未完成: - - `XCUIEditorLib / XCUIEditorHost / XCUIEditorApp` 等 target 命名尚未统一收口 - - integration 构建已恢复,但 warning 口径与测试运行入口说明还未统一 - - 文档体系尚未全面同步当前结构 - -### 最近一次已验证通过 - -- `cmake --build . --config Debug --target editor_ui_tests` -- `cmake --build . --config Debug --target editor_ui_integration_tests` -- `cmake --build . --config Debug --target XCUIEditorApp` -- `.\tests\UI\Editor\unit\Debug\editor_ui_tests.exe --gtest_filter=UIEditorWindowWorkspaceControllerTest.*` -- `.\tests\UI\Editor\unit\Debug\editor_ui_tests.exe` -- 多组 `editor_ui_tests` 定向用例通过: - - `UIEditorWorkspaceModelTest.*` - - `EditorHostCommandBridgeTest.*` - - `EditorShellAssetValidationTest.*` - - `EditorUIStructuredShellTest.*` - - `UIEditorPanelRegistryTest.*` - - `UIEditorPanelContentHostTest.*` - - `UIEditorWorkspaceComposeTest.*` - - `UIEditorWindowWorkspaceControllerTest.*` -- 全量 `editor_ui_tests` 已通过:`337` 个测试全部通过 -- `editor_ui_integration_tests` 已恢复全量构建通过 -- 当前残留构建噪音主要是若干非阻塞 `LNK4199`(`/DELAYLOAD:assimp-vc143-mt.dll`)warning - -### 接下来优先级 - -为尽快收口,后续优先顺序调整为: - -1. 继续补齐 `window-workspace / transfer` 剩余不变式和状态机约束 -2. 继续压缩 `XCUIEditorLib` public header 对 `XCEngine` 的真实暴露面 -3. 最后统一 target/public API 命名和文档口径 - -## 1. 文档定位 - -这份文档是针对当前 `new_editor/` 主线的一次正式收口计划。 - -它不讨论“要不要继续做 XCUI 新编辑器”,而是默认这条线继续推进,并专门解决当前已经暴露出的以下问题: - -- UI `Core / Runtime / Editor` 三层只停留在目录语义,缺少真实构建边界 -- `new_editor` 公共库层与 `app` 宿主层边界开始混杂 -- panel id、workspace、menu、command、route 等关键标识分散硬编码 -- panel registry 只是元数据表,不是可扩展运行时入口 -- workspace/window-workspace 不变量不完整,校验与运行时主键不一致 -- `default shell` 与真实应用 shell 已经分叉 -- 测试入口、测试目录与目标命名存在历史残留 -- 命名体系没有收口,长期沟通成本偏高 - -这份计划的目标是把 `new_editor` 从“已经能工作的一套实现”收成“边界稳定、可继续扩展、可维护、可验证的正式主线”。 - -## 2. 当前问题总览 - -### 2.1 三层分层没有真正落到 target graph - -当前 UI 三层在概念上已经存在,但依赖图上并没有被真正约束。 - -现状: - -- `engine/CMakeLists.txt` 中 UI Core/Runtime 只是 `XCEngine` 大静态库中的一部分 -- `new_editor/CMakeLists.txt` 曾经让 `XCUIEditorLib` 直接 `PUBLIC` 依赖整个 `XCEngine`,当前已先收紧为 `PRIVATE` 链接并显式暴露必要 include root -- Editor 层没有被限制只能依赖 UI Core/Runtime 的最小子集 - -结果: - -- 分层更多靠约定,不靠构建系统保证 -- Editor 层未来极易直接吃到 engine 里任意模块 -- 后续如果要拆库、裁剪、做更细的测试边界,代价会越来越高 - -### 2.2 面板系统是 stringly-typed,单一事实源缺失 - -当前 `hierarchy / scene / game / inspector / console / project` 这些 panel id 分散存在于多处: - -- panel registry -- workspace 默认布局 -- shell presentation -- menu checked state -- command registry -- active route 映射 -- viewport 特判逻辑 - -结果: - -- 新增一个 panel 需要同时修改多处 -- 某一处漏改不会立刻在编译期暴露 -- 系统长期演进后极易出现名称漂移和行为不一致 - -### 2.3 PanelRegistry 不是正式扩展点 - -当前 registry 只描述: - -- `panelId` -- `defaultTitle` -- `presentationKind` -- `canHide / canClose` - -但真正的运行时对象和生命周期仍然由 `EditorShellRuntime` 显式持有和手工更新: - -- `HierarchyPanel` -- `ProjectPanel` -- `InspectorPanel` -- `ConsolePanel` -- 视口相关宿主对象 - -结果: - -- registry 和真实运行时是两套系统 -- 想做“注册一个面板即可接入”的能力时,现有结构撑不住 -- detached window、多 panel host、后续插件化面板都缺少干净入口 - -### 2.4 Workspace / WindowWorkspace 不变量不完整 - -当前 workspace 校验存在关键缺口: - -- 校验 panel id 唯一 -- 但没有校验 `nodeId` 唯一 - -而运行时很多操作都把 `nodeId` 当主键: - -- 查找 node -- 删除 node -- tab 拖拽 -- dock/transfer -- cross-window drop - -结果: - -- 坏布局数据可能“通过校验但行为不稳定” -- 一旦 layout persistence 或跨窗口拖拽写入异常数据,调试会很困难 - -### 2.5 公共默认 shell 与真实 app shell 已经分叉 - -当前存在两套默认构造逻辑: - -- `BuildDefaultEditorShellAsset()`:只有一个 placeholder root -- `BuildEditorShellAsset()`:真实六面板应用 shell - -结果: - -- `default` 这个命名已经失真 -- 公共 API、测试、应用入口对“默认 shell”理解不一致 -- 后面继续扩展时容易把 demo/default/baseline/app shell 混在一起 - -### 2.6 测试入口与测试命名已经出现历史残留 - -当前测试结构存在明显不一致: - -- `tests/UI` 被当作正式 XCUI 入口 -- `tests/NewEditor` 仍然残留,但没有接入总测试树 -- `tests/NewEditor/CMakeLists.txt` 还引用不存在的 `XCNewEditorLib` -- 还有测试引用当前工作树中已经不存在的 `new_editor/ui/...` - -结果: - -- 新人很难判断哪组测试才是正式入口 -- 历史死分支会持续误导后续重构 -- CI 或本地构建规则很难收口 - -### 2.7 Editor UI 测试开始穿透到 app 私有实现 - -当前 `tests/UI/Editor/unit` 已经直接编译 `new_editor/app/...` 私有实现。 - -结果: - -- 库层与宿主层边界开始被测试反向打穿 -- 原本应该可复用的 `include + src` 层将越来越依赖 `app` -- 未来拆分模块、复用库、做轻量 host 都会受影响 - -### 2.8 命名体系未收口 - -当前命名并存: - -- `XCEditor` -- `XCUIEditorLib` -- `XCUIEditorApp` -- `XCNewEditorLib` 历史残留 -- `Old editor shell baseline loaded.` 这类旧文案 - -结果: - -- 概念层级不清 -- API、构建目标、测试目标之间映射困难 -- 文档与实现更容易继续分叉 - -## 3. 重构目标 - -本次收口的正式目标如下。 - -### 3.1 构建边界收口 - -把 UI 三层和宿主层边界真正落到 target graph: - -- `UI Core` -- `UI Runtime` -- `UI Editor` -- `Editor Host/App` - -至少做到: - -- 依赖方向清晰 -- 低层不依赖高层 -- `new_editor/include + src` 不默认暴露整个 `XCEngine` - -### 3.2 单一事实源收口 - -把 panel、workspace、menu、command、route 的关键事实源统一起来,减少分散硬编码。 - -最终目标: - -- 新增 panel 时,有且仅有一处主要注册入口 -- 其余信息从注册结果派生 - -### 3.3 运行时扩展点收口 - -让 `PanelRegistry` 从描述表升级成真正的运行时接入点,逐步承载: - -- panel descriptor -- panel presentation kind -- panel host binding -- panel factory / lifecycle -- panel capability metadata - -### 3.4 状态模型收口 - -为 workspace、window-workspace、layout persistence 补齐完整不变量: - -- `panelId` 唯一 -- `nodeId` 唯一 -- `windowId` 唯一 -- transfer 前后 session/workspace 一致性可验证 -- 布局读写格式与运行时约束一致 - -### 3.5 测试体系收口 - -明确: - -- 哪些测试验证 UI Core/Runtime/Editor 库层 -- 哪些测试验证 `new_editor/app` 宿主层 -- 哪些目录是正式入口 -- 哪些历史目录要删除或迁移 - -### 3.6 命名与文档收口 - -统一: - -- public API 命名 -- CMake target 命名 -- 测试目标命名 -- 文档中的主线术语 - -## 4. 非目标 - -这次重构不以以下事项为目标: - -- 不重写所有 widget 的视觉实现 -- 不替换 Win32/D3D12 宿主技术路线 -- 不把旧 `editor/` 直接删掉 -- 不在这一轮做插件市场级别的面板系统 -- 不在这一轮做完整多后端 UI runtime 切换 - -本次重点是收边界、收命名、收状态模型、收测试入口。 - -## 5. 总体原则 - -### 5.1 先收结构,再扩功能 - -本次优先级不是“继续堆新面板功能”,而是先让已有结构进入可维护状态。 - -### 5.2 先建真实边界,再谈目录美化 - -只有目录重排但不改变依赖图,不算收口完成。 - -### 5.3 先建立单一事实源,再减少重复代码 - -重复代码本身不是第一问题;更大的问题是重复定义同一个业务事实。 - -### 5.4 先收测试入口,再扩测试规模 - -在测试入口不清楚之前,继续加更多测试只会放大混乱。 - -### 5.5 以增量迁移替代一次性推翻 - -当前 `new_editor` 已经有大量实现和测试,必须分阶段迁移,避免“全量返工式重构”。 - -## 6. 分阶段实施计划 - -## Phase 0:基线冻结与问题建档 - -### 目标 - -先把当前正式主线、历史残留、迁移边界标清楚,避免后续边改边迷路。 - -### 任务 - -1. 建立 `new_editor` 当前模块清单: - - 公共库层 - - app 宿主层 - - Win32/D3D12 host 层 - - 测试层 - -2. 明确当前正式入口: - - 正式 public API 入口 - - 正式 app 入口 - - 正式测试入口 - -3. 列出必须迁移和必须删除的历史残留: - - `tests/NewEditor` - - `XCNewEditorLib` - - 已失效文档引用 - - 已不存在资源路径引用 - -### 产出 - -- 模块边界表 -- 历史残留清单 -- 迁移映射表 - -### 验收标准 - -- 团队可以用一页文档回答“哪个目录是正式入口、哪个不是” - -## Phase 1:构建图与模块边界收口 - -### 目标 - -把“概念分层”改成“真实 target 分层”。 - -### 任务 - -1. 从 `XCEngine` 中把 UI 相关内容拆成明确层级: - - `XCEngineUICore` - - `XCEngineUIRuntime` - - 如有必要,再提供兼容聚合 target - -2. 把 `new_editor/include + src` 整理成明确 editor 库层: - - 只依赖 UI Core/Runtime 和必要基础模块 - - 不默认对外公开整个 engine 大库 - -3. 明确 `new_editor/app` 仅作为宿主与应用组合层: - - 依赖 editor 库层 - - 依赖平台与渲染宿主 - - 不反向成为 editor 库层的隐式依赖 - -4. 对 `tests/UI/Editor` 和未来 app 测试分别绑定到正确 target - -### 涉及范围 - -- `engine/CMakeLists.txt` -- `new_editor/CMakeLists.txt` -- `tests/UI/**/CMakeLists.txt` - -### 风险 - -- target 拆分会先暴露出大量原本被大库掩盖的 include/依赖穿透 - -### 验收标准 - -- editor 公共库层可以不依赖整个 `XCEngine` 大 target -- app 层和库层的依赖方向可在 CMake 上直接读出来 - -### 当前进展(更新于 `2026-04-17`) - -- `XCUIEditorAppLib` 已从 exe 启动层中拆出 -- `XCUIEditorLib` 已改为 `PRIVATE` 链接 `XCEngine` -- 为兼容当前 public header 暴露的 engine 头文件,已显式公开 `engine/include` 与 `engine/include/XCEngine` -- 尚未把 UI Core/Runtime 从 `XCEngine` 进一步拆成独立 target - -## Phase 2:Panel 单一事实源收口 - -### 目标 - -把 panel 相关定义统一到一个正式注册入口。 - -### 任务 - -1. 建立统一的 panel schema/registration 源: - - `panelId` - - `defaultTitle` - - `presentationKind` - - `routeKind` - - `hostingKind` - - `menu visibility` - - `command exposure` - -2. 让以下内容从同一注册源派生,而不是各自手写: - - 默认 workspace - - shell presentation - - View 菜单 checked state - - activate/view commands - - active route 映射 - - viewport panel 分类 - -3. 消灭散落在各处的 panel id 字符串常量 - -### 涉及范围 - -- `new_editor/app/Composition/*` -- `new_editor/app/State/*` -- `new_editor/app/Composition/EditorShellRuntimeViewport.cpp` - -### 验收标准 - -- 新增一个 panel 时,核心业务定义只需要修改一处主注册入口 -- 其余生成逻辑只做派生,不重复声明事实 - -## Phase 3:PanelRegistry 升级为正式运行时扩展点 - -### 目标 - -让 panel registry 真正承载运行时接入,而不是只有元数据。 - -### 任务 - -1. 区分 panel descriptor 与 panel runtime binding: - - 描述信息 - - host capability - - factory/lifecycle - - externally hosted / viewport hosted / internal compose - -2. 逐步把 `EditorShellRuntime` 里硬编码持有的 panel 运行时对象迁移到统一注册/装配流程 - -3. 明确 panel lifecycle: - - create - - mount - - update - - append/render - - unmount - - destroy - -4. 把 `PanelContentHost` 从“仅同步 bounds”升级到“和注册系统协同工作的 host layer” - -### 涉及范围 - -- `new_editor/include/XCEditor/Panels/*` -- `new_editor/src/Panels/*` -- `new_editor/app/Composition/EditorShellRuntime*` -- `new_editor/app/Features/*` - -### 风险 - -- 一次性全迁移风险较大,建议先从 `Hierarchy`、`Project` 这类 HostedContent 面板开始 - -### 验收标准 - -- `EditorShellRuntime` 不再需要手工知道所有具体面板类型 -- registry 可以回答“这个 panel 如何创建、如何更新、如何被 host” - -## Phase 4:Workspace 与 WindowWorkspace 不变量补齐 - -### 目标 - -让 workspace 成为真正可靠的状态模型,而不是“多数情况下可工作”的树结构。 - -### 任务 - -1. 为 workspace validation 补充: - - duplicate `nodeId` - - invalid detached root shape - - invalid transfer result - - invalid active panel after mutation - -2. 为 window-workspace 补充跨窗口一致性校验: - - panel 是否在多个窗口重复出现 - - transfer 前后 session/state 是否完整 - - primary window/active window 与真实窗口集是否一致 - -3. 明确 layout persistence 协议与模型不变量一致: - - 读入后 canonicalize - - 校验失败时的回退策略 - - 跨版本兼容策略 - -4. 为 cross-window drag/drop 增加更明确的状态机约束 - -### 当前进展(更新于 `2026-04-17`) - -- 已完成 `workspace duplicate nodeId` 校验与单元测试 -- 已完成 `window-workspace` 跨窗口 `panelId` 重复校验与单元测试 -- 已完成 detached single-panel transfer 对 `sourceNodeId` 与 `open/visible` 的约束补齐,并补单元测试 -- 已完成同窗口 `move/dock` 的结果校验与回滚防线补齐 -- 已完成 detached window 起始 global-tab-drag 的源请求校验 -- 已完成 live window-set 对 destroyed/no-`HWND` 窗口的 primary/active 过滤 -- 已完成 window-sync 失败回滚与 closing-window 过滤 -- 已完成 closing-window 在 render/transfer/global-tab-drag 路径上的排除 -- `transfer/session/state` 主链路已基本收口,剩余更多属于后续演进项而非当前主线阻塞 - -### 涉及范围 - -- `new_editor/include/XCEditor/Workspace/*` -- `new_editor/src/Workspace/*` -- `new_editor/app/Platform/Win32/WindowManager/*` - -### 验收标准 - -- 所有运行时依赖的主键都有校验 -- transfer/dock/move/detach 的结果状态可以被稳定验证 - -## Phase 5:Default Shell 与 App Shell 语义收口 - -### 目标 - -解决“默认 shell”命名与真实行为不一致的问题。 - -### 任务 - -1. 重新定义公共 API 语义: - - 哪个是 minimal/default baseline shell - - 哪个是 production app shell - - 哪个是 test fixture shell - -2. 重命名现有构造函数与测试用语,避免 `default` 误导 - -3. 让测试明确依赖: - - baseline shell fixture - - full editor shell fixture - - application shell build path - -### 涉及范围 - -- `new_editor/src/Shell/*` -- `new_editor/app/Composition/*` -- 相关测试 - -### 验收标准 - -- 外部调用者可以仅靠名称判断该 shell 构造函数的语义 -- 不再存在“默认只有 placeholder,但 app 默认是六面板”这种歧义 - -## Phase 6:测试体系与目录入口收口 - -### 目标 - -建立清晰的测试层级和单一入口。 - -### 任务 - -1. 明确保留与删除策略: - - `tests/UI/Core` - - `tests/UI/Runtime` - - `tests/UI/Editor` - - app/smoke/integration 单独归位 - -2. 清理 `tests/NewEditor`: - - 若继续保留,必须接入总测试树并修复目标名 - - 若不保留,迁移内容后删除目录 - -3. 杜绝 editor 库层测试直接编译 app 私有实现 - - 如果确有需要,应拆出正式可测接口或单独建立 app test target - -4. 修复失效资源路径与文档中的错误测试入口描述 - -### 涉及范围 - -- `tests/CMakeLists.txt` -- `tests/UI/**` -- `tests/NewEditor/**` -- 测试 README 与相关文档 - -### 验收标准 - -- 团队能明确说出“库层测试在哪,app 层测试在哪” -- 不再存在无法编译或未接入的历史测试死目录 - -### 当前进展(更新于 `2026-04-17`) - -- `tests/NewEditor` 已删除 -- `tests/UI/Editor/unit` 已停止直接编译 `new_editor/app` 私有 `.cpp` -- `tests/UI/Editor/integration` 叶子目标 CMake 已收敛到 shared helper -- helper 内已统一 `/utf-8`、`/FS` 与 `MSVC_RUNTIME_LIBRARY` -- `editor_ui_integration_tests` 已恢复全量构建通过 -- 尚未统一 integration warning 口径与运行入口文档 - -## Phase 7:命名与文档收口 - -### 目标 - -统一命名,降低认知负担。 - -### 任务 - -1. 统一 public API、target、test target 的命名规则 - -建议方向: - -- `XCEditor*` 用于 editor 公共 API 与库层 -- `XCEditorHost*` 用于平台/渲染宿主层 -- `XCEditorApp*` 用于最终应用层 - -2. 清理历史残留命名: - - `XCUIEditorLib` - - `XCUIEditorApp` - - `XCNewEditorLib` - - 旧文案中的 `Old editor shell` - -3. 更新 README、测试入口文档、架构文档 - -### 验收标准 - -- 从 public header、CMake target、测试目标名可以直接看出层级 -- 文档不再引用失效 target 或失效目录 - -## 7. 推荐实施顺序 - -建议按以下顺序推进,而不是并行乱改。 - -### 第一批:先收结构骨架 - -1. Phase 0 -2. Phase 1 -3. Phase 2 - -原因: - -- 这三步决定后面所有代码应该往哪里落 -- 如果不先收这三步,后面 panel/runtime/test 清理会持续返工 - -### 第二批:再收状态模型 - -1. Phase 3 -2. Phase 4 -3. Phase 5 - -原因: - -- panel runtime 接口和 workspace 不变量必须一起收 -- 否则会出现“接口改了,但状态模型还是旧的”这种半重构状态 - -### 第三批:最后收测试与命名 - -1. Phase 6 -2. Phase 7 - -原因: - -- 测试入口和命名要依赖前面的结构调整结果 -- 太早改测试命名,后面还得再改一轮 - -## 8. 目录与文件层面的建议落点 - -这部分不是最终目录定案,而是建议的收口方向。 - -### 8.1 engine 层 - -建议逐步形成: - -- `engine/include/XCEngine/UI/Core/*` -- `engine/include/XCEngine/UI/Runtime/*` -- `engine/src/UI/Core/*` -- `engine/src/UI/Runtime/*` - -并在 CMake 上对应真实 target。 - -### 8.2 editor 公共库层 - -建议逐步形成: - -- `new_editor/include/XCEditor/Foundation/*` -- `new_editor/include/XCEditor/Workspace/*` -- `new_editor/include/XCEditor/Panels/*` -- `new_editor/include/XCEditor/Shell/*` -- `new_editor/include/XCEditor/Collections/*` -- `new_editor/include/XCEditor/Fields/*` -- `new_editor/include/XCEditor/Viewport/*` - -以及对应的 `new_editor/src/*` 实现。 - -### 8.3 app 宿主层 - -建议限定为: - -- `new_editor/app/Bootstrap/*` -- `new_editor/app/Platform/*` -- `new_editor/app/Rendering/*` -- `new_editor/app/Composition/*` -- `new_editor/app/Features/*` -- `new_editor/app/State/*` - -其中: - -- `Composition` 负责应用装配 -- `Features` 负责具体面板业务 -- `State` 负责 app session/context - -### 8.4 测试层 - -建议最终明确分成: - -- `tests/UI/Core` -- `tests/UI/Runtime` -- `tests/UI/Editor` -- `tests/App/Editor` 或 `tests/EditorApp` - -不要再保留语义不清的 `tests/NewEditor`。 - -## 9. 风险与应对 - -### 风险 1:边界拆分后暴露大量 include/依赖穿透 - -应对: - -- 分阶段拆 target -- 允许先引入过渡 target -- 每一阶段都先保证能编译,再继续下拆 - -### 风险 2:panel 系统迁移时功能回退 - -应对: - -- 先迁移 HostedContent 面板 -- 再迁移 viewport shell -- 最后迁移多窗口与 cross-window drag - -### 风险 3:测试在重构中大面积失效 - -应对: - -- 先保留现有有效测试 -- 对失效测试先停用/迁移,不要假装它们还有效 -- 建立明确的 fixture 层级 - -### 风险 4:命名修改带来大规模 churn - -应对: - -- 先完成结构收口再统一 rename -- 对外 API 可以短期保留兼容别名 -- 文档在 rename 同一阶段一次性更新 - -## 10. 阶段验收标准 - -### Milestone A:结构边界完成 - -满足以下条件即可判定通过: - -- UI Core/Runtime/Editor 的 target 边界已经建立 -- `new_editor` 公共库层不再默认依赖整个大 engine target -- app 层与库层依赖方向清晰 - -### Milestone B:panel 与 workspace 模型完成 - -满足以下条件即可判定通过: - -- panel 关键事实源统一 -- registry 成为正式运行时接入点 -- workspace/window-workspace 校验补齐主键与一致性约束 - -### Milestone C:测试与命名完成 - -满足以下条件即可判定通过: - -- 测试入口单一且自洽 -- 历史死目录已迁移或删除 -- 命名体系统一,文档同步完成 - -## 11. 建议的执行策略 - -建议执行方式如下: - -1. 先做一轮只改 CMake、命名清单、边界文档和最小适配层的“骨架提交” -2. 再做 panel schema 与 workspace 校验的“核心结构提交” -3. 再做 panel runtime/lifecycle 的“运行时迁移提交” -4. 最后做测试迁移、目录清理、命名统一的“收尾提交” - -不建议一次性大改所有目录和所有实现。 - -## 12. 结论 - -当前 `new_editor` 最大的问题不是“某个 widget 写得不够好”,而是: - -- 分层没有真正体现在依赖图上 -- panel 事实源不统一 -- registry 不是正式扩展点 -- workspace 不变量不完整 -- 测试与命名存在历史残留 - -因此这次收口的正确方向不是继续堆功能,而是按本文的阶段顺序先把结构收稳。 - -只有完成这一轮收口,`new_editor` 才适合作为未来正式编辑器主线继续扩展。 diff --git a/docs/used/XCUI_Phase_Status_2026-04-05.md b/docs/used/XCUI_Phase_Status_2026-04-05.md deleted file mode 100644 index da95086b..00000000 --- a/docs/used/XCUI_Phase_Status_2026-04-05.md +++ /dev/null @@ -1,349 +0,0 @@ -# XCUI Phase Status 2026-04-05 - -## Scope - -Current execution stays inside the XCUI module and `new_editor`. -Old `editor` replacement is explicitly out of scope for this phase. - -## Latest Checkpoint - -- Phase 1 sandbox batch committed and pushed as `67a28bd` (`Add XCUI new editor sandbox phase 1`). -- Phase 2 common/runtime batch committed and pushed as `ade5be3` (`Add XCUI runtime screen layer and demo textarea`). -- Phase 3 has now produced a stable mixed batch across common/runtime/editor: - - schema document definition data is now retained on `UIDocumentModel` and round-trips through the UI artifact path - - schema self-definition validation is now stricter around enum/document-only metadata combinations - - engine runtime coverage was tightened again around `UISystem` and concrete document-host rendering - - `LayoutLab` continues as the editor widget proving ground for tree/list/property-section style controls - - the demo sandbox and editor bridge APIs were tightened again without touching the old editor replacement scope - - `new_editor` now has an explicit two-step window compositor seam through `IWindowUICompositor` / `ImGuiWindowUICompositor`, layered on top of `IEditorHostCompositor` / `ImGuiHostCompositor` -- The native-host / hosted-preview publication follow-up is now landed in `new_editor`: - - `NativeWindowUICompositor` is now buildable alongside the legacy ImGui compositor - - `Application` now defaults to a native XCUI host path instead of creating the ImGui shell by default - - the native default path now reuses shell-agnostic `XCUIDemoPanel::ComposeFrame(...)` / `XCUILayoutLabPanel::ComposeFrame(...)` results instead of keeping a second demo/layout runtime + input-bridge stack inside `Application` - - the native compositor now publishes hosted-preview textures as SRV-backed `UITextureRegistration` / `UITextureHandle` values instead of relying on ImGui-only descriptor semantics - - the native shell now begins the hosted-preview queue/registry lifecycle each frame, queues native preview frames, drains them during the native render pass, and consumes published hosted-surface images directly in panel cards with live/warming placeholder states - - the old ImGui shell path remains present as an explicit compatibility host instead of the default host - - `XCUIInputBridge.h` no longer drags `imgui.h` through the public XCUI input seam -- The default native text path is now also de-ImGuiized inside `new_editor`: - - `XCUIStandaloneTextAtlasProvider` now builds and owns its atlas through a Windows/GDI raster path instead of using ImGui font-atlas internals - - the standalone atlas provider now exposes both `RGBA32` and `Alpha8` views, supports non-nominal size resolution, lazily adds non-prebaked BMP glyphs, and falls back to `?` when a glyph cannot be rasterized -- The default native shell path now also has a cleaner translation-unit seam: - - legacy ImGui shell chrome / HUD rendering now lives in a dedicated legacy-only `Application` translation unit instead of keeping direct `ImGui::*` calls inside the main native host TU - - `Application.cpp` no longer directly includes ``, even though the compatibility host path is still compiled into `new_editor` -- The `new_editor` build now also has an explicit compatibility-source slice: - - legacy ImGui shell sources and vendored ImGui backend sources are now grouped into a dedicated compatibility static library instead of being compiled directly as part of the main `XCNewEditor` source list -- The main-target header-isolation milestone is now also landed in `new_editor`: - - `XCNewEditor` drops direct `${IMGUI_SOURCE_DIR}` / `${IMGUI_SOURCE_DIR}/backends` include-directory requirements - - the generated `XCNewEditor.vcxproj` no longer advertises `imgui-src` or `editor/src` on the default include surface - - the default native compile path no longer reaches `` through `editor/src/UI/ImGuiBackendBridge.h` or equivalent bridge headers - - `XCNewEditorImGuiCompat` remains the explicit compatibility-only consumer of legacy ImGui headers and backend sources -- The compositor/font compat boundary is now tighter as well: - - `IWindowUICompositor.h` and `IEditorHostCompositor.h` no longer declare `CreateImGui*` factories on the generic XCUI seam - - ImGui factory entry points now live only in `LegacyImGuiHostInterop.h` - - `XCUIEditorFontSetup.h` no longer exposes `ImFont` / `ImFontAtlas`; the public API is now a current-context font bootstrap helper - - `XCUIStandaloneTextAtlasProvider.h` no longer depends on the ImGui-oriented font bootstrap header -- The generic shell command/state boundary is now narrower as well: - - `XCUIShellChromeState` no longer carries the legacy host demo-window toggle or command id on the generic shell-state surface - - `Application.h` no longer exposes that legacy demo command through generic `ShellCommandIds`, `ShellCommandBindings`, or `RegisterShellViewCommands(...)` - - the legacy demo window toggle now lives as a compatibility-only command inside `ApplicationLegacyImGui.cpp` - - `Application.h` now also uses backend-neutral compatibility-host naming for the non-native window-host path, so the generic application header no longer names `LegacyImGui` in its mode enum or private host-frame method declarations - - generic `Application` host-mode/member naming is now also shifting from `LegacyImGui` toward `CompatibilityHost`, so the default shell surface does not have to encode ImGui into every host-facing method name -- Old `editor` replacement remains deferred; all active execution still stays inside XCUI shared code and `new_editor`. - -## Three-Layer Status - -### 1. Common Core - -- `UI::DrawData`, input event types, focus routing, style/theme resolution are in active use. -- `UIDocumentCompiler` is buildable again after repairing the duplicated schema helper regression introduced by overlapping schema work. -- `UIDocumentModel` / `UIDocumentResource` now retain schema definition metadata explicitly, including memory accounting and `UISchema` accessors. -- `.xcschema` round-trip coverage is now present through compile, loader, artifact write, and artifact read paths. -- Build-system hardening for MSVC/PDB output paths has started in root CMake, `engine/CMakeLists.txt`, `new_editor/CMakeLists.txt`, and `tests/NewEditor/CMakeLists.txt`. -- Shared engine-side XCUI runtime scaffolding is now present under `engine/include/XCEngine/UI/Runtime` and `engine/src/UI/Runtime`. -- Shared engine-side `UIDocumentScreenHost` now compiles `.xcui` / `.xctheme` screen documents into a runtime-facing document host path instead of leaving all document ownership in `new_editor`. -- Shared text-editing primitives now live under `engine/include/XCEngine/UI/Text` and `engine/src/UI/Text`, so UTF-8 caret movement, line splitting, and multiline navigation are no longer trapped inside `XCUI Demo`. -- Shared text-input controller/state now also lives under `engine/include/XCEngine/UI/Text` and `engine/src/UI/Text`, so character insertion, backspace/delete, submit, and multiline key handling no longer need to be reimplemented per host. -- Shared editor collection primitive classification and metric helpers now also live under `engine/include/XCEngine/UI/Widgets` and `engine/src/UI/Widgets`, covering the current `ScrollView` / `TreeView` / `ListView` / `PropertySection` / `FieldRow` prototype taxonomy. -- Shared single-selection state now also lives under `engine/include/XCEngine/UI/Widgets` and `engine/src/UI/Widgets` as `UISelectionModel`, so collection-style widget selection no longer has to stay private to `LayoutLab`. -- Shared expansion state now also lives under `engine/include/XCEngine/UI/Widgets` and `engine/src/UI/Widgets` as `UIExpansionModel`, so collapsible tree/property-style widget state no longer has to stay private to `LayoutLab`. -- Shared keyboard-navigation state now also lives under `engine/include/XCEngine/UI/Widgets` and `engine/src/UI/Widgets` as `UIKeyboardNavigationModel`, so list/tree/property-style widgets can share current-index, anchor, and home/end/step navigation rules instead of re-rolling them per sandbox. -- Shared property-edit session state now also lives under `engine/include/XCEngine/UI/Widgets` and `engine/src/UI/Widgets` as `UIPropertyEditModel`, so editor-facing field rows can reuse begin/edit/commit/cancel transaction state instead of baking that directly into sandbox runtimes. -- Core regression coverage now includes `UIContext`, layout, style, runtime screen player/system, and real document-host tests through `core_ui_tests`. - -Current gap: - -- Minimal schema self-definition support is landed, including consistency checks for enum/document-only schema metadata, but schema-driven validation for `.xcui` / `.xctheme` instances is still not implemented. -- Shared widget/runtime instantiation is still thin and mostly editor-side. -- Common widget primitives are still incomplete: shared text-input presentation/composition on top of the new text controller, multi-selection/focus-traversal/virtualized collection state on top of the new editor-primitive helpers, shared scroll/navigation-scope/caret-layout helpers, and promotion of the current native text-atlas path into a shared/cross-platform text subsystem. - -### 2. Runtime/Game Layer - -- The main concrete progress here is that the retained-mode demo runtime now supports a real `TextField` input path with UTF-8 text entry and backspace handling. -- The demo runtime has moved past single-line input: multiline `TextArea` behavior is now covered in the sandbox testbed. -- Engine-side runtime ownership is no longer zero: `UIScreenPlayer`, `UIDocumentScreenHost`, and `UISystem` now define a shared runtime contract for loading a screen document, ticking it with input, and collecting `UI::UIDrawData`. -- `UISystem` now supports layered screen composition semantics: stacked screen players, top-interactive input routing, and modal layers that block lower screens. -- `UIScreenStackController::ReplaceTop` now preserves the previous top screen if the replacement screen fails to load, so runtime menu flows do not silently drop their active layer on bad assets. -- `SceneRuntime` now owns a dedicated `UISceneRuntimeContext`, so game/runtime code has a first-class place to configure viewport/focus, queue `UIInputEvent`s, drive `UISystem` each `Update`, and inspect the latest UI frame result. -- Runtime screen emission now also carries concrete button text in the shared document host path instead of silently dropping button labels. -- `UISystemFrameResult` now also preserves viewport rect, submitted input count, frame delta, and focus state, and both `UISystem` and `UISceneRuntimeContext` now expose `ConsumeLastFrame()` so runtime/game hosts can drain the last retained frame packet without copying editor-side concepts into the shared layer. -- `UIScreenPlayer` now also exposes `ConsumeLastFrame()`, so player/system/context all share the same consume-vs-borrow frame ownership semantics in the runtime layer. - -Current gap: - -- Runtime UI is now wired into `SceneRuntime`, but render submission is still limited to producing `UIDrawData`; there is no game-view/runtime presenter path that automatically draws those frames yet. -- The runtime widget library is still shallow and missing the editor-grade controls that will later be shared downward. - -### 3. Editor Layer - -- `new_editor` remains the isolated XCUI sandbox. -- Native hosted preview is now working end-to-end as `RHI offscreen surface -> SRV-backed publication -> hosted surface image present` through the native shell path. -- Hosted preview surface descriptors now stay on XCUI-owned value types (`UITextureHandle`, `UIPoint`, `UIRect`) instead of exposing ImGui texture/UV types through the generic preview contract. -- Shared `UI::UIDrawData` image commands now carry explicit `uvMin` / `uvMax` source-rect semantics, and the native panel canvas host preserves those UVs when it records hosted surface-image preview commands. -- `XCUI Demo` remains the long-lived effect and behavior testbed. -- `XCUI Demo` now covers both single-line and multiline text authoring behavior, including click caret placement, delete/backspace, tab indentation, and optional text-area line numbers. -- `XCUI Demo` now consumes the shared `UITextInputController` path for text editing instead of carrying a private key-handling state machine. -- `LayoutLab` now includes a `ScrollView` prototype and a more editor-like three-column authored layout. -- `LayoutLab` now also covers editor-facing widget prototypes: `TreeView`, `TreeItem`, `ListView`, `ListItem`, `PropertySection`, and `FieldRow`. -- `LayoutLab` now consumes the shared `UIEditorCollectionPrimitives` helper layer for collection-widget tag classification, clipping flags, and default metric resolution instead of keeping that taxonomy private to the sandbox runtime. -- `LayoutLab` now also consumes the shared `UISelectionModel` for click-selection persistence across collection-style widgets, and the diagnostics panel now exposes both hovered and selected element ids. -- `LayoutLab` now also consumes the shared `UIExpansionModel` for tree expansion and property-section collapse, with reserved property headers, disclosure glyphs, and persisted click-toggle behavior in the sandbox runtime. -- `new_editor` now also has an isolated `XCUIEditorCommandRouter` model with shortcut matching, enable predicates, and direct command invocation semantics covered by dedicated tests, ready for shell-frame integration. -- `XCUI Demo` now exports pending per-frame command ids through `DrainPendingCommandIds()`, so editor-side hosts have a clean seam for observing demo/runtime command traffic without parsing draw data. -- `XCUI Demo` and `LayoutLab` panel canvases are now being pulled behind a dedicated `IXCUIPanelCanvasHost` seam, so canvas surface presentation, hover/focus fallback state, and overlay draw hooks no longer have to stay hard-coded inside each ImGui panel implementation. -- The panel-canvas seam now keeps the generic host contract on observable canvas behavior only; backend/capability identity probing has been removed from `IXCUIPanelCanvasHost`, and the minimal `NullXCUIPanelCanvasHost` remains the concrete placeholder host for non-ImGui paths. -- `XCUI Demo` and `LayoutLab` panel input now also flows through an explicit `IXCUIInputSnapshotSource` seam, so panel/runtime code no longer reads `ImGuiIO` / `ImGui::IsKeyPressed` / `ImGui::IsMouseClicked` directly when the shell wants to use an ImGui adapter. -- `new_editor` now also has an explicit `ImGuiXCUIInputSnapshotSource` adapter, keeping ImGui-specific input capture in the host adapter layer instead of inside panel/runtime update code. -- `XCUI Demo` and `LayoutLab` panels now both expose shell-agnostic per-frame composition results (`ComposeFrame(...)` + `GetLastFrameComposition()`), so native/compat shells can reuse one panel-local frame pipeline instead of duplicating runtime/input/preview assembly logic. -- `XCUIDemoPanel::ComposeFrame(...)` now also accepts an injected input snapshot plus explicit placeholder/frame options, which lets the native shell reuse the panel pipeline without falling back to the compatibility-path placeholder behavior on direct draw-data cards. -- Panel diagnostics were expanded to clearly separate preview/runtime/input state and native vs legacy paths. -- The editor bridge layer now has smoke coverage for swapchain after-UI rendering hooks and SRV-backed ImGui texture descriptor registration. -- `Application` no longer owns the ImGui backend directly; window presentation now routes through `IWindowUICompositor` with an `ImGuiWindowUICompositor` implementation, which currently delegates to `IEditorHostCompositor` / `ImGuiHostCompositor`. -- Hosted preview offscreen surfaces now keep compositor-returned `UITextureRegistration` / `UITextureHandle` data inside `Application` instead of storing `ImTextureID` directly. -- The generic hosted-preview presenter contract no longer owns `ImGuiTransitionBackend`; the ImGui presenter now sits in a separate `ImGuiXCUIHostedPreviewPresenter` header while the native queue/surface registry remains XCUI-generic. -- The generic hosted-preview frame contract no longer carries an ImGui draw-list pointer; the legacy ImGui presenter resolves its inline draw target from the active ImGui window context instead of pushing that type through the XCUI contract. -- The legacy ImGui hosted-preview presenter now also accepts an explicit draw-target binding object, so presenter-side `ImGui::GetWindowDrawList()` lookup is no longer hard-coded inside the generic presenter path and can stay isolated behind the ImGui adapter layer. -- `Application` shell menu toggles and global shortcuts now route through `XCUIEditorCommandRouter` instead of directly mutating shell booleans from menu callbacks, giving the editor layer a real command-routing seam. -- `LayoutLab` runtime now consumes the shared `UIKeyboardNavigationModel` for abstract list/tree/property navigation actions (`previous/next/home/end/collapse/expand`), so keyboard collection traversal rules are no longer trapped in sandbox-local state. -- `LayoutLab` panel input now also maps concrete arrow/home/end keys into those shared navigation actions, so keyboard traversal is reachable from the sandbox UI instead of staying runtime-only. -- `XCUIDemoRuntime` now bridges pointer activation, text-edit commands, and shortcut-triggered commands through a unified command path, and `DrainPendingCommandIds()` now preserves mixed pointer/text/shortcut ordering. -- `new_editor` now also has a pure `XCUIShellChromeState` model covering panel visibility, hosted-preview mode, and shell-level view toggles without depending on ImGui, `Application`, or the old editor. -- `XCUIShellChromeState` hosted-preview mode naming is now backend-neutral (`HostedPresenter` / `NativeOffscreen`) instead of encoding `ImGui` into the XCUI shell model. -- The shell chrome view-toggle model no longer carries the legacy host demo window at all on the generic XCUI seam; that command now lives only inside the compatibility-only legacy host implementation. -- `new_editor` now also has a concrete `NativeWindowUICompositor` path and native-focused compositor tests, so the window compositor seam is no longer ImGui-only. -- `Application` now also has a native XCUI shell path that: - - becomes the default `new_editor` startup path - - lays out `XCUI Demo` and `Layout Lab` as native cards directly in the swapchain window - - routes shell shortcuts through the same command router without reading ImGui capture state in the default host path - - reuses panel-local `ComposeFrame(...)` entry points for demo/layout runtime, input, hosted-preview, and overlay composition instead of maintaining duplicate native-shell runtime/input state in `Application` - - composes one native `UIDrawData` packet and submits it through `NativeWindowUICompositor` -- Native shell preview-mode reconfiguration now rebuilds the native panel bindings instead of rebinding a legacy hosted presenter, so the default host path no longer needs the ImGui presenter when a card stays on direct draw-data composition. -- The native shell layout policy now also lives behind `XCUINativeShellLayout`, so top-bar/footer/workspace geometry, panel split rules, and active-panel transfer on pointer press are no longer hard-coded inline inside `Application.cpp`. -- `NativeXCUIPanelCanvasHost` now backs that direct shell path as an externally driven canvas/session host for native cards instead of assuming an ImGui child-window model, and it now emits native `Image` draw commands for hosted surface-image previews while preserving per-surface UVs. -- `NativeWindowUICompositor` now creates and frees SRV-backed texture registrations for hosted preview surfaces, so native publication no longer depends on ImGui descriptor handles. -- `Application` now runs the hosted-preview lifecycle in both legacy and native frame paths, treats published textures as XCUI-owned `UITextureHandle` state, queues native preview frames from `BuildNativeShellDrawData(...)`, and drains them during native rendering before shell chrome overlays. -- `XCUIInputBridge.h` no longer includes `imgui.h`, so the public XCUI input bridge seam is now host-neutral at the header boundary. -- `XCNewEditor` builds successfully to `build/new_editor/bin/Debug/XCNewEditor.exe`. - -Current gap: - -- The default shell host is now native, and the legacy ImGui shell/panel path has been split out of the default executable into the standalone `XCNewEditorImGuiCompat` compatibility slice. -- The default native shell path is now split away from direct `ImGui::*` calls at the main-target header/include level and no longer links the compatibility slice by default. -- The default native shell now also consumes shared `XCUINativeShellLayout` and `UIEditorPanelChrome` helpers for panel split/chrome policy instead of duplicating that card layout logic entirely inside `Application.cpp`. -- The native shell currently proves direct runtime composition, but its shell chrome is still a bespoke `Application`-side layout rather than a fully shared XCUI-authored editor shell document. -- Editor-specialized widgets are still incomplete at the shared-module level: the authored prototypes exist, but virtualization, multi-selection/focus traversal, toolbar/menu chrome, menu interaction widgets, and icon-atlas widgets are not yet extracted into reusable XCUI modules. -- The default native text path now uses a standalone Windows/GDI atlas through `XCUIStandaloneTextAtlasProvider`, but that provider still lives inside `new_editor` and is not yet promoted into a shared/cross-platform text subsystem. - -## Validated This Phase - -- `new_editor_xcui_demo_panel_tests`: `4/4` -- `new_editor_xcui_demo_runtime_tests`: `12/12` -- `new_editor_xcui_input_bridge_tests`: `4/4` -- `new_editor_imgui_xcui_input_adapter_tests`: `2/2` -- `new_editor_xcui_layout_lab_runtime_tests`: `12/12` -- `new_editor_xcui_rhi_command_compiler_tests`: `7/7` -- `new_editor_xcui_rhi_render_backend_tests`: `5/5` -- `new_editor_xcui_standalone_text_atlas_provider_tests`: `6/6` -- `new_editor_xcui_hosted_preview_presenter_tests`: `20/20` -- `new_editor_legacy_imgui_host_interop_tests`: `4/4` -- `new_editor_imgui_window_ui_compositor_tests`: `7/7` -- `new_editor_native_window_ui_compositor_tests`: `8/8` -- `new_editor_xcui_editor_command_router_tests`: `5/5` -- `new_editor_application_shell_command_bindings_tests`: `11/11` -- `new_editor_xcui_shell_chrome_state_tests`: `11/11` -- `new_editor_xcui_panel_canvas_host_tests`: `4/4` -- `new_editor_imgui_xcui_panel_canvas_host_tests`: `1/1` -- `new_editor_native_xcui_panel_canvas_host_tests`: `4/4` -- `new_editor_xcui_layout_lab_panel_tests`: `6/6` -- `XCNewEditor` Debug target builds successfully -- `XCNewEditor.exe` native-default smoke run stayed alive for `5s` -- `core_ui_tests`: `52 total` (`50` passed, `2` skipped because `KeyCode::Delete` currently aliases `Backspace`) -- `scene_tests`: `68/68` -- `core_ui_style_tests`: `5/5` -- `ui_resource_tests`: `11/11` -- `editor_tests` targeted bridge smoke: `3/3` - -## Landed This Phase - -- Demo runtime `TextField` with UTF-8 text insertion, caret state, and backspace. -- Demo runtime multiline `TextArea` path in the sandbox and test coverage for caret movement / multiline input. -- Common-core `UITextEditing` extraction now owns UTF-8 offset stepping, codepoint counting, line splitting, and vertical caret motion with dedicated `core_ui_tests` coverage. -- Common-core `UITextInputController` extraction now owns per-field text state, character insertion, enter-submit, and multiline keyboard editing behavior with dedicated `core_ui_tests` coverage. -- Common-core `UIEditorCollectionPrimitives` extraction now owns the editor collection tag taxonomy and default metric resolution used by current `LayoutLab` widget prototypes, with dedicated `core_ui_tests` coverage. -- Common-core `UISelectionModel` extraction now owns reusable single-selection state for collection-style widgets, with dedicated `core_ui_tests` coverage. -- Common-core `UIExpansionModel` extraction now owns reusable expansion/collapse state for tree/property-style widgets, with dedicated `core_ui_tests` coverage. -- Common-core `UIKeyboardNavigationModel` extraction now owns reusable current-index/anchor navigation state for collection-style widgets, with dedicated `core_ui_tests` coverage. -- Common-core `UIPropertyEditModel` extraction now owns reusable property-field edit session state, including staged values and commit/cancel behavior, with dedicated `core_ui_tests` coverage. -- Runtime frame ownership was tightened again: - - `UIScreenPlayer::ConsumeLastFrame()` now exposes consume-style packet ownership at the player layer - - `UISystemFrameResult` now carries viewport rect, submitted input event count, frame delta, and focus state - - `UISystem::ConsumeLastFrame()` moves the retained packet out of the runtime layer - - `UISceneRuntimeContext::ConsumeLastFrame()` forwards the same shared runtime seam upward -- Demo runtime text editing was extended with: - - click-to-place caret - - `Delete` support - - `Tab` / `Shift+Tab` indentation for text areas - - optional text-area line-number gutter rendering -- Demo authored resources updated to exercise the input field. -- LayoutLab `ScrollView` prototype with clipping and hover rejection outside clipped content. -- LayoutLab editor-widget prototypes for tree/list/property-style sections with dedicated runtime coverage. -- LayoutLab click-selection now persists through the shared `UISelectionModel`, including selected-state diagnostics and reusable visual selection feedback on cards, collection rows, and field rows. -- LayoutLab tree expansion and property-section collapse now persist through the shared `UIExpansionModel`, including reserved property headers, disclosure glyphs, and runtime coverage for collapsed/expanded visibility. -- `XCUIDemoRuntime` now exposes `DrainPendingCommandIds()` so hosts can observe emitted runtime commands in order across pointer/text interactions without scraping UI text or draw-command payloads. -- `XCUIDemoRuntime` command recording was tightened so pointer activation, text editing, and shortcut-triggered commands now share one bridge path and preserve mixed ordering in `DrainPendingCommandIds()`. -- Schema document support extended with: - - retained `UISchemaDefinition` data on `UIDocumentModel` - - artifact schema version bump for UI documents - - loader/resource accessors and memory accounting - - schema compile/load/artifact regression coverage - - schema consistency rules for: - - `allowedValues` only on `enum` - - `documentKind` / `restrictDocumentKind` only on `document` - - explicit `documentKind` required when `restrictDocumentKind=true` -- Engine runtime layer added: - - `UIScreenPlayer` - - `UIDocumentScreenHost` - - `UISceneRuntimeContext` - - `UIScreenStackController` - - `UISystem` - - layered screen composition and modal blocking semantics -- Runtime/game integration scaffolding now includes reusable `HUD/menu/modal` stack helpers on top of `UISystem`. -- `UIScreenStackController` replacement now rolls back safely on failure instead of popping the active top layer first. -- `SceneRuntime` now exposes XCUI runtime ownership directly: - - `GetUISystem()` - - `GetUIScreenStackController()` - - `GetLastUIFrame()` - - `SetUIViewportRect(...)` - - `SetUIFocused(...)` - - `QueueUIInputEvent(...)` - - `ClearQueuedUIInputEvents()` - - automatic `UISystem` ticking during `SceneRuntime::Update(...)` -- Runtime document-host draw emission now preserves button labels for shared screen rendering. -- RHI image path improvements: - - clipped image UV adjustment - - mirrored image UV preservation - - explicit image UV/source-rect submission through `UI::UIDrawData` - - external texture binding reuse - - per-batch scissor application -- Editor bridge helpers now expose: - - an `afterUiRender` swapchain callback hook in `D3D12WindowRenderer` - - SRV-view based texture descriptor registration in `ImGuiBackendBridge` - - smoke tests for window renderer, ImGui backend bridge, and console sink registration -- `new_editor` host presentation now has a first-class compositor seam: - - `IWindowUICompositor` - - `ImGuiWindowUICompositor` - - `IEditorHostCompositor` - - `ImGuiHostCompositor` - - `Application` frame/present flow routed through the compositor instead of direct `m_imguiBackend` ownership -- The window-level XCUI compositor seam now also has a dedicated regression target around `ImGuiWindowUICompositor`, covering initialization, render-frame ordering, Win32 message forwarding, texture registration forwarding, and shutdown safety. -- The legacy compatibility seam now also has dedicated regression coverage around `LegacyImGuiHostInterop`, covering compat factory wiring, ImGui input-capture forwarding, legacy font bootstrap, and the disabled demo-window path. -- The window compositor and hosted-preview seams gained more edge-case coverage around no-UI render passes, compositor re-initialization/rebinding, partial logical-size fallback, and descriptor reuse for repeated queued-frame keys. -- `new_editor` now has a dedicated `XCUIEditorCommandRouter` test target covering command registration, replacement, enable predicates, accelerator matching, and policy gates around focus/keyboard capture/text input. -- `Application` now integrates `XCUIEditorCommandRouter` into the shell itself: - - `View` menu items invoke routed commands instead of directly mutating shell state - - shell-level shortcuts now flow from `XCUIWin32InputSource` through `XCUIInputBridge` into command matching - - hosted-preview mode toggles still trigger presenter reconfiguration through the routed command bindings -- `new_editor` panel canvas ownership is now being split behind `IXCUIPanelCanvasHost`, with an `ImGuiXCUIPanelCanvasHost` adapter carrying the legacy path so panel code stops directly owning `ImGui::Image` / `ImGui::InvisibleButton` / draw-list preview plumbing. -- `XCUIDemoPanel` and `XCUILayoutLabPanel` no longer create an ImGui hosted-preview presenter or ImGui panel canvas host implicitly; default construction now stays on null/explicitly injected backends until the outer shell binds a concrete host adapter. -- `Application` now binds `ImGuiXCUIPanelCanvasHost` explicitly at the shell composition root, so the current ImGui panel host path is visible as a host-layer decision instead of a panel-layer fallback. -- `XCUIDemoPanel` and `XCUILayoutLabPanel` no longer read ImGui input directly; both now consume an injected `IXCUIInputSnapshotSource`, and the new `ImGuiXCUIInputSnapshotSource` keeps the current ImGui-backed input path isolated behind an explicit adapter. -- `new_editor` now also has a pure `XCUIShellChromeState` model with dedicated tests, covering shell panel visibility, hosted-preview mode, and shell view toggles without depending on ImGui or `Application`. -- `XCUIShellChromeState` now also exposes effective hosted-preview state helpers and shell view-toggle command-id helpers, so shell routing code no longer has to manually combine enablement and requested preview mode. -- `XCUIShellChromeState` hosted-preview modes were renamed away from `LegacyImGui`, so XCUI shell state no longer treats ImGui as the generic fallback concept. -- The panel-canvas seam now has dedicated null/compat/native coverage around stable debug names, passive-session safety, externally driven native snapshots, and compat-host factory creation without relying on explicit backend/capability reporting. -- The native host follow-up is now present in `new_editor`: - - `NativeWindowUICompositor` provides a swapchain-native XCUI packet present path beside the legacy ImGui compositor - - `Application` now defaults to that native host path and directly composes `XCUI Demo` plus `Layout Lab` into one native shell frame - - `NativeXCUIPanelCanvasHost` now drives externally configured native card sessions for that shell path and records hosted surface-image preview commands with preserved UVs - - new native compositor/native canvas-host tests now cover the new host seam -- `XCUIInputBridge.h` no longer includes `imgui.h`, so XCUI input translation is no longer coupled to ImGui at the public header boundary. -- `SceneRuntime` layered XCUI routing now has dedicated regression coverage for: - - top-interactive layer input ownership - - blocking/modal layer suppression of lower layers - - hidden top-layer pass-through back to visible underlying layers -- Shared `UITextInputController` coverage now includes more caret-boundary / modifier branches; the remaining `Delete` distinction stays blocked on `KeyCode::Delete` and `KeyCode::Backspace` still sharing the same enum value. -- Window compositor texture registration now also flows back into `Application` as XCUI-owned `UITextureRegistration` / `UITextureHandle` data instead of exposing raw `ImTextureID` there. -- Hosted preview contracts were tightened again: - - generic preview surface metadata stays on XCUI-owned value types - - `ImGuiTransitionBackend` moved behind `ImGuiXCUIHostedPreviewPresenter` - - generic preview frame submission no longer carries an ImGui draw-list pointer - - the ImGui presenter now resolves inline draw targets through an explicit ImGui-only binding seam - - panel/runtime callers still preserve the same legacy and native-preview behavior -- Native hosted-preview publication milestone is now wired through the default shell path: - - `NativeWindowUICompositor` publishes hosted preview textures as SRV-backed XCUI registrations and frees them through the compositor seam - - `Application::BeginHostedPreviewFrameLifecycle(...)` now resets queue/registry state for both legacy and native frame paths - - hosted-preview surface readiness now keys on published texture availability instead of ImGui-style descriptor validity - - `BuildNativeShellDrawData(...)` now queues native preview frames for `XCUI Demo` / `LayoutLab`, while shell cards consume the previously published hosted-surface image with warming/live placeholder text - - native compositor and shell-command tests now cover the new publication / lifecycle guards -- `LayoutLab` now resolves editor collection widget taxonomy and metrics through shared `UIEditorCollectionPrimitives` helpers instead of duplicating the same tag and metric rules inside the sandbox runtime. -- `LayoutLab` runtime now consumes shared keyboard-navigation semantics for list/tree/property traversal, while the remaining panel-level key mapping is tracked as an editor-host integration gap rather than a runtime gap. -- `LayoutLab` panel now maps concrete arrow/home/end keys into the shared navigation model, with dedicated panel-level coverage proving that the sandbox UI can actually drive the runtime navigation seam end-to-end. -- `new_editor` panel/shell diagnostics improvements for hosted preview state. -- XCUI asset document loading changed to prefer direct source compilation before `ResourceManager` fallback for the sandbox path, fixing the LayoutLab crash. -- `UIDocumentCompiler.cpp` repaired enough to restore full local builds after the duplicated schema-helper regression. -- MSVC debug build hardening was tightened again so large parallel `engine` rebuilds stop tripping over compile-PDB contention. -- `XCUIStandaloneTextAtlasProvider` no longer uses ImGui font-atlas/internal baking helpers: - - atlas ownership now stays inside a standalone provider implementation built on Windows/GDI glyph rasterization - - default editor atlas prewarms the current supported nominal sizes, exposes both `RGBA32` and `Alpha8` atlas views, lazily inserts non-prebaked BMP glyphs, and falls back to `?` for unrasterizable codepoints - - standalone atlas coverage now includes reset/rebuild, non-nominal size resolution, lazy glyph insertion/fallback behavior, and smoke use without any ImGui context -- Legacy shell chrome / HUD rendering is now split out of the main `Application.cpp` translation unit: - - the direct `ImGui::*` shell rendering path now lives in a dedicated legacy-only `Application` implementation file - - the main `Application.cpp` native host path no longer directly includes ``, reducing default-path compile-time coupling while the remaining main-target header/include cleanup stays open -- `new_editor` build composition is now split into main/native and compatibility slices: - - the main `XCNewEditor` target no longer compiles legacy ImGui shell/panel/backend source files directly - - legacy ImGui shell/panel/backend sources plus vendored ImGui sources now build behind a dedicated compatibility static library that the main executable links - - the main target no longer carries ImGui include directories or older editor bridge headers on its direct compile surface; those remain confined to the compatibility slice -- The default XCUI compile seam is now also cleaner: - - generic compositor headers no longer advertise compat-only `CreateImGui*` factories - - compat-only host/window compositor factories now live behind `LegacyImGuiHostInterop.*` - - public font bootstrap headers no longer leak `ImFont` / `ImFontAtlas` types into generic XCUI consumers - -## Phase Risks Still Open - -- Schema instance validation is still open beyond `.xcschema` self-definition and artifact round-trip coverage. -- `ScrollView` is still authored/static; no wheel-driven scrolling or virtualization yet. -- `XCNewEditor` no longer depends on ImGui at the default-path header/include level and no longer links `XCNewEditorImGuiCompat`; the legacy shell/panel path now lives only in the standalone compatibility slice. -- Legacy panel implementations such as `XCUIDemoPanel` / `XCUILayoutLabPanel` still render as ImGui windows inside the compatibility slice, so editor-layer behavior is not yet fully carried by XCUI-native shell composition. -- The default native text path now owns its atlas without ImGui, but the provider is still Windows-only and remains trapped inside `new_editor` instead of a shared/cross-platform text layer. -- Hosted-preview compatibility presentation still depends on an ImGui-only inline presenter path when not using the queued native surface path. -- Editor widget coverage is still prototype-driven inside `LayoutLab`; it has not yet been promoted into a full reusable shared widget/runtime layer with command routing, virtualization, and property-edit transactions. - -## Execution-Plan Alignment - -- Against `XCUI完整架构设计与执行计划.md`, current `new_editor` progress should be treated as an early `Phase 8` foothold rather than full `Milestone E` completion: - - landed: `NativeWindowUICompositor`, native shell packet composition, native hosted-preview publication, XCUI-owned texture registrations, native panel surface-image presentation, standalone native text-atlas ownership inside `new_editor`, legacy `Application` TU split, `XCNewEditorImGuiCompat`, main-target header/include isolation from ImGui, default-executable unlinking from the compatibility slice, compat-only factory/font seam tightening, shared native shell layout helpers, and shared panel-chrome/flat-hierarchy helpers - - not yet landed: promotion of the native text-atlas path into a shared/cross-platform text subsystem, shared XCUI-authored editor shell chrome, and retirement of legacy ImGui-window panel implementations -- That means the next de-ImGui push should not keep centering on hosted-preview publication; that milestone is now effectively closed for the default native shell path. -- The real remaining default-path blockers are: - - move editor-layer behavior off the legacy ImGui-window panel implementations and into native/XCUI shell composition - - keep compat-only factories/types from drifting back into generic XCUI seams while the compatibility slice remains linked - - harden and promote `XCUIStandaloneTextAtlasProvider` / editor font bootstrap into a shared native text subsystem - - move native shell chrome out of bespoke `Application` layout code and into a shared XCUI shell model or authored shell document - -## Next Phase - -1. Expand runtime/game-layer ownership from the current `SceneRuntime` UI context into scene-declared HUD/menu bootstrapping, draw submission, and higher-level runtime UI policies. -2. Promote the current editor-facing widget prototypes out of authored `LayoutLab` content and into reusable XCUI widget/runtime modules, then continue with toolbar/menu chrome, shell-state adoption, virtualization, and broader focus/multi-selection behavior. -3. Push the remaining de-ImGui cleanup from build/header isolation into behavior isolation: keep ImGui confined to `XCNewEditorImGuiCompat`, keep generic XCUI seams free of compat-only factories/types, and keep shrinking the legacy shell boundary. -4. Promote the current standalone native text/font path out of `new_editor`, harden its atlas invalidation/caching contract, and keep the remaining font/bootstrap ownership compatibility-only. -5. Promote the native shell chrome and card layout out of bespoke `Application` code into a shared XCUI/editor-layer shell model or authored shell document, then retire the remaining ImGui-window panel path. -6. Continue phased validation, commit, push, and plan refresh after each stable batch. diff --git a/docs/used/XCUI_Project运行时解耦收口计划_2026-04-18.md b/docs/used/XCUI_Project运行时解耦收口计划_2026-04-18.md deleted file mode 100644 index 5f7855de..00000000 --- a/docs/used/XCUI_Project运行时解耦收口计划_2026-04-18.md +++ /dev/null @@ -1,116 +0,0 @@ -# XCUI Project 运行时解耦收口计划 -日期: `2026-04-18` -状态: `已完成` - -## 1. 根因确认 - -当前 `new_editor` 的 `Project` 主线,最大的问题不是单个交互缺陷,也不是单纯的胖文件,而是缺少独立的 `Project runtime/service` 层。 - -现状表现为: - -- `ProjectPanel` 既负责 UI 布局和输入,又负责命令目标解析、文件系统动作分派、上下文菜单语义、打开行为和部分状态协调。 -- `ProjectBrowserModel` 名义上是 model,实际上同时承担了项目路径语义、文件系统写操作、目录树投影和右侧资源列表投影。 -- `Project` 的选择和打开行为目前依赖 `ProjectPanel::Event -> WorkspaceEventSync -> EditorContext` 的事件回放同步,而不是围绕单一真源同步。 -- `Hierarchy` 已经有 `EditorSceneRuntime` 作为真源,但 `Project` 没有对等层级,因此 `ProjectPanel` 被迫长成领域逻辑宿主。 - -这会直接导致: - -- 状态源头分裂,`current folder / asset selection / session.selection` 只能靠补同步维持一致。 -- 任何新功能都会继续堆进 `ProjectPanel.cpp`。 -- 命令和 UI 表面强耦合,无法形成稳定的正式主链。 - -## 2. 本轮目标 - -本轮不是继续往 `ProjectPanel` 堆功能,而是先把 `Project` 的真源补出来,建立清晰依赖方向: - -- `EditorContext` 持有 `EditorProjectRuntime` -- `EditorProjectRuntime` 持有 `ProjectBrowserModel` 和项目选择/打开请求等领域状态 -- `ProjectPanel` 只消费 runtime,负责展示、输入、局部交互会话 -- `WorkspaceEventSync` 不再以 `ProjectPanel` 事件作为项目选择和打开场景的唯一来源 - -收口完成后的目标状态: - -- `ProjectPanel` 不再自持 project 领域真状态 -- `Project` 的 selection/open 主链改为 runtime 真源同步 -- 后续 `duplicate / clipboard / instantiate model / preview` 都能挂到 runtime 层继续收 - -## 3. 执行步骤 - -### 阶段 A: 引入 Project runtime 真源 - -- 新增 `EditorProjectRuntime` -- 让它负责: - - 初始化项目路径上下文 - - 持有 `ProjectBrowserModel` - - 持有当前项目选择状态 - - 持有待消费的“打开场景/打开项目项”请求 - - 对外暴露正式项目操作接口 - -### 阶段 B: ProjectPanel 改为消费 runtime - -- `ProjectPanel` 增加 `SetProjectRuntime(...)` -- `ProjectPanel` 改为通过 runtime 访问: - - 当前目录 - - 左树/右侧资源数据 - - 选择状态 - - create / rename / delete / move / open 等能力 -- 面板内保留的状态只包括: - - 布局缓存 - - hover/pressed/capture - - drag state - - inline rename session - - context menu session - -### 阶段 C: 收口同步链路 - -- `EditorContext` 新增 `GetProjectRuntime()` -- `EditorContext` 增加从 project runtime 同步到 `EditorSession.selection` 的正式入口 -- `WorkspaceEventSync` 改成: - - `ProjectPanel::Event` 主要用于 trace/status - - 项目选择和打开场景行为从 `EditorProjectRuntime` 拉取并消费 - -### 阶段 D: 验证与补测 - -- 补充 runtime 相关单测 -- 更新现有 `ProjectPanel` 单测,确认不再依赖面板事件回放驱动 selection/open 主链 -- 验证: - - `editor_app_feature_tests` - - `editor_ui_tests` - - `XCUIEditorApp` Debug 构建 - -## 4. 本轮明确不做 - -- 不做 API 文档 -- 不把旧版 ImGui ProjectPanel 直接搬过来 -- 不先做表面 `cpp` 拆分掩盖根因 -- 不在 runtime 真源没立住之前继续堆新的 Project 面板功能点 - -## 5. 当前执行切入点 - -第一刀直接落在 `EditorProjectRuntime`: - -1. 建 runtime -2. 把 `ProjectBrowserModel` 的所有权移到 runtime -3. 让 `ProjectPanel` 改成通过 runtime 读写项目状态 -4. 再把 selection/open 的同步从 event 回放改掉 - -## 6. 完成结果 - -- `EditorProjectRuntime` 已成为 `Project` 命令语义的正式真源,新增了 `ResolveAssetCommandTarget(...)` 和 `ResolveEditCommandTarget(...)`,命令目标解析不再依赖 `ProjectPanel` 的本地选择态。 -- `ProjectPanel` 已删掉自身的命令目标解析实现,当前只做 runtime 委托、UI 布局、输入处理和局部交互会话。 -- `ProjectPanel` 内若干仍然偷偷读取本地选择态的命令分支已同步收口,`rename/delete/drag/background clear` 的关键判断改为围绕 `EditorProjectRuntime` 真状态执行。 -- `ProjectPanel` 的测试已更新为验证 runtime 真源,而不是继续把 `ProjectPanel::Event` 当成 selection/open 主链的唯一依据。 - -## 7. 补测与验证 - -- 新增并更新测试: - - `tests/UI/Editor/unit/test_editor_project_runtime.cpp` - - `tests/UI/Editor/unit/test_project_panel.cpp` -- 已验证通过: - - `editor_app_feature_tests.exe` `35/35` - - `editor_ui_tests.exe` `349/349` - - `XCUIEditorApp` Debug 构建成功 - -## 8. 计划边界说明 - -这份计划的目标是把 `Project` 主线的真源和依赖方向立住并收口,不包含新的 `duplicate / clipboard / instantiate model / preview` 功能实现。后续这些能力可以继续挂到已经建立好的 `EditorProjectRuntime` 主链上扩展。 diff --git a/docs/used/XCUI_Project面板参考旧Editor完善计划_2026-04-18.md b/docs/used/XCUI_Project面板参考旧Editor完善计划_2026-04-18.md deleted file mode 100644 index ef64cafb..00000000 --- a/docs/used/XCUI_Project面板参考旧Editor完善计划_2026-04-18.md +++ /dev/null @@ -1,251 +0,0 @@ -# XCUI Project 面板参考旧 Editor 收口计划 -日期: `2026-04-18` -状态: `第二阶段进行中,Project 主链已接通` - -## 0. 当前执行进展(2026-04-18) - -本轮已经完成: - -- `ProjectBrowserModel` 已经升级为真实项目域模型,补齐 `kind / extension / canOpen / canPreview` 等项目项语义。 -- `rename / delete / open(scene)` 已经不是占位事件,而是走真实文件系统/场景主线。 -- `create folder` 已经接成正式 `assets.create_folder` host command,并进入 `ProjectPanel` 的 inline rename 会话。 -- `create material / copy path / show in explorer` 已经接回正式 `assets.*` 主链,不再停留在占位事件。 -- 右侧资源项现在已经具备独立 drag-drop 状态机,支持拖到左侧树目录和当前目录内文件夹 tile,并统一走 `ProjectBrowserModel::MoveItemToFolder()`。 -- `CanMoveItemToFolder()` 已经补到模型层,拖拽悬停高亮和最终提交共用同一套合法性判断。 -- 测试已经从“只有文件夹重排”扩展到 `create folder / move item / host command bridge / ProjectPanel command`。 -- `ProjectPanel` 右键已经接出真实 context menu popup,并直接复用 `edit.* / assets.*` 正式命令链。 -- 右键菜单已经区分“背景当前目录目标”和“文件夹 tile 目标”,不会再把上下文动作错误地绑死到当前选择。 -- 当前验证状态:`editor_app_feature_tests` 25/25,`editor_ui_tests` 345/345,`XCUIEditorApp` Debug 构建通过。 - -当前还没收完的部分: - -- `instantiate model / clipboard transfer` 还没有接回新主线。 -- `ProjectPathContext / project descriptor` 语义还没有完全替代当前 `repoRoot / project / Assets` 约定。 -- `ProjectPanel.cpp` 仍然偏胖,后续还需要继续按布局/输入/命令拆薄。 - -## 1. 问题定义 - -当前 `new_editor` 的 `ProjectPanel` 已经有基础外壳,但离“可持续扩展的正式工程面板”还差一整条主链,根因不是单个交互点没接,而是领域抽象还没立稳。 - -现状里的核心问题: - -- `ProjectPanel` 自己承担了太多职责:布局、输入分发、双击判定、面包屑、右侧网格、左侧树、分割条、事件发射都在一个类里,但真正的项目域模型只有一个偏轻量的 `ProjectBrowserModel`。 -- `ProjectBrowserModel` 现在只覆盖了“文件夹树 + 当前目录资源列表 + 文件夹重排”,没有统一承接 `rename / delete / create / open / reveal / copy path / asset move` 这类项目域操作。 -- `ProjectPanel::EvaluateEditCommand()` 里 `delete / duplicate / cut / copy / paste` 仍然是直接阻断,`rename` 也只是发 `RenameRequested` 事件,没有像 `Hierarchy` 一样形成真实提交链路。 -- 右侧资源项抽象过弱。当前 `AssetEntry` 只有 `itemId / absolutePath / displayName / directory`,缺少旧版 `AssetItem` 里已经具备的 `type / extension / image-preview capability / children` 等信息,导致后续所有类型化行为都只能继续堆条件分支。 -- 左树和右侧浏览区没有共享统一的“项目项”能力模型。现在左侧能拖文件夹改层级,右侧只能选中和双击打开,资产移动、重命名、删除、创建等能力没有统一入口。 -- `ProjectBrowserModel::Initialize()` 直接把根目录硬编码到 `repoRoot / project / Assets`,这能跑 demo,但不是旧版 editor 那种完整的 project-root / descriptor 语义。 -- 搜索、上下文菜单、创建资源、显示到资源管理器、复制相对路径、场景打开、模型实例化、导入状态展示等旧版 `ProjectPanel` 能力,在 `new_editor` 里还没有体系化落地。 -- 测试覆盖明显偏窄,目前只有文件夹重排/移到根目录两条单测,不足以保护后续重构。 - -这意味着如果继续在当前 `ProjectPanel` 上一个点一个点补功能,最后只会变成第二座 `Hierarchy` 旧问题的屎山:UI 层越来越厚,项目域能力越来越分散,命令链路无法复用,测试也钉不住。 - -## 2. 旧 Editor 参考基线 - -旧版 `editor` 里和 Project 相关的成熟能力,主要分布在: - -- `editor/src/Managers/ProjectManager.*` -- `editor/src/panels/ProjectPanel.*` -- `editor/src/Commands/ProjectCommands.h` - -这套实现已经证明有效的能力边界包括: - -- 以项目根目录和 `Assets` 目录为中心的正式项目语义,而不是单纯目录浏览。 -- 统一的资源项抽象,能区分 `Folder / Scene / Model / Material / Texture / Script / File`。 -- 文件夹创建、删除、移动、重命名,并正确处理 `.meta` sidecar。 -- 右侧资源浏览支持搜索、双击打开、上下文菜单、内联重命名。 -- 资源操作不是直接塞进面板,而是通过 `ProjectManager + ProjectCommands` 形成可复用的命令边界。 -- 一些附加能力已经接到工程主线,例如 `Open Scene`、`Show in Explorer`、`Copy Path`、`Instantiate Model`、导入状态/场景加载状态显示。 - -`new_editor` 不应该直接搬运 ImGui 面板代码,但应该把这里已经验证过的领域边界和能力拆分方式复用过来。 - -## 3. 本次收口目标 - -本轮针对 `Project` 面板的目标不是“把旧面板照抄一遍”,而是从根上把 `new_editor` 的 Project 主线立起来。 - -完成标准: - -- `ProjectPanel` 降级为薄 UI 宿主,不再自己持有过多项目域规则。 -- 建立统一的 Project 域抽象,左树、右侧网格、面包屑、上下文菜单、编辑命令都从同一套项目项模型取数和落命令。 -- 把 `rename / delete / create folder / open / reveal / copy path / folder move` 打成真实能力,不再停留在事件请求或占位提示。 -- 对齐旧 editor 的最关键工作流:浏览、导航、搜索、打开、重命名、删除、创建、拖拽移动。 -- 为后续扩展 `material create / scene open / model instantiate / preview` 留出明确挂点。 -- 单测覆盖从“只有文件夹拖拽”扩展到“项目项生命周期和命令语义”。 - -本轮明确不做: - -- 不直接把旧版 `ImGui ProjectPanel` 整块搬进 `new_editor`。 -- 不在没有领域抽象的情况下,先往 `ProjectPanel.cpp` 堆更多右键菜单和 if-else。 -- 不顺手去做完整资产导入器 UI、Library 管理页、API 文档之类的旁支工作。 -- 不在项目路径解析体系没有准备好之前,就把所有旧 editor 的 project descriptor 逻辑一次性硬塞进来。 - -## 4. 重构方案 - -### 4.1 先把 Project 域抽象补齐 - -要先解决的不是面板长相,而是“谁拥有项目项事实、谁负责文件系统语义、谁负责命令合法性”。 - -建议做法: - -- 以当前 `ProjectBrowserModel` 为基础,升级成真正的 Project workspace model,而不是只做轻量目录投影。 -- 给项目项建立统一描述结构,至少包含: - - `itemId` - - `absolutePath` - - `displayName` - - `nameWithExtension` - - `kind`(Folder / Scene / Model / Material / Texture / Script / File) - - `directory` - - `extensionLower` - - `canOpen` - - `canRename` - - `canDelete` - - `canMove` - - `canPreview` -- 所有文件系统写操作统一收口到 model/service 层,而不是分散在面板事件处理中。 -- 目录重排、资源移动、重命名、删除、创建目录等操作都要在同一层做合法性校验和 `.meta` 处理。 - -这一步完成后,`ProjectPanel` 只消费“可渲染、可交互、可执行命令”的结果,不再自己拼命理解项目语义。 - -### 4.2 统一左树、右侧浏览区和命令路由 - -当前左树和右侧浏览区实际上各走各的逻辑,后面功能越补越会分叉。 - -需要收口成一套统一链路: - -- 左侧树使用项目项模型里的 folder 节点。 -- 右侧浏览区使用同一个模型输出的 current-folder visible items。 -- 面包屑、当前目录、选中项、上下文菜单目标、编辑命令目标,都由统一的 `ProjectSelection/CommandTarget` 解析层生成。 -- `edit.rename / edit.delete / edit.duplicate / edit.copy / edit.paste` 的可执行性判断,不能继续写死在 `ProjectPanel` 里,而要向项目域能力层查询。 -- 双击打开、右键菜单打开、快捷键打开,最终都走同一条 `OpenProjectItem(...)` 路径。 - -这样后面无论是树里 rename,还是右侧网格 rename,都是一个命令,不会再出现“左边能做、右边不能做”的问题。 - -### 4.3 先补齐最核心的编辑命令闭环 - -这一轮优先把旧 editor 里最关键、最常用、并且已经有成熟语义的能力补齐: - -第一批必须落地: - -- `rename` - - 接入 inline rename,会话状态由面板持有,真实提交由项目域层执行。 - - 目录和文件都要支持,文件保留扩展名语义,兼容大小写改名。 -- `delete` - - 真实删除文件/目录,并清理对应 `.meta`。 - - 删除后保持当前目录、选中项和面包屑状态一致。 -- `create folder` - - 从当前目录或上下文目标目录创建,命名策略对齐旧 editor。 - - 创建后直接进入 inline rename。 -- `open` - - 目录双击进入。 - - `Scene` 双击打开场景。 - - 其它类型至少预留统一 open hook,不要再把逻辑散落在 UI 层。 -- `move` - - 左树文件夹拖拽继续保留,但要统一到项目域 move 语义。 - - 右侧浏览区也要接同一套 move 语义,最终支持资源项拖到目录。 -- `copy path / show in explorer` - - 这是低风险高收益能力,且旧 editor 已验证,应该尽快接回。 - -第二批按主线成熟度接入: - -- `create material` -- `instantiate model in scene` -- `duplicate` -- `clipboard transfer` - -其中 `duplicate / clipboard` 如果底层资产元数据重写和 GUID 语义还没准备好,可以继续拆阶段,但必须在计划里明确挂到项目域层,而不是永远留在 UI stub 里。 - -### 4.4 ProjectPanel UI 层只保留展示和会话状态 - -面板本身要收薄,保留这些职责即可: - -- 布局和绘制 -- splitter 状态 -- hover / pressed / pointer capture 等纯交互态 -- inline rename session -- context menu 弹出态 -- 搜索输入框状态 - -不应该继续放大的职责: - -- 文件系统写操作 -- 资源类型推断 -- 场景打开规则 -- `.meta` 搬运策略 -- 项目根目录解析 - -这一步建议顺手把 `ProjectPanel.cpp` 拆分,至少分成: - -- `ProjectPanel.Layout.cpp` -- `ProjectPanel.Input.cpp` -- `ProjectPanel.Rendering.cpp` -- `ProjectPanel.Commands.cpp` - -否则这个文件会继续膨胀,后面每补一项功能都更难收口。 - -### 4.5 搜索和类型展示对齐旧 Editor - -旧 editor 的 Project 面板不是单纯列目录,它已经有“资源浏览器”语义。 - -因此搜索和类型展示需要作为正式能力补回: - -- 搜索先做当前目录过滤,语义对齐旧版 `MatchesSearch`。 -- 搜索输入不应改变真实当前目录,只改变右侧 visible items。 -- 项目项要有类型和扩展名信息,右侧 tile 和后续 context menu/open 行为都依赖它。 -- 图标至少先做到 `Folder / Scene / Model / Material / Texture / Script / File` 的基本区分。 -- 图片预览和模型预览可以后置,但抽象要预留 `preview provider` 接口,避免以后继续改数据结构。 - -### 4.6 项目根目录语义向旧 Editor 对齐 - -当前 `ProjectBrowserModel` 直接使用 `repoRoot / project / Assets`,这是一个过渡实现,不适合作为长期正式语义。 - -建议分阶段收口: - -- 第一阶段先抽出 `ProjectPathContext`,把 `repoRoot`、`projectRoot`、`assetsRoot` 分清楚。 -- 第二阶段再决定是否接旧 editor 的 `ProjectRootResolver / Project.xcproject` 描述能力。 -- 在这之前,所有 Project 域代码都不要再写死 `"project/Assets"` 字面量。 - -## 5. 验证计划 - -测试必须跟着抽象一起补,不然这条线后面一定回退。 - -至少要新增这些覆盖: - -- `ProjectWorkspaceModel` / `ProjectBrowserModel` - - create folder - - rename folder - - rename file while preserving extension - - delete file/folder and `.meta` - - move file/folder - - current-folder remap after move/rename/delete - - search filtering - - kind/type classification -- `ProjectPanel` - - rename 请求能真实进入 inline rename - - rename commit 后真实刷新 UI 状态 - - 双击目录只触发一次导航 - - 双击 scene 走统一 open 路径 - - 背景点击清空选择 - - 面包屑导航和树导航状态一致 - -构建验证: - -- `cmake --build build --config Debug --target editor_app_feature_tests` -- `cmake --build build --config Debug --target XCUIEditorApp` - -## 6. 执行顺序 - -1. 抽出 Project 域能力层,统一项目项结构和基础文件系统操作。 -2. 把 `rename / delete / create folder / open / move` 接成真实命令闭环。 -3. 在 `ProjectPanel` 接入 inline rename、上下文菜单、统一 command target。 -4. 补搜索、类型展示、基础图标语义。 -5. 拆薄 `ProjectPanel.cpp`,把布局/输入/渲染/命令分文件。 -6. 补单测和构建验证。 - -## 7. 结果预期 - -收完这条线之后,`new_editor` 的 `Project` 面板应该达到的状态是: - -- 不再是“能点开的 demo 面板”,而是正式项目浏览器。 -- 左树、右侧网格、面包屑、编辑命令、上下文菜单共享同一套项目域语义。 -- 后续再加 `material create / preview / drag-drop to inspector / instantiate model` 时,不需要重新推翻现有结构。 -- 与旧 editor 的差距从“能力缺口和架构缺口同时存在”,收缩为“少数高级能力未接入,但主干架构已经对齐”。 diff --git a/docs/used/XCUI_Subplan-01_Core_Tree_State_完成归档_2026-04-04.md b/docs/used/XCUI_Subplan-01_Core_Tree_State_完成归档_2026-04-04.md deleted file mode 100644 index 15b746fe..00000000 --- a/docs/used/XCUI_Subplan-01_Core_Tree_State_完成归档_2026-04-04.md +++ /dev/null @@ -1,93 +0,0 @@ -# XCUI Subplan 01:Core Tree / State / Invalidation - -归档日期: - -- `2026-04-04` - -状态: - -- 已完成 - -本次实际完成内容: - -- 新增 XCUI core 基础契约:`UIElementId`、`UIDirtyFlags`、`IUIViewModel`、`RevisionedViewModelBase` -- 新增 retained-mode build 层:`UIBuildElementDesc`、`UIBuildList`、`UIBuildContext` -- 新增 retained-mode tree 层:`UIElementTree`、`UIElementNode`、`UIElementTreeRebuildResult` -- 新增统一入口:`UIContext` -- 实现最小闭环:tree rebuild、dirty flag 标记、layout dirty 向祖先传播、dirty root 收集 -- 新增并验证 UI core 测试:tree 创建、unchanged rebuild、local state invalidation、view model invalidation、structure change、未闭合 build scope 失败 - -本次涉及文件: - -- `engine/include/XCEngine/UI/Types.h` -- `engine/include/XCEngine/UI/Core/UIInvalidation.h` -- `engine/include/XCEngine/UI/Core/UIViewModel.h` -- `engine/include/XCEngine/UI/Core/UIBuildContext.h` -- `engine/include/XCEngine/UI/Core/UIElementTree.h` -- `engine/include/XCEngine/UI/Core/UIContext.h` -- `engine/src/UI/Core/UIBuildContext.cpp` -- `engine/src/UI/Core/UIElementTree.cpp` -- `tests/core/UI/CMakeLists.txt` -- `tests/core/UI/test_ui_core.cpp` - -验证结果: - -- `cmake --build . --config Debug --target core_ui_tests` -- `ctest -C Debug --output-on-failure -R UICoreTest --test-dir .` -- 结果:`6/6` 通过 - -当前结论: - -- `Subplan 01` 的最小 retained-mode core 已经可用 -- 后续 `Subplan 03/05/06/07/08/09` 可以基于这套 core 继续推进 - -原始 subplan 内容归档如下: - -# Subplan 01:XCUI Core Tree / State / Invalidation - -目标: - -- 搭出 XCUI 的 retained-mode 核心骨架。 -- 明确 `ElementTree`、`NodeId`、`View`、`ViewModel`、`dirty flag`、`rebuild`、`lifecycle` 的最小闭环。 - -负责人边界: - -- 负责 `engine/include/XCEngine/UI/` 与 `engine/src/UI/Core/` 的核心树模型。 -- 不负责具体布局算法。 -- 不负责 ImGui 适配绘制。 - -建议目录: - -- `engine/include/XCEngine/UI/Core/` -- `engine/src/UI/Core/` -- `tests` 中对应 XCUI core 测试文件 - -前置依赖: - -- 依赖主线完成 `Phase 0` 的基础类型和 UI 生命周期边界清理。 - -现在就可以先做的内容: - -- 设计 `UIElementId` / `UIElement` / `UIContext` / `UIBuildContext` -- 设计 dirty 标记与增量重建规则 -- 设计 ViewModel 读写边界和 command 回调入口 -- 写最小 tree rebuild 测试 - -明确不做: - -- 不接入 `.xcui` 文件 -- 不接入 editor 面板 -- 不写具体 widget 大库 - -交付物: - -- XCUI core 基础类与生命周期定义 -- tree rebuild / invalidation / state propagation 单元测试 -- 一个最小 demo:代码构建 UI tree 并触发一次增量更新 - -验收标准: - -- 可以构建一棵稳定的 UI tree -- 局部状态变化时只标脏必要节点 -- 重建逻辑与布局/渲染解耦 -- 其他 subplan 可以基于该模块定义控件树和状态更新 diff --git a/docs/used/XCUI_Subplan-02_LayoutEngine_完成归档_2026-04-04.md b/docs/used/XCUI_Subplan-02_LayoutEngine_完成归档_2026-04-04.md deleted file mode 100644 index b5c72044..00000000 --- a/docs/used/XCUI_Subplan-02_LayoutEngine_完成归档_2026-04-04.md +++ /dev/null @@ -1,50 +0,0 @@ -# XCUI Subplan 02:Layout Engine 完成归档 - -归档日期: - -- `2026-04-04` - -原始来源: - -- [../XCUI完整架构设计与执行计划.md](../XCUI完整架构设计与执行计划.md) - -本次完成范围: - -- 落地 XCUI 纯算法布局基础类型: - - `UILayoutLength` - - `UILayoutConstraints` - - `UILayoutThickness` - - `UILayoutItem` - - `UIStackLayoutOptions` - - `UIOverlayLayoutOptions` -- 落地 measure / arrange 双阶段布局算法 -- 实现 `Horizontal Stack` / `Vertical Stack` / `Overlay` 三类 MVP 容器 -- 支持 `px / content / stretch` -- 支持 `padding / spacing / margin / min / max / alignment` -- 建立独立 `ui_tests` 测试目标并通过验证 - -实际代码落点: - -- [engine/include/XCEngine/UI/Types.h](D:/Xuanchi/Main/XCEngine/engine/include/XCEngine/UI/Types.h) -- [engine/include/XCEngine/UI/Layout/LayoutTypes.h](D:/Xuanchi/Main/XCEngine/engine/include/XCEngine/UI/Layout/LayoutTypes.h) -- [engine/include/XCEngine/UI/Layout/LayoutEngine.h](D:/Xuanchi/Main/XCEngine/engine/include/XCEngine/UI/Layout/LayoutEngine.h) -- [tests/Core/Math/CMakeLists.txt](D:/Xuanchi/Main/XCEngine/tests/Core/Math/CMakeLists.txt) -- [tests/Core/Math/test_ui_layout.cpp](D:/Xuanchi/Main/XCEngine/tests/Core/Math/test_ui_layout.cpp) - -验证结果: - -- `cmake --build . --config Debug --target math_tests -- /m:1` -- `ctest -C Debug --test-dir D:\\Xuanchi\\Main\\XCEngine\\build\\tests\\Core\\Math --output-on-failure -R UI_Layout` -- 结果:`5/5 UI_Layout tests passed` - -与原子计划相比,当前仍未覆盖: - -- `Scroll` 容器 -- 更复杂的主轴分布策略 -- 与 XCUI tree/state 的正式对接 -- 文本测量与真实控件树集成 - -建议后续承接: - -- 由 `Subplan-01` 提供 tree / node / invalidation 契约后,把当前布局算法接入正式 UI tree -- 后续再补 `Scroll`、更完整容器族、文本测量桥接 diff --git a/docs/used/XCUI_Subplan-05_ImGui_Transition_Backend_完成归档_2026-04-04.md b/docs/used/XCUI_Subplan-05_ImGui_Transition_Backend_完成归档_2026-04-04.md deleted file mode 100644 index 10e416a5..00000000 --- a/docs/used/XCUI_Subplan-05_ImGui_Transition_Backend_完成归档_2026-04-04.md +++ /dev/null @@ -1,83 +0,0 @@ -# XCUI Subplan 05: ImGui Transition Backend - -归档日期: -- `2026-04-04` - -状态: - -- 已完成 - -本次实际完成内容: -- 新增 XCUI 绘制数据契约:`UIColor`、`UIDrawCommandType`、`UIDrawCommand`、`UIDrawList`、`UIDrawData` -- 新增 `ImGuiTransitionBackend` 过渡后端,支持 `FilledRect`、`RectOutline`、`Text`、`Image`、`PushClipRect`、`PopClipRect` -- 新增最小 editor 接入样例 `XCUIDemoPanel`,用于在现有编辑器壳层中演示 XCUI draw data 到 ImGui draw call 的过渡链路 -- 将 demo panel 接入 editor workspace,并补齐 editor/tests 构建入口 -- 新增 Subplan-05 配套测试,覆盖 draw data 聚合与 backend flush 行为 - -本次涉及文件: -- `editor/CMakeLists.txt` -- `editor/src/Core/EditorWorkspace.h` -- `editor/src/XCUIBackend/ImGuiTransitionBackend.h` -- `editor/src/panels/XCUIDemoPanel.cpp` -- `editor/src/panels/XCUIDemoPanel.h` -- `engine/include/XCEngine/UI/DrawData.h` -- `tests/editor/CMakeLists.txt` -- `tests/editor/test_xcui_draw_data.cpp` -- `tests/editor/test_xcui_imgui_transition_backend.cpp` - -验证结果: -- `cmake --build . --config Debug --target editor_tests -- /m:1 /p:CL_MPCount=1` -- `ctest -C Debug -R "XCUIDrawDataTest|XCUIImGuiTransitionBackendTest" --output-on-failure` -- 结果:`4/4` 通过 -- `cmake --build . --config Debug --target XCEditor -- /m:1 /p:UseMultiToolTask=false /p:CL_MPCount=1` -- 结果:通过 - -提交记录: -- `75ded6f` `Add XCUI ImGui transition backend MVP` - -当前结论: -- `Subplan-05` 的最小过渡后端已经可用,可以作为 XCUI 在 editor 中落地的第一层渲染适配桥 -- XCUI 逻辑层仍然不直接依赖 ImGui API,ImGui 仅存在于过渡 backend 和 editor 接入层 -- 后续 `Subplan-08`、`Subplan-09` 可以直接基于这套 draw data 和 transition backend 继续推进 - -原始 subplan 内容归档如下: - -# Subplan 05:XCUI ImGui Transition Backend - -目标: -- 在过渡阶段,让 ImGui 只承担宿主窗口和 draw submission 容器的职责 -- 由 XCUI 自己生成 draw list,再交给 ImGui backend 落屏 - -负责人边界: -- 负责 `editor/src/XCUIBackend/` 或等价新目录 -- 负责 XCUI draw primitive 到 ImGui draw call 的映射 -- 不负责 XCUI tree、布局、样式的内部规则 - -建议目录: -- `editor/src/XCUIBackend/` -- `editor/src/UI/` 中与 XCUI backend 直接相关的桥接代码 -- `tests/Editor` 中 backend 相关测试 - -前置依赖: -- 需要 `Subplan-01` 给出稳定 draw data 和 frame submission 契约 -- 需要 `Subplan-03` 提供样式查询结果 - -现在就可以先做的内容: -- 定义 `UIDrawList` / `UIDrawCommand` / `UIDrawText` / `UIDrawImage` / `UIDrawClip` -- 先做矩形、边框、文字、图片四类 primitive -- 设计 frame begin / submit / end 的 adapter 流程 -- 写一个最小 demo panel,用 XCUI draw list 通过 ImGui 显示 - -明确不做: -- 不做 RHI native backend -- 不做 docking 逻辑 - -交付物: -- XCUI 到 ImGui 的过渡 backend -- primitive 转换测试或快照测试 -- 最小 editor 接入样例 - -验收标准: -- XCUI 逻辑层不直接依赖 ImGui API -- ImGui 只出现在 backend 适配层 -- 可以渲染基础控件和文本 diff --git a/docs/used/XCUI_树拖放统一与Project目录层级重构计划_2026-04-17.md b/docs/used/XCUI_树拖放统一与Project目录层级重构计划_2026-04-17.md deleted file mode 100644 index e935e1ec..00000000 --- a/docs/used/XCUI_树拖放统一与Project目录层级重构计划_2026-04-17.md +++ /dev/null @@ -1,136 +0,0 @@ -# XCUI 树拖放统一与 Project 目录层级重构计划 - -日期:2026-04-17 - -## 背景 - -当前 `HierarchyPanel` 和 `ProjectPanel` 左侧树都复用了 `UIEditorTreeView` / `UIEditorTreeViewInteraction` 作为基础树控件,但“节点拖拽重排”并不在公共层: - -- `HierarchyPanel` 自己维护 `armed / dragging / dropTarget / commit` 状态,并直接调用 `HierarchyModel::CanReparent / Reparent / MoveToRoot` -- `ProjectPanel` 仅把左树当作目录导航树使用,没有目录拖放能力 -- `ProjectBrowserModel` 当前是只读文件系统树模型,不提供目录层级修改接口 - -这导致两边表现不一致,也让 Project 左树无法通过拖放调整目录层级。 - -## 问题 - -1. 树拖放逻辑重复且散落在 `HierarchyPanel` 中,无法复用 -2. 公共树交互层只覆盖选择、展开、键盘导航,不覆盖拖放重排 -3. `ProjectPanel` 左树缺少目录层级修改能力 -4. `ProjectBrowserModel` 缺少目录移动、根级移动、父级查询、非法目标校验等接口 -5. Project 左树未来即便支持拖放,也容易与右侧 grid / breadcrumb / splitter 发生输入冲突 - -## 目标 - -1. 抽出一层可复用的“树节点拖放控制器”,复用给 `HierarchyPanel` 和 `ProjectPanel` -2. 保持 `UIEditorTreeView` 基础交互层不膨胀,不把业务重排直接塞进底层组件 -3. 为 `ProjectBrowserModel` 增加最小可用的目录层级移动能力 -4. 让 `ProjectPanel` 左树支持: - - 拖到目录节点上,变成其子目录 - - 拖到树空白区域,回到 `Assets` 根级 -5. 收紧 Project 左树拖放期间的输入所有权,避免误触右侧区域 - -## 方案 - -### 1. 公共树拖放控制层 - -新增一层轻量的公共树拖放控制器,职责只包括: - -- 识别拖拽起手、激活阈值、拖放目标命中、根级放置、指针捕获请求 -- 在拖放开始后抑制基础 TreeView 的重复指针选择事件 -- 通过回调方式把 `CanDrop / CommitDrop / SelectDraggedItem` 交还给业务侧 - -明确不放进这层的内容: - -- Scene 层级语义 -- 文件系统移动实现 -- 面板事件上报 -- Drop preview 绘制风格 - -### 2. HierarchyPanel 收口 - -将 `HierarchyPanel` 现有私有拖放流程迁移到公共控制层: - -- 保留 `HierarchyModel` 作为业务模型 -- 用回调接入 `CanReparent / Reparent / MoveToRoot` -- 保留现有 selection、rename、reparent 事件发射语义 -- 保留当前 drop preview 绘制效果 - -### 3. ProjectBrowserModel 能力补齐 - -新增目录层级修改接口: - -- `GetParentFolderId` -- `CanReparentFolder` -- `ReparentFolder` -- `MoveFolderToRoot` - -实现要求: - -- 禁止移动 `Assets` 根目录 -- 禁止拖到自己或自己的子孙目录下 -- 禁止无效同级 no-op 移动 -- 禁止目标目录已存在同名目录 -- 使用文件系统原子目录重命名/移动 -- 移动后刷新树和资产列表 -- 若当前浏览目录位于被移动目录子树内,自动重映射 `currentFolderId` - -### 4. ProjectPanel 接入 - -`ProjectPanel` 左树接入公共拖放控制层: - -- 支持拖动目录树节点改变目录层级 -- 拖放期间请求 host pointer capture -- 拖放期间抑制右侧 grid、breadcrumb、splitter 的指针副作用 -- 拖放成功后刷新当前布局与目录选择 -- 增加与 `HierarchyPanel` 一致的基础 drop preview - -## 执行步骤 - -1. 新增公共树拖放控制器,先替换 `HierarchyPanel` -2. 为 `ProjectBrowserModel` 增加目录移动接口与状态重映射 -3. 在 `ProjectPanel` 中接入公共树拖放控制器 -4. 补齐 Project 左树拖放期间的输入抑制与视觉反馈 -5. 编译验证 `XCUIEditorAppLib` 和 `XCUIEditorApp` - -## 验收标准 - -1. `HierarchyPanel` 行为与当前一致,不回退 -2. `ProjectPanel` 左树可通过拖放改变目录层级 -3. 拖放目录到非法位置会被拒绝,不破坏文件系统 -4. 拖放目录过程中不会误触发右侧资源选择、打开、breadcrumb 导航或 splitter -5. `XCUIEditorApp` Debug 编译通过 - -## 执行状态 - -当前状态:主体已完成,已进入收口验证。 - -已完成: - -1. 已新增公共树拖放控制层 `TreeItemDragDrop`,并接入 `HierarchyPanel` 与 `ProjectPanel` -2. `HierarchyPanel` 已改为复用公共拖放状态机,保留原有 selection / rename / reparent 事件语义 -3. `ProjectBrowserModel` 已补齐 `GetParentFolderId / CanReparentFolder / ReparentFolder / MoveFolderToRoot` -4. `ProjectPanel` 左树已支持目录拖放重排、根级回置、drop preview、拖放期间输入抑制 -5. 已补齐目录移动时文件夹自身 `.meta` 的同步移动,避免只移动目录本体导致资源元数据错位 -6. 已补齐拖放成功后的状态收口: - - 重新同步当前目录选择 - - 清理 Project 资源选中并发出清理事件 - - 同帧重建树布局,避免拖放后短暂使用旧布局绘制 - - 指针离开时清空 drop target,避免边界情况下误提交 - -已完成验证: - -1. 定向编译通过: - - `build/new_editor/XCUIEditorAppLib.vcxproj` Debug - - `build/new_editor/XCUIEditorApp.vcxproj` Debug -2. 冒烟运行通过: - - `build/new_editor/Debug/XCUIEditor.exe` - - 环境变量:`XCUIEDITOR_SMOKE_TEST=1` -3. 新增并通过目录移动单测: - - `ProjectBrowserModelTests.ReparentFolderMovesFolderMetaAndRemapsCurrentFolder` - - `ProjectBrowserModelTests.MoveFolderToRootMovesFolderMetaAndRemapsCurrentFolder` - -说明: - -1. 顶层 `cmake --build build/new_editor --config Debug --target XCUIEditorAppLib` 会被仓库内无关的 `engine/src/Scripting/Mono/MonoScriptRuntime.cpp` 访问控制错误连带打断,因此本轮改为直接构建 `build/new_editor/*.vcxproj` 做增量验证 -2. 这不影响本轮 `new_editor` 相关改动的编译与可执行产物更新,`build/new_editor/Debug/XCUIEditor.exe` 已重新生成 diff --git a/docs/used/XCUI完整架构设计与执行计划.md b/docs/used/XCUI完整架构设计与执行计划.md deleted file mode 100644 index 550673de..00000000 --- a/docs/used/XCUI完整架构设计与执行计划.md +++ /dev/null @@ -1,1588 +0,0 @@ -# XCUI完整架构设计与执行计划 - -## 1. 文档定位 - -本文档定义 `XCUI` 的完整目标、分层架构、核心数据模型、运行机制、编辑器集成方式、迁移策略与执行计划。 - -`XCUI` 的定位不是“再做一个游戏内 UI 模块”,而是: - -- XCEngine 的统一 UI 平台 -- Editor 与 Runtime 共用的一套 UI Core -- 面向 AI Coding Agent 友好的 UI 开发体系 -- 未来替代当前 editor 中 ImGui 主路径的正式方案 - -本文档默认服务于两个目标: - -- 给后续实现提供明确蓝图 -- 给 AI / 人类开发者提供稳定的架构边界 - ---- - -## 2. 背景与问题定义 - -当前 XCEngine 的 editor 已经形成了较清晰的主链: - -- `RHI -> Rendering -> Editor Viewport -> AssetDatabase/Library -> Mono Scripting` - -但 UI 层目前仍以 ImGui 即时模式为主,存在以下问题: - -- 过程式 UI 代码体量大,面板文件容易膨胀 -- 状态、布局、事件、业务逻辑容易缠绕在一起 -- AI 不擅长稳定修改大段 imperative native UI 代码 -- 现有 UI 逻辑难以沉淀为资源化、组件化、可热重载的体系 -- editor UI 和未来 runtime UI 之间没有统一技术底座 - -### 2.1 当前问题不是“有没有 UI”,而是“UI 的组织方式不适合 AI” - -当前 editor 已经有大量真实能力: - -- Hierarchy -- Project -- Inspector -- Console -- Scene/Game Viewport -- Play Mode / Scripting / Asset 工作流 - -因此,XCUI 的目标不是重做 editor 后端能力,而是重做 UI 前端组织方式。 - -### 2.2 为什么不能直接照抄 Unity UI - -不采用 Unity 风格 UI 的主要原因: - -- `RectTransform / Anchor / Pivot / Canvas` 模型复杂度高 -- 组件化表达不够直接 -- inspector/custom editor 体系过重 -- 对 AI 来说,状态流和布局意图不够清晰 -- editor UI 与 runtime UI 分裂严重 - -### 2.3 为什么也不直接采用浏览器/WebView路线 - -本文档明确不采用“真正 HTML/CSS/JS + 浏览器内核”作为主路线,原因如下: - -- 这会把问题从 UI 架构转移成浏览器宿主工程 -- 会引入大量与引擎无关的复杂性 -- 不利于 editor/runtime 共享同一套 native UI Core -- 会让 viewport、输入、宿主生命周期复杂化 - -XCUI 借鉴前端范式,但不引入浏览器运行时。 - ---- - -## 3. XCUI的总体目标 - -### 3.1 核心目标 - -XCUI 必须同时满足以下目标: - -1. AI 能稳定理解、生成、修改 UI -2. Editor 与 Runtime 共用统一 UI Core -3. UI 可资源化、可热重载、可调试 -4. 复杂度显著低于 Unity UI -5. 能逐步替代 editor 当前的 ImGui 主路径 -6. 能承载 editor 级别的面板、布局、属性表单、树控件和 viewport 壳层 - -### 3.2 非目标 - -XCUI 明确不做以下事情: - -- 不做浏览器 -- 不做 DOM -- 不做完整 CSS cascade -- 不复刻 Unity UGUI / UI Toolkit -- 不把普通 UI 节点做成 Scene GameObject -- 不在 V1 做完整 world-space UI 体系 - -### 3.3 成功标准 - -XCUI 成功的标志不是“能画几个按钮”,而是: - -- editor 核心公共边界中不再暴露 ImGui 类型 -- Inspector / Project / Console / Hierarchy 至少 4 个核心面板迁移到 XCUI -- Scene/Game 使用 `ViewportSlot` 承载视口纹理和输入边界 -- `.xcui/.xctheme/.xcschema` 支持热重载 -- runtime HUD/menu 可以基于同一套 UI Core 构建 -- editor 最终可不依赖 ImGui 运行主 UI - ---- - -## 4. 总体架构 - -### 4.1 分层结构 - -XCUI 采用如下四层架构: - -```text -Editor / Runtime Application - | - v -XCUI Views / ViewModels / Commands - | - v -XCUI Core -(State / Binding / Layout / Style / Input / Render / Widgets) - | - v -Backend -(ImGui Adapter -> 过渡期) -(Native RHI Renderer -> 最终目标) -``` - -### 4.2 在仓库中的推荐目录 - -```text -engine/ - include/XCEngine/UI/ - Core/ - State/ - Binding/ - Layout/ - Style/ - Input/ - Render/ - Text/ - Widgets/ - Markup/ - Schema/ - Runtime/ - DevTools/ - src/UI/ - -editor/ - src/XCUIHost/ - src/XCUIViewModels/ - src/XCUIAdapters/ - src/XCUICommands/ - ui/views/ - ui/themes/ - ui/schemas/ - -project/ - Assets/UI/ - Assets/Themes/ - Assets/Schemas/ - -tests/ - UI/ -``` -### 4.2.1 当前执行覆盖规则(2026-04-09) - -以下规则在当前阶段覆盖本节中所有与目录落点有关的模糊表述: - -- `editor/` 当前视为 ImGui 版本冻结区;在 XCUI editor shell 成熟前,不直接在该目录中推进替换开发。 -- `tests/UI` 是当前 XCUI 的正式验证入口,也是 `Editor` 基础层的唯一实验场;所有基础能力验证、交互试验、状态流检查都必须优先放在 `tests/UI/Editor/unit` 与 `tests/UI/Editor/integration`。 -- `new_editor/` 当前不作为试验场,也不是验证入口;它承载未来 editor 重建所需的 `Editor` 基础层库与宿主骨架,禁止往其中追加业务面板、临时验证逻辑或与 `tests/UI` 重复的实验入口。 -- `new_editor/` 当前目录结构按库式 `Editor UI` 形态维护:公共头放在 `include/XCEditor/Core|Widgets`,实现放在 `src/Core|Widgets`,宿主代码放在 `app/Application.*|Host/*`,不再把库层与宿主层揉成一套业务目录。 -- `Core / Runtime` 当前仍可继续推进资源化、热重载与资源驱动验证。 -- `Editor` 当前采用固定代码样式:Editor 默认样式、palette、metrics 与视觉语义由代码层维护,不再把 Editor 主题解析作为当前主线,也不在基础层未完成前把业务推进到 `new_editor/`。 -- `engine/UI` 当前继续只放 `Core / Runtime / shared` 部分,不再继续沉积 editor-only 代码。 -- 等 `Editor` 基础层在 `tests/UI/Editor` 中稳定收口后,再规划从 `new_editor/` 迁入正式 `editor/` 的接入阶段,而不是继续在旧 `editor/` 与 `new_editor/` 之间双线推进。 - -#### 当前过渡期目录(自 2026-04-09 起执行) -```text -engine/ - include/XCEngine/UI/ - src/UI/ - # 只承载 Core / Runtime / shared UI 基础层 - -new_editor/ - include/XCEditor/ - src/ - app/ - ui/ - # Editor 基础层库 + 临时宿主骨架;不承载测试验证入口与业务面板 - -editor/ - # 当前 ImGui 版本冻结,不作为本阶段 XCUI 主实现目录 - -tests/ - UI/ - # XCUI 正式验证体系入口,不承载正式 editor 实现 -``` - -### 4.3 模块划分 - -| 模块 | 职责 | -|---|---| -| `UI/Core` | UI 树、节点生命周期、失效传播、实例管理 | -| `UI/State` | 响应式状态、派生状态、store | -| `UI/Binding` | 数据绑定、命令绑定、viewmodel 适配 | -| `UI/Layout` | 测量、布局、容器规则 | -| `UI/Style` | 样式、主题 token、状态样式 | -| `UI/Input` | 指针、键盘、焦点、拖拽、快捷键 | -| `UI/Render` | draw list、渲染命令、后端抽象 | -| `UI/Text` | 字体、文本测量、IME、换行 | -| `UI/Widgets` | 基础控件和复合控件 | -| `UI/Markup` | `.xcui` 解析、编译、热重载 | -| `UI/Schema` | inspector/form schema | -| `UI/Runtime` | runtime screen/player/系统接入 | -| `UI/DevTools` | UI 树、layout、style、binding 调试工具 | - ---- - -## 5. 核心设计原则 - -### 5.1 Retained-mode,而不是即时模式 - -XCUI 的正式形态是 retained-mode: - -- UI 树长期存在 -- 状态变化驱动局部更新 -- 布局与绘制是独立阶段 -- 不以“每帧手写过程式调用”作为主路径 - -### 5.2 声明式优先,代码式保底 - -XCUI 应同时支持: - -- `Markup-first` -- `Code-first` - -但主路径必须是 `Markup-first`,原因如下: - -- 树状 UI 更适合 AI 生成和修改 -- 结构、样式、行为边界更清晰 -- 更适合热重载和资源化 - -### 5.3 单向数据流 - -强制采用: - -- `State -> View` -- `UI Event -> Command -> Domain -> State Update -> View Refresh` - -禁止: - -- Widget 直接到处改业务对象 -- UI 节点绕过命令系统随意操作 `SceneManager` -- 控件内部偷偷维护大量业务状态 - -### 5.4 Viewport 是宿主,不是普通控件 - -`Scene/Game Viewport` 必须视为特殊宿主节点: - -- 它承载纹理 -- 它定义输入边界 -- 它连接 viewport 服务 -- 它不负责普通 UI 布局逻辑之外的 gizmo/picking/世界 overlay - -### 5.5 Schema 优先于硬编码 Inspector - -对于 Inspector、Material、ImportSettings、Script 字段: - -- 默认走 schema + auto form -- 只有少量复杂控件才走 custom section - -目标是让“新增/修改一个属性面板”更多是改 schema 和 view,而不是继续写大块 imperative C++。 - ---- - -## 6. 编程模型 - -### 6.1 View / ViewModel / Command - -#### View - -职责: - -- 描述结构 -- 描述布局 -- 描述样式 -- 建立绑定 -- 发出命令意图 - -不负责: - -- 直接修改业务对象 -- 直接处理 domain 规则 - -#### ViewModel - -职责: - -- 订阅 editor/runtime 的 domain state -- 整理成 UI 可绑定状态 -- 组织派生状态 -- 绑定命令和事件 - -#### Command - -职责: - -- 把 UI 意图翻译成稳定的业务命令 -- editor 命令落到现有 `Commands::*` -- runtime 命令落到游戏 UI action handler - -### 6.2 推荐命令命名 - -```text -Editor.Entity.Create -Editor.Entity.Delete -Editor.Entity.Rename -Editor.Project.Open -Editor.Project.Refresh -Editor.Scripting.Rebuild -Editor.Layout.Reset -Runtime.Menu.Open -Runtime.Menu.Close -Runtime.HUD.Toggle -``` - -### 6.3 绑定表达式约束 - -XCUI 不引入完整脚本表达式引擎。 - -Markup 中支持的表达式范围只建议包括: - -- `bind` -- `if` -- `not` -- `equals` -- `foreach` -- `command` - -这能保证: - -- AI 易于生成 -- 编译器易于诊断 -- runtime 复杂度可控 - ---- - -## 7. 文件格式设计 - -### 7.1 `.xcui` - -用于描述 UI 视图树。 - -示例: - -```xml - - - - - -