Compare commits
71 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e63457c72b | |||
| 514dee10cc | |||
| 82c39e2521 | |||
| d52eefe2ff | |||
| 03e0b362f7 | |||
| c10367a42e | |||
| b0d3141eee | |||
| 1c77781ac4 | |||
| 39b083f7f7 | |||
| 3048c7cc90 | |||
| 865a35e4d0 | |||
| 191a7b03d2 | |||
| ad98660b1d | |||
| 34c635ba22 | |||
| dcede7f975 | |||
| e251b77d0d | |||
| 6efbaf450e | |||
| c1e7a0d49f | |||
| 6d97f4fc3a | |||
| 1a3b3577fd | |||
| c42fd4d48b | |||
| 2c2a8b8669 | |||
| a805803858 | |||
| ab87ad85d7 | |||
| 17326f455e | |||
| 77fe0a97a4 | |||
| 2a8448cfbc | |||
| 4372c1ce7b | |||
| b44f5ca9fc | |||
| 4a42b757c7 | |||
| 6f877b70b4 | |||
| 5f75879250 | |||
| a46554842d | |||
| 6f370beb0a | |||
| 173ab89158 | |||
| b863dfe727 | |||
| d21f573cc5 | |||
| 8b646bf30a | |||
| 3187ccbfe1 | |||
| 6950bd15c2 | |||
| 9b4a302f6a | |||
| 36c4ae414b | |||
| 35e7602eb8 | |||
| a231bf1fe1 | |||
| eb636dbd89 | |||
| 0ec0eff90c | |||
| 26e75e093e | |||
| 99eae1fe9f | |||
| 33e9041d60 | |||
| 9760bcb00e | |||
| ed90911a5c | |||
| 4dda59a510 | |||
| b40eeb32b8 | |||
| bc47e6e5ac | |||
| 8bfca5e8f2 | |||
| fff33185b9 | |||
| 0411cd0ec1 | |||
| 08ff505b67 | |||
| 1bbbc22bcb | |||
| 76452a9c73 | |||
| a2d21e69b6 | |||
| f1d7e879ac | |||
| d75bd95e89 | |||
| 808335126f | |||
| 4b105ec88f | |||
| ee1f817dc6 | |||
| 01dabcf6b0 | |||
| 0f84e52c21 | |||
| 5d3d52496e | |||
| 75cf48f4fe | |||
| 13ad95dc23 |
@@ -42,6 +42,15 @@ enable_testing()
|
||||
|
||||
option(XCENGINE_ENABLE_MONO_SCRIPTING "Build the Mono-based C# scripting runtime" ON)
|
||||
option(XCENGINE_BUILD_XCUI_EDITOR_APP "Build the XCUI editor shell app" ON)
|
||||
if(XCENGINE_BUILD_XCUI_EDITOR_APP)
|
||||
set(XCENGINE_ENABLE_RENDERING_EDITOR_SUPPORT_DEFAULT ON)
|
||||
else()
|
||||
set(XCENGINE_ENABLE_RENDERING_EDITOR_SUPPORT_DEFAULT OFF)
|
||||
endif()
|
||||
option(
|
||||
XCENGINE_ENABLE_RENDERING_EDITOR_SUPPORT
|
||||
"Build renderer-owned editor support features such as object-id picking"
|
||||
${XCENGINE_ENABLE_RENDERING_EDITOR_SUPPORT_DEFAULT})
|
||||
set(
|
||||
XCENGINE_PHYSX_ROOT_DIR
|
||||
"${CMAKE_SOURCE_DIR}/engine/third_party/physx"
|
||||
|
||||
@@ -0,0 +1,251 @@
|
||||
# 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
|
||||
@@ -0,0 +1,129 @@
|
||||
# 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 层补丁
|
||||
146
docs/plan/NewEditor_SystemAudit_2026-04-22.md
Normal file
146
docs/plan/NewEditor_SystemAudit_2026-04-22.md
Normal file
@@ -0,0 +1,146 @@
|
||||
# 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。
|
||||
@@ -0,0 +1,133 @@
|
||||
# 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.
|
||||
@@ -0,0 +1,202 @@
|
||||
# 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` 只负责消费这层能力
|
||||
|
||||
这才是和成熟引擎一致的分层方向。
|
||||
196
docs/used/NewEditor_ArchitectureClosurePlan_完成归档_2026-04-22.md
Normal file
196
docs/used/NewEditor_ArchitectureClosurePlan_完成归档_2026-04-22.md
Normal file
@@ -0,0 +1,196 @@
|
||||
# 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. 构建、启动、截图验证全部通过
|
||||
@@ -0,0 +1,274 @@
|
||||
# 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
|
||||
@@ -0,0 +1,279 @@
|
||||
# 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
|
||||
@@ -0,0 +1,118 @@
|
||||
# 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
|
||||
@@ -0,0 +1,187 @@
|
||||
# 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.
|
||||
@@ -0,0 +1,79 @@
|
||||
# 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`
|
||||
@@ -0,0 +1,146 @@
|
||||
# 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
|
||||
@@ -0,0 +1,213 @@
|
||||
# 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
|
||||
@@ -0,0 +1,56 @@
|
||||
# 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` 闭环
|
||||
@@ -0,0 +1,291 @@
|
||||
# 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
|
||||
|
||||
只有按这个顺序,才能既不破坏功能,又从根上消灭这套双写宿主残片。
|
||||
@@ -0,0 +1,529 @@
|
||||
# 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.
|
||||
@@ -0,0 +1,90 @@
|
||||
# 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.
|
||||
271
docs/used/NewEditor_审查遗留总收口计划_2026-04-19.md
Normal file
271
docs/used/NewEditor_审查遗留总收口计划_2026-04-19.md
Normal file
@@ -0,0 +1,271 @@
|
||||
# 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,再迁移实现。
|
||||
@@ -0,0 +1,67 @@
|
||||
# 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 秒通过
|
||||
@@ -0,0 +1,66 @@
|
||||
# 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 秒通过
|
||||
@@ -0,0 +1,144 @@
|
||||
# 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 冒烟通过
|
||||
@@ -0,0 +1,68 @@
|
||||
# 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 冒烟通过。
|
||||
@@ -0,0 +1,73 @@
|
||||
# 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 冒烟通过。
|
||||
@@ -0,0 +1,63 @@
|
||||
# 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`
|
||||
@@ -0,0 +1,107 @@
|
||||
# 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 收口计划。
|
||||
@@ -0,0 +1,107 @@
|
||||
# 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`
|
||||
@@ -0,0 +1,74 @@
|
||||
# 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`
|
||||
@@ -0,0 +1,86 @@
|
||||
# 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`
|
||||
@@ -0,0 +1,40 @@
|
||||
# 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`。
|
||||
@@ -0,0 +1,28 @@
|
||||
# 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`。
|
||||
@@ -0,0 +1,93 @@
|
||||
# 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
|
||||
@@ -0,0 +1,90 @@
|
||||
# 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 冒烟通过。
|
||||
@@ -0,0 +1,64 @@
|
||||
# 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 秒通过
|
||||
@@ -0,0 +1,82 @@
|
||||
# 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 冒烟通过
|
||||
@@ -0,0 +1,70 @@
|
||||
# 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 冒烟通过。
|
||||
@@ -0,0 +1,100 @@
|
||||
# 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 冒烟通过
|
||||
@@ -0,0 +1,31 @@
|
||||
# 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`。
|
||||
@@ -0,0 +1,85 @@
|
||||
# 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 冒烟通过。
|
||||
@@ -0,0 +1,48 @@
|
||||
# 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`。
|
||||
@@ -0,0 +1,69 @@
|
||||
# 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 冒烟通过。
|
||||
@@ -0,0 +1,80 @@
|
||||
# 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 冒烟通过。
|
||||
BIN
docs/used/c.png
Normal file
BIN
docs/used/c.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 57 KiB |
BIN
docs/used/color.png
Normal file
BIN
docs/used/color.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 41 KiB |
@@ -98,6 +98,15 @@ Containers::String GetUIDocumentDefaultRootTag(UIDocumentKind kind)
|
||||
message(STATUS "UIDocumentCompiler.cpp is missing; using generated fallback implementation for build stability.")
|
||||
endif()
|
||||
|
||||
set(XCENGINE_RENDERING_EDITOR_SUPPORT_SOURCES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Picking/ObjectIdCodec.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Picking/RenderObjectIdRegistry.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Passes/BuiltinObjectIdPass.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Picking/RenderObjectIdRegistry.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Passes/BuiltinObjectIdPass.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Passes/BuiltinObjectIdPassResources.cpp
|
||||
)
|
||||
|
||||
add_library(XCEngine STATIC
|
||||
# Core (Types, RefCounted, SmartPtr, Event)
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Core/Types.h
|
||||
@@ -520,6 +529,7 @@ add_library(XCEngine STATIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Planning/SceneRenderRequestPlanner.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Planning/SceneRenderRequestUtils.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Picking/ObjectIdCodec.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Picking/RenderObjectIdRegistry.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Extraction/RenderSceneExtractor.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Extraction/RenderSceneUtility.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Graph/RenderGraphTypes.h
|
||||
@@ -539,6 +549,7 @@ add_library(XCEngine STATIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/RenderSurface.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/RenderSurfacePipelineUtils.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/SceneRenderFeaturePass.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/SceneRenderFeaturePassId.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/SceneRenderFeatureHost.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/ShaderVariantUtils.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Internal/RenderSurfacePipelineUtils.h
|
||||
@@ -596,8 +607,6 @@ add_library(XCEngine STATIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Passes/BuiltinDepthStylePassBaseResources.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Passes/BuiltinDepthOnlyPass.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Passes/BuiltinShadowCasterPass.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Passes/BuiltinObjectIdPass.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Passes/BuiltinObjectIdPassResources.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Passes/BuiltinVectorFullscreenPass.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Passes/BuiltinFinalColorPass.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Features/BuiltinGaussianSplatPass.cpp
|
||||
@@ -892,3 +901,77 @@ if(XCENGINE_HAS_NANOVDB)
|
||||
)
|
||||
endif()
|
||||
|
||||
if(XCENGINE_ENABLE_RENDERING_EDITOR_SUPPORT)
|
||||
target_compile_definitions(XCEngine PRIVATE
|
||||
XCENGINE_ENABLE_RENDERING_EDITOR_SUPPORT=1
|
||||
)
|
||||
|
||||
add_library(XCEngineRenderingEditorSupport STATIC
|
||||
${XCENGINE_RENDERING_EDITOR_SUPPORT_SOURCES}
|
||||
)
|
||||
|
||||
target_include_directories(XCEngineRenderingEditorSupport PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/third_party
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/third_party/GLAD/include
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/third_party/stb
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/third_party/assimp/include
|
||||
)
|
||||
|
||||
if(XCENGINE_HAS_NANOVDB)
|
||||
target_include_directories(XCEngineRenderingEditorSupport PRIVATE
|
||||
${XCENGINE_NANOVDB_INCLUDE_DIR}
|
||||
)
|
||||
target_compile_definitions(XCEngineRenderingEditorSupport PRIVATE
|
||||
XCENGINE_HAS_NANOVDB=1
|
||||
)
|
||||
endif()
|
||||
|
||||
if(XCENGINE_ENABLE_PHYSX)
|
||||
target_include_directories(XCEngineRenderingEditorSupport PRIVATE
|
||||
${XCENGINE_PHYSX_INCLUDE_DIR}
|
||||
)
|
||||
target_compile_definitions(XCEngineRenderingEditorSupport PRIVATE
|
||||
XCENGINE_ENABLE_PHYSX=1
|
||||
)
|
||||
else()
|
||||
target_compile_definitions(XCEngineRenderingEditorSupport PRIVATE
|
||||
XCENGINE_ENABLE_PHYSX=0
|
||||
)
|
||||
endif()
|
||||
|
||||
if(MSVC)
|
||||
target_compile_options(XCEngineRenderingEditorSupport PRIVATE
|
||||
/FS
|
||||
/W3
|
||||
$<$<CONFIG:Debug,RelWithDebInfo>:/Z7>)
|
||||
set_target_properties(XCEngineRenderingEditorSupport PROPERTIES
|
||||
MSVC_DEBUG_INFORMATION_FORMAT "$<$<CONFIG:Debug,RelWithDebInfo>:Embedded>"
|
||||
COMPILE_PDB_NAME "XCEngineRenderingEditorSupport-compile"
|
||||
COMPILE_PDB_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/compile-pdb"
|
||||
COMPILE_PDB_OUTPUT_DIRECTORY_DEBUG "${CMAKE_CURRENT_BINARY_DIR}/compile-pdb/Debug"
|
||||
COMPILE_PDB_OUTPUT_DIRECTORY_RELEASE "${CMAKE_CURRENT_BINARY_DIR}/compile-pdb/Release"
|
||||
COMPILE_PDB_OUTPUT_DIRECTORY_MINSIZEREL "${CMAKE_CURRENT_BINARY_DIR}/compile-pdb/MinSizeRel"
|
||||
COMPILE_PDB_OUTPUT_DIRECTORY_RELWITHDEBINFO "${CMAKE_CURRENT_BINARY_DIR}/compile-pdb/RelWithDebInfo"
|
||||
)
|
||||
else()
|
||||
target_compile_options(XCEngineRenderingEditorSupport PRIVATE -Wall)
|
||||
endif()
|
||||
|
||||
target_compile_definitions(XCEngineRenderingEditorSupport PRIVATE
|
||||
XCENGINE_ENABLE_RENDERING_EDITOR_SUPPORT=1
|
||||
XCENGINE_SUPPORT_OPENGL
|
||||
XCENGINE_SUPPORT_VULKAN
|
||||
)
|
||||
|
||||
target_link_libraries(XCEngine PRIVATE
|
||||
XCEngineRenderingEditorSupport
|
||||
)
|
||||
else()
|
||||
target_compile_definitions(XCEngine PRIVATE
|
||||
XCENGINE_ENABLE_RENDERING_EDITOR_SUPPORT=0
|
||||
)
|
||||
endif()
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include "../RHIResourceView.h"
|
||||
#include "../RHIEnums.h"
|
||||
#include "D3D12Texture.h"
|
||||
#include "D3D12Enums.h"
|
||||
|
||||
using Microsoft::WRL::ComPtr;
|
||||
@@ -15,7 +16,6 @@ namespace RHI {
|
||||
|
||||
class D3D12DescriptorHeap;
|
||||
class D3D12Buffer;
|
||||
|
||||
class D3D12ResourceView : public RHIResourceView {
|
||||
public:
|
||||
D3D12ResourceView();
|
||||
@@ -30,8 +30,10 @@ public:
|
||||
ResourceViewType GetViewType() const override { return m_viewType; }
|
||||
ResourceViewDimension GetDimension() const override { return m_dimension; }
|
||||
Format GetFormat() const override { return m_format; }
|
||||
RHITexture* GetTextureResource() const override { return m_texture; }
|
||||
|
||||
void SetOwnedHeap(std::unique_ptr<class D3D12DescriptorHeap> heap);
|
||||
void SetTextureResource(D3D12Texture* texture) { m_texture = texture; }
|
||||
|
||||
void InitializeAsRenderTarget(ID3D12Device* device, ID3D12Resource* resource,
|
||||
const D3D12_RENDER_TARGET_VIEW_DESC* desc,
|
||||
@@ -86,6 +88,7 @@ private:
|
||||
ResourceViewDimension m_dimension;
|
||||
D3D12_CPU_DESCRIPTOR_HANDLE m_handle;
|
||||
ID3D12Resource* m_resource;
|
||||
D3D12Texture* m_texture;
|
||||
D3D12DescriptorHeap* m_heap;
|
||||
uint32_t m_slotIndex;
|
||||
std::unique_ptr<D3D12DescriptorHeap> m_ownedHeap;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "XCEngine/RHI/OpenGL/OpenGLFramebuffer.h"
|
||||
#include "XCEngine/RHI/OpenGL/OpenGLTexture.h"
|
||||
#include "XCEngine/RHI/RHIResourceView.h"
|
||||
#include "XCEngine/RHI/RHITypes.h"
|
||||
|
||||
@@ -71,7 +72,8 @@ public:
|
||||
uint32_t GetBufferSize() const { return m_bufferSize; }
|
||||
uint32_t GetBufferStride() const { return m_bufferStride; }
|
||||
const FramebufferAttachment& GetFramebufferAttachment() const { return m_framebufferAttachment; }
|
||||
const OpenGLTexture* GetTextureResource() const { return m_texture; }
|
||||
RHITexture* GetTextureResource() const override { return m_texture; }
|
||||
OpenGLTexture* GetOpenGLTextureResource() const { return m_texture; }
|
||||
|
||||
private:
|
||||
ResourceViewType m_viewType;
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
namespace XCEngine {
|
||||
namespace RHI {
|
||||
|
||||
class RHITexture;
|
||||
|
||||
class RHIResourceView {
|
||||
public:
|
||||
virtual ~RHIResourceView() = default;
|
||||
@@ -19,6 +21,9 @@ public:
|
||||
virtual ResourceViewType GetViewType() const = 0;
|
||||
virtual ResourceViewDimension GetDimension() const = 0;
|
||||
virtual Format GetFormat() const = 0;
|
||||
virtual RHITexture* GetTextureResource() const {
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
class RHIVertexBufferView : public RHIResourceView {
|
||||
|
||||
@@ -2,13 +2,12 @@
|
||||
|
||||
#include "XCEngine/RHI/RHIResourceView.h"
|
||||
#include "XCEngine/RHI/Vulkan/VulkanCommon.h"
|
||||
#include "XCEngine/RHI/Vulkan/VulkanTexture.h"
|
||||
|
||||
namespace XCEngine {
|
||||
namespace RHI {
|
||||
|
||||
class VulkanBuffer;
|
||||
class VulkanTexture;
|
||||
|
||||
class VulkanResourceView : public RHIResourceView {
|
||||
public:
|
||||
VulkanResourceView() = default;
|
||||
@@ -30,6 +29,7 @@ public:
|
||||
ResourceViewType GetViewType() const override { return m_viewType; }
|
||||
ResourceViewDimension GetDimension() const override { return m_dimension; }
|
||||
Format GetFormat() const override { return m_format; }
|
||||
RHITexture* GetTextureResource() const override { return m_texture; }
|
||||
|
||||
VulkanTexture* GetTexture() const { return m_texture; }
|
||||
VkImageView GetImageView() const { return m_imageView; }
|
||||
|
||||
@@ -48,6 +48,10 @@ public:
|
||||
SceneRenderInjectionPoint GetInjectionPoint() const override {
|
||||
return SceneRenderInjectionPoint::BeforeTransparent;
|
||||
}
|
||||
NativeSceneFeaturePassId GetNativeSceneFeaturePassId()
|
||||
const override {
|
||||
return NativeSceneFeaturePassId::BuiltinGaussianSplat;
|
||||
}
|
||||
bool Initialize(const RenderContext& context) override;
|
||||
bool IsActive(const RenderSceneData& sceneData) const override;
|
||||
bool Prepare(
|
||||
|
||||
@@ -43,6 +43,10 @@ public:
|
||||
SceneRenderInjectionPoint GetInjectionPoint() const override {
|
||||
return SceneRenderInjectionPoint::BeforeTransparent;
|
||||
}
|
||||
NativeSceneFeaturePassId GetNativeSceneFeaturePassId()
|
||||
const override {
|
||||
return NativeSceneFeaturePassId::BuiltinVolumetric;
|
||||
}
|
||||
bool Initialize(const RenderContext& context) override;
|
||||
bool IsActive(const RenderSceneData& sceneData) const override;
|
||||
bool Prepare(
|
||||
|
||||
@@ -32,6 +32,7 @@ private:
|
||||
RenderGraphTextureDesc desc = {};
|
||||
RenderGraphTextureKind kind = RenderGraphTextureKind::Transient;
|
||||
RHI::RHIResourceView* importedView = nullptr;
|
||||
RHI::RHITexture* importedTexture = nullptr;
|
||||
RenderGraphImportedTextureOptions importedOptions = {};
|
||||
};
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@ private:
|
||||
RenderGraphTextureDesc desc = {};
|
||||
RenderGraphTextureKind kind = RenderGraphTextureKind::Transient;
|
||||
RHI::RHIResourceView* importedView = nullptr;
|
||||
RHI::RHITexture* importedTexture = nullptr;
|
||||
RenderGraphImportedTextureOptions importedOptions = {};
|
||||
};
|
||||
|
||||
|
||||
@@ -104,7 +104,7 @@ struct RenderGraphTextureTransitionPlan {
|
||||
|
||||
struct RenderGraphExecutionContext {
|
||||
const RenderContext& renderContext;
|
||||
const RenderGraphRuntimeResources* runtimeResources = nullptr;
|
||||
RenderGraphRuntimeResources* runtimeResources = nullptr;
|
||||
|
||||
RHI::RHIResourceView* ResolveTextureView(
|
||||
RenderGraphTextureHandle handle,
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <XCEngine/Core/Asset/ResourceHandle.h>
|
||||
#include <XCEngine/Core/Containers/String.h>
|
||||
#include <XCEngine/Core/Math/Vector4.h>
|
||||
#include <XCEngine/Rendering/Builtin/BuiltinPassTypes.h>
|
||||
#include <XCEngine/Rendering/RenderPass.h>
|
||||
#include <XCEngine/Resources/Shader/Shader.h>
|
||||
#include <XCEngine/RHI/RHIEnums.h>
|
||||
@@ -27,6 +28,23 @@ public:
|
||||
RHI::RHIDescriptorSet* set = nullptr;
|
||||
};
|
||||
|
||||
struct BoundSetState {
|
||||
RHI::RHIResourceView* sourceColorTextureView = nullptr;
|
||||
std::vector<RHI::RHIResourceView*> materialTextureViews = {};
|
||||
};
|
||||
|
||||
struct PassResourceLayout {
|
||||
std::vector<BuiltinPassSetLayoutMetadata> setLayouts = {};
|
||||
std::vector<OwnedDescriptorSet> descriptorSets = {};
|
||||
std::vector<BoundSetState> boundSetStates = {};
|
||||
RHI::RHIPipelineLayout* pipelineLayout = nullptr;
|
||||
PassResourceBindingLocation passConstants = {};
|
||||
PassResourceBindingLocation sourceColorTexture = {};
|
||||
PassResourceBindingLocation linearClampSampler = {};
|
||||
uint32_t firstDescriptorSet = 0u;
|
||||
uint32_t descriptorSetCount = 0u;
|
||||
};
|
||||
|
||||
explicit BuiltinVectorFullscreenPass(
|
||||
const Math::Vector4& vectorPayload = Math::Vector4::One(),
|
||||
Containers::String shaderPath = {},
|
||||
@@ -51,7 +69,15 @@ public:
|
||||
private:
|
||||
bool EnsureInitialized(const RenderContext& renderContext, const RenderSurface& surface);
|
||||
bool CreateResources(const RenderContext& renderContext, const RenderSurface& surface);
|
||||
bool CreateOwnedDescriptorSet(
|
||||
const BuiltinPassSetLayoutMetadata& setLayout,
|
||||
OwnedDescriptorSet& descriptorSet);
|
||||
bool UpdateDescriptorSets(const RenderPassContext& context);
|
||||
RHI::RHIResourceView* ResolveExtraTextureView(
|
||||
const RenderPassContext& context,
|
||||
const BuiltinPassResourceBindingDesc& bindingDesc) const;
|
||||
void DestroyResources();
|
||||
void DestroyPassResourceLayout();
|
||||
void DestroyOwnedDescriptorSet(OwnedDescriptorSet& descriptorSet);
|
||||
|
||||
Containers::String m_shaderPath;
|
||||
@@ -64,12 +90,8 @@ private:
|
||||
uint32_t m_renderTargetSampleQuality = 0u;
|
||||
Resources::ResourceHandle<Resources::Shader> m_shader;
|
||||
RHI::RHISampler* m_sampler = nullptr;
|
||||
RHI::RHIPipelineLayout* m_pipelineLayout = nullptr;
|
||||
RHI::RHIPipelineState* m_pipelineState = nullptr;
|
||||
OwnedDescriptorSet m_constantsSet = {};
|
||||
OwnedDescriptorSet m_textureSet = {};
|
||||
OwnedDescriptorSet m_samplerSet = {};
|
||||
RHI::RHIResourceView* m_boundSourceColorView = nullptr;
|
||||
PassResourceLayout m_passLayout = {};
|
||||
};
|
||||
|
||||
} // namespace Passes
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
#include <XCEngine/Core/Math/Vector4.h>
|
||||
#include <XCEngine/Core/Types.h>
|
||||
|
||||
#include <limits>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Rendering {
|
||||
|
||||
@@ -17,27 +15,6 @@ inline bool IsValidRenderObjectId(RenderObjectId renderObjectId) {
|
||||
return renderObjectId != kInvalidRenderObjectId;
|
||||
}
|
||||
|
||||
inline bool CanConvertRuntimeObjectIdToRenderObjectId(Core::uint64 runtimeObjectId) {
|
||||
return runtimeObjectId != 0u &&
|
||||
runtimeObjectId <= static_cast<Core::uint64>(std::numeric_limits<RenderObjectId>::max());
|
||||
}
|
||||
|
||||
inline bool TryConvertRuntimeObjectIdToRenderObjectId(
|
||||
Core::uint64 runtimeObjectId,
|
||||
RenderObjectId& outRenderObjectId) {
|
||||
outRenderObjectId = kInvalidRenderObjectId;
|
||||
if (!CanConvertRuntimeObjectIdToRenderObjectId(runtimeObjectId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outRenderObjectId = static_cast<RenderObjectId>(runtimeObjectId);
|
||||
return true;
|
||||
}
|
||||
|
||||
inline Core::uint64 ConvertRenderObjectIdToRuntimeObjectId(RenderObjectId renderObjectId) {
|
||||
return static_cast<Core::uint64>(renderObjectId);
|
||||
}
|
||||
|
||||
inline EncodedObjectId EncodeRenderObjectIdToEncodedId(RenderObjectId renderObjectId) {
|
||||
return renderObjectId;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/Core/Types.h>
|
||||
#include <XCEngine/Rendering/Picking/ObjectIdCodec.h>
|
||||
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Rendering {
|
||||
|
||||
class RenderObjectIdRegistry {
|
||||
public:
|
||||
static RenderObjectIdRegistry& Get();
|
||||
|
||||
RenderObjectIdRegistry(const RenderObjectIdRegistry&) = delete;
|
||||
RenderObjectIdRegistry& operator=(const RenderObjectIdRegistry&) = delete;
|
||||
|
||||
RenderObjectId GetOrAllocateRenderObjectId(Core::uint64 runtimeObjectId);
|
||||
bool TryGetRenderObjectId(
|
||||
Core::uint64 runtimeObjectId,
|
||||
RenderObjectId& outRenderObjectId) const;
|
||||
bool TryResolveRuntimeObjectId(
|
||||
RenderObjectId renderObjectId,
|
||||
Core::uint64& outRuntimeObjectId) const;
|
||||
Core::uint64 ResolveRuntimeObjectId(RenderObjectId renderObjectId) const;
|
||||
|
||||
void Reset();
|
||||
Core::uint64 GetGeneration() const;
|
||||
|
||||
private:
|
||||
RenderObjectIdRegistry() = default;
|
||||
|
||||
RenderObjectId AllocateRenderObjectIdLocked();
|
||||
void BumpGenerationLocked();
|
||||
|
||||
mutable std::mutex m_mutex = {};
|
||||
RenderObjectId m_nextRenderObjectId = 1u;
|
||||
Core::uint64 m_generation = 1u;
|
||||
std::unordered_map<Core::uint64, RenderObjectId>
|
||||
m_renderObjectIdByRuntimeObjectId = {};
|
||||
std::unordered_map<RenderObjectId, Core::uint64>
|
||||
m_runtimeObjectIdByRenderObjectId = {};
|
||||
};
|
||||
|
||||
} // namespace Rendering
|
||||
} // namespace XCEngine
|
||||
@@ -104,14 +104,6 @@ public:
|
||||
GetSharedPipelineBackendAsset() const {
|
||||
return nullptr;
|
||||
}
|
||||
virtual bool UsesNativeCameraFramePlanBaseline() const {
|
||||
return false;
|
||||
}
|
||||
virtual bool UsesNativeCameraFramePlanBaseline(
|
||||
int32_t rendererIndex) const {
|
||||
(void)rendererIndex;
|
||||
return UsesNativeCameraFramePlanBaseline();
|
||||
}
|
||||
virtual bool TryGetDefaultFinalColorSettings(FinalColorSettings&) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ public:
|
||||
bool RecordScenePhase(ScenePhase scenePhase);
|
||||
bool RecordSceneDrawSettings(const DrawSettings& drawSettings);
|
||||
bool RecordInjectionPoint(SceneRenderInjectionPoint injectionPoint);
|
||||
bool RecordFeaturePass(const Containers::String& featurePassName);
|
||||
bool RecordFeaturePass(NativeSceneFeaturePassId featurePassId);
|
||||
|
||||
private:
|
||||
SceneRenderFeaturePassBeginCallback BuildBeginRecordedPassCallback(
|
||||
|
||||
@@ -69,6 +69,9 @@ public:
|
||||
|
||||
private:
|
||||
bool EnsureInitialized(const RenderContext& context);
|
||||
RenderPass* ResolvePipelineBackendStandalonePass(
|
||||
CameraFrameStage stage,
|
||||
int32_t rendererIndex) const;
|
||||
void BindStageRecorderPipelineBackend();
|
||||
void ShutdownInitializedComponents();
|
||||
void ResetInitializationState();
|
||||
@@ -98,7 +101,6 @@ public:
|
||||
managedAssetRuntime);
|
||||
|
||||
std::unique_ptr<RenderPipeline> CreatePipeline() const override;
|
||||
void ConfigurePipeline(RenderPipeline& pipeline) const override;
|
||||
FinalColorSettings GetDefaultFinalColorSettings() const override;
|
||||
|
||||
private:
|
||||
|
||||
@@ -23,12 +23,18 @@ class RenderGraphBuilder;
|
||||
class RenderGraphBlackboard;
|
||||
|
||||
struct RenderPassContext {
|
||||
struct TextureBindingView {
|
||||
Containers::String resourceName = {};
|
||||
RHI::RHIResourceView* resourceView = nullptr;
|
||||
};
|
||||
|
||||
const RenderContext& renderContext;
|
||||
const RenderSurface& surface;
|
||||
const RenderSceneData& sceneData;
|
||||
const RenderSurface* sourceSurface = nullptr;
|
||||
RHI::RHIResourceView* sourceColorView = nullptr;
|
||||
RHI::ResourceStates sourceColorState = RHI::ResourceStates::Common;
|
||||
std::vector<TextureBindingView> textureBindings = {};
|
||||
};
|
||||
|
||||
inline RenderPassContext BuildRenderPassContext(
|
||||
@@ -39,7 +45,8 @@ inline RenderPassContext BuildRenderPassContext(
|
||||
executionContext.sceneData,
|
||||
executionContext.sourceSurface,
|
||||
executionContext.sourceColorView,
|
||||
executionContext.sourceColorState
|
||||
executionContext.sourceColorState,
|
||||
{}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,12 @@ struct RenderPassGraphIO {
|
||||
bool writeDepth = false;
|
||||
};
|
||||
|
||||
struct RenderPassGraphTextureBindingRequest {
|
||||
Containers::String resourceName = {};
|
||||
RenderGraphTextureHandle texture = {};
|
||||
RenderGraphTextureAspect aspect = RenderGraphTextureAspect::Color;
|
||||
};
|
||||
|
||||
inline RenderPassGraphIO BuildSourceColorFullscreenRasterPassGraphIO() {
|
||||
return {
|
||||
true,
|
||||
@@ -52,7 +58,9 @@ bool RecordCallbackRasterRenderPass(
|
||||
const RenderPassRenderGraphContext& context,
|
||||
const RenderPassGraphIO& io,
|
||||
RenderPassGraphExecutePassCallback executePassCallback,
|
||||
std::vector<RenderGraphTextureHandle> additionalReadTextures = {});
|
||||
std::vector<RenderGraphTextureHandle> additionalReadTextures = {},
|
||||
std::vector<RenderGraphTextureHandle> additionalReadDepthTextures = {},
|
||||
std::vector<RenderPassGraphTextureBindingRequest> textureBindingRequests = {});
|
||||
|
||||
bool RecordRasterRenderPass(
|
||||
RenderPass& pass,
|
||||
@@ -100,12 +108,14 @@ inline bool RecordSourceColorFullscreenCallbackRasterPass(
|
||||
inline bool RecordColorDepthCallbackRasterPass(
|
||||
const RenderPassRenderGraphContext& context,
|
||||
RenderPassGraphExecutePassCallback executePassCallback,
|
||||
std::vector<RenderGraphTextureHandle> additionalReadTextures = {}) {
|
||||
std::vector<RenderGraphTextureHandle> additionalReadTextures = {},
|
||||
std::vector<RenderGraphTextureHandle> additionalReadDepthTextures = {}) {
|
||||
return RecordCallbackRasterRenderPass(
|
||||
context,
|
||||
BuildColorDepthRasterPassGraphIO(),
|
||||
std::move(executePassCallback),
|
||||
std::move(additionalReadTextures));
|
||||
std::move(additionalReadTextures),
|
||||
std::move(additionalReadDepthTextures));
|
||||
}
|
||||
|
||||
} // namespace Rendering
|
||||
|
||||
@@ -23,7 +23,7 @@ public:
|
||||
bool* recordedAnyPass = nullptr) const;
|
||||
bool RecordFeaturePass(
|
||||
const SceneRenderFeaturePassRenderGraphContext& context,
|
||||
const Containers::String& featurePassName,
|
||||
NativeSceneFeaturePassId featurePassId,
|
||||
bool* recordedPass = nullptr) const;
|
||||
bool Execute(
|
||||
const FrameExecutionContext& executionContext,
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <XCEngine/Rendering/Graph/RenderGraphTypes.h>
|
||||
#include <XCEngine/Rendering/RenderPass.h>
|
||||
#include <XCEngine/Rendering/RenderSurface.h>
|
||||
#include <XCEngine/Rendering/SceneRenderFeaturePassId.h>
|
||||
|
||||
#include <functional>
|
||||
|
||||
@@ -44,6 +45,11 @@ public:
|
||||
return SceneRenderInjectionPoint::BeforeTransparent;
|
||||
}
|
||||
|
||||
virtual NativeSceneFeaturePassId
|
||||
GetNativeSceneFeaturePassId() const {
|
||||
return NativeSceneFeaturePassId::Invalid;
|
||||
}
|
||||
|
||||
bool SupportsInjectionPoint(SceneRenderInjectionPoint injectionPoint) const {
|
||||
return GetInjectionPoint() == injectionPoint;
|
||||
}
|
||||
|
||||
15
engine/include/XCEngine/Rendering/SceneRenderFeaturePassId.h
Normal file
15
engine/include/XCEngine/Rendering/SceneRenderFeaturePassId.h
Normal file
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Rendering {
|
||||
|
||||
enum class NativeSceneFeaturePassId : uint32_t {
|
||||
Invalid = 0u,
|
||||
BuiltinGaussianSplat = 1u,
|
||||
BuiltinVolumetric = 2u
|
||||
};
|
||||
|
||||
} // namespace Rendering
|
||||
} // namespace XCEngine
|
||||
@@ -34,9 +34,6 @@ Containers::String GetBuiltinUnlitShaderPath();
|
||||
Containers::String GetBuiltinDepthOnlyShaderPath();
|
||||
Containers::String GetBuiltinShadowCasterShaderPath();
|
||||
Containers::String GetBuiltinObjectIdShaderPath();
|
||||
Containers::String GetBuiltinObjectIdOutlineShaderPath();
|
||||
Containers::String GetBuiltinSelectionMaskShaderPath();
|
||||
Containers::String GetBuiltinSelectionOutlineShaderPath();
|
||||
Containers::String GetBuiltinSkyboxShaderPath();
|
||||
Containers::String GetBuiltinGaussianSplatShaderPath();
|
||||
Containers::String GetBuiltinGaussianSplatUtilitiesShaderPath();
|
||||
|
||||
@@ -106,20 +106,43 @@ inline UITabStripLayoutResult ArrangeUITabStrip(
|
||||
const float gap = (std::max)(0.0f, metrics.tabGap);
|
||||
const float totalGapWidth = gap * static_cast<float>(desiredHeaderWidths.size() - 1u);
|
||||
const float availableTabsWidth = (std::max)(0.0f, bounds.width - totalGapWidth);
|
||||
const float minTabWidth = (std::max)(0.0f, metrics.tabMinWidth);
|
||||
|
||||
std::vector<float> resolvedHeaderWidths = {};
|
||||
resolvedHeaderWidths.reserve(desiredHeaderWidths.size());
|
||||
|
||||
float totalDesiredWidth = 0.0f;
|
||||
float totalMinimumWidth = 0.0f;
|
||||
for (float width : desiredHeaderWidths) {
|
||||
totalDesiredWidth += (std::max)(0.0f, width);
|
||||
const float desiredWidth = (std::max)((std::max)(0.0f, width), minTabWidth);
|
||||
resolvedHeaderWidths.push_back(desiredWidth);
|
||||
totalDesiredWidth += desiredWidth;
|
||||
totalMinimumWidth += minTabWidth;
|
||||
}
|
||||
|
||||
const float scale = totalDesiredWidth > 0.0f && totalDesiredWidth > availableTabsWidth
|
||||
? availableTabsWidth / totalDesiredWidth
|
||||
: 1.0f;
|
||||
if (totalDesiredWidth > availableTabsWidth && totalDesiredWidth > 0.0f) {
|
||||
if (totalMinimumWidth > 0.0f && totalMinimumWidth >= availableTabsWidth) {
|
||||
const float minWidthScale = availableTabsWidth / totalMinimumWidth;
|
||||
for (float& width : resolvedHeaderWidths) {
|
||||
width = minTabWidth * minWidthScale;
|
||||
}
|
||||
} else {
|
||||
const float totalShrinkableWidth = totalDesiredWidth - totalMinimumWidth;
|
||||
const float overflowWidth = totalDesiredWidth - availableTabsWidth;
|
||||
const float excessScale = totalShrinkableWidth > 0.0f
|
||||
? ((std::max)(totalShrinkableWidth - overflowWidth, 0.0f) / totalShrinkableWidth)
|
||||
: 0.0f;
|
||||
for (float& width : resolvedHeaderWidths) {
|
||||
const float shrinkableWidth = (std::max)(width - minTabWidth, 0.0f);
|
||||
width = minTabWidth + shrinkableWidth * excessScale;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float cursorX = bounds.x;
|
||||
for (std::size_t index = 0; index < desiredHeaderWidths.size(); ++index) {
|
||||
const float width = totalDesiredWidth > 0.0f
|
||||
? (std::max)(0.0f, desiredHeaderWidths[index]) * scale
|
||||
? resolvedHeaderWidths[index]
|
||||
: availableTabsWidth / static_cast<float>(desiredHeaderWidths.size());
|
||||
result.tabHeaderRects[index] = UIRect(cursorX, bounds.y, width, headerHeight);
|
||||
cursorX += width + gap;
|
||||
|
||||
@@ -683,10 +683,9 @@ std::vector<fs::path> CollectBuiltinShaderAssetPaths() {
|
||||
GetBuiltinUnlitShaderPath(),
|
||||
GetBuiltinDepthOnlyShaderPath(),
|
||||
GetBuiltinShadowCasterShaderPath(),
|
||||
#if XCENGINE_ENABLE_RENDERING_EDITOR_SUPPORT
|
||||
GetBuiltinObjectIdShaderPath(),
|
||||
GetBuiltinObjectIdOutlineShaderPath(),
|
||||
GetBuiltinSelectionMaskShaderPath(),
|
||||
GetBuiltinSelectionOutlineShaderPath(),
|
||||
#endif
|
||||
GetBuiltinSkyboxShaderPath(),
|
||||
GetBuiltinGaussianSplatShaderPath(),
|
||||
GetBuiltinGaussianSplatUtilitiesShaderPath(),
|
||||
|
||||
@@ -1597,6 +1597,7 @@ RHIResourceView* D3D12Device::CreateRenderTargetView(RHITexture* texture, const
|
||||
}
|
||||
|
||||
view->InitializeAsRenderTarget(m_device.Get(), resource, &rtvDesc, heap.get(), 0);
|
||||
view->SetTextureResource(d3d12Texture);
|
||||
view->SetOwnedHeap(std::move(heap));
|
||||
|
||||
return view;
|
||||
@@ -1619,6 +1620,7 @@ RHIResourceView* D3D12Device::CreateDepthStencilView(RHITexture* texture, const
|
||||
}
|
||||
|
||||
view->InitializeAsDepthStencil(m_device.Get(), resource, &dsvDesc, heap.get(), 0);
|
||||
view->SetTextureResource(d3d12Texture);
|
||||
view->SetOwnedHeap(std::move(heap));
|
||||
return view;
|
||||
}
|
||||
@@ -1738,6 +1740,7 @@ RHIResourceView* D3D12Device::CreateShaderResourceView(RHITexture* texture, cons
|
||||
}
|
||||
|
||||
view->InitializeAsShaderResource(m_device.Get(), resource, &srvDesc, heap.get(), 0);
|
||||
view->SetTextureResource(d3d12Texture);
|
||||
if (IsDepthFormat(format)) {
|
||||
view->SetFormat(format);
|
||||
}
|
||||
@@ -1815,6 +1818,7 @@ RHIResourceView* D3D12Device::CreateUnorderedAccessView(RHITexture* texture, con
|
||||
}
|
||||
|
||||
view->InitializeAsUnorderedAccess(m_device.Get(), resource, &uavDesc, heap.get(), 0);
|
||||
view->SetTextureResource(d3d12Texture);
|
||||
view->SetOwnedHeap(std::move(heap));
|
||||
return view;
|
||||
}
|
||||
|
||||
@@ -111,6 +111,7 @@ D3D12ResourceView::D3D12ResourceView()
|
||||
, m_dimension(ResourceViewDimension::Unknown)
|
||||
, m_handle({0})
|
||||
, m_resource(nullptr)
|
||||
, m_texture(nullptr)
|
||||
, m_heap(nullptr)
|
||||
, m_slotIndex(0)
|
||||
, m_ownedHeap(nullptr)
|
||||
@@ -127,6 +128,7 @@ D3D12ResourceView::~D3D12ResourceView() {
|
||||
void D3D12ResourceView::Shutdown() {
|
||||
m_handle = {};
|
||||
m_resource = nullptr;
|
||||
m_texture = nullptr;
|
||||
if (m_ownedHeap) {
|
||||
m_ownedHeap->Shutdown();
|
||||
m_ownedHeap.reset();
|
||||
|
||||
@@ -52,7 +52,7 @@ bool GetOpenGLClearTargetExtent(const OpenGLResourceView* view, GLsizei& width,
|
||||
return false;
|
||||
}
|
||||
|
||||
const OpenGLTexture* texture = view->GetTextureResource();
|
||||
const OpenGLTexture* texture = view->GetOpenGLTextureResource();
|
||||
if (texture == nullptr) {
|
||||
return false;
|
||||
}
|
||||
@@ -631,7 +631,7 @@ void OpenGLCommandList::TransitionBarrier(RHIResourceView* resource, ResourceSta
|
||||
(void)stateBefore;
|
||||
if (resource != nullptr && resource->IsValid()) {
|
||||
OpenGLResourceView* view = static_cast<OpenGLResourceView*>(resource);
|
||||
OpenGLTexture* texture = const_cast<OpenGLTexture*>(view->GetTextureResource());
|
||||
OpenGLTexture* texture = view->GetOpenGLTextureResource();
|
||||
if (texture != nullptr) {
|
||||
texture->SetState(stateAfter);
|
||||
}
|
||||
@@ -750,7 +750,7 @@ void OpenGLCommandList::SetRenderTargets(uint32_t count, RHIResourceView** rende
|
||||
|
||||
for (uint32_t i = 0; i < count; ++i) {
|
||||
auto* view = static_cast<OpenGLResourceView*>(renderTargets[i]);
|
||||
const OpenGLTexture* texture = view != nullptr ? view->GetTextureResource() : nullptr;
|
||||
const OpenGLTexture* texture = view != nullptr ? view->GetOpenGLTextureResource() : nullptr;
|
||||
if (texture != nullptr) {
|
||||
width = texture->GetWidth();
|
||||
height = texture->GetHeight();
|
||||
@@ -760,7 +760,7 @@ void OpenGLCommandList::SetRenderTargets(uint32_t count, RHIResourceView** rende
|
||||
|
||||
if ((width == 0 || height == 0) && depthStencil != nullptr) {
|
||||
auto* dsv = static_cast<OpenGLResourceView*>(depthStencil);
|
||||
const OpenGLTexture* depthTexture = dsv != nullptr ? dsv->GetTextureResource() : nullptr;
|
||||
const OpenGLTexture* depthTexture = dsv != nullptr ? dsv->GetOpenGLTextureResource() : nullptr;
|
||||
if (depthTexture != nullptr) {
|
||||
width = depthTexture->GetWidth();
|
||||
height = depthTexture->GetHeight();
|
||||
@@ -782,7 +782,7 @@ void OpenGLCommandList::SetRenderTargets(uint32_t count, RHIResourceView** rende
|
||||
} else {
|
||||
ReleaseComposedFramebuffer();
|
||||
OpenGLResourceView* view = static_cast<OpenGLResourceView*>(renderTargets[0]);
|
||||
const OpenGLTexture* texture = view != nullptr ? view->GetTextureResource() : nullptr;
|
||||
const OpenGLTexture* texture = view != nullptr ? view->GetOpenGLTextureResource() : nullptr;
|
||||
if (texture != nullptr) {
|
||||
m_currentRenderTargetWidth = texture->GetWidth();
|
||||
m_currentRenderTargetHeight = texture->GetHeight();
|
||||
@@ -795,7 +795,7 @@ void OpenGLCommandList::SetRenderTargets(uint32_t count, RHIResourceView** rende
|
||||
ReleaseComposedFramebuffer();
|
||||
OpenGLResourceView* dsv = static_cast<OpenGLResourceView*>(depthStencil);
|
||||
if (dsv && dsv->IsValid()) {
|
||||
const OpenGLTexture* depthTexture = dsv->GetTextureResource();
|
||||
const OpenGLTexture* depthTexture = dsv->GetOpenGLTextureResource();
|
||||
if (depthTexture != nullptr) {
|
||||
m_currentRenderTargetWidth = depthTexture->GetWidth();
|
||||
m_currentRenderTargetHeight = depthTexture->GetHeight();
|
||||
@@ -1056,8 +1056,8 @@ void OpenGLCommandList::CopyResource(RHIResourceView* dst, RHIResourceView* src)
|
||||
|
||||
GLuint dstTex = dstView->GetTexture();
|
||||
GLuint srcTex = srcView->GetTexture();
|
||||
const OpenGLTexture* dstTexture = dstView->GetTextureResource();
|
||||
const OpenGLTexture* srcTexture = srcView->GetTextureResource();
|
||||
const OpenGLTexture* dstTexture = dstView->GetOpenGLTextureResource();
|
||||
const OpenGLTexture* srcTexture = srcView->GetOpenGLTextureResource();
|
||||
if (dstTex && srcTex && dstTexture != nullptr && srcTexture != nullptr) {
|
||||
const GLuint srcTarget = ToOpenGL(srcTexture->GetOpenGLType());
|
||||
const GLuint dstTarget = ToOpenGL(dstTexture->GetOpenGLType());
|
||||
|
||||
@@ -196,7 +196,7 @@ void OpenGLDescriptorSet::Update(uint32_t offset, RHIResourceView* view) {
|
||||
}
|
||||
|
||||
binding->textureIds[0] = glView->GetTexture();
|
||||
if (const OpenGLTexture* texture = glView->GetTextureResource()) {
|
||||
if (const OpenGLTexture* texture = glView->GetOpenGLTextureResource()) {
|
||||
binding->textureTargets[0] = static_cast<uint32_t>(ToOpenGL(texture->GetOpenGLType()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
#include "Rendering/Execution/Internal/CameraFrameGraph/Executor.h"
|
||||
#include "Rendering/GraphicsSettingsState.h"
|
||||
#include "Rendering/Internal/RenderPipelineFactory.h"
|
||||
#if XCENGINE_ENABLE_RENDERING_EDITOR_SUPPORT
|
||||
#include "Rendering/Passes/BuiltinObjectIdPass.h"
|
||||
#endif
|
||||
#include "Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.h"
|
||||
#include "Rendering/RenderPipelineAsset.h"
|
||||
#include "Rendering/RenderSurface.h"
|
||||
@@ -29,6 +32,18 @@ bool IsManagedPipelineAsset(
|
||||
pipelineAsset.get()) != nullptr;
|
||||
}
|
||||
|
||||
void ConfigureTopLevelToolingPasses(RenderPipeline& pipeline) {
|
||||
#if XCENGINE_ENABLE_RENDERING_EDITOR_SUPPORT
|
||||
// Object-id is a tooling/editor request. It should live on the
|
||||
// top-level pipeline used by CameraRenderer, not on a scene backend.
|
||||
pipeline.SetCameraFrameStandalonePass(
|
||||
CameraFrameStage::ObjectId,
|
||||
std::make_unique<Passes::BuiltinObjectIdPass>());
|
||||
#else
|
||||
(void)pipeline;
|
||||
#endif
|
||||
}
|
||||
|
||||
RenderPipelineStageSupportContext BuildStageSupportContext(
|
||||
const CameraFramePlan& plan,
|
||||
CameraFrameStage stage) {
|
||||
@@ -124,6 +139,10 @@ void CameraRenderer::ResetPipeline(std::unique_ptr<RenderPipeline> pipeline) {
|
||||
&m_pipelineAsset);
|
||||
}
|
||||
|
||||
if (m_pipeline != nullptr) {
|
||||
ConfigureTopLevelToolingPasses(*m_pipeline);
|
||||
}
|
||||
|
||||
m_managedPipelineEnvironmentGeneration =
|
||||
UsesManagedPipelineBinding()
|
||||
? GetGraphicsSettingsState().GetEnvironmentGeneration()
|
||||
@@ -234,6 +253,7 @@ bool CameraRenderer::Render(
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#if XCENGINE_ENABLE_RENDERING_EDITOR_SUPPORT
|
||||
if (plan.request.objectId.IsRequested() &&
|
||||
!plan.request.objectId.IsValid()) {
|
||||
Debug::Logger::Get().Error(
|
||||
@@ -241,6 +261,14 @@ bool CameraRenderer::Render(
|
||||
"CameraRenderer::Render failed: object-id request invalid");
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
if (plan.request.objectId.IsRequested()) {
|
||||
Debug::Logger::Get().Error(
|
||||
Debug::LogCategory::Rendering,
|
||||
"CameraRenderer::Render failed: object-id support is disabled in this build");
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
DirectionalShadowExecutionState shadowState = {};
|
||||
if (m_directionalShadowRuntime == nullptr ||
|
||||
|
||||
@@ -128,7 +128,13 @@ bool CompareVisibleGaussianSplats(
|
||||
|
||||
void BuildRendererLists(RenderSceneData& sceneData) {
|
||||
sceneData.cullingResults.Clear();
|
||||
sceneData.cullingResults.rendererLists.reserve(5u);
|
||||
sceneData.cullingResults.rendererLists.reserve(
|
||||
#if XCENGINE_ENABLE_RENDERING_EDITOR_SUPPORT
|
||||
5u
|
||||
#else
|
||||
4u
|
||||
#endif
|
||||
);
|
||||
sceneData.cullingResults.rendererLists.push_back(
|
||||
BuildRendererList(RendererListType::AllVisible, sceneData.visibleItems));
|
||||
sceneData.cullingResults.rendererLists.push_back(
|
||||
@@ -137,8 +143,10 @@ void BuildRendererLists(RenderSceneData& sceneData) {
|
||||
BuildRendererList(RendererListType::Transparent, sceneData.visibleItems));
|
||||
sceneData.cullingResults.rendererLists.push_back(
|
||||
BuildRendererList(RendererListType::ShadowCaster, sceneData.visibleItems));
|
||||
#if XCENGINE_ENABLE_RENDERING_EDITOR_SUPPORT
|
||||
sceneData.cullingResults.rendererLists.push_back(
|
||||
BuildRendererList(RendererListType::ObjectId, sceneData.visibleItems));
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
#include "Components/TransformComponent.h"
|
||||
#include "Components/VolumeRendererComponent.h"
|
||||
#include "Rendering/Materials/RenderMaterialResolve.h"
|
||||
#if XCENGINE_ENABLE_RENDERING_EDITOR_SUPPORT
|
||||
#include "Rendering/Picking/RenderObjectIdRegistry.h"
|
||||
#endif
|
||||
#include "Scene/Scene.h"
|
||||
|
||||
#include <unordered_set>
|
||||
@@ -19,9 +22,13 @@ namespace Rendering {
|
||||
namespace {
|
||||
|
||||
RenderObjectId BuildRenderObjectIdOrInvalid(const Components::GameObject& gameObject) {
|
||||
RenderObjectId renderObjectId = kInvalidRenderObjectId;
|
||||
TryConvertRuntimeObjectIdToRenderObjectId(gameObject.GetID(), renderObjectId);
|
||||
return renderObjectId;
|
||||
#if XCENGINE_ENABLE_RENDERING_EDITOR_SUPPORT
|
||||
return RenderObjectIdRegistry::Get().GetOrAllocateRenderObjectId(
|
||||
gameObject.GetID());
|
||||
#else
|
||||
(void)gameObject;
|
||||
return kInvalidRenderObjectId;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -87,6 +87,8 @@ RenderGraphTextureHandle RenderGraphBuilder::ImportTexture(
|
||||
resource.desc = desc;
|
||||
resource.kind = RenderGraphTextureKind::Imported;
|
||||
resource.importedView = importedView;
|
||||
resource.importedTexture =
|
||||
importedView != nullptr ? importedView->GetTextureResource() : nullptr;
|
||||
resource.importedOptions = importedOptions;
|
||||
m_graph.m_textures.push_back(resource);
|
||||
|
||||
|
||||
@@ -115,9 +115,9 @@ bool RenderGraphCompiler::Compile(
|
||||
|
||||
if (texture.kind == RenderGraphTextureKind::Imported &&
|
||||
texture.importedOptions.graphOwnsTransitions &&
|
||||
texture.importedView == nullptr) {
|
||||
texture.importedTexture == nullptr) {
|
||||
WriteError(
|
||||
Containers::String("RenderGraph imported texture requires a valid view when graph owns transitions: ") +
|
||||
Containers::String("RenderGraph imported texture requires a texture-backed view when graph owns transitions: ") +
|
||||
texture.name,
|
||||
outErrorMessage);
|
||||
return false;
|
||||
@@ -237,6 +237,7 @@ bool RenderGraphCompiler::Compile(
|
||||
compiledTexture.desc = graph.m_textures[textureIndex].desc;
|
||||
compiledTexture.kind = graph.m_textures[textureIndex].kind;
|
||||
compiledTexture.importedView = graph.m_textures[textureIndex].importedView;
|
||||
compiledTexture.importedTexture = graph.m_textures[textureIndex].importedTexture;
|
||||
compiledTexture.importedOptions = graph.m_textures[textureIndex].importedOptions;
|
||||
outCompiledGraph.m_textures.push_back(std::move(compiledTexture));
|
||||
|
||||
|
||||
@@ -40,6 +40,26 @@ RenderGraphTextureViewType ResolveBarrierViewType(RHI::ResourceStates state) {
|
||||
: RenderGraphTextureViewType::ShaderResource;
|
||||
}
|
||||
|
||||
RHI::ResourceViewType ResolveRequestedResourceViewType(
|
||||
RenderGraphTextureViewType viewType) {
|
||||
switch (viewType) {
|
||||
case RenderGraphTextureViewType::RenderTarget:
|
||||
return RHI::ResourceViewType::RenderTarget;
|
||||
case RenderGraphTextureViewType::DepthStencil:
|
||||
return RHI::ResourceViewType::DepthStencil;
|
||||
case RenderGraphTextureViewType::UnorderedAccess:
|
||||
return RHI::ResourceViewType::UnorderedAccess;
|
||||
case RenderGraphTextureViewType::ShaderResource:
|
||||
default:
|
||||
return RHI::ResourceViewType::ShaderResource;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsTextureViewDimension(RHI::ResourceViewDimension dimension) {
|
||||
return dimension != RHI::ResourceViewDimension::Unknown &&
|
||||
!RHI::IsBufferResourceViewDimension(dimension);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class RenderGraphRuntimeResources {
|
||||
@@ -62,7 +82,10 @@ public:
|
||||
for (size_t textureIndex = 0u; textureIndex < m_graph.m_textures.size(); ++textureIndex) {
|
||||
const CompiledRenderGraph::CompiledTexture& texture = m_graph.m_textures[textureIndex];
|
||||
const RenderGraphTextureLifetime& lifetime = m_graph.m_textureLifetimes[textureIndex];
|
||||
TextureAllocation& allocation = m_textureAllocations[textureIndex];
|
||||
if (texture.kind == RenderGraphTextureKind::Imported) {
|
||||
allocation.texture = texture.importedTexture;
|
||||
allocation.primaryImportedView = texture.importedView;
|
||||
m_textureStates[textureIndex] = texture.importedOptions.initialState;
|
||||
}
|
||||
|
||||
@@ -105,29 +128,23 @@ public:
|
||||
|
||||
RHI::RHIResourceView* ResolveTextureView(
|
||||
RenderGraphTextureHandle handle,
|
||||
RenderGraphTextureViewType viewType) const {
|
||||
RenderGraphTextureViewType viewType,
|
||||
const RenderContext& renderContext) {
|
||||
if (!handle.IsValid() || handle.index >= m_graph.m_textures.size()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const CompiledRenderGraph::CompiledTexture& texture = m_graph.m_textures[handle.index];
|
||||
TextureAllocation& allocation = m_textureAllocations[handle.index];
|
||||
if (texture.kind == RenderGraphTextureKind::Imported) {
|
||||
return texture.importedView;
|
||||
return ResolveImportedTextureView(
|
||||
texture,
|
||||
allocation,
|
||||
viewType,
|
||||
renderContext);
|
||||
}
|
||||
|
||||
const TextureAllocation& allocation = m_textureAllocations[handle.index];
|
||||
switch (viewType) {
|
||||
case RenderGraphTextureViewType::RenderTarget:
|
||||
return allocation.renderTargetView;
|
||||
case RenderGraphTextureViewType::DepthStencil:
|
||||
return allocation.depthStencilView;
|
||||
case RenderGraphTextureViewType::ShaderResource:
|
||||
return allocation.shaderResourceView;
|
||||
case RenderGraphTextureViewType::UnorderedAccess:
|
||||
return allocation.unorderedAccessView;
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
return ResolveCachedView(allocation, viewType);
|
||||
}
|
||||
|
||||
bool TryGetTextureDesc(
|
||||
@@ -158,7 +175,6 @@ public:
|
||||
const CompiledRenderGraph::CompiledTexture& texture = m_graph.m_textures[textureIndex];
|
||||
if (texture.kind != RenderGraphTextureKind::Imported ||
|
||||
!texture.importedOptions.graphOwnsTransitions ||
|
||||
texture.importedView == nullptr ||
|
||||
textureIndex >= m_graph.m_textureLifetimes.size() ||
|
||||
!m_graph.m_textureLifetimes[textureIndex].used ||
|
||||
!IsGraphManagedTransientState(texture.importedOptions.finalState)) {
|
||||
@@ -208,8 +224,197 @@ private:
|
||||
RHI::RHIResourceView* depthStencilView = nullptr;
|
||||
RHI::RHIResourceView* shaderResourceView = nullptr;
|
||||
RHI::RHIResourceView* unorderedAccessView = nullptr;
|
||||
RHI::RHIResourceView* primaryImportedView = nullptr;
|
||||
bool ownsTexture = false;
|
||||
};
|
||||
|
||||
static RHI::RHIResourceView* ResolveCachedView(
|
||||
const TextureAllocation& allocation,
|
||||
RenderGraphTextureViewType viewType) {
|
||||
switch (viewType) {
|
||||
case RenderGraphTextureViewType::RenderTarget:
|
||||
return allocation.renderTargetView;
|
||||
case RenderGraphTextureViewType::DepthStencil:
|
||||
return allocation.depthStencilView;
|
||||
case RenderGraphTextureViewType::ShaderResource:
|
||||
return allocation.shaderResourceView;
|
||||
case RenderGraphTextureViewType::UnorderedAccess:
|
||||
return allocation.unorderedAccessView;
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
static RHI::RHIResourceView*& ResolveCachedViewSlot(
|
||||
TextureAllocation& allocation,
|
||||
RenderGraphTextureViewType viewType) {
|
||||
switch (viewType) {
|
||||
case RenderGraphTextureViewType::RenderTarget:
|
||||
return allocation.renderTargetView;
|
||||
case RenderGraphTextureViewType::DepthStencil:
|
||||
return allocation.depthStencilView;
|
||||
case RenderGraphTextureViewType::ShaderResource:
|
||||
return allocation.shaderResourceView;
|
||||
case RenderGraphTextureViewType::UnorderedAccess:
|
||||
default:
|
||||
return allocation.unorderedAccessView;
|
||||
}
|
||||
}
|
||||
|
||||
static RHI::ResourceViewDimension ResolveDefaultImportedViewDimension(
|
||||
const CompiledRenderGraph::CompiledTexture& texture) {
|
||||
const RHI::TextureType textureType =
|
||||
static_cast<RHI::TextureType>(texture.desc.textureType);
|
||||
const bool isMultisampled = texture.desc.sampleCount > 1u;
|
||||
|
||||
switch (textureType) {
|
||||
case RHI::TextureType::Texture1D:
|
||||
return RHI::ResourceViewDimension::Texture1D;
|
||||
case RHI::TextureType::Texture2DArray:
|
||||
return isMultisampled
|
||||
? RHI::ResourceViewDimension::Texture2DMSArray
|
||||
: RHI::ResourceViewDimension::Texture2DArray;
|
||||
case RHI::TextureType::Texture3D:
|
||||
return RHI::ResourceViewDimension::Texture3D;
|
||||
case RHI::TextureType::TextureCube:
|
||||
return RHI::ResourceViewDimension::TextureCube;
|
||||
case RHI::TextureType::TextureCubeArray:
|
||||
return RHI::ResourceViewDimension::TextureCubeArray;
|
||||
case RHI::TextureType::Texture2D:
|
||||
default:
|
||||
return isMultisampled
|
||||
? RHI::ResourceViewDimension::Texture2DMS
|
||||
: RHI::ResourceViewDimension::Texture2D;
|
||||
}
|
||||
}
|
||||
|
||||
static RHI::Format ResolveImportedViewFormat(
|
||||
const CompiledRenderGraph::CompiledTexture& texture,
|
||||
const TextureAllocation& allocation) {
|
||||
if (allocation.primaryImportedView != nullptr &&
|
||||
allocation.primaryImportedView->GetFormat() != RHI::Format::Unknown) {
|
||||
return allocation.primaryImportedView->GetFormat();
|
||||
}
|
||||
|
||||
if (allocation.texture != nullptr &&
|
||||
allocation.texture->GetFormat() != RHI::Format::Unknown) {
|
||||
return allocation.texture->GetFormat();
|
||||
}
|
||||
|
||||
return static_cast<RHI::Format>(texture.desc.format);
|
||||
}
|
||||
|
||||
static RHI::ResourceViewDimension ResolveImportedViewDimension(
|
||||
const CompiledRenderGraph::CompiledTexture& texture,
|
||||
const TextureAllocation& allocation) {
|
||||
if (allocation.primaryImportedView != nullptr &&
|
||||
IsTextureViewDimension(allocation.primaryImportedView->GetDimension())) {
|
||||
return allocation.primaryImportedView->GetDimension();
|
||||
}
|
||||
|
||||
return ResolveDefaultImportedViewDimension(texture);
|
||||
}
|
||||
|
||||
static bool CanCreateImportedView(
|
||||
const CompiledRenderGraph::CompiledTexture& texture,
|
||||
RenderGraphTextureViewType viewType) {
|
||||
const bool isDepthTexture =
|
||||
IsDepthFormat(static_cast<RHI::Format>(texture.desc.format));
|
||||
switch (viewType) {
|
||||
case RenderGraphTextureViewType::RenderTarget:
|
||||
return !isDepthTexture;
|
||||
case RenderGraphTextureViewType::DepthStencil:
|
||||
return isDepthTexture;
|
||||
case RenderGraphTextureViewType::ShaderResource:
|
||||
return true;
|
||||
case RenderGraphTextureViewType::UnorderedAccess:
|
||||
return !isDepthTexture;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
RHI::RHIResourceView* ResolveImportedTextureView(
|
||||
const CompiledRenderGraph::CompiledTexture& texture,
|
||||
TextureAllocation& allocation,
|
||||
RenderGraphTextureViewType viewType,
|
||||
const RenderContext& renderContext) {
|
||||
if (allocation.primaryImportedView != nullptr &&
|
||||
allocation.primaryImportedView->GetViewType() ==
|
||||
ResolveRequestedResourceViewType(viewType) &&
|
||||
allocation.primaryImportedView->IsValid()) {
|
||||
return allocation.primaryImportedView;
|
||||
}
|
||||
|
||||
RHI::RHIResourceView*& cachedView =
|
||||
ResolveCachedViewSlot(allocation, viewType);
|
||||
if (cachedView != nullptr) {
|
||||
return cachedView;
|
||||
}
|
||||
|
||||
if (!CanCreateImportedView(texture, viewType) ||
|
||||
allocation.texture == nullptr ||
|
||||
renderContext.device == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
cachedView = CreateImportedTextureView(
|
||||
texture,
|
||||
allocation,
|
||||
viewType,
|
||||
renderContext);
|
||||
return cachedView;
|
||||
}
|
||||
|
||||
static void DestroyOwnedView(RHI::RHIResourceView*& view) {
|
||||
if (view == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
view->Shutdown();
|
||||
delete view;
|
||||
view = nullptr;
|
||||
}
|
||||
|
||||
RHI::RHIResourceView* CreateImportedTextureView(
|
||||
const CompiledRenderGraph::CompiledTexture& texture,
|
||||
const TextureAllocation& allocation,
|
||||
RenderGraphTextureViewType viewType,
|
||||
const RenderContext& renderContext) {
|
||||
if (renderContext.device == nullptr || allocation.texture == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RHI::ResourceViewDesc viewDesc = {};
|
||||
const RHI::Format viewFormat =
|
||||
ResolveImportedViewFormat(texture, allocation);
|
||||
if (viewFormat != RHI::Format::Unknown) {
|
||||
viewDesc.format = static_cast<Core::uint32>(viewFormat);
|
||||
}
|
||||
viewDesc.dimension = ResolveImportedViewDimension(texture, allocation);
|
||||
|
||||
switch (viewType) {
|
||||
case RenderGraphTextureViewType::RenderTarget:
|
||||
return renderContext.device->CreateRenderTargetView(
|
||||
allocation.texture,
|
||||
viewDesc);
|
||||
case RenderGraphTextureViewType::DepthStencil:
|
||||
return renderContext.device->CreateDepthStencilView(
|
||||
allocation.texture,
|
||||
viewDesc);
|
||||
case RenderGraphTextureViewType::ShaderResource:
|
||||
return renderContext.device->CreateShaderResourceView(
|
||||
allocation.texture,
|
||||
viewDesc);
|
||||
case RenderGraphTextureViewType::UnorderedAccess:
|
||||
return renderContext.device->CreateUnorderedAccessView(
|
||||
allocation.texture,
|
||||
viewDesc);
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool ShouldGraphManageTransitions(RenderGraphTextureHandle handle) const {
|
||||
if (!handle.IsValid() || handle.index >= m_graph.m_textures.size()) {
|
||||
return false;
|
||||
@@ -240,8 +445,21 @@ private:
|
||||
return true;
|
||||
}
|
||||
|
||||
RHI::RHIResourceView* resourceView =
|
||||
ResolveTextureView(handle, ResolveBarrierViewType(targetState));
|
||||
const CompiledRenderGraph::CompiledTexture& texture =
|
||||
m_graph.m_textures[handle.index];
|
||||
TextureAllocation& allocation = m_textureAllocations[handle.index];
|
||||
RHI::RHIResourceView* resourceView = nullptr;
|
||||
if (texture.kind == RenderGraphTextureKind::Imported &&
|
||||
allocation.primaryImportedView != nullptr &&
|
||||
allocation.primaryImportedView->GetTextureResource() != nullptr &&
|
||||
allocation.primaryImportedView->IsValid()) {
|
||||
resourceView = allocation.primaryImportedView;
|
||||
} else {
|
||||
resourceView = ResolveTextureView(
|
||||
handle,
|
||||
ResolveBarrierViewType(targetState),
|
||||
renderContext);
|
||||
}
|
||||
if (resourceView == nullptr) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage =
|
||||
@@ -260,35 +478,19 @@ private:
|
||||
}
|
||||
|
||||
static void DestroyTextureAllocation(TextureAllocation& allocation) {
|
||||
if (allocation.renderTargetView != nullptr) {
|
||||
allocation.renderTargetView->Shutdown();
|
||||
delete allocation.renderTargetView;
|
||||
allocation.renderTargetView = nullptr;
|
||||
}
|
||||
DestroyOwnedView(allocation.renderTargetView);
|
||||
DestroyOwnedView(allocation.depthStencilView);
|
||||
DestroyOwnedView(allocation.shaderResourceView);
|
||||
DestroyOwnedView(allocation.unorderedAccessView);
|
||||
|
||||
if (allocation.depthStencilView != nullptr) {
|
||||
allocation.depthStencilView->Shutdown();
|
||||
delete allocation.depthStencilView;
|
||||
allocation.depthStencilView = nullptr;
|
||||
}
|
||||
|
||||
if (allocation.shaderResourceView != nullptr) {
|
||||
allocation.shaderResourceView->Shutdown();
|
||||
delete allocation.shaderResourceView;
|
||||
allocation.shaderResourceView = nullptr;
|
||||
}
|
||||
|
||||
if (allocation.unorderedAccessView != nullptr) {
|
||||
allocation.unorderedAccessView->Shutdown();
|
||||
delete allocation.unorderedAccessView;
|
||||
allocation.unorderedAccessView = nullptr;
|
||||
}
|
||||
|
||||
if (allocation.texture != nullptr) {
|
||||
if (allocation.ownsTexture && allocation.texture != nullptr) {
|
||||
allocation.texture->Shutdown();
|
||||
delete allocation.texture;
|
||||
allocation.texture = nullptr;
|
||||
}
|
||||
|
||||
allocation.texture = nullptr;
|
||||
allocation.primaryImportedView = nullptr;
|
||||
allocation.ownsTexture = false;
|
||||
}
|
||||
|
||||
bool TextureRequiresUnorderedAccess(Core::uint32 textureIndex) const {
|
||||
@@ -327,6 +529,7 @@ private:
|
||||
DestroyTextureAllocation(allocation);
|
||||
return false;
|
||||
}
|
||||
allocation.ownsTexture = true;
|
||||
|
||||
RHI::ResourceViewDesc viewDesc = {};
|
||||
viewDesc.format = texture.desc.format;
|
||||
@@ -353,22 +556,25 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
if (!isDepthTexture) {
|
||||
allocation.shaderResourceView =
|
||||
renderContext.device->CreateShaderResourceView(allocation.texture, viewDesc);
|
||||
if (allocation.shaderResourceView == nullptr) {
|
||||
allocation.shaderResourceView =
|
||||
renderContext.device->CreateShaderResourceView(
|
||||
allocation.texture,
|
||||
viewDesc);
|
||||
if (allocation.shaderResourceView == nullptr) {
|
||||
DestroyTextureAllocation(allocation);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isDepthTexture &&
|
||||
requiresUnorderedAccess) {
|
||||
allocation.unorderedAccessView =
|
||||
renderContext.device->CreateUnorderedAccessView(
|
||||
allocation.texture,
|
||||
viewDesc);
|
||||
if (allocation.unorderedAccessView == nullptr) {
|
||||
DestroyTextureAllocation(allocation);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (requiresUnorderedAccess) {
|
||||
allocation.unorderedAccessView =
|
||||
renderContext.device->CreateUnorderedAccessView(allocation.texture, viewDesc);
|
||||
if (allocation.unorderedAccessView == nullptr) {
|
||||
DestroyTextureAllocation(allocation);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -389,7 +595,10 @@ RHI::RHIResourceView* RenderGraphExecutionContext::ResolveTextureView(
|
||||
RenderGraphTextureHandle handle,
|
||||
RenderGraphTextureViewType viewType) const {
|
||||
return runtimeResources != nullptr
|
||||
? runtimeResources->ResolveTextureView(handle, viewType)
|
||||
? runtimeResources->ResolveTextureView(
|
||||
handle,
|
||||
viewType,
|
||||
renderContext)
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,15 +5,9 @@
|
||||
|
||||
#include "Rendering/Execution/DirectionalShadowExecutionState.h"
|
||||
#include "Rendering/Pipelines/BuiltinForwardPipeline.h"
|
||||
#include "Rendering/Pipelines/Internal/BuiltinForwardSceneSetup.h"
|
||||
#include "Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.h"
|
||||
#include "Rendering/Pipelines/ScriptableRenderPipelineHost.h"
|
||||
#include "Rendering/Passes/BuiltinDepthOnlyPass.h"
|
||||
#include "Rendering/Passes/BuiltinObjectIdPass.h"
|
||||
#include "Rendering/Passes/BuiltinShadowCasterPass.h"
|
||||
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Rendering {
|
||||
@@ -29,167 +23,6 @@ CreateBuiltinForwardPipelineRendererAsset() {
|
||||
return s_builtinForwardPipelineAsset;
|
||||
}
|
||||
|
||||
std::unique_ptr<RenderPass> CreateBuiltinDepthOnlyStandalonePass() {
|
||||
return std::make_unique<Passes::BuiltinDepthOnlyPass>();
|
||||
}
|
||||
|
||||
std::unique_ptr<RenderPass> CreateBuiltinObjectIdStandalonePass() {
|
||||
return std::make_unique<Passes::BuiltinObjectIdPass>();
|
||||
}
|
||||
|
||||
std::unique_ptr<RenderPass> CreateBuiltinShadowCasterStandalonePass() {
|
||||
return std::make_unique<Passes::BuiltinShadowCasterPass>();
|
||||
}
|
||||
|
||||
using PipelineBackendAssetRegistry =
|
||||
std::unordered_map<std::string, PipelineBackendAssetFactory>;
|
||||
using CameraFrameStandalonePassRegistry =
|
||||
std::unordered_map<std::string, CameraFrameStandalonePassFactory>;
|
||||
using CameraFramePlanPolicyRegistry =
|
||||
std::unordered_map<std::string, CameraFramePlanPolicy>;
|
||||
using DirectionalShadowExecutionPolicyRegistry =
|
||||
std::unordered_map<std::string, DirectionalShadowExecutionPolicy>;
|
||||
|
||||
PipelineBackendAssetRegistry& GetPipelineBackendAssetRegistry() {
|
||||
static PipelineBackendAssetRegistry registry = {};
|
||||
return registry;
|
||||
}
|
||||
|
||||
CameraFrameStandalonePassRegistry& GetCameraFrameStandalonePassRegistry() {
|
||||
static CameraFrameStandalonePassRegistry registry = {};
|
||||
return registry;
|
||||
}
|
||||
|
||||
CameraFramePlanPolicyRegistry& GetCameraFramePlanPolicyRegistry() {
|
||||
static CameraFramePlanPolicyRegistry registry = {};
|
||||
return registry;
|
||||
}
|
||||
|
||||
DirectionalShadowExecutionPolicyRegistry&
|
||||
GetDirectionalShadowExecutionPolicyRegistry() {
|
||||
static DirectionalShadowExecutionPolicyRegistry registry = {};
|
||||
return registry;
|
||||
}
|
||||
|
||||
std::unordered_set<std::string>& GetBuiltinPipelineBackendAssetKeys() {
|
||||
static std::unordered_set<std::string> builtinKeys = {};
|
||||
return builtinKeys;
|
||||
}
|
||||
|
||||
std::unordered_set<std::string>& GetBuiltinCameraFrameStandalonePassKeys() {
|
||||
static std::unordered_set<std::string> builtinKeys = {};
|
||||
return builtinKeys;
|
||||
}
|
||||
|
||||
std::unordered_set<std::string>& GetBuiltinCameraFramePlanPolicyKeys() {
|
||||
static std::unordered_set<std::string> builtinKeys = {};
|
||||
return builtinKeys;
|
||||
}
|
||||
|
||||
std::unordered_set<std::string>&
|
||||
GetBuiltinDirectionalShadowExecutionPolicyKeys() {
|
||||
static std::unordered_set<std::string> builtinKeys = {};
|
||||
return builtinKeys;
|
||||
}
|
||||
|
||||
std::mutex& GetPipelineBackendAssetRegistryMutex() {
|
||||
static std::mutex mutex;
|
||||
return mutex;
|
||||
}
|
||||
|
||||
std::mutex& GetCameraFrameStandalonePassRegistryMutex() {
|
||||
static std::mutex mutex;
|
||||
return mutex;
|
||||
}
|
||||
|
||||
std::mutex& GetCameraFramePlanPolicyRegistryMutex() {
|
||||
static std::mutex mutex;
|
||||
return mutex;
|
||||
}
|
||||
|
||||
std::mutex& GetDirectionalShadowExecutionPolicyRegistryMutex() {
|
||||
static std::mutex mutex;
|
||||
return mutex;
|
||||
}
|
||||
|
||||
void EnsureBuiltinPipelineBackendAssetRegistryInitialized() {
|
||||
static const bool initialized = []() {
|
||||
PipelineBackendAssetRegistry& registry =
|
||||
GetPipelineBackendAssetRegistry();
|
||||
registry.emplace(
|
||||
"BuiltinForward",
|
||||
&CreateBuiltinForwardPipelineRendererAsset);
|
||||
GetBuiltinPipelineBackendAssetKeys().insert("BuiltinForward");
|
||||
return true;
|
||||
}();
|
||||
(void)initialized;
|
||||
}
|
||||
|
||||
void EnsureBuiltinCameraFrameStandalonePassRegistryInitialized() {
|
||||
static const bool initialized = []() {
|
||||
CameraFrameStandalonePassRegistry& registry =
|
||||
GetCameraFrameStandalonePassRegistry();
|
||||
registry.emplace(
|
||||
"BuiltinDepthOnly",
|
||||
&CreateBuiltinDepthOnlyStandalonePass);
|
||||
registry.emplace(
|
||||
"BuiltinObjectId",
|
||||
&CreateBuiltinObjectIdStandalonePass);
|
||||
registry.emplace(
|
||||
"BuiltinShadowCaster",
|
||||
&CreateBuiltinShadowCasterStandalonePass);
|
||||
std::unordered_set<std::string>& builtinKeys =
|
||||
GetBuiltinCameraFrameStandalonePassKeys();
|
||||
builtinKeys.insert("BuiltinDepthOnly");
|
||||
builtinKeys.insert("BuiltinObjectId");
|
||||
builtinKeys.insert("BuiltinShadowCaster");
|
||||
return true;
|
||||
}();
|
||||
(void)initialized;
|
||||
}
|
||||
|
||||
void EnsureBuiltinCameraFramePlanPolicyRegistryInitialized() {
|
||||
static const bool initialized = []() {
|
||||
CameraFramePlanPolicyRegistry& registry =
|
||||
GetCameraFramePlanPolicyRegistry();
|
||||
registry.emplace(
|
||||
"BuiltinDefaultCameraFramePlan",
|
||||
[](CameraFramePlan& plan,
|
||||
const FinalColorSettings& defaultFinalColorSettings) {
|
||||
ApplyDefaultRenderPipelineAssetCameraFramePlanPolicy(
|
||||
plan,
|
||||
defaultFinalColorSettings);
|
||||
});
|
||||
GetBuiltinCameraFramePlanPolicyKeys().insert(
|
||||
"BuiltinDefaultCameraFramePlan");
|
||||
return true;
|
||||
}();
|
||||
(void)initialized;
|
||||
}
|
||||
|
||||
void EnsureBuiltinDirectionalShadowExecutionPolicyRegistryInitialized() {
|
||||
static const bool initialized = []() {
|
||||
DirectionalShadowExecutionPolicyRegistry& registry =
|
||||
GetDirectionalShadowExecutionPolicyRegistry();
|
||||
registry.emplace(
|
||||
"BuiltinDirectionalShadow",
|
||||
[](const CameraFramePlan& plan,
|
||||
const DirectionalShadowSurfaceAllocation& shadowAllocation,
|
||||
DirectionalShadowExecutionState& shadowState) {
|
||||
ApplyDefaultRenderPipelineDirectionalShadowExecutionPolicy(
|
||||
plan,
|
||||
shadowAllocation,
|
||||
shadowState);
|
||||
return shadowState.shadowCasterRequest.IsValid() &&
|
||||
shadowState.shadowData.IsValid();
|
||||
});
|
||||
GetBuiltinDirectionalShadowExecutionPolicyKeys().insert(
|
||||
"BuiltinDirectionalShadow");
|
||||
return true;
|
||||
}();
|
||||
(void)initialized;
|
||||
}
|
||||
|
||||
std::unique_ptr<SceneDrawBackend> TryCreateSceneDrawBackendFromAsset(
|
||||
const std::shared_ptr<const RenderPipelineAsset>& asset) {
|
||||
if (asset == nullptr) {
|
||||
@@ -221,266 +54,12 @@ std::shared_ptr<const RenderPipelineAsset> CreateFallbackRenderPipelineAsset() {
|
||||
return std::make_shared<Pipelines::ScriptableRenderPipelineHostAsset>();
|
||||
}
|
||||
|
||||
bool RegisterPipelineBackendAssetFactory(
|
||||
const std::string& key,
|
||||
PipelineBackendAssetFactory factory) {
|
||||
if (key.empty() || !factory) {
|
||||
return false;
|
||||
}
|
||||
|
||||
EnsureBuiltinPipelineBackendAssetRegistryInitialized();
|
||||
|
||||
std::lock_guard<std::mutex> lock(
|
||||
GetPipelineBackendAssetRegistryMutex());
|
||||
PipelineBackendAssetRegistry& registry =
|
||||
GetPipelineBackendAssetRegistry();
|
||||
if (registry.find(key) != registry.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
registry.emplace(key, std::move(factory));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UnregisterPipelineBackendAssetFactory(const std::string& key) {
|
||||
if (key.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
EnsureBuiltinPipelineBackendAssetRegistryInitialized();
|
||||
|
||||
std::lock_guard<std::mutex> lock(
|
||||
GetPipelineBackendAssetRegistryMutex());
|
||||
if (GetBuiltinPipelineBackendAssetKeys().find(key) !=
|
||||
GetBuiltinPipelineBackendAssetKeys().end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PipelineBackendAssetRegistry& registry =
|
||||
GetPipelineBackendAssetRegistry();
|
||||
return registry.erase(key) != 0u;
|
||||
}
|
||||
|
||||
std::shared_ptr<const RenderPipelineAsset> CreateDefaultPipelineBackendAsset() {
|
||||
static const std::shared_ptr<const RenderPipelineAsset> s_defaultAsset =
|
||||
CreateBuiltinForwardPipelineRendererAsset();
|
||||
return s_defaultAsset;
|
||||
}
|
||||
|
||||
std::shared_ptr<const RenderPipelineAsset> CreatePipelineBackendAssetByKey(
|
||||
const std::string& key) {
|
||||
if (key.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
EnsureBuiltinPipelineBackendAssetRegistryInitialized();
|
||||
|
||||
std::lock_guard<std::mutex> lock(
|
||||
GetPipelineBackendAssetRegistryMutex());
|
||||
const PipelineBackendAssetRegistry& registry =
|
||||
GetPipelineBackendAssetRegistry();
|
||||
const auto it = registry.find(key);
|
||||
if (it == registry.end() || !it->second) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return it->second();
|
||||
}
|
||||
|
||||
bool RegisterCameraFrameStandalonePassFactory(
|
||||
const std::string& key,
|
||||
CameraFrameStandalonePassFactory factory) {
|
||||
if (key.empty() || !factory) {
|
||||
return false;
|
||||
}
|
||||
|
||||
EnsureBuiltinCameraFrameStandalonePassRegistryInitialized();
|
||||
|
||||
std::lock_guard<std::mutex> lock(
|
||||
GetCameraFrameStandalonePassRegistryMutex());
|
||||
CameraFrameStandalonePassRegistry& registry =
|
||||
GetCameraFrameStandalonePassRegistry();
|
||||
if (registry.find(key) != registry.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
registry.emplace(key, std::move(factory));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UnregisterCameraFrameStandalonePassFactory(
|
||||
const std::string& key) {
|
||||
if (key.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
EnsureBuiltinCameraFrameStandalonePassRegistryInitialized();
|
||||
|
||||
std::lock_guard<std::mutex> lock(
|
||||
GetCameraFrameStandalonePassRegistryMutex());
|
||||
if (GetBuiltinCameraFrameStandalonePassKeys().find(key) !=
|
||||
GetBuiltinCameraFrameStandalonePassKeys().end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
CameraFrameStandalonePassRegistry& registry =
|
||||
GetCameraFrameStandalonePassRegistry();
|
||||
return registry.erase(key) != 0u;
|
||||
}
|
||||
|
||||
std::unique_ptr<RenderPass> CreateCameraFrameStandalonePassByKey(
|
||||
const std::string& key) {
|
||||
if (key.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
EnsureBuiltinCameraFrameStandalonePassRegistryInitialized();
|
||||
|
||||
std::lock_guard<std::mutex> lock(
|
||||
GetCameraFrameStandalonePassRegistryMutex());
|
||||
const CameraFrameStandalonePassRegistry& registry =
|
||||
GetCameraFrameStandalonePassRegistry();
|
||||
const auto it = registry.find(key);
|
||||
if (it == registry.end() || !it->second) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return it->second();
|
||||
}
|
||||
|
||||
bool RegisterCameraFramePlanPolicy(
|
||||
const std::string& key,
|
||||
CameraFramePlanPolicy policy) {
|
||||
if (key.empty() || !policy) {
|
||||
return false;
|
||||
}
|
||||
|
||||
EnsureBuiltinCameraFramePlanPolicyRegistryInitialized();
|
||||
|
||||
std::lock_guard<std::mutex> lock(
|
||||
GetCameraFramePlanPolicyRegistryMutex());
|
||||
CameraFramePlanPolicyRegistry& registry =
|
||||
GetCameraFramePlanPolicyRegistry();
|
||||
if (registry.find(key) != registry.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
registry.emplace(key, std::move(policy));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UnregisterCameraFramePlanPolicy(
|
||||
const std::string& key) {
|
||||
if (key.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
EnsureBuiltinCameraFramePlanPolicyRegistryInitialized();
|
||||
|
||||
std::lock_guard<std::mutex> lock(
|
||||
GetCameraFramePlanPolicyRegistryMutex());
|
||||
if (GetBuiltinCameraFramePlanPolicyKeys().find(key) !=
|
||||
GetBuiltinCameraFramePlanPolicyKeys().end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
CameraFramePlanPolicyRegistry& registry =
|
||||
GetCameraFramePlanPolicyRegistry();
|
||||
return registry.erase(key) != 0u;
|
||||
}
|
||||
|
||||
bool ApplyCameraFramePlanPolicyByKey(
|
||||
const std::string& key,
|
||||
CameraFramePlan& plan,
|
||||
const FinalColorSettings& defaultFinalColorSettings) {
|
||||
if (key.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
EnsureBuiltinCameraFramePlanPolicyRegistryInitialized();
|
||||
|
||||
std::lock_guard<std::mutex> lock(
|
||||
GetCameraFramePlanPolicyRegistryMutex());
|
||||
const CameraFramePlanPolicyRegistry& registry =
|
||||
GetCameraFramePlanPolicyRegistry();
|
||||
const auto it = registry.find(key);
|
||||
if (it == registry.end() || !it->second) {
|
||||
return false;
|
||||
}
|
||||
|
||||
it->second(
|
||||
plan,
|
||||
defaultFinalColorSettings);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RegisterDirectionalShadowExecutionPolicy(
|
||||
const std::string& key,
|
||||
DirectionalShadowExecutionPolicy policy) {
|
||||
if (key.empty() || !policy) {
|
||||
return false;
|
||||
}
|
||||
|
||||
EnsureBuiltinDirectionalShadowExecutionPolicyRegistryInitialized();
|
||||
|
||||
std::lock_guard<std::mutex> lock(
|
||||
GetDirectionalShadowExecutionPolicyRegistryMutex());
|
||||
DirectionalShadowExecutionPolicyRegistry& registry =
|
||||
GetDirectionalShadowExecutionPolicyRegistry();
|
||||
if (registry.find(key) != registry.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
registry.emplace(key, std::move(policy));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UnregisterDirectionalShadowExecutionPolicy(
|
||||
const std::string& key) {
|
||||
if (key.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
EnsureBuiltinDirectionalShadowExecutionPolicyRegistryInitialized();
|
||||
|
||||
std::lock_guard<std::mutex> lock(
|
||||
GetDirectionalShadowExecutionPolicyRegistryMutex());
|
||||
if (GetBuiltinDirectionalShadowExecutionPolicyKeys().find(key) !=
|
||||
GetBuiltinDirectionalShadowExecutionPolicyKeys().end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DirectionalShadowExecutionPolicyRegistry& registry =
|
||||
GetDirectionalShadowExecutionPolicyRegistry();
|
||||
return registry.erase(key) != 0u;
|
||||
}
|
||||
|
||||
bool ConfigureDirectionalShadowExecutionStateByKey(
|
||||
const std::string& key,
|
||||
const CameraFramePlan& plan,
|
||||
const DirectionalShadowSurfaceAllocation& shadowAllocation,
|
||||
DirectionalShadowExecutionState& shadowState) {
|
||||
if (key.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
EnsureBuiltinDirectionalShadowExecutionPolicyRegistryInitialized();
|
||||
|
||||
std::lock_guard<std::mutex> lock(
|
||||
GetDirectionalShadowExecutionPolicyRegistryMutex());
|
||||
const DirectionalShadowExecutionPolicyRegistry& registry =
|
||||
GetDirectionalShadowExecutionPolicyRegistry();
|
||||
const auto it = registry.find(key);
|
||||
if (it == registry.end() || !it->second) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return it->second(
|
||||
plan,
|
||||
shadowAllocation,
|
||||
shadowState);
|
||||
}
|
||||
|
||||
std::shared_ptr<const RenderPipelineAsset> ResolveRenderPipelineAssetOrDefault(
|
||||
std::shared_ptr<const RenderPipelineAsset> preferredAsset) {
|
||||
if (preferredAsset != nullptr) {
|
||||
@@ -567,7 +146,14 @@ std::unique_ptr<SceneDrawBackend> CreateSceneDrawBackendFromAsset(
|
||||
}
|
||||
|
||||
std::unique_ptr<SceneDrawBackend> CreateDefaultSceneDrawBackend() {
|
||||
return std::make_unique<Pipelines::BuiltinForwardPipeline>();
|
||||
if (std::unique_ptr<SceneDrawBackend> sceneDrawBackend =
|
||||
TryCreateSceneDrawBackendFromAsset(
|
||||
CreateDefaultPipelineBackendAsset());
|
||||
sceneDrawBackend != nullptr) {
|
||||
return sceneDrawBackend;
|
||||
}
|
||||
|
||||
return Pipelines::Internal::CreateConfiguredBuiltinForwardPipeline();
|
||||
}
|
||||
|
||||
} // namespace Internal
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Rendering {
|
||||
@@ -10,63 +8,12 @@ namespace Rendering {
|
||||
class SceneDrawBackend;
|
||||
class RenderPipeline;
|
||||
class RenderPipelineAsset;
|
||||
class RenderPass;
|
||||
struct CameraFramePlan;
|
||||
struct FinalColorSettings;
|
||||
struct DirectionalShadowExecutionState;
|
||||
struct DirectionalShadowSurfaceAllocation;
|
||||
|
||||
namespace Internal {
|
||||
|
||||
using PipelineBackendAssetFactory =
|
||||
std::function<std::shared_ptr<const RenderPipelineAsset>()>;
|
||||
using CameraFrameStandalonePassFactory =
|
||||
std::function<std::unique_ptr<RenderPass>()>;
|
||||
using CameraFramePlanPolicy =
|
||||
std::function<void(
|
||||
CameraFramePlan&,
|
||||
const FinalColorSettings&)>;
|
||||
using DirectionalShadowExecutionPolicy =
|
||||
std::function<bool(
|
||||
const CameraFramePlan&,
|
||||
const DirectionalShadowSurfaceAllocation&,
|
||||
DirectionalShadowExecutionState&)>;
|
||||
|
||||
std::shared_ptr<const RenderPipelineAsset> CreateConfiguredRenderPipelineAsset();
|
||||
std::shared_ptr<const RenderPipelineAsset> CreateFallbackRenderPipelineAsset();
|
||||
bool RegisterPipelineBackendAssetFactory(
|
||||
const std::string& key,
|
||||
PipelineBackendAssetFactory factory);
|
||||
bool UnregisterPipelineBackendAssetFactory(const std::string& key);
|
||||
std::shared_ptr<const RenderPipelineAsset> CreateDefaultPipelineBackendAsset();
|
||||
std::shared_ptr<const RenderPipelineAsset> CreatePipelineBackendAssetByKey(
|
||||
const std::string& key);
|
||||
bool RegisterCameraFrameStandalonePassFactory(
|
||||
const std::string& key,
|
||||
CameraFrameStandalonePassFactory factory);
|
||||
bool UnregisterCameraFrameStandalonePassFactory(
|
||||
const std::string& key);
|
||||
std::unique_ptr<RenderPass> CreateCameraFrameStandalonePassByKey(
|
||||
const std::string& key);
|
||||
bool RegisterCameraFramePlanPolicy(
|
||||
const std::string& key,
|
||||
CameraFramePlanPolicy policy);
|
||||
bool UnregisterCameraFramePlanPolicy(
|
||||
const std::string& key);
|
||||
bool ApplyCameraFramePlanPolicyByKey(
|
||||
const std::string& key,
|
||||
CameraFramePlan& plan,
|
||||
const FinalColorSettings& defaultFinalColorSettings);
|
||||
bool RegisterDirectionalShadowExecutionPolicy(
|
||||
const std::string& key,
|
||||
DirectionalShadowExecutionPolicy policy);
|
||||
bool UnregisterDirectionalShadowExecutionPolicy(
|
||||
const std::string& key);
|
||||
bool ConfigureDirectionalShadowExecutionStateByKey(
|
||||
const std::string& key,
|
||||
const CameraFramePlan& plan,
|
||||
const DirectionalShadowSurfaceAllocation& shadowAllocation,
|
||||
DirectionalShadowExecutionState& shadowState);
|
||||
|
||||
std::shared_ptr<const RenderPipelineAsset> ResolveRenderPipelineAssetOrDefault(
|
||||
std::shared_ptr<const RenderPipelineAsset> preferredAsset);
|
||||
|
||||
@@ -2,10 +2,11 @@
|
||||
|
||||
#include "Core/Asset/ResourceManager.h"
|
||||
#include "Debug/Logger.h"
|
||||
#include <XCEngine/Rendering/RenderPassGraphContract.h>
|
||||
#include "Rendering/Builtin/BuiltinPassLayoutUtils.h"
|
||||
#include "Rendering/Internal/RenderSurfacePipelineUtils.h"
|
||||
#include "Rendering/Internal/ShaderVariantUtils.h"
|
||||
#include "Rendering/RenderSurface.h"
|
||||
#include <XCEngine/Rendering/RenderPassGraphContract.h>
|
||||
#include "Resources/BuiltinResources.h"
|
||||
#include "RHI/RHICommandList.h"
|
||||
#include "RHI/RHIDescriptorPool.h"
|
||||
@@ -16,7 +17,9 @@
|
||||
#include "RHI/RHIResourceView.h"
|
||||
#include "RHI/RHISampler.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Rendering {
|
||||
@@ -45,7 +48,8 @@ const Resources::ShaderPass* FindCompatiblePass(
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const Resources::ShaderPass* colorScalePass = shader.FindPass("ColorScale");
|
||||
const Resources::ShaderPass* colorScalePass =
|
||||
shader.FindPass("ColorScale");
|
||||
if (colorScalePass != nullptr &&
|
||||
::XCEngine::Rendering::Internal::ShaderPassHasGraphicsVariants(
|
||||
shader,
|
||||
@@ -55,13 +59,87 @@ const Resources::ShaderPass* FindCompatiblePass(
|
||||
}
|
||||
|
||||
if (shader.GetPassCount() > 0 &&
|
||||
::XCEngine::Rendering::Internal::ShaderPassHasGraphicsVariants(shader, shader.GetPasses()[0].name, backend)) {
|
||||
::XCEngine::Rendering::Internal::ShaderPassHasGraphicsVariants(
|
||||
shader,
|
||||
shader.GetPasses()[0].name,
|
||||
backend)) {
|
||||
return &shader.GetPasses()[0];
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool UsesContiguousDescriptorSets(
|
||||
const BuiltinPassResourceBindingPlan& bindingPlan) {
|
||||
if (bindingPlan.bindings.Empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<bool> usedSets(
|
||||
static_cast<size_t>(bindingPlan.maxSetIndex) + 1u,
|
||||
false);
|
||||
Core::uint32 minSet = UINT32_MAX;
|
||||
Core::uint32 maxSet = 0u;
|
||||
for (const BuiltinPassResourceBindingDesc& binding :
|
||||
bindingPlan.bindings) {
|
||||
usedSets[binding.location.set] = true;
|
||||
minSet = std::min(minSet, binding.location.set);
|
||||
maxSet = std::max(maxSet, binding.location.set);
|
||||
}
|
||||
|
||||
for (Core::uint32 setIndex = minSet;
|
||||
setIndex <= maxSet;
|
||||
++setIndex) {
|
||||
if (!usedSets[setIndex]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsSupportedFullscreenBindingPlan(
|
||||
const BuiltinPassResourceBindingPlan& bindingPlan) {
|
||||
if (!UsesContiguousDescriptorSets(bindingPlan)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const BuiltinPassResourceBindingDesc& binding :
|
||||
bindingPlan.bindings) {
|
||||
switch (binding.semantic) {
|
||||
case BuiltinPassResourceSemantic::PassConstants:
|
||||
case BuiltinPassResourceSemantic::SourceColorTexture:
|
||||
case BuiltinPassResourceSemantic::MaterialTexture:
|
||||
case BuiltinPassResourceSemantic::LinearClampSampler:
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const RenderPassContext::TextureBindingView* FindTextureBindingView(
|
||||
const RenderPassContext& context,
|
||||
const BuiltinPassResourceBindingDesc& bindingDesc) {
|
||||
auto matchesName =
|
||||
[&bindingDesc](
|
||||
const RenderPassContext::TextureBindingView& bindingView) {
|
||||
return bindingView.resourceName ==
|
||||
bindingDesc.name;
|
||||
};
|
||||
|
||||
const auto it =
|
||||
std::find_if(
|
||||
context.textureBindings.begin(),
|
||||
context.textureBindings.end(),
|
||||
matchesName);
|
||||
return it != context.textureBindings.end()
|
||||
? &(*it)
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
RHI::GraphicsPipelineDesc CreatePipelineDesc(
|
||||
RHI::RHIType backendType,
|
||||
RHI::RHIPipelineLayout* pipelineLayout,
|
||||
@@ -70,34 +148,54 @@ RHI::GraphicsPipelineDesc CreatePipelineDesc(
|
||||
const RenderSurface& surface) {
|
||||
RHI::GraphicsPipelineDesc pipelineDesc = {};
|
||||
pipelineDesc.pipelineLayout = pipelineLayout;
|
||||
pipelineDesc.topologyType = static_cast<uint32_t>(RHI::PrimitiveTopologyType::Triangle);
|
||||
::XCEngine::Rendering::Internal::ApplySingleColorAttachmentPropertiesToGraphicsPipelineDesc(
|
||||
surface,
|
||||
pipelineDesc);
|
||||
pipelineDesc.depthStencilFormat = static_cast<uint32_t>(RHI::Format::Unknown);
|
||||
pipelineDesc.topologyType = static_cast<uint32_t>(
|
||||
RHI::PrimitiveTopologyType::Triangle);
|
||||
::XCEngine::Rendering::Internal::
|
||||
ApplySingleColorAttachmentPropertiesToGraphicsPipelineDesc(
|
||||
surface,
|
||||
pipelineDesc);
|
||||
pipelineDesc.depthStencilFormat = static_cast<uint32_t>(
|
||||
RHI::Format::Unknown);
|
||||
|
||||
pipelineDesc.rasterizerState.fillMode = static_cast<uint32_t>(RHI::FillMode::Solid);
|
||||
pipelineDesc.rasterizerState.cullMode = static_cast<uint32_t>(RHI::CullMode::None);
|
||||
pipelineDesc.rasterizerState.frontFace = static_cast<uint32_t>(RHI::FrontFace::CounterClockwise);
|
||||
pipelineDesc.rasterizerState.fillMode = static_cast<uint32_t>(
|
||||
RHI::FillMode::Solid);
|
||||
pipelineDesc.rasterizerState.cullMode = static_cast<uint32_t>(
|
||||
RHI::CullMode::None);
|
||||
pipelineDesc.rasterizerState.frontFace =
|
||||
static_cast<uint32_t>(RHI::FrontFace::CounterClockwise);
|
||||
pipelineDesc.rasterizerState.depthClipEnable = true;
|
||||
|
||||
pipelineDesc.blendState.blendEnable = false;
|
||||
pipelineDesc.blendState.srcBlend = static_cast<uint32_t>(RHI::BlendFactor::One);
|
||||
pipelineDesc.blendState.dstBlend = static_cast<uint32_t>(RHI::BlendFactor::Zero);
|
||||
pipelineDesc.blendState.srcBlendAlpha = static_cast<uint32_t>(RHI::BlendFactor::One);
|
||||
pipelineDesc.blendState.dstBlendAlpha = static_cast<uint32_t>(RHI::BlendFactor::Zero);
|
||||
pipelineDesc.blendState.blendOp = static_cast<uint32_t>(RHI::BlendOp::Add);
|
||||
pipelineDesc.blendState.blendOpAlpha = static_cast<uint32_t>(RHI::BlendOp::Add);
|
||||
pipelineDesc.blendState.colorWriteMask = static_cast<uint8_t>(RHI::ColorWriteMask::All);
|
||||
pipelineDesc.blendState.srcBlend = static_cast<uint32_t>(
|
||||
RHI::BlendFactor::One);
|
||||
pipelineDesc.blendState.dstBlend = static_cast<uint32_t>(
|
||||
RHI::BlendFactor::Zero);
|
||||
pipelineDesc.blendState.srcBlendAlpha =
|
||||
static_cast<uint32_t>(RHI::BlendFactor::One);
|
||||
pipelineDesc.blendState.dstBlendAlpha =
|
||||
static_cast<uint32_t>(RHI::BlendFactor::Zero);
|
||||
pipelineDesc.blendState.blendOp = static_cast<uint32_t>(
|
||||
RHI::BlendOp::Add);
|
||||
pipelineDesc.blendState.blendOpAlpha = static_cast<uint32_t>(
|
||||
RHI::BlendOp::Add);
|
||||
pipelineDesc.blendState.colorWriteMask =
|
||||
static_cast<uint8_t>(RHI::ColorWriteMask::All);
|
||||
|
||||
pipelineDesc.depthStencilState.depthTestEnable = false;
|
||||
pipelineDesc.depthStencilState.depthWriteEnable = false;
|
||||
pipelineDesc.depthStencilState.depthFunc = static_cast<uint32_t>(RHI::ComparisonFunc::Always);
|
||||
pipelineDesc.depthStencilState.depthFunc = static_cast<uint32_t>(
|
||||
RHI::ComparisonFunc::Always);
|
||||
|
||||
const Resources::ShaderPass* shaderPass = shader.FindPass(passName);
|
||||
const Resources::ShaderBackend backend = ::XCEngine::Rendering::Internal::ToShaderBackend(backendType);
|
||||
const Resources::ShaderPass* shaderPass =
|
||||
shader.FindPass(passName);
|
||||
const Resources::ShaderBackend backend =
|
||||
::XCEngine::Rendering::Internal::ToShaderBackend(
|
||||
backendType);
|
||||
if (const Resources::ShaderStageVariant* vertexVariant =
|
||||
shader.FindVariant(passName, Resources::ShaderType::Vertex, backend)) {
|
||||
shader.FindVariant(
|
||||
passName,
|
||||
Resources::ShaderType::Vertex,
|
||||
backend)) {
|
||||
if (shaderPass != nullptr) {
|
||||
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
|
||||
shader.GetPath(),
|
||||
@@ -108,7 +206,10 @@ RHI::GraphicsPipelineDesc CreatePipelineDesc(
|
||||
}
|
||||
}
|
||||
if (const Resources::ShaderStageVariant* fragmentVariant =
|
||||
shader.FindVariant(passName, Resources::ShaderType::Fragment, backend)) {
|
||||
shader.FindVariant(
|
||||
passName,
|
||||
Resources::ShaderType::Fragment,
|
||||
backend)) {
|
||||
if (shaderPass != nullptr) {
|
||||
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
|
||||
shader.GetPath(),
|
||||
@@ -132,7 +233,8 @@ BuiltinVectorFullscreenPass::BuiltinVectorFullscreenPass(
|
||||
, m_preferredPassName(std::move(preferredPassName))
|
||||
, m_vectorPayload(vectorPayload) {
|
||||
if (m_shaderPath.Empty()) {
|
||||
m_shaderPath = Resources::GetBuiltinColorScalePostProcessShaderPath();
|
||||
m_shaderPath =
|
||||
Resources::GetBuiltinColorScalePostProcessShaderPath();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,44 +257,56 @@ bool BuiltinVectorFullscreenPass::RecordRenderGraph(
|
||||
context);
|
||||
}
|
||||
|
||||
bool BuiltinVectorFullscreenPass::Execute(const RenderPassContext& context) {
|
||||
bool BuiltinVectorFullscreenPass::Execute(
|
||||
const RenderPassContext& context) {
|
||||
if (!context.renderContext.IsValid() ||
|
||||
context.sourceSurface == nullptr ||
|
||||
context.sourceColorView == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (!::XCEngine::Rendering::Internal::HasSingleColorAttachment(*context.sourceSurface)) {
|
||||
!::XCEngine::Rendering::Internal::HasSingleColorAttachment(
|
||||
context.surface)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::vector<RHI::RHIResourceView*>& colorAttachments = context.surface.GetColorAttachments();
|
||||
if (!::XCEngine::Rendering::Internal::HasSingleColorAttachment(context.surface) ||
|
||||
colorAttachments.empty() ||
|
||||
const std::vector<RHI::RHIResourceView*>& colorAttachments =
|
||||
context.surface.GetColorAttachments();
|
||||
if (colorAttachments.empty() ||
|
||||
colorAttachments[0] == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const Math::RectInt renderArea = context.surface.GetRenderArea();
|
||||
if (renderArea.width <= 0 || renderArea.height <= 0) {
|
||||
const Math::RectInt renderArea =
|
||||
context.surface.GetRenderArea();
|
||||
if (renderArea.width <= 0 ||
|
||||
renderArea.height <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!EnsureInitialized(context.renderContext, context.surface)) {
|
||||
if (!EnsureInitialized(
|
||||
context.renderContext,
|
||||
context.surface)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PostProcessConstants constants = {};
|
||||
constants.vectorPayload = m_vectorPayload;
|
||||
m_constantsSet.set->WriteConstant(0, &constants, sizeof(constants));
|
||||
|
||||
if (m_boundSourceColorView != context.sourceColorView) {
|
||||
m_textureSet.set->Update(0, context.sourceColorView);
|
||||
m_boundSourceColorView = context.sourceColorView;
|
||||
if (m_passLayout.sourceColorTexture.IsValid() &&
|
||||
(context.sourceSurface == nullptr ||
|
||||
context.sourceColorView == nullptr)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RHI::RHICommandList* commandList = context.renderContext.commandList;
|
||||
RHI::RHIResourceView* renderTarget = colorAttachments[0];
|
||||
const bool autoTransitionSource = context.sourceSurface->IsAutoTransitionEnabled();
|
||||
if (!UpdateDescriptorSets(context)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RHI::RHICommandList* commandList =
|
||||
context.renderContext.commandList;
|
||||
if (commandList == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RHI::RHIResourceView* renderTarget =
|
||||
colorAttachments[0];
|
||||
const bool autoTransitionSource =
|
||||
m_passLayout.sourceColorTexture.IsValid() &&
|
||||
context.sourceSurface != nullptr &&
|
||||
context.sourceSurface->IsAutoTransitionEnabled();
|
||||
|
||||
if (context.surface.IsAutoTransitionEnabled()) {
|
||||
commandList->TransitionBarrier(
|
||||
@@ -200,13 +314,16 @@ bool BuiltinVectorFullscreenPass::Execute(const RenderPassContext& context) {
|
||||
context.surface.GetColorStateBefore(),
|
||||
RHI::ResourceStates::RenderTarget);
|
||||
}
|
||||
if (autoTransitionSource) {
|
||||
commandList->TransitionBarrier(
|
||||
context.sourceColorView,
|
||||
context.sourceColorState,
|
||||
RHI::ResourceStates::PixelShaderResource);
|
||||
} else if (context.sourceColorState != RHI::ResourceStates::PixelShaderResource) {
|
||||
return false;
|
||||
if (m_passLayout.sourceColorTexture.IsValid()) {
|
||||
if (autoTransitionSource) {
|
||||
commandList->TransitionBarrier(
|
||||
context.sourceColorView,
|
||||
context.sourceColorState,
|
||||
RHI::ResourceStates::PixelShaderResource);
|
||||
} else if (context.sourceColorState !=
|
||||
RHI::ResourceStates::PixelShaderResource) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
commandList->SetRenderTargets(1, &renderTarget, nullptr);
|
||||
@@ -228,15 +345,38 @@ bool BuiltinVectorFullscreenPass::Execute(const RenderPassContext& context) {
|
||||
|
||||
commandList->SetViewport(viewport);
|
||||
commandList->SetScissorRect(scissorRect);
|
||||
commandList->SetPrimitiveTopology(RHI::PrimitiveTopology::TriangleList);
|
||||
commandList->SetPrimitiveTopology(
|
||||
RHI::PrimitiveTopology::TriangleList);
|
||||
commandList->SetPipelineState(m_pipelineState);
|
||||
|
||||
RHI::RHIDescriptorSet* descriptorSets[] = {
|
||||
m_constantsSet.set,
|
||||
m_textureSet.set,
|
||||
m_samplerSet.set
|
||||
};
|
||||
commandList->SetGraphicsDescriptorSets(0, 3, descriptorSets, m_pipelineLayout);
|
||||
if (m_passLayout.descriptorSetCount > 0u) {
|
||||
std::vector<RHI::RHIDescriptorSet*> descriptorSets(
|
||||
m_passLayout.descriptorSetCount,
|
||||
nullptr);
|
||||
for (Core::uint32 descriptorOffset = 0u;
|
||||
descriptorOffset < m_passLayout.descriptorSetCount;
|
||||
++descriptorOffset) {
|
||||
const Core::uint32 setIndex =
|
||||
m_passLayout.firstDescriptorSet +
|
||||
descriptorOffset;
|
||||
if (setIndex >=
|
||||
m_passLayout.descriptorSets.size() ||
|
||||
m_passLayout.descriptorSets[setIndex].set ==
|
||||
nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
descriptorSets[descriptorOffset] =
|
||||
m_passLayout.descriptorSets[setIndex].set;
|
||||
}
|
||||
|
||||
commandList->SetGraphicsDescriptorSets(
|
||||
m_passLayout.firstDescriptorSet,
|
||||
m_passLayout.descriptorSetCount,
|
||||
descriptorSets.data(),
|
||||
m_passLayout.pipelineLayout);
|
||||
}
|
||||
|
||||
commandList->Draw(3, 1, 0, 0);
|
||||
commandList->EndRenderPass();
|
||||
|
||||
@@ -246,7 +386,8 @@ bool BuiltinVectorFullscreenPass::Execute(const RenderPassContext& context) {
|
||||
RHI::ResourceStates::RenderTarget,
|
||||
context.surface.GetColorStateAfter());
|
||||
}
|
||||
if (autoTransitionSource) {
|
||||
if (m_passLayout.sourceColorTexture.IsValid() &&
|
||||
autoTransitionSource) {
|
||||
commandList->TransitionBarrier(
|
||||
context.sourceColorView,
|
||||
RHI::ResourceStates::PixelShaderResource,
|
||||
@@ -260,7 +401,8 @@ void BuiltinVectorFullscreenPass::Shutdown() {
|
||||
DestroyResources();
|
||||
}
|
||||
|
||||
void BuiltinVectorFullscreenPass::SetVectorPayload(const Math::Vector4& vectorPayload) {
|
||||
void BuiltinVectorFullscreenPass::SetVectorPayload(
|
||||
const Math::Vector4& vectorPayload) {
|
||||
m_vectorPayload = vectorPayload;
|
||||
}
|
||||
|
||||
@@ -268,7 +410,8 @@ const Math::Vector4& BuiltinVectorFullscreenPass::GetVectorPayload() const {
|
||||
return m_vectorPayload;
|
||||
}
|
||||
|
||||
void BuiltinVectorFullscreenPass::SetShaderPath(const Containers::String& shaderPath) {
|
||||
void BuiltinVectorFullscreenPass::SetShaderPath(
|
||||
const Containers::String& shaderPath) {
|
||||
if (m_shaderPath == shaderPath) {
|
||||
return;
|
||||
}
|
||||
@@ -291,7 +434,8 @@ void BuiltinVectorFullscreenPass::SetPreferredPassName(
|
||||
m_preferredPassName = preferredPassName;
|
||||
}
|
||||
|
||||
const Containers::String& BuiltinVectorFullscreenPass::GetPreferredPassName() const {
|
||||
const Containers::String&
|
||||
BuiltinVectorFullscreenPass::GetPreferredPassName() const {
|
||||
return m_preferredPassName;
|
||||
}
|
||||
|
||||
@@ -299,22 +443,25 @@ bool BuiltinVectorFullscreenPass::EnsureInitialized(
|
||||
const RenderContext& renderContext,
|
||||
const RenderSurface& surface) {
|
||||
const RHI::Format renderTargetFormat =
|
||||
::XCEngine::Rendering::Internal::ResolveSurfaceColorFormat(surface, 0u);
|
||||
::XCEngine::Rendering::Internal::ResolveSurfaceColorFormat(
|
||||
surface,
|
||||
0u);
|
||||
const uint32_t renderTargetSampleCount =
|
||||
::XCEngine::Rendering::Internal::ResolveSurfaceSampleCount(surface);
|
||||
::XCEngine::Rendering::Internal::ResolveSurfaceSampleCount(
|
||||
surface);
|
||||
const uint32_t renderTargetSampleQuality =
|
||||
::XCEngine::Rendering::Internal::ResolveSurfaceSampleQuality(surface);
|
||||
::XCEngine::Rendering::Internal::ResolveSurfaceSampleQuality(
|
||||
surface);
|
||||
if (m_device == renderContext.device &&
|
||||
m_backendType == renderContext.backendType &&
|
||||
m_renderTargetFormat == renderTargetFormat &&
|
||||
m_renderTargetSampleCount == renderTargetSampleCount &&
|
||||
m_renderTargetSampleQuality == renderTargetSampleQuality &&
|
||||
m_pipelineLayout != nullptr &&
|
||||
m_renderTargetSampleCount ==
|
||||
renderTargetSampleCount &&
|
||||
m_renderTargetSampleQuality ==
|
||||
renderTargetSampleQuality &&
|
||||
m_passLayout.pipelineLayout != nullptr &&
|
||||
m_pipelineState != nullptr &&
|
||||
m_sampler != nullptr &&
|
||||
m_constantsSet.set != nullptr &&
|
||||
m_textureSet.set != nullptr &&
|
||||
m_samplerSet.set != nullptr) {
|
||||
m_sampler != nullptr) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -330,38 +477,98 @@ bool BuiltinVectorFullscreenPass::CreateResources(
|
||||
}
|
||||
|
||||
RHI::Format renderTargetFormat = RHI::Format::Unknown;
|
||||
if (!::XCEngine::Rendering::Internal::TryResolveSingleColorAttachmentFormat(surface, renderTargetFormat)) {
|
||||
if (!::XCEngine::Rendering::Internal::
|
||||
TryResolveSingleColorAttachmentFormat(
|
||||
surface,
|
||||
renderTargetFormat)) {
|
||||
Debug::Logger::Get().Error(
|
||||
Debug::LogCategory::Rendering,
|
||||
"BuiltinVectorFullscreenPass requires exactly one valid destination color attachment");
|
||||
"BuiltinVectorFullscreenPass requires exactly one "
|
||||
"valid destination color attachment");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_shaderPath.Empty()) {
|
||||
Debug::Logger::Get().Error(
|
||||
Debug::LogCategory::Rendering,
|
||||
"BuiltinVectorFullscreenPass requires a shader path before resource creation");
|
||||
"BuiltinVectorFullscreenPass requires a shader path "
|
||||
"before resource creation");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_shader = Resources::ResourceManager::Get().Load<Resources::Shader>(m_shaderPath);
|
||||
m_shader =
|
||||
Resources::ResourceManager::Get().Load<Resources::Shader>(
|
||||
m_shaderPath);
|
||||
if (!m_shader.IsValid()) {
|
||||
Debug::Logger::Get().Error(
|
||||
Debug::LogCategory::Rendering,
|
||||
"BuiltinVectorFullscreenPass failed to load configured fullscreen shader resource");
|
||||
"BuiltinVectorFullscreenPass failed to load "
|
||||
"configured fullscreen shader resource");
|
||||
DestroyResources();
|
||||
return false;
|
||||
}
|
||||
|
||||
const Resources::ShaderBackend backend = ::XCEngine::Rendering::Internal::ToShaderBackend(renderContext.backendType);
|
||||
const Resources::ShaderBackend backend =
|
||||
::XCEngine::Rendering::Internal::ToShaderBackend(
|
||||
renderContext.backendType);
|
||||
const Resources::ShaderPass* selectedPass =
|
||||
FindCompatiblePass(*m_shader.Get(), backend, m_preferredPassName);
|
||||
FindCompatiblePass(
|
||||
*m_shader.Get(),
|
||||
backend,
|
||||
m_preferredPassName);
|
||||
if (selectedPass == nullptr) {
|
||||
Debug::Logger::Get().Error(
|
||||
Debug::LogCategory::Rendering,
|
||||
m_preferredPassName.Empty()
|
||||
? "BuiltinVectorFullscreenPass could not resolve a compatible fullscreen shader pass"
|
||||
: "BuiltinVectorFullscreenPass could not resolve the configured fullscreen shader pass");
|
||||
? "BuiltinVectorFullscreenPass could not resolve "
|
||||
"a compatible fullscreen shader pass"
|
||||
: "BuiltinVectorFullscreenPass could not resolve "
|
||||
"the configured fullscreen shader pass");
|
||||
DestroyResources();
|
||||
return false;
|
||||
}
|
||||
|
||||
BuiltinPassResourceBindingPlan bindingPlan = {};
|
||||
Containers::String bindingPlanError;
|
||||
if (!TryBuildBuiltinPassResourceBindingPlan(
|
||||
*selectedPass,
|
||||
bindingPlan,
|
||||
&bindingPlanError)) {
|
||||
Debug::Logger::Get().Error(
|
||||
Debug::LogCategory::Rendering,
|
||||
(Containers::String(
|
||||
"BuiltinVectorFullscreenPass failed to "
|
||||
"resolve shader resource bindings: ") +
|
||||
bindingPlanError)
|
||||
.CStr());
|
||||
DestroyResources();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsSupportedFullscreenBindingPlan(bindingPlan)) {
|
||||
Debug::Logger::Get().Error(
|
||||
Debug::LogCategory::Rendering,
|
||||
"BuiltinVectorFullscreenPass only supports "
|
||||
"PassConstants, SourceColorTexture, "
|
||||
"MaterialTexture, and LinearClampSampler "
|
||||
"resource semantics");
|
||||
DestroyResources();
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<BuiltinPassSetLayoutMetadata> setLayouts;
|
||||
Containers::String setLayoutError;
|
||||
if (!TryBuildBuiltinPassSetLayouts(
|
||||
bindingPlan,
|
||||
setLayouts,
|
||||
&setLayoutError)) {
|
||||
Debug::Logger::Get().Error(
|
||||
Debug::LogCategory::Rendering,
|
||||
(Containers::String(
|
||||
"BuiltinVectorFullscreenPass failed to build "
|
||||
"descriptor set layouts: ") +
|
||||
setLayoutError)
|
||||
.CStr());
|
||||
DestroyResources();
|
||||
return false;
|
||||
}
|
||||
@@ -369,62 +576,64 @@ bool BuiltinVectorFullscreenPass::CreateResources(
|
||||
m_device = renderContext.device;
|
||||
m_backendType = renderContext.backendType;
|
||||
m_renderTargetFormat = renderTargetFormat;
|
||||
m_renderTargetSampleCount = ::XCEngine::Rendering::Internal::ResolveSurfaceSampleCount(surface);
|
||||
m_renderTargetSampleQuality = ::XCEngine::Rendering::Internal::ResolveSurfaceSampleQuality(surface);
|
||||
m_renderTargetSampleCount =
|
||||
::XCEngine::Rendering::Internal::ResolveSurfaceSampleCount(
|
||||
surface);
|
||||
m_renderTargetSampleQuality =
|
||||
::XCEngine::Rendering::Internal::
|
||||
ResolveSurfaceSampleQuality(surface);
|
||||
|
||||
RHI::DescriptorSetLayoutBinding constantBinding = {};
|
||||
constantBinding.binding = 0;
|
||||
constantBinding.type = static_cast<uint32_t>(RHI::DescriptorType::CBV);
|
||||
constantBinding.count = 1;
|
||||
constantBinding.visibility = static_cast<uint32_t>(RHI::ShaderVisibility::All);
|
||||
m_passLayout.setLayouts = std::move(setLayouts);
|
||||
m_passLayout.descriptorSets.resize(
|
||||
m_passLayout.setLayouts.size());
|
||||
m_passLayout.boundSetStates.resize(
|
||||
m_passLayout.setLayouts.size());
|
||||
m_passLayout.passConstants = bindingPlan.passConstants;
|
||||
m_passLayout.sourceColorTexture =
|
||||
bindingPlan.sourceColorTexture;
|
||||
m_passLayout.linearClampSampler =
|
||||
bindingPlan.linearClampSampler;
|
||||
m_passLayout.firstDescriptorSet =
|
||||
bindingPlan.firstDescriptorSet;
|
||||
m_passLayout.descriptorSetCount =
|
||||
bindingPlan.descriptorSetCount;
|
||||
|
||||
RHI::DescriptorSetLayoutBinding textureBinding = {};
|
||||
textureBinding.binding = 0;
|
||||
textureBinding.type = static_cast<uint32_t>(RHI::DescriptorType::SRV);
|
||||
textureBinding.count = 1;
|
||||
textureBinding.visibility = static_cast<uint32_t>(RHI::ShaderVisibility::All);
|
||||
|
||||
RHI::DescriptorSetLayoutBinding samplerBinding = {};
|
||||
samplerBinding.binding = 0;
|
||||
samplerBinding.type = static_cast<uint32_t>(RHI::DescriptorType::Sampler);
|
||||
samplerBinding.count = 1;
|
||||
samplerBinding.visibility = static_cast<uint32_t>(RHI::ShaderVisibility::All);
|
||||
|
||||
RHI::DescriptorSetLayoutDesc constantLayout = {};
|
||||
constantLayout.bindings = &constantBinding;
|
||||
constantLayout.bindingCount = 1;
|
||||
|
||||
RHI::DescriptorSetLayoutDesc textureLayout = {};
|
||||
textureLayout.bindings = &textureBinding;
|
||||
textureLayout.bindingCount = 1;
|
||||
|
||||
RHI::DescriptorSetLayoutDesc samplerLayout = {};
|
||||
samplerLayout.bindings = &samplerBinding;
|
||||
samplerLayout.bindingCount = 1;
|
||||
|
||||
RHI::DescriptorSetLayoutDesc setLayouts[] = {
|
||||
constantLayout,
|
||||
textureLayout,
|
||||
samplerLayout
|
||||
};
|
||||
std::vector<RHI::DescriptorSetLayoutDesc> nativeSetLayouts(
|
||||
m_passLayout.setLayouts.size());
|
||||
for (size_t setIndex = 0u;
|
||||
setIndex < m_passLayout.setLayouts.size();
|
||||
++setIndex) {
|
||||
nativeSetLayouts[setIndex] =
|
||||
m_passLayout.setLayouts[setIndex].layout;
|
||||
}
|
||||
|
||||
RHI::RHIPipelineLayoutDesc pipelineLayoutDesc = {};
|
||||
pipelineLayoutDesc.setLayouts = setLayouts;
|
||||
pipelineLayoutDesc.setLayoutCount = 3;
|
||||
m_pipelineLayout = m_device->CreatePipelineLayout(pipelineLayoutDesc);
|
||||
if (m_pipelineLayout == nullptr) {
|
||||
pipelineLayoutDesc.setLayouts =
|
||||
nativeSetLayouts.empty()
|
||||
? nullptr
|
||||
: nativeSetLayouts.data();
|
||||
pipelineLayoutDesc.setLayoutCount =
|
||||
static_cast<uint32_t>(nativeSetLayouts.size());
|
||||
m_passLayout.pipelineLayout =
|
||||
m_device->CreatePipelineLayout(pipelineLayoutDesc);
|
||||
if (m_passLayout.pipelineLayout == nullptr) {
|
||||
DestroyResources();
|
||||
return false;
|
||||
}
|
||||
|
||||
RHI::SamplerDesc samplerDesc = {};
|
||||
samplerDesc.filter = static_cast<uint32_t>(RHI::FilterMode::Linear);
|
||||
samplerDesc.addressU = static_cast<uint32_t>(RHI::TextureAddressMode::Clamp);
|
||||
samplerDesc.addressV = static_cast<uint32_t>(RHI::TextureAddressMode::Clamp);
|
||||
samplerDesc.addressW = static_cast<uint32_t>(RHI::TextureAddressMode::Clamp);
|
||||
samplerDesc.filter =
|
||||
static_cast<uint32_t>(RHI::FilterMode::Linear);
|
||||
samplerDesc.addressU = static_cast<uint32_t>(
|
||||
RHI::TextureAddressMode::Clamp);
|
||||
samplerDesc.addressV = static_cast<uint32_t>(
|
||||
RHI::TextureAddressMode::Clamp);
|
||||
samplerDesc.addressW = static_cast<uint32_t>(
|
||||
RHI::TextureAddressMode::Clamp);
|
||||
samplerDesc.mipLodBias = 0.0f;
|
||||
samplerDesc.maxAnisotropy = 1;
|
||||
samplerDesc.comparisonFunc = static_cast<uint32_t>(RHI::ComparisonFunc::Always);
|
||||
samplerDesc.comparisonFunc = static_cast<uint32_t>(
|
||||
RHI::ComparisonFunc::Always);
|
||||
samplerDesc.minLod = 0.0f;
|
||||
samplerDesc.maxLod = 1000.0f;
|
||||
m_sampler = m_device->CreateSampler(samplerDesc);
|
||||
@@ -433,58 +642,47 @@ bool BuiltinVectorFullscreenPass::CreateResources(
|
||||
return false;
|
||||
}
|
||||
|
||||
auto createOwnedDescriptorSet =
|
||||
[this](const RHI::DescriptorSetLayoutDesc& layout,
|
||||
RHI::DescriptorHeapType heapType,
|
||||
bool shaderVisible,
|
||||
OwnedDescriptorSet& ownedSet) -> bool {
|
||||
RHI::DescriptorPoolDesc poolDesc = {};
|
||||
poolDesc.type = heapType;
|
||||
poolDesc.descriptorCount = 1;
|
||||
poolDesc.shaderVisible = shaderVisible;
|
||||
ownedSet.pool = m_device->CreateDescriptorPool(poolDesc);
|
||||
if (ownedSet.pool == nullptr) {
|
||||
for (size_t setIndex = 0u;
|
||||
setIndex < m_passLayout.setLayouts.size();
|
||||
++setIndex) {
|
||||
const BuiltinPassSetLayoutMetadata& setLayout =
|
||||
m_passLayout.setLayouts[setIndex];
|
||||
if (setLayout.bindings.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!CreateOwnedDescriptorSet(
|
||||
setLayout,
|
||||
m_passLayout.descriptorSets[setIndex])) {
|
||||
DestroyResources();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (setLayout.usesSampler) {
|
||||
if (!setLayout.usesLinearClampSampler ||
|
||||
!m_passLayout.linearClampSampler.IsValid() ||
|
||||
m_passLayout.linearClampSampler.set !=
|
||||
setIndex) {
|
||||
DestroyResources();
|
||||
return false;
|
||||
}
|
||||
|
||||
ownedSet.set = ownedSet.pool->AllocateSet(layout);
|
||||
if (ownedSet.set == nullptr) {
|
||||
DestroyOwnedDescriptorSet(ownedSet);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
if (!createOwnedDescriptorSet(
|
||||
constantLayout,
|
||||
RHI::DescriptorHeapType::CBV_SRV_UAV,
|
||||
false,
|
||||
m_constantsSet) ||
|
||||
!createOwnedDescriptorSet(
|
||||
textureLayout,
|
||||
RHI::DescriptorHeapType::CBV_SRV_UAV,
|
||||
true,
|
||||
m_textureSet) ||
|
||||
!createOwnedDescriptorSet(
|
||||
samplerLayout,
|
||||
RHI::DescriptorHeapType::Sampler,
|
||||
true,
|
||||
m_samplerSet)) {
|
||||
DestroyResources();
|
||||
return false;
|
||||
m_passLayout.descriptorSets[setIndex]
|
||||
.set->UpdateSampler(
|
||||
m_passLayout.linearClampSampler.binding,
|
||||
m_sampler);
|
||||
}
|
||||
}
|
||||
|
||||
m_samplerSet.set->UpdateSampler(0, m_sampler);
|
||||
|
||||
m_pipelineState = m_device->CreatePipelineState(
|
||||
CreatePipelineDesc(
|
||||
m_backendType,
|
||||
m_pipelineLayout,
|
||||
m_passLayout.pipelineLayout,
|
||||
*m_shader.Get(),
|
||||
selectedPass->name,
|
||||
surface));
|
||||
if (m_pipelineState == nullptr || !m_pipelineState->IsValid()) {
|
||||
if (m_pipelineState == nullptr ||
|
||||
!m_pipelineState->IsValid()) {
|
||||
DestroyResources();
|
||||
return false;
|
||||
}
|
||||
@@ -492,24 +690,149 @@ bool BuiltinVectorFullscreenPass::CreateResources(
|
||||
return true;
|
||||
}
|
||||
|
||||
void BuiltinVectorFullscreenPass::DestroyResources() {
|
||||
m_boundSourceColorView = nullptr;
|
||||
bool BuiltinVectorFullscreenPass::CreateOwnedDescriptorSet(
|
||||
const BuiltinPassSetLayoutMetadata& setLayout,
|
||||
OwnedDescriptorSet& descriptorSet) {
|
||||
RHI::DescriptorPoolDesc poolDesc = {};
|
||||
poolDesc.type = setLayout.heapType;
|
||||
poolDesc.descriptorCount =
|
||||
CountBuiltinPassHeapDescriptors(
|
||||
setLayout.heapType,
|
||||
setLayout.bindings);
|
||||
poolDesc.shaderVisible = setLayout.shaderVisible;
|
||||
|
||||
descriptorSet.pool =
|
||||
m_device->CreateDescriptorPool(poolDesc);
|
||||
if (descriptorSet.pool == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
descriptorSet.set =
|
||||
descriptorSet.pool->AllocateSet(setLayout.layout);
|
||||
if (descriptorSet.set == nullptr) {
|
||||
DestroyOwnedDescriptorSet(descriptorSet);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BuiltinVectorFullscreenPass::UpdateDescriptorSets(
|
||||
const RenderPassContext& context) {
|
||||
for (size_t setIndex = 0u;
|
||||
setIndex < m_passLayout.setLayouts.size();
|
||||
++setIndex) {
|
||||
const BuiltinPassSetLayoutMetadata& setLayout =
|
||||
m_passLayout.setLayouts[setIndex];
|
||||
if (setLayout.bindings.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
OwnedDescriptorSet& descriptorSet =
|
||||
m_passLayout.descriptorSets[setIndex];
|
||||
BoundSetState& boundSetState =
|
||||
m_passLayout.boundSetStates[setIndex];
|
||||
if (descriptorSet.set == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (setLayout.usesPassConstants) {
|
||||
if (!m_passLayout.passConstants.IsValid() ||
|
||||
m_passLayout.passConstants.set != setIndex) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const PostProcessConstants constants = {
|
||||
m_vectorPayload
|
||||
};
|
||||
descriptorSet.set->WriteConstant(
|
||||
m_passLayout.passConstants.binding,
|
||||
&constants,
|
||||
sizeof(constants));
|
||||
}
|
||||
|
||||
if (setLayout.usesSourceColorTexture) {
|
||||
if (context.sourceColorView == nullptr ||
|
||||
!m_passLayout.sourceColorTexture.IsValid() ||
|
||||
m_passLayout.sourceColorTexture.set !=
|
||||
setIndex) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (boundSetState.sourceColorTextureView !=
|
||||
context.sourceColorView) {
|
||||
descriptorSet.set->Update(
|
||||
m_passLayout.sourceColorTexture.binding,
|
||||
context.sourceColorView);
|
||||
boundSetState.sourceColorTextureView =
|
||||
context.sourceColorView;
|
||||
}
|
||||
}
|
||||
|
||||
if (setLayout.usesMaterialTextures) {
|
||||
if (boundSetState.materialTextureViews.size() !=
|
||||
setLayout.materialTextureBindings.size()) {
|
||||
boundSetState.materialTextureViews.assign(
|
||||
setLayout.materialTextureBindings.size(),
|
||||
nullptr);
|
||||
}
|
||||
|
||||
for (size_t bindingIndex = 0u;
|
||||
bindingIndex <
|
||||
setLayout.materialTextureBindings.size();
|
||||
++bindingIndex) {
|
||||
const BuiltinPassResourceBindingDesc&
|
||||
textureBinding =
|
||||
setLayout.materialTextureBindings
|
||||
[bindingIndex];
|
||||
RHI::RHIResourceView* resolvedTextureView =
|
||||
ResolveExtraTextureView(
|
||||
context,
|
||||
textureBinding);
|
||||
if (resolvedTextureView == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (boundSetState.materialTextureViews
|
||||
[bindingIndex] !=
|
||||
resolvedTextureView) {
|
||||
descriptorSet.set->Update(
|
||||
textureBinding.location.binding,
|
||||
resolvedTextureView);
|
||||
boundSetState.materialTextureViews
|
||||
[bindingIndex] = resolvedTextureView;
|
||||
}
|
||||
}
|
||||
} else if (!boundSetState.materialTextureViews
|
||||
.empty()) {
|
||||
boundSetState.materialTextureViews.clear();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
RHI::RHIResourceView*
|
||||
BuiltinVectorFullscreenPass::ResolveExtraTextureView(
|
||||
const RenderPassContext& context,
|
||||
const BuiltinPassResourceBindingDesc& bindingDesc) const {
|
||||
const RenderPassContext::TextureBindingView* bindingView =
|
||||
FindTextureBindingView(
|
||||
context,
|
||||
bindingDesc);
|
||||
return bindingView != nullptr
|
||||
? bindingView->resourceView
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
void BuiltinVectorFullscreenPass::DestroyResources() {
|
||||
if (m_pipelineState != nullptr) {
|
||||
m_pipelineState->Shutdown();
|
||||
delete m_pipelineState;
|
||||
m_pipelineState = nullptr;
|
||||
}
|
||||
|
||||
DestroyOwnedDescriptorSet(m_samplerSet);
|
||||
DestroyOwnedDescriptorSet(m_textureSet);
|
||||
DestroyOwnedDescriptorSet(m_constantsSet);
|
||||
|
||||
if (m_pipelineLayout != nullptr) {
|
||||
m_pipelineLayout->Shutdown();
|
||||
delete m_pipelineLayout;
|
||||
m_pipelineLayout = nullptr;
|
||||
}
|
||||
DestroyPassResourceLayout();
|
||||
|
||||
if (m_sampler != nullptr) {
|
||||
m_sampler->Shutdown();
|
||||
@@ -525,7 +848,30 @@ void BuiltinVectorFullscreenPass::DestroyResources() {
|
||||
m_renderTargetSampleQuality = 0u;
|
||||
}
|
||||
|
||||
void BuiltinVectorFullscreenPass::DestroyOwnedDescriptorSet(OwnedDescriptorSet& descriptorSet) {
|
||||
void BuiltinVectorFullscreenPass::DestroyPassResourceLayout() {
|
||||
for (OwnedDescriptorSet& descriptorSet :
|
||||
m_passLayout.descriptorSets) {
|
||||
DestroyOwnedDescriptorSet(descriptorSet);
|
||||
}
|
||||
m_passLayout.descriptorSets.clear();
|
||||
m_passLayout.boundSetStates.clear();
|
||||
m_passLayout.setLayouts.clear();
|
||||
|
||||
if (m_passLayout.pipelineLayout != nullptr) {
|
||||
m_passLayout.pipelineLayout->Shutdown();
|
||||
delete m_passLayout.pipelineLayout;
|
||||
m_passLayout.pipelineLayout = nullptr;
|
||||
}
|
||||
|
||||
m_passLayout.passConstants = {};
|
||||
m_passLayout.sourceColorTexture = {};
|
||||
m_passLayout.linearClampSampler = {};
|
||||
m_passLayout.firstDescriptorSet = 0u;
|
||||
m_passLayout.descriptorSetCount = 0u;
|
||||
}
|
||||
|
||||
void BuiltinVectorFullscreenPass::DestroyOwnedDescriptorSet(
|
||||
OwnedDescriptorSet& descriptorSet) {
|
||||
if (descriptorSet.set != nullptr) {
|
||||
descriptorSet.set->Shutdown();
|
||||
delete descriptorSet.set;
|
||||
|
||||
133
engine/src/Rendering/Picking/RenderObjectIdRegistry.cpp
Normal file
133
engine/src/Rendering/Picking/RenderObjectIdRegistry.cpp
Normal file
@@ -0,0 +1,133 @@
|
||||
#include <XCEngine/Rendering/Picking/RenderObjectIdRegistry.h>
|
||||
|
||||
#include <limits>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Rendering {
|
||||
|
||||
RenderObjectIdRegistry& RenderObjectIdRegistry::Get() {
|
||||
static RenderObjectIdRegistry registry = {};
|
||||
return registry;
|
||||
}
|
||||
|
||||
RenderObjectId RenderObjectIdRegistry::GetOrAllocateRenderObjectId(
|
||||
Core::uint64 runtimeObjectId) {
|
||||
if (runtimeObjectId == 0u) {
|
||||
return kInvalidRenderObjectId;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
const auto existing =
|
||||
m_renderObjectIdByRuntimeObjectId.find(runtimeObjectId);
|
||||
if (existing != m_renderObjectIdByRuntimeObjectId.end()) {
|
||||
return existing->second;
|
||||
}
|
||||
|
||||
const RenderObjectId renderObjectId = AllocateRenderObjectIdLocked();
|
||||
if (!IsValidRenderObjectId(renderObjectId)) {
|
||||
return kInvalidRenderObjectId;
|
||||
}
|
||||
|
||||
m_renderObjectIdByRuntimeObjectId.emplace(runtimeObjectId, renderObjectId);
|
||||
m_runtimeObjectIdByRenderObjectId.emplace(renderObjectId, runtimeObjectId);
|
||||
BumpGenerationLocked();
|
||||
return renderObjectId;
|
||||
}
|
||||
|
||||
bool RenderObjectIdRegistry::TryGetRenderObjectId(
|
||||
Core::uint64 runtimeObjectId,
|
||||
RenderObjectId& outRenderObjectId) const {
|
||||
outRenderObjectId = kInvalidRenderObjectId;
|
||||
if (runtimeObjectId == 0u) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
const auto existing =
|
||||
m_renderObjectIdByRuntimeObjectId.find(runtimeObjectId);
|
||||
if (existing == m_renderObjectIdByRuntimeObjectId.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outRenderObjectId = existing->second;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RenderObjectIdRegistry::TryResolveRuntimeObjectId(
|
||||
RenderObjectId renderObjectId,
|
||||
Core::uint64& outRuntimeObjectId) const {
|
||||
outRuntimeObjectId = 0u;
|
||||
if (!IsValidRenderObjectId(renderObjectId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
const auto existing =
|
||||
m_runtimeObjectIdByRenderObjectId.find(renderObjectId);
|
||||
if (existing == m_runtimeObjectIdByRenderObjectId.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outRuntimeObjectId = existing->second;
|
||||
return true;
|
||||
}
|
||||
|
||||
Core::uint64 RenderObjectIdRegistry::ResolveRuntimeObjectId(
|
||||
RenderObjectId renderObjectId) const {
|
||||
Core::uint64 runtimeObjectId = 0u;
|
||||
TryResolveRuntimeObjectId(renderObjectId, runtimeObjectId);
|
||||
return runtimeObjectId;
|
||||
}
|
||||
|
||||
void RenderObjectIdRegistry::Reset() {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_nextRenderObjectId = 1u;
|
||||
m_renderObjectIdByRuntimeObjectId.clear();
|
||||
m_runtimeObjectIdByRenderObjectId.clear();
|
||||
BumpGenerationLocked();
|
||||
}
|
||||
|
||||
Core::uint64 RenderObjectIdRegistry::GetGeneration() const {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
return m_generation;
|
||||
}
|
||||
|
||||
RenderObjectId RenderObjectIdRegistry::AllocateRenderObjectIdLocked() {
|
||||
constexpr Core::uint64 kMaxAssignableRenderObjectIdCount =
|
||||
static_cast<Core::uint64>(std::numeric_limits<RenderObjectId>::max()) - 1u;
|
||||
if (m_runtimeObjectIdByRenderObjectId.size() >=
|
||||
static_cast<std::size_t>(kMaxAssignableRenderObjectIdCount)) {
|
||||
return kInvalidRenderObjectId;
|
||||
}
|
||||
|
||||
for (Core::uint64 attempt = 0u;
|
||||
attempt < kMaxAssignableRenderObjectIdCount;
|
||||
++attempt) {
|
||||
const RenderObjectId candidate = m_nextRenderObjectId;
|
||||
++m_nextRenderObjectId;
|
||||
if (m_nextRenderObjectId == kInvalidRenderObjectId) {
|
||||
m_nextRenderObjectId = 1u;
|
||||
}
|
||||
|
||||
if (!IsValidRenderObjectId(candidate)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (m_runtimeObjectIdByRenderObjectId.find(candidate) ==
|
||||
m_runtimeObjectIdByRenderObjectId.end()) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
return kInvalidRenderObjectId;
|
||||
}
|
||||
|
||||
void RenderObjectIdRegistry::BumpGenerationLocked() {
|
||||
++m_generation;
|
||||
if (m_generation == 0u) {
|
||||
m_generation = 1u;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Rendering
|
||||
} // namespace XCEngine
|
||||
@@ -6,9 +6,7 @@ namespace XCEngine {
|
||||
namespace Rendering {
|
||||
namespace Pipelines {
|
||||
|
||||
BuiltinForwardPipeline::BuiltinForwardPipeline() {
|
||||
Internal::RegisterBuiltinForwardSceneFeatures(m_forwardSceneFeatureHost);
|
||||
}
|
||||
BuiltinForwardPipeline::BuiltinForwardPipeline() = default;
|
||||
|
||||
BuiltinForwardPipeline::~BuiltinForwardPipeline() {
|
||||
Shutdown();
|
||||
@@ -28,7 +26,7 @@ SceneRenderFeaturePass* BuiltinForwardPipeline::GetForwardSceneFeaturePass(size_
|
||||
}
|
||||
|
||||
std::unique_ptr<RenderPipeline> BuiltinForwardPipelineAsset::CreatePipeline() const {
|
||||
return std::make_unique<BuiltinForwardPipeline>();
|
||||
return Internal::CreateConfiguredBuiltinForwardPipeline();
|
||||
}
|
||||
|
||||
} // namespace Pipelines
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
#include "Rendering/Features/BuiltinGaussianSplatPass.h"
|
||||
#include "Rendering/Features/BuiltinVolumetricPass.h"
|
||||
#include "Rendering/SceneRenderFeatureHost.h"
|
||||
#include "Rendering/Passes/BuiltinDepthOnlyPass.h"
|
||||
#include "Rendering/Passes/BuiltinShadowCasterPass.h"
|
||||
#include "Rendering/Pipelines/BuiltinForwardPipeline.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
@@ -11,9 +13,35 @@ namespace Rendering {
|
||||
namespace Pipelines {
|
||||
namespace Internal {
|
||||
|
||||
void RegisterBuiltinForwardSceneFeatures(SceneRenderFeatureHost& featureHost) {
|
||||
featureHost.AddFeaturePass(std::make_unique<Features::BuiltinGaussianSplatPass>());
|
||||
featureHost.AddFeaturePass(std::make_unique<Features::BuiltinVolumetricPass>());
|
||||
namespace {
|
||||
|
||||
void ConfigureBuiltinForwardStandalonePasses(
|
||||
BuiltinForwardPipeline& pipeline) {
|
||||
pipeline.SetCameraFrameStandalonePass(
|
||||
CameraFrameStage::DepthOnly,
|
||||
std::make_unique<Passes::BuiltinDepthOnlyPass>());
|
||||
pipeline.SetCameraFrameStandalonePass(
|
||||
CameraFrameStage::ShadowCaster,
|
||||
std::make_unique<Passes::BuiltinShadowCasterPass>());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void ConfigureBuiltinForwardPipeline(
|
||||
BuiltinForwardPipeline& pipeline) {
|
||||
ConfigureBuiltinForwardStandalonePasses(pipeline);
|
||||
pipeline.AddForwardSceneFeaturePass(
|
||||
std::make_unique<Features::BuiltinGaussianSplatPass>());
|
||||
pipeline.AddForwardSceneFeaturePass(
|
||||
std::make_unique<Features::BuiltinVolumetricPass>());
|
||||
}
|
||||
|
||||
std::unique_ptr<BuiltinForwardPipeline>
|
||||
CreateConfiguredBuiltinForwardPipeline() {
|
||||
std::unique_ptr<BuiltinForwardPipeline> pipeline =
|
||||
std::make_unique<BuiltinForwardPipeline>();
|
||||
ConfigureBuiltinForwardPipeline(*pipeline);
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
} // namespace Internal
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Rendering {
|
||||
|
||||
class SceneRenderFeatureHost;
|
||||
|
||||
namespace Pipelines {
|
||||
class BuiltinForwardPipeline;
|
||||
|
||||
namespace Internal {
|
||||
|
||||
void RegisterBuiltinForwardSceneFeatures(SceneRenderFeatureHost& featureHost);
|
||||
void ConfigureBuiltinForwardPipeline(
|
||||
BuiltinForwardPipeline& pipeline);
|
||||
std::unique_ptr<BuiltinForwardPipeline>
|
||||
CreateConfiguredBuiltinForwardPipeline();
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace Pipelines
|
||||
|
||||
@@ -112,12 +112,6 @@ void ManagedScriptableRenderPipelineAsset::ConfigureCameraFramePlan(
|
||||
if (const std::shared_ptr<const ManagedRenderPipelineAssetRuntime> runtime =
|
||||
ResolveManagedAssetRuntime();
|
||||
runtime != nullptr) {
|
||||
if (runtime->UsesNativeCameraFramePlanBaseline(
|
||||
plan.request.rendererIndex)) {
|
||||
ApplyDefaultRenderPipelineAssetCameraFramePlanBaselinePolicy(
|
||||
plan,
|
||||
GetDefaultFinalColorSettings());
|
||||
}
|
||||
runtime->ConfigureCameraFramePlan(plan);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -374,9 +374,10 @@ bool NativeSceneRecorder::RecordInjectionPoint(
|
||||
}
|
||||
|
||||
bool NativeSceneRecorder::RecordFeaturePass(
|
||||
const Containers::String& featurePassName) {
|
||||
NativeSceneFeaturePassId featurePassId) {
|
||||
if (m_context.stage != CameraFrameStage::MainScene ||
|
||||
featurePassName.Empty()) {
|
||||
featurePassId ==
|
||||
NativeSceneFeaturePassId::Invalid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -400,7 +401,7 @@ bool NativeSceneRecorder::RecordFeaturePass(
|
||||
m_clearAttachments,
|
||||
beginRecordedPass,
|
||||
endRecordedPass),
|
||||
featurePassName,
|
||||
featurePassId,
|
||||
&recordedPass)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -3,10 +3,8 @@
|
||||
#include "Rendering/Execution/DirectionalShadowExecutionState.h"
|
||||
#include "Rendering/Internal/RenderPipelineFactory.h"
|
||||
#include "Rendering/Pipelines/BuiltinForwardPipeline.h"
|
||||
#include "Rendering/Pipelines/Internal/BuiltinForwardSceneSetup.h"
|
||||
#include "Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.h"
|
||||
#include "Rendering/Passes/BuiltinDepthOnlyPass.h"
|
||||
#include "Rendering/Passes/BuiltinObjectIdPass.h"
|
||||
#include "Rendering/Passes/BuiltinShadowCasterPass.h"
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Rendering {
|
||||
@@ -30,7 +28,7 @@ std::unique_ptr<RenderPipelineBackend> CreatePipelineBackendFromAsset(
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_unique<BuiltinForwardPipeline>();
|
||||
return Internal::CreateConfiguredBuiltinForwardPipeline();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -214,7 +212,15 @@ RenderPass* ScriptableRenderPipelineHost::GetCameraFrameStandalonePass(
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return RenderPipeline::GetCameraFrameStandalonePass(stage);
|
||||
if (RenderPass* const hostPass =
|
||||
RenderPipeline::GetCameraFrameStandalonePass(stage);
|
||||
hostPass != nullptr) {
|
||||
return hostPass;
|
||||
}
|
||||
|
||||
return ResolvePipelineBackendStandalonePass(
|
||||
stage,
|
||||
rendererIndex);
|
||||
}
|
||||
|
||||
bool ScriptableRenderPipelineHost::EnsureInitialized(const RenderContext& context) {
|
||||
@@ -260,6 +266,21 @@ bool ScriptableRenderPipelineHost::EnsureInitialized(const RenderContext& contex
|
||||
return true;
|
||||
}
|
||||
|
||||
RenderPass* ScriptableRenderPipelineHost::ResolvePipelineBackendStandalonePass(
|
||||
CameraFrameStage stage,
|
||||
int32_t rendererIndex) const {
|
||||
const auto* const backendPipeline =
|
||||
dynamic_cast<const RenderPipeline*>(m_pipelineBackend.get());
|
||||
if (backendPipeline == nullptr ||
|
||||
backendPipeline == this) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return backendPipeline->GetCameraFrameStandalonePass(
|
||||
stage,
|
||||
rendererIndex);
|
||||
}
|
||||
|
||||
void ScriptableRenderPipelineHost::BindStageRecorderPipelineBackend() {
|
||||
if (m_stageRecorder != nullptr) {
|
||||
m_stageRecorder->SetPipelineBackend(m_pipelineBackend.get());
|
||||
@@ -352,28 +373,9 @@ ScriptableRenderPipelineHostAsset::ScriptableRenderPipelineHostAsset(
|
||||
}
|
||||
|
||||
std::unique_ptr<RenderPipeline> ScriptableRenderPipelineHostAsset::CreatePipeline() const {
|
||||
std::unique_ptr<RenderPipeline> pipeline =
|
||||
std::make_unique<ScriptableRenderPipelineHost>(
|
||||
m_pipelineBackendAsset,
|
||||
m_managedAssetRuntime);
|
||||
if (pipeline != nullptr) {
|
||||
ConfigurePipeline(*pipeline);
|
||||
}
|
||||
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
void ScriptableRenderPipelineHostAsset::ConfigurePipeline(
|
||||
RenderPipeline& pipeline) const {
|
||||
pipeline.SetCameraFrameStandalonePass(
|
||||
CameraFrameStage::ObjectId,
|
||||
std::make_unique<Passes::BuiltinObjectIdPass>());
|
||||
pipeline.SetCameraFrameStandalonePass(
|
||||
CameraFrameStage::DepthOnly,
|
||||
std::make_unique<Passes::BuiltinDepthOnlyPass>());
|
||||
pipeline.SetCameraFrameStandalonePass(
|
||||
CameraFrameStage::ShadowCaster,
|
||||
std::make_unique<Passes::BuiltinShadowCasterPass>());
|
||||
return std::make_unique<ScriptableRenderPipelineHost>(
|
||||
m_pipelineBackendAsset,
|
||||
m_managedAssetRuntime);
|
||||
}
|
||||
|
||||
FinalColorSettings ScriptableRenderPipelineHostAsset::GetDefaultFinalColorSettings() const {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "Rendering/Execution/Internal/CameraFrameGraph/SurfaceUtils.h"
|
||||
#include "Rendering/FrameData/RenderSceneData.h"
|
||||
#include "Rendering/Graph/RenderGraph.h"
|
||||
#include "RHI/RHIResourceView.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
@@ -146,13 +147,65 @@ bool ResolveGraphManagedOutputSurface(
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ResolveGraphManagedTextureBindingViews(
|
||||
const std::vector<RenderPassGraphTextureBindingRequest>&
|
||||
textureBindingRequests,
|
||||
RenderGraphTextureHandle sourceColorTexture,
|
||||
RHI::RHIResourceView* sourceColorView,
|
||||
const RenderGraphExecutionContext& graphContext,
|
||||
std::vector<RenderPassContext::TextureBindingView>&
|
||||
outTextureBindings) {
|
||||
outTextureBindings.clear();
|
||||
outTextureBindings.reserve(textureBindingRequests.size());
|
||||
|
||||
for (const RenderPassGraphTextureBindingRequest& bindingRequest :
|
||||
textureBindingRequests) {
|
||||
if (bindingRequest.resourceName.Empty() ||
|
||||
!bindingRequest.texture.IsValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RHI::RHIResourceView* resolvedView = nullptr;
|
||||
if (sourceColorTexture.IsValid() &&
|
||||
bindingRequest.texture.index ==
|
||||
sourceColorTexture.index) {
|
||||
resolvedView = sourceColorView;
|
||||
} else {
|
||||
resolvedView =
|
||||
graphContext.ResolveTextureView(
|
||||
bindingRequest.texture,
|
||||
RenderGraphTextureViewType::
|
||||
ShaderResource);
|
||||
}
|
||||
|
||||
if (resolvedView == nullptr ||
|
||||
resolvedView->GetViewType() !=
|
||||
RHI::ResourceViewType::ShaderResource) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RenderPassContext::TextureBindingView
|
||||
resolvedBinding = {};
|
||||
resolvedBinding.resourceName =
|
||||
bindingRequest.resourceName;
|
||||
resolvedBinding.resourceView = resolvedView;
|
||||
outTextureBindings.push_back(
|
||||
std::move(resolvedBinding));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool RecordCallbackRasterRenderPass(
|
||||
const RenderPassRenderGraphContext& context,
|
||||
const RenderPassGraphIO& io,
|
||||
RenderPassGraphExecutePassCallback executePassCallback,
|
||||
std::vector<RenderGraphTextureHandle> additionalReadTextures) {
|
||||
std::vector<RenderGraphTextureHandle> additionalReadTextures,
|
||||
std::vector<RenderGraphTextureHandle> additionalReadDepthTextures,
|
||||
std::vector<RenderPassGraphTextureBindingRequest>
|
||||
textureBindingRequests) {
|
||||
if (!executePassCallback) {
|
||||
return false;
|
||||
}
|
||||
@@ -191,6 +244,8 @@ bool RecordCallbackRasterRenderPass(
|
||||
endPassCallback,
|
||||
executePassCallback,
|
||||
additionalReadTextures,
|
||||
additionalReadDepthTextures,
|
||||
textureBindingRequests,
|
||||
io](
|
||||
RenderGraphPassBuilder& passBuilder) {
|
||||
if (io.readSourceColor && sourceColorTexture.IsValid()) {
|
||||
@@ -203,6 +258,13 @@ bool RecordCallbackRasterRenderPass(
|
||||
}
|
||||
}
|
||||
|
||||
for (RenderGraphTextureHandle readDepthTexture :
|
||||
additionalReadDepthTextures) {
|
||||
if (readDepthTexture.IsValid()) {
|
||||
passBuilder.ReadDepthTexture(readDepthTexture);
|
||||
}
|
||||
}
|
||||
|
||||
if (io.writeColor) {
|
||||
for (RenderGraphTextureHandle colorTarget : colorTargets) {
|
||||
if (colorTarget.IsValid()) {
|
||||
@@ -230,6 +292,7 @@ bool RecordCallbackRasterRenderPass(
|
||||
beginPassCallback,
|
||||
endPassCallback,
|
||||
executePassCallback,
|
||||
textureBindingRequests,
|
||||
io](
|
||||
const RenderGraphExecutionContext& executionContext) {
|
||||
if (executionSucceeded != nullptr && !(*executionSucceeded)) {
|
||||
@@ -274,13 +337,28 @@ bool RecordCallbackRasterRenderPass(
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<RenderPassContext::TextureBindingView>
|
||||
resolvedTextureBindings = {};
|
||||
if (!ResolveGraphManagedTextureBindingViews(
|
||||
textureBindingRequests,
|
||||
sourceColorTexture,
|
||||
resolvedSourceColorView,
|
||||
executionContext,
|
||||
resolvedTextureBindings)) {
|
||||
if (executionSucceeded != nullptr) {
|
||||
*executionSucceeded = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const RenderPassContext passContext = {
|
||||
renderContext,
|
||||
*resolvedSurface,
|
||||
*sceneData,
|
||||
resolvedSourceSurface,
|
||||
resolvedSourceColorView,
|
||||
resolvedSourceColorState
|
||||
resolvedSourceColorState,
|
||||
std::move(resolvedTextureBindings)
|
||||
};
|
||||
if (beginPassCallback &&
|
||||
!beginPassCallback(passContext)) {
|
||||
|
||||
@@ -48,12 +48,13 @@ Containers::String BuildNamedFeatureGraphPassName(
|
||||
return Containers::String(name.c_str());
|
||||
}
|
||||
|
||||
bool MatchesFeaturePassName(
|
||||
bool MatchesFeaturePassId(
|
||||
const SceneRenderFeaturePass& featurePass,
|
||||
const Containers::String& featurePassName) {
|
||||
const char* const passName = featurePass.GetName();
|
||||
return passName != nullptr &&
|
||||
featurePassName == Containers::String(passName);
|
||||
NativeSceneFeaturePassId featurePassId) {
|
||||
return featurePassId !=
|
||||
NativeSceneFeaturePassId::Invalid &&
|
||||
featurePass.GetNativeSceneFeaturePassId() ==
|
||||
featurePassId;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -180,13 +181,14 @@ bool SceneRenderFeatureHost::Record(
|
||||
|
||||
bool SceneRenderFeatureHost::RecordFeaturePass(
|
||||
const SceneRenderFeaturePassRenderGraphContext& context,
|
||||
const Containers::String& featurePassName,
|
||||
NativeSceneFeaturePassId featurePassId,
|
||||
bool* recordedPass) const {
|
||||
if (recordedPass != nullptr) {
|
||||
*recordedPass = false;
|
||||
}
|
||||
|
||||
if (featurePassName.Empty()) {
|
||||
if (featurePassId ==
|
||||
NativeSceneFeaturePassId::Invalid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -196,7 +198,9 @@ bool SceneRenderFeatureHost::RecordFeaturePass(
|
||||
m_featurePasses[featureIndex];
|
||||
SceneRenderFeaturePass* const featurePass = featurePassOwner.get();
|
||||
if (featurePass == nullptr ||
|
||||
!MatchesFeaturePassName(*featurePass, featurePassName)) {
|
||||
!MatchesFeaturePassId(
|
||||
*featurePass,
|
||||
featurePassId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -217,7 +221,7 @@ bool SceneRenderFeatureHost::RecordFeaturePass(
|
||||
Debug::LogCategory::Rendering,
|
||||
(Containers::String(
|
||||
"SceneRenderFeatureHost record failed for feature pass '") +
|
||||
featurePassName +
|
||||
featurePass->GetName() +
|
||||
"': " +
|
||||
featurePass->GetName())
|
||||
.CStr());
|
||||
@@ -233,8 +237,12 @@ bool SceneRenderFeatureHost::RecordFeaturePass(
|
||||
Debug::Logger::Get().Error(
|
||||
Debug::LogCategory::Rendering,
|
||||
(Containers::String(
|
||||
"SceneRenderFeatureHost could not resolve feature pass: ") +
|
||||
featurePassName)
|
||||
"SceneRenderFeatureHost could not resolve feature pass id: ") +
|
||||
Containers::String(
|
||||
std::to_string(
|
||||
static_cast<uint32_t>(
|
||||
featurePassId))
|
||||
.c_str()))
|
||||
.CStr());
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -32,9 +32,6 @@ constexpr const char* kBuiltinUnlitShaderPath = "builtin://shaders/unlit";
|
||||
constexpr const char* kBuiltinDepthOnlyShaderPath = "builtin://shaders/depth-only";
|
||||
constexpr const char* kBuiltinShadowCasterShaderPath = "builtin://shaders/shadow-caster";
|
||||
constexpr const char* kBuiltinObjectIdShaderPath = "builtin://shaders/object-id";
|
||||
constexpr const char* kBuiltinObjectIdOutlineShaderPath = "builtin://shaders/object-id-outline";
|
||||
constexpr const char* kBuiltinSelectionMaskShaderPath = "builtin://shaders/selection-mask";
|
||||
constexpr const char* kBuiltinSelectionOutlineShaderPath = "builtin://shaders/selection-outline";
|
||||
constexpr const char* kBuiltinSkyboxShaderPath = "builtin://shaders/skybox";
|
||||
constexpr const char* kBuiltinGaussianSplatShaderPath = "builtin://shaders/gaussian-splat";
|
||||
constexpr const char* kBuiltinGaussianSplatUtilitiesShaderPath =
|
||||
@@ -63,12 +60,6 @@ constexpr const char* kBuiltinShadowCasterShaderAssetRelativePath =
|
||||
"engine/assets/builtin/shaders/shadow-caster.shader";
|
||||
constexpr const char* kBuiltinObjectIdShaderAssetRelativePath =
|
||||
"engine/assets/builtin/shaders/object-id.shader";
|
||||
constexpr const char* kBuiltinObjectIdOutlineShaderAssetRelativePath =
|
||||
"engine/assets/builtin/shaders/object-id-outline.shader";
|
||||
constexpr const char* kBuiltinSelectionMaskShaderAssetRelativePath =
|
||||
"engine/assets/builtin/shaders/selection-mask.shader";
|
||||
constexpr const char* kBuiltinSelectionOutlineShaderAssetRelativePath =
|
||||
"engine/assets/builtin/shaders/selection-outline.shader";
|
||||
constexpr const char* kBuiltinSkyboxShaderAssetRelativePath =
|
||||
"engine/assets/builtin/shaders/skybox.shader";
|
||||
constexpr const char* kBuiltinGaussianSplatShaderAssetRelativePath =
|
||||
@@ -156,18 +147,11 @@ const char* GetBuiltinShaderAssetRelativePath(const Containers::String& builtinS
|
||||
if (builtinShaderPath == Containers::String(kBuiltinShadowCasterShaderPath)) {
|
||||
return kBuiltinShadowCasterShaderAssetRelativePath;
|
||||
}
|
||||
#if XCENGINE_ENABLE_RENDERING_EDITOR_SUPPORT
|
||||
if (builtinShaderPath == Containers::String(kBuiltinObjectIdShaderPath)) {
|
||||
return kBuiltinObjectIdShaderAssetRelativePath;
|
||||
}
|
||||
if (builtinShaderPath == Containers::String(kBuiltinObjectIdOutlineShaderPath)) {
|
||||
return kBuiltinObjectIdOutlineShaderAssetRelativePath;
|
||||
}
|
||||
if (builtinShaderPath == Containers::String(kBuiltinSelectionMaskShaderPath)) {
|
||||
return kBuiltinSelectionMaskShaderAssetRelativePath;
|
||||
}
|
||||
if (builtinShaderPath == Containers::String(kBuiltinSelectionOutlineShaderPath)) {
|
||||
return kBuiltinSelectionOutlineShaderAssetRelativePath;
|
||||
}
|
||||
#endif
|
||||
if (builtinShaderPath == Containers::String(kBuiltinSkyboxShaderPath)) {
|
||||
return kBuiltinSkyboxShaderAssetRelativePath;
|
||||
}
|
||||
@@ -731,19 +715,12 @@ Shader* BuildBuiltinShadowCasterShader(const Containers::String& path) {
|
||||
}
|
||||
|
||||
Shader* BuildBuiltinObjectIdShader(const Containers::String& path) {
|
||||
#if XCENGINE_ENABLE_RENDERING_EDITOR_SUPPORT
|
||||
return TryLoadBuiltinShaderFromAsset(path);
|
||||
}
|
||||
|
||||
Shader* BuildBuiltinObjectIdOutlineShader(const Containers::String& path) {
|
||||
return TryLoadBuiltinShaderFromAsset(path);
|
||||
}
|
||||
|
||||
Shader* BuildBuiltinSelectionMaskShader(const Containers::String& path) {
|
||||
return TryLoadBuiltinShaderFromAsset(path);
|
||||
}
|
||||
|
||||
Shader* BuildBuiltinSelectionOutlineShader(const Containers::String& path) {
|
||||
return TryLoadBuiltinShaderFromAsset(path);
|
||||
#else
|
||||
(void)path;
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
Shader* BuildBuiltinSkyboxShader(const Containers::String& path) {
|
||||
@@ -852,22 +829,12 @@ bool TryGetBuiltinShaderPathByShaderName(
|
||||
outPath = GetBuiltinShadowCasterShaderPath();
|
||||
return true;
|
||||
}
|
||||
#if XCENGINE_ENABLE_RENDERING_EDITOR_SUPPORT
|
||||
if (shaderName == "Builtin Object Id") {
|
||||
outPath = GetBuiltinObjectIdShaderPath();
|
||||
return true;
|
||||
}
|
||||
if (shaderName == "Builtin Object Id Outline") {
|
||||
outPath = GetBuiltinObjectIdOutlineShaderPath();
|
||||
return true;
|
||||
}
|
||||
if (shaderName == "Builtin Selection Mask") {
|
||||
outPath = GetBuiltinSelectionMaskShaderPath();
|
||||
return true;
|
||||
}
|
||||
if (shaderName == "Builtin Selection Outline") {
|
||||
outPath = GetBuiltinSelectionOutlineShaderPath();
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
if (shaderName == "Builtin Skybox") {
|
||||
outPath = GetBuiltinSkyboxShaderPath();
|
||||
return true;
|
||||
@@ -948,19 +915,11 @@ Containers::String GetBuiltinShadowCasterShaderPath() {
|
||||
}
|
||||
|
||||
Containers::String GetBuiltinObjectIdShaderPath() {
|
||||
#if XCENGINE_ENABLE_RENDERING_EDITOR_SUPPORT
|
||||
return Containers::String(kBuiltinObjectIdShaderPath);
|
||||
}
|
||||
|
||||
Containers::String GetBuiltinObjectIdOutlineShaderPath() {
|
||||
return Containers::String(kBuiltinObjectIdOutlineShaderPath);
|
||||
}
|
||||
|
||||
Containers::String GetBuiltinSelectionMaskShaderPath() {
|
||||
return Containers::String(kBuiltinSelectionMaskShaderPath);
|
||||
}
|
||||
|
||||
Containers::String GetBuiltinSelectionOutlineShaderPath() {
|
||||
return Containers::String(kBuiltinSelectionOutlineShaderPath);
|
||||
#else
|
||||
return Containers::String();
|
||||
#endif
|
||||
}
|
||||
|
||||
Containers::String GetBuiltinSkyboxShaderPath() {
|
||||
@@ -1083,14 +1042,10 @@ LoadResult CreateBuiltinShaderResource(const Containers::String& path) {
|
||||
shader = BuildBuiltinDepthOnlyShader(path);
|
||||
} else if (path == GetBuiltinShadowCasterShaderPath()) {
|
||||
shader = BuildBuiltinShadowCasterShader(path);
|
||||
#if XCENGINE_ENABLE_RENDERING_EDITOR_SUPPORT
|
||||
} else if (path == GetBuiltinObjectIdShaderPath()) {
|
||||
shader = BuildBuiltinObjectIdShader(path);
|
||||
} else if (path == GetBuiltinObjectIdOutlineShaderPath()) {
|
||||
shader = BuildBuiltinObjectIdOutlineShader(path);
|
||||
} else if (path == GetBuiltinSelectionMaskShaderPath()) {
|
||||
shader = BuildBuiltinSelectionMaskShader(path);
|
||||
} else if (path == GetBuiltinSelectionOutlineShaderPath()) {
|
||||
shader = BuildBuiltinSelectionOutlineShader(path);
|
||||
#endif
|
||||
} else if (path == GetBuiltinSkyboxShaderPath()) {
|
||||
shader = BuildBuiltinSkyboxShader(path);
|
||||
} else if (path == GetBuiltinGaussianSplatShaderPath()) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -144,6 +144,9 @@ set(XCENGINE_SCRIPT_CORE_SOURCES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/AssemblyInfo.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Behaviour.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Camera.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/CameraClearMode.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/CameraProjectionType.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/CameraStackType.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Color.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Component.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Debug.cs
|
||||
@@ -180,13 +183,16 @@ set(XCENGINE_SCRIPT_CORE_SOURCES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Rendering/Core/DirectionalShadowPlanningSettings.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Rendering/Core/DrawingSettings.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Rendering/Core/FinalColorExposureMode.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Rendering/Core/FinalColorOverrideSettings.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Rendering/Core/FinalColorOutputTransferMode.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Rendering/Core/FinalColorSettings.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Rendering/Core/FinalColorToneMappingMode.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Rendering/Core/FilteringSettings.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Rendering/Core/GraphicsSettings.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Rendering/Core/NativeSceneFeaturePassId.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Rendering/Core/RenderSceneSetupContext.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Rendering/Core/RenderPipelineAsset.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Rendering/Core/RenderClearFlags.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Rendering/Core/RenderQueueRange.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Rendering/Core/RenderStateBlock.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Rendering/Core/RenderStateMask.cs
|
||||
@@ -212,6 +218,7 @@ set(XCENGINE_SCRIPT_CORE_SOURCES
|
||||
set(XCENGINE_RENDER_PIPELINES_UNIVERSAL_SOURCES
|
||||
# Universal renderer package
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/CameraData.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/ColorScalePostProcessSettings.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/ColorScalePostProcessRendererFeature.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/BuiltinFinalColorPass.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/BuiltinGaussianSplatRendererFeature.cs
|
||||
@@ -226,12 +233,14 @@ set(XCENGINE_RENDER_PIPELINES_UNIVERSAL_SOURCES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/EnvironmentData.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/FinalColorData.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/LightingData.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/RenderClearFlags.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/RenderEnvironmentMode.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/RenderObjectsFeatureSettings.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/RenderPassEvent.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/RenderObjectsRendererFeature.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/RendererBlock.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/RendererBackedRenderPipeline.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/RendererBackedRenderPipelineAsset.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/RendererBlocks.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/RendererCameraRequestContext.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/RendererDrivenRenderPipeline.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/RendererRecordingContext.cs
|
||||
@@ -242,15 +251,29 @@ set(XCENGINE_RENDER_PIPELINES_UNIVERSAL_SOURCES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRenderPass.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRenderer.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRendererData.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRendererDataCollection.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRendererDataComposition.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRendererFeatureCollection.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRendererFeature.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/StageColorData.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalAdditionalCameraData.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalDefaultRendererFeatureFactory.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalDefaultRendererDataFactory.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalFinalColorSettings.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalFinalOutputBlock.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalMainSceneData.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalMainSceneBlock.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalMainSceneFeatureUtility.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalNativeSceneFeatureController.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalColorScalePostProcessController.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalDepthPrepassBlock.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalPostProcessBlock.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRenderObjectsFeatureController.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRenderPipeline.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRenderer.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRendererData.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRenderPipelineAsset.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalShadowCasterBlock.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalShadowSettings.cs
|
||||
)
|
||||
|
||||
|
||||
@@ -59,6 +59,80 @@ namespace Gameplay
|
||||
}
|
||||
}
|
||||
|
||||
internal static class ProbeScriptableObjectFactory
|
||||
{
|
||||
public static T Create<T>()
|
||||
where T : ScriptableObject
|
||||
{
|
||||
T instance =
|
||||
ScriptableObject.CreateInstance<T>();
|
||||
if (instance != null)
|
||||
{
|
||||
return instance;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException(
|
||||
"Failed to create ScriptableObject probe instance: " +
|
||||
typeof(T).FullName);
|
||||
}
|
||||
|
||||
public static ScriptableRendererData[] CreateRendererDataList(
|
||||
params ScriptableRendererData[] rendererData)
|
||||
{
|
||||
return Compact(rendererData);
|
||||
}
|
||||
|
||||
public static ScriptableRendererFeature[] CreateRendererFeatureList(
|
||||
params ScriptableRendererFeature[] rendererFeatures)
|
||||
{
|
||||
return Compact(rendererFeatures);
|
||||
}
|
||||
|
||||
private static T[] Compact<T>(
|
||||
params T[] items)
|
||||
where T : class
|
||||
{
|
||||
if (items == null ||
|
||||
items.Length == 0)
|
||||
{
|
||||
return Array.Empty<T>();
|
||||
}
|
||||
|
||||
int validCount = 0;
|
||||
for (int i = 0; i < items.Length; ++i)
|
||||
{
|
||||
if (items[i] != null)
|
||||
{
|
||||
validCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (validCount == items.Length)
|
||||
{
|
||||
return items;
|
||||
}
|
||||
|
||||
if (validCount == 0)
|
||||
{
|
||||
return Array.Empty<T>();
|
||||
}
|
||||
|
||||
T[] compactItems = new T[validCount];
|
||||
int writeIndex = 0;
|
||||
for (int i = 0; i < items.Length; ++i)
|
||||
{
|
||||
if (items[i] == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
compactItems[writeIndex++] = items[i];
|
||||
}
|
||||
|
||||
return compactItems;
|
||||
}
|
||||
}
|
||||
|
||||
internal enum SceneInjectionKind
|
||||
{
|
||||
BeforeOpaque,
|
||||
@@ -345,6 +419,84 @@ namespace Gameplay
|
||||
renderer.EnqueuePass(renderPass);
|
||||
}
|
||||
}
|
||||
|
||||
public override void ConfigureCameraFramePlan(
|
||||
ScriptableRenderPipelinePlanningContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool hasPostProcessPass = false;
|
||||
bool hasFinalOutputPass = false;
|
||||
for (int i = 0; i < m_passes.Length; ++i)
|
||||
{
|
||||
ScriptableRenderPass renderPass = m_passes[i];
|
||||
if (renderPass == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (renderPass.SupportsStage(
|
||||
CameraFrameStage.PostProcess))
|
||||
{
|
||||
hasPostProcessPass = true;
|
||||
}
|
||||
|
||||
if (renderPass.SupportsStage(
|
||||
CameraFrameStage.FinalOutput))
|
||||
{
|
||||
hasFinalOutputPass = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool needsGraphManagedPostProcessOutput =
|
||||
context.HasFinalColorProcessing() ||
|
||||
context.IsStageRequested(
|
||||
CameraFrameStage.FinalOutput) ||
|
||||
hasFinalOutputPass;
|
||||
|
||||
if (hasPostProcessPass)
|
||||
{
|
||||
if (!context.IsStageRequested(
|
||||
CameraFrameStage.PostProcess))
|
||||
{
|
||||
context.RequestFullscreenStage(
|
||||
CameraFrameStage.PostProcess,
|
||||
CameraFrameColorSource.MainSceneColor,
|
||||
needsGraphManagedPostProcessOutput);
|
||||
}
|
||||
else if (needsGraphManagedPostProcessOutput &&
|
||||
context.GetStageColorSource(
|
||||
CameraFrameStage.PostProcess) !=
|
||||
CameraFrameColorSource.ExplicitSurface &&
|
||||
!context.UsesGraphManagedOutputColor(
|
||||
CameraFrameStage.PostProcess))
|
||||
{
|
||||
CameraFrameColorSource source =
|
||||
context.GetStageColorSource(
|
||||
CameraFrameStage.PostProcess);
|
||||
context.ClearFullscreenStage(
|
||||
CameraFrameStage.PostProcess);
|
||||
context.RequestFullscreenStage(
|
||||
CameraFrameStage.PostProcess,
|
||||
source,
|
||||
true);
|
||||
}
|
||||
}
|
||||
|
||||
if (hasFinalOutputPass &&
|
||||
!context.IsStageRequested(
|
||||
CameraFrameStage.FinalOutput))
|
||||
{
|
||||
context.RequestFullscreenStage(
|
||||
CameraFrameStage.FinalOutput,
|
||||
hasPostProcessPass
|
||||
? CameraFrameColorSource.PostProcessColor
|
||||
: CameraFrameColorSource.MainSceneColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class ProbeSceneRenderer : ScriptableRenderer
|
||||
@@ -484,13 +636,26 @@ namespace Gameplay
|
||||
internal sealed class ManagedFeaturePassOrderCustomFeature
|
||||
: ScriptableRendererFeature
|
||||
{
|
||||
private readonly ManagedFeaturePassOrderProbePass m_pass;
|
||||
private ManagedFeaturePassOrderProbePass m_pass;
|
||||
|
||||
public ManagedFeaturePassOrderCustomFeature(
|
||||
string token)
|
||||
public string token = string.Empty;
|
||||
|
||||
protected override int ComputeRuntimeStateHash()
|
||||
{
|
||||
int hash =
|
||||
base.ComputeRuntimeStateHash();
|
||||
hash =
|
||||
RuntimeStateHashUtility.Combine(
|
||||
hash,
|
||||
token ?? string.Empty);
|
||||
return hash;
|
||||
}
|
||||
|
||||
public override void Create()
|
||||
{
|
||||
m_pass =
|
||||
new ManagedFeaturePassOrderProbePass(token);
|
||||
new ManagedFeaturePassOrderProbePass(
|
||||
token ?? string.Empty);
|
||||
}
|
||||
|
||||
public override void AddRenderPasses(
|
||||
@@ -504,7 +669,11 @@ namespace Gameplay
|
||||
return;
|
||||
}
|
||||
|
||||
renderer.EnqueuePass(m_pass);
|
||||
CreateInstance();
|
||||
if (m_pass != null)
|
||||
{
|
||||
renderer.EnqueuePass(m_pass);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -524,11 +693,19 @@ namespace Gameplay
|
||||
public ManagedFeaturePassOrderProbeRendererData()
|
||||
: base(false)
|
||||
{
|
||||
rendererFeatures = new ScriptableRendererFeature[]
|
||||
{
|
||||
new ManagedFeaturePassOrderCustomFeature("CustomA"),
|
||||
new ManagedFeaturePassOrderCustomFeature("CustomB")
|
||||
};
|
||||
ManagedFeaturePassOrderCustomFeature customA =
|
||||
ProbeScriptableObjectFactory
|
||||
.Create<ManagedFeaturePassOrderCustomFeature>();
|
||||
ManagedFeaturePassOrderCustomFeature customB =
|
||||
ProbeScriptableObjectFactory
|
||||
.Create<ManagedFeaturePassOrderCustomFeature>();
|
||||
customA.token = "CustomA";
|
||||
customB.token = "CustomB";
|
||||
rendererFeatures =
|
||||
ProbeScriptableObjectFactory
|
||||
.CreateRendererFeatureList(
|
||||
customA,
|
||||
customB);
|
||||
}
|
||||
|
||||
protected override ScriptableRenderer CreateProbeRenderer()
|
||||
@@ -1018,18 +1195,13 @@ namespace Gameplay
|
||||
internal sealed class ManagedUniversalRenderPipelineProbeRendererData
|
||||
: ProbeRendererData
|
||||
{
|
||||
private readonly Vector4 m_postProcessScale;
|
||||
|
||||
public ManagedUniversalRenderPipelineProbeRendererData(
|
||||
Vector4 postProcessScale)
|
||||
{
|
||||
m_postProcessScale = postProcessScale;
|
||||
}
|
||||
public Vector4 postProcessScale =
|
||||
new Vector4(1.03f, 0.98f, 0.94f, 1.0f);
|
||||
|
||||
protected override ScriptableRenderer CreateProbeRenderer()
|
||||
{
|
||||
return new ManagedUniversalRenderPipelineProbe(
|
||||
m_postProcessScale);
|
||||
postProcessScale);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1126,10 +1298,11 @@ namespace Gameplay
|
||||
: base(false)
|
||||
{
|
||||
ManagedRendererInvalidationProbeState.CreateFeatureCallCount++;
|
||||
rendererFeatures = new ScriptableRendererFeature[]
|
||||
{
|
||||
new ManagedRendererInvalidationProbeFeature()
|
||||
};
|
||||
rendererFeatures =
|
||||
ProbeScriptableObjectFactory
|
||||
.CreateRendererFeatureList(
|
||||
ProbeScriptableObjectFactory
|
||||
.Create<ManagedRendererInvalidationProbeFeature>());
|
||||
}
|
||||
|
||||
protected override ScriptableRenderer CreateProbeRenderer()
|
||||
@@ -1205,11 +1378,12 @@ namespace Gameplay
|
||||
: base(false)
|
||||
{
|
||||
m_feature =
|
||||
new ManagedPersistentFeatureProbeRendererFeature();
|
||||
rendererFeatures = new ScriptableRendererFeature[]
|
||||
{
|
||||
m_feature
|
||||
};
|
||||
ProbeScriptableObjectFactory
|
||||
.Create<ManagedPersistentFeatureProbeRendererFeature>();
|
||||
rendererFeatures =
|
||||
ProbeScriptableObjectFactory
|
||||
.CreateRendererFeatureList(
|
||||
m_feature);
|
||||
}
|
||||
|
||||
protected override ScriptableRenderer CreateProbeRenderer()
|
||||
@@ -1260,10 +1434,11 @@ namespace Gameplay
|
||||
public ManagedFeaturePlannedPostProcessRendererData()
|
||||
: base(false)
|
||||
{
|
||||
rendererFeatures = new ScriptableRendererFeature[]
|
||||
{
|
||||
new ManagedFeaturePlannedPostProcessRendererFeature()
|
||||
};
|
||||
rendererFeatures =
|
||||
ProbeScriptableObjectFactory
|
||||
.CreateRendererFeatureList(
|
||||
ProbeScriptableObjectFactory
|
||||
.Create<ManagedFeaturePlannedPostProcessRendererFeature>());
|
||||
}
|
||||
|
||||
protected override ScriptableRenderer CreateProbeRenderer()
|
||||
@@ -1354,10 +1529,11 @@ namespace Gameplay
|
||||
: base(false)
|
||||
{
|
||||
ManagedLifecycleProbeState.CreateFeatureCallCount++;
|
||||
rendererFeatures = new ScriptableRendererFeature[]
|
||||
{
|
||||
new ManagedLifecycleProbeRendererFeature()
|
||||
};
|
||||
rendererFeatures =
|
||||
ProbeScriptableObjectFactory
|
||||
.CreateRendererFeatureList(
|
||||
ProbeScriptableObjectFactory
|
||||
.Create<ManagedLifecycleProbeRendererFeature>());
|
||||
}
|
||||
|
||||
protected override ScriptableRenderer CreateProbeRenderer()
|
||||
@@ -1427,10 +1603,11 @@ namespace Gameplay
|
||||
{
|
||||
public ManagedUniversalLifecycleProbeAsset()
|
||||
{
|
||||
rendererDataList = new ScriptableRendererData[]
|
||||
{
|
||||
new ManagedLifecycleProbeRendererData()
|
||||
};
|
||||
rendererDataList =
|
||||
ProbeScriptableObjectFactory
|
||||
.CreateRendererDataList(
|
||||
ProbeScriptableObjectFactory
|
||||
.Create<ManagedLifecycleProbeRendererData>());
|
||||
}
|
||||
|
||||
protected override ScriptableRenderPipeline CreatePipeline()
|
||||
@@ -1453,10 +1630,11 @@ namespace Gameplay
|
||||
|
||||
public ManagedRenderPipelineProbeAsset()
|
||||
{
|
||||
rendererDataList = new ScriptableRendererData[]
|
||||
{
|
||||
new ManagedRenderPipelineProbeRendererData()
|
||||
};
|
||||
rendererDataList =
|
||||
ProbeScriptableObjectFactory
|
||||
.CreateRendererDataList(
|
||||
ProbeScriptableObjectFactory
|
||||
.Create<ManagedRenderPipelineProbeRendererData>());
|
||||
}
|
||||
|
||||
protected override ScriptableRenderPipeline CreatePipeline()
|
||||
@@ -1472,10 +1650,10 @@ namespace Gameplay
|
||||
public ManagedPostProcessRenderPipelineProbeAsset()
|
||||
{
|
||||
rendererDataList =
|
||||
new ScriptableRendererData[]
|
||||
{
|
||||
new ManagedPostProcessRenderPipelineProbeRendererData()
|
||||
};
|
||||
ProbeScriptableObjectFactory
|
||||
.CreateRendererDataList(
|
||||
ProbeScriptableObjectFactory
|
||||
.Create<ManagedPostProcessRenderPipelineProbeRendererData>());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1499,11 +1677,14 @@ namespace Gameplay
|
||||
|
||||
private ScriptableRendererData[] CreateRendererDataList()
|
||||
{
|
||||
return new ScriptableRendererData[]
|
||||
{
|
||||
new ManagedUniversalRenderPipelineProbeRendererData(
|
||||
postProcessScale)
|
||||
};
|
||||
ManagedUniversalRenderPipelineProbeRendererData rendererData =
|
||||
ProbeScriptableObjectFactory
|
||||
.Create<ManagedUniversalRenderPipelineProbeRendererData>();
|
||||
rendererData.postProcessScale =
|
||||
postProcessScale;
|
||||
return ProbeScriptableObjectFactory
|
||||
.CreateRendererDataList(
|
||||
rendererData);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1514,10 +1695,11 @@ namespace Gameplay
|
||||
{
|
||||
ManagedRendererReuseProbeRendererData.CreateRendererCallCount = 0;
|
||||
ManagedRendererReuseProbeRendererData.SetupRendererCallCount = 0;
|
||||
rendererDataList = new ScriptableRendererData[]
|
||||
{
|
||||
new ManagedRendererReuseProbeRendererData()
|
||||
};
|
||||
rendererDataList =
|
||||
ProbeScriptableObjectFactory
|
||||
.CreateRendererDataList(
|
||||
ProbeScriptableObjectFactory
|
||||
.Create<ManagedRendererReuseProbeRendererData>());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1530,11 +1712,13 @@ namespace Gameplay
|
||||
public ManagedRendererInvalidationProbeAsset()
|
||||
{
|
||||
ManagedRendererInvalidationProbeState.Reset();
|
||||
m_rendererData = new ManagedRendererInvalidationProbeRendererData();
|
||||
rendererDataList = new ScriptableRendererData[]
|
||||
{
|
||||
m_rendererData
|
||||
};
|
||||
m_rendererData =
|
||||
ProbeScriptableObjectFactory
|
||||
.Create<ManagedRendererInvalidationProbeRendererData>();
|
||||
rendererDataList =
|
||||
ProbeScriptableObjectFactory
|
||||
.CreateRendererDataList(
|
||||
m_rendererData);
|
||||
}
|
||||
|
||||
protected override ScriptableRenderPipeline
|
||||
@@ -1565,11 +1749,12 @@ namespace Gameplay
|
||||
{
|
||||
ManagedPersistentFeatureProbeState.Reset();
|
||||
m_rendererData =
|
||||
new ManagedPersistentFeatureProbeRendererData();
|
||||
rendererDataList = new ScriptableRendererData[]
|
||||
{
|
||||
m_rendererData
|
||||
};
|
||||
ProbeScriptableObjectFactory
|
||||
.Create<ManagedPersistentFeatureProbeRendererData>();
|
||||
rendererDataList =
|
||||
ProbeScriptableObjectFactory
|
||||
.CreateRendererDataList(
|
||||
m_rendererData);
|
||||
}
|
||||
|
||||
public void InvalidateDefaultRendererForTest()
|
||||
@@ -1589,10 +1774,11 @@ namespace Gameplay
|
||||
public ManagedFeaturePassOrderProbeAsset()
|
||||
{
|
||||
ManagedFeaturePassOrderProbeState.Reset();
|
||||
rendererDataList = new ScriptableRendererData[]
|
||||
{
|
||||
new ManagedFeaturePassOrderProbeRendererData()
|
||||
};
|
||||
rendererDataList =
|
||||
ProbeScriptableObjectFactory
|
||||
.CreateRendererDataList(
|
||||
ProbeScriptableObjectFactory
|
||||
.Create<ManagedFeaturePassOrderProbeRendererData>());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1684,10 +1870,10 @@ namespace Gameplay
|
||||
public ManagedPlannedFullscreenRenderPipelineProbeAsset()
|
||||
{
|
||||
rendererDataList =
|
||||
new ScriptableRendererData[]
|
||||
{
|
||||
new ManagedPlannedFullscreenRenderPipelineProbeRendererData()
|
||||
};
|
||||
ProbeScriptableObjectFactory
|
||||
.CreateRendererDataList(
|
||||
ProbeScriptableObjectFactory
|
||||
.Create<ManagedPlannedFullscreenRenderPipelineProbeRendererData>());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1697,10 +1883,10 @@ namespace Gameplay
|
||||
public ManagedFeaturePlannedPostProcessProbeAsset()
|
||||
{
|
||||
rendererDataList =
|
||||
new ScriptableRendererData[]
|
||||
{
|
||||
new ManagedFeaturePlannedPostProcessRendererData()
|
||||
};
|
||||
ProbeScriptableObjectFactory
|
||||
.CreateRendererDataList(
|
||||
ProbeScriptableObjectFactory
|
||||
.Create<ManagedFeaturePlannedPostProcessRendererData>());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1710,10 +1896,10 @@ namespace Gameplay
|
||||
public ManagedClearedPostProcessRenderPipelineProbeAsset()
|
||||
{
|
||||
rendererDataList =
|
||||
new ScriptableRendererData[]
|
||||
{
|
||||
new ManagedPlannedFullscreenRenderPipelineProbeRendererData()
|
||||
};
|
||||
ProbeScriptableObjectFactory
|
||||
.CreateRendererDataList(
|
||||
ProbeScriptableObjectFactory
|
||||
.Create<ManagedPlannedFullscreenRenderPipelineProbeRendererData>());
|
||||
}
|
||||
|
||||
protected override void ConfigureCameraFramePlan(
|
||||
@@ -1760,10 +1946,11 @@ namespace Gameplay
|
||||
{
|
||||
public ManagedCameraRequestConfiguredRenderPipelineProbeAsset()
|
||||
{
|
||||
rendererDataList = new ScriptableRendererData[]
|
||||
{
|
||||
new ManagedCameraRequestConfiguredRendererData()
|
||||
};
|
||||
rendererDataList =
|
||||
ProbeScriptableObjectFactory
|
||||
.CreateRendererDataList(
|
||||
ProbeScriptableObjectFactory
|
||||
.Create<ManagedCameraRequestConfiguredRendererData>());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1773,10 +1960,10 @@ namespace Gameplay
|
||||
public ManagedRenderContextCameraDataProbeAsset()
|
||||
{
|
||||
rendererDataList =
|
||||
new ScriptableRendererData[]
|
||||
{
|
||||
new ManagedRenderContextCameraDataProbeRendererData()
|
||||
};
|
||||
ProbeScriptableObjectFactory
|
||||
.CreateRendererDataList(
|
||||
ProbeScriptableObjectFactory
|
||||
.Create<ManagedRenderContextCameraDataProbeRendererData>());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1785,10 +1972,11 @@ namespace Gameplay
|
||||
{
|
||||
public ManagedFinalColorRenderPipelineProbeAsset()
|
||||
{
|
||||
rendererDataList = new ScriptableRendererData[]
|
||||
{
|
||||
new ManagedRenderPipelineProbeRendererData()
|
||||
};
|
||||
rendererDataList =
|
||||
ProbeScriptableObjectFactory
|
||||
.CreateRendererDataList(
|
||||
ProbeScriptableObjectFactory
|
||||
.Create<ManagedRenderPipelineProbeRendererData>());
|
||||
}
|
||||
|
||||
protected override FinalColorSettings GetDefaultFinalColorSettings()
|
||||
@@ -1803,10 +1991,10 @@ namespace Gameplay
|
||||
public ManagedRenderContextFinalColorDataProbeAsset()
|
||||
{
|
||||
rendererDataList =
|
||||
new ScriptableRendererData[]
|
||||
{
|
||||
new ManagedRenderContextCameraDataProbeRendererData()
|
||||
};
|
||||
ProbeScriptableObjectFactory
|
||||
.CreateRendererDataList(
|
||||
ProbeScriptableObjectFactory
|
||||
.Create<ManagedRenderContextCameraDataProbeRendererData>());
|
||||
}
|
||||
|
||||
protected override FinalColorSettings GetDefaultFinalColorSettings()
|
||||
@@ -1821,10 +2009,10 @@ namespace Gameplay
|
||||
public ManagedRenderContextStageColorDataProbeAsset()
|
||||
{
|
||||
rendererDataList =
|
||||
new ScriptableRendererData[]
|
||||
{
|
||||
new ManagedRenderContextStageColorDataProbeRendererData()
|
||||
};
|
||||
ProbeScriptableObjectFactory
|
||||
.CreateRendererDataList(
|
||||
ProbeScriptableObjectFactory
|
||||
.Create<ManagedRenderContextStageColorDataProbeRendererData>());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1966,7 +2154,8 @@ namespace Gameplay
|
||||
public void Start()
|
||||
{
|
||||
GraphicsSettings.renderPipelineAsset =
|
||||
new ManagedRendererInvalidationProbeAsset();
|
||||
ProbeScriptableObjectFactory
|
||||
.Create<ManagedRendererInvalidationProbeAsset>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2035,7 +2224,8 @@ namespace Gameplay
|
||||
public void Start()
|
||||
{
|
||||
GraphicsSettings.renderPipelineAsset =
|
||||
new ManagedPersistentFeatureProbeAsset();
|
||||
ProbeScriptableObjectFactory
|
||||
.Create<ManagedPersistentFeatureProbeAsset>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2088,7 +2278,8 @@ namespace Gameplay
|
||||
public void Start()
|
||||
{
|
||||
GraphicsSettings.renderPipelineAsset =
|
||||
new ManagedFeaturePassOrderProbeAsset();
|
||||
ProbeScriptableObjectFactory
|
||||
.Create<ManagedFeaturePassOrderProbeAsset>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2111,7 +2302,8 @@ namespace Gameplay
|
||||
public void Start()
|
||||
{
|
||||
GraphicsSettings.renderPipelineAsset =
|
||||
new ManagedAssetInvalidationProbeAsset();
|
||||
ProbeScriptableObjectFactory
|
||||
.Create<ManagedAssetInvalidationProbeAsset>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2193,7 +2385,8 @@ namespace Gameplay
|
||||
ManagedRendererReuseProbeRendererData.CreateRendererCallCount = 0;
|
||||
ManagedRendererReuseProbeRendererData.SetupRendererCallCount = 0;
|
||||
GraphicsSettings.renderPipelineAsset =
|
||||
new ManagedRenderPipelineProbeAsset();
|
||||
ProbeScriptableObjectFactory
|
||||
.Create<ManagedRenderPipelineProbeAsset>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2273,7 +2466,8 @@ namespace Gameplay
|
||||
{
|
||||
ManagedLifecycleProbeState.Reset();
|
||||
GraphicsSettings.renderPipelineAsset =
|
||||
new ManagedUniversalLifecycleProbeAsset();
|
||||
ProbeScriptableObjectFactory
|
||||
.Create<ManagedUniversalLifecycleProbeAsset>();
|
||||
}
|
||||
|
||||
public void Update()
|
||||
@@ -2525,11 +2719,13 @@ namespace Gameplay
|
||||
ManagedUniversalRenderPipelineProbe.RecordPostProcessCallCount = 0;
|
||||
ManagedUniversalRenderPipelineProbe.LastPostProcessScale =
|
||||
new Vector4(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
ManagedUniversalRenderPipelineProbeAsset asset =
|
||||
ProbeScriptableObjectFactory
|
||||
.Create<ManagedUniversalRenderPipelineProbeAsset>();
|
||||
asset.postProcessScale =
|
||||
new Vector4(1.11f, 0.97f, 0.93f, 1.0f);
|
||||
GraphicsSettings.renderPipelineAsset =
|
||||
new ManagedUniversalRenderPipelineProbeAsset
|
||||
{
|
||||
postProcessScale = new Vector4(1.11f, 0.97f, 0.93f, 1.0f)
|
||||
};
|
||||
asset;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
@@ -2583,7 +2779,8 @@ namespace Gameplay
|
||||
{
|
||||
CameraDataObservationPass.Reset();
|
||||
GraphicsSettings.renderPipelineAsset =
|
||||
new ManagedRenderContextCameraDataProbeAsset();
|
||||
ProbeScriptableObjectFactory
|
||||
.Create<ManagedRenderContextCameraDataProbeAsset>();
|
||||
}
|
||||
|
||||
public void Update()
|
||||
@@ -2655,7 +2852,8 @@ namespace Gameplay
|
||||
{
|
||||
CameraDataObservationPass.Reset();
|
||||
GraphicsSettings.renderPipelineAsset =
|
||||
new ManagedRenderContextCameraDataProbeAsset();
|
||||
ProbeScriptableObjectFactory
|
||||
.Create<ManagedRenderContextCameraDataProbeAsset>();
|
||||
}
|
||||
|
||||
public void Update()
|
||||
@@ -2699,7 +2897,8 @@ namespace Gameplay
|
||||
{
|
||||
CameraDataObservationPass.Reset();
|
||||
GraphicsSettings.renderPipelineAsset =
|
||||
new ManagedRenderContextCameraDataProbeAsset();
|
||||
ProbeScriptableObjectFactory
|
||||
.Create<ManagedRenderContextCameraDataProbeAsset>();
|
||||
}
|
||||
|
||||
public void Update()
|
||||
@@ -2742,7 +2941,8 @@ namespace Gameplay
|
||||
{
|
||||
CameraDataObservationPass.Reset();
|
||||
GraphicsSettings.renderPipelineAsset =
|
||||
new ManagedRenderContextCameraDataProbeAsset();
|
||||
ProbeScriptableObjectFactory
|
||||
.Create<ManagedRenderContextCameraDataProbeAsset>();
|
||||
}
|
||||
|
||||
public void Update()
|
||||
@@ -2816,7 +3016,8 @@ namespace Gameplay
|
||||
{
|
||||
CameraDataObservationPass.Reset();
|
||||
GraphicsSettings.renderPipelineAsset =
|
||||
new ManagedRenderContextFinalColorDataProbeAsset();
|
||||
ProbeScriptableObjectFactory
|
||||
.Create<ManagedRenderContextFinalColorDataProbeAsset>();
|
||||
}
|
||||
|
||||
public void Update()
|
||||
@@ -2867,7 +3068,8 @@ namespace Gameplay
|
||||
{
|
||||
StageColorObservationPass.Reset();
|
||||
GraphicsSettings.renderPipelineAsset =
|
||||
new ManagedRenderContextStageColorDataProbeAsset();
|
||||
ProbeScriptableObjectFactory
|
||||
.Create<ManagedRenderContextStageColorDataProbeAsset>();
|
||||
}
|
||||
|
||||
public void Update()
|
||||
|
||||
@@ -8,40 +8,41 @@ namespace XCEngine.Rendering.Universal
|
||||
{
|
||||
public RenderPassEvent passEvent =
|
||||
RenderPassEvent.BeforeRenderingTransparents;
|
||||
|
||||
private NativeSceneFeaturePass m_pass;
|
||||
private readonly UniversalNativeSceneFeatureController m_controller =
|
||||
new UniversalNativeSceneFeatureController(
|
||||
NativeSceneFeaturePassId
|
||||
.BuiltinGaussianSplat);
|
||||
|
||||
protected override int ComputeRuntimeStateHash()
|
||||
{
|
||||
int hash =
|
||||
base.ComputeRuntimeStateHash();
|
||||
hash =
|
||||
RuntimeStateHashUtility.Combine(
|
||||
hash,
|
||||
(int)passEvent);
|
||||
return hash;
|
||||
return m_controller.AppendRuntimeStateHash(
|
||||
hash,
|
||||
passEvent);
|
||||
}
|
||||
|
||||
public override void Create()
|
||||
{
|
||||
m_pass = new NativeSceneFeaturePass(
|
||||
"BuiltinGaussianSplatPass",
|
||||
passEvent);
|
||||
m_controller.Create(passEvent);
|
||||
}
|
||||
|
||||
public override void AddRenderPasses(
|
||||
ScriptableRenderer renderer,
|
||||
RenderingData renderingData)
|
||||
{
|
||||
if (renderer == null)
|
||||
if (renderer == null ||
|
||||
renderingData == null ||
|
||||
!renderingData.isMainSceneStage)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CreateInstance();
|
||||
|
||||
m_pass.Configure(passEvent);
|
||||
renderer.EnqueuePass(m_pass);
|
||||
m_controller.EnqueuePass(
|
||||
renderer,
|
||||
renderingData,
|
||||
passEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,40 +8,41 @@ namespace XCEngine.Rendering.Universal
|
||||
{
|
||||
public RenderPassEvent passEvent =
|
||||
RenderPassEvent.BeforeRenderingTransparents;
|
||||
|
||||
private NativeSceneFeaturePass m_pass;
|
||||
private readonly UniversalNativeSceneFeatureController m_controller =
|
||||
new UniversalNativeSceneFeatureController(
|
||||
NativeSceneFeaturePassId
|
||||
.BuiltinVolumetric);
|
||||
|
||||
protected override int ComputeRuntimeStateHash()
|
||||
{
|
||||
int hash =
|
||||
base.ComputeRuntimeStateHash();
|
||||
hash =
|
||||
RuntimeStateHashUtility.Combine(
|
||||
hash,
|
||||
(int)passEvent);
|
||||
return hash;
|
||||
return m_controller.AppendRuntimeStateHash(
|
||||
hash,
|
||||
passEvent);
|
||||
}
|
||||
|
||||
public override void Create()
|
||||
{
|
||||
m_pass = new NativeSceneFeaturePass(
|
||||
"BuiltinVolumetricPass",
|
||||
passEvent);
|
||||
m_controller.Create(passEvent);
|
||||
}
|
||||
|
||||
public override void AddRenderPasses(
|
||||
ScriptableRenderer renderer,
|
||||
RenderingData renderingData)
|
||||
{
|
||||
if (renderer == null)
|
||||
if (renderer == null ||
|
||||
renderingData == null ||
|
||||
!renderingData.isMainSceneStage)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CreateInstance();
|
||||
|
||||
m_pass.Configure(passEvent);
|
||||
renderer.EnqueuePass(m_pass);
|
||||
m_controller.EnqueuePass(
|
||||
renderer,
|
||||
renderingData,
|
||||
passEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using XCEngine;
|
||||
using XCEngine.Rendering;
|
||||
|
||||
namespace XCEngine.Rendering.Universal
|
||||
{
|
||||
|
||||
@@ -6,24 +6,31 @@ namespace XCEngine.Rendering.Universal
|
||||
internal sealed class ColorScalePostProcessPass
|
||||
: ScriptableRenderPass
|
||||
{
|
||||
private readonly ColorScalePostProcessRendererFeature m_feature;
|
||||
private Vector4 m_colorScale = new Vector4(
|
||||
1.0f,
|
||||
1.0f,
|
||||
1.0f,
|
||||
1.0f);
|
||||
|
||||
public ColorScalePostProcessPass(
|
||||
ColorScalePostProcessRendererFeature feature)
|
||||
public ColorScalePostProcessPass()
|
||||
{
|
||||
m_feature = feature;
|
||||
renderPassEvent =
|
||||
RenderPassEvent.BeforeRenderingPostProcessing;
|
||||
}
|
||||
|
||||
public void Configure(
|
||||
Vector4 colorScale)
|
||||
{
|
||||
m_colorScale = colorScale;
|
||||
}
|
||||
|
||||
protected override bool RecordRenderGraph(
|
||||
ScriptableRenderContext context,
|
||||
RenderingData renderingData)
|
||||
{
|
||||
if (context == null ||
|
||||
renderingData == null ||
|
||||
!renderingData.isPostProcessStage ||
|
||||
m_feature == null)
|
||||
!renderingData.isPostProcessStage)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -32,7 +39,7 @@ namespace XCEngine.Rendering.Universal
|
||||
context,
|
||||
context.sourceColorTexture,
|
||||
context.primaryColorTarget,
|
||||
m_feature.colorScale,
|
||||
m_colorScale,
|
||||
"Universal.ColorScalePostProcess");
|
||||
}
|
||||
}
|
||||
@@ -40,8 +47,9 @@ namespace XCEngine.Rendering.Universal
|
||||
public sealed class ColorScalePostProcessRendererFeature
|
||||
: ScriptableRendererFeature
|
||||
{
|
||||
private ColorScalePostProcessPass m_pass;
|
||||
|
||||
private readonly UniversalColorScalePostProcessController
|
||||
m_controller =
|
||||
new UniversalColorScalePostProcessController();
|
||||
public Vector4 colorScale = new Vector4(
|
||||
1.0f,
|
||||
1.0f,
|
||||
@@ -52,17 +60,22 @@ namespace XCEngine.Rendering.Universal
|
||||
{
|
||||
int hash =
|
||||
base.ComputeRuntimeStateHash();
|
||||
hash =
|
||||
RuntimeStateHashUtility.Combine(
|
||||
hash,
|
||||
colorScale);
|
||||
return hash;
|
||||
return m_controller.AppendRuntimeStateHash(
|
||||
hash,
|
||||
BuildSettings());
|
||||
}
|
||||
|
||||
public override void Create()
|
||||
{
|
||||
m_pass =
|
||||
new ColorScalePostProcessPass(this);
|
||||
m_controller.Create(
|
||||
BuildSettings());
|
||||
}
|
||||
|
||||
public override void ConfigureCameraFramePlan(
|
||||
ScriptableRenderPipelinePlanningContext context)
|
||||
{
|
||||
m_controller.ConfigureCameraFramePlan(
|
||||
context);
|
||||
}
|
||||
|
||||
public override void AddRenderPasses(
|
||||
@@ -70,14 +83,25 @@ namespace XCEngine.Rendering.Universal
|
||||
RenderingData renderingData)
|
||||
{
|
||||
if (renderer == null ||
|
||||
renderingData == null)
|
||||
renderingData == null ||
|
||||
!renderingData.isPostProcessStage)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CreateInstance();
|
||||
m_controller.EnqueuePass(
|
||||
renderer,
|
||||
renderingData,
|
||||
BuildSettings());
|
||||
}
|
||||
|
||||
renderer.EnqueuePass(m_pass);
|
||||
private ColorScalePostProcessSettings BuildSettings()
|
||||
{
|
||||
return new ColorScalePostProcessSettings
|
||||
{
|
||||
colorScale = colorScale
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user