// license:BSD-3-Clause
// copyright-holders:Nicola Salmoria, Aaron Giles
/*********************************************************************

    drawgfxt.ipp

    Template function implementing drawgfx core operations. Drivers
    can use these if they need custom behavior not provided by the
    existing drawgfx functions.

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

#ifndef MAME_EMU_DRAWGFXT_IPP
#define MAME_EMU_DRAWGFXT_IPP

#pragma once


/***************************************************************************
    PIXEL OPERATIONS
***************************************************************************/

/*-------------------------------------------------
    PIXEL_OP_COPY_OPAQUE - render all pixels
    regardless of pen, copying directly
-------------------------------------------------*/

#define PIXEL_OP_COPY_OPAQUE(DEST, SOURCE)                                          \
do                                                                                  \
{                                                                                   \
	(DEST) = SOURCE;                                                                \
}                                                                                   \
while (0)
#define PIXEL_OP_COPY_OPAQUE_PRIORITY(DEST, PRIORITY, SOURCE)                       \
do                                                                                  \
{                                                                                   \
	if (((1 << ((PRIORITY) & 0x1f)) & pmask) == 0)                                  \
		(DEST) = SOURCE;                                                            \
	(PRIORITY) = 31;                                                                \
}                                                                                   \
while (0)
#define PIXEL_OP_COPY_OPAQUE_PRIMASK(DEST, PRIORITY, SOURCE)                        \
do                                                                                  \
{                                                                                   \
	(DEST) = SOURCE;                                                                \
	(PRIORITY) = ((PRIORITY) & pmask) | pcode;                                      \
}                                                                                   \
while (0)

/*-------------------------------------------------
    PIXEL_OP_COPY_TRANSPEN - render all pixels
    except those matching 'transpen', copying
    directly
-------------------------------------------------*/

#define PIXEL_OP_COPY_TRANSPEN(DEST, SOURCE)                                        \
do                                                                                  \
{                                                                                   \
	u32 srcdata = (SOURCE);                                                         \
	if (srcdata != trans_pen)                                                       \
		(DEST) = SOURCE;                                                            \
}                                                                                   \
while (0)
#define PIXEL_OP_COPY_TRANSPEN_PRIORITY(DEST, PRIORITY, SOURCE)                     \
do                                                                                  \
{                                                                                   \
	u32 srcdata = (SOURCE);                                                         \
	if (srcdata != trans_pen)                                                       \
	{                                                                               \
		if (((1 << ((PRIORITY) & 0x1f)) & pmask) == 0)                              \
			(DEST) = SOURCE;                                                        \
		(PRIORITY) = 31;                                                            \
	}                                                                               \
}                                                                                   \
while (0)
#define PIXEL_OP_COPY_TRANSPEN_PRIMASK(DEST, PRIORITY, SOURCE)                      \
do                                                                                  \
{                                                                                   \
	u32 srcdata = (SOURCE);                                                         \
	if (srcdata != trans_pen)                                                       \
	{                                                                               \
		(DEST) = SOURCE;                                                            \
		(PRIORITY) = ((PRIORITY) & pmask) | pcode;                                  \
	}                                                                               \
}                                                                                   \
while (0)

/*-------------------------------------------------
    PIXEL_OP_COPY_TRANSALPHA - render all pixels
    except those with an alpha of zero, copying
    directly
-------------------------------------------------*/

#define PIXEL_OP_COPY_TRANSALPHA(DEST, SOURCE)                                      \
do                                                                                  \
{                                                                                   \
	u32 srcdata = (SOURCE);                                                         \
	if ((srcdata & 0xff000000) != 0)                                                \
		(DEST) = SOURCE;                                                            \
}                                                                                   \
while (0)
#define PIXEL_OP_COPY_TRANSALPHA_PRIORITY(DEST, PRIORITY, SOURCE)                   \
do                                                                                  \
{                                                                                   \
	u32 srcdata = (SOURCE);                                                         \
	if ((srcdata & 0xff000000) != 0)                                                \
	{                                                                               \
		if (((1 << ((PRIORITY) & 0x1f)) & pmask) == 0)                              \
			(DEST) = SOURCE;                                                        \
		(PRIORITY) = 31;                                                            \
	}                                                                               \
}                                                                                   \
while (0)
#define PIXEL_OP_COPY_TRANSALPHA_PRIMASK(DEST, PRIORITY, SOURCE)                    \
do                                                                                  \
{                                                                                   \
	u32 srcdata = (SOURCE);                                                         \
	if ((srcdata & 0xff000000) != 0)                                                \
	{                                                                               \
		(DEST) = SOURCE;                                                            \
		(PRIORITY) = ((PRIORITY) & pmask) | pcode;                                  \
	}                                                                               \
}                                                                                   \
while (0)

/*-------------------------------------------------
    PIXEL_OP_REMAP_OPAQUE - render all pixels
    regardless of pen, mapping the pen via the
    'paldata' array
-------------------------------------------------*/

#define PIXEL_OP_REMAP_OPAQUE(DEST, SOURCE)                                         \
do                                                                                  \
{                                                                                   \
	(DEST) = paldata[SOURCE];                                                       \
}                                                                                   \
while (0)
#define PIXEL_OP_REMAP_OPAQUE_PRIORITY(DEST, PRIORITY, SOURCE)                      \
do                                                                                  \
{                                                                                   \
	if (((1 << ((PRIORITY) & 0x1f)) & pmask) == 0)                                  \
		(DEST) = paldata[SOURCE];                                                   \
	(PRIORITY) = 31;                                                                \
}                                                                                   \
while (0)
#define PIXEL_OP_REMAP_OPAQUE_PRIMASK(DEST, PRIORITY, SOURCE)                       \
do                                                                                  \
{                                                                                   \
	(DEST) = paldata[SOURCE];                                                       \
	(PRIORITY) = ((PRIORITY) & pmask) | pcode;                                      \
}                                                                                   \
while (0)

/*-------------------------------------------------
    PIXEL_OP_REBASE_OPAQUE - render all pixels
    regardless of pen, adding 'color' to the
    pen value
-------------------------------------------------*/

#define PIXEL_OP_REBASE_OPAQUE(DEST, SOURCE)                                        \
do                                                                                  \
{                                                                                   \
	(DEST) = color + (SOURCE);                                                      \
}                                                                                   \
while (0)
#define PIXEL_OP_REBASE_OPAQUE_PRIORITY(DEST, PRIORITY, SOURCE)                     \
do                                                                                  \
{                                                                                   \
	if (((1 << ((PRIORITY) & 0x1f)) & pmask) == 0)                                  \
		(DEST) = color + (SOURCE);                                                  \
	(PRIORITY) = 31;                                                                \
}                                                                                   \
while (0)

/*-------------------------------------------------
    PIXEL_OP_REMAP_TRANSPEN - render all pixels
    except those matching 'trans_pen', mapping the
    pen via the 'paldata' array
-------------------------------------------------*/

#define PIXEL_OP_REMAP_TRANSPEN(DEST, SOURCE)                                       \
do                                                                                  \
{                                                                                   \
	u32 srcdata = (SOURCE);                                                         \
	if (srcdata != trans_pen)                                                       \
		(DEST) = paldata[srcdata];                                                  \
}                                                                                   \
while (0)
#define PIXEL_OP_REMAP_TRANSPEN_PRIORITY(DEST, PRIORITY, SOURCE)                    \
do                                                                                  \
{                                                                                   \
	u32 srcdata = (SOURCE);                                                         \
	if (srcdata != trans_pen)                                                       \
	{                                                                               \
		if (((1 << ((PRIORITY) & 0x1f)) & pmask) == 0)                              \
			(DEST) = paldata[srcdata];                                              \
		(PRIORITY) = 31;                                                            \
	}                                                                               \
}                                                                                   \
while (0)

/*-------------------------------------------------
    PIXEL_OP_REBASE_TRANSPEN - render all pixels
    except those matching 'transpen', adding
    'color' to the pen value
-------------------------------------------------*/

