71 Commits

Author SHA1 Message Date
e63457c72b new_editor: remove win32 no-op redundancy 2026-04-23 01:44:22 +08:00
514dee10cc Fix editor text caret measurement 2026-04-23 01:43:23 +08:00
82c39e2521 Fix XCUIEditorApp tree host source registration 2026-04-23 01:23:48 +08:00
d52eefe2ff Unify new editor tree filter host 2026-04-23 01:12:36 +08:00
03e0b362f7 new_editor: stabilize resize lifecycle groundwork 2026-04-23 00:36:28 +08:00
c10367a42e fix(new_editor): stabilize inspector add-component layout and project browser scrolling 2026-04-22 23:30:17 +08:00
b0d3141eee docs: archive expired new_editor plans 2026-04-22 22:58:34 +08:00
1c77781ac4 fix(new_editor): restore editor panel scrolling and archive workspace plan 2026-04-22 22:53:01 +08:00
39b083f7f7 new_editor: retire xcui editor lib and narrow project reconcile 2026-04-22 22:50:56 +08:00
3048c7cc90 feat(new_editor): add standalone add-component utility window 2026-04-22 22:07:02 +08:00
865a35e4d0 Refactor new_editor vector field family 2026-04-22 21:48:35 +08:00
191a7b03d2 docs(plan): archive utility window semantic split plan 2026-04-22 21:17:18 +08:00
ad98660b1d fix(new_editor): reduce live resize jitter 2026-04-22 21:08:36 +08:00
34c635ba22 refactor(new_editor): split utility window semantics from workspace 2026-04-22 21:02:24 +08:00
dcede7f975 fix(new_editor): finalize synchronous resize presentation cleanup 2026-04-22 20:34:31 +08:00
e251b77d0d refactor(new_editor): extract window content boundary 2026-04-22 20:32:56 +08:00
6efbaf450e new_editor: deduplicate hosted panel command focus 2026-04-22 18:42:46 +08:00
c1e7a0d49f new_editor: remove tree panel behavior layer 2026-04-22 18:37:05 +08:00
6d97f4fc3a new_editor: build window mutation input from live state 2026-04-22 17:49:38 +08:00
1a3b3577fd new_editor: decouple workspace event sync 2026-04-22 16:37:20 +08:00
c42fd4d48b new_editor: reduce app shell capture plumbing 2026-04-22 16:34:28 +08:00
2c2a8b8669 new_editor: close editor-layer app boundary 2026-04-22 16:24:59 +08:00
a805803858 docs: archive rendering editor support boundary plan 2026-04-22 16:17:50 +08:00
ab87ad85d7 rendering: formalize editor support object id boundary 2026-04-22 16:17:27 +08:00
17326f455e refactor(new_editor): finish shell runtime stage split 2026-04-22 15:15:40 +08:00
77fe0a97a4 refactor(new_editor): split shell runtime update stages 2026-04-22 15:01:03 +08:00
2a8448cfbc refactor(new_editor): extract shell draw composer 2026-04-22 14:53:24 +08:00
4372c1ce7b refactor(new_editor): centralize window lifecycle ownership 2026-04-22 14:47:48 +08:00
b44f5ca9fc refactor(new_editor): centralize win32 frame execution 2026-04-22 14:37:25 +08:00
4a42b757c7 docs(new_editor): plan frame lifecycle runtime refactor 2026-04-22 13:03:28 +08:00
6f877b70b4 docs(new_editor): record system audit findings 2026-04-22 13:03:27 +08:00
5f75879250 docs(new_editor): archive architecture closure plan 2026-04-22 13:03:26 +08:00
a46554842d refactor(srp): formalize default renderer compositions 2026-04-22 12:50:48 +08:00
6f370beb0a refactor(srp): formalize default renderer asset composition 2026-04-22 12:44:39 +08:00
173ab89158 new_editor: close startup screenshot ownership 2026-04-22 02:47:28 +08:00
b863dfe727 new_editor: unify d3d12 ui primitive pipeline 2026-04-22 02:47:27 +08:00
d21f573cc5 docs(new_editor): archive legacy closure plan 2026-04-22 02:47:26 +08:00
8b646bf30a refactor(srp): centralize renderer data collections 2026-04-22 02:40:51 +08:00
3187ccbfe1 refactor(srp): formalize renderer feature collections 2026-04-22 02:31:07 +08:00
6950bd15c2 refactor(srp): split feature runtime controllers 2026-04-22 02:23:33 +08:00
9b4a302f6a refactor(srp): unify main-scene feature injection 2026-04-22 02:15:03 +08:00
36c4ae414b new_editor: close legacy d2d host path 2026-04-22 02:14:26 +08:00
35e7602eb8 refactor(srp): centralize post-process stage planning 2026-04-22 02:08:09 +08:00
a231bf1fe1 refactor(srp): extract universal renderer block owners 2026-04-22 02:00:25 +08:00
eb636dbd89 refactor(srp): scope managed pass collection to active stage 2026-04-22 01:53:25 +08:00
0ec0eff90c new_editor: unify shared UI text measurement semantics 2026-04-22 01:50:00 +08:00
26e75e093e docs: archive completed and superseded new_editor plans 2026-04-22 01:49:45 +08:00
99eae1fe9f refactor(srp): formalize renderer block recording 2026-04-22 01:43:54 +08:00
33e9041d60 refactor(srp): require explicit managed renderer stage planning 2026-04-22 01:23:52 +08:00
9760bcb00e new_editor: fix adaptive tab width semantics 2026-04-22 01:12:46 +08:00
ed90911a5c refactor(srp): resolve imported render graph textures through multi-view runtime 2026-04-22 01:05:38 +08:00
4dda59a510 refactor(srp): bind managed fullscreen auxiliary inputs through render graph 2026-04-22 00:43:25 +08:00
b40eeb32b8 docs: add new editor closure notes and reference captures 2026-04-22 00:22:45 +08:00
bc47e6e5ac tests: remove legacy test tree 2026-04-22 00:22:32 +08:00
8bfca5e8f2 new_editor: isolate project panel state and harden runtime reset 2026-04-22 00:19:35 +08:00
fff33185b9 new_editor: tighten d3d12 ui frame resource handling 2026-04-22 00:19:19 +08:00
0411cd0ec1 new_editor: stabilize multi-window host lifecycle 2026-04-22 00:19:06 +08:00
08ff505b67 refactor(srp): add depth-aware managed fullscreen raster graph bridge 2026-04-22 00:07:10 +08:00
1bbbc22bcb refactor(rendering): treat object id as top-level tooling pass 2026-04-21 23:34:03 +08:00
76452a9c73 refactor(srp): move standalone stage fallback ownership into backend 2026-04-21 23:22:29 +08:00
a2d21e69b6 refactor(srp): move native scene feature registration into backend setup 2026-04-21 23:06:17 +08:00
f1d7e879ac refactor(srp): replace native scene feature strings with ids 2026-04-21 22:52:08 +08:00
d75bd95e89 refactor(srp): move final color policy into request seam 2026-04-21 22:34:40 +08:00
808335126f refactor(srp): make URP scene setup policy explicit 2026-04-21 22:03:24 +08:00
4b105ec88f fix(new_editor): stabilize detached window close lifecycle 2026-04-21 21:47:05 +08:00
ee1f817dc6 refactor(srp): tighten request and scene setup seams 2026-04-21 21:42:03 +08:00
01dabcf6b0 fix(new_editor): tune native d3d12 text sizing 2026-04-21 21:37:52 +08:00
0f84e52c21 refactor(new_editor): unify d3d12 text run caching 2026-04-21 21:07:06 +08:00
5d3d52496e fix(urp): prevent shadow fallback when shadow caster is disabled 2026-04-21 21:05:27 +08:00
75cf48f4fe refactor(rendering): remove unused pipeline factory registries 2026-04-21 20:57:29 +08:00
13ad95dc23 refactor(srp): unify probe scriptableobject authoring 2026-04-21 20:51:53 +08:00
1349 changed files with 20689 additions and 3528838 deletions

