// license:BSD-3-Clause // copyright-holders:Patrick Mackinlay /* * Silicon Graphics RE2 device. * * TODO: * - z buffer r/w and function * - stipple, dither, raster operations * - other pixel modes * - line drawing */ #include "emu.h" #include "debugger.h" #include "sgi_re2.h" #define LOG_GENERAL (1U << 0) #define LOG_REG (1U << 1) //#define VERBOSE (LOG_GENERAL|LOG_REG) #include "logmacro.h" static char const *const regname[] = { nullptr, nullptr, nullptr, nullptr, "ENABRGB", "BIGENDIAN", "FUNC", "HADDR", "NOPUP", "XYFRAC", "RGB", "YX", "PUPDATA", "PATL", "PATH", "DZI", "DZF", "DR", "DG", "DB", "Z", "R", "G", "B", "STIP", "STIPCOUNT", "DX", "DY", "NUMPIX", "X", "Y", "IR", "RWDATA", "PIXMASK", "AUXMASK", "WIDDATA", "UAUXDATA", "RWMODE", "READBUF", "PIXTYPE", "ASELECT", "ALIGNPAT", "ENABPAT", "ENABSTIP", "ENABDITH", "ENABWID", "CURWID", "DEPTHFN", "REPSTIP", "ENABLWID", "FBOPTION", "TOPSCAN", "TESTMODE", "TESTDATA", "ZBOPTION", "XZOOM", "UPACMODE", "YMIN", "YMAX", "XMIN", "XMAX", "COLORCMP", "MEGOPTION", nullptr, }; static u32 const regmask[] = { 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000001, 0x00000001, 0x0000000f, 0x00000003, 0x00000001, 0x0000000f, 0x07ffffff, 0x003fffff, 0x00000003, 0x0000ffff, 0x0000ffff, 0x00ffffff, 0x00003fff, 0x00ffffff, 0x000fffff, 0x000fffff, 0x00ffffff, 0x007fffff, 0x0007ffff, 0x0007ffff, 0x0000ffff, 0x000000ff, 0x0000ffff, 0x0000ffff, 0x000007ff, 0x0000ffff, 0x000007ff, 0x00000007, 0xffffffff, 0x00ffffff, 0x000001ff, 0x0000000f, 0x0000000f, 0x00000007, 0x00000001, 0x00000003, 0x0000003f, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x0000000f, 0x0000000f, 0x000000ff, 0x00000001, 0x00000003, 0x0003ffff, 0x00000001, 0x00003fff, 0x00000001, 0x000000ff, 0x00000003, 0x000007ff, 0x000007ff, 0x00000fff, 0x00000fff, 0x00000001, 0x00000001, 0x00000000, }; DEFINE_DEVICE_TYPE(SGI_RE2, sgi_re2_device, "sgi_re2", "SGI Raster Engine 2") sgi_re2_device::sgi_re2_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock) : device_t(mconfig, SGI_RE2, tag, owner, clock) , m_xmap(*this, "^xmap%u", 0U) , m_cursor(*this, "^cursor%u", 0U) , m_ramdac(*this, "^ramdac%u", 0U) , m_options_port(*this, "^options") , m_rdy_cb(*this) , m_drq_cb(*this) , m_rdy(false) , m_drq(false) { } void sgi_re2_device::device_start() { m_rdy_cb.resolve(); m_drq_cb.resolve(); m_vram = std::make_unique(1280 * 1024); m_dram = std::make_unique(1280 * 1024); // save state for (unsigned i = 0; i < ARRAY_LENGTH(m_reg); i++) if (regmask[i]) save_item(m_reg[i], regname[i]); set_rdy(true); set_drq(false); m_step = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(sgi_re2_device::step), this)); } void sgi_re2_device::device_reset() { u8 const options = m_options_port->read(); m_vram_mask = 0xffffffffU; m_dram_mask = (options & 0x10) ? 0xffffffff : 0; m_state = IDLE; m_ir_pending = false; set_rdy(true); set_drq(false); for (u32 ® : m_reg) reg = 0; // reset register values indicate presence of RE2 m_reg[REG_DZF] = 0; m_reg[REG_DZF] = ~u32(0); // FIXME: how is clipping disabled on reset? m_clip.set(0, 1279, 0, 1023); } u32 sgi_re2_device::reg_r(offs_t offset) { u32 data = 0xffffffff & regmask[offset]; // only some registers can be read switch (offset) { case REG_RWDATA: if (m_state == DMA_R) { data = m_reg[REG_RWDATA]; read_buffer(); step(); } else logerror("rwdata read when empty\n"); break; case REG_DZI: case REG_DZF: case REG_STIP: case REG_STIPCOUNT: data = m_reg[offset]; LOGMASKED(LOG_REG, "reg_r register 0x%02x (%s) data 0x%x\n", offset, regname[offset], data); break; default: logerror("reg_r unhandled register 0x%02x\n", offset); break; } return data; } void sgi_re2_device::reg_w(offs_t offset, u32 data) { if (regmask[offset]) { m_reg[offset] = data & regmask[offset]; if (offset != REG_RWDATA) LOGMASKED(LOG_REG, "reg_w register 0x%02x (%s) data 0x%x\n", offset, regname[offset], m_reg[offset]); // special case register handling switch (offset) { case REG_RGB: m_reg[REG_B] = (data & 0x000000ff) << 11; m_reg[REG_G] = (data & 0x0000ff00) << 3; m_reg[REG_R] = (data & 0x0fff0000) >> 5; break; case REG_YX: m_reg[REG_X] = (data & 0x00000fff) >> 0; m_reg[REG_Y] = (data & 0x007ff000) >> 12; break; case REG_IR: m_ir_pending = true; if (m_state == IDLE) step(); break; case REG_RWDATA: if (m_state == DMA_W) { write_buffer(); step(); } break; case REG_YMIN: case REG_YMAX: case REG_XMIN: case REG_XMAX: m_clip.set( (m_reg[REG_XMIN] >> 3) * 5 + (m_reg[REG_XMIN] & 0x7), (m_reg[REG_XMAX] >> 3) * 5 + (m_reg[REG_XMAX] & 0x7), m_reg[REG_YMIN], m_reg[REG_YMAX]); break; } } else logerror("reg_w unhandled register 0x%02x data 0x%x\n", offset, data); } void sgi_re2_device::step(void *ptr, int param) { switch (m_state) { case IDLE: set_rdy(!m_ir_pending); if (m_ir_pending) { m_ir_pending = false; m_state = EXECUTE; m_step->adjust(attotime::zero); } break; case EXECUTE: execute(); if (m_state == IDLE) step(); break; default: break; } } void sgi_re2_device::execute() { // load buffered registers m_enabrgb = bool(m_reg[REG_ENABRGB]); m_bigendian = bool(m_reg[REG_BIGENDIAN]); m_func[0] = BIT(m_reg[REG_FUNC], 0) ? ~u32(0) : 0; m_func[1] = BIT(m_reg[REG_FUNC], 1) ? ~u32(0) : 0; m_func[2] = BIT(m_reg[REG_FUNC], 2) ? ~u32(0) : 0; m_func[3] = BIT(m_reg[REG_FUNC], 3) ? ~u32(0) : 0; // TODO: haddr m_nopup = bool(m_reg[REG_NOPUP]); // TODO: xyfrac m_pupdata = m_reg[REG_PUPDATA]; m_pat = (m_reg[REG_PATH] << 16) | m_reg[REG_PATL]; m_dz = (s64(u64(m_reg[REG_DZI]) << 40) >> 26) | m_reg[REG_DZF]; m_dr = s32(m_reg[REG_DR] << 8) >> 8; m_dg = s32(m_reg[REG_DG] << 12) >> 12; m_db = s32(m_reg[REG_DB] << 12) >> 12; m_z = s64(u64(m_reg[REG_Z]) << 40) >> 26; m_r = m_reg[REG_R]; m_g = m_reg[REG_G]; m_b = m_reg[REG_B]; m_stip = m_reg[REG_STIP]; m_stipcount = m_reg[REG_STIPCOUNT]; m_dx = s32(s16(m_reg[REG_DX])); m_dy = s32(s16(m_reg[REG_DY])); m_numpix = m_reg[REG_NUMPIX]; m_x = ((m_reg[REG_X] >> 3) * 5 + (m_reg[REG_X] & 0x7)) << 14; m_y = m_reg[REG_Y] << 14; m_ir = m_reg[REG_IR]; switch (m_ir) { case IR_SHADED: LOG("ri draw shaded span\n"); draw_shaded_span(); break; case IR_FLAT: LOG("ri draw 1x5 flat span\n"); draw_flat_span(5); break; case IR_FLAT4: LOG("ri draw 1x20 flat span\n"); draw_flat_span(20); break; case IR_TOPLINE: LOG("ri draw top of anti-aliased line\n"); m_state = IDLE; break; case IR_BOTLINE: LOG("ri draw bottom of anti-aliased line\n"); m_state = IDLE; break; case IR_READBUF: LOG("ri read buffer rwmode %d\n", m_reg[REG_RWMODE]); read_buffer(); break; case IR_WRITEBUF: LOG("write buffer rwmode %d\n", m_reg[REG_RWMODE]); write_buffer(); break; } } void sgi_re2_device::draw_shaded_span() { /* * raster operation logic: * u32 const result = (m_func[0] & src & dst) + (m_func[1] & src & ~dst) + (m_func[2] & ~src & dst) + (m_func[3] & ~src & ~dst); */ u32 const mask = (m_reg[REG_AUXMASK] << 24) | m_reg[REG_PIXMASK]; u32 const aux = m_nopup ? (m_reg[REG_WIDDATA] << 28) | (m_reg[REG_UAUXDATA] << 24) : (m_reg[REG_WIDDATA] << 28) | ((m_reg[REG_UAUXDATA] & 0x3) << 26) | (m_pupdata << 24); for (unsigned n = 0; m_numpix--; n++) { // TODO: z buffer check if (m_clip.contains(m_x >> 14, m_y >> 14)) { if (pattern(m_x >> 14, n)) { offs_t const offset = (m_y >> 14) * 0x500 + (m_x >> 14); if (wid(IR_SHADED, offset)) { u32 const color = (m_r >> 11) << 0 | (m_g >> 11) << 8 | (m_b >> 11) << 16; vram_w(offset, aux | color, mask); } } } increment(); } m_state = IDLE; } void sgi_re2_device::draw_flat_span(unsigned const n) { u32 const mask = (m_reg[REG_AUXMASK] << 24) | m_reg[REG_PIXMASK]; u32 const aux = m_nopup ? (m_reg[REG_WIDDATA] << 28) | (m_reg[REG_UAUXDATA] << 24) : (m_reg[REG_WIDDATA] << 28) | ((m_reg[REG_UAUXDATA] & 0x3) << 26) | (m_pupdata << 24); offs_t const offset = (m_y >> 14) * 0x500 + (m_x >> 14); for (unsigned i = 0; i < m_numpix; i++) { if (m_clip.contains((m_x >> 14) + i, m_y >> 14)) { u32 const color = (m_r >> 11) << 0 | (m_g >> 11) << 8 | (m_b >> 11) << 16; vram_w(offset + i, aux | color, mask); } if ((i % n) == 0) increment(); } m_state = IDLE; } void sgi_re2_device::increment() { m_x += m_dx; m_y += m_dy; m_z += m_dz; m_r += m_dr; m_g += m_dg; m_b += m_db; } void sgi_re2_device::read_buffer() { if (m_numpix > 0) { switch (m_reg[REG_RWMODE]) { case RWMODE_FB_P: m_reg[REG_RWDATA] = m_vram[(m_y >> 14) * 0x500 + (m_x >> 14)]; break; } increment(); m_numpix--; m_state = DMA_R; } else m_state = IDLE; set_drq(m_state == DMA_R); } void sgi_re2_device::write_buffer() { if (m_state == DMA_W) { for (unsigned i = 0; i <= m_reg[REG_UPACMODE]; i++) { if (m_clip.contains(m_x >> 14, m_y >> 14)) { offs_t const offset = (m_y >> 14) * 0x500 + (m_x >> 14); // FIXME: wid only for rwmode 0, 1, 2, 6 if (wid(IR_WRITEBUF, offset)) { // unpack pixel (TODO: HADDR != 0) u32 const data = unpack(m_reg[REG_RWDATA], i, m_reg[REG_UPACMODE]); // TODO: xzoom // TODO: pattern // format and write // TODO: raster op // write the pixel switch (m_reg[REG_RWMODE]) { case RWMODE_UAUX: vram_w(offset, data << 24, (m_reg[REG_AUXMASK] & (m_nopup ? 0xf : 0xc)) << 24); break; } } } increment(); m_numpix--; } } if (m_numpix > 0) m_state = DMA_W; else m_state = IDLE; set_drq(m_state == DMA_W); } u32 sgi_re2_device::unpack(u32 data, unsigned const n, u32 const mode) const { switch (mode) { case 1: data = u16(data >> (16 * (1 - n))); break; case 3: data = u8(data >> (8 * (3 - n))); break; } return data; } bool sgi_re2_device::wid(unsigned const ir, offs_t const offset) { if (!m_reg[REG_ENABWID]) return true; if ((ir == IR_TOPLINE || ir == IR_BOTLINE) && !m_reg[REG_ENABLWID]) return true; unsigned const wid = m_vram[offset] >> 28; // 2 or 4 wid bitplanes? if (m_reg[REG_FBOPTION] & 1) { if (BIT(m_reg[REG_DEPTHFN], 3)) return (wid & 0xe) == (m_reg[REG_CURWID] & 0xe); else return (wid & 0xf) == (m_reg[REG_CURWID] & 0xf); } else return (wid & 0x3) == (m_reg[REG_CURWID] & 0x3); } bool sgi_re2_device::pattern(unsigned const x, unsigned const n) const { if (!m_reg[REG_ENABPAT]) return true; unsigned const index = (m_reg[REG_ALIGNPAT] ? x : n) % 32; return BIT(m_pat, 31 - index); } u32 sgi_re2_device::screen_update(screen_device &screen, bitmap_rgb32 &bitmap, rectangle const &cliprect) { // TODO: variable topscan row and column for (unsigned screen_y = screen.visible_area().min_y, mem_y = 1023; screen_y <= screen.visible_area().max_y; screen_y++, mem_y--) for (unsigned screen_x = screen.visible_area().min_x, mem_x = 0; screen_x <= screen.visible_area().max_x; screen_x++, mem_x++) { unsigned const channel = mem_x % 5; u32 const data = m_vram[(mem_y * 0x500) + mem_x]; u16 const mode = m_xmap[channel]->mode_r(data >> 28); // default is 24 bit rgb single buffered rgb_t color = rgb_t(data >> 0, data >> 8, data >> 16); // check overlay or underlay if (((data >> 20) & mode & sgi_xmap2_device::MODE_OE) || ((mode & sgi_xmap2_device::MODE_UE) && !(data & 0x00ffffffU))) color = m_xmap[channel]->overlay_r(data >> 24); else switch (mode & sgi_xmap2_device::MODE_DM) { case 0: // 8 bit indexed single buffered { u16 const index = BIT(mode, sgi_xmap2_device::BIT_ME) ? ((mode & sgi_xmap2_device::MODE_MC) >> 2) | u8(data) : u8(data); color = m_xmap[channel]->pen_color(index); } break; case 1: // 4 bit indexed double buffered { u8 const buffer = BIT(mode, sgi_xmap2_device::BIT_BS) ? u8(data) >> 4 : data & 0x0f; u16 const index = BIT(mode, sgi_xmap2_device::BIT_ME) ? ((mode & sgi_xmap2_device::MODE_MC) >> 2) | buffer : buffer; color = m_xmap[channel]->pen_color(index); } break; case 2: // 12 bit indexed double buffered { u16 const buffer = u16(BIT(mode, sgi_xmap2_device::BIT_BS) ? data >> 12 : data) & 0x0fff; u16 const index = BIT(mode, sgi_xmap2_device::BIT_ME) ? ((mode & sgi_xmap2_device::MODE_MC) >> 2) | (buffer & 0xff) : buffer; color = m_xmap[channel]->pen_color(index); } break; case 5: // 12 bit rgb double buffered color = BIT(mode, sgi_xmap2_device::BIT_BS) ? rgb_t( ((data >> 0x00) & 0xf0) | ((data >> 0x04) & 0x0f), ((data >> 0x08) & 0xf0) | ((data >> 0x0c) & 0x0f), ((data >> 0x10) & 0xf0) | ((data >> 0x14) & 0x0f)) : rgb_t( ((data << 0x04) & 0xf0) | ((data >> 0x00) & 0x0f), ((data >> 0x04) & 0xf0) | ((data >> 0x08) & 0x0f), ((data >> 0x0c) & 0xf0) | ((data >> 0x10) & 0x0f)); break; } // read the cursor devices u8 const cursor = (m_cursor[0]->cur_r(screen_x, screen_y) ? 1 : 0) | (m_cursor[1]->cur_r(screen_x, screen_y) ? 2 : 0); // apply the gamma ramp and output the pixel bitmap.pix(screen_y, screen_x) = rgb_t( m_ramdac[0]->lookup(color.r(), cursor), m_ramdac[1]->lookup(color.g(), cursor), m_ramdac[2]->lookup(color.b(), cursor)); } return 0; }