/***************************************************************************

    render.c

    Core rendering system.

****************************************************************************

    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.

****************************************************************************

    Windows-specific to-do:
        * no fallback if we run out of video memory

    Longer-term to do: (once old renderer is gone)
        * make vector updates asynchronous

****************************************************************************

    Overview of objects:

        render_target -- This represents a final rendering target. It
            is specified using integer width/height values, can have
            non-square pixels, and you can specify its rotation. It is
            what really determines the final rendering details. The OSD
            layer creates one or more of these to encapsulate the
            rendering process. Each render_target holds a list of
            layout_files that it can use for drawing. When rendering, it
            makes use of both layout_files and render_containers.

        render_container -- Containers are the top of a hierarchy that is
            not directly related to the objects above. Containers hold
            high level primitives that are generated at runtime by the
            video system. They are used currently for each screen and
            the user interface. These high-level primitives are broken down
            into low-level primitives at render time.

***************************************************************************/

#include "emu.h"
#include "emuopts.h"
#include "render.h"
#include "rendfont.h"
#include "rendlay.h"
#include "rendutil.h"
#include "config.h"
#include "xmlfile.h"
#include <ctype.h>



//**************************************************************************
//  CONSTANTS
//**************************************************************************

#define INTERNAL_FLAG_CHAR		0x00000001

enum
{
	COMPONENT_TYPE_IMAGE = 0,
	COMPONENT_TYPE_RECT,
	COMPONENT_TYPE_DISK,
	COMPONENT_TYPE_MAX
};


enum
{
	CONTAINER_ITEM_LINE = 0,
	CONTAINER_ITEM_QUAD,
	CONTAINER_ITEM_MAX
};



//**************************************************************************
//  MACROS
//**************************************************************************

#define ISWAP(var1, var2) do { int temp = var1; var1 = var2; var2 = temp; } while (0)
#define FSWAP(var1, var2) do { float temp = var1; var1 = var2; var2 = temp; } while (0)



//**************************************************************************
//  TYPE DEFINITIONS
//**************************************************************************

// an object_transform is used to track transformations when building an object list
struct object_transform
{
	float				xoffs, yoffs;		// offset transforms
	float				xscale, yscale;		// scale transforms
	render_color		color;				// color transform
	int					orientation;		// orientation transform
	bool				no_center;			// center the container?
};



//**************************************************************************
//  GLOBAL VARIABLES
//**************************************************************************

// precomputed UV coordinates for various orientations
static const render_quad_texuv oriented_texcoords[8] =
{
	{ { 0,0 }, { 1,0 }, { 0,1 }, { 1,1 } },		// 0
	{ { 1,0 }, { 0,0 }, { 1,1 }, { 0,1 } },		// ORIENTATION_FLIP_X
	{ { 0,1 }, { 1,1 }, { 0,0 }, { 1,0 } },		// ORIENTATION_FLIP_Y
	{ { 1,1 }, { 0,1 }, { 1,0 }, { 0,0 } },		// ORIENTATION_FLIP_X | ORIENTATION_FLIP_Y
	{ { 0,0 }, { 0,1 }, { 1,0 }, { 1,1 } },		// ORIENTATION_SWAP_XY
	{ { 0,1 }, { 0,0 }, { 1,1 }, { 1,0 } },		// ORIENTATION_SWAP_XY | ORIENTATION_FLIP_X
	{ { 1,0 }, { 1,1 }, { 0,0 }, { 0,1 } },		// ORIENTATION_SWAP_XY | ORIENTATION_FLIP_Y
	{ { 1,1 }, { 1,0 }, { 0,1 }, { 0,0 } }		// ORIENTATION_SWAP_XY | ORIENTATION_FLIP_X | ORIENTATION_FLIP_Y
};

// layer orders
static const int layer_order_standard[] = { ITEM_LAYER_SCREEN, ITEM_LAYER_OVERLAY, ITEM_LAYER_BACKDROP, ITEM_LAYER_BEZEL, ITEM_LAYER_CPANEL, ITEM_LAYER_MARQUEE };
static const int layer_order_alternate[] = { ITEM_LAYER_BACKDROP, ITEM_LAYER_SCREEN, ITEM_LAYER_OVERLAY, ITEM_LAYER_BEZEL, ITEM_LAYER_CPANEL, ITEM_LAYER_MARQUEE };



//**************************************************************************
//  INLINE FUNCTIONS
//**************************************************************************

//-------------------------------------------------
//  apply_orientation - apply orientation to a
//  set of bounds
//-------------------------------------------------

inline void apply_orientation(render_bounds &bounds, int orientation)
{
	// swap first
	if (orientation & ORIENTATION_SWAP_XY)
	{
		FSWAP(bounds.x0, bounds.y0);
		FSWAP(bounds.x1, bounds.y1);
	}

	// apply X flip
	if (orientation & ORIENTATION_FLIP_X)
	{
		bounds.x0 = 1.0f - bounds.x0;
		bounds.x1 = 1.0f - bounds.x1;
	}

	// apply Y flip
	if (orientation & ORIENTATION_FLIP_Y)
	{
		bounds.y0 = 1.0f - bounds.y0;
		bounds.y1 = 1.0f - bounds.y1;
	}
}


//-------------------------------------------------
//  normalize_bounds - normalize bounds so that
//  x0/y0 are less than x1/y1
//-------------------------------------------------

inline void normalize_bounds(render_bounds &bounds)
{
	if (bounds.x0 > bounds.x1)
		FSWAP(bounds.x0, bounds.x1);
	if (bounds.y0 > bounds.y1)
		FSWAP(bounds.y0, bounds.y1);
}


//-------------------------------------------------
//  get_layer_and_blendmode - return the
//  appropriate layer index and blendmode
//-------------------------------------------------

inline item_layer get_layer_and_blendmode(const layout_view &view, int index, int &blendmode)
{
	//  if we have multiple backdrop pieces and no overlays, render:
    //      backdrop (add) + screens (add) + bezels (alpha) + cpanels (alpha) + marquees (alpha)
    //  else render:
    //      screens (add) + overlays (RGB multiply) + backdrop (add) + bezels (alpha) + cpanels (alpha) + marquees (alpha)

    const int *layer_order = layer_order_standard;
	if (view.first_item(ITEM_LAYER_BACKDROP) != NULL && view.first_item(ITEM_LAYER_BACKDROP)->next() != NULL && view.first_item(ITEM_LAYER_OVERLAY) == NULL)
		layer_order = layer_order_alternate;

	// select the layer
	int layer = layer_order[index];

	// pick a blendmode
	if (layer == ITEM_LAYER_SCREEN && layer_order == layer_order_standard)
		blendmode = -1;
	else if (layer == ITEM_LAYER_SCREEN || (layer == ITEM_LAYER_BACKDROP && layer_order == layer_order_standard))
		blendmode = BLENDMODE_ADD;
	else if (layer == ITEM_LAYER_OVERLAY)
		blendmode = BLENDMODE_RGB_MULTIPLY;
	else
		blendmode = BLENDMODE_ALPHA;

	return item_layer(layer);
}



//**************************************************************************
//  RENDER PRIMITIVE
//**************************************************************************

//-------------------------------------------------
//  reset - reset the state of a primitive after
//  it is re-allocated
//-------------------------------------------------

void render_primitive::reset()
{
	memset(&type, 0, FPTR(&texcoords + 1) - FPTR(&type));
}



//**************************************************************************
//  RENDER PRIMITIVE LIST
//**************************************************************************

//-------------------------------------------------
//  render_primitive_list - constructor
//-------------------------------------------------

render_primitive_list::render_primitive_list()
	: m_lock(osd_lock_alloc())
{
}


//-------------------------------------------------
//  ~render_primitive_list - destructor
//-------------------------------------------------

render_primitive_list::~render_primitive_list()
{
	release_all();
	osd_lock_free(m_lock);
}


//-------------------------------------------------
//  add_reference - add a new reference
//-------------------------------------------------

inline void render_primitive_list::add_reference(void *refptr)
{
	// skip if we already have one
	if (has_reference(refptr))
		return;

	// set the refptr and link us into the list
	reference *ref = m_reference_allocator.alloc();
	ref->m_refptr = refptr;
	m_reflist.append(*ref);
}


//-------------------------------------------------
//  has_reference - find a refptr in a reference
//  list
//-------------------------------------------------

inline bool render_primitive_list::has_reference(void *refptr) const
{
	// skip if we already have one
	for (reference *ref = m_reflist.first(); ref != NULL; ref = ref->next())
		if (ref->m_refptr == refptr)
			return true;
	return false;
}


//-------------------------------------------------
//  alloc - allocate a new empty primitive
//-------------------------------------------------

inline render_primitive *render_primitive_list::alloc(render_primitive::primitive_type type)
{
	render_primitive *result = m_primitive_allocator.alloc();
	result->reset();
	result->type = type;
	return result;
}


//-------------------------------------------------
//  release_all - release the contents of
//  a render list
//-------------------------------------------------

void render_primitive_list::release_all()
{
	// release all the live items while under the lock
	acquire_lock();
	m_primitive_allocator.reclaim_all(m_primlist);
	m_reference_allocator.reclaim_all(m_reflist);
	release_lock();
}


//-------------------------------------------------
//  append_or_return - append a primitive to the
//  end of the list, or return it to the free
//  list, based on a flag
//-------------------------------------------------

void render_primitive_list::append_or_return(render_primitive &prim, bool clipped)
{
	if (!clipped)
		m_primlist.append(prim);
	else
		m_primitive_allocator.reclaim(prim);
}



//**************************************************************************
//  RENDER TEXTURE
//**************************************************************************

//-------------------------------------------------
//  render_texture - constructor
//-------------------------------------------------

render_texture::render_texture()
	: m_manager(NULL),
	  m_next(NULL),
	  m_bitmap(NULL),
	  m_format(TEXFORMAT_ARGB32),
	  m_bcglookup(NULL),
	  m_bcglookup_entries(0),
	  m_scaler(NULL),
	  m_param(NULL),
	  m_curseq(0)
{
	m_sbounds.set(0, -1, 0, -1);
	memset(m_scaled, 0, sizeof(m_scaled));
}


//-------------------------------------------------
//  ~render_texture - destructor
//-------------------------------------------------

render_texture::~render_texture()
{
	release();
}


//-------------------------------------------------
//  reset - reset the state of a texture after
//  it has been re-allocated
//-------------------------------------------------

void render_texture::reset(render_manager &manager, texture_scaler_func scaler, void *param)
{
	m_manager = &manager;
	if (scaler != NULL)
	{
		assert(m_format == TEXFORMAT_ARGB32);
		m_scaler = scaler;
		m_param = param;
	}
}


//-------------------------------------------------
//  release - release resources when we are freed
//-------------------------------------------------