#define PIXEL_OP_REBASE_TRANSPEN(DEST, SOURCE)                                      \
do                                                                                  \
{                                                                                   \
	u32 srcdata = (SOURCE);                                                         \
	if (srcdata != trans_pen)                                                       \
		(DEST) = color + srcdata;                                                   \
}                                                                                   \
while (0)
#define PIXEL_OP_REBASE_TRANSPEN_PRIORITY(DEST, PRIORITY, SOURCE)                   \
do                                                                                  \
{                                                                                   \
	u32 srcdata = (SOURCE);                                                         \
	if (srcdata != trans_pen)                                                       \
	{                                                                               \
		if (((1 << ((PRIORITY) & 0x1f)) & pmask) == 0)                              \
			(DEST) = color + srcdata;                                               \
		(PRIORITY) = 31;                                                            \
	}                                                                               \
}                                                                                   \
while (0)

/*-------------------------------------------------
    PIXEL_OP_REMAP_TRANSMASK - render all pixels
    except those matching 'trans_mask', mapping the
    pen via the 'paldata' array
-------------------------------------------------*/

#define PIXEL_OP_REMAP_TRANSMASK(DEST, SOURCE)                                      \
do                                                                                  \
{                                                                                   \
	u32 srcdata = (SOURCE);                                                         \
	if (((trans_mask >> srcdata) & 1) == 0)                                         \
		(DEST) = paldata[srcdata];                                                  \
}                                                                                   \
while (0)
#define PIXEL_OP_REMAP_TRANSMASK_PRIORITY(DEST, PRIORITY, SOURCE)                   \
do                                                                                  \
{                                                                                   \
	u32 srcdata = (SOURCE);                                                         \
	if (((trans_mask >> srcdata) & 1) == 0)                                         \
	{                                                                               \
		if (((1 << ((PRIORITY) & 0x1f)) & pmask) == 0)                              \
			(DEST) = paldata[srcdata];                                              \
		(PRIORITY) = 31;                                                            \
	}                                                                               \
}                                                                                   \
while (0)

/*-------------------------------------------------
    PIXEL_OP_REBASE_TRANSMASK - render all pixels
    except those matching 'trans_mask', adding
    'color' to the pen value
-------------------------------------------------*/

#define PIXEL_OP_REBASE_TRANSMASK(DEST, SOURCE)                                     \
do                                                                                  \
{                                                                                   \
	u32 srcdata = (SOURCE);                                                         \
	if (((trans_mask >> srcdata) & 1) == 0)                                         \
		(DEST) = color + srcdata;                                                   \
}                                                                                   \
while (0)
#define PIXEL_OP_REBASE_TRANSMASK_PRIORITY(DEST, PRIORITY, SOURCE)                  \
do                                                                                  \
{                                                                                   \
	u32 srcdata = (SOURCE);                                                         \
	if (((trans_mask >> srcdata) & 1) == 0)                                         \
	{                                                                               \
		if (((1 << ((PRIORITY) & 0x1f)) & pmask) == 0)                              \
			(DEST) = color + srcdata;                                               \
		(PRIORITY) = 31;                                                            \
	}                                                                               \
}                                                                                   \
while (0)

/*-------------------------------------------------
    PIXEL_OP_REBASE_TRANSTABLE - look up each pen in
    'pentable'; if the entry is DRAWMODE_NONE,
    don't draw it; if the entry is DRAWMODE_SOURCE,
    add 'color' to the pen value; if the entry is
    DRAWMODE_SHADOW, generate a shadow of the
    destination pixel using 'shadowtable'

    PIXEL_OP_REMAP_TRANSTABLE - look up each pen in
    'pentable'; if the entry is DRAWMODE_NONE,
    don't draw it; if the entry is DRAWMODE_SOURCE,
    look up the pen via the 'paldata' array; if the
    entry is DRAWMODE_SHADOW, generate a shadow of
    the destination pixel using 'shadowtable'
-------------------------------------------------*/

#define PIXEL_OP_REBASE_TRANSTABLE16(DEST, SOURCE)                                  \
do                                                                                  \
{                                                                                   \
	u32 srcdata = (SOURCE);                                                         \
	u32 entry = pentable[srcdata];                                                  \
	if (entry != DRAWMODE_NONE)                                                     \
	{                                                                               \
		if (entry == DRAWMODE_SOURCE)                                               \
			(DEST) = color + srcdata;                                               \
		else                                                                        \
			(DEST) = shadowtable[DEST];                                             \
	}                                                                               \
}                                                                                   \
while (0)
#define PIXEL_OP_REMAP_TRANSTABLE32(DEST, SOURCE)                                   \
do                                                                                  \
{                                                                                   \
	u32 srcdata = (SOURCE);                                                         \
	u32 entry = pentable[srcdata];                                                  \
	if (entry != DRAWMODE_NONE)                                                     \
	{                                                                               \
		if (entry == DRAWMODE_SOURCE)                                               \
			(DEST) = paldata[srcdata];                                              \
		else                                                                        \
			(DEST) = shadowtable[rgb_t(DEST).as_rgb15()];                           \
	}                                                                               \
}                                                                                   \
while (0)
#define PIXEL_OP_REBASE_TRANSTABLE16_PRIORITY(DEST, PRIORITY, SOURCE)               \
do                                                                                  \
{                                                                                   \
	u32 srcdata = (SOURCE);                                                         \
	u32 entry = pentable[srcdata];                                                  \
	if (entry != DRAWMODE_NONE)                                                     \
	{                                                                               \
		u8 pridata = (PRIORITY);                                                    \
		if (entry == DRAWMODE_SOURCE)                                               \
		{                                                                           \
			if (((1 << (pridata & 0x1f)) & pmask) == 0)                             \
				(DEST) = color + srcdata;                                           \
			(PRIORITY) = 31;                                                        \
		}                                                                           \
		else if ((pridata & 0x80) == 0 && ((1 << (pridata & 0x1f)) & pmask) == 0)   \
		{                                                                           \
			(DEST) = shadowtable[DEST];                                             \
			(PRIORITY) = pridata | 0x80;                                            \
		}                                                                           \
	}                                                                               \
}                                                                                   \
while (0)
#define PIXEL_OP_REMAP_TRANSTABLE32_PRIORITY(DEST, PRIORITY, SOURCE)                \
do                                                                                  \
{                                                                                   \
	u32 srcdata = (SOURCE);                                                         \
	u32 entry = pentable[srcdata];                                                  \
	if (entry != DRAWMODE_NONE)                                                     \
	{                                                                               \
		u8 pridata = (PRIORITY);                                                    \
		if (entry == DRAWMODE_SOURCE)                                               \
		{                                                                           \
			if (((1 << (pridata & 0x1f)) & pmask) == 0)                             \
				(DEST) = paldata[srcdata];                                          \
			(PRIORITY) = 31;                                                        \
		}                                                                           \
		else if ((pridata & 0x80) == 0 && ((1 << (pridata & 0x1f)) & pmask) == 0)   \
		{                                                                           \
			(DEST) = shadowtable[rgb_t(DEST).as_rgb15()];                           \
			(PRIORITY) = pridata | 0x80;                                            \
		}                                                                           \
	}                                                                               \
}                                                                                   \
while (0)

/*-------------------------------------------------
    PIXEL_OP_REMAP_TRANSPEN_ALPHA - render all
    pixels except those matching 'transpen',
    mapping the pen to via the 'paldata' array;
    the resulting color is RGB alpha blended
    against the destination using 'alpha'
-------------------------------------------------*/

#define PIXEL_OP_REMAP_TRANSPEN_ALPHA32(DEST, SOURCE)                               \
do                                                                                  \
{                                                                                   \
	u32 srcdata = (SOURCE);                                                         \
	if (srcdata != trans_pen)                                                       \
		(DEST) = alpha_blend_r32((DEST), paldata[srcdata], alpha_val);              \
}                                                                                   \
while (0)
#define PIXEL_OP_REMAP_TRANSPEN_ALPHA32_PRIORITY(DEST, PRIORITY, SOURCE)            \
do                                                                                  \
{                                                                                   \
	u32 srcdata = (SOURCE);                                                         \
	if (srcdata != trans_pen)                                                       \
	{                                                                               \
		if (((1 << ((PRIORITY) & 0x1f)) & pmask) == 0)                              \
			(DEST) = alpha_blend_r32((DEST), paldata[srcdata], alpha_val);          \
		(PRIORITY) = 31;                                                            \
	}                                                                               \
}                                                                                   \
while (0)


