// license:BSD-3-Clause // copyright-holders:Sergey Svishchev /********************************************************************* agat9video.cpp Implementation of Agat-9 onboard video. 6 native video modes: - 32x32 color text - 64x32 mono text with reverse video - 128x128 16 color graphics - 256x256 4 color graphics - 256x256 and 512x256 mono graphics 2 apple video modes: 40col text and HGR. C7xx: video mode select *********************************************************************/ #include "emu.h" #include "video/agat9.h" #include "screen.h" #define BLACK 0 #define RED 1 #define GREEN 2 #define YELLOW 3 #define BLUE 4 #define PURPLE 5 #define CYAN 6 #define WHITE 7 /*************************************************************************** PARAMETERS ***************************************************************************/ //************************************************************************** // GLOBAL VARIABLES //************************************************************************** DEFINE_DEVICE_TYPE(AGAT9VIDEO, agat9video_device, "agat9video", "Agat-9 Video") //------------------------------------------------- // device_add_mconfig - add device configuration //------------------------------------------------- void agat9video_device::device_add_mconfig(machine_config &config) { SCREEN(config, m_screen, SCREEN_TYPE_RASTER); m_screen->set_raw(XTAL(10'500'000), 672, 0, 512, 312, 0, 256); m_screen->set_screen_update(FUNC(agat9video_device::screen_update)); m_screen->set_palette(DEVICE_SELF); } //************************************************************************** // LIVE DEVICE //************************************************************************** agat9video_device::agat9video_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : device_t(mconfig, AGAT9VIDEO, tag, owner, clock), device_palette_interface(mconfig, *this), m_screen(*this, "a9screen"), m_ram_dev(*this, finder_base::DUMMY_TAG), m_char_region(*this, finder_base::DUMMY_TAG), m_char_ptr(nullptr), m_char_size(0), m_start_address(0) { } //------------------------------------------------- // device_start - device-specific startup //------------------------------------------------- void agat9video_device::device_start() { int i, j; uint16_t c; static const uint8_t hires_artifact_color_table[] = { BLACK, PURPLE, GREEN, WHITE, BLACK, BLUE, RED, WHITE }; m_char_ptr = m_char_region->base(); m_char_size = m_char_region->bytes(); /* 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 { 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]; } } // per http://agatcomp.ru/Reading/IiO/87-2-077.djvu for (int i = 0; 8 > i; ++i) { set_pen_color(i + 0, rgb_t(BIT(i, 0) ? 0xff : 0, BIT(i, 1) ? 0xff : 0, BIT(i, 2) ? 0xff : 0)); set_pen_color(i + 8, rgb_t(BIT(i, 0) ? 0x7f : 0, BIT(i, 1) ? 0x7f : 0, BIT(i, 2) ? 0x7f : 0)); } save_item(NAME(m_start_address)); save_item(NAME(m_page2)); save_item(NAME(m_flash)); save_item(NAME(m_mix)); save_item(NAME(m_graphics)); } void agat9video_device::device_reset() { // XXX to be confirmed m_video_mode = TEXT_LORES; m_start_address = 0x7800; m_mode = palette_index = 0; // apple m_page2 = false; m_flash = false; m_mix = false; m_graphics = false; } READ8_MEMBER(agat9video_device::read) { if(!machine().side_effects_disabled()) do_io(offset); // XXX only 'Moscow' revision return m_mode; } WRITE8_MEMBER(agat9video_device::write) { do_io(offset); } READ8_MEMBER(agat9video_device::apple_read) { logerror("%s: %04x read (%s)\n", machine().describe_context(), 0xc050 + offset, offset<8?"apple":"palette"); if(!machine().side_effects_disabled()) do_apple_io(offset); // XXX return m_mode; } WRITE8_MEMBER(agat9video_device::apple_write) { logerror("%s: %04x write (%s)\n", machine().describe_context(), 0xc050 + offset, offset<8?"apple":"palette"); do_apple_io(offset); } void agat9video_device::do_apple_io(int offset) { if (offset < 0x8) { m_video_mode = APPLE; m_screen->set_visible_area(0, 280-1, 0, 192-1); } switch (offset) { case 0x0: m_graphics = true; break; case 0x1: m_graphics = false; break; case 0x2: m_mix = false; break; case 0x3: m_mix = true; break; case 0x4: m_page2 = false; break; case 0x5: m_page2 = true; break; case 0x8: palette_index &= 0xfe; break; case 0x9: palette_index |= 0x01; break; case 0xa: palette_index &= 0xfd; break; case 0xb: palette_index |= 0x02; break; } } void agat9video_device::do_io(int offset) { logerror("%s: %04x access, ", machine().describe_context(), 0xc700 + offset); m_mode = offset; m_screen->set_visible_area(0, 2*256-1, 0, 256-1); switch (offset & 3) { case 0: // 256x256, 4 colors, 64 bytes per scanline, 0x4000 page length, odd scanlines at 0x2000 m_video_mode = GRAPHICS_COLOR_HIRES; m_start_address = ((offset & 0x60) << 9) + ((offset & 0x08) << 13); logerror("offset %04X, video mode 0 (COLOR_HIRES)\n", m_start_address); break; case 1: // 128x128, 16 colors, 64 bytes per scanline, 0x2000 page length, linear m_video_mode = GRAPHICS_COLOR_LORES; m_start_address = ((offset & 0x70) << 9) + ((offset & 0x08) << 13); logerror("offset %04X, video mode 1 (COLOR_LORES)\n", m_start_address); break; // b6..b4 == page number, b3..b2 == subpage number case 2: // 64x32, 0x1000 page length if (offset > 0x80) { m_video_mode = TEXT_HIRES; m_start_address = (offset - 0x82) << 9; logerror("offset %04X, video mode 2 (TEXT_HIRES)\n", m_start_address); } // 32x32, 0x1000 page length else { m_video_mode = TEXT_LORES; m_start_address = (offset - 0x02) << 9; logerror("offset %04X, video mode 2 (TEXT_LORES)\n", m_start_address); } break; case 3: // 512x256, 64 bytes per scanline, 0x4000 page length, odd scanlines at 0x2000 if (offset > 0x80) { m_video_mode = GRAPHICS_MONO_HIRES; m_start_address = ((offset & 0x60) << 9) + ((offset & 0x08) << 13); logerror("offset %04X, video mode 3 (MONO_HIRES)\n", m_start_address); } // 256x256, 32 bytes per scanline, 0x2000 page length, linear else { m_video_mode = GRAPHICS_MONO_LORES; m_start_address = ((offset & 0x70) << 9) + ((offset & 0x08) << 13); logerror("offset %04X, video mode 3 (MONO_LORES)\n", m_start_address); } break; } } void agat9video_device::plot_text_character(bitmap_ind16 &bitmap, int xpos, int ypos, int xscale, uint32_t code, const uint8_t *textgfx_data, uint32_t textgfx_datalen, int fg, int bg) { int x, y, i; const uint8_t *chardata; uint16_t color; /* look up the character data */ chardata = &textgfx_data[(code * 8)]; for (y = 0; y < 8; y++) { for (x = 0; x < 8; x++) { color = (chardata[y] & (1 << (7 - x))) ? fg : bg; for (i = 0; i < xscale; i++) { bitmap.pix16(ypos + y, xpos + (x * xscale) + i) = color; } } } } int color_1_p[4] = { 0, 4, 0, 5 }; int color_2_p[4][2] = { { 0, 7 }, { 7, 0 }, { 0, 2 }, { 2, 0 }, }; void agat9video_device::text_update_lores(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect, int beginrow, int endrow) { int row, col; uint32_t address; uint8_t ch, attr; int fg = 0; int bg = color_1_p[palette_index]; beginrow = std::max(beginrow, cliprect.min_y - (cliprect.min_y % 8)); endrow = std::min(endrow, cliprect.max_y - (cliprect.max_y % 8) + 7); for (row = beginrow; row <= endrow; row += 8) { for (col = 0; col < 32; col++) { /* calculate address */ address = m_start_address + (col * 2) + (row * 8); ch = m_ram_dev->read(address); attr = m_ram_dev->read(address + 1); fg = bitswap<8>(attr,7,6,5,3,4,2,1,0) & 15; if (BIT(attr, 5)) { plot_text_character(bitmap, col * 16, row, 2, ch, m_char_ptr, m_char_size, fg, bg); } else { plot_text_character(bitmap, col * 16, row, 2, ch, m_char_ptr, m_char_size, bg, fg); } } } } void agat9video_device::text_update_hires(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect, int beginrow, int endrow) { int row, col; uint32_t address; uint8_t ch; int fg, bg; beginrow = std::max(beginrow, cliprect.min_y - (cliprect.min_y % 8)); endrow = std::min(endrow, cliprect.max_y - (cliprect.max_y % 8) + 7); if (m_start_address & 0x800) { fg = color_2_p[palette_index][1]; bg = color_2_p[palette_index][0]; } else { fg = color_2_p[palette_index][0]; bg = color_2_p[palette_index][1]; } for (row = beginrow; row <= endrow; row += 8) { for (col = 0; col < 64; col++) { /* calculate address */ address = m_start_address + col + (row * 8); ch = m_ram_dev->read(address); plot_text_character(bitmap, col * 8, row, 1, ch, m_char_ptr, m_char_size, fg, bg); } } } void agat9video_device::graph_update_mono_lores(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect, int beginrow, int endrow) { int row, col, b; uint32_t address; uint16_t *p; uint8_t gfx, v; int fg = 7, bg = 0; beginrow = std::max(beginrow, cliprect.min_y - (cliprect.min_y % 8)); endrow = std::min(endrow, cliprect.max_y - (cliprect.max_y % 8) + 7); for (row = beginrow; row <= endrow; row++) { p = &bitmap.pix16(row); for (col = 0; col < 32; col++) { address = m_start_address + col + (row * 0x20); gfx = m_ram_dev->read(address); for (b = 0; b < 8; b++) { v = (gfx & 0x80) >> 7; v = color_2_p[palette_index][v]; gfx <<= 1; *(p++) = v ? fg : bg; *(p++) = v ? fg : bg; } } } } void agat9video_device::graph_update_mono_hires(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect, int beginrow, int endrow) { int row, col, b; uint32_t address; uint16_t *p; uint8_t gfx, v; int fg = 7, bg = 0; beginrow = std::max(beginrow, cliprect.min_y - (cliprect.min_y % 8)); endrow = std::min(endrow, cliprect.max_y - (cliprect.max_y % 8) + 7); for (row = beginrow; row <= endrow; row++) { p = &bitmap.pix16(row); for (col = 0; col < 64; col++) { address = m_start_address + col + ((row / 2) * 0x40) + 0x2000 * (row & 1); gfx = m_ram_dev->read(address); for (b = 0; b < 8; b++) { v = (gfx & 0x80) >> 7; v = color_2_p[palette_index][v]; gfx <<= 1; *(p++) = v ? fg : bg; } } } } int color_4_p[4][4] = { { 0, 1, 2, 4 }, { 7, 1, 2, 4 }, { 0, 0, 2, 4 }, { 0, 1, 0, 4 } }; void agat9video_device::graph_update_color_hires(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect, int beginrow, int endrow) { int row, col, b; uint32_t address; uint16_t *p; uint8_t gfx, v; beginrow = std::max(beginrow, cliprect.min_y - (cliprect.min_y % 8)); endrow = std::min(endrow, cliprect.max_y - (cliprect.max_y % 8) + 7); for (row = beginrow; row <= endrow; row++) { p = &bitmap.pix16(row); for (col = 0; col < 0x40; col++) { address = m_start_address + col + ((row / 2) * 0x40) + 0x2000 * (row & 1); gfx = m_ram_dev->read(address); for (b = 0; b < 4; b++) { v = (gfx & 0xc0) >> 6; v = color_4_p[palette_index][v]; gfx <<= 2; *(p++) = v; *(p++) = v; } } } } void agat9video_device::graph_update_color_lores(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect, int beginrow, int endrow) { int row, col, b; uint32_t address; uint16_t *p; uint8_t gfx, v; beginrow = std::max(beginrow, cliprect.min_y - (cliprect.min_y % 8)); endrow = std::min(endrow, cliprect.max_y - (cliprect.max_y % 8) + 7); for (row = beginrow; row <= endrow; row++) { p = &bitmap.pix16(row); for (col = 0; col < 0x40; col++) { address = m_start_address + col + ((row / 2) * 0x40); gfx = m_ram_dev->read(address); for (b = 0; b < 2; b++) { v = (gfx & 0xf0) >> 4; gfx <<= 4; *(p++) = v; *(p++) = v; *(p++) = v; *(p++) = v; } } } } // Apple ][ video modes void agat9video_device::plot_text_character_apple(bitmap_ind16 &bitmap, int xpos, int ypos, int xscale, uint32_t code, const uint8_t *textgfx_data, uint32_t textgfx_datalen, int fg, int bg) { int x, y, i; const uint8_t *chardata; uint16_t color; if ((code >= 0x40) && (code <= 0x7f)) { code &= 0x3f; if (m_flash) { i = fg; fg = bg; bg = i; } } /* look up the character data */ chardata = &textgfx_data[(code * 8)]; for (y = 0; y < 8; y++) { for (x = 0; x < 7; x++) { color = (chardata[y] & (1 << (6 - x))) ? fg : bg; for (i = 0; i < xscale; i++) { bitmap.pix16(ypos + y, xpos + (x * xscale) + i) = color; } } } } void agat9video_device::text_update_apple(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect, int beginrow, int endrow) { int row, col; uint32_t start_address; uint32_t address; int fg, bg; start_address = m_page2 ? 0x800 : 0x400; fg = color_2_p[palette_index][1]; bg = color_2_p[palette_index][0]; beginrow = std::max(beginrow, cliprect.min_y - (cliprect.min_y % 8)); endrow = std::min(endrow, cliprect.max_y - (cliprect.max_y % 8) + 7); for (row = beginrow; row <= endrow; row += 8) { for (col = 0; col < 40; col++) { /* calculate address */ address = start_address + ((((row/8) & 0x07) << 7) | (((row/8) & 0x18) * 5 + col)); plot_text_character_apple(bitmap, col * 7, row, 1, m_ram_dev->read(address), m_char_ptr, m_char_size, fg, bg); } } } void agat9video_device::hgr_update(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect, int beginrow, int endrow) { int row, col, b, va; int offset; uint8_t vram_row[42]; uint16_t v; uint16_t *p; uint32_t w; uint16_t *artifact_map_ptr; int begincol = 0, endcol = 40; /* sanity checks */ if (beginrow < cliprect.min_y) beginrow = cliprect.min_y; if (endrow > cliprect.max_y) endrow = cliprect.max_y; if (endrow < beginrow) return; // we generate 2 pixels per "column" so adjust if (begincol < (cliprect.min_x/7)) begincol = (cliprect.min_x/7); if (endcol > (cliprect.max_x/7)) endcol = (cliprect.max_x/7); if (cliprect.max_x > 39*7) endcol = 40; if (endcol < begincol) return; va = m_page2 ? 0x4000 : 0x2000; vram_row[0] = 0; vram_row[41] = 0; for (row = beginrow; row <= endrow; row++) { for (col = begincol; col < endcol; col++) { offset = ((((row / 8) & 0x07) << 7) | (((row / 8) & 0x18) * 5 + col)) | ((row & 7) << 10); vram_row[1 + col] = m_ram_dev->read(va + offset); } p = &bitmap.pix16(row); /* * p. 50 of technical manual: * * 1. a 'zero' bit is always drawn as 'black' pixel * 2. two consecutive 'one' bits are always drawn as 'white' pixel, even bits from different bytes * 3. even pixels can be 'black', 'purple' (5) or 'blue' (4) * 4. odd pixels can be 'black', 'green' (2) or 'red' (1) * 5. bit 7 affects color of pixels in this byte. zero = 'blue' or 'red', one = 'purple' or 'green'. */ for (col = 0; col < 40; col++) { 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); artifact_map_ptr = &m_hires_artifact_map[((vram_row[col + 1] & 0x80) >> 7) * 16]; for (b = 0; b < 7; b++) { v = artifact_map_ptr[((w >> (b + 7 - 1)) & 0x07) | (((b ^ col) & 0x01) << 3)]; *(p++) = v; } } } } uint32_t agat9video_device::screen_update(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect) { switch (m_video_mode) { case APPLE: // from apple2e.cpp // always update the flash timer here so it's smooth regardless of mode switches m_flash = ((machine().time() * 4).seconds() & 1) ? true : false; if (m_graphics) { if (m_mix) { hgr_update(screen, bitmap, cliprect, 0, 159); text_update_apple(screen, bitmap, cliprect, 160, 191); } else { hgr_update(screen, bitmap, cliprect, 0, 191); } } else { text_update_apple(screen, bitmap, cliprect, 0, 191); } break; case TEXT_LORES: text_update_lores(screen, bitmap, cliprect, 0, 255); break; case TEXT_HIRES: text_update_hires(screen, bitmap, cliprect, 0, 255); break; case GRAPHICS_MONO_LORES: graph_update_mono_lores(screen, bitmap, cliprect, 0, 255); break; case GRAPHICS_MONO_HIRES: graph_update_mono_hires(screen, bitmap, cliprect, 0, 255); break; case GRAPHICS_COLOR_LORES: graph_update_color_lores(screen, bitmap, cliprect, 0, 255); break; case GRAPHICS_COLOR_HIRES: graph_update_color_hires(screen, bitmap, cliprect, 0, 255); break; default: graph_update_mono_lores(screen, bitmap, cliprect, 0, 255); break; } return 0; }