View File

@@ -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"

View File

@@ -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

View File

@@ -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 层补丁

View 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。

View File

@@ -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.

View File

@@ -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 supporteditor 负责消费”。
### 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` 只负责消费这层能力
这才是和成熟引擎一致的分层方向。

View 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. 构建、启动、截图验证全部通过

View File

@@ -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

View File

@@ -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 users persistent expansion state
5. clearing the query restores the normal tree view and the panels 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 hosts 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

View File

@@ -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

View File

@@ -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.

View File

@@ -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`

View File

@@ -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

View File

@@ -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

View File

@@ -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` 闭环

View File

@@ -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
只有按这个顺序,才能既不破坏功能,又从根上消灭这套双写宿主残片。

View File

@@ -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.

View File

@@ -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.

View 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再迁移实现。

View File

@@ -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 秒通过

View File

@@ -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 秒通过

View File

@@ -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 冒烟通过

View File

@@ -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 冒烟通过。

View File

@@ -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 冒烟通过。

View File

@@ -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`

View File

@@ -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 aspectcolor / 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 收口计划。

View File

@@ -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`

View File

@@ -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`

View File

@@ -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`

View File

@@ -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`

View File

@@ -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`

View File

@@ -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 不创建 RTVcolor 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

View File

@@ -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 冒烟通过。

