/*PGC emulation*/ #include #include #include #include "ibm.h" #include "device.h" #include "io.h" #include "mem.h" #include "timer.h" #include "thread.h" #include "video.h" #include "vid_pgc.h" #include /* 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 behaviour with a separate thread. */ #define PGC_CGA_WIDTH 640 #define PGC_CGA_HEIGHT 400 static int pgc_parse_command(pgc_core_t *pgc, const pgc_command_t **pcmd); int pgc_clist_byte(pgc_core_t *pgc, uint8_t *val); 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" }; #define HWORD(u) ((u) >> 16) #define LWORD(u) ((u) & 0xFFFF) /* Initial palettes */ uint32_t init_palette[6][256] = { #include "pgc_palettes.h" }; /* 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 */ #define WAKE_DELAY (TIMER_USEC * 500) void pgc_wake(pgc_core_t *pgc) { if (!timer_is_enabled(&pgc->wake_timer)) { timer_set_delay_u64(&pgc->wake_timer, WAKE_DELAY); } } /* When the wake timer expires, that's when the drawing thread is actually * woken */ static void pgc_wake_timer(void *p) { pgc_core_t *pgc = (pgc_core_t *)p; PGCLOG(("pgc: Waking up\n")); thread_set_event(pgc->pgc_wake_thread); } /* This is called by the drawing thread when it's waiting for the host * to put more input in its input FIFO, or drain output from its output * FIFO. */ void pgc_sleep(pgc_core_t *pgc) { PGCLOG(("pgc: Sleeping on %d %d %d 0x%02x 0x%02x\n", pgc->waiting_input_fifo, pgc->waiting_output_fifo, pgc->waiting_error_fifo, pgc->mapram[0x300], pgc->mapram[0x301])); /* Race condition: If host wrote to the PGC during the pclog() that * won't be noticed */ if (pgc->waiting_input_fifo && pgc->mapram[0x300] != pgc->mapram[0x301]) { pgc->waiting_input_fifo = 0; return; } /* Same if they read */ if (pgc->waiting_output_fifo && pgc->mapram[0x302] != (uint8_t)(pgc->mapram[0x303] - 1)) { pgc->waiting_output_fifo = 0; return; } thread_wait_event(pgc->pgc_wake_thread, -1); thread_reset_event(pgc->pgc_wake_thread); } /* Switch between CGA mode (DISPLAY 1) and native mode (DISPLAY 0) */ void pgc_setdisplay(pgc_core_t *pgc, int cga) { PGCLOG(("pgc_setdisplay(%d): cga_selected=%d cga_enabled=%d\n", cga, pgc->cga_selected, pgc->cga_enabled)); if (pgc->cga_selected != (pgc->cga_enabled && cga)) { pgc->cga_selected = (pgc->cga_enabled && cga); if (pgc->cga_selected) { mem_mapping_enable(&pgc->cga_mapping); pgc->screenw = PGC_CGA_WIDTH; pgc->screenh = PGC_CGA_HEIGHT; } else { mem_mapping_disable(&pgc->cga_mapping); pgc->screenw = pgc->visw; pgc->screenh = pgc->vish; } pgc_recalctimings(pgc); } } /* Convert coordinates based on the current window / viewport to raster * coordinates. */ void pgc_dto_raster(pgc_core_t *pgc, double *x, double *y) { /* double x0 = *x, y0 = *y; */ *x += (pgc->vp_x1 - pgc->win_x1); *y += (pgc->vp_y1 - pgc->win_y1); /* PGCLOG(("Coords to raster: (%f, %f) -> (%f, %f)\n", x0, y0, *x, *y)); */ } /* Overloads that take ints */ void pgc_sto_raster(pgc_core_t *pgc, int16_t *x, int16_t *y) { double xd = *x, yd = *y; pgc_dto_raster(pgc, &xd, &yd); *x = (int16_t)xd; *y = (int16_t)yd; } void pgc_ito_raster(pgc_core_t *pgc, int32_t *x, int32_t *y) { double xd = *x, yd = *y; pgc_dto_raster(pgc, &xd, &yd); *x = (int32_t)xd; *y = (int32_t)yd; } /* Add a byte to a command list. We allow command lists to be * arbitrarily large */ int pgc_commandlist_append(pgc_commandlist_t *list, uint8_t v) { if (list->listmax == 0 || list->list == NULL) { list->list = malloc(4096); if (!list->list) { PGCLOG(("Out of memory initialising command list\n")); return 0; } list->listmax = 4096; } while (list->wrptr >= list->listmax) { uint8_t *buf = realloc(list->list, 2 * list->listmax); if (!buf) { PGCLOG(("Out of memory growing command list\n")); return 0; } list->list = buf; list->listmax *= 2; } list->list[list->wrptr++] = v; 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_core_t *pgc) { uint8_t param; pgc_commandlist_t cl; const pgc_command_t *cmd; memset(&cl, 0, sizeof(cl)); if (!pgc_param_byte(pgc, ¶m)) return; PGCLOG(("CLBEG(%d)\n", param)); while (1) { if (!pgc_parse_command(pgc, &cmd)) { /* PGC has been reset */ return; } if (!cmd) { pgc_error(pgc, PGC_ERROR_OPCODE); return; } else if (pgc->hex_command == 0x71) /* CLEND */ { pgc->clist[param] = cl; return; } else { if (!pgc_commandlist_append(&cl, pgc->hex_command)) { pgc_error(pgc, PGC_ERROR_OVERFLOW); return; } if (cmd->parser) { if (!(*cmd->parser)(pgc, &cl, cmd->p)) { return; } } } } } static void hndl_clend(pgc_core_t *pgc) { /* Shouldn't 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_core_t *pgc) { uint8_t param; pgc_commandlist_t *clprev = pgc->clcur; if (!pgc_param_byte(pgc, ¶m)) return; pgc->clcur = &pgc->clist[param]; pgc->clcur->rdptr = 0; pgc->clcur->repeat = 1; pgc->clcur->chain = clprev; } /* Execute a command list multiple times. */ static void hndl_cloop(pgc_core_t *pgc) { uint8_t param; int16_t repeat; pgc_commandlist_t *clprev = pgc->clcur; if (!pgc_param_byte(pgc, ¶m)) return; if (!pgc_param_word(pgc, &repeat)) return; pgc->clcur = &pgc->clist[param]; pgc->clcur->rdptr = 0; pgc->clcur->repeat = repeat; pgc->clcur->chain = clprev; } /* Read back a command list */ static void hndl_clread(pgc_core_t *pgc) { uint8_t param; int n; if (!pgc_param_byte(pgc, ¶m)) return; for (n = 0; n < pgc->clist[param].wrptr; n++) { if (!pgc_result_byte(pgc, pgc->clist[param].list[n])) return; } } /* Delete a command list */ static void hndl_cldel(pgc_core_t *pgc) { uint8_t param; if (!pgc_param_byte(pgc, ¶m)) return; memset(&pgc->clist[param], 0, sizeof(pgc_commandlist_t)); } /* Clear the screen to a specified colour */ static void hndl_clears(pgc_core_t *pgc) { uint8_t param; int y; if (!pgc_param_byte(pgc, ¶m)) return; for (y = 0; y < pgc->screenh; y++) memset(pgc->vram + y * pgc->maxw, param, pgc->screenw); } /* Select drawing colour */ static void hndl_color(pgc_core_t *pgc) { uint8_t param; if (!pgc_param_byte(pgc, ¶m)) return; pgc->colour = param; PGCLOG(("COLOR(%d)\n", param)); } /* Set drawing mode. * * 0 => Draw * 1 => Invert * 2 => XOR (IM-1024) * 3 => AND (IM-1024) * */ static void hndl_linfun(pgc_core_t *pgc) { uint8_t param; if (!pgc_param_byte(pgc, ¶m)) return; /* TODO: Not range-checked. Strictly speaking we should limit to 0-1 * for the PGC and 0-3 for the IM-1024. */ pgc->draw_mode = param; PGCLOG(("LINFUN(%d)\n", param)); } /* Set the line drawing pattern */ static void hndl_linpat(pgc_core_t *pgc) { uint16_t param; if (!pgc_param_word(pgc, (int16_t *)¶m)) return; pgc->line_pattern = param; PGCLOG(("LINPAT(0x%04x)\n", param)); } /* Set the polygon fill mode (0=hollow, 1=filled, 2=fast fill) */ static void hndl_prmfil(pgc_core_t *pgc) { uint8_t param; if (!pgc_param_byte(pgc, ¶m)) return; PGCLOG(("PRMFIL(%d)\n", param)); if (param < 3) { pgc->fill_mode = param; } else { pgc_error(pgc, PGC_ERROR_RANGE); } } /* Set the 2D drawing position */ static void hndl_move(pgc_core_t *pgc) { int32_t x = 0, y = 0; if (!pgc_param_coord(pgc, &x)) return; if (!pgc_param_coord(pgc, &y)) return; pgc->x = x; pgc->y = y; PGCLOG(("MOVE %x.%04x,%x.%04x\n", HWORD(x), LWORD(x), HWORD(y), LWORD(y))); } /* Set the 3D drawing position */ static void hndl_move3(pgc_core_t *pgc) { int32_t x = 0, y = 0, z = 0; if (!pgc_param_coord(pgc, &x)) return; if (!pgc_param_coord(pgc, &y)) return; if (!pgc_param_coord(pgc, &z)) return; pgc->x = x; pgc->y = y; pgc->z = z; } /* Relative move (2D) */ static void hndl_mover(pgc_core_t *pgc) { int32_t x = 0, y = 0; if (!pgc_param_coord(pgc, &x)) return; if (!pgc_param_coord(pgc, &y)) return; pgc->x += x; pgc->y += y; } /* Relative move (3D) */ static void hndl_mover3(pgc_core_t *pgc) { int32_t x = 0, y = 0, z = 0; if (!pgc_param_coord(pgc, &x)) return; if (!pgc_param_coord(pgc, &y)) return; if (!pgc_param_coord(pgc, &z)) return; pgc->x += x; pgc->y += y; pgc->z += z; } /* Draw a line (using PGC fixed-point coordinates) */ uint16_t pgc_draw_line(pgc_core_t *pgc, int32_t x0, int32_t y0, int32_t x1, int32_t y1, uint16_t linemask) { PGCLOG(("pgc_draw_line: (%d,%d) to (%d,%d)\n", x0 >> 16, y0 >> 16, x1 >> 16, y1 >> 16)); /* Convert from PGC fixed-point to device coordinates */ x0 >>= 16; x1 >>= 16; y0 >>= 16; y1 >>= 16; pgc_ito_raster(pgc, &x0, &y0); pgc_ito_raster(pgc, &x1, &y1); return pgc_draw_line_r(pgc, x0, y0, x1, y1, linemask); } /* Draw a line (using raster coordinates) Bresenham's Algorithm from * * The line pattern mask to use is passed in. The return value is the line * pattern mask rotated by the number of points drawn. */ uint16_t pgc_draw_line_r(pgc_core_t *pgc, 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(pgc, 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 horizontal line in the current fill pattern * (using raster coordinates) */ void pgc_fill_line_r(pgc_core_t *pgc, int32_t x0, int32_t x1, int32_t y0) { int32_t x; int32_t mask = 0x8000 >> (x0 & 0x0F); if (x0 > x1) { x = x1; x1 = x0; x0 = x; } for (x = x0; x <= x1; x++) { if (pgc->fill_pattern[y0 & 0x0F] & mask) pgc_plot(pgc, 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_core_t *pgc, unsigned corners, int32_t *x, int32_t *y) { double *nodex; double *dx; double *dy; unsigned n, nodes, i, j; double ymin, ymax, ypos; PGCLOG(("pgc_fill_polygon(%d corners)\n", corners)); if (corners < 2) return; /* Degenerate polygon */ nodex = malloc(corners * sizeof(double)); dx = malloc(corners * sizeof(double)); dy = malloc(corners * sizeof(double)); if (!nodex || !dx || !dy) 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 */ /* 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); /* PGCLOG(("pgc_fill_polygon ypos=%f nodes=%d ", ypos, nodes)); for (i = 0; i < nodes; i++) { PGCLOG(("%f;", nodex[i])); } PGCLOG(("\n")); */ /* And fill between them */ for (i = 0; i < nodes; i += 2) { int16_t x1 = nodex[i], x2 = nodex[i + 1], y1 = ypos, y2 = ypos; pgc_sto_raster(pgc, &x1, &y1); pgc_sto_raster(pgc, &x2, &y2); /* PGCLOG(("pgc_fill_polygon raster %d,%d to %d,%d\n", x1, y1, x2, y2)); */ pgc_fill_line_r(pgc, x1, x2, y1); } } free(nodex); free(dx); free(dy); } /* Draw a filled ellipse (using PGC fixed-point coordinates) */ void pgc_draw_ellipse(pgc_core_t *pgc, 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 = pgc->y / 65536.0; double x0 = pgc->x / 65536.0; double ypos = 0.0, xpos = 0.0; double x1; double xlast = 0.0; int16_t linemask = pgc->line_pattern; pgc_dto_raster(pgc, &x0, &y0); PGCLOG(("Ellipse: Colour=%d Drawmode=%d fill=%d\n", pgc->colour, pgc->draw_mode, pgc->fill_mode)); for (ypos = 0; ypos <= h; ypos++) { if (ypos == 0) { if (pgc->fill_mode) { pgc_fill_line_r(pgc, x0 - w, x0 + w, y0); } if (linemask & 0x8000) { pgc_plot(pgc, x0 + w, y0); pgc_plot(pgc, x0 - w, y0); linemask = (linemask << 1) | 1; } else { linemask = linemask << 1; } xlast = w; } else { x1 = sqrt((h * h) - (ypos * ypos)) * w / h; if (pgc->fill_mode) { pgc_fill_line_r(pgc, x0 - x1, x0 + x1, y0 + ypos); pgc_fill_line_r(pgc, x0 - x1, x0 + x1, y0 - ypos); } /* Draw border */ for (xpos = xlast; xpos >= x1; xpos--) { if (linemask & 0x8000) { pgc_plot(pgc, x0 + xpos, y0 + ypos); pgc_plot(pgc, x0 - xpos, y0 + ypos); pgc_plot(pgc, x0 + xpos, y0 - ypos); pgc_plot(pgc, x0 - xpos, y0 - ypos); linemask = (linemask << 1) | 1; } else { linemask = linemask << 1; } } xlast = x1; } } } /* Handle the ELIPSE (sic) command */ static void hndl_ellipse(pgc_core_t *pgc) { int32_t x = 0, y = 0; if (!pgc_param_coord(pgc, &x)) return; if (!pgc_param_coord(pgc, &y)) return; pgc_draw_ellipse(pgc, x, y); } /* Handle the POLY command */ static void hndl_poly(pgc_core_t *pgc) { uint8_t count; int32_t x[256]; int32_t y[256]; int32_t n; if (!pgc_param_byte(pgc, &count)) return; for (n = 0; n < count; n++) { if (!pgc_param_coord(pgc, &x[n])) return; if (!pgc_param_coord(pgc, &y[n])) return; } PGCLOG(("POLY (%d)\n", count)); } /* Parse but don't execute a POLY command (for adding to a command list) */ static int parse_poly(pgc_core_t *pgc, pgc_commandlist_t *cl, int c) { uint8_t count; PGCLOG(("parse_poly\n")); if (!pgc_param_byte(pgc, &count)) return 0; PGCLOG(("parse_poly: count=%02x\n", count)); if (!pgc_commandlist_append(cl, count)) { pgc_error(pgc, PGC_ERROR_OVERFLOW); return 0; } PGCLOG(("parse_poly: parse %d coords\n", 2 * count)); return pgc_parse_coords(pgc, cl, 2 * count); } /* Parse but don't execute a command with a fixed number of byte parameters */ int pgc_parse_bytes(pgc_core_t *pgc, pgc_commandlist_t *cl, int count) { uint8_t *param = malloc(count); int n; if (!param) { pgc_error(pgc, PGC_ERROR_OVERFLOW); return 0; } for (n = 0; n < count; n++) { if (!pgc_param_byte(pgc, ¶m[n])) { free(param); return 0; } } for (n = 0; n < count; n++) { if (!pgc_commandlist_append(cl, param[n])) { pgc_error(pgc, 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_core_t *pgc, pgc_commandlist_t *cl, int count) { int16_t *param = malloc(count * sizeof(int16_t)); int n; if (!param) { pgc_error(pgc, PGC_ERROR_OVERFLOW); return 0; } for (n = 0; n < count; n++) { if (!pgc_param_word(pgc, ¶m[n])) return 0; } for (n = 0; n < count; n++) { if (!pgc_commandlist_append(cl, param[n] & 0xFF) || !pgc_commandlist_append(cl, param[n] >> 8)) { pgc_error(pgc, PGC_ERROR_OVERFLOW); free(param); return 0; } } return 1; } /* Parse but don't execute a command with a fixed number of coord parameters */ int pgc_parse_coords(pgc_core_t *pgc, pgc_commandlist_t *cl, int count) { int32_t *param = malloc(count * sizeof(int32_t)); int n; if (!param) { pgc_error(pgc, PGC_ERROR_OVERFLOW); return 0; } for (n = 0; n < count; n++) { if (!pgc_param_coord(pgc, ¶m[n])) return 0; } /* Here's how the real PGC serialises 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++) { /* Serialise integer part */ if (!pgc_commandlist_append(cl, (param[n] >> 16) & 0xFF) || !pgc_commandlist_append(cl, (param[n] >> 24) & 0xFF) || /* Serialise fraction part */ !pgc_commandlist_append(cl, (param[n] ) & 0xFF) || !pgc_commandlist_append(cl, (param[n] >> 8) & 0xFF)) { pgc_error(pgc, PGC_ERROR_OVERFLOW); free(param); return 0; } } return 1; } /* Handle the DISPLAY command */ static void hndl_display(pgc_core_t *pgc) { uint8_t param; if (!pgc_param_byte(pgc, ¶m)) return; PGCLOG(("DISPLAY(%d)\n", param)); if (param > 1) { pgc_error(pgc, PGC_ERROR_RANGE); } else { pgc_setdisplay(pgc, param); } } /* Handle the IMAGEW command (memory to screen blit) */ static void hndl_imagew(pgc_core_t *pgc) { int16_t row, col1, col2; uint8_t v1, v2; if (!pgc_param_word(pgc, &row)) return; if (!pgc_param_word(pgc, &col1)) return; if (!pgc_param_word(pgc, &col2)) return; if (row >= pgc->screenh || col1 >= pgc->maxw || col2 >= pgc->maxw) { pgc_error(pgc, PGC_ERROR_RANGE); return; } /* In ASCII mode, what is written is a stream of bytes */ if (pgc->ascii_mode) { while (col1 <= col2) { if (!pgc_param_byte(pgc, &v1)) return; pgc_write_pixel(pgc, col1, row, v1); ++col1; } return; } else /* In hex mode, it's RLE compressed */ { while (col1 <= col2) { if (!pgc_param_byte(pgc, &v1)) return; if (v1 & 0x80) /* Literal run */ { v1 -= 0x7F; while (col1 <= col2 && v1 != 0) { if (!pgc_param_byte(pgc, &v2)) return; pgc_write_pixel(pgc, col1, row, v2); ++col1; --v1; } } else /* Repeated run */ { if (!pgc_param_byte(pgc, &v2)) return; ++v1; while (col1 <= col2 && v1 != 0) { pgc_write_pixel(pgc, col1, row, v2); ++col1; --v1; } } } } } /* Select one of the built-in palettes */ static void pgc_init_lut(pgc_core_t *pgc, int param) { if (param >= 0 && param < 6) { memcpy(pgc->palette, init_palette[param], sizeof(pgc->palette)); } else if (param == 0xFF) { memcpy(pgc->palette, pgc->userpal, sizeof(pgc->palette)); } else { pgc_error(pgc, PGC_ERROR_RANGE); } } /* Save the current palette */ static void hndl_lutsav(pgc_core_t *pgc) { memcpy(pgc->userpal, pgc->palette, sizeof(pgc->palette)); } /* Handle LUTINT (select palette) */ static void hndl_lutint(pgc_core_t *pgc) { uint8_t param; if (!pgc_param_byte(pgc, ¶m)) return; pgc_init_lut(pgc, param); } /* Handle LUTRD (read palette register) */ static void hndl_lutrd(pgc_core_t *pgc) { uint8_t param; uint32_t col; if (!pgc_param_byte(pgc, ¶m)) return; col = pgc->palette[param]; pgc_result_byte(pgc, (col >> 20) & 0x0F); pgc_result_byte(pgc, (col >> 12) & 0x0F); pgc_result_byte(pgc, (col >> 4) & 0x0F); } /* Handle LUT (write palette register) */ static void hndl_lut(pgc_core_t *pgc) { uint8_t param[4]; int n; for (n = 0; n < 4; n++) { if (!pgc_param_byte(pgc, ¶m[n])) return; if (n > 0 && param[n] > 15) { pgc_error(pgc, PGC_ERROR_RANGE); param[n] &= 0x0F; } } pgc->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_core_t *pgc) { uint8_t param; uint32_t col; if (!pgc_param_byte(pgc, ¶m)) return; col = pgc->palette[param]; pgc_result_byte(pgc, (col >> 16) & 0xFF); pgc_result_byte(pgc, (col >> 8) & 0xFF); pgc_result_byte(pgc, col & 0xFF); } void pgc_hndl_lut8(pgc_core_t *pgc) { uint8_t param[4]; int n; for (n = 0; n < 4; n++) { if (!pgc_param_byte(pgc, ¶m[n])) return; } pgc->palette[param[0]] = makecol((param[1]), (param[2]), (param[3])); } /* Handle AREAPT (set 16x16 fill pattern) */ static void hndl_areapt(pgc_core_t *pgc) { int16_t pattern[16]; int n; for (n = 0; n < 16; n++) { if (!pgc_param_word(pgc, &pattern[n])) return; } memcpy(pgc->fill_pattern, pattern, sizeof(pgc->fill_pattern)); PGCLOG(("AREAPT(%04x %04x %04x %04x...)\n", pattern[0] & 0xFFFF, pattern[1] & 0xFFFF, pattern[2] & 0xFFFF, pattern[3] & 0xFFFF)); } /* Handle CA (select ASCII mode) */ static void hndl_ca(pgc_core_t *pgc) { pgc->ascii_mode = 1; } /* Handle CX (select hex mode) */ static void hndl_cx(pgc_core_t *pgc) { pgc->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_core_t *pgc) { uint8_t param; if (!pgc->inputbyte(pgc, ¶m)) return; if (param == 'A') pgc->ascii_mode = 1; if (param == 'X') pgc->ascii_mode = 0; } /* RESETF resets the PGC */ static void hndl_resetf(pgc_core_t *pgc) { pgc_reset(pgc); } /* TJUST sets text justify settings */ static void hndl_tjust(pgc_core_t *pgc) { uint8_t param[2]; if (!pgc->inputbyte(pgc, ¶m[0])) return; if (!pgc->inputbyte(pgc, ¶m[1])) return; if (param[0] >= 1 && param[0] <= 3 && param[1] >= 1 && param[1] <= 3) { pgc->tjust_h = param[0]; pgc->tjust_v = param[1]; } else { pgc_error(pgc, PGC_ERROR_RANGE); } } /* TSIZE controls text horizontal spacing */ static void hndl_tsize(pgc_core_t *pgc) { int32_t param = 0; if (!pgc_param_coord(pgc, ¶m)) return; pgc->tsize = param; PGCLOG(("TSIZE %d\n", 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_core_t *pgc) { int16_t x1, x2, y1, y2; if (!pgc_param_word(pgc, &x1)) return; if (!pgc_param_word(pgc, &x2)) return; if (!pgc_param_word(pgc, &y1)) return; if (!pgc_param_word(pgc, &y2)) return; PGCLOG(("VWPORT %d,%d,%d,%d\n", x1,x2,y1,y2)); pgc->vp_x1 = x1; pgc->vp_x2 = x2; pgc->vp_y1 = y1; pgc->vp_y2 = y2; } /* WINDOW defines the coordinate system in use */ static void hndl_window(pgc_core_t *pgc) { int16_t x1, x2, y1, y2; if (!pgc_param_word(pgc, &x1)) return; if (!pgc_param_word(pgc, &x2)) return; if (!pgc_param_word(pgc, &y1)) return; if (!pgc_param_word(pgc, &y2)) return; PGCLOG(("WINDOW %d,%d,%d,%d\n", x1,x2,y1,y2)); pgc->win_x1 = x1; pgc->win_x2 = x2; pgc->win_y1 = y1; pgc->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_command_t pgc_core_commands[] = { { "AREAPT", 0xE7, hndl_areapt, pgc_parse_words, 16 }, { "AP", 0xE7, hndl_areapt, pgc_parse_words, 16 }, { "~~~~~~", 0x43, hndl_c }, /* Handle CA / CX in hex mode */ { "CA", 0xD2, hndl_ca }, { "CLBEG", 0x70, hndl_clbeg }, { "CB", 0x70, hndl_clbeg }, { "CLDEL", 0x74, hndl_cldel, pgc_parse_bytes, 1 }, { "CD", 0x74, hndl_cldel, pgc_parse_bytes, 1 }, { "CLEND", 0x71, hndl_clend }, { "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 }, { "CL", 0x73, hndl_cloop }, { "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 }, { "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 }, { "IW", 0xD9, hndl_imagew }, { "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 }, { "P", 0x30, hndl_poly, parse_poly }, { "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 } }; /* Writes to CGA registers are copied into the transfer memory buffer */ void pgc_out(uint16_t addr, uint8_t val, void *p) { pgc_core_t *pgc = (pgc_core_t *)p; switch(addr) { case 0x3D0: case 0x3D2: case 0x3D4: case 0x3D6: pgc->mapram[0x3D0] = val; break; case 0x3D1: case 0x3D3: case 0x3D5: case 0x3D7: if (pgc->mapram[0x3D0] < 18) { pgc->mapram[0x3E0 + pgc->mapram[0x3D0]] = val; } break; case 0x3D8: pgc->mapram[0x3D8] = val; break; case 0x3D9: pgc->mapram[0x3D9] = val; break; } } /* Read back the CGA registers */ uint8_t pgc_in(uint16_t addr, void *p) { pgc_core_t *pgc = (pgc_core_t *)p; switch(addr) { case 0x3D0: case 0x3D2: case 0x3D4: case 0x3D6: return pgc->mapram[0x3D0]; case 0x3D1: case 0x3D3: case 0x3D5: case 0x3D7: if (pgc->mapram[0x3D0] < 18) { return pgc->mapram[0x3E0 + pgc->mapram[0x3D0]]; } return 0xFF; case 0x3D8: return pgc->mapram[0x3D8]; case 0x3D9: return pgc->mapram[0x3D9]; case 0x3DA: return pgc->mapram[0x3DA]; } return 0xFF; } /* Memory write to the transfer buffer */ void pgc_write(uint32_t addr, uint8_t val, void *p) { pgc_core_t *pgc = (pgc_core_t *)p; /* 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 (pgc->mapram[addr] != val) { pgc->mapram[addr] = val; switch (addr) { case 0x300: /* Input write pointer */ if (pgc->waiting_input_fifo && pgc->mapram[0x300] != pgc->mapram[0x301]) { pgc->waiting_input_fifo = 0; pgc_wake(pgc); } break; case 0x303: /* Output read pointer */ if (pgc->waiting_output_fifo && pgc->mapram[0x302] != (uint8_t)(pgc->mapram[0x303] - 1)) { pgc->waiting_output_fifo = 0; pgc_wake(pgc); } break; case 0x305: /* Error read pointer */ if (pgc->waiting_error_fifo && pgc->mapram[0x304] != (uint8_t)(pgc->mapram[0x305] - 1)) { pgc->waiting_error_fifo = 0; pgc_wake(pgc); } break; case 0x306: /* Cold start flag */ /* XXX This should be in IM-1024 specific code */ pgc->mapram[0x306] = 0; break; case 0x030C: /* Display type */ pgc_setdisplay(p, pgc->mapram[0x30C]); pgc->mapram[0x30D] = pgc->mapram[0x30C]; break; case 0x3FF: /* Reboot the PGC (handled on core thread) */ pgc_wake(pgc); break; } // end switch (addr) } } if (addr >= 0xB8000 && addr < 0xBC000 && pgc->cga_selected) { addr &= 0x3FFF; pgc->cga_vram[addr] = val; } } uint8_t pgc_read(uint32_t addr, void *p) { pgc_core_t *pgc = (pgc_core_t *)p; if (addr >= 0xC6000 && addr < 0xC6800) { addr &= 0x7FF; return pgc->mapram[addr]; } if (addr >= 0xB8000 && addr < 0xBC000 && pgc->cga_selected) { addr &= 0x3FFF; return pgc->cga_vram[addr]; } return 0xFF; } /* Called by the drawing thread to read the 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 */ int pgc_input_byte(pgc_core_t *pgc, uint8_t *result) { /* If input buffer empty, wait for it to fill */ while (pgc->mapram[0x300] == pgc->mapram[0x301]) { pgc->waiting_input_fifo = 1; pgc_sleep(pgc); } if (pgc->mapram[0x3FF]) /* Reset triggered */ { pgc_reset(pgc); return 0; } *result = pgc->mapram[pgc->mapram[0x301]]; ++pgc->mapram[0x301]; return 1; } /* Called by the drawing thread to 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 */ int pgc_output_byte(pgc_core_t *pgc, uint8_t val) { /* If output buffer full, wait for it to empty */ while (pgc->mapram[0x302] == (uint8_t)(pgc->mapram[0x303] - 1)) { PGCLOG(("Output buffer state: %02x %02x Sleeping\n", pgc->mapram[0x302], pgc->mapram[0x303])); pgc->waiting_output_fifo = 1; pgc_sleep(pgc); } if (pgc->mapram[0x3FF]) /* Reset triggered */ { pgc_reset(pgc); return 0; } pgc->mapram[0x100 + pgc->mapram[0x302]] = val; ++pgc->mapram[0x302]; /* PGCLOG(("Output %02x: new state: %02x %02x\n", val, pgc->mapram[0x302], pgc->mapram[0x303])); */ return 1; } /* Helper to write an entire string to the output buffer */ int pgc_output_string(pgc_core_t *pgc, const char *s) { while (*s) { if (!pgc_output_byte(pgc, *s)) return 0; ++s; } return 1; } /* As pgc_output_byte, for the error buffer */ int pgc_error_byte(pgc_core_t *pgc, uint8_t val) { /* If error buffer full, wait for it to empty */ while (pgc->mapram[0x304] == pgc->mapram[0x305] - 1) { pgc->waiting_error_fifo = 1; pgc_sleep(pgc); } if (pgc->mapram[0x3FF]) /* Reset triggered */ { pgc_reset(pgc); return 0; } pgc->mapram[0x200 + pgc->mapram[0x304]] = val; ++pgc->mapram[0x304]; return 1; } /* As pgc_output_string, for the error buffer */ int pgc_error_string(pgc_core_t *pgc, const char *s) { while (*s) { if (!pgc_error_byte(pgc, *s)) return 0; ++s; } return 1; } /* Report an error, either in ASCII or in hex */ int pgc_error(pgc_core_t *pgc, int err) { if (pgc->mapram[0x307]) /* Errors enabled? */ { if (pgc->ascii_mode) { if (err >= PGC_ERROR_RANGE && err <= PGC_ERROR_MISSING) return pgc_error_string(pgc, pgc_err_msgs[err]); return pgc_error_string(pgc, "Unknown error\r"); } else { return pgc_error_byte(pgc, err); } } return 1; } static inline int is_whitespace(char ch) { return (ch != 0 && strchr(" \r\n\t,;()+-", ch) != NULL); } /* Read a byte and interpret as ASCII: ignore control characters other than * CR, LF or tab. */ int pgc_input_char(pgc_core_t *pgc, char *result) { uint8_t ch; while (1) { if (!pgc->inputbyte(pgc, &ch)) return 0; ch &= 0x7F; if (ch == '\r' || ch == '\n' || ch == '\t' || ch >= ' ') { *result = toupper(ch); return 1; } } } /* Parameter passed is not a number: abort */ static int err_digit(pgc_core_t *pgc) { uint8_t asc; do /* Swallow everything until the next separator */ { if (!pgc->inputbyte(pgc, &asc)) return 0; } while (!is_whitespace(asc)); pgc_error(pgc, PGC_ERROR_DIGIT); return 0; } typedef enum parse_state_t { 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_core_t *pgc, 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 (pgc->clcur) { for (n = 0; n < 4; n++) { if (!pgc_clist_byte(pgc, &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 (!pgc->ascii_mode) { for (n = 0; n < 4; n++) { if (!pgc->inputbyte(pgc, &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 (!pgc->inputbyte(pgc, &asc)) return 0; if (asc == '-') sign = -1; } while (is_whitespace(asc)); /* There had better be a digit next */ if (!isdigit(asc)) { pgc_error(pgc, 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 (!pgc->inputbyte(pgc, &asc)) return 0; state = PS_FRACTION; continue; } else { pgc_error(pgc, PGC_ERROR_MISSING); return err_digit(pgc); } break; /* Scientific notation */ case 'd': case 'D': case 'e': case 'E': esign = 1; if (!pgc->inputbyte(pgc, &asc)) return 0; if (asc == '-') { sign = -1; if (!pgc->inputbyte(pgc, &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(pgc, PGC_ERROR_MISSING); return err_digit(pgc); } asc -= '0'; /* asc is digit */ switch (state) { case PS_MAIN: integer = (integer * 10)+asc; if (integer & 0x8000) /* Overflow */ { pgc_error(pgc, 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 (!pgc->inputbyte(pgc, &asc)) return 0; } while (!is_whitespace(asc)); res = (frac << 16) / dp; PGCLOG(("integer=%u frac=%u exponent=%u dp=%d res=0x%08x\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; } /* Pull the next byte from the current command list */ int pgc_clist_byte(pgc_core_t *pgc, uint8_t *val) { if (pgc->clcur == NULL) return 0; if (pgc->clcur->rdptr < pgc->clcur->wrptr) { *val = pgc->clcur->list[pgc->clcur->rdptr++]; } else { *val = 0; } /* If we've reached the end, reset to the beginning and * (if repeating) run the repeat */ if (pgc->clcur->rdptr >= pgc->clcur->wrptr) { pgc->clcur->rdptr = 0; --pgc->clcur->repeat; if (pgc->clcur->repeat == 0) { pgc->clcur = pgc->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_core_t *pgc, uint8_t *val) { int32_t c; if (pgc->clcur) return pgc_clist_byte(pgc, val); if (!pgc->ascii_mode) return pgc->inputbyte(pgc, val); if (!pgc_param_coord(pgc, &c)) return 0; c = (c >> 16); /* Drop fractional part */ if (c > 255) { pgc_error(pgc, 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_core_t *pgc, int16_t *val) { int32_t c; if (pgc->clcur) { uint8_t lo, hi; if (!pgc_clist_byte(pgc, &lo)) return 0; if (!pgc_clist_byte(pgc, &hi)) return 0; *val = (((int16_t)hi) << 8) | lo; return 1; } if (!pgc->ascii_mode) { uint8_t lo, hi; if (!pgc->inputbyte(pgc, &lo)) return 0; if (!pgc->inputbyte(pgc, &hi)) return 0; *val = (((int16_t)hi) << 8) | lo; return 1; } if (!pgc_param_coord(pgc, &c)) return 0; c = (c >> 16); if (c > 0x7FFF || c < -0x7FFF) { pgc_error(pgc, PGC_ERROR_RANGE); return 0; } *val = (int16_t)c; return 1; } /* Output a byte, either as hex or ASCII depending on the mode */ int pgc_result_byte(pgc_core_t *pgc, uint8_t val) { char buf[20]; if (!pgc->ascii_mode) return pgc_output_byte(pgc, val); if (pgc->result_count) { if (!pgc_output_byte(pgc, ',')) return 0; } sprintf(buf, "%d", val); ++pgc->result_count; return pgc_output_string(pgc, buf); } /* Output a word, either as hex or ASCII depending on the mode */ int pgc_result_word(pgc_core_t *pgc, int16_t val) { char buf[20]; if (!pgc->ascii_mode) { if (!pgc_output_byte(pgc, val & 0xFF)) return 0; return pgc_output_byte(pgc, val >> 8); } if (pgc->result_count) { if (!pgc_output_byte(pgc, ',')) return 0; } sprintf(buf, "%d", val); ++pgc->result_count; return pgc_output_string(pgc, buf); } /* Write a screen pixel (x and y are raster coordinates, ink is the * value to write) */ void pgc_write_pixel(pgc_core_t *pgc, uint16_t x, uint16_t y, uint8_t ink) { uint8_t *vram; /* Suppress out-of-range writes; clip to viewport */ if (x < pgc->vp_x1 || x > pgc->vp_x2 || x >= pgc->maxw || y < pgc->vp_y1 || y > pgc->vp_y2 || y >= pgc->maxh) { PGCLOG(("pgc_write_pixel clipped: (%d,%d) " "vp_x1=%d vp_y1=%d vp_x2=%d vp_y2=%d " "ink=0x%02x\n", x, y, pgc->vp_x1, pgc->vp_y1, pgc->vp_x2, pgc->vp_y2, ink)); return; } vram = pgc_vram_addr(pgc, x, y); if (vram) *vram = ink; } /* Read a screen pixel (x and y are raster coordinates) */ uint8_t pgc_read_pixel(pgc_core_t *pgc, uint16_t x, uint16_t y) { uint8_t *vram; /* Suppress out-of-range reads */ if (x >= pgc->maxw || y >= pgc->maxh) { return 0; } vram = pgc_vram_addr(pgc, x, y); if (vram) return *vram; return 0; } /* Plot a point in the current colour and draw mode. Raster coordinates. */ void pgc_plot(pgc_core_t *pgc, uint16_t x, uint16_t y) { uint8_t *vram; /* Only allow plotting within the current viewport. */ if (x < pgc->vp_x1 || x > pgc->vp_x2 || x >= pgc->maxw || y < pgc->vp_y1 || y > pgc->vp_y2 || y >= pgc->maxh) { PGCLOG(("pgc_plot clipped: (%d,%d) %d <= x <= %d; %d <= y <= %d; " "mode=%d ink=0x%02x\n", x, y, pgc->vp_x1, pgc->vp_x2, pgc->vp_y1, pgc->vp_y2, pgc->draw_mode, pgc->colour)); return; } vram = pgc_vram_addr(pgc, x, y); if (!vram) return; /* TODO: Does not implement the PGC plane mask (set by MASK) */ switch (pgc->draw_mode) { default: case 0: *vram = pgc->colour; break; /* Write */ case 1: *vram ^= 0xFF; break; /* Invert */ case 2: *vram ^= pgc->colour; break; /* XOR colour */ case 3: *vram &= pgc->colour; break; /* AND */ } } /* Given raster coordinates, find the matching address in PGC video RAM */ uint8_t *pgc_vram_addr(pgc_core_t *pgc, int16_t x, int16_t y) { int offset; /* We work from the bottom left-hand corner */ if (y < 0 || y >= pgc->maxh || x < 0 || x >= pgc->maxw) return NULL; offset = (pgc->maxh - 1 -y) * (pgc->maxw) + x; // PGCLOG(("pgc_vram_addr x=%d y=%d offset=%d\n", x, y, offset); if (offset < 0 || offset >= (pgc->maxw * pgc->maxh)) { return NULL; } return &pgc->vram[offset]; } /* Read in the next command, either as hex (1 byte) or ASCII (up to 6 * characters) */ int pgc_read_command(pgc_core_t *pgc) { if (pgc->clcur) { return pgc_clist_byte(pgc, &pgc->hex_command); } else if (pgc->ascii_mode) { char ch; int count = 0; while (count < 7) { if (!pgc_input_char(pgc, &ch)) return 0; if (is_whitespace(ch)) { /* Pad to 6 characters */ while (count < 6) pgc->asc_command[count++] = ' '; pgc->asc_command[6] = 0; return 1; } pgc->asc_command[count++] = toupper(ch); } return 1; } else { return pgc->inputbyte(pgc, &pgc->hex_command); } } /* Read in the next command and parse it */ static int pgc_parse_command(pgc_core_t *pgc, const pgc_command_t **pcmd) { const pgc_command_t *cmd; char match[7]; *pcmd = NULL; pgc->hex_command = 0; memset(pgc->asc_command, ' ', 6); pgc->asc_command[6] = 0; if (!pgc_read_command(pgc)) { /* PGC has been reset */ return 0; } /* Scan the list of valid commands. pgc->pgc_commands may be a subclass * list (terminated with '*') or the core list (terminated with '@') */ for (cmd = pgc->pgc_commands; cmd->ascii[0] != '@'; cmd++) { /* End of subclass command list, chain to core */ if (cmd->ascii[0] == '*') cmd = pgc_core_commands; /* If in ASCII mode match on the ASCII command */ if (pgc->ascii_mode && !pgc->clcur) { sprintf(match, "%-6.6s", cmd->ascii); if (!strncmp(match, pgc->asc_command, 6)) { *pcmd = cmd; pgc->hex_command = cmd->hex; break; } } else /* Otherwise match on the hex command */ { if (cmd->hex == pgc->hex_command) { sprintf(pgc->asc_command, "%-6.6s", cmd->ascii); *pcmd = cmd; break; } } } return 1; } /* The PGC drawing thread main loop. Read in commands and execute them ad * infinitum */ void pgc_core_thread(void *p) { pgc_core_t *pgc = (pgc_core_t *)p; const pgc_command_t *cmd; PGCLOG(("pgc_core_thread begins")); while (1) { if (!pgc_parse_command(pgc, &cmd)) { /* PGC has been reset */ continue; } PGCLOG(("PGC command: [%02x] '%s' found=%d\n", pgc->hex_command, pgc->asc_command, (cmd != NULL))); if (cmd) { pgc->result_count = 0; (*cmd->handler)(pgc); } else { pgc_error(pgc, PGC_ERROR_OPCODE); } } } void pgc_recalctimings(pgc_core_t *pgc) { double disptime, _dispontime, _dispofftime; double pixel_clock = (cpuclock * (double)(1ull << 32)) / (pgc->cga_selected ? 25175000.0 : pgc->native_pixel_clock); /* Use a fixed 640 columns, like the T3100e */ disptime = pgc->screenw + 11; _dispontime = pgc->screenw * pixel_clock; _dispofftime = (disptime - pgc->screenw) * pixel_clock; pgc->dispontime = (uint64_t)_dispontime; pgc->dispofftime = (uint64_t)_dispofftime; } /* Draw the display in CGA (640x400) text mode */ void pgc_cga_text(pgc_core_t *pgc, int w) { int x, c; uint8_t chr, attr; int drawcursor = 0; uint32_t cols[2]; int pitch = (pgc->mapram[0x3E9] + 1) * 2; uint16_t sc = (pgc->displine & 0x0F) % pitch; uint16_t ma = (pgc->mapram[0x3ED] | (pgc->mapram[0x3EC] << 8)) & 0x3fff; uint16_t ca = (pgc->mapram[0x3EF] | (pgc->mapram[0x3EE] << 8)) & 0x3fff; uint8_t *addr; int cw = (w == 80) ? 8 : 16; addr = &pgc->cga_vram[((ma << 1) + (((pgc->displine / pitch)*w)) * 2) & 0x3ffe]; ma += (pgc->displine / pitch) * w; for (x = 0; x < w; x++) { chr = addr[0]; attr = addr[1]; addr += 2; /* Cursor enabled? */ if (ma == ca && (pgc->cgablink & 8) && (pgc->mapram[0x3EA] & 0x60) != 0x20) { drawcursor = ((pgc->mapram[0x3EA] & 0x1F) <= (sc >> 1)) && ((pgc->mapram[0x3EB] & 0x1F) >= (sc >> 1)); } else drawcursor = 0; if (pgc->mapram[0x3D8] & 0x20) { cols[1] = attr & 15; cols[0] = (attr >> 4) & 7; if ((pgc->cgablink & 8) && (attr & 0x80) && !drawcursor) cols[1] = cols[0]; } else { cols[1] = attr & 15; cols[0] = attr >> 4; } if (drawcursor) { for (c = 0; c < cw; c++) { if (w == 80) ((uint32_t *)buffer32->line[pgc->displine & 2047])[(x << 3) + c] = cols[(fontdatm[chr + pgc->fontbase][sc] & (1 << (c ^ 7))) ? 1 : 0] ^ 0xffffff; else ((uint32_t *)buffer32->line[pgc->displine & 2047])[(x << 4) + c] = cols[(fontdatm[chr + pgc->fontbase][sc] & (1 << (c ^ 7))) ? 1 : 0] ^ 0xffffff; } } else { for (c = 0; c < cw; c++) { if (w == 80) ((uint32_t *)buffer32->line[pgc->displine & 2047])[(x << 3) + c] = cols[(fontdatm[chr + pgc->fontbase][sc] & (1 << (c ^ 7))) ? 1 : 0]; else ((uint32_t *)buffer32->line[pgc->displine & 2047])[(x << 4) + c] = cols[(fontdatm[chr + pgc->fontbase][sc] & (1 << ((c >> 1) ^ 7))) ? 1 : 0]; } } ma++; } } /* Draw the display in CGA (320x200) graphics mode */ void pgc_cga_gfx40(pgc_core_t *pgc) { int x, c; uint32_t cols[4]; int col; uint16_t ma = (pgc->mapram[0x3ED] | (pgc->mapram[0x3EC] << 8)) & 0x3fff; uint8_t *addr; uint16_t dat; cols[0] = pgc->mapram[0x3D9] & 15; col = (pgc->mapram[0x3D9] & 16) ? 8 : 0; if (pgc->mapram[0x3D8] & 4) { cols[1] = col | 3; cols[2] = col | 4; cols[3] = col | 7; } else if (pgc->mapram[0x3D9] & 32) { cols[1] = col | 3; cols[2] = col | 5; cols[3] = col | 7; } else { cols[1] = col | 2; cols[2] = col | 4; cols[3] = col | 6; } for (x = 0; x < 40; x++) { addr = &pgc->cga_vram[(ma + 2 * x + 80 * (pgc->displine >> 2) + 0x2000 * ((pgc->displine >> 1) & 1)) & 0x3FFF]; dat = (addr[0] << 8) | addr[1]; pgc->ma++; for (c = 0; c < 8; c++) { ((uint32_t *)buffer32->line[pgc->displine & 2047])[(x << 4) + (c << 1)] = ((uint32_t *)buffer32->line[pgc->displine & 2047])[(x << 4) + (c << 1) + 1] = cols[dat >> 14]; dat <<= 2; } } } /* Draw the display in CGA (640x200) graphics mode */ void pgc_cga_gfx80(pgc_core_t *pgc) { int x, c; uint32_t cols[2]; uint16_t ma = (pgc->mapram[0x3ED] | (pgc->mapram[0x3EC] << 8)) & 0x3fff; uint8_t *addr; uint16_t dat; cols[0] = 0; cols[1] = pgc->mapram[0x3D9] & 15; for (x = 0; x < 40; x++) { addr = &pgc->cga_vram[(ma + 2 * x + 80 * (pgc->displine >> 2) + 0x2000 * ((pgc->displine >> 1) & 1)) & 0x3FFF]; dat = (addr[0] << 8) | addr[1]; pgc->ma++; for (c = 0; c < 16; c++) { ((uint32_t *)buffer32->line[pgc->displine & 2047])[(x << 4) + c] = cols[dat >> 15]; dat <<= 1; } } } /* Draw the screen in CGA mode. Based on the simplified WY700 renderer * rather than the original CGA, since the PGC doesn't have a real 6845 */ void pgc_cga_poll(pgc_core_t *pgc) { int c; uint32_t cols[2]; if (!pgc->linepos) { timer_advance_u64(&pgc->timer, pgc->dispofftime); pgc->mapram[0x3DA] |= 1; pgc->linepos = 1; if (pgc->cgadispon) { if (pgc->displine == 0) { video_wait_for_buffer(); } if ((pgc->mapram[0x3D8] & 0x12) == 0x12) { pgc_cga_gfx80(pgc); } else if (pgc->mapram[0x3D8] & 0x02) { pgc_cga_gfx40(pgc); } else if (pgc->mapram[0x3D8] & 0x01) { pgc_cga_text(pgc, 80); } else { pgc_cga_text(pgc, 40); } } else { cols[0] = ((pgc->mapram[0x3D8] & 0x12) == 0x12) ? 0 : (pgc->mapram[0x3D9] & 15); hline(buffer32, 0, pgc->displine & 2047, PGC_CGA_WIDTH, cols[0]); } for (c = 0; c < PGC_CGA_WIDTH; c++) ((uint32_t *)buffer32->line[pgc->displine & 2047])[c] = cgapal[((uint32_t *)buffer32->line[pgc->displine & 2047])[c] & 0xf]; pgc->displine++; if (pgc->displine == PGC_CGA_HEIGHT) { pgc->mapram[0x3DA] |= 8; pgc->cgadispon = 0; } if (pgc->displine == PGC_CGA_HEIGHT + 32) { pgc->mapram[0x3DA] &= ~8; pgc->cgadispon = 1; pgc->displine = 0; } } else { if (pgc->cgadispon) { pgc->mapram[0x3DA] &= ~1; } timer_advance_u64(&pgc->timer, pgc->dispontime); pgc->linepos = 0; if (pgc->displine == PGC_CGA_HEIGHT) { if (PGC_CGA_WIDTH != xsize || PGC_CGA_HEIGHT != ysize) { xsize = PGC_CGA_WIDTH; ysize = PGC_CGA_HEIGHT; updatewindowsize(xsize, ysize); } video_blit_memtoscreen(0, 0, 0, ysize, 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 (pgc->mapram[0x3D8] & 0x12) { case 0x12: video_bpp = 1; break; case 0x02: video_bpp = 2; break; default: video_bpp = 0; break; } pgc->cgablink++; } } } /* Draw the screen in CGA or native mode. */ void pgc_poll(void *p) { pgc_core_t *pgc = (pgc_core_t *)p; int x, y; if (pgc->cga_selected) { pgc_cga_poll(pgc); return; } /* Not CGA, so must be native mode */ if (!pgc->linepos) { timer_advance_u64(&pgc->timer, pgc->dispofftime); pgc->mapram[0x3DA] |= 1; pgc->linepos = 1; if (pgc->cgadispon && pgc->displine < pgc->maxh) { if (pgc->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 = pgc->displine - 2 * pgc->pan_y; for (x = 0; x < pgc->screenw; x++) { if (x + pgc->pan_x < pgc->maxw) { ((uint32_t *)buffer32->line[pgc->displine & 2047])[x] = pgc->palette[pgc->vram[y * pgc->maxw + x]]; } else { ((uint32_t *)buffer32->line[pgc->displine & 2047])[x] = pgc->palette[0]; } } } else { hline(buffer32, 0, pgc->displine & 2047, pgc->screenw, pgc->palette[0]); } pgc->displine++; if (pgc->displine == pgc->screenh) { pgc->mapram[0x3DA] |= 8; pgc->cgadispon = 0; } if (pgc->displine == pgc->screenh + 32) { pgc->mapram[0x3DA] &= ~8; pgc->cgadispon = 1; pgc->displine = 0; } } else { if (pgc->cgadispon) { pgc->mapram[0x3DA] &= ~1; } timer_advance_u64(&pgc->timer, pgc->dispontime); pgc->linepos = 0; if (pgc->displine == pgc->screenh) { if (pgc->screenw != xsize || pgc->screenh != ysize) { xsize = pgc->screenw; ysize = pgc->screenh; updatewindowsize(xsize, ysize); } video_blit_memtoscreen(0, 0, 0, ysize, xsize, ysize); frames++; video_res_x = pgc->screenw; video_res_y = pgc->screenh; video_bpp = 8; pgc->cgablink++; } } } /* Initialise RAM and registers to default values */ void pgc_reset(pgc_core_t *pgc) { int n; memset(pgc->mapram, 0, sizeof(pgc->mapram)); /* There is no point in emulating the 'CGA disable' jumper as this is only * appropriate for a dual-head system, not emulated by PCEM */ pgc->mapram[0x30B] = pgc->cga_enabled = 1; pgc->mapram[0x3F8] = 0x03; /* Minor version */ pgc->mapram[0x3F9] = 0x01; /* Minor version */ pgc->mapram[0x3FB] = 0xA5; /* } */ pgc->mapram[0x3FC] = 0x5A; /* PGC self-test passed */ pgc->mapram[0x3FD] = 0x55; /* } */ pgc->mapram[0x3FE] = 0x5A; /* } */ pgc->ascii_mode = 1; /* Start off in ASCII mode */ pgc->line_pattern = 0xFFFF; memset(pgc->fill_pattern, 0xFF, sizeof(pgc->fill_pattern)); pgc->colour = 0xFF; pgc->tjust_h = 1; pgc->tjust_v = 1; /* Reset panning */ pgc->pan_x = 0; pgc->pan_y = 0; /* Reset clipping */ pgc->vp_x1 = 0; pgc->vp_y1 = 0; pgc->vp_x2 = pgc->visw - 1; pgc->vp_y2 = pgc->vish - 1; /* Empty command lists */ for (n = 0; n < 256; n++) { pgc->clist[n].wrptr = 0; pgc->clist[n].rdptr = 0; pgc->clist[n].repeat = 0; pgc->clist[n].chain = 0; } pgc->clcur = NULL; /* Select CGA display */ pgc->cga_selected = -1; pgc_setdisplay(pgc, pgc->cga_enabled); pgc->mapram[0x30C] = pgc->cga_enabled; pgc->mapram[0x30D] = pgc->cga_enabled; /* Default palette is 0 */ pgc_init_lut(pgc, 0); hndl_lutsav(pgc); } /* Initialisation 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_core_init(pgc_core_t *pgc, int maxw, int maxh, int visw, int vish, int (*inpbyte)(struct pgc_core_t *pgc, uint8_t *result), double native_pixel_clock) { int n; mem_mapping_add(&pgc->mapping, 0xC6000, 0x800, pgc_read, NULL, NULL, pgc_write, NULL, NULL, NULL, MEM_MAPPING_EXTERNAL, pgc); mem_mapping_add(&pgc->cga_mapping, 0xB8000, 0x8000, pgc_read, NULL, NULL, pgc_write, NULL, NULL, NULL, MEM_MAPPING_EXTERNAL, pgc); io_sethandler(0x3D0, 0x0010, pgc_in, NULL, NULL, pgc_out, NULL, NULL, pgc); pgc->maxw = maxw; pgc->maxh = maxh; pgc->visw = visw; pgc->vish = vish; pgc->vram = malloc(maxw * maxh); pgc->cga_vram = malloc(0x4000); pgc->clist = calloc(256, sizeof(pgc_commandlist_t)); pgc->clcur = NULL; pgc->native_pixel_clock = native_pixel_clock; memset(pgc->vram, 0, maxw * maxh); memset(pgc->cga_vram, 0, 0x4000); /* Empty command lists */ for (n = 0; n < 256; n++) { pgc->clist[n].list = NULL; pgc->clist[n].listmax = 0; pgc->clist[n].wrptr = 0; pgc->clist[n].rdptr = 0; pgc->clist[n].repeat = 0; pgc->clist[n].chain = NULL; } pgc_reset(pgc); pgc->inputbyte = inpbyte; pgc->pgc_commands = pgc_core_commands; pgc->pgc_wake_thread = thread_create_event(); pgc->pgc_thread = thread_create(pgc_core_thread, pgc); timer_add(&pgc->timer, pgc_poll, (void *)pgc, 1); timer_add(&pgc->wake_timer, pgc_wake_timer, (void *)pgc, 0); } /* Initialisation code specific to the PGC */ void *pgc_standalone_init() { pgc_core_t *pgc = malloc(sizeof(pgc_core_t)); memset(pgc, 0, sizeof(pgc_core_t)); /* Framebuffer and screen are both 640x480 */ pgc_core_init(pgc, 640, 480, 640, 480, pgc_input_byte, 25175000.0); return pgc; } void pgc_close(void *p) { pgc_core_t *pgc = (pgc_core_t *)p; thread_kill(pgc->pgc_thread); thread_destroy_event(pgc->pgc_wake_thread); if (pgc->cga_vram) { free(pgc->cga_vram); } if (pgc->vram) { free(pgc->vram); } free(pgc); } void pgc_speed_changed(void *p) { pgc_core_t *pgc = (pgc_core_t *)p; pgc_recalctimings(pgc); } device_config_t pgc_config[] = { { .type = -1 } }; device_t pgc_device = { "PGC", 0, pgc_standalone_init, pgc_close, NULL, pgc_speed_changed, NULL, NULL, pgc_config };