From f27e1fb8a296140ddc95e12f7c0bb988570d7288 Mon Sep 17 00:00:00 2001 From: Jasmine Iwanek Date: Wed, 11 Dec 2024 13:03:21 -0500 Subject: [PATCH] Support for Quadram Quadcolor I / I + II --- src/include/86box/vid_quadcolor.h | 93 +++ src/include/86box/video.h | 3 + src/video/CMakeLists.txt | 5 +- src/video/vid_cga_quadcolor.c | 981 ++++++++++++++++++++++++++++++ src/video/vid_table.c | 1 + 5 files changed, 1081 insertions(+), 2 deletions(-) create mode 100644 src/include/86box/vid_quadcolor.h create mode 100644 src/video/vid_cga_quadcolor.c diff --git a/src/include/86box/vid_quadcolor.h b/src/include/86box/vid_quadcolor.h new file mode 100644 index 000000000..41b39dfb0 --- /dev/null +++ b/src/include/86box/vid_quadcolor.h @@ -0,0 +1,93 @@ +/* + * 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. + * + * Quadram Quadcolor I / I+II emulation + * + * Authors: Benedikt Freisen, + * Jasmine Iwanek, + * + * Copyright 2024 Benedikt Freisen. + Copyright 2025 Jasmine Iwanek. + */ + +#ifndef _VID_QUADCOLOR_H_ +#define _VID_QUADCOLOR_H_ + +typedef struct quadcolor_t { + mem_mapping_t mapping; + mem_mapping_t mapping_2; + + int crtcreg; +#if 0 + uint8_t crtc[CGA_NUM_CRTC_REGS]; +#else + uint8_t crtc[32]; +#endif + uint8_t cgastat; + + uint8_t cgamode; + uint8_t cgacol; + + uint8_t lp_strobe; + + uint8_t quadcolor_ctrl; + uint8_t quadcolor_2_oe; + uint16_t page_offset; + + int fontbase; + int linepos; + int displine; + int scanline; + int vc; + int cgadispon; + int cursorvisible; // Determines if the cursor is visible FOR THE CURRENT SCANLINE. + int cursoron; + int cgablink; + int vsynctime; + int vadj; + uint16_t memaddr; + uint16_t memaddr_backup; + int oddeven; + + int qc2idx; + uint8_t qc2mask; + + uint64_t dispontime; + uint64_t dispofftime; + pc_timer_t timer; + + int firstline; + int lastline; + + int drawcursor; + + int fullchange; + + uint8_t *vram; + uint8_t *vram_2; + + uint8_t charbuffer[256]; + + int revision; + int composite; + int rgb_type; + int double_type; + + int has_2nd_charset; + int has_quadcolor_2; +} quadcolor_t; + +void quadcolor_init(quadcolor_t *quadcolor); +void quadcolor_out(uint16_t addr, uint8_t val, void *priv); +uint8_t quadcolor_in(uint16_t addr, void *priv); +void quadcolor_write(uint32_t addr, uint8_t val, void *priv); +uint8_t quadcolor_read(uint32_t addr, void *priv); +void quadcolor_recalctimings(quadcolor_t *quadcolor); +void quadcolor_poll(void *priv); + +#endif /* _VID_QUADCOLOR_H_ */ diff --git a/src/include/86box/video.h b/src/include/86box/video.h index 986529d6f..70d031a4f 100644 --- a/src/include/86box/video.h +++ b/src/include/86box/video.h @@ -491,6 +491,9 @@ extern const device_t paradise_wd90c11_megapc_device; extern const device_t paradise_wd90c11_device; extern const device_t paradise_wd90c30_device; +/* Quadram Quadcolor I / I + II */ +extern const device_t quadcolor_device; + /* Realtek (S)VGA */ extern const device_t realtek_rtg3105_device; extern const device_t realtek_rtg3106_device; diff --git a/src/video/CMakeLists.txt b/src/video/CMakeLists.txt index 4cac8b76d..1159c93e2 100644 --- a/src/video/CMakeLists.txt +++ b/src/video/CMakeLists.txt @@ -55,8 +55,9 @@ add_library(vid OBJECT vid_cga_colorplus.c vid_cga_ncr.c vid_cga_olivetti.c - vid_cga_toshiba_t1000.c - vid_cga_toshiba_t3100e.c + vid_cga_quadcolor.c + vid_cga_toshiba_t1000.c + vid_cga_toshiba_t3100e.c # PCJr/Tandy vid_pcjr.c diff --git a/src/video/vid_cga_quadcolor.c b/src/video/vid_cga_quadcolor.c new file mode 100644 index 000000000..5974682c8 --- /dev/null +++ b/src/video/vid_cga_quadcolor.c @@ -0,0 +1,981 @@ +/* + * 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. + * + * Quadram Quadcolor I / I+II emulation + * + * Authors: Sarah Walker, + * Miran Grca, + * W. M. Martinez, + * Benedikt Freisen, + * Jasmine Iwanek, + * + * Copyright 2008-2019 Sarah Walker. + * Copyright 2016-2019 Miran Grca. + * Copyright 2023 W. M. Martinez + * Copyright 2024 Benedikt Freisen. + * Copyright 2025 Jasmine Iwanek. + */ + +/* This has been derived from CGA emulation */ +/* omissions: simulated snow (Quadcolor has dual-ported RAM), single and dual 8x16 font configuration */ +/* additions: ports 0x3dd and 0x3de, 2nd char set, 2nd VRAM bank, hi-res bg color, Quadcolor II memory and mode */ +/* assumptions: MA line 12 XORed with Bank Select, hi-res bg is also border color, QC2 mode has simple address counter */ + +#include +#include +#include +#include +#include +#include +#include <86box/86box.h> +#include "cpu.h" +#include <86box/io.h> +#include <86box/timer.h> +#include <86box/pit.h> +#include <86box/mem.h> +#include <86box/rom.h> +#include <86box/device.h> +#include <86box/video.h> +#include <86box/vid_cga.h> +#include <86box/vid_quadcolor.h> +#include <86box/vid_cga_comp.h> +#include <86box/plat_unused.h> + +#define CGA_RGB 0 +#define CGA_COMPOSITE 1 + +#define COMPOSITE_OLD 0 +#define COMPOSITE_NEW 1 + +#define DOUBLE_NONE 0 +#define DOUBLE_SIMPLE 1 +#define DOUBLE_INTERPOLATE_SRGB 2 +#define DOUBLE_INTERPOLATE_LINEAR 3 + +#define DEVICE_VRAM 0x8000 +#define DEVICE_VRAM_MASK 0x7fff + +typedef union { + uint32_t color; + struct { + uint8_t b; + uint8_t g; + uint8_t r; + uint8_t a; + }; +} color_t; + +static uint8_t crtcmask[32] = { + 0xff, 0xff, 0xff, 0xff, 0x7f, 0x1f, 0x7f, 0x7f, 0xf3, 0x1f, 0x7f, 0x1f, 0x3f, 0xff, 0x3f, 0xff, + 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +static uint8_t interp_lut[2][256][256]; + +static video_timings_t timing_quadcolor = { .type = VIDEO_ISA, .write_b = 8, .write_w = 16, .write_l = 32, .read_b = 8, .read_w = 16, .read_l = 32 }; + +void quadcolor_recalctimings(quadcolor_t *quadcolor); + +static void +quadcolor_update_latch(quadcolor_t *quadcolor) +{ + uint32_t lp_latch = quadcolor->displine * quadcolor->crtc[CGA_CRTC_HDISP]; + + quadcolor->crtc[CGA_CRTC_LIGHT_PEN_ADDR_HIGH] = (lp_latch >> 8) & 0x3f; + quadcolor->crtc[CGA_CRTC_LIGHT_PEN_ADDR_LOW] = lp_latch & 0xff; +} + +void +quadcolor_out(uint16_t addr, uint8_t val, void *priv) +{ + quadcolor_t *quadcolor = (quadcolor_t *) priv; + uint8_t old; + + if ((addr >= 0x3d0) && (addr <= 0x3d7)) + addr = (addr & 0xff9) | 0x004; + + switch (addr) { + case CGA_REGISTER_CRTC_INDEX: + quadcolor->crtcreg = val & 31; + return; + case CGA_REGISTER_CRTC_DATA: + old = quadcolor->crtc[quadcolor->crtcreg]; + quadcolor->crtc[quadcolor->crtcreg] = val & crtcmask[quadcolor->crtcreg]; + if (old != val) { + // Recalc the timings if we are writing any invalid CRTC register or a valid CRTC register + // except the CURSOR and LIGHT PEN registers + if ((quadcolor->crtcreg < 0xe) || (quadcolor->crtcreg > 0x11)) { + quadcolor->fullchange = changeframecount; + quadcolor_recalctimings(quadcolor); + } + } + return; + case CGA_REGISTER_MODE_CONTROL: + old = quadcolor->cgamode; + quadcolor->cgamode = val; + + if (old ^ val) { + if ((old ^ val) & 0x07) + update_cga16_color(val); + + quadcolor_recalctimings(quadcolor); + } + return; + case CGA_REGISTER_COLOR_SELECT: + old = quadcolor->cgacol; + quadcolor->cgacol = val; + if (old ^ val) + quadcolor_recalctimings(quadcolor); + return; + + case CGA_REGISTER_CLEAR_LIGHT_PEN_LATCH: + if (quadcolor->lp_strobe == 1) + quadcolor->lp_strobe = 0; + return; + case CGA_REGISTER_SET_LIGHT_PEN_LATCH: + if (quadcolor->lp_strobe == 0) { + quadcolor->lp_strobe = 1; + quadcolor_update_latch(quadcolor); + } + return; + + case 0x3dd: + quadcolor->quadcolor_ctrl = val & 0x3f; + /* helper variable that can be XORed onto the VRAM address to select the page to display */ + quadcolor->page_offset = (val & 0x10) << 8; + /* in dual 8x8 font configuration, use fontbase 256 if "Character Set Select" bit is set */ + if (quadcolor->has_2nd_charset) + quadcolor->fontbase = (val & 0x20) << 3; + return; + case 0x3de: + /* NOTE: the polarity of this register is the opposite of what the manual says */ + if (quadcolor->has_quadcolor_2) + quadcolor->quadcolor_2_oe = !(val & 0x10); + return; + + default: + break; + } +} + +uint8_t +quadcolor_in(uint16_t addr, void *priv) +{ + quadcolor_t *quadcolor = (quadcolor_t *) priv; + uint8_t ret = 0xff; + + if ((addr >= 0x3d0) && (addr <= 0x3d7)) + addr = (addr & 0xff9) | 0x004; + + switch (addr) { + case CGA_REGISTER_CRTC_INDEX: + ret = quadcolor->crtcreg; + break; + case CGA_REGISTER_CRTC_DATA: + ret = quadcolor->crtc[quadcolor->crtcreg]; + break; + case CGA_REGISTER_STATUS: + ret = quadcolor->cgastat; + break; + case CGA_REGISTER_CLEAR_LIGHT_PEN_LATCH: + if (quadcolor->lp_strobe == 1) + quadcolor->lp_strobe = 0; + break; + case CGA_REGISTER_SET_LIGHT_PEN_LATCH: + if (quadcolor->lp_strobe == 0) { + quadcolor->lp_strobe = 1; + quadcolor_update_latch(quadcolor); + } + break; + + default: + break; + } + + return ret; +} + +void +quadcolor_waitstates(UNUSED(void *priv)) +{ + int ws_array[16] = { 3, 4, 5, 6, 7, 8, 4, 5, 6, 7, 8, 4, 5, 6, 7, 8 }; + int ws; + + ws = ws_array[cycles & 0xf]; + cycles -= ws; +} + +void +quadcolor_write(uint32_t addr, uint8_t val, void *priv) +{ + quadcolor_t *quadcolor = (quadcolor_t *) priv; + + quadcolor->vram[addr & DEVICE_VRAM_MASK] = val; + quadcolor_waitstates(quadcolor); +} + +uint8_t +quadcolor_read(uint32_t addr, void *priv) +{ + quadcolor_t *quadcolor = (quadcolor_t *) priv; + + quadcolor_waitstates(quadcolor); + return quadcolor->vram[addr & DEVICE_VRAM_MASK]; +} + +void +quadcolor_2_write(uint32_t addr, uint8_t val, void *priv) +{ + quadcolor_t *quadcolor = (quadcolor_t *) priv; + quadcolor->vram_2[addr & 0xffff] = val; +} + +uint8_t +quadcolor_2_read(uint32_t addr, void *priv) +{ + quadcolor_t *quadcolor = (quadcolor_t *) priv; + return quadcolor->vram_2[addr & 0xffff]; +} + +void +quadcolor_recalctimings(quadcolor_t *quadcolor) +{ + double disptime; + double _dispontime; + double _dispofftime; + + if (quadcolor->cgamode & CGA_MODE_FLAG_HIGHRES) { + disptime = (double) (quadcolor->crtc[CGA_CRTC_HTOTAL] + 1); + _dispontime = (double) quadcolor->crtc[CGA_CRTC_HDISP]; + } else { + disptime = (double) ((quadcolor->crtc[CGA_CRTC_HTOTAL] + 1) << 1); + _dispontime = (double) (quadcolor->crtc[CGA_CRTC_HDISP] << 1); + } + _dispofftime = disptime - _dispontime; + _dispontime = _dispontime * CGACONST; + _dispofftime = _dispofftime * CGACONST; + quadcolor->dispontime = (uint64_t) (_dispontime); + quadcolor->dispofftime = (uint64_t) (_dispofftime); +} + +static inline uint8_t +get_next_qc2_pixel(quadcolor_t *quadcolor) +{ + uint8_t mask = quadcolor->qc2mask; + quadcolor->qc2mask = ~quadcolor->qc2mask; + uint8_t pixel = (quadcolor->vram_2[quadcolor->qc2idx] & mask) >> (quadcolor->qc2mask & 4); + + quadcolor->qc2idx += quadcolor->qc2mask >> 7; + + return quadcolor->quadcolor_2_oe ? pixel : 0; +} + +static void +quadcolor_render(quadcolor_t *quadcolor, int line) +{ + uint16_t cursoraddr = (quadcolor->crtc[CGA_CRTC_CURSOR_ADDR_LOW] | (quadcolor->crtc[CGA_CRTC_CURSOR_ADDR_HIGH] << 8)) & DEVICE_VRAM_MASK; + int drawcursor; + int x; + int column; + uint8_t chr; + uint8_t attr; + uint16_t dat; + int cols[4]; + int col; + + int32_t highres_graphics_flag = (CGA_MODE_FLAG_HIGHRES_GRAPHICS | CGA_MODE_FLAG_GRAPHICS); + + if (((quadcolor->cgamode & highres_graphics_flag) == highres_graphics_flag)) { + for (column = 0; column < 8; ++column) { + buffer32->line[line][column] = 0; + if (quadcolor->cgamode & CGA_MODE_FLAG_HIGHRES) + buffer32->line[line][column + (quadcolor->crtc[CGA_CRTC_HDISP] << 3) + 8] = (quadcolor->quadcolor_ctrl & 15); /* TODO: Is Quadcolor bg color actually relevant, here? */ + else + buffer32->line[line][column + (quadcolor->crtc[CGA_CRTC_HDISP] << 4) + 8] = (quadcolor->quadcolor_ctrl & 15); /* TODO: Is Quadcolor bg color actually relevant, here? */ + } + } else { + for (column = 0; column < 8; ++column) { + buffer32->line[line][column] = (quadcolor->cgacol & 15) + 16; + if (quadcolor->cgamode & CGA_MODE_FLAG_HIGHRES) + buffer32->line[line][column + (quadcolor->crtc[CGA_CRTC_HDISP] << 3) + 8] = (quadcolor->cgacol & 15) + 16; + else + buffer32->line[line][column + (quadcolor->crtc[CGA_CRTC_HDISP] << 4) + 8] = (quadcolor->cgacol & 15) + 16; + } + } + if (quadcolor->cgamode & CGA_MODE_FLAG_HIGHRES) { /* 80-column text */ + for (x = 0; x < quadcolor->crtc[CGA_CRTC_HDISP]; x++) { + if (quadcolor->cgamode & CGA_MODE_FLAG_VIDEO_ENABLE) { + chr = quadcolor->charbuffer[x << 1]; + attr = quadcolor->charbuffer[(x << 1) + 1]; + } else + chr = attr = 0; + drawcursor = ((quadcolor->memaddr == cursoraddr) && quadcolor->cursorvisible && quadcolor->cursoron); + cols[1] = (attr & 15) + 16; + if (quadcolor->cgamode & CGA_MODE_FLAG_BLINK) { + cols[0] = ((attr >> 4) & 7) + 16; + if ((quadcolor->cgablink & 8) && (attr & 0x80) && !quadcolor->drawcursor) + cols[1] = cols[0]; + } else + cols[0] = (attr >> 4) + 16; + if (drawcursor) { + for (column = 0; column < 8; column++) { + buffer32->line[line][(x << 3) + column + 8] + = (cols[(fontdat[chr + quadcolor->fontbase][quadcolor->scanline & 7] & (1 << (column ^ 7))) ? 1 : 0] ^ 15) | get_next_qc2_pixel(quadcolor); + } + } else { + for (column = 0; column < 8; column++) { + buffer32->line[line][(x << 3) + column + 8] + = cols[(fontdat[chr + quadcolor->fontbase][quadcolor->scanline & 7] & (1 << (column ^ 7))) ? 1 : 0] | get_next_qc2_pixel(quadcolor); + } + } + quadcolor->memaddr++; + } + } else if (!(quadcolor->cgamode & CGA_MODE_FLAG_GRAPHICS)) { /* not graphics (nor 80-column text) => 40-column text */ + for (x = 0; x < quadcolor->crtc[CGA_CRTC_HDISP]; x++) { + if (quadcolor->cgamode & CGA_MODE_FLAG_VIDEO_ENABLE) { + chr = quadcolor->vram[(quadcolor->page_offset ^ (quadcolor->memaddr << 1)) & DEVICE_VRAM_MASK]; + attr = quadcolor->vram[(quadcolor->page_offset ^ ((quadcolor->memaddr << 1) + 1)) & DEVICE_VRAM_MASK]; + } else + chr = attr = 0; + drawcursor = ((quadcolor->memaddr == cursoraddr) && quadcolor->cursorvisible && quadcolor->cursoron); + cols[1] = (attr & 15) + 16; + if (quadcolor->cgamode & CGA_MODE_FLAG_BLINK) { + cols[0] = ((attr >> 4) & 7) + 16; + if ((quadcolor->cgablink & 8) && (attr & 0x80)) + cols[1] = cols[0]; + } else + cols[0] = (attr >> 4) + 16; + quadcolor->memaddr++; + if (drawcursor) { + for (column = 0; column < 8; column++) { + dat = (cols[(fontdat[chr + quadcolor->fontbase][quadcolor->scanline & 7] & (1 << (column ^ 7))) ? 1 : 0] ^ 15); + buffer32->line[line][(x << 4) + (column << 1) + 8] = dat | get_next_qc2_pixel(quadcolor); + buffer32->line[line][(x << 4) + (column << 1) + 9] = dat | get_next_qc2_pixel(quadcolor); + + } + } else { + for (column = 0; column < 8; column++) { + dat = cols[(fontdat[chr + quadcolor->fontbase][quadcolor->scanline & 7] & (1 << (column ^ 7))) ? 1 : 0]; + buffer32->line[line][(x << 4) + (column << 1) + 8] = dat | get_next_qc2_pixel(quadcolor); + buffer32->line[line][(x << 4) + (column << 1) + 9] = dat | get_next_qc2_pixel(quadcolor); + + } + } + } + } else if (!(quadcolor->cgamode & CGA_MODE_FLAG_HIGHRES_GRAPHICS)) { /* not hi-res (but graphics) => 4-color mode */ + cols[0] = (quadcolor->cgacol & 15) | 16; + col = (quadcolor->cgacol & 16) ? 24 : 16; + if (quadcolor->cgamode & CGA_MODE_FLAG_BW) { + cols[1] = col | 3; /* Cyan */ + cols[2] = col | 4; /* Red */ + cols[3] = col | 7; /* White */ + } else if (quadcolor->cgacol & 32) { + cols[1] = col | 3; /* Cyan */ + cols[2] = col | 5; /* Magenta */ + cols[3] = col | 7; /* White */ + } else { + cols[1] = col | 2; /* Green */ + cols[2] = col | 4; /* Red */ + cols[3] = col | 6; /* Yellow */ + } + for (x = 0; x < quadcolor->crtc[CGA_CRTC_HDISP]; x++) { + if (quadcolor->cgamode & CGA_MODE_FLAG_VIDEO_ENABLE) + dat = (quadcolor->vram[quadcolor->page_offset ^ (((quadcolor->memaddr << 1) & 0x1fff) + ((quadcolor->scanline & 1) * 0x2000))] << 8) | + quadcolor->vram[quadcolor->page_offset ^ (((quadcolor->memaddr << 1) & 0x1fff) + ((quadcolor->scanline & 1) * 0x2000) + 1)]; + else + dat = 0; + quadcolor->memaddr++; + for (column = 0; column < 8; column++) { + buffer32->line[line][(x << 4) + (column << 1) + 8] = cols[dat >> 14] | get_next_qc2_pixel(quadcolor); + buffer32->line[line][(x << 4) + (column << 1) + 9] = cols[dat >> 14] | get_next_qc2_pixel(quadcolor); + dat <<= 2; + } + } + } else { /* 2-color hi-res graphics mode */ + cols[0] = quadcolor->quadcolor_ctrl & 15; /* background color (Quadcolor-specific) */; + cols[1] = (quadcolor->cgacol & 15) + 16; + for (x = 0; x < quadcolor->crtc[CGA_CRTC_HDISP]; x++) { + if (quadcolor->cgamode & CGA_MODE_FLAG_VIDEO_ENABLE) /* video enabled */ + dat = (quadcolor->vram[quadcolor->page_offset ^ (((quadcolor->memaddr << 1) & 0x1fff) + ((quadcolor->scanline & 1) * 0x2000))] << 8) | + quadcolor->vram[quadcolor->page_offset ^ (((quadcolor->memaddr << 1) & 0x1fff) + ((quadcolor->scanline & 1) * 0x2000) + 1)]; + else + dat = quadcolor->quadcolor_ctrl & 15; /* TODO: Is Quadcolor bg color actually relevant, here? Probably. See QC2 manual p.46 1. */; + quadcolor->memaddr++; + for (column = 0; column < 16; column++) { + buffer32->line[line][(x << 4) + column + 8] = cols[dat >> 15] | get_next_qc2_pixel(quadcolor); + dat <<= 1; + } + } + } +} + +static void +quadcolor_render_blank(quadcolor_t *quadcolor, int line) +{ + int32_t highres_graphics_flag = (CGA_MODE_FLAG_HIGHRES_GRAPHICS | CGA_MODE_FLAG_GRAPHICS); + + /* `+ 16` isn't in PCem's version */ + int col = ((quadcolor->cgamode & highres_graphics_flag) == highres_graphics_flag) ? (quadcolor->quadcolor_ctrl & 15) + 16 : (quadcolor->cgacol & 15) + 16; /* TODO: Is Quadcolor bg color actually relevant, here? */ + + if (quadcolor->cgamode & CGA_MODE_FLAG_HIGHRES) + hline(buffer32, 0, line, (quadcolor->crtc[CGA_CRTC_HDISP] << 3) + 16, col); + else + hline(buffer32, 0, line, (quadcolor->crtc[CGA_CRTC_HDISP] << 4) + 16, col); +} + +static void +quadcolor_render_process(quadcolor_t *quadcolor, int line) +{ + int x; + uint8_t border; + int32_t highres_graphics_flag = (CGA_MODE_FLAG_HIGHRES_GRAPHICS | CGA_MODE_FLAG_GRAPHICS); + + if (quadcolor->cgamode & CGA_MODE_FLAG_HIGHRES) + x = (quadcolor->crtc[CGA_CRTC_HDISP] << 3) + 16; + else + x = (quadcolor->crtc[CGA_CRTC_HDISP] << 4) + 16; + + if (quadcolor->composite) { + border = ((quadcolor->cgamode & highres_graphics_flag) == highres_graphics_flag) ? 0 : (quadcolor->cgacol & 15); + + Composite_Process(quadcolor->cgamode, border, x >> 2, buffer32->line[line]); + } else + video_process_8(x, line); +} + +static uint8_t +quadcolor_interpolate_srgb(uint8_t co1, uint8_t co2, double fraction) +{ + uint8_t ret = ((co2 - co1) * fraction + co1); + + return ret; +} + +static uint8_t +quadcolor_interpolate_linear(uint8_t co1, uint8_t co2, double fraction) +{ + double c1, c2; + double r1, r2; + uint8_t ret; + + c1 = ((double) co1) / 255.0; + c1 = pow((co1 >= 0) ? c1 : -c1, 2.19921875); + if (co1 <= 0) + c1 = -c1; + c2 = ((double) co2) / 255.0; + c2 = pow((co2 >= 0) ? c2 : -c2, 2.19921875); + if (co2 <= 0) + c2 = -c2; + r1 = ((c2 - c1) * fraction + c1); + r2 = pow((r1 >= 0.0) ? r1 : -r1, 1.0 / 2.19921875); + if (r1 <= 0.0) + r2 = -r2; + ret = (uint8_t) round(r2 * 255.0); + + return ret; +} + +static color_t +quadcolor_interpolate_lookup(quadcolor_t *quadcolor, color_t color1, color_t color2, UNUSED(double fraction)) +{ + color_t ret; + uint8_t dt = quadcolor->double_type - DOUBLE_INTERPOLATE_SRGB; + + ret.a = 0x00; + ret.r = interp_lut[dt][color1.r][color2.r]; + ret.g = interp_lut[dt][color1.g][color2.g]; + ret.b = interp_lut[dt][color1.b][color2.b]; + + return ret; +} + +static void +quadcolor_interpolate(quadcolor_t *quadcolor, int x, int y, int w, int h) +{ + double quotient = 0.5; + + for (int i = y; i < (y + h); i++) { + if (i & 1) for (int j = x; j < (x + w); j++) { + int prev = i - 1; + int next = i + 1; + color_t prev_color, next_color; + color_t black; + color_t interim_1, interim_2; + color_t final; + + if (i < 0) + continue; + + black.color = 0x00000000; + + if ((prev >= 0) && (prev < (y + h))) + prev_color.color = buffer32->line[prev][j]; + else + prev_color.color = 0x00000000; + + if ((next >= 0) && (next < (y + h))) + next_color.color = buffer32->line[next][j]; + else + next_color.color = 0x00000000; + + interim_1 = quadcolor_interpolate_lookup(quadcolor, prev_color, black, quotient); + interim_2 = quadcolor_interpolate_lookup(quadcolor, black, next_color, quotient); + final = quadcolor_interpolate_lookup(quadcolor, interim_1, interim_2, quotient); + + buffer32->line[i][j] = final.color; + } + } +} + +static void +quadcolor_blit_memtoscreen(quadcolor_t *quadcolor, int x, int y, int w, int h) +{ + if (quadcolor->double_type > DOUBLE_SIMPLE) + quadcolor_interpolate(quadcolor, x, y, w, h); + + video_blit_memtoscreen(x, y, w, h); +} + +void +quadcolor_poll(void *priv) +{ + quadcolor_t *quadcolor = (quadcolor_t *) priv; + int x; + int scanline_old; + int oldvc; + int xs_temp; + int ys_temp; + int old_ma; + + if (!quadcolor->linepos) { + timer_advance_u64(&quadcolor->timer, quadcolor->dispofftime); + quadcolor->cgastat |= 1; + quadcolor->linepos = 1; + scanline_old = quadcolor->scanline; + if ((quadcolor->crtc[CGA_CRTC_INTERLACE] & 3) == 3) + quadcolor->scanline = ((quadcolor->scanline << 1) + quadcolor->oddeven) & 7; + if (quadcolor->cgadispon) { + if (quadcolor->displine < quadcolor->firstline) { + quadcolor->firstline = quadcolor->displine; + video_wait_for_buffer(); + } + quadcolor->lastline = quadcolor->displine; + switch (quadcolor->double_type) { + default: + quadcolor_render(quadcolor, quadcolor->displine << 1); + quadcolor_render_blank(quadcolor, (quadcolor->displine << 1) + 1); + break; + case DOUBLE_NONE: + quadcolor_render(quadcolor, quadcolor->displine); + break; + case DOUBLE_SIMPLE: + old_ma = quadcolor->memaddr; + quadcolor_render(quadcolor, quadcolor->displine << 1); + quadcolor->memaddr = old_ma; + quadcolor_render(quadcolor, (quadcolor->displine << 1) + 1); + break; + } + } else { + switch (quadcolor->double_type) { + default: + quadcolor_render_blank(quadcolor, quadcolor->displine << 1); + break; + case DOUBLE_NONE: + quadcolor_render_blank(quadcolor, quadcolor->displine); + break; + case DOUBLE_SIMPLE: + quadcolor_render_blank(quadcolor, quadcolor->displine << 1); + quadcolor_render_blank(quadcolor, (quadcolor->displine << 1) + 1); + break; + } + } + + switch (quadcolor->double_type) { + default: + quadcolor_render_process(quadcolor, quadcolor->displine << 1); + quadcolor_render_process(quadcolor, (quadcolor->displine << 1) + 1); + break; + case DOUBLE_NONE: + quadcolor_render_process(quadcolor, quadcolor->displine); + break; + } + + quadcolor->scanline = scanline_old; + if (quadcolor->vc == quadcolor->crtc[CGA_CRTC_VSYNC] && !quadcolor->scanline) + quadcolor->cgastat |= 8; + quadcolor->displine++; + if (quadcolor->displine >= 360) + quadcolor->displine = 0; + } else { + timer_advance_u64(&quadcolor->timer, quadcolor->dispontime); + quadcolor->linepos = 0; + if (quadcolor->vsynctime) { + quadcolor->vsynctime--; + if (!quadcolor->vsynctime) + quadcolor->cgastat &= ~8; + quadcolor->qc2idx = 0; + quadcolor->qc2mask = 0xf0; + } + if (quadcolor->scanline == (quadcolor->crtc[CGA_CRTC_CURSOR_END] & 31) || ((quadcolor->crtc[CGA_CRTC_INTERLACE] & 3) == 3 && + quadcolor->scanline == ((quadcolor->crtc[CGA_CRTC_CURSOR_END] & 31) >> 1))) { + quadcolor->cursorvisible = 0; + } + if ((quadcolor->crtc[CGA_CRTC_INTERLACE] & 3) == 3 && quadcolor->scanline == (quadcolor->crtc[CGA_CRTC_MAX_SCANLINE_ADDR] >> 1)) + quadcolor->memaddr_backup = quadcolor->memaddr; + if (quadcolor->vadj) { + quadcolor->scanline++; + quadcolor->scanline &= 31; + quadcolor->memaddr = quadcolor->memaddr_backup; + quadcolor->vadj--; + if (!quadcolor->vadj) { + quadcolor->cgadispon = 1; + quadcolor->memaddr = quadcolor->memaddr_backup = (quadcolor->crtc[CGA_CRTC_START_ADDR_LOW] | (quadcolor->crtc[CGA_CRTC_START_ADDR_HIGH] << 8)) & DEVICE_VRAM_MASK; + quadcolor->scanline = 0; + } + } else if (quadcolor->scanline == quadcolor->crtc[CGA_CRTC_MAX_SCANLINE_ADDR]) { + quadcolor->memaddr_backup = quadcolor->memaddr; + quadcolor->scanline = 0; + oldvc = quadcolor->vc; + quadcolor->vc++; + quadcolor->vc &= 127; + + if (quadcolor->vc == quadcolor->crtc[CGA_CRTC_VDISP]) + quadcolor->cgadispon = 0; + + if (oldvc == quadcolor->crtc[CGA_CRTC_VTOTAL]) { + quadcolor->vc = 0; + quadcolor->vadj = quadcolor->crtc[CGA_CRTC_VTOTAL_ADJUST]; + if (!quadcolor->vadj) { + quadcolor->cgadispon = 1; + quadcolor->memaddr = quadcolor->memaddr_backup = (quadcolor->crtc[CGA_CRTC_START_ADDR_LOW] | (quadcolor->crtc[CGA_CRTC_START_ADDR_HIGH] << 8)) & DEVICE_VRAM_MASK; + } + + switch (quadcolor->crtc[CGA_CRTC_CURSOR_START] & 0x60) { + case 0x20: + quadcolor->cursoron = 0; + break; + case 0x60: + quadcolor->cursoron = quadcolor->cgablink & 0x10; + break; + default: + quadcolor->cursoron = quadcolor->cgablink & 0x08; + break; + } + } + + if (quadcolor->vc == quadcolor->crtc[CGA_CRTC_VSYNC]) { + quadcolor->cgadispon = 0; + quadcolor->displine = 0; + quadcolor->vsynctime = 16; + if (quadcolor->crtc[CGA_CRTC_VSYNC]) { + if (quadcolor->cgamode & CGA_MODE_FLAG_HIGHRES) + x = (quadcolor->crtc[CGA_CRTC_HDISP] << 3) + 16; + else + x = (quadcolor->crtc[CGA_CRTC_HDISP] << 4) + 16; + quadcolor->lastline++; + + xs_temp = x; + ys_temp = quadcolor->lastline - quadcolor->firstline; + if (quadcolor->double_type > DOUBLE_NONE) + ys_temp <<= 1; + + if ((xs_temp > 0) && (ys_temp > 0)) { + if (xs_temp < 64) + xs_temp = 656; + if (ys_temp < 32) + ys_temp = 200; + if (!enable_overscan) + xs_temp -= 16; + + if ((quadcolor->cgamode & CGA_MODE_FLAG_VIDEO_ENABLE) && ((xs_temp != xsize) || + (ys_temp != ysize) || video_force_resize_get())) { + xsize = xs_temp; + ysize = ys_temp; + if (quadcolor->double_type > DOUBLE_NONE) + set_screen_size(xsize, ysize + (enable_overscan ? 16 : 0)); + else + set_screen_size(xsize, ysize + (enable_overscan ? 8 : 0)); + + if (video_force_resize_get()) + video_force_resize_set(0); + } + + if (quadcolor->double_type > DOUBLE_NONE) { + if (enable_overscan) + quadcolor_blit_memtoscreen(quadcolor, 0, (quadcolor->firstline - 4) << 1, + xsize, ((quadcolor->lastline - quadcolor->firstline) << 1) + 16); + else + quadcolor_blit_memtoscreen(quadcolor, 8, quadcolor->firstline << 1, + xsize, (quadcolor->lastline - quadcolor->firstline) << 1); + } else { + if (enable_overscan) + video_blit_memtoscreen(0, quadcolor->firstline - 4, + xsize, (quadcolor->lastline - quadcolor->firstline) + 8); + else + video_blit_memtoscreen(8, quadcolor->firstline, + xsize, quadcolor->lastline - quadcolor->firstline); + } + } + + frames++; + + video_res_x = xsize; + video_res_y = ysize; + if (quadcolor->cgamode & CGA_MODE_FLAG_HIGHRES) { + video_res_x /= 8; + video_res_y /= quadcolor->crtc[CGA_CRTC_MAX_SCANLINE_ADDR] + 1; + video_bpp = 0; + } else if (!(quadcolor->cgamode & CGA_MODE_FLAG_GRAPHICS)) { + video_res_x /= 16; + video_res_y /= quadcolor->crtc[CGA_CRTC_MAX_SCANLINE_ADDR] + 1; + video_bpp = 0; + } else if (!(quadcolor->cgamode & CGA_MODE_FLAG_HIGHRES_GRAPHICS)) { + video_res_x /= 2; + video_bpp = 2; + } else + video_bpp = 1; + } + quadcolor->firstline = 1000; + quadcolor->lastline = 0; + quadcolor->cgablink++; + quadcolor->oddeven ^= 1; + } + } else { + quadcolor->scanline++; + quadcolor->scanline &= 31; + quadcolor->memaddr = quadcolor->memaddr_backup; + } + if (quadcolor->cgadispon) + quadcolor->cgastat &= ~1; + if (quadcolor->scanline == (quadcolor->crtc[CGA_CRTC_CURSOR_START] & 31) || ((quadcolor->crtc[CGA_CRTC_INTERLACE] & 3) == 3 && + quadcolor->scanline == ((quadcolor->crtc[CGA_CRTC_CURSOR_START] & 31) >> 1))) + quadcolor->cursorvisible = 1; + if (quadcolor->cgadispon && (quadcolor->cgamode & CGA_MODE_FLAG_HIGHRES)) { + for (x = 0; x < (quadcolor->crtc[CGA_CRTC_HDISP] << 1); x++) + quadcolor->charbuffer[x] = quadcolor->vram[(quadcolor->page_offset ^ ((quadcolor->memaddr << 1) + x)) & DEVICE_VRAM_MASK]; + } + } +} + +void +quadcolor_init(quadcolor_t *quadcolor) +{ + timer_add(&quadcolor->timer, quadcolor_poll, quadcolor, 1); + quadcolor->composite = 0; +} + +void * +quadcolor_standalone_init(UNUSED(const device_t *info)) +{ + int display_type; + quadcolor_t *quadcolor = calloc(1, sizeof(quadcolor_t)); + + video_inform(VIDEO_FLAG_TYPE_CGA, &timing_quadcolor); + + display_type = device_get_config_int("display_type"); + quadcolor->composite = (display_type != CGA_RGB); + quadcolor->revision = device_get_config_int("composite_type"); + quadcolor->has_2nd_charset = device_get_config_int("has_2nd_charset"); + quadcolor->has_quadcolor_2 = device_get_config_int("has_quadcolor_2"); + + quadcolor->vram = malloc(DEVICE_VRAM); + quadcolor->vram_2 = malloc(0x10000); + + cga_comp_init(quadcolor->revision); + timer_add(&quadcolor->timer, quadcolor_poll, quadcolor, 1); + mem_mapping_add(&quadcolor->mapping, 0xb8000, 0x08000, quadcolor_read, NULL, NULL, quadcolor_write, NULL, NULL, NULL /*quadcolor->vram*/, MEM_MAPPING_EXTERNAL, quadcolor); + /* add mapping for vram_2 at 0xd0000, mirrored at 0xe0000 */ + if (quadcolor->has_quadcolor_2) + mem_mapping_add(&quadcolor->mapping_2, 0xd0000, 0x20000, quadcolor_2_read, NULL, NULL, quadcolor_2_write, NULL, NULL, NULL, MEM_MAPPING_EXTERNAL, + quadcolor); + + io_sethandler(0x03d0, 0x0010, quadcolor_in, NULL, NULL, quadcolor_out, NULL, NULL, quadcolor); + + overscan_x = overscan_y = 16; + + quadcolor->rgb_type = device_get_config_int("rgb_type"); + cga_palette = (quadcolor->rgb_type << 1); + cgapal_rebuild(); + update_cga16_color(quadcolor->cgamode); + + quadcolor->double_type = device_get_config_int("double_type"); + + for (uint16_t i = 0; i < 256; i++) { + for (uint16_t j = 0; j < 256; j++) { + interp_lut[0][i][j] = quadcolor_interpolate_srgb(i, j, 0.5); + interp_lut[1][i][j] = quadcolor_interpolate_linear(i, j, 0.5); + } + } + + switch(device_get_config_int("font")) { + case 0: + loadfont(FONT_IBM_MDA_437_PATH, 0); + break; + case 1: + loadfont(FONT_IBM_MDA_437_NORDIC_PATH, 0); + break; + case 4: + loadfont(FONT_TULIP_DGA_PATH, 0); + break; + } + + return quadcolor; +} + +void +quadcolor_close(void *priv) +{ + quadcolor_t *quadcolor = (quadcolor_t *) priv; + + free(quadcolor->vram); + free(quadcolor->vram_2); + free(quadcolor); +} + +void +quadcolor_speed_changed(void *priv) +{ + quadcolor_t *quadcolor = (quadcolor_t *) priv; + + quadcolor_recalctimings(quadcolor); +} + +// clang-format off +const device_config_t quadcolor_config[] = { + { + .name = "display_type", + .description = "Display type", + .type = CONFIG_SELECTION, + .default_string = NULL, + .default_int = CGA_RGB, + .file_filter = NULL, + .spinner = { 0 }, + .selection = { + { .description = "RGB", .value = CGA_RGB }, + { .description = "Composite", .value = CGA_COMPOSITE }, + { .description = "" } + }, + .bios = { { 0 } } + }, + { + .name = "composite_type", + .description = "Composite type", + .type = CONFIG_SELECTION, + .default_string = NULL, + .default_int = COMPOSITE_OLD, + .file_filter = NULL, + .spinner = { 0 }, + .selection = { + { .description = "Old", .value = COMPOSITE_OLD }, + { .description = "New", .value = COMPOSITE_NEW }, + { .description = "" } + }, + .bios = { { 0 } } + }, + { + .name = "rgb_type", + .description = "RGB type", + .type = CONFIG_SELECTION, + .default_string = NULL, + .default_int = 5, + .file_filter = NULL, + .spinner = { 0 }, + .selection = { + { .description = "Color (generic)", .value = 0 }, + { .description = "Green Monochrome", .value = 1 }, + { .description = "Amber Monochrome", .value = 2 }, + { .description = "Gray Monochrome", .value = 3 }, + { .description = "Color (no brown)", .value = 4 }, + { .description = "Color (IBM 5153)", .value = 5 }, + { .description = "" } + }, + .bios = { { 0 } } + }, + { + .name = "double_type", + .description = "Line doubling type", + .type = CONFIG_SELECTION, + .default_string = NULL, + .default_int = DOUBLE_NONE, + .file_filter = NULL, + .spinner = { 0 }, + .selection = { + { .description = "None", .value = DOUBLE_NONE }, + { .description = "Simple doubling", .value = DOUBLE_SIMPLE }, + { .description = "sRGB interpolation", .value = DOUBLE_INTERPOLATE_SRGB }, + { .description = "Linear interpolation", .value = DOUBLE_INTERPOLATE_LINEAR }, + { .description = "" } + }, + .bios = { { 0 } } + }, + { + .name = "font", + .description = "Font", + .type = CONFIG_SELECTION, + .default_string = NULL, + .default_int = 0, + .file_filter = NULL, + .spinner = { 0 }, + .selection = { + { .description = "US (CP 437)", .value = 0 }, + { .description = "IBM Nordic (CP 437-Nordic)", .value = 1 }, + { .description = "Tulip DGA", .value = 4 }, + { .description = "" } + }, + .bios = { { 0 } } + }, + { + .name = "has_2nd_charset", + .description = "Has secondary 8x8 character set", + .type = CONFIG_BINARY, + .default_string = NULL, + .default_int = 0, + .file_filter = NULL, + .spinner = { 0 }, + .selection = { { 0 } }, + .bios = { { 0 } } + }, + { + .name = "has_quadcolor_2", + .description = "Has Quadcolor II daughter board", + .type = CONFIG_BINARY, + .default_string = NULL, + .default_int = 1, + .file_filter = NULL, + .spinner = { 0 }, + .selection = { { 0 } }, + .bios = { { 0 } } + }, + { + .name = "contrast", + .description = "Alternate monochrome contrast", + .type = CONFIG_BINARY, + .default_string = NULL, + .default_int = 0, + .file_filter = NULL, + .spinner = { 0 }, + .selection = { { 0 } }, + .bios = { { 0 } } + }, + { .name = "", .description = "", .type = CONFIG_END } +}; +// clang-format on + +const device_t quadcolor_device = { + .name = "Quadram Quadcolor I / I+II", + .internal_name = "quadcolor", + .flags = DEVICE_ISA, + .local = 0, + .init = quadcolor_standalone_init, + .close = quadcolor_close, + .reset = NULL, + .available = NULL, + .speed_changed = quadcolor_speed_changed, + .force_redraw = NULL, + .config = quadcolor_config +}; diff --git a/src/video/vid_table.c b/src/video/vid_table.c index b97defe8c..2e42e5c2c 100644 --- a/src/video/vid_table.c +++ b/src/video/vid_table.c @@ -67,6 +67,7 @@ video_cards[] = { { .device = &ati18800_device, .flags = VIDEO_FLAG_TYPE_NONE }, { .device = &ati18800_wonder_device, .flags = VIDEO_FLAG_TYPE_NONE }, { .device = &cga_device, .flags = VIDEO_FLAG_TYPE_NONE }, + { .device = &quadcolor_device, .flags = VIDEO_FLAG_TYPE_NONE }, { .device = &sega_device, .flags = VIDEO_FLAG_TYPE_NONE }, { .device = &jega_device, .flags = VIDEO_FLAG_TYPE_NONE }, { .device = &gd5401_isa_device, .flags = VIDEO_FLAG_TYPE_NONE },