void render_texture::release()
{
	// free all scaled versions
	for (int scalenum = 0; scalenum < ARRAY_LENGTH(m_scaled); scalenum++)
	{
		m_manager->invalidate_all(m_scaled[scalenum].bitmap);
		auto_free(m_manager->machine(), m_scaled[scalenum].bitmap);
		m_scaled[scalenum].bitmap = NULL;
		m_scaled[scalenum].seqid = 0;
	}

	// invalidate references to the original bitmap as well
	m_manager->invalidate_all(m_bitmap);
	m_bitmap = NULL;
	m_sbounds.set(0, -1, 0, -1);
	m_format = TEXFORMAT_ARGB32;
	m_curseq = 0;

	// free any B/C/G lookup tables
	auto_free(m_manager->machine(), m_bcglookup);
	m_bcglookup = NULL;
	m_bcglookup_entries = 0;
}


//-------------------------------------------------
//  set_bitmap - set a new source bitmap
//-------------------------------------------------

void render_texture::set_bitmap(bitmap_t &bitmap, const rectangle &sbounds, texture_format format)
{
	assert(bitmap.cliprect().contains(sbounds));

	// ensure we have a valid palette for palettized modes
	if (format == TEXFORMAT_PALETTE16 || format == TEXFORMAT_PALETTEA16)
		assert(bitmap.palette() != NULL);

	// invalidate references to the old bitmap
	if (&bitmap != m_bitmap && m_bitmap != NULL)
		m_manager->invalidate_all(m_bitmap);

	// set the new bitmap/palette
	m_bitmap = &bitmap;
	m_sbounds = sbounds;
	m_format = format;

	// invalidate all scaled versions
	for (int scalenum = 0; scalenum < ARRAY_LENGTH(m_scaled); scalenum++)
	{
		if (m_scaled[scalenum].bitmap != NULL)
		{
			m_manager->invalidate_all(m_scaled[scalenum].bitmap);
			auto_free(m_manager->machine(), m_scaled[scalenum].bitmap);
		}
		m_scaled[scalenum].bitmap = NULL;
		m_scaled[scalenum].seqid = 0;
	}
}


//-------------------------------------------------
//  hq_scale - generic high quality resampling
//  scaler
//-------------------------------------------------

void render_texture::hq_scale(bitmap_argb32 &dest, bitmap_argb32 &source, const rectangle &sbounds, void *param)
{
	render_color color = { 1.0f, 1.0f, 1.0f, 1.0f };
	bitmap_argb32 sourcesub(source, sbounds);
	render_resample_argb_bitmap_hq(dest, sourcesub, color);
}


//-------------------------------------------------
//  get_scaled - get a scaled bitmap (if we can)
//-------------------------------------------------

bool render_texture::get_scaled(UINT32 dwidth, UINT32 dheight, render_texinfo &texinfo, render_primitive_list &primlist)
{
	// source width/height come from the source bounds
	int swidth = m_sbounds.width();
	int sheight = m_sbounds.height();

	// ensure height/width are non-zero
	if (dwidth < 1) dwidth = 1;
	if (dheight < 1) dheight = 1;

	// are we scaler-free? if so, just return the source bitmap
	const rgb_t *palbase = (m_format == TEXFORMAT_PALETTE16 || m_format == TEXFORMAT_PALETTEA16) ? palette_entry_list_adjusted(m_bitmap->palette()) : NULL;
	if (m_scaler == NULL || (m_bitmap != NULL && swidth == dwidth && sheight == dheight))
	{
		// add a reference and set up the source bitmap
		primlist.add_reference(m_bitmap);
		texinfo.base = m_bitmap->raw_pixptr(m_sbounds.min_y, m_sbounds.min_x);
		texinfo.rowpixels = m_bitmap->rowpixels();
		texinfo.width = swidth;
		texinfo.height = sheight;
		texinfo.palette = palbase;
		texinfo.seqid = ++m_curseq;
		return true;
	}

	// make sure we can recover the original argb32 bitmap
	bitmap_argb32 dummy;
	bitmap_argb32 &srcbitmap = (m_bitmap != NULL) ? downcast<bitmap_argb32 &>(*m_bitmap) : dummy;

	// is it a size we already have?
	scaled_texture *scaled = NULL;
	int scalenum;
	for (scalenum = 0; scalenum < ARRAY_LENGTH(m_scaled); scalenum++)
	{
		scaled = &m_scaled[scalenum];

		// we need a non-NULL bitmap with matching dest size
		if (scaled->bitmap != NULL && dwidth == scaled->bitmap->width() && dheight == scaled->bitmap->height())
			break;
	}

	// did we get one?
	if (scalenum == ARRAY_LENGTH(m_scaled))
	{
		int lowest = -1;

		// didn't find one -- take the entry with the lowest seqnum
		for (scalenum = 0; scalenum < ARRAY_LENGTH(m_scaled); scalenum++)
			if ((lowest == -1 || m_scaled[scalenum].seqid < m_scaled[lowest].seqid) && !primlist.has_reference(m_scaled[scalenum].bitmap))
				lowest = scalenum;
		assert_always(lowest != -1, "Too many live texture instances!");

		// throw out any existing entries
		scaled = &m_scaled[lowest];
		if (scaled->bitmap != NULL)
		{
			m_manager->invalidate_all(scaled->bitmap);
			auto_free(m_manager->machine(), scaled->bitmap);
		}

		// allocate a new bitmap
		scaled->bitmap = auto_alloc(m_manager->machine(), bitmap_argb32(dwidth, dheight));
		scaled->seqid = ++m_curseq;

		// let the scaler do the work
		(*m_scaler)(*scaled->bitmap, srcbitmap, m_sbounds, m_param);
	}

	// finally fill out the new info
	primlist.add_reference(scaled->bitmap);
	texinfo.base = &scaled->bitmap->pix32(0);
	texinfo.rowpixels = scaled->bitmap->rowpixels();
	texinfo.width = dwidth;
	texinfo.height = dheight;
	texinfo.palette = palbase;
	texinfo.seqid = scaled->seqid;
	return true;
}


//-------------------------------------------------
//  get_adjusted_palette - return the adjusted
//  palette for a texture
//-------------------------------------------------

const rgb_t *render_texture::get_adjusted_palette(render_container &container)
{
	const rgb_t *adjusted;
	int numentries;

	// override the palette with our adjusted palette
	switch (m_format)
	{
		case TEXFORMAT_PALETTE16:
		case TEXFORMAT_PALETTEA16:

			// if no adjustment necessary, return the raw palette
			assert(m_bitmap->palette() != NULL);
			adjusted = palette_entry_list_adjusted(m_bitmap->palette());
			if (!container.has_brightness_contrast_gamma_changes())
				return adjusted;

			// if this is the machine palette, return our precomputed adjusted palette
			adjusted = container.bcg_lookup_table(m_format, m_bitmap->palette());
			if (adjusted != NULL)
				return adjusted;

			// otherwise, ensure we have memory allocated and compute the adjusted result ourself
			numentries = palette_get_num_colors(m_bitmap->palette()) * palette_get_num_groups(m_bitmap->palette());
			if (m_bcglookup == NULL || m_bcglookup_entries < numentries)
			{
				rgb_t *newlookup = auto_alloc_array(m_manager->machine(), rgb_t, numentries);
				memcpy(newlookup, m_bcglookup, m_bcglookup_entries * sizeof(rgb_t));
				auto_free(m_manager->machine(), m_bcglookup);
				m_bcglookup = newlookup;
				m_bcglookup_entries = numentries;
			}
			for (int index = 0; index < numentries; index++)
			{
				UINT8 r = container.apply_brightness_contrast_gamma(RGB_RED(adjusted[index]));
				UINT8 g = container.apply_brightness_contrast_gamma(RGB_GREEN(adjusted[index]));
				UINT8 b = container.apply_brightness_contrast_gamma(RGB_BLUE(adjusted[index]));
				m_bcglookup[index] = MAKE_ARGB(RGB_ALPHA(adjusted[index]), r, g, b);
			}
			return m_bcglookup;

		case TEXFORMAT_RGB32:
		case TEXFORMAT_ARGB32:
		case TEXFORMAT_YUY16:

			// if no adjustment necessary, return NULL
			if (!container.has_brightness_contrast_gamma_changes())
				return NULL;
				return container.bcg_lookup_table(m_format);

		default:
			assert(FALSE);
	}

	return NULL;
}



//**************************************************************************
//  RENDER CONTAINER
//**************************************************************************

//-------------------------------------------------
//  render_container - constructor
//-------------------------------------------------

render_container::render_container(render_manager &manager, screen_device *screen)
	: m_next(NULL),
	  m_manager(manager),
	  m_itemlist(manager.machine().respool()),
	  m_item_allocator(manager.machine().respool()),
	  m_screen(screen),
	  m_overlaybitmap(NULL),
	  m_overlaytexture(NULL),
	  m_palclient(NULL)
{
	// all palette entries are opaque by default
	for (int color = 0; color < ARRAY_LENGTH(m_bcglookup); color++)
		m_bcglookup[color] = MAKE_ARGB(0xff,0x00,0x00,0x00);

	// make sure it is empty
	empty();

	// if we have a screen, read and apply the options
	if (screen != NULL)
	{
		// set the initial orientation and brightness/contrast/gamma
		m_user.m_orientation = manager.machine().system().flags & ORIENTATION_MASK;
		m_user.m_brightness = manager.machine().options().brightness();
		m_user.m_contrast = manager.machine().options().contrast();
		m_user.m_gamma = manager.machine().options().gamma();
	}

	// allocate a client to the main palette
	if (manager.machine().palette != NULL)
		m_palclient = palette_client_alloc(manager.machine().palette);
	recompute_lookups();
}


//-------------------------------------------------
//  ~render_container - destructor
//-------------------------------------------------

render_container::~render_container()
{
	// free all the container items
	empty();

	// free the overlay texture
	m_manager.texture_free(m_overlaytexture);

	// release our palette client
	if (m_palclient != NULL)
		palette_client_free(m_palclient);
}


//-------------------------------------------------
//  set_overlay - set the overlay bitmap for the
//  container
//-------------------------------------------------

void render_container::set_overlay(bitmap_argb32 *bitmap)
{
	// free any existing texture
	m_manager.texture_free(m_overlaytexture);

	// set the new data and allocate the texture
	m_overlaybitmap = bitmap;
	if (m_overlaybitmap != NULL)
	{
		m_overlaytexture = m_manager.texture_alloc(render_container::overlay_scale);
		m_overlaytexture->set_bitmap(*bitmap, bitmap->cliprect(), TEXFORMAT_ARGB32);
	}
}


//-------------------------------------------------
//  set_user_settings - set the current user
//  settings for a container
//-------------------------------------------------

void render_container::set_user_settings(const user_settings &settings)
{
	m_user = settings;
	recompute_lookups();
}


//-------------------------------------------------
//  add_line - add a line item to this container
//-------------------------------------------------

void render_container::add_line(float x0, float y0, float x1, float y1, float width, rgb_t argb, UINT32 flags)
{
	item &newitem = add_generic(CONTAINER_ITEM_LINE, x0, y0, x1, y1, argb);
	newitem.m_width = width;
	newitem.m_flags = flags;
}


//-------------------------------------------------
//  add_quad - add a quad item to this container
//-------------------------------------------------

void render_container::add_quad(float x0, float y0, float x1, float y1, rgb_t argb, render_texture *texture, UINT32 flags)
{
	item &newitem = add_generic(CONTAINER_ITEM_QUAD, x0, y0, x1, y1, argb);
	newitem.m_texture = texture;
	newitem.m_flags = flags;
}


