The ISR still fills the ring buffer (mandatory for 115200 baud), but the
app now polls ReadComm directly via a PeekMessage loop instead of waiting
for WM_COMMNOTIFY. Blink uses GetTickCount instead of WM_TIMER. This
eliminates all Windows message overhead from the data path while keeping
the message loop alive for keyboard, paint, and scrollbar.
Removed from KPCOMM.PAS: NotifyWndProc, hidden notification window,
RegisterClass/CreateWindow, EnableCommNotification, SetCommEventMask,
DoCommEvent, Process*Notify methods, OnComm/CommEvent/RThreshold/
SThreshold properties, modem shadow state (CTS/DSR/CD).
Removed from KPANSI.PAS: WM_TIMER handler, SetTimer/KillTimer, replaced
with public TickBlink method using GetTickCount at 500ms intervals.
Removed from drv/isr.c: checkNotify function and its call from
isrDispatch. Removed from drv/commdrv.c: pfnPostMessage, all
rxNotifySent/txNotifySent edge-trigger bookkeeping, gutted
enableNotification to a no-op API-compat stub. Removed from
drv/commdrv.h: rxNotifySent/txNotifySent fields (shifts struct layout),
PostMessageProcT typedef, pfnPostMessage extern.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove BeginUpdate/EndUpdate batching from TKPAnsi entirely -- Write now
renders immediately via FlipToScreen after every ParseData call. Remove
FPendingScroll (caused rendering deadlock: EndUpdate refused to call
FlipToScreen while FPendingScroll > 0, but only FlipToScreen cleared it).
DoScrollUp simplified to set FAllDirty directly.
CommEvent drain loop retained (required by edge-triggered CN_RECEIVE) but
each chunk renders immediately -- no deferred batching. Edge-triggered
notifications verified starvation-free at all levels: ISR, driver, KPCOMM
dispatch, terminal rendering, and keyboard output path.
Add comprehensive variable comments to all project files: TKPAnsi (44
fields), TKPComm (23 fields), TMainForm (9 fields), PortStateT, and
driver globals.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Data now hits the screen immediately when it arrives — no artificial
delay. The render throttle (RenderTickMs, FLastRenderTick) is removed
entirely. ParseData and EndUpdate call FlipToScreen unconditionally;
callers control batching via BeginUpdate/EndUpdate.
Blink toggle no longer calls DirtyAll. New DirtyBlinkRows method only
marks the cursor row and rows containing blink cells, reducing blink
overhead from ~63ms (25 rows) to ~3ms (1-3 rows) on a 486. Cursor
ghost handling in FlipToScreen dirties the old cursor row when the
cursor moves between rows.
Constant mini-frame values (Stride, CellH, PixSeg, GlyphSeg) are
pushed once before the column loop instead of per-cell, saving 320
push instructions per row.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Rewrite RenderRow inner loop: split each glyph byte into two nibbles,
look up 4 pre-resolved palette bytes per nibble from a 64-byte table,
and write as word stores — zero branching in the hot path. Replace 25
per-row GlobalAlloc buffers with a single reusable buffer and move glyph
data into a GlobalAlloc'd block shared with the nibble table. All
arithmetic is 16-bit Word (no Longint). Uses mini-frame technique to
safely access local variables from inline ASM after DS/SI/DI clobber.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Build a monochrome font atlas at startup (BuildAtlas), then render
terminal cells by writing palette indices directly into 8bpp DIB row
buffers (RenderRow). Each dirty row reaches the screen via a single
SetDIBitsToDevice call instead of ~12 GDI calls (TextOut, SetTextColor,
SetBkColor, BitBlt). This reduces per-frame GDI overhead by ~10x,
targeting smooth playback of BBS door games on Win16.
Key changes:
- TTermCell FG/BG from TColor to Byte (palette index 0-15)
- Font atlas: render 256 CP437 glyphs into monochrome bitmap, extract
per-glyph pixel masks via GetBitmapBits
- Per-row 8bpp DIB buffers via GlobalAlloc replace dual memory DCs
- RenderRow: zero-GDI atlas lookup + byte writes with cursor overlay
- FlipToScreen: ScrollDC on screen only, SetDIBitsToDevice per dirty row
- Text blink via FTextBlinkOn + re-render replaces dual-buffer phase swap
- Removed: CreateBuffers, DestroyBuffers, PaintLine, ClearBufRect,
RedrawBuffers, DrawRow, FBufDC/FBufBmp/FBlinkPhase fields
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Three fixes:
1. Cursor font mismatch: cursor overlay TextOut used the default DC
font instead of FPaintFont (OEM_CHARSET). The wrong font size left
residual pixels that buffer BitBlt didn't fully cover. Fix: select
FPaintFont + OPAQUE BkMode into the CS_OWNDC screen DC once in
RecalcCellSize, and re-assert before cursor TextOut in case VCL
Paint altered the DC state.
2. WM_TIMER starvation: on Win16, WM_TIMER is lowest-priority and
never dispatched while WM_COMMNOTIFY floods the queue. Fix: render
from EndUpdate using GetTickCount throttle (no timer dependency).
The timer remains as fallback for idle blink and trailing data.
3. Scroll performance: restore deferred ScrollDC at render time (not
during parsing) so only newly-revealed rows need PaintLine instead
of all 25. Dirty flags shift with each DoScrollUp to track the
correct logical line positions after scroll.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Data parsing (ProcessChar) is now purely memory operations. EndUpdate
and ParseData set FRenderPending instead of calling FlipToScreen.
A 55ms timer (~18 Hz, matching Win16 tick resolution) drives rendering:
dirty rows are painted and BitBlt'd to screen only on timer ticks.
Blink toggles every 9 ticks (~500ms). This prevents high-throughput
data (door games, file transfers) from saturating the CPU with GDI
calls — between timer ticks, data just accumulates in cell buffers.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ScrollDC shifted buffer pixels during parsing before dirty rows were
rendered, causing unrendered garbage to propagate into non-dirty rows
on successive scrolls. Replace with FAllDirty to repaint all rows from
cell data after any scroll. With batched TextOut this costs ~250 GDI
calls per scroll instead of the old 12,000, so the overhead is minimal.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Separate parsing from rendering to eliminate per-character GDI calls.
ProcessChar now only updates cell data in memory; rendering is deferred
to FlipToScreen which batches consecutive same-color cells into single
TextOut calls (~5-10 per row instead of 80). Partial BitBlt transfers
only the dirty row band to the screen. Non-blinking rows render to one
buffer and BitBlt to the second, halving GDI work for typical content.
Also removes redundant GetCommError from KPComm receive path and adds
BeginUpdate/EndUpdate batching in the test app's CommEvent handler.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace TBitmap back buffer with raw GDI memory DC to prevent Delphi's
TCanvas from overriding the OEM_CHARSET font selection. Add OUT_RASTER_PRECIS
to CreateFontIndirect to ensure Windows maps to a true CP437 raster font
instead of a TrueType substitution. Optimize paint loop with fixed char
array instead of string concatenation. Restore Update call in ParseData
so WM_PAINT is not starved by WM_COMMNOTIFY. Add text blink support via
timer-driven FG/BG toggling and store blink as cell attribute instead of
mapping to bright background.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Handle ENQ (0x05), ESC[c (Device Attributes), ESC[5n (Device Status
Report), and ESC[6n (Cursor Position Report). Responses are sent
back through OnKeyData so BBSes can detect ANSI terminal capability.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
TKPAnsi is a TCustomControl descendant providing a visual ANSI terminal
with 16-color palette, scrollback buffer, blinking cursor, and ANSI music.
Supports CSI sequences (cursor movement, erase, SGR colors/attributes,
insert/delete lines/chars, scroll), DEC private modes (wrap, cursor
visibility), and keyboard translation (arrows, function keys, etc.).
Test app (TESTMAIN.PAS) updated to wire TKPAnsi to TKPComm as a
full terminal: received data feeds the terminal display, keystrokes
are sent out the serial port.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>