From 823422ef6c91ff5f514f314c3271a447234551bc Mon Sep 17 00:00:00 2001 From: benrg Date: Mon, 2 Jan 2023 12:14:47 -0800 Subject: Improved Apple II hires graphics color simulation (#10773) This algorithm decodes the scan line to 560 bits in the same way as the hardware (and MAME's monochrome modes) and uses a 7-bit sliding window on the decoded bits. The result is the same as before if all high bits are clear. If high bits are set, the output is shifted right by one pixel as it should be, and the unique artifact colors at the boundaries between bytes with opposite high bits are simulated. (For more about those colors see page 8-20 of Understanding the Apple II by Jim Sather.) The monochrome modes are rewritten to use the same decoding logic, which incidentally fixes some bugs: the left pixel of a 14-pixel group was not updated at 0-to-1 bit-7 transitions (leaving old pixels on the screen), the test against cliprect was incorrect, and m_dhires was ignored. --- src/mame/apple/apple2video.cpp | 269 ++++++++++++++++------------------------- src/mame/apple/apple2video.h | 1 - 2 files changed, 105 insertions(+), 165 deletions(-) diff --git a/src/mame/apple/apple2video.cpp b/src/mame/apple/apple2video.cpp index 93f1eaa1834..9f7c29c0869 100644 --- a/src/mame/apple/apple2video.cpp +++ b/src/mame/apple/apple2video.cpp @@ -48,43 +48,6 @@ a2_video_device::a2_video_device(const machine_config &mconfig, const char *tag, void a2_video_device::device_start() { - static const uint8_t hires_artifact_color_table[] = - { - BLACK, PURPLE, GREEN, WHITE, - BLACK, BLUE, ORANGE, WHITE - }; - - // generate hi-res artifact data - int i, j; - uint16_t c; - - /* 2^3 dependent pixels * 2 color sets * 2 offsets */ - m_hires_artifact_map = std::make_unique(8 * 2 * 2); - - /* build hires artifact map */ - for (i = 0; i < 8; i++) - { - for (j = 0; j < 2; j++) - { - if (i & 0x02) - { - if ((i & 0x05) != 0) - c = 3; - else - c = j ? 2 : 1; - } - else - { - if ((i & 0x05) == 0x05) - c = j ? 1 : 2; - else - c = 0; - } - m_hires_artifact_map[ 0 + j*8 + i] = hires_artifact_color_table[(c + 0) % 8]; - m_hires_artifact_map[16 + j*8 + i] = hires_artifact_color_table[(c + 4) % 8]; - } - } - // initialise for device_palette_interface init_palette(); @@ -651,10 +614,63 @@ template void a2_video_device::text_update(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect, int beginrow, int endrow); template void a2_video_device::text_update(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect, int beginrow, int endrow); +// This table defines the color of each HGR pixel based on a 7-bit sliding window. +// All colors follow from bit-reversal and bit-flip symmetries and the rules +// [0000]11x +// x[0000]xx +// [00100]xx +// x[00100]x +// x[01100]x +// [01110]xx +// x[01110]x +// where x is a don't-care bit, and the bracketed bits are the 4-bit repeating +// pattern that determines the color. The color is duplicated in both nibbles of +// each byte to slightly simplify the rotate-4-bits logic. 0x55 (an otherwise +// unused color) is a placeholder for impossible bit patterns. +static uint8_t const hgr_color_lut[0x80] = { + 0x00,0x00,0x00,0x00,0x88,0x55,0x00,0x00,0x11,0x11,0x55,0x55,0x99,0x99,0xDD,0xFF, + 0x22,0x55,0x55,0x22,0x55,0x55,0x55,0x55,0x33,0x33,0x55,0x77,0xBB,0x55,0xFF,0xFF, + 0x00,0x00,0x55,0x55,0x55,0x55,0xCC,0xCC,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55, + 0x00,0x22,0x66,0x66,0x55,0x55,0xEE,0xEE,0x77,0x77,0x55,0x55,0xFF,0xFF,0xFF,0xFF, + 0x00,0x00,0x00,0x00,0x55,0x55,0x88,0x88,0x11,0x11,0x55,0x55,0x99,0x99,0xDD,0xFF, + 0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x33,0x33,0x55,0x55,0x55,0x55,0xFF,0xFF, + 0x00,0x00,0x55,0x44,0x88,0x55,0xCC,0xCC,0x55,0x55,0x55,0x55,0xDD,0x55,0x55,0xDD, + 0x00,0x22,0x66,0x66,0x55,0x55,0xEE,0xEE,0xFF,0xFF,0x55,0x77,0xFF,0xFF,0xFF,0xFF +}; + +// This alternate table turns n repeats of 0110 into a run of 4n colored pixels, versus +// 4n-2 for the other one. n=1 runs produced by the other table are noticeably too dim, +// while with this table the brightness is consistent and roughly correct. But the +// wider runs look too wide and make text annoying to read. This table is generated by +// [0000]xx1 x[0000]xx [00100]xx x[00100]x [01100]xx x[01100]x [01110]x0 x[01110]x. +// static uint8_t const hgr_color_lut[0x80] = { +// 0x00,0x00,0x00,0x00,0x88,0x55,0xCC,0x00,0x11,0x11,0x55,0x55,0x99,0x99,0xDD,0xFF, +// 0x22,0x55,0x55,0x22,0x55,0x55,0x55,0x55,0x33,0x33,0x55,0x77,0xBB,0x55,0xFF,0xFF, +// 0x00,0x00,0x55,0x55,0x55,0x55,0xCC,0xCC,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55, +// 0x66,0x66,0x66,0x66,0x55,0x55,0xEE,0xEE,0x77,0x33,0x55,0x55,0xFF,0xFF,0xFF,0xFF, +// 0x00,0x00,0x00,0x00,0x55,0x55,0xCC,0x88,0x11,0x11,0x55,0x55,0x99,0x99,0x99,0x99, +// 0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x33,0x33,0x55,0x55,0x55,0x55,0xFF,0xFF, +// 0x00,0x00,0x55,0x44,0x88,0x55,0xCC,0xCC,0x55,0x55,0x55,0x55,0xDD,0x55,0x55,0xDD, +// 0x00,0x22,0x66,0x66,0x55,0x55,0xEE,0xEE,0xFF,0x33,0x55,0x77,0xFF,0xFF,0xFF,0xFF +// }; + +static unsigned decode_hires_byte(uint8_t byte, unsigned last_output_bit) { + // duplicate the bottom 7 bits by bit twiddling + unsigned word = byte; + word = (word ^ (word << 4)) & 0x70F; + word = (word ^ (word << 2)) & 0x1333; + word = (word ^ (word << 1)) & 0x1555; + word *= 3; + // shift right on the screen (left in the word) if the high bit is set + if (byte & 0x80) + { + word = (word * 2 + last_output_bit) & 0x3FFF; + } + return word; +} + void a2_video_device::hgr_update(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect, int beginrow, int endrow) { - int mon_type = m_sysconfig & 0x03; - /* sanity checks */ if (beginrow < cliprect.top()) beginrow = cliprect.top(); @@ -663,150 +679,75 @@ void a2_video_device::hgr_update(screen_device &screen, bitmap_ind16 &bitmap, co if (endrow < beginrow) return; + int mon_type = m_sysconfig & 0x03; + + // CEC mono HGR mode + if ((m_monohgr) && (mon_type == 0)) + { + mon_type = 1; + } + + // IIgs $C021 monochrome HGR + if (m_monochrome & 0x80) + { + mon_type = 1; + } + + int fg; + int const bg = BLACK; + + switch (mon_type) + { + default: fg = WHITE; break; + case 2: fg = GREEN; break; + case 3: fg = ORANGE; break; + } + uint8_t const *const vram = &m_ram_ptr[(m_page2 ? 0x4000 : 0x2000)]; - uint8_t vram_row[42]; - vram_row[0] = 0; - vram_row[41] = 0; + + // verified on h/w: setting dhires w/o 80col emulates a rev. 0 Apple ][ with no orange/blue + uint8_t const bit7_mask = m_dhires ? 0x7f : 0xff; for (int row = beginrow; row <= endrow; row++) { - for (int col = 0; col < 40; col++) - { - int const offset = ((((row/8) & 0x07) << 7) | (((row/8) & 0x18) * 5 + col)) | ((row & 7) << 10); - vram_row[1+col] = vram[offset]; - } + uint8_t const *const vram_row = vram + (((row/8) & 0x07) << 7) + (((row/8) & 0x18) * 5) + ((row & 7) << 10); + + // w holds 3+14+14=31 bits: 3 bits of the previous 14-pixel group and the current and next groups. + uint32_t w = decode_hires_byte(vram_row[0] & bit7_mask, 0) << (3 + 14); uint16_t *p = &bitmap.pix(row); for (int col = 0; col < 40; col++) { - uint32_t w = (((uint32_t) vram_row[col+0] & 0x7f) << 0) - | (((uint32_t) vram_row[col+1] & 0x7f) << 7) - | (((uint32_t) vram_row[col+2] & 0x7f) << 14); - - // verified on h/w: setting dhires w/o 80col emulates a rev. 0 Apple ][ with no orange/blue - uint16_t const *artifact_map_ptr; - if (m_dhires) - { - artifact_map_ptr = m_hires_artifact_map.get(); - } - else - { - artifact_map_ptr = &m_hires_artifact_map[((vram_row[col + 1] & 0x80) >> 7) * 16]; - } - - // CEC mono HGR mode - if ((m_monohgr) && (mon_type == 0)) - { - mon_type = 1; - } - - // IIgs $C021 monochrome HGR - if (m_monochrome & 0x80) + w >>= 14; + if (col + 1 < 40) { - mon_type = 1; + w |= decode_hires_byte(vram_row[col + 1] & bit7_mask, w >> (3 + 13)) << (3 + 14); } switch (mon_type) { - case 0: - for (int b = 0; b < 7; b++) - { - if ((((col*14) + b) >= cliprect.left()) && (((col*14) + b) <= cliprect.right())) - { - uint16_t const v = artifact_map_ptr[((w >> (b + 7-1)) & 0x07) | (((b ^ col) & 0x01) << 3)]; - *(p++) = v; - *(p++) = v; - } - else - { - p++; - p++; - } - } - break; - - case 1: - w >>= 7; - if (vram_row[col+1] & 0x80) - { - p++; - } - for (int b = 0; b < 7; b++) - { - uint16_t const v = (w & 1); - w >>= 1; - if ((((col*14) + b) >= cliprect.left()) && (((col*14) + b) <= cliprect.right())) - { - *(p++) = v ? WHITE : BLACK; - *(p++) = v ? WHITE : BLACK; - } - else - { - p++; - p++; - } - } - if (vram_row[col+1] & 0x80) - { - p--; - } - break; - - case 2: - w >>= 7; - if (vram_row[col+1] & 0x80) - { - p++; - } - for (int b = 0; b < 7; b++) - { - uint16_t const v = (w & 1); - w >>= 1; - if ((((col*14) + b) >= cliprect.left()) && (((col*14) + b) <= cliprect.right())) - { - *(p++) = v ? GREEN : BLACK; - *(p++) = v ? GREEN : BLACK; - } - else - { - p++; - p++; - } - } - if (vram_row[col+1] & 0x80) + case 0: + for (int b = 0; b < 14; b++) + { + if (((col * 14 + b) >= cliprect.left()) && ((col * 14 + b) <= cliprect.right())) { - p--; + *p = uint8_t(hgr_color_lut[(w >> b) & 0x7f] << ((col * 14 + b) & 3)) >> 4; } - break; + p++; + } + break; - case 3: - w >>= 7; - if (vram_row[col+1] & 0x80) - { - p++; - } - for (int b = 0; b < 7; b++) - { - uint16_t const v = (w & 1); - w >>= 1; - if ((((col*14) + b) >= cliprect.left()) && (((col*14) + b) <= cliprect.right())) - { - *(p++) = v ? ORANGE : BLACK; - *(p++) = v ? ORANGE : BLACK; - } - else - { - p++; - p++; - } - } - if (vram_row[col+1] & 0x80) + default: // 1, 2, 3 + for (int b = 0; b < 14; b++) + { + if (((col * 14 + b) >= cliprect.left()) && ((col * 14 + b) <= cliprect.right())) { - p--; + *p = (w & (8 << b)) ? fg : bg; } - break; - + p++; + } + break; } } } diff --git a/src/mame/apple/apple2video.h b/src/mame/apple/apple2video.h index e0edd659ef3..d16c651a9f1 100644 --- a/src/mame/apple/apple2video.h +++ b/src/mame/apple/apple2video.h @@ -35,7 +35,6 @@ public: u8 m_GSfg = 0, m_GSbg = 0, m_GSborder = 0, m_newvideo = 0, m_monochrome = 0, m_rgbmode = 0; u32 m_GSborder_colors[16]{}, m_shr_palette[256]{}; std::unique_ptr m_8bit_graphics; - std::unique_ptr m_hires_artifact_map; u8 *m_ram_ptr = nullptr, *m_aux_ptr = nullptr, *m_char_ptr = nullptr; u16 m_aux_mask = 0xffff; -- cgit v1.2.3