//-------------------------------------------------
//  add_char - add a char item to this container
//-------------------------------------------------

void render_container::add_char(float x0, float y0, float height, float aspect, rgb_t argb, render_font &font, UINT16 ch)
{
	// compute the bounds of the character cell and get the texture
	render_bounds bounds;
	bounds.x0 = x0;
	bounds.y0 = y0;
	render_texture *texture = font.get_char_texture_and_bounds(height, aspect, ch, bounds);

	// add it like a quad
	item &newitem = add_generic(CONTAINER_ITEM_QUAD, bounds.x0, bounds.y0, bounds.x1, bounds.y1, argb);
	newitem.m_texture = texture;
	newitem.m_flags = PRIMFLAG_TEXORIENT(ROT0) | PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA);
	newitem.m_internal = INTERNAL_FLAG_CHAR;
}


//-------------------------------------------------
//  apply_brightness_contrast_gamma - apply the
//  container's brightess, contrast, and gamma to
//  an 8-bit value
//-------------------------------------------------

UINT8 render_container::apply_brightness_contrast_gamma(UINT8 value)
{
	return ::apply_brightness_contrast_gamma(value, m_user.m_brightness, m_user.m_contrast, m_user.m_gamma);
}


//-------------------------------------------------
//  apply_brightness_contrast_gamma_fp - apply the
//  container's brightess, contrast, and gamma to
//  a floating-point value
//-------------------------------------------------

float render_container::apply_brightness_contrast_gamma_fp(float value)
{
	return ::apply_brightness_contrast_gamma_fp(value, m_user.m_brightness, m_user.m_contrast, m_user.m_gamma);
}


//-------------------------------------------------
//  bcg_lookup_table - return the appropriate
//  brightness/contrast/gamma lookup table for a
//  given texture mode
//-------------------------------------------------

const rgb_t *render_container::bcg_lookup_table(int texformat, palette_t *palette)
{
	switch (texformat)
	{
		case TEXFORMAT_PALETTE16:
		case TEXFORMAT_PALETTEA16:
			return (palette != NULL && palette == palette_client_get_palette(m_palclient)) ? m_bcglookup : NULL;

		case TEXFORMAT_RGB32:
		case TEXFORMAT_ARGB32:
		case TEXFORMAT_YUY16:
			return m_bcglookup256;

		default:
			return NULL;
	}
}


//-------------------------------------------------
//  overlay_scale - scaler for an overlay
//-------------------------------------------------

void render_container::overlay_scale(bitmap_argb32 &dest, bitmap_argb32 &source, const rectangle &sbounds, void *param)
{
	// simply replicate the source bitmap over the target
	for (int y = 0; y < dest.height(); y++)
	{
		UINT32 *src = &source.pix32(y % source.height());
		UINT32 *dst = &dest.pix32(y);
		int sx = 0;

		// loop over columns
		for (int x = 0; x < dest.width(); x++)
		{
			*dst++ = src[sx++];
			if (sx >= source.width())
				sx = 0;
		}
	}
}


//-------------------------------------------------
//  add_generic - add a generic item to a
//  container
//-------------------------------------------------

render_container::item &render_container::add_generic(UINT8 type, float x0, float y0, float x1, float y1, rgb_t argb)
{
	item *newitem = m_item_allocator.alloc();

	// copy the data into the new item
	newitem->m_type = type;
	newitem->m_bounds.x0 = x0;
	newitem->m_bounds.y0 = y0;
	newitem->m_bounds.x1 = x1;
	newitem->m_bounds.y1 = y1;
	newitem->m_color.r = (float)RGB_RED(argb) * (1.0f / 255.0f);
	newitem->m_color.g = (float)RGB_GREEN(argb) * (1.0f / 255.0f);
	newitem->m_color.b = (float)RGB_BLUE(argb) * (1.0f / 255.0f);
	newitem->m_color.a = (float)RGB_ALPHA(argb) * (1.0f / 255.0f);
	newitem->m_flags = 0;
	newitem->m_internal = 0;
	newitem->m_width = 0;
	newitem->m_texture = NULL;

	// add the item to the container
	return m_itemlist.append(*newitem);
}


//-------------------------------------------------
//  recompute_lookups - recompute the lookup table
//  for the render container
//-------------------------------------------------

void render_container::recompute_lookups()
{
	// recompute the 256 entry lookup table
	for (int i = 0; i < 0x100; i++)
	{
		UINT8 adjustedval = apply_brightness_contrast_gamma(i);
		m_bcglookup256[i + 0x000] = adjustedval << 0;
		m_bcglookup256[i + 0x100] = adjustedval << 8;
		m_bcglookup256[i + 0x200] = adjustedval << 16;
		m_bcglookup256[i + 0x300] = adjustedval << 24;
	}

	// recompute the palette entries
	if (m_palclient != NULL)
	{
		palette_t *palette = palette_client_get_palette(m_palclient);
		const pen_t *adjusted_palette = palette_entry_list_adjusted(palette);
		int colors = palette_get_num_colors(palette) * palette_get_num_groups(palette);

		for (int i = 0; i < colors; i++)
		{
			pen_t newval = adjusted_palette[i];
			m_bcglookup[i] = (newval & 0xff000000) |
									  m_bcglookup256[0x200 + RGB_RED(newval)] |
									  m_bcglookup256[0x100 + RGB_GREEN(newval)] |
									  m_bcglookup256[0x000 + RGB_BLUE(newval)];
		}
	}
}


//-------------------------------------------------
//  update_palette - update any dirty palette
//  entries
//-------------------------------------------------

void render_container::update_palette()
{
	// skip if no client
	if (m_palclient == NULL)
		return;

	// get the dirty list
	UINT32 mindirty, maxdirty;
	const UINT32 *dirty = palette_client_get_dirty_list(m_palclient, &mindirty, &maxdirty);

	// iterate over dirty items and update them
	if (dirty != NULL)
	{
		palette_t *palette = palette_client_get_palette(m_palclient);
		const pen_t *adjusted_palette = palette_entry_list_adjusted(palette);

		// loop over chunks of 32 entries, since we can quickly examine 32 at a time
		for (UINT32 entry32 = mindirty / 32; entry32 <= maxdirty / 32; entry32++)
		{
			UINT32 dirtybits = dirty[entry32];
			if (dirtybits != 0)

				// this chunk of 32 has dirty entries; fix them up
				for (UINT32 entry = 0; entry < 32; entry++)
					if (dirtybits & (1 << entry))
					{
						UINT32 finalentry = entry32 * 32 + entry;
						rgb_t newval = adjusted_palette[finalentry];
						m_bcglookup[finalentry] = (newval & 0xff000000) |
													  m_bcglookup256[0x200 + RGB_RED(newval)] |
													  m_bcglookup256[0x100 + RGB_GREEN(newval)] |
													  m_bcglookup256[0x000 + RGB_BLUE(newval)];
					}
		}
	}
}


//-------------------------------------------------
//  user_settings - constructor
//-------------------------------------------------

render_container::user_settings::user_settings()
	: m_orientation(0),
	  m_brightness(1.0f),
	  m_contrast(1.0f),
	  m_gamma(1.0f),
	  m_xscale(1.0f),
	  m_yscale(1.0f),
	  m_xoffset(0.0f),
	  m_yoffset(0.0f)
{
}



//**************************************************************************
//  RENDER TARGET
//**************************************************************************

//-------------------------------------------------
//  render_target - constructor
//-------------------------------------------------

render_target::render_target(render_manager &manager, const char *layoutfile, UINT32 flags)
	: m_next(NULL),
	  m_manager(manager),
	  m_curview(NULL),
	  m_filelist(*auto_alloc(manager.machine(), simple_list<layout_file>(manager.machine().respool()))),
	  m_flags(flags),
	  m_listindex(0),
	  m_width(640),
	  m_height(480),
	  m_pixel_aspect(0.0f),
	  m_max_refresh(0),
	  m_orientation(0),
	  m_base_view(NULL),
	  m_base_orientation(ROT0),
	  m_maxtexwidth(65536),
	  m_maxtexheight(65536),
	  m_debug_containers(manager.machine().respool())
{
	// determine the base layer configuration based on options
	m_base_layerconfig.set_backdrops_enabled(manager.machine().options().use_backdrops());
	m_base_layerconfig.set_overlays_enabled(manager.machine().options().use_overlays());
	m_base_layerconfig.set_bezels_enabled(manager.machine().options().use_bezels());
	m_base_layerconfig.set_cpanels_enabled(manager.machine().options().use_cpanels());
	m_base_layerconfig.set_marquees_enabled(manager.machine().options().use_marquees());
	m_base_layerconfig.set_zoom_to_screen(manager.machine().options().artwork_crop());

	// determine the base orientation based on options
	m_orientation = ROT0;
	if (!manager.machine().options().rotate())
		m_base_orientation = orientation_reverse(manager.machine().system().flags & ORIENTATION_MASK);

	// rotate left/right
	if (manager.machine().options().ror() || (manager.machine().options().auto_ror() && (manager.machine().system().flags & ORIENTATION_SWAP_XY)))
		m_base_orientation = orientation_add(ROT90, m_base_orientation);
	if (manager.machine().options().rol() || (manager.machine().options().auto_rol() && (manager.machine().system().flags & ORIENTATION_SWAP_XY)))
		m_base_orientation = orientation_add(ROT270, m_base_orientation);

	// flip X/Y
	if (manager.machine().options().flipx())
		m_base_orientation ^= ORIENTATION_FLIP_X;
	if (manager.machine().options().flipy())
		m_base_orientation ^= ORIENTATION_FLIP_Y;

	// set the orientation and layerconfig equal to the base
	m_orientation = m_base_orientation;
	m_layerconfig = m_base_layerconfig;

	// load the layout files
	load_layout_files(layoutfile, flags & RENDER_CREATE_SINGLE_FILE);

	// set the current view to the first one
	set_view(0);

	// make us the UI target if there is none
	if (!hidden() && manager.m_ui_target == NULL)
		manager.set_ui_target(*this);
}


//-------------------------------------------------
//  ~render_target - destructor
//-------------------------------------------------

render_target::~render_target()
{
	auto_free(m_manager.machine(), &m_filelist);
}


//-------------------------------------------------
//  is_ui_target - return true if this is the
//  UI target
//-------------------------------------------------

bool render_target::is_ui_target() const
{
	return (this == &m_manager.ui_target());
}


//-------------------------------------------------
//  index - return the index of this target
//-------------------------------------------------

int render_target::index() const
{
	return m_manager.m_targetlist.indexof(*this);
}


//-------------------------------------------------
//  set_bounds - set the bounds and pixel aspect
//  of a target
//-------------------------------------------------

void render_target::set_bounds(INT32 width, INT32 height, float pixel_aspect)
{
	m_width = width;
	m_height = height;
	m_bounds.x0 = m_bounds.y0 = 0;
	m_bounds.x1 = (float)width;
	m_bounds.y1 = (float)height;
	m_pixel_aspect = pixel_aspect;
}


//-------------------------------------------------
//  set_view - dynamically change the view for
//  a target
//-------------------------------------------------

