/*
THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX
SOFTWARE CORPORATION ("PARALLAX").  PARALLAX, IN DISTRIBUTING THE CODE TO
END-USERS, AND SUBJECT TO ALL OF THE TERMS AND CONDITIONS HEREIN, GRANTS A
ROYALTY-FREE, PERPETUAL LICENSE TO SUCH END-USERS FOR USE BY SUCH END-USERS
IN USING, DISPLAYING,  AND CREATING DERIVATIVE WORKS THEREOF, SO LONG AS
SUCH USE, DISPLAY OR CREATION IS FOR NON-COMMERCIAL, ROYALTY OR REVENUE
FREE PURPOSES.  IN NO EVENT SHALL THE END-USER USE THE COMPUTER CODE
CONTAINED HEREIN FOR REVENUE-BEARING PURPOSES.  THE END-USER UNDERSTANDS
AND AGREES TO THE TERMS HEREIN AND ACCEPTS THE SAME BY USE OF THIS FILE.  
COPYRIGHT 1993-1999 PARALLAX SOFTWARE CORPORATION.  ALL RIGHTS RESERVED.
*/


#pragma off (unreferenced)
static char rcsid[] = "$Id: ddraw.c 1.10 1996/08/20 16:30:52 samir Exp $";
#pragma on (unreferenced)


#define WIN95
#define _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include "win\ddraw.h"

#include <stdio.h>
#include <mem.h>

#include "gr.h"
#include "mono.h"
#include "error.h"
#include "winapp.h"
#include "dd.h"
#include "args.h"


//	Direct X Variables ---------------------------------------------------------

LPDIRECTDRAW			_lpDD=0;				// Direct Draw Object
LPDIRECTDRAWSURFACE	_lpDDSPrimary=0;	// Primary Display Screen (Page 1)
LPDIRECTDRAWSURFACE	_lpDDSBack=0;		// Page 2 or offscreen canvas
LPDIRECTDRAWCLIPPER	_lpDDClipper=0;	// Window Clipper Object
LPDIRECTDRAWPALETTE	_lpDDPalette=0;	// Direct Draw Palette;
DDMODEINFO				_DDModeList[16];	// Mode list for Exclusive mode.
int						_DDNumModes = 0;	// Number of display modes
BOOL						_DDFullScreen;		// Full Screen DD mode?
BOOL						_DDExclusive;		// Exclusive mode?
BOOL						_DDSysMemSurfacing=TRUE;
int						W95DisplayMode;	// Display mode.
int						W95OldDisplayMode;

dd_caps					ddDriverCaps;		// Driver Caps.

int						_DDFlags=0;			// Direct Draw Flags
int						_DDLockCounter=0;	// DirectDraw Lock Surface counter

static BOOL				DDUseEmulation=FALSE;
static HWND				DDWnd=NULL;

static int				DDVideoLocks = 0;
static int				DDSystemLocks = 0;

//	Function prototypes --------------------------------------------------------

HRESULT CALLBACK EnumDispModesCB(LPDDSURFACEDESC lpddsd, LPVOID context);
BOOL CheckDDResult(HRESULT ddresult, char *funcname);
BOOL DDRestoreCanvas(dd_grs_canvas *canvas);


//	Direct Draw Initialization
// ----------------------------------------------------------------------------

