MAMEVECTOR64その1

はじめにMSYS2 MinGWをインストールして、Windowsで最新版のMAMEがビルドできることを確認します。

www.msys2.org

gist.github.com

つぎにLinuxのMAMEVECTOR64をWindowsでビルドします。

trmm.net

termios.hについてはこちらをベースにしました。

github.com

エラーが出たら修正を繰り返してビルドできました。

$ git clone https://github.com/veeso/termiWin
$ cp termiWin/include/termi*.h /mingw64/include/
$ git clone https://github.com/osresearch/mame/
$ cd mame
$ export MINGW64=/mingw64
$ make NOWERROR=1 SUBTARGET=vector
$ ar rcs build/mingw-gcc/bin/x64/Release/libformats.a build/mingw-gcc/obj/x64/Release/src/lib/formats/*.o
$ make NOWERROR=1 SUBTARGET=vector
$ ar rcs build/mingw-gcc/bin/x64/Release/mame_vector/liboptional.a $(find build/mingw-gcc/obj/x64/Release/src/devices/cpu -name "*.o")
$ make NOWERROR=1 SUBTARGET=vector
$ ar rcs build/mingw-gcc/bin/x64/Release/mame_vector/libbus.a build/mingw-gcc/obj/x64/Release/src/devices/bus/generic/*.o
$ make NOWERROR=1 SUBTARGET=vector
$ ar rcs build/mingw-gcc/bin/x64/Release/mame_vector/liboptional.a $(find build/mingw-gcc/obj/x64/Release/src/devices/machine -name "*.o")
$ ar rcs build/mingw-gcc/bin/x64/Release/mame_vector/liboptional.a $(find build/mingw-gcc/obj/x64/Release/src/devices/sound -name "*.o")
$ ar rcs build/mingw-gcc/bin/x64/Release/mame_vector/libbus.a $(find build/mingw-gcc/obj/x64/Release/src/devices/bus/vectrex -name "*.o")
$ make NOWERROR=1 SUBTARGET=vector

以下のファイルを修正しました。

scripts/build/verinfo.py(抜粋)

try:
#    fp = open(srcfile, 'rU')
    fp = open(srcfile, 'r')
except IOError:
    sys.stderr.write("Unable to open source file '%s'\n" % srcfile)
    sys.exit(1)

src/devices/cpu/m6502/m6502make.py(抜粋)

    try:
#        f = open(fname, "rU")
        f = open(fname, "r")
    except Exception:
        err = sys.exc_info()[1]
        logging.error("cannot read opcodes file %s [%s]", fname, err)
        sys.exit(1)

(省略)

    try:
#        f = open(fname, "rU")
        f = open(fname, "r")
    except Exception:
        err = sys.exc_info()[1]
        logging.error("cannot read display file %s [%s]", fname, err)
        sys.exit(1)

src/devices/cpu/m6809/m6809make.py(抜粋)

   try:
#      f = open(fname, "rU")
        f = open(fname, "r")
    except Exception:
        err = sys.exc_info()[1]
        sys.stderr.write("Cannot read opcodes file %s [%s]\n" % (fname, err))
        sys.exit(1)    

src/emu/video/vector.cpp

// license:BSD-3-Clause
// copyright-holders:Brad Oliver,Aaron Giles,Bernd Wiebelt,Allard van der Bas
/******************************************************************************
 *
 * vector.c
 *
 *        anti-alias code by Andrew Caldwell
 *        (still more to add)
 *
 * 040227 Fixed miny clip scaling which was breaking in mhavoc. AREK
 * 010903 added support for direct RGB modes MLR
 * 980611 use translucent vectors. Thanks to Peter Hirschberg
 *        and Neil Bradley for the inspiration. BW
 * 980307 added cleverer dirty handling. BW, ASG
 *        fixed antialias table .ac
 * 980221 rewrote anti-alias line draw routine
 *        added inline assembly multiply fuction for 8086 based machines
 *        beam diameter added to draw routine
 *        beam diameter is accurate in anti-alias line draw (Tcosin)
 *        flicker added .ac
 * 980203 moved LBO's routines for drawing into a buffer of vertices
 *        from avgdvg.c to this location. Scaling is now initialized
 *        by calling vector_init(...). BW
 * 980202 moved out of msdos.c ASG
 * 980124 added anti-alias line draw routine
 *        modified avgdvg.c and sega.c to support new line draw routine
 *        added two new tables Tinten and Tmerge (for 256 color support)
 *        added find_color routine to build above tables .ac
 *
 **************************************************************************** */

#include "emu.h"
#include "emuopts.h"
#include "rendutil.h"
#include "vector.h"

// Serial port related includes
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
#include <errno.h>

#include <inttypes.h>
#include <sys/time.h>
 

#define FLT_EPSILON 1E-5

#define VECTOR_WIDTH_DENOM 512

#define MAX_POINTS 10000

#define VECTOR_SERIAL_MAX 4095