/***************************************************************************
    BASIC DRAWGFX CORE
***************************************************************************/

/*
    Input parameters:

        bitmap_t &dest - the bitmap to render to
        const rectangle &cliprect - a clipping rectangle (assumed to be clipped to the size of 'dest')
        gfx_element *gfx - pointer to the gfx_element to render
        u32 code - index of the entry within gfx_element
        int flipx - non-zero means render right-to-left instead of left-to-right
        int flipy - non-zero means render bottom-to-top instead of top-to-bottom
        s32 destx - the top-left X coordinate to render to
        s32 desty - the top-left Y coordinate to render to
        bitmap_t &priority - the priority bitmap (if and only if priority is to be applied)
*/


template <typename BitmapType, typename FunctionClass>
inline void gfx_element::drawgfx_core(BitmapType &dest, const rectangle &cliprect, u32 code, int flipx, int flipy, s32 destx, s32 desty, FunctionClass pixel_op)
{
	g_profiler.start(PROFILER_DRAWGFX);
	do {
		assert(dest.valid());
		assert(dest.cliprect().contains(cliprect));
		assert(code < elements());

		// ignore empty/invalid cliprects
		if (cliprect.empty())
			break;

		// compute final pixel in X and exit if we are entirely clipped
		s32 destendx = destx + width() - 1;
		if (destx > cliprect.right() || destendx < cliprect.left())
			break;

		// apply left clip
		s32 srcx = 0;
		if (destx < cliprect.left())
		{
			srcx = cliprect.left() - destx;
			destx = cliprect.left();
		}

		// apply right clip
		if (destendx > cliprect.right())
			destendx = cliprect.right();

		// compute final pixel in Y and exit if we are entirely clipped
		s32 destendy = desty + height() - 1;
		if (desty > cliprect.bottom() || destendy < cliprect.top())
			break;

		// apply top clip
		s32 srcy = 0;
		if (desty < cliprect.top())
		{
			srcy = cliprect.top() - desty;
			desty = cliprect.top();
		}

		// apply bottom clip
		if (destendy > cliprect.bottom())
			destendy = cliprect.bottom();

		// apply X flipping
		if (flipx)
			srcx = width() - 1 - srcx;

		// apply Y flipping
		s32 dy = rowbytes();
		if (flipy)
		{
			srcy = height() - 1 - srcy;
			dy = -dy;
		}

		// fetch the source data
		const u8 *srcdata = get_data(code);

		// compute how many blocks of 4 pixels we have
		u32 numblocks = (destendx + 1 - destx) / 4;
		u32 leftovers = (destendx + 1 - destx) - 4 * numblocks;

		// adjust srcdata to point to the first source pixel of the row
		srcdata += srcy * rowbytes() + srcx;

		// non-flipped 8bpp case
		if (!flipx)
		{
			// iterate over pixels in Y
			for (s32 cury = desty; cury <= destendy; cury++)
			{
				auto *destptr = &dest.pix(cury, destx);
				const u8 *srcptr = srcdata;
				srcdata += dy;

				// iterate over unrolled blocks of 4
				for (s32 curx = 0; curx < numblocks; curx++)
				{
					pixel_op(destptr[0], srcptr[0]);
					pixel_op(destptr[1], srcptr[1]);
					pixel_op(destptr[2], srcptr[2]);
					pixel_op(destptr[3], srcptr[3]);

					srcptr += 4;
					destptr += 4;
				}

				// iterate over leftover pixels
				for (s32 curx = 0; curx < leftovers; curx++)
				{
					pixel_op(destptr[0], srcptr[0]);
					srcptr++;
					destptr++;
				}
			}
		}

		// flipped 8bpp case
		else
		{
			// iterate over pixels in Y
			for (s32 cury = desty; cury <= destendy; cury++)
			{
				auto *destptr = &dest.pix(cury, destx);
				const u8 *srcptr = srcdata;
				srcdata += dy;

				// iterate over unrolled blocks of 4
				for (s32 curx = 0; curx < numblocks; curx++)
				{
					pixel_op(destptr[0], srcptr[ 0]);
					pixel_op(destptr[1], srcptr[-1]);
					pixel_op(destptr[2], srcptr[-2]);
					pixel_op(destptr[3], srcptr[-3]);

					srcptr -= 4;
					destptr += 4;
				}

				// iterate over leftover pixels
				for (s32 curx = 0; curx < leftovers; curx++)
				{
					pixel_op(destptr[0], srcptr[0]);
					srcptr--;
					destptr++;
				}
			}
		}
	} while (0);
	g_profiler.stop();
}


template <typename BitmapType, typename PriorityType, typename FunctionClass>
inline void gfx_element::drawgfx_core(BitmapType &dest, const rectangle &cliprect, u32 code, int flipx, int flipy, s32 destx, s32 desty, PriorityType &priority, FunctionClass pixel_op)
{
	g_profiler.start(PROFILER_DRAWGFX);
	do {
		assert(dest.valid());
		assert(priority.valid());
		assert(dest.cliprect().contains(cliprect));
		assert(code < elements());

		// ignore empty/invalid cliprects
		if (cliprect.empty())
			break;

		// compute final pixel in X and exit if we are entirely clipped
		s32 destendx = destx + width() - 1;
		if (destx > cliprect.right() || destendx < cliprect.left())
			break;

		// apply left clip
		s32 srcx = 0;
		if (destx < cliprect.left())
		{
			srcx = cliprect.left() - destx;
			destx = cliprect.left();
		}

		// apply right clip
		if (destendx > cliprect.right())
			destendx = cliprect.right();

		// compute final pixel in Y and exit if we are entirely clipped
		s32 destendy = desty + height() - 1;
		if (desty > cliprect.bottom() || destendy < cliprect.top())
			break;

		// apply top clip
		s32 srcy = 0;
		if (desty < cliprect.top())
		{
			srcy = cliprect.top() - desty;
			desty = cliprect.top();
		}

		// apply bottom clip
		if (destendy > cliprect.bottom())
			destendy = cliprect.bottom();

		// apply X flipping
		if (flipx)
			srcx = width() - 1 - srcx;

		// apply Y flipping
		s32 dy = rowbytes();
		if (flipy)
		{
			srcy = height() - 1 - srcy;
			dy = -dy;
		}

		// fetch the source data
		const u8 *srcdata = get_data(code);

		// compute how many blocks of 4 pixels we have
		u32 numblocks = (destendx + 1 - destx) / 4;
		u32 leftovers = (destendx + 1 - destx) - 4 * numblocks;

		// adjust srcdata to point to the first source pixel of the row
		srcdata += srcy * rowbytes() + srcx;

		// non-flipped 8bpp case
		if (!flipx)
		{
			// iterate over pixels in Y
			for (s32 cury = desty; cury <= destendy; cury++)
			{
				auto *priptr = &priority.pix(cury, destx);
				auto *destptr = &dest.pix(cury, destx);
				const u8 *srcptr = srcdata;
				srcdata += dy;

				// iterate over unrolled blocks of 4
				for (s32 curx = 0; curx < numblocks; curx++)
				{
					pixel_op(destptr[0], priptr[0], srcptr[0]);
					pixel_op(destptr[1], priptr[1], srcptr[1]);
					pixel_op(destptr[2], priptr[2], srcptr[2]);
					pixel_op(destptr[3], priptr[3], srcptr[3]);

					srcptr += 4;
					destptr += 4;
					priptr += 4;
				}

				// iterate over leftover pixels
				for (s32 curx = 0; curx < leftovers; curx++)
				{
					pixel_op(destptr[0], priptr[0], srcptr[0]);
					srcptr++;
					destptr++;
					priptr++;
				}
			}
		}

		// flipped 8bpp case
		else
		{
			// iterate over pixels in Y
			for (s32 cury = desty; cury <= destendy; cury++)
			{
				auto *priptr = &priority.pix(cury, destx);
				auto *destptr = &dest.pix(cury, destx);
				const u8 *srcptr = srcdata;
				srcdata += dy;

				// iterate over unrolled blocks of 4
				for (s32 curx = 0; curx < numblocks; curx++)
				{
					pixel_op(destptr[0], priptr[0], srcptr[ 0]);
					pixel_op(destptr[1], priptr[1], srcptr[-1]);
					pixel_op(destptr[2], priptr[2], srcptr[-2]);
					pixel_op(destptr[3], priptr[3], srcptr[-3]);

					srcptr -= 4;
					destptr += 4;
					priptr += 4;
				}

				// iterate over leftover pixels
				for (s32 curx = 0; curx < leftovers; curx++)
				{
					pixel_op(destptr[0], priptr[0], srcptr[0]);
					srcptr--;
					destptr++;
					priptr++;
				}
			}
		}
	} while (0);
	g_profiler.stop();
}