void render_target::set_view(int viewindex)
{
	layout_view *view = view_by_index(viewindex);
	if (view != NULL)
	{
		m_curview = view;
		view->recompute(m_layerconfig);
	}
}


//-------------------------------------------------
//  set_max_texture_size - set the upper bound on
//  the texture size
//-------------------------------------------------

void render_target::set_max_texture_size(int maxwidth, int maxheight)
{
	m_maxtexwidth = maxwidth;
	m_maxtexheight = maxheight;
}


//-------------------------------------------------
//  configured_view - select a view for this
//  target based on the configuration parameters
//-------------------------------------------------

int render_target::configured_view(const char *viewname, int targetindex, int numtargets)
{
	layout_view *view = NULL;
	int viewindex;

	// auto view just selects the nth view
	if (strcmp(viewname, "auto") != 0)
	{
		// scan for a matching view name
		for (view = view_by_index(viewindex = 0); view != NULL; view = view_by_index(++viewindex))
			if (mame_strnicmp(view->name(), viewname, strlen(viewname)) == 0)
				break;
	}

	// if we don't have a match, default to the nth view
	screen_device_iterator iter(m_manager.machine().root_device());
	int scrcount = iter.count();
	if (view == NULL && scrcount > 0)
	{
		// if we have enough targets to be one per screen, assign in order
		if (numtargets >= scrcount)
		{
			int ourindex = index() % scrcount;
			screen_device *screen = iter.byindex(ourindex);

			// find the first view with this screen and this screen only
			for (view = view_by_index(viewindex = 0); view != NULL; view = view_by_index(++viewindex))
			{
				const render_screen_list &viewscreens = view->screens();
				if (viewscreens.count() == 1 && viewscreens.contains(*screen))
					break;
				if (viewscreens.count() == 0)
				{
					view = NULL;
					break;
				}
			}
		}

		// otherwise, find the first view that has all the screens
		if (view == NULL)
		{
			for (view = view_by_index(viewindex = 0); view != NULL; view = view_by_index(++viewindex))
			{
				const render_screen_list &viewscreens = view->screens();
				if (viewscreens.count() == 0)
					break;
				if (viewscreens.count() >= scrcount)
				{
					screen_device *screen;
					for (screen = iter.first(); screen != NULL; screen = iter.next())
						if (!viewscreens.contains(*screen))
							break;
					if (screen == NULL)
						break;
				}
			}
		}
	}

	// make sure it's a valid view
	return (view != NULL) ? view_index(*view) : 0;
}


//-------------------------------------------------
//  view_name - return the name of the given view
//-------------------------------------------------

const char *render_target::view_name(int viewindex)
{
	layout_view *view = view_by_index(viewindex);
	return (view != NULL) ? view->name() : NULL;
}


//-------------------------------------------------
//  translated_view_name -
//  return the localized name of the indexed view
//-------------------------------------------------

const char *render_target::translated_view_name(int viewindex)
{
	const char *s = view_name(viewindex);
	const char *idx[8];
	const char **pp;
	astring temp, res;

	if (!s)
		return NULL;

	pp = idx;
	while (*s)
	{
		if (isdigit(*s))
		{
			*pp++ = s;

			temp.cat("%");
			temp.cat("d");

			for (s++; *s; s++)
				if (!isdigit(*s))
					break;
		}
		else
			temp.cat(s++, 1);
	}

	s = _(temp);

	pp = idx;
	while (*s)
	{
		if (s[0] == '%' && s[1] == 'd')
		{
			s += 2;

			while (isdigit(**pp))
				res.cat((*pp)++, 1);
			pp++;
		}
		else
			res.cat(s++, 1);
	}

	s = mame_strdup(res);

	return s;
}


//-------------------------------------------------
//  render_target_get_view_screens - return a
//  bitmask of which screens are visible on a
//  given view
//-------------------------------------------------

const render_screen_list &render_target::view_screens(int viewindex)
{
	layout_view *view = view_by_index(viewindex);
	return (view != NULL) ? view->screens() : s_empty_screen_list;
}


//-------------------------------------------------
//  compute_visible_area - compute the visible
//  area for the given target with the current
//  layout and proposed new parameters
//-------------------------------------------------

void render_target::compute_visible_area(INT32 target_width, INT32 target_height, float target_pixel_aspect, int target_orientation, INT32 &visible_width, INT32 &visible_height)
{
	float width, height;
	float scale;

	// constrained case
	if (target_pixel_aspect != 0.0f)
	{
		// start with the aspect ratio of the square pixel layout
		width = m_curview->effective_aspect(m_layerconfig);
		height = 1.0f;

		// first apply target orientation
		if (target_orientation & ORIENTATION_SWAP_XY)
			FSWAP(width, height);

		// apply the target pixel aspect ratio
		height *= target_pixel_aspect;

		// based on the height/width ratio of the source and target, compute the scale factor
		if (width / height > (float)target_width / (float)target_height)
			scale = (float)target_width / width;
		else
			scale = (float)target_height / height;
	}

	// stretch-to-fit case
	else
	{
		width = (float)target_width;
		height = (float)target_height;
		scale = 1.0f;
	}

	// set the final width/height
	visible_width = render_round_nearest(width * scale);
	visible_height = render_round_nearest(height * scale);
}


//-------------------------------------------------
//  compute_minimum_size - compute the "minimum"
//  size of a target, which is the smallest bounds
//  that will ensure at least 1 target pixel per
//  source pixel for all included screens
//-------------------------------------------------

void render_target::compute_minimum_size(INT32 &minwidth, INT32 &minheight)
{
	float maxxscale = 1.0f, maxyscale = 1.0f;
	int screens_considered = 0;

	// early exit in case we are called between device teardown and render teardown
	if (m_manager.machine().phase() == MACHINE_PHASE_EXIT)
	{
		minwidth = 640;
		minheight = 480;
		return;
	}

	if (m_curview == NULL)
		throw emu_fatalerror("Mandatory artwork is missing");

	// scan the current view for all screens
	for (item_layer layer = ITEM_LAYER_FIRST; layer < ITEM_LAYER_MAX; layer++)

		// iterate over items in the layer
		for (layout_view::item *curitem = m_curview->first_item(layer); curitem != NULL; curitem = curitem->next())
			if (curitem->screen() != NULL)
			{
				// use a hard-coded default visible area for vector screens
				screen_device *screen = curitem->screen();
				const rectangle vectorvis(0, 639, 0, 479);
				const rectangle &visarea = (screen->screen_type() == SCREEN_TYPE_VECTOR) ? vectorvis : screen->visible_area();

				// apply target orientation to the bounds
				render_bounds bounds = curitem->bounds();
				apply_orientation(bounds, m_orientation);
				normalize_bounds(bounds);

				// based on the orientation of the screen container, check the bitmap
				float xscale, yscale;
				if (!(orientation_add(m_orientation, screen->container().orientation()) & ORIENTATION_SWAP_XY))
				{
					xscale = float(visarea.width()) / bounds.width();
					yscale = float(visarea.height()) / bounds.height();
				}
				else
				{
					xscale = float(visarea.height()) / bounds.width();
					yscale = float(visarea.width()) / bounds.height();
				}

				// pick the greater
				maxxscale = MAX(xscale, maxxscale);
				maxyscale = MAX(yscale, maxyscale);
				screens_considered++;
			}

	// if there were no screens considered, pick a nominal default
	if (screens_considered == 0)
	{
		maxxscale = 640.0f;
		maxyscale = 480.0f;
	}

	// round up
	minwidth = render_round_nearest(maxxscale);
	minheight = render_round_nearest(maxyscale);
}


//-------------------------------------------------
//  get_primitives - return a list of primitives
//  for a given render target
//-------------------------------------------------

render_primitive_list &render_target::get_primitives()
{
	// remember the base values if this is the first frame
	if (m_base_view == NULL)
		m_base_view = m_curview;

	// switch to the next primitive list
	render_primitive_list &list = m_primlist[m_listindex];
	m_listindex = (m_listindex + 1) % ARRAY_LENGTH(m_primlist);
	list.acquire_lock();

	// free any previous primitives
	list.release_all();

	// compute the visible width/height
	INT32 viswidth, visheight;
	compute_visible_area(m_width, m_height, m_pixel_aspect, m_orientation, viswidth, visheight);

	// create a root transform for the target
	object_transform root_xform;
	root_xform.xoffs = (float)(m_width - viswidth) / 2;
	root_xform.yoffs = (float)(m_height - visheight) / 2;
	root_xform.xscale = (float)viswidth;
	root_xform.yscale = (float)visheight;
	root_xform.color.r = root_xform.color.g = root_xform.color.b = root_xform.color.a = 1.0f;
	root_xform.orientation = m_orientation;
    root_xform.no_center = false;

	// iterate over layers back-to-front, but only if we're running
	if (m_manager.machine().phase() >= MACHINE_PHASE_RESET)
		for (item_layer layernum = ITEM_LAYER_FIRST; layernum < ITEM_LAYER_MAX; layernum++)
		{
			int blendmode;
			item_layer layer = get_layer_and_blendmode(*m_curview, layernum, blendmode);
			if (m_curview->layer_enabled(layer))
			{
				// iterate over items in the layer
				for (layout_view::item *curitem = m_curview->first_item(layer); curitem != NULL; curitem = curitem->next())
				{
					// first apply orientation to the bounds
					render_bounds bounds = curitem->bounds();
					apply_orientation(bounds, root_xform.orientation);
					normalize_bounds(bounds);

					// apply the transform to the item
					object_transform item_xform;
					item_xform.xoffs = root_xform.xoffs + bounds.x0 * root_xform.xscale;
					item_xform.yoffs = root_xform.yoffs + bounds.y0 * root_xform.yscale;
					item_xform.xscale = (bounds.x1 - bounds.x0) * root_xform.xscale;
					item_xform.yscale = (bounds.y1 - bounds.y0) * root_xform.yscale;
					item_xform.color.r = curitem->color().r * root_xform.color.r;
					item_xform.color.g = curitem->color().g * root_xform.color.g;
					item_xform.color.b = curitem->color().b * root_xform.color.b;
					item_xform.color.a = curitem->color().a * root_xform.color.a;
					item_xform.orientation = orientation_add(curitem->orientation(), root_xform.orientation);
                    item_xform.no_center = false;

					// if there is no associated element, it must be a screen element
					if (curitem->screen() != NULL)
						add_container_primitives(list, item_xform, curitem->screen()->container(), blendmode);
					else
						add_element_primitives(list, item_xform, *curitem->element(), curitem->state(), blendmode);
				}
			}
		}

	// if we are not in the running stage, draw an outer box
	else
	{
		render_primitive *prim = list.alloc(render_primitive::QUAD);
		set_render_bounds_xy(&prim->bounds, 0.0f, 0.0f, (float)m_width, (float)m_height);
		set_render_color(&prim->color, 1.0f, 1.0f, 1.0f, 1.0f);
		prim->texture.base = NULL;
		prim->flags = PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA);
		list.append(*prim);

		if (m_width > 1 && m_height > 1)
		{
			prim = list.alloc(render_primitive::QUAD);
			set_render_bounds_xy(&prim->bounds, 1.0f, 1.0f, (float)(m_width - 1), (float)(m_height - 1));
			set_render_color(&prim->color, 1.0f, 0.0f, 0.0f, 0.0f);
			prim->texture.base = NULL;
			prim->flags = PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA);
			list.append(*prim);
		}
	}

	// process the debug containers
	for (render_container *debug = m_debug_containers.first(); debug != NULL; debug = debug->next())
	{
		object_transform ui_xform;
		ui_xform.xoffs = 0;
		ui_xform.yoffs = 0;
		ui_xform.xscale = (float)m_width;
		ui_xform.yscale = (float)m_height;
		ui_xform.color.r = ui_xform.color.g = ui_xform.color.b = ui_xform.color.a = 1.0f;
		ui_xform.color.a = 0.9f;
		ui_xform.orientation = m_orientation;
		ui_xform.no_center = true;

		// add UI elements
		add_container_primitives(list, ui_xform, *debug, BLENDMODE_ALPHA);
	}

	// process the UI if we are the UI target
	if (is_ui_target())
	{
		// compute the transform for the UI
		object_transform ui_xform;
		ui_xform.xoffs = 0;
		ui_xform.yoffs = 0;
		ui_xform.xscale = (float) m_width;
		ui_xform.yscale = (float) m_height;
		ui_xform.color.r = ui_xform.color.g = ui_xform.color.b = ui_xform.color.a = 1.0f;
		ui_xform.orientation = m_orientation;
        ui_xform.no_center = false;

		// add UI elements
		add_container_primitives(list, ui_xform, m_manager.ui_container(), BLENDMODE_ALPHA);
	}

	// optimize the list before handing it off
	add_clear_and_optimize_primitive_list(list);
	list.release_lock();
	return list;
}


