Skip to content

Rasterize headless frames only when their pixels are read - #602

Merged
matt-edmondson merged 2 commits into
mainfrom
claude/project-thread-fbbnzt
Oct 8, 2026
Merged

matt-edmondson merged 2 commits into
mainfrom
claude/project-thread-fbbnzt

Conversation

@matt-edmondson

@matt-edmondson matt-edmondson commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Requested by Matt · project thread

Before: ImGuiAppHarness.Step rasterized every frame on the CPU, whether or not a test ever looked at it. Profiling ImageGui's UI suite (ktsu-dev/ImageGui#76) put about 95% of its time there.

After: a stepped frame is rasterized only when its pixels are read, through Capture() or Target. Stepping is cheap; a read pays for one frame. What a read sees is unchanged: every frame clears the whole target, so the last frame stepped is the only one that matters, and it is drawn with the textures it was submitted with.

How: HeadlessImGuiContext.EndFrameDeferred hands the draw data to SoftwareRenderer, which draws it lazily from the Target getter. The harness discards an unread frame before the next NewFrame, which is when ImGui reuses that draw data, and before disposing the context. Any texture or render-target mutation while a frame is pending (UpdateTexture, DeleteTexture, render target clear/draw/resize/delete) draws the pending frame first, so a texture changed between a step and a read does not repaint the stepped frame. SoftwareRenderer used standalone (RenderDrawData, Clear) stays eager.

One caveat, documented on Target and in the harness README: a Bitmap32 held from Target across a step still shows the frame it was read on. Nothing in this repository does that.

DeferredRasterizationTests pins it: unread frames are never rasterized and a frame read twice is rasterized once; a read shows the last frame stepped; a texture updated, or a render target cleared, between step and read is drawn as submitted. Every suite in the solution passes locally in Release.

Measured locally (Release, same container, wall-clock per suite)

Suite main this PR
ImGuiAppDemo.UITests 254s 8s
ImGuiWidgetsDemo.UITests 221s 23s
ImGui.Widgets.UITests 166s 18s
ImGui.NodeEditor.Tests 108s 3s
ImGuiStylerDemo.UITests 50s 4s
ImGuiPopupsDemo.UITests 43s 3s
WidgetGallery.UITests 29s 10s
ImGuiSyntaxHighlightingDemo.UITests 12s 2s
ImGui.Popups.Tests 6s 1s
ImGuiMarkdownDemo.UITests 6s 2s
Total ~895s ~74s

🤖 Generated with Claude Code

https://claude.ai/code/session_01TB1B6ZUvpNiBAjpfYU42K2

ImGuiAppHarness now defers each frame's rasterization until Capture() or
Target reads it. Stepping hands the draw data to SoftwareRenderer, which
draws it on read; a step discards an unread frame before NewFrame reuses
its draw data. Changing a texture or render target while a frame is
pending draws that frame first, so a read always shows the last frame
stepped, drawn with the textures it was submitted with.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TB1B6ZUvpNiBAjpfYU42K2
@matt-edmondson matt-edmondson self-assigned this Oct 7, 2026
Comment thread tests/ImGui.App.Testing.Tests/DeferredRasterizationTests.cs Fixed
Comment thread tests/ImGui.App.Testing.Tests/DeferredRasterizationTests.cs Fixed
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TB1B6ZUvpNiBAjpfYU42K2
@sonarqubecloud

sonarqubecloud Bot commented Oct 7, 2026

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants