//============================================================
//
//	winddraw7.c - Win32 DirectDraw code
//
//============================================================

// standard windows headers
#define WIN32_LEAN_AND_MEAN
#include <windows.h>

// undef WINNT for ddraw.h to prevent duplicate definition
#undef WINNT
#include <ddraw.h>
#define DIRECT3D_VERSION	0x0700
#include <d3d.h>

// standard C headers
#include <math.h>

// MAME headers
#include "driver.h"
#include "window.h"
#include "video.h"
#include "blit.h"



//============================================================
//	IMPORTS
//============================================================

// from input.c
extern int verbose;



//============================================================
//	DEBUGGING
//============================================================

#define SHOW_FLIP_TIMES 		0



//============================================================
//	LOCAL VARIABLES
//============================================================

// Direct3D objects
static LPDIRECT3D7 d3d;
static LPDIRECT3DDEVICE7 d3ddev;

// DirectDraw objects
static LPDIRECTDRAW7 ddraw7;
static LPDIRECTDRAWGAMMACONTROL gamma_control;
static LPDIRECTDRAWSURFACE7 primary_surface;
static LPDIRECTDRAWSURFACE7 back_surface;
static LPDIRECTDRAWSURFACE7 blit_surface;
static LPDIRECTDRAWSURFACE7 texture_surface;
static LPDIRECTDRAWSURFACE7 texture_surface2;
static LPDIRECTDRAWCLIPPER primary_clipper;

// DirectDraw object info
static DDCAPS ddraw_caps;
static D3DDEVICEDESC7 d3d_desc;
static DDSURFACEDESC2 primary_desc;
static DDSURFACEDESC2 blit_desc;
static DDSURFACEDESC2 texture_desc;
static int changed_resolutions;
static int forced_updates;

// video bounds
static int max_width;
static int max_height;
static int pref_depth;
static int effect_min_xscale;
static int effect_min_yscale;
static struct rectangle last_bounds;

// mode finding
static double best_score;
static int best_width;
static int best_height;
static int best_depth;
static int best_refresh;

// derived attributes
static int needs_6bpp_per_gun;
static int pixel_aspect_ratio;



//============================================================
//	PROTOTYPES
//============================================================

static double compute_mode_score(int width, int height, int depth, int refresh);
static int set_resolution(void);
static int create_surfaces(void);
static int create_blit_surface(void);
static int create_texture_surface(void);
static int set_renderstate(void);
static void set_brightness(void);
static int create_clipper(void);
static void erase_surfaces(void);
static void release_surfaces(void);
static void compute_color_masks(const DDSURFACEDESC2 *desc);
static int render_to_texture(struct mame_bitmap *bitmap, const struct rectangle *bounds, void *vector_dirty_pixels, int update);
static int render_to_primary(struct mame_bitmap *bitmap, const struct rectangle *bounds, void *vector_dirty_pixels, int update);
static int draw_and_flip(LPDIRECTDRAWSURFACE7 target_surface, LPRECT src, LPRECT dst, int update);



//============================================================
//	erase_outer_rect
//============================================================

INLINE void erase_outer_rect(RECT *outer, RECT *inner, LPDIRECTDRAWSURFACE7 surface)
{
	DDBLTFX blitfx = { sizeof(DDBLTFX) };
	RECT clear;

	// erase the blit surface
	blitfx.DUMMYUNIONNAMEN(5).dwFillColor = 0;

	// clear the left edge
	if (inner->left > outer->left)
	{
		clear = *outer;
		clear.right = inner->left;
		if (surface)
			IDirectDrawSurface7_Blt(surface, &clear, NULL, NULL, DDBLT_COLORFILL | DDBLT_WAIT, &blitfx);
	}

	// clear the right edge
	if (inner->right < outer->right)
	{
		clear = *outer;
		clear.left = inner->right;
		if (surface)
			IDirectDrawSurface7_Blt(surface, &clear, NULL, NULL, DDBLT_COLORFILL | DDBLT_WAIT, &blitfx);
	}

	// clear the top edge
	if (inner->top > outer->top)
	{
		clear = *outer;
		clear.bottom = inner->top;
		if (surface)
			IDirectDrawSurface7_Blt(surface, &clear, NULL, NULL, DDBLT_COLORFILL | DDBLT_WAIT, &blitfx);
	}

	// clear the bottom edge
	if (inner->bottom < outer->bottom)
	{
		clear = *outer;
		clear.top = inner->bottom;
		if (surface)
			IDirectDrawSurface7_Blt(surface, &clear, NULL, NULL, DDBLT_COLORFILL | DDBLT_WAIT, &blitfx);
	}
}



//============================================================
//	win_ddraw_wait_vsync
//============================================================

void win_ddraw_wait_vsync(void)
{
	HRESULT result;
	BOOL is_vblank;

	// if we're not already in VBLANK, wait for it
	result = IDirectDraw7_GetVerticalBlankStatus(ddraw7, &is_vblank);
	if (result == DD_OK && !is_vblank)
		result = IDirectDraw7_WaitForVerticalBlank(ddraw7, DDWAITVB_BLOCKBEGIN, 0);
}



//============================================================
//	win_ddraw_init
//============================================================

int win_ddraw_init(int width, int height, int depth, int attributes, const struct win_effect_data *effect)
{
	HRESULT result;
	DDCAPS hel_caps;

	// set the graphics mode width/height to the window size
	if (width && height && depth)
	{
		max_width = width;
		max_height = height;
		pref_depth = depth;
		needs_6bpp_per_gun	= ((attributes & VIDEO_NEEDS_6BITS_PER_GUN) != 0);
		pixel_aspect_ratio	= (attributes & VIDEO_PIXEL_ASPECT_RATIO_MASK);
	}
	if (effect)
	{
		effect_min_xscale = effect->min_xscale;
		effect_min_yscale = effect->min_yscale;
	}

	// now attempt to create it
	result = DirectDrawCreateEx(NULL, (VOID**)&ddraw7, &IID_IDirectDraw7, NULL);
	if (result != DD_OK)
	{
		fprintf(stderr, "Error creating DirectDraw: %08x\n", (UINT32)result);
		goto cant_create_ddraw;
	}

	// get the capabilities
	ddraw_caps.dwSize = sizeof(ddraw_caps);
	hel_caps.dwSize = sizeof(hel_caps);
	result = IDirectDraw7_GetCaps(ddraw7, &ddraw_caps, &hel_caps);
	if (result != DD_OK)
	{
		fprintf(stderr, "Error getting DirectDraw capabilities: %08x\n", (UINT32)result);
		goto cant_get_caps;
	}

	// determine if hardware stretching is available
	if (win_hw_stretch)
		win_hw_stretch = ((ddraw_caps.dwCaps & DDCAPS_BLTSTRETCH) != 0);
	if (win_hw_stretch && verbose)
		fprintf(stderr, "Hardware stretching supported\n");

	// set the cooperative level
	// for non-window modes, we will use full screen here
	result = IDirectDraw7_SetCooperativeLevel(ddraw7, win_video_window, win_window_mode ? DDSCL_NORMAL : DDSCL_FULLSCREEN | DDSCL_EXCLUSIVE);
	if (result != DD_OK)
	{
		fprintf(stderr, "Error setting cooperative level: %08x\n", (UINT32)result);
		goto cant_set_coop_level;
	}

	// full screen mode: set the resolution
	changed_resolutions = 0;
	if (set_resolution())
		goto cant_set_resolution;

	// create the surfaces
	if (create_surfaces())
		goto cant_create_surfaces;

	// force some updates
	forced_updates = 5;
	return 0;

	// error handling
cant_create_surfaces:
cant_set_resolution:
cant_set_coop_level:
cant_get_caps:
	IDirectDraw7_Release(ddraw7);
cant_create_ddraw:
	ddraw7 = NULL;
	return 1;
}



//============================================================
//	win_ddraw_kill
//============================================================

void win_ddraw_kill(void)
{
	if (d3ddev)
		IDirect3DDevice7_Release(d3ddev);
	d3ddev = NULL;

	if (d3d)
		IDirect3D7_Release(d3d);
	d3d = NULL;

	// release the surfaces
	release_surfaces();

	// restore resolutions
	if (ddraw7 != NULL && changed_resolutions)
		IDirectDraw7_RestoreDisplayMode(ddraw7);

	// reset cooperative level
	if (ddraw7 != NULL && win_video_window != 0)
		IDirectDraw7_SetCooperativeLevel(ddraw7, win_video_window, DDSCL_NORMAL);

	// delete the core objects
	if (ddraw7 != NULL)
		IDirectDraw7_Release(ddraw7);
	ddraw7 = NULL;
}



//============================================================
//	enum_callback
//============================================================

static HRESULT WINAPI enum_callback(LPDDSURFACEDESC2 desc, LPVOID context)
{
	int depth = desc->DUMMYUNIONNAMEN(4).ddpfPixelFormat.DUMMYUNIONNAMEN(1).dwRGBBitCount;
	double score;

	// compute this mode's score
	score = compute_mode_score(desc->dwWidth, desc->dwHeight, depth, 0);

	// is it the best?
	if (score > best_score)
	{
		// if so, remember it
		best_score = score;
		best_width = desc->dwWidth;
		best_height = desc->dwHeight;
		best_depth = depth;
		best_refresh = 0;
	}
	return DDENUMRET_OK;
}



//============================================================
//	enum2_callback
//============================================================

static HRESULT WINAPI enum2_callback(LPDDSURFACEDESC2 desc, LPVOID context)
{
	int refresh = (win_match_refresh || win_gfx_refresh) ? desc->DUMMYUNIONNAMEN(2).dwRefreshRate : 0;
#ifdef JMAME // USE_DX8_SDK
	int depth = desc->DUMMYUNIONNAMEN(4).ddpfPixelFormat.DUMMYUNIONNAMEN(1).dwRGBBitCount;
#else
#ifdef _MSC_VER
	int depth = desc->DUMMYUNIONNAMEN(4).ddpfPixelFormat.DUMMYUNIONNAMEN(1).dwRGBBitCount;
#else
	int depth = desc->ddpfPixelFormat.DUMMYUNIONNAMEN(1).dwRGBBitCount;
#endif
#endif
	double score;

	// compute this mode's score
	score = compute_mode_score(desc->dwWidth, desc->dwHeight, depth, refresh);

	// is it the best?
	if (score > best_score)
	{
		// if so, remember it
		best_score = score;
		best_width = desc->dwWidth;
		best_height = desc->dwHeight;
		best_depth = depth;
		best_refresh = refresh;
	}
	return DDENUMRET_OK;
}



//============================================================
//	compute_mode_score
//============================================================

static double compute_mode_score(int width, int height, int depth, int refresh)
{
	static const double depth_matrix[4][2][4] =
	{
			// !needs_6bpp_per_gun		  // needs_6bpp_per_gun
		{ { 0.00, 0.75, 0.25, 0.50 },	{ 0.00, 0.25, 0.50, 0.75 } },	// 8bpp source
		{ { 0.00, 1.00, 0.25, 0.50 },	{ 0.00, 0.50, 0.75, 1.00 } },	// 16bpp source
		{ { 0.00, 0.00, 0.00, 0.00 },	{ 0.00, 0.00, 0.00, 0.00 } },	// 24bpp source (doesn't exist)
		{ { 0.00, 0.50, 0.75, 1.00 },	{ 0.00, 0.50, 0.75, 1.00 } }	// 32bpp source
	};

	double size_score, depth_score, refresh_score, final_score;
	int target_width, target_height;

	// first compute a score based on size

	// if not stretching, we need to keep minx and miny scale equal
	if (!win_hw_stretch)
	{
		if (effect_min_yscale > effect_min_xscale)
			effect_min_xscale = effect_min_yscale;
		else
			effect_min_yscale = effect_min_xscale;
	}

	// determine minimum requirements
	target_width = max_width * effect_min_xscale;
	target_height = max_height * effect_min_yscale;
	if (pixel_aspect_ratio == VIDEO_PIXEL_ASPECT_RATIO_1_2)
		target_height *= 2;
	else if (win_old_scanlines)
		target_width *= 2, target_height *= 2;
	if (pixel_aspect_ratio == VIDEO_PIXEL_ASPECT_RATIO_2_1)
		target_width *= 2;

	// hardware stretch modes prefer at least 2x expansion
	if (win_hw_stretch)
	{
		if (target_width < max_width * 2 + 2)
			target_width = max_width * 2 + 2;
		if (target_height < max_height * 2 + 2)
			target_height = max_height * 2 + 2;
	}

	// compute initial score based on difference between target and current
	size_score = 1.0 / (1.0 + fabs(width - target_width) + fabs(height - target_height));

	// if we're looking for a particular mode, make sure it matches
	if (win_gfx_width && win_gfx_height && (width != win_gfx_width || height != win_gfx_height))
		return 0.0;

	// if mode is too small, it's a zero, unless the user specified otherwise
	if ((width < max_width || height < max_height) && (!win_gfx_width || !win_gfx_height))
		return 0.0;

	// if mode is smaller than we'd like, it only scores up to 0.1
	if (width < target_width || height < target_height)
		size_score *= 0.1;

	// next compute depth score
	depth_score = depth_matrix[(pref_depth + 7) / 8 - 1][needs_6bpp_per_gun][(depth + 7) / 8 - 1];

	// only 16bpp and above now supported
	if (depth < 16)
		return 0.0;

	// if we're looking for a particular depth, make sure it matches
	if (win_gfx_depth && depth != win_gfx_depth)
		return 0.0;

	// finally, compute refresh score
	refresh_score = 1.0 / (1.0 + fabs((double)refresh - Machine->drv->frames_per_second));

	// if we're looking for a particular refresh, make sure it matches
	if (win_gfx_refresh && refresh && refresh != win_gfx_refresh)
		return 0.0;

	// if refresh is smaller than we'd like, it only scores up to 0.1
	if ((double)refresh < Machine->drv->frames_per_second)
		refresh_score *= 0.1;

	// weight size highest, followed by depth and refresh
	final_score = (size_score * 100.0 + depth_score * 10.0 + refresh_score) / 111.0;
	return final_score;
}



//============================================================
//	set_resolution
//============================================================

static int set_resolution(void)
{
	DDSURFACEDESC2 currmode = { sizeof(DDSURFACEDESC2) };
	double resolution_aspect;
	HRESULT result;

	// skip if not switching resolution
	if (!win_window_mode && (win_switch_res || win_switch_bpp))
	{
		// if we're only switching depth, set win_gfx_width and win_gfx_height to the current resolution
		if (!win_switch_res || !win_switch_bpp)
		{
			// attempt to get the current display mode
			result = IDirectDraw7_GetDisplayMode(ddraw7, &currmode);
			if (result != DD_OK)
			{
				fprintf(stderr, "Error getting display mode: %08x\n", (UINT32)result);
				goto cant_get_mode;
			}

			// force to the current width/height
			if (!win_switch_res)
			{
				win_gfx_width = currmode.dwWidth;
				win_gfx_height = currmode.dwHeight;
			}
			if (!win_switch_bpp)
				win_gfx_depth = currmode.DUMMYUNIONNAMEN(4).ddpfPixelFormat.DUMMYUNIONNAMEN(1).dwRGBBitCount;
		}

		// enumerate display modes
		best_score = 0.0;
		result = IDirectDraw7_EnumDisplayModes(ddraw7, (win_match_refresh || win_gfx_refresh) ? DDEDM_REFRESHRATES : 0, NULL, NULL, enum2_callback);
		if (result != DD_OK)
		{
			fprintf(stderr, "Error enumerating modes: %08x\n", (UINT32)result);
			goto cant_enumerate_modes;
		}

		if (verbose)
		{
			if (best_refresh)
				fprintf(stderr, "Best mode = %dx%dx%d @ %d Hz\n", best_width, best_height, best_depth, best_refresh);
			else
				fprintf(stderr, "Best mode = %dx%dx%d @ default Hz\n", best_width, best_height, best_depth);
		}

		// set it
		if (best_width != 0)
		{
			result = IDirectDraw7_SetDisplayMode(ddraw7, best_width, best_height, best_depth, best_refresh, 0);
			if (result != DD_OK)
			{
				fprintf(stderr, "Error setting mode: %08x\n", (UINT32)result);
				goto cant_set_mode;
			}
			changed_resolutions = 1;
		}
	}

	// attempt to get the current display mode
	result = IDirectDraw7_GetDisplayMode(ddraw7, &currmode);
	if (result != DD_OK)
	{
		fprintf(stderr, "Error getting display mode: %08x\n", (UINT32)result);
		goto cant_get_mode;
	}

	// compute the adjusted aspect ratio
	resolution_aspect = (double)currmode.dwWidth / (double)currmode.dwHeight;
	win_aspect_ratio_adjust = resolution_aspect / win_screen_aspect;
	return 0;

	// error handling - non fatal in general
cant_set_mode:
cant_get_mode:
cant_enumerate_modes:
	return 0;
}



//============================================================
//	create_surfaces
//============================================================

static int create_surfaces(void)
{
	HRESULT result;

	// make a description of the primary surface
	memset(&primary_desc, 0, sizeof(primary_desc));
	primary_desc.dwSize = sizeof(primary_desc);
	primary_desc.dwFlags = DDSD_CAPS;
	primary_desc.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_3DDEVICE;

	// for full screen mode, allocate flipping surfaces
	if (!win_window_mode && win_triple_buffer)
	{
		primary_desc.dwFlags |= DDSD_BACKBUFFERCOUNT;
		primary_desc.ddsCaps.dwCaps |= DDSCAPS_FLIP | DDSCAPS_COMPLEX;
		primary_desc.DUMMYUNIONNAMEN(5).dwBackBufferCount = 2;
	}

	// then create the primary surface
	result = IDirectDraw7_CreateSurface(ddraw7, &primary_desc, &primary_surface, NULL);
	if (result != DD_OK)
	{
		if (verbose)
			fprintf(stderr, "Error creating primary surface: %08x\n", (UINT32)result);
		goto cant_create_primary;
	}

	// get a description of the primary surface
	result = IDirectDrawSurface7_GetSurfaceDesc(primary_surface, &primary_desc);
	if (result != DD_OK)
	{
		fprintf(stderr, "Error getting primary surface desc: %08x\n", (UINT32)result);
		goto cant_get_primary_desc;
	}

	// determine the color masks and force the palette to recalc
	compute_color_masks(&primary_desc);

	// if this is a full-screen mode, attempt to create a color control object
	if (!win_window_mode && win_gfx_brightness != 0.0)
		set_brightness();

	// print out the good stuff
	if (verbose)
		fprintf(stderr, "Primary surface created: %dx%dx%d (R=%08x G=%08x B=%08x)\n",
				(int)primary_desc.dwWidth,
				(int)primary_desc.dwHeight,
				(int)primary_desc.DUMMYUNIONNAMEN(4).ddpfPixelFormat.DUMMYUNIONNAMEN(1).dwRGBBitCount,
				(UINT32)primary_desc.DUMMYUNIONNAMEN(4).ddpfPixelFormat.DUMMYUNIONNAMEN(2).dwRBitMask,
				(UINT32)primary_desc.DUMMYUNIONNAMEN(4).ddpfPixelFormat.DUMMYUNIONNAMEN(3).dwGBitMask,
				(UINT32)primary_desc.DUMMYUNIONNAMEN(4).ddpfPixelFormat.DUMMYUNIONNAMEN(4).dwBBitMask);

	// full screen mode: get the back surface
	back_surface = NULL;
	if (!win_window_mode && win_triple_buffer)
	{
		DDSCAPS2 caps = { DDSCAPS_BACKBUFFER };
		result = IDirectDrawSurface7_GetAttachedSurface(primary_surface, &caps, &back_surface);
		if (result != DD_OK)
		{
			fprintf(stderr, "Error getting attached back surface: %08x\n", (UINT32)result);
			goto cant_get_back_surface;
		}
	}

	// stretch mode: create a blit surface
	if (win_hw_stretch)
	{
		D3DVIEWPORT7 vp;

		if (win_window_mode)
			if (create_blit_surface())
				goto cant_create_blit;

		result = IDirectDraw7_QueryInterface(ddraw7, &IID_IDirect3D7, (void **)&d3d);
		if (result != DD_OK)
		{
			fprintf(stderr, "Error can not create IDirect3D7: %08x\n", (UINT32)result);
			goto cant_create_d3d;
		}

//		if(blit_surface)
//			result = IDirect3D7_CreateDevice(d3d, &IID_IDirect3DHALDevice, blit_surface, &d3ddev);
//		else if(back_surface)
//			result = IDirect3D7_CreateDevice(d3d, &IID_IDirect3DHALDevice, back_surface, &d3ddev);
//		else
			result = IDirect3D7_CreateDevice(d3d, &IID_IDirect3DHALDevice, primary_surface, &d3ddev);
		if (result != DD_OK)
		{
			fprintf(stderr, "Error can not create IDirect3DHALDevice: %08x\n", (UINT32)result);
			goto cant_create_d3ddev;
		}

		result = IDirect3DDevice7_GetCaps(d3ddev,&d3d_desc);
		if (result != DD_OK)
		{
			fprintf(stderr, "Error getting Direct3D capabilities: %08x\n", (UINT32)result);
			goto cant_get_caps;
		}

		vp.dwX = 0;
		vp.dwY = 0;
		vp.dwWidth = primary_desc.dwWidth;
		vp.dwHeight = primary_desc.dwHeight;
		vp.dvMinZ = 0.0f;
		vp.dvMaxZ = 1.0f;
		IDirect3DDevice7_SetViewport(d3ddev, &vp);

		if (create_texture_surface())
			goto cant_create_texture;
	}

	// create a clipper for windowed mode
	if (win_window_mode)
	{
		if (create_clipper())
			goto cant_init_clipper;
	}

	// erase all the surfaces we created
	erase_surfaces();
	return 0;

	// error handling
cant_init_clipper:
cant_create_texture:
cant_get_caps:
	if (d3ddev)
		IDirect3DDevice7_Release(d3ddev);
	d3ddev = NULL;
cant_create_d3ddev:
	if (d3d)
		IDirect3D7_Release(d3d);
	d3d = NULL;
cant_create_d3d:
	if (blit_surface)
		IDirectDrawSurface7_Release(blit_surface);
	blit_surface = NULL;
cant_create_blit:
cant_get_back_surface:
	if (gamma_control)
		IDirectDrawColorControl_Release(gamma_control);
	gamma_control = NULL;
cant_get_primary_desc:
	IDirectDrawSurface7_Release(primary_surface);
	primary_surface = NULL;
cant_create_primary:
	return 1;
}



//============================================================
//	create_blit_surface
//============================================================

static int create_blit_surface(void)
{
	HRESULT result;

	// now make a description of our blit surface, based on the primary surface
	blit_desc = primary_desc;
	blit_desc.dwFlags = DDSD_WIDTH | DDSD_HEIGHT | DDSD_PIXELFORMAT | DDSD_CAPS;
	blit_desc.ddsCaps.dwCaps = DDSCAPS_3DDEVICE | DDSCAPS_VIDEOMEMORY;

	// then create the blit surface
	result = IDirectDraw7_CreateSurface(ddraw7, &blit_desc, &blit_surface, NULL);

	// fall back to system memory if video mem doesn't work
	if (result != DD_OK)
	{
		if (verbose)
			fprintf(stderr, "Error creating blit surface: %08x\n", (UINT32)result);
		goto cant_create_blit;
	}

	// get a description of the blit surface
	result = IDirectDrawSurface7_GetSurfaceDesc(blit_surface, &blit_desc);
	if (result != DD_OK)
	{
		fprintf(stderr, "Error getting blit surface desc: %08x\n", (UINT32)result);
		goto cant_get_blit_desc;
	}

	// blit surface color masks override the primary surface color masks
	compute_color_masks(&blit_desc);

	// print out the good stuff
	if (verbose)
		fprintf(stderr, "Blit surface created: %dx%dx%d (R=%08x G=%08x B=%08x)\n",
				(int)blit_desc.dwWidth,
				(int)blit_desc.dwHeight,
				(int)blit_desc.DUMMYUNIONNAMEN(4).ddpfPixelFormat.DUMMYUNIONNAMEN(1).dwRGBBitCount,
				(UINT32)blit_desc.DUMMYUNIONNAMEN(4).ddpfPixelFormat.DUMMYUNIONNAMEN(2).dwRBitMask,
				(UINT32)blit_desc.DUMMYUNIONNAMEN(4).ddpfPixelFormat.DUMMYUNIONNAMEN(3).dwGBitMask,
				(UINT32)blit_desc.DUMMYUNIONNAMEN(4).ddpfPixelFormat.DUMMYUNIONNAMEN(4).dwBBitMask);
	return 0;

	// error handling
cant_get_blit_desc:
	if (blit_surface)
		IDirectDrawSurface7_Release(blit_surface);
	blit_surface = NULL;
cant_create_blit:
	return 1;
}



//============================================================
//	create_texture_surface
//============================================================

static int create_texture_surface(void)
{
	HRESULT result;

	// Culculate texture size
	DWORD width = 1, height = 1;
	if ((d3d_desc.dpcTriCaps.dwTextureCaps & D3DPTEXTURECAPS_POW2) != 0) {
		while(width < (max_width * effect_min_xscale) + 18) width = width << 1;
		while(height < (max_height * effect_min_yscale) + 2) height = height << 1;
	} else {
		width = (max_width * effect_min_xscale) + 18;
		height = (max_height * effect_min_yscale) + 2;
	}

	if (width < d3d_desc.dwMinTextureWidth) width = d3d_desc.dwMinTextureWidth;
	if (height < d3d_desc.dwMinTextureHeight) height = d3d_desc.dwMinTextureHeight;

	if ((d3d_desc.dpcTriCaps.dwTextureCaps & D3DPTEXTURECAPS_SQUAREONLY) != 0) {
		if(width > height) height = width;
		else width = height;
	}

//	if (width > d3d_desc.dwMaxTextureWidth) width = d3d_desc.dwMinTextureWidth;
//	if (height > d3d_desc.dwMaxTextureHeight) height = d3d_desc.dwMinTextureHeight;

	// now make a description of our texture surface, based on the primary surface
	texture_desc = primary_desc;
	texture_desc.dwFlags = DDSD_WIDTH | DDSD_HEIGHT | DDSD_PIXELFORMAT | DDSD_CAPS;
	texture_desc.dwWidth = width;
	texture_desc.dwHeight = height;
	texture_desc.ddsCaps.dwCaps = DDSCAPS_VIDEOMEMORY | DDSCAPS_TEXTURE;

	// then create the texture surface
	result = IDirectDraw7_CreateSurface(ddraw7, &texture_desc, &texture_surface, NULL);
	if (win_motionblur)
#ifdef JMAME
	{
		if (Machine->drv->video_attributes & VIDEO_TYPE_VECTOR)
			result = IDirectDraw7_CreateSurface(ddraw7, &texture_desc, &texture_surface, NULL);
		else
			result = IDirectDraw7_CreateSurface(ddraw7, &texture_desc, &texture_surface2, NULL);
	}
#else
		result = IDirectDraw7_CreateSurface(ddraw7, &texture_desc, &texture_surface2, NULL);
#endif

	// fall back to system memory if video mem doesn't work
	if (result != DD_OK)
	{
		if (verbose)
			fprintf(stderr, "Error creating texture surface: %08x\n", (UINT32)result);
		goto cant_create_texture;
	}

	// get a description of the blit surface
	result = IDirectDrawSurface7_GetSurfaceDesc(texture_surface, &texture_desc);
	if (result != DD_OK)
	{
		fprintf(stderr, "Error getting texture surface desc: %08x\n", (UINT32)result);
		goto cant_get_texture_desc;
	}

	// texture surface color masks override the primary surface color masks
	compute_color_masks(&texture_desc);

	// set render state
	set_renderstate();

	// print out the good stuff
	if (verbose)
		fprintf(stderr, "Texture surface created: %dx%dx%d (R=%08x G=%08x B=%08x)\n",
				(int)texture_desc.dwWidth,
				(int)texture_desc.dwHeight,
				(int)texture_desc.DUMMYUNIONNAMEN(4).ddpfPixelFormat.DUMMYUNIONNAMEN(1).dwRGBBitCount,
				(UINT32)texture_desc.DUMMYUNIONNAMEN(4).ddpfPixelFormat.DUMMYUNIONNAMEN(2).dwRBitMask,
				(UINT32)texture_desc.DUMMYUNIONNAMEN(4).ddpfPixelFormat.DUMMYUNIONNAMEN(3).dwGBitMask,
				(UINT32)texture_desc.DUMMYUNIONNAMEN(4).ddpfPixelFormat.DUMMYUNIONNAMEN(4).dwBBitMask);
	return 0;

	// error handling
cant_get_texture_desc:
	if (texture_surface2)
		IDirectDrawSurface7_Release(texture_surface2);
	texture_surface = NULL;
	if (texture_surface)
		IDirectDrawSurface7_Release(texture_surface);
	texture_surface = NULL;
cant_create_texture:
	return 1;
}