//-------------------------------------------------
//  map_point_container - attempts to map a point
//  on the specified render_target to the
//  specified container, if possible
//-------------------------------------------------

bool render_target::map_point_container(INT32 target_x, INT32 target_y, render_container &container, float &container_x, float &container_y)
{
	const char *input_tag;
	UINT32 input_mask;
	return map_point_internal(target_x, target_y, &container, container_x, container_y, input_tag, input_mask);
}


//-------------------------------------------------
//  map_point_input - attempts to map a point on
//  the specified render_target to the specified
//  container, if possible
//-------------------------------------------------

bool render_target::map_point_input(INT32 target_x, INT32 target_y, const char *&input_tag, UINT32 &input_mask, float &input_x, float &input_y)
{
	return map_point_internal(target_x, target_y, NULL, input_x, input_y, input_tag, input_mask);
}


//-------------------------------------------------
//  invalidate_all - if any of our primitive lists
//  contain a reference to the given pointer,
//  clear them
//-------------------------------------------------

void render_target::invalidate_all(void *refptr)
{
	// iterate through all our primitive lists
	for (int listnum = 0; listnum < ARRAY_LENGTH(m_primlist); listnum++)
	{
		render_primitive_list &list = m_primlist[listnum];

		// if we have a reference to this object, release our list
		list.acquire_lock();
		if (list.has_reference(refptr))
			list.release_all();
		list.release_lock();
	}
}


//-------------------------------------------------
//  debug_alloc - allocate a container for a debug
//  view
//-------------------------------------------------

render_container *render_target::debug_alloc()
{
	return &m_debug_containers.append(*m_manager.container_alloc());
}


//-------------------------------------------------
//  debug_free - free a container for a debug view
//-------------------------------------------------

void render_target::debug_free(render_container &container)
{
	m_debug_containers.remove(container);
}


//-------------------------------------------------
//  debug_top - move a debug view container to
//  the top of the list
//-------------------------------------------------

void render_target::debug_top(render_container &container)
{
	m_debug_containers.prepend(m_debug_containers.detach(container));
}


//-------------------------------------------------
//  update_layer_config - recompute after a layer
//  config change
//-------------------------------------------------

void render_target::update_layer_config()
{
	m_curview->recompute(m_layerconfig);
}


//-------------------------------------------------
//  load_layout_files - load layout files for a
//  given render target
//-------------------------------------------------

void render_target::load_layout_files(const char *layoutfile, bool singlefile)
{
	bool have_default = false;
	// if there's an explicit file, load that first
	const char *basename = m_manager.machine().basename();
	if (layoutfile != NULL)
		have_default |= load_layout_file(basename, layoutfile);

	// if we're only loading this file, we know our final result
	if (singlefile)
		return;

	// try to load a file based on the driver name
	const game_driver &system = m_manager.machine().system();
	if (!load_layout_file(basename, system.name))
		have_default |= load_layout_file(basename, "default");
	else 
		have_default |= true;

	// if a default view has been specified, use that as a fallback
	if (system.default_layout != NULL)
		have_default |= load_layout_file(NULL, system.default_layout);
	if (m_manager.machine().config().m_default_layout != NULL)
		have_default |= load_layout_file(NULL, m_manager.machine().config().m_default_layout);

	// try to load another file based on the parent driver name
	int cloneof = driver_list::clone(system);
	if (cloneof != -1) {
		if (!load_layout_file(driver_list::driver(cloneof).name, driver_list::driver(cloneof).name))
			have_default |= load_layout_file(driver_list::driver(cloneof).name, "default");
		else 
			have_default |= true;
	}
	screen_device_iterator iter(m_manager.machine().root_device());
	int screens = iter.count();
	// now do the built-in layouts for single-screen games
	if (screens == 1)
	{
		if (system.flags & ORIENTATION_SWAP_XY)
			load_layout_file(NULL, layout_vertical);
		else
			load_layout_file(NULL, layout_horizont);
		assert_always(m_filelist.count() > 0, "Couldn't parse default layout??");
	}
	if (!have_default) 
	{
		if (screens == 0)
		{			
			load_layout_file(NULL, layout_noscreens);
			assert_always(m_filelist.count() > 0, "Couldn't parse default layout??");
		}
		else
		if (screens == 2)
		{
			load_layout_file(NULL, layout_dualhsxs);
			assert_always(m_filelist.count() > 0, "Couldn't parse default layout??");
		}
		else
		if (screens == 3)
		{
			load_layout_file(NULL, layout_triphsxs);
			assert_always(m_filelist.count() > 0, "Couldn't parse default layout??");
		}
		else
		if (screens == 4)
		{
			load_layout_file(NULL, layout_quadhsxs);
			assert_always(m_filelist.count() > 0, "Couldn't parse default layout??");
		}
	}
}


//-------------------------------------------------
//  load_layout_file - load a single layout file
//  and append it to our list
//-------------------------------------------------

bool render_target::load_layout_file(const char *dirname, const char *filename)
{
	// if the first character of the "file" is an open brace, assume it is an XML string
	xml_data_node *rootnode;
	if (filename[0] == '<')
		rootnode = xml_string_read(filename, NULL);

	// otherwise, assume it is a file
	else
	{
		// build the path and optionally prepend the directory
		astring fname(filename, ".lay");
		if (dirname != NULL)
			fname.ins(0, PATH_SEPARATOR).ins(0, dirname);

		// attempt to open the file; bail if we can't
		emu_file layoutfile(manager().machine().options().art_path(), OPEN_FLAG_READ);
		file_error filerr = layoutfile.open(fname);
		if (filerr != FILERR_NONE)
			return false;

		// read the file
		rootnode = xml_file_read(layoutfile, NULL);
	}

	// if we didn't get a properly-formatted XML file, record a warning and exit
	if (rootnode == NULL)
	{
		if (filename[0] != '<')
			mame_printf_warning("Improperly formatted XML file '%s', ignoring\n", filename);
		else
			mame_printf_warning("Improperly formatted XML string, ignoring\n");
		return false;
	}

	// parse and catch any errors
	bool result = true;
	try
	{
		m_filelist.append(*auto_alloc(m_manager.machine(), layout_file(m_manager.machine(), *rootnode, dirname)));
	}
	catch (emu_fatalerror &err)
	{
		if (filename[0] != '<')
			mame_printf_warning("Error in XML file '%s': %s\n", filename, err.string());
		else
			mame_printf_warning("Error in XML string: %s", err.string());
		result = false;
	}

	// free the root node
	xml_file_free(rootnode);
	return result;
}


//-------------------------------------------------
//  add_container_primitives - add primitives
//  based on the container
//-------------------------------------------------

