8.1 KiB
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:
D3D12WindowRendererD3D12UiRendererD3D12WindowRenderLoop
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:
- Active path: native
D3D12rendering into the swapchain backbuffer. - Residual path:
UIDrawData -> D2D replay, with optionalD3D11On12resource 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:
- Main-window realtime rendering remains native
D3D12. - Main-window screenshot capture also uses native
D3D12backbuffer capture. new_editorno longer contains any host-sideD3D11On12orD2Dbridge code for the main window.- Old hwnd
D2Drenderer code is deleted, not merely disconnected. XCUIEditorHostno longer compiles obsolete interop units.- 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
D3D12UiRendererarchitecture back into draw-data replay through another API. - Do not remove
DirectWritefromD3D12UiTextSystem; 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
UIDrawDatareplay - 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
NativeRendererinterop 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.cppapp/Rendering/D3D12/D3D12WindowInteropContext.cppd2d1.libd3d11.lib
Expected retained dependencies:
d3d12.libd3dcompiler.libdwrite.libdxgi.libwindowscodecs.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.hD3D12WindowCapture.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.happ/Rendering/D3D12/D3D12WindowRenderer.cppapp/Rendering/D3D12/D3D12WindowRenderLoop.happ/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.happ/Rendering/Native/AutoScreenshot.cppapp/Platform/Win32/EditorWindowRuntimeController.happ/Platform/Win32/EditorWindowRuntimeController.cppapp/Platform/Win32/EditorWindow.cpp
Needed changes:
- stop passing
UIDrawDatainto 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.happ/Rendering/Native/NativeRenderer.cppapp/Rendering/Native/NativeRendererHelpers.happ/Rendering/D3D12/D3D12WindowInteropContext.happ/Rendering/D3D12/D3D12WindowInteropContext.cppapp/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:
XCUIEditorHostXCUIEditorAppLibXCUIEditorApp
8.2 Runtime validation
Must validate:
- editor launches normally
- main window still presents correctly
- manual screenshot still succeeds
- startup auto-capture still succeeds if enabled
- no screenshot path closes or stalls the main window unexpectedly
8.3 Structural validation
Searches after completion must show:
- no
NativeRendererreferences innew_editor/app - no
D3D12WindowInteropContextreferences innew_editor - no
D3D11On12references innew_editor - no
ID2D/D2D1references innew_editor/app
Exception:
DirectWritereferences insideD3D12UiTextSystemremain allowed
9. Execution Order
- Add native
D3D12backbuffer capture path. - Route
AutoScreenshotControllerthrough that new path. - Remove runtime-controller dependence on
NativeRenderer. - Delete
NativeRendererandD3D12WindowInteropContext. - Clean CMake/link dependencies.
- Build and smoke-test.
- Run structural grep validation.
10. Success Criteria
This plan is complete only if all of the following are true:
- screenshots come from the native
D3D12backbuffer path - no main-window host rendering code path can fall back to
D2D - no
D3D11On12bridge remains innew_editor - old dead window-render code is physically removed
- build and smoke validation pass