BOOL DDInit(int mode)
{
	LPDIRECTDRAW lpdd;
	DDCAPS ddcaps, ddcaps2;
	HRESULT ddresult;
	int num;

	DDWnd = GetLibraryWindow();

//	Create Direct Draw Object (Use Emulation if Hardware is off)
	if (!_lpDD)	{
		ddresult = DirectDrawCreate(NULL, &lpdd, NULL);
		
		if (ddresult == DDERR_NODIRECTDRAWHW) {
         ddresult = DirectDrawCreate( (LPVOID) DDCREATE_EMULATIONONLY, &lpdd, NULL );
			if (!CheckDDResult(ddresult, "InitDD:DirectDrawCreate emulation"))
				return FALSE;
			DDUseEmulation = TRUE;
			logentry("DirectDraw: forcing emulation.\n");
		}
		else if (ddresult != DD_OK) return FALSE;
		logentry("DirectDraw: DirectX API hardware compliant.\n");
	}
	else return FALSE;

	atexit(DDKill);

//	Determine hardware caps
//	Determine capture mode (fullscreen takes exclusive, window is normal)
	if (mode == DDGR_FULLSCREEN) {
		DWORD flags;

		flags = DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN;

	#ifndef NDEBUG
		if (!FindArg("-nomodex")) flags |= DDSCL_ALLOWMODEX;
	#else
		flags |= DDSCL_ALLOWMODEX;
	#endif

		if (!FindArg("-disallowreboot")) flags |= DDSCL_ALLOWREBOOT;
 
		ddresult = IDirectDraw_SetCooperativeLevel(lpdd, DDWnd, flags); 

		if (!CheckDDResult(ddresult, "DDInit::SetCooperativeLevel")) {	
			IDirectDraw_Release(lpdd);
			return FALSE;
		}

		_DDExclusive = TRUE;
		_DDFullScreen = TRUE;			
	}
	else if (mode == DDGR_EXWINDOW) {
		ddresult = IDirectDraw_SetCooperativeLevel(lpdd, DDWnd,
										DDSCL_EXCLUSIVE |
										DDSCL_FULLSCREEN);
		if (!CheckDDResult(ddresult, "DDInit::SetCooperativeLevel"))
			return FALSE;
		_DDExclusive = TRUE;
		_DDFullScreen = FALSE;
	}
	else if (mode == DDGR_WINDOW) {
		ddresult = IDirectDraw_SetCooperativeLevel(lpdd, DDWnd,
										DDSCL_NORMAL);
		if (!CheckDDResult(ddresult, "DDInit::SetCooperativeLevel"))
			return FALSE;
		_DDExclusive = FALSE;
		_DDFullScreen = FALSE;
	}
	else return FALSE;
	
//	Get Display modes/Window Sizes
//	Force invalidation of all modes for now
	for (num = 0; num < 16; num++)
	{
		_DDModeList[num].rw = _DDModeList[num].w = -1;
		_DDModeList[num].rh = _DDModeList[num].h = -1;
	}

	W95DisplayMode = SM95_640x480x8;
	num = 0;
	if (mode == DDGR_FULLSCREEN) {
		ddresult = IDirectDraw_EnumDisplayModes(lpdd, 0, NULL, 0, 
									EnumDispModesCB);
		if(!CheckDDResult(ddresult, "DDInit::EnumDisplayModes")) {
			IDirectDraw_Release(lpdd);
			return FALSE;
		}
	}
	else if (mode == DDGR_EXWINDOW) {
		_DDModeList[SM95_320x200x8X].rw = 320;
		_DDModeList[SM95_320x200x8X].rh = 200;
		_DDModeList[SM95_320x200x8X].w = 640;
		_DDModeList[SM95_320x200x8X].h = 480;
		_DDModeList[SM95_320x200x8X].emul = 1;
		_DDModeList[SM95_320x200x8X].dbuf = 0;
		_DDModeList[SM95_320x200x8X].modex = 0;
		_DDModeList[SM95_320x200x8X].paged = 0; 

		_DDModeList[SM95_640x480x8].rw = 640;
		_DDModeList[SM95_640x480x8].rh = 480;
		_DDModeList[SM95_640x480x8].w = 640;
		_DDModeList[SM95_640x480x8].h = 480;
		_DDModeList[SM95_640x480x8].emul = 1;
		_DDModeList[SM95_640x480x8].dbuf = 0;
		_DDModeList[SM95_640x480x8].modex = 0;
		_DDModeList[SM95_640x480x8].paged = 0; 

		_DDModeList[SM95_800x600x8].rw = 800;
		_DDModeList[SM95_800x600x8].rh = 600;
		_DDModeList[SM95_800x600x8].w = 640;
		_DDModeList[SM95_800x600x8].h = 480;
		_DDModeList[SM95_800x600x8].emul = 1;
		_DDModeList[SM95_800x600x8].dbuf = 0;
		_DDModeList[SM95_800x600x8].modex = 0;
		_DDModeList[SM95_800x600x8].paged = 0; 
		_DDNumModes = 3;
	}
	else if (mode == DDGR_WINDOW) {	
		_DDModeList[SM95_320x200x8X].rw = 320;
		_DDModeList[SM95_320x200x8X].rh = 200;
		_DDModeList[SM95_320x200x8X].w = 640;
		_DDModeList[SM95_320x200x8X].h = 480;
		_DDModeList[SM95_320x200x8X].emul = 1;
		_DDModeList[SM95_320x200x8X].dbuf = 0;
		_DDModeList[SM95_320x200x8X].modex = 0;
		_DDModeList[SM95_320x200x8X].paged = 0; 

		_DDModeList[SM95_640x480x8].rw = 640;
		_DDModeList[SM95_640x480x8].rh = 480;
		_DDModeList[SM95_640x480x8].w = 640;
		_DDModeList[SM95_640x480x8].h = 480;
		_DDModeList[SM95_640x480x8].emul = 1;
		_DDModeList[SM95_640x480x8].dbuf = 0;
		_DDModeList[SM95_640x480x8].modex = 0;
		_DDModeList[SM95_640x480x8].paged = 0; 

		_DDModeList[SM95_800x600x8].rw = 800;
		_DDModeList[SM95_800x600x8].rh = 600;
		_DDModeList[SM95_800x600x8].w = 800;
		_DDModeList[SM95_800x600x8].h = 600;
		_DDModeList[SM95_800x600x8].emul = 1;
		_DDModeList[SM95_800x600x8].dbuf = 0;
		_DDModeList[SM95_800x600x8].modex = 0;
		_DDModeList[SM95_800x600x8].paged = 0; 
		_DDNumModes = 3;
	}
	else return FALSE;

//	Set appropriate display mode or window mode

	_lpDD = lpdd;

	memset(&ddcaps, 0, sizeof(ddcaps));
	ddcaps.dwSize = sizeof(ddcaps);
	ddcaps2.dwSize = sizeof(ddcaps);
	ddresult = IDirectDraw_GetCaps(_lpDD, &ddcaps, NULL);
	if (!CheckDDResult(ddresult, "InitDD::GetCaps")) 
		return FALSE;

	logentry("DirectDraw: VRAM free:  %d\n", ddcaps.dwVidMemFree);	
	logentry("DirectDraw: VRAM total: %d\n", ddcaps.dwVidMemTotal);

#ifndef NDEBUG
	if (FindArg("-TsengDebug1")) {
		IDirectDraw_Release(lpdd);
		return FALSE;
	}
#endif

	DDSetDisplayMode(W95DisplayMode, 0);

#ifndef NDEBUG
	if (FindArg("-TsengDebug2")) {
		IDirectDraw_Release(lpdd);
		return FALSE;
	}
#endif
	
	// If 'windowed' do this.
	if (!_DDFullScreen) 
	{
			ddresult = IDirectDraw_CreateClipper(_lpDD, 0, &_lpDDClipper, NULL);
			if (!CheckDDResult(ddresult, "DDCreateScreen::CreateClipper"))
				return FALSE;

			ddresult = IDirectDrawClipper_SetHWnd(_lpDDClipper, 0, DDWnd);
			if (!CheckDDResult(ddresult, "DDCreateScreen::SetHWnd"))
				return FALSE;

			ddresult = IDirectDrawSurface_SetClipper(_lpDDSPrimary, _lpDDClipper);
			if (!CheckDDResult(ddresult, "DDCreateScreen::SetClipper"))
				return FALSE;
	}

//	Register Optimizations

	ddcaps.dwSize = sizeof(ddcaps);
	ddcaps2.dwSize = sizeof(ddcaps);
	ddresult = IDirectDraw_GetCaps(lpdd, &ddcaps, &ddcaps2);
	if (!CheckDDResult(ddresult, "DDInit::GetCaps"))
		return FALSE;

#ifndef NDEBUG
	if (FindArg("-TsengDebug3")) {
		IDirectDraw_Release(lpdd);
		return FALSE;
	}
#endif

	if (FindArg("-vidram")) {
		logentry("DirectDraw: Forcing VRAM rendering.\n");
		_DDSysMemSurfacing = FALSE;
	}
	else if (FindArg("-sysram")) {
		logentry("DirectDraw: Forcing SRAM rendering.\n");
		_DDSysMemSurfacing = TRUE;
	}
	else if (ddcaps.dwCaps & DDCAPS_BANKSWITCHED) {
		logentry("DirectDraw: Hardware is bank-switched.  Using SRAM rendering.\n");
		_DDSysMemSurfacing = TRUE;
	}
	else {
		logentry("DirectDraw: Hardware is not bank-switched.  Using VRAM rendering.\n");
		_DDSysMemSurfacing = FALSE;
	}
		
	if (ddcaps.dwCaps	& DDCAPS_COLORKEYHWASSIST) 
		ddDriverCaps.hwcolorkey = 1;
	else 
		ddDriverCaps.hwcolorkey = 0;
	if (ddcaps.dwCaps & DDCAPS_BLTSTRETCH)
		ddDriverCaps.hwbltstretch = 1;
	else 
		ddDriverCaps.hwbltstretch = 0;
		

//@@	mprintf((0, "DD::Hardware="));
//@@	if (ddcaps.dwCaps & DDCAPS_NOHARDWARE) mprintf((0, "Off\n"));
//@@	else mprintf((0, "On\n"));
//@@
//@@	mprintf((0, "DD::VideoMem=%u bytes\n", ddcaps.dwVidMemTotal));

//@@	mprintf((0, "DD::SrcColorKey="));	
//@@	if (ddcaps.dwCKeyCaps & DDCKEYCAPS_SRCBLT) mprintf((0, "Hardware\n"));
//@@	else mprintf((0, "Emulation\n"));

	return TRUE;
}