/***************************************************************************
    BASIC DRAWGFXZOOM CORE
***************************************************************************/

/*
    Input parameters:

        bitmap_t &dest - the bitmap to render to
        const rectangle &cliprect - a clipping rectangle (assumed to be clipped to the size of 'dest')
        gfx_element *gfx - pointer to the gfx_element to render
        u32 code - index of the entry within gfx_element
        int flipx - non-zero means render right-to-left instead of left-to-right
        int flipy - non-zero means render bottom-to-top instead of top-to-bottom
        s32 destx - the top-left X coordinate to render to
        s32 desty - the top-left Y coordinate to render to
        u32 scalex - the 16.16 scale factor in the X dimension
        u32 scaley - the 16.16 scale factor in the Y dimension
        bitmap_t &priority - the priority bitmap (if and only if priority is to be applied)
*/


template <typename BitmapType, typename FunctionClass>
inline void gfx_element::drawgfxzoom_core(BitmapType &dest, const rectangle &cliprect, u32 code, int flipx, int flipy, s32 destx, s32 desty, u32 scalex, u32 scaley, FunctionClass pixel_op)
{
	g_profiler.start(PROFILER_DRAWGFX);
	do {
		assert(dest.valid());
		assert(dest.cliprect().contains(cliprect));

		// ignore empty/invalid cliprects
		if (cliprect.empty())
			break;

		// compute scaled size
		u32 dstwidth = (scalex * width() + 0x8000) >> 16;
		u32 dstheight = (scaley * height() + 0x8000) >> 16;
		if (dstwidth < 1 || dstheight < 1)
			break;

		// compute 16.16 source steps in dx and dy
		s32 dx = (width() << 16) / dstwidth;
		s32 dy = (height() << 16) / dstheight;

		// compute final pixel in X and exit if we are entirely clipped
		s32 destendx = destx + dstwidth - 1;
		if (destx > cliprect.right() || destendx < cliprect.left())
			break;

		// apply left clip
		s32 srcx = 0;
		if (destx < cliprect.left())
		{
			srcx = (cliprect.left() - destx) * dx;
			destx = cliprect.left();
		}

		// apply right clip
		if (destendx > cliprect.right())
			destendx = cliprect.right();

		// compute final pixel in Y and exit if we are entirely clipped
		s32 destendy = desty + dstheight - 1;
		if (desty > cliprect.bottom() || destendy < cliprect.top())
			break;

		// apply top clip
		s32 srcy = 0;
		if (desty < cliprect.top())
		{
			srcy = (cliprect.top() - desty) * dy;
			desty = cliprect.top();
		}

		// apply bottom clip
		if (destendy > cliprect.bottom())
			destendy = cliprect.bottom();

		// apply X flipping
		if (flipx)
		{
			srcx = (dstwidth - 1) * dx - srcx;
			dx = -dx;
		}

		// apply Y flipping
		if (flipy)
		{
			srcy = (dstheight - 1) * dy - srcy;
			dy = -dy;
		}

		// fetch the source data
		const u8 *srcdata = get_data(code);

		// compute how many blocks of 4 pixels we have
		u32 numblocks = (destendx + 1 - destx) / 4;
		u32 leftovers = (destendx + 1 - destx) - 4 * numblocks;

		// iterate over pixels in Y
		for (s32 cury = desty; cury <= destendy; cury++)
		{
			auto *destptr = &dest.pix(cury, destx);
			const u8 *srcptr = srcdata + (srcy >> 16) * rowbytes();
			s32 cursrcx = srcx;
			srcy += dy;

			// iterate over unrolled blocks of 4
			for (s32 curx = 0; curx < numblocks; curx++)
			{
				pixel_op(destptr[0], srcptr[cursrcx >> 16]);
				cursrcx += dx;
				pixel_op(destptr[1], srcptr[cursrcx >> 16]);
				cursrcx += dx;
				pixel_op(destptr[2], srcptr[cursrcx >> 16]);
				cursrcx += dx;
				pixel_op(destptr[3], srcptr[cursrcx >> 16]);
				cursrcx += dx;

				destptr += 4;
			}

			// iterate over leftover pixels
			for (s32 curx = 0; curx < leftovers; curx++)
			{
				pixel_op(destptr[0], srcptr[cursrcx >> 16]);
				cursrcx += dx;
				destptr++;
			}
		}
	} while (0);
	g_profiler.stop();
}


template <typename BitmapType, typename PriorityType, typename FunctionClass>
inline void gfx_element::drawgfxzoom_core(BitmapType &dest, const rectangle &cliprect, u32 code, int flipx, int flipy, s32 destx, s32 desty, u32 scalex, u32 scaley, PriorityType &priority, FunctionClass pixel_op)
{
	g_profiler.start(PROFILER_DRAWGFX);
	do {
		assert(dest.valid());
		assert(priority.valid());
		assert(dest.cliprect().contains(cliprect));

		// ignore empty/invalid cliprects
		if (cliprect.empty())
			break;

		// compute scaled size
		u32 dstwidth = (scalex * width() + 0x8000) >> 16;
		u32 dstheight = (scaley * height() + 0x8000) >> 16;
		if (dstwidth < 1 || dstheight < 1)
			break;

		// compute 16.16 source steps in dx and dy
		s32 dx = (width() << 16) / dstwidth;
		s32 dy = (height() << 16) / dstheight;

		// compute final pixel in X and exit if we are entirely clipped
		s32 destendx = destx + dstwidth - 1;
		if (destx > cliprect.right() || destendx < cliprect.left())
			break;

		// apply left clip
		s32 srcx = 0;
		if (destx < cliprect.left())
		{
			srcx = (cliprect.left() - destx) * dx;
			destx = cliprect.left();
		}

		// apply right clip
		if (destendx > cliprect.right())
			destendx = cliprect.right();

		// compute final pixel in Y and exit if we are entirely clipped
		s32 destendy = desty + dstheight - 1;
		if (desty > cliprect.bottom() || destendy < cliprect.top())
		{
			g_profiler.stop();
			return;
		}

		// apply top clip
		s32 srcy = 0;
		if (desty < cliprect.top())
		{
			srcy = (cliprect.top() - desty) * dy;
			desty = cliprect.top();
		}

		// apply bottom clip
		if (destendy > cliprect.bottom())
			destendy = cliprect.bottom();

		// apply X flipping
		if (flipx)
		{
			srcx = (dstwidth - 1) * dx - srcx;
			dx = -dx;
		}

		// apply Y flipping
		if (flipy)
		{
			srcy = (dstheight - 1) * dy - srcy;
			dy = -dy;
		}

		// fetch the source data
		const u8 *srcdata = get_data(code);

		// compute how many blocks of 4 pixels we have
		u32 numblocks = (destendx + 1 - destx) / 4;
		u32 leftovers = (destendx + 1 - destx) - 4 * numblocks;

		// iterate over pixels in Y
		for (s32 cury = desty; cury <= destendy; cury++)
		{
			auto *priptr = &priority.pix(cury, destx);
			auto *destptr = &dest.pix(cury, destx);
			const u8 *srcptr = srcdata + (srcy >> 16) * rowbytes();
			s32 cursrcx = srcx;
			srcy += dy;

			// iterate over unrolled blocks of 4
			for (s32 curx = 0; curx < numblocks; curx++)
			{
				pixel_op(destptr[0], priptr[0], srcptr[cursrcx >> 16]);
				cursrcx += dx;
				pixel_op(destptr[1], priptr[1], srcptr[cursrcx >> 16]);
				cursrcx += dx;
				pixel_op(destptr[2], priptr[2], srcptr[cursrcx >> 16]);
				cursrcx += dx;
				pixel_op(destptr[3], priptr[3], srcptr[cursrcx >> 16]);
				cursrcx += dx;

				destptr += 4;
				priptr += 4;
			}

			// iterate over leftover pixels
			for (s32 curx = 0; curx < leftovers; curx++)
			{
				pixel_op(destptr[0], priptr[0], srcptr[cursrcx >> 16]);
				cursrcx += dx;
				destptr++;
				priptr++;
			}
		}
	} while (0);
	g_profiler.stop();
}



