You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Casso's steady-state GPU cost scales with window area even when the only
thing changing is the emulated picture, which occupies a small fraction of it.
Three things repaint the whole window every frame, unconditionally, and none of
them need to.
This issue is the investigation, not a committed design. Step 1 is a
measurement, and it decides which of the two candidate directions is worth
doing — possibly neither.
Where it stands today
Measured on desk-scene-models at commit 918b93c0, RELMER-SP8, Debug,
at a BASIC prompt with a blinking cursor (so it presents every frame):
client area
GPU
1400x1470 (2.06 Mpix)
10.2%
2880x1837 (5.29 Mpix)
18.9%
Roughly 4.7% fixed + 2.7% per Mpix of window area. For comparison, before
the CRT chain was sized to the picture (21f7ff16) it was ~8% fixed +
~8%/Mpix, so the area coefficient is already about a third of what it was.
Per frame, in that state, the GPU does:
The 3D desk scene: three draws. Two full-back-buffer textured quads (the
cached back and front plates) and the picture mesh between them. No model
rasterization, no MSAA resolve, no shadow passes, no vertex uploads. The
plate cache was instrumented and hits 60 frames out of 60 -- the scene is
genuinely idle, which is why turning the 3D CRT monitor off in Settings
changes nothing measurable.
The CRT chain: 2 to 9 fullscreen passes at PICTURE size, plus one CopyResource of the picture-sized texture for the persistence prior.
Brightness and the final copy always run; bloom (3 passes), color bleed,
scanlines and gamma each skip when their parameter is zero.
Chrome: a full Dxui panel-tree repaint, uncached, every frame.
At 2880x1837 the two plate composites alone are ~10.6 Mpix of blending per
frame, 635 Mpix/s, to re-lay furniture that is bit-identical to the previous
frame. The picture is a small hole in the middle of them.
What blocks partial redraw
Three unconditional full-window operations:
DxuiRenderTarget.cpp:288 clears the entire back buffer.
DeskScene::Render composites the back and front plates over the whole
back buffer (CompositeFullTarget).
DxuiHwndSource::PaintContent calls RootPanel()->Paint, which walks the
entire panel tree. Dxui has no invalidation model.
And the swap chain is DXGI_SWAP_EFFECT_FLIP_DISCARD
(DxuiHwndSource.cpp:1418), which discards back-buffer contents -- which is
why dirty-rect Present1 was previously found inert here.
Step 1 (do this first): where do the ~14 points actually go?
At 5.29 Mpix the area term contributes ~14 of the 18.9 points. That is split
between the two plate composites, the chrome repaint, and the present /
DirectComposition composite, and the split is unknown. Get it before
committing to either direction below -- if chrome dominates, option B is the
answer and the swap-chain work is the wrong target.
Suggested instrument: D3D11 timestamp queries around each stage, or simply
short-circuit one stage at a time in a scratch build and re-measure back to
back in one sitting.
Measure in Release. The numbers above are Debug and are useful only as
ratios. And never A/B GPU cost across separate builds -- this iGPU's clocks
move enough between runs to invent effects that do not exist; switch the thing
at runtime, or short-circuit and re-measure in one session.
Option A: real dirty-rect presentation
Switch to DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL, then scissor the clear, the
composites, and the chrome paint to the changed region.
The swap-effect change itself is one line and buys nothing on its own.
It only removes the prohibition on partial redraw; the partial redraw is the
work.
FLIP_SEQUENTIAL does not hand you the previous frame. With BufferCount = 2 the buffer you acquire holds the frame from two presents
ago, so this needs per-buffer dirty tracking (union the dirty regions over
the last N frames) or alternating frames show stale content.
Scissoring the clear and the two composites is contained -- they are already
textured quads.
Scissoring the chrome is the Dxui-wide invalidation refactor: panels
track dirty rects, the painter clips, and every widget that animates has to
report invalidation correctly. This is the bulk of the work and nearly all
of the risk; a missed invalidation is a visual artifact that surfaces in one
interaction weeks later. Note this overlaps Move Dxui compositing off the UI thread (retained-mode immutable-snapshot) #100, which wants a retained-mode
immutable-snapshot model for a different reason (getting paint off the UI
thread) -- if Move Dxui compositing off the UI thread (retained-mode immutable-snapshot) #100 happens, its snapshot model is the natural place for
invalidation to live, and these should be planned together rather than
twice.
Option B: a composed-frame cache (no Dxui refactor)
Keep a persistent full-size "composed frame" texture. Render the picture into
it scissored, and copy it to the back buffer. Two full-screen blends plus a
full chrome repaint become one full-screen copy plus a small blend.
This needs only a coarse global "chrome changed" flag -- any input event,
tooltip tick, animation or state change sets it, otherwise the previous
composition is reused -- not per-panel regions. Still O(window area), but a
copy is much cheaper than blend-plus-repaint.
Lower ceiling than A, far lower cost and risk. Probably the better
effort/reward if step 1 says chrome and composites are the bulk.
What will not go away
Whatever is done here, the floor is set by:
DWM / DirectComposition compositing the window itself. This is a composition
swap chain (CreateSwapChainForComposition), and that composite is not ours
to scissor.
The CRT chain running its passes at picture size.
Re-rendering the picture region itself when it genuinely changes.
So "zero while the picture animates" is not on the table; the goal is to make
the cost scale with the PICTURE's area rather than the WINDOW's.
Prior art in the tree
21f7ff16 -- sized the CRT chain to the picture instead of the back buffer
(64% -> 24.5% at the time).
44972523 -- cached the desk scene into the two plates (41.6% -> 15.8%).
918b93c0 -- stopped presenting 60 fps over a static picture; idle went
47% -> 0%. That fixed the IDLE case. This issue is the ACTIVE case, where
the picture is legitimately changing and the window is repainted around it.
Casso's steady-state GPU cost scales with window area even when the only
thing changing is the emulated picture, which occupies a small fraction of it.
Three things repaint the whole window every frame, unconditionally, and none of
them need to.
This issue is the investigation, not a committed design. Step 1 is a
measurement, and it decides which of the two candidate directions is worth
doing — possibly neither.
Where it stands today
Measured on
desk-scene-modelsat commit918b93c0, RELMER-SP8, Debug,at a BASIC prompt with a blinking cursor (so it presents every frame):
Roughly 4.7% fixed + 2.7% per Mpix of window area. For comparison, before
the CRT chain was sized to the picture (
21f7ff16) it was ~8% fixed +~8%/Mpix, so the area coefficient is already about a third of what it was.
Per frame, in that state, the GPU does:
cached back and front plates) and the picture mesh between them. No model
rasterization, no MSAA resolve, no shadow passes, no vertex uploads. The
plate cache was instrumented and hits 60 frames out of 60 -- the scene is
genuinely idle, which is why turning the 3D CRT monitor off in Settings
changes nothing measurable.
CopyResourceof the picture-sized texture for the persistence prior.Brightness and the final copy always run; bloom (3 passes), color bleed,
scanlines and gamma each skip when their parameter is zero.
At 2880x1837 the two plate composites alone are ~10.6 Mpix of blending per
frame, 635 Mpix/s, to re-lay furniture that is bit-identical to the previous
frame. The picture is a small hole in the middle of them.
What blocks partial redraw
Three unconditional full-window operations:
DxuiRenderTarget.cpp:288clears the entire back buffer.DeskScene::Rendercomposites the back and front plates over the wholeback buffer (
CompositeFullTarget).DxuiHwndSource::PaintContentcallsRootPanel()->Paint, which walks theentire panel tree. Dxui has no invalidation model.
And the swap chain is
DXGI_SWAP_EFFECT_FLIP_DISCARD(
DxuiHwndSource.cpp:1418), which discards back-buffer contents -- which iswhy dirty-rect
Present1was previously found inert here.Step 1 (do this first): where do the ~14 points actually go?
At 5.29 Mpix the area term contributes ~14 of the 18.9 points. That is split
between the two plate composites, the chrome repaint, and the present /
DirectComposition composite, and the split is unknown. Get it before
committing to either direction below -- if chrome dominates, option B is the
answer and the swap-chain work is the wrong target.
Suggested instrument: D3D11 timestamp queries around each stage, or simply
short-circuit one stage at a time in a scratch build and re-measure back to
back in one sitting.
Measure in Release. The numbers above are Debug and are useful only as
ratios. And never A/B GPU cost across separate builds -- this iGPU's clocks
move enough between runs to invent effects that do not exist; switch the thing
at runtime, or short-circuit and re-measure in one session.
Option A: real dirty-rect presentation
Switch to
DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL, then scissor the clear, thecomposites, and the chrome paint to the changed region.
It only removes the prohibition on partial redraw; the partial redraw is the
work.
FLIP_SEQUENTIALdoes not hand you the previous frame. WithBufferCount = 2the buffer you acquire holds the frame from two presentsago, so this needs per-buffer dirty tracking (union the dirty regions over
the last N frames) or alternating frames show stale content.
textured quads.
track dirty rects, the painter clips, and every widget that animates has to
report invalidation correctly. This is the bulk of the work and nearly all
of the risk; a missed invalidation is a visual artifact that surfaces in one
interaction weeks later. Note this overlaps Move Dxui compositing off the UI thread (retained-mode immutable-snapshot) #100, which wants a retained-mode
immutable-snapshot model for a different reason (getting paint off the UI
thread) -- if Move Dxui compositing off the UI thread (retained-mode immutable-snapshot) #100 happens, its snapshot model is the natural place for
invalidation to live, and these should be planned together rather than
twice.
Option B: a composed-frame cache (no Dxui refactor)
Keep a persistent full-size "composed frame" texture. Render the picture into
it scissored, and copy it to the back buffer. Two full-screen blends plus a
full chrome repaint become one full-screen copy plus a small blend.
This needs only a coarse global "chrome changed" flag -- any input event,
tooltip tick, animation or state change sets it, otherwise the previous
composition is reused -- not per-panel regions. Still O(window area), but a
copy is much cheaper than blend-plus-repaint.
Lower ceiling than A, far lower cost and risk. Probably the better
effort/reward if step 1 says chrome and composites are the bulk.
What will not go away
Whatever is done here, the floor is set by:
swap chain (
CreateSwapChainForComposition), and that composite is not oursto scissor.
So "zero while the picture animates" is not on the table; the goal is to make
the cost scale with the PICTURE's area rather than the WINDOW's.
Prior art in the tree
21f7ff16-- sized the CRT chain to the picture instead of the back buffer(64% -> 24.5% at the time).
44972523-- cached the desk scene into the two plates (41.6% -> 15.8%).918b93c0-- stopped presenting 60 fps over a static picture; idle went47% -> 0%. That fixed the IDLE case. This issue is the ACTIVE case, where
the picture is legitimately changing and the window is repainted around it.