// license:BSD-3-Clause // copyright-holders:Alex Pasadyn, Zsolt Vasvari, Ernesto Corvi, Aaron Giles // thanks-to:Kurt Mahan /************************************************************************* Video Emulation for Midway T-unit, W-unit, and X-unit games. **************************************************************************/ #include "emu.h" #include "midtunit.h" #include "midtunit.ipp" #include "screen.h" #include "midtview.ipp" #include "debug/debugcon.h" #include "debug/debugcmd.h" #include "debugger.h" #include "emuopts.h" // Used by PNG logging #include "fileio.h" // Used by PNG logging #include "png.h" // Used by PNG logging //#include // Used by JSON logging //#include // Used by JSON logging DEFINE_DEVICE_TYPE(MIDTUNIT_VIDEO, midtunit_video_device, "tunitvid", "Midway T-Unit Video") DEFINE_DEVICE_TYPE(MIDWUNIT_VIDEO, midwunit_video_device, "wunitvid", "Midway W-Unit Video") DEFINE_DEVICE_TYPE(MIDXUNIT_VIDEO, midxunit_video_device, "xunitvid", "Midway X-Unit Video") /* compile-time options */ #define LOG_DMA 0 /* DMAs are logged if the 'L' key is pressed */ midtunit_video_device::midtunit_video_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock) : device_t(mconfig, type, tag, owner, clock) , m_maincpu(*this, finder_base::DUMMY_TAG) , m_palette(*this, finder_base::DUMMY_TAG) , m_gfxrom(*this, finder_base::DUMMY_TAG) #if DEBUG_MIDTUNIT_BLITTER , m_debug_palette(*this, "debugpalette") #endif { } midtunit_video_device::midtunit_video_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : midtunit_video_device(mconfig, MIDTUNIT_VIDEO, tag, owner, clock) { } midwunit_video_device::midwunit_video_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock) : midtunit_video_device(mconfig, type, tag, owner, clock) { } midwunit_video_device::midwunit_video_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : midtunit_video_device(mconfig, MIDWUNIT_VIDEO, tag, owner, clock) { } midxunit_video_device::midxunit_video_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : midwunit_video_device(mconfig, MIDXUNIT_VIDEO, tag, owner, clock) { } /************************************* * * Debugger commands * *************************************/ void midtunit_video_device::debug_init() { if (machine().debug_flags & DEBUG_FLAG_ENABLED) { using namespace std::placeholders; machine().debugger().console().register_command("midblit", CMDFLAG_CUSTOM_HELP, 1, 4, std::bind(&midtunit_video_device::debug_commands, this, _1)); } } void midtunit_video_device::debug_commands(const std::vector ¶ms) { if (params.size() < 1) return; if (params[0] == "pngdma") debug_png_dma_command(params); else debug_help_command(params); } void midtunit_video_device::debug_help_command(const std::vector ¶ms) { debugger_console &con = machine().debugger().console(); con.printf("Available Midway Blitter commands:\n"); con.printf(" midblit pngdma,[,][,] -- Enable or disable dumping of DMA-drawn sprite PNGs to , with or without JSON metadata\n"); con.printf(" midblit help -- this list\n"); } void midtunit_video_device::debug_png_dma_command(const std::vector ¶ms) { debugger_console &con = machine().debugger().console(); if (params.size() < 2) { con.printf("Error: not enough parameters for midblit pngdma command\n"); return; } if (params.size() > 4) { con.printf("Error: too many parameters for midblit pngdma command\n"); return; } bool old_state = m_log_png; bool new_state = false; if (!machine().debugger().commands().validate_boolean_parameter(params[1], new_state)) return; if (!new_state) { if (params.size() > 2) { con.printf("Error: too many parameters for midblit pngdma command\n"); return; } m_log_png = false; return; } if (params.size() < 3) { con.printf("Error: not enough parameters for midblit pngdma command\n"); return; } if (params[2].empty() || params[2].length() > 2047) { con.printf("Error: invalid path parameter for midblit pngdma command\n"); return; } strncpy(m_log_path, params[2].c_str(), 2047); if (params.size() == 4) { if (!machine().debugger().commands().validate_boolean_parameter(params[3], m_log_json)) return; } m_log_png = new_state; if (!old_state && new_state) { m_logged_rom = make_unique_clear(0x4000000); } else if (old_state && !new_state) { m_logged_rom.reset(); } } /************************************* * * Video startup * *************************************/ void midtunit_video_device::device_start() { debug_init(); /* allocate memory */ m_local_videoram = std::make_unique(0x100000/2); #if DEBUG_MIDTUNIT_BLITTER m_debug_videoram = std::make_unique(0x100000/2); #endif m_logged_rom.reset(); m_log_png = false; m_dma_timer = timer_alloc(FUNC(midtunit_video_device::dma_done), this); /* reset all the globals */ m_gfxbank_offset[0] = 0x000000; m_gfxbank_offset[1] = 0x400000; memset(m_dma_register, 0, sizeof(m_dma_register)); memset(&m_dma_state, 0, sizeof(dma_state)); m_dma_state.gfxrom = m_gfxrom->base(); /* register for state saving */ save_item(NAME(m_midtunit_control)); save_item(NAME(m_gfxbank_offset)); save_pointer(NAME(m_local_videoram), 0x100000/sizeof(m_local_videoram[0])); save_item(NAME(m_videobank_select)); save_item(NAME(m_dma_register)); INIT_TEMPLATED_DMA_DRAW_GROUP(m_dma_draw_skip_scale, true, true); INIT_TEMPLATED_DMA_DRAW_GROUP(m_dma_draw_noskip_scale, false, true); INIT_TEMPLATED_DMA_DRAW_GROUP(m_dma_draw_skip_noscale, true, false); INIT_TEMPLATED_DMA_DRAW_GROUP(m_dma_draw_noskip_noscale, false, false); m_gfx_rom_large = false; } void midwunit_video_device::device_start() { midtunit_video_device::device_start(); m_gfx_rom_large = true; } void midxunit_video_device::device_start() { midtunit_video_device::device_start(); m_gfx_rom_large = true; m_videobank_select = 1; } /************************************* * * Banked graphics ROM access * *************************************/ uint16_t midtunit_video_device::midtunit_gfxrom_r(offs_t offset) { uint8_t *base = m_gfxrom->base() + m_gfxbank_offset[(offset >> 21) & 1]; offset = (offset & 0x01fffff) * 2; return base[offset] | (base[offset + 1] << 8); } uint16_t midwunit_video_device::midwunit_gfxrom_r(offs_t offset) { uint8_t *base = m_gfxrom->base() + m_gfxbank_offset[0]; offset *= 2; return base[offset] | (base[offset + 1] << 8); } /************************************* * * Video RAM read/write * *************************************/ void midtunit_video_device::midtunit_vram_w(offs_t offset, uint16_t data, uint16_t mem_mask) { offset *= 2; if (m_videobank_select) { if (ACCESSING_BITS_0_7) m_local_videoram[offset] = (data & 0xff) | ((m_dma_register[DMA_PALETTE] & 0xff) << 8); if (ACCESSING_BITS_8_15) m_local_videoram[offset + 1] = ((data >> 8) & 0xff) | (m_dma_register[DMA_PALETTE] & 0xff00); } else { if (ACCESSING_BITS_0_7) m_local_videoram[offset] = (m_local_videoram[offset] & 0xff) | ((data & 0xff) << 8); if (ACCESSING_BITS_8_15) m_local_videoram[offset + 1] = (m_local_videoram[offset + 1] & 0xff) | (data & 0xff00); } } void midtunit_video_device::midtunit_vram_data_w(offs_t offset, uint16_t data, uint16_t mem_mask) { offset *= 2; if (ACCESSING_BITS_0_7) m_local_videoram[offset] = (data & 0xff) | ((m_dma_register[DMA_PALETTE] & 0xff) << 8); if (ACCESSING_BITS_8_15) m_local_videoram[offset + 1] = ((data >> 8) & 0xff) | (m_dma_register[DMA_PALETTE] & 0xff00); } void midtunit_video_device::midtunit_vram_color_w(offs_t offset, uint16_t data, uint16_t mem_mask) { offset *= 2; if (ACCESSING_BITS_0_7) m_local_videoram[offset] = (m_local_videoram[offset] & 0xff) | ((data & 0xff) << 8); if (ACCESSING_BITS_8_15) m_local_videoram[offset + 1] = (m_local_videoram[offset + 1] & 0xff) | (data & 0xff00); } uint16_t midtunit_video_device::midtunit_vram_r(offs_t offset) { offset *= 2; if (m_videobank_select) return (m_local_videoram[offset] & 0x00ff) | (m_local_videoram[offset + 1] << 8); else return (m_local_videoram[offset] >> 8) | (m_local_videoram[offset + 1] & 0xff00); } uint16_t midtunit_video_device::midtunit_vram_data_r(offs_t offset) { offset *= 2; return (m_local_videoram[offset] & 0x00ff) | (m_local_videoram[offset + 1] << 8); } uint16_t midtunit_video_device::midtunit_vram_color_r(offs_t offset) { offset *= 2; return (m_local_videoram[offset] >> 8) | (m_local_videoram[offset + 1] & 0xff00); } /************************************* * * Shift register read/write * *************************************/ TMS340X0_TO_SHIFTREG_CB_MEMBER(midtunit_video_device::to_shiftreg) { memcpy(shiftreg, &m_local_videoram[address >> 3], 2 * 512 * sizeof(uint16_t)); } TMS340X0_FROM_SHIFTREG_CB_MEMBER(midtunit_video_device::from_shiftreg) { memcpy(&m_local_videoram[address >> 3], shiftreg, 2 * 512 * sizeof(uint16_t)); } /************************************* * * Control register * *************************************/ void midtunit_video_device::midtunit_control_w(offs_t offset, uint16_t data, uint16_t mem_mask) { /* other important bits: bit 2 (0x0004) is toggled periodically */ logerror("T-unit control = %04X\n", data); COMBINE_DATA(&m_midtunit_control); /* gfx bank select is bit 7 */ if (!(m_midtunit_control & 0x0080) || !m_gfx_rom_large) m_gfxbank_offset[0] = 0x000000; else m_gfxbank_offset[0] = 0x800000; /* video bank select is bit 5 */ m_videobank_select = (m_midtunit_control >> 5) & 1; } void midwunit_video_device::midwunit_control_w(offs_t offset, uint16_t data, uint16_t mem_mask) { /* other important bits: bit 2 (0x0004) is toggled periodically */ logerror("Wolf-unit control = %04X\n", data); COMBINE_DATA(&m_midtunit_control); /* gfx bank select is bits 8-9 */ m_gfxbank_offset[0] = 0x800000 * ((m_midtunit_control >> 8) & 3); /* video bank select is unknown */ m_videobank_select = (m_midtunit_control >> 11) & 1; } uint16_t midwunit_video_device::midwunit_control_r() { return m_midtunit_control; } /************************************* * * Palette handlers * *************************************/ void midxunit_video_device::midxunit_paletteram_w(offs_t offset, uint16_t data, uint16_t mem_mask) { if (!(offset & 1)) m_palette->write16(offset / 2, data, mem_mask); } uint16_t midxunit_video_device::midxunit_paletteram_r(offs_t offset) { return m_palette->read16(offset / 2); } /************************************* * * DMA drawing routines * *************************************/ /*** fast pixel extractors ***/ #if !defined(ALIGN_SHORTS) && defined(LSB_FIRST) #define EXTRACTGEN(m) ((*(uint16_t *)&base[o >> 3] >> (o & 7)) & (m)) #elif defined(powerc) #define EXTRACTGEN(m) ((__lhbrx(base, o >> 3) >> (o & 7)) & (m)) #else #define EXTRACTGEN(m) (((base[o >> 3] | (base[(o >> 3) + 1] << 8)) >> (o & 7)) & (m)) #endif /*** core blitter routine macro ***/ template void midtunit_video_device::dma_draw() { int height = m_dma_state.height << 8; uint8_t *base = m_dma_state.gfxrom; uint32_t offset = m_dma_state.offset; uint16_t pal = m_dma_state.palette; uint16_t color = pal | m_dma_state.color; int sy = m_dma_state.ypos; int iy = 0; int ty; int mask = (1 << BitsPerPixel) - 1; int xstep = Scale ? m_dma_state.xstep : 0x100; /* loop over the height */ while (iy < height) { int startskip = m_dma_state.startskip << 8; [[maybe_unused]] int endskip = m_dma_state.endskip << 8; int width = m_dma_state.width << 8; int sx = m_dma_state.xpos; int ix = 0; int tx; uint32_t o = offset; int pre, post; uint16_t *d; /* handle skipping */ if (Skip) { uint8_t value = EXTRACTGEN(0xff); o += 8; /* adjust for preskip */ pre = (value & 0x0f) << (m_dma_state.preskip + 8); tx = pre / xstep; if (XFlip) sx = (sx - tx) & XPOSMASK; else sx = (sx + tx) & XPOSMASK; ix += tx * xstep; /* adjust for postskip */ post = ((value >> 4) & 0x0f) << (m_dma_state.postskip + 8); width -= post; endskip -= post; } /* handle Y clipping */ if (sy < m_dma_state.topclip || sy > m_dma_state.botclip) goto clipy; /* handle start skip */ if (ix < startskip) { tx = ((startskip - ix) / xstep) * xstep; ix += tx; o += (tx >> 8) * BitsPerPixel; } /* handle end skip */ if ((width >> 8) > m_dma_state.width - m_dma_state.endskip) width = (m_dma_state.width - m_dma_state.endskip) << 8; /* determine destination pointer */ #if DEBUG_MIDTUNIT_BLITTER d = m_doing_debug_dma ? &m_debug_videoram[sy * 512] : &m_local_videoram[sy * 512]; #else d = &m_local_videoram[sy * 512]; #endif /* loop until we draw the entire width */ while (ix < width) { /* only process if not clipped */ if (sx >= m_dma_state.leftclip && sx <= m_dma_state.rightclip) { /* special case similar handling of zero/non-zero */ if (Zero == NonZero) { if (Zero == PIXEL_COLOR) d[sx] = color; else if (Zero == PIXEL_COPY) d[sx] = (EXTRACTGEN(mask)) | pal; } /* otherwise, read the pixel and look */ else { int pixel = (EXTRACTGEN(mask)); /* non-zero pixel case */ if (pixel) { if (NonZero == PIXEL_COLOR) d[sx] = color; else if (NonZero == PIXEL_COPY) d[sx] = pixel | pal; } /* zero pixel case */ else { if (Zero == PIXEL_COLOR) d[sx] = color; else if (Zero == PIXEL_COPY) d[sx] = pal; } } } /* update pointers */ if (XFlip) sx = (sx - 1) & XPOSMASK; else sx = (sx + 1) & XPOSMASK; /* advance to the next pixel */ if (!Scale) { ix += 0x100; o += BitsPerPixel; } else { tx = ix >> 8; ix += xstep; tx = (ix >> 8) - tx; o += BitsPerPixel * tx; } } clipy: /* advance to the next row */ if (m_dma_state.yflip) sy = (sy - 1) & YPOSMASK; else sy = (sy + 1) & YPOSMASK; if (!Scale) { iy += 0x100; width = m_dma_state.width; if (Skip) { offset += 8; width -= (pre + post) >> 8; if (width > 0) offset += width * BitsPerPixel; } else { offset += width * BitsPerPixel; } } else { ty = iy >> 8; iy += m_dma_state.ystep; ty = (iy >> 8) - ty; if (!Skip) { offset += ty * m_dma_state.width * BitsPerPixel; } else if (ty--) { o = offset + 8; width = m_dma_state.width - ((pre + post) >> 8); if (width > 0) o += width * BitsPerPixel; while (ty--) { uint8_t value = EXTRACTGEN(0xff); o += 8; pre = (value & 0x0f) << m_dma_state.preskip; post = ((value >> 4) & 0x0f) << m_dma_state.postskip; width = m_dma_state.width - pre - post; if (width > 0) o += width * BitsPerPixel; } offset = o; } } } } DEFINE_TEMPLATED_DMA_DRAW_GROUP(true, true); DEFINE_TEMPLATED_DMA_DRAW_GROUP(false, true); DEFINE_TEMPLATED_DMA_DRAW_GROUP(true, false); DEFINE_TEMPLATED_DMA_DRAW_GROUP(false, false); /************************************* * * DMA finished callback * *************************************/ TIMER_CALLBACK_MEMBER(midtunit_video_device::dma_done) { m_dma_register[DMA_COMMAND] &= ~0x8000; /* tell the cpu we're done */ m_maincpu->set_input_line(0, ASSERT_LINE); } /************************************* * * DMA reader * *************************************/ uint16_t midtunit_video_device::midtunit_dma_r(offs_t offset) { /* rmpgwt sometimes reads register 0, expecting it to return the */ /* current DMA status; thus we map register 0 to register 1 */ /* openice does it as well */ if (offset == 0) offset = 1; return m_dma_register[offset]; } /************************************* * * DMA write handler * *************************************/ /* * DMA registers * ------------------ * * Register | Bit | Use * ----------+-FEDCBA9876543210-+------------ * 0 | xxxxxxxx-------- | pixels to drop at the start of each row * | --------xxxxxxxx | pixels to drop at the end of each row * 1 | x--------------- | trigger write (or clear if zero) * | -421------------ | image bpp (0=8) * | ----84---------- | post skip size = (1<> 5) & 1; int pixels = 0; /* blend with the current register contents */ int regnum = register_map[regbank][offset]; COMBINE_DATA(&m_dma_register[regnum]); /* only writes to DMA_COMMAND actually cause actions */ if (regnum != DMA_COMMAND) return; /* high bit triggers action */ int command = m_dma_register[DMA_COMMAND]; m_maincpu->set_input_line(0, CLEAR_LINE); if (!(command & 0x8000)) return; g_profiler.start(PROFILER_USER1); /* determine bpp */ int bpp = (command >> 12) & 7; /* fill in the basic data */ m_dma_state.xpos = m_dma_register[DMA_XSTART] & XPOSMASK; m_dma_state.ypos = m_dma_register[DMA_YSTART] & YPOSMASK; m_dma_state.width = m_dma_register[DMA_WIDTH] & 0x3ff; m_dma_state.height = m_dma_register[DMA_HEIGHT] & 0x3ff; m_dma_state.palette = m_dma_register[DMA_PALETTE] & 0x7f00; m_dma_state.color = m_dma_register[DMA_COLOR] & 0xff; /* fill in the rev 2 data */ m_dma_state.yflip = (command & 0x20) >> 5; m_dma_state.preskip = (command >> 8) & 3; m_dma_state.postskip = (command >> 10) & 3; m_dma_state.xstep = m_dma_register[DMA_SCALE_X] ? m_dma_register[DMA_SCALE_X] : 0x100; m_dma_state.ystep = m_dma_register[DMA_SCALE_Y] ? m_dma_register[DMA_SCALE_Y] : 0x100; /* clip the clippers */ m_dma_state.topclip = m_dma_register[DMA_TOPCLIP] & 0x1ff; m_dma_state.botclip = m_dma_register[DMA_BOTCLIP] & 0x1ff; m_dma_state.leftclip = m_dma_register[DMA_LEFTCLIP] & 0x3ff; m_dma_state.rightclip = m_dma_register[DMA_RIGHTCLIP] & 0x3ff; /* determine the offset */ uint32_t gfxoffset = m_dma_register[DMA_OFFSETLO] | (m_dma_register[DMA_OFFSETHI] << 16); /* special case: drawing mode C doesn't need to know about any pixel data */ if ((command & 0x0f) == 0x0c) gfxoffset = 0; /* determine the location */ if (!m_gfx_rom_large && gfxoffset >= 0x2000000) gfxoffset -= 0x2000000; if (gfxoffset >= 0xf8000000) gfxoffset -= 0xf8000000; if (gfxoffset < 0x10000000) m_dma_state.offset = gfxoffset; else { logerror("DMA source out of range: %08X\n", gfxoffset); goto skipdma; } if (LOG_DMA || DEBUG_MIDTUNIT_BLITTER) { if (machine().input().code_pressed(KEYCODE_COLON)) { logerror("DMA command %04X: (bpp=%d skip=%d xflip=%d yflip=%d preskip=%d postskip=%d)\n", command, (command >> 12) & 7, (command >> 7) & 1, (command >> 4) & 1, (command >> 5) & 1, (command >> 8) & 3, (command >> 10) & 3); logerror(" offset=%08X pos=(%d,%d) w=%d h=%d clip=(%d,%d)-(%d,%d)\n", gfxoffset, m_dma_state.xpos, m_dma_state.ypos, m_dma_state.width, m_dma_state.height, m_dma_state.leftclip, m_dma_state.topclip, m_dma_state.rightclip, m_dma_state.botclip); logerror(" palette=%04X color=%04X lskip=%02X rskip=%02X xstep=%04X ystep=%04X test=%04X config=%04X\n", m_dma_register[DMA_PALETTE], m_dma_register[DMA_COLOR], m_dma_register[DMA_LRSKIP] >> 8, m_dma_register[DMA_LRSKIP] & 0xff, m_dma_register[DMA_SCALE_X], m_dma_register[DMA_SCALE_Y], m_dma_register[DMA_UNKNOWN_E], m_dma_register[DMA_CONFIG]); logerror("----\n"); } } /* there seems to be two types of behavior for the DMA chip */ /* for MK1 and MK2, the upper byte of the LRSKIP is the */ /* starting skip value, and the lower byte is the ending */ /* skip value; for the NBA Jam, Hangtime, and Open Ice, the */ /* full word seems to be the starting skip value. */ if (command & 0x40) { m_dma_state.startskip = m_dma_register[DMA_LRSKIP] & 0xff; m_dma_state.endskip = m_dma_register[DMA_LRSKIP] >> 8; } else { m_dma_state.startskip = 0; m_dma_state.endskip = m_dma_register[DMA_LRSKIP]; } if (m_log_png) { if (command & 0x80) { log_bitmap(command, bpp ? bpp : 8, true); } else { log_bitmap(command, bpp ? bpp : 8, false); } } /* then draw */ if (m_dma_state.xstep == 0x100 && m_dma_state.ystep == 0x100) { if (command & 0x80) ((this)->*(m_dma_draw_skip_noscale[(command & 0x1f)*8 + bpp]))(); else ((this)->*(m_dma_draw_noskip_noscale[(command & 0x1f)*8 + bpp]))(); pixels = m_dma_state.width * m_dma_state.height; } else { if (command & 0x80) ((this)->*(m_dma_draw_skip_scale[(command & 0x1f)*8 + bpp]))(); else ((this)->*(m_dma_draw_noskip_scale[(command & 0x1f)*8 + bpp]))(); if (m_dma_state.xstep && m_dma_state.ystep) pixels = ((m_dma_state.width << 8) / m_dma_state.xstep) * ((m_dma_state.height << 8) / m_dma_state.ystep); else pixels = 0; } /* signal we're done */ skipdma: m_dma_timer->adjust(attotime::from_nsec(41 * pixels)); g_profiler.stop(); } /************************************* * * Core refresh routine * *************************************/ TMS340X0_SCANLINE_IND16_CB_MEMBER(midtunit_video_device::scanline_update) { uint16_t const *const src = &m_local_videoram[(params->rowaddr << 9) & 0x3fe00]; uint16_t *const dest = &bitmap.pix(scanline); int coladdr = params->coladdr << 1; /* copy the non-blanked portions of this scanline */ for (int x = params->heblnk; x < params->hsblnk; x++) dest[x] = src[coladdr++ & 0x1ff] & 0x7fff; } TMS340X0_SCANLINE_IND16_CB_MEMBER(midxunit_video_device::scanline_update) { uint32_t fulladdr = ((params->rowaddr << 16) | params->coladdr) >> 3; uint16_t const *const src = &m_local_videoram[fulladdr & 0x3fe00]; uint16_t *const dest = &bitmap.pix(scanline); /* copy the non-blanked portions of this scanline */ for (int x = params->heblnk; x < params->hsblnk; x++) dest[x] = src[fulladdr++ & 0x1ff] & 0x7fff; } void midtunit_video_device::log_bitmap(int command, int bpp, bool Skip) { const uint32_t raw_offset = m_dma_register[DMA_OFFSETLO] | (m_dma_register[DMA_OFFSETHI] << 16); if (m_logged_rom[raw_offset >> 6] & (1ULL << (raw_offset & 0x3f))) return; int Zero = PIXEL_SKIP; int NonZero = PIXEL_SKIP; switch (command & 0xf) { case 1: Zero = PIXEL_COPY; NonZero = PIXEL_SKIP; break; case 2: Zero = PIXEL_SKIP; NonZero = PIXEL_COPY; break; case 3: Zero = PIXEL_COPY; NonZero = PIXEL_COPY; break; case 4: Zero = PIXEL_COLOR; NonZero = PIXEL_SKIP; break; case 5: Zero = PIXEL_COLOR; NonZero = PIXEL_SKIP; break; case 6: Zero = PIXEL_COLOR; NonZero = PIXEL_COPY; break; case 7: Zero = PIXEL_COLOR; NonZero = PIXEL_COPY; break; case 8: Zero = PIXEL_SKIP; NonZero = PIXEL_COLOR; break; case 9: Zero = PIXEL_COPY; NonZero = PIXEL_COLOR; break; case 10: Zero = PIXEL_SKIP; NonZero = PIXEL_COLOR; break; case 11: Zero = PIXEL_COPY; NonZero = PIXEL_COLOR; break; case 12: Zero = PIXEL_COLOR; NonZero = PIXEL_COLOR; break; case 13: Zero = PIXEL_COLOR; NonZero = PIXEL_COLOR; break; case 14: Zero = PIXEL_COLOR; NonZero = PIXEL_COLOR; break; case 15: Zero = PIXEL_COLOR; NonZero = PIXEL_COLOR; break; default: return; } emu_file file(m_log_path, OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS); char name_buf[256]; snprintf(name_buf, 255, "0x%08x.png", raw_offset); auto const filerr = file.open(name_buf); if (filerr) { return; } m_logged_rom[raw_offset >> 6] |= 1ULL << (raw_offset & 0x3f); m_log_bitmap.allocate(m_dma_state.width, m_dma_state.height); m_log_bitmap.fill(0); uint8_t *base = m_dma_state.gfxrom; uint32_t offset = m_dma_state.offset; uint16_t pal = m_dma_state.palette; uint16_t color = pal | m_dma_state.color; int mask = (1 << bpp) - 1; /* loop over the height */ for (int y = 0; y < m_dma_state.height; y++) { int startskip = m_dma_state.startskip; [[maybe_unused]] int endskip = m_dma_state.endskip; int width = m_dma_state.width; int ix = 0; int tx; uint32_t o = offset; int pre = 0, post = 0; /* handle skipping */ if (Skip) { uint8_t value = EXTRACTGEN(0xff); o += 8; /* adjust for preskip */ pre = (value & 0x0f) << m_dma_state.preskip; tx = pre; ix += tx; /* adjust for postskip */ post = ((value >> 4) & 0x0f) << m_dma_state.postskip; width -= post; endskip -= post; } /* handle start skip */ if (ix < startskip) { tx = (startskip - ix); ix += tx; o += tx * bpp; } /* handle end skip */ if (width > m_dma_state.width - m_dma_state.endskip) width = m_dma_state.width - m_dma_state.endskip; bitmap_rgb32::pixel_t *d = &m_log_bitmap.pix(y, ix); /* determine destination pointer */ /* loop until we draw the entire width */ while (ix < width) { /* special case similar handling of zero/non-zero */ if (Zero == NonZero) { if (Zero == PIXEL_COLOR) *d = m_palette->palette()->entry_list_raw()[color]; else if (Zero == PIXEL_COPY) *d = m_palette->palette()->entry_list_raw()[(EXTRACTGEN(mask)) | pal]; } /* otherwise, read the pixel and look */ else { int pixel = (EXTRACTGEN(mask)); /* non-zero pixel case */ if (pixel) { if (NonZero == PIXEL_COLOR) *d = m_palette->palette()->entry_list_raw()[color]; else if (NonZero == PIXEL_COPY) *d = m_palette->palette()->entry_list_raw()[pixel | pal]; } /* zero pixel case */ else { if (Zero == PIXEL_COLOR) *d = m_palette->palette()->entry_list_raw()[color]; else if (Zero == PIXEL_COPY) *d = m_palette->palette()->entry_list_raw()[pal]; } } /* advance to the next pixel */ ix++; d++; o += bpp; } /* advance to the next row */ width = m_dma_state.width; if (Skip) { offset += 8; width -= pre + post; if (width > 0) offset += width * bpp; } else { offset += width * bpp; } } util::png_write_bitmap(file, nullptr, m_log_bitmap, 0, nullptr); #if 0 if (m_log_json) { char hex_buf[11]; rapidjson::StringBuffer s; rapidjson::PrettyWriter writer(s); emu_file json(m_log_path, OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS); snprintf(name_buf, 255, "0x%08x.json", raw_offset); auto const jsonerr = json.open(name_buf); if (jsonerr) { return; } writer.StartObject(); writer.Key("DMAState"); writer.StartObject(); sprintf(hex_buf, "0x%08x", raw_offset); writer.Key("MemoryAddress"); writer.String(hex_buf); sprintf(hex_buf, "0x%08x", m_dma_state.offset >> 3); writer.Key("ROMSourceOffsetByte"); writer.String(hex_buf); writer.Key("ROMSourceOffsetBit"); writer.Int(m_dma_state.offset & 7); writer.Key("Size"); writer.StartArray(); writer.Int(m_dma_state.width); writer.Int(m_dma_state.height); writer.EndArray(); writer.Key("BitsPerPixel"); writer.Uint(bpp); writer.Key("PaletteBank"); writer.Uint(m_dma_state.palette >> 8); writer.Key("FGColor"); writer.Uint(m_dma_state.color); writer.Key("YFlip"); writer.Bool(m_dma_state.yflip ? true : false); writer.Key("PreSkipScale"); writer.Uint(m_dma_state.preskip); writer.Key("PostSkipScale"); writer.Uint(m_dma_state.postskip); writer.Key("RowSkipBits"); writer.Int(m_dma_state.rowbits); writer.Key("StartPixelsToSkip"); writer.Int(m_dma_state.startskip); writer.Key("EndPixelsToSkip"); writer.Int(m_dma_state.endskip); writer.EndObject(); writer.EndObject(); json.puts(s.GetString()); json.close(); } #endif }