//	Direct Draw Destruction
// ----------------------------------------------------------------------------

void DDKill()
{
//	Perform cleanup for full screen case and window case
	DDKillScreen();
	if (_lpDDPalette) IDirectDrawPalette_Release(_lpDDPalette);
	if (_lpDD) IDirectDraw_Release(_lpDD);

	_DDExclusive = _DDFullScreen = FALSE;
	_DDNumModes = 0;
	_DDLockCounter = 0;
	_lpDD = NULL;
}


//	Direct Draw Surface Creation
//		Create Screen (Page 0 and possibly Page 1 or offscreen buffer)
// ----------------------------------------------------------------------------

BOOL DDCreateScreen(int flags)
{
	DDSCAPS ddscaps;
	DDCAPS ddcaps, ddcaps2;
	DDSURFACEDESC ddsd;
	HRESULT ddresult;

	memset(&ddcaps, 0, sizeof(ddcaps));
	memset(&ddcaps2, 0, sizeof(ddcaps2));
	ddcaps.dwSize = sizeof(ddcaps);
	ddcaps2.dwSize = sizeof(ddcaps);
	ddresult = IDirectDraw_GetCaps(_lpDD, &ddcaps, &ddcaps2);
	if (!CheckDDResult(ddresult, "DDCreateScreen::GetCaps"))
		return FALSE;


	logentry("DirectDraw HW Caps:  %x\nDirectDraw HEL Caps: %x\n",ddcaps.dwCaps,ddcaps2.dwCaps);
	if (ddcaps.dwCaps & DDCAPS_BANKSWITCHED) {
		logentry("DirectDraw: Hardware is bank-switched.  Using SRAM rendering.\n");
		_DDSysMemSurfacing = TRUE;
	}
	else {
		logentry("DirectDraw: Hardware is not bank-switched.  Using VRAM rendering.\n");
		_DDSysMemSurfacing = FALSE;
	}

//	Determine GFX caps.
	if (ddcaps.dwCaps	& DDCAPS_COLORKEYHWASSIST) 
		ddDriverCaps.hwcolorkey = 1;
	else 
		ddDriverCaps.hwcolorkey = 0;
	if (ddcaps.dwCaps & DDCAPS_BLTSTRETCH)
		ddDriverCaps.hwbltstretch = 1;
	else 
		ddDriverCaps.hwbltstretch = 0;
	
	memset(&ddsd, 0, sizeof(ddsd));
	ddsd.dwSize = sizeof(ddsd);

	if (_DDFullScreen && GRMODEINFO(paged)) {
	//	We should use page flipping
		ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
		ddsd.dwBackBufferCount = 1;
		ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE |
										DDSCAPS_FLIP |
										DDSCAPS_COMPLEX;
		ddresult = IDirectDraw_CreateSurface(_lpDD, &ddsd, &_lpDDSPrimary, NULL);
		if (!CheckDDResult(ddresult, "DDCreateScreen::CreateSurface -fullscreen"))
			return FALSE;
			
		ddscaps.dwCaps = DDSCAPS_BACKBUFFER;
		ddresult = IDirectDrawSurface_GetAttachedSurface(_lpDDSPrimary,
													&ddscaps, &_lpDDSBack);
		if (!CheckDDResult(ddresult, "DDCreateScreen::GetAttachedSurface"))
			return FALSE;
	}
	else {
	// We just create a primary and offscreen buffer
		if (GRMODEINFO(emul) && !_lpDDSPrimary) {
		// make sure we don't reinitialize the screen if we already made it
		//	beforehand for windowed version
			ddsd.dwFlags = DDSD_CAPS;
			ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;
			ddresult = IDirectDraw_CreateSurface(_lpDD, &ddsd, &_lpDDSPrimary, NULL);
			if (!CheckDDResult(ddresult, "DDCreateScreen::CreateSurface -windowed"))
				return FALSE;
		}
		else if (!GRMODEINFO(emul)) {
		// If we aren't emulating
			ddsd.dwFlags = DDSD_CAPS;
			ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;
			ddresult = IDirectDraw_CreateSurface(_lpDD, &ddsd, &_lpDDSPrimary, NULL);
			if (!CheckDDResult(ddresult, "DDCreateScreen::CreateSurface -windowed"))
				return FALSE;
		}

		if (GRMODEINFO(emul)) {
			_lpDDSBack = DDCreateSurface(_DDModeList[W95DisplayMode].rw, 
										_DDModeList[W95DisplayMode].rh, 1);
			if (!_lpDDSBack) {
				mprintf((0,"Call to create DDSBackBuffer failed."));
				return FALSE;
			}
		}
		else _lpDDSBack = NULL;	
	
	}

//	Create 8-bit palette
	{
		ubyte pal[768];
		memset(pal, 0, 768);
		
		memset(&ddsd, 0, sizeof(ddsd));
		ddsd.dwSize = sizeof(ddsd);
		IDirectDrawSurface_GetSurfaceDesc(_lpDDSPrimary, &ddsd);

		logentry("Primary surface pixel format: %x, %d\n", ddsd.ddpfPixelFormat.dwFlags, ddsd.ddpfPixelFormat.dwRGBBitCount); 

		_lpDDPalette = DDCreatePalette(pal);
		Assert(_lpDDPalette != NULL);
		DDSetPalette(_lpDDPalette);
	}

	return TRUE;
}