#define VECTOR_TEAM \
   "-* Vector Heads *-\n" \
   "Brad Oliver\n" \
   "Aaron Giles\n" \
   "Bernd Wiebelt\n" \
   "Allard van der Bas\n" \
   "Al Kossow (VECSIM)\n" \
   "Hedley Rainnie (VECSIM)\n" \
   "Eric Smith (VECSIM)\n" \
   "Neil Bradley (technical advice)\n" \
   "Andrew Caldwell (anti-aliasing)\n" \
   "- *** -\n"

///-----
#ifdef __cplusplus > 201711L
  #define TERMIWIN_MAYBE_UNUSED [[maybe_unused]]
#else
  #ifdef __GNUC__
    #define TERMIWIN_MAYBE_UNUSED __attribute__((unused))
  #else
    #define TERMIWIN_MAYBE_UNUSED
  #endif
#endif

#include <fcntl.h>
#include <stdlib.h>

typedef struct COM {
  HANDLE hComm;
  int fd; //Actually it's completely useless
  char port[128];
} COM;

DCB SerialParams = { 0 }; //Initializing DCB structure
struct COM com;
COMMTIMEOUTS timeouts = { 0 }; //Initializing COMMTIMEOUTS structure

//LOCAL functions

//nbyte 0->7

int getByte(tcflag_t flag, int nbyte, int nibble) {

  int byte;
  if (nibble == 1)
    byte = (flag >> (8 * (nbyte)) & 0x0f);
  else
    byte = (flag >> (8 * (nbyte)) & 0xf0);
  return byte;
}

//INPUT FUNCTIONS

enum{
  i_IXOFF = 0x01,
  i_IXON = 0x02,
  i_IXOFF_IXON = 0x03,
  i_PARMRK = 0x04,
  i_PARMRK_IXOFF = 0x05,
  i_PARMRK_IXON = 0x06,
  i_PARMRK_IXON_IXOFF = 0x07
};

int getIXOptions(tcflag_t flag) {
  int byte = getByte(flag, 1, 1);

  return byte;
}

//LOCALOPT FUNCTIONS

enum{
  l_NOECHO = 0x00,
  l_ECHO = 0x01,
  l_ECHO_ECHOE = 0x03,
  l_ECHO_ECHOK = 0x05,
  l_ECHO_ECHONL = 0x09,
  l_ECHO_ECHOE_ECHOK = 0x07,
  l_ECHO_ECHOE_ECHONL = 0x0b,
  l_ECHO_ECHOE_ECHOK_ECHONL = 0x0f,
  l_ECHO_ECHOK_ECHONL = 0x0d,
  l_ECHOE = 0x02,
  l_ECHOE_ECHOK = 0x06,
  l_ECHOE_ECHONL = 0x0a,
  l_ECHOE_ECHOK_ECHONL = 0x0e,
  l_ECHOK = 0x04,
  l_ECHOK_ECHONL = 0x0c,
  l_ECHONL = 0x08
};

int getEchoOptions(tcflag_t flag) {
  int byte = getByte(flag, 1, 1);
  return byte;
}

enum{
  l_ICANON = 0x10,
  l_ICANON_ISIG = 0x50,
  l_ICANON_IEXTEN = 0x30,
  l_ICANON_NOFLSH = 0x90,
  l_ICANON_ISIG_IEXTEN = 0x70,
  l_ICANON_ISIG_NOFLSH = 0xd0,
  l_ICANON_IEXTEN_NOFLSH = 0xb0,
  l_ICANON_ISIG_IEXTEN_NOFLSH = 0xf0,
  l_ISIG = 0x40,
  l_ISIG_IEXTEN = 0x60,
  l_ISIG_NOFLSH = 0xc0,
  l_ISIG_IEXTEN_NOFLSH = 0xe0,
  l_IEXTEN = 0x20,
  l_IEXTEN_NOFLSH = 0xa0,
  l_NOFLSH = 0x80,
};

int getLocalOptions(tcflag_t flag) {
  int byte = getByte(flag, 1, 0);
  return byte;
}

enum{
  l_TOSTOP = 0x01
};

int getToStop(tcflag_t flag) {
  int byte = getByte(flag, 1, 1);
  return byte;
}

//CONTROLOPT FUNCTIONS

int getCharSet(tcflag_t flag) {

  //FLAG IS MADE UP OF 8 BYTES, A FLAG IS MADE UP OF A NIBBLE -> 4 BITS, WE NEED TO EXTRACT THE SECOND NIBBLE (1st) FROM THE FIFTH BYTE (6th).
  int byte = getByte(flag, 1, 1);

  switch (byte) {

  case 0X0:
    return CS5;
    break;

  case 0X4:
    return CS6;
    break;

  case 0X8:
    return CS7;
    break;

  case 0Xc:
    return CS8;
    break;

  default:
    return CS8;
    break;
  }
}

enum{
  c_ALL_ENABLED = 0xd0,
  c_PAREVEN_CSTOPB = 0x50,
  c_PAREVEN_NOCSTOPB = 0x40,
  c_PARODD_NOCSTOPB = 0xc0,
  c_NOPARENB_CSTOPB = 0x10,
  c_ALL_DISABLED = 0x00,
};

