You said:
prime (HEAD)ChatGPT said:
I’ll audit the full range from 626259f^ through prime, examining per-commit and aggregate diffs, then run supported builds/tests and report concrete defects with commit and line evidence.
The 37 commits center on wintext.c/term.c; I’m prioritizing DIB ownership, clipping, pixel-write ordering, cache invalidation, and release-wide -O3/LTO changes.
Concrete regressions now include unsynchronized DIB writes, omitted horizontal-padding blits, a selected-font handle leak, and persistent DC state corruption. I also found scrollbar-cache invalidation and ASCII-path scope errors.
Additional final-code regressions include broken thick box-line junctions, VT52 fraction misclassification, double-height graphics clipping into the preceding row, and GDI+ caching a stale device context.
Verdict
Yes. I would not ship prime unchanged.
I audited all 37 commits from 626259f3acf83b68ec881e7e7b22356499ae7842 through prime at e343f8dc1055d6a4aad07cb4f88a62e99a2e0603. Of those, 25 are model-authored: 17 by Claude and 8 by OpenAI.
I found:
-
Eight actionable regressions in active code
-
One conditional regression in the normally disabled GDI+ fallback
-
One probable pixel-geometry regression that merits a Windows screenshot test
I did not find an obvious memory-corruption or security vulnerability. Most problems involve rendering correctness, persistent GDI state, resource lifetime, and incorrect optimization predicates.
Active regressions
1. Buffered updates never transfer the padding area
Introduced by: Claude, 3e3ad702
Retained/narrowed by: Claude, 47d2c456
Severity: High when DisplayBuffering=yes
win_paint_buffer_end() only blits the terminal text rectangle:
C// src/wintext.c:1920 int x = PADDING; int y = OFFSET + PADDING + top * cell_height; int w = cell_width * term.cols; int h = cell_height * (bot - top); BitBlt(dc, x, y, w, h, paint_buf_dc, x, y, SRCCOPY);
But rendering deliberately writes outside that rectangle:
-
src/term.c:4115–4122callswin_text(term.cols, ...)andwin_text(-1, ...)to erase italic/glyph overhang in the right and left padding. -
src/wintext.c:4493–4511extends background-image fills into left, right, top, and bottom padding.
Those pixels are drawn into paint_buf_dc but are never copied to the window.
Symptoms:
-
Stale or ghosted italic pixels at the left/right edge
-
Edge glyphs failing to erase their overhang
-
Background-image padding remaining stale after scrolling or repaint
-
Top/bottom image padding not being transferred on full repaint
Fix: Track a dirty pixel rectangle rather than only dirty rows, or blit the full client width for the dirty row band. Include top/bottom padding when the first or last terminal row is involved.
2. Font reconfiguration can leak an HFONT selected into the persistent buffer DC
Introduced by: Claude, 1b8af2b4
Severity: High for repeated font changes, zooming, DPI changes, or alternative-font reconfiguration
win_select_font() leaves the selected font inside the persistent paint_buf_dc:
C// src/wintext.c:1382 if (paint_buffered) { assert(dc == paint_buf_dc); SelectObject(dc, f); paint_buf_font = f; paint_buf_font_ok = true; }
During font recreation, win_init_fontfamily() merely forgets the cached handle:
C// src/wintext.c:775 win_select_font_reset(); for (uint i = 0; i < FONT_MAXNO; i++) { if (ff->fonts[i]) { DeleteObject(ff->fonts[i]); ff->fonts[i] = 0; } }
It does not deselect the font from paint_buf_dc. DeleteObject() therefore fails, the return value is ignored, and the program then discards the only handle reference. Microsoft explicitly documents that DeleteObject() returns zero when the object is currently selected into a DC. Microsoft Learn
Result: one GDI font handle can leak per affected reinitialization.
Fix: Before deleting font handles, select a stock font into paint_buf_dc, then reset the cache. Also check the DeleteObject() result in debug builds.
3. Direct DIB writes are no longer synchronized with preceding GDI drawing
Introduced by: Claude, 17fd6990
Severity: High; API-contract violation with potentially driver-dependent artifacts
f8a63aea correctly called GdiFlush() before writing directly into the DIB section. 17fd6990 removed that flush and replaced it with one flush at the beginning of the entire frame:
C// src/wintext.c:1883 GdiFlush();
Later in the same frame, GDI text or line operations can be issued, followed by:
C// src/wintext.c:1984 uint * row = paint_buf_bits + (size_t)fy * paint_buf_w + left; for (int fx = 0; fx < right - left; fx++) { row[fx] = pix; }
The beginning-of-frame flush cannot synchronize GDI commands issued after it. Microsoft’s CreateDIBSection documentation requires callers to ensure GDI has completed drawing before accessing the bitmap bits directly, specifically by using GdiFlush(). Microsoft Learn
The commit message’s empirical observation that this DC appeared “unbatched” is not a valid substitute for the API requirement.
Potential symptoms:
-
Background fills racing with glyph or line drawing
-
One-frame ordering artifacts
-
Driver-, OS-, or configuration-dependent corruption
Fix: Restore GdiFlush() before each transition from GDI drawing to CPU DIB access. A lower-overhead implementation could track whether GDI operations have occurred since the previous flush, but every relevant GDI call must then update that state.
4. GM_ADVANCED leaks permanently into the persistent buffer DC
Exposed broadly by: Claude, 3e3ad702
Severity: Medium
The RTL and width-scaling paths call SetGraphicsMode(dc, GM_ADVANCED):
C// src/wintext.c:4528 coord_transformed = SetGraphicsMode(dc, GM_ADVANCED);
and:
C// src/wintext.c:4751 coord_transformed2 = SetGraphicsMode(dc, GM_ADVANCED);
They restore only the world transform:
CSetWorldTransform(dc, &old_xform);
They never restore the previous graphics mode.
Before buffering, this state leak was mostly bounded to the current common window DC acquisition, although it could already affect later drawing in the same frame. The persistent memory DC makes it survive across every subsequent buffered frame.
That changes the behavior of later Rectangle() calls used for cursors at src/wintext.c:5728, 5735, 5805, and 5812. In GM_COMPATIBLE, the lower and right edges are excluded; in GM_ADVANCED, rectangles are lower-right inclusive. Microsoft Learn
Symptoms:
-
Cursor boxes becoming one pixel wider/taller after rendering transformed text
-
Possible inconsistent rectangle edges depending on whether RTL or width-scaled text has previously appeared
Fix: Use SaveDC()/RestoreDC() around each transformation. Alternatively, restore the world transform and then call SetGraphicsMode(dc, old_mode).
5. The scrollbar cache can retain application-provided scrollbar values
Introduced by: Claude, efdff1a5
Severity: Medium
The normal scrollbar cache is invalidated only when do_update() actually observes one of these conditions:
C// src/wintext.c:2144 if (cfg.scrollbar && term.show_scrollbar && !term.app_scrollbar) { ... } else { scrollinfo_cached = false; }
Application scrollbar sequences directly call SetScrollInfo() from win_set_scrollview():
C// src/winmain.c:1330 term.app_scrollbar = pos; if (pos) { SetScrollInfo(...); }
When pos == 0, the application mode is cleared but normal scrollbar values are not restored.
Terminal input is parsed as a batch, and only afterward is a display update scheduled at src/termout.c:6209–6210. Therefore an application can send, in one input write:
CSI 5;100;20 # t CSI 0 # t
The application scrollbar is set and cleared before do_update() runs. The cache never observes term.app_scrollbar != 0, remains valid, and may decide that the normal tuple is unchanged. The application’s range/thumb values then remain visible.
Fix: Make the scrollbar cache explicitly invalidatable and call that invalidation from every external SB_VERT writer. On application-scrollbar exit, force an unconditional normal SetScrollInfo().
6. ASCII run merging assumes all Ligatures=0 ASCII uses the unshaped path, but alternate fonts do not
Introduced by: Claude, 33ec57b0
Compounded by: OpenAI, 4466cd17
Severity: Medium
The terminal run builder globally decides:
C// src/term.c:3209 bool unshaped_ascii = cfg.font_render == FR_TEXTOUT || cfg.ligatures == 0;
It then:
-
suppresses bidi-class breaks between printable ASCII,
-
avoids some
bidi_class()calls, -
forces ASCII/non-ASCII boundaries.
But win_text() bypasses Uniscribe for Ligatures=0 only in the primary font:
C// src/wintext.c:4360 if (use_uniscribe && cfg.ligatures == 0 && findex == 0) { ... }
The accompanying comment explicitly says alternate fonts retain Uniscribe fallback.
So, for alternate-font ASCII:
-
term.calters the run boundaries on the assumption that rendering is unshaped. -
wintext.cstill sends the merged run through Uniscribe.
That can change shaping, fallback, punctuation treatment, and digit/script adaptation. It also removes class-change boundaries whose existing comment says are required to avoid “glyph confusion (#285).”
Fix: Make the unshaped predicate depend on the effective font family of the run. With Ligatures=0, only primary-font ASCII should receive the relaxed run-breaking behavior.
7. VT52 fraction numerators are incorrectly classified as wholly self-drawn graphics
Introduced by: OpenAI, 3d8e68f3
Severity: Low to medium; narrow terminal mode
The optimization suppresses italic/overhang two-phase rendering for every internal font family above 10:
C// src/term.c:4223 int out_findex = (attr.attr & FONTFAM_MASK) >> ATTR_FONTFAM_SHIFT; if (out_findex > 10) { split_flags = TATTR_MARKCURS; }
But family 13 is not wholly self-drawn:
C// src/wintext.c:3939 // 13 VT52 fraction numerators 3/ 5/ 7/
The later Claude commit a5811f34 even documents this correctly:
C// src/wintext.c:4197 // vt52 fraction runs render through the normal text path bool selfdrawn_run = boxpower || boxcoded || dectcs;
Family 13 renders a superscript glyph normally and then draws the fraction slash. Removing italic/overhang splitting lets later background passes overwrite an italic or otherwise overhanging numerator.
Fix: Replace out_findex > 10 with an explicit test for families 11, 12, and 14. Family 13 must retain the normal split flags.
8. The clip-removal optimization lets double-height block graphics overwrite the preceding row
Introduced by: OpenAI, 05291ad0
Severity: High correctness impact, but specific to DEC double-height lines
The commit declares all block elements and most box-drawing glyphs clip-safe:
C// src/wintext.c:5425 if (ch >= 0x2580 && ch <= 0x259F) { return true; }
For a LATTR_BOT double-height line, however:
C// src/wintext.c:5030 int char_height = cell_height; if (lattr >= LATTR_TOP) { char_height *= 2; } int y0 = y; int yclip = y0; if (lattr == LATTR_BOT) { y0 -= cell_height; }
A full block, U+2588, then fills from y - cell_height through the bottom-row area. Before 05291ad0, setclipr(xi, yclip, len) excluded the preceding top row. The optimized path skips that clip, so repainting the bottom half can draw into the previous row.
This follows deterministically from the coordinates; it is not merely a possible driver behavior.
Fix: Force clipping whenever (lattr & LATTR_MODE) == LATTR_TOP or LATTR_BOT. Apply the per-glyph clip-safety optimization only to normal or double-width lines.
Conditional regression
9. Cached GDI+ GpGraphics can remain attached to the wrong or deleted HDC
Made substantially worse by: Claude, 3e3ad702
Severity: High if use_gdiplus_brush_fallback is enabled
fill_rect() caches a single bg_graphics:
C// src/wintext.c:2831 if (bg_graphics) { fill(); }
It does not check whether the supplied HDC dc matches the HDC from which bg_graphics was created.
Buffering introduces two distinct destinations:
-
persistent
paint_buf_dc, -
direct window DC during buffering bypasses.
It also deletes and recreates paint_buf_dc on resize without first deleting bg_graphics.
A GDI+ Graphics object created from an HDC is explicitly associated with that HDC and must be deleted before that DC is released. Microsoft Learn
This can produce:
-
image-background drawing into an old buffer instead of the current window,
-
missing backgrounds during direct-paint bypasses,
-
use of a
GpGraphicsassociated with a deleted memory DC after resize.
The GDI+ cache itself predates the reviewed range, so this is not a wholly new defect. The buffering commit turns an already unsafe lifetime assumption into a much more deterministic wrong-target/DC-destruction problem.
The repository does not define use_gdiplus_brush_fallback by default, so ordinary builds probably do not execute this path.
Fix: Store the associated HDC alongside bg_graphics, recreate on HDC mismatch, and delete the graphics object before dropping the buffer DC. Creating it per fill would be slower but simpler and correct.
Probable visual regression
10. Axis-aligned LineTo → rectangle conversion loses square end-cap coverage
Introduced by: OpenAI, e1a1d8cf
Confidence: High enough to require a pixel test, but I would not call it fully confirmed without Windows rendering output
The previous box-line implementation used geometric pens with PS_ENDCAP_SQUARE. The replacement draws axis-aligned strokes as rectangles, expanding only perpendicular to the stroke:
Cif (x1 == x2) { x1 -= w / 2; x2 += w - w / 2; } if (y1 == y2) { y1 -= w / 2; y2 += w - w / 2; }
It does not extend the rectangle along the stroke’s start or end axis. Square geometric-pen caps do extend past both line endpoints. Microsoft Learn
At widths above one pixel, corners such as ┌, ┐, └, and ┘ can therefore lose the outer corner pixel or develop a small notch where the horizontal and vertical rectangles meet.
Fix: Extend each axis-aligned rectangle by approximately half the stroke width along both endpoints, with careful odd-width handling. Compare against golden screenshots at 100%, 150%, and 200% DPI before retaining the optimization.
Recommended disposition
The four most urgent fixes are:
-
Correct the buffer blit region.
-
Deselect persistent-DC fonts before deletion.
-
Restore GDI/DIB synchronization.
-
Restore the complete DC state after transformations.
Then:
-
Revert or repair
05291ad0. -
Add explicit scrollbar-cache invalidation.
-
Correct the two font-family predicates in
33ec57b0/4466cd17and3d8e68f3. -
Add HDC ownership tracking to the GDI+ cache.
-
Pixel-test
e1a1d8cf.
As an immediate containment measure, DisplayBuffering=no avoids the newly introduced buffered behavior behind findings 1–4, although it does not fix the scrollbar, run-merging, VT52, or double-height clipping defects.
I attempted to build the fork, but this environment has no MinGW/Cygwin compiler or Wine runtime, and the Makefile initially inferred the container hostname as its target toolchain. Consequently, this was a complete static commit/code-path audit rather than a Windows runtime test. The first eight findings are based on direct control-flow, coordinate, lifetime, or documented API invariants; finding 10 is the one I would reserve for visual confirmation.
You said:
ChatGPT said:
I’ll create an ordered, reviewable patch series against prime, verify clean application to an untouched checkout, run available checks, and include a cover letter and bundled archive.
The repository is clean at prime; I’ll isolate the riskier rendering-equivalence change from correctness fixes so it can be reviewed or omitted independently.
All ten findings now have individual commits. I’m reviewing persistent-DC interactions and apply order, conservatively reverting the uncertain axis-aligned stroke optimization before export.
Patch series
The archive contains the ten numbered patches, application instructions, the manual regression matrix, SHA-256 checksums, a combined diff, and a single-file mail series.
Base: e343f8dc1055d6a4aad07cb4f88a62e99a2e0603 (prime)
Expected final tree: 318e451d5ee65649f4c1703c2a71143190a32e77
Supporting files:
-
README.md
# mintty `prime` review-fix patch series This series applies to: - branch/tag name used during review: `prime` - base commit: `e343f8dc1055d6a4aad07cb4f88a62e99a2e0603` - expected final tree: `318e451d5ee65649f4c1703c2a71143190a32e77` The patches are intentionally split one finding per commit and must be applied in numeric order. ## Apply From the mintty repository: ```sh git switch prime git am /path/to/mintty_patches/0*.patch ``` To apply using the included quilt-style ordering file: ```sh while IFS= read -r patch_file; do git am "/path/to/mintty_patches/$patch_file" done < /path/to/mintty_patches/series ``` After applying, verify the tree rather than the commit hash; `git am` uses the local committer identity and date: ```sh test "$(git rev-parse HEAD^{tree})" = 318e451d5ee65649f4c1703c2a71143190a32e77 git diff --check e343f8dc1055d6a4aad07cb4f88a62e99a2e0603..HEAD ``` ## Series | Patch | Finding addressed | Change | |---|---|---| | 0001 | Buffered blits omitted edge and vertical padding | Blit the full terminal viewport width for the dirty band, include edge padding, exclude tab/search UI, initialize stale padding deterministically, and invalidate the buffer after direct padding paint. | | 0002 | `HFONT` leaked while selected into the persistent buffer DC | Select `SYSTEM_FONT` before deleting application fonts and reset the selection cache. | | 0003 | CPU DIB writes were not synchronized with preceding GDI work | Move `GdiFlush()` to the actual GDI-to-DIB transition. This is the patch most directly related to ordering artefacts and tearing in the direct-fill path. | | 0004 | `GM_ADVANCED` persisted after line, glyph, and emoji transforms | Save and restore the previous graphics mode on success and failure paths. | | 0005 | Application scrollbar writes could leave the normal scrollbar cache falsely valid | Expose explicit cache invalidation and call it for every application-scrollbar write. | | 0006 | `Ligatures=0` ASCII merging incorrectly included alternate fonts that still use Uniscribe | Match the run-builder predicate to the renderer's effective font family. | | 0007 | VT52 fraction numerators lost normal italic/overhang splitting | Restrict the fully self-drawn optimization to families 11, 12, and 14. | | 0008 | Clip elision let DECDHL bottom-half graphics overwrite the preceding row | Force clipping for both double-height halves. | | 0009 | A cached GDI+ `GpGraphics` outlived or mismatched its HDC and was interleaved with GDI | Create, use, and delete the graphics object within each fallback fill. This affects the normally disabled GDI+ brush fallback. | | 0010 | Rectangle-rendered solid box strokes did not preserve square endpoint coverage | Keep dashed rectangles, but return solid axis-aligned strokes to the geometric-pen path. This is the conservative fix for the visual-risk finding. | ## Verification performed The exported patches were applied with `git am` to a fresh worktree at the exact base commit. The applied tree and reviewed tree both resolved to: ```text 318e451d5ee65649f4c1703c2a71143190a32e77 ``` The following checks passed on both the reviewed branch and the fresh application: ```sh git diff --check prime..HEAD bash -n tools/paint-stress-test ``` The two source checks implemented by the repository's `checksrc` target also passed when run directly: ```sh cd src if egrep -an \ -e '^(f*printf|#define debug)' \ -e '^(if|do|for|while) *[({]' \ -e '\((false|0) *&' \ -e '\((true|1) *\|' \ -e '\(0 &' \ -e '^///' \ *.[hc] then exit 1 fi if sed \ -e 's,.*IDM_.*0x\([0-9A-F]*\) *,\1,' \ -e t \ -e d \ winids.h | sort | uniq -d | grep . then exit 1 fi ``` A Windows build and runtime pixel test were not possible in the review environment because it lacks a Cygwin/MSYS mintty toolchain and Wine. Patch 0010 therefore deliberately restores the established geometric-pen path instead of introducing new endpoint arithmetic. ## Windows regression matrix Run a debug build with `DisplayBuffering=yes`, then execute `tools/paint-stress-test`. In addition, check these cases: 1. Resize and rapidly scroll with italic text touching both horizontal edges. Repeat with a graphic background, the search bar visible, and the tab bar enabled. Padding must update atomically without overwriting either UI area. 2. Repeatedly zoom the font and change primary/alternate fonts while watching the process GDI-object count. It should not grow by one font object per reconfiguration. 3. Exercise high-volume coloured output and box/block graphics. Look specifically for transient background/glyph ordering artefacts after patch 0003. 4. Render RTL presentation lines, width-scaled glyphs, and italic emoji, then inspect rectangular cursors and selection boxes for one-pixel growth. 5. Send an application scrollbar set and clear in one write: ```sh printf '\e[5;100;20#t\e[0#t' ``` The ordinary terminal scrollbar range and thumb must be restored on the next update. 6. With `Ligatures=0`, mix primary-font ASCII and ASCII assigned to alternate fonts. Alternate-font runs must retain their prior Uniscribe boundaries and fallback behavior. 7. In VT52 fraction mode, place italic fraction numerators next to cells with contrasting backgrounds and inspect overhang cleanup. 8. Draw full blocks and box glyphs in DEC double-height top and bottom lines, then repaint only the bottom half. The top row must remain unchanged. 9. For a build that enables `use_gdiplus_brush_fallback`, force the fallback and alternate buffered/direct paints plus resizes. Background fills must stay on the current HDC. 10. At 100%, 150%, and 200% DPI, inspect `┌┐└┘├┤┬┴┼` with light, heavy, and double strokes for corner notches. ## Other forms The parent artifact directory also contains: - `mintty-review-fixes.mbox`: the complete mail series in one file. - `mintty-review-fixes-combined.patch`: one non-committed combined diff for inspection only. - `mintty-review-fixes.zip`: this directory and all numbered patches. -
-
-
-
| # | Patch |
|---|---|
| 1 | |
| 2 | |
| 3 | |
| 4 | |
| 5 | |
| 6 | |
| 7 | |
| 8 | |
| 9 | |
| 10 |
Patch 3 is the change most directly connected to the tearing/order artifacts you observed. Microsoft explicitly requires GdiFlush() synchronization before directly accessing the bits of a CreateDIBSection bitmap after GDI drawing. Microsoft Learn
Patch 9 deliberately creates and destroys the GpGraphics object around each fallback fill. Microsoft requires a Graphics object created from an HDC to be destroyed before that DC is released, and advises against interleaving ordinary GDI calls while the object remains attached. Microsoft Learn+1
Apply from the repository with:
Bashgit switch prime git am /path/to/mintty_patches/0*.patch
I verified the final exported files by applying all ten with git am to a fresh worktree at the exact prime commit. The applied tree matched the reviewed branch byte-for-byte at tree object 318e451d5ee65649f4c1703c2a71143190a32e77. git diff --check, the repository’s source checks, the patch checksums, archive integrity, and bash -n tools/paint-stress-test all passed.
A Windows build and runtime pixel test were not possible in this environment because it lacks a Cygwin/MSYS mintty toolchain and Wine. For that reason, patch 10 conservatively restores the established geometric-pen path rather than attempting new square-cap endpoint arithmetic.
You said:
tig with lots of line art, as pictured. could you just please me if there is a performance regression in anything? no need to diagnose the cause yetChatGPT said:
Yes—there is a large, unambiguous performance regression in line-art rendering after the ten patches.
| Metric | Before | After | Change |
|---|---|---|---|
| Painted cells | 6.778M | 6.821M | +0.6% |
| Total update time | 15.28 s | 36.89 s | +141% |
| Line-art update time | 12.87 s | 35.25 s | +174% |
| Median line-art update | 38.7 ms | 128.7 ms | 3.32× slower |
| P95 line-art update | 70.3 ms | 236.7 ms | 3.37× slower |
win_text time | 8.93 s | 31.81 s | 3.56× |
| Self-drawn graphics time | 1.87 s | 23.79 s | 12.7× |
| Self-draw time per box character | 1.24 µs | 17.15 µs | 13.8× |
This is not explained by a heavier after-test: the after log painted only 0.6% more cells and actually processed about 8% fewer box-drawing characters.
I do not see a convincing general regression outside the line-art path:
-
Ordinary text-output time is approximately flat or slightly lower.
-
BitBlt time rose about 5%, but the number of pixels copied rose about 5.7%, so per-pixel performance is effectively unchanged.
-
Non-line-art updates do not show a clear slowdown after normalization.
-
The initial buffer creation was slower in the after run, but there is only one such event in each log, so that is not enough evidence to call a separate regression.
So the conclusion is: severe line-art regression; no clear broad rendering regression elsewhere.
You said:
ChatGPT said:
Yes. The performance-priority answer is:
Remove patches 3 and 10 together.
Do not remove only patch 10 while leaving patch 3 applied.
-
Patch 10 replaced the fast rectangle implementation for ordinary axis-aligned box drawing with geometric GDI lines. In the traces, rectangle operations fell from 1,442,763 to 1,568, while line operations rose from 62,711 to 2,720,118. Self-drawn rendering became about 13.8× slower per box character.
-
Patch 3 calls
GdiFlush()before every direct DIB fill. Ordinary background-fill time increased from 0.115 seconds to 1.631 seconds, even though the number of those fills was slightly lower. Once patch 10 is removed, this would affect approximately 2.5 million fills in this workload.
That combination explains almost all of the observed regression and plausibly why tearing looks worse: the median line-art frame increased from roughly 39 ms to 129 ms, making partially presented updates much easier to see.
Recommended disposition
| Original patch | Action | Reason |
|---|---|---|
| 1 | Keep | Prevents persistent stale padding and edge overhang. The cost is only about 0.6% extra horizontal BitBlt area in your tested geometries. |
| 2 | Keep | Prevents leaking fonts selected into the persistent DC. No steady-state rendering cost. |
| 3 | Remove | Large hot-path regression. It addresses strict rendering synchronization, not memory safety. |
| 4 | Keep | Restores persistent DC state after transforms. No cost unless transformed text or emoji is used. |
| 5 | Keep | Prevents application scrollbar values from remaining stuck. No normal rendering cost. |
| 6 | Optional | Alternate-font shaping fidelity. Not needed for memory or update integrity and can produce more output runs in that niche path. |
| 7 | Optional | VT52 fraction overhang fidelity. It adds an overlay pass only for that unusual character family. |
| 8 | Keep | Prevents DEC double-height line art from overwriting the preceding row. It has no effect on ordinary lines such as the tig display. |
| 9 | Optional | Its GDI+ fallback is not enabled anywhere in this tree. Therefore it has no effect in a normal build, but would be slower if that private fallback macro were enabled. |
| 10 | Remove | Dominant regression; only preserves subtle square-cap rasterization. |
For your stated priorities, the cleanest set is therefore 1, 2, 4, 5, and 8.
Dropping patch 3 returns to the fork's former policy of one GdiFlush() per frame rather than one per CPU fill. That deliberately accepts the possibility of transient DIB/GDI ordering artifacts. It does not reintroduce a known memory-corruption path. Eliminating that tradeoff without losing performance would require a larger renderer redesign that separates CPU background drawing from GDI text drawing.
Patch bundle
It contains three usable series:
Already applied all ten patches
Apply these two corrective patches:
Bashgit am /path/to/on-top-of-all-10/0*.patch
Starting all-ten tree:
318e451d5ee65649f4c1703c2a71143190a32e77
Expected repaired tree:
e457af56b5f2adb9b3e18f7340f486fe9e7dcce6
This is the first variant I would test. It retains the other eight fixes and should recover nearly all of the measured tig performance.
Smaller series from clean prime
This contains only patches corresponding to original fixes 1, 2, 4, 5, and 8:
Bashgit am /path/to/from-prime-safety-update/0*.patch
Starting commit:
e343f8dc1055d6a4aad07cb4f88a62e99a2e0603
Expected tree:
243cf48138637280d3ec64ce02f8e7a6a54af363
Optional trim after the two corrective patches
This removes original patches 6, 7, and 9 as well, producing a tree byte-for-byte identical to the clean five-patch series:
The optional trim is not necessary to recover the performance seen in these logs.
# mintty performance-priority repair series
## Recommendation
The supplied traces identify two patches that should not remain in a speed-priority build:
1. `0003-wintext-synchronize-GDI-before-direct-DIB-writes.patch`
2. `0010-wintext-preserve-square-caps-on-box-strokes.patch`
Do not remove only patch 10 while retaining patch 3. Restoring the fast rectangle path makes `win_fill_rect` hot again, and patch 3 would then execute `GdiFlush()` for roughly 2.5 million fills in this particular trace.
The `on-top-of-all-10` directory contains two patches that undo those changes while retaining the other eight fixes.
The `from-prime-safety-update` directory is a smaller clean series for `prime` at `e343f8dc1055d6a4aad07cb4f88a62e99a2e0603`. It keeps only the fixes that are cheap in normal rendering and materially improve update/resource/state integrity:
- transfer padding written by buffered rendering;
- deselect an `HFONT` before deleting/recreating fonts;
- restore the graphics mode after temporary transforms;
- invalidate scrollbar state after application scrollbar writes;
- retain clipping for DEC double-height self-drawn lines.
It intentionally omits the strict per-fill DIB synchronization, alternate-font shaping correction, VT52 fraction overhang correction, GDI+ fallback lifetime change, and square-cap fidelity change.
## What the logs show
The before and after sessions are close enough in rendered work to expose the regression clearly: painted cells differ by only about 0.6 percent.
| Metric | Before | After all 10 | Change |
|---|---:|---:|---:|
| Total update time | 15.285 s | 36.893 s | +141% |
| Self-drawn graphics time | 1.874 s | 23.787 s | 12.7x |
| Self-draw time per box character | 1.245 us | 17.146 us | 13.8x |
| Ordinary background-fill time | 0.115 s | 1.631 s | 14.2x |
| Axis-aligned/self-draw rectangle operations | 1,442,763 | 1,568 | effectively removed |
| Self-draw line operations | 62,711 | 2,720,118 | 43.4x |
The operation change maps directly to patch 10. The ordinary fill-time increase maps directly to patch 3's `GdiFlush()` in every direct DIB fill.
Patch 1 does increase each buffered blit by the padding width. In the supplied geometry this is 16 pixels on a 2,607-pixel text area at 237 columns, and 24 pixels on a 3,795-pixel text area at 345 columns: about 0.6 percent horizontally. That is a small, intentional cost to prevent stale padding and edge overhang from becoming persistent.
## Apply to a tree that already has all 10 patches
The starting tree must match the all-10 result, whose tree object is:
```
318e451d5ee65649f4c1703c2a71143190a32e77
```
Apply:
```sh
git am /path/to/on-top-of-all-10/0*.patch
```
Expected final tree:
```
e457af56b5f2adb9b3e18f7340f486fe9e7dcce6
```
This retains patches 1, 2, 4, 5, 6, 7, 8, and 9, while undoing 3 and 10.
For the stricter profile that keeps only patches 1, 2, 4, 5, and 8, apply the optional trim after the two mandatory repair patches:
```sh
git am /path/to/optional-trim-after-repair/0*.patch
```
That resulting tree is byte-for-byte identical to the clean `from-prime-safety-update` result. The optional trim reintroduces only niche visual/fallback issues; it is not necessary to recover the observed `tig` performance.
## Apply the smaller series to clean `prime`
Starting commit:
```
e343f8dc1055d6a4aad07cb4f88a62e99a2e0603
```
Apply:
```sh
git am /path/to/from-prime-safety-update/0*.patch
```
Expected final tree:
```
243cf48138637280d3ec64ce02f8e7a6a54af363
```
## Patch disposition
| Original patch | Disposition | Reason |
|---:|---|---|
| 1 | Keep | Prevents failed/stale updates in padding and edge overhang. Tiny extra blit area. |
| 2 | Keep | Prevents leaked selected font handles during reconfiguration. No steady-state cost. |
| 3 | Remove | Adds about one `GdiFlush()` per direct fill; measurable hot-path regression. It addresses rendering ordering, not memory safety. |
| 4 | Keep | Restores persistent DC state. Only adds work when a transform is actually used. |
| 5 | Keep | Prevents stale scrollbar values. No normal rendering cost. |
| 6 | Optional | Alternate-font shaping fidelity. Can split more runs in that niche path; not needed for safety/update integrity. |
| 7 | Optional | VT52 fraction overhang fidelity. Can add an extra overlay pass only for that rare family. |
| 8 | Keep | Prevents DEC double-height bottom halves from overwriting the preceding row. No effect on ordinary lines. |
| 9 | Optional | The GDI+ brush fallback is not enabled anywhere in this tree. The patch has no effect in the normal build, but creates/deletes a `GpGraphics` for every fallback fill if the private macro is enabled. |
| 10 | Remove | Pure pixel-fidelity change and the dominant observed regression. |
## Rendering tradeoff
The reduced build deliberately returns to the pre-patch-3 synchronization policy: one frame-level `GdiFlush()` rather than strict synchronization before every CPU write to the DIB. Strict synchronization and the current interleaved `CPU fill -> GDI text -> CPU fill` renderer cannot both be cheap without a larger renderer redesign. This choice may retain transient rendering artifacts or tearing, but it does not reintroduce a known memory-corruption path.
All three exported series were reapplied to fresh worktrees and matched their expected tree objects. git diff --check and the repository’s checksrc target passed. A Windows compilation and runtime benchmark were not available here, so the expected performance recovery should still be confirmed with another MINTTY_PERF=1 run.
You said:
ChatGPT said:
The 10+2 build restores rendering performance to the original baseline, and is slightly faster on the line-art workload within normal run-to-run variation.
| Metric | Before 10 | After 10 | After 10+2 |
|---|---|---|---|
| Update time per dirty cell | 2.255 µs | 5.409 µs | 2.188 µs |
| Terminal-paint time per dirty cell | 2.099 µs | 5.255 µs | 2.038 µs |
| Median line-art update | 38.75 ms | 128.71 ms | 37.96 ms |
| P95 line-art update | 70.27 ms | 236.70 ms | 64.67 ms |
| Self-draw time per box character | 1.245 µs | 17.146 µs | 1.065 µs |
Compared with the original run, the 10+2 build is:
-
about 3% faster overall per painted cell;
-
about 2% faster at the median line-art frame;
-
about 8% faster at the line-art P95;
-
about 14% faster in self-draw time per box character.
Compared with the slow 10-patch build, median and P95 line-art latency dropped by roughly 71–73%, while self-draw cost per box character dropped by about 94%.
The new log has a higher raw total update time—19.70 seconds versus 15.28 seconds originally—but it also contains substantially more work: 9.00 million versus 6.78 million dirty cells, and 2.03 million versus 1.51 million box characters. Once normalized, there is no remaining rendering-performance regression.
So: the two corrective patches did what they were supposed to do; keep this 10+2 state.