void render_target::add_container_primitives(render_primitive_list &list, const object_transform &xform, render_container &container, int blendmode)
{
	// first update the palette for the container, if it is dirty
	container.update_palette();

	// compute the clip rect
	render_bounds cliprect;
	cliprect.x0 = xform.xoffs;
	cliprect.y0 = xform.yoffs;
	cliprect.x1 = xform.xoffs + xform.xscale;
	cliprect.y1 = xform.yoffs + xform.yscale;
	sect_render_bounds(&cliprect, &m_bounds);

	// compute the container transform
	object_transform container_xform;
	container_xform.orientation = orientation_add(container.orientation(), xform.orientation);
	{
		float xscale = (container_xform.orientation & ORIENTATION_SWAP_XY) ? container.yscale() : container.xscale();
		float yscale = (container_xform.orientation & ORIENTATION_SWAP_XY) ? container.xscale() : container.yscale();
		float xoffs = (container_xform.orientation & ORIENTATION_SWAP_XY) ? container.yoffset() : container.xoffset();
		float yoffs = (container_xform.orientation & ORIENTATION_SWAP_XY) ? container.xoffset() : container.yoffset();
		if (container_xform.orientation & ORIENTATION_FLIP_X) xoffs = -xoffs;
		if (container_xform.orientation & ORIENTATION_FLIP_Y) yoffs = -yoffs;
		container_xform.xscale = xform.xscale * xscale;
		container_xform.yscale = xform.yscale * yscale;
		if (xform.no_center)
		{
			container_xform.xoffs = xform.xscale * (xoffs) + xform.xoffs;
			container_xform.yoffs = xform.yscale * (yoffs) + xform.yoffs;
		}
		else
		{
			container_xform.xoffs = xform.xscale * (0.5f - 0.5f * xscale + xoffs) + xform.xoffs;
			container_xform.yoffs = xform.yscale * (0.5f - 0.5f * yscale + yoffs) + xform.yoffs;
		}
		container_xform.color = xform.color;
	}

	// iterate over elements
	for (render_container::item *curitem = container.first_item(); curitem != NULL; curitem = curitem->next())
	{
		// compute the oriented bounds
		render_bounds bounds = curitem->bounds();
		apply_orientation(bounds, container_xform.orientation);

		// allocate the primitive and set the transformed bounds/color data
		render_primitive *prim = list.alloc(render_primitive::INVALID);
		prim->bounds.x0 = render_round_nearest(container_xform.xoffs + bounds.x0 * container_xform.xscale);
		prim->bounds.y0 = render_round_nearest(container_xform.yoffs + bounds.y0 * container_xform.yscale);
		if (curitem->internal() & INTERNAL_FLAG_CHAR)
		{
			prim->bounds.x1 = prim->bounds.x0 + render_round_nearest((bounds.x1 - bounds.x0) * container_xform.xscale);
			prim->bounds.y1 = prim->bounds.y0 + render_round_nearest((bounds.y1 - bounds.y0) * container_xform.yscale);
		}
		else
		{
			prim->bounds.x1 = render_round_nearest(container_xform.xoffs + bounds.x1 * container_xform.xscale);
			prim->bounds.y1 = render_round_nearest(container_xform.yoffs + bounds.y1 * container_xform.yscale);
		}

		// compute the color of the primitive
		prim->color.r = container_xform.color.r * curitem->color().r;
		prim->color.g = container_xform.color.g * curitem->color().g;
		prim->color.b = container_xform.color.b * curitem->color().b;
		prim->color.a = container_xform.color.a * curitem->color().a;

		// now switch off the type
		bool clipped = true;
		switch (curitem->type())
		{
			case CONTAINER_ITEM_LINE:
				// adjust the color for brightness/contrast/gamma
				prim->color.a = container.apply_brightness_contrast_gamma_fp(prim->color.a);
				prim->color.r = container.apply_brightness_contrast_gamma_fp(prim->color.r);
				prim->color.g = container.apply_brightness_contrast_gamma_fp(prim->color.g);
				prim->color.b = container.apply_brightness_contrast_gamma_fp(prim->color.b);

				// set the line type
				prim->type = render_primitive::LINE;

				// scale the width by the minimum of X/Y scale factors
				prim->width = curitem->width() * MIN(container_xform.xscale, container_xform.yscale);
				prim->flags = curitem->flags();

				// clip the primitive
				clipped = render_clip_line(&prim->bounds, &cliprect);
				break;

			case CONTAINER_ITEM_QUAD:
				// set the quad type
				prim->type = render_primitive::QUAD;

				// normalize the bounds
				normalize_bounds(prim->bounds);

				// get the scaled bitmap and set the resulting palette
				if (curitem->texture() != NULL)
				{
					// determine the final orientation
					int finalorient = orientation_add(PRIMFLAG_GET_TEXORIENT(curitem->flags()), container_xform.orientation);

					// based on the swap values, get the scaled final texture
					int width = (finalorient & ORIENTATION_SWAP_XY) ? (prim->bounds.y1 - prim->bounds.y0) : (prim->bounds.x1 - prim->bounds.x0);
					int height = (finalorient & ORIENTATION_SWAP_XY) ? (prim->bounds.x1 - prim->bounds.x0) : (prim->bounds.y1 - prim->bounds.y0);
					width = MIN(width, m_maxtexwidth);
					height = MIN(height, m_maxtexheight);
					if (curitem->texture()->get_scaled(width, height, prim->texture, list))
					{
						// set the palette
						prim->texture.palette = curitem->texture()->get_adjusted_palette(container);

						// determine UV coordinates and apply clipping
						prim->texcoords = oriented_texcoords[finalorient];
						clipped = render_clip_quad(&prim->bounds, &cliprect, &prim->texcoords);

						// apply the final orientation from the quad flags and then build up the final flags
						prim->flags = (curitem->flags() & ~(PRIMFLAG_TEXORIENT_MASK | PRIMFLAG_BLENDMODE_MASK | PRIMFLAG_TEXFORMAT_MASK)) |
										PRIMFLAG_TEXORIENT(finalorient) |
										PRIMFLAG_TEXFORMAT(curitem->texture()->format());
						if (blendmode != -1)
							prim->flags |= PRIMFLAG_BLENDMODE(blendmode);
						else
							prim->flags |= PRIMFLAG_BLENDMODE(PRIMFLAG_GET_BLENDMODE(curitem->flags()));
					}
				}
				else
				{
					// adjust the color for brightness/contrast/gamma
					prim->color.r = container.apply_brightness_contrast_gamma_fp(prim->color.r);
					prim->color.g = container.apply_brightness_contrast_gamma_fp(prim->color.g);
					prim->color.b = container.apply_brightness_contrast_gamma_fp(prim->color.b);

					// no texture -- set the basic flags
					prim->texture.base = NULL;
					prim->flags = PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA);

					// apply clipping
					clipped = render_clip_quad(&prim->bounds, &cliprect, NULL);
				}
				break;
		}

		// add to the list or free if we're clipped out
		list.append_or_return(*prim, clipped);
	}

	// add the overlay if it exists
	if (container.overlay() != NULL && m_layerconfig.screen_overlay_enabled())
	{
		INT32 width, height;

		// allocate a primitive
		render_primitive *prim = list.alloc(render_primitive::QUAD);
		set_render_bounds_wh(&prim->bounds, xform.xoffs, xform.yoffs, xform.xscale, xform.yscale);
		prim->color = container_xform.color;
		width = render_round_nearest(prim->bounds.x1) - render_round_nearest(prim->bounds.x0);
		height = render_round_nearest(prim->bounds.y1) - render_round_nearest(prim->bounds.y0);

		bool got_scaled = container.overlay()->get_scaled(
				(container_xform.orientation & ORIENTATION_SWAP_XY) ? height : width,
				(container_xform.orientation & ORIENTATION_SWAP_XY) ? width : height, prim->texture, list);
		if (got_scaled)
		{
			// determine UV coordinates
			prim->texcoords = oriented_texcoords[container_xform.orientation];

			// set the flags and add it to the list
			prim->flags = PRIMFLAG_TEXORIENT(container_xform.orientation) |
							PRIMFLAG_BLENDMODE(BLENDMODE_RGB_MULTIPLY) |
							PRIMFLAG_TEXFORMAT(container.overlay()->format()) |
							PRIMFLAG_TEXSHADE(1);
		}
		list.append_or_return(*prim, !got_scaled);
	}
}


//-------------------------------------------------
//  add_element_primitives - add the primitive
//  for an element in the current state
//-------------------------------------------------

void render_target::add_element_primitives(render_primitive_list &list, const object_transform &xform, layout_element &element, int state, int blendmode)
{
	// if we're out of range, bail
	if (state > element.maxstate())
		return;
	if (state < 0)
		state = 0;

	// get a pointer to the relevant texture
	render_texture *texture = element.state_texture(state);
	if (texture != NULL)
	{
		render_primitive *prim = list.alloc(render_primitive::QUAD);

		// configure the basics
		prim->color = xform.color;
		prim->flags = PRIMFLAG_TEXORIENT(xform.orientation) | PRIMFLAG_BLENDMODE(blendmode) | PRIMFLAG_TEXFORMAT(texture->format());

		// compute the bounds
		INT32 width = render_round_nearest(xform.xscale);
		INT32 height = render_round_nearest(xform.yscale);
		set_render_bounds_wh(&prim->bounds, render_round_nearest(xform.xoffs), render_round_nearest(xform.yoffs), (float) width, (float) height);
		if (xform.orientation & ORIENTATION_SWAP_XY)
			ISWAP(width, height);
		width = MIN(width, m_maxtexwidth);
		height = MIN(height, m_maxtexheight);

		// get the scaled texture and append it
		bool clipped = true;
		if (texture->get_scaled(width, height, prim->texture, list))
		{
			// compute the clip rect
			render_bounds cliprect;
			cliprect.x0 = render_round_nearest(xform.xoffs);
			cliprect.y0 = render_round_nearest(xform.yoffs);
			cliprect.x1 = render_round_nearest(xform.xoffs + xform.xscale);
			cliprect.y1 = render_round_nearest(xform.yoffs + xform.yscale);
			sect_render_bounds(&cliprect, &m_bounds);

			// determine UV coordinates and apply clipping
			prim->texcoords = oriented_texcoords[xform.orientation];
			clipped = render_clip_quad(&prim->bounds, &cliprect, &prim->texcoords);
		}

		// add to the list or free if we're clipped out
		list.append_or_return(*prim, clipped);
	}
}


//-------------------------------------------------
//  map_point_internal - internal logic for
//  mapping points
//-------------------------------------------------

bool render_target::map_point_internal(INT32 target_x, INT32 target_y, render_container *container, float &mapped_x, float &mapped_y, const char *&mapped_input_tag, UINT32 &mapped_input_mask)
{
	// default to point not mapped
	mapped_x = -1.0;
	mapped_y = -1.0;
	mapped_input_tag = NULL;
	mapped_input_mask = 0;

	// convert target coordinates to float
	float target_fx = (float)target_x / m_width;
	float target_fy = (float)target_y / m_height;

	// explicitly check for the UI container
	if (container != NULL && container == &m_manager.ui_container())
	{
		// this hit test went against the UI container
		if (target_fx >= 0.0 && target_fx < 1.0 && target_fy >= 0.0 && target_fy < 1.0)
		{
			// this point was successfully mapped
			mapped_x = target_fx;
			mapped_y = target_fy;
			return true;
		}
		return false;
	}

	// loop through each layer
	for (item_layer layernum = ITEM_LAYER_FIRST; layernum < ITEM_LAYER_MAX; layernum++)
	{
		int blendmode;
		item_layer layer = get_layer_and_blendmode(*m_curview, layernum, blendmode);
		if (m_curview->layer_enabled(layer))
		{
			// iterate over items in the layer
			for (layout_view::item *item = m_curview->first_item(layer); item != NULL; item = item->next())
			{
				bool checkit;

				// if we're looking for a particular container, verify that we have the right one
				if (container != NULL)
					checkit = (item->screen() != NULL && &item->screen()->container() == container);

				// otherwise, assume we're looking for an input
				else
					checkit = item->has_input();

				// this target is worth looking at; now check the point
				if (checkit && target_fx >= item->bounds().x0 && target_fx < item->bounds().x1 && target_fy >= item->bounds().y0 && target_fy < item->bounds().y1)
				{
					// point successfully mapped
					mapped_x = (target_fx - item->bounds().x0) / (item->bounds().x1 - item->bounds().x0);
					mapped_y = (target_fy - item->bounds().y0) / (item->bounds().y1 - item->bounds().y0);
					mapped_input_tag = item->input_tag_and_mask(mapped_input_mask);
					return true;
				}
			}
		}
	}
	return false;
}


//-------------------------------------------------
//  view_name - return the name of the indexed
//  view, or NULL if it doesn't exist
//-------------------------------------------------

layout_view *render_target::view_by_index(int index) const
{
	// scan the list of views within each layout, skipping those that don't apply
	for (layout_file *file = m_filelist.first(); file != NULL; file = file->next())
		for (layout_view *view = file->first_view(); view != NULL; view = view->next())
			if (!(m_flags & RENDER_CREATE_NO_ART) || !view->has_art())
				if (index-- == 0)
					return view;
	return NULL;
}


//-------------------------------------------------
//  view_index - return the index of the given
//  view
//-------------------------------------------------

int render_target::view_index(layout_view &targetview) const
{
	// find the first named match
	int index = 0;

	// scan the list of views within each layout, skipping those that don't apply
	for (layout_file *file = m_filelist.first(); file != NULL; file = file->next())
		for (layout_view *view = file->first_view(); view != NULL; view = view->next())
			if (!(m_flags & RENDER_CREATE_NO_ART) || !view->has_art())
			{
				if (&targetview == view)
					return index;
				index++;
			}
	return 0;
}


//-------------------------------------------------
//  config_load - process config information
//-------------------------------------------------