int getControlOptions(tcflag_t flag) {
  int byte = getByte(flag, 1, 0);
  return byte;
}

//LIBFUNCTIONS

int tcgetattr(int fd, struct termios* TERMIWIN_MAYBE_UNUSED termios_p) {

  if (fd != com.fd) return -1;
  int TERMIWIN_MAYBE_UNUSED ret = 0;

  ret = GetCommState(com.hComm, &SerialParams);

  return 0;
}

int tcsetattr(int fd, int TERMIWIN_MAYBE_UNUSED optional_actions, const struct termios* termios_p) {

  if (fd != com.fd) return -1;
  int ret = 0;

  //Store flags into local variables
  tcflag_t iflag = termios_p->c_iflag;
  tcflag_t lflag = termios_p->c_lflag;
  tcflag_t cflag = termios_p->c_cflag;
  tcflag_t TERMIWIN_MAYBE_UNUSED oflag = termios_p->c_oflag;

  //iflag

  int IX = getIXOptions(iflag);

  if ((IX == i_IXOFF_IXON) || (IX == i_PARMRK_IXON_IXOFF)) {

    SerialParams.fOutX = TRUE;
    SerialParams.fInX = TRUE;
    SerialParams.fTXContinueOnXoff = TRUE;
  }

  //lflag
  int TERMIWIN_MAYBE_UNUSED EchoOpt = getEchoOptions(lflag);
  int TERMIWIN_MAYBE_UNUSED l_opt = getLocalOptions(lflag);
  int TERMIWIN_MAYBE_UNUSED tostop = getToStop(lflag);

  //Missing parameters...

  //cflags

  int CharSet = getCharSet(cflag);
  int c_opt = getControlOptions(cflag);

  switch (CharSet) {

  case CS5:
    SerialParams.ByteSize = 5;
    break;

  case CS6:
    SerialParams.ByteSize = 6;
    break;

  case CS7:
    SerialParams.ByteSize = 7;
    break;

  case CS8:
    SerialParams.ByteSize = 8;
    break;
  }

  switch (c_opt) {

  case c_ALL_ENABLED:
    SerialParams.Parity = ODDPARITY;
    SerialParams.StopBits = TWOSTOPBITS;
    break;

  case c_ALL_DISABLED:
    SerialParams.Parity = NOPARITY;
    SerialParams.StopBits = ONESTOPBIT;
    break;

  case c_PAREVEN_CSTOPB:
    SerialParams.Parity = EVENPARITY;
    SerialParams.StopBits = TWOSTOPBITS;
    break;

  case c_PAREVEN_NOCSTOPB:
    SerialParams.Parity = EVENPARITY;
    SerialParams.StopBits = ONESTOPBIT;
    break;

  case c_PARODD_NOCSTOPB:
    SerialParams.Parity = ODDPARITY;
    SerialParams.StopBits = ONESTOPBIT;
    break;

  case c_NOPARENB_CSTOPB:
    SerialParams.Parity = NOPARITY;
    SerialParams.StopBits = TWOSTOPBITS;
    break;
  }

  //aflags

  /*
  int OP;
  if(oflag == OPOST)
  else ...
  */
  //Missing parameters...

  //special characters

  if (termios_p->c_cc[VEOF] != 0) SerialParams.EofChar = (char)termios_p->c_cc[VEOF];
  if (termios_p->c_cc[VINTR] != 0) SerialParams.EvtChar = (char)termios_p->c_cc[VINTR];

  if (termios_p->c_cc[VMIN] == 1) { //Blocking

    timeouts.ReadIntervalTimeout = 0;         // in milliseconds
    timeouts.ReadTotalTimeoutConstant = 0;    // in milliseconds
    timeouts.ReadTotalTimeoutMultiplier = 0;  // in milliseconds
    timeouts.WriteTotalTimeoutConstant = 0;   // in milliseconds
    timeouts.WriteTotalTimeoutMultiplier = 0; // in milliseconds

  } else { //Non blocking

    timeouts.ReadIntervalTimeout = termios_p->c_cc[VTIME] * 100;         // in milliseconds
    timeouts.ReadTotalTimeoutConstant = termios_p->c_cc[VTIME] * 100;    // in milliseconds
    timeouts.ReadTotalTimeoutMultiplier = termios_p->c_cc[VTIME] * 100;  // in milliseconds
    timeouts.WriteTotalTimeoutConstant = termios_p->c_cc[VTIME] * 100;   // in milliseconds
    timeouts.WriteTotalTimeoutMultiplier = termios_p->c_cc[VTIME] * 100; // in milliseconds
  }

  SetCommTimeouts(com.hComm, &timeouts);

  //EOF

  ret = SetCommState(com.hComm, &SerialParams);
  if (ret != 0)
    return 0;
  else
    return -1;
}

int tcsendbreak(int fd, int TERMIWIN_MAYBE_UNUSED duration) {

  if (fd != com.fd) return -1;

  int ret = 0;
  ret = TransmitCommChar(com.hComm, '\x00');
  if (ret != 0)
    return 0;
  else
    return -1;
}