//	DirectDraw Screen Kill
//	----------------------------------------------------------------------------

void DDKillScreen()
{
	if (_lpDDClipper) IDirectDrawClipper_Release(_lpDDClipper);
	if (_lpDDPalette) IDirectDrawPalette_Release(_lpDDPalette);
	if (_lpDDSBack) IDirectDrawSurface_Release(_lpDDSBack);
	if (_lpDDSPrimary) {

		if (!GRMODEINFO(modex)) {
			DDBLTFX ddbltfx;
			HRESULT ddresult;

			memset(&ddbltfx, 0, sizeof(DDBLTFX));
	   	ddbltfx.dwSize = sizeof( ddbltfx );
		   ddbltfx.dwFillColor = (WORD)(BM_XRGB(0,0,0));

	   	ddresult = IDirectDrawSurface_Blt(
                            _lpDDSPrimary,  			// dest surface
                            NULL,                 	// dest rect
                            NULL,                  // src surface
                            NULL,                  // src rect
                            DDBLT_COLORFILL | DDBLT_WAIT,
                            &ddbltfx);
		}

		IDirectDrawSurface_Release(_lpDDSPrimary);
	}

	_lpDDClipper = NULL;
	_lpDDPalette = NULL;
	_lpDDSBack = NULL;
	_lpDDSPrimary = NULL;
}	


void DDKillEmulatedScreen()
{
	if (_lpDDSBack) IDirectDrawSurface_Release(_lpDDSBack);

	_lpDDSBack = NULL;
}


//	DDSetDisplayMode
//	----------------------------------------------------------------------------

void DDSetDisplayMode(int display_mode, int flags)
{
	HRESULT ddresult;

	W95DisplayMode = display_mode;
	W95OldDisplayMode = display_mode;
	if (_DDFullScreen) {
		logentry("Setting screen display mode to (%dx%dx%d::%dx%dx%d).\n", _DDModeList[W95DisplayMode].w, _DDModeList[W95DisplayMode].h,	_DDModeList[W95DisplayMode].bpp,_DDModeList[W95DisplayMode].rw, _DDModeList[W95DisplayMode].rh,	_DDModeList[W95DisplayMode].bpp);
	
		DDKillScreen();
		ddresult = IDirectDraw_SetDisplayMode(_lpDD, _DDModeList[W95DisplayMode].w,
										_DDModeList[W95DisplayMode].h,
										_DDModeList[W95DisplayMode].bpp);
		if (!CheckDDResult(ddresult, "DDInit::SetDisplayMode")) {
			Error("Unable to set display mode: %d.\n", W95DisplayMode);
		}
		DDCreateScreen(flags);
	}
	else {
		RECT rect;
		DWORD dwStyle;

		DDKillEmulatedScreen();

		dwStyle = GetWindowLong(DDWnd, GWL_STYLE);
		dwStyle &= ~WS_POPUP;
		dwStyle |= WS_OVERLAPPED | WS_CAPTION | WS_THICKFRAME | WS_MINIMIZEBOX;

		SetRect(&rect, 0, 0, _DDModeList[W95DisplayMode].w,
				_DDModeList[W95DisplayMode].h);	
		SetWindowLong(DDWnd, GWL_STYLE, dwStyle);
		AdjustWindowRectEx(&rect, 
						GetWindowLong(DDWnd, GWL_STYLE),
						GetMenu(DDWnd)!=NULL,
						GetWindowLong(DDWnd, GWL_EXSTYLE));

		SetWindowPos(DDWnd, NULL, 0,0,
					rect.right-rect.left,
					rect.bottom-rect.top,
				SWP_NOMOVE | 
				SWP_NOZORDER | 
				SWP_NOACTIVATE);
		SetWindowPos(DDWnd, HWND_NOTOPMOST, 0, 0, 0, 0, 
				SWP_NOSIZE |
				SWP_NOMOVE |
				SWP_NOACTIVATE);

		DDCreateScreen(flags);
	}
}


//	DDRestoreScreen
//		Restore screens 
//	----------------------------------------------------------------------------
int DDRestoreScreen()
{
	mprintf((1, "Need to Restore DDraw Page0 and Page1.\n"));
	if (!_lpDDSPrimary || IDirectDrawSurface_Restore(_lpDDSPrimary) !=DD_OK) {
		mprintf((1, "Warning: Unable to restore Primary Surface.\n"));
		return 0;
	}
	if (!GRMODEINFO(paged) && _lpDDSBack) {
		if (IDirectDrawSurface_Restore(_lpDDSBack) != DD_OK) {
			mprintf((1, "Warning: Unable to restore Back Surface.\n"));
			return 0;
		}
	}
	else if (!_DDFullScreen) {
		if (!_lpDDSBack || IDirectDrawSurface_Restore(_lpDDSBack) != DD_OK) {
			mprintf((1, "Warning: Unable to restore Back Surface.\n"));
			return 0;
		}
	}

	return 1;
}


//	DDFlip
//		Flip Screens using DirectDraw::Flip
//	----------------------------------------------------------------------------
void DDFlip()
{
	HRESULT ddresult;

	ddresult = IDirectDrawSurface_Flip(_lpDDSPrimary, NULL, 0);
	if (ddresult != DD_OK) {
		mprintf((1, "DDFlip:: Unable to flip screen (%X)\n", ddresult));
		Int3();									// Bad flipping
	}
}


//	DDCreateSurface
//		Create an offscreen surface hopefully in video memory
//	----------------------------------------------------------------------------
LPDIRECTDRAWSURFACE DDCreateSurface(int width, int height, BOOL vram)
{
	DDSURFACEDESC ddsd;
	HRESULT ddresult;
	LPDIRECTDRAWSURFACE lpdds;
	DDCOLORKEY ddck;

	if (_DDSysMemSurfacing && !vram) return DDCreateSysMemSurface(width, height);

	memset(&ddsd, 0, sizeof(ddsd));
	ddsd.dwSize = sizeof(ddsd);
	ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH;
	ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
	ddsd.dwWidth = width;
	ddsd.dwHeight = height;

//	logentry("Creating %dx%d sysram/vidram surface.\n", width, height);
	ddresult = IDirectDraw_CreateSurface(_lpDD, &ddsd, &lpdds, NULL);
	if (ddresult != DD_OK) {
		logentry("DDRAW::CreateSurface err: %x\n", ddresult);		
		return NULL;
	}

	return lpdds;
}


LPDIRECTDRAWSURFACE DDCreateSysMemSurface(int width, int height)
{
	DDSURFACEDESC ddsd;
	HRESULT ddresult;
	LPDIRECTDRAWSURFACE lpdds;

	memset(&ddsd, 0, sizeof(ddsd));
	ddsd.dwSize = sizeof(ddsd);
	ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH;
	ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_SYSTEMMEMORY;
	ddsd.dwWidth = width;
	ddsd.dwHeight = height;

//	logentry("Creating %dx%d sysram surface.\n", width, height);
	ddresult = IDirectDraw_CreateSurface(_lpDD, &ddsd, &lpdds, NULL);
	if (ddresult != DD_OK) {
		logentry("DDRAW::CreateSysMemSurface err: %x\n", ddresult);		
		return NULL;
	}

	return lpdds;
}


//	DDGetPalette
//	----------------------------------------------------------------------------
LPDIRECTDRAWPALETTE DDGetPalette(LPDIRECTDRAWSURFACE lpdds)
{
	HRESULT ddresult;
	LPDIRECTDRAWPALETTE lpddp;

	ddresult = IDirectDrawSurface_GetPalette(lpdds, &lpddp);
	if (ddresult != DD_OK) {
		mprintf((1, "DDERR: GetPalette %x.\n", ddresult));
		return NULL;
	}
	return lpddp;
}
	

//	DDCreatePalette
//	----------------------------------------------------------------------------
LPDIRECTDRAWPALETTE DDCreatePalette(ubyte *pal)
{
	HRESULT ddresult;
	LPDIRECTDRAWPALETTE lpddpal;
	PALETTEENTRY pe[256];
	int i;

	for (i = 0; i < 256; i++)
	{
		pe[i].peRed = pal[i*3];
		pe[i].peGreen = pal[i*3+1];
		pe[i].peBlue = pal[i*3+2];
		pe[i].peFlags = 0;
	}

	ddresult = IDirectDraw_CreatePalette(_lpDD, 
								DDPCAPS_8BIT | DDPCAPS_ALLOW256, 
								pe, 
								&lpddpal, NULL);
	if (ddresult != DD_OK) {
		mprintf((1, "DDERR: CreatePalette %x.\n", ddresult));
		return NULL;
	}
	
	return lpddpal;
}


//	DDSetPalette
//	----------------------------------------------------------------------------
void DDSetPalette(LPDIRECTDRAWPALETTE lpDDPal)
{
	HRESULT ddresult;

	ddresult = IDirectDrawSurface_SetPalette(_lpDDSPrimary, lpDDPal);
	if (ddresult != DD_OK) {
		if (ddresult == DDERR_SURFACELOST) {
			IDirectDrawSurface_Restore(_lpDDSPrimary);
			IDirectDrawSurface_SetPalette(_lpDDSPrimary, lpDDPal);
		}
		else {
			Error("Unable to attach palette to primary surface: %x.", ddresult);
		}
	}
}


//	DDLock and DDUnlock Canvas
//		This is required to access any canvas.
//	----------------------------------------------------------------------------
#ifndef NDEBUG
void DDLockCanvas_D(dd_grs_canvas *canvas, char *filename, int line)
{
	HRESULT ddresult;
	DDSURFACEDESC ddsd;
	RECT rect;
	grs_bitmap *bmp;


	bmp = &canvas->canvas.cv_bitmap;
	memset(&ddsd, 0, sizeof(ddsd));
	ddsd.dwSize = sizeof(ddsd);
	
	if (canvas->lock_count == 0) {
	// Obtain info about a rectangle on the surface
		SetRect(&rect, bmp->bm_x, bmp->bm_y, 
				bmp->bm_x+bmp->bm_w, bmp->bm_y+bmp->bm_h);

	RetryLock:

		ddresult = IDirectDrawSurface_Lock(canvas->lpdds, 	
										&rect,
										&ddsd,
										DDLOCK_WAIT,
										NULL);
		if (ddresult != DD_OK) {
			if (ddresult == DDERR_SURFACELOST) {
				if (!DDRestoreCanvas(canvas))
					Error("Unable to restore surface for lock:%x (%s line %d)\n", ddresult, filename, line);
				else goto RetryLock;
			}
			else {
				while (canvas->lock_count) DDUnlockCanvas(canvas);
				Error("Unable to lock canvas: %x (%s line %d)\n", ddresult, filename, line);
			}
		}
		bmp->bm_data = (unsigned char *)ddsd.lpSurface;
		bmp->bm_rowsize = (short)ddsd.lPitch;

//		if (canvas->sram && !GRMODEINFO(modex)) {
//		//	Manually calculate?
//			bmp->bm_data = bmp->bm_data + (bmp->bm_y*bmp->bm_rowsize);
//			bmp->bm_data += bmp->bm_x;
//		} 

		_DDLockCounter++;
	}
	canvas->lock_count++;
}
#endif


void DDLockCanvas(dd_grs_canvas *canvas)
{
	HRESULT ddresult;
	DDSURFACEDESC ddsd;
	RECT rect;
	grs_bitmap *bmp;


	bmp = &canvas->canvas.cv_bitmap;
	memset(&ddsd, 0, sizeof(ddsd));
	ddsd.dwSize = sizeof(ddsd);
	
	if (canvas->lock_count == 0) {
	// Obtain info about a rectangle on the surface
		SetRect(&rect, bmp->bm_x, bmp->bm_y, 
				bmp->bm_x+bmp->bm_w, bmp->bm_y+bmp->bm_h);

	RetryLock:

		ddresult = IDirectDrawSurface_Lock(canvas->lpdds, 	
										&rect,
										&ddsd,
										DDLOCK_WAIT,
										NULL);
		if (ddresult != DD_OK) {
			if (ddresult == DDERR_SURFACELOST) {
				if (!DDRestoreCanvas(canvas))
					Error("Unable to restore surface for lock:");
				else goto RetryLock;
			}
			else {
				while (canvas->lock_count) DDUnlockCanvas(canvas);
				Error("Unable to lock canvas: %x\n", ddresult);
			}
		}
		bmp->bm_data = (unsigned char *)ddsd.lpSurface;
		bmp->bm_rowsize = (short)ddsd.lPitch;

//		if (canvas->sram && !GRMODEINFO(modex)) {
//		//	Manually calculate?
//			bmp->bm_data = bmp->bm_data + (bmp->bm_y*bmp->bm_rowsize);
//			bmp->bm_data += bmp->bm_x;
//		} 

		_DDLockCounter++;
	}
	canvas->lock_count++;
}


void DDUnlockCanvas(dd_grs_canvas *canvas)
{
	HRESULT ddresult;
	grs_bitmap *bmp;

	bmp = &canvas->canvas.cv_bitmap;
	
	if (canvas->lock_count == 1) {
//		if (canvas->sram && !GRMODEINFO(modex)) {
//			bmp->bm_data = bmp->bm_data - bmp->bm_x;
//			bmp->bm_data = bmp->bm_data - (bmp->bm_y*bmp->bm_rowsize);
//		}
		ddresult = IDirectDrawSurface_Unlock(canvas->lpdds, 
								canvas->canvas.cv_bitmap.bm_data);
		if (ddresult != DD_OK) {	
				Error("Unable to unlock canvas: %x\n", ddresult);
				exit(1);
		}

		canvas->canvas.cv_bitmap.bm_data = NULL;
		canvas->canvas.cv_bitmap.bm_rowsize = 0;
		_DDLockCounter--;
	}
	canvas->lock_count--;
}


void DDLockDebug()
{
	logentry("VRAM locks: %d.  SRAM locks: %d\n", DDVideoLocks, DDSystemLocks);
}



//	DDFreeSurface
//	----------------------------------------------------------------------------
void DDFreeSurface(LPDIRECTDRAWSURFACE lpdds)
{
	HRESULT ddresult;

	Assert(lpdds != NULL);

	ddresult = IDirectDrawSurface_Release(lpdds);
	if (ddresult != DD_OK) {
		logentry("DDRAW::FreeSurface err: %x\n", ddresult);		
		Error("DDFreeSurface: Unable to free surface.");
	}
}