View File

@@ -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 秒通过

View File

@@ -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 冒烟通过

View File

@@ -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 冒烟通过。

View File

@@ -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 冒烟通过

View File

@@ -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 statehost 仍可能回落到 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`

View File

@@ -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 冒烟通过。

View File

@@ -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`

View File

@@ -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 冒烟通过。

View File

@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

BIN
docs/used/color.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

View File

@@ -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()

View File

@@ -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;

View File

@@ -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;

View File

@@ -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 {

View File

@@ -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; }

View File

@@ -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(

View File

@@ -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(

View File

@@ -32,6 +32,7 @@ private:
RenderGraphTextureDesc desc = {};
RenderGraphTextureKind kind = RenderGraphTextureKind::Transient;
RHI::RHIResourceView* importedView = nullptr;
RHI::RHITexture* importedTexture = nullptr;
RenderGraphImportedTextureOptions importedOptions = {};
};

View File

@@ -48,6 +48,7 @@ private:
RenderGraphTextureDesc desc = {};
RenderGraphTextureKind kind = RenderGraphTextureKind::Transient;
RHI::RHIResourceView* importedView = nullptr;
RHI::RHITexture* importedTexture = nullptr;
RenderGraphImportedTextureOptions importedOptions = {};
};

View File

@@ -104,7 +104,7 @@ struct RenderGraphTextureTransitionPlan {
struct RenderGraphExecutionContext {
const RenderContext& renderContext;
const RenderGraphRuntimeResources* runtimeResources = nullptr;
RenderGraphRuntimeResources* runtimeResources = nullptr;
RHI::RHIResourceView* ResolveTextureView(
RenderGraphTextureHandle handle,

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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(

View File

@@ -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:

View File

@@ -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,
{}
};
}

View File

@@ -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

View File

@@ -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,

View File

@@ -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;
}

View 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

View File

@@ -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();

View File

@@ -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;

View File

@@ -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(),

View File

@@ -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;
}

View File

@@ -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();

View File

@@ -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());

View File

@@ -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()));
}
}

View File

@@ -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 ||

View File

@@ -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

View File

@@ -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

View File

@@ -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);

View File

@@ -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));

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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);

View File

@@ -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;

View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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 {

View File

@@ -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)) {

View File

@@ -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;
}

View File

@@ -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

BIN
font.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 KiB

View File

@@ -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
)

View File

@@ -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()

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -1,4 +1,5 @@
using XCEngine;
using XCEngine.Rendering;
namespace XCEngine.Rendering.Universal
{

View File

@@ -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