int tcdrain(int fd) {

  if (fd != com.fd) return -1;
  return FlushFileBuffers(com.hComm);
}

int tcflush(int fd, int queue_selector) {

  if (fd != com.fd) return -1;
  int rc = 0;

  switch (queue_selector) {

  case TCIFLUSH:
    rc = PurgeComm(com.hComm, PURGE_RXCLEAR);
    break;

  case TCOFLUSH:
    rc = PurgeComm(com.hComm, PURGE_TXCLEAR);
    break;

  case TCIOFLUSH:
    rc = PurgeComm(com.hComm, PURGE_RXCLEAR);
    rc *= PurgeComm(com.hComm, PURGE_TXCLEAR);
    break;

  default:
    rc = 0;
    break;
  }

  if (rc != 0)
    return 0;
  else
    return -1;
}

int tcflow(int fd, int action) {

  if (fd != com.fd) return -1;
  int rc = 0;

  switch (action) {

  case TCOOFF:
    rc = PurgeComm(com.hComm, PURGE_TXABORT);
    break;

  case TCOON:
    rc = ClearCommBreak(com.hComm);
    break;

  case TCIOFF:
    rc = PurgeComm(com.hComm, PURGE_RXABORT);
    break;

  case TCION:
    rc = ClearCommBreak(com.hComm);
    break;

  default:
    rc = 0;
    break;
  }

  if (rc != 0)
    return 0;
  else
    return -1;
}

void cfmakeraw(struct termios* TERMIWIN_MAYBE_UNUSED termios_p) {

  SerialParams.ByteSize = 8;
  SerialParams.StopBits = ONESTOPBIT;
  SerialParams.Parity = NOPARITY;
}

speed_t cfgetispeed(const struct termios* TERMIWIN_MAYBE_UNUSED termios_p) {

  return SerialParams.BaudRate;
}

speed_t cfgetospeed(const struct termios* TERMIWIN_MAYBE_UNUSED termios_p) {

  return SerialParams.BaudRate;
}

int cfsetispeed(struct termios* TERMIWIN_MAYBE_UNUSED termios_p, speed_t speed) {

  SerialParams.BaudRate = speed;
  return 0;
}

int cfsetospeed(struct termios* TERMIWIN_MAYBE_UNUSED termios_p, speed_t speed) {

  SerialParams.BaudRate = speed;
  return 0;
}

int cfsetspeed(struct termios* TERMIWIN_MAYBE_UNUSED termios_p, speed_t speed) {

  SerialParams.BaudRate = speed;
  return 0;
}

ssize_t read_serial(int fd, void* buffer, size_t count) {

  if (fd != com.fd) return -1;
///  int rc = 0;
  DWORD rc = 0;
  int ret;

  ret = ReadFile(com.hComm, buffer, count, &rc, NULL);

  if (ret == 0)
    return -1;
  else
    return rc;
}

ssize_t write_serial(int fd, const void* buffer, size_t count) {

  if (fd != com.fd) return -1;
///  int rc = 0;
  DWORD rc = 0;
  int ret;

  ret = WriteFile(com.hComm, buffer, count, &rc, NULL);

  if (ret == 0)
    return -1;
  else
    return rc;
}

