docs(plan): add new_editor d3d12 ui root refactor plan
This commit is contained in:
@@ -0,0 +1,443 @@
|
||||
# NewEditor D3D12 UI Root Architecture Refactor Plan
|
||||
|
||||
Date: `2026-04-21`
|
||||
|
||||
Supersedes:
|
||||
- `docs/used/NewEditor_D3D12_UI_Pass_RefactorPlan_Archived_2026-04-21.md`
|
||||
|
||||
## Goal
|
||||
|
||||
Keep `new_editor` on the native `D3D12` main-window path and refactor the UI stack from the root so the renderer can actually reach native-backend performance ceilings.
|
||||
|
||||
This plan is not a patch plan. It is a structural replacement plan.
|
||||
|
||||
## Hard Constraints
|
||||
|
||||
- Do not roll the main editor window back to `D2D` or `D3D11On12`.
|
||||
- Do not keep long-term fallback-heavy mixed rendering for the main window.
|
||||
- Do not treat scene rendering optimization as part of this task.
|
||||
- Do not optimize around symptoms while preserving the current multi-rebuild architecture.
|
||||
- Do not touch unrelated dirty worktree changes outside the scoped editor/UI files.
|
||||
|
||||
## Confirmed Root Problems
|
||||
|
||||
### 1. The editor frame graph rebuilds shell/workspace layout too many times per frame
|
||||
|
||||
Confirmed call-chain facts:
|
||||
|
||||
- `new_editor/app/Platform/Win32/EditorWindow.cpp`
|
||||
- `new_editor/app/Platform/Win32/EditorWindowFrameOrchestrator.cpp`
|
||||
- `new_editor/app/Composition/EditorShellRuntime.cpp`
|
||||
- `new_editor/src/Shell/UIEditorShellInteraction.cpp`
|
||||
- `new_editor/src/Workspace/UIEditorWorkspaceInteraction.cpp`
|
||||
- `new_editor/src/Workspace/UIEditorWorkspaceCompose.cpp`
|
||||
- `new_editor/src/Docking/UIEditorDockHostInteraction.cpp`
|
||||
- `new_editor/src/Docking/UIEditorDockHost.cpp`
|
||||
|
||||
Current behavior:
|
||||
|
||||
- the shell request is rebuilt multiple times inside one frame
|
||||
- the workspace compose path is rebuilt again for input ownership preview and again for final compose
|
||||
- dock host interaction rebuilds dock layout twice inside one rebuild
|
||||
- the renderer then consumes a fresh immediate `UIDrawData` again instead of a retained render product
|
||||
|
||||
Result:
|
||||
|
||||
- CPU cost scales with repeated full-tree work before rendering even begins
|
||||
- a fast renderer still receives expensive, redundant input
|
||||
|
||||
### 2. The current native D3D12 UI renderer is not GPU-native in architecture
|
||||
|
||||
Confirmed files:
|
||||
|
||||
- `new_editor/app/Rendering/D3D12/D3D12UiRenderer.cpp`
|
||||
- `new_editor/app/Rendering/D3D12/D3D12UiTextureHost.cpp`
|
||||
- `new_editor/app/Rendering/Native/NativeRenderer.cpp`
|
||||
|
||||
Current behavior:
|
||||
|
||||
- rounded rects, circles, outlines and fills are tessellated on the CPU every frame
|
||||
- text is cached as `string -> texture`, not `glyph -> atlas`
|
||||
- rasterized text misses go through `DirectWrite + WIC + upload`
|
||||
- the renderer rebuilds vertices, indices and batches from high-level draw commands every frame
|
||||
|
||||
Result:
|
||||
|
||||
- `D3D12` is only the final API, not the actual rendering architecture
|
||||
- the current implementation replaces a mature high-level renderer with a naive CPU front-end
|
||||
|
||||
### 3. The main editor output is still immediate-command oriented instead of retained and dirty-driven
|
||||
|
||||
Confirmed files:
|
||||
|
||||
- `new_editor/app/Composition/EditorShellRuntime.cpp`
|
||||
- `new_editor/app/Features/Project/ProjectPanel.cpp`
|
||||
- `new_editor/app/Features/Hierarchy/HierarchyPanel.cpp`
|
||||
- `new_editor/app/Features/Inspector/InspectorPanel.cpp`
|
||||
- `new_editor/src/Shell/UIEditorShellCompose.cpp`
|
||||
|
||||
Current behavior:
|
||||
|
||||
- panels already keep interaction and layout state
|
||||
- but final rendering still emits immediate `UIDrawList` commands every frame
|
||||
- there is no long-lived UI render scene, panel packet cache, or subtree dirty invalidation on the final render path
|
||||
|
||||
Result:
|
||||
|
||||
- stable UI still pays repeated build and translation costs
|
||||
|
||||
## End State
|
||||
|
||||
The final main-window rendering chain must be:
|
||||
|
||||
```text
|
||||
single authoritative shell/workspace frame
|
||||
-> retained UI scene / panel render packets
|
||||
-> native D3D12 primitive pass + native D3D12 text pass
|
||||
-> same command list
|
||||
-> same direct queue
|
||||
-> explicit backbuffer state transitions
|
||||
-> present
|
||||
```
|
||||
|
||||
The final main-window chain must not contain:
|
||||
|
||||
- `D3D11On12`
|
||||
- `D2D` draw submission
|
||||
- per-frame CPU polygon generation for common UI primitives
|
||||
- per-string text textures as the primary text path
|
||||
- repeated full compose/layout rebuilds inside one frame
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- scene renderer optimization
|
||||
- viewport render graph redesign
|
||||
- Vulkan/OpenGL editor-host parity during this task
|
||||
- cosmetic UI redesign
|
||||
|
||||
## Refactor Principles
|
||||
|
||||
- Freeze the already validated present/swapchain/backbuffer path first.
|
||||
- Refactor top-down ownership and bottom-up rendering architecture together.
|
||||
- Make one frame produce one authoritative layout/compose snapshot.
|
||||
- Move from immediate draw commands to retained render packets.
|
||||
- Move from CPU geometry generation to GPU-friendly primitive evaluation.
|
||||
- Move from string texture caching to glyph atlas rendering.
|
||||
- Remove dead compatibility paths after replacement is validated.
|
||||
|
||||
## Workstream A: Freeze The Validated D3D12 Present Path
|
||||
|
||||
Purpose:
|
||||
|
||||
- protect the working `D3D12` present path from further accidental churn while higher layers are rebuilt
|
||||
|
||||
Scope:
|
||||
|
||||
- `new_editor/app/Rendering/D3D12/D3D12WindowRenderLoop.*`
|
||||
- `new_editor/app/Rendering/D3D12/D3D12WindowRenderer.*`
|
||||
- `new_editor/app/Rendering/D3D12/D3D12WindowSwapChainPresenter.*`
|
||||
- `new_editor/app/Rendering/D3D12/D3D12HostDevice.*`
|
||||
|
||||
Required outcome:
|
||||
|
||||
- preserve current explicit `Present -> RenderTarget -> Present` flow
|
||||
- preserve current `Immediate + maxFrameLatency=1` policy unless later measurement proves a different policy is better
|
||||
- do not mix this workstream with UI-pass redesign logic
|
||||
|
||||
## Workstream B: Collapse Shell And Workspace To One Authoritative Frame Build
|
||||
|
||||
Purpose:
|
||||
|
||||
- remove repeated layout/compose/rebuild work inside a single frame
|
||||
|
||||
Primary files:
|
||||
|
||||
- `new_editor/app/Platform/Win32/EditorWindowFrameOrchestrator.cpp`
|
||||
- `new_editor/app/Composition/EditorShellRuntime.cpp`
|
||||
- `new_editor/src/Shell/UIEditorShellInteraction.cpp`
|
||||
- `new_editor/src/Shell/UIEditorShellCompose.cpp`
|
||||
- `new_editor/src/Workspace/UIEditorWorkspaceInteraction.cpp`
|
||||
- `new_editor/src/Workspace/UIEditorWorkspaceCompose.cpp`
|
||||
- `new_editor/src/Docking/UIEditorDockHostInteraction.cpp`
|
||||
- `new_editor/src/Docking/UIEditorDockHost.cpp`
|
||||
- `new_editor/src/Panels/UIEditorPanelContentHost.cpp`
|
||||
|
||||
Required structural changes:
|
||||
|
||||
- introduce one authoritative frame object for shell/workspace layout, hit-test inputs, mounted panel bounds and viewport slots
|
||||
- stop rebuilding shell request multiple times unless a real state mutation invalidates the frame
|
||||
- remove preview compose + final compose duplication where the same layout data is recomputed from scratch
|
||||
- remove the double `BuildUIEditorDockHostLayout()` pattern inside dock-host interaction rebuilds
|
||||
- make input ownership resolution consume the authoritative layout snapshot instead of forcing an extra full compose
|
||||
- ensure panel mount bounds, viewport slot bounds and dock hit-test data all come from the same frame snapshot
|
||||
|
||||
Deliverables:
|
||||
|
||||
- one frame build entrypoint
|
||||
- one dock-host layout build per frame in the steady state
|
||||
- explicit invalidation points when menu state, dock mutation, panel visibility or viewport mounts actually change
|
||||
|
||||
Acceptance criteria:
|
||||
|
||||
- no steady-state frame should perform repeated full shell/workspace compose passes without a state mutation that requires recomputation
|
||||
|
||||
## Workstream C: Introduce A Retained UI Scene Instead Of Per-Frame Immediate UIDrawData
|
||||
|
||||
Purpose:
|
||||
|
||||
- stop rebuilding the final render payload from scratch for stable UI
|
||||
|
||||
Primary files:
|
||||
|
||||
- `new_editor/app/Composition/EditorShellRuntime.cpp`
|
||||
- `new_editor/app/Features/Project/ProjectPanel.*`
|
||||
- `new_editor/app/Features/Hierarchy/HierarchyPanel.*`
|
||||
- `new_editor/app/Features/Inspector/InspectorPanel.*`
|
||||
- `new_editor/app/Features/Console/ConsolePanel.*`
|
||||
- `new_editor/app/Features/ColorPicker/ColorPickerPanel.*`
|
||||
- `new_editor/app/Features/Scene/SceneViewportFeature.*`
|
||||
- `new_editor/app/Platform/Win32/EditorWindow.cpp`
|
||||
|
||||
Required structural changes:
|
||||
|
||||
- define a retained `UiScene` or equivalent render-product layer for the main editor window
|
||||
- each major shell region and hosted panel produces a stable render packet instead of immediate draw commands
|
||||
- packets carry primitive instances, image instances, text runs, clip rectangles and z/order information
|
||||
- packets are rebuilt only when their owning state is dirty
|
||||
- stable frame append becomes packet aggregation, not command regeneration
|
||||
|
||||
Recommended ownership model:
|
||||
|
||||
- shell chrome packet
|
||||
- menu bar packet
|
||||
- toolbar packet
|
||||
- status bar packet
|
||||
- dock host packet
|
||||
- per-panel packet
|
||||
- overlay packet
|
||||
|
||||
Acceptance criteria:
|
||||
|
||||
- a steady-state empty panel must not rebuild full command streams each frame
|
||||
- unchanged panels must be reusable across frames
|
||||
|
||||
## Workstream D: Replace The Current D3D12UiRenderer With A Real Native Primitive Pass
|
||||
|
||||
Purpose:
|
||||
|
||||
- stop treating `D3D12` as a thin API wrapper around CPU-generated meshes
|
||||
|
||||
Primary files:
|
||||
|
||||
- `new_editor/app/Rendering/D3D12/D3D12UiRenderer.*`
|
||||
- potentially new files under `new_editor/app/Rendering/D3D12/`
|
||||
|
||||
Current path to retire:
|
||||
|
||||
- CPU polygon generation for rounded rects and circles
|
||||
- CPU-generated outline rings for common primitives
|
||||
- direct translation of every immediate command to transient vertices and indices
|
||||
|
||||
Target design:
|
||||
|
||||
- fixed quad or minimal base geometry
|
||||
- instance buffers for:
|
||||
- solid rect
|
||||
- rounded rect
|
||||
- border rect
|
||||
- line
|
||||
- circle
|
||||
- image quad
|
||||
- shader-side shape evaluation for fill, border and clipping
|
||||
- batch grouping by pipeline, texture set, clip state and blend behavior
|
||||
|
||||
Required renderer modules:
|
||||
|
||||
- `D3D12UiPrimitivePass`
|
||||
- `D3D12UiFrameResources`
|
||||
- `D3D12UiClipState` or equivalent scissor/clip stream
|
||||
- `D3D12UiImagePass` or merged image primitive path
|
||||
|
||||
Acceptance criteria:
|
||||
|
||||
- no CPU tessellation for common editor primitives in the steady state
|
||||
- vertex/index growth no longer scales with roundness or circle segment counts
|
||||
|
||||
## Workstream E: Replace String Texture Text Rendering With Glyph Atlas Text Rendering
|
||||
|
||||
Purpose:
|
||||
|
||||
- remove the largest text-side architectural bottleneck
|
||||
|
||||
Primary files:
|
||||
|
||||
- `new_editor/app/Rendering/D3D12/D3D12UiRenderer.*`
|
||||
- `new_editor/app/Rendering/D3D12/D3D12UiTextureHost.*`
|
||||
- `new_editor/app/Rendering/Native/NativeRenderer.*`
|
||||
- new text-system files under `new_editor/app/Rendering/D3D12/` or a dedicated text namespace
|
||||
|
||||
Current path to retire:
|
||||
|
||||
- `text + font size + dpi -> whole string texture`
|
||||
- `RasterizeTextMask()`
|
||||
- transient WIC bitmap creation for display text misses
|
||||
|
||||
Target design:
|
||||
|
||||
- `DirectWrite` remains for shaping, metrics and glyph raster generation
|
||||
- per-glyph atlas pages on the GPU
|
||||
- glyph run cache keyed by text content, font, size, DPI and shaping result
|
||||
- render text as glyph instances or glyph quads, not per-string textures
|
||||
- share text metrics cache with render cache so measure and render do not do separate work
|
||||
|
||||
Required renderer modules:
|
||||
|
||||
- `D3D12UiTextSystem`
|
||||
- `D3D12UiGlyphAtlas`
|
||||
- `D3D12UiGlyphRunCache`
|
||||
- `D3D12UiTextPass`
|
||||
|
||||
Acceptance criteria:
|
||||
|
||||
- no main-window display text path uses per-string textures as the primary render mechanism
|
||||
- repeated labels reuse shaped runs and glyph atlas entries
|
||||
|
||||
## Workstream F: Establish Dirty/Invalidation-Driven Packet Rebuild Rules
|
||||
|
||||
Purpose:
|
||||
|
||||
- make retained rendering actually effective
|
||||
|
||||
Primary files:
|
||||
|
||||
- shell/workspace interaction and compose files
|
||||
- panel state files
|
||||
- new retained-scene cache files
|
||||
|
||||
Required invalidation classes:
|
||||
|
||||
- layout dirty
|
||||
- visual dirty
|
||||
- text content dirty
|
||||
- texture binding dirty
|
||||
- clip hierarchy dirty
|
||||
- panel visibility/mount dirty
|
||||
- DPI dirty
|
||||
|
||||
Rules:
|
||||
|
||||
- layout dirty rebuilds bounds and dependent packets
|
||||
- visual dirty only rebuilds affected render packet contents
|
||||
- text dirty only rebuilds affected text runs
|
||||
- viewport texture changes do not invalidate unrelated shell packets
|
||||
- panel-local changes do not invalidate the full editor scene
|
||||
|
||||
Acceptance criteria:
|
||||
|
||||
- dirty propagation is explicit and local
|
||||
- unchanged subtrees survive frame-to-frame without packet regeneration
|
||||
|
||||
## Workstream G: Remove Obsolete Main-Window Compatibility Paths
|
||||
|
||||
Purpose:
|
||||
|
||||
- finish the refactor cleanly instead of leaving dead bridges in the hot path
|
||||
|
||||
Primary files:
|
||||
|
||||
- `new_editor/app/Rendering/Native/NativeRenderer.*`
|
||||
- `new_editor/app/Rendering/D3D12/D3D12WindowInteropContext.*`
|
||||
- `new_editor/app/Rendering/Native/AutoScreenshot.*`
|
||||
- any remaining main-window D3D11On12 bridge code
|
||||
|
||||
Required cleanup:
|
||||
|
||||
- remove old main-window `D3D11On12/D2D` composition entrypoints
|
||||
- keep screenshot/offscreen interop only if it still serves a non-main-window path
|
||||
- remove unused string-texture caches once glyph atlas path is validated
|
||||
- remove dead UI texture semantics only needed by the old D2D bridge
|
||||
|
||||
Acceptance criteria:
|
||||
|
||||
- no main-window render code path references D3D11On12 wrapped resources
|
||||
- screenshot/offscreen helpers are clearly isolated from the main editor hot path
|
||||
|
||||
## Workstream H: Instrumentation And Verification
|
||||
|
||||
Purpose:
|
||||
|
||||
- prevent another round of “wrong bottleneck, wrong fix”
|
||||
|
||||
Required measurements before and during each phase:
|
||||
|
||||
- shell/workspace frame build count per presented frame
|
||||
- dock-host layout rebuild count per presented frame
|
||||
- panel packet rebuild count per frame
|
||||
- primitive instance counts by type
|
||||
- text run cache hit/miss counts
|
||||
- glyph atlas upload count and uploaded pixel count
|
||||
- batch count
|
||||
- total draw call count
|
||||
- CPU time for:
|
||||
- shell/workspace frame build
|
||||
- retained scene rebuild
|
||||
- primitive pass build
|
||||
- text shaping/raster/atlas upload
|
||||
- final render submission
|
||||
|
||||
Instrumentation targets:
|
||||
|
||||
- `EditorWindowRuntimeController`
|
||||
- `EditorWindowFrameOrchestrator`
|
||||
- shell/workspace interaction and compose files
|
||||
- `D3D12UiRenderer`
|
||||
- future primitive/text pass modules
|
||||
|
||||
Acceptance criteria:
|
||||
|
||||
- each major architectural replacement can be validated against measured cost shifts
|
||||
|
||||
## Recommended Execution Order
|
||||
|
||||
1. Freeze present path and add instrumentation.
|
||||
2. Collapse shell/workspace to one authoritative frame build.
|
||||
3. Introduce retained UI scene and per-panel render packets.
|
||||
4. Replace primitive rendering with GPU-native instance-based primitive pass.
|
||||
5. Replace text rendering with glyph atlas rendering.
|
||||
6. Add explicit dirty propagation and cache invalidation rules.
|
||||
7. Remove obsolete main-window D2D/D3D11On12 paths.
|
||||
8. Tighten screenshot/offscreen ownership boundaries.
|
||||
9. Final performance verification and cleanup pass.
|
||||
|
||||
## Commit Strategy
|
||||
|
||||
Recommended submission boundaries:
|
||||
|
||||
1. docs and instrumentation only
|
||||
2. shell/workspace authoritative frame collapse
|
||||
3. retained UI scene and panel packet introduction
|
||||
4. primitive pass replacement
|
||||
5. text system replacement
|
||||
6. cleanup and dead-path removal
|
||||
7. final verification and documentation
|
||||
|
||||
## Risks To Manage Explicitly
|
||||
|
||||
- input hit-testing drift if layout ownership changes without updating all consumers
|
||||
- stale packet reuse if dirty propagation is incomplete
|
||||
- glyph atlas eviction bugs causing missing text
|
||||
- clip/scissor mismatches after primitive-pass conversion
|
||||
- hidden dependencies on old `UIDrawData` immediate ordering
|
||||
- viewport texture lifetime issues when packet caching crosses frame boundaries
|
||||
|
||||
## Final Acceptance Standard
|
||||
|
||||
This refactor is only considered complete when all of the following are true:
|
||||
|
||||
- the main editor window stays on the native `D3D12` path only
|
||||
- one steady-state frame does not repeatedly rebuild shell/workspace layout
|
||||
- unchanged panels reuse retained render products
|
||||
- common UI primitives are not CPU-tessellated per frame
|
||||
- main-window display text is rendered from a glyph atlas path
|
||||
- old D3D11On12/D2D main-window composition code is removed or fully isolated from the hot path
|
||||
- performance analysis shows the dominant steady-state cost moved away from repeated CPU rebuild work and naive UI translation
|
||||
@@ -0,0 +1,919 @@
|
||||
# new_editor 主窗口 UI 原生 D3D12 UI Pass 重构计划
|
||||
日期: `2026-04-21`
|
||||
|
||||
## 1. 文档定位
|
||||
|
||||
这份计划只解决一件事:
|
||||
|
||||
把 `new_editor` 主窗口 UI 从当前这条
|
||||
|
||||
`D3D12 -> D3D11On12 -> D2D/DirectWrite -> D3D11 Flush -> Present`
|
||||
|
||||
的每帧跨 API 合成链里彻底拿出来,改成同一条 `D3D12 command list / direct queue` 内完成的原生 `UI pass`。
|
||||
|
||||
这不是“补一个优化点”,而是一次热路径架构替换。目标不是在现有 `D2D` 链上继续打补丁,而是把主窗口 UI 的显示方案改对。
|
||||
|
||||
## 2. 范围与边界
|
||||
|
||||
本次重构纳入范围:
|
||||
|
||||
- `new_editor` 主窗口和 detached editor window 的 UI 热路径。
|
||||
- 主窗口 swapchain backbuffer 的准备、UI 录制、提交、显式回到 `Present`。
|
||||
- `UIDrawData` 到 GPU draw call 的整条路径。
|
||||
- editor 自己的图片纹理、asset preview 缩略图、embedded PNG、scene/game viewport 纹理在 UI pass 中的采样。
|
||||
- 文本的测量、排版、字形缓存、GPU 绘制。
|
||||
- 自动截图/手动截图,要求最终走和屏幕一致的渲染路径。
|
||||
- 旧的 `D3D11On12/D2D` 热路径删除和收口。
|
||||
|
||||
本次重构明确不纳入范围:
|
||||
|
||||
- scene 本身的渲染优化。
|
||||
- shell/workspace/dock 交互层的 CPU 端重复构建问题。
|
||||
- 非 `new_editor` 的游戏运行时渲染路径。
|
||||
- Vulkan/OpenGL 后端的 editor host 实现。
|
||||
|
||||
## 3. 代码事实与当前根问题
|
||||
|
||||
当前主窗口 UI 热路径由下列代码组成:
|
||||
|
||||
- `new_editor/app/Rendering/Native/NativeRenderer.cpp`
|
||||
- `new_editor/app/Rendering/D3D12/D3D12WindowInteropContext.cpp`
|
||||
- `new_editor/app/Rendering/D3D12/D3D12WindowRenderLoop.cpp`
|
||||
- `new_editor/app/Rendering/D3D12/D3D12WindowRenderer.cpp`
|
||||
- `new_editor/app/Rendering/D3D12/D3D12WindowSwapChainPresenter.cpp`
|
||||
|
||||
当前每帧主窗口 UI 的实际流程是:
|
||||
|
||||
```text
|
||||
BeginFrame
|
||||
-> scene viewport 等内容先在 D3D12 command list 上录制
|
||||
-> PreparePresentSurface()
|
||||
-> SubmitFrame(false)
|
||||
-> D3D11On12 AcquireWrappedResources(backbuffer + source textures)
|
||||
-> D2D/DirectWrite 逐命令绘制 UIDrawData
|
||||
-> D3D11On12 ReleaseWrappedResources(...)
|
||||
-> ID3D11DeviceContext::Flush()
|
||||
-> SignalFrameCompletion()
|
||||
-> PresentFrame()
|
||||
```
|
||||
|
||||
这里有几个必须正视的事实:
|
||||
|
||||
1. `NativeRenderer` 现在不是单一职责类。
|
||||
它同时承担了:
|
||||
- D2D 主窗口绘制
|
||||
- DWrite 文本测量
|
||||
- WIC 图片解码
|
||||
- D3D11On12 interop
|
||||
- 截图输出
|
||||
|
||||
2. `D3D12WindowInteropContext` 把 backbuffer 和 viewport source texture 每帧包成 `ID3D11Resource/ID2D1Bitmap`。
|
||||
这意味着 UI 最终不是在原生 D3D12 backbuffer 上完成,而是在跨 API 包装后再画一遍。
|
||||
|
||||
3. `D3D12WindowSwapChainPresenter::PreparePresentSurface()` 只做了 `Present -> RenderTarget`。
|
||||
当前 `RenderTarget -> Present` 的回归是依赖 `D3D11On12 ReleaseWrappedResources(...)` 隐式完成的。
|
||||
一旦移除 interop,这个状态回归必须由 D3D12 显式补齐。
|
||||
|
||||
4. `UITextureHandle` 现在有两套旧语义:
|
||||
- `DescriptorHandle` 用于 `NativeRenderer` 自己的 CPU/WIC/D2D 纹理。
|
||||
- `ShaderResourceView` 用于 viewport 纹理,再经 `D3D11On12/D2D` 包装成可绘制 bitmap。
|
||||
这套语义是被旧显示方案绑出来的,不适合原生 D3D12 UI pass。
|
||||
|
||||
5. 当前图片解码路径输出的是 `32bppPBGRA`,而 engine RHI 公共格式里只有 `R8G8B8A8_UNorm`,没有 `BGRA` 公共格式。
|
||||
这意味着 texture contract 也必须一起收口,不能直接把旧 `D2D` 像素契约搬过来。
|
||||
|
||||
## 4. 重构完成后的目标架构
|
||||
|
||||
重构后的目标架构是:
|
||||
|
||||
```text
|
||||
UIDrawData
|
||||
-> CPU batch/build
|
||||
-> same D3D12 command list
|
||||
-> same direct queue
|
||||
-> current swapchain backbuffer
|
||||
-> explicit RenderTarget -> Present
|
||||
-> Present
|
||||
```
|
||||
|
||||
最终形态必须满足以下硬约束:
|
||||
|
||||
1. 正常运行一帧时,主窗口 UI 热路径里不再创建或使用:
|
||||
- `ID3D11Device`
|
||||
- `ID3D11On12Device`
|
||||
- `ID2D1DeviceContext`
|
||||
- `ID2D1Bitmap`
|
||||
- `AcquireWrappedResources`
|
||||
- `ReleaseWrappedResources`
|
||||
- `ID3D11DeviceContext::Flush`
|
||||
|
||||
2. UI 必须和 viewport 内容共用同一个 frame command list。
|
||||
|
||||
3. 当前 backbuffer 的状态切换必须是显式对称的:
|
||||
- `Present -> RenderTarget`
|
||||
- UI 绘制
|
||||
- `RenderTarget -> Present`
|
||||
|
||||
4. viewport 纹理不再经过 `D3D11On12 + D2D bitmap` 中转,而是直接作为 GPU SRV 采样。
|
||||
|
||||
5. 主窗口 path 不允许再保留 “D3D12 失败就回退 D2D hwnd render target” 这种长期热路径 fallback。
|
||||
调试阶段可以临时保留兼容开关,但最终收口版本必须删掉。
|
||||
|
||||
## 5. 核心设计决策
|
||||
|
||||
### 5.1 不再保留一个“大而杂”的 NativeRenderer
|
||||
|
||||
`NativeRenderer` 当前把五种职责揉在一起,这会直接导致重构不彻底。
|
||||
|
||||
最终必须拆成以下职责层:
|
||||
|
||||
- `D3D12UiRenderer`
|
||||
负责把 `UIDrawData` 变成 D3D12 draw calls。
|
||||
|
||||
- `D3D12UiTextureHost`
|
||||
实现 `TexturePort`,负责 editor 自己的图片纹理加载、上传、SRV 分配与释放。
|
||||
|
||||
- `DirectWriteTextSystem`
|
||||
负责文本测量、排版、glyph cache、atlas 更新。
|
||||
它可以继续依赖 `DirectWrite`,但不能再依赖 `D2D` 作为每帧显示后端。
|
||||
|
||||
- `D3D12UiCaptureRenderer`
|
||||
负责截图,把同一套 UI pass 录制到离屏纹理,再读回 PNG。
|
||||
|
||||
是否保留 `NativeRenderer` 这个类型名不是重点,重点是最终不能再有一个类同时握着:
|
||||
|
||||
- D2D render target
|
||||
- D3D11On12 interop
|
||||
- 纹理加载
|
||||
- 文本测量
|
||||
- 主窗口 present 逻辑
|
||||
|
||||
### 5.2 UI pass 使用“CPU 批处理三角形 + 单一 alpha blend pipeline”路线
|
||||
|
||||
主窗口 UI 的 draw command 类型并不复杂:
|
||||
|
||||
- FilledRect
|
||||
- FilledRectLinearGradient
|
||||
- RectOutline
|
||||
- Line
|
||||
- FilledTriangle
|
||||
- FilledCircle
|
||||
- CircleOutline
|
||||
- Text
|
||||
- Image
|
||||
- PushClipRect / PopClipRect
|
||||
|
||||
因此最稳妥的路线不是搞多套 D2D 等价抽象,而是:
|
||||
|
||||
把全部 UI 命令转换为 GPU triangle batches。
|
||||
|
||||
最终统一成一套基本顶点格式:
|
||||
|
||||
- `position`
|
||||
- `uv`
|
||||
- `color`
|
||||
|
||||
最终统一成一条主图形 pipeline:
|
||||
|
||||
- alpha blend 打开
|
||||
- 深度关闭
|
||||
- cull 关闭
|
||||
- scissor 打开
|
||||
- 正交投影
|
||||
|
||||
具体策略:
|
||||
|
||||
- 纯色/描边/线段/圆/圆角矩形:
|
||||
由 CPU 直接三角化,采样一个 `1x1 white texture`。
|
||||
|
||||
- 线性渐变矩形:
|
||||
仍然三角化,但颜色写到顶点颜色里,shader 只做 `texture * vertexColor`。
|
||||
|
||||
- 图片:
|
||||
采样对应 SRV。
|
||||
|
||||
- 文本:
|
||||
采样 glyph atlas 对应 SRV。
|
||||
|
||||
这样做的好处是:
|
||||
|
||||
- 主窗口 UI pass 可以收口成一条真正原生的 GPU 渲染路径。
|
||||
- 不需要在 GPU 侧再保留 D2D 风格图元接口。
|
||||
- draw order 与现有 `UIDrawData` 顺序一致,行为最稳定。
|
||||
- clip 直接映射成 scissor,简单明确。
|
||||
|
||||
### 5.3 文本继续使用 DirectWrite 做“CPU 端排版/字形分析”,但不再用 D2D 绘制
|
||||
|
||||
成熟编辑器不是每帧把 swapchain 包成 D2D bitmap 再 `DrawTextW`。
|
||||
更合理的做法是:
|
||||
|
||||
- 用 `DirectWrite` 做字体测量和文本 shaping。
|
||||
- 把 glyph coverage 缓存进 atlas。
|
||||
- 真正显示时在 GPU 上绘制 glyph quads。
|
||||
|
||||
因此本次文本路线定为:
|
||||
|
||||
- 保留 `DirectWrite` 作为文本系统。
|
||||
- 删除 `D2D::DrawTextW` 作为主窗口显示后端。
|
||||
- 新建 glyph atlas。
|
||||
- 文本 draw command 最终落成普通 textured quads。
|
||||
|
||||
### 5.4 viewport 纹理直接采样,不再每帧 CreateWrappedResource/CreateBitmapFromDxgiSurface
|
||||
|
||||
scene/game viewport 纹理当前已经有 `UITextureHandle.nativeHandle = GPU descriptor handle`。
|
||||
|
||||
这意味着在原生 D3D12 UI pass 里,viewport 图片本质上已经具备直接采样条件。
|
||||
|
||||
因此:
|
||||
|
||||
- `D3D12WindowInteropContext::PrepareSourceTextures()` 整条链直接删除。
|
||||
- UI pass 直接读取 `UITextureHandle.nativeHandle` 对应的 SRV。
|
||||
- `resourceHandle` 只保留给释放路径或调试路径使用,不再参与渲染期查表。
|
||||
|
||||
### 5.5 截图也必须切到同一套 GPU 路径
|
||||
|
||||
如果主窗口显示已经换成原生 D3D12 UI pass,但截图仍然走旧的 `D2D offscreen render target`,那验证链路会分叉,后续会一直出现:
|
||||
|
||||
- 屏幕上看见的是一套
|
||||
- 自动截图导出的是另一套
|
||||
|
||||
所以截图也必须纳入重构,而不是留作旧系统残留。
|
||||
|
||||
## 6. 模块拆分计划
|
||||
|
||||
### 6.1 D3D12UiRenderer
|
||||
|
||||
职责:
|
||||
|
||||
- 接收 `UIDrawData`
|
||||
- 维护 per-frame vertex/index/upload ring
|
||||
- 维护 pipeline / root signature / sampler / white texture
|
||||
- 维护 clip stack -> scissor
|
||||
- 将 batches 录制到当前 `D3D12CommandList`
|
||||
|
||||
必须覆盖的命令:
|
||||
|
||||
- `FilledRect`
|
||||
- `FilledRectLinearGradient`
|
||||
- `RectOutline`
|
||||
- `Line`
|
||||
- `FilledTriangle`
|
||||
- `FilledCircle`
|
||||
- `CircleOutline`
|
||||
- `Text`
|
||||
- `Image`
|
||||
- `PushClipRect`
|
||||
- `PopClipRect`
|
||||
|
||||
必须支持的渲染状态:
|
||||
|
||||
- alpha blend
|
||||
- cull none
|
||||
- depth off
|
||||
- scissor on
|
||||
- swapchain RT 格式自适应
|
||||
|
||||
### 6.2 D3D12UiTextureHost
|
||||
|
||||
职责:
|
||||
|
||||
- 实现 `Ports::TexturePort`
|
||||
- 加载文件/内存/RGBA 纹理
|
||||
- 创建 `R8G8B8A8_UNorm` GPU texture
|
||||
- 在 shader-visible descriptor heap 中分配 SRV
|
||||
- 为 `UITextureHandle` 填入:
|
||||
- `nativeHandle = GPU descriptor handle`
|
||||
- `width`
|
||||
- `height`
|
||||
- `kind`
|
||||
- 必要时 `resourceHandle`
|
||||
- 管理纹理释放和 descriptor 回收
|
||||
|
||||
这里必须明确:
|
||||
|
||||
- 旧的 `NativeTextureResource::cachedBitmap` 模型要彻底删除。
|
||||
- 图片像素契约要改成适合 D3D12 的 `RGBA straight alpha` 或统一后的 GPU 格式。
|
||||
- 不能再保留 `D2D bitmap lazy-create`。
|
||||
|
||||
### 6.3 DirectWriteTextSystem
|
||||
|
||||
职责:
|
||||
|
||||
- 实现 `UIEditorTextMeasurer`
|
||||
- 复用/管理 `IDWriteFactory`
|
||||
- 缓存 `IDWriteTextFormat`
|
||||
- 缓存文本 layout 结果
|
||||
- 把 glyph rasterize 到 atlas
|
||||
- 为文本 draw 生成 glyph quads
|
||||
|
||||
必须考虑的点:
|
||||
|
||||
- 现有 editor 既有英文,也必须允许中文文本正确显示。
|
||||
- 需要支持 DPI scale。
|
||||
- 需要支持重复标签的缓存复用,避免每帧重新 shape。
|
||||
- `MeasureTextWidth()` 和真正渲染应共享同一套文本 layout cache,避免两套逻辑各算一遍。
|
||||
|
||||
### 6.4 D3D12UiCaptureRenderer
|
||||
|
||||
职责:
|
||||
|
||||
- 创建离屏 color texture
|
||||
- 用同一套 `D3D12UiRenderer` 逻辑把 `UIDrawData` 画进去
|
||||
- GPU readback
|
||||
- WIC 编码 PNG
|
||||
|
||||
必须保证:
|
||||
|
||||
- 截图结果与真实主窗口 UI pass 使用同一套 shader / batch / text atlas / texture contract。
|
||||
|
||||
## 7. 关键数据契约调整
|
||||
|
||||
### 7.1 UITextureHandle 语义收口
|
||||
|
||||
受影响文件:
|
||||
|
||||
- `engine/include/XCEngine/UI/Types.h`
|
||||
- `new_editor/app/Rendering/Native/NativeRenderer.cpp`
|
||||
- `new_editor/app/Rendering/Native/NativeRendererHelpers.h`
|
||||
- `new_editor/app/Rendering/D3D12/D3D12WindowRenderer.cpp`
|
||||
- `new_editor/app/Rendering/D3D12/D3D12WindowInteropContext.cpp`
|
||||
|
||||
需要做的事:
|
||||
|
||||
1. 去掉“D2D bitmap handle”和“viewport SRV handle”这两套历史含义。
|
||||
2. 统一主窗口 UI 渲染期对 `nativeHandle` 的解释:
|
||||
`nativeHandle` 就是 GPU 可采样 descriptor handle。
|
||||
3. `resourceHandle` 不再用于每帧渲染查找 `D3D11On12 wrapped resource`。
|
||||
4. `kind` 要么重命名为更准确的 GPU 语义,要么保留枚举名但重新定义 contract,并把所有调用点一起收口。
|
||||
|
||||
### 7.2 图片像素格式收口
|
||||
|
||||
当前 `DecodeTextureFrame()` 输出 `32bppPBGRA`,这是为 D2D 服务的。
|
||||
|
||||
原生 D3D12 UI pass 必须改成统一的 GPU 纹理契约:
|
||||
|
||||
- 统一使用 `R8G8B8A8_UNorm`
|
||||
- 明确使用 straight alpha 还是 premultiplied alpha
|
||||
- blend state 和 shader 按这一个约定实现
|
||||
|
||||
这个点必须全链路一致:
|
||||
|
||||
- 文件纹理加载
|
||||
- 内存纹理加载
|
||||
- embedded PNG
|
||||
- asset preview 缩略图
|
||||
- glyph atlas
|
||||
- white texture
|
||||
|
||||
## 8. D3D12 渲染链改造计划
|
||||
|
||||
### 8.1 D3D12WindowSwapChainPresenter
|
||||
|
||||
受影响文件:
|
||||
|
||||
- `new_editor/app/Rendering/D3D12/D3D12WindowSwapChainPresenter.h`
|
||||
- `new_editor/app/Rendering/D3D12/D3D12WindowSwapChainPresenter.cpp`
|
||||
|
||||
必须改造:
|
||||
|
||||
1. `PreparePresentSurface()` 要改成更明确的 backbuffer render begin 语义。
|
||||
2. 新增对称的 backbuffer render end / present finalize 语义。
|
||||
3. 不再依赖 interop 帮忙把 backbuffer 从 `RenderTarget` 回到 `Present`。
|
||||
4. `RenderSurface` 元数据要和真实 backbuffer 状态一致。
|
||||
|
||||
建议改成两段式接口:
|
||||
|
||||
- `PrepareCurrentBackBufferForUiRender(renderContext)`
|
||||
- `FinalizeCurrentBackBufferForPresent(renderContext)`
|
||||
|
||||
### 8.2 D3D12WindowRenderer
|
||||
|
||||
受影响文件:
|
||||
|
||||
- `new_editor/app/Rendering/D3D12/D3D12WindowRenderer.h`
|
||||
- `new_editor/app/Rendering/D3D12/D3D12WindowRenderer.cpp`
|
||||
|
||||
必须改造:
|
||||
|
||||
1. 保留 `BeginFrame()`。
|
||||
2. 不再让 `SubmitFrame(false)` 提前提交,再由别的 API 补 UI。
|
||||
3. 提供当前 frame 的:
|
||||
- `RenderContext`
|
||||
- `D3D12CommandList`
|
||||
- 当前 backbuffer surface
|
||||
4. `PresentFrame()` 前必须确保本 frame command list 已经包含 UI pass。
|
||||
|
||||
### 8.3 D3D12WindowRenderLoop
|
||||
|
||||
受影响文件:
|
||||
|
||||
- `new_editor/app/Rendering/D3D12/D3D12WindowRenderLoop.h`
|
||||
- `new_editor/app/Rendering/D3D12/D3D12WindowRenderLoop.cpp`
|
||||
|
||||
这是主窗口热路径的 orchestrator,必须重写逻辑。
|
||||
|
||||
当前职责里最需要删除的是:
|
||||
|
||||
- interop attach/detach
|
||||
- “present failure -> 回退 hwnd D2D renderer”
|
||||
|
||||
最终流程应当是:
|
||||
|
||||
```text
|
||||
BeginFrame()
|
||||
-> EditorShellRuntime 更新并生成 UIDrawData
|
||||
-> RenderRequestedViewports(renderContext)
|
||||
-> PrepareCurrentBackBufferForUiRender(renderContext)
|
||||
-> D3D12UiRenderer::Render(drawData, renderContext, backbufferSurface)
|
||||
-> FinalizeCurrentBackBufferForPresent(renderContext)
|
||||
-> SubmitFrame()
|
||||
-> SignalFrameCompletion()
|
||||
-> PresentFrame()
|
||||
```
|
||||
|
||||
也就是说,`D3D12WindowRenderLoop::Present()` 不能再只是“调用某个 UI renderer 再兜底”,而必须变成真正的原生 D3D12 submit/present 收口点。
|
||||
|
||||
## 9. UI pass 具体实现计划
|
||||
|
||||
### 9.1 Batch 构建
|
||||
|
||||
建议新增一层 CPU batch builder。
|
||||
|
||||
输入:
|
||||
|
||||
- `UIDrawData`
|
||||
|
||||
输出:
|
||||
|
||||
- 顶点流
|
||||
- 索引流
|
||||
- batch 列表
|
||||
|
||||
每个 batch 至少包含:
|
||||
|
||||
- `firstIndex`
|
||||
- `indexCount`
|
||||
- `scissorRect`
|
||||
- `textureDescriptor`
|
||||
|
||||
batch 切分条件:
|
||||
|
||||
- clip/scissor 变化
|
||||
- texture 变化
|
||||
- pipeline 变化
|
||||
|
||||
禁止的做法:
|
||||
|
||||
- 继续一条命令调用一次 D3D API
|
||||
- 继续在渲染期动态创建图形对象
|
||||
- 继续依赖 D2D 帮忙做 clip/gradient/text
|
||||
|
||||
### 9.2 几何生成
|
||||
|
||||
必须明确每种命令的几何策略:
|
||||
|
||||
- `FilledRect`
|
||||
两三角形,圆角时用 CPU 扇形近似。
|
||||
|
||||
- `RectOutline`
|
||||
生成为四条边的条带几何,或拆成若干三角形。
|
||||
|
||||
- `Line`
|
||||
生成厚线四边形。
|
||||
|
||||
- `FilledTriangle`
|
||||
直接三角形。
|
||||
|
||||
- `FilledCircle`
|
||||
CPU 分段近似生成扇形。
|
||||
|
||||
- `CircleOutline`
|
||||
CPU 分段生成环带。
|
||||
|
||||
- `FilledRectLinearGradient`
|
||||
与普通矩形相同,但顶点颜色按方向插值。
|
||||
|
||||
- `Image`
|
||||
生成标准 textured quad。
|
||||
|
||||
- `Text`
|
||||
生成 glyph textured quads。
|
||||
|
||||
### 9.3 Clip 栈
|
||||
|
||||
现有 `PushClipRect/PopClipRect` 必须映射成真正的 scissor 逻辑。
|
||||
|
||||
具体要求:
|
||||
|
||||
- CPU 维护 clip stack。
|
||||
- `intersectWithCurrentClip` 必须保留语义。
|
||||
- clip 变化时强制 flush 当前 batch。
|
||||
- D3D12 command list 在每个 batch 提交前设置 scissor。
|
||||
|
||||
### 9.4 Per-frame 上传资源
|
||||
|
||||
不能每帧创建/销毁 vertex/index buffer。
|
||||
|
||||
必须改成:
|
||||
|
||||
- 以 `frame slot` 为单位分配 upload buffer ring
|
||||
- 每个 frame slot 至少持有:
|
||||
- vertex upload buffer
|
||||
- index upload buffer
|
||||
- 必要时 frame constants buffer
|
||||
- 重用现有 `D3D12HostDevice::kFrameContextCount`
|
||||
|
||||
这里要注意:
|
||||
|
||||
- 现有 `D3D12HostDevice` 已经按 frame slot 做 fence 跟踪。
|
||||
- UI pass 的动态上传资源也必须跟这个 frame slot 生命周期绑定,不能另起一套未受 fence 保护的资源轮换。
|
||||
|
||||
### 9.5 Root signature / pipeline / sampler
|
||||
|
||||
推荐把 UI pass 收敛为一套固定资源布局:
|
||||
|
||||
- root constants 或 frame CBV:
|
||||
正交投影、屏幕尺寸等 frame 常量
|
||||
- descriptor table:
|
||||
当前 batch 使用的 SRV
|
||||
- static sampler:
|
||||
至少一个 `linear clamp`
|
||||
|
||||
需要明确:
|
||||
|
||||
- 是否保留 `point clamp` 给像素风图标或 object-id 类资源
|
||||
- 是否通过 `white texture` 统一纯色图元路径
|
||||
|
||||
## 10. 文本系统重构计划
|
||||
|
||||
### 10.1 测量与渲染共享同一套文本缓存
|
||||
|
||||
受影响文件:
|
||||
|
||||
- `new_editor/include/XCEditor/Foundation/UIEditorTextMeasurement.h`
|
||||
- `new_editor/app/Rendering/Native/NativeRenderer.h`
|
||||
- `new_editor/app/Rendering/Native/NativeRenderer.cpp`
|
||||
- `new_editor/src/Shell/UIEditorShellInteraction.cpp`
|
||||
- `new_editor/app/Features/Project/ProjectPanel.cpp`
|
||||
- `new_editor/app/Platform/Win32/EditorWindowChromeController.cpp`
|
||||
|
||||
必须改造:
|
||||
|
||||
1. `MeasureTextWidth()` 不再挂在一个 D2D renderer 身上。
|
||||
2. 文本测量与渲染共享一套 `DirectWriteTextSystem`。
|
||||
3. 同一个 `(font size, string)` 的 layout 结果可以同时服务:
|
||||
- 宽度测量
|
||||
- glyph quad 生成
|
||||
|
||||
### 10.2 Glyph atlas
|
||||
|
||||
必须解决的问题:
|
||||
|
||||
- atlas 尺寸策略
|
||||
- glyph key 设计
|
||||
- atlas 满了之后的增长或淘汰
|
||||
- atlas dirty 区域上传
|
||||
- 多 DPI / 多字号支持
|
||||
|
||||
推荐 key:
|
||||
|
||||
- font family
|
||||
- font size
|
||||
- weight/style
|
||||
- glyph index
|
||||
- antialias mode
|
||||
|
||||
推荐策略:
|
||||
|
||||
- 初始 atlas 固定尺寸
|
||||
- dirty rect 子区域上传
|
||||
- 如 atlas 不够则扩容并重建 descriptor,不做每帧整图重建
|
||||
|
||||
### 10.3 文字行为一致性
|
||||
|
||||
必须验证:
|
||||
|
||||
- 标题栏 FPS 文本
|
||||
- 面板标题
|
||||
- 菜单栏
|
||||
- tab 文本
|
||||
- ProjectPanel 路径 breadcrumb
|
||||
- Inspector / Hierarchy / StatusBar
|
||||
- 中文字符串显示
|
||||
- DPI 切换后文字大小与定位
|
||||
|
||||
## 11. 纹理与图片路径重构计划
|
||||
|
||||
### 11.1 现有图片调用点
|
||||
|
||||
必须覆盖以下来源:
|
||||
|
||||
- `EmbeddedPngLoader`
|
||||
- `BuiltInIcons`
|
||||
- `SceneViewportToolOverlay`
|
||||
- asset preview 缩略图
|
||||
- viewport surface texture handle
|
||||
|
||||
对应文件:
|
||||
|
||||
- `new_editor/app/Support/EmbeddedPngLoader.cpp`
|
||||
- `new_editor/app/Rendering/Assets/BuiltInIcons.cpp`
|
||||
- `new_editor/app/Features/Scene/SceneViewportToolOverlay.cpp`
|
||||
- `new_editor/app/Rendering/Viewport/ViewportRenderTargets.cpp`
|
||||
- `new_editor/app/Rendering/D3D12/D3D12WindowRenderer.cpp`
|
||||
|
||||
### 11.2 descriptor 分配器
|
||||
|
||||
`D3D12ShaderResourceDescriptorAllocator` 现在只明确服务 viewport texture。
|
||||
|
||||
重构后它要么:
|
||||
|
||||
- 直接升级为 editor 全局 UI SRV 分配器
|
||||
|
||||
要么:
|
||||
|
||||
- viewport texture allocator 和 editor texture allocator 分离
|
||||
|
||||
但最终必须解决三个问题:
|
||||
|
||||
1. editor 自己的图片纹理和 viewport 纹理都能在 UI pass 中按同一方式绑定。
|
||||
2. descriptor 不泄漏。
|
||||
3. resize / window shutdown / texture release 时 descriptor 生命周期正确。
|
||||
|
||||
## 12. 截图链路重构计划
|
||||
|
||||
受影响文件:
|
||||
|
||||
- `new_editor/app/Rendering/Native/AutoScreenshot.cpp`
|
||||
- `new_editor/app/Rendering/Native/AutoScreenshot.h`
|
||||
- 以及新的 GPU capture 模块
|
||||
|
||||
重构后截图流程:
|
||||
|
||||
```text
|
||||
UIDrawData
|
||||
-> D3D12 offscreen color texture
|
||||
-> same UI pipeline render
|
||||
-> readback buffer
|
||||
-> WIC encode PNG
|
||||
```
|
||||
|
||||
必须删除:
|
||||
|
||||
- `CreateWicBitmapRenderTarget`
|
||||
- `ID2D1RenderTarget::EndDraw` 截图路径
|
||||
|
||||
## 13. 旧代码删除与收口计划
|
||||
|
||||
### 13.1 必删模块
|
||||
|
||||
以下旧模块在最终收口时不应继续存在于主窗口热路径:
|
||||
|
||||
- `new_editor/app/Rendering/D3D12/D3D12WindowInteropContext.h`
|
||||
- `new_editor/app/Rendering/D3D12/D3D12WindowInteropContext.cpp`
|
||||
- `new_editor/app/Rendering/D3D12/D3D12WindowInteropHelpers.h`
|
||||
|
||||
以下旧模块要么删除,要么被拆空成兼容壳,最终不能继续承担主窗口 UI 显示:
|
||||
|
||||
- `new_editor/app/Rendering/Native/NativeRenderer.h`
|
||||
- `new_editor/app/Rendering/Native/NativeRenderer.cpp`
|
||||
- `new_editor/app/Rendering/Native/NativeRendererHelpers.h`
|
||||
|
||||
### 13.2 构建系统收口
|
||||
|
||||
受影响文件:
|
||||
|
||||
- `new_editor/CMakeLists.txt`
|
||||
|
||||
需要做的事:
|
||||
|
||||
1. 移除 interop 相关源文件。
|
||||
2. 新增 `D3D12UiRenderer`、text system、texture host、capture renderer 源文件。
|
||||
3. 最终主窗口热路径不再依赖 `d3d11.lib`。
|
||||
4. 如果截图也已完全切走,则 `d2d1.lib` 也应移除。
|
||||
|
||||
`dwrite.lib` 和 `windowscodecs.lib` 可以继续保留,因为它们分别服务:
|
||||
|
||||
- 文本测量/字形分析
|
||||
- PNG/WIC 编码解码
|
||||
|
||||
## 14. 受影响文件总表
|
||||
|
||||
### 14.1 必改文件
|
||||
|
||||
- `new_editor/CMakeLists.txt`
|
||||
- `engine/include/XCEngine/UI/Types.h`
|
||||
- `new_editor/app/Platform/Win32/EditorWindowRuntimeController.h`
|
||||
- `new_editor/app/Platform/Win32/EditorWindowRuntimeController.cpp`
|
||||
- `new_editor/app/Rendering/D3D12/D3D12WindowRenderLoop.h`
|
||||
- `new_editor/app/Rendering/D3D12/D3D12WindowRenderLoop.cpp`
|
||||
- `new_editor/app/Rendering/D3D12/D3D12WindowRenderer.h`
|
||||
- `new_editor/app/Rendering/D3D12/D3D12WindowRenderer.cpp`
|
||||
- `new_editor/app/Rendering/D3D12/D3D12WindowSwapChainPresenter.h`
|
||||
- `new_editor/app/Rendering/D3D12/D3D12WindowSwapChainPresenter.cpp`
|
||||
- `new_editor/app/Rendering/D3D12/D3D12ShaderResourceDescriptorAllocator.h`
|
||||
- `new_editor/app/Rendering/D3D12/D3D12ShaderResourceDescriptorAllocator.cpp`
|
||||
- `new_editor/app/Rendering/Assets/BuiltInIcons.cpp`
|
||||
- `new_editor/app/Support/EmbeddedPngLoader.cpp`
|
||||
- `new_editor/app/Rendering/Native/AutoScreenshot.cpp`
|
||||
- `new_editor/app/Rendering/Native/AutoScreenshot.h`
|
||||
|
||||
### 14.2 高概率新增文件
|
||||
|
||||
- `new_editor/app/Rendering/D3D12/D3D12UiRenderer.h`
|
||||
- `new_editor/app/Rendering/D3D12/D3D12UiRenderer.cpp`
|
||||
- `new_editor/app/Rendering/D3D12/D3D12UiBatchBuilder.h`
|
||||
- `new_editor/app/Rendering/D3D12/D3D12UiBatchBuilder.cpp`
|
||||
- `new_editor/app/Rendering/D3D12/D3D12UiTextureHost.h`
|
||||
- `new_editor/app/Rendering/D3D12/D3D12UiTextureHost.cpp`
|
||||
- `new_editor/app/Rendering/D3D12/DirectWriteTextSystem.h`
|
||||
- `new_editor/app/Rendering/D3D12/DirectWriteTextSystem.cpp`
|
||||
- `new_editor/app/Rendering/D3D12/D3D12UiCaptureRenderer.h`
|
||||
- `new_editor/app/Rendering/D3D12/D3D12UiCaptureRenderer.cpp`
|
||||
- `new_editor/resources/shaders/ui/...`
|
||||
|
||||
### 14.3 必删文件
|
||||
|
||||
- `new_editor/app/Rendering/D3D12/D3D12WindowInteropContext.h`
|
||||
- `new_editor/app/Rendering/D3D12/D3D12WindowInteropContext.cpp`
|
||||
- `new_editor/app/Rendering/D3D12/D3D12WindowInteropHelpers.h`
|
||||
|
||||
## 15. 分阶段实施顺序
|
||||
|
||||
### 阶段 A: 先把显示热路径切正
|
||||
|
||||
目标:
|
||||
|
||||
- 主窗口不再走 `D3D11On12/D2D`
|
||||
- viewport 纹理直接采样
|
||||
- backbuffer 状态显式闭环
|
||||
|
||||
执行顺序:
|
||||
|
||||
1. 引入新的 `D3D12UiRenderer` 骨架。
|
||||
2. 改 `D3D12WindowSwapChainPresenter`,补齐 backbuffer begin/end。
|
||||
3. 改 `D3D12WindowRenderLoop`,把 UI pass 放进同一 command list。
|
||||
4. 先支持:
|
||||
- FilledRect
|
||||
- RectOutline
|
||||
- Line
|
||||
- Image
|
||||
- PushClipRect / PopClipRect
|
||||
5. 让空面板、普通图标 UI、viewport 纹理直采先跑通。
|
||||
|
||||
阶段 A 完成标准:
|
||||
|
||||
- 主窗口帧内不再出现 interop acquire/release/flush。
|
||||
- 关闭 scene 只留空面板时,仍然走原生 D3D12 UI pass。
|
||||
|
||||
### 阶段 B: 补齐全部 UI 命令
|
||||
|
||||
目标:
|
||||
|
||||
- 所有现有 `UIDrawCommandType` 行为可用
|
||||
|
||||
执行顺序:
|
||||
|
||||
1. 补三角化命令:
|
||||
- gradient rect
|
||||
- triangle
|
||||
- circle
|
||||
- outline circle
|
||||
2. 补圆角矩形/描边近似策略。
|
||||
3. 调整 batch 合并与 scissor flush。
|
||||
|
||||
### 阶段 C: 文本系统切换
|
||||
|
||||
目标:
|
||||
|
||||
- 删除 `D2D::DrawTextW` 作为显示手段
|
||||
|
||||
执行顺序:
|
||||
|
||||
1. 引入 `DirectWriteTextSystem`
|
||||
2. 先完成文本测量迁移
|
||||
3. 再完成 glyph atlas 与文本 quad 输出
|
||||
4. 替换主窗口文本绘制
|
||||
|
||||
阶段 C 完成标准:
|
||||
|
||||
- FPS 文本、菜单、tab、ProjectPanel、StatusBar 文本都走 atlas 路径。
|
||||
|
||||
### 阶段 D: 截图链路切换
|
||||
|
||||
目标:
|
||||
|
||||
- 屏幕和截图使用同一 UI 渲染路径
|
||||
|
||||
执行顺序:
|
||||
|
||||
1. 新建离屏 UI capture renderer
|
||||
2. 接到 `AutoScreenshot`
|
||||
3. 删除 D2D screenshot path
|
||||
|
||||
### 阶段 E: 删除旧代码与链接依赖
|
||||
|
||||
目标:
|
||||
|
||||
- 真正收口,而不是新旧并存
|
||||
|
||||
执行顺序:
|
||||
|
||||
1. 删除 `D3D12WindowInteropContext`
|
||||
2. 删除 interop helpers
|
||||
3. 删除旧 `NativeRenderer` 中的 D2D/D3D11On12 代码
|
||||
4. 清理 `CMake`
|
||||
|
||||
## 16. 风险清单
|
||||
|
||||
### 16.1 状态回归风险
|
||||
|
||||
如果只删 interop、不补 `RenderTarget -> Present`,就会直接出现:
|
||||
|
||||
- present 失败
|
||||
- 黑屏
|
||||
- DXGI 报错
|
||||
|
||||
这是第一优先级风险。
|
||||
|
||||
### 16.2 文本外观差异风险
|
||||
|
||||
从 D2D 文本切到 atlas 后,最容易变化的是:
|
||||
|
||||
- baseline
|
||||
- 字距
|
||||
- 灰度抗锯齿观感
|
||||
- 中文字形边缘
|
||||
|
||||
这需要单独做 UI 对比验证。
|
||||
|
||||
### 16.3 像素格式风险
|
||||
|
||||
当前图片链路是 `PBGRA`,新链路如果切成 `RGBA` 但 blend/shader 没一起改,会出现:
|
||||
|
||||
- 图标发黑
|
||||
- 半透明边缘发灰
|
||||
- 预乘错误
|
||||
|
||||
### 16.4 descriptor 生命周期风险
|
||||
|
||||
如果 SRV 分配与释放没有彻底梳理,会出现:
|
||||
|
||||
- viewport 纹理释放后 descriptor 悬挂
|
||||
- asset preview 刷新泄漏 descriptor
|
||||
- 多窗口关闭后 descriptor 未回收
|
||||
|
||||
### 16.5 截图路径分叉风险
|
||||
|
||||
如果截图仍走旧 D2D 路径,后续任何 UI 行为验证都会变得不可信。
|
||||
|
||||
## 17. 验证标准
|
||||
|
||||
### 17.1 代码级验证
|
||||
|
||||
最终热路径代码里不应再存在以下调用:
|
||||
|
||||
- `D3D11On12CreateDevice`
|
||||
- `AcquireWrappedResources`
|
||||
- `ReleaseWrappedResources`
|
||||
- `CreateBitmapFromDxgiSurface`
|
||||
- `ID3D11DeviceContext::Flush`
|
||||
- `ID2D1DeviceContext::SetTarget(backbuffer)`
|
||||
- `DrawTextW` 用于主窗口 present
|
||||
|
||||
### 17.2 运行时验证
|
||||
|
||||
必须覆盖以下场景:
|
||||
|
||||
- 只留一个空白面板
|
||||
- 只留 scene 面板
|
||||
- scene 为空场景
|
||||
- 多面板正常布局
|
||||
- tab 拖出独立窗口
|
||||
- DPI 改变
|
||||
- 窗口 resize
|
||||
- asset preview 缩略图出现与回收
|
||||
- scene/game viewport 纹理显示
|
||||
- 自动截图
|
||||
- 标题栏 FPS 持续刷新
|
||||
|
||||
### 17.3 图形调试验证
|
||||
|
||||
建议用 PIX 或 RenderDoc 验证:
|
||||
|
||||
- 一帧内主窗口 UI 是否确实录制在同一条 direct command list 上
|
||||
- swapchain backbuffer 状态切换是否完整
|
||||
- 是否已不存在 D3D11 packet / interop 提交
|
||||
|
||||
## 18. 收口判定
|
||||
|
||||
只有同时满足以下条件,这次重构才算收口彻底:
|
||||
|
||||
1. 主窗口 UI 每帧显示热路径完全不再依赖 `D3D11On12/D2D`。
|
||||
2. viewport 纹理改为 GPU 直接采样。
|
||||
3. backbuffer 显式状态闭环完成。
|
||||
4. 文本显示改为 atlas + GPU quad,而不是 `DrawTextW`。
|
||||
5. 截图与屏幕使用同一套 UI pass。
|
||||
6. 旧 interop 代码被删除,而不是隐藏保留。
|
||||
|
||||
如果只做到其中前两三项,那只是“部分切换”,不算这次重构真正结束。
|
||||
Reference in New Issue
Block a user