diff options
-rw-r--r-- | docs/config.txt | 14 | ||||
-rw-r--r-- | src/emu/emuopts.c | 3 | ||||
-rw-r--r-- | src/emu/emuopts.h | 1 | ||||
-rw-r--r-- | src/emu/video.c | 187 | ||||
-rw-r--r-- | src/lib/util/bitmap.c | 29 | ||||
-rw-r--r-- | src/lib/util/bitmap.h | 2 |
6 files changed, 232 insertions, 4 deletions
diff --git a/docs/config.txt b/docs/config.txt index a3ca21a1fac..19614fb24f3 100644 --- a/docs/config.txt +++ b/docs/config.txt @@ -514,9 +514,21 @@ Core state/playback options -wavwrite <filename> Writes the final mixer output to the given <filename> in WAV format, - producing an audio recording of the game session. The default is + producing an audio recording of the game session. The default is NULL (no recording). +-[no]burnin + + Tracks brightness of the screen during play and at the end of + emulation generates a PNG that can be used to simulate burn-in + effects on other games. The resulting PNG is created such that the + least used-areas of the screen are fully white (since burned-in areas + are darker, all other areas of the screen must be lightened a touch). + The intention is that this PNG can be loaded via an artwork file with + a low alpha (e.g, 0.1-0.2 seems to work well) and blended over the + entire screen. The PNG files are saved in the snap directory under + the gamename/burnin-<screen.name>.png. The default is OFF (-noburnin). + Core performance options diff --git a/src/emu/emuopts.c b/src/emu/emuopts.c index 24d134044dc..90179d8bc1f 100644 --- a/src/emu/emuopts.c +++ b/src/emu/emuopts.c @@ -43,7 +43,7 @@ const options_entry mame_core_options[] = { "inipath", ".;ini", 0, "path to ini files" }, { "fontpath", ".", 0, "path to font files" }, { "cheatpath", "cheat", 0, "path to cheat files" }, - { "crsshairpath", "crsshair", 0, "path to crosshair files" }, + { "crosshairpath", "crosshair", 0, "path to crosshair files" }, /* output directory options */ { NULL, NULL, OPTION_HEADER, "CORE OUTPUT DIRECTORY OPTIONS" }, @@ -68,6 +68,7 @@ const options_entry mame_core_options[] = { "snapname", "%g/%i", 0, "override of the default snapshot/movie naming; %g == gamename, %i == index" }, { "snapsize", "auto", 0, "specify snapshot/movie resolution (<width>x<height>) or 'auto' to use minimal size " }, { "snapview", "internal", 0, "specify snapshot/movie view or 'internal' to use internal pixel-aspect views" }, + { "burnin", "0", OPTION_BOOLEAN, "create burn-in snapshots for each screen" }, /* performance options */ { NULL, NULL, OPTION_HEADER, "CORE PERFORMANCE OPTIONS" }, diff --git a/src/emu/emuopts.h b/src/emu/emuopts.h index b7f57b5a7b8..03edd5e6824 100644 --- a/src/emu/emuopts.h +++ b/src/emu/emuopts.h @@ -68,6 +68,7 @@ #define OPTION_SNAPNAME "snapname" #define OPTION_SNAPSIZE "snapsize" #define OPTION_SNAPVIEW "snapview" +#define OPTION_BURNIN "burnin" /* core performance options */ #define OPTION_AUTOFRAMESKIP "autoframeskip" diff --git a/src/emu/video.c b/src/emu/video.c index de27d64c592..9c400875d9c 100644 --- a/src/emu/video.c +++ b/src/emu/video.c @@ -59,6 +59,7 @@ struct _screen_state /* textures and bitmaps */ render_texture * texture[2]; /* 2x textures for the screen bitmap */ bitmap_t * bitmap[2]; /* 2x bitmaps for rendering */ + bitmap_t * burnin; /* burn-in bitmap */ UINT8 curbitmap; /* current bitmap index */ UINT8 curtexture; /* current texture index */ INT32 texture_format; /* texture format of bitmap for this screen */ @@ -203,6 +204,10 @@ static file_error mame_fopen_next(running_machine *machine, const char *pathopti static void video_mng_record_frame(running_machine *machine); static void video_avi_record_frame(running_machine *machine); +/* burn-in generation */ +static void video_update_burnin(running_machine *machine); +static void video_finalize_burnin(const device_config *screen); + /* software rendering */ static void rgb888_draw_primitives(const render_primitive *primlist, void *dstdata, UINT32 width, UINT32 height, UINT32 pitch); @@ -363,7 +368,7 @@ void video_init(running_machine *machine) /* extract snap resolution if present */ if (sscanf(options_get_string(mame_options(), OPTION_SNAPSIZE), "%dx%d", &global.snap_width, &global.snap_height) != 2) global.snap_width = global.snap_height = 0; - + /* start recording movie if specified */ filename = options_get_string(mame_options(), OPTION_MNGWRITE); if (filename[0] != 0) @@ -1197,6 +1202,18 @@ static DEVICE_START( video_screen ) if (screen->machine->config->video_attributes & VIDEO_UPDATE_SCANLINE) timer_adjust_oneshot(state->scanline_timer, video_screen_get_time_until_pos(screen, 0, 0), 0); + /* create burn-in bitmap */ + if (options_get_int(mame_options(), OPTION_BURNIN) > 0) + { + int width, height; + if (sscanf(options_get_string(mame_options(), OPTION_SNAPSIZE), "%dx%d", &width, &height) != 2 || width == 0 || height == 0) + width = height = 300; + state->burnin = bitmap_alloc(width, height, BITMAP_FORMAT_INDEXED64); + if (state->burnin == NULL) + fatalerror("Error allocating burn-in bitmap for screen at (%dx%d)\n", width, height); + bitmap_fill(state->burnin, NULL, 0); + } + state_save_register_device_item(screen, 0, state->width); state_save_register_device_item(screen, 0, state->height); state_save_register_device_item(screen, 0, state->visarea.min_x); @@ -1248,6 +1265,11 @@ static DEVICE_STOP( video_screen ) bitmap_free(state->bitmap[0]); if (state->bitmap[1] != NULL) bitmap_free(state->bitmap[1]); + if (state->burnin != NULL) + { + video_finalize_burnin(screen); + bitmap_free(state->burnin); + } } @@ -1523,11 +1545,12 @@ static int finish_screen_updates(running_machine *machine) state->changed = FALSE; } - /* update our movie recording state */ + /* update our movie recording and burn-in state */ if (!mame_is_paused(machine)) { video_mng_record_frame(machine); video_avi_record_frame(machine); + video_update_burnin(machine); } /* draw any crosshairs */ @@ -2587,6 +2610,166 @@ void video_avi_add_sound(running_machine *machine, const INT16 *sound, int numsa /*************************************************************************** + BURN-IN GENERATION +***************************************************************************/ + +/*------------------------------------------------- + video_update_burnin - update the burnin bitmap + for all screens +-------------------------------------------------*/ + +#undef rand +static void video_update_burnin(running_machine *machine) +{ + const device_config *screen; + + /* iterate over screens and update the burnin for the ones that care */ + for (screen = video_screen_first(machine->config); screen != NULL; screen = video_screen_next(screen)) + { + screen_state *state = get_safe_token(screen); + if (state->burnin != NULL) + { + bitmap_t *srcbitmap = state->bitmap[state->curtexture]; + int srcwidth = srcbitmap->width; + int srcheight = srcbitmap->height; + int dstwidth = state->burnin->width; + int dstheight = state->burnin->height; + int xstep = (srcwidth << 16) / dstwidth; + int ystep = (srcheight << 16) / dstheight; + int xstart = (rand() % 32767) * xstep / 32767; + int ystart = (rand() % 32767) * ystep / 32767; + int srcx, srcy; + int x, y; + + /* iterate over rows in the destination */ + for (y = 0, srcy = ystart; y < dstheight; y++, srcy += ystep) + { + UINT64 *dst = BITMAP_ADDR64(state->burnin, y, 0); + + /* handle the 16-bit palettized case */ + if (srcbitmap->format == BITMAP_FORMAT_INDEXED16) + { + const UINT16 *src = BITMAP_ADDR16(srcbitmap, srcy >> 16, 0); + const rgb_t *palette = palette_entry_list_adjusted(machine->palette); + for (x = 0, srcx = xstart; x < dstwidth; x++, srcx += xstep) + { + rgb_t pixel = palette[src[srcx >> 16]]; + dst[x] += RGB_GREEN(pixel) + RGB_RED(pixel) + RGB_BLUE(pixel); + } + } + + /* handle the 15-bit RGB case */ + else if (srcbitmap->format == BITMAP_FORMAT_RGB15) + { + const UINT16 *src = BITMAP_ADDR16(srcbitmap, srcy >> 16, 0); + for (x = 0, srcx = xstart; x < dstwidth; x++, srcx += xstep) + { + rgb15_t pixel = src[srcx >> 16]; + dst[x] += ((pixel >> 10) & 0x1f) + ((pixel >> 5) & 0x1f) + ((pixel >> 0) & 0x1f); + } + } + + /* handle the 32-bit RGB case */ + else if (srcbitmap->format == BITMAP_FORMAT_RGB32) + { + const UINT32 *src = BITMAP_ADDR32(srcbitmap, srcy >> 16, 0); + for (x = 0, srcx = xstart; x < dstwidth; x++, srcx += xstep) + { + rgb_t pixel = src[srcx >> 16]; + dst[x] += RGB_GREEN(pixel) + RGB_RED(pixel) + RGB_BLUE(pixel); + } + } + } + } + } +} + + +/*------------------------------------------------- + video_finalize_burnin - finalize the burnin + bitmaps for all screens +-------------------------------------------------*/ + +static void video_finalize_burnin(const device_config *screen) +{ + screen_state *state = get_safe_token(screen); + if (state->burnin != NULL) + { + astring *fname = astring_alloc(); + rectangle scaledvis; + bitmap_t *finalmap; + UINT64 minval = ~(UINT64)0; + UINT64 maxval = 0; + file_error filerr; + mame_file *file; + int x, y; + + /* compute the scaled visible region */ + scaledvis.min_x = state->visarea.min_x * state->burnin->width / state->width; + scaledvis.max_x = state->visarea.max_x * state->burnin->width / state->width; + scaledvis.min_y = state->visarea.min_y * state->burnin->height / state->height; + scaledvis.max_y = state->visarea.max_y * state->burnin->height / state->height; + + /* wrap a bitmap around the subregion we care about */ + finalmap = bitmap_alloc(scaledvis.max_x + 1 - scaledvis.min_x, + scaledvis.max_y + 1 - scaledvis.min_y, + BITMAP_FORMAT_ARGB32); + + /* find the maximum value */ + for (y = 0; y < finalmap->height; y++) + { + UINT64 *src = BITMAP_ADDR64(state->burnin, y, 0); + for (x = 0; x < finalmap->width; x++) + { + minval = MIN(minval, src[x]); + maxval = MAX(maxval, src[x]); + } + } + + /* now normalize and convert to RGB */ + for (y = 0; y < finalmap->height; y++) + { + UINT64 *src = BITMAP_ADDR64(state->burnin, y, 0); + UINT32 *dst = BITMAP_ADDR32(finalmap, y, 0); + for (x = 0; x < finalmap->width; x++) + { + int brightness = (UINT64)(maxval - src[x]) * 255 / (maxval - minval); + dst[x] = MAKE_ARGB(0xff, brightness, brightness, brightness); + } + } + + /* write the final PNG */ + + /* compute the name and create the file */ + astring_printf(fname, "%s" PATH_SEPARATOR "burnin-%s.png", screen->machine->basename, screen->tag); + filerr = mame_fopen(SEARCHPATH_SCREENSHOT, astring_c(fname), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS, &file); + if (filerr == FILERR_NONE) + { + png_info pnginfo = { 0 }; + png_error pngerr; + char text[256]; + + /* add two text entries describing the image */ + sprintf(text, APPNAME " %s", build_version); + png_add_text(&pnginfo, "Software", text); + sprintf(text, "%s %s", screen->machine->gamedrv->manufacturer, screen->machine->gamedrv->description); + png_add_text(&pnginfo, "System", text); + + /* now do the actual work */ + pngerr = png_write_bitmap(mame_core_file(file), &pnginfo, finalmap, 0, NULL); + + /* free any data allocated */ + png_free(&pnginfo); + mame_fclose(file); + } + astring_free(fname); + bitmap_free(finalmap); + } +} + + + +/*************************************************************************** CONFIGURATION HELPERS ***************************************************************************/ diff --git a/src/lib/util/bitmap.c b/src/lib/util/bitmap.c index 5ffe709654d..32bddd397e8 100644 --- a/src/lib/util/bitmap.c +++ b/src/lib/util/bitmap.c @@ -243,6 +243,32 @@ void bitmap_fill(bitmap_t *dest, const rectangle *cliprect, UINT32 color) } } break; + + case 64: + /* 64bpp can use memset if the bytes are equal */ + if ((UINT8)(color >> 8) == (UINT8)color && (UINT16)(color >> 16) == (UINT16)color) + { + for (y = fill.min_y; y <= fill.max_y; y++) + memset(BITMAP_ADDR64(dest, y, fill.min_x), (UINT8)color, (fill.max_x + 1 - fill.min_x) * 4); + } + else + { + UINT64 *destrow, *destrow0; + + /* Fill the first line the hard way */ + destrow = BITMAP_ADDR64(dest, fill.min_y, 0); + for (x = fill.min_x; x <= fill.max_x; x++) + destrow[x] = (UINT64)color; + + /* For the other lines, just copy the first one */ + destrow0 = BITMAP_ADDR64(dest, fill.min_y, fill.min_x); + for (y = fill.min_y + 1; y <= fill.max_y; y++) + { + destrow = BITMAP_ADDR64(dest, y, fill.min_x); + memcpy(destrow, destrow0, (fill.max_x + 1 - fill.min_x) * 4); + } + } + break; } } @@ -274,6 +300,9 @@ int bitmap_format_to_bpp(bitmap_format format) case BITMAP_FORMAT_RGB32: case BITMAP_FORMAT_ARGB32: return 32; + + case BITMAP_FORMAT_INDEXED64: + return 64; default: break; diff --git a/src/lib/util/bitmap.h b/src/lib/util/bitmap.h index 95c4e4476ef..ea2fd6389d5 100644 --- a/src/lib/util/bitmap.h +++ b/src/lib/util/bitmap.h @@ -29,6 +29,7 @@ enum _bitmap_format BITMAP_FORMAT_INDEXED8, /* 8bpp indexed */ BITMAP_FORMAT_INDEXED16, /* 16bpp indexed */ BITMAP_FORMAT_INDEXED32, /* 32bpp indexed */ + BITMAP_FORMAT_INDEXED64, /* 64bpp indexed */ BITMAP_FORMAT_RGB15, /* 15bpp 5-5-5 RGB */ BITMAP_FORMAT_RGB32, /* 32bpp 8-8-8 RGB */ BITMAP_FORMAT_ARGB32, /* 32bpp 8-8-8-8 ARGB */ @@ -77,6 +78,7 @@ struct _bitmap_t #define BITMAP_ADDR8(bitmap, y, x) BITMAP_ADDR(bitmap, UINT8, y, x) #define BITMAP_ADDR16(bitmap, y, x) BITMAP_ADDR(bitmap, UINT16, y, x) #define BITMAP_ADDR32(bitmap, y, x) BITMAP_ADDR(bitmap, UINT32, y, x) +#define BITMAP_ADDR64(bitmap, y, x) BITMAP_ADDR(bitmap, UINT64, y, x) |