int open_serial(const char* portname, int opt) {

  if (strlen(portname) < 4) return -1;

  // Set to zero
  memset(com.port, 0x00, 128);

  //COMxx
  size_t portSize = 0;
  if (strlen(portname) > 4) {
    portSize = sizeof(char) * strlen("\\\\.\\COM10") + 1;
#ifdef _MSC_VER
    strncat_s(com.port, portSize, "\\\\.\\", strlen("\\\\.\\"));
#else
    strncat(com.port, "\\\\.\\", strlen("\\\\.\\"));
#endif
  }
  //COMx
  else {
    portSize = sizeof(char) * 5;
  }

#ifdef _MSC_VER
  strncat_s(com.port, portSize, portname, 4);
#else
  strncat(com.port, portname, 4);
#endif
  com.port[portSize] = 0x00;

  switch (opt) {

  case O_RDWR:
    com.hComm = CreateFile(com.port, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
    break;

  case O_RDONLY:
    com.hComm = CreateFile(com.port, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
    break;

  case O_WRONLY:
    com.hComm = CreateFile(com.port, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
    break;
  }

  if (com.hComm == INVALID_HANDLE_VALUE) {
    return -1;
  }
  com.fd = atoi(portname + 3); // COMx and COMxx
  SerialParams.DCBlength = sizeof(SerialParams);
  return com.fd;
}

int close_serial(int TERMIWIN_MAYBE_UNUSED fd) {

  int ret = CloseHandle(com.hComm);
  if (ret != 0)
    return 0;
  else
    return -1;
}

int select_serial(int TERMIWIN_MAYBE_UNUSED nfds, fd_set* readfds, fd_set* TERMIWIN_MAYBE_UNUSED writefds, fd_set* TERMIWIN_MAYBE_UNUSED exceptfds, struct timeval* TERMIWIN_MAYBE_UNUSED timeout) {

  SetCommMask(com.hComm, EV_RXCHAR);
  DWORD dwEventMask;
  if (WaitCommEvent(com.hComm, &dwEventMask, NULL) == 0) {
    return -1; // Return -1 if failed
  }
  if (dwEventMask == EV_RXCHAR) {
    return com.fd;
  } else {
    if (readfds) {
      // Clear file descriptor if event is not RXCHAR
      FD_CLR(com.fd, readfds);
    }
  }
  // NOTE: write event not detectable!
  // NOTE: no timeout
  return 0; // No data
}

//Returns hComm from the COM structure
HANDLE getHandle() {
  return com.hComm;
}
///-----

#define VCLEAN  0
#define VDIRTY  1
#define VCLIP   2

// device type definition
const device_type VECTOR = &device_creator<vector_device>;

vector_device::vector_device(const machine_config &mconfig, device_type type, const char *name, const char *tag, device_t *owner, UINT32 clock, const char *shortname, const char *source)
    : device_t(mconfig, type, name, tag, owner, clock, shortname, source),
        device_video_interface(mconfig, *this),
        m_vector_list(nullptr),
        m_min_intensity(255),
        m_max_intensity(0)
{
}

vector_device::vector_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock)
    : device_t(mconfig, VECTOR, "VECTOR", tag, owner, clock, "vector_device", __FILE__),
        device_video_interface(mconfig, *this),
        m_vector_list(nullptr),
        m_min_intensity(255),
        m_max_intensity(0)
{
}

float vector_device::m_flicker = 0.0f;
float vector_device::m_beam_width_min = 0.0f;
float vector_device::m_beam_width_max = 0.0f;
float vector_device::m_beam_intensity_weight = 0.0f;
int vector_device::m_vector_index;

struct serial_segment_t {
    struct serial_segment_t * next;
    int intensity;
    int x0;
    int y0;
    int x1;
    int y1;

    serial_segment_t(
        int x0,
        int y0,
        int x1,
        int y1,
        int intensity
    ) :
        next(NULL),
        intensity(intensity),
        x0(x0),
        y0(y0),
        x1(x1),
        y1(y1)
    {
    }
};

int
serial_open(
        const char * const dev
)
{
///        const int fd = open(dev, O_RDWR | O_NONBLOCK | O_NOCTTY, 0666);
        const int fd = open(dev, O_RDWR, 0666);
        if (fd < 0)
                return -1;

        // Disable modem control signals
        struct termios attr;
        tcgetattr(fd, &attr);
        attr.c_cflag |= CLOCAL | CREAD;
        attr.c_oflag &= ~OPOST;
        tcsetattr(fd, TCSANOW, &attr);

        return fd;
}

void vector_device::serial_draw_point(
    unsigned x,
    unsigned y,
    int intensity
)
{
    // make sure that we are in range; should always be
    // due to clipping on the window, but just in case
    if (x < 0) x = 0;
    if (y < 0) y = 0;

    if (x > VECTOR_SERIAL_MAX) x = VECTOR_SERIAL_MAX;
    if (y > VECTOR_SERIAL_MAX) y = VECTOR_SERIAL_MAX;

    // always flip the Y, since the vectorscope measures
    // 0,0 at the bottom left corner, but this coord uses
    // the top left corner.
    y = VECTOR_SERIAL_MAX - y;

    unsigned bright;
    if (intensity > m_serial_bright)
        bright = 63;
    else
    if (intensity <= 0)
        bright = 0;
    else
        bright = (intensity * 64) / 256;

    if (bright > 63)
        bright = 63;

    if (m_serial_rotate == 1)
    {
        // +90
        unsigned tmp = x;
        x = VECTOR_SERIAL_MAX - y;
        y = tmp;
    } else
    if (m_serial_rotate == 2)
    {
        // +180
        x = VECTOR_SERIAL_MAX - x;
        y = VECTOR_SERIAL_MAX - y;
    } else
    if (m_serial_rotate == 3)
    {
        // -90
        unsigned t = x;
        x = y;
        y = VECTOR_SERIAL_MAX - t;
    }

    uint32_t cmd = 0
        | (2 << 30)
        | (bright & 0x3F) << 24
        | (x & 0xFFF) << 12
        | (y & 0xFFF) <<  0
        ;

    //printf("%08x %8d %8d %3d\n", cmd, x, y, intensity);

    m_serial_buf[m_serial_offset++] = cmd >> 24;
    m_serial_buf[m_serial_offset++] = cmd >> 16;
    m_serial_buf[m_serial_offset++] = cmd >>  8;
    m_serial_buf[m_serial_offset++] = cmd >>  0;

    // todo: check for overflow;
    // should always have enough points
}


// This will only be called with non-zero intensity lines.
// we keep a linked list of the vectors and sort them with
// a greedy insertion sort.
void vector_device::serial_draw_line(
    float xf0,
    float yf0,
    float xf1,
    float yf1,
    int intensity
)
{
    if (m_serial_fd < 0)
        return;

    // scale and shift each of the axes.
    const int x0 = (xf0 * VECTOR_SERIAL_MAX - VECTOR_SERIAL_MAX/2) * m_serial_scale_x + m_serial_offset_x;
    const int y0 = (yf0 * VECTOR_SERIAL_MAX - VECTOR_SERIAL_MAX/2) * m_serial_scale_y + m_serial_offset_y;
    const int x1 = (xf1 * VECTOR_SERIAL_MAX - VECTOR_SERIAL_MAX/2) * m_serial_scale_x + m_serial_offset_x;
    const int y1 = (yf1 * VECTOR_SERIAL_MAX - VECTOR_SERIAL_MAX/2) * m_serial_scale_y + m_serial_offset_y;

    serial_segment_t * const new_segment
        = new serial_segment_t(x0, y0, x1, y1, intensity);

    if (this->m_serial_segments_tail)
        this->m_serial_segments_tail->next = new_segment;
    else
        this->m_serial_segments = new_segment;

    this->m_serial_segments_tail = new_segment;
}


void vector_device::serial_reset()
{
    m_serial_offset = 0;
    m_serial_buf[m_serial_offset++] = 0;
    m_serial_buf[m_serial_offset++] = 0;
    m_serial_buf[m_serial_offset++] = 0;
    m_serial_buf[m_serial_offset++] = 0;
    m_serial_buf[m_serial_offset++] = 0;
    m_serial_buf[m_serial_offset++] = 0;
    m_serial_buf[m_serial_offset++] = 0;
    m_serial_buf[m_serial_offset++] = 0;

    m_vector_transit[0] = 0;
    m_vector_transit[1] = 0;
    m_vector_transit[2] = 0;
}


void vector_device::serial_send()
{
    if (m_serial_fd < 0)
        return;

    int last_x = -1;
    int last_y = -1;

    // find the next closest point to the last one.
    // greedy sorting algorithm reduces beam transit time
    // fairly significantly. doesn't matter for the
    // vectorscope, but makes a big difference for Vectrex
    // and other slower displays.
    while(this->m_serial_segments)
    {
        int reverse = 0;
        int min = 1e6;
        serial_segment_t ** min_seg
            = &this->m_serial_segments;

        if (m_serial_sort)
        for(serial_segment_t ** s = min_seg ; *s ; s = &(*s)->next)
        {
            int dx0 = (*s)->x0 - last_x;
            int dy0 = (*s)->y0 - last_y;
            int dx1 = (*s)->x1 - last_x;
            int dy1 = (*s)->y1 - last_y;
            int d0 = sqrt(dx0*dx0 + dy0*dy0);
            int d1 = sqrt(dx1*dx1 + dy1*dy1);

            if(d0 < min)
            {
                min_seg = s;
                min = d0;
                reverse = 0;
            }

            if (d1 < min)
            {
                min_seg = s;
                min = d1;
                reverse = 1;
            }

            // if we have hit two identical points,
            // then stop the search here.
            if (min == 0)
                break;
        }

        serial_segment_t * const s = *min_seg;
        if (!s)
            break;
    
        const int x0 = reverse ? s->x1 : s->x0;
        const int y0 = reverse ? s->y1 : s->y0;
        const int x1 = reverse ? s->x0 : s->x1;
        const int y1 = reverse ? s->y0 : s->y1;

        // if this is not a continuous segment,
        // we must add a transit command
        if (last_x != x0 || last_y != y0)
        {
            serial_draw_point(x0, y0, 0);
            int dx = x0 - last_x;
            int dy = y0 - last_y;
            m_vector_transit[0] += sqrt(dx*dx + dy*dy);
        }

        // transit to the new point
        int dx = x1 - x0;
        int dy = y1 - y0;
        int dist = sqrt(dx*dx + dy*dy);

        serial_draw_point(x1, y1, s->intensity);
        last_x = x1;
        last_y = y1;

        if (s->intensity > m_serial_bright)
            m_vector_transit[2] += dist;
        else
            m_vector_transit[1] += dist;

        // delete this segment from the list
        *min_seg = s->next;
        delete s;
    }

    // ensure that we erase our tracks
    if(this->m_serial_segments != NULL)
        fprintf(stderr, "errr?\n");
    this->m_serial_segments = NULL;
    this->m_serial_segments_tail = NULL;

    // add the "done" command to the message
    m_serial_buf[m_serial_offset++] = 1;
    m_serial_buf[m_serial_offset++] = 1;
    m_serial_buf[m_serial_offset++] = 1;
    m_serial_buf[m_serial_offset++] = 1;

    size_t offset = 0;

    if(1)
    printf("%zu vectors: off=%u on=%u bright=%u%s\n",
        m_serial_offset/4,
        m_vector_transit[0],
        m_vector_transit[1],
        m_vector_transit[2],
        m_serial_drop_frame ? " !" : ""
    );

    static unsigned skip_frame;
    unsigned eagain = 0;

    if (m_serial_drop_frame || skip_frame++ % 2 != 0)
    {
        // we skipped a frame, don't skip the next one
        m_serial_drop_frame = 0;
    } else
    while (offset < m_serial_offset)
    {
        size_t wlen = m_serial_offset - offset;
///        if (wlen > 64)
///            wlen = 64;
        if (wlen > 4096)
            wlen = 4096;

        ssize_t rc = write(m_serial_fd, m_serial_buf + offset, m_serial_offset - offset);
        if (rc <= 0)
        {
            eagain++;
            if (errno == EAGAIN)
                continue;
            perror(m_serial);
            close(m_serial_fd);
            m_serial_fd = -1;
            break;
        }

        offset += rc;
    }

    printf("%d eagain.\n", eagain);
    if (eagain > 20)
        m_serial_drop_frame = 1;

    serial_reset();
}



void vector_device::device_start()
{
    /* Grab the settings for this session */
    m_beam_width_min = machine().options().beam_width_min();
    m_beam_width_max = machine().options().beam_width_max();
    m_beam_intensity_weight = machine().options().beam_intensity_weight();
    m_flicker = machine().options().flicker();

    m_vector_index = 0;

    /* allocate memory for tables */
    m_vector_list = make_unique_clear<point[]>(MAX_POINTS);

    /* Setup the serial output of the XY coords if configured */
    m_serial = machine().options().vector_serial();
    const float scale = machine().options().vector_scale();
    if (scale != 0.0)
    {
        // user specified a scale on the command line
        m_serial_scale_x = m_serial_scale_y = scale;
    } else {
        // use the per-axis scales
        m_serial_scale_x = machine().options().vector_scale_x();
        m_serial_scale_y = machine().options().vector_scale_y();
    }

    m_serial_segments = m_serial_segments_tail = NULL;

    m_serial_offset_x = machine().options().vector_offset_x();
    m_serial_offset_y = machine().options().vector_offset_y();
    m_serial_rotate = machine().options().vector_rotate();
    m_serial_bright = machine().options().vector_bright();
    m_serial_drop_frame = 0;
    m_serial_sort = 1;

    // allocate enough buffer space, although we should never use this much
    m_serial_buf = auto_alloc_array_clear(machine(), unsigned char, (MAX_POINTS+2) * 4);
    if (!m_serial_buf)
    {
        // todo: how to signal an error?
    }

    serial_reset();

    if (!m_serial || strcmp(m_serial,"") == 0)
    {
        fprintf(stderr, "no serial vector display configured\n");
        m_serial_fd = -1;
    } else {
        m_serial_fd = serial_open(m_serial);
        fprintf(stderr, "serial dev='%s' fd=%d\n", m_serial, m_serial_fd);
    }
}

void vector_device::set_flicker(float newval)
{
    m_flicker = newval;
}

float vector_device::get_flicker()
{
    return m_flicker;
}

void vector_device::set_beam_width_min(float newval)
{
    m_beam_width_min = newval;
}

float vector_device::get_beam_width_min()
{
    return m_beam_width_min;
}

void vector_device::set_beam_width_max(float newval)
{
    m_beam_width_max = newval;
}

float vector_device::get_beam_width_max()
{
    return m_beam_width_max;
}

void vector_device::set_beam_intensity_weight(float newval)
{
    m_beam_intensity_weight = newval;
}

float vector_device::get_beam_intensity_weight()
{
    return m_beam_intensity_weight;
}


/*
 * www.dinodini.wordpress.com/2010/04/05/normalized-tunable-sigmoid-functions/
 */
float vector_device::normalized_sigmoid(float n, float k)
{
    // valid for n and k in range of -1.0 and 1.0
    return (n - n * k) / (k - fabs(n) * 2.0f * k + 1.0f);
}


/*
 * Adds a line end point to the vertices list. The vector processor emulation
 * needs to call this.
 */
void vector_device::add_point(int x, int y, rgb_t color, int intensity)
{
    point *newpoint;

//printf("%d %d: %d,%d,%d @ %d\n", x, y, color.r(), color.b(), color.g(), intensity);

    // hack for the vectrex
    // -- convert "128,128,128" @ 255 to "255,255,255" @ 127
    if (color.r() == 128
    &&  color.b() == 128
    &&  color.g() == 128
    &&  intensity == 255)
    {
        color = rgb_t(255,255,255);
        intensity = 128;
    }

    intensity = MAX(0, MIN(255, intensity));

    m_min_intensity = intensity > 0 ? MIN(m_min_intensity, intensity) : m_min_intensity;
    m_max_intensity = intensity > 0 ? MAX(m_max_intensity, intensity) : m_max_intensity;

    if (m_flicker && (intensity > 0))
    {
        float random = (float)(machine().rand() & 255) / 255.0f; // random value between 0.0 and 1.0

        intensity -= (int)(intensity * random * m_flicker);

        intensity = MAX(0, MIN(255, intensity));
    }

    newpoint = &m_vector_list[m_vector_index];
    newpoint->x = x;
    newpoint->y = y;
    newpoint->col = color;
    newpoint->intensity = intensity;
    newpoint->status = VDIRTY; /* mark identical lines as clean later */

    m_vector_index++;
    if (m_vector_index >= MAX_POINTS)
    {
        m_vector_index--;
        logerror("*** Warning! Vector list overflow!\n");
    }
}


/*
 * Add new clipping info to the list
 */
void vector_device::add_clip(int x1, int yy1, int x2, int y2)
{
    point *newpoint;

    newpoint = &m_vector_list[m_vector_index];
    newpoint->x = x1;
    newpoint->y = yy1;
    newpoint->arg1 = x2;
    newpoint->arg2 = y2;
    newpoint->status = VCLIP;

    m_vector_index++;
    if (m_vector_index >= MAX_POINTS)
    {
        m_vector_index--;
        logerror("*** Warning! Vector list overflow!\n");
    }
}


/*
 * The vector CPU creates a new display list. We save the old display list,
 * but only once per refresh.
 */
void vector_device::clear_list(void)
{
    m_vector_index = 0;
}


UINT32 vector_device::screen_update(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect)
{
    UINT32 flags = PRIMFLAG_ANTIALIAS(screen.machine().options().antialias() ? 1 : 0) | PRIMFLAG_BLENDMODE(BLENDMODE_ADD) | PRIMFLAG_VECTOR(1);
    const rectangle &visarea = screen.visible_area();
    float xscale = 1.0f / (65536 * visarea.width());
    float yscale = 1.0f / (65536 * visarea.height());
    float xoffs = (float)visarea.min_x;
    float yoffs = (float)visarea.min_y;
    float xratio = xscale / yscale;
    float yratio = yscale / xscale;
    xratio = (xratio < 1.0f) ? xratio : 1.0f;
    yratio = (yratio < 1.0f) ? yratio : 1.0f;

    point *curpoint;
    render_bounds clip;
    int lastx = 0;
    int lasty = 0;

    curpoint = m_vector_list.get();

    screen.container().empty();
    screen.container().add_rect(0.0f, 0.0f, 1.0f, 1.0f, rgb_t(0xff,0x00,0x00,0x00), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_VECTORBUF(1));

    clip.x0 = clip.y0 = 0.0f;
    clip.x1 = clip.y1 = 1.0f;

    for (int i = 0; i < m_vector_index; i++)
    {
        render_bounds coords;

        if (curpoint->status == VCLIP)
        {
            coords.x0 = ((float)curpoint->x - xoffs) * xscale;
            coords.y0 = ((float)curpoint->y - yoffs) * yscale;
            coords.x1 = ((float)curpoint->arg1 - xoffs) * xscale;
            coords.y1 = ((float)curpoint->arg2 - yoffs) * yscale;

            clip.x0 = (coords.x0 > 0.0f) ? coords.x0 : 0.0f;
            clip.y0 = (coords.y0 > 0.0f) ? coords.y0 : 0.0f;
            clip.x1 = (coords.x1 < 1.0f) ? coords.x1 : 1.0f;
            clip.y1 = (coords.y1 < 1.0f) ? coords.y1 : 1.0f;
        }
        else
        {
            float beam_intensity_width = m_beam_width_min;

            float intensity = (float)curpoint->intensity / 255.0f;

            // check for dynamic intensity
            if (m_min_intensity != m_max_intensity)
            {
                float intensity_weight = normalized_sigmoid(intensity, m_beam_intensity_weight);
                beam_intensity_width = (m_beam_width_max - m_beam_width_min) * intensity_weight + m_beam_width_min;
            }

            float beam_width = beam_intensity_width * (1.0f / (float)VECTOR_WIDTH_DENOM);

            coords.x0 = ((float)lastx - xoffs) * xscale;
            coords.y0 = ((float)lasty - yoffs) * yscale;
            coords.x1 = ((float)curpoint->x - xoffs) * xscale;
            coords.y1 = ((float)curpoint->y - yoffs) * yscale;

            // extend zero-length vector line (vector point) by quarter beam_width on both sides
            if (fabs(coords.x0 - coords.x1) < FLT_EPSILON &&
                fabs(coords.y0 - coords.y1) < FLT_EPSILON)
            {
                coords.x0 += xratio * beam_width * 0.25f;
                coords.y0 += yratio * beam_width * 0.25f;
                coords.x1 -= xratio * beam_width * 0.25f;
                coords.y1 -= yratio * beam_width * 0.25f;
            }

            if (curpoint->intensity != 0 && !render_clip_line(&coords, &clip))
            {
                screen.container().add_line(
                    coords.x0, coords.y0, coords.x1, coords.y1,
                    beam_width,
                    (curpoint->intensity << 24) | (curpoint->col & 0xffffff),
                    flags);

                serial_draw_line(
                    coords.x0, coords.y0,
                    coords.x1, coords.y1,
                    curpoint->intensity);
            }

            lastx = curpoint->x;
            lasty = curpoint->y;
        }

        curpoint++;
    }

    serial_send();

    return 0;
}