2727 lines
69 KiB
C
2727 lines
69 KiB
C
/*
|
|
* 86Box A hypervisor and IBM PC system emulator that specializes in
|
|
* running old operating systems and software designed for IBM
|
|
* PC systems and compatibles from 1981 through fairly recent
|
|
* system designs based on the PCI bus.
|
|
*
|
|
* This file is part of the 86Box distribution.
|
|
*
|
|
* This implements just enough of the Professional Graphics
|
|
* Controller to act as a basis for the Vermont Microsystems
|
|
* IM-1024.
|
|
*
|
|
* PGC features implemented include:
|
|
* > The CGA-compatible display modes
|
|
* > Switching to and from native mode
|
|
* > Communicating with the host PC
|
|
*
|
|
* Numerous features are implemented partially or not at all,
|
|
* such as:
|
|
* > 2D drawing
|
|
* > 3D drawing
|
|
* > Command lists
|
|
* Some of these are marked TODO.
|
|
*
|
|
* The PGC has two display modes: CGA (in which it appears in
|
|
* the normal CGA memory and I/O ranges) and native (in which
|
|
* all functions are accessed through reads and writes to 1K
|
|
* of memory at 0xC6000).
|
|
*
|
|
* The PGC's 8088 processor monitors this buffer and executes
|
|
* instructions left there for it. We simulate this behavior
|
|
* with a separate thread.
|
|
*
|
|
* **NOTE** This driver is not finished yet:
|
|
*
|
|
* - cursor will blink at very high speed if used on a machine
|
|
* with clock greater than 4.77MHz. We should "scale down"
|
|
* this speed, to become relative to a 4.77MHz-based system.
|
|
*
|
|
* - pgc_plot() should be overloaded by clones if they support
|
|
* modes other than WRITE and INVERT, like the IM-1024.
|
|
*
|
|
* - test it with the Windows 1.x driver?
|
|
*
|
|
* This is expected to be done shortly.
|
|
*
|
|
*
|
|
*
|
|
* Authors: Fred N. van Kempen, <decwiz@yahoo.com>
|
|
* John Elliott, <jce@seasip.info>
|
|
*
|
|
* Copyright 2019 Fred N. van Kempen.
|
|
* Copyright 2019 John Elliott.
|
|
*
|
|
* 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.
|
|
* 59 Temple Place - Suite 330
|
|
* Boston, MA 02111-1307
|
|
* USA.
|
|
*/
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <wchar.h>
|
|
#include <ctype.h>
|
|
#include <math.h>
|
|
#define HAVE_STDARG_H
|
|
#include <86box/86box.h>
|
|
#include <86box/io.h>
|
|
#include <86box/mem.h>
|
|
#include <86box/rom.h>
|
|
#include <86box/timer.h>
|
|
#include <86box/device.h>
|
|
#include <86box/pit.h>
|
|
#include <86box/plat.h>
|
|
#include <86box/thread.h>
|
|
#include <86box/video.h>
|
|
#include <86box/vid_cga.h>
|
|
#include <86box/vid_pgc.h>
|
|
|
|
#define PGC_CGA_WIDTH 640
|
|
#define PGC_CGA_HEIGHT 400
|
|
|
|
#define HWORD(u) ((u) >> 16)
|
|
#define LWORD(u) ((u) &0xffff)
|
|
|
|
#define WAKE_DELAY (TIMER_USEC * 500)
|
|
|
|
static const char *pgc_err_msgs[] = {
|
|
"Range \r",
|
|
"Integer \r",
|
|
"Memory \r",
|
|
"Overflow\r",
|
|
"Digit \r",
|
|
"Opcode \r",
|
|
"Running \r",
|
|
"Stack \r",
|
|
"Too long\r",
|
|
"Area \r",
|
|
"Missing \r",
|
|
"Unknown \r"
|
|
};
|
|
|
|
/* Initial palettes */
|
|
static const uint32_t init_palette[6][256] = {
|
|
#include <86box/vid_pgc_palette.h>
|
|
};
|
|
|
|
static video_timings_t timing_pgc = { .type = VIDEO_ISA, .write_b = 8, .write_w = 16, .write_l = 32, .read_b = 8, .read_w = 16, .read_l = 32 };
|
|
|
|
#ifdef ENABLE_PGC_LOG
|
|
int pgc_do_log = ENABLE_PGC_LOG;
|
|
|
|
static void
|
|
pgc_log(const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
|
|
if (pgc_do_log) {
|
|
va_start(ap, fmt);
|
|
pclog_ex(fmt, ap);
|
|
va_end(ap);
|
|
}
|
|
}
|
|
#else
|
|
# define pgc_log(fmt, ...)
|
|
#endif
|
|
|
|
static inline int
|
|
is_whitespace(char ch)
|
|
{
|
|
return (ch != 0 && strchr(" \r\n\t,;()+-", ch) != NULL);
|
|
}
|
|
|
|
/*
|
|
* Write a byte to the output buffer.
|
|
*
|
|
* If buffer is full will sleep until it is not. Returns 0 if
|
|
* a PGC reset has been triggered by a write to 0xC63FF.
|
|
*/
|
|
static int
|
|
output_byte(pgc_t *dev, uint8_t val)
|
|
{
|
|
/* If output buffer full, wait for it to empty. */
|
|
while (!dev->stopped && dev->mapram[0x302] == (uint8_t) (dev->mapram[0x303] - 1)) {
|
|
pgc_log("PGC: output buffer state: %02x %02x Sleeping\n",
|
|
dev->mapram[0x302], dev->mapram[0x303]);
|
|
dev->waiting_output_fifo = 1;
|
|
pgc_sleep(dev);
|
|
}
|
|
|
|
if (dev->mapram[0x3ff]) {
|
|
/* Reset triggered. */
|
|
pgc_reset(dev);
|
|
return 0;
|
|
}
|
|
|
|
dev->mapram[0x100 + dev->mapram[0x302]] = val;
|
|
dev->mapram[0x302]++;
|
|
|
|
pgc_log("PGC: output %02x: new state: %02x %02x\n", val,
|
|
dev->mapram[0x302], dev->mapram[0x303]);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Helper to write an entire string to the output buffer. */
|
|
static int
|
|
output_string(pgc_t *dev, const char *s)
|
|
{
|
|
while (*s) {
|
|
if (!output_byte(dev, *s))
|
|
return 0;
|
|
s++;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* As output_byte, for the error buffer. */
|
|
static int
|
|
error_byte(pgc_t *dev, uint8_t val)
|
|
{
|
|
/* If error buffer full, wait for it to empty. */
|
|
while (!dev->stopped && dev->mapram[0x304] == dev->mapram[0x305] - 1) {
|
|
dev->waiting_error_fifo = 1;
|
|
pgc_sleep(dev);
|
|
}
|
|
|
|
if (dev->mapram[0x3ff]) {
|
|
/* Reset triggered. */
|
|
pgc_reset(dev);
|
|
return 0;
|
|
}
|
|
|
|
dev->mapram[0x200 + dev->mapram[0x304]] = val;
|
|
dev->mapram[0x304]++;
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* As output_string, for the error buffer. */
|
|
static int
|
|
error_string(pgc_t *dev, const char *s)
|
|
{
|
|
while (*s) {
|
|
if (!error_byte(dev, *s))
|
|
return 0;
|
|
s++;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Read next byte from the input buffer.
|
|
*
|
|
* If no byte available will sleep until one is. Returns 0 if
|
|
* a PGC reset has been triggered by a write to 0xC63FF.
|
|
*/
|
|
static int
|
|
input_byte(pgc_t *dev, uint8_t *result)
|
|
{
|
|
/* If input buffer empty, wait for it to fill. */
|
|
while (!dev->stopped && (dev->mapram[0x300] == dev->mapram[0x301])) {
|
|
dev->waiting_input_fifo = 1;
|
|
pgc_sleep(dev);
|
|
}
|
|
|
|
if (dev->stopped)
|
|
return 0;
|
|
|
|
if (dev->mapram[0x3ff]) {
|
|
/* Reset triggered. */
|
|
pgc_reset(dev);
|
|
return 0;
|
|
}
|
|
|
|
*result = dev->mapram[dev->mapram[0x301]];
|
|
dev->mapram[0x301]++;
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Read a byte and interpret as ASCII.
|
|
*
|
|
* Ignore control characters other than CR, LF or tab.
|
|
*/
|
|
static int
|
|
input_char(pgc_t *dev, char *result)
|
|
{
|
|
uint8_t ch;
|
|
|
|
while (1) {
|
|
if (!dev->inputbyte(dev, &ch))
|
|
return 0;
|
|
|
|
ch &= 0x7f;
|
|
if (ch == '\r' || ch == '\n' || ch == '\t' || ch >= ' ') {
|
|
*result = toupper(ch);
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Read in the next command.
|
|
*
|
|
* This can be either as hex (1 byte) or ASCII (up to 6 characters).
|
|
*/
|
|
static int
|
|
read_command(pgc_t *dev)
|
|
{
|
|
if (dev->stopped)
|
|
return 0;
|
|
|
|
if (dev->clcur)
|
|
return pgc_clist_byte(dev, &dev->hex_command);
|
|
|
|
if (dev->ascii_mode) {
|
|
char ch;
|
|
int count = 0;
|
|
|
|
while (count < 7) {
|
|
if (dev->stopped)
|
|
return 0;
|
|
|
|
if (!input_char(dev, &ch))
|
|
return 0;
|
|
|
|
if (is_whitespace(ch)) {
|
|
/* Pad to 6 characters */
|
|
while (count < 6)
|
|
dev->asc_command[count++] = ' ';
|
|
dev->asc_command[6] = 0;
|
|
|
|
return 1;
|
|
}
|
|
dev->asc_command[count++] = toupper(ch);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
return dev->inputbyte(dev, &dev->hex_command);
|
|
}
|
|
|
|
/* Read in the next command and parse it. */
|
|
static int
|
|
parse_command(pgc_t *dev, const pgc_cmd_t **pcmd)
|
|
{
|
|
const pgc_cmd_t *cmd;
|
|
char match[7];
|
|
|
|
*pcmd = NULL;
|
|
dev->hex_command = 0;
|
|
memset(dev->asc_command, ' ', 6);
|
|
dev->asc_command[6] = 0;
|
|
|
|
if (!read_command(dev)) {
|
|
/* PGC has been reset. */
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Scan the list of valid commands.
|
|
*
|
|
* dev->commands may be a subclass list (terminated with '*')
|
|
* or the core list (terminated with '@')
|
|
*/
|
|
for (cmd = dev->commands; cmd->ascii[0] != '@'; cmd++) {
|
|
/* End of subclass command list, chain to core. */
|
|
if (cmd->ascii[0] == '*')
|
|
cmd = dev->master;
|
|
|
|
/* If in ASCII mode match on the ASCII command. */
|
|
if (dev->ascii_mode && !dev->clcur) {
|
|
sprintf(match, "%-6.6s", cmd->ascii);
|
|
if (!strncmp(match, dev->asc_command, 6)) {
|
|
*pcmd = cmd;
|
|
dev->hex_command = cmd->hex;
|
|
break;
|
|
}
|
|
} else {
|
|
/* Otherwise match on the hex command. */
|
|
if (cmd->hex == dev->hex_command) {
|
|
sprintf(dev->asc_command, "%-6.6s", cmd->ascii);
|
|
*pcmd = cmd;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Beginning of a command list.
|
|
*
|
|
* Parse commands up to the next CLEND, storing
|
|
* them (in hex form) in the named command list.
|
|
*/
|
|
static void
|
|
hndl_clbeg(pgc_t *dev)
|
|
{
|
|
const pgc_cmd_t *cmd;
|
|
uint8_t param = 0;
|
|
pgc_cl_t cl;
|
|
|
|
if (!pgc_param_byte(dev, ¶m))
|
|
return;
|
|
pgc_log("PGC: CLBEG(%i)\n", param);
|
|
|
|
memset(&cl, 0x00, sizeof(pgc_cl_t));
|
|
|
|
while (1) {
|
|
if (!parse_command(dev, &cmd)) {
|
|
/* PGC has been reset. */
|
|
return;
|
|
}
|
|
if (!cmd) {
|
|
pgc_error(dev, PGC_ERROR_OPCODE);
|
|
return;
|
|
} else if (dev->hex_command == 0x71) {
|
|
/* CLEND */
|
|
dev->clist[param] = cl;
|
|
return;
|
|
} else {
|
|
if (!pgc_cl_append(&cl, dev->hex_command)) {
|
|
pgc_error(dev, PGC_ERROR_OVERFLOW);
|
|
return;
|
|
}
|
|
|
|
if (cmd->parser) {
|
|
if (!(*cmd->parser)(dev, &cl, cmd->p))
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
hndl_clend(pgc_t *dev)
|
|
{
|
|
/* Should not happen outside a CLBEG. */
|
|
}
|
|
|
|
/*
|
|
* Execute a command list.
|
|
*
|
|
* If one was already executing, remember
|
|
* it so we can return to it afterwards.
|
|
*/
|
|
static void
|
|
hndl_clrun(pgc_t *dev)
|
|
{
|
|
pgc_cl_t *clprev = dev->clcur;
|
|
uint8_t param = 0;
|
|
|
|
if (!pgc_param_byte(dev, ¶m))
|
|
return;
|
|
|
|
dev->clcur = &dev->clist[param];
|
|
dev->clcur->rdptr = 0;
|
|
dev->clcur->repeat = 1;
|
|
dev->clcur->chain = clprev;
|
|
}
|
|
|
|
/* Execute a command list multiple times. */
|
|
static void
|
|
hndl_cloop(pgc_t *dev)
|
|
{
|
|
pgc_cl_t *clprev = dev->clcur;
|
|
uint8_t param = 0;
|
|
int16_t repeat = 0;
|
|
|
|
if (!pgc_param_byte(dev, ¶m))
|
|
return;
|
|
if (!pgc_param_word(dev, &repeat))
|
|
return;
|
|
|
|
dev->clcur = &dev->clist[param];
|
|
dev->clcur->rdptr = 0;
|
|
dev->clcur->repeat = repeat;
|
|
dev->clcur->chain = clprev;
|
|
}
|
|
|
|
/* Read back a command list. */
|
|
static void
|
|
hndl_clread(pgc_t *dev)
|
|
{
|
|
uint8_t param = 0;
|
|
uint32_t n;
|
|
|
|
if (!pgc_param_byte(dev, ¶m))
|
|
return;
|
|
|
|
for (n = 0; n < dev->clist[param].wrptr; n++) {
|
|
if (!pgc_result_byte(dev, dev->clist[param].list[n]))
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Delete a command list. */
|
|
static void
|
|
hndl_cldel(pgc_t *dev)
|
|
{
|
|
uint8_t param = 0;
|
|
|
|
if (!pgc_param_byte(dev, ¶m))
|
|
return;
|
|
|
|
memset(&dev->clist[param], 0, sizeof(pgc_cl_t));
|
|
}
|
|
|
|
/* Clear the screen to a specified color. */
|
|
static void
|
|
hndl_clears(pgc_t *dev)
|
|
{
|
|
uint8_t param = 0;
|
|
uint32_t y;
|
|
|
|
if (!pgc_param_byte(dev, ¶m))
|
|
return;
|
|
|
|
for (y = 0; y < dev->screenh; y++)
|
|
memset(dev->vram + y * dev->maxw, param, dev->screenw);
|
|
}
|
|
|
|
/* Select drawing color. */
|
|
static void
|
|
hndl_color(pgc_t *dev)
|
|
{
|
|
uint8_t param = 0;
|
|
|
|
if (!pgc_param_byte(dev, ¶m))
|
|
return;
|
|
|
|
pgc_log("PGC: COLOR(%i)\n", param);
|
|
dev->color = param;
|
|
}
|
|
|
|
/*
|
|
* Set drawing mode.
|
|
*
|
|
* 0 => Draw
|
|
* 1 => Invert
|
|
*/
|
|
static void
|
|
hndl_linfun(pgc_t *dev)
|
|
{
|
|
uint8_t param = 0;
|
|
|
|
if (!pgc_param_byte(dev, ¶m))
|
|
return;
|
|
|
|
pgc_log("PGC: LINFUN(%i)\n", param);
|
|
if (param < 2)
|
|
dev->draw_mode = param;
|
|
else
|
|
pgc_error(dev, PGC_ERROR_RANGE);
|
|
}
|
|
|
|
/* Set the line drawing pattern. */
|
|
static void
|
|
hndl_linpat(pgc_t *dev)
|
|
{
|
|
uint16_t param = 0;
|
|
|
|
if (!pgc_param_word(dev, (int16_t *) ¶m))
|
|
return;
|
|
|
|
pgc_log("PGC: LINPAT(0x%04x)\n", param);
|
|
dev->line_pattern = param;
|
|
}
|
|
|
|
/* Set the polygon fill mode (0=hollow, 1=filled, 2=fast fill). */
|
|
static void
|
|
hndl_prmfil(pgc_t *dev)
|
|
{
|
|
uint8_t param = 0;
|
|
|
|
if (!pgc_param_byte(dev, ¶m))
|
|
return;
|
|
|
|
pgc_log("PGC: PRMFIL(%i)\n", param);
|
|
if (param < 3)
|
|
dev->fill_mode = param;
|
|
else
|
|
pgc_error(dev, PGC_ERROR_RANGE);
|
|
}
|
|
|
|
/* Set the 2D drawing position. */
|
|
static void
|
|
hndl_move(pgc_t *dev)
|
|
{
|
|
int32_t x = 0, y = 0;
|
|
|
|
if (!pgc_param_coord(dev, &x))
|
|
return;
|
|
if (!pgc_param_coord(dev, &y))
|
|
return;
|
|
|
|
pgc_log("PCG: MOVE %x.%04x,%x.%04x\n",
|
|
HWORD(x), LWORD(x), HWORD(y), LWORD(y));
|
|
dev->x = x;
|
|
dev->y = y;
|
|
}
|
|
|
|
/* Set the 3D drawing position. */
|
|
static void
|
|
hndl_move3(pgc_t *dev)
|
|
{
|
|
int32_t x = 0, y = 0, z = 0;
|
|
|
|
if (!pgc_param_coord(dev, &x))
|
|
return;
|
|
if (!pgc_param_coord(dev, &y))
|
|
return;
|
|
if (!pgc_param_coord(dev, &z))
|
|
return;
|
|
|
|
dev->x = x;
|
|
dev->y = y;
|
|
dev->z = z;
|
|
}
|
|
|
|
/* Relative move (2D). */
|
|
static void
|
|
hndl_mover(pgc_t *dev)
|
|
{
|
|
int32_t x = 0, y = 0;
|
|
|
|
if (!pgc_param_coord(dev, &x))
|
|
return;
|
|
if (!pgc_param_coord(dev, &y))
|
|
return;
|
|
|
|
dev->x += x;
|
|
dev->y += y;
|
|
}
|
|
|
|
/* Relative move (3D). */
|
|
static void
|
|
hndl_mover3(pgc_t *dev)
|
|
{
|
|
int32_t x = 0, y = 0, z = 0;
|
|
|
|
if (!pgc_param_coord(dev, &x))
|
|
return;
|
|
if (!pgc_param_coord(dev, &y))
|
|
return;
|
|
if (!pgc_param_coord(dev, &z))
|
|
return;
|
|
|
|
dev->x += x;
|
|
dev->y += y;
|
|
dev->z += z;
|
|
}
|
|
|
|
/* Given raster coordinates, find the matching address in PGC video RAM. */
|
|
uint8_t *
|
|
pgc_vram_addr(pgc_t *dev, int16_t x, int16_t y)
|
|
{
|
|
int offset;
|
|
|
|
/* We work from the bottom left-hand corner. */
|
|
if (y < 0 || (uint32_t) y >= dev->maxh || x < 0 || (uint32_t) x >= dev->maxw)
|
|
return NULL;
|
|
|
|
offset = (dev->maxh - 1 - y) * (dev->maxw) + x;
|
|
pgc_log("PGC: vram_addr(x=%i,y=%i) = %i\n", x, y, offset);
|
|
|
|
if (offset < 0 || (uint32_t) offset >= (dev->maxw * dev->maxh))
|
|
return NULL;
|
|
|
|
return &dev->vram[offset];
|
|
}
|
|
|
|
/*
|
|
* Write a screen pixel.
|
|
* X and Y are raster coordinates, ink is the value to write.
|
|
*/
|
|
void
|
|
pgc_write_pixel(pgc_t *dev, uint16_t x, uint16_t y, uint8_t ink)
|
|
{
|
|
uint8_t *vram;
|
|
|
|
/* Suppress out-of-range writes; clip to viewport. */
|
|
if (x < dev->vp_x1 || x > dev->vp_x2 || x >= dev->maxw || y < dev->vp_y1 || y > dev->vp_y2 || y >= dev->maxh) {
|
|
pgc_log("PGC: write_pixel clipped: (%i,%i) "
|
|
"vp_x1=%i vp_y1=%i vp_x2=%i vp_y2=%i "
|
|
"ink=0x%02x\n",
|
|
x, y, dev->vp_x1, dev->vp_y1, dev->vp_x2, dev->vp_y2, ink);
|
|
return;
|
|
}
|
|
|
|
vram = pgc_vram_addr(dev, x, y);
|
|
if (vram)
|
|
*vram = ink;
|
|
}
|
|
|
|
/* Read a screen pixel (x and y are raster coordinates). */
|
|
uint8_t
|
|
pgc_read_pixel(pgc_t *dev, uint16_t x, uint16_t y)
|
|
{
|
|
uint8_t *vram;
|
|
|
|
/* Suppress out-of-range reads. */
|
|
if (x >= dev->maxw || y >= dev->maxh)
|
|
return 0;
|
|
|
|
vram = pgc_vram_addr(dev, x, y);
|
|
if (vram)
|
|
return *vram;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Plot a point in the current color and draw mode. Raster coordinates.
|
|
*
|
|
* FIXME: this should be overloaded by clones if they support
|
|
* modes other than WRITE and INVERT, like the IM-1024.
|
|
*/
|
|
void
|
|
pgc_plot(pgc_t *dev, uint16_t x, uint16_t y)
|
|
{
|
|
uint8_t *vram;
|
|
|
|
/* Only allow plotting within the current viewport. */
|
|
if (x < dev->vp_x1 || x > dev->vp_x2 || x >= dev->maxw || y < dev->vp_y1 || y > dev->vp_y2 || y >= dev->maxh) {
|
|
pgc_log("PGC: plot clipped: (%i,%i) %i <= x <= %i; %i <= y <= %i; "
|
|
"mode=%i ink=0x%02x\n",
|
|
x, y,
|
|
dev->vp_x1, dev->vp_x2, dev->vp_y1, dev->vp_y2,
|
|
dev->draw_mode, dev->color);
|
|
return;
|
|
}
|
|
|
|
vram = pgc_vram_addr(dev, x, y);
|
|
if (!vram)
|
|
return;
|
|
|
|
/* TODO: Does not implement the PGC plane mask (set by MASK). */
|
|
switch (dev->draw_mode) {
|
|
default:
|
|
case 0: /* WRITE */
|
|
*vram = dev->color;
|
|
break;
|
|
|
|
case 1: /* INVERT */
|
|
*vram ^= 0xff;
|
|
break;
|
|
|
|
case 2: /* XOR color */
|
|
// FIXME: see notes
|
|
*vram ^= dev->color;
|
|
break;
|
|
|
|
case 3: /* AND color */
|
|
// FIXME: see notes
|
|
*vram &= dev->color;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Draw a line (using raster coordinates).
|
|
*
|
|
* Bresenham's Algorithm from:
|
|
* <https://rosettacode.org/wiki/Bitmap/Bresenham%27s_line_algorithm#C>
|
|
*
|
|
* The line pattern mask to use is passed in. Return value is the
|
|
* line pattern mask, rotated by the number of points drawn.
|
|
*/
|
|
uint16_t
|
|
pgc_draw_line_r(pgc_t *dev, int32_t x0, int32_t y0, int32_t x1, int32_t y1, uint16_t linemask)
|
|
{
|
|
int32_t dx, dy, sx, sy, err, e2;
|
|
|
|
dx = abs(x1 - x0);
|
|
dy = abs(y1 - y0);
|
|
sx = (x0 < x1) ? 1 : -1;
|
|
sy = (y0 < y1) ? 1 : -1;
|
|
err = (dx > dy ? dx : -dy) / 2;
|
|
|
|
for (;;) {
|
|
if (linemask & 0x8000) {
|
|
pgc_plot(dev, x0, y0);
|
|
linemask = (linemask << 1) | 1;
|
|
} else
|
|
linemask = (linemask << 1);
|
|
|
|
if (x0 == x1 && y0 == y1)
|
|
break;
|
|
|
|
e2 = err;
|
|
if (e2 > -dx) {
|
|
err -= dy;
|
|
x0 += sx;
|
|
}
|
|
if (e2 < dy) {
|
|
err += dx;
|
|
y0 += sy;
|
|
}
|
|
}
|
|
|
|
return linemask;
|
|
}
|
|
|
|
/* Draw a line (using PGC fixed-point coordinates). */
|
|
uint16_t
|
|
pgc_draw_line(pgc_t *dev, int32_t x0, int32_t y0, int32_t x1, int32_t y1, uint16_t linemask)
|
|
{
|
|
pgc_log("pgc_draw_line: (%i,%i) to (%i,%i)\n",
|
|
x0 >> 16, y0 >> 16, x1 >> 16, y1 >> 16);
|
|
|
|
/* Convert from PGC fixed-point to device coordinates */
|
|
x0 >>= 16;
|
|
y0 >>= 16;
|
|
pgc_ito_raster(dev, &x0, &y0);
|
|
|
|
x1 >>= 16;
|
|
y1 >>= 16;
|
|
pgc_ito_raster(dev, &x1, &y1);
|
|
|
|
return pgc_draw_line_r(dev, x0, y0, x1, y1, linemask);
|
|
}
|
|
|
|
/*
|
|
* Draw a horizontal line in the current fill pattern
|
|
* (using raster coordinates).
|
|
*/
|
|
void
|
|
pgc_fill_line_r(pgc_t *dev, int32_t x0, int32_t x1, int32_t y0)
|
|
{
|
|
int32_t mask = 0x8000 >> (x0 & 0x0f);
|
|
int32_t x;
|
|
|
|
if (x0 > x1) {
|
|
x = x1;
|
|
x1 = x0;
|
|
x0 = x;
|
|
}
|
|
|
|
for (x = x0; x <= x1; x++) {
|
|
if (dev->fill_pattern[y0 & 0x0F] & mask)
|
|
pgc_plot(dev, x, y0);
|
|
mask = mask >> 1;
|
|
if (mask == 0)
|
|
mask = 0x8000;
|
|
}
|
|
}
|
|
|
|
/* For sorting polygon nodes. */
|
|
static int
|
|
compare_double(const void *a, const void *b)
|
|
{
|
|
const double *da = (const double *) a;
|
|
const double *db = (const double *) b;
|
|
|
|
if (*da < *db)
|
|
return 1;
|
|
if (*da > *db)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Draw a filled polygon (using PGC fixed-point coordinates). */
|
|
void
|
|
pgc_fill_polygon(pgc_t *dev, unsigned corners, int32_t *x, int32_t *y)
|
|
{
|
|
double *nodex;
|
|
double *dx;
|
|
double *dy;
|
|
unsigned n, nodes, i, j;
|
|
double ymin, ymax, ypos;
|
|
|
|
pgc_log("PGC: fill_polygon(%i corners)\n", corners);
|
|
|
|
if (!x || !y || (corners < 2))
|
|
return; /* Degenerate polygon */
|
|
|
|
nodex = (double *) malloc(corners * sizeof(double));
|
|
dx = (double *) malloc(corners * sizeof(double));
|
|
dy = (double *) malloc(corners * sizeof(double));
|
|
if (!nodex || !dx || !dy) {
|
|
if (nodex) {
|
|
free(nodex);
|
|
nodex = NULL;
|
|
}
|
|
if (dx) {
|
|
free(dx);
|
|
dx = NULL;
|
|
}
|
|
if (dy) {
|
|
free(dy);
|
|
dy = NULL;
|
|
}
|
|
return;
|
|
}
|
|
|
|
ymin = ymax = y[0] / 65536.0;
|
|
for (n = 0; n < corners; n++) {
|
|
/* Convert from PGC fixed-point to native floating-point. */
|
|
dx[n] = x[n] / 65536.0;
|
|
dy[n] = y[n] / 65536.0;
|
|
|
|
if (dy[n] < ymin)
|
|
ymin = dy[n];
|
|
if (dy[n] > ymax)
|
|
ymax = dy[n];
|
|
}
|
|
|
|
/* Polygon fill. Based on <http://alienryderflex.com/polygon_fill/> */
|
|
/* For each row, work out where the polygon lines intersect with
|
|
* that row. */
|
|
for (ypos = ymin; ypos <= ymax; ypos++) {
|
|
nodes = 0;
|
|
j = corners - 1;
|
|
for (i = 0; i < corners; i++) {
|
|
if ((dy[i] < ypos && dy[j] >= ypos) || (dy[j] < ypos && dy[i] >= ypos)) /* Line crosses */ {
|
|
nodex[nodes++] = dx[i] + (ypos - dy[i]) / (dy[j] - dy[i]) * (dx[j] - dx[i]);
|
|
}
|
|
j = i;
|
|
}
|
|
|
|
/* Sort the intersections. */
|
|
if (nodes)
|
|
qsort(nodex, nodes, sizeof(double), compare_double);
|
|
|
|
/* And fill between them. */
|
|
for (i = 0; i < nodes; i += 2) {
|
|
int16_t x1 = (int16_t) nodex[i], x2 = (int16_t) nodex[i + 1],
|
|
y1 = (int16_t) ypos, y2 = (int16_t) ypos;
|
|
pgc_sto_raster(dev, &x1, &y1);
|
|
pgc_sto_raster(dev, &x2, &y2);
|
|
pgc_fill_line_r(dev, x1, x2, y1);
|
|
}
|
|
}
|
|
|
|
free(nodex);
|
|
free(dx);
|
|
free(dy);
|
|
}
|
|
|
|
/* Draw a filled ellipse (using PGC fixed-point coordinates). */
|
|
void
|
|
pgc_draw_ellipse(pgc_t *dev, int32_t x, int32_t y)
|
|
{
|
|
/* Convert from PGC fixed-point to native floating-point. */
|
|
double h = y / 65536.0;
|
|
double w = x / 65536.0;
|
|
double y0 = dev->y / 65536.0;
|
|
double x0 = dev->x / 65536.0;
|
|
double ypos, xpos;
|
|
double x1;
|
|
double xlast = 0.0;
|
|
int16_t linemask = dev->line_pattern;
|
|
|
|
pgc_log("PGC: ellipse(color=%i drawmode=%i fill=%i)\n",
|
|
dev->color, dev->draw_mode, dev->fill_mode);
|
|
|
|
pgc_dto_raster(dev, &x0, &y0);
|
|
|
|
for (ypos = 0; ypos <= h; ypos++) {
|
|
if (ypos == 0) {
|
|
if (dev->fill_mode)
|
|
pgc_fill_line_r(dev, (uint16_t) (x0 - w),
|
|
(uint16_t) (x0 + w), (uint16_t) y0);
|
|
if (linemask & 0x8000) {
|
|
pgc_plot(dev, (uint16_t) (x0 + w), (uint16_t) y0);
|
|
pgc_plot(dev, (uint16_t) (x0 - w), (uint16_t) y0);
|
|
linemask = (linemask << 1) | 1;
|
|
} else
|
|
linemask = linemask << 1;
|
|
|
|
xlast = w;
|
|
} else {
|
|
x1 = sqrt((h * h) - (ypos * ypos)) * w / h;
|
|
|
|
if (dev->fill_mode) {
|
|
pgc_fill_line_r(dev, (uint16_t) (x0 - x1),
|
|
(uint16_t) (x0 + x1),
|
|
(uint16_t) (y0 + ypos));
|
|
pgc_fill_line_r(dev, (uint16_t) (x0 - x1),
|
|
(uint16_t) (x0 + x1),
|
|
(uint16_t) (y0 - ypos));
|
|
}
|
|
|
|
/* Draw border. */
|
|
for (xpos = xlast; xpos >= x1; xpos--) {
|
|
if (linemask & 0x8000) {
|
|
pgc_plot(dev, (uint16_t) (x0 + xpos),
|
|
(uint16_t) (y0 + ypos));
|
|
pgc_plot(dev, (uint16_t) (x0 - xpos),
|
|
(uint16_t) (y0 + ypos));
|
|
pgc_plot(dev, (uint16_t) (x0 + xpos),
|
|
(uint16_t) (y0 - ypos));
|
|
pgc_plot(dev, (uint16_t) (x0 - xpos),
|
|
(uint16_t) (y0 - ypos));
|
|
linemask = (linemask << 1) | 1;
|
|
} else
|
|
linemask = linemask << 1;
|
|
}
|
|
|
|
xlast = x1;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Handle the ELIPSE (sic) command. */
|
|
static void
|
|
hndl_ellipse(pgc_t *dev)
|
|
{
|
|
int32_t x = 0, y = 0;
|
|
|
|
if (!pgc_param_coord(dev, &x))
|
|
return;
|
|
if (!pgc_param_coord(dev, &y))
|
|
return;
|
|
|
|
pgc_draw_ellipse(dev, x, y);
|
|
}
|
|
|
|
/* Handle the POLY command. */
|
|
static void
|
|
hndl_poly(pgc_t *dev)
|
|
{
|
|
uint8_t count;
|
|
int32_t x[256];
|
|
int32_t y[256];
|
|
int32_t n;
|
|
|
|
if (!pgc_param_byte(dev, &count))
|
|
return;
|
|
|
|
pgc_log("PGC: POLY (%i)\n", count);
|
|
for (n = 0; n < count; n++) {
|
|
if (!pgc_param_coord(dev, &x[n]))
|
|
return;
|
|
if (!pgc_param_coord(dev, &y[n]))
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Parse but don't execute a POLY command (for adding to a command list) */
|
|
static int
|
|
parse_poly(pgc_t *dev, pgc_cl_t *cl, int c)
|
|
{
|
|
uint8_t count;
|
|
|
|
#ifdef ENABLE_PGC_LOG
|
|
pgc_log("PCG: parse_poly\n");
|
|
#endif
|
|
if (!pgc_param_byte(dev, &count))
|
|
return 0;
|
|
pgc_log("PCG: parse_poly: count=%02x\n", count);
|
|
|
|
if (!pgc_cl_append(cl, count)) {
|
|
pgc_error(dev, PGC_ERROR_OVERFLOW);
|
|
return 0;
|
|
}
|
|
pgc_log("PCG: parse_poly: parse %i coords\n", 2 * count);
|
|
|
|
return pgc_parse_coords(dev, cl, 2 * count);
|
|
}
|
|
|
|
/* Handle the DISPLAY command. */
|
|
static void
|
|
hndl_display(pgc_t *dev)
|
|
{
|
|
uint8_t param;
|
|
|
|
if (!pgc_param_byte(dev, ¶m))
|
|
return;
|
|
|
|
pgc_log("PGC: DISPLAY(%i)\n", param);
|
|
|
|
if (param > 1)
|
|
pgc_error(dev, PGC_ERROR_RANGE);
|
|
else
|
|
pgc_setdisplay(dev, param);
|
|
}
|
|
|
|
/* Handle the IMAGEW command (memory to screen blit). */
|
|
static void
|
|
hndl_imagew(pgc_t *dev)
|
|
{
|
|
int16_t row, col1, col2;
|
|
uint8_t v1, v2;
|
|
|
|
if (!pgc_param_word(dev, &row))
|
|
return;
|
|
if (!pgc_param_word(dev, &col1))
|
|
return;
|
|
if (!pgc_param_word(dev, &col2))
|
|
return;
|
|
|
|
if ((uint32_t) row >= dev->screenh || (uint32_t) col1 >= dev->maxw || (uint32_t) col2 >= dev->maxw) {
|
|
pgc_error(dev, PGC_ERROR_RANGE);
|
|
return;
|
|
}
|
|
|
|
/* In ASCII mode, what is written is a stream of bytes. */
|
|
if (dev->ascii_mode) {
|
|
while (col1 <= col2) {
|
|
if (!pgc_param_byte(dev, &v1))
|
|
return;
|
|
pgc_write_pixel(dev, col1, row, v1);
|
|
col1++;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/* In hex mode, it's RLE compressed. */
|
|
while (col1 <= col2) {
|
|
if (!pgc_param_byte(dev, &v1))
|
|
return;
|
|
|
|
if (v1 & 0x80) {
|
|
/* Literal run. */
|
|
v1 -= 0x7f;
|
|
while (col1 <= col2 && v1 != 0) {
|
|
if (!pgc_param_byte(dev, &v2))
|
|
return;
|
|
pgc_write_pixel(dev, col1, row, v2);
|
|
col1++;
|
|
v1--;
|
|
}
|
|
} else {
|
|
/* Repeated run. */
|
|
if (!pgc_param_byte(dev, &v2))
|
|
return;
|
|
|
|
v1++;
|
|
while (col1 <= col2 && v1 != 0) {
|
|
pgc_write_pixel(dev, col1, row, v2);
|
|
col1++;
|
|
v1--;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Select one of the built-in palettes. */
|
|
static void
|
|
init_lut(pgc_t *dev, int param)
|
|
{
|
|
if (param >= 0 && param < 6)
|
|
memcpy(dev->palette, init_palette[param], sizeof(dev->palette));
|
|
else if (param == 0xff)
|
|
memcpy(dev->palette, dev->userpal, sizeof(dev->palette));
|
|
else
|
|
pgc_error(dev, PGC_ERROR_RANGE);
|
|
}
|
|
|
|
/* Save the current palette. */
|
|
static void
|
|
hndl_lutsav(pgc_t *dev)
|
|
{
|
|
memcpy(dev->userpal, dev->palette, sizeof(dev->palette));
|
|
}
|
|
|
|
/* Handle LUTINT (select palette). */
|
|
static void
|
|
hndl_lutint(pgc_t *dev)
|
|
{
|
|
uint8_t param;
|
|
|
|
if (!pgc_param_byte(dev, ¶m))
|
|
return;
|
|
|
|
init_lut(dev, param);
|
|
}
|
|
|
|
/* Handle LUTRD (read palette register). */
|
|
static void
|
|
hndl_lutrd(pgc_t *dev)
|
|
{
|
|
uint8_t param;
|
|
uint32_t col;
|
|
|
|
if (!pgc_param_byte(dev, ¶m))
|
|
return;
|
|
|
|
col = dev->palette[param];
|
|
|
|
pgc_result_byte(dev, (col >> 20) & 0x0f);
|
|
pgc_result_byte(dev, (col >> 12) & 0x0f);
|
|
pgc_result_byte(dev, (col >> 4) & 0x0f);
|
|
}
|
|
|
|
/* Handle LUT (write palette register). */
|
|
static void
|
|
hndl_lut(pgc_t *dev)
|
|
{
|
|
uint8_t param[4];
|
|
int n;
|
|
|
|
for (n = 0; n < 4; n++) {
|
|
if (!pgc_param_byte(dev, ¶m[n]))
|
|
return;
|
|
if (n > 0 && param[n] > 15) {
|
|
pgc_error(dev, PGC_ERROR_RANGE);
|
|
param[n] &= 0x0f;
|
|
}
|
|
}
|
|
|
|
dev->palette[param[0]] = makecol((param[1] * 0x11),
|
|
(param[2] * 0x11),
|
|
(param[3] * 0x11));
|
|
}
|
|
|
|
/*
|
|
* LUT8RD and LUT8 are extensions implemented by several PGC clones,
|
|
* so here are functions that implement them even though they aren't
|
|
* used by the PGC.
|
|
*/
|
|
void
|
|
pgc_hndl_lut8rd(pgc_t *dev)
|
|
{
|
|
uint8_t param;
|
|
uint32_t col;
|
|
|
|
if (!pgc_param_byte(dev, ¶m))
|
|
return;
|
|
|
|
col = dev->palette[param];
|
|
|
|
pgc_result_byte(dev, (col >> 16) & 0xff);
|
|
pgc_result_byte(dev, (col >> 8) & 0xff);
|
|
pgc_result_byte(dev, col & 0xff);
|
|
}
|
|
|
|
void
|
|
pgc_hndl_lut8(pgc_t *dev)
|
|
{
|
|
uint8_t param[4];
|
|
int n;
|
|
|
|
for (n = 0; n < 4; n++)
|
|
if (!pgc_param_byte(dev, ¶m[n]))
|
|
return;
|
|
|
|
dev->palette[param[0]] = makecol((param[1]), (param[2]), (param[3]));
|
|
}
|
|
|
|
/* Handle AREAPT (set 16x16 fill pattern). */
|
|
static void
|
|
hndl_areapt(pgc_t *dev)
|
|
{
|
|
int16_t pat[16];
|
|
int n;
|
|
|
|
for (n = 0; n < 16; n++)
|
|
if (!pgc_param_word(dev, &pat[n]))
|
|
return;
|
|
|
|
pgc_log("PGC: AREAPT(%04x %04x %04x %04x...)\n",
|
|
pat[0] & 0xffff, pat[1] & 0xffff, pat[2] & 0xffff, pat[3] & 0xffff);
|
|
|
|
memcpy(dev->fill_pattern, pat, sizeof(dev->fill_pattern));
|
|
}
|
|
|
|
/* Handle CA (select ASCII mode). */
|
|
static void
|
|
hndl_ca(pgc_t *dev)
|
|
{
|
|
dev->ascii_mode = 1;
|
|
}
|
|
|
|
/* Handle CX (select hex mode). */
|
|
static void
|
|
hndl_cx(pgc_t *dev)
|
|
{
|
|
dev->ascii_mode = 0;
|
|
}
|
|
|
|
/*
|
|
* CA and CX remain valid in hex mode; they are handled
|
|
* as command 0x43 ('C') with a one-byte parameter.
|
|
*/
|
|
static void
|
|
hndl_c(pgc_t *dev)
|
|
{
|
|
uint8_t param;
|
|
|
|
if (!dev->inputbyte(dev, ¶m))
|
|
return;
|
|
|
|
if (param == 'A')
|
|
dev->ascii_mode = 1;
|
|
|
|
if (param == 'X')
|
|
dev->ascii_mode = 0;
|
|
}
|
|
|
|
/* RESETF resets the PGC. */
|
|
static void
|
|
hndl_resetf(pgc_t *dev)
|
|
{
|
|
pgc_reset(dev);
|
|
}
|
|
|
|
/* TJUST sets text justify settings. */
|
|
static void
|
|
hndl_tjust(pgc_t *dev)
|
|
{
|
|
uint8_t param[2];
|
|
|
|
if (!dev->inputbyte(dev, ¶m[0]))
|
|
return;
|
|
if (!dev->inputbyte(dev, ¶m[1]))
|
|
return;
|
|
|
|
if (param[0] >= 1 && param[0] <= 3 && param[1] >= 1 && param[1] <= 3) {
|
|
dev->tjust_h = param[0];
|
|
dev->tjust_v = param[1];
|
|
} else
|
|
pgc_error(dev, PGC_ERROR_RANGE);
|
|
}
|
|
|
|
/* TSIZE controls text horizontal spacing. */
|
|
static void
|
|
hndl_tsize(pgc_t *pgc)
|
|
{
|
|
int32_t param = 0;
|
|
|
|
if (!pgc_param_coord(pgc, ¶m))
|
|
return;
|
|
|
|
pgc_log("PGC: TSIZE %i\n", param);
|
|
pgc->tsize = param;
|
|
}
|
|
|
|
/*
|
|
* VWPORT sets up the viewport (roughly, the clip rectangle) in
|
|
* raster coordinates, measured from the bottom left of the screen.
|
|
*/
|
|
static void
|
|
hndl_vwport(pgc_t *dev)
|
|
{
|
|
int16_t x1, x2, y1, y2;
|
|
|
|
if (!pgc_param_word(dev, &x1))
|
|
return;
|
|
if (!pgc_param_word(dev, &x2))
|
|
return;
|
|
if (!pgc_param_word(dev, &y1))
|
|
return;
|
|
if (!pgc_param_word(dev, &y2))
|
|
return;
|
|
|
|
pgc_log("PGC: VWPORT %i,%i,%i,%i\n", x1, x2, y1, y2);
|
|
dev->vp_x1 = x1;
|
|
dev->vp_x2 = x2;
|
|
dev->vp_y1 = y1;
|
|
dev->vp_y2 = y2;
|
|
}
|
|
|
|
/* WINDOW defines the coordinate system in use. */
|
|
static void
|
|
hndl_window(pgc_t *dev)
|
|
{
|
|
int16_t x1, x2, y1, y2;
|
|
|
|
if (!pgc_param_word(dev, &x1))
|
|
return;
|
|
if (!pgc_param_word(dev, &x2))
|
|
return;
|
|
if (!pgc_param_word(dev, &y1))
|
|
return;
|
|
if (!pgc_param_word(dev, &y2))
|
|
return;
|
|
|
|
pgc_log("PGC: WINDOW %i,%i,%i,%i\n", x1, x2, y1, y2);
|
|
dev->win_x1 = x1;
|
|
dev->win_x2 = x2;
|
|
dev->win_y1 = y1;
|
|
dev->win_y2 = y2;
|
|
}
|
|
|
|
/*
|
|
* The list of commands implemented by this mini-PGC.
|
|
*
|
|
* In order to support the original PGC and clones, we support two lists;
|
|
* core commands (listed below) and subclass commands (listed in the clone).
|
|
*
|
|
* Each row has five parameters:
|
|
* ASCII-mode command
|
|
* Hex-mode command
|
|
* Function that executes this command
|
|
* Function that parses this command when building a command list
|
|
* Parameter for the parse function
|
|
*
|
|
* TODO: This list omits numerous commands present in a genuine PGC
|
|
* (ARC, AREA, AREABC, BUFFER, CIRCLE etc etc).
|
|
* TODO: Some commands don't have a parse function (for example, IMAGEW)
|
|
*
|
|
* The following ASCII entries have special meaning:
|
|
* ~~~~~~ command is valid only in hex mode
|
|
* ****** end of subclass command list, now process core command list
|
|
* @@@@@@ end of core command list
|
|
*
|
|
*/
|
|
static const pgc_cmd_t pgc_commands[] = {
|
|
{"AREAPT", 0xe7, hndl_areapt, pgc_parse_words, 16},
|
|
{ "AP", 0xe7, hndl_areapt, pgc_parse_words, 16},
|
|
{ "~~~~~~", 0x43, hndl_c, NULL, 0 },
|
|
{ "CA", 0xd2, hndl_ca, NULL, 0 },
|
|
{ "CLBEG", 0x70, hndl_clbeg, NULL, 0 },
|
|
{ "CB", 0x70, hndl_clbeg, NULL, 0 },
|
|
{ "CLDEL", 0x74, hndl_cldel, pgc_parse_bytes, 1 },
|
|
{ "CD", 0x74, hndl_cldel, pgc_parse_bytes, 1 },
|
|
{ "CLEND", 0x71, hndl_clend, NULL, 0 },
|
|
{ "CLRUN", 0x72, hndl_clrun, pgc_parse_bytes, 1 },
|
|
{ "CR", 0x72, hndl_clrun, pgc_parse_bytes, 1 },
|
|
{ "CLRD", 0x75, hndl_clread, pgc_parse_bytes, 1 },
|
|
{ "CRD", 0x75, hndl_clread, pgc_parse_bytes, 1 },
|
|
{ "CLOOP", 0x73, hndl_cloop, NULL, 0 },
|
|
{ "CL", 0x73, hndl_cloop, NULL, 0 },
|
|
{ "CLEARS", 0x0f, hndl_clears, pgc_parse_bytes, 1 },
|
|
{ "CLS", 0x0f, hndl_clears, pgc_parse_bytes, 1 },
|
|
{ "COLOR", 0x06, hndl_color, pgc_parse_bytes, 1 },
|
|
{ "C", 0x06, hndl_color, pgc_parse_bytes, 1 },
|
|
{ "CX", 0xd1, hndl_cx, NULL, 0 },
|
|
{ "DISPLA", 0xd0, hndl_display, pgc_parse_bytes, 1 },
|
|
{ "DI", 0xd0, hndl_display, pgc_parse_bytes, 1 },
|
|
{ "ELIPSE", 0x39, hndl_ellipse, pgc_parse_coords, 2 },
|
|
{ "EL", 0x39, hndl_ellipse, pgc_parse_coords, 2 },
|
|
{ "IMAGEW", 0xd9, hndl_imagew, NULL, 0 },
|
|
{ "IW", 0xd9, hndl_imagew, NULL, 0 },
|
|
{ "LINFUN", 0xeb, hndl_linfun, pgc_parse_bytes, 1 },
|
|
{ "LF", 0xeb, hndl_linfun, pgc_parse_bytes, 1 },
|
|
{ "LINPAT", 0xea, hndl_linpat, pgc_parse_words, 1 },
|
|
{ "LP", 0xea, hndl_linpat, pgc_parse_words, 1 },
|
|
{ "LUTINT", 0xec, hndl_lutint, pgc_parse_bytes, 1 },
|
|
{ "LI", 0xec, hndl_lutint, pgc_parse_bytes, 1 },
|
|
{ "LUTRD", 0x50, hndl_lutrd, pgc_parse_bytes, 1 },
|
|
{ "LUTSAV", 0xed, hndl_lutsav, NULL, 0 },
|
|
{ "LUT", 0xee, hndl_lut, pgc_parse_bytes, 4 },
|
|
{ "MOVE", 0x10, hndl_move, pgc_parse_coords, 2 },
|
|
{ "M", 0x10, hndl_move, pgc_parse_coords, 2 },
|
|
{ "MOVE3", 0x12, hndl_move3, pgc_parse_coords, 3 },
|
|
{ "M3", 0x12, hndl_move3, pgc_parse_coords, 3 },
|
|
{ "MOVER", 0x11, hndl_mover, pgc_parse_coords, 2 },
|
|
{ "MR", 0x11, hndl_mover, pgc_parse_coords, 2 },
|
|
{ "MOVER3", 0x13, hndl_mover3, pgc_parse_coords, 3 },
|
|
{ "MR3", 0x13, hndl_mover3, pgc_parse_coords, 3 },
|
|
{ "PRMFIL", 0xe9, hndl_prmfil, pgc_parse_bytes, 1 },
|
|
{ "PF", 0xe9, hndl_prmfil, pgc_parse_bytes, 1 },
|
|
{ "POLY", 0x30, hndl_poly, parse_poly, 0 },
|
|
{ "P", 0x30, hndl_poly, parse_poly, 0 },
|
|
{ "RESETF", 0x04, hndl_resetf, NULL, 0 },
|
|
{ "RF", 0x04, hndl_resetf, NULL, 0 },
|
|
{ "TJUST", 0x85, hndl_tjust, pgc_parse_bytes, 2 },
|
|
{ "TJ", 0x85, hndl_tjust, pgc_parse_bytes, 2 },
|
|
{ "TSIZE", 0x81, hndl_tsize, pgc_parse_coords, 1 },
|
|
{ "TS", 0x81, hndl_tsize, pgc_parse_coords, 1 },
|
|
{ "VWPORT", 0xb2, hndl_vwport, pgc_parse_words, 4 },
|
|
{ "VWP", 0xb2, hndl_vwport, pgc_parse_words, 4 },
|
|
{ "WINDOW", 0xb3, hndl_window, pgc_parse_words, 4 },
|
|
{ "WI", 0xb3, hndl_window, pgc_parse_words, 4 },
|
|
|
|
{ "@@@@@@", 0x00, NULL, NULL, 0 }
|
|
};
|
|
|
|
/* When the wake timer expires, that's when the drawing thread is actually
|
|
* woken */
|
|
static void
|
|
wake_timer(void *priv)
|
|
{
|
|
pgc_t *dev = (pgc_t *) priv;
|
|
|
|
#ifdef ENABLE_PGC_LOG
|
|
pgc_log("PGC: woke up\n");
|
|
#endif
|
|
|
|
thread_set_event(dev->pgc_wake_thread);
|
|
}
|
|
|
|
/*
|
|
* The PGC drawing thread main loop.
|
|
*
|
|
* Read in commands and execute them ad infinitum.
|
|
*/
|
|
static void
|
|
pgc_thread(void *priv)
|
|
{
|
|
pgc_t *dev = (pgc_t *) priv;
|
|
const pgc_cmd_t *cmd;
|
|
|
|
#ifdef ENABLE_PGC_LOG
|
|
pgc_log("PGC: thread begins\n");
|
|
#endif
|
|
|
|
for (;;) {
|
|
if (!parse_command(dev, &cmd)) {
|
|
/* Are we shutting down? */
|
|
if (dev->stopped) {
|
|
#ifdef ENABLE_PGC_LOG
|
|
pgc_log("PGC: Thread stopping...\n");
|
|
#endif
|
|
dev->stopped = 0;
|
|
break;
|
|
}
|
|
|
|
/* Nope, just a reset. */
|
|
continue;
|
|
}
|
|
|
|
pgc_log("PGC: Command: [%02x] '%s' found = %i\n",
|
|
dev->hex_command, dev->asc_command, (cmd != NULL));
|
|
|
|
if (cmd) {
|
|
dev->result_count = 0;
|
|
(*cmd->handler)(dev);
|
|
} else
|
|
pgc_error(dev, PGC_ERROR_OPCODE);
|
|
}
|
|
|
|
#ifdef ENABLE_PGC_LOG
|
|
pgc_log("PGC: thread stopped\n");
|
|
#endif
|
|
}
|
|
|
|
/* Parameter passed is not a number: abort. */
|
|
static int
|
|
err_digit(pgc_t *dev)
|
|
{
|
|
uint8_t asc;
|
|
|
|
do {
|
|
/* Swallow everything until the next separator */
|
|
if (!dev->inputbyte(dev, &asc))
|
|
return 0;
|
|
} while (!is_whitespace(asc));
|
|
|
|
pgc_error(dev, PGC_ERROR_DIGIT);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Output a byte, either as hex or ASCII depending on the mode. */
|
|
int
|
|
pgc_result_byte(pgc_t *dev, uint8_t val)
|
|
{
|
|
char buf[20];
|
|
|
|
if (!dev->ascii_mode)
|
|
return output_byte(dev, val);
|
|
|
|
if (dev->result_count) {
|
|
if (!output_byte(dev, ','))
|
|
return 0;
|
|
}
|
|
sprintf(buf, "%i", val);
|
|
dev->result_count++;
|
|
|
|
return output_string(dev, buf);
|
|
}
|
|
|
|
/* Output a word, either as hex or ASCII depending on the mode. */
|
|
int
|
|
pgc_result_word(pgc_t *dev, int16_t val)
|
|
{
|
|
char buf[20];
|
|
|
|
if (!dev->ascii_mode) {
|
|
if (!output_byte(dev, val & 0xFF))
|
|
return 0;
|
|
return output_byte(dev, val >> 8);
|
|
}
|
|
|
|
if (dev->result_count) {
|
|
if (!output_byte(dev, ','))
|
|
return 0;
|
|
}
|
|
sprintf(buf, "%i", val);
|
|
dev->result_count++;
|
|
|
|
return output_string(dev, buf);
|
|
}
|
|
|
|
/* Report an error, either in ASCII or in hex. */
|
|
int
|
|
pgc_error(pgc_t *dev, int err)
|
|
{
|
|
if (dev->mapram[0x307]) {
|
|
/* Errors enabled? */
|
|
if (dev->ascii_mode) {
|
|
if (err >= PGC_ERROR_RANGE && err <= PGC_ERROR_MISSING)
|
|
return error_string(dev, pgc_err_msgs[err]);
|
|
return error_string(dev, "Unknown error\r");
|
|
} else {
|
|
return error_byte(dev, err);
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Initialize RAM and registers to default values. */
|
|
void
|
|
pgc_reset(pgc_t *dev)
|
|
{
|
|
int n;
|
|
|
|
memset(dev->mapram, 0x00, sizeof(dev->mapram));
|
|
|
|
/* The 'CGA disable' jumper is not currently implemented. */
|
|
dev->mapram[0x30b] = dev->cga_enabled = 1;
|
|
dev->mapram[0x30c] = dev->cga_enabled;
|
|
dev->mapram[0x30d] = dev->cga_enabled;
|
|
|
|
dev->mapram[0x3f8] = 0x03; /* minor version */
|
|
dev->mapram[0x3f9] = 0x01; /* minor version */
|
|
dev->mapram[0x3fb] = 0xa5; /* } */
|
|
dev->mapram[0x3fc] = 0x5a; /* PGC self-test passed */
|
|
dev->mapram[0x3fd] = 0x55; /* } */
|
|
dev->mapram[0x3fe] = 0x5a; /* } */
|
|
|
|
dev->ascii_mode = 1; /* start off in ASCII mode */
|
|
dev->line_pattern = 0xffff;
|
|
memset(dev->fill_pattern, 0xff, sizeof(dev->fill_pattern));
|
|
dev->color = 0xff;
|
|
dev->tjust_h = 1;
|
|
dev->tjust_v = 1;
|
|
|
|
/* Reset panning. */
|
|
dev->pan_x = 0;
|
|
dev->pan_y = 0;
|
|
|
|
/* Reset clipping. */
|
|
dev->vp_x1 = 0;
|
|
dev->vp_y1 = 0;
|
|
dev->vp_x2 = dev->visw - 1;
|
|
dev->vp_y2 = dev->vish - 1;
|
|
|
|
/* Empty command lists. */
|
|
for (n = 0; n < 256; n++) {
|
|
dev->clist[n].wrptr = 0;
|
|
dev->clist[n].rdptr = 0;
|
|
dev->clist[n].repeat = 0;
|
|
dev->clist[n].chain = 0;
|
|
}
|
|
dev->clcur = NULL;
|
|
|
|
/* Select CGA display. */
|
|
dev->cga_selected = -1;
|
|
pgc_setdisplay(dev, dev->cga_enabled);
|
|
|
|
/* Default palette is 0. */
|
|
init_lut(dev, 0);
|
|
hndl_lutsav(dev);
|
|
}
|
|
|
|
/* Switch between CGA mode (DISPLAY 1) and native mode (DISPLAY 0). */
|
|
void
|
|
pgc_setdisplay(pgc_t *dev, int cga)
|
|
{
|
|
pgc_log("PGC: setdisplay(%i): cga_selected=%i cga_enabled=%i\n",
|
|
cga, dev->cga_selected, dev->cga_enabled);
|
|
|
|
if (dev->cga_selected != (dev->cga_enabled && cga)) {
|
|
dev->cga_selected = (dev->cga_enabled && cga);
|
|
dev->displine = 0;
|
|
|
|
if (dev->cga_selected) {
|
|
mem_mapping_enable(&dev->cga_mapping);
|
|
dev->screenw = PGC_CGA_WIDTH;
|
|
dev->screenh = PGC_CGA_HEIGHT;
|
|
} else {
|
|
mem_mapping_disable(&dev->cga_mapping);
|
|
dev->screenw = dev->visw;
|
|
dev->screenh = dev->vish;
|
|
}
|
|
|
|
pgc_recalctimings(dev);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* When idle, the PGC drawing thread sleeps. pgc_wake() awakens it - but
|
|
* not immediately. Like the Voodoo, it has a short delay so that writes
|
|
* can be batched.
|
|
*/
|
|
void
|
|
pgc_wake(pgc_t *dev)
|
|
{
|
|
if (!timer_is_enabled(&dev->wake_timer))
|
|
timer_set_delay_u64(&dev->wake_timer, WAKE_DELAY);
|
|
}
|
|
|
|
/* Wait for more input data, or for output to drain. */
|
|
void
|
|
pgc_sleep(pgc_t *dev)
|
|
{
|
|
pgc_log("PGC: sleeping on %i %i %i %i 0x%02x 0x%02x\n",
|
|
dev->stopped,
|
|
dev->waiting_input_fifo, dev->waiting_output_fifo,
|
|
dev->waiting_error_fifo, dev->mapram[0x300], dev->mapram[0x301]);
|
|
|
|
/* Avoid entering waiting state. */
|
|
if (dev->stopped) {
|
|
dev->waiting_input_fifo = 0;
|
|
dev->waiting_output_fifo = 0;
|
|
return;
|
|
}
|
|
|
|
/* Race condition: If host wrote to the PGC during the that
|
|
* won't be noticed */
|
|
if (dev->waiting_input_fifo && dev->mapram[0x300] != dev->mapram[0x301]) {
|
|
dev->waiting_input_fifo = 0;
|
|
return;
|
|
}
|
|
|
|
/* Same if they read. */
|
|
if (dev->waiting_output_fifo && dev->mapram[0x302] != (uint8_t) (dev->mapram[0x303] - 1)) {
|
|
dev->waiting_output_fifo = 0;
|
|
return;
|
|
}
|
|
|
|
thread_wait_event(dev->pgc_wake_thread, -1);
|
|
thread_reset_event(dev->pgc_wake_thread);
|
|
}
|
|
|
|
/* Pull the next byte from the current command list. */
|
|
int
|
|
pgc_clist_byte(pgc_t *dev, uint8_t *val)
|
|
{
|
|
if (dev->clcur == NULL)
|
|
return 0;
|
|
|
|
if (dev->clcur->rdptr < dev->clcur->wrptr)
|
|
*val = dev->clcur->list[dev->clcur->rdptr++];
|
|
else
|
|
*val = 0;
|
|
|
|
/* If we've reached the end, reset to the beginning and
|
|
* (if repeating) run the repeat */
|
|
if (dev->clcur->rdptr >= dev->clcur->wrptr) {
|
|
dev->clcur->rdptr = 0;
|
|
dev->clcur->repeat--;
|
|
if (dev->clcur->repeat == 0)
|
|
dev->clcur = dev->clcur->chain;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Read in a byte, either as hex (1 byte) or ASCII (decimal).
|
|
* Returns 0 if PGC reset detected while the value is being read.
|
|
*/
|
|
int
|
|
pgc_param_byte(pgc_t *dev, uint8_t *val)
|
|
{
|
|
int32_t c;
|
|
|
|
if (dev->clcur)
|
|
return pgc_clist_byte(dev, val);
|
|
|
|
if (!dev->ascii_mode)
|
|
return dev->inputbyte(dev, val);
|
|
|
|
if (!pgc_param_coord(dev, &c))
|
|
return 0;
|
|
|
|
c = (c >> 16); /* drop fractional part */
|
|
if (c > 255) {
|
|
pgc_error(dev, PGC_ERROR_RANGE);
|
|
return 0;
|
|
}
|
|
*val = (uint8_t) c;
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Read in a word, either as hex (2 bytes) or ASCII (decimal).
|
|
* Returns 0 if PGC reset detected while the value is being read.
|
|
*/
|
|
int
|
|
pgc_param_word(pgc_t *dev, int16_t *val)
|
|
{
|
|
uint8_t lo, hi;
|
|
int32_t c;
|
|
|
|
if (dev->clcur) {
|
|
if (!pgc_clist_byte(dev, &lo))
|
|
return 0;
|
|
if (!pgc_clist_byte(dev, &hi))
|
|
return 0;
|
|
*val = (((int16_t) hi) << 8) | lo;
|
|
|
|
return 1;
|
|
}
|
|
|
|
if (!dev->ascii_mode) {
|
|
if (!dev->inputbyte(dev, &lo))
|
|
return 0;
|
|
if (!dev->inputbyte(dev, &hi))
|
|
return 0;
|
|
*val = (((int16_t) hi) << 8) | lo;
|
|
|
|
return 1;
|
|
}
|
|
|
|
if (!pgc_param_coord(dev, &c))
|
|
return 0;
|
|
|
|
c = (c >> 16);
|
|
if (c > 0x7fff || c < -0x7fff) {
|
|
pgc_error(dev, PGC_ERROR_RANGE);
|
|
return 0;
|
|
}
|
|
*val = (int16_t) c;
|
|
|
|
return 1;
|
|
}
|
|
|
|
typedef enum {
|
|
PS_MAIN,
|
|
PS_FRACTION,
|
|
PS_EXPONENT
|
|
} parse_state_t;
|
|
|
|
/*
|
|
* Read in a PGC coordinate.
|
|
*
|
|
* Either as hex (4 bytes) or ASCII (xxxx.yyyyEeee)
|
|
*
|
|
* Returns 0 if PGC reset detected while the value is being read.
|
|
*/
|
|
int
|
|
pgc_param_coord(pgc_t *dev, int32_t *value)
|
|
{
|
|
uint8_t asc;
|
|
int sign = 1;
|
|
int esign = 1;
|
|
int n;
|
|
uint16_t dp = 1;
|
|
uint16_t integer = 0;
|
|
uint16_t frac = 0;
|
|
uint16_t exponent = 0;
|
|
uint32_t res;
|
|
parse_state_t state = PS_MAIN;
|
|
uint8_t encoded[4];
|
|
|
|
/* If there is a command list running, pull the bytes out of that
|
|
* command list */
|
|
if (dev->clcur) {
|
|
for (n = 0; n < 4; n++)
|
|
if (!pgc_clist_byte(dev, &encoded[n]))
|
|
return 0;
|
|
integer = (((int16_t) encoded[1]) << 8) | encoded[0];
|
|
frac = (((int16_t) encoded[3]) << 8) | encoded[2];
|
|
|
|
*value = (((int32_t) integer) << 16) | frac;
|
|
return 1;
|
|
}
|
|
|
|
/* If in hex mode, read in the encoded integer and fraction parts
|
|
* from the hex stream */
|
|
if (!dev->ascii_mode) {
|
|
for (n = 0; n < 4; n++)
|
|
if (!dev->inputbyte(dev, &encoded[n]))
|
|
return 0;
|
|
integer = (((int16_t) encoded[1]) << 8) | encoded[0];
|
|
frac = (((int16_t) encoded[3]) << 8) | encoded[2];
|
|
|
|
*value = (((int32_t) integer) << 16) | frac;
|
|
return 1;
|
|
}
|
|
|
|
/* Parsing an ASCII value; skip separators. */
|
|
do {
|
|
if (!dev->inputbyte(dev, &asc))
|
|
return 0;
|
|
if (asc == '-')
|
|
sign = -1;
|
|
} while (is_whitespace(asc));
|
|
|
|
/* There had better be a digit next. */
|
|
if (!isdigit(asc)) {
|
|
pgc_error(dev, PGC_ERROR_MISSING);
|
|
return 0;
|
|
}
|
|
|
|
do {
|
|
switch (asc) {
|
|
/* Decimal point is acceptable in 'main' state
|
|
* (start of fraction) not otherwise */
|
|
case '.':
|
|
if (state == PS_MAIN) {
|
|
if (!dev->inputbyte(dev, &asc))
|
|
return 0;
|
|
state = PS_FRACTION;
|
|
continue;
|
|
} else {
|
|
pgc_error(dev, PGC_ERROR_MISSING);
|
|
return err_digit(dev);
|
|
}
|
|
break;
|
|
|
|
/* Scientific notation. */
|
|
case 'd':
|
|
case 'D':
|
|
case 'e':
|
|
case 'E':
|
|
esign = 1;
|
|
if (!dev->inputbyte(dev, &asc))
|
|
return 0;
|
|
if (asc == '-') {
|
|
sign = -1;
|
|
if (!dev->inputbyte(dev, &asc))
|
|
return 0;
|
|
}
|
|
state = PS_EXPONENT;
|
|
continue;
|
|
|
|
/* Should be a number or a separator. */
|
|
default:
|
|
if (is_whitespace(asc))
|
|
break;
|
|
if (!isdigit(asc)) {
|
|
pgc_error(dev, PGC_ERROR_MISSING);
|
|
return err_digit(dev);
|
|
}
|
|
asc -= '0'; /* asc is digit */
|
|
|
|
switch (state) {
|
|
case PS_MAIN:
|
|
integer = (integer * 10) + asc;
|
|
if (integer & 0x8000) {
|
|
/* Overflow */
|
|
pgc_error(dev, PGC_ERROR_RANGE);
|
|
integer = 0x7fff;
|
|
}
|
|
break;
|
|
|
|
case PS_FRACTION:
|
|
frac = (frac * 10) + asc;
|
|
dp *= 10;
|
|
break;
|
|
|
|
case PS_EXPONENT:
|
|
exponent = (exponent * 10) + asc;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!dev->inputbyte(dev, &asc))
|
|
return 0;
|
|
} while (!is_whitespace(asc));
|
|
|
|
res = (frac << 16) / dp;
|
|
pgc_log("PGC: integer=%u frac=%u exponent=%u dp=%i res=0x%08lx\n",
|
|
integer, frac, exponent, dp, res);
|
|
|
|
res = (res & 0xffff) | (integer << 16);
|
|
if (exponent) {
|
|
for (n = 0; n < exponent; n++) {
|
|
if (esign > 0)
|
|
res *= 10;
|
|
else
|
|
res /= 10;
|
|
}
|
|
}
|
|
*value = sign * res;
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Add a byte to a command list.
|
|
*
|
|
* We allow command lists to be arbitrarily large.
|
|
*/
|
|
int
|
|
pgc_cl_append(pgc_cl_t *list, uint8_t v)
|
|
{
|
|
uint8_t *buf;
|
|
|
|
if (list->listmax == 0 || list->list == NULL) {
|
|
list->list = (uint8_t *) malloc(4096);
|
|
if (!list->list) {
|
|
#ifdef ENABLE_PGC_LOG
|
|
pgc_log("PGC: out of memory initializing command list\n");
|
|
#endif
|
|
return 0;
|
|
}
|
|
list->listmax = 4096;
|
|
}
|
|
|
|
while (list->wrptr >= list->listmax) {
|
|
buf = (uint8_t *) realloc(list->list, 2 * list->listmax);
|
|
if (!buf) {
|
|
#ifdef ENABLE_PGC_LOG
|
|
pgc_log("PGC: out of memory growing command list\n");
|
|
#endif
|
|
return 0;
|
|
}
|
|
list->list = buf;
|
|
list->listmax *= 2;
|
|
}
|
|
|
|
list->list[list->wrptr++] = v;
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Parse but don't execute a command with a fixed number of byte parameters. */
|
|
int
|
|
pgc_parse_bytes(pgc_t *dev, pgc_cl_t *cl, int count)
|
|
{
|
|
uint8_t *param = (uint8_t *) malloc(count);
|
|
int n;
|
|
|
|
if (!param) {
|
|
pgc_error(dev, PGC_ERROR_OVERFLOW);
|
|
return 0;
|
|
}
|
|
|
|
for (n = 0; n < count; n++) {
|
|
if (!pgc_param_byte(dev, ¶m[n])) {
|
|
free(param);
|
|
return 0;
|
|
}
|
|
|
|
if (!pgc_cl_append(cl, param[n])) {
|
|
pgc_error(dev, PGC_ERROR_OVERFLOW);
|
|
free(param);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
free(param);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Parse but don't execute a command with a fixed number of word parameters. */
|
|
int
|
|
pgc_parse_words(pgc_t *dev, pgc_cl_t *cl, int count)
|
|
{
|
|
int16_t *param = (int16_t *) malloc(count * sizeof(int16_t));
|
|
int n;
|
|
|
|
if (!param) {
|
|
pgc_error(dev, PGC_ERROR_OVERFLOW);
|
|
return 0;
|
|
}
|
|
|
|
for (n = 0; n < count; n++) {
|
|
if (!pgc_param_word(dev, ¶m[n])) {
|
|
free(param);
|
|
return 0;
|
|
}
|
|
|
|
if (!pgc_cl_append(cl, param[n] & 0xff) || !pgc_cl_append(cl, param[n] >> 8)) {
|
|
pgc_error(dev, PGC_ERROR_OVERFLOW);
|
|
free(param);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
free(param);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Parse but don't execute a command with a fixed number of coord parameters */
|
|
int
|
|
pgc_parse_coords(pgc_t *dev, pgc_cl_t *cl, int count)
|
|
{
|
|
int32_t *param = (int32_t *) malloc(count * sizeof(int32_t));
|
|
int n;
|
|
|
|
if (!param) {
|
|
pgc_error(dev, PGC_ERROR_OVERFLOW);
|
|
return 0;
|
|
}
|
|
|
|
for (n = 0; n < count; n++) {
|
|
if (!pgc_param_coord(dev, ¶m[n])) {
|
|
free(param);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* Here is how the real PGC serializes coords:
|
|
*
|
|
* 100.5 -> 64 00 00 80 ie 0064.8000
|
|
* 100.3 -> 64 00 CD 4C ie 0064.4CCD
|
|
*/
|
|
for (n = 0; n < count; n++) {
|
|
/* Serialize integer part. */
|
|
if (!pgc_cl_append(cl, (param[n] >> 16) & 0xff) || !pgc_cl_append(cl, (param[n] >> 24) & 0xff) ||
|
|
|
|
/* Serialize fraction part. */
|
|
!pgc_cl_append(cl, (param[n]) & 0xff) || !pgc_cl_append(cl, (param[n] >> 8) & 0xff)) {
|
|
pgc_error(dev, PGC_ERROR_OVERFLOW);
|
|
free(param);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
free(param);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Convert coordinates based on the current window / viewport to raster
|
|
* coordinates. */
|
|
void
|
|
pgc_dto_raster(pgc_t *dev, double *x, double *y)
|
|
{
|
|
#ifdef ENABLE_PGC_LOG
|
|
double x0 = *x, y0 = *y;
|
|
#endif
|
|
|
|
*x += (dev->vp_x1 - dev->win_x1);
|
|
*y += (dev->vp_y1 - dev->win_y1);
|
|
|
|
pgc_log("PGC: coords to raster: (%f, %f) -> (%f, %f)\n", x0, y0, *x, *y);
|
|
}
|
|
|
|
/* Overloads that take ints. */
|
|
void
|
|
pgc_sto_raster(pgc_t *dev, int16_t *x, int16_t *y)
|
|
{
|
|
double xd = *x, yd = *y;
|
|
|
|
pgc_dto_raster(dev, &xd, &yd);
|
|
*x = (int16_t) xd;
|
|
*y = (int16_t) yd;
|
|
}
|
|
|
|
void
|
|
pgc_ito_raster(pgc_t *dev, int32_t *x, int32_t *y)
|
|
{
|
|
double xd = *x, yd = *y;
|
|
|
|
pgc_dto_raster(dev, &xd, &yd);
|
|
*x = (int32_t) xd;
|
|
*y = (int32_t) yd;
|
|
}
|
|
|
|
void
|
|
pgc_recalctimings(pgc_t *dev)
|
|
{
|
|
double disptime, _dispontime, _dispofftime;
|
|
double pixel_clock = (cpuclock / (dev->cga_selected ? 25175000.0 : dev->native_pixel_clock) * (double) (1ull << 32));
|
|
uint8_t crtc0 = 97, crtc1 = 80; /* Values from MDA, taken from there due to the 25 MHz refresh rate. */
|
|
|
|
/* Multiply pixel clock by 8. */
|
|
pixel_clock *= 8.0;
|
|
/* Use a fixed 640x400 display. */
|
|
disptime = crtc0 + 1;
|
|
_dispontime = crtc1;
|
|
_dispofftime = disptime - _dispontime;
|
|
_dispontime *= pixel_clock;
|
|
_dispofftime *= pixel_clock;
|
|
dev->dispontime = (uint64_t) (_dispontime);
|
|
dev->dispofftime = (uint64_t) (_dispofftime);
|
|
}
|
|
|
|
/* Write to CGA registers are copied into the transfer memory buffer. */
|
|
void
|
|
pgc_out(uint16_t addr, uint8_t val, void *priv)
|
|
{
|
|
pgc_t *dev = (pgc_t *) priv;
|
|
|
|
pgc_log("PGC: out(%04x, %02x)\n", addr, val);
|
|
|
|
switch (addr) {
|
|
case 0x03d0: /* CRTC Index register */
|
|
case 0x03d2:
|
|
case 0x03d4:
|
|
case 0x03d6:
|
|
dev->mapram[0x03d0] = val;
|
|
break;
|
|
|
|
case 0x03d1: /* CRTC Data register */
|
|
case 0x03d3:
|
|
case 0x03d5:
|
|
case 0x03d7:
|
|
if (dev->mapram[0x03d0] < 18)
|
|
dev->mapram[0x03e0 + dev->mapram[0x03d0]] = val;
|
|
break;
|
|
|
|
case 0x03d8: /* CRTC Mode Control register */
|
|
dev->mapram[0x03d8] = val;
|
|
break;
|
|
|
|
case 0x03d9: /* CRTC Color Select register */
|
|
dev->mapram[0x03d9] = val;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Read back the CGA registers. */
|
|
uint8_t
|
|
pgc_in(uint16_t addr, void *priv)
|
|
{
|
|
pgc_t *dev = (pgc_t *) priv;
|
|
uint8_t ret = 0xff;
|
|
|
|
switch (addr) {
|
|
case 0x03d0: /* CRTC Index register */
|
|
case 0x03d2:
|
|
case 0x03d4:
|
|
case 0x03d6:
|
|
ret = dev->mapram[0x03d0];
|
|
break;
|
|
|
|
case 0x03d1: /* CRTC Data register */
|
|
case 0x03d3:
|
|
case 0x03d5:
|
|
case 0x03d7:
|
|
if (dev->mapram[0x03d0] < 18)
|
|
ret = dev->mapram[0x03e0 + dev->mapram[0x03d0]];
|
|
break;
|
|
|
|
case 0x03d8: /* CRTC Mode Control register */
|
|
ret = dev->mapram[0x03d8];
|
|
break;
|
|
|
|
case 0x03d9: /* CRTC Color Select register */
|
|
ret = dev->mapram[0x03d9];
|
|
break;
|
|
|
|
case 0x03da: /* CRTC Status register */
|
|
ret = dev->mapram[0x03da];
|
|
break;
|
|
}
|
|
|
|
pgc_log("PGC: in(%04x) = %02x\n", addr, ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Memory write to the transfer buffer. */
|
|
/* TODO: Check the CGA mapping repeat stuff. */
|
|
void
|
|
pgc_write(uint32_t addr, uint8_t val, void *priv)
|
|
{
|
|
pgc_t *dev = (pgc_t *) priv;
|
|
|
|
/*
|
|
* It seems variable whether the PGC maps 1K or 2K at 0xc6000.
|
|
*
|
|
* Map 2K here in case a clone requires it.
|
|
*/
|
|
if (addr >= 0xc6000 && addr < 0xc6800) {
|
|
addr &= 0x7ff;
|
|
|
|
/* If one of the FIFOs has been updated, this may cause
|
|
* the drawing thread to be woken */
|
|
|
|
if (dev->mapram[addr] != val) {
|
|
dev->mapram[addr] = val;
|
|
|
|
switch (addr) {
|
|
case 0x300: /* input write pointer */
|
|
if (dev->waiting_input_fifo && dev->mapram[0x300] != dev->mapram[0x301]) {
|
|
dev->waiting_input_fifo = 0;
|
|
pgc_wake(dev);
|
|
}
|
|
break;
|
|
|
|
case 0x303: /* output read pointer */
|
|
if (dev->waiting_output_fifo && dev->mapram[0x302] != (uint8_t) (dev->mapram[0x303] - 1)) {
|
|
dev->waiting_output_fifo = 0;
|
|
pgc_wake(dev);
|
|
}
|
|
break;
|
|
|
|
case 0x305: /* error read pointer */
|
|
if (dev->waiting_error_fifo && dev->mapram[0x304] != (uint8_t) (dev->mapram[0x305] - 1)) {
|
|
dev->waiting_error_fifo = 0;
|
|
pgc_wake(dev);
|
|
}
|
|
break;
|
|
|
|
case 0x306: /* cold start flag */
|
|
/* XXX This should be in IM-1024 specific code */
|
|
dev->mapram[0x306] = 0;
|
|
break;
|
|
|
|
case 0x30c: /* display type */
|
|
pgc_setdisplay(priv, dev->mapram[0x30c]);
|
|
dev->mapram[0x30d] = dev->mapram[0x30c];
|
|
break;
|
|
|
|
case 0x3ff: /* reboot the PGC */
|
|
pgc_wake(dev);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (addr >= 0xb8000 && addr < 0xc0000 && dev->cga_selected) {
|
|
addr &= 0x3fff;
|
|
dev->cga_vram[addr] = val;
|
|
}
|
|
}
|
|
|
|
/* TODO: Check the CGA mapping repeat stuff. */
|
|
uint8_t
|
|
pgc_read(uint32_t addr, void *priv)
|
|
{
|
|
pgc_t *dev = (pgc_t *) priv;
|
|
uint8_t ret = 0xff;
|
|
|
|
if (addr >= 0xc6000 && addr < 0xc6800) {
|
|
addr &= 0x7ff;
|
|
ret = dev->mapram[addr];
|
|
} else if (addr >= 0xb8000 && addr < 0xc0000 && dev->cga_selected) {
|
|
addr &= 0x3fff;
|
|
ret = dev->cga_vram[addr];
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Draw the display in CGA (640x400) text mode. */
|
|
void
|
|
pgc_cga_text(pgc_t *dev, int w)
|
|
{
|
|
int x, c;
|
|
uint8_t chr, attr;
|
|
int drawcursor = 0;
|
|
uint32_t cols[2];
|
|
int pitch = (dev->mapram[0x3e9] + 1) * 2;
|
|
uint16_t sc = (dev->displine & 0x0f) % pitch;
|
|
uint16_t ma = (dev->mapram[0x3ed] | (dev->mapram[0x3ec] << 8)) & 0x3fff;
|
|
uint16_t ca = (dev->mapram[0x3ef] | (dev->mapram[0x3ee] << 8)) & 0x3fff;
|
|
uint8_t *addr;
|
|
uint32_t val;
|
|
int cw = (w == 80) ? 8 : 16;
|
|
|
|
addr = &dev->cga_vram[((ma + ((dev->displine / pitch) * w)) * 2) & 0x3ffe];
|
|
ma += (dev->displine / pitch) * w;
|
|
|
|
for (x = 0; x < w; x++) {
|
|
chr = *addr++;
|
|
attr = *addr++;
|
|
|
|
/* Cursor enabled? */
|
|
if (ma == ca && (dev->cgablink & 8) && (dev->mapram[0x3ea] & 0x60) != 0x20) {
|
|
drawcursor = ((dev->mapram[0x3ea] & 0x1f) <= (sc >> 1)) && ((dev->mapram[0x3eb] & 0x1f) >= (sc >> 1));
|
|
} else
|
|
drawcursor = 0;
|
|
|
|
if (dev->mapram[0x3d8] & 0x20) {
|
|
cols[1] = (attr & 15) + 16;
|
|
cols[0] = ((attr >> 4) & 7) + 16;
|
|
if ((dev->cgablink & 8) && (attr & 0x80) && !drawcursor)
|
|
cols[1] = cols[0];
|
|
} else {
|
|
cols[1] = (attr & 15) + 16;
|
|
cols[0] = (attr >> 4) + 16;
|
|
}
|
|
|
|
for (c = 0; c < cw; c++) {
|
|
if (drawcursor)
|
|
val = cols[(fontdatm[chr + dev->fontbase][sc] & (1 << (c ^ 7))) ? 1 : 0] ^ 0x0f;
|
|
else
|
|
val = cols[(fontdatm[chr + dev->fontbase][sc] & (1 << (c ^ 7))) ? 1 : 0];
|
|
if (cw == 8) /* 80x25 CGA text screen. */
|
|
buffer32->line[dev->displine][(x * cw) + c] = val;
|
|
else { /* 40x25 CGA text screen. */
|
|
buffer32->line[dev->displine][(x * cw) + (c * 2)] = val;
|
|
buffer32->line[dev->displine][(x * cw) + (c * 2) + 1] = val;
|
|
}
|
|
}
|
|
|
|
ma++;
|
|
}
|
|
}
|
|
|
|
/* Draw the display in CGA (320x200) graphics mode. */
|
|
void
|
|
pgc_cga_gfx40(pgc_t *dev)
|
|
{
|
|
int x, c;
|
|
uint32_t cols[4];
|
|
int col;
|
|
uint16_t ma = (dev->mapram[0x3ed] | (dev->mapram[0x3ec] << 8)) & 0x3fff;
|
|
uint8_t *addr;
|
|
uint16_t dat;
|
|
|
|
cols[0] = (dev->mapram[0x3d9] & 15) + 16;
|
|
col = ((dev->mapram[0x3d9] & 16) ? 8 : 0) + 16;
|
|
|
|
/* From John Elliott's site:
|
|
On a real CGA, if bit 2 of port 03D8h and bit 5 of port 03D9h are both set,
|
|
the palette used in graphics modes is red/cyan/white. On a PGC, it's
|
|
magenta/cyan/white. You still get red/cyan/white if bit 5 of port 03D9h is
|
|
not set. This is a firmware issue rather than hardware. */
|
|
if (dev->mapram[0x3d9] & 32) {
|
|
cols[1] = col | 3;
|
|
cols[2] = col | 5;
|
|
cols[3] = col | 7;
|
|
} else if (dev->mapram[0x3d8] & 4) {
|
|
cols[1] = col | 3;
|
|
cols[2] = col | 4;
|
|
cols[3] = col | 7;
|
|
} else {
|
|
cols[1] = col | 2;
|
|
cols[2] = col | 4;
|
|
cols[3] = col | 6;
|
|
}
|
|
|
|
for (x = 0; x < 40; x++) {
|
|
addr = &dev->cga_vram[(ma + 2 * x + 80 * (dev->displine >> 2) + 0x2000 * ((dev->displine >> 1) & 1)) & 0x3fff];
|
|
dat = (addr[0] << 8) | addr[1];
|
|
dev->ma++;
|
|
for (c = 0; c < 8; c++) {
|
|
buffer32->line[dev->displine][(x << 4) + (c << 1)] = buffer32->line[dev->displine][(x << 4) + (c << 1) + 1] = cols[dat >> 14];
|
|
dat <<= 2;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Draw the display in CGA (640x200) graphics mode. */
|
|
void
|
|
pgc_cga_gfx80(pgc_t *dev)
|
|
{
|
|
int x, c;
|
|
uint32_t cols[2];
|
|
uint16_t ma = (dev->mapram[0x3ed] | (dev->mapram[0x3ec] << 8)) & 0x3fff;
|
|
uint8_t *addr;
|
|
uint16_t dat;
|
|
|
|
cols[0] = 16;
|
|
cols[1] = (dev->mapram[0x3d9] & 15) + 16;
|
|
|
|
for (x = 0; x < 40; x++) {
|
|
addr = &dev->cga_vram[(ma + 2 * x + 80 * (dev->displine >> 2) + 0x2000 * ((dev->displine >> 1) & 1)) & 0x3fff];
|
|
dat = (addr[0] << 8) | addr[1];
|
|
dev->ma++;
|
|
for (c = 0; c < 16; c++) {
|
|
buffer32->line[dev->displine][(x << 4) + c] = cols[dat >> 15];
|
|
dat <<= 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Draw the screen in CGA mode. */
|
|
void
|
|
pgc_cga_poll(pgc_t *dev)
|
|
{
|
|
uint32_t cols[2];
|
|
|
|
if (!dev->linepos) {
|
|
timer_advance_u64(&dev->timer, dev->dispofftime);
|
|
dev->mapram[0x03da] |= 1;
|
|
dev->linepos = 1;
|
|
|
|
if (dev->cgadispon) {
|
|
if (dev->displine == 0)
|
|
video_wait_for_buffer();
|
|
|
|
if ((dev->mapram[0x03d8] & 0x12) == 0x12)
|
|
pgc_cga_gfx80(dev);
|
|
else if (dev->mapram[0x03d8] & 0x02)
|
|
pgc_cga_gfx40(dev);
|
|
else if (dev->mapram[0x03d8] & 0x01)
|
|
pgc_cga_text(dev, 80);
|
|
else
|
|
pgc_cga_text(dev, 40);
|
|
} else {
|
|
cols[0] = ((dev->mapram[0x03d8] & 0x12) == 0x12) ? 0 : ((dev->mapram[0x03d9] & 15) + 16);
|
|
hline(buffer32, 0, dev->displine, PGC_CGA_WIDTH, cols[0]);
|
|
}
|
|
|
|
video_process_8(PGC_CGA_WIDTH, dev->displine);
|
|
|
|
if (++dev->displine == PGC_CGA_HEIGHT) {
|
|
dev->mapram[0x3da] |= 8;
|
|
dev->cgadispon = 0;
|
|
}
|
|
if (dev->displine == PGC_CGA_HEIGHT + 32) {
|
|
dev->mapram[0x3da] &= ~8;
|
|
dev->cgadispon = 1;
|
|
dev->displine = 0;
|
|
}
|
|
} else {
|
|
if (dev->cgadispon)
|
|
dev->mapram[0x3da] &= ~1;
|
|
timer_advance_u64(&dev->timer, dev->dispontime);
|
|
dev->linepos = 0;
|
|
|
|
if (dev->displine == PGC_CGA_HEIGHT) {
|
|
if (PGC_CGA_WIDTH != xsize || PGC_CGA_HEIGHT != ysize) {
|
|
xsize = PGC_CGA_WIDTH;
|
|
ysize = PGC_CGA_HEIGHT;
|
|
set_screen_size(xsize, ysize);
|
|
|
|
if (video_force_resize_get())
|
|
video_force_resize_set(0);
|
|
}
|
|
video_blit_memtoscreen(0, 0, xsize, ysize);
|
|
frames++;
|
|
|
|
/* We have a fixed 640x400 screen for CGA modes. */
|
|
video_res_x = PGC_CGA_WIDTH;
|
|
video_res_y = PGC_CGA_HEIGHT;
|
|
switch (dev->mapram[0x3d8] & 0x12) {
|
|
case 0x12:
|
|
video_bpp = 1;
|
|
break;
|
|
|
|
case 0x02:
|
|
video_bpp = 2;
|
|
break;
|
|
|
|
default:
|
|
video_bpp = 0;
|
|
break;
|
|
}
|
|
dev->cgablink++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Draw the screen in CGA or native mode. */
|
|
void
|
|
pgc_poll(void *priv)
|
|
{
|
|
pgc_t *dev = (pgc_t *) priv;
|
|
uint32_t x, y;
|
|
|
|
if (dev->cga_selected) {
|
|
pgc_cga_poll(dev);
|
|
return;
|
|
}
|
|
|
|
/* Not CGA, so must be native mode. */
|
|
if (!dev->linepos) {
|
|
timer_advance_u64(&dev->timer, dev->dispofftime);
|
|
dev->mapram[0x3da] |= 1;
|
|
dev->linepos = 1;
|
|
if (dev->cgadispon && (uint32_t) dev->displine < dev->maxh) {
|
|
if (dev->displine == 0)
|
|
video_wait_for_buffer();
|
|
|
|
/* Don't know why pan needs to be multiplied by -2, but
|
|
* the IM1024 driver uses PAN -112 for an offset of
|
|
* 224. */
|
|
y = dev->displine - 2 * dev->pan_y;
|
|
for (x = 0; x < dev->screenw; x++) {
|
|
if (x + dev->pan_x < dev->maxw)
|
|
buffer32->line[dev->displine][x] = dev->palette[dev->vram[y * dev->maxw + x]];
|
|
else
|
|
buffer32->line[dev->displine][x] = dev->palette[0];
|
|
}
|
|
} else {
|
|
hline(buffer32, 0, dev->displine, dev->screenw, dev->palette[0]);
|
|
}
|
|
|
|
if (++dev->displine == dev->screenh) {
|
|
dev->mapram[0x3da] |= 8;
|
|
dev->cgadispon = 0;
|
|
}
|
|
|
|
if (dev->displine == dev->screenh + 32) {
|
|
dev->mapram[0x3da] &= ~8;
|
|
dev->cgadispon = 1;
|
|
dev->displine = 0;
|
|
}
|
|
} else {
|
|
if (dev->cgadispon)
|
|
dev->mapram[0x3da] &= ~1;
|
|
timer_advance_u64(&dev->timer, dev->dispontime);
|
|
dev->linepos = 0;
|
|
|
|
if (dev->displine == dev->screenh) {
|
|
if (dev->screenw != xsize || dev->screenh != ysize) {
|
|
xsize = dev->screenw;
|
|
ysize = dev->screenh;
|
|
set_screen_size(xsize, ysize);
|
|
|
|
if (video_force_resize_get())
|
|
video_force_resize_set(0);
|
|
}
|
|
video_blit_memtoscreen(0, 0, xsize, ysize);
|
|
frames++;
|
|
|
|
video_res_x = dev->screenw;
|
|
video_res_y = dev->screenh;
|
|
video_bpp = 8;
|
|
dev->cgablink++;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
pgc_speed_changed(void *priv)
|
|
{
|
|
pgc_t *dev = (pgc_t *) priv;
|
|
|
|
pgc_recalctimings(dev);
|
|
}
|
|
|
|
void
|
|
pgc_close_common(void *priv)
|
|
{
|
|
pgc_t *dev = (pgc_t *) priv;
|
|
|
|
/*
|
|
* Close down the worker thread by setting a
|
|
* flag, and then simulating a reset so it
|
|
* stops reading data.
|
|
*/
|
|
#ifdef ENABLE_PGC_LOG
|
|
pgc_log("PGC: telling thread to stop...\n");
|
|
#endif
|
|
dev->stopped = 1;
|
|
dev->mapram[0x3ff] = 1;
|
|
if (dev->waiting_input_fifo || dev->waiting_output_fifo) {
|
|
/* Do an immediate wake-up. */
|
|
wake_timer(priv);
|
|
}
|
|
|
|
/* Wait for thread to stop. */
|
|
#ifdef ENABLE_PGC_LOG
|
|
pgc_log("PGC: waiting for thread to stop...\n");
|
|
#endif
|
|
// while (dev->stopped);
|
|
thread_wait(dev->pgc_thread);
|
|
#ifdef ENABLE_PGC_LOG
|
|
pgc_log("PGC: thread stopped, closing up.\n");
|
|
#endif
|
|
|
|
if (dev->cga_vram)
|
|
free(dev->cga_vram);
|
|
if (dev->vram)
|
|
free(dev->vram);
|
|
}
|
|
|
|
void
|
|
pgc_close(void *priv)
|
|
{
|
|
pgc_t *dev = (pgc_t *) priv;
|
|
|
|
pgc_close_common(priv);
|
|
|
|
free(dev);
|
|
}
|
|
|
|
/*
|
|
* Initialization code common to the PGC and its subclasses.
|
|
*
|
|
* Pass the 'input byte' function in since this is overridden in
|
|
* the IM-1024, and needs to be set before the drawing thread is
|
|
* launched.
|
|
*/
|
|
void
|
|
pgc_init(pgc_t *dev, int maxw, int maxh, int visw, int vish,
|
|
int (*inpbyte)(pgc_t *, uint8_t *), double npc)
|
|
{
|
|
int i;
|
|
|
|
/* Make it a 16k mapping at C4000 (will be C4000-C7FFF),
|
|
because of the emulator's granularity - the original
|
|
mapping will conflict with hard disk controller BIOS'es. */
|
|
mem_mapping_add(&dev->mapping, 0xc4000, 16384,
|
|
pgc_read, NULL, NULL, pgc_write, NULL, NULL,
|
|
NULL, MEM_MAPPING_EXTERNAL, dev);
|
|
mem_mapping_add(&dev->cga_mapping, 0xb8000, 32768,
|
|
pgc_read, NULL, NULL, pgc_write, NULL, NULL,
|
|
NULL, MEM_MAPPING_EXTERNAL, dev);
|
|
|
|
io_sethandler(0x03d0, 16,
|
|
pgc_in, NULL, NULL, pgc_out, NULL, NULL, dev);
|
|
|
|
dev->maxw = maxw;
|
|
dev->maxh = maxh;
|
|
dev->visw = visw;
|
|
dev->vish = vish;
|
|
|
|
dev->vram = (uint8_t *) malloc((size_t) maxw * maxh);
|
|
memset(dev->vram, 0x00, (size_t) maxw * maxh);
|
|
dev->cga_vram = (uint8_t *) malloc(16384);
|
|
memset(dev->cga_vram, 0x00, 16384);
|
|
|
|
/* Create and initialize command lists. */
|
|
dev->clist = (pgc_cl_t *) malloc(256 * sizeof(pgc_cl_t));
|
|
memset(dev->clist, 0x00, 256 * sizeof(pgc_cl_t));
|
|
for (i = 0; i < 256; i++) {
|
|
dev->clist[i].list = NULL;
|
|
dev->clist[i].listmax = 0;
|
|
dev->clist[i].wrptr = 0;
|
|
dev->clist[i].rdptr = 0;
|
|
dev->clist[i].repeat = 0;
|
|
dev->clist[i].chain = NULL;
|
|
}
|
|
dev->clcur = NULL;
|
|
dev->native_pixel_clock = npc;
|
|
|
|
pgc_reset(dev);
|
|
|
|
dev->inputbyte = inpbyte;
|
|
dev->master = dev->commands = pgc_commands;
|
|
dev->pgc_wake_thread = thread_create_event();
|
|
dev->pgc_thread = thread_create(pgc_thread, dev);
|
|
|
|
timer_add(&dev->timer, pgc_poll, dev, 1);
|
|
|
|
timer_add(&dev->wake_timer, wake_timer, dev, 0);
|
|
}
|
|
|
|
static void *
|
|
pgc_standalone_init(const device_t *info)
|
|
{
|
|
pgc_t *dev;
|
|
|
|
dev = (pgc_t *) malloc(sizeof(pgc_t));
|
|
memset(dev, 0x00, sizeof(pgc_t));
|
|
dev->type = info->local;
|
|
|
|
/* Framebuffer and screen are both 640x480. */
|
|
pgc_init(dev, 640, 480, 640, 480, input_byte, 25175000.0);
|
|
|
|
video_inform(VIDEO_FLAG_TYPE_CGA, &timing_pgc);
|
|
|
|
return (dev);
|
|
}
|
|
|
|
const device_t pgc_device = {
|
|
.name = "PGC",
|
|
.internal_name = "pgc",
|
|
.flags = DEVICE_ISA,
|
|
.local = 0,
|
|
.init = pgc_standalone_init,
|
|
.close = pgc_close,
|
|
.reset = NULL,
|
|
{ .available = NULL },
|
|
.speed_changed = pgc_speed_changed,
|
|
.force_redraw = NULL,
|
|
.config = NULL
|
|
};
|