mame/src/emu/screen.c
Miodrag Milanovic 601056b130 - Added shortname to devices in order to make ROM loading per device possible. [Miodrag Milanovic]
- Updated all devices containing ROM regions to have short names and all modern devices too
- Created new validation to check existence of short name if device contain ROM region defined
2011-02-10 19:08:37 +00:00

1076 lines
34 KiB
C

/***************************************************************************
screen.c
Core MAME screen device.
****************************************************************************
Copyright Aaron Giles
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
* Neither the name 'MAME' nor the names of its contributors may be
used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY AARON GILES ''AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL AARON GILES BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
***************************************************************************/
#include "emu.h"
#include "emuopts.h"
#include "png.h"
#include "rendutil.h"
//**************************************************************************
// DEBUGGING
//**************************************************************************
#define VERBOSE (0)
#define LOG_PARTIAL_UPDATES(x) do { if (VERBOSE) logerror x; } while (0)
//**************************************************************************
// GLOBAL VARIABLES
//**************************************************************************
const device_type SCREEN = screen_device_config::static_alloc_device_config;
const attotime screen_device::DEFAULT_FRAME_PERIOD(attotime::from_hz(DEFAULT_FRAME_RATE));
//**************************************************************************
// SCREEN DEVICE CONFIGURATION
//**************************************************************************
//-------------------------------------------------
// screen_device_config - constructor
//-------------------------------------------------
screen_device_config::screen_device_config(const machine_config &mconfig, const char *tag, const device_config *owner, UINT32 clock)
: device_config(mconfig, static_alloc_device_config, "Video Screen", "video", tag, owner, clock),
m_type(SCREEN_TYPE_RASTER),
m_width(0),
m_height(0),
m_oldstyle_vblank_supplied(false),
m_refresh(0),
m_vblank(0),
m_format(BITMAP_FORMAT_INVALID),
m_xoffset(0.0f),
m_yoffset(0.0f),
m_xscale(1.0f),
m_yscale(1.0f)
{
}
//-------------------------------------------------
// static_alloc_device_config - allocate a new
// configuration object
//-------------------------------------------------
device_config *screen_device_config::static_alloc_device_config(const machine_config &mconfig, const char *tag, const device_config *owner, UINT32 clock)
{
return global_alloc(screen_device_config(mconfig, tag, owner, clock));
}
//-------------------------------------------------
// alloc_device - allocate a new device object
//-------------------------------------------------
device_t *screen_device_config::alloc_device(running_machine &machine) const
{
return auto_alloc(&machine, screen_device(machine, *this));
}
//-------------------------------------------------
// static_set_format - configuration helper
// to set the bitmap format
//-------------------------------------------------
void screen_device_config::static_set_format(device_config *device, bitmap_format format)
{
screen_device_config *screen = downcast<screen_device_config *>(device);
screen->m_format = format;
}
//-------------------------------------------------
// static_set_type - configuration helper
// to set the screen type
//-------------------------------------------------
void screen_device_config::static_set_type(device_config *device, screen_type_enum type)
{
screen_device_config *screen = downcast<screen_device_config *>(device);
screen->m_type = type;
}
//-------------------------------------------------
// static_set_raw - configuration helper
// to set the raw screen parameters
//-------------------------------------------------
void screen_device_config::static_set_raw(device_config *device, UINT32 pixclock, UINT16 htotal, UINT16 hbend, UINT16 hbstart, UINT16 vtotal, UINT16 vbend, UINT16 vbstart)
{
screen_device_config *screen = downcast<screen_device_config *>(device);
screen->m_refresh = HZ_TO_ATTOSECONDS(pixclock) * htotal * vtotal;
screen->m_vblank = screen->m_refresh / vtotal * (vtotal - (vbstart - vbend));
screen->m_width = htotal;
screen->m_height = vtotal;
screen->m_visarea.min_x = hbend;
screen->m_visarea.max_x = hbstart - 1;
screen->m_visarea.min_y = vbend;
screen->m_visarea.max_y = vbstart - 1;
}
//-------------------------------------------------
// static_set_refresh - configuration helper
// to set the refresh rate
//-------------------------------------------------
void screen_device_config::static_set_refresh(device_config *device, attoseconds_t rate)
{
screen_device_config *screen = downcast<screen_device_config *>(device);
screen->m_refresh = rate;
}
//-------------------------------------------------
// static_set_vblank_time - configuration helper
// to set the VBLANK duration
//-------------------------------------------------
void screen_device_config::static_set_vblank_time(device_config *device, attoseconds_t time)
{
screen_device_config *screen = downcast<screen_device_config *>(device);
screen->m_vblank = time;
screen->m_oldstyle_vblank_supplied = true;
}
//-------------------------------------------------
// static_set_size - configuration helper to set
// the width/height of the screen
//-------------------------------------------------
void screen_device_config::static_set_size(device_config *device, UINT16 width, UINT16 height)
{
screen_device_config *screen = downcast<screen_device_config *>(device);
screen->m_width = width;
screen->m_height = height;
}
//-------------------------------------------------
// static_set_visarea - configuration helper to
// set the visible area of the screen
//-------------------------------------------------
void screen_device_config::static_set_visarea(device_config *device, INT16 minx, INT16 maxx, INT16 miny, INT16 maxy)
{
screen_device_config *screen = downcast<screen_device_config *>(device);
screen->m_visarea.min_x = minx;
screen->m_visarea.max_x = maxx;
screen->m_visarea.min_y = miny;
screen->m_visarea.max_y = maxy;
}
//-------------------------------------------------
// static_set_default_position - configuration
// helper to set the default position and scale
// factors for the screen
//-------------------------------------------------
void screen_device_config::static_set_default_position(device_config *device, double xscale, double xoffs, double yscale, double yoffs)
{
screen_device_config *screen = downcast<screen_device_config *>(device);
screen->m_xscale = xscale;
screen->m_xoffset = xoffs;
screen->m_yscale = yscale;
screen->m_yoffset = yoffs;
}
//-------------------------------------------------
// device_validity_check - verify device
// configuration
//-------------------------------------------------
bool screen_device_config::device_validity_check(const game_driver &driver) const
{
bool error = false;
// sanity check dimensions
if (m_width <= 0 || m_height <= 0)
{
mame_printf_error("%s: %s screen '%s' has invalid display dimensions\n", driver.source_file, driver.name, tag());
error = true;
}
// sanity check display area
if (m_type != SCREEN_TYPE_VECTOR)
{
if ((m_visarea.max_x < m_visarea.min_x) ||
(m_visarea.max_y < m_visarea.min_y) ||
(m_visarea.max_x >= m_width) ||
(m_visarea.max_y >= m_height))
{
mame_printf_error("%s: %s screen '%s' has an invalid display area\n", driver.source_file, driver.name, tag());
error = true;
}
// sanity check screen formats
if (m_format != BITMAP_FORMAT_INDEXED16 &&
m_format != BITMAP_FORMAT_RGB15 &&
m_format != BITMAP_FORMAT_RGB32)
{
mame_printf_error("%s: %s screen '%s' has unsupported format\n", driver.source_file, driver.name, tag());
error = true;
}
}
// check for zero frame rate
if (m_refresh == 0)
{
mame_printf_error("%s: %s screen '%s' has a zero refresh rate\n", driver.source_file, driver.name, tag());
error = true;
}
return error;
}
//**************************************************************************
// SCREEN DEVICE
//**************************************************************************
//-------------------------------------------------
// screen_device - constructor
//-------------------------------------------------
screen_device::screen_device(running_machine &_machine, const screen_device_config &config)
: device_t(_machine, config),
m_config(config),
m_container(NULL),
m_width(m_config.m_width),
m_height(m_config.m_height),
m_visarea(m_config.m_visarea),
m_burnin(NULL),
m_curbitmap(0),
m_curtexture(0),
m_texture_format(0),
m_changed(true),
m_last_partial_scan(0),
m_screen_overlay_bitmap(NULL),
m_frame_period(m_config.m_refresh),
m_scantime(1),
m_pixeltime(1),
m_vblank_period(0),
m_vblank_start_time(attotime::zero),
m_vblank_end_time(attotime::zero),
m_vblank_begin_timer(NULL),
m_vblank_end_timer(NULL),
m_scanline0_timer(NULL),
m_scanline_timer(NULL),
m_frame_number(0),
m_partial_updates_this_frame(0),
m_callback_list(NULL)
{
memset(m_texture, 0, sizeof(m_texture));
memset(m_bitmap, 0, sizeof(m_bitmap));
}
//-------------------------------------------------
// ~screen_device - destructor
//-------------------------------------------------
screen_device::~screen_device()
{
m_machine.render().texture_free(m_texture[0]);
m_machine.render().texture_free(m_texture[1]);
if (m_burnin != NULL)
finalize_burnin();
global_free(m_screen_overlay_bitmap);
}
//-------------------------------------------------
// device_start - device-specific startup
//-------------------------------------------------
void screen_device::device_start()
{
// configure the default cliparea
render_container::user_settings settings;
m_container->get_user_settings(settings);
settings.m_xoffset = m_config.m_xoffset;
settings.m_yoffset = m_config.m_yoffset;
settings.m_xscale = m_config.m_xscale;
settings.m_yscale = m_config.m_yscale;
m_container->set_user_settings(settings);
// allocate the VBLANK timers
m_vblank_begin_timer = machine->scheduler().timer_alloc(FUNC(static_vblank_begin_callback), (void *)this);
m_vblank_end_timer = machine->scheduler().timer_alloc(FUNC(static_vblank_end_callback), (void *)this);
// allocate a timer to reset partial updates
m_scanline0_timer = machine->scheduler().timer_alloc(FUNC(static_scanline0_callback), (void *)this);
// allocate a timer to generate per-scanline updates
if ((machine->config->m_video_attributes & VIDEO_UPDATE_SCANLINE) != 0)
m_scanline_timer = machine->scheduler().timer_alloc(FUNC(static_scanline_update_callback), (void *)this);
// configure the screen with the default parameters
configure(m_config.m_width, m_config.m_height, m_config.m_visarea, m_config.m_refresh);
// reset VBLANK timing
m_vblank_start_time = attotime::zero;
m_vblank_end_time = attotime(0, m_vblank_period);
// start the timer to generate per-scanline updates
if ((machine->config->m_video_attributes & VIDEO_UPDATE_SCANLINE) != 0)
m_scanline_timer->adjust(time_until_pos(0));
// create burn-in bitmap
if (options_get_int(machine->options(), OPTION_BURNIN) > 0)
{
int width, height;
if (sscanf(options_get_string(machine->options(), OPTION_SNAPSIZE), "%dx%d", &width, &height) != 2 || width == 0 || height == 0)
width = height = 300;
m_burnin = auto_alloc(machine, bitmap_t(width, height, BITMAP_FORMAT_INDEXED64));
if (m_burnin == NULL)
fatalerror("Error allocating burn-in bitmap for screen at (%dx%d)\n", width, height);
bitmap_fill(m_burnin, NULL, 0);
}
// load the effect overlay
const char *overname = options_get_string(machine->options(), OPTION_EFFECT);
if (overname != NULL && strcmp(overname, "none") != 0)
load_effect_overlay(overname);
// register items for saving
save_item(NAME(m_width));
save_item(NAME(m_height));
save_item(NAME(m_visarea.min_x));
save_item(NAME(m_visarea.min_y));
save_item(NAME(m_visarea.max_x));
save_item(NAME(m_visarea.max_y));
save_item(NAME(m_last_partial_scan));
save_item(NAME(m_frame_period));
save_item(NAME(m_scantime));
save_item(NAME(m_pixeltime));
save_item(NAME(m_vblank_period));
save_item(NAME(m_vblank_start_time));
save_item(NAME(m_vblank_end_time));
save_item(NAME(m_frame_number));
}
//-------------------------------------------------
// device_post_load - device-specific update
// after a save state is loaded
//-------------------------------------------------
void screen_device::device_post_load()
{
realloc_screen_bitmaps();
}
//-------------------------------------------------
// configure - configure screen parameters
//-------------------------------------------------
void screen_device::configure(int width, int height, const rectangle &visarea, attoseconds_t frame_period)
{
// validate arguments
assert(width > 0);
assert(height > 0);
assert(visarea.min_x >= 0);
assert(visarea.min_y >= 0);
assert(m_config.m_type == SCREEN_TYPE_VECTOR || visarea.min_x < width);
assert(m_config.m_type == SCREEN_TYPE_VECTOR || visarea.min_y < height);
assert(frame_period > 0);
// fill in the new parameters
m_width = width;
m_height = height;
m_visarea = visarea;
// reallocate bitmap if necessary
realloc_screen_bitmaps();
// compute timing parameters
m_frame_period = frame_period;
m_scantime = frame_period / height;
m_pixeltime = frame_period / (height * width);
// if there has been no VBLANK time specified in the MACHINE_DRIVER, compute it now
// from the visible area, otherwise just used the supplied value
if (m_config.m_vblank == 0 && !m_config.m_oldstyle_vblank_supplied)
m_vblank_period = m_scantime * (height - (visarea.max_y + 1 - visarea.min_y));
else
m_vblank_period = m_config.m_vblank;
// if we are on scanline 0 already, reset the update timer immediately
// otherwise, defer until the next scanline 0
if (vpos() == 0)
m_scanline0_timer->adjust(attotime::zero);
else
m_scanline0_timer->adjust(time_until_pos(0));
// start the VBLANK timer
m_vblank_begin_timer->adjust(time_until_vblank_start());
// adjust speed if necessary
m_machine.video().update_refresh_speed();
}
//-------------------------------------------------
// reset_origin - reset the timing such that the
// given (x,y) occurs at the current time
//-------------------------------------------------
void screen_device::reset_origin(int beamy, int beamx)
{
// compute the effective VBLANK start/end times
attotime curtime = machine->time();
m_vblank_end_time = curtime - attotime(0, beamy * m_scantime + beamx * m_pixeltime);
m_vblank_start_time = m_vblank_end_time - attotime(0, m_vblank_period);
// if we are resetting relative to (0,0) == VBLANK end, call the
// scanline 0 timer by hand now; otherwise, adjust it for the future
if (beamy == 0 && beamx == 0)
scanline0_callback();
else
m_scanline0_timer->adjust(time_until_pos(0));
// if we are resetting relative to (visarea.max_y + 1, 0) == VBLANK start,
// call the VBLANK start timer now; otherwise, adjust it for the future
if (beamy == m_visarea.max_y + 1 && beamx == 0)
vblank_begin_callback();
else
m_vblank_begin_timer->adjust(time_until_vblank_start());
}
//-------------------------------------------------
// realloc_screen_bitmaps - reallocate bitmaps
// and textures as necessary
//-------------------------------------------------
void screen_device::realloc_screen_bitmaps()
{
if (m_config.m_type == SCREEN_TYPE_VECTOR)
return;
int curwidth = 0, curheight = 0;
// extract the current width/height from the bitmap
if (m_bitmap[0] != NULL)
{
curwidth = m_bitmap[0]->width;
curheight = m_bitmap[0]->height;
}
// if we're too small to contain this width/height, reallocate our bitmaps and textures
if (m_width > curwidth || m_height > curheight)
{
// free what we have currently
m_machine.render().texture_free(m_texture[0]);
m_machine.render().texture_free(m_texture[1]);
auto_free(machine, m_bitmap[0]);
auto_free(machine, m_bitmap[1]);
// compute new width/height
curwidth = MAX(m_width, curwidth);
curheight = MAX(m_height, curheight);
// choose the texture format - convert the screen format to a texture format
palette_t *palette = NULL;
switch (m_config.m_format)
{
case BITMAP_FORMAT_INDEXED16: m_texture_format = TEXFORMAT_PALETTE16; palette = machine->palette; break;
case BITMAP_FORMAT_RGB15: m_texture_format = TEXFORMAT_RGB15; palette = NULL; break;
case BITMAP_FORMAT_RGB32: m_texture_format = TEXFORMAT_RGB32; palette = NULL; break;
default: fatalerror("Invalid bitmap format!"); break;
}
// allocate bitmaps
m_bitmap[0] = auto_alloc(machine, bitmap_t(curwidth, curheight, m_config.m_format));
bitmap_set_palette(m_bitmap[0], machine->palette);
m_bitmap[1] = auto_alloc(machine, bitmap_t(curwidth, curheight, m_config.m_format));
bitmap_set_palette(m_bitmap[1], machine->palette);
// allocate textures
m_texture[0] = m_machine.render().texture_alloc();
m_texture[0]->set_bitmap(m_bitmap[0], &m_visarea, m_texture_format, palette);
m_texture[1] = m_machine.render().texture_alloc();
m_texture[1]->set_bitmap(m_bitmap[1], &m_visarea, m_texture_format, palette);
}
}
//-------------------------------------------------
// set_visible_area - just set the visible area
//-------------------------------------------------
void screen_device::set_visible_area(int min_x, int max_x, int min_y, int max_y)
{
// validate arguments
assert(min_x >= 0);
assert(min_y >= 0);
assert(min_x < max_x);
assert(min_y < max_y);
rectangle visarea;
visarea.min_x = min_x;
visarea.max_x = max_x;
visarea.min_y = min_y;
visarea.max_y = max_y;
configure(m_width, m_height, visarea, m_frame_period);
}
//-------------------------------------------------
// update_partial - perform a partial update from
// the last scanline up to and including the
// specified scanline
//-----------------------------------------------*/
bool screen_device::update_partial(int scanline)
{
// validate arguments
assert(scanline >= 0);
LOG_PARTIAL_UPDATES(("Partial: update_partial(%s, %d): ", tag(), scanline));
// these two checks only apply if we're allowed to skip frames
if (!(machine->config->m_video_attributes & VIDEO_ALWAYS_UPDATE))
{
// if skipping this frame, bail
if (m_machine.video().skip_this_frame())
{
LOG_PARTIAL_UPDATES(("skipped due to frameskipping\n"));
return FALSE;
}
// skip if this screen is not visible anywhere
if (!m_machine.render().is_live(*this))
{
LOG_PARTIAL_UPDATES(("skipped because screen not live\n"));
return FALSE;
}
}
// skip if less than the lowest so far
if (scanline < m_last_partial_scan)
{
LOG_PARTIAL_UPDATES(("skipped because less than previous\n"));
return FALSE;
}
// set the start/end scanlines
rectangle clip = m_visarea;
if (m_last_partial_scan > clip.min_y)
clip.min_y = m_last_partial_scan;
if (scanline < clip.max_y)
clip.max_y = scanline;
// render if necessary
bool result = false;
if (clip.min_y <= clip.max_y)
{
UINT32 flags = UPDATE_HAS_NOT_CHANGED;
g_profiler.start(PROFILER_VIDEO);
LOG_PARTIAL_UPDATES(("updating %d-%d\n", clip.min_y, clip.max_y));
flags = machine->driver_data<driver_device>()->video_update(*this, *m_bitmap[m_curbitmap], clip);
m_partial_updates_this_frame++;
g_profiler.stop();
// if we modified the bitmap, we have to commit
m_changed |= ~flags & UPDATE_HAS_NOT_CHANGED;
result = true;
}
// remember where we left off
m_last_partial_scan = scanline + 1;
return result;
}
//-------------------------------------------------
// update_now - perform an update from the last
// beam position up to the current beam position
//-------------------------------------------------
void screen_device::update_now()
{
int current_vpos = vpos();
int current_hpos = hpos();
// since we can currently update only at the scanline
// level, we are trying to do the right thing by
// updating including the current scanline, only if the
// beam is past the halfway point horizontally.
// If the beam is in the first half of the scanline,
// we only update up to the previous scanline.
// This minimizes the number of pixels that might be drawn
// incorrectly until we support a pixel level granularity
if (current_hpos < (m_width / 2) && current_vpos > 0)
current_vpos = current_vpos - 1;
update_partial(current_vpos);
}
//-------------------------------------------------
// vpos - returns the current vertical position
// of the beam
//-------------------------------------------------
int screen_device::vpos() const
{
attoseconds_t delta = (machine->time() - m_vblank_start_time).as_attoseconds();
int vpos;
// round to the nearest pixel
delta += m_pixeltime / 2;
// compute the v position relative to the start of VBLANK
vpos = delta / m_scantime;
// adjust for the fact that VBLANK starts at the bottom of the visible area
return (m_visarea.max_y + 1 + vpos) % m_height;
}
//-------------------------------------------------
// hpos - returns the current horizontal position
// of the beam
//-------------------------------------------------
int screen_device::hpos() const
{
attoseconds_t delta = (machine->time() - m_vblank_start_time).as_attoseconds();
// round to the nearest pixel
delta += m_pixeltime / 2;
// compute the v position relative to the start of VBLANK
int vpos = delta / m_scantime;
// subtract that from the total time
delta -= vpos * m_scantime;
// return the pixel offset from the start of this scanline
return delta / m_pixeltime;
}
//-------------------------------------------------
// time_until_pos - returns the amount of time
// remaining until the beam is at the given
// hpos,vpos
//-------------------------------------------------
attotime screen_device::time_until_pos(int vpos, int hpos) const
{
// validate arguments
assert(vpos >= 0);
assert(hpos >= 0);
// since we measure time relative to VBLANK, compute the scanline offset from VBLANK
vpos += m_height - (m_visarea.max_y + 1);
vpos %= m_height;
// compute the delta for the given X,Y position
attoseconds_t targetdelta = (attoseconds_t)vpos * m_scantime + (attoseconds_t)hpos * m_pixeltime;
// if we're past that time (within 1/2 of a pixel), head to the next frame
attoseconds_t curdelta = (machine->time() - m_vblank_start_time).as_attoseconds();
if (targetdelta <= curdelta + m_pixeltime / 2)
targetdelta += m_frame_period;
while (targetdelta <= curdelta)
targetdelta += m_frame_period;
// return the difference
return attotime(0, targetdelta - curdelta);
}
//-------------------------------------------------
// time_until_vblank_end - returns the amount of
// time remaining until the end of the current
// VBLANK (if in progress) or the end of the next
// VBLANK
//-------------------------------------------------
attotime screen_device::time_until_vblank_end() const
{
// if we are in the VBLANK region, compute the time until the end of the current VBLANK period
attotime target_time = m_vblank_end_time;
if (!vblank())
target_time += attotime(0, m_frame_period);
return target_time - machine->time();
}
//-------------------------------------------------
// register_vblank_callback - registers a VBLANK
// callback
//-------------------------------------------------
void screen_device::register_vblank_callback(vblank_state_changed_func vblank_callback, void *param)
{
// validate arguments
assert(vblank_callback != NULL);
// check if we already have this callback registered
callback_item **itemptr;
for (itemptr = &m_callback_list; *itemptr != NULL; itemptr = &(*itemptr)->m_next)
if ((*itemptr)->m_callback == vblank_callback)
break;
// if not found, register
if (*itemptr == NULL)
{
*itemptr = auto_alloc(machine, callback_item);
(*itemptr)->m_next = NULL;
(*itemptr)->m_callback = vblank_callback;
(*itemptr)->m_param = param;
}
}
//-------------------------------------------------
// vblank_begin_callback - call any external
// callbacks to signal the VBLANK period has begun
//-------------------------------------------------
void screen_device::vblank_begin_callback()
{
// reset the starting VBLANK time
m_vblank_start_time = machine->time();
m_vblank_end_time = m_vblank_start_time + attotime(0, m_vblank_period);
// call the screen specific callbacks
for (callback_item *item = m_callback_list; item != NULL; item = item->m_next)
(*item->m_callback)(*this, item->m_param, true);
// if this is the primary screen and we need to update now
if (this == machine->primary_screen && !(machine->config->m_video_attributes & VIDEO_UPDATE_AFTER_VBLANK))
machine->video().frame_update();
// reset the VBLANK start timer for the next frame
m_vblank_begin_timer->adjust(time_until_vblank_start());
// if no VBLANK period, call the VBLANK end callback immedietely, otherwise reset the timer
if (m_vblank_period == 0)
vblank_end_callback();
else
m_vblank_end_timer->adjust(time_until_vblank_end());
}
//-------------------------------------------------
// vblank_end_callback - call any external
// callbacks to signal the VBLANK period has ended
//-------------------------------------------------
void screen_device::vblank_end_callback()
{
// call the screen specific callbacks
for (callback_item *item = m_callback_list; item != NULL; item = item->m_next)
(*item->m_callback)(*this, item->m_param, false);
// if this is the primary screen and we need to update now
if (this == machine->primary_screen && (machine->config->m_video_attributes & VIDEO_UPDATE_AFTER_VBLANK))
machine->video().frame_update();
// increment the frame number counter
m_frame_number++;
}
//-------------------------------------------------
// scanline0_callback - reset partial updates
// for a screen
//-------------------------------------------------
void screen_device::scanline0_callback()
{
// reset partial updates
m_last_partial_scan = 0;
m_partial_updates_this_frame = 0;
m_scanline0_timer->adjust(time_until_pos(0));
}
//-------------------------------------------------
// scanline_update_callback - perform partial
// updates on each scanline
//-------------------------------------------------
void screen_device::scanline_update_callback(int scanline)
{
// force a partial update to the current scanline
update_partial(scanline);
// compute the next visible scanline
scanline++;
if (scanline > m_visarea.max_y)
scanline = m_visarea.min_y;
m_scanline_timer->adjust(time_until_pos(scanline), scanline);
}
//-------------------------------------------------
// update_quads - set up the quads for this
// screen
//-------------------------------------------------
bool screen_device::update_quads()
{
// only update if live
if (m_machine.render().is_live(*this))
{
// only update if empty and not a vector game; otherwise assume the driver did it directly
if (m_config.m_type != SCREEN_TYPE_VECTOR && (machine->config->m_video_attributes & VIDEO_SELF_RENDER) == 0)
{
// if we're not skipping the frame and if the screen actually changed, then update the texture
if (!m_machine.video().skip_this_frame() && m_changed)
{
rectangle fixedvis = m_visarea;
fixedvis.max_x++;
fixedvis.max_y++;
palette_t *palette = (m_texture_format == TEXFORMAT_PALETTE16) ? machine->palette : NULL;
m_texture[m_curbitmap]->set_bitmap(m_bitmap[m_curbitmap], &fixedvis, m_texture_format, palette);
m_curtexture = m_curbitmap;
m_curbitmap = 1 - m_curbitmap;
}
// create an empty container with a single quad
m_container->empty();
m_container->add_quad(0.0f, 0.0f, 1.0f, 1.0f, MAKE_ARGB(0xff,0xff,0xff,0xff), m_texture[m_curtexture], PRIMFLAG_BLENDMODE(BLENDMODE_NONE) | PRIMFLAG_SCREENTEX(1));
}
}
// reset the screen changed flags
bool result = m_changed;
m_changed = false;
return result;
}
//-------------------------------------------------
// update_burnin - update the burnin bitmap
//-------------------------------------------------
void screen_device::update_burnin()
{
#undef rand
if (m_burnin == NULL)
return;
bitmap_t *srcbitmap = m_bitmap[m_curtexture];
if (srcbitmap == NULL)
return;
int srcwidth = srcbitmap->width;
int srcheight = srcbitmap->height;
int dstwidth = m_burnin->width;
int dstheight = m_burnin->height;
int xstep = (srcwidth << 16) / dstwidth;
int ystep = (srcheight << 16) / dstheight;
int xstart = ((UINT32)rand() % 32767) * xstep / 32767;
int ystart = ((UINT32)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(m_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);
}
}
}
}
//-------------------------------------------------
// finalize_burnin - finalize the burnin bitmap
//-------------------------------------------------
void screen_device::finalize_burnin()
{
if (m_burnin == NULL)
return;
// compute the scaled visible region
rectangle scaledvis;
scaledvis.min_x = m_visarea.min_x * m_burnin->width / m_width;
scaledvis.max_x = m_visarea.max_x * m_burnin->width / m_width;
scaledvis.min_y = m_visarea.min_y * m_burnin->height / m_height;
scaledvis.max_y = m_visarea.max_y * m_burnin->height / m_height;
// wrap a bitmap around the subregion we care about
bitmap_t *finalmap = auto_alloc(machine, bitmap_t(scaledvis.max_x + 1 - scaledvis.min_x,
scaledvis.max_y + 1 - scaledvis.min_y,
BITMAP_FORMAT_ARGB32));
int srcwidth = m_burnin->width;
int srcheight = m_burnin->height;
int dstwidth = finalmap->width;
int dstheight = finalmap->height;
int xstep = (srcwidth << 16) / dstwidth;
int ystep = (srcheight << 16) / dstheight;
// find the maximum value
UINT64 minval = ~(UINT64)0;
UINT64 maxval = 0;
for (int y = 0; y < srcheight; y++)
{
UINT64 *src = BITMAP_ADDR64(m_burnin, y, 0);
for (int x = 0; x < srcwidth; x++)
{
minval = MIN(minval, src[x]);
maxval = MAX(maxval, src[x]);
}
}
if (minval == maxval)
return;
// now normalize and convert to RGB
for (int y = 0, srcy = 0; y < dstheight; y++, srcy += ystep)
{
UINT64 *src = BITMAP_ADDR64(m_burnin, srcy >> 16, 0);
UINT32 *dst = BITMAP_ADDR32(finalmap, y, 0);
for (int x = 0, srcx = 0; x < dstwidth; x++, srcx += xstep)
{
int brightness = (UINT64)(maxval - src[srcx >> 16]) * 255 / (maxval - minval);
dst[x] = MAKE_ARGB(0xff, brightness, brightness, brightness);
}
}
// write the final PNG
// compute the name and create the file
astring fname;
fname.printf("%s" PATH_SEPARATOR "burnin-%s.png", machine->basename(), tag());
mame_file *file;
file_error filerr = mame_fopen(SEARCHPATH_SCREENSHOT, 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", machine->m_game.manufacturer, machine->m_game.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);
}
}
//-------------------------------------------------
// finalize_burnin - finalize the burnin bitmap
//-------------------------------------------------
void screen_device::load_effect_overlay(const char *filename)
{
// ensure that there is a .png extension
astring fullname(filename);
int extension = fullname.rchr(0, '.');
if (extension != -1)
fullname.del(extension, -1);
fullname.cat(".png");
// load the file
m_screen_overlay_bitmap = render_load_png(OPTION_ARTPATH, NULL, fullname, NULL, NULL);
if (m_screen_overlay_bitmap != NULL)
m_container->set_overlay(m_screen_overlay_bitmap);
else
mame_printf_warning("Unable to load effect PNG file '%s'\n", fullname.cstr());
}