/***************************************************************************
    BASIC COPYBITMAP CORE
***************************************************************************/

/*
    Input parameters:

        bitmap_t &dest - the bitmap to copy to
        bitmap_t &src - the bitmap to copy from (must be same bpp as dest)
        const rectangle &cliprect - a clipping rectangle (assumed to be clipped to the size of 'dest')
        int flipx - non-zero means render right-to-left instead of left-to-right
        int flipy - non-zero means render bottom-to-top instead of top-to-bottom
        s32 destx - the top-left X coordinate to copy to
        s32 desty - the top-left Y coordinate to copy to
        bitmap_t &priority - the priority bitmap (if and only if priority is to be applied)
*/

template <typename BitmapType, typename FunctionClass>
inline void copybitmap_core(BitmapType &dest, const BitmapType &src, int flipx, int flipy, s32 destx, s32 desty, const rectangle &cliprect, FunctionClass pixel_op)
{
	g_profiler.start(PROFILER_COPYBITMAP);
	do {
		assert(dest.valid());
		assert(src.valid());
		assert(dest.cliprect().contains(cliprect));

		// ignore empty/invalid cliprects
		if (cliprect.empty())
			break;

		// standard setup; dx counts bytes in X, dy counts pixels in Y
		s32 dx = 1;
		s32 dy = src.rowpixels();

		// compute final pixel in X and exit if we are entirely clipped
		s32 destendx = destx + src.width() - 1;
		if (destx > cliprect.right() || destendx < cliprect.left())
			break;

		// apply left clip
		s32 srcx = 0;
		if (destx < cliprect.left())
		{
			srcx = cliprect.left() - destx;
			destx = cliprect.left();
		}

		// apply right clip
		if (destendx > cliprect.right())
			destendx = cliprect.right();

		// compute final pixel in Y and exit if we are entirely clipped
		s32 destendy = desty + src.height() - 1;
		if (desty > cliprect.bottom() || destendy < cliprect.top())
			break;

		// apply top clip
		s32 srcy = 0;
		if (desty < cliprect.top())
		{
			srcy = cliprect.top() - desty;
			desty = cliprect.top();
		}

		// apply bottom clip
		if (destendy > cliprect.bottom())
			destendy = cliprect.bottom();

		// apply X flipping
		if (flipx)
		{
			srcx = src.width() - 1 - srcx;
			dx = -dx;
		}

		// apply Y flipping
		if (flipy)
		{
			srcy = src.height() - 1 - srcy;
			dy = -dy;
		}

		// compute how many blocks of 4 pixels we have
		u32 numblocks = (destendx + 1 - destx) / 4;
		u32 leftovers = (destendx + 1 - destx) - 4 * numblocks;

		// compute the address of the first source pixel of the first row
		const auto *srcdata = &src.pix(srcy, srcx);

		// non-flipped case
		if (!flipx)
		{
			// iterate over pixels in Y
			for (s32 cury = desty; cury <= destendy; cury++)
			{
				auto *destptr = &dest.pix(cury, destx);
				const auto *srcptr = srcdata;
				srcdata += dy;

				// iterate over unrolled blocks of 4
				for (s32 curx = 0; curx < numblocks; curx++)
				{
					pixel_op(destptr[0], srcptr[0]);
					pixel_op(destptr[1], srcptr[1]);
					pixel_op(destptr[2], srcptr[2]);
					pixel_op(destptr[3], srcptr[3]);

					srcptr += 4;
					destptr += 4;
				}

				// iterate over leftover pixels
				for (s32 curx = 0; curx < leftovers; curx++)
				{
					pixel_op(destptr[0], srcptr[0]);
					srcptr++;
					destptr++;
				}
			}
		}

		// flipped case
		else
		{
			// iterate over pixels in Y
			for (s32 cury = desty; cury <= destendy; cury++)
			{
				auto *destptr = &dest.pix(cury, destx);
				const auto *srcptr = srcdata;
				srcdata += dy;

				// iterate over unrolled blocks of 4
				for (s32 curx = 0; curx < numblocks; curx++)
				{
					pixel_op(destptr[0], srcptr[ 0]);
					pixel_op(destptr[1], srcptr[-1]);
					pixel_op(destptr[2], srcptr[-2]);
					pixel_op(destptr[3], srcptr[-3]);

					srcptr -= 4;
					destptr += 4;
				}

				// iterate over leftover pixels
				for (s32 curx = 0; curx < leftovers; curx++)
				{
					pixel_op(destptr[0], srcptr[0]);
					srcptr--;
					destptr++;
				}
			}
		}
	} while (0);
	g_profiler.stop();
}


template <typename BitmapType, typename PriorityType, typename FunctionClass>
inline void copybitmap_core(BitmapType &dest, const BitmapType &src, int flipx, int flipy, s32 destx, s32 desty, const rectangle &cliprect, PriorityType &priority, FunctionClass pixel_op)
{
	g_profiler.start(PROFILER_COPYBITMAP);
	do {
		assert(dest.valid());
		assert(src.valid());
		assert(priority.valid());
		assert(dest.cliprect().contains(cliprect));

		// ignore empty/invalid cliprects
		if (cliprect.empty())
			break;

		// standard setup; dx counts bytes in X, dy counts pixels in Y
		s32 dx = 1;
		s32 dy = src.rowpixels();

		// compute final pixel in X and exit if we are entirely clipped
		s32 destendx = destx + src.width() - 1;
		if (destx > cliprect.right() || destendx < cliprect.left())
			break;

		// apply left clip
		s32 srcx = 0;
		if (destx < cliprect.left())
		{
			srcx = cliprect.left() - destx;
			destx = cliprect.left();
		}

		// apply right clip
		if (destendx > cliprect.right())
			destendx = cliprect.right();

		// compute final pixel in Y and exit if we are entirely clipped
		s32 destendy = desty + src.height() - 1;
		if (desty > cliprect.bottom() || destendy < cliprect.top())
			break;

		// apply top clip
		s32 srcy = 0;
		if (desty < cliprect.top())
		{
			srcy = cliprect.top() - desty;
			desty = cliprect.top();
		}

		// apply bottom clip
		if (destendy > cliprect.bottom())
			destendy = cliprect.bottom();

		// apply X flipping
		if (flipx)
		{
			srcx = src.width() - 1 - srcx;
			dx = -dx;
		}

		// apply Y flipping
		if (flipy)
		{
			srcy = src.height() - 1 - srcy;
			dy = -dy;
		}

		// compute how many blocks of 4 pixels we have
		u32 numblocks = (destendx + 1 - destx) / 4;
		u32 leftovers = (destendx + 1 - destx) - 4 * numblocks;

		// compute the address of the first source pixel of the first row
		const auto *srcdata = &src.pix(srcy, srcx);

		// non-flipped case
		if (!flipx)
		{
			// iterate over pixels in Y
			for (s32 cury = desty; cury <= destendy; cury++)
			{
				auto *priptr = &priority.pix(cury, destx);
				auto *destptr = &dest.pix(cury, destx);
				const auto *srcptr = srcdata;
				srcdata += dy;

				// iterate over unrolled blocks of 4
				for (s32 curx = 0; curx < numblocks; curx++)
				{
					pixel_op(destptr[0], priptr[0], srcptr[0]);
					pixel_op(destptr[1], priptr[1], srcptr[1]);
					pixel_op(destptr[2], priptr[2], srcptr[2]);
					pixel_op(destptr[3], priptr[3], srcptr[3]);

					srcptr += 4;
					destptr += 4;
					priptr += 4;
				}

				// iterate over leftover pixels
				for (s32 curx = 0; curx < leftovers; curx++)
				{
					pixel_op(destptr[0], priptr[0], srcptr[0]);
					srcptr++;
					destptr++;
					priptr++;
				}
			}
		}

		// flipped case
		else
		{
			// iterate over pixels in Y
			for (s32 cury = desty; cury <= destendy; cury++)
			{
				auto *priptr = &priority.pix(cury, destx);
				auto *destptr = &dest.pix(cury, destx);
				const auto *srcptr = srcdata;
				srcdata += dy;

				// iterate over unrolled blocks of 4
				for (s32 curx = 0; curx < numblocks; curx++)
				{
					pixel_op(destptr[0], priptr[0], srcptr[ 0]);
					pixel_op(destptr[1], priptr[1], srcptr[-1]);
					pixel_op(destptr[2], priptr[2], srcptr[-2]);
					pixel_op(destptr[3], priptr[3], srcptr[-3]);

					srcptr -= 4;
					destptr += 4;
					priptr += 4;
				}

				// iterate over leftover pixels
				for (s32 curx = 0; curx < leftovers; curx++)
				{
					pixel_op(destptr[0], priptr[0], srcptr[0]);
					srcptr--;
					destptr++;
					priptr++;
				}
			}
		}
	} while (0);
	g_profiler.stop();
}



