// license:BSD-3-Clause // copyright-holders:Sandro Ronco /***************************************************************************** SPG290 PPU TODO: - Horizontal and vertical compression - Characters flip - Various graphics glitches *****************************************************************************/ #include "emu.h" #include "spg290_ppu.h" DEFINE_DEVICE_TYPE(SPG290_PPU, spg290_ppu_device, "spg290_ppu", "SPG290 PPU") spg290_ppu_device::spg290_ppu_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : device_t(mconfig, SPG290_PPU, tag, owner, clock) , m_screen(*this, finder_base::DUMMY_TAG) , m_sprite_palette_ram(*this, "sprite_palette_ram") , m_char_palette_ram(*this, "char_palette_ram") , m_hoffset_ram(*this, "hoffset_ram") , m_voffset_ram(*this, "voffset_ram") , m_sprite_ram(*this, "sprite_ram") , m_vblank_irq_cb(*this) , m_space_read_cb(*this) { } void spg290_ppu_device::map(address_map &map) { map(0x0000, 0x00ff).rw(FUNC(spg290_ppu_device::regs_r), FUNC(spg290_ppu_device::regs_w)); map(0x1000, 0x17ff).ram().share(m_char_palette_ram); map(0x1800, 0x1fff).ram().share(m_sprite_palette_ram); map(0x2000, 0x27ff).ram().share(m_hoffset_ram); map(0x3000, 0x37ff).ram().share(m_voffset_ram); map(0x4000, 0x4fff).ram().share(m_sprite_ram); } //------------------------------------------------- // device_start - device-specific startup //------------------------------------------------- void spg290_ppu_device::device_start() { m_vblank_irq_cb.resolve_safe(); m_space_read_cb.resolve_safe(0); save_item(NAME(m_control)); save_item(NAME(m_sprite_control)); save_item(NAME(m_irq_control)); save_item(NAME(m_irq_status)); save_item(NAME(m_sprite_max)); save_item(NAME(m_sprite_buf_start)); save_item(NAME(m_blend_mode)); save_item(NAME(m_frame_buff)); save_item(NAME(m_transrgb)); save_item(NAME(m_vcomp_value)); save_item(NAME(m_vcomp_offset)); save_item(NAME(m_vcomp_step)); save_item(NAME(m_irq_timing_v)); save_item(NAME(m_irq_timing_h)); save_item(NAME(m_vblank_lines)); save_item(STRUCT_MEMBER(m_txs, control)); save_item(STRUCT_MEMBER(m_txs, attribute)); save_item(STRUCT_MEMBER(m_txs, posx)); save_item(STRUCT_MEMBER(m_txs, posy)); save_item(STRUCT_MEMBER(m_txs, nptr)); save_item(STRUCT_MEMBER(m_txs, blend)); save_item(STRUCT_MEMBER(m_txs, buf_start)); } //------------------------------------------------- // device_reset - device-specific reset //------------------------------------------------- void spg290_ppu_device::device_reset() { m_control = 0; m_sprite_control = 0; m_irq_control = 0; m_irq_status = 0; m_sprite_max = 0; m_sprite_buf_start = 0; m_blend_mode = 0; m_frame_buff[0] = 0; m_frame_buff[1] = 0; m_frame_buff[2] = 0; m_transrgb = 0; m_vcomp_value = 0; m_vcomp_offset = 0; m_vcomp_step = 0; m_irq_timing_v = 0; m_irq_timing_h = 0; m_vblank_lines = 0; memset(m_txs, 0, sizeof(m_txs)); m_vblank_irq_cb(CLEAR_LINE); } uint32_t spg290_ppu_device::regs_r(offs_t offset, uint32_t mem_mask) { switch(offset << 2) { case 0x00: // PPU Control return m_control; case 0x04: // Sprite Control return m_sprite_control; case 0x08: // PPU Max Sprites return m_sprite_max; case 0x0c: // blend mode return m_blend_mode; case 0x10: // PPU Max Sprites return m_transrgb; case 0x20: // Text Layer 1 X pos return m_txs[0].posx; case 0x24: // Text Layer 1 Y pos return m_txs[0].posy; case 0x28: // Text Layer 1 attribute return m_txs[0].attribute; case 0x2c: // Text Layer 1 control return m_txs[0].control; case 0x30: // Text Layer 1 number ptr return m_txs[0].nptr; case 0x38: // Text Layer 1 buffer blend return m_txs[0].blend; case 0x3c: // Text Layer 2 X pos return m_txs[1].posx; case 0x40: // Text Layer 2 Y pos return m_txs[1].posy; case 0x44: // Text Layer 2 attribute return m_txs[1].attribute; case 0x48: // Text Layer 2 control return m_txs[1].control; case 0x4c: // Text Layer 2 number ptr return m_txs[1].nptr; case 0x54: // Text Layer 2 buffer blend return m_txs[1].blend; case 0x58: // Text Layer 3 X pos return m_txs[2].posx; case 0x5c: // Text Layer 3 Y pos return m_txs[2].posy; case 0x60: // Text Layer 3 attribute return m_txs[2].attribute; case 0x64: // Text Layer 3 control return m_txs[2].control; case 0x68: // Text Layer 3 number ptr return m_txs[2].nptr; case 0x70: // Tx3 buffer blend return m_txs[2].blend; case 0x74: // vertical comp value return m_vcomp_value; case 0x78: // vertical comp offset return m_vcomp_offset; case 0x7c: // vertical comp step return m_vcomp_step; case 0x80: // PPU IRQ Control return m_irq_control; case 0x84: // PPU IRQ ack return m_irq_status; case 0x88: // IRQTimingV return m_irq_timing_v; case 0x8c: // IRQTimingH return m_irq_timing_h; case 0x90: // vblank lines return m_vblank_lines; case 0x94: // lines count return m_vblank_lines + m_screen->vpos(); case 0xa0: case 0xa4: case 0xa8: // Tx1 buffer start return m_txs[0].buf_start[offset & 3]; case 0xac: case 0xb0: case 0xb4: // Tx2 buffer start return m_txs[1].buf_start[(offset+1) & 3]; case 0xb8: case 0xbc: case 0xc0: // Tx3 buffer starts return m_txs[2].buf_start[(offset+2) & 3]; case 0xc4: case 0xc8: case 0xcc: // frame buffer start return m_frame_buff[(offset-1) & 3]; case 0xd0: // Sprites buffer start return m_sprite_buf_start; default: logerror("[%s] %s: unknown read %x\n", tag(), machine().describe_context(), offset << 2); } return 0; } void spg290_ppu_device::regs_w(offs_t offset, uint32_t data, uint32_t mem_mask) { switch(offset << 2) { case 0x00: // PPU Control COMBINE_DATA(&m_control); break; case 0x04: // Sprite Control COMBINE_DATA(&m_sprite_control); break; case 0x08: // PPU Max Sprites COMBINE_DATA(&m_sprite_max); break; case 0x0c: // blend mode COMBINE_DATA(&m_blend_mode); break; case 0x10: // PPU Max Sprites COMBINE_DATA(&m_transrgb); break; case 0x20: // Text Layer 1 X pos COMBINE_DATA(&m_txs[0].posx); break; case 0x24: // Text Layer 1 Y pos COMBINE_DATA(&m_txs[0].posy); break; case 0x28: // Text Layer 1 attribute COMBINE_DATA(&m_txs[0].attribute); break; case 0x2c: // Text Layer 1 control COMBINE_DATA(&m_txs[0].control); break; case 0x30: // Text Layer 1 number ptr COMBINE_DATA(&m_txs[0].nptr); break; case 0x38: // Text Layer 1 buffer blend COMBINE_DATA(&m_txs[0].blend); break; case 0x3c: // Text Layer 2 X pos COMBINE_DATA(&m_txs[1].posx); break; case 0x40: // Text Layer 2 Y pos COMBINE_DATA(&m_txs[1].posy); break; case 0x44: // Text Layer 2 attribute COMBINE_DATA(&m_txs[1].attribute); break; case 0x48: // Text Layer 2 control COMBINE_DATA(&m_txs[1].control); break; case 0x4c: // Text Layer 2 number ptr COMBINE_DATA(&m_txs[1].nptr); break; case 0x54: // Text Layer 2 buffer blend COMBINE_DATA(&m_txs[1].blend); break; case 0x58: // Text Layer 3 X pos COMBINE_DATA(&m_txs[2].posx); break; case 0x5c: // Text Layer 3 Y pos COMBINE_DATA(&m_txs[2].posy); break; case 0x60: // Text Layer 3 attribute COMBINE_DATA(&m_txs[2].attribute); break; case 0x64: // Text Layer 3 control COMBINE_DATA(&m_txs[2].control); break; case 0x68: // Text Layer 3 number ptr COMBINE_DATA(&m_txs[2].nptr); break; case 0x70: // Tx3 buffer blend COMBINE_DATA(&m_txs[2].blend); break; case 0x74: // vertical comp value COMBINE_DATA(&m_vcomp_value); break; case 0x78: // vertical comp offset COMBINE_DATA(&m_vcomp_offset); break; case 0x7c: // vertical comp step COMBINE_DATA(&m_vcomp_step); break; case 0x80: // PPU IRQ Control COMBINE_DATA(&m_irq_control); m_irq_status &= m_irq_control; break; case 0x84: // PPU IRQ ack if (ACCESSING_BITS_0_7) { m_irq_status &= ~data; if (!(m_irq_status & m_irq_control & 0x07)) m_vblank_irq_cb(CLEAR_LINE); } break; case 0x88: // IRQTimingV COMBINE_DATA(&m_irq_timing_v); break; case 0x8c: // IRQTimingH COMBINE_DATA(&m_irq_timing_h); break; case 0x90: // vblank lines COMBINE_DATA(&m_vblank_lines); break; case 0xa0: case 0xa4: case 0xa8: // Tx1 buffer start COMBINE_DATA(&m_txs[0].buf_start[offset & 3]); break; case 0xac: case 0xb0: case 0xb4: // Tx2 buffer start COMBINE_DATA(&m_txs[1].buf_start[(offset+1) & 3]); break; case 0xb8: case 0xbc: case 0xc0: // Tx3 buffer starts COMBINE_DATA(&m_txs[2].buf_start[(offset+2) & 3]); break; case 0xc4: case 0xc8: case 0xcc: // frame buffer start COMBINE_DATA(&m_frame_buff[(offset-1) & 3]); break; case 0xd0: // Sprites buffer start COMBINE_DATA(&m_sprite_buf_start); break; default: logerror("[%s] %s: unknown write %x = %x\n", tag(), machine().describe_context(), offset << 2, data); } } void spg290_ppu_device::screen_vblank(int state) { if (state && (m_irq_control & 0x01)) // VBlanking Start IRQ { m_irq_status |= 0x01; m_vblank_irq_cb(ASSERT_LINE); } else if (!state && (m_irq_control & 0x02)) // VBlanking End IRQ { m_irq_status |= 0x02; m_vblank_irq_cb(ASSERT_LINE); } } inline rgb_t spg290_ppu_device::blend_colors(const rgb_t &c0, const rgb_t &c1, uint8_t level) { if (m_blend_mode & 1) { int r = (c0.r() * level / 63) - (c1.r() * (63 - level) / 63); int g = (c0.b() * level / 63) - (c1.b() * (63 - level) / 63); int b = (c0.b() * level / 63) - (c1.b() * (63 - level) / 63); return rgb_t(r, g, b); } else { int r = (c0.r() * level / 63) + (c1.r() * (63 - level) / 63); int g = (c0.b() * level / 63) + (c1.b() * (63 - level) / 63); int b = (c0.b() * level / 63) + (c1.b() * (63 - level) / 63); return rgb_t(r, g, b); } } inline void spg290_ppu_device::argb1555(bitmap_rgb32 &bitmap, const rectangle &cliprect, uint16_t posy, uint16_t posx, uint16_t argb, uint8_t blend) { if (!(argb & 0x8000) && cliprect.contains(posx, posy)) { rgb_t color = rgb_t(pal5bit(argb >> 10), pal5bit(argb >> 5), pal5bit(argb >> 0)); if (blend) color = blend_colors(bitmap.pix(posy, posx), color, blend); bitmap.pix(posy, posx) = color; } } inline void spg290_ppu_device::rgb565(bitmap_rgb32 &bitmap, const rectangle &cliprect, uint16_t posy, uint16_t posx, uint16_t rgb, uint8_t blend) { if ((!(m_transrgb & 0x10000) || (m_transrgb & 0xffff) != rgb) && cliprect.contains(posx, posy)) { rgb_t color = rgb_t(pal5bit(rgb >> 11), pal6bit(rgb >> 5), pal5bit(rgb >> 0)); if (blend) color = blend_colors(bitmap.pix(posy, posx), color, blend); bitmap.pix(posy, posx) = color; } } void spg290_ppu_device::blit_sprite(bitmap_rgb32 &bitmap, const rectangle &cliprect, uint32_t control, uint32_t attribute, uint32_t buf_start) { uint16_t sprite_num = control & 0xffff; uint16_t sprite_x = (control >> 16) & 0x3ff; uint16_t sprite_y = (attribute >> 16) & 0x3ff; uint8_t sprite_hsize = 8 << ((attribute >> 4) & 0x03); uint8_t sprite_vsize = 8 << ((attribute >> 6) & 0x03); uint8_t bit_pixel = ((attribute & 3) + 1) << 1; uint8_t sprite_flip = (attribute >> 2) & 0x03; uint16_t pen_bank = ((attribute >> 8) & 0x1f) * 0x10; uint8_t blend = (attribute & 0x8000) ? (attribute >> 26) & 0x3f : 0; uint8_t pixel_word = 32 / bit_pixel; uint8_t pixel_byte = 8 / bit_pixel; uint8_t word_line = sprite_hsize / pixel_word; uint16_t pen_mask = (1 << bit_pixel) - 1; if (sprite_num == 0) return; uint32_t sprite_base = buf_start + (sprite_num * (sprite_hsize * sprite_vsize * bit_pixel / 8)); if (sprite_x > cliprect.max_x) sprite_x = (sprite_x & 0x3ff) - 0x400; if (sprite_y > cliprect.max_y) sprite_y = (sprite_y & 0x3ff) - 0x400; for (int y=0; y < sprite_vsize; y++) for (int x=0; x < word_line; x++) { uint32_t data = m_space_read_cb(sprite_base + (y * word_line + x) * 4); for (int i=0; i < 4; i++) { for (int b=0; b < pixel_byte; b++) { int bitpos = i * pixel_byte + b; uint16_t pen = (data >> (8 - (b + 1) * bit_pixel)) & pen_mask; uint16_t posx = cliprect.min_x; if (sprite_flip & 0x01) posx += sprite_x + (sprite_hsize - (x * pixel_word + bitpos)); else posx += sprite_x + x * pixel_word + bitpos; uint16_t posy = cliprect.min_y; if (sprite_flip & 0x02) posy += sprite_y + (sprite_vsize - y); else posy += sprite_y + y; argb1555(bitmap, cliprect, posy, posx, m_sprite_palette_ram[(pen_bank + pen) & 0x1ff], blend); } data >>= 8; } } } void spg290_ppu_device::blit_bitmap(bitmap_rgb32 &bitmap, const rectangle &cliprect, uint32_t control, uint32_t attribute, int posy, int posx, uint32_t nptr, uint32_t buf_start, uint8_t blend) { int max_x = cliprect.max_x + posx; int max_y = cliprect.max_y + posy; for (int y=0; y < max_y; y++) { int line = (control & 0x04) ? 0 : y; uint32_t tx_start = m_space_read_cb(nptr + line * 4) * 2; for (int x=0; x < max_x; x+=2) { uint16_t px = cliprect.min_x + x; uint16_t py = cliprect.min_y + y; uint32_t data = m_space_read_cb(buf_start + tx_start + x * 2); for (int b=0; b < 2; b++) { if (control & 0x1000) rgb565(bitmap, cliprect, py - posy, px + b - posx, data & 0xffff, blend); else argb1555(bitmap, cliprect, py - posy, px + b - posx, data & 0xffff, blend); data >>= 16; } } } } void spg290_ppu_device::blit_character(bitmap_rgb32 &bitmap, const rectangle &cliprect, uint32_t control, uint32_t attribute, int posy, int posx, uint32_t nptr, uint32_t buf_start, uint8_t blend) { uint8_t hsize = 8 << ((attribute >> 4) & 0x03); uint8_t vsize = 8 << ((attribute >> 6) & 0x03); uint8_t bit_pixel = ((attribute & 3) + 1) << 1; uint16_t pen_bank = ((attribute >> 8) & 0x1f) * 0x10; //uint8_t char_flip = (attribute >> 2) & 0x03; uint8_t pixel_word = 32 / bit_pixel; uint8_t chars_line = 1024 / hsize; uint16_t pen_mask = (1 << bit_pixel) - 1; int max_x = (cliprect.max_x + posx) / vsize + 1; int max_y = (cliprect.max_y + posy) / hsize + 1; for (int y=0; y <= max_y; y++) { for (int x=0; x <= max_x; x++) { int line = (control & 0x04) ? 0 : y; uint32_t addr = line * chars_line + x; uint32_t char_id = m_space_read_cb(nptr + addr * 2); if (addr & 1) char_id >>= 16; uint32_t char_addr = buf_start + (char_id & 0xffff) * hsize * vsize; for (int cy=0; cy < vsize; cy++) { uint32_t line_addr = char_addr + cy * hsize; for (int cx=0; cx < hsize; cx++) { uint32_t data = m_space_read_cb(line_addr + (cx * bit_pixel) / 8); uint16_t px = cliprect.min_x + x * hsize + cx; uint16_t py = cliprect.min_y + y * vsize + cy; uint16_t pen = (data >> ((cx % pixel_word) * bit_pixel)) & pen_mask; uint16_t pix = m_char_palette_ram[(pen_bank + pen) & 0x3ff]; if (control & 0x1000) rgb565(bitmap, cliprect, py - posy, px - posx, pix, blend); else argb1555(bitmap, cliprect, py - posy, px - posx, pix, blend); } } } } } uint32_t spg290_ppu_device::screen_update(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect) { bitmap.fill(rgb_t::black(), cliprect); if (!(m_control & 0x1000)) return 0; for (int depth=0; depth<4; depth++) { //if (machine().input().code_pressed(KEYCODE_1_PAD) && depth == 0) continue; //if (machine().input().code_pressed(KEYCODE_2_PAD) && depth == 1) continue; //if (machine().input().code_pressed(KEYCODE_3_PAD) && depth == 2) continue; //if (machine().input().code_pressed(KEYCODE_4_PAD) && depth == 3) continue; // draw the bitmap/text layers for (int l=0; l<3; l++) { //if (machine().input().code_pressed(KEYCODE_7_PAD) && l == 0) continue; //if (machine().input().code_pressed(KEYCODE_8_PAD) && l == 1) continue; //if (machine().input().code_pressed(KEYCODE_9_PAD) && l == 2) continue; if ((m_txs[l].control & 0x08) && ((m_txs[l].attribute >> 13) & 3) == depth) { uint8_t blend = (m_txs[l].control & 0x0100) ? m_txs[l].blend : 0; // posx is 10-bit two's complement int posx = m_txs[l].posx & 0x3ff; if (posx & 0x200) posx = (posx & 0x3ff) - 0x400; // posy is 9-bit two's complement int posy = m_txs[l].posy & 0x1ff; if (posy & 0x100) posy = (posy & 0x1ff) - 0x200; if (m_txs[l].control & 0x01) blit_bitmap(bitmap, cliprect, m_txs[l].control, m_txs[l].attribute, posy, posx, m_txs[l].nptr, m_txs[l].buf_start[0], blend); else blit_character(bitmap, cliprect, m_txs[l].control, m_txs[l].attribute, posy, posx, m_txs[l].nptr, m_txs[l].buf_start[0], blend); } } //if (machine().input().code_pressed(KEYCODE_0_PAD)) continue; // draw the sprites if (m_sprite_control & 1) { for (int i=0; i <= (m_sprite_max & 0x1ff); i++) { if (((m_sprite_ram[i * 2 + 1] >> 13) & 3) == depth) blit_sprite(bitmap, cliprect, m_sprite_ram[i * 2], m_sprite_ram[i * 2 + 1], m_sprite_buf_start); } } } return 0; }