//============================================================
//	set_renderstate
//============================================================

static int set_renderstate(void)
{
	HRESULT result;
	D3DMATRIX mat = {1.0f,0.0f,0.0f,0.0f,0.0f,1.0f,0.0f,0.0f,0.0f,0.0f,1.0f,0.0f,0.0f,0.0f,0.0f,1.0f};

	result = IDirect3DDevice7_SetRenderState(d3ddev, D3DRENDERSTATE_ZENABLE, D3DZB_FALSE);
	result = IDirect3DDevice7_SetRenderState(d3ddev, D3DRENDERSTATE_ZWRITEENABLE, FALSE);
	result = IDirect3DDevice7_SetRenderState(d3ddev, D3DRENDERSTATE_CULLMODE, D3DCULL_NONE);
	result = IDirect3DDevice7_SetRenderState(d3ddev, D3DRENDERSTATE_SRCBLEND, D3DBLEND_SRCALPHA);
	result = IDirect3DDevice7_SetRenderState(d3ddev, D3DRENDERSTATE_DESTBLEND, D3DBLEND_INVSRCALPHA);
	result = IDirect3DDevice7_SetRenderState(d3ddev, D3DRENDERSTATE_ALPHABLENDENABLE, TRUE);
	result = IDirect3DDevice7_SetRenderState(d3ddev, D3DRENDERSTATE_LIGHTING, FALSE);
	result = IDirect3DDevice7_SetRenderState(d3ddev, D3DRENDERSTATE_SPECULARENABLE, FALSE);

	result = IDirect3DDevice7_SetTextureStageState(d3ddev, 0, D3DTSS_COLOROP, D3DTOP_MODULATE);
	result = IDirect3DDevice7_SetTextureStageState(d3ddev, 0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
	result = IDirect3DDevice7_SetTextureStageState(d3ddev, 0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);
	result = IDirect3DDevice7_SetTextureStageState(d3ddev, 0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);
	result = IDirect3DDevice7_SetTextureStageState(d3ddev, 0, D3DTSS_ALPHAARG1, D3DTA_DIFFUSE);
	result = IDirect3DDevice7_SetTextureStageState(d3ddev, 0, D3DTSS_MAGFILTER, win_bilinear ? D3DTFG_POINT : D3DTFG_LINEAR);
	result = IDirect3DDevice7_SetTextureStageState(d3ddev, 0, D3DTSS_MINFILTER, D3DTFN_LINEAR);

	result = IDirect3DDevice7_SetTransform(d3ddev, D3DTRANSFORMSTATE_WORLD, &mat);
	result = IDirect3DDevice7_SetTransform(d3ddev, D3DTRANSFORMSTATE_VIEW, &mat);
	result = IDirect3DDevice7_SetTransform(d3ddev, D3DTRANSFORMSTATE_PROJECTION, &mat);

	return 0;
}



//============================================================
//	set_brightness
//============================================================

static void set_brightness(void)
{
	HRESULT result;

	// see if we can get a GammaControl object
	result = IDirectDrawSurface7_QueryInterface(primary_surface, &IID_IDirectDrawGammaControl, (void **)&gamma_control);
	if (result != DD_OK)
	{
		if (verbose)
			fprintf(stderr, "Warning: could not create gamma control to change brightness: %08x\n", (UINT32)result);
		gamma_control = NULL;
	}

	// if we got it, proceed
	if (gamma_control)
	{
		DDGAMMARAMP ramp;
		int i;

		// fill the gamma ramp
		for (i = 0; i < 256; i++)
		{
			double val = ((float)i / 255.0) * win_gfx_brightness;
			if (val > 1.0)
				val = 1.0;
			ramp.red[i] = ramp.green[i] = ramp.blue[i] = (WORD)(val * 65535.0);
		}

		// attempt to get the current settings
		result = IDirectDrawGammaControl_SetGammaRamp(gamma_control, 0, &ramp);
		if (result != DD_OK)
			fprintf(stderr, "Error setting gamma ramp: %08x\n", (UINT32)result);
	}
}



//============================================================
//	create_clipper
//============================================================

static int create_clipper(void)
{
	HRESULT result;

	// create a clipper for the primary surface
	result = IDirectDraw7_CreateClipper(ddraw7, 0, &primary_clipper, NULL);
	if (result != DD_OK)
	{
		fprintf(stderr, "Error creating clipper: %08x\n", (UINT32)result);
		goto cant_create_clipper;
	}

	// set the clipper's hwnd
	result = IDirectDrawClipper_SetHWnd(primary_clipper, 0, win_video_window);
	if (result != DD_OK)
	{
		fprintf(stderr, "Error setting clipper hwnd: %08x\n", (UINT32)result);
		goto cant_set_hwnd;
	}

	// set the clipper on the primary surface
	result = IDirectDrawSurface7_SetClipper(primary_surface, primary_clipper);
	if (result != DD_OK)
	{
		fprintf(stderr, "Error setting clipper on primary surface: %08x\n", (UINT32)result);
		goto cant_set_surface;
	}
	return 0;

	// error handling
cant_set_surface:
cant_set_hwnd:
	IDirectDrawClipper_Release(primary_clipper);
cant_create_clipper:
	return 1;
}



//============================================================
//	erase_surfaces
//============================================================

static void erase_surfaces(void)
{
	DDBLTFX blitfx = { sizeof(DDBLTFX) };
	HRESULT result = DD_OK;
	int i;

	// erase the blit surface
	blitfx.DUMMYUNIONNAMEN(5).dwFillColor = 0;

	if (texture_surface)
		result = IDirectDrawSurface7_Blt(texture_surface, NULL, NULL, NULL, DDBLT_COLORFILL | DDBLT_WAIT, &blitfx);

	if (texture_surface2)
		result = IDirectDrawSurface7_Blt(texture_surface2, NULL, NULL, NULL, DDBLT_COLORFILL | DDBLT_WAIT, &blitfx);

	if (blit_surface)
		result = IDirectDrawSurface7_Blt(blit_surface, NULL, NULL, NULL, DDBLT_COLORFILL | DDBLT_WAIT, &blitfx);

	// loop through enough to get all the back buffers
	if (!win_window_mode)
	{
		if (back_surface)
			for (i = 0; i < 5; i++)
			{
				// first flip
				result = IDirectDrawSurface7_Flip(primary_surface, NULL, DDFLIP_WAIT);

				// then do a color fill blit
				result = IDirectDrawSurface7_Blt(back_surface, NULL, NULL, NULL, DDBLT_COLORFILL | DDBLT_WAIT, &blitfx);
			}
		else
			result = IDirectDrawSurface7_Blt(primary_surface, NULL, NULL, NULL, DDBLT_COLORFILL | DDBLT_WAIT, &blitfx);
	}
}



//============================================================
//	release_surfaces
//============================================================

static void release_surfaces(void)
{
	if (d3ddev) IDirect3DDevice7_SetTexture(d3ddev, 0, NULL);

	// release the texture surface
	if (texture_surface)
		IDirectDrawSurface7_Release(texture_surface);
	texture_surface = NULL;

	if (texture_surface2)
		IDirectDrawSurface7_Release(texture_surface2);
	texture_surface2 = NULL;

	// release the blit surface
	if (blit_surface)
		IDirectDrawSurface7_Release(blit_surface);
	blit_surface = NULL;

	// release the clipper
	if (primary_clipper)
		IDirectDrawClipper_Release(primary_clipper);
	primary_clipper = NULL;

	// release the color controls
	if (gamma_control)
		IDirectDrawColorControl_Release(gamma_control);
	gamma_control = NULL;

	// release the primary surface
	if (primary_surface)
		IDirectDrawSurface7_Release(primary_surface);
	primary_surface = NULL;
}



//============================================================
//	compute_color_masks
//============================================================

static void compute_color_masks(const DDSURFACEDESC2 *desc)
{
	// 16bpp case
	if (desc->DUMMYUNIONNAMEN(4).ddpfPixelFormat.DUMMYUNIONNAMEN(1).dwRGBBitCount == 16)
	{
		int temp;

		// red
		win_color16_rdst_shift = win_color16_rsrc_shift = 0;
		temp = desc->DUMMYUNIONNAMEN(4).ddpfPixelFormat.DUMMYUNIONNAMEN(2).dwRBitMask;
		while (!(temp & 1))
			temp >>= 1, win_color16_rdst_shift++;
		while (!(temp & 0x80))
			temp <<= 1, win_color16_rsrc_shift++;

		// green
		win_color16_gdst_shift = win_color16_gsrc_shift = 0;
		temp = desc->DUMMYUNIONNAMEN(4).ddpfPixelFormat.DUMMYUNIONNAMEN(3).dwGBitMask;
		while (!(temp & 1))
			temp >>= 1, win_color16_gdst_shift++;
		while (!(temp & 0x80))
			temp <<= 1, win_color16_gsrc_shift++;

		// blue
		win_color16_bdst_shift = win_color16_bsrc_shift = 0;
		temp = desc->DUMMYUNIONNAMEN(4).ddpfPixelFormat.DUMMYUNIONNAMEN(4).dwBBitMask;
		while (!(temp & 1))
			temp >>= 1, win_color16_bdst_shift++;
		while (!(temp & 0x80))
			temp <<= 1, win_color16_bsrc_shift++;
	}

	// 24/32bpp case
	else if (desc->DUMMYUNIONNAMEN(4).ddpfPixelFormat.DUMMYUNIONNAMEN(1).dwRGBBitCount == 24 ||
			 desc->DUMMYUNIONNAMEN(4).ddpfPixelFormat.DUMMYUNIONNAMEN(1).dwRGBBitCount == 32)
	{
		int temp;

		// red
		win_color32_rdst_shift = 0;
		temp = desc->DUMMYUNIONNAMEN(4).ddpfPixelFormat.DUMMYUNIONNAMEN(2).dwRBitMask;
		while (!(temp & 1))
			temp >>= 1, win_color32_rdst_shift++;

		// green
		win_color32_gdst_shift = 0;
		temp = desc->DUMMYUNIONNAMEN(4).ddpfPixelFormat.DUMMYUNIONNAMEN(3).dwGBitMask;
		while (!(temp & 1))
			temp >>= 1, win_color32_gdst_shift++;

		// blue
		win_color32_bdst_shift = 0;
		temp = desc->DUMMYUNIONNAMEN(4).ddpfPixelFormat.DUMMYUNIONNAMEN(4).dwBBitMask;
		while (!(temp & 1))
			temp >>= 1, win_color32_bdst_shift++;
	}

	// mark the lookups invalid
	palette_lookups_invalid = 1;
}



//============================================================
//	win_ddraw_draw
//============================================================

int win_ddraw_draw(struct mame_bitmap *bitmap, const struct rectangle *bounds, void *vector_dirty_pixels, int update)
{
	int result;

	// handle forced updates
	if (forced_updates)
	{
		forced_updates--;
		update = 1;
	}

	// if we don't have our surfaces, try to recreate them
	if (!primary_surface)
	{
		release_surfaces();
		if (create_surfaces())
			return 0;
	}

	// if we're using hardware stretching, render to the blit surface,
	// then blit that and stretch
	if (win_hw_stretch)
		result = render_to_texture(bitmap, bounds, vector_dirty_pixels, update);

	// otherwise, render directly to the primary/back surface
	else
		result = render_to_primary(bitmap, bounds, vector_dirty_pixels, update);

	return result;
}



//============================================================
//	lock_must_succeed
//============================================================

static int lock_must_succeed(const struct rectangle *bounds, void *vector_dirty_pixels)
{
	// determine up front if this lock must succeed; by default, it depends on
	// whether or not we're throttling
	int result = throttle;

	// if we're using dirty pixels, we must succeed as well, or else we will leave debris
	if (vector_dirty_pixels)
		result = 1;

	// if we're blitting a different source rect than before, we also must
	// succeed, or else we will miss some areas
	if (bounds)
	{
		if (bounds->min_x != last_bounds.min_x || bounds->min_y != last_bounds.min_y ||
			bounds->max_x != last_bounds.max_x || bounds->max_y != last_bounds.max_y)
			result = 1;
		last_bounds = *bounds;
	}

	return result;
}



//============================================================
//	render_to_texture
//============================================================

static int render_to_texture(struct mame_bitmap *bitmap, const struct rectangle *bounds, void *vector_dirty_pixels, int update)
{
	int dstdepth = texture_desc.DUMMYUNIONNAMEN(4).ddpfPixelFormat.DUMMYUNIONNAMEN(1).dwRGBBitCount;
	int wait_for_lock = lock_must_succeed(bounds, vector_dirty_pixels);
	LPDIRECTDRAWSURFACE7 target_surface;
	struct win_blit_params params;
	HRESULT result;
	RECT src, dst;
	int dstxoffs;
	static LPDIRECTDRAWSURFACE7 active_surface;

	active_surface = (active_surface == texture_surface && texture_surface2) ? texture_surface2 : texture_surface;

tryagain:
	// attempt to lock the texture surface
	result = IDirectDrawSurface7_Lock(active_surface, NULL, &texture_desc, wait_for_lock ? DDLOCK_WAIT : 0, NULL);

	if (result == DDERR_SURFACELOST)
		goto surface_lost;

	// if it was busy (and we're not throttling), just punt
	if (result == DDERR_SURFACEBUSY || result == DDERR_WASSTILLDRAWING)
		return 1;
	if (result != DD_OK)
	{
		if (verbose)
			fprintf(stderr, "Unable to lock texture_surface: %08x\n", (UINT32)result);
		return 0;
	}

	// align the destination to 16 bytes
	dstxoffs = (((UINT32)texture_desc.lpSurface + 16) & ~15) - (UINT32)texture_desc.lpSurface;
	dstxoffs /= (dstdepth / 8);

	// perform the low-level blit
	params.dstdata		= texture_desc.lpSurface;
	params.dstpitch		= texture_desc.DUMMYUNIONNAMEN(1).lPitch;
	params.dstdepth		= dstdepth;
	params.dstxoffs		= dstxoffs;
	params.dstyoffs		= 1;
	params.dstxscale	= effect_min_xscale;
	params.dstyscale	= effect_min_yscale;
	params.dstyskip		= 0;
	params.dsteffect	= win_determine_effect(&params);

	params.srcdata		= bitmap->base;
	params.srcpitch		= bitmap->rowbytes;
	params.srcdepth		= bitmap->depth;
	params.srclookup	= win_prepare_palette(&params);
	params.srcxoffs		= win_visible_rect.left;
	params.srcyoffs		= win_visible_rect.top;
	params.srcwidth		= win_visible_width;
	params.srcheight	= win_visible_height;

	params.vecdirty		= vector_dirty_pixels;

	params.flipx		= blit_flipx;
	params.flipy		= blit_flipy;
	params.swapxy		= blit_swapxy;

	// adjust for more optimal bounds
	if (bounds && !update && !vector_dirty_pixels)
	{
		params.dstxoffs += (bounds->min_x - win_visible_rect.left) * effect_min_xscale;
		params.dstyoffs += (bounds->min_y - win_visible_rect.top) * effect_min_yscale;
		params.srcxoffs += bounds->min_x - win_visible_rect.left;
		params.srcyoffs += bounds->min_y - win_visible_rect.top;
		params.srcwidth = bounds->max_x - bounds->min_x + 1;
		params.srcheight = bounds->max_y - bounds->min_y + 1;
	}

	win_perform_blit(&params, 0);

	// unlock the surface
	IDirectDrawSurface7_Unlock(active_surface, NULL);

	// make the src rect
	src.left = dstxoffs - 1;
	src.top = 0;
	src.right = (dstxoffs + win_visible_width * effect_min_xscale) + 1;
	src.bottom = (win_visible_height * effect_min_yscale) + 2;

	// window mode
	if (win_window_mode)
	{
		// just convert the client area to screen coords
		GetClientRect(win_video_window, &dst);
		ClientToScreen(win_video_window, &((LPPOINT)&dst)[0]);
		ClientToScreen(win_video_window, &((LPPOINT)&dst)[1]);

		// target surface is the primary
		target_surface = primary_surface;
	}

	// full screen mode
	else
	{
		// win_start_maximized the rect, constraining to the aspect ratio
		dst.left = dst.top = 0;
		dst.right = primary_desc.dwWidth;
		dst.bottom = primary_desc.dwHeight;
		win_constrain_to_aspect_ratio(&dst, WMSZ_BOTTOMRIGHT);

		// center
		dst.left += (primary_desc.dwWidth - (dst.right - dst.left)) / 2;
		dst.top += (primary_desc.dwHeight - (dst.bottom - dst.top)) / 2;
		dst.right += dst.left;
		dst.bottom += dst.top;

		// target surface is the back buffer
		target_surface = back_surface ? back_surface : primary_surface;
	}

	// draw and flip
	if (!draw_and_flip(target_surface, &src, &dst, update))
		return 0;

	return 1;

surface_lost:
	if (verbose)
		fprintf(stderr, "Recreating surfaces\n");

	// go ahead and adjust the window
	win_adjust_window();

	// release and recreate the surfaces
	release_surfaces();
	if (!create_surfaces())
		goto tryagain;

	// otherwise, return failure
	return 0;
}



//============================================================
//	draw_and_flip
//============================================================

static int draw_and_flip(LPDIRECTDRAWSURFACE7 target_surface, LPRECT src, LPRECT dst, int update)
{
	HRESULT result;

	// sync to VBLANK?
	if ((win_wait_vsync || win_sync_refresh) && throttle && mame_get_performance_info()->game_speed_percent > 95)
	{
		// this counts as idle time
		profiler_mark(PROFILER_IDLE);

		win_ddraw_wait_vsync();

		// idle time done
		profiler_mark(PROFILER_END);
	}

	result = IDirect3DDevice7_SetRenderTarget(d3ddev,win_window_mode ? blit_surface : target_surface,0);

	result = IDirect3DDevice7_BeginScene(d3ddev);
	if (result == D3D_OK)
	{
		D3DTLVERTEX	vertex[4];

		vertex[0].sz = 0.0f;
		vertex[0].rhw = 1.0f;
		vertex[0].color = 0x80ffffff;
		vertex[0].specular = 0;
		vertex[1] = vertex[2] = vertex[3] = vertex[0];

		vertex[0].sx = (float)dst->left - 0.5f;
		vertex[0].sy = (float)dst->top - 0.5f;
		vertex[1].sx = (float)dst->right - 0.5f;
		vertex[1].sy = (float)dst->top - 0.5f;
		vertex[2].sx = (float)dst->left - 0.5f;
		vertex[2].sy = (float)dst->bottom - 0.5f;
		vertex[3].sx = (float)dst->right - 0.5f;
		vertex[3].sy = (float)dst->bottom - 0.5f;

		vertex[0].tu = (float)src->left / (float)texture_desc.dwWidth;
		vertex[0].tv = (float)src->top / (float)texture_desc.dwHeight;

		vertex[1].tu = (float)src->right / (float)texture_desc.dwWidth;
		vertex[1].tv = (float)src->top / (float)texture_desc.dwHeight;

		vertex[2].tu = (float)src->left / (float)texture_desc.dwWidth;
		vertex[2].tv = (float)src->bottom / (float)texture_desc.dwHeight;

		vertex[3].tu = (float)src->right / (float)texture_desc.dwWidth;
		vertex[3].tv = (float)src->bottom / (float)texture_desc.dwHeight;

		result = IDirect3DDevice7_SetTexture(d3ddev, 0, texture_surface);
		result = IDirect3DDevice7_SetRenderState(d3ddev, D3DRENDERSTATE_ALPHABLENDENABLE, FALSE);
		IDirect3DDevice7_DrawPrimitive(d3ddev, D3DPT_TRIANGLESTRIP, D3DFVF_TLVERTEX, &vertex, 4, 0);

		if (win_motionblur && texture_surface2)
		{
			result = IDirect3DDevice7_SetTexture(d3ddev, 0, texture_surface2);
			result = IDirect3DDevice7_SetRenderState(d3ddev, D3DRENDERSTATE_ALPHABLENDENABLE, TRUE);
			IDirect3DDevice7_DrawPrimitive(d3ddev, D3DPT_TRIANGLESTRIP, D3DFVF_TLVERTEX, &vertex, 4, 0);
		}

		result = IDirect3DDevice7_EndScene(d3ddev);
	}

	// sync to VBLANK?
	if (win_window_mode && (win_wait_vsync || win_sync_refresh) && throttle && mame_get_performance_info()->game_speed_percent > 95)
	{
		// this counts as idle time
		profiler_mark(PROFILER_IDLE);

		win_ddraw_wait_vsync();

		// idle time done
		profiler_mark(PROFILER_END);
	}

tryagain:
	// do the blit
	if (win_window_mode)
	{
		if(dst->left < 0) dst->left = 0;
		if(dst->top < 0) dst->top = 0;
		if(dst->right > primary_desc.dwWidth) dst->right = primary_desc.dwWidth;
		if(dst->bottom > primary_desc.dwHeight) dst->bottom = primary_desc.dwHeight;
		result = IDirectDrawSurface7_Blt(target_surface, dst, blit_surface, dst, DDBLT_ASYNC, NULL);
		if (result == DDERR_SURFACELOST)
			goto surface_lost;
		if (result != DD_OK && result != DDERR_WASSTILLDRAWING)
		{
			// otherwise, print the error and fall back
			fprintf(stderr, "Unable to blt blit_surface: %08x\n", (UINT32)result);
			return 0;
		}
	}

	// full screen mode: flip
	if (!win_window_mode && back_surface && result != DDERR_WASSTILLDRAWING)
	{
#if SHOW_FLIP_TIMES
		static cycles_t total;
		static int count;
		cycles_t start = osd_cycles(), stop;
#endif

		IDirectDrawSurface7_Flip(primary_surface, NULL,
			(win_wait_vsync || win_sync_refresh) && throttle ? 0 : DDFLIP_NOVSYNC);

#if SHOW_FLIP_TIMES
		stop = osd_cycles();
		if (++count > 100)
		{
			total += stop - start;
			usrintf_showmessage("Avg Flip = %d", (int)(total / (count - 100)));
		}
#endif
	}
	return 1;

surface_lost:
	if (verbose)
		fprintf(stderr, "Recreating surfaces\n");

	// go ahead and adjust the window
	win_adjust_window();

	// release and recreate the surfaces
	release_surfaces();
	if (!create_surfaces())
		goto tryagain;

	// otherwise, return failure
	return 0;
}



//============================================================
//	render_to_primary
//============================================================

static int render_to_primary(struct mame_bitmap *bitmap, const struct rectangle *bounds, void *vector_dirty_pixels, int update)
{
	int wait_for_lock = lock_must_succeed(bounds, vector_dirty_pixels);
	DDSURFACEDESC2 temp_desc = { sizeof(temp_desc) };
	LPDIRECTDRAWSURFACE7 target_surface;
	struct win_blit_params params;
	int xmult, ymult, dstdepth;
	HRESULT result;
	RECT outer, inner, temp;

tryagain:
	// window mode
	if (win_window_mode)
	{
		// just convert the client area to screen coords
		GetClientRect(win_video_window, &outer);
		ClientToScreen(win_video_window, &((LPPOINT)&outer)[0]);
		ClientToScreen(win_video_window, &((LPPOINT)&outer)[1]);
		inner = outer;

		// target surface is the primary
		target_surface = primary_surface;
	}

	// full screen mode
	else
	{
		// win_start_maximized the rect, constraining to the aspect ratio
		outer.left = outer.top = 0;
		outer.right = primary_desc.dwWidth;
		outer.bottom = primary_desc.dwHeight;
		inner = outer;
		win_constrain_to_aspect_ratio(&inner, WMSZ_BOTTOMRIGHT);

		// target surface is the back buffer
		target_surface = back_surface ? back_surface : primary_surface;
	}

	// compute the multipliers
	win_compute_multipliers(&inner, &xmult, &ymult);

	// center within the display rect
	inner.left = outer.left + ((outer.right - outer.left) - (win_visible_width * xmult)) / 2;
	inner.top = outer.top + ((outer.bottom - outer.top) - (win_visible_height * ymult)) / 2;
	inner.right = inner.left + win_visible_width * xmult;
	inner.bottom = inner.top + win_visible_height * ymult;

	// make sure we're not clipped
	if (win_window_mode)
	{
		UINT8 clipbuf[sizeof(RGNDATA) + sizeof(RECT)];
		RGNDATA *clipdata = (RGNDATA *)clipbuf;
		DWORD clipsize = sizeof(clipbuf);

		// get the size of the clip list; bail if we don't get back just a single rect
		result = IDirectDrawClipper_GetClipList(primary_clipper, &inner, clipdata, &clipsize);
		IntersectRect(&temp, (RECT *)clipdata->Buffer, &inner);
		if (result != DD_OK || !EqualRect(&temp, &inner))
			return 0;
	}

	// attempt to lock the target surface
	result = IDirectDrawSurface7_Lock(target_surface, NULL, &temp_desc, wait_for_lock ? DDLOCK_WAIT : 0, NULL);
	if (result == DDERR_SURFACELOST)
		goto surface_lost;
	dstdepth = temp_desc.DUMMYUNIONNAMEN(4).ddpfPixelFormat.DUMMYUNIONNAMEN(1).dwRGBBitCount;

	// try to align the destination
	while (inner.left > outer.left && (((UINT32)temp_desc.lpSurface + ((dstdepth + 7) / 8) * inner.left) & 15) != 0)
		inner.left--, inner.right--;

	// clamp to the display rect
	IntersectRect(&temp, &inner, &outer);
	inner = temp;

	// if it was busy (and we're not throttling), just punt
	if (result == DDERR_SURFACEBUSY || result == DDERR_WASSTILLDRAWING)
		return 1;
	if (result != DD_OK)
	{
		if (verbose)
			fprintf(stderr, "Unable to lock target_surface: %08x\n", (UINT32)result);
		return 0;
	}

	// perform the low-level blit
	params.dstdata		= temp_desc.lpSurface;
	params.dstpitch		= temp_desc.DUMMYUNIONNAMEN(1).lPitch;
	params.dstdepth		= dstdepth;
	params.dstxoffs		= inner.left;
	params.dstyoffs		= inner.top;
	params.dstxscale	= xmult;
	params.dstyscale	= (!win_old_scanlines || ymult == 1) ? ymult : ymult - 1;
	params.dstyskip		= (!win_old_scanlines || ymult == 1) ? 0 : 1;
	params.dsteffect	= win_determine_effect(&params);

	params.srcdata		= bitmap->base;
	params.srcpitch		= bitmap->rowbytes;
	params.srcdepth		= bitmap->depth;
	params.srclookup	= win_prepare_palette(&params);
	params.srcxoffs		= win_visible_rect.left;
	params.srcyoffs		= win_visible_rect.top;
	params.srcwidth		= win_visible_width;
	params.srcheight	= win_visible_height;

	params.vecdirty		= vector_dirty_pixels;

	params.flipx		= blit_flipx;
	params.flipy		= blit_flipy;
	params.swapxy		= blit_swapxy;

	// need to disable vector dirtying if we're rendering directly to a back buffer
	if (!win_window_mode && back_surface)
		params.vecdirty = NULL;

	// adjust for more optimal bounds
	if (bounds && !update && !vector_dirty_pixels)
	{
		params.dstxoffs += (bounds->min_x - win_visible_rect.left) * xmult;
		params.dstyoffs += (bounds->min_y - win_visible_rect.top) * ymult;
		params.srcxoffs += bounds->min_x - win_visible_rect.left;
		params.srcyoffs += bounds->min_y - win_visible_rect.top;
		params.srcwidth = bounds->max_x - bounds->min_x + 1;
		params.srcheight = bounds->max_y - bounds->min_y + 1;
	}

	win_perform_blit(&params, update);

	// unlock the surface
	IDirectDrawSurface7_Unlock(target_surface, NULL);

	// erase the edges if updating
	if (update)
		erase_outer_rect(&outer, &inner, target_surface);

	// full screen mode: flip
	if (!win_window_mode && back_surface && result != DDERR_WASSTILLDRAWING)
	{
#if SHOW_FLIP_TIMES
		static cycles_t total;
		static int count;
		cycles_t start = osd_cycles(), stop;
#endif

		IDirectDrawSurface7_Flip(primary_surface, NULL,
			(win_wait_vsync || win_sync_refresh) && throttle ? 0 : DDFLIP_NOVSYNC);

#if SHOW_FLIP_TIMES
		stop = osd_cycles();
		if (++count > 100)
		{
			total += stop - start;
			usrintf_showmessage("Avg Flip = %d", (int)(total / (count - 100)));
		}
#endif
	}
	return 1;

surface_lost:
	if (verbose)
		fprintf(stderr, "Recreating surfaces\n");

	// go ahead and adjust the window
	win_adjust_window();

	// release and recreate the surfaces
	release_surfaces();
	if (!create_surfaces())
		goto tryagain;

	// otherwise, return failure
	return 0;
}