/***************************************************************************
    BASIC COPYROZBITMAP CORE
***************************************************************************/

/*
    Input parameters:

        bitmap_t &dest - the bitmap to copy to
        bitmap_t &src - the bitmap to copy from (must be same bpp as dest)
        const rectangle &cliprect - a clipping rectangle (assumed to be clipped to the size of 'dest')
        s32 destx - the 16.16 source X position at destination pixel (0,0)
        s32 desty - the 16.16 source Y position at destination pixel (0,0)
        s32 incxx - the 16.16 amount to increment in source X for each destination X pixel
        s32 incyx - the 16.16 amount to increment in source Y for each destination X pixel
        s32 incxy - the 16.16 amount to increment in source X for each destination Y pixel
        s32 incyy - the 16.16 amount to increment in source Y for each destination Y pixel
        bool wraparound - true means wrap when hitting the edges of the source
        bitmap_t &priority - the priority bitmap (if and only if priority is to be applied)
*/

template <typename BitmapType, typename FunctionClass>
inline void copyrozbitmap_core(BitmapType &dest, const rectangle &cliprect, const BitmapType &src, s32 startx, s32 starty, s32 incxx, s32 incxy, s32 incyx, s32 incyy, bool wraparound, FunctionClass pixel_op)
{
	g_profiler.start(PROFILER_COPYBITMAP);

	assert(dest.valid());
	assert(dest.valid());
	assert(dest.cliprect().contains(cliprect));
	assert(!wraparound || (src.width() & (src.width() - 1)) == 0);
	assert(!wraparound || (src.height() & (src.height() - 1)) == 0);

	// ignore empty/invalid cliprects
	if (cliprect.empty())
	{
		g_profiler.stop();
		return;
	}

	// compute fixed-point 16.16 size of the source bitmap
	u32 srcfixwidth = src.width() << 16;
	u32 srcfixheight = src.height() << 16;

	// advance the starting coordinates to the top-left of the cliprect
	startx += cliprect.left() * incxx + cliprect.top() * incyx;
	starty += cliprect.left() * incxy + cliprect.top() * incyy;

	// compute how many blocks of 4 pixels we have
	u32 numblocks = cliprect.width() / 4;
	u32 leftovers = cliprect.width() - 4 * numblocks;

	// if incxy and incyx are 0, then we aren't rotating, just zooming
	if (incxy == 0 && incyx == 0)
	{
		// zoom-only, non-wraparound case
		if (!wraparound)
		{
			// iterate over pixels in Y
			for (s32 cury = cliprect.top(); cury <= cliprect.bottom(); cury++)
			{
				auto *destptr = &dest.pix(cury, cliprect.left());
				s32 srcx = startx;
				s32 srcy = starty;

				starty += incyy;

				// check srcy for the whole row at once
				if (u32(srcy) < srcfixheight)
				{
					const auto *srcptr = &src.pix(srcy >> 16);

					// iterate over unrolled blocks of 4
					for (s32 curx = 0; curx < numblocks; curx++)
					{
						if (u32(srcx) < srcfixwidth)
							pixel_op(destptr[0], srcptr[srcx >> 16]);
						srcx += incxx;

						if (u32(srcx) < srcfixwidth)
							pixel_op(destptr[1], srcptr[srcx >> 16]);
						srcx += incxx;

						if (u32(srcx) < srcfixwidth)
							pixel_op(destptr[2], srcptr[srcx >> 16]);
						srcx += incxx;

						if (u32(srcx) < srcfixwidth)
							pixel_op(destptr[3], srcptr[srcx >> 16]);
						srcx += incxx;

						destptr += 4;
					}

					// iterate over leftover pixels
					for (s32 curx = 0; curx < leftovers; curx++)
					{
						if (u32(srcx) < srcfixwidth)
							pixel_op(destptr[0], srcptr[srcx >> 16]);
						srcx += incxx;
						destptr++;
					}
				}
			}
		}

		// zoom-only, wraparound case
		else
		{
			// convert srcfixwidth/height into a mask and apply
			srcfixwidth--;
			srcfixheight--;
			startx &= srcfixwidth;
			starty &= srcfixheight;

			// iterate over pixels in Y
			for (s32 cury = cliprect.top(); cury <= cliprect.bottom(); cury++)
			{
				auto *destptr = &dest.pix(cury, cliprect.left());
				const auto *srcptr = &src.pix(starty >> 16);
				s32 srcx = startx;

				starty = (starty + incyy) & srcfixheight;

				// iterate over unrolled blocks of 4
				for (s32 curx = 0; curx < numblocks; curx++)
				{
					pixel_op(destptr[0], srcptr[srcx >> 16]);
					srcx = (srcx + incxx) & srcfixwidth;

					pixel_op(destptr[1], srcptr[srcx >> 16]);
					srcx = (srcx + incxx) & srcfixwidth;

					pixel_op(destptr[2], srcptr[srcx >> 16]);
					srcx = (srcx + incxx) & srcfixwidth;

					pixel_op(destptr[3], srcptr[srcx >> 16]);
					srcx = (srcx + incxx) & srcfixwidth;

					destptr += 4;
				}

				// iterate over leftover pixels
				for (s32 curx = 0; curx < leftovers; curx++)
				{
					pixel_op(destptr[0], srcptr[srcx >> 16]);
					srcx = (srcx + incxx) & srcfixwidth;
					destptr++;
				}
			}
		}
	}

	// full rotation case
	else
	{
		// full rotation, non-wraparound case
		if (!wraparound)
		{
			// iterate over pixels in Y
			for (s32 cury = cliprect.top(); cury <= cliprect.bottom(); cury++)
			{
				auto *destptr = &dest.pix(cury, cliprect.left());
				s32 srcx = startx;
				s32 srcy = starty;

				startx += incyx;
				starty += incyy;

				// iterate over unrolled blocks of 4
				for (s32 curx = 0; curx < numblocks; curx++)
				{
					if (u32(srcx) < srcfixwidth && u32(srcy) < srcfixheight)
					{
						const auto *srcptr = &src.pix(srcy >> 16, srcx >> 16);
						pixel_op(destptr[0], srcptr[0]);
					}
					srcx += incxx;
					srcy += incxy;

					if (u32(srcx) < srcfixwidth && u32(srcy) < srcfixheight)
					{
						const auto *srcptr = &src.pix(srcy >> 16, srcx >> 16);
						pixel_op(destptr[1], srcptr[0]);
					}
					srcx += incxx;
					srcy += incxy;

					if (u32(srcx) < srcfixwidth && u32(srcy) < srcfixheight)
					{
						const auto *srcptr = &src.pix(srcy >> 16, srcx >> 16);
						pixel_op(destptr[2], srcptr[0]);
					}
					srcx += incxx;
					srcy += incxy;

					if (u32(srcx) < srcfixwidth && u32(srcy) < srcfixheight)
					{
						const auto *srcptr = &src.pix(srcy >> 16, srcx >> 16);
						pixel_op(destptr[3], srcptr[0]);
					}
					srcx += incxx;
					srcy += incxy;

					destptr += 4;
				}

				// iterate over leftover pixels
				for (s32 curx = 0; curx < leftovers; curx++)
				{
					if (u32(srcx) < srcfixwidth && u32(srcy) < srcfixheight)
					{
						const auto *srcptr = &src.pix(srcy >> 16, srcx >> 16);
						pixel_op(destptr[0], srcptr[0]);
					}
					srcx += incxx;
					srcy += incxy;
					destptr++;
				}
			}
		}

		// zoom-only, wraparound case
		else
		{
			// convert srcfixwidth/height into a mask and apply
			srcfixwidth--;
			srcfixheight--;
			startx &= srcfixwidth;
			starty &= srcfixheight;

			// iterate over pixels in Y
			for (s32 cury = cliprect.top(); cury <= cliprect.bottom(); cury++)
			{
				auto *destptr = &dest.pix(cury, cliprect.left());
				s32 srcx = startx;
				s32 srcy = starty;

				startx = (startx + incyx) & srcfixwidth;
				starty = (starty + incyy) & srcfixheight;

				// iterate over unrolled blocks of 4
				for (s32 curx = 0; curx < numblocks; curx++)
				{
					const auto *srcptr = &src.pix(srcy >> 16, srcx >> 16);
					pixel_op(destptr[0], srcptr[0]);
					srcx = (srcx + incxx) & srcfixwidth;
					srcy = (srcy + incxy) & srcfixheight;

					srcptr = &src.pix(srcy >> 16, srcx >> 16);
					pixel_op(destptr[1], srcptr[0]);
					srcx = (srcx + incxx) & srcfixwidth;
					srcy = (srcy + incxy) & srcfixheight;

					srcptr = &src.pix(srcy >> 16, srcx >> 16);
					pixel_op(destptr[2], srcptr[0]);
					srcx = (srcx + incxx) & srcfixwidth;
					srcy = (srcy + incxy) & srcfixheight;

					srcptr = &src.pix(srcy >> 16, srcx >> 16);
					pixel_op(destptr[3], srcptr[0]);
					srcx = (srcx + incxx) & srcfixwidth;
					srcy = (srcy + incxy) & srcfixheight;

					destptr += 4;
				}

				// iterate over leftover pixels
				for (s32 curx = 0; curx < leftovers; curx++)
				{
					const auto *srcptr = &src.pix(srcy >> 16, srcx >> 16);
					pixel_op(destptr[0], srcptr[0]);
					srcx = (srcx + incxx) & srcfixwidth;
					srcy = (srcy + incxy) & srcfixheight;
					destptr++;
				}
			}
		}
	}
	g_profiler.stop();
}


