Texturing (validated live -- game-live-textured.png, the ravine's brown dirt terrain): the wire model is Division's intensity+ramp scheme. action 26 uploads 8-bit intensity texels ([node][nbytes][w][h] + rows; type 13 = texture); texmap (12) references the texture; material (11) references its texmap and a ramp (14: lo/hi RGB); texel color = lerp(lo, hi, i/255). The backend bakes RGBA per material, uploads to GL, maps with wire UVs (stride-5: floats 3-4; stride-8/9: floats 6-7). Serial (directserial fork options, tracked in vpx-device/serialport/): - rxpollus:<us> -- receive poll tick (stock 1ms); 100us discovers inbound bytes ~10x sooner. Validated: sim advanced, camera moved with real RIO. - rxburst:<n> -- stock DOSBox re-serializes received bytes at emulated wire speed (~1ms/byte at 9600) though they already paid wire time on the real cable; a 15-byte analog reply gained ~14ms, blowing the RIO's few-ms ACK window and dropping the game into its 15-second retry fallback (L4CTRL.CPP limit=15.0 //0.2). rxburst:16 delivers buffered bytes 16x faster. game_rio.conf: realport:COM1 rxpollus:100 rxburst:16. Crash-on-advance fixed: *_TSHD.BGF terrain shadows live only in arena subdirs the search path misses; null shadow renderable was dereferenced once the sim moved. All 11 copied into VIDEO/GEO in the working image; game now runs sustained (560+ frames, textured, no crash). Details in RIO-NOTES.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
376 lines
11 KiB
C++
376 lines
11 KiB
C++
/*
|
|
* Copyright (C) 2002-2021 The DOSBox Team
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
|
|
#include "dosbox.h"
|
|
|
|
#if C_DIRECTSERIAL
|
|
|
|
#include "logging.h"
|
|
#include "serialport.h"
|
|
#include "directserial.h"
|
|
#include "misc_util.h"
|
|
#include "pic.h"
|
|
|
|
#include "libserial.h"
|
|
|
|
/* This is a serial passthrough class. Its amazingly simple to */
|
|
/* write now that the serial ports themselves were abstracted out */
|
|
|
|
CDirectSerial::CDirectSerial (Bitu id, CommandLine* cmd)
|
|
:CSerial (id, cmd) {
|
|
InstallationSuccessful = false;
|
|
comport = nullptr;
|
|
|
|
rx_retry = 0;
|
|
rx_retry_max = 0;
|
|
rx_state = 0;
|
|
|
|
std::string tmpstring;
|
|
if(!cmd->FindStringBegin("realport:",tmpstring,false)) return;
|
|
|
|
LOG_MSG ("Serial%d: Opening %s", (int)(COMNUMBER), tmpstring.c_str());
|
|
if(!SERIAL_open(tmpstring.c_str(), &comport)) {
|
|
char errorbuffer[256];
|
|
SERIAL_getErrorString(errorbuffer, sizeof(errorbuffer));
|
|
LOG_MSG("Serial%d: Serial Port \"%s\" could not be opened.",
|
|
(int)(COMNUMBER), tmpstring.c_str());
|
|
LOG_MSG("%s",errorbuffer);
|
|
return;
|
|
}
|
|
|
|
// rxdelay: How many milliseconds to wait before causing an
|
|
// overflow when the application is unresponsive.
|
|
if(getBituSubstring("rxdelay:", &rx_retry_max, cmd)) {
|
|
if(!(rx_retry_max<=10000)) {
|
|
rx_retry_max=0;
|
|
}
|
|
}
|
|
|
|
// rxpollus: host-port receive polling tick in MICROSECONDS (50..1000,
|
|
// default 1000 = the stock 1ms tick). Lower values discover incoming
|
|
// bytes sooner -- needed for hard-real-time serial peripherals like the
|
|
// VWE RIO cockpit board, which drops comms on ACKs late by a few ms.
|
|
Bitu rx_poll_us = 0;
|
|
if(getBituSubstring("rxpollus:", &rx_poll_us, cmd)) {
|
|
if(rx_poll_us < 50) rx_poll_us = 50;
|
|
if(rx_poll_us > 1000) rx_poll_us = 1000;
|
|
rx_poll_ms = (float)rx_poll_us / 1000.0f;
|
|
LOG_MSG("Serial%d: receive poll tick %uus", (int)(COMNUMBER),
|
|
(unsigned)rx_poll_us);
|
|
}
|
|
|
|
// rxburst: deliver host-buffered received bytes <n> times faster than
|
|
// emulated wire speed (they already paid wire time on the real cable).
|
|
Bitu rx_burst = 0;
|
|
if(getBituSubstring("rxburst:", &rx_burst, cmd)) {
|
|
if(rx_burst < 1) rx_burst = 1;
|
|
if(rx_burst > 64) rx_burst = 64;
|
|
rx_burst_div = (float)rx_burst;
|
|
LOG_MSG("Serial%d: receive burst delivery x%u", (int)(COMNUMBER),
|
|
(unsigned)rx_burst);
|
|
}
|
|
|
|
CSerial::Init_Registers();
|
|
InstallationSuccessful = true;
|
|
rx_state = D_RX_IDLE;
|
|
setEvent(SERIAL_POLLING_EVENT, rx_poll_ms); // receive tick
|
|
}
|
|
|
|
CDirectSerial::~CDirectSerial () {
|
|
if(comport) SERIAL_close(comport);
|
|
// We do not use own events so we don't have to clear them.
|
|
}
|
|
|
|
// CanReceive: true:UART part has room left
|
|
// doReceive: true:there was really a byte to receive
|
|
// rx_retry is incremented in polling events
|
|
|
|
// in POLLING_EVENT: always add new polling event
|
|
// D_RX_IDLE + CanReceive + doReceive -> D_RX_WAIT , add RX_EVENT
|
|
// D_RX_IDLE + CanReceive + not doReceive -> D_RX_IDLE
|
|
// D_RX_IDLE + not CanReceive -> D_RX_BLOCKED, add RX_EVENT
|
|
|
|
// D_RX_BLOCKED + CanReceive + doReceive -> D_RX_FASTWAIT, rem RX_EVENT
|
|
// rx_retry=0 , add RX_EVENT
|
|
// D_RX_BLOCKED + CanReceive + !doReceive -> D_RX_IDLE, rem RX_EVENT
|
|
// rx_retry=0
|
|
// D_RX_BLOCKED + !CanReceive + doReceive + retry < max -> D_RX_BLOCKED, rx_retry++
|
|
// D_RX_BLOCKED + !CanReceive + doReceive + retry >=max -> rx_retry=0
|
|
|
|
// to be continued...
|
|
|
|
void CDirectSerial::handleUpperEvent(uint16_t type) {
|
|
/*
|
|
#if SERIAL_DEBUG
|
|
const char* s;
|
|
const char* s2;
|
|
switch(type) {
|
|
case SERIAL_POLLING_EVENT: s = "POLLING_EVENT"; break;
|
|
case SERIAL_RX_EVENT: s = "RX_EVENT"; break;
|
|
case SERIAL_TX_EVENT: s = "TX_EVENT"; break;
|
|
case SERIAL_THR_EVENT: s = "THR_EVENT"; break;
|
|
}
|
|
switch(rx_state) {
|
|
case D_RX_IDLE: s2 = "RX_IDLE"; break;
|
|
case D_RX_WAIT: s2 = "RX_WAIT"; break;
|
|
case D_RX_BLOCKED: s2 = "RX_BLOCKED"; break;
|
|
case D_RX_FASTWAIT: s2 = "RX_FASTWAIT"; break;
|
|
}
|
|
log_ser(dbg_aux,"Directserial: Event enter %s, %s",s,s2);
|
|
#endif
|
|
*/
|
|
|
|
switch(type) {
|
|
case SERIAL_POLLING_EVENT: {
|
|
setEvent(SERIAL_POLLING_EVENT, rx_poll_ms);
|
|
// update Modem input line states
|
|
switch(rx_state) {
|
|
case D_RX_IDLE:
|
|
if(CanReceiveByte()) {
|
|
if(doReceive()) {
|
|
// a byte was received
|
|
rx_state=D_RX_WAIT;
|
|
setEvent(SERIAL_RX_EVENT, bytetime*0.9f/rx_burst_div);
|
|
} // else still idle
|
|
} else {
|
|
#if SERIAL_DEBUG
|
|
if(!dbgmsg_poll_block) {
|
|
log_ser(dbg_aux,"Directserial: block on polling.");
|
|
dbgmsg_poll_block=true;
|
|
}
|
|
#endif
|
|
rx_state=D_RX_BLOCKED;
|
|
// have both delays (1ms + bytetime)
|
|
setEvent(SERIAL_RX_EVENT, bytetime*0.9f/rx_burst_div);
|
|
}
|
|
break;
|
|
case D_RX_BLOCKED:
|
|
// one timeout tick
|
|
if(!CanReceiveByte()) {
|
|
rx_retry++;
|
|
if(rx_retry>=rx_retry_max) {
|
|
// it has timed out:
|
|
rx_retry=0;
|
|
removeEvent(SERIAL_RX_EVENT);
|
|
if(doReceive()) {
|
|
// read away everything
|
|
// this will set overrun errors
|
|
while(doReceive());
|
|
rx_state=D_RX_WAIT;
|
|
setEvent(SERIAL_RX_EVENT, bytetime*0.9f/rx_burst_div);
|
|
} else {
|
|
// much trouble about nothing
|
|
rx_state=D_RX_IDLE;
|
|
}
|
|
} // else wait further
|
|
} else {
|
|
// good: we can receive again
|
|
#if SERIAL_DEBUG
|
|
dbgmsg_poll_block=false;
|
|
dbgmsg_rx_block=false;
|
|
#endif
|
|
removeEvent(SERIAL_RX_EVENT);
|
|
rx_retry=0;
|
|
if(doReceive()) {
|
|
rx_state=D_RX_FASTWAIT;
|
|
setEvent(SERIAL_RX_EVENT, bytetime*0.65f/rx_burst_div);
|
|
} else {
|
|
// much trouble about nothing
|
|
rx_state=D_RX_IDLE;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case D_RX_WAIT:
|
|
case D_RX_FASTWAIT:
|
|
break;
|
|
}
|
|
updateMSR();
|
|
break;
|
|
}
|
|
case SERIAL_RX_EVENT: {
|
|
switch(rx_state) {
|
|
case D_RX_IDLE:
|
|
LOG_MSG("internal error in directserial");
|
|
break;
|
|
|
|
case D_RX_BLOCKED: // try to receive
|
|
case D_RX_WAIT:
|
|
case D_RX_FASTWAIT:
|
|
if(CanReceiveByte()) {
|
|
// just works or unblocked
|
|
rx_retry=0; // not waiting anymore
|
|
if(doReceive()) {
|
|
if(rx_state==D_RX_WAIT) setEvent(SERIAL_RX_EVENT, bytetime*0.9f/rx_burst_div);
|
|
else {
|
|
// maybe unblocked
|
|
rx_state=D_RX_FASTWAIT;
|
|
setEvent(SERIAL_RX_EVENT, bytetime*0.65f/rx_burst_div);
|
|
}
|
|
} else {
|
|
// didn't receive anything
|
|
rx_state=D_RX_IDLE;
|
|
}
|
|
} else {
|
|
// blocking now or still blocked
|
|
#if SERIAL_DEBUG
|
|
if(rx_state==D_RX_BLOCKED) {
|
|
if(!dbgmsg_rx_block) {
|
|
log_ser(dbg_aux,"Directserial: rx still blocked (retry=%d)",rx_retry);
|
|
dbgmsg_rx_block=true;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
else log_ser(dbg_aux,"Directserial: block on continued rx (retry=%d).",rx_retry);
|
|
#endif
|
|
setEvent(SERIAL_RX_EVENT, bytetime*0.65f/rx_burst_div);
|
|
rx_state=D_RX_BLOCKED;
|
|
}
|
|
|
|
break;
|
|
}
|
|
updateMSR();
|
|
break;
|
|
}
|
|
case SERIAL_TX_EVENT: {
|
|
// Maybe echo circuit works a bit better this way
|
|
if(rx_state==D_RX_IDLE && CanReceiveByte()) {
|
|
if(doReceive()) {
|
|
// a byte was received
|
|
rx_state=D_RX_WAIT;
|
|
setEvent(SERIAL_RX_EVENT, bytetime*0.9f/rx_burst_div);
|
|
}
|
|
}
|
|
ByteTransmitted();
|
|
updateMSR();
|
|
break;
|
|
}
|
|
case SERIAL_THR_EVENT: {
|
|
ByteTransmitting();
|
|
setEvent(SERIAL_TX_EVENT,bytetime*1.1f);
|
|
break;
|
|
}
|
|
}
|
|
/*
|
|
#if SERIAL_DEBUG
|
|
switch(type) {
|
|
case SERIAL_POLLING_EVENT: s = "POLLING_EVENT"; break;
|
|
case SERIAL_RX_EVENT: s = "RX_EVENT"; break;
|
|
case SERIAL_TX_EVENT: s = "TX_EVENT"; break;
|
|
case SERIAL_THR_EVENT: s = "THR_EVENT"; break;
|
|
}
|
|
switch(rx_state) {
|
|
case D_RX_IDLE: s2 = "RX_IDLE"; break;
|
|
case D_RX_WAIT: s2 = "RX_WAIT"; break;
|
|
case D_RX_BLOCKED: s2 = "RX_BLOCKED"; break;
|
|
case D_RX_FASTWAIT: s2 = "RX_FASTWAIT"; break;
|
|
}
|
|
log_ser(dbg_aux,"Directserial: Event exit %s, %s",s,s2);
|
|
#endif*/
|
|
}
|
|
|
|
bool CDirectSerial::doReceive() {
|
|
int value = SERIAL_getextchar(comport);
|
|
if(value) {
|
|
receiveByteEx((uint8_t)(value&0xff),(uint8_t)((value&0xff00)>>8));
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// updatePortConfig is called when emulated app changes the serial port
|
|
// parameters baudrate, stopbits, number of databits, parity.
|
|
void CDirectSerial::updatePortConfig (uint16_t divider, uint8_t lcr) {
|
|
uint8_t parity = 0;
|
|
|
|
switch ((lcr & 0x38)>>3) {
|
|
case 0x1: parity='o'; break;
|
|
case 0x3: parity='e'; break;
|
|
case 0x5: parity='m'; break;
|
|
case 0x7: parity='s'; break;
|
|
default: parity='n'; break;
|
|
}
|
|
|
|
uint8_t bytelength = (lcr & 0x3)+5;
|
|
|
|
// baudrate
|
|
Bitu baudrate;
|
|
if(divider==0) baudrate=115200u;
|
|
else baudrate = 115200u / divider;
|
|
|
|
// stopbits
|
|
uint8_t stopbits;
|
|
if (lcr & 0x4) {
|
|
if (bytelength == 5) stopbits = SERIAL_15STOP;
|
|
else stopbits = SERIAL_2STOP;
|
|
} else stopbits = SERIAL_1STOP;
|
|
|
|
if(!SERIAL_setCommParameters(comport, (int)baudrate, (char)parity, (char)stopbits, (char)bytelength)) {
|
|
#if SERIAL_DEBUG
|
|
log_ser(dbg_aux,"Serial port settings not supported by host." );
|
|
#endif
|
|
LOG_MSG ("Serial%d: Desired serial mode not supported (%d,%d,%c,%d)",
|
|
(int)(COMNUMBER),(int)baudrate,(int)bytelength,parity,(int)stopbits);
|
|
}
|
|
CDirectSerial::setRTSDTR(getRTS(), getDTR());
|
|
}
|
|
|
|
void CDirectSerial::updateMSR () {
|
|
int new_status = SERIAL_getmodemstatus(comport);
|
|
|
|
setCTS((new_status&SERIAL_CTS)? true:false);
|
|
setDSR((new_status&SERIAL_DSR)? true:false);
|
|
setRI((new_status&SERIAL_RI)? true:false);
|
|
setCD((new_status&SERIAL_CD)? true:false);
|
|
}
|
|
|
|
void CDirectSerial::transmitByte (uint8_t val, bool first) {
|
|
if(!SERIAL_sendchar(comport, (char)val))
|
|
LOG_MSG("Serial%d: COM port error: write failed!", (int)COMNUMBER);
|
|
if(first) setEvent(SERIAL_THR_EVENT, bytetime/8);
|
|
else setEvent(SERIAL_TX_EVENT, bytetime);
|
|
}
|
|
|
|
|
|
// setBreak(val) switches break on or off
|
|
void CDirectSerial::setBreak (bool value) {
|
|
SERIAL_setBREAK(comport,value);
|
|
}
|
|
|
|
// updateModemControlLines(mcr) sets DTR and RTS.
|
|
void CDirectSerial::setRTSDTR(bool rts, bool dtr) {
|
|
SERIAL_setRTS(comport,rts);
|
|
SERIAL_setDTR(comport,dtr);
|
|
}
|
|
|
|
void CDirectSerial::setRTS(bool val) {
|
|
SERIAL_setRTS(comport,val);
|
|
}
|
|
|
|
void CDirectSerial::setDTR(bool val) {
|
|
SERIAL_setDTR(comport,val);
|
|
}
|
|
|
|
#endif
|