void render_target::config_load(xml_data_node &targetnode)
{
	// find the view
	const char *viewname = xml_get_attribute_string(&targetnode, "view", NULL);
	if (viewname != NULL)
		for (int viewnum = 0; viewnum < 1000; viewnum++)
		{
			const char *testname = view_name(viewnum);
			if (testname == NULL)
				break;
			if (!strcmp(viewname, testname))
			{
				set_view(viewnum);
				break;
			}
		}

	// modify the artwork config
	int tmpint = xml_get_attribute_int(&targetnode, "backdrops", -1);
	if (tmpint == 0 || tmpint == 1)
		set_backdrops_enabled(tmpint);

	tmpint = xml_get_attribute_int(&targetnode, "overlays", -1);
	if (tmpint == 0 || tmpint == 1)
		set_overlays_enabled(tmpint);

	tmpint = xml_get_attribute_int(&targetnode, "bezels", -1);
	if (tmpint == 0 || tmpint == 1)
		set_bezels_enabled(tmpint);

	tmpint = xml_get_attribute_int(&targetnode, "cpanels", -1);
	if (tmpint == 0 || tmpint == 1)
		set_cpanels_enabled(tmpint);

	tmpint = xml_get_attribute_int(&targetnode, "marquees", -1);
	if (tmpint == 0 || tmpint == 1)
		set_marquees_enabled(tmpint);

	tmpint = xml_get_attribute_int(&targetnode, "zoom", -1);
	if (tmpint == 0 || tmpint == 1)
		set_zoom_to_screen(tmpint);

	// apply orientation
	tmpint = xml_get_attribute_int(&targetnode, "rotate", -1);
	if (tmpint != -1)
	{
		if (tmpint == 90)
			tmpint = ROT90;
		else if (tmpint == 180)
			tmpint = ROT180;
		else if (tmpint == 270)
			tmpint = ROT270;
		else
			tmpint = ROT0;
		set_orientation(orientation_add(tmpint, orientation()));

		// apply the opposite orientation to the UI
		if (is_ui_target())
		{
			render_container::user_settings settings;
			render_container &ui_container = m_manager.ui_container();

			ui_container.get_user_settings(settings);
			settings.m_orientation = orientation_add(orientation_reverse(tmpint), settings.m_orientation);
			ui_container.set_user_settings(settings);
		}
	}
}


//-------------------------------------------------
//  config_save - save our configuration, or
//  return false if we are the same as the default
//-------------------------------------------------

bool render_target::config_save(xml_data_node &targetnode)
{
	bool changed = false;

	// output the basics
	xml_set_attribute_int(&targetnode, "index", index());

	// output the view
	if (m_curview != m_base_view)
	{
		xml_set_attribute(&targetnode, "view", m_curview->name());
		changed = true;
	}

	// output the layer config
	if (m_layerconfig != m_base_layerconfig)
	{
		xml_set_attribute_int(&targetnode, "backdrops", m_layerconfig.backdrops_enabled());
		xml_set_attribute_int(&targetnode, "overlays", m_layerconfig.overlays_enabled());
		xml_set_attribute_int(&targetnode, "bezels", m_layerconfig.bezels_enabled());
		xml_set_attribute_int(&targetnode, "cpanels", m_layerconfig.cpanels_enabled());
		xml_set_attribute_int(&targetnode, "marquees", m_layerconfig.marquees_enabled());
		xml_set_attribute_int(&targetnode, "zoom", m_layerconfig.zoom_to_screen());
		changed = true;
	}

	// output rotation
	if (m_orientation != m_base_orientation)
	{
		int rotate = 0;
		if (orientation_add(ROT90, m_base_orientation) == m_orientation)
			rotate = 90;
		else if (orientation_add(ROT180, m_base_orientation) == m_orientation)
			rotate = 180;
		else if (orientation_add(ROT270, m_base_orientation) == m_orientation)
			rotate = 270;
		assert(rotate != 0);
		xml_set_attribute_int(&targetnode, "rotate", rotate);
		changed = true;
	}

	return changed;
}


//-------------------------------------------------
//  init_clear_extents - reset the extents list
//-------------------------------------------------

void render_target::init_clear_extents()
{
	m_clear_extents[0] = -m_height;
	m_clear_extents[1] = 1;
	m_clear_extents[2] = m_width;
	m_clear_extent_count = 3;
}


//-------------------------------------------------
//  remove_clear_extent - remove a quad from the
//  list of stuff to clear, unless it overlaps
//  a previous quad
//-------------------------------------------------

bool render_target::remove_clear_extent(const render_bounds &bounds)
{
	INT32 *max = &m_clear_extents[MAX_CLEAR_EXTENTS];
	INT32 *last = &m_clear_extents[m_clear_extent_count];
	INT32 *ext = &m_clear_extents[0];
	INT32 boundsx0 = ceil(bounds.x0);
	INT32 boundsx1 = floor(bounds.x1);
	INT32 boundsy0 = ceil(bounds.y0);
	INT32 boundsy1 = floor(bounds.y1);
	INT32 y0, y1 = 0;

	// loop over Y extents
	while (ext < last)
	{
		INT32 *linelast;

		// first entry of each line should always be negative
		assert(ext[0] < 0.0f);
		y0 = y1;
		y1 = y0 - ext[0];

		// do we intersect this extent?
		if (boundsy0 < y1 && boundsy1 > y0)
		{
			INT32 *xext;
			INT32 x0, x1 = 0;

			// split the top
			if (y0 < boundsy0)
			{
				int diff = boundsy0 - y0;

				// make a copy of this extent
				memmove(&ext[ext[1] + 2], &ext[0], (last - ext) * sizeof(*ext));
				last += ext[1] + 2;
				assert_always(last < max, "Ran out of clear extents!\n");

				// split the extent between pieces
				ext[ext[1] + 2] = -(-ext[0] - diff);
				ext[0] = -diff;

				// advance to the new extent
				y0 -= ext[0];
				ext += ext[1] + 2;
				y1 = y0 - ext[0];
			}

			// split the bottom
			if (y1 > boundsy1)
			{
				int diff = y1 - boundsy1;

				// make a copy of this extent
				memmove(&ext[ext[1] + 2], &ext[0], (last - ext) * sizeof(*ext));
				last += ext[1] + 2;
				assert_always(last < max, "Ran out of clear extents!\n");

				// split the extent between pieces
				ext[ext[1] + 2] = -diff;
				ext[0] = -(-ext[0] - diff);

				// recompute y1
				y1 = y0 - ext[0];
			}

			// now remove the X extent
			linelast = &ext[ext[1] + 2];
			xext = &ext[2];
			while (xext < linelast)
			{
				x0 = x1;
				x1 = x0 + xext[0];

				// do we fully intersect this extent?
				if (boundsx0 >= x0 && boundsx1 <= x1)
				{
					// yes; split it
					memmove(&xext[2], &xext[0], (last - xext) * sizeof(*xext));
					last += 2;
					linelast += 2;
					assert_always(last < max, "Ran out of clear extents!\n");

					// split this extent into three parts
					xext[0] = boundsx0 - x0;
					xext[1] = boundsx1 - boundsx0;
					xext[2] = x1 - boundsx1;

					// recompute x1
					x1 = boundsx1;
					xext += 2;
				}

				// do we partially intersect this extent?
				else if (boundsx0 < x1 && boundsx1 > x0)
					goto abort;

				// advance
				xext++;

				// do we partially intersect the next extent (which is a non-clear extent)?
				if (xext < linelast)
				{
					x0 = x1;
					x1 = x0 + xext[0];
					if (boundsx0 < x1 && boundsx1 > x0)
						goto abort;
					xext++;
				}
			}

			// update the count
			ext[1] = linelast - &ext[2];
		}

		// advance to the next row
		ext += 2 + ext[1];
	}

	// update the total count
	m_clear_extent_count = last - &m_clear_extents[0];
	return true;

abort:
	// update the total count even on a failure as we may have split extents
	m_clear_extent_count = last - &m_clear_extents[0];
	return false;
}


//-------------------------------------------------
//  add_clear_extents - add the accumulated
//  extents as a series of quads to clear
//-------------------------------------------------

void render_target::add_clear_extents(render_primitive_list &list)
{
	simple_list<render_primitive> clearlist;
	INT32 *last = &m_clear_extents[m_clear_extent_count];
	INT32 *ext = &m_clear_extents[0];
	INT32 y0, y1 = 0;

	// loop over all extents
	while (ext < last)
	{
		INT32 *linelast = &ext[ext[1] + 2];
		INT32 *xext = &ext[2];
		INT32 x0, x1 = 0;

		// first entry should always be negative
		assert(ext[0] < 0);
		y0 = y1;
		y1 = y0 - ext[0];

		// now remove the X extent
		while (xext < linelast)
		{
			x0 = x1;
			x1 = x0 + *xext++;

			// only add entries for non-zero widths
			if (x1 - x0 > 0)
			{
				render_primitive *prim = list.alloc(render_primitive::QUAD);
				set_render_bounds_xy(&prim->bounds, (float)x0, (float)y0, (float)x1, (float)y1);
				set_render_color(&prim->color, 1.0f, 0.0f, 0.0f, 0.0f);
				prim->texture.base = NULL;
				prim->flags = PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA);
				clearlist.append(*prim);
			}

			// skip the non-clearing extent
			x0 = x1;
			x1 = x0 + *xext++;
		}

		// advance to the next part
		ext += 2 + ext[1];
	}

	// we know that the first primitive in the list will be the global clip
	// so we insert the clears immediately after
	list.m_primlist.prepend_list(clearlist);
}


//-------------------------------------------------
//  add_clear_and_optimize_primitive_list -
//  optimize the primitive list
//-------------------------------------------------

void render_target::add_clear_and_optimize_primitive_list(render_primitive_list &list)
{
	// start with the assumption that we need to clear the whole screen
	init_clear_extents();

	// scan the list until we hit an intersection quad or a line
	for (render_primitive *prim = list.first(); prim != NULL; prim = prim->next())
	{
		// switch off the type
		switch (prim->type)
		{
			case render_primitive::LINE:
				goto done;

			case render_primitive::QUAD:
			{
				// stop when we hit an alpha texture
				if (PRIMFLAG_GET_TEXFORMAT(prim->flags) == TEXFORMAT_ARGB32 || PRIMFLAG_GET_TEXFORMAT(prim->flags) == TEXFORMAT_PALETTEA16)
					goto done;

				// if this quad can't be cleanly removed from the extents list, we're done
				if (!remove_clear_extent(prim->bounds))
					goto done;

				// change the blendmode on the first primitive to be NONE
				if (PRIMFLAG_GET_BLENDMODE(prim->flags) == BLENDMODE_RGB_MULTIPLY)
				{
					// RGB multiply will multiply against 0, leaving nothing
					set_render_color(&prim->color, 1.0f, 0.0f, 0.0f, 0.0f);
					prim->texture.base = NULL;
					prim->flags = (prim->flags & ~PRIMFLAG_BLENDMODE_MASK) | PRIMFLAG_BLENDMODE(BLENDMODE_NONE);
				}
				else
				{
					// for alpha or add modes, we will blend against 0 or add to 0; treat it like none
					prim->flags = (prim->flags & ~PRIMFLAG_BLENDMODE_MASK) | PRIMFLAG_BLENDMODE(BLENDMODE_NONE);
				}

				// since alpha is disabled, premultiply the RGB values and reset the alpha to 1.0
				prim->color.r *= prim->color.a;
				prim->color.g *= prim->color.a;
				prim->color.b *= prim->color.a;
				prim->color.a = 1.0f;
				break;
			}

			default:
				throw emu_fatalerror("Unexpected primitive type");
		}
	}

done:
	// now add the extents to the clear list
	add_clear_extents(list);
}