template <typename BitmapType, typename PriorityType, typename FunctionClass>
inline void copyrozbitmap_core(BitmapType &dest, const rectangle &cliprect, const BitmapType &src, s32 startx, s32 starty, s32 incxx, s32 incxy, s32 incyx, s32 incyy, bool wraparound, PriorityType &priority, FunctionClass pixel_op)
{
	g_profiler.start(PROFILER_COPYBITMAP);

	assert(dest.valid());
	assert(dest.valid());
	assert(priority.valid());
	assert(dest.cliprect().contains(cliprect));
	assert(!wraparound || (src.width() & (src.width() - 1)) == 0);
	assert(!wraparound || (src.height() & (src.height() - 1)) == 0);

	// ignore empty/invalid cliprects
	if (cliprect.empty())
	{
		g_profiler.stop();
		return;
	}

	// compute fixed-point 16.16 size of the source bitmap
	u32 srcfixwidth = src.width() << 16;
	u32 srcfixheight = src.height() << 16;

	// advance the starting coordinates to the top-left of the cliprect
	startx += cliprect.left() * incxx + cliprect.top() * incyx;
	starty += cliprect.left() * incxy + cliprect.top() * incyy;

	// compute how many blocks of 4 pixels we have
	u32 numblocks = cliprect.width() / 4;
	u32 leftovers = cliprect.width() - 4 * numblocks;

	// if incxy and incyx are 0, then we aren't rotating, just zooming
	if (incxy == 0 && incyx == 0)
	{
		// zoom-only, non-wraparound case
		if (!wraparound)
		{
			// iterate over pixels in Y
			for (s32 cury = cliprect.top(); cury <= cliprect.bottom(); cury++)
			{
				auto *priptr = &priority.pix(cury, cliprect.left());
				auto *destptr = &dest.pix(cury, cliprect.left());
				s32 srcx = startx;
				s32 srcy = starty;

				starty += incyy;

				// check srcy for the whole row at once
				if (u32(srcy) < srcfixheight)
				{
					const auto *srcptr = &src.pix(srcy >> 16);

					// iterate over unrolled blocks of 4
					for (s32 curx = 0; curx < numblocks; curx++)
					{
						if (u32(srcx) < srcfixwidth)
							pixel_op(destptr[0], priptr[0], srcptr[srcx >> 16]);
						srcx += incxx;

						if (u32(srcx) < srcfixwidth)
							pixel_op(destptr[1], priptr[1], srcptr[srcx >> 16]);
						srcx += incxx;

						if (u32(srcx) < srcfixwidth)
							pixel_op(destptr[2], priptr[2], srcptr[srcx >> 16]);
						srcx += incxx;

						if (u32(srcx) < srcfixwidth)
							pixel_op(destptr[3], priptr[3], srcptr[srcx >> 16]);
						srcx += incxx;

						destptr += 4;
						priptr += 4;
					}

					// iterate over leftover pixels
					for (s32 curx = 0; curx < leftovers; curx++)
					{
						if (u32(srcx) < srcfixwidth)
							pixel_op(destptr[0], priptr[0], srcptr[srcx >> 16]);
						srcx += incxx;
						destptr++;
						priptr++;
					}
				}
			}
		}

		// zoom-only, wraparound case
		else
		{
			// convert srcfixwidth/height into a mask and apply
			srcfixwidth--;
			srcfixheight--;
			startx &= srcfixwidth;
			starty &= srcfixheight;

			// iterate over pixels in Y
			for (s32 cury = cliprect.top(); cury <= cliprect.bottom(); cury++)
			{
				auto *priptr = &priority.pix(cury, cliprect.left());
				auto *destptr = &dest.pix(cury, cliprect.left());
				const auto *srcptr = &src.pix(starty >> 16);
				s32 srcx = startx;

				starty = (starty + incyy) & srcfixheight;

				// iterate over unrolled blocks of 4
				for (s32 curx = 0; curx < numblocks; curx++)
				{
					pixel_op(destptr[0], priptr[0], srcptr[srcx >> 16]);
					srcx = (srcx + incxx) & srcfixwidth;

					pixel_op(destptr[1], priptr[1], srcptr[srcx >> 16]);
					srcx = (srcx + incxx) & srcfixwidth;

					pixel_op(destptr[2], priptr[2], srcptr[srcx >> 16]);
					srcx = (srcx + incxx) & srcfixwidth;

					pixel_op(destptr[3], priptr[3], srcptr[srcx >> 16]);
					srcx = (srcx + incxx) & srcfixwidth;

					destptr += 4;
					priptr += 4;
				}

				// iterate over leftover pixels
				for (s32 curx = 0; curx < leftovers; curx++)
				{
					pixel_op(destptr[0], priptr[0], srcptr[srcx >> 16]);
					srcx = (srcx + incxx) & srcfixwidth;
					destptr++;
					priptr++;
				}
			}
		}
	}

	// full rotation case
	else
	{
		// full rotation, non-wraparound case
		if (!wraparound)
		{
			// iterate over pixels in Y
			for (s32 cury = cliprect.top(); cury <= cliprect.bottom(); cury++)
			{
				auto *priptr = &priority.pix(cury, cliprect.left());
				auto *destptr = &dest.pix(cury, cliprect.left());
				s32 srcx = startx;
				s32 srcy = starty;

				startx += incyx;
				starty += incyy;

				// iterate over unrolled blocks of 4
				for (s32 curx = 0; curx < numblocks; curx++)
				{
					if (u32(srcx) < srcfixwidth && u32(srcy) < srcfixheight)
					{
						const auto *srcptr = &src.pix(srcy >> 16, srcx >> 16);
						pixel_op(destptr[0], priptr[0], srcptr[0]);
					}
					srcx += incxx;
					srcy += incxy;

					if (u32(srcx) < srcfixwidth && u32(srcy) < srcfixheight)
					{
						const auto *srcptr = &src.pix(srcy >> 16, srcx >> 16);
						pixel_op(destptr[1], priptr[1], srcptr[0]);
					}
					srcx += incxx;
					srcy += incxy;

					if (u32(srcx) < srcfixwidth && u32(srcy) < srcfixheight)
					{
						const auto *srcptr = &src.pix(srcy >> 16, srcx >> 16);
						pixel_op(destptr[2], priptr[2], srcptr[0]);
					}
					srcx += incxx;
					srcy += incxy;

					if (u32(srcx) < srcfixwidth && u32(srcy) < srcfixheight)
					{
						const auto *srcptr = &src.pix(srcy >> 16, srcx >> 16);
						pixel_op(destptr[3], priptr[3], srcptr[0]);
					}
					srcx += incxx;
					srcy += incxy;

					destptr += 4;
					priptr += 4;
				}

				// iterate over leftover pixels
				for (s32 curx = 0; curx < leftovers; curx++)
				{
					if (u32(srcx) < srcfixwidth && u32(srcy) < srcfixheight)
					{
						const auto *srcptr = &src.pix(srcy >> 16, srcx >> 16);
						pixel_op(destptr[0], priptr[0], srcptr[0]);
					}
					srcx += incxx;
					srcy += incxy;
					destptr++;
					priptr++;
				}
			}
		}

		// zoom-only, wraparound case
		else
		{
			// convert srcfixwidth/height into a mask and apply
			srcfixwidth--;
			srcfixheight--;
			startx &= srcfixwidth;
			starty &= srcfixheight;

			// iterate over pixels in Y
			for (s32 cury = cliprect.top(); cury <= cliprect.bottom(); cury++)
			{
				auto *priptr = &priority.pix(cury, cliprect.left());
				auto *destptr = &dest.pix(cury, cliprect.left());
				s32 srcx = startx;
				s32 srcy = starty;

				startx = (startx + incyx) & srcfixwidth;
				starty = (starty + incyy) & srcfixheight;

				// iterate over unrolled blocks of 4
				for (s32 curx = 0; curx < numblocks; curx++)
				{
					const auto *srcptr = &src.pix(srcy >> 16, srcx >> 16);
					pixel_op(destptr[0], priptr[0], srcptr[0]);
					srcx = (srcx + incxx) & srcfixwidth;
					srcy = (srcy + incxy) & srcfixheight;

					srcptr = &src.pix(srcy >> 16, srcx >> 16);
					pixel_op(destptr[1], priptr[1], srcptr[0]);
					srcx = (srcx + incxx) & srcfixwidth;
					srcy = (srcy + incxy) & srcfixheight;

					srcptr = &src.pix(srcy >> 16, srcx >> 16);
					pixel_op(destptr[2], priptr[2], srcptr[0]);
					srcx = (srcx + incxx) & srcfixwidth;
					srcy = (srcy + incxy) & srcfixheight;

					srcptr = &src.pix(srcy >> 16, srcx >> 16);
					pixel_op(destptr[3], priptr[3], srcptr[0]);
					srcx = (srcx + incxx) & srcfixwidth;
					srcy = (srcy + incxy) & srcfixheight;

					destptr += 4;
					priptr += 4;
				}

				// iterate over leftover pixels
				for (s32 curx = 0; curx < leftovers; curx++)
				{
					const auto *srcptr = &src.pix(srcy >> 16, srcx >> 16);
					pixel_op(destptr[0], priptr[0], srcptr[0]);
					srcx = (srcx + incxx) & srcfixwidth;
					srcy = (srcy + incxy) & srcfixheight;
					destptr++;
					priptr++;
				}
			}
		}
	}
	g_profiler.stop();
}



