Drain all available serial data before calling FlipToScreen so
overlapping screen changes collapse into a single GDI render pass.
Messages are checked between chunks to keep keyboard responsive.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Default RX FIFO trigger level of 8 (FCR_TRIG_8) caused exactly 8
characters to buffer in hardware before the ISR fired. Change default
to 1 (FCR_TRIG_1) for responsive single-character interrupts while
keeping the 16-byte FIFO enabled as a safety buffer. COMnRxTRIGGER
SYSTEM.INI key still allows override.
The PM_NOYIELD polling loop never yielded the CPU timeslice, starving
other Windows applications. Add Yield call when no serial data is
flowing so other apps get CPU time. During bulk data flow HasData
stays true and the loop runs at full speed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
PeekMessage without PM_NOYIELD surrenders the timeslice on every empty
queue check (~55ms per yield in Win16). Adding pm_NoYield keeps the
polling loop hot so keystrokes and serial echoes are processed without
scheduler delays.
FlipToScreen was calling GetDC/ReleaseDC on every loop iteration even
with zero dirty rows. Added early-exit scan before acquiring a DC.
TickBlink was calling FlipToScreen redundantly (main loop also calls it).
Removed the FlipToScreen from TickBlink and reordered the main loop to
TickBlink (dirty only) then FlipToScreen (single render pass).
Also: removed FBlinkOn := True reset from ParseData (was dirtying the
cursor row on every incoming chunk), added WriteDeferred for parse-only
without render, moved FlipToScreen from private to public, added Show
call before entering the polling loop.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
VBX registration is non-functional in VB4 16-bit (VBRegisterModel is a
no-op, VBGetModelInfo never called). Native Delphi component avoids all
DLL/export/registration issues — compiles directly into the IDE.
TKPComm is a TComponent descendant calling the Windows 3.1 comm API
directly. Uses RegisterClass/CreateWindow for WM_COMMNOTIFY dispatch
with the component instance pointer stored in cbWndExtra. Includes a
test application (KPTEST) with send/receive UI built entirely in code.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>