// license:BSD-3-Clause // copyright-holders:R. Belmont,Ryan Holtz /*************************************************************************** gba_lcd.c File to handle emulation of the video hardware of the Game Boy Advance By R. Belmont, Ryan Holtz ***************************************************************************/ #include "emu.h" #include "gba_lcd.h" #include "rendlay.h" #include "screen.h" /* LCD I/O Registers */ #define DISPCNT HWLO(0x000) /* 0x4000000 2 R/W LCD Control */ #define GRNSWAP HWHI(0x000) /* 0x4000002 2 R/W Undocumented - Green Swap */ #define DISPSTAT HWLO(0x004) /* 0x4000004 2 R/W General LCD Status (STAT,LYC) */ #define VCOUNT HWHI(0x004) /* 0x4000006 2 R Vertical Counter (LY) */ #define BG0CNT HWLO(0x008) /* 0x4000008 2 R/W BG0 Control */ #define BG1CNT HWHI(0x008) /* 0x400000a 2 R/W BG1 Control */ #define BG2CNT HWLO(0x00c) /* 0x400000c 2 R/W BG2 Control */ #define BG3CNT HWHI(0x00c) /* 0x400000e 2 R/W BG3 Control */ #define BG0HOFS HWLO(0x010) /* 0x4000010 2 W BG0 X-Offset */ #define BG0VOFS HWHI(0x010) /* 0x4000012 2 W BG0 Y-Offset */ #define BG1HOFS HWLO(0x014) /* 0x4000014 2 W BG1 X-Offset */ #define BG1VOFS HWHI(0x014) /* 0x4000016 2 W BG1 Y-Offset */ #define BG2HOFS HWLO(0x018) /* 0x4000018 2 W BG2 X-Offset */ #define BG2VOFS HWHI(0x018) /* 0x400001a 2 W BG2 Y-Offset */ #define BG3HOFS HWLO(0x01c) /* 0x400001c 2 W BG3 X-Offset */ #define BG3VOFS HWHI(0x01c) /* 0x400001e 2 W BG3 Y-Offset */ #define BG2PA HWLO(0x020) /* 0x4000020 2 W BG2 Rotation/Scaling Parameter A (dx) */ #define BG2PB HWHI(0x020) /* 0x4000022 2 W BG2 Rotation/Scaling Parameter B (dmx) */ #define BG2PC HWLO(0x024) /* 0x4000024 2 W BG2 Rotation/Scaling Parameter C (dy) */ #define BG2PD HWHI(0x024) /* 0x4000026 2 W BG2 Rotation/Scaling Parameter D (dmy) */ #define BG2X WORD(0x028) /* 0x4000028 4 W BG2 Reference Point X-Coordinate */ #define BG2Y WORD(0x02c) /* 0x400002c 4 W BG2 Reference Point Y-Coordinate */ #define BG3PA HWLO(0x030) /* 0x4000030 2 W BG3 Rotation/Scaling Parameter A (dx) */ #define BG3PB HWHI(0x030) /* 0x4000032 2 W BG3 Rotation/Scaling Parameter B (dmx) */ #define BG3PC HWLO(0x034) /* 0x4000034 2 W BG3 Rotation/Scaling Parameter C (dy) */ #define BG3PD HWHI(0x034) /* 0x4000036 2 W BG3 Rotation/Scaling Parameter D (dmy) */ #define BG3X WORD(0x038) /* 0x4000038 4 W BG3 Reference Point X-Coordinate */ #define BG3Y WORD(0x03c) /* 0x400003c 4 W BG3 Reference Point Y-Coordinate */ #define WIN0H HWLO(0x040) /* 0x4000040 2 W Window 0 Horizontal Dimensions */ #define WIN1H HWHI(0x040) /* 0x4000042 2 W Window 1 Horizontal Dimensions */ #define WIN0V HWLO(0x044) /* 0x4000044 2 W Window 0 Vertical Dimensions */ #define WIN1V HWHI(0x044) /* 0x4000046 2 W Window 1 Vertical Dimensions */ #define WININ HWLO(0x048) /* 0x4000048 2 R/W Inside of Window 0 and 1 */ #define WINOUT HWHI(0x048) /* 0x400004a 2 R/W Inside of OBJ Window & Outside of Windows */ #define MOSAIC HWLO(0x04c) /* 0x400004c 2 W Mosaic Size */ /* 0x400004e 2 - Unused */ #define BLDCNT HWLO(0x050) /* 0x4000050 2 R/W Color Special Effects Selection */ #define BLDALPHA HWHI(0x050) /* 0x4000052 2 W Alpha Blending Coefficients */ #define BLDY HWLO(0x054) /* 0x4000054 2 W Brightness (Fade-In/Out) Coefficient */ /* 0x4000056 2 - Unused */ #define DISPSTAT_SET(val) HWLO_SET(0x004, val) #define DISPSTAT_RESET(val) HWLO_RESET(0x004, val) #define VERBOSE_LEVEL (0) static inline void ATTR_PRINTF(3,4) verboselog(device_t &device, int n_level, const char *s_fmt, ...) { if (VERBOSE_LEVEL >= n_level) { va_list v; char buf[32768]; va_start(v, s_fmt); vsprintf(buf, s_fmt, v); va_end(v); device.logerror("%08x: %s", device.machine().describe_context(), buf); } } class object { public: object(device_t &device, uint16_t *oam, int index) : m_device(device) { m_attr0 = oam[(4 * index) + 0]; m_attr1 = oam[(4 * index) + 1]; m_attr2 = oam[(4 * index) + 2]; } int pos_y() { return m_attr0 & 0x00ff; } bool roz() { return m_attr0 & 0x0100; } bool roz_double() { return m_attr0 & 0x0200; } uint16_t mode_mask() { return m_attr0 & 0x0c00; } bool mosaic() { return m_attr0 & 0x1000; } bool palette_256() { return m_attr0 & 0x2000; } int pos_x() { return m_attr1 & 0x01ff; } int roz_param() { return (m_attr1 & 0x3e00) >> 9; } bool hflip() { return m_attr1 & 0x1000; } bool vflip() { return m_attr1 & 0x2000; } int tile_number() { return m_attr2 & 0x03ff; } int priority() { return (m_attr2 & 0x0c00) >> 10; } int palette() { return (m_attr2 & 0xf000) >> 8; } enum class mode : uint16_t { normal = 0x0000, alpha = 0x0400, window = 0x0800 }; mode mode_enum() { return enum_value(m_attr0 & 0x0c00); } void size(int &width, int &height) { static const int size_table[4][4][2] = { { { 8, 8 }, { 16, 16 }, { 32, 32 }, { 64, 64 } }, // square { { 16, 8 }, { 32, 8 }, { 32, 16 }, { 64, 32 } }, // horizontal rect { { 8, 16 }, { 8, 32 }, { 16, 32 }, { 32, 64 } }, // vertical rect { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } } // invalid }; int shape = (m_attr0 & 0xc000) >> 14; int size = (m_attr1 & 0xc000) >> 14; width = size_table[shape][size][0]; height = size_table[shape][size][1]; if (shape == 4) verboselog(m_device, 0, "WARNING: attempted to draw an object of invalid shape\n"); } private: device_t &m_device; uint16_t m_attr0; uint16_t m_attr1; uint16_t m_attr2; }; DEFINE_DEVICE_TYPE(GBA_LCD, gba_lcd_device, "gba_lcd", "GBA LCD") gba_lcd_device::gba_lcd_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : device_t(mconfig, GBA_LCD, tag, owner, clock) , device_video_interface(mconfig, *this) , m_int_hblank_cb(*this) , m_int_vblank_cb(*this) , m_int_vcount_cb(*this) , m_dma_hblank_cb(*this) , m_dma_vblank_cb(*this) { } inline uint8_t gba_lcd_device::bg_video_mode() { uint8_t mode = DISPCNT & 0x0007; if (mode > 5) { verboselog(*this, 0, "WARNING: attempted to set invalid BG video mode %d\n", mode); return 0; } return mode; } inline bool gba_lcd_device::is_set(dispcnt flag) { return DISPCNT & underlying_value(flag); } inline void gba_lcd_device::set(dispstat flag) { DISPSTAT_SET(underlying_value(flag)); } inline void gba_lcd_device::clear(dispstat flag) { DISPSTAT_RESET(underlying_value(flag)); } inline bool gba_lcd_device::is_set(dispstat flag) { return DISPSTAT & underlying_value(flag); } inline bool gba_lcd_device::is_set(uint16_t bgxcnt, bgcnt flag) { return bgxcnt & underlying_value(flag); } inline uint8_t gba_lcd_device::bg_priority(uint16_t bgxcnt) { return bgxcnt & 0x0003; } inline uint32_t gba_lcd_device::bg_char_base(uint16_t bgxcnt) { return ((bgxcnt & 0x003c) >> 2) * 0x4000; } inline uint32_t gba_lcd_device::bg_screen_base(uint16_t bgxcnt) { return ((bgxcnt & 0x1f00) >> 8) * 0x800; } inline void gba_lcd_device::bg_screen_size(uint16_t bgxcnt, bool text, int &width, int &height) { static const int size_table[2][4][2] = { { { 256, 256 }, { 512, 256 }, { 256, 512 }, { 512, 512 } }, // text mode { { 128, 128 }, { 256, 256 }, { 512, 512 }, { 1024, 1024 } } // rotation/scaling (roz) mode }; int mode = text ? 0 : 1; int size = (bgxcnt & 0xc000) >> 14; width = size_table[mode][size][0]; height = size_table[mode][size][1]; } inline uint16_t gba_lcd_device::mosaic_size(size_type type) { return ((MOSAIC >> (4 * underlying_value(type))) & 0xf) + 1; } inline gba_lcd_device::sfx gba_lcd_device::color_sfx() { return enum_value(BLDCNT & 0x00c0); } inline uint8_t gba_lcd_device::color_sfx_target(target id) { return (BLDCNT >> (8 * underlying_value(id))) & 0x3f; } inline void gba_lcd_device::update_mask(uint8_t* mask, int y) { bool inwin0 = false; bool inwin1 = false; if (is_set(dispcnt::win0_en)) inwin0 = is_in_window_v(y, 0); if (is_set(dispcnt::win1_en)) inwin1 = is_in_window_v(y, 1); for (auto x = 0; x < 240; x++) { mask[x] = WINOUT & 0x00ff; if (m_scanline[5][x] != TRANSPARENT_PIXEL) mask[x] = WINOUT >> 8; if (inwin1 && is_in_window_h(x, 1)) mask[x] = WININ >> 8; if (inwin0 && is_in_window_h(x, 0)) mask[x] = WININ & 0x00ff; } } void gba_lcd_device::draw_scanline(int y) { uint16_t *scanline = &m_bitmap.pix16(y); if (is_set(dispcnt::forced_blank)) { // forced blank is white for (auto x = 0; x < 240; x++) scanline[x] = 0x7fff; return; } uint8_t mode = bg_video_mode(); uint8_t submode; if (is_set(dispcnt::win0_en) || is_set(dispcnt::win1_en) || is_set(dispcnt::obj_win_en)) submode = 2; else if (color_sfx() != sfx::none) submode = 1; else submode = 0; int depth = 0; if (mode == 3) depth = 16; else if (mode == 4) depth = 8; else if (mode == 5) depth = 4; // make all layers transparent at start for (auto l = 0; l < 6; l++) { for (auto x = 0; x < 240; x++) { m_scanline[l][x] = TRANSPARENT_PIXEL; } } // draw background switch (mode) { case 0: draw_bg_scanline(m_scanline[0], y, dispcnt::bg0_en, BG0CNT, BG0HOFS, BG0VOFS); draw_bg_scanline(m_scanline[1], y, dispcnt::bg1_en, BG1CNT, BG1HOFS, BG1VOFS); draw_bg_scanline(m_scanline[2], y, dispcnt::bg2_en, BG2CNT, BG2HOFS, BG2VOFS); draw_bg_scanline(m_scanline[3], y, dispcnt::bg3_en, BG3CNT, BG3HOFS, BG3VOFS); break; case 1: draw_bg_scanline(m_scanline[0], y, dispcnt::bg0_en, BG0CNT, BG0HOFS, BG0VOFS); draw_bg_scanline(m_scanline[1], y, dispcnt::bg1_en, BG1CNT, BG1HOFS, BG1VOFS); draw_roz_scanline(m_scanline[2], y, dispcnt::bg2_en, BG2CNT, BG2X, BG2Y, BG2PA, BG2PB, BG2PC, BG2PD, m_bg2x, m_bg2y); break; case 2: draw_roz_scanline(m_scanline[2], y, dispcnt::bg2_en, BG2CNT, BG2X, BG2Y, BG2PA, BG2PB, BG2PC, BG2PD, m_bg2x, m_bg2y); draw_roz_scanline(m_scanline[3], y, dispcnt::bg3_en, BG3CNT, BG3X, BG3Y, BG3PA, BG3PB, BG3PC, BG3PD, m_bg3x, m_bg3y); break; case 3: case 4: case 5: draw_roz_bitmap_scanline(m_scanline[2], y, dispcnt::bg2_en, BG2CNT, BG2X, BG2Y, BG2PA, BG2PB, BG2PC, BG2PD, m_bg2x, m_bg2y, depth); break; } uint8_t mask[240]; // draw objects draw_oam(m_scanline[4], y); if (submode == 2) { draw_oam_window(m_scanline[5], y); update_mask(mask, y); } else { memset(mask, 0xff, sizeof(mask)); } uint32_t backdrop = ((uint16_t *)m_pram.get())[0] | 0x30000000; for (auto x = 0; x < 240; x++) { uint32_t color = backdrop; uint8_t top = 0x20; for (auto l = 0; l < 5; l++) { if ((m_scanline[l][x] >> 24) < (color >> 24) && mask[x] & (0x01 << l)) { color = m_scanline[l][x]; top = (0x01 << l); } } if (color & 0x00010000) { if (submode != 0 || top == 0x10) { uint32_t back = backdrop; uint8_t top2 = 0x20; for (auto l = 0; l < 4; l++) { if ((m_scanline[l][x] >> 24) < (back >> 24) && mask[x] & (0x01 << l)) { back = m_scanline[l][x]; top2 = (0x01 << l); } } if (top2 & color_sfx_target(target::second)) { color = alpha_blend(color, back); } else if (top & color_sfx_target(target::first)) { switch (color_sfx()) { case sfx::lighten: color = increase_brightness(color); break; case sfx::darken: color = decrease_brightness(color); break; default: break; } } } } else if (submode == 1 || (submode == 2 && mask[x] & 0x20)) { if (top & color_sfx_target(target::first)) { switch (color_sfx()) { case sfx::none: break; case sfx::alpha: { uint32_t back = backdrop; uint8_t top2 = 0x20; for (auto l = 0; l < 5; l++) { if ((m_scanline[l][x] >> 24) < (back >> 24) && mask[x] & (0x01 << l)) { if (top != (0x01 << l)) { back = m_scanline[l][x]; top2 = (0x01 << l); } } } if (top2 & color_sfx_target(target::second)) color = alpha_blend(color, back); break; } case sfx::lighten: color = increase_brightness(color); break; case sfx::darken: color = decrease_brightness(color); break; } } } scanline[x] = color & 0x7fff; } } void gba_lcd_device::draw_roz_bitmap_scanline(uint32_t *scanline, int ypos, dispcnt bg_enable, uint32_t ctrl, int32_t X, int32_t Y, int32_t PA, int32_t PB, int32_t PC, int32_t PD, internal_reg ¤tx, internal_reg ¤ty, int depth) { if (!is_set(bg_enable)) return; uint8_t *src8 = (uint8_t *)m_vram.get(); uint16_t *src16 = (uint16_t *)m_vram.get(); uint16_t *palette = (uint16_t *)m_pram.get(); int32_t sx = (depth == 4) ? 160 : 240; int32_t sy = (depth == 4) ? 128 : 160; uint32_t prio = (bg_priority(ctrl) << 25) + 0x1000000; if (is_set(dispcnt::alt_frame_sel)) { if (depth == 8) src8 += 0xa000; if (depth == 4) src16 += 0xa000 / 2; } // sign extend roz parameters if (X & 0x08000000) X |= 0xf0000000; if (Y & 0x08000000) Y |= 0xf0000000; if (PA & 0x8000) PA |= 0xffff0000; if (PB & 0x8000) PB |= 0xffff0000; if (PC & 0x8000) PC |= 0xffff0000; if (PD & 0x8000) PD |= 0xffff0000; if (currentx.update) { currentx.status = X; currentx.update = false; } else { currentx.status += PB; } if (currenty.update) { currenty.status = Y; currenty.update = false; } else { currenty.status += PD; } int32_t cx = currentx.status; int32_t cy = currenty.status; if (is_set(ctrl, bgcnt::mosaic_en)) { uint16_t mosaic_line = mosaic_size(size_type::bg_v); int32_t tempy = (ypos / mosaic_line) * mosaic_line; cx = X + tempy * PB; cy = Y + tempy * PD; } int32_t pixx = cx >> 8; int32_t pixy = cy >> 8; for (auto x = 0; x < 240; x++) { if (pixx >= 0 && pixy >= 0 && pixx < sx && pixy < sy) { if (depth == 8) { uint8_t color = src8[pixy * sx + pixx]; if (color) scanline[x] = palette[color] | prio; } else { scanline[x] = src16[pixy * sx + pixx] | prio; } } cx += PA; cy += PC; pixx = cx >> 8; pixy = cy >> 8; } if (is_set(ctrl, bgcnt::mosaic_en)) { uint16_t mosaicx = mosaic_size(size_type::bg_h); if (mosaicx > 1) { int32_t m = 1; for (auto x = 0; x < 239; x++) { scanline[x + 1] = scanline[x]; m++; if (m == mosaicx) { m = 1; x++; } } } } } void gba_lcd_device::draw_roz_scanline(uint32_t *scanline, int ypos, dispcnt bg_enable, uint32_t ctrl, int32_t X, int32_t Y, int32_t PA, int32_t PB, int32_t PC, int32_t PD, internal_reg ¤tx, internal_reg ¤ty) { if (!is_set(bg_enable)) return; uint8_t *mgba_vram = (uint8_t *)m_vram.get(); uint16_t *pgba_pram = (uint16_t *)m_pram.get(); uint32_t priority = (bg_priority(ctrl) << 25) + 0x1000000; uint32_t base = bg_char_base(ctrl); uint32_t mapbase = bg_screen_base(ctrl); // size of map in submaps int width, height; bg_screen_size(ctrl, false, width, height); // sign extend roz parameters if (X & 0x08000000) X |= 0xf0000000; if (Y & 0x08000000) Y |= 0xf0000000; if (PA & 0x8000) PA |= 0xffff0000; if (PB & 0x8000) PB |= 0xffff0000; if (PC & 0x8000) PC |= 0xffff0000; if (PD & 0x8000) PD |= 0xffff0000; if (currentx.update) { currentx.status = X; currentx.update = false; } else { currentx.status += PB; } if (currenty.update) { currenty.status = Y; currenty.update = false; } else { currenty.status += PD; } int32_t cx = currentx.status; int32_t cy = currenty.status; if (is_set(ctrl, bgcnt::mosaic_en)) { uint16_t mosaic_line = mosaic_size(size_type::bg_v); int y = ypos % mosaic_line; cx -= y * PB; cy -= y * PD; } int32_t pixx = cx >> 8; int32_t pixy = cy >> 8; if (is_set(ctrl, bgcnt::wraparound_en)) { pixx %= width; pixy %= height; if (pixx < 0) pixx += width; if (pixy < 0) pixy += height; } for (auto x = 0; x < 240; x++) { if (pixx >= 0 && pixy >= 0 && pixx < width && pixy < height) { int tilex = pixx & 7; int tiley = pixy & 7; // shall we shift for is_set(ctrl, bgcnt::palette_256)? or is not effective for ROZ? uint32_t tile = mgba_vram[mapbase + (pixx >> 3) + (pixy >> 3) * (width >> 3)]; uint16_t pixel = mgba_vram[base + (tile << 6) + (tiley << 3) + tilex]; // plot it if (pixel) scanline[x] = pgba_pram[pixel] | priority; } cx += PA; cy += PC; pixx = cx >> 8; pixy = cy >> 8; if (is_set(ctrl, bgcnt::wraparound_en)) { pixx %= width; pixy %= height; if (pixx < 0) pixx += width; if (pixy < 0) pixy += height; } } if (is_set(ctrl, bgcnt::mosaic_en)) { uint16_t mosaicx = mosaic_size(size_type::bg_h); if (mosaicx > 1) { int m = 1; for (auto x = 0; x < 239; x++) { scanline[x + 1] = scanline[x]; m++; if (m == mosaicx) { m = 1; x++; } } } } } void gba_lcd_device::draw_bg_scanline(uint32_t *scanline, int ypos, dispcnt bg_enable, uint32_t ctrl, uint32_t hofs, uint32_t vofs) { if (!is_set(bg_enable)) return; uint8_t *vram = (uint8_t*)m_vram.get(); uint16_t *palette = (uint16_t *)m_pram.get(); uint8_t *chardata = &vram[bg_char_base(ctrl)]; uint16_t *screendata = (uint16_t *)&vram[bg_screen_base(ctrl)]; uint32_t priority = (bg_priority(ctrl) << 25) + 0x1000000; uint16_t mosaicx = mosaic_size(size_type::bg_h); uint16_t mosaicy = mosaic_size(size_type::bg_v); int width, height; bg_screen_size(ctrl, true, width, height); int32_t pixx = hofs % width; int32_t pixy = (vofs + ypos) % height; if (is_set(ctrl, bgcnt::mosaic_en) && ypos % mosaicy) { mosaicy = (ypos / mosaicy) * mosaicy; pixy = (vofs + mosaicy) % height; } if (pixy > 255 && height > 256) { pixy &= 0x000000ff; screendata += 0x400; if (width > 256) { screendata += 0x400; } } int32_t stride = (pixy >> 3) << 5; uint16_t *src = screendata + 0x400 * (pixx >> 8) + ((pixx & 255) >> 3) + stride; for (auto x = 0; x < 240; x++) { uint16_t data = *src; int32_t tile = tile_number(data); int32_t tilex = pixx & 7; int32_t tiley = pixy & 7; uint8_t color; uint8_t palindex; if (tile_hflip(data)) tilex = 7 - tilex; if (tile_vflip(data)) tiley = 7 - tiley; if (is_set(ctrl, bgcnt::palette_256)) { color = chardata[(tile << 6) + (tiley << 3) + tilex]; palindex = 0; } else { color = chardata[(tile << 5) + (tiley << 2) + (tilex >> 1)]; if (tilex & 1) color >>= 4; else color &= 0x0f; palindex = (data >> 8) & 0x00f0; } if (color) scanline[x] = palette[palindex + color] | priority; if (tile_hflip(data)) { if (tilex == 0) src++; } else if (tilex == 7) { src++; } pixx++; if (pixx == 256) { if (width > 256) { src = screendata + 0x400 + stride; } else { src = screendata + stride; pixx = 0; } } else if (pixx >= width) { pixx = 0; src = screendata + stride; } } if (is_set(ctrl, bgcnt::mosaic_en) && mosaicx > 1) { int32_t m = 1; for (auto x = 0; x < 239; x++) { scanline[x+1] = scanline[x]; m++; if (m == mosaicx) { m = 1; x++; } } } } void gba_lcd_device::draw_oam_window(uint32_t *scanline, int y) { if (!is_set(dispcnt::obj_win_en)) return; uint16_t *oam = (uint16_t *)m_oam.get(); uint8_t *src = (uint8_t *)m_vram.get(); for (auto obj_index = 127; obj_index >= 0; obj_index--) { object obj(*this, oam, obj_index); if (obj.mode_enum() != object::mode::window) continue; uint32_t tile_number = obj.tile_number(); if (bg_video_mode() > 2 && tile_number < 0x200) continue; int32_t sx = obj.pos_x(); int32_t sy = obj.pos_y(); if (sy > 160) sy -= 256; int width, height; obj.size(width, height); if (obj.roz()) { int32_t fx = width; int32_t fy = height; if (obj.roz_double()) { fx *= 2; fy *= 2; } int32_t cury = y - sy; if (cury < 0 || cury >= fy) continue; if (sx >= 240 && ((sx + fx) % 512) >= 240) continue; int rot = obj.roz_param(); int16_t dx = (int16_t)oam[(rot << 4) + 3]; int16_t dmx = (int16_t)oam[(rot << 4) + 7]; int16_t dy = (int16_t)oam[(rot << 4) + 11]; int16_t dmy = (int16_t)oam[(rot << 4) + 15]; int32_t rx = (width << 7) - (fx >> 1) * dx - (fy >> 1) * dmx + cury * dmx; int32_t ry = (height << 7) - (fx >> 1) * dy - (fy >> 1) * dmy + cury * dmy; int inc = 32; if (obj.palette_256()) { if (is_set(dispcnt::vram_map_1d)) inc = sx >> 2; else tile_number &= 0x3fe; for (auto x = 0; x < fx; x++) { int32_t ax = rx >> 8; int32_t ay = ry >> 8; if (ax >= 0 && ax < sx && ay >= 0 && ay < sy) { uint8_t color = src[0x10000 + ((((tile_number + (ay >> 3) * inc) << 5) + ((ay & 0x07) << 3) + ((ax >> 3) << 6) + (ax & 0x07)) & 0x7fff)]; if (color) scanline[sx] = 1; } sx = (sx + 1) % 512; rx += dx; ry += dy; } } else { if (is_set(dispcnt::vram_map_1d)) inc = sx >> 3; for (auto x = 0; x < fx; x++) { int32_t ax = rx >> 8; int32_t ay = ry >> 8; if (ax >= 0 && ax < sx && ay >= 0 && ay < sy) { uint8_t color = src[0x10000 + ((((tile_number + (ay >> 3) * inc) << 5) + ((ay & 0x07) << 2) + ((ax >> 3) << 5) + ((ax & 0x07) >> 1)) & 0x7fff)]; if (ax & 1) color >>= 4; else color &= 0x0f; if (color) scanline[sx] = 1; } sx = (sx + 1) % 512; rx += dx; ry += dy; } } } else { // when roz bit is not set double roz bit means 'disable object' if (obj.roz_double()) continue; int32_t cury = y - sy; if (cury < 0 || cury >= height) continue; if ((sx >= 240) && (((sx + width) % 512) >= 240)) continue; int inc = 32; if (obj.vflip()) cury = height - cury - 1; int32_t ax = obj.hflip() ? (width - 1) : 0; if (obj.palette_256()) { if (is_set(dispcnt::vram_map_1d)) inc = width >> 2; else tile_number &= 0x3fe; uint32_t address = 0x10000 + ((((tile_number + (cury >> 3) * inc) << 5) + ((cury & 7) << 3) + ((ax >> 3) << 6) + (ax & 7)) & 0x7fff); if (obj.hflip()) ax = 7; for (auto x = 0; x < width; x++) { if (sx < 240) { uint8_t color = src[address]; if (color) scanline[sx] = 1; } sx = (sx + 1) % 512; if (obj.hflip()) { ax--; address--; if (ax == -1) { address -= 56; ax = 7; } if (address < 0x10000) address += 0x8000; } else { ax++; address++; if (ax == 8) { address += 56; ax = 0; } if (address > 0x17fff) address -= 0x8000; } } } else { if (is_set(dispcnt::vram_map_1d)) inc = width >> 3; uint32_t address = 0x10000 + ((((tile_number + (cury >> 3) * inc) << 5) + ((cury & 0x07) << 2) + ((ax >> 3) << 5) + ((ax & 0x07) >> 1)) & 0x7fff); if (obj.hflip()) { ax = 7; for (auto x = width - 1; x >= 0; x--) { if (sx < 240) { uint8_t color = src[address]; if (x & 1) color >>= 4; else color &= 0x0f; if (color) scanline[sx] = 1; } sx = (sx + 1) % 512; ax--; if ((x & 1) == 0) address--; if (ax == -1) { ax = 7; address -= 28; } if (address < 0x10000) address += 0x8000; } } else { for (auto x = 0; x < width; x++) { if (sx < 240) { uint8_t color = src[address]; if (x & 1) color >>= 4; else color &= 0x0f; if (color) scanline[sx] = 1; } sx = (sx + 1) % 512; ax++; if (x & 1) address++; if (ax == 8) { address += 28; ax = 0; } if (address > 0x17fff) address -= 0x8000; } } } } } } void gba_lcd_device::draw_oam(uint32_t *scanline, int y) { if (!is_set(dispcnt::obj_en)) return; int32_t mosaiccnt = 0; uint16_t mosaicx = mosaic_size(size_type::obj_h); uint16_t mosaicy = mosaic_size(size_type::obj_v); uint16_t *oam = (uint16_t *)m_oam.get(); uint8_t *src = (uint8_t *)m_vram.get(); uint16_t *palette = (uint16_t *)m_pram.get(); for (auto obj_index = 0; obj_index < 128; obj_index++) { object obj(*this, oam, obj_index); if (obj.mode_enum() == object::mode::window) continue; uint32_t priority = obj.priority(); uint32_t prio = (priority << 25) | (obj.mode_mask() << 6); int width, height; obj.size(width, height); uint32_t tile_number = obj.tile_number(); if (bg_video_mode() > 2 && tile_number < 0x200) continue; if (obj.roz()) { int32_t sx = obj.pos_x(); int32_t sy = obj.pos_y(); if (sy > 160) sy -= 256; int32_t fx = width; int32_t fy = height; if (obj.roz_double()) { fx *= 2; fy *= 2; } int32_t cury = y - sy; if (cury < 0 || cury >= fy) continue; if (sx >= 240 && ((sx + fx) % 512) >= 240) continue; int32_t oamparam = obj.roz_param(); int16_t dx = (int16_t)oam[(oamparam << 4) + 3]; int16_t dmx = (int16_t)oam[(oamparam << 4) + 7]; int16_t dy = (int16_t)oam[(oamparam << 4) + 11]; int16_t dmy = (int16_t)oam[(oamparam << 4) + 15]; if (obj.mosaic()) cury -= (cury % mosaicy); int32_t rx = (width << 7) - (fx >> 1) * dx - (fy >> 1) * dmx + cury * dmx; int32_t ry = (height << 7) - (fx >> 1) * dy - (fy >> 1) * dmy + cury * dmy; int32_t inc = 32; if (obj.palette_256()) { if (is_set(dispcnt::vram_map_1d)) inc = width >> 2; else tile_number &= 0x3fe; for (auto x = 0; x < fx; x++) { int32_t pixx = rx >> 8; int32_t pixy = ry >> 8; if (!(pixx < 0 || pixx >= width || pixy < 0 || pixy >= height || sx >= 240)) { uint8_t color = src[0x10000 + ((((tile_number + (pixy >> 3) * inc) << 5) + ((pixy & 7) << 3) + ((pixx >> 3) << 6) + (pixx & 7)) & 0x7fff)]; if (color == 0 && priority < ((scanline[sx] >> 25) & 3)) { scanline[sx] = (scanline[sx] & 0xf9ffffff) | prio; if (obj.mosaic() && mosaiccnt != 0) { scanline[sx] = (scanline[sx - 1] & 0xf9ffffff) | prio; } } else if (color != 0 && prio < (scanline[sx] & 0xff000000)) { scanline[sx] = palette[256 + color] | prio; if (obj.mosaic() && mosaiccnt != 0) { scanline[sx] = (scanline[sx - 1] & 0xf9ffffff) | prio; } } if (obj.mosaic()) mosaiccnt = (mosaiccnt + 1) % mosaicx; } sx = (sx + 1) % 512; rx += dx; ry += dy; } } else { if (is_set(dispcnt::vram_map_1d)) inc = width >> 3; for (auto x = 0; x < fx; x++) { int32_t pixx = rx >> 8; int32_t pixy = ry >> 8; if (!(pixx < 0 || pixx >= width || pixy < 0 || pixy >= height || sx >= 240)) { uint8_t color = src[0x10000 + ((((tile_number + (pixy >> 3) * inc) << 5) + ((pixy & 7) << 2) + ((pixx >> 3) << 5) + ((pixx & 7) >> 1)) & 0x7fff)]; if (pixx & 1) color >>= 4; else color &= 0x0f; if (color == 0 && priority < ((scanline[sx] >> 25) & 3)) { scanline[sx] = (scanline[sx] & 0xf9ffffff) | prio; if (obj.mosaic() && mosaiccnt != 0) { scanline[sx] = (scanline[sx - 1] & 0xf9ffffff) | prio; } } else if (color != 0 && prio < (scanline[sx] & 0xff000000)) { scanline[sx] = palette[256 + obj.palette() + color] | prio; if (obj.mosaic() && mosaiccnt != 0) { scanline[sx] = (scanline[sx - 1] & 0xf9ffffff) | prio; } } } if (obj.mosaic()) mosaiccnt = (mosaiccnt + 1) % mosaicx; sx = (sx + 1) % 512; rx += dx; ry += dy; } } } else { // when roz bit is not set double roz bit means 'disable object' if (obj.roz_double()) continue; int32_t sx = obj.pos_x(); int32_t sy = obj.pos_y(); if (sy > 160) sy -= 256; int32_t cury = y - sy; if (cury < 0 || cury >= height) continue; if (sx >= 240 && ((sx + width) % 512) >= 240) continue; int32_t inc = 32; if (obj.vflip()) cury = height - cury - 1; int32_t pixx = obj.hflip() ? (width - 1) : 0; if (obj.mosaic()) cury -= (cury % mosaicy); if (obj.palette_256()) { if (is_set(dispcnt::vram_map_1d)) inc = width >> 2; else tile_number &= 0x3fe; uint32_t address = 0x10000 + ((((tile_number + (cury >> 3) * inc) << 5) + ((cury & 7) << 3) + ((pixx >> 3) << 6) + (pixx & 7)) & 0x7fff); if (obj.hflip()) pixx = 7; for (auto x = 0; x < width; x++) { if (sx < 240) { uint8_t color = src[address]; if (color == 0 && priority < ((scanline[sx] >> 25) & 3)) { scanline[sx] = (scanline[sx] & 0xf9ffffff) | prio; if (obj.mosaic() && mosaiccnt != 0) { scanline[sx] = (scanline[sx - 1] & 0xf9ffffff) | prio; } } else if (color != 0 && prio < (scanline[sx] & 0xff000000)) { scanline[sx] = palette[256 + color] | prio; if (obj.mosaic() && mosaiccnt != 0) { scanline[sx] = (scanline[sx - 1] & 0xf9ffffff) | prio; } } } if (obj.mosaic()) mosaiccnt = (mosaiccnt + 1) % mosaicx; sx = (sx + 1) % 512; if (obj.hflip()) { pixx--; address--; if (pixx == -1) { address -= 56; pixx = 7; } if (address < 0x10000) address += 0x8000; } else { pixx++; address++; if (pixx == 8) { address += 56; pixx = 0; } if (address > 0x17fff) address -= 0x8000; } } } else { if (is_set(dispcnt::vram_map_1d)) inc = width >> 3; uint32_t address = 0x10000 + ((((tile_number + (cury >> 3) * inc) << 5) + ((cury & 7) << 2) + ((pixx >> 3) << 5) + ((pixx & 7) >> 1)) & 0x7fff); if (obj.hflip()) { pixx = 7; for (auto x = width - 1; x >= 0; x--) { if (sx < 240) { uint8_t color = src[address]; if (x & 1) color >>= 4; else color &= 0x0f; if (color == 0 && priority < ((scanline[sx] >> 25) & 3)) { scanline[sx] = (scanline[sx] & 0xf9ffffff) | prio; if (obj.mosaic() && mosaiccnt != 0) { scanline[sx] = (scanline[sx - 1] & 0xf9ffffff) | prio; } } else if (color != 0 && prio < (scanline[sx] & 0xff000000)) { scanline[sx] = palette[256 + obj.palette() + color] | prio; if (obj.mosaic() && mosaiccnt != 0) { scanline[sx] = (scanline[sx - 1] & 0xf9ffffff) | prio; } } } sx = (sx + 1) % 512; pixx--; if (!(x & 1)) address--; if (pixx == -1) { address -= 28; pixx = 7; } if (address < 0x10000) address += 0x8000; } } else { for (auto x = 0; x < width; x++) { if (sx < 240) { uint8_t color = src[address]; if (x & 1) color >>= 4; else color &= 0x0f; if (color == 0 && priority < ((scanline[sx] >> 25) & 3)) { scanline[sx] = (scanline[sx] & 0xf9ffffff) | prio; if (obj.mosaic() && mosaiccnt != 0) { scanline[sx] = (scanline[sx - 1] & 0xf9ffffff) | prio; } } else if (color != 0 && prio < (scanline[sx] & 0xff000000)) { scanline[sx] = palette[256 + obj.palette() + color] | prio; if (obj.mosaic() && mosaiccnt != 0) { scanline[sx] = (scanline[sx - 1] & 0xf9ffffff) | prio; } } } if (obj.mosaic()) mosaiccnt = (mosaiccnt + 1) % mosaicx; sx = (sx + 1) % 512; pixx++; if (x & 1) address++; if (pixx == 8) { address += 28; pixx = 0; } if (address > 0x17fff) address -= 0x8000; } } } } } } inline bool gba_lcd_device::is_in_window_h(int x, int window) { uint16_t reg = (window == 0) ? WIN0H : WIN1H; uint8_t x0 = reg >> 8; uint8_t x1 = reg & 0x00ff; if (x0 <= x1) { if (x >= x0 && x < x1) return true; } else { if (x >= x0 || x < x1) return true; } return false; } inline bool gba_lcd_device::is_in_window_v(int y, int window) { uint16_t reg = (window == 0) ? WIN0V : WIN1V; uint8_t v0 = reg >> 8; uint8_t v1 = reg & 0x00ff; if ((v0 == v1) && (v0 >= 0xe8)) return true; if (v1 >= v0) { if (y >= v0 && y < v1) return true; } else { if (y >= v0 || y < v1) return true; } return false; } static const int coeff[32] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16 }; inline uint32_t gba_lcd_device::alpha_blend(uint32_t color0, uint32_t color1) { int ca = coeff[BLDALPHA & 0x1f]; int cb = coeff[(BLDALPHA >> 8) & 0x1f]; if (color0 != TRANSPARENT_PIXEL) { int r0 = (color0 >> 0) & 0x1f; int g0 = (color0 >> 5) & 0x1f; int b0 = (color0 >> 10) & 0x1f; int r1 = (color1 >> 0) & 0x1f; int g1 = (color1 >> 5) & 0x1f; int b1 = (color1 >> 10) & 0x1f; int r = ((r0 * ca) >> 4) + ((r1 * cb) >> 4); int g = ((g0 * ca) >> 4) + ((g1 * cb) >> 4); int b = ((b0 * ca) >> 4) + ((b1 * cb) >> 4); if (r > 0x1f) r = 0x1f; if (g > 0x1f) g = 0x1f; if (b > 0x1f) b = 0x1f; return (color0 & 0xffff0000) | (b << 10) | (g << 5) | r; } return color0; } inline uint32_t gba_lcd_device::increase_brightness(uint32_t color) { int cc = coeff[BLDY & 0x1f]; int r = (color >> 0) & 0x1f; int g = (color >> 5) & 0x1f; int b = (color >> 10) & 0x1f; r += ((0x1f - r) * cc) >> 4; g += ((0x1f - g) * cc) >> 4; b += ((0x1f - b) * cc) >> 4; if (r > 0x1f) r = 0x1f; if (g > 0x1f) g = 0x1f; if (b > 0x1f) b = 0x1f; return (color & 0xffff0000) | (b << 10) | (g << 5) | r; } inline uint32_t gba_lcd_device::decrease_brightness(uint32_t color) { int cc = coeff[BLDY & 0x1f]; int r = (color >> 0) & 0x1f; int g = (color >> 5) & 0x1f; int b = (color >> 10) & 0x1f; r -= (r * cc) >> 4; g -= (g * cc) >> 4; b -= (b * cc) >> 4; if (r < 0) r = 0; if (g < 0) g = 0; if (b < 0) b = 0; return (color & 0xffff0000) | (b << 10) | (g << 5) | r; } static const char *reg_names[] = { /* LCD I/O Registers */ "DISPCNT", "GRNSWAP", "DISPSTAT", "VCOUNT", "BG0CNT", "BG1CNT", "BG2CNT", "BG3CNT", "BG0HOFS", "BG0VOFS", "BG1HOFS", "BG1VOFS", "BG2HOFS", "BG2VOFS", "BG3HOFS", "BG3VOFS", "BG2PA", "BG2PB", "BG2PC", "BG2PD", "BG2X_L", "BG2X_H", "BG2Y_L", "BG2Y_H", "BG3PA", "BG3PB", "BG3PC", "BG3PD", "BG3X_L", "BG3X_H", "BG3Y_L", "BG3Y_H", "WIN0H", "WIN1H", "WIN0V", "WIN1V", "WININ", "WINOUT", "MOSAIC", "Unused", "BLDCNT", "BLDALPHA", "BLDY", "Unused", "Unused", "Unused", "Unused", "Unused", }; READ32_MEMBER(gba_lcd_device::video_r) { uint32_t retval = 0; switch (offset) { case 0x0004/4: retval = DISPSTAT | (screen().vpos() << 16); break; default: if (ACCESSING_BITS_0_15) { retval |= m_regs[offset] & 0x0000ffff; } if (ACCESSING_BITS_16_31) { retval |= m_regs[offset] & 0xffff0000; } break; } assert_always(offset < ARRAY_LENGTH(reg_names) / 2, "Not enough register names in gba_lcd_device"); if (ACCESSING_BITS_0_15) { verboselog(*this, 2, "GBA I/O Read: %s = %04x\n", reg_names[offset * 2], retval & 0x0000ffff); } if (ACCESSING_BITS_16_31) { verboselog(*this, 2, "GBA I/O Read: %s = %04x\n", reg_names[offset * 2 + 1], (retval & 0xffff0000) >> 16); } return retval; } WRITE32_MEMBER(gba_lcd_device::video_w) { COMBINE_DATA(&m_regs[offset]); assert_always(offset < ARRAY_LENGTH(reg_names) / 2, "Not enough register names in gba_lcd_device"); if (ACCESSING_BITS_0_15) { verboselog(*this, 2, "GBA I/O Write: %s = %04x\n", reg_names[offset * 2], data & 0x0000ffff); } if (ACCESSING_BITS_16_31) { verboselog(*this, 2, "GBA I/O Write: %s = %04x\n", reg_names[offset * 2 + 1], (data & 0xffff0000) >> 16); } switch (offset) { case 0x0028/4: m_bg2x.update = true; break; case 0x002c/4: m_bg2y.update = true; break; case 0x0038/4: m_bg3x.update = true; break; case 0x003c/4: m_bg3y.update = true; break; } } READ32_MEMBER(gba_lcd_device::gba_pram_r) { return m_pram[offset]; } WRITE32_MEMBER(gba_lcd_device::gba_pram_w) { COMBINE_DATA(&m_pram[offset]); } READ32_MEMBER(gba_lcd_device::gba_vram_r) { return m_vram[offset]; } WRITE32_MEMBER(gba_lcd_device::gba_vram_w) { COMBINE_DATA(&m_vram[offset]); } READ32_MEMBER(gba_lcd_device::gba_oam_r) { return m_oam[offset]; } WRITE32_MEMBER(gba_lcd_device::gba_oam_w) { COMBINE_DATA(&m_oam[offset]); } TIMER_CALLBACK_MEMBER(gba_lcd_device::perform_hbl) { int scanline = screen().vpos(); // reload LCD controller internal registers from I/O ones at vblank if (scanline == 0) { m_bg2x.update = true; m_bg2y.update = true; m_bg3x.update = true; m_bg3y.update = true; } // draw only visible scanlines if (scanline < 160) { draw_scanline(scanline); if (!m_dma_hblank_cb.isnull()) m_dma_hblank_cb(ASSERT_LINE); } if (is_set(dispstat::hblank_irq_en)) { if (!m_int_hblank_cb.isnull()) m_int_hblank_cb(ASSERT_LINE); } set(dispstat::hblank); m_hbl_timer->adjust(attotime::never); } TIMER_CALLBACK_MEMBER(gba_lcd_device::perform_scan) { clear(dispstat::hblank); clear(dispstat::vcount); int scanline = screen().vpos(); // VBLANK is set for scanlines 160 through 226 (but not 227, which is the last line) if (scanline >= 160 && scanline < 227) { set(dispstat::vblank); // VBL IRQ and DMA on line 160 if (scanline == 160) { if (is_set(dispstat::vblank_irq_en)) { if (!m_int_vblank_cb.isnull()) m_int_vblank_cb(ASSERT_LINE); } if (!m_dma_vblank_cb.isnull()) m_dma_vblank_cb(ASSERT_LINE); } } else { clear(dispstat::vblank); } // handle VCOUNT match interrupt flag if (scanline == ((DISPSTAT >> 8) & 0xff)) { set(dispstat::vcount); if (is_set(dispstat::vcount_irq_en)) { if (!m_int_vcount_cb.isnull()) m_int_vcount_cb(ASSERT_LINE); } } m_hbl_timer->adjust(screen().time_until_pos(scanline, 240)); m_scan_timer->adjust(screen().time_until_pos((scanline + 1) % 228, 0)); } PALETTE_INIT_MEMBER(gba_lcd_device, gba) { for (uint8_t b = 0; b < 32; b++) { for (uint8_t g = 0; g < 32; g++) { for (uint8_t r = 0; r < 32; r++) { palette.set_pen_color((b << 10) | (g << 5) | r, pal5bit(r), pal5bit(g), pal5bit(b)); } } } } uint32_t gba_lcd_device::screen_update(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect) { copybitmap(bitmap, m_bitmap, 0, 0, 0, 0, cliprect); return 0; } void gba_lcd_device::device_start() { /* resolve callbacks */ m_int_hblank_cb.resolve(); m_int_vblank_cb.resolve(); m_int_vcount_cb.resolve(); m_dma_hblank_cb.resolve(); m_dma_vblank_cb.resolve(); m_pram = make_unique_clear(0x400 / 4); m_vram = make_unique_clear(0x18000 / 4); m_oam = make_unique_clear(0x400 / 4); screen().register_screen_bitmap(m_bitmap); /* create a timer to fire scanline functions */ m_scan_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(gba_lcd_device::perform_scan),this)); m_hbl_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(gba_lcd_device::perform_hbl),this)); m_scan_timer->adjust(screen().time_until_pos(0, 0)); save_item(NAME(m_regs)); save_pointer(NAME(m_pram), 0x400 / 4); save_pointer(NAME(m_vram), 0x18000 / 4); save_pointer(NAME(m_oam), 0x400 / 4); save_item(NAME(m_bg2x.status)); save_item(NAME(m_bg2x.update)); save_item(NAME(m_bg2y.status)); save_item(NAME(m_bg2y.update)); save_item(NAME(m_bg3x.status)); save_item(NAME(m_bg3x.update)); save_item(NAME(m_bg3y.status)); save_item(NAME(m_bg3y.update)); save_item(NAME(m_scanline)); } void gba_lcd_device::device_reset() { memset(m_regs, 0, sizeof(m_regs)); m_bg2x = { 0, false }; m_bg2y = { 0, false }; m_bg3x = { 0, false }; m_bg3y = { 0, false }; m_scan_timer->adjust(screen().time_until_pos(0, 0)); m_hbl_timer->adjust(attotime::never); } MACHINE_CONFIG_START(gba_lcd_device::device_add_mconfig) MCFG_SCREEN_ADD("screen", LCD) MCFG_SCREEN_RAW_PARAMS(XTAL(16'777'216) / 4, 308, 0, 240, 228, 0, 160) MCFG_SCREEN_UPDATE_DEVICE(DEVICE_SELF, gba_lcd_device, screen_update) MCFG_SCREEN_PALETTE("palette") MCFG_DEFAULT_LAYOUT(layout_lcd) MCFG_PALETTE_ADD("palette", 32768) MCFG_PALETTE_INIT_OWNER(gba_lcd_device, gba) MACHINE_CONFIG_END