//**************************************************************************
//  CORE IMPLEMENTATION
//**************************************************************************

//-------------------------------------------------
//  render_manager - constructor
//-------------------------------------------------

render_manager::render_manager(running_machine &machine)
	: m_machine(machine),
	  m_targetlist(machine.respool()),
	  m_ui_target(NULL),
	  m_live_textures(0),
	  m_texture_allocator(machine.respool()),
	  m_ui_container(auto_alloc(machine, render_container(*this))),
	  m_screen_container_list(machine.respool())
{
	// register callbacks
	config_register(machine, "video", config_saveload_delegate(FUNC(render_manager::config_load), this), config_saveload_delegate(FUNC(render_manager::config_save), this));

	// create one container per screen
	screen_device_iterator iter(machine.root_device());
	for (screen_device *screen = iter.first(); screen != NULL; screen = iter.next())
		screen->set_container(*container_alloc(screen));
}


//-------------------------------------------------
//  ~render_manager - destructor
//-------------------------------------------------

render_manager::~render_manager()
{
	// free all the containers since they may own textures
	container_free(m_ui_container);
	m_screen_container_list.reset();

	// better not be any outstanding textures when we die
	assert(m_live_textures == 0);
}


//-------------------------------------------------
//  is_live - return if the screen is 'live'
//-------------------------------------------------

bool render_manager::is_live(screen_device &screen) const
{
	// iterate over all live targets and or together their screen masks
	for (render_target *target = m_targetlist.first(); target != NULL; target = target->next())
		if (!target->hidden() && target->view_screens(target->view()).contains(screen))
			return true;
	return false;
}


//-------------------------------------------------
//  max_update_rate - return the smallest maximum
//  update rate across all targets
//-------------------------------------------------

float render_manager::max_update_rate() const
{
	// iterate over all live targets and or together their screen masks
	float minimum = 0;
	for (render_target *target = m_targetlist.first(); target != NULL; target = target->next())
		if (target->max_update_rate() != 0)
		{
			if (minimum == 0)
				minimum = target->max_update_rate();
			else
				minimum = MIN(target->max_update_rate(), minimum);
		}

	return minimum;
}


//-------------------------------------------------
//  target_alloc - allocate a new target
//-------------------------------------------------

render_target *render_manager::target_alloc(const char *layoutfile, UINT32 flags)
{
	return &m_targetlist.append(*auto_alloc(machine(), render_target(*this, layoutfile, flags)));
}


//-------------------------------------------------
//  target_free - free a target
//-------------------------------------------------

void render_manager::target_free(render_target *target)
{
	if (target != NULL)
		m_targetlist.remove(*target);
}


//-------------------------------------------------
//  target_by_index - get a render_target by index
//-------------------------------------------------

render_target *render_manager::target_by_index(int index) const
{
	// count up the targets until we hit the requested index
	for (render_target *target = m_targetlist.first(); target != NULL; target = target->next())
		if (!target->hidden())
			if (index-- == 0)
				return target;
	return NULL;
}


//-------------------------------------------------
//  ui_aspect - return the aspect ratio for UI
//  fonts
//-------------------------------------------------

float render_manager::ui_aspect()
{
	int orient = orientation_add(m_ui_target->orientation(), m_ui_container->orientation());

	// based on the orientation of the target, compute height/width or width/height
	float aspect;
	if (!(orient & ORIENTATION_SWAP_XY))
		aspect = (float)m_ui_target->height() / (float)m_ui_target->width();
	else
		aspect = (float)m_ui_target->width() / (float)m_ui_target->height();

	// if we have a valid pixel aspect, apply that and return
	if (m_ui_target->pixel_aspect() != 0.0f)
		return aspect / m_ui_target->pixel_aspect();

	// if not, clamp for extreme proportions
	if (aspect < 0.66f)
		aspect = 0.66f;
	if (aspect > 1.5f)
		aspect = 1.5f;
	return aspect;
}


//-------------------------------------------------
//  texture_alloc - allocate a new texture
//-------------------------------------------------

render_texture *render_manager::texture_alloc(texture_scaler_func scaler, void *param)
{
	// allocate a new texture and reset it
	render_texture *tex = m_texture_allocator.alloc();
	tex->reset(*this, scaler, param);
	m_live_textures++;
	return tex;
}


//-------------------------------------------------
//  texture_free - release a texture
//-------------------------------------------------

void render_manager::texture_free(render_texture *texture)
{
	if (texture != NULL)
	{
		m_live_textures--;
		texture->release();
	}
	m_texture_allocator.reclaim(texture);
}


//-------------------------------------------------
//  font_alloc - allocate a new font instance
//-------------------------------------------------

render_font *render_manager::font_alloc(const char *filename)
{
	return auto_alloc(machine(), render_font(*this, filename));
}


//-------------------------------------------------
//  font_free - release a font instance
//-------------------------------------------------

void render_manager::font_free(render_font *font)
{
	auto_free(machine(), font);
}


//-------------------------------------------------
//  invalidate_all - remove all refs to a
//  particular reference pointer
//-------------------------------------------------

void render_manager::invalidate_all(void *refptr)
{
	// permit NULL
	if (refptr == NULL)
		return;

	// loop over targets
	for (render_target *target = m_targetlist.first(); target != NULL; target = target->next())
		target->invalidate_all(refptr);
}


//-------------------------------------------------
//  container_alloc - allocate a new container
//-------------------------------------------------

render_container *render_manager::container_alloc(screen_device *screen)
{
	render_container *container = auto_alloc(machine(), render_container(*this, screen));
	if (screen != NULL)
		m_screen_container_list.append(*container);
	return container;
}


//-------------------------------------------------
//  container_free - release a container
//-------------------------------------------------

void render_manager::container_free(render_container *container)
{
	m_screen_container_list.detach(*container);
	auto_free(machine(), container);
}


//-------------------------------------------------
//  config_load - read and apply data from the
//  configuration file
//-------------------------------------------------

void render_manager::config_load(int config_type, xml_data_node *parentnode)
{
	// we only care about game files
	if (config_type != CONFIG_TYPE_GAME)
		return;

	// might not have any data
	if (parentnode == NULL)
		return;

	// check the UI target
	xml_data_node *uinode = xml_get_sibling(parentnode->child, "interface");
	if (uinode != NULL)
	{
		render_target *target = target_by_index(xml_get_attribute_int(uinode, "target", 0));
		if (target != NULL)
			set_ui_target(*target);
	}

	// iterate over target nodes
	for (xml_data_node *targetnode = xml_get_sibling(parentnode->child, "target"); targetnode; targetnode = xml_get_sibling(targetnode->next, "target"))
	{
		render_target *target = target_by_index(xml_get_attribute_int(targetnode, "index", -1));
		if (target != NULL)
			target->config_load(*targetnode);
	}

	// iterate over screen nodes
	for (xml_data_node *screennode = xml_get_sibling(parentnode->child, "screen"); screennode; screennode = xml_get_sibling(screennode->next, "screen"))
	{
		int index = xml_get_attribute_int(screennode, "index", -1);
		render_container *container = m_screen_container_list.find(index);
		render_container::user_settings settings;

		// fetch current settings
		container->get_user_settings(settings);

		// fetch color controls
		settings.m_brightness = xml_get_attribute_float(screennode, "brightness", settings.m_brightness);
		settings.m_contrast = xml_get_attribute_float(screennode, "contrast", settings.m_contrast);
		settings.m_gamma = xml_get_attribute_float(screennode, "gamma", settings.m_gamma);

		// fetch positioning controls
		settings.m_xoffset = xml_get_attribute_float(screennode, "hoffset", settings.m_xoffset);
		settings.m_xscale = xml_get_attribute_float(screennode, "hstretch", settings.m_xscale);
		settings.m_yoffset = xml_get_attribute_float(screennode, "voffset", settings.m_yoffset);
		settings.m_yscale = xml_get_attribute_float(screennode, "vstretch", settings.m_yscale);

		// set the new values
		container->set_user_settings(settings);
	}
}


//-------------------------------------------------
//  config_save - save data to the configuration
//  file
//-------------------------------------------------

void render_manager::config_save(int config_type, xml_data_node *parentnode)
{
	// we only care about game files
	if (config_type != CONFIG_TYPE_GAME)
		return;

	// write out the interface target
	if (m_ui_target->index() != 0)
	{
		// create a node for it
		xml_data_node *uinode = xml_add_child(parentnode, "interface", NULL);
		if (uinode != NULL)
			xml_set_attribute_int(uinode, "target", m_ui_target->index());
	}

	// iterate over targets
	for (int targetnum = 0; targetnum < 1000; targetnum++)
	{
		// get this target and break when we fail
		render_target *target = target_by_index(targetnum);
		if (target == NULL)
			break;

		// create a node
		xml_data_node *targetnode = xml_add_child(parentnode, "target", NULL);
		if (targetnode != NULL && !target->config_save(*targetnode))
			xml_delete_node(targetnode);
	}

	// iterate over screen containers
	int scrnum = 0;
	for (render_container *container = m_screen_container_list.first(); container != NULL; container = container->next(), scrnum++)
	{
		// create a node
		xml_data_node *screennode = xml_add_child(parentnode, "screen", NULL);
		if (screennode != NULL)
		{
			bool changed = false;

			// output the basics
			xml_set_attribute_int(screennode, "index", scrnum);

			render_container::user_settings settings;
			container->get_user_settings(settings);

			// output the color controls
			if (settings.m_brightness != machine().options().brightness())
			{
				xml_set_attribute_float(screennode, "brightness", settings.m_brightness);
				changed = true;
			}

			if (settings.m_contrast != machine().options().contrast())
			{
				xml_set_attribute_float(screennode, "contrast", settings.m_contrast);
				changed = true;
			}

			if (settings.m_gamma != machine().options().gamma())
			{
				xml_set_attribute_float(screennode, "gamma", settings.m_gamma);
				changed = true;
			}

			// output the positioning controls
			if (settings.m_xoffset != 0.0f)
			{
				xml_set_attribute_float(screennode, "hoffset", settings.m_xoffset);
				changed = true;
			}

			if (settings.m_xscale != 1.0f)
			{
				xml_set_attribute_float(screennode, "hstretch", settings.m_xscale);
				changed = true;
			}

			if (settings.m_yoffset != 0.0f)
			{
				xml_set_attribute_float(screennode, "voffset", settings.m_yoffset);
				changed = true;
			}

			if (settings.m_yscale != 1.0f)
			{
				xml_set_attribute_float(screennode, "vstretch", settings.m_yscale);
				changed = true;
			}

			// if nothing changed, kill the node
			if (!changed)
				xml_delete_node(screennode);
		}
	}
}