/***************************************************************************
    BASIC DRAWSCANLINE CORE
***************************************************************************/

/*
    Input parameters:

        bitmap_t &bitmap - the bitmap to copy to
        s32 destx - the X coordinate to copy to
        s32 desty - the Y coordinate to copy to
        s32 length - the total number of pixels to copy
        const UINTx *srcptr - pointer to memory containing the source pixels
        bitmap_t &priority - the priority bitmap (if and only if priority is to be applied)
*/

template <typename BitmapType, typename SourceType, typename FunctionClass>
inline void drawscanline_core(BitmapType &bitmap, s32 destx, s32 desty, s32 length, const SourceType *srcptr, FunctionClass pixel_op)
{
	assert(bitmap.valid());
	assert(destx >= 0);
	assert(destx + length <= bitmap.width());
	assert(desty >= 0);
	assert(desty < bitmap.height());
	assert(srcptr != nullptr);

	auto *destptr = &bitmap.pix(desty, destx);

	// iterate over unrolled blocks of 4
	while (length >= 4)
	{
		pixel_op(destptr[0], srcptr[0]);
		pixel_op(destptr[1], srcptr[1]);
		pixel_op(destptr[2], srcptr[2]);
		pixel_op(destptr[3], srcptr[3]);

		length -= 4;
		srcptr += 4;
		destptr += 4;
	}

	// iterate over leftover pixels
	while (length-- > 0)
	{
		pixel_op(destptr[0], srcptr[0]);
		srcptr++;
		destptr++;
	}
}


template <typename BitmapType, typename SourceType, typename PriorityType, typename FunctionClass>
inline void drawscanline_core(BitmapType &bitmap, s32 destx, s32 desty, s32 length, const SourceType *srcptr, PriorityType &priority, FunctionClass pixel_op)
{
	assert(bitmap.valid());
	assert(destx >= 0);
	assert(destx + length <= bitmap.width());
	assert(desty >= 0);
	assert(desty < bitmap.height());
	assert(srcptr != nullptr);
	assert(priority.valid());

	auto *priptr = &priority.pix(desty, destx);
	auto *destptr = &bitmap.pix(desty, destx);

	// iterate over unrolled blocks of 4
	while (length >= 4)
	{
		pixel_op(destptr[0], priptr[0], srcptr[0]);
		pixel_op(destptr[1], priptr[1], srcptr[1]);
		pixel_op(destptr[2], priptr[2], srcptr[2]);
		pixel_op(destptr[3], priptr[3], srcptr[3]);

		length -= 4;
		srcptr += 4;
		destptr += 4;
		priptr += 4;
	}

	// iterate over leftover pixels
	while (length-- > 0)
	{
		pixel_op(destptr[0], priptr[0], srcptr[0]);
		srcptr++;
		destptr++;
		priptr++;
	}
}


#endif  // MAME_EMU_DRAWGFXT_IPP
