A useful minimum for a Windows GUI
What this experiment asks
How small can a useful Windows application remain without sacrificing text input, accessibility, or responsive interaction? XUI explores that question with a native C++20 framework, a file browser, a Task Manager sample, and a control gallery. This report includes September 13, 2026 measurements, the subsequent resize investigation, and an audit of the earlier flip-model claim.
Correction: the earlier flip-model experiment did not establish that an explicit swap chain cannot fix resize retention.
It measured a different raw-renderer configuration at one fixed size, not the application through grow/shrink cycles.
The statement that flip-model rendering failed to solve the resize problem exceeded that evidence.
The
The subsequent matched comparison used the exact explicit-swap-chain branch and its baseline.
The branch reduced transient private working set, but did not eliminate retained private commit on this machine.
After resize cycles and 30 seconds at the original small size, median commit was 99.80 MiB baseline versus 102.32 MiB branch.
The
The result is not a claim of the smallest possible GUI. A blank HWND is smaller, but it does not provide a usable application. Nor is this a comparison against Qt, Electron, or WinUI. That comparison requires an equivalent application, workload, and measurement protocol.
Three findings matter:
- One shared Direct2D target removed substantial presentation overhead in an earlier browser.
- Deferred native creation and smaller retained caches reduced warm gallery private commit by 8.09 MiB.
- A later renderer candidate saved memory, but failed to establish performance equivalence. The production change was reverted.
A separate hardware-rendering experiment exposed a roughly 64.53 MiB allocation jump across one pixel of client width. That result limits framework-only explanations for the observed memory footprint. It does not establish that every retained allocation is correct.
A useful minimum, not a blank window
The implemented roadmap contains 25 control families, not merely 25 primitive widgets. The gallery contains 46 pages. Its scope includes collections, navigation, documents, command surfaces, vector graphics, and optional runtime hosts. The applications use the public control API rather than application-specific drawing callbacks.
The architecture separates responsibilities:
Application composition and callbacks
|
Identity and state -> Layout -> Interaction
| |
+---- Drawing --+---- UI Automation
|
Win32 + Direct2D + DirectWrite + WIC
Native text peers
Stable identities connect selection, focus, asynchronous results, and UI Automation (UIA). Layout uses device-independent coordinates. The backend handles native windows, drawing, and accessibility. This separation permits cache release without deletion of the application model.
Win32 owns the windows and message loop. One Direct2D HWND render target serves each root window, including its custom controls. DirectWrite supplies text layout. Windows Imaging Component (WIC) supplies image decoding. Native EDIT and RichEdit peers preserve platform editing behavior.
The minimum includes keyboard navigation, Unicode text, selection, native undo, and IME-aware input handling. Removing those features to obtain a smaller memory number changes the question. Custom UIA providers and native text providers also remain part of the application. Their cost is not an optional defect.
Virtual collections draw visible rows rather than retaining a native control for every row. Bounded queues, cancellation, and generation checks limit asynchronous work. Ordinary idle windows use event-driven updates rather than a continuous render loop. The live Task Manager still samples periodically because its data changes.
The base build uses the static MSVC runtime and Windows system libraries. It needs no third-party runtime. Optional WebView2 is different: its browser runtime and child processes have real costs. The native memory workloads did not initialize that browser. Their process counters therefore do not describe an active browser application.
What the counters mean
One MiB means 1,048,576 bytes. These counters answer different questions:
Process.PrivateMemorySize64 supplies private commit in the measurement script.
QueryWorkingSet identifies private resident pages.
Framework counters separately report targets, peers, text layouts, and estimated native bitmap bytes.
The bitmap estimate uses four bytes per pixel and excludes driver allocations.
Working-set trimming can reduce resident pages without reducing committed memory. These experiments used no working-set trim, forced garbage collection, or heap compaction. A small executable does not imply a small runtime footprint. The runtime, fonts, system components, graphics surfaces, and driver allocations also consume resources.
Timing needs the same care. Process CPU time measures work charged to the target process, but Windows reports it in quantized increments. Process cycle counts are separate measurements, not elapsed milliseconds. Wall time includes scheduling, synchronous dispatch, and presentation waits. Frame timing can reflect display synchronization rather than drawing cost alone.
The startup probe observes a completed root paint. It does not identify the first pixel that the compositor displays. Forced full-paint timings and normal input timings are distinct workloads.
Conditions and binary scope
The recent experiments used native ARM64 binaries, not x64 emulation.
The recorded machine used a Qualcomm Adreno X1-85 GPU with driver 31.0.133.1.
The configuration log records Windows 10.0.28637.
The compiler was MSVC 19.44, with Windows SDK 10.0.26100.0.
Release compilation used /O2, /Ob2, /MT, /GL, and /LTCG.
Matched comparisons retained the same hardware, architecture, data, dark theme, and 96 DPI. They used equal physical client sizes, not equal outer-window dimensions. Builds and tests stopped before measurement. The recent performance runs required an owned foreground window. The Task Manager used live process data, so its row counts varied.
The final retained-feature binaries had these file sizes:
These are individual artifacts, not a combined deployment total. This validation build enabled optional WebView2 support, although the measured native workloads did not load it. The default CMake configuration disables that integration.
An early foundation executable occupied 265,216 bytes. That historical milestone had a much smaller feature scope. It is not a head-to-head comparison with the current gallery or a complete distribution-size claim.
Two accepted changes, two different baselines
One target instead of nine
The September 11 browser comparison used five fresh processes per binary.
Both variants used 60 fixture files and a 924-by-641 client area at 96 DPI.
The historical baseline was ab9070b.
Values below are medians, with observed ranges.
These historical aggregates come from the milestone archive.
Their original run files are absent from this checkout.
The change removed eight target/brush pairs, not eight controls. The approximately 6.3-fold improvement applies to that combined dispatch-and-presentation workload. It is not a claim about every operation. The smaller private working set is also not a private-commit result.
Retain state, release expendable resources
The September 13 resource pass used three fresh processes per variant. The gallery completed two cycles through all 46 pages. Each cycle included a retained popup open/close and a return to the first page.
These values are medians. The gallery saved 8.09 MiB of private commit. The smaller Task Manager and Explorer differences do not establish reductions beyond run variation.
PageView now materializes native peers on first selection.
Visited peers remain alive, including native editor selection and undo.
Hidden controls release cached text layouts.
The native composition buffer matches current clipped regions instead of independent historical maximum dimensions.
The renderer also releases scene geometry unused by the completed frame.
The initial CPU counter includes startup and settlement before the sample. Deferred creation reduced that work but increased first-visit latency. The warm ranges overlapped, which does not prove performance equivalence. Settled gallery and Explorer observations recorded zero idle paints. The Task Manager retained its normal sampler.
This comparison used forced layout and full-paint navigation. It must remain separate from the later normal-input experiment. Neither accepted change makes every interaction faster.
A memory saving that did not ship
The next pass applied a stricter requirement: another memory reduction must not sacrifice performance.
Its baseline already included the accepted resource changes.
The candidate set D2D1_RENDER_TARGET_USAGE_GDI_COMPATIBLE on the hardware target.
Six matched runs per application produced these initial private-commit medians:
Run order alternated between baseline-first and candidate-first. A further six matched gallery runs used normal input updates instead of forced updates. The analysis used 10,000 deterministic paired bootstrap resamples. Its independent units were matched runs, not correlated page events.
The following differences are candidate minus baseline for mean first-visit navigation:
The forced workload became slower. The normal-input interval permitted both improvement and regression. It did not establish equivalence, nor did it prove a universal slowdown.
The candidate was therefore reverted.
All four final production .text sections matched the preserved baseline.
This pass delivered no new runtime memory improvement.
Retain-contents, minimum feature level 9, bitmap remoting, and immediate presentation produced no established improvement in the exploratory fixed-size runs.
A raw flip-sequential target also retained substantial memory at 1080x780.
That result did not cover changed-size cycles or an explicit swap chain integrated into XUI.
The performance-based rejection applied to the GDI-compatible target, not an application-level flip-model implementation.
The hardware allocation boundary
A separate raw probe exercised Direct2D without XUI controls, text, images, or application workers.
The recent boundary comparison used its rounded drawing mode.
That path draws a rectangle and rounded rectangle within a clip.
It does not represent an application framework benchmark.
At 865 pixels, a separate first-paint snapshot recorded 3,313,070 bytes of busy heap memory against 93,450,240 bytes of private commit. Those are different counters and sampling stages. The disparity argues against ordinary retained control objects as the dominant explanation. Mapped and image commit remained unchanged across the boundary samples.
The evidence supports a graphics-triggered allocation. This latest pass did not collect exact driver allocator stacks. It does not prove that all framework code is correct or that resize-related leaks are impossible.
A cache retains reusable resources.
A high-water allocation retains capacity after peak demand.
A leak retains resources without a valid continuing owner.
Those definitions do not diagnose a particular memory curve.
The
Reproduce the measurements
Build the native baseline
Install Visual Studio 2022 with ARM64 C++ tools, CMake, and a Windows SDK.
Open a developer PowerShell with CMake on PATH.
Run these commands from the repository root:
cmake -S . -B build\report-repro -G "Visual Studio 17 2022" -A ARM64 `
-DBUILD_TESTING=ON -DXUI_ENABLE_WEBVIEW2=OFF
cmake --build build\report-repro --config Release --parallel 4
cmake --build build\report-repro --config Release --target xui_window_memory_probe
The probe target is excluded from the default build.
The generator selects an installed Visual Studio instance.
Multiple installations can require an explicit CMAKE_GENERATOR_INSTANCE.
This base build excludes optional browser support, unlike the recorded integration build.
Measure owned application windows
Stop unrelated builds and tests. Keep the measurement window in the foreground. Create a new fixture directory without overwriting an existing directory:
$fixture = Join-Path (Get-Location) 'build\report-repro\owned-files-60'
if (Test-Path $fixture) { throw 'Fixture already exists. Use a new owned directory.' }
New-Item -ItemType Directory $fixture | Out-Null
1..60 | ForEach-Object {
Set-Content (Join-Path $fixture ('owned-file-{0:D2}.txt' -f $_)) `
'Owned XUI memory comparison fixture.'
}
& .\tests\measure-window-memory.ps1 `
-Executable build\report-repro\Release\xui_demo.exe `
-Arguments ('"' + $fixture + '"') -Runs 3 -Sizes @() `
-Performance -ResourceMetrics `
-Output build\report-repro\explorer.json
& .\tests\measure-window-memory.ps1 `
-Executable build\report-repro\Release\xui_gallery.exe `
-Gallery -Runs 3 -Sizes @() -Performance -ResourceMetrics `
-Output build\report-repro\gallery.json
& .\tests\measure-window-memory.ps1 `
-Executable build\report-repro\Release\xui_task_manager.exe `
-TaskManager -Runs 3 -Sizes @() -Performance -ResourceMetrics `
-Output build\report-repro\task.json
-Sizes @() preserves the initial extent and avoids a size sweep.
The JSON records the actual physical client size and DPI.
The performance mode requires exactly 60 Explorer rows and completed visible thumbnails.
Gallery mode requires exactly 46 catalog pages.
It includes two navigation cycles and popup closure.
-ForceInputPaint selects the distinct forced-update protocol.
Measure fresh raw targets
Run each size in a fresh process:
foreach ($width in 864,865) {
& .\tests\measure-window-memory.ps1 `
-Executable build\report-repro\Release\xui_window_memory_probe.exe `
-Arguments "rounded $width 780 0" -Runs 1 -Sizes @() `
-Regions -CaptureOutput `
-Output "build\report-repro\raw-$width.json"
}
The probe accepts positional arguments: kind, width, height, automatic-close flag, usage, presentation flags, and minimum feature level.
There is no --raw-d2d switch.
The script owns and closes each process.
These commands generate new observations, not guaranteed copies of the recorded results.
Historical before/after comparisons additionally require their preserved binaries.
Evidence, coverage, and limits
The latest default-renderer build passed all 37 native tests on its first attempt. Coverage includes native composition, UIA, editor retention, simulated device recreation, and injected DPI. These tests do not replace physical mixed-monitor moves, real IME sessions, screen-reader use, or long-duration operation. They also do not prove performance equivalence.
The first binding smoke attempt hit a key-posting race after an intentional-failure client closed its window. The unmodified retry passed all eight smoke cases. Both attempts remain in the local evidence.
Key source references:
include\xui\controls.hpp:ControlandPageViewidentity, layout, and retained state.src\application.cpp:Window::Implpeer management andApplication::run.src\drawing.cpp:Drawing::begin,Drawing::scene, and resource counters.tests\measure-window-memory.ps1:SnapshotandWindowMemoryProbe.Measure.tests\window_memory_probe.cpp:wmain,paint, andresources.tests\measure-browser.ps1: the historical 60-file browser protocol.CMakeLists.txt: runtime, architecture, optional integration, and probe targets.
Local provenance, relative to the repository root:
The listed build artifacts are local, ignored evidence, not downloadable attachments. This report includes the central aggregates so readers do not need those files to understand its conclusions. Raw artifacts can contain local paths and require sanitization before sharing.
The practical lesson is to minimize duplicated rendering resources and expendable caches while preserving useful state. Memory, input latency, and correctness need separate evidence. When its performance cost remains uncertain, a rejected optimization is a valid result.
Resize investigation — September 13, 2026
Result and scope
The demo reproduced the large increase and retained most of it after a resize back to the small size. The evidence indicates bounded, factory-associated graphics retention in these runs, not an accumulating XUI resource leak. This result does not establish that every XUI workload is free of leaks. No production renderer change resulted from this investigation.
The reported “10 MB commit” remains an observation with an unknown counter and sampling time. The current demo reached about 30 MiB private commit and 15 MiB private working set at the first completed paint observed. It reached about 33 MiB private commit after ten seconds. After enlargement, private commit exceeded 100 MiB. After shrinking and thirty seconds of idle time, private working set remained near 83 MiB. Thus, the large increase and retention reproduced, but the exact initial 10 MB commit value did not.
Method
The investigation used the current build\controls\Release\xui_demo.exe and an independent rebuild in build\resize-memory.
The controls, memory-tight, and resize-memory demo builds had identical .text hashes.
The rebuild used ARM64 Release, MSVC 19.44.35228.0, /MT, LTO, SDK 10.0.26100.0, and the existing optional WebView2 SDK.
Windows reported version 10.0.28637.0 and Adreno X1-85 driver 31.0.133.1.
Each workload used three fresh processes. The demo used an owned directory with exactly 60 text files. Each process completed a fixed-size batch and a varied-size batch:
- The small client measured 800 × 600 physical pixels.
- The fixed batch enlarged the client to 1100 × 850, then restored 800 × 600.
- The varied batch used 1400 × 500, 800 × 1000, and intermediate dimensions before each return to 800 × 600.
- The current demo, raw probe, and empty XUI probe completed 24 cycles per batch.
- The rebuilt demo completed 50 cycles per batch, or 100 cycles per process.
All measured windows reported 96 DPI.
The script recorded requested and actual dimensions.
It required completed paints and new XUI layouts after size changes.
The final raw probe also counted WM_SIZE messages and exposed the actual target pixel dimensions.
The demo minimum outer size was 460 × 420 DIPs, so it did not constrain these dimensions.
The script recorded startup, the first completed paint observed, and one-second and ten-second samples. Thirty same-size paints at 800 × 600 preceded the first large resize. Each cycle included a sample after the shrink. Final idle samples occurred after 0.5, 2, 10, and 30 seconds. The earliest process sample preceded window discovery, but it was not a synchronized prepaint breakpoint. The raw probe separately recorded entry and prepaint heap counters.
Measurements included private commit, private working set, allocation bases, commitment histograms, handles, GDI objects, USER objects, and existing XUI resource counters. The histogram groups committed bytes by allocation base, not by individual VirtualQueryEx subregion. HeapWalk samples covered the raw and empty XUI probes, not the unmodified demo. The counters came from separate API calls, so startup and immediate-resize samples were not atomic. The script used no working-set trimming, heap compaction, or forced collection.
Only newly created processes received window messages. The main script required the launched PID and the expected root window class. No existing user window received a resize or close message. These were synchronous redraw experiments, not foreground-input or resize-latency benchmarks.
Memory results
Values are medians of three processes, in MiB. The initial sample occurred after ten seconds. The demo initially measured 924 × 641 pixels. The other workloads initially measured 800 × 600 pixels.
Immediately after the final shrink, the rebuilt demo retained more temporary memory. Its median private commit was 113.25 MiB at 0.5 seconds and 113.09 MiB at two seconds. It decreased to 99.44 MiB by ten seconds without an explicit release request. The thirty-second value was also 99.44 MiB.
Some shorter batches contained discrete allocation steps rather than an immediately flat curve. The extended demo runs separated those steps from continued growth. The table uses cycles 25–50 within each batch, with least-squares slopes from the individual process samples.
The small-window resource counters stayed constant through both batches in all six demo processes. Each sample had one render target, 49 retained peers, 14 live text layouts, 65 GDI objects, and 74 USER objects. The native DIB returned to 51,360 bytes, and the native bitmaps returned to 93,440 bytes. At the first large size, those buffers occupied 75,360 and 141,440 bytes respectively. The cumulative text-layout creation counter increased, but the live layout count did not. These results exclude the previously corrected independent width/height buffer-retention pattern from this reproduction.
Drawing-operation and lifetime isolation
An empty window alone did not reproduce the large allocation.
Three additional fresh-process runs per mode compared raw Clear, a clipped rectangle, and an unclipped rectangle.
All modes used the same hardware-capable HWND target configuration and the same physical sizes.
These short samples included repeated paints and a one-second idle interval. They were not the thirty-second samples from the main experiment. A rectangle without clipping was sufficient to reproduce the large increase. Neither rounded geometry, text, native controls, nor XUI buffer ownership was necessary. Therefore, a clear-only window is not a representative memory baseline for a populated interface on this system.
The raw probe then released resources without exiting its process. After the resize batches, releasing the brush and render target left a median 90.03 MiB private commit after thirty seconds. The factory remained alive, and the render-target counter was zero. Releasing the factory as well reduced private commit to 3.87 MiB and private working set to 2.38 MiB. Recreating the factory and a small target restored a usable window.
An independent repeat used 40 resize cycles in one fresh raw-probe process.
Target-only release retained 90.03 MiB of private commit after thirty seconds.
Factory release reduced commit to 3.88 MiB by ten seconds, without process exit.
The samples remain in build\resize-memory\parent-raw-verification.json.
VirtualQueryEx showed large commitment changes inside existing private reservations. The repeated batches did not retain a new large allocation base for each resize. The factory-release experiment removed the large reservations. Raw HeapWalk busy bytes remained near 3.2–3.3 MiB during the resize batches. All sampled heaps supported HeapWalk. The large retained allocation therefore did not correspond to comparable growth in the enumerated heap blocks.
The raw and empty XUI probes also destroyed and reopened five root windows in each process.
The raw probe retained its factory across those root lifetimes.
Its large allocation survived root destruction, without another 64 MiB increase for each root.
The empty XUI probe destroyed each complete Window object before the next root.
Its handles and GUI object counts remained bounded, and later reopened-window memory stayed in the same range.
These checks used the same process throughout, not process exit as evidence of resource release.
The strongest attribution is the Direct2D factory-associated rendering path on this hardware and driver. The experiment did not collect allocator call stacks. It cannot distinguish an internal Direct2D cache from a driver cache with the same lifetime. It also cannot assign every small retained page to one owner or exclude smaller, slower leaks. Factory recreation on each shrink discards useful rendering resources. Such a change requires separate correctness and performance evidence. This investigation did not make that production change.
Reproduction and validation
Use the build procedure earlier in this report to create the demo and probe.
Use the owned 60-file fixture procedure from the same section.
For the recorded build profile, enable the existing optional WebView2 SDK and XUI_DESKTOP_TESTS.
Use fresh output names for each run.
& .\tests\measure-resize-memory.ps1 `
-Executable build\report-repro\Release\xui_demo.exe -Kind demo `
-Fixture build\report-repro\owned-files-60 -Runs 3 -Cycles 50 `
-Output build\report-repro\resize-demo.json
& .\tests\measure-resize-memory.ps1 `
-Executable build\report-repro\Release\xui_window_memory_probe.exe `
-Kind raw -Runs 3 -Cycles 24 -Lifetimes 5 `
-Output build\report-repro\resize-raw.json
& .\tests\measure-resize-memory.ps1 `
-Executable build\report-repro\Release\xui_window_memory_probe.exe `
-Kind xui -Runs 3 -Cycles 24 -Lifetimes 5 `
-Output build\report-repro\resize-empty-xui.json
foreach ($kind in 'clear','clip','noclip') {
& .\tests\measure-window-memory.ps1 `
-Executable build\report-repro\Release\xui_window_memory_probe.exe `
-Arguments "$kind 800 600 0" -Runs 3 `
-Sizes @('1100x850','800x600') -Regions -CaptureOutput `
-Output "build\report-repro\primitive-$kind.json"
}
The probe now accepts an eighth positional argument for the number of root lifetimes in raw and xui modes.
Its test-only messages support heap snapshots, target-only release, factory release, and recreation.
The main script performs the ownership checks before these messages.
Both targeted regressions passed: xui_window_tests and xui_split_window_tests.
The checks covered existing native-resource lifetimes, retained editor state, resize, and simulated DPI changes.
The evidence checker also passed for all twelve main measurement processes.
It checked completed cycles, physical dimensions, constant retained-resource counters, root lifetimes, and executable hashes.
It did not impose a driver-specific memory threshold.
The first raw attempt exposed an error in the measurement script, not a renderer failure.
Generic visible-window discovery was too broad for the gap between root lifetimes.
The corrected script filters by PID and root class.
The failed attempt remains in the evidence, separate from the three successful raw repeats.
An initial CTest command selected no tests because desktop registration was disabled.
After registration, the final command used --no-tests=error and passed both tests.
Local evidence remains under build\resize-memory:
delivery.json,baseline.json, andmanifest.json: conclusions, binary hashes, source hashes, and build profile.demo-current.jsonanddemo-extended.json: six demo processes and all resize samples.raw-final.jsonandempty-xui.json: repeated resize and same-process lifetime samples.primitive-clear.json,primitive-clip.json, andprimitive-noclip.json: drawing-operation comparisons.- The
.stdout.txtsidecars: heap counters and resource-release boundaries. summary.json,summarize.py,verification.json, andverify.py: aggregates and evidence checks.window-tests-final.log: the two successful native regressions.raw.jsonandwindow-tests.log: the failed harness attempt and the initial empty test selection.
Audit of the earlier flip-model claim
The explicit-swap-chain branch prompted a review of the earlier claim.
Leonard Hecker supplied commit 4c1f9ab21d7e72c9f105176537a941c58e28aece, based on 74bc1f8d64a8f4b889b52017eab7932d8dc0126b.
The accompanying account reports WPR allocation traces through ID2D1HwndRenderTarget::Resize.
That trace attribution is supplied evidence, not an independently inspected trace in this report.
What the earlier experiment actually did
The earlier probe was real.
tests\window_memory_probe.cpp::paint creates a D3D11 device, a flip-sequential swap chain, and a Direct2D surface render target.
The preserved build log and two output files record the flip path.
The later output also reports hardware_supported=1.
The final recorded invocation was:
xui_window_memory_probe.exe flip 1080 780 0 0
build\memory-tight\raw-flip-final.json records 93,528,064 bytes of initial private commit, or 89.20 MiB.
Its executable hash is 545FF967335DE0ED36B7BC4B7BFB884471592F53B1BC83E8E07E2FEC96963A41.
The earlier raw-flip.json records an initial sample and another sample at the same 1080x780 size.
Neither file establishes behavior through a changed-size cycle.
The small reduction in that experiment was not a performance-based rejection of the later branch.
Why it is not the same experiment
The branch releases target-dependent resources before ResizeBuffers and explicitly manages presentation.
The old application instead called ID2D1HwndRenderTarget::Resize.
This is a substantive renderer change, not the rejected GDI-compatible target flag.
The original writeup generalized from one raw configuration to a class of solutions. That was an error in experimental scope and interpretation. A high memory result in the earlier probe cannot establish that this branch has the same behavior. Likewise, several changed properties prevent attribution of any improvement to the swap effect alone without a controlled comparison.
The earlier measurements remain in the report as historical evidence, not as grounds to reject the branch. The subsequent comparison separates memory savings, performance costs, and rendering correctness.
Matched branch results
Both builds used the exact commits identified earlier, exported with git archive.
Neither source snapshot contained renderer edits or diagnostic instrumentation.
The builds used ARM64 Release, MSVC 19.44.35228, Windows SDK 10.0.26100.0, /MT, and LTO.
Both enabled the existing optional WebView2 SDK for the regression suite.
The memory workload did not initialize WebView2.
The machine used Qualcomm Adreno X1-85 graphics, driver 31.0.133.1, and 96 DPI. Module snapshots from both measured demo processes contained the Qualcomm D3D11 driver. Neither snapshot contained the WARP module. This supports hardware rendering, but does not replace a direct device query or an ETW trace. The measurement procedure did not force WARP.
Three fresh demo processes per variant used the same 60-file fixture. The run order was baseline, branch, branch, baseline, baseline, branch. Each process completed 24 fixed grow/shrink cycles and 24 varied-size cycles. The fixed client sizes were 800x600 and 1100x850 pixels. The varied workload also included 1400x500 and 800x1000 sizes. Final samples followed 0.5, 2, 10, and 30 seconds at 800x600.
The table reports medians across the three processes, in MiB. Private commit and private working set are separate counters.
The first enlargement used 17.09 MiB less private working set with the branch. The varied-batch sample used 8.85 MiB less private commit. These are real improvements at those observation points, not proof of a lower continuous peak. Neither improvement persisted in the final settled counters. Final commit ranged from 99.73 to 100.24 MiB for the baseline and 102.16 to 104.32 MiB for the branch.
Thus, the exact branch did not remove the retained-commit increase in this workload. This result does not disprove the supplied WPR attribution. Allocation stacks and retained process counters answer different questions. It also does not rule out another swap-chain configuration or a different hardware result. No configuration ablation isolated the effects of the device flags, buffer count, scaling, target type, or alpha mode.
Performance observations
The fixed-resize workload produced 144 synchronous timing observations per variant, across three processes. Median wall time was 25.51 ms baseline versus 30.22 ms branch. The 95th percentile was 39.84 ms versus 53.30 ms. These observations include layout, the requested repaint, and presentation waits. They are not input-to-photon measurements or 144 independent process runs.
Separate interaction profiles completed three runs per variant for Explorer and Task Manager. The gallery completed three baseline runs and two branch runs. Foreground loss interrupted additional attempts. The evidence retains those attempts but excludes them from numerical aggregates. Other system activity remained uncontrolled, so these samples do not establish performance equivalence. No performance threshold stopped the branch comparison or caused its rejection.
Rendering and recovery findings
The unmodified branch passed 32 of 37 native tests. The five corresponding baseline tests passed. All five branch retries failed, although some assertions depend on capture or desktop interaction rather than renderer correctness alone.
Separate Windows Graphics Capture checks used only each test process's root HWND. They showed the unmodified demo at five physical sizes, with visible labels and file icons, followed by normal exit. The document-control test also passed native text composition, UIA, target recreation, and nine theme/DPI cases. Those results do not replace the incomplete flicker and Shell-thumbnail coverage. The suite is not a clean pass, and the capture failures do not prove that users see missing icons.
Source inspection found a recovery gap in Drawing::begin, at branch src\drawing.cpp:203-205.
If ResizeBuffers returns DXGI_ERROR_DEVICE_REMOVED or DXGI_ERROR_DEVICE_RESET, the method discards graphics resources and returns false.
Native::paint in src\application.cpp then calls EndPaint without scheduling another paint.
An unrelated later event can trigger recovery, but this failure path does not request recovery itself.
This is a source finding, not a reproduced device-loss event.
Existing target-recreation tests do not establish coverage of that new path.
The branch also changes the Direct2D alpha mode from IGNORE to PREMULTIPLIED.
Microsoft documents that a non-IGNORE alpha mode changes default text antialiasing from ClearType to grayscale.
The branch does not explicitly override that default.
This is a rendering behavior change, not by itself a reason to reject the branch.
The reference is ClearType and alpha modes.
Branch evidence and reproduction
The checkout remains on main.
This review did not merge the branch, edit production code, or create a commit.
The baseline demo is 1,403,392 bytes, and the branch demo is 1,405,440 bytes.
Their local paths are:
D:\dev\private\xui\build\swapchain-review\baseline-build\Release\xui_demo.exe
D:\dev\private\xui\build\swapchain-review\branch-build\Release\xui_demo.exe
build\swapchain-review\benchmark-delivery.json records commands, source archives, binary hashes, measurements, test results, and limitations.
The six resize-*.json files contain the raw resize samples.
The perf-*.json files contain the completed interaction profiles.
The branch-tests.log, baseline-diagnostic-tests.log, and branch-diagnostic-tests.log files retain every regression result.
The compositor-*.bmp files contain the owned-window captures.
Git does not track these local artifacts.
For an additional measurement with the existing branch build, use a fresh output filename:
& .\tests\measure-resize-memory.ps1 `
-Executable build\swapchain-review\branch-build\Release\xui_demo.exe `
-Kind demo -Fixture build\swapchain-review\fixture -Runs 1 -Cycles 24 `
-Output build\swapchain-review\additional-branch-resize.json