Optimize TKPAnsi rendering with batched TextOut and dirty row tracking
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>
This commit is contained in:
parent
be566a5767
commit
ca99d1d21b
4 changed files with 576 additions and 235 deletions
File diff suppressed because it is too large
Load diff
|
|
@ -351,7 +351,6 @@ end;
|
||||||
|
|
||||||
function TKPComm.GetInput: string;
|
function TKPComm.GetInput: string;
|
||||||
var
|
var
|
||||||
Stat: TComStat;
|
|
||||||
BytesToRead: Integer;
|
BytesToRead: Integer;
|
||||||
BytesRead: Integer;
|
BytesRead: Integer;
|
||||||
Buf: array[0..255] of Char;
|
Buf: array[0..255] of Char;
|
||||||
|
|
@ -360,15 +359,12 @@ begin
|
||||||
if not FPortOpen or (FCommId < 0) then
|
if not FPortOpen or (FCommId < 0) then
|
||||||
Exit;
|
Exit;
|
||||||
|
|
||||||
GetCommError(FCommId, Stat);
|
{ Read directly without querying GetCommError first. ReadComm }
|
||||||
BytesToRead := Stat.cbInQue;
|
{ returns the number of bytes actually available (up to BytesToRead) }
|
||||||
|
{ so the extra GetCommError round-trip is unnecessary overhead. }
|
||||||
|
BytesToRead := 255;
|
||||||
if (FInputLen > 0) and (BytesToRead > FInputLen) then
|
if (FInputLen > 0) and (BytesToRead > FInputLen) then
|
||||||
BytesToRead := FInputLen;
|
BytesToRead := FInputLen;
|
||||||
if BytesToRead > 255 then
|
|
||||||
BytesToRead := 255;
|
|
||||||
if BytesToRead <= 0 then
|
|
||||||
Exit;
|
|
||||||
|
|
||||||
BytesRead := ReadComm(FCommId, @Buf, BytesToRead);
|
BytesRead := ReadComm(FCommId, @Buf, BytesToRead);
|
||||||
if BytesRead <= 0 then
|
if BytesRead <= 0 then
|
||||||
|
|
@ -558,14 +554,12 @@ end;
|
||||||
|
|
||||||
|
|
||||||
procedure TKPComm.ProcessReceiveNotify;
|
procedure TKPComm.ProcessReceiveNotify;
|
||||||
var
|
|
||||||
Stat: TComStat;
|
|
||||||
begin
|
begin
|
||||||
if FRThreshold <= 0 then
|
if FRThreshold <= 0 then
|
||||||
Exit;
|
Exit;
|
||||||
GetCommError(FCommId, Stat);
|
{ WM_COMMNOTIFY with CN_RECEIVE means data is available -- the driver }
|
||||||
if Integer(Stat.cbInQue) >= FRThreshold then
|
{ already checked the threshold. No need to call GetCommError here. }
|
||||||
DoCommEvent(comEvReceive);
|
DoCommEvent(comEvReceive);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -85,9 +85,19 @@ begin
|
||||||
case FComm.CommEvent of
|
case FComm.CommEvent of
|
||||||
comEvReceive:
|
comEvReceive:
|
||||||
begin
|
begin
|
||||||
S := FComm.Input;
|
{ Drain all available data in a single update batch. This }
|
||||||
if Length(S) > 0 then
|
{ suppresses per-Write rendering so we get one paint at the }
|
||||||
FAnsi.Write(S);
|
{ end instead of one per 255-byte chunk. }
|
||||||
|
FAnsi.BeginUpdate;
|
||||||
|
try
|
||||||
|
repeat
|
||||||
|
S := FComm.Input;
|
||||||
|
if Length(S) > 0 then
|
||||||
|
FAnsi.Write(S);
|
||||||
|
until Length(S) = 0;
|
||||||
|
finally
|
||||||
|
FAnsi.EndUpdate;
|
||||||
|
end;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
@ -131,7 +141,7 @@ begin
|
||||||
FEditSettings.Left := 148;
|
FEditSettings.Left := 148;
|
||||||
FEditSettings.Top := 8;
|
FEditSettings.Top := 8;
|
||||||
FEditSettings.Width := 140;
|
FEditSettings.Width := 140;
|
||||||
FEditSettings.Text := '9600,N,8,1';
|
FEditSettings.Text := '115200,N,8,1';
|
||||||
|
|
||||||
FBtnOpen := TButton.Create(Self);
|
FBtnOpen := TButton.Create(Self);
|
||||||
FBtnOpen.Parent := Self;
|
FBtnOpen.Parent := Self;
|
||||||
|
|
@ -164,6 +174,15 @@ begin
|
||||||
FAnsi.Left := 0;
|
FAnsi.Left := 0;
|
||||||
FAnsi.Top := 38;
|
FAnsi.Top := 38;
|
||||||
FAnsi.OnKeyData := AnsiKeyData;
|
FAnsi.OnKeyData := AnsiKeyData;
|
||||||
|
|
||||||
|
{ Font diagnostic: write known CP437 box-drawing characters. }
|
||||||
|
{ If the OEM font is working, you should see: }
|
||||||
|
{ Line 1: single-line box top ┌───┐ }
|
||||||
|
{ Line 2: shade + full block ░▒▓█ }
|
||||||
|
{ Line 3: single-line box bottom └───┘ }
|
||||||
|
{ If you see accented letters (Ú Ä ¿ ° ± ² Û À Ù), the font is }
|
||||||
|
{ ANSI_CHARSET instead of OEM_CHARSET. }
|
||||||
|
FAnsi.Write(#$DA#$C4#$C4#$C4#$BF' '#$B0#$B1#$B2#$DB' '#$C0#$C4#$C4#$C4#$D9#13#10);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -259,6 +259,13 @@ void applyBaudRate(PortStateT *port, uint16_t baud)
|
||||||
base = port->baseAddr;
|
base = port->baseAddr;
|
||||||
divisor = (uint16_t)(BAUD_DIVISOR_BASE / actualBaud);
|
divisor = (uint16_t)(BAUD_DIVISOR_BASE / actualBaud);
|
||||||
|
|
||||||
|
// Guard: divisor 0 means the UART treats it as 65536, giving ~1.76 baud.
|
||||||
|
// This can happen when BuildCommDCB stores a raw truncated value for
|
||||||
|
// 115200 (e.g. 0xE101 = 57601) and a future rate exceeds 115200.
|
||||||
|
if (divisor == 0) {
|
||||||
|
divisor = 1;
|
||||||
|
}
|
||||||
|
|
||||||
// Set DLAB to access divisor latch
|
// Set DLAB to access divisor latch
|
||||||
lcr = (uint8_t)_inp(base + UART_LCR);
|
lcr = (uint8_t)_inp(base + UART_LCR);
|
||||||
_outp(base + UART_LCR, lcr | LCR_DLAB);
|
_outp(base + UART_LCR, lcr | LCR_DLAB);
|
||||||
|
|
@ -315,6 +322,11 @@ void applyLineParams(PortStateT *port, uint8_t byteSize, uint8_t parity, uint8_t
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dbgHex16("KPCOMM: applyLine byteSize", (uint16_t)byteSize);
|
||||||
|
dbgHex16("KPCOMM: applyLine parity", (uint16_t)parity);
|
||||||
|
dbgHex16("KPCOMM: applyLine stopBits", (uint16_t)stopBits);
|
||||||
|
dbgHex16("KPCOMM: applyLine LCR", (uint16_t)lcr);
|
||||||
|
|
||||||
_outp(base + UART_LCR, lcr);
|
_outp(base + UART_LCR, lcr);
|
||||||
|
|
||||||
port->byteSize = byteSize;
|
port->byteSize = byteSize;
|
||||||
|
|
@ -948,10 +960,12 @@ int16_t FAR PASCAL _export inicom(DCB FAR *dcb)
|
||||||
_inp(port->baseAddr + UART_RBR);
|
_inp(port->baseAddr + UART_RBR);
|
||||||
|
|
||||||
// Populate ComDEB for third-party compatibility
|
// Populate ComDEB for third-party compatibility
|
||||||
port->comDeb.port = port->baseAddr;
|
port->comDeb.port = port->baseAddr;
|
||||||
port->comDeb.baudRate = port->baudRate;
|
port->comDeb.baudRate = port->baudRate;
|
||||||
port->comDeb.qInSize = port->rxSize;
|
port->comDeb.qInSize = port->rxSize;
|
||||||
port->comDeb.qOutSize = port->txSize;
|
port->comDeb.qOutSize = port->txSize;
|
||||||
|
port->comDeb.lcrShadow = (uint8_t)_inp(port->baseAddr + UART_LCR);
|
||||||
|
port->comDeb.mcrShadow = (uint8_t)_inp(port->baseAddr + UART_MCR);
|
||||||
|
|
||||||
// Enable receive and line status interrupts
|
// Enable receive and line status interrupts
|
||||||
_outp(port->baseAddr + UART_IER, IER_RDA | IER_LSI | IER_MSI);
|
_outp(port->baseAddr + UART_IER, IER_RDA | IER_LSI | IER_MSI);
|
||||||
|
|
@ -1101,7 +1115,9 @@ void primeTx(PortStateT *port)
|
||||||
// reactivateOpenCommPorts - Reactivate all ports after task switch (ordinal 18)
|
// reactivateOpenCommPorts - Reactivate all ports after task switch (ordinal 18)
|
||||||
//
|
//
|
||||||
// Called by Windows when switching back to this VM.
|
// Called by Windows when switching back to this VM.
|
||||||
// Re-enables interrupts and restores MCR state.
|
// Restores full UART state: baud rate, line params (LCR), MCR, FIFOs,
|
||||||
|
// and re-enables interrupts. A DOS fullscreen app or VM switch may
|
||||||
|
// have reprogrammed the UART, so we must restore everything.
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
void FAR PASCAL _export reactivateOpenCommPorts(void)
|
void FAR PASCAL _export reactivateOpenCommPorts(void)
|
||||||
{
|
{
|
||||||
|
|
@ -1117,6 +1133,12 @@ void FAR PASCAL _export reactivateOpenCommPorts(void)
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Restore baud rate (sets DLAB, writes divisor, clears DLAB)
|
||||||
|
applyBaudRate(port, port->baudRate);
|
||||||
|
|
||||||
|
// Restore line parameters (word length, parity, stop bits)
|
||||||
|
applyLineParams(port, port->byteSize, port->parity, port->stopBits);
|
||||||
|
|
||||||
// Restore MCR (DTR, RTS, OUT2)
|
// Restore MCR (DTR, RTS, OUT2)
|
||||||
mcr = MCR_OUT2;
|
mcr = MCR_OUT2;
|
||||||
if (port->dtrState) {
|
if (port->dtrState) {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue