Bring the pod's 128x32 plasma readout in-fork, completing the self-contained
glass cockpit -- no vPLASMA process, no pipe.
- serialplasma.{cpp,h}: CSerialPlasma, a serial<n>=plasma backend that parses
the game's ESC P graphics stream (L4PLASMA streams whole changed rows;
receive-only, no replies) into a 128x32 1bpp framebuffer. Transcribed from
vPLASMA's PlasmaProtocol + VPlasmaDevice (graphics path); the factory
text-mode/fonts aren't used by the game and aren't rendered, but their escape
operands are still consumed so the parser can't desync. Seam PLASMA_GetFrame()
for the renderer.
- vpxlog.cpp: draw the framebuffer as an amber-on-black window in the explode
layout, lazily created once a plasma port exists, parked centered under the
lower-left MFD (VPX_PLASMA="x,y,w,h" overrides); WS_EX_NOACTIVATE so it can't
steal DOSBox focus.
- Register SERIAL_TYPE_PLASMA (serialport.h/.cpp, dosbox.cpp -- documented in
the vpx-device README step 3d, since those stock files live in the git-ignored
src tree).
- Confs: serial2 in the _rio confs + deploy templates switches from
namedpipe pipe:vplasma to the in-fork plasma.
Live-validated 2026-07-24. Real pods keep directserial realport:COM2; the
standalone vPLASMA app keeps namedpipe pipe:vplasma.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
160 lines
4.5 KiB
C++
160 lines
4.5 KiB
C++
/*
|
|
* VWE fork: in-fork cockpit plasma display (serial<n>=plasma). See
|
|
* serialplasma.h. Graphics-only (ESC P) transcription of vPLASMA's device;
|
|
* receive-only, single emulator thread except the framebuffer snapshot which
|
|
* the VPX render thread reads under a lock.
|
|
*/
|
|
|
|
#include "dosbox.h"
|
|
|
|
#include "logging.h"
|
|
#include "pic.h"
|
|
#include "serialport.h"
|
|
#include "serialplasma.h"
|
|
|
|
#include <cstdarg>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
|
|
#define PLASMA_ESC 0x1B
|
|
|
|
static CSerialPlasma* plasma_instance = nullptr;
|
|
|
|
bool PLASMA_GetFrame(uint8_t out[512]) {
|
|
if (!plasma_instance) return false;
|
|
plasma_instance->getFrame(out);
|
|
return true;
|
|
}
|
|
|
|
CSerialPlasma::CSerialPlasma(Bitu id, CommandLine* cmd) : CSerial(id, cmd) {
|
|
(void)cmd;
|
|
InstallationSuccessful = false;
|
|
logv = (getenv("VWE_PLASMA_LOG") != nullptr);
|
|
|
|
LOG_MSG("Serial%d: in-fork plasma display (128x32, ESC P graphics)", (int)COMNUMBER);
|
|
|
|
CSerial::Init_Registers();
|
|
// the display is always present -- assert the modem-in lines the game reads
|
|
setRI(false);
|
|
setCD(true);
|
|
setDSR(true);
|
|
setCTS(true);
|
|
|
|
if (plasma_instance == nullptr) plasma_instance = this;
|
|
else LOG_MSG("Serial%d: another plasma port already owns the display window", (int)COMNUMBER);
|
|
|
|
InstallationSuccessful = true;
|
|
}
|
|
|
|
CSerialPlasma::~CSerialPlasma() {
|
|
if (plasma_instance == this) plasma_instance = nullptr;
|
|
}
|
|
|
|
void CSerialPlasma::logf(const char* fmt, ...) {
|
|
if (!logv) return;
|
|
va_list ap; va_start(ap, fmt);
|
|
fprintf(stderr, "[plasma] ");
|
|
vfprintf(stderr, fmt, ap);
|
|
fprintf(stderr, "\n");
|
|
va_end(ap);
|
|
}
|
|
|
|
void CSerialPlasma::getFrame(uint8_t out[512]) {
|
|
std::lock_guard<std::mutex> lk(fbMx);
|
|
memcpy(out, fb, 512);
|
|
}
|
|
|
|
// ---- ESC command parser (VPlasmaDevice.Step, graphics path) ------------------
|
|
void CSerialPlasma::feed(uint8_t b) {
|
|
switch (st) {
|
|
case P_TEXT:
|
|
// The game is graphics-only; a printable/control byte in text mode is
|
|
// not something it sends, so swallow it (text mode isn't rendered).
|
|
if (b == PLASMA_ESC) st = P_ESC;
|
|
return;
|
|
|
|
case P_ESC:
|
|
st = P_TEXT;
|
|
switch (b) {
|
|
case '@': // clear screen
|
|
{ std::lock_guard<std::mutex> lk(fbMx); memset(fb, 0, 512); }
|
|
logf("clear (ESC @)");
|
|
break;
|
|
case 'L': // home cursor -- no-op for graphics
|
|
break;
|
|
case 'G': case 'K': case 'H': // cursor mode / font / attributes
|
|
case 'Q': case 'R': // set row / column
|
|
case 'I': case 'i': // draw / display page
|
|
st = P_OPERAND; // consume 1 operand, unused (text mode)
|
|
break;
|
|
case 'P': // graphics write
|
|
hdrFill = 0;
|
|
st = P_GHDR;
|
|
break;
|
|
default:
|
|
logf("unknown ESC 0x%02X ('%c')", b, (b >= 0x20 && b < 0x7F) ? (char)b : '?');
|
|
break;
|
|
}
|
|
return;
|
|
|
|
case P_OPERAND:
|
|
st = P_TEXT; // operand read + discarded
|
|
return;
|
|
|
|
case P_GHDR:
|
|
hdr[hdrFill++] = b;
|
|
if (hdrFill == 5) { // screen, y, xbyte, w, h
|
|
int w = hdr[3], h = hdr[4];
|
|
dataLen = w * h;
|
|
dataIdx = 0;
|
|
logf("ESC P y=%u xbyte=%u %ux%u", hdr[1], hdr[2], (unsigned)w, (unsigned)h);
|
|
st = dataLen > 0 ? P_GDATA : P_TEXT;
|
|
}
|
|
return;
|
|
|
|
case P_GDATA:
|
|
{
|
|
int w = hdr[3];
|
|
int rowOfBlock = w > 0 ? dataIdx / w : 0;
|
|
int byteOfRow = w > 0 ? dataIdx % w : 0;
|
|
int y = hdr[1] + rowOfBlock; // 0..31
|
|
int xByte = hdr[2] + byteOfRow; // 0..15
|
|
// MSB = leftmost pixel; the game packs whole rows (xbyte=0, w=16).
|
|
if (y >= 0 && y < 32 && xByte >= 0 && xByte < 16) {
|
|
std::lock_guard<std::mutex> lk(fbMx);
|
|
fb[y * 16 + xByte] = b;
|
|
}
|
|
if (byteOfRow == w - 1 && y >= 0 && y < 32) graphicsRows++;
|
|
if (++dataIdx >= dataLen) {
|
|
st = P_TEXT;
|
|
if ((graphicsRows % 200) == 0) logf("rows drawn: %ld", graphicsRows);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
// ---- UART plumbing (receive-only; drain the game's TX like the pipe) ---------
|
|
void CSerialPlasma::transmitByte(uint8_t val, bool first) {
|
|
if (first) setEvent(SERIAL_THR_EVENT, bytetime / 10);
|
|
else setEvent(SERIAL_TX_EVENT, bytetime);
|
|
feed(val);
|
|
}
|
|
|
|
void CSerialPlasma::handleUpperEvent(uint16_t type) {
|
|
if (type == SERIAL_THR_EVENT) {
|
|
ByteTransmitting();
|
|
setEvent(SERIAL_TX_EVENT, bytetime * 1.1f);
|
|
} else if (type == SERIAL_TX_EVENT) {
|
|
ByteTransmitted();
|
|
}
|
|
}
|
|
|
|
void CSerialPlasma::updatePortConfig(uint16_t divider, uint8_t lcr) { (void)divider; (void)lcr; }
|
|
void CSerialPlasma::updateMSR() {}
|
|
void CSerialPlasma::setBreak(bool value) { (void)value; }
|
|
void CSerialPlasma::setRTSDTR(bool rts, bool dtr) { (void)rts; (void)dtr; }
|
|
void CSerialPlasma::setRTS(bool val) { (void)val; }
|
|
void CSerialPlasma::setDTR(bool val) { (void)val; }
|