Compare commits

...

4 commits

Author SHA1 Message Date
3fc2b410ba Render characters immediately during parsing, not in deferred pass
WriteDeferredBuf now acquires a screen DC and renders each character
run via ExtTextOut as it arrives, eliminating the per-row deferred
dirty scan.  FlushPendingScrolls coalesces scroll-ups into a single
ScrollDC call.  FlipToScreen becomes a lightweight blink/fallback pass.

Removed 50ms render throttle from TESTMAIN -- no longer needed since
characters appear on screen as they are parsed.

Simplified: removed ClearLine (duplicate of AllocLine), DirtyAll,
DirtyRow, GetCursorCol/Row (dead code), FTextBlinkOn (always equal
to FBlinkOn), ParseData (inlined), ETO_CLIPPED (unused), redundant
zero-initializations in constructor.  Write and WriteDeferred now
delegate to WriteDeferredBuf.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 16:18:33 -06:00
a9e25ec67f Re-select OEM font into DC before every render pass
Relying on CS_OWNDC to retain the font between frames is fragile --
Delphi's Canvas infrastructure can deselect it during paint cycles.
Without the OEM font, ExtTextOut renders with SYSTEM_FONT (ANSI_CHARSET),
causing CP437 box-drawing glyphs to display as accented letters, cell
metrics to mismatch (lines clipped), and cursor position to drift.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 19:23:22 -06:00
78753a65d8 Render ExtTextOut directly to screen DC, eliminating memory DC intermediate
The MemDC + BitBlt approach wrote every pixel twice (ExtTextOut to system
memory, then BitBlt to video memory).  Now ExtTextOut renders directly to
the CS_OWNDC screen DC, letting the display driver write text straight
into the framebuffer via its optimized raster font path.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 19:17:18 -06:00
a7780c8030 Replace DIB pixel rendering with ExtTextOut + memory DC BitBlt
The 8bpp DIB pipeline (font atlas, nibble lookup table, inline ASM glyph
expansion, SetDIBitsToDevice) is replaced with GDI text output: ExtTextOut
per color run into a memory DC, then BitBlt per row to the screen.

The memory bitmap is in native device format, so BitBlt is a raw copy with
no 8bpp-to-device color conversion.  ExtTextOut goes through the display
driver's optimized text path instead of software pixel expansion.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 19:00:58 -06:00
2 changed files with 320 additions and 647 deletions

File diff suppressed because it is too large Load diff

View file

@ -166,19 +166,15 @@ end;
procedure TMainForm.Run; procedure TMainForm.Run;
const const
RenderMs = 50; { Minimum ms between renders during bulk flow (20 fps) }
BufSize = 2048; { Read buffer -- 8x larger than 255-byte string limit } BufSize = 2048; { Read buffer -- 8x larger than 255-byte string limit }
var var
Msg: TMsg; Msg: TMsg;
Buf: array[0..BufSize - 1] of Char; Buf: array[0..BufSize - 1] of Char;
Len: Integer; Len: Integer;
HasData: Boolean; HasData: Boolean;
Now: Longint;
LastRenderTick: Longint;
begin begin
Show; Show;
FDone := False; FDone := False;
LastRenderTick := GetTickCount;
while not FDone do while not FDone do
begin begin
{ Process all pending Windows messages (keyboard, paint, scrollbar) } { Process all pending Windows messages (keyboard, paint, scrollbar) }
@ -196,8 +192,8 @@ begin
if FDone then if FDone then
Break; Break;
{ Drain all available serial data before rendering. Reads up to } { Drain all available serial data. WriteDeferredBuf renders each }
{ 2048 bytes per call, bypassing the 255-byte short string limit. } { character run immediately via ExtTextOut -- no deferred pass. }
{ Messages are checked between chunks so keyboard stays responsive. } { Messages are checked between chunks so keyboard stays responsive. }
HasData := False; HasData := False;
if FComm.PortOpen then if FComm.PortOpen then
@ -225,16 +221,11 @@ begin
if FDone then if FDone then
Break; Break;
{ Render throttle: during bulk data flow, only render every RenderMs } { Blink + dirty-row pass. During normal data flow, WriteDeferredBuf }
{ to decouple parse throughput from GDI overhead. When idle, render } { already rendered inline so FlipToScreen is a no-op. Only blink }
{ immediately for interactive responsiveness. } { toggle (every 500ms) or scrollbar updates produce dirty rows here. }
Now := GetTickCount;
if (not HasData) or (Now - LastRenderTick >= RenderMs) then
begin
FAnsi.TickBlink; FAnsi.TickBlink;
FAnsi.FlipToScreen; FAnsi.FlipToScreen;
LastRenderTick := Now;
end;
{ Yield CPU to other apps when no serial data is flowing. } { Yield CPU to other apps when no serial data is flowing. }
{ PM_NOYIELD keeps message draining fast; Yield here gives other } { PM_NOYIELD keeps message draining fast; Yield here gives other }