//	DDRestoreCanvas
//	----------------------------------------------------------------------------
BOOL DDRestoreCanvas(dd_grs_canvas *canvas)
{
	HRESULT ddresult;
	
	Assert(canvas->lpdds != NULL);
	
	ddresult = IDirectDrawSurface_Restore(canvas->lpdds);
	if (ddresult != DD_OK) {
		if (ddresult != DDERR_WRONGMODE) {
			logentry("DDRAW::RestoreCanvas::Surface err: %x\n", ddresult);		
			return FALSE;
		}
		mprintf((0, "Recreating surfaces:\n"));
	// Must recreate canvas
		if (canvas->lpdds == _lpDDSPrimary || canvas->lpdds == _lpDDSBack) {
			mprintf((0, "DDRestoreCanvas::Screen memory was lost!\n"));
			exit(1);
		}
		if (canvas->sram) {
		// force sysmem canvas!
			canvas->lpdds = DDCreateSysMemSurface(canvas->canvas.cv_bitmap.bm_w,
											canvas->canvas.cv_bitmap.bm_h);
		}
		else {
			canvas->lpdds = DDCreateSurface(canvas->canvas.cv_bitmap.bm_w, 
											canvas->canvas.cv_bitmap.bm_h,
											_DDSysMemSurfacing);
		}					
	}
	return TRUE;
}



//	CheckDDResult
//	----------------------------------------------------------------------------
BOOL CheckDDResult(HRESULT ddresult, char *funcname)
{
	char buf[256];

	if (ddresult != DD_OK) {
		sprintf(buf, "DirectDraw error %x detected in\r\n\t%s", ddresult, funcname);
		logentry(buf);
		MessageBox(NULL, buf, "DESCENT2::DDRAW", MB_OK);
		return FALSE;
	}
	else return TRUE;
}


//	EnumDispModesCB
//	----------------------------------------------------------------------------
HRESULT CALLBACK EnumDispModesCB(LPDDSURFACEDESC lpddsd, LPVOID context)
{
	DWORD width, height,bpp;
	int mode;
	DWORD modex;	

	width = lpddsd->dwWidth;
	height = lpddsd->dwHeight;
	bpp = lpddsd->ddpfPixelFormat.dwRGBBitCount;
	modex = lpddsd->ddsCaps.dwCaps;

	modex = modex & DDSCAPS_MODEX;

	if (width == 640 && height == 480 && bpp==8)   
		mode = SM95_640x480x8;
	else if (width == 640 && height == 400 && bpp==8)
		mode = SM95_640x400x8;
	else if (width == 320 && height == 200 && bpp==8) 
		mode = SM95_320x200x8X;
	else if (width == 800 && height == 600 && bpp==8) 
		mode = SM95_800x600x8;
	else if (width == 1024 && height == 768 && bpp==8)
		mode = SM95_1024x768x8;
	else
		return DDENUMRET_OK;

	_DDModeList[mode].rw 		= width;
	_DDModeList[mode].rh 		= height;
	_DDModeList[mode].emul 		= 0;
	_DDModeList[mode].modex		= 0;
	_DDModeList[mode].paged		= 0;
	_DDModeList[mode].dbuf		= 0;

	if (mode == SM95_320x200x8X) {
		_DDModeList[mode].modex = 1;
		_DDModeList[mode].dbuf = 1;
		_DDModeList[mode].paged = 1;
	}
	else if (mode == SM95_640x400x8) {
	//	Support a range of emulated modes
	//	320x200x8 Double Pixeled
		_DDModeList[SM95_320x200x8].rw = 320;
		_DDModeList[SM95_320x200x8].rh = 200;
		_DDModeList[SM95_320x200x8].emul = 1;
		_DDModeList[SM95_320x200x8].dbuf = 0;
		_DDModeList[SM95_320x200x8].modex = 0;
		_DDModeList[SM95_320x200x8].paged = 0; 
		_DDModeList[SM95_320x200x8].w = 640;
		_DDModeList[SM95_320x200x8].h = 400;
		_DDModeList[SM95_320x200x8].bpp = 8;
		
		_DDModeList[SM95_320x400x8].rw = 320;
		_DDModeList[SM95_320x400x8].rh = 400;
		_DDModeList[SM95_320x400x8].emul = 1;
		_DDModeList[SM95_320x400x8].dbuf = 0;
		_DDModeList[SM95_320x400x8].modex = 0;
		_DDModeList[SM95_320x400x8].paged = 0; 
		_DDModeList[SM95_320x400x8].w = 640;
		_DDModeList[SM95_320x400x8].h = 400;
		_DDModeList[SM95_320x400x8].bpp = 8;

		_DDNumModes+=2;

		_DDModeList[mode].dbuf = 1;
	}
	else {
		_DDModeList[mode].dbuf = 1;
	}

   _DDModeList[mode].w   = width;
   _DDModeList[mode].h   = height;
   _DDModeList[mode].bpp = bpp;

    _DDNumModes++;

    return DDENUMRET_OK;
}


int DDCheckMode(int mode)
{
	if (_DDModeList[mode].w==-1 && _DDModeList[mode].h==-1) return 1;
	else return 0;
}
