/*
** fb_d3d9.cpp
** Code to let ZDoom use Direct3D 9 as a simple framebuffer
**
**---------------------------------------------------------------------------
** Copyright 1998-2011 Randy Heit
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
**    notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
**    notice, this list of conditions and the following disclaimer in the
**    documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
**    derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
** This file does _not_ implement hardware-acclerated 3D rendering. It is
** just a means of getting the pixel data to the screen in a more reliable
** method on modern hardware by copying the entire frame to a texture,
** drawing that to the screen, and presenting.
**
** That said, it does implement hardware-accelerated 2D rendering.
*/

// HEADER FILES ------------------------------------------------------------

#ifdef _DEBUG
#define D3D_DEBUG_INFO
#endif
#define DIRECT3D_VERSION 0x0900
#define WIN32_LEAN_AND_MEAN

#include <windows.h>
#include <d3d9.h>

#include <stdio.h>

#define USE_WINDOWS_DWORD
#include "doomtype.h"

#include "c_dispatch.h"
#include "templates.h"
#include "i_system.h"
#include "i_video.h"
#include "i_input.h"
#include "v_video.h"
#include "v_pfx.h"
#include "stats.h"
#include "doomerrors.h"
#include "r_main.h"
#include "r_data/r_translate.h"
#include "f_wipe.h"
#include "sbar.h"
#include "win32iface.h"
#include "doomstat.h"
#include "v_palette.h"
#include "w_wad.h"
#include "r_data/colormaps.h"
#include "SkylineBinPack.h"

// MACROS ------------------------------------------------------------------

// The number of points for the vertex buffer.
#define NUM_VERTS		10240

// The number of indices for the index buffer.
#define NUM_INDEXES		((NUM_VERTS * 6) / 4)

// The number of quads we can batch together.
#define MAX_QUAD_BATCH	(NUM_INDEXES / 6)

// The default size for a texture atlas.
#define DEF_ATLAS_WIDTH		512
#define DEF_ATLAS_HEIGHT	512

// TYPES -------------------------------------------------------------------

IMPLEMENT_CLASS(D3DFB, false, false)

struct D3DFB::PackedTexture
{
	D3DFB::Atlas *Owner;

	PackedTexture **Prev, *Next;

	// Pixels this image covers
	RECT Area;

	// Texture coordinates for this image
	float Left, Top, Right, Bottom;

	// Texture has extra space on the border?
	bool Padded;
};

struct D3DFB::Atlas
{
	Atlas(D3DFB *fb, int width, int height, D3DFORMAT format);
	~Atlas();

	PackedTexture *AllocateImage(const Rect &rect, bool padded);
	void FreeBox(PackedTexture *box);

	SkylineBinPack Packer;
	Atlas *Next;
	IDirect3DTexture9 *Tex;
	D3DFORMAT Format;
	PackedTexture *UsedList;	// Boxes that contain images
	int Width, Height;
	bool OneUse;
};

class D3DTex : public FNativeTexture
{
public:
	D3DTex(FTexture *tex, D3DFB *fb, bool wrapping);
	~D3DTex();

	FTexture *GameTex;
	D3DFB::PackedTexture *Box;

	D3DTex **Prev;
	D3DTex *Next;

	bool IsGray;

	bool Create(D3DFB *fb, bool wrapping);
	bool Update();
	bool CheckWrapping(bool wrapping);
	D3DFORMAT GetTexFormat();
	FTextureFormat ToTexFmt(D3DFORMAT fmt);
};

class D3DPal : public FNativePalette
{
public:
	D3DPal(FRemapTable *remap, D3DFB *fb);
	~D3DPal();

	D3DPal **Prev;
	D3DPal *Next;

	IDirect3DTexture9 *Tex;
	D3DCOLOR BorderColor;
	bool DoColorSkip;

	bool Update();

	FRemapTable *Remap;
	int RoundedPaletteSize;
};

// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------

// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------

void DoBlending (const PalEntry *from, PalEntry *to, int count, int r, int g, int b, int a);

// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------

// EXTERNAL DATA DECLARATIONS ----------------------------------------------

extern HWND Window;
extern IVideo *Video;
extern BOOL AppActive;
extern int SessionState;
extern bool VidResizing;

EXTERN_CVAR (Bool, fullscreen)
EXTERN_CVAR (Float, Gamma)
EXTERN_CVAR (Bool, vid_vsync)
EXTERN_CVAR (Float, transsouls)
EXTERN_CVAR (Int, vid_refreshrate)

extern IDirect3D9 *D3D;

extern cycle_t BlitCycles;

// PRIVATE DATA DEFINITIONS ------------------------------------------------

const char *const D3DFB::ShaderNames[D3DFB::NUM_SHADERS] =
{
	"NormalColor.pso",
	"NormalColorPal.pso",
	"NormalColorInv.pso",
	"NormalColorPalInv.pso",

	"RedToAlpha.pso",
	"RedToAlphaInv.pso",

	"VertexColor.pso",

	"SpecialColormap.pso",
	"SpecialColorMapPal.pso",

	"InGameColormap.pso",
	"InGameColormapDesat.pso",
	"InGameColormapInv.pso",
	"InGameColormapInvDesat.pso",
	"InGameColormapPal.pso",
	"InGameColormapPalDesat.pso",
	"InGameColormapPalInv.pso",
	"InGameColormapPalInvDesat.pso",

	"BurnWipe.pso",
	"GammaCorrection.pso",
};

// PUBLIC DATA DEFINITIONS -------------------------------------------------

CUSTOM_CVAR(Bool, vid_hw2d, true, CVAR_NOINITCALL)
{
	V_SetBorderNeedRefresh();
	ST_SetNeedRefresh();
}

CVAR(Bool, d3d_antilag, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
CVAR(Int, d3d_showpacks, 0, 0)
CVAR(Bool, vid_hwaalines, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)

// CODE --------------------------------------------------------------------

//==========================================================================
//
// D3DFB - Constructor
//
//==========================================================================

D3DFB::D3DFB (UINT adapter, int width, int height, bool bgra, bool fullscreen)
	: BaseWinFB (width, height, bgra)
{
	D3DPRESENT_PARAMETERS d3dpp;

	LastHR = 0;

	Adapter = adapter;
	D3DDevice = NULL;
	VertexBuffer = NULL;
	IndexBuffer = NULL;
	FBTexture = NULL;
	TempRenderTexture = NULL;
	RenderTexture[0] = NULL;
	RenderTexture[1] = NULL;
	InitialWipeScreen = NULL;
	ScreenshotTexture = NULL;
	ScreenshotSurface = NULL;
	FinalWipeScreen = NULL;
	PaletteTexture = NULL;
	GammaTexture = NULL;
	FrontCopySurface = NULL;
	for (int i = 0; i < NUM_SHADERS; ++i)
	{
		Shaders[i] = NULL;
	}
	GammaShader = NULL;
	BlockSurface[0] = NULL;
	BlockSurface[1] = NULL;
	VSync = vid_vsync;
	BlendingRect.left = 0;
	BlendingRect.top = 0;
	BlendingRect.right = FBWidth;
	BlendingRect.bottom = FBHeight;
	In2D = 0;
	Palettes = NULL;
	Textures = NULL;
	Accel2D = true;
	GatheringWipeScreen = false;
	ScreenWipe = NULL;
	InScene = false;
	QuadExtra = new BufferedTris[MAX_QUAD_BATCH];
	Atlases = NULL;
	PixelDoubling = 0;
	SkipAt = -1;
	CurrRenderTexture = 0;
	RenderTextureToggle = 0;

	Gamma = 1.0;
	FlashColor0 = 0;
	FlashColor1 = 0xFFFFFFFF;
	FlashColor = 0;
	FlashAmount = 0;

	NeedGammaUpdate = false;
	NeedPalUpdate = false;

	if (MemBuffer == NULL)
	{
		return;
	}

	memcpy(SourcePalette, GPalette.BaseColors, sizeof(PalEntry)*256);

	Windowed = !(static_cast<Win32Video *>(Video)->GoFullscreen(fullscreen));

	TrueHeight = height;
	if (fullscreen)
	{
		for (Win32Video::ModeInfo *mode = static_cast<Win32Video *>(Video)->m_Modes; mode != NULL; mode = mode->next)
		{
			if (mode->width == Width && mode->height == Height)
			{
				TrueHeight = mode->realheight;
				PixelDoubling = mode->doubling;
				break;
			}
		}
	}
	// Offset from top of screen to top of letterboxed screen
	LBOffsetI = (TrueHeight - Height) / 2;
	LBOffset = float(LBOffsetI);

	FillPresentParameters(&d3dpp, fullscreen, VSync);

	HRESULT hr;

	LOG("CreateDevice attempt 1 hwvp\n");
	if (FAILED(hr = D3D->CreateDevice(Adapter, D3DDEVTYPE_HAL, Window,
		D3DCREATE_HARDWARE_VERTEXPROCESSING | D3DCREATE_FPU_PRESERVE, &d3dpp, &D3DDevice)) &&
		(hr != D3DERR_DEVICELOST || D3DDevice == NULL))
	{
		LOG2("CreateDevice returned hr %08x dev %p; attempt 2 swvp\n", hr, D3DDevice);
		if (FAILED(D3D->CreateDevice(Adapter, D3DDEVTYPE_HAL, Window,
			D3DCREATE_SOFTWARE_VERTEXPROCESSING | D3DCREATE_FPU_PRESERVE, &d3dpp, &D3DDevice)) &&
			(hr != D3DERR_DEVICELOST || D3DDevice == NULL))
		{
			if (d3dpp.FullScreen_RefreshRateInHz != 0)
			{
				d3dpp.FullScreen_RefreshRateInHz = 0;
				LOG2("CreateDevice returned hr %08x dev %p; attempt 3 (hwvp, default Hz)\n", hr, D3DDevice);
				if (FAILED(hr = D3D->CreateDevice(Adapter, D3DDEVTYPE_HAL, Window,
					D3DCREATE_HARDWARE_VERTEXPROCESSING | D3DCREATE_FPU_PRESERVE, &d3dpp, &D3DDevice)) &&
					(hr != D3DERR_DEVICELOST || D3DDevice == NULL))
				{
					LOG2("CreateDevice returned hr %08x dev %p; attempt 4 (swvp, default Hz)\n", hr, D3DDevice);
					if (FAILED(D3D->CreateDevice(Adapter, D3DDEVTYPE_HAL, Window,
						D3DCREATE_SOFTWARE_VERTEXPROCESSING | D3DCREATE_FPU_PRESERVE, &d3dpp, &D3DDevice)) &&
						hr != D3DERR_DEVICELOST)
					{
						D3DDevice = NULL;
					}
				}
			}
		}
	}
	LOG2("Final CreateDevice returned HR %08x and device %p\n", hr, D3DDevice);
	LastHR = hr;
	if (D3DDevice != NULL)
	{
		D3DADAPTER_IDENTIFIER9 adapter_id;
		D3DDEVICE_CREATION_PARAMETERS create_params;

		if (FAILED(hr = D3DDevice->GetDeviceCaps(&DeviceCaps)))
		{
			memset(&DeviceCaps, 0, sizeof(DeviceCaps));
		}
		if (SUCCEEDED(hr = D3DDevice->GetCreationParameters(&create_params)) &&
			SUCCEEDED(hr = D3D->GetAdapterIdentifier(create_params.AdapterOrdinal, 0, &adapter_id)))
		{
			// NVidia's drivers lie, claiming they don't support
			// antialiased lines when, really, they do.
			if (adapter_id.VendorId == 0x10de)
			{
				DeviceCaps.LineCaps |= D3DLINECAPS_ANTIALIAS;
			}
			// ATI's drivers apparently also lie, so screw this caps bit.
		}
		CreateResources();
		SetInitialState();
	}
}

//==========================================================================
//
// D3DFB - Destructor
//
//==========================================================================

D3DFB::~D3DFB ()
{
	ReleaseResources();
	SAFE_RELEASE( D3DDevice );
	delete[] QuadExtra;
}

//==========================================================================
//
// D3DFB :: SetInitialState
//
// Called after initial device creation and reset, when everything is set
// to D3D's defaults.
//
//==========================================================================

void D3DFB::SetInitialState()
{
	AlphaBlendEnabled = FALSE;
	AlphaBlendOp = D3DBLENDOP_ADD;
	AlphaSrcBlend = D3DBLEND(0);
	AlphaDestBlend = D3DBLEND(0);

	CurPixelShader = NULL;
	memset(Constant, 0, sizeof(Constant));

	for (unsigned i = 0; i < countof(Texture); ++i)
	{
		Texture[i] = NULL;
		D3DDevice->SetSamplerState(i, D3DSAMP_ADDRESSU, (i == 1 && SM14) ? D3DTADDRESS_BORDER : D3DTADDRESS_CLAMP);
		D3DDevice->SetSamplerState(i, D3DSAMP_ADDRESSV, (i == 1 && SM14) ? D3DTADDRESS_BORDER : D3DTADDRESS_CLAMP);
		if (i > 1)
		{
			// Set linear filtering for the SM14 gamma texture.
			D3DDevice->SetSamplerState(i, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
		}
	}

	NeedGammaUpdate = true;
	NeedPalUpdate = true;
	OldRenderTarget = NULL;

	if (!Windowed && SM14)
	{
		// Fix for Radeon 9000, possibly other R200s: When the device is
		// reset, it resets the gamma ramp, but the driver apparently keeps a
		// cached copy of the ramp that it doesn't update, so when
		// SetGammaRamp is called later to handle the NeedGammaUpdate flag,
		// it doesn't do anything, because the gamma ramp is the same as the
		// one passed in the last call, even though the visible gamma ramp 
		// actually has changed.
		//
		// So here we force the gamma ramp to something absolutely horrible and
		// trust that we will be able to properly set the gamma later when
		// NeedGammaUpdate is handled.
		D3DGAMMARAMP ramp;
		memset(&ramp, 0, sizeof(ramp));
		D3DDevice->SetGammaRamp(0, 0, &ramp);
	}

	// This constant is used for grayscaling weights (.xyz) and color inversion (.w)
	float weights[4] = { 77/256.f, 143/256.f, 37/256.f, 1 };
	D3DDevice->SetPixelShaderConstantF(PSCONST_Weights, weights, 1);

	// D3DRS_ALPHATESTENABLE defaults to FALSE
	// D3DRS_ALPHAREF defaults to 0
	D3DDevice->SetRenderState(D3DRS_ALPHAFUNC, D3DCMP_NOTEQUAL);
	AlphaTestEnabled = FALSE;

	CurBorderColor = 0;

	// Clear to black, just in case it wasn't done already.
	D3DDevice->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,0), 0, 0);
}

//==========================================================================
//
// D3DFB :: FillPresentParameters
//
//==========================================================================

void D3DFB::FillPresentParameters (D3DPRESENT_PARAMETERS *pp, bool fullscreen, bool vsync)
{
	memset (pp, 0, sizeof(*pp));
	pp->Windowed = !fullscreen;
	pp->SwapEffect = D3DSWAPEFFECT_DISCARD;
	pp->BackBufferWidth = Width << PixelDoubling;
	pp->BackBufferHeight = TrueHeight << PixelDoubling;
	pp->BackBufferFormat = fullscreen ? D3DFMT_A8R8G8B8 : D3DFMT_UNKNOWN;
	pp->BackBufferCount = 1;
	pp->hDeviceWindow = Window;
	pp->PresentationInterval = vsync ? D3DPRESENT_INTERVAL_ONE : D3DPRESENT_INTERVAL_IMMEDIATE;
	if (fullscreen)
	{
		pp->FullScreen_RefreshRateInHz = vid_refreshrate;
	}
}

//==========================================================================
//
// D3DFB :: CreateResources
//
//==========================================================================

bool D3DFB::CreateResources()
{
	Atlases = NULL;
	if (!Windowed)
	{
		// Remove the window border in fullscreen mode
		SetWindowLong (Window, GWL_STYLE, WS_POPUP|WS_VISIBLE|WS_SYSMENU);
	}
	else
	{
		// Resize the window to match desired dimensions
		RECT rect = { 0, 0, Width, Height };
		AdjustWindowRectEx(&rect, WS_VISIBLE|WS_OVERLAPPEDWINDOW, FALSE, WS_EX_APPWINDOW);
		int sizew = rect.right - rect.left;
		int sizeh = rect.bottom - rect.top;
		LOG2 ("Resize window to %dx%d\n", sizew, sizeh);
		VidResizing = true;
		// Make sure the window has a border in windowed mode
		SetWindowLong(Window, GWL_STYLE, WS_VISIBLE|WS_OVERLAPPEDWINDOW);
		if (GetWindowLong(Window, GWL_EXSTYLE) & WS_EX_TOPMOST)
		{
			// Direct3D 9 will apparently add WS_EX_TOPMOST to fullscreen windows,
			// and removing it is a little tricky. Using SetWindowLongPtr to clear it
			// will not do the trick, but sending the window behind everything will.
			SetWindowPos(Window, HWND_BOTTOM, 0, 0, sizew, sizeh,
				SWP_DRAWFRAME | SWP_NOCOPYBITS | SWP_NOMOVE);
			SetWindowPos(Window, HWND_TOP, 0, 0, 0, 0, SWP_NOCOPYBITS | SWP_NOMOVE | SWP_NOSIZE);
		}
		else
		{
			SetWindowPos(Window, NULL, 0, 0, sizew, sizeh,
				SWP_DRAWFRAME | SWP_NOCOPYBITS | SWP_NOMOVE | SWP_NOZORDER);
		}
		I_RestoreWindowedPos();
		VidResizing = false;
	}
	if (!LoadShaders())
	{
		return false;
	}
	if (!CreateFBTexture() ||
		!CreatePaletteTexture())
	{
		return false;
	}
	if (!CreateVertexes())
	{
		return false;
	}
	CreateGammaTexture();
	CreateBlockSurfaces();
	return true;
}

//==========================================================================
//
// D3DFB :: LoadShaders
//
// Returns true if all required shaders were loaded. (Gamma and burn wipe
// are the only ones not considered "required".)
//
//==========================================================================

bool D3DFB::LoadShaders()
{
	static const char models[][4] = { "30/", "20/", "14/" };
	FString shaderdir, shaderpath;
	unsigned model, i;
	int lump;

	// We determine the best available model simply by trying them all in
	// order of decreasing preference.
	for (model = 0; model < countof(models); ++model)
	{
		shaderdir = "shaders/d3d/sm";
		shaderdir += models[model];
		for (i = 0; i < NUM_SHADERS; ++i)
		{
			shaderpath = shaderdir;
			shaderpath += ShaderNames[i];
			lump = Wads.CheckNumForFullName(shaderpath);
			if (lump >= 0)
			{
				FMemLump data = Wads.ReadLump(lump);
				if (FAILED(D3DDevice->CreatePixelShader((DWORD *)data.GetMem(), &Shaders[i])) &&
					i < SHADER_BurnWipe)
				{
					break;
				}
			}
		}
		if (i == NUM_SHADERS)
		{ // Success!
			SM14 = (model == countof(models) - 1);
			return true;
		}
		// Failure. Release whatever managed to load (which is probably nothing.)
		for (i = 0; i < NUM_SHADERS; ++i)
		{
			SAFE_RELEASE( Shaders[i] );
		}
	}
	return false;
}

//==========================================================================
//
// D3DFB :: ReleaseResources
//
//==========================================================================

void D3DFB::ReleaseResources ()
{
	I_SaveWindowedPos ();
	KillNativeTexs();
	KillNativePals();
	ReleaseDefaultPoolItems();
	SAFE_RELEASE( ScreenshotSurface );
	SAFE_RELEASE( ScreenshotTexture );
	SAFE_RELEASE( PaletteTexture );
	for (int i = 0; i < NUM_SHADERS; ++i)
	{
		SAFE_RELEASE( Shaders[i] );
	}
	GammaShader = NULL;
	if (ScreenWipe != NULL)
	{
		delete ScreenWipe;
		ScreenWipe = NULL;
	}
	Atlas *pack, *next;
	for (pack = Atlases; pack != NULL; pack = next)
	{
		next = pack->Next;
		delete pack;
	}
	GatheringWipeScreen = false;
}

//==========================================================================
//
// D3DFB :: ReleaseDefaultPoolItems
//
// Free resources created with D3DPOOL_DEFAULT.
//
//==========================================================================

void D3DFB::ReleaseDefaultPoolItems()
{
	SAFE_RELEASE( FBTexture );
	SAFE_RELEASE( FinalWipeScreen );
	SAFE_RELEASE( RenderTexture[0] );
	SAFE_RELEASE( RenderTexture[1] );
	SAFE_RELEASE( InitialWipeScreen );
	SAFE_RELEASE( VertexBuffer );
	SAFE_RELEASE( IndexBuffer );
	SAFE_RELEASE( BlockSurface[0] );
	SAFE_RELEASE( BlockSurface[1] );
	SAFE_RELEASE( FrontCopySurface );
}

//==========================================================================
//
// D3DFB :: Reset
//
//==========================================================================

bool D3DFB::Reset ()
{
	D3DPRESENT_PARAMETERS d3dpp;

	ReleaseDefaultPoolItems();
	FillPresentParameters (&d3dpp, !Windowed, VSync);
	if (!SUCCEEDED(D3DDevice->Reset (&d3dpp)))
	{
		if (d3dpp.FullScreen_RefreshRateInHz != 0)
		{
			d3dpp.FullScreen_RefreshRateInHz = 0;
			if (!SUCCEEDED(D3DDevice->Reset (&d3dpp)))
			{
				return false;
			}
		}
		else
		{
			return false;
		}
	}
	LOG("Device was reset\n");
	if (!CreateFBTexture() || !CreateVertexes())
	{
		return false;
	}
	CreateBlockSurfaces();
	SetInitialState();
	return true;
}

//==========================================================================
//
// D3DFB :: CreateBlockSurfaces
//
// Create blocking surfaces for antilag. It's okay if these can't be
// created; antilag just won't work.
//
//==========================================================================

void D3DFB::CreateBlockSurfaces()
{
	BlockNum = 0;
	if (SUCCEEDED(D3DDevice->CreateOffscreenPlainSurface(16, 16, D3DFMT_A8R8G8B8,
		D3DPOOL_DEFAULT, &BlockSurface[0], 0)))
	{
		if (FAILED(D3DDevice->CreateOffscreenPlainSurface(16, 16, D3DFMT_A8R8G8B8,
			D3DPOOL_DEFAULT, &BlockSurface[1], 0)))
		{
			BlockSurface[0]->Release();
			BlockSurface[0] = NULL;
		}
	}
}

//==========================================================================
//
// D3DFB :: KillNativePals
//
// Frees all native palettes.
//
//==========================================================================

void D3DFB::KillNativePals()
{
	while (Palettes != NULL)
	{
		delete Palettes;
	}
}

//==========================================================================
//
// D3DFB :: KillNativeTexs
//
// Frees all native textures.
//
//==========================================================================

void D3DFB::KillNativeTexs()
{
	while (Textures != NULL)
	{
		delete Textures;
	}
}

//==========================================================================
//
// D3DFB :: CreateFBTexture
//
// Creates the "Framebuffer" texture. With the advent of hardware-assisted
// 2D, this is something of a misnomer now. The FBTexture is only used for
// uploading the software 3D image to video memory so that it can be drawn
// to the real frame buffer.
//
// It also creates the TempRenderTexture, since this seemed like a
// convenient place to do so.
//
//==========================================================================

bool D3DFB::CreateFBTexture ()
{
	FBFormat = IsBgra() ? D3DFMT_A8R8G8B8 : D3DFMT_L8;

	if (FAILED(D3DDevice->CreateTexture(Width, Height, 1, D3DUSAGE_DYNAMIC, FBFormat, D3DPOOL_DEFAULT, &FBTexture, NULL)))
	{
		int pow2width, pow2height, i;

		for (i = 1; i < Width; i <<= 1) {} pow2width = i;
		for (i = 1; i < Height; i <<= 1) {} pow2height = i;

		if (FAILED(D3DDevice->CreateTexture(pow2width, pow2height, 1, D3DUSAGE_DYNAMIC, FBFormat, D3DPOOL_DEFAULT, &FBTexture, NULL)))
		{
			return false;
		}
		else
		{
			FBWidth = pow2width;
			FBHeight = pow2height;
		}
	}
	else
	{
		FBWidth = Width;
		FBHeight = Height;
	}
	RenderTextureToggle = 0;
	RenderTexture[0] = NULL;
	RenderTexture[1] = NULL;
	if (FAILED(D3DDevice->CreateTexture(FBWidth, FBHeight, 1, D3DUSAGE_RENDERTARGET, D3DFMT_X8R8G8B8, D3DPOOL_DEFAULT, &RenderTexture[0], NULL)))
	{
		return false;
	}
	if (Windowed || PixelDoubling)
	{
		// Windowed or pixel doubling: Create another render texture so we can flip between them.
		RenderTextureToggle = 1;
		if (FAILED(D3DDevice->CreateTexture(FBWidth, FBHeight, 1, D3DUSAGE_RENDERTARGET, D3DFMT_X8R8G8B8, D3DPOOL_DEFAULT, &RenderTexture[1], NULL)))
		{
			return false;
		}
	}
	else
	{
		// Fullscreen and not pixel doubling: Create a render target to have the back buffer copied to.
		if (FAILED(D3DDevice->CreateRenderTarget(Width, Height, D3DFMT_X8R8G8B8, D3DMULTISAMPLE_NONE, 0, FALSE, &FrontCopySurface, NULL)))
		{
			return false;
		}
	}
	// Initialize the TempRenderTextures to black.
	for (int i = 0; i <= RenderTextureToggle; ++i)
	{
		IDirect3DSurface9 *surf;
		if (SUCCEEDED(RenderTexture[i]->GetSurfaceLevel(0, &surf)))
		{
			D3DDevice->ColorFill(surf, NULL, D3DCOLOR_XRGB(0,0,0));
			surf->Release();
		}
	}
	TempRenderTexture = RenderTexture[0];
	CurrRenderTexture = 0;
	return true;
}

//==========================================================================
//
// D3DFB :: CreatePaletteTexture
//
//==========================================================================

bool D3DFB::CreatePaletteTexture ()
{
	if (FAILED(D3DDevice->CreateTexture (256, 1, 1, 0, D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, &PaletteTexture, NULL)))
	{
		return false;
	}
	return true;
}

//==========================================================================
//
// D3DFB :: CreateGammaTexture
//
//==========================================================================

bool D3DFB::CreateGammaTexture ()
{
	// If this fails, you just won't get gamma correction in a window
	// on SM14 cards.
	assert(GammaTexture == NULL);
	if (SM14)
	{
		return SUCCEEDED(D3DDevice->CreateTexture(256, 1, 1, 0, D3DFMT_A8R8G8B8,
			D3DPOOL_MANAGED, &GammaTexture, NULL));
	}
	return false;
}

//==========================================================================
//
// D3DFB :: CreateVertexes
//
//==========================================================================

bool D3DFB::CreateVertexes ()
{
	VertexPos = -1;
	IndexPos = -1;
	QuadBatchPos = -1;
	BatchType = BATCH_None;
	if (FAILED(D3DDevice->CreateVertexBuffer(sizeof(FBVERTEX)*NUM_VERTS, 
		D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, D3DFVF_FBVERTEX, D3DPOOL_DEFAULT, &VertexBuffer, NULL)))
	{
		return false;
	}
	if (FAILED(D3DDevice->CreateIndexBuffer(sizeof(WORD)*NUM_INDEXES,
		D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, D3DFMT_INDEX16, D3DPOOL_DEFAULT, &IndexBuffer, NULL)))
	{
		return false;
	}
	return true;
}

//==========================================================================
//
// D3DFB :: CalcFullscreenCoords
//
//==========================================================================

void D3DFB::CalcFullscreenCoords (FBVERTEX verts[4], bool viewarea_only, bool can_double, D3DCOLOR color0, D3DCOLOR color1) const
{
	float offset = OldRenderTarget != NULL ? 0 : LBOffset;
	float top = offset - 0.5f;
	float texright = float(Width) / float(FBWidth);
	float texbot = float(Height) / float(FBHeight);
	float mxl, mxr, myt, myb, tmxl, tmxr, tmyt, tmyb;

	if (viewarea_only)
	{ // Just calculate vertices for the viewarea/BlendingRect
		mxl = float(BlendingRect.left) - 0.5f;
		mxr = float(BlendingRect.right) - 0.5f;
		myt = float(BlendingRect.top) + top;
		myb = float(BlendingRect.bottom) + top;
		tmxl = float(BlendingRect.left) / float(Width) * texright;
		tmxr = float(BlendingRect.right) / float(Width) * texright;
		tmyt = float(BlendingRect.top) / float(Height) * texbot;
		tmyb = float(BlendingRect.bottom) / float(Height) * texbot;
	}
	else
	{ // Calculate vertices for the whole screen
		mxl = -0.5f;
		mxr = float(Width << (can_double ? PixelDoubling : 0)) - 0.5f;
		myt = top;
		myb = float(Height << (can_double ? PixelDoubling : 0)) + top;
		tmxl = 0;
		tmxr = texright;
		tmyt = 0;
		tmyb = texbot;
	}

	//{   mxl, myt, 0, 1, 0, 0xFFFFFFFF,    tmxl,    tmyt },
	//{   mxr, myt, 0, 1, 0, 0xFFFFFFFF,    tmxr,    tmyt },
	//{   mxr, myb, 0, 1, 0, 0xFFFFFFFF,    tmxr,    tmyb },
	//{   mxl, myb, 0, 1, 0, 0xFFFFFFFF,    tmxl,    tmyb },

	verts[0].x = mxl;
	verts[0].y = myt;
	verts[0].z = 0;
	verts[0].rhw = 1;
	verts[0].color0 = color0;
	verts[0].color1 = color1;
	verts[0].tu = tmxl;
	verts[0].tv = tmyt;

	verts[1].x = mxr;
	verts[1].y = myt;
	verts[1].z = 0;
	verts[1].rhw = 1;
	verts[1].color0 = color0;
	verts[1].color1 = color1;
	verts[1].tu = tmxr;
	verts[1].tv = tmyt;

	verts[2].x = mxr;
	verts[2].y = myb;
	verts[2].z = 0;
	verts[2].rhw = 1;
	verts[2].color0 = color0;
	verts[2].color1 = color1;
	verts[2].tu = tmxr;
	verts[2].tv = tmyb;

	verts[3].x = mxl;
	verts[3].y = myb;
	verts[3].z = 0;
	verts[3].rhw = 1;
	verts[3].color0 = color0;
	verts[3].color1 = color1;
	verts[3].tu = tmxl;
	verts[3].tv = tmyb;
}

//==========================================================================
//
// D3DFB :: GetPageCount
//
//==========================================================================

int D3DFB::GetPageCount ()
{
	return 1;
}

//==========================================================================
//
// D3DFB :: PaletteChanged
//
//==========================================================================

void D3DFB::PaletteChanged ()
{
}

//==========================================================================
//
// D3DFB :: QueryNewPalette
//
//==========================================================================

int D3DFB::QueryNewPalette ()
{
	return 0;
}

//==========================================================================
//
// D3DFB :: IsValid
//
//==========================================================================

bool D3DFB::IsValid ()
{
	return D3DDevice != NULL;
}

//==========================================================================
//
// D3DFB :: GetHR
//
//==========================================================================

HRESULT D3DFB::GetHR ()
{
	return LastHR;
}

//==========================================================================
//
// D3DFB :: IsFullscreen
//
//==========================================================================

bool D3DFB::IsFullscreen ()
{
	return !Windowed;
}

//==========================================================================
//
// D3DFB :: Lock
//
//==========================================================================

bool D3DFB::Lock (bool buffered)
{
	if (LockCount++ > 0)
	{
		return false;
	}
	assert (!In2D);
	Accel2D = vid_hw2d;
	Buffer = MemBuffer;
	return false;
}

//==========================================================================
//
// D3DFB :: Unlock
//
//==========================================================================

void D3DFB::Unlock ()
{
	LOG1 ("Unlock     <%d>\n", LockCount);

	if (LockCount == 0)
	{
		return;
	}

	if (UpdatePending && LockCount == 1)
	{
		Update ();
	}
	else if (--LockCount == 0)
	{
		Buffer = NULL;
	}
}

//==========================================================================
//
// D3DFB :: Update
//
// When In2D == 0: Copy buffer to screen and present
// When In2D == 1: Copy buffer to screen but do not present
// When In2D == 2: Set up for 2D drawing but do not draw anything
// When In2D == 3: Present and set In2D to 0
//
//==========================================================================

void D3DFB::Update ()
{
	if (In2D == 3)
	{
		if (InScene)
		{
			DrawRateStuff();
			DrawPackedTextures(d3d_showpacks);
			EndBatch();		// Make sure all batched primitives are drawn.
			In2D = 0;
			Flip();
		}
		In2D = 0;
		return;
	}

	if (LockCount != 1)
	{
		I_FatalError ("Framebuffer must have exactly 1 lock to be updated");
		if (LockCount > 0)
		{
			UpdatePending = true;
			--LockCount;
		}
		return;
	}

	if (In2D == 0)
	{
		DrawRateStuff();
	}

	if (NeedGammaUpdate)
	{
		float psgamma[4];
		float igamma;

		NeedGammaUpdate = false;
		igamma = 1 / Gamma;
		if (!Windowed)
		{
			D3DGAMMARAMP ramp;

			for (int i = 0; i < 256; ++i)
			{
				ramp.blue[i] = ramp.green[i] = ramp.red[i] = WORD(65535.f * powf(i / 255.f, igamma));
			}
			LOG("SetGammaRamp\n");
			D3DDevice->SetGammaRamp(0, D3DSGR_CALIBRATE, &ramp);
		}
		else
		{
			if (igamma != 1)
			{
				UpdateGammaTexture(igamma);
				GammaShader = Shaders[SHADER_GammaCorrection];
			}
			else
			{
				GammaShader = NULL;
			}
		}
		psgamma[2] = psgamma[1] = psgamma[0] = igamma;
		psgamma[3] = 0.5;		// For SM14 version
		D3DDevice->SetPixelShaderConstantF(PSCONST_Gamma, psgamma, 1);
	}
	
	if (NeedPalUpdate)
	{
		UploadPalette();
	}

	BlitCycles.Reset();
	BlitCycles.Clock();

	LockCount = 0;
	HRESULT hr = D3DDevice->TestCooperativeLevel();
	if (FAILED(hr) && (hr != D3DERR_DEVICENOTRESET || !Reset()))
	{
		Sleep(1);
		return;
	}
	Draw3DPart(In2D <= 1);
	if (In2D == 0)
	{
		Flip();
	}

	BlitCycles.Unclock();
	//LOG1 ("cycles = %d\n", BlitCycles);

	Buffer = NULL;
	UpdatePending = false;
}

//==========================================================================
//
// D3DFB :: Flip
//
//==========================================================================

void D3DFB::Flip()
{
	assert(InScene);

	DrawLetterbox();
	DoWindowedGamma();
	D3DDevice->EndScene();

	CopyNextFrontBuffer();

	// Attempt to counter input lag.
	if (d3d_antilag && BlockSurface[0] != NULL)
	{
		D3DLOCKED_RECT lr;
		volatile int dummy;
		D3DDevice->ColorFill(BlockSurface[BlockNum], NULL, D3DCOLOR_ARGB(0xFF,0,0x20,0x50));
		BlockNum ^= 1;
		if (!FAILED((BlockSurface[BlockNum]->LockRect(&lr, NULL, D3DLOCK_READONLY))))
		{
			dummy = *(int *)lr.pBits;
			BlockSurface[BlockNum]->UnlockRect();
		}
	}
	// Limiting the frame rate is as simple as waiting for the timer to signal this event.
	if (FPSLimitEvent != NULL)
	{
		WaitForSingleObject(FPSLimitEvent, 1000);
	}
	D3DDevice->Present(NULL, NULL, NULL, NULL);
	InScene = false;

	if (RenderTextureToggle)
	{
		// Flip the TempRenderTexture to the other one now.
		CurrRenderTexture ^= RenderTextureToggle;
		TempRenderTexture = RenderTexture[CurrRenderTexture];
	}

	if (Windowed)
	{
		RECT box;
		GetClientRect(Window, &box);
		if (box.right > 0 && box.right > 0 && (Width != box.right || Height != box.bottom))
		{
			Resize(box.right, box.bottom);

			TrueHeight = Height;
			PixelDoubling = 0;
			LBOffsetI = 0;
			LBOffset = 0.0f;
			Reset();

			V_OutputResized(Width, Height);
		}
	}
}

//==========================================================================
//
// D3DFB :: CopyNextFrontBuffer
//
// Duplicates the contents of the back buffer that will become the front
// buffer upon Present into FrontCopySurface so that we can get the
// contents of the display without wasting time in GetFrontBufferData().
//
//==========================================================================

void D3DFB::CopyNextFrontBuffer()
{
	IDirect3DSurface9 *backbuff;

	if (Windowed || PixelDoubling)
	{
		// Windowed mode or pixel doubling: TempRenderTexture has what we want
		SAFE_RELEASE( FrontCopySurface );
		if (SUCCEEDED(TempRenderTexture->GetSurfaceLevel(0, &backbuff)))
		{
			FrontCopySurface = backbuff;
		}
	}
	else
	{
		// Fullscreen, not pixel doubled: The back buffer has what we want,
		// but it might be letter boxed.
		if (SUCCEEDED(D3DDevice->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &backbuff)))
		{
			RECT srcrect = { 0, LBOffsetI, Width, LBOffsetI + Height };
			D3DDevice->StretchRect(backbuff, &srcrect, FrontCopySurface, NULL, D3DTEXF_NONE);
			backbuff->Release();
		}
	}
}

//==========================================================================
//
// D3DFB :: PaintToWindow
//
//==========================================================================

bool D3DFB::PaintToWindow ()
{
	HRESULT hr;

	if (LockCount != 0)
	{
		return false;
	}
	hr = D3DDevice->TestCooperativeLevel();
	if (FAILED(hr))
	{
		if (hr != D3DERR_DEVICENOTRESET || !Reset())
		{
			Sleep (1);
			return false;
		}
	}
	Draw3DPart(true);
	return true;
}

//==========================================================================
//
// D3DFB :: Draw3DPart
//
// The software 3D part, to be exact.
//
//==========================================================================

void D3DFB::Draw3DPart(bool copy3d)
{
	if (copy3d)
	{
		RECT texrect = { 0, 0, Width, Height };
		D3DLOCKED_RECT lockrect;

		if ((FBWidth == Width && FBHeight == Height &&
			SUCCEEDED(FBTexture->LockRect (0, &lockrect, NULL, D3DLOCK_DISCARD))) ||
			SUCCEEDED(FBTexture->LockRect (0, &lockrect, &texrect, 0)))
		{
			if (IsBgra() && FBFormat == D3DFMT_A8R8G8B8)
			{
				if (lockrect.Pitch == Pitch * sizeof(uint32_t) && Pitch == Width)
				{
					memcpy(lockrect.pBits, MemBuffer, Width * Height * sizeof(uint32_t));
				}
				else
				{
					uint32_t *dest = (uint32_t *)lockrect.pBits;
					uint32_t *src = (uint32_t*)MemBuffer;
					for (int y = 0; y < Height; y++)
					{
						memcpy(dest, src, Width * sizeof(uint32_t));
						dest = reinterpret_cast<uint32_t*>(reinterpret_cast<uint8_t*>(dest) + lockrect.Pitch);
						src += Pitch;
					}
				}
			}
			else if (!IsBgra() && FBFormat == D3DFMT_L8)
			{
				if (lockrect.Pitch == Pitch && Pitch == Width)
				{
					memcpy(lockrect.pBits, MemBuffer, Width * Height);
				}
				else
				{
					BYTE *dest = (BYTE *)lockrect.pBits;
					BYTE *src = (BYTE *)MemBuffer;
					for (int y = 0; y < Height; y++)
					{
						memcpy(dest, src, Width);
						dest = reinterpret_cast<BYTE*>(reinterpret_cast<uint8_t*>(dest) + lockrect.Pitch);
						src += Pitch;
					}
				}
			}
			else
			{
				memset(lockrect.pBits, 0, lockrect.Pitch * Height);
			}
			FBTexture->UnlockRect (0);
		}
	}
	InScene = true;
	D3DDevice->BeginScene();
	D3DDevice->SetRenderState(D3DRS_ANTIALIASEDLINEENABLE, vid_hwaalines);
	assert(OldRenderTarget == NULL);
	if (TempRenderTexture != NULL &&
		((Windowed && TempRenderTexture != FinalWipeScreen) || GatheringWipeScreen || PixelDoubling))
	{
		IDirect3DSurface9 *targetsurf;
		if (SUCCEEDED(TempRenderTexture->GetSurfaceLevel(0, &targetsurf)))
		{
			if (SUCCEEDED(D3DDevice->GetRenderTarget(0, &OldRenderTarget)))
			{
				if (FAILED(D3DDevice->SetRenderTarget(0, targetsurf)))
				{
					// Setting the render target failed.
				}
			}
			targetsurf->Release();
		}
	}

	SetTexture(0, FBTexture);
	SetPaletteTexture(PaletteTexture, 256, BorderColor);
	D3DDevice->SetFVF (D3DFVF_FBVERTEX);
	memset(Constant, 0, sizeof(Constant));
	SetAlphaBlend(D3DBLENDOP(0));
	EnableAlphaTest(FALSE);
	if (IsBgra())
		SetPixelShader(Shaders[SHADER_NormalColor]);
	else
		SetPixelShader(Shaders[SHADER_NormalColorPal]);
	if (copy3d)
	{
		FBVERTEX verts[4];
		D3DCOLOR color0, color1;
		if (Accel2D)
		{
			auto &map = swrenderer::realfixedcolormap;
			if (map == NULL)
			{
				color0 = 0;
				color1 = 0xFFFFFFF;
			}
			else
			{
				color0 = D3DCOLOR_COLORVALUE(map->ColorizeStart[0]/2, map->ColorizeStart[1]/2, map->ColorizeStart[2]/2, 0);
				color1 = D3DCOLOR_COLORVALUE(map->ColorizeEnd[0]/2, map->ColorizeEnd[1]/2, map->ColorizeEnd[2]/2, 1);
				if (IsBgra())
					SetPixelShader(Shaders[SHADER_SpecialColormap]);
				else
					SetPixelShader(Shaders[SHADER_SpecialColormapPal]);
			}
		}
		else
		{
			color0 = FlashColor0;
			color1 = FlashColor1;
		}
		CalcFullscreenCoords(verts, Accel2D, false, color0, color1);
		D3DDevice->DrawPrimitiveUP(D3DPT_TRIANGLEFAN, 2, verts, sizeof(FBVERTEX));
	}
	if (IsBgra())
		SetPixelShader(Shaders[SHADER_NormalColor]);
	else
		SetPixelShader(Shaders[SHADER_NormalColorPal]);
}

//==========================================================================
//
// D3DFB :: DrawLetterbox
//
// Draws the black bars at the top and bottom of the screen for letterboxed
// modes.
//
//==========================================================================

void D3DFB::DrawLetterbox()
{
	if (LBOffsetI != 0)
	{
		D3DRECT rects[2] = { { 0, 0, Width, LBOffsetI }, { 0, Height + LBOffsetI, Width, TrueHeight } };
		D3DDevice->Clear (2, rects, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,0), 1.f, 0);
	}
}

//==========================================================================
//
// D3DFB :: DoWindowedGamma
//
// Draws the render target texture to the real back buffer using a gamma-
// correcting pixel shader.
//
//==========================================================================

void D3DFB::DoWindowedGamma()
{
	if (OldRenderTarget != NULL)
	{
		FBVERTEX verts[4];

		CalcFullscreenCoords(verts, false, true, 0, 0xFFFFFFFF);
		D3DDevice->SetRenderTarget(0, OldRenderTarget);
		D3DDevice->SetFVF(D3DFVF_FBVERTEX);
		SetTexture(0, TempRenderTexture);
		SetPixelShader(Windowed && GammaShader ? GammaShader : Shaders[SHADER_NormalColor]);
		if (SM14 && Windowed && GammaShader)
		{
			SetTexture(2, GammaTexture);
			SetTexture(3, GammaTexture);
			SetTexture(4, GammaTexture);
		}
		SetAlphaBlend(D3DBLENDOP(0));
		EnableAlphaTest(FALSE);
		D3DDevice->DrawPrimitiveUP(D3DPT_TRIANGLEFAN, 2, verts, sizeof(FBVERTEX));
		OldRenderTarget->Release();
		OldRenderTarget = NULL;
		if (SM14 && Windowed && GammaShader)
		{
//			SetTexture(0, GammaTexture);
//			SetPixelShader(Shaders[SHADER_NormalColor]);
//			D3DDevice->DrawPrimitiveUP(D3DPT_TRIANGLEFAN, 2, verts, sizeof(FBVERTEX));
		}
	}
}

//==========================================================================
//
// D3DFB :: UpdateGammaTexture
//
// Updates the gamma texture used by the PS14 shader. We only use the first
// half of the texture so that we needn't worry about imprecision causing
// it to grab from the border.
//
//==========================================================================

void D3DFB::UpdateGammaTexture(float igamma)
{
	D3DLOCKED_RECT lockrect;

	if (GammaTexture != NULL && SUCCEEDED(GammaTexture->LockRect(0, &lockrect, NULL, 0)))
	{
		BYTE *pix = (BYTE *)lockrect.pBits;
		for (int i = 0; i <= 128; ++i)
		{
			pix[i*4+2] = pix[i*4+1] = pix[i*4] = BYTE(255.f * powf(i / 128.f, igamma));
			pix[i*4+3] = 255;
		}
		GammaTexture->UnlockRect(0);
	}
}

//==========================================================================
//
// D3DFB :: DoOffByOneCheck
//
// Pixel Shader 1.x does not have enough precision to properly map a "color"
// from the source texture to an index in the palette texture. The best we
// can do is use 255 pixels of the palette and get the 256th from the
// texture border color. This routine determines which pixel of the texture
// is skipped so that we don't use it for palette data.
//
//==========================================================================

void D3DFB::DoOffByOneCheck ()
{
	IDirect3DSurface9 *savedrendertarget;
	IDirect3DSurface9 *testsurf, *readsurf;
	D3DLOCKED_RECT lockrect;
	RECT testrect = { 0, 0, 256, 1 };
	float texright = 256.f / float(FBWidth);
	float texbot = 1.f / float(FBHeight);
	FBVERTEX verts[4] =
	{
		{ -0.5f,  -0.5f, 0.5f, 1.f, D3DCOLOR_RGBA(0,0,0,0), D3DCOLOR_RGBA(255,255,255,255),      0.f,    0.f },
		{ 255.5f, -0.5f, 0.5f, 1.f, D3DCOLOR_RGBA(0,0,0,0), D3DCOLOR_RGBA(255,255,255,255), texright,    0.f },
		{ 255.5f,  0.5f, 0.5f, 1.f, D3DCOLOR_RGBA(0,0,0,0), D3DCOLOR_RGBA(255,255,255,255), texright, texbot },
		{ -0.5f,   0.5f, 0.5f, 1.f, D3DCOLOR_RGBA(0,0,0,0), D3DCOLOR_RGBA(255,255,255,255),      0.f, texbot }
	};
	int i, c;

	if (SkipAt >= 0)
	{
		return;
	}

	// Create an easily recognizable R3G3B2 palette.
	if (SUCCEEDED(PaletteTexture->LockRect(0, &lockrect, NULL, 0)))
	{
		BYTE *pal = (BYTE *)(lockrect.pBits);
		for (i = 0; i < 256; ++i)
		{
			pal[i*4+0] = (i & 0x03) << 6;		// blue
			pal[i*4+1] = (i & 0x1C) << 3;		// green
			pal[i*4+2] = (i & 0xE0);			// red;
			pal[i*4+3] = 255;
		}
		PaletteTexture->UnlockRect (0);
	}
	else
	{
		return;
	}
	// Prepare a texture with values 0-256.
	if (SUCCEEDED(FBTexture->LockRect(0, &lockrect, &testrect, 0)))
	{
		for (i = 0; i < 256; ++i)
		{
			((BYTE *)lockrect.pBits)[i] = i;
		}
		FBTexture->UnlockRect(0);
	}
	else
	{
		return;
	}
	// Create a render target that we can draw it to.
	if (FAILED(D3DDevice->GetRenderTarget(0, &savedrendertarget)))
	{
		return;
	}
	if (FAILED(D3DDevice->CreateRenderTarget(256, 1, D3DFMT_A8R8G8B8, D3DMULTISAMPLE_NONE, 0, FALSE, &testsurf, NULL)))
	{
		return;
	}
	if (FAILED(D3DDevice->CreateOffscreenPlainSurface(256, 1, D3DFMT_A8R8G8B8, D3DPOOL_SYSTEMMEM, &readsurf, NULL)))
	{
		testsurf->Release();
		return;
	}
	if (FAILED(D3DDevice->SetRenderTarget(0, testsurf)))
	{
		testsurf->Release();
		readsurf->Release();
		return;
	}
	// Write it to the render target using the pixel shader.
	D3DDevice->BeginScene();
	D3DDevice->SetTexture(0, FBTexture);
	D3DDevice->SetTexture(1, PaletteTexture);
	D3DDevice->SetFVF(D3DFVF_FBVERTEX);
	D3DDevice->SetPixelShader(Shaders[SHADER_NormalColorPal]);
	SetConstant(PSCONST_PaletteMod, 1.f, 0.5f / 256.f, 0, 0);
	D3DDevice->DrawPrimitiveUP(D3DPT_TRIANGLEFAN, 2, verts, sizeof(FBVERTEX));
	D3DDevice->EndScene();
	D3DDevice->SetRenderTarget(0, savedrendertarget);
	savedrendertarget->Release();
	// Now read it back and see where it skips an entry
	if (SUCCEEDED(D3DDevice->GetRenderTargetData(testsurf, readsurf)) &&
		SUCCEEDED(readsurf->LockRect(&lockrect, &testrect, D3DLOCK_READONLY)))
	{
		const BYTE *pix = (const BYTE *)lockrect.pBits;
		for (i = 0; i < 256; ++i, pix += 4)
		{
			c = (pix[0] >> 6) |					// blue
				((pix[1] >> 5) << 2) |			// green
				((pix[2] >> 5) << 5);			// red
			if (c != i)
			{
				break;
			}
		}
	}
	readsurf->UnlockRect();
	readsurf->Release();
	testsurf->Release();
	SkipAt = i;
}

void D3DFB::UploadPalette ()
{
	D3DLOCKED_RECT lockrect;

	if (SkipAt < 0)
	{
		if (SM14)
		{
			DoOffByOneCheck();
		}
		else
		{
			SkipAt = 256;
		}
	}
	if (SUCCEEDED(PaletteTexture->LockRect(0, &lockrect, NULL, 0)))
	{
		BYTE *pix = (BYTE *)lockrect.pBits;
		int i;

		for (i = 0; i < SkipAt; ++i, pix += 4)
		{
			pix[0] = SourcePalette[i].b;
			pix[1] = SourcePalette[i].g;
			pix[2] = SourcePalette[i].r;
			pix[3] = (i == 0 ? 0 : 255);
			// To let masked textures work, the first palette entry's alpha is 0.
		}
		pix += 4;
		for (; i < 255; ++i, pix += 4)
		{
			pix[0] = SourcePalette[i].b;
			pix[1] = SourcePalette[i].g;
			pix[2] = SourcePalette[i].r;
			pix[3] = 255;
		}
		PaletteTexture->UnlockRect(0);
		BorderColor = D3DCOLOR_XRGB(SourcePalette[255].r, SourcePalette[255].g, SourcePalette[255].b);
	}
}

PalEntry *D3DFB::GetPalette ()
{
	return SourcePalette;
}

void D3DFB::UpdatePalette ()
{
	NeedPalUpdate = true;
}

bool D3DFB::SetGamma (float gamma)
{
	LOG1 ("SetGamma %g\n", gamma);
	Gamma = gamma;
	NeedGammaUpdate = true;
	return true;
}

bool D3DFB::SetFlash (PalEntry rgb, int amount)
{
	FlashColor = rgb;
	FlashAmount = amount;

	// Fill in the constants for the pixel shader to do linear interpolation between the palette and the flash:
	float r = rgb.r / 255.f, g = rgb.g / 255.f, b = rgb.b / 255.f, a = amount / 256.f;
	FlashColor0 = D3DCOLOR_COLORVALUE(r * a, g * a, b * a, 0);
	a = 1 - a;
	FlashColor1 = D3DCOLOR_COLORVALUE(a, a, a, 1);
	return true;
}

void D3DFB::GetFlash (PalEntry &rgb, int &amount)
{
	rgb = FlashColor;
	amount = FlashAmount;
}

void D3DFB::GetFlashedPalette (PalEntry pal[256])
{
	memcpy (pal, SourcePalette, 256*sizeof(PalEntry));
	if (FlashAmount)
	{
		DoBlending (pal, pal, 256, FlashColor.r, FlashColor.g, FlashColor.b, FlashAmount);
	}
}

void D3DFB::SetVSync (bool vsync)
{
	if (VSync != vsync)
	{
		VSync = vsync;
		Reset();
	}
}

void D3DFB::NewRefreshRate ()
{
	if (!Windowed)
	{
		Reset();
	}
}

void D3DFB::Blank ()
{
	// Only used by movie player, which isn't working with D3D9 yet.
}

void D3DFB::SetBlendingRect(int x1, int y1, int x2, int y2)
{
	BlendingRect.left = x1;
	BlendingRect.top = y1;
	BlendingRect.right = x2;
	BlendingRect.bottom = y2;
}

//==========================================================================
//
// D3DFB :: GetScreenshotBuffer
//
// Returns a pointer into a surface holding the current screen data.
//
//==========================================================================

void D3DFB::GetScreenshotBuffer(const BYTE *&buffer, int &pitch, ESSType &color_type)
{
	D3DLOCKED_RECT lrect;

	if (!Accel2D)
	{
		Super::GetScreenshotBuffer(buffer, pitch, color_type);
		return;
	}
	buffer = NULL;
	if ((ScreenshotTexture = GetCurrentScreen()) != NULL)
	{
		if (FAILED(ScreenshotTexture->GetSurfaceLevel(0, &ScreenshotSurface)))
		{
			ScreenshotTexture->Release();
			ScreenshotTexture = NULL;
		}
		else if (FAILED(ScreenshotSurface->LockRect(&lrect, NULL, D3DLOCK_READONLY | D3DLOCK_NOSYSLOCK)))
		{
			ScreenshotSurface->Release();
			ScreenshotSurface = NULL;
			ScreenshotTexture->Release();
			ScreenshotTexture = NULL;
		}
		else
		{
			buffer = (const BYTE *)lrect.pBits;
			pitch = lrect.Pitch;
			color_type = SS_BGRA;
		}
	}
}

//==========================================================================
//
// D3DFB :: ReleaseScreenshotBuffer
//
//==========================================================================

void D3DFB::ReleaseScreenshotBuffer()
{
	if (LockCount > 0)
	{
		Super::ReleaseScreenshotBuffer();
	}
	if (ScreenshotSurface != NULL)
	{
		ScreenshotSurface->UnlockRect();
		ScreenshotSurface->Release();
		ScreenshotSurface = NULL;
	}
	SAFE_RELEASE( ScreenshotTexture );
}

//==========================================================================
//
// D3DFB :: GetCurrentScreen
//
// Returns a texture containing the pixels currently visible on-screen.
//
//==========================================================================

IDirect3DTexture9 *D3DFB::GetCurrentScreen(D3DPOOL pool)
{
	IDirect3DTexture9 *tex;
	IDirect3DSurface9 *surf;
	D3DSURFACE_DESC desc;
	HRESULT hr;

	assert(pool == D3DPOOL_SYSTEMMEM || pool == D3DPOOL_DEFAULT);

	if (FrontCopySurface == NULL || FAILED(FrontCopySurface->GetDesc(&desc)))
	{
		return NULL;
	}
	if (pool == D3DPOOL_SYSTEMMEM)
	{
		hr = D3DDevice->CreateTexture(desc.Width, desc.Height, 1, 0, desc.Format, D3DPOOL_SYSTEMMEM, &tex, NULL);
	}
	else
	{
		hr = D3DDevice->CreateTexture(FBWidth, FBHeight, 1, D3DUSAGE_RENDERTARGET, desc.Format, D3DPOOL_DEFAULT, &tex, NULL);
	}
	if (FAILED(hr))
	{
		return NULL;
	}
	if (FAILED(tex->GetSurfaceLevel(0, &surf)))
	{
		tex->Release();
		return NULL;
	}
	if (pool == D3DPOOL_SYSTEMMEM)
	{
		// Video -> System memory : use GetRenderTargetData
		hr = D3DDevice->GetRenderTargetData(FrontCopySurface, surf);
	}
	else
	{
		// Video -> Video memory : use StretchRect
		RECT destrect = { 0, 0, Width, Height };
		hr = D3DDevice->StretchRect(FrontCopySurface, NULL, surf, &destrect, D3DTEXF_POINT);
	}
	surf->Release();
	if (FAILED(hr))
	{
		tex->Release();
		return NULL;
	}
	return tex;
}

/**************************************************************************/
/*                                  2D Stuff                              */
/**************************************************************************/

//==========================================================================
//
// D3DFB :: DrawPackedTextures
//
// DEBUG: Draws the texture atlases to the screen, starting with the
// 1-based packnum. Ignores atlases that are flagged for use by one
// texture only.
//
//==========================================================================

void D3DFB::DrawPackedTextures(int packnum)
{
	D3DCOLOR empty_colors[8] =
	{
		0x50FF0000, 0x5000FF00, 0x500000FF, 0x50FFFF00,
		0x50FF00FF, 0x5000FFFF, 0x50FF8000, 0x500080FF
	};
	Atlas *pack;
	int x = 8, y = 8;

	if (packnum <= 0)
	{
		return;
	}
	pack = Atlases;
	// Find the first texture atlas that is an actual atlas.
	while (pack != NULL && pack->OneUse)
	{ // Skip textures that aren't used as atlases
		pack = pack->Next;
	}
	// Skip however many atlases we would have otherwise drawn
	// until we've skipped <packnum> of them.
	while (pack != NULL && packnum != 1)
	{
		if (!pack->OneUse)
		{ // Skip textures that aren't used as atlases
			packnum--;
		}
		pack = pack->Next;
	}
	// Draw atlases until we run out of room on the screen.
	while (pack != NULL)
	{
		if (pack->OneUse)
		{ // Skip textures that aren't used as atlases
			pack = pack->Next;
			continue;
		}

		AddColorOnlyRect(x-1, y-1-LBOffsetI, 258, 258, D3DCOLOR_XRGB(255,255,0));
		int back = 0;
		for (PackedTexture *box = pack->UsedList; box != NULL; box = box->Next)
		{
			AddColorOnlyQuad(
				x + box->Area.left * 256 / pack->Width,
				y + box->Area.top * 256 / pack->Height,
				(box->Area.right - box->Area.left) * 256 / pack->Width,
				(box->Area.bottom - box->Area.top) * 256 / pack->Height, empty_colors[back]);
			back = (back + 1) & 7;
		}
//		AddColorOnlyQuad(x, y-LBOffsetI, 256, 256, D3DCOLOR_ARGB(180,0,0,0));

		CheckQuadBatch();

		BufferedTris *quad = &QuadExtra[QuadBatchPos];
		FBVERTEX *vert = &VertexData[VertexPos];

		quad->Group1 = 0;
		if (pack->Format == D3DFMT_L8/* && !tex->IsGray*/)
		{
			quad->Flags = BQF_WrapUV | BQF_GamePalette/* | BQF_DisableAlphaTest*/;
			quad->ShaderNum = BQS_PalTex;
		}
		else
		{
			quad->Flags = BQF_WrapUV/* | BQF_DisableAlphaTest*/;
			quad->ShaderNum = BQS_Plain;
		}
		quad->Palette = NULL;
		quad->Texture = pack->Tex;
		quad->NumVerts = 4;
		quad->NumTris = 2;

		float x0 = float(x) - 0.5f;
		float y0 = float(y) - 0.5f;
		float x1 = x0 + 256.f;
		float y1 = y0 + 256.f;

		vert[0].x = x0;
		vert[0].y = y0;
		vert[0].z = 0;
		vert[0].rhw = 1;
		vert[0].color0 = 0;
		vert[0].color1 = 0xFFFFFFFF;
		vert[0].tu = 0;
		vert[0].tv = 0;

		vert[1].x = x1;
		vert[1].y = y0;
		vert[1].z = 0;
		vert[1].rhw = 1;
		vert[1].color0 = 0;
		vert[1].color1 = 0xFFFFFFFF;
		vert[1].tu = 1;
		vert[1].tv = 0;

		vert[2].x = x1;
		vert[2].y = y1;
		vert[2].z = 0;
		vert[2].rhw = 1;
		vert[2].color0 = 0;
		vert[2].color1 = 0xFFFFFFFF;
		vert[2].tu = 1;
		vert[2].tv = 1;

		vert[3].x = x0;
		vert[3].y = y1;
		vert[3].z = 0;
		vert[3].rhw = 1;
		vert[3].color0 = 0;
		vert[3].color1 = 0xFFFFFFFF;
		vert[3].tu = 0;
		vert[3].tv = 1;

		IndexData[IndexPos    ] = VertexPos;
		IndexData[IndexPos + 1] = VertexPos + 1;
		IndexData[IndexPos + 2] = VertexPos + 2;
		IndexData[IndexPos + 3] = VertexPos;
		IndexData[IndexPos + 4] = VertexPos + 2;
		IndexData[IndexPos + 5] = VertexPos + 3;

		QuadBatchPos++;
		VertexPos += 4;
		IndexPos += 6;

		x += 256 + 8;
		if (x > Width - 256)
		{
			x = 8;
			y += 256 + 8;
			if (y > TrueHeight - 256)
			{
				return;
			}
		}
		pack = pack->Next;
	}
}

//==========================================================================
//
// D3DFB :: AllocPackedTexture
//
// Finds space to pack an image inside a texture atlas and returns it.
// Large images and those that need to wrap always get their own textures.
//
//==========================================================================

D3DFB::PackedTexture *D3DFB::AllocPackedTexture(int w, int h, bool wrapping, D3DFORMAT format)
{
	Atlas *pack;
	Rect box;
	bool padded;

	// The - 2 to account for padding
	if (w > 256 - 2 || h > 256 - 2 || wrapping)
	{ // Create a new texture atlas.
		pack = new Atlas(this, w, h, format);
		pack->OneUse = true;
		box = pack->Packer.Insert(w, h);
		padded = false;
	}
	else
	{ // Try to find space in an existing texture atlas.
		w += 2; // Add padding
		h += 2;
		for (pack = Atlases; pack != NULL; pack = pack->Next)
		{
			// Use the first atlas it fits in.
			if (pack->Format == format)
			{
				box = pack->Packer.Insert(w, h);
				if (box.width != 0)
				{
					break;
				}
			}
		}
		if (pack == NULL)
		{ // Create a new texture atlas.
			pack = new Atlas(this, DEF_ATLAS_WIDTH, DEF_ATLAS_HEIGHT, format);
			box = pack->Packer.Insert(w, h);
		}
		padded = true;
	}
	assert(box.width != 0 && box.height != 0);
	return pack->AllocateImage(box, padded);
}

//==========================================================================
//
// Atlas Constructor
//
//==========================================================================

D3DFB::Atlas::Atlas(D3DFB *fb, int w, int h, D3DFORMAT format)
	: Packer(w, h, true)
{
	Tex = NULL;
	Format = format;
	UsedList = NULL;
	OneUse = false;
	Width = 0;
	Height = 0;
	Next = NULL;

	// Attach to the end of the atlas list
	Atlas **prev = &fb->Atlases;
	while (*prev != NULL)
	{
		prev = &((*prev)->Next);
	}
	*prev = this;

#if 1
	if (FAILED(fb->D3DDevice->CreateTexture(w, h, 1, 0, format, D3DPOOL_MANAGED, &Tex, NULL)))
#endif
	{ // Try again, using power-of-2 sizes
		int i;

		for (i = 1; i < w; i <<= 1) {} w = i;
		for (i = 1; i < h; i <<= 1) {} h = i;
		if (FAILED(fb->D3DDevice->CreateTexture(w, h, 1, 0, format, D3DPOOL_MANAGED, &Tex, NULL)))
		{
			return;
		}
	}
	Width = w;
	Height = h;
}

//==========================================================================
//
// Atlas Destructor
//
//==========================================================================

D3DFB::Atlas::~Atlas()
{
	PackedTexture *box, *next;

	SAFE_RELEASE( Tex );
	for (box = UsedList; box != NULL; box = next)
	{
		next = box->Next;
		delete box;
	}
}

//==========================================================================
//
// Atlas :: AllocateImage
//
// Moves the box from the empty list to the used list, sizing it to the
// requested dimensions and adding additional boxes to the empty list if
// needed.
//
// The passed box *MUST* be in this texture atlas's empty list.
//
//==========================================================================

D3DFB::PackedTexture *D3DFB::Atlas::AllocateImage(const Rect &rect, bool padded)
{
	PackedTexture *box = new PackedTexture;

	box->Owner = this;
	box->Area.left = rect.x;
	box->Area.top = rect.y;
	box->Area.right = rect.x + rect.width;
	box->Area.bottom = rect.y + rect.height;

	box->Left = float(box->Area.left + padded) / Width;
	box->Right = float(box->Area.right - padded) / Width;
	box->Top = float(box->Area.top + padded) / Height;
	box->Bottom = float(box->Area.bottom - padded) / Height;

	box->Padded = padded;

	// Add it to the used list.
	box->Next = UsedList;
	if (box->Next != NULL)
	{
		box->Next->Prev = &box->Next;
	}
	UsedList = box;
	box->Prev = &UsedList;

	return box;
}

//==========================================================================
//
// Atlas :: FreeBox
//
// Removes a box from the used list and deletes it. Space is returned to the
// waste list. Once all boxes for this atlas are freed, the entire bin
// packer is reinitialized for maximum efficiency.
//
//==========================================================================

void D3DFB::Atlas::FreeBox(D3DFB::PackedTexture *box)
{
	*(box->Prev) = box->Next;
	if (box->Next != NULL)
	{
		box->Next->Prev = box->Prev;
	}
	Rect waste;
	waste.x = box->Area.left;
	waste.y = box->Area.top;
	waste.width = box->Area.right - box->Area.left;
	waste.height = box->Area.bottom - box->Area.top;
	box->Owner->Packer.AddWaste(waste);
	delete box;
	if (UsedList == NULL)
	{
		Packer.Init(Width, Height, true);
	}
}

//==========================================================================
//
// D3DTex Constructor
//
//==========================================================================

D3DTex::D3DTex(FTexture *tex, D3DFB *fb, bool wrapping)
{
	// Attach to the texture list for the D3DFB
	Next = fb->Textures;
	if (Next != NULL)
	{
		Next->Prev = &Next;
	}
	Prev = &fb->Textures;
	fb->Textures = this;

	GameTex = tex;
	Box = NULL;
	IsGray = false;

	Create(fb, wrapping);
}

//==========================================================================
//
// D3DTex Destructor
//
//==========================================================================

D3DTex::~D3DTex()
{
	if (Box != NULL)
	{
		Box->Owner->FreeBox(Box);
		Box = NULL;
	}
	// Detach from the texture list
	*Prev = Next;
	if (Next != NULL)
	{
		Next->Prev = Prev;
	}
	// Remove link from the game texture
	if (GameTex != NULL)
	{
		GameTex->Native = NULL;
	}
}

//==========================================================================
//
// D3DTex :: CheckWrapping
//
// Returns true if the texture is compatible with the specified wrapping
// mode.
//
//==========================================================================

bool D3DTex::CheckWrapping(bool wrapping)
{
	 // If it doesn't need to wrap, then it works.
	if (!wrapping)
	{
		return true;
	}
	// If it needs to wrap, then it can't be packed inside another texture.
	return Box->Owner->OneUse;
}

//==========================================================================
//
// D3DTex :: Create
//
// Creates an IDirect3DTexture9 for the texture and copies the image data
// to it. Note that unlike FTexture, this image is row-major.
//
//==========================================================================

bool D3DTex::Create(D3DFB *fb, bool wrapping)
{
	assert(Box == NULL);
	if (Box != NULL)
	{
		Box->Owner->FreeBox(Box);
	}

	Box = fb->AllocPackedTexture(GameTex->GetWidth(), GameTex->GetHeight(), wrapping, GetTexFormat());

	if (Box == NULL)
	{
		return false;
	}
	if (!Update())
	{
		Box->Owner->FreeBox(Box);
		Box = NULL;
		return false;
	}
	return true;
}

//==========================================================================
//
// D3DTex :: Update
//
// Copies image data from the underlying FTexture to the D3D texture.
//
//==========================================================================

bool D3DTex::Update()
{
	D3DSURFACE_DESC desc;
	D3DLOCKED_RECT lrect;
	RECT rect;
	BYTE *dest;

	assert(Box != NULL);
	assert(Box->Owner != NULL);
	assert(Box->Owner->Tex != NULL);
	assert(GameTex != NULL);

	if (FAILED(Box->Owner->Tex->GetLevelDesc(0, &desc)))
	{
		return false;
	}
	rect = Box->Area;
	if (FAILED(Box->Owner->Tex->LockRect(0, &lrect, &rect, 0)))
	{
		return false;
	}
	dest = (BYTE *)lrect.pBits;
	if (Box->Padded)
	{
		dest += lrect.Pitch + (desc.Format == D3DFMT_L8 ? 1 : 4);
	}
	GameTex->FillBuffer(dest, lrect.Pitch, GameTex->GetHeight(), ToTexFmt(desc.Format));
	if (Box->Padded)
	{
		// Clear top padding row.
		dest = (BYTE *)lrect.pBits;
		int numbytes = GameTex->GetWidth() + 2;
		if (desc.Format != D3DFMT_L8)
		{
			numbytes <<= 2;
		}
		memset(dest, 0, numbytes);
		dest += lrect.Pitch;
		// Clear left and right padding columns.
		if (desc.Format == D3DFMT_L8)
		{
			for (int y = Box->Area.bottom - Box->Area.top - 2; y > 0; --y)
			{
				dest[0] = 0;
				dest[numbytes-1] = 0;
				dest += lrect.Pitch;
			}
		}
		else
		{
			for (int y = Box->Area.bottom - Box->Area.top - 2; y > 0; --y)
			{
				*(DWORD *)dest = 0;
				*(DWORD *)(dest + numbytes - 4) = 0;
				dest += lrect.Pitch;
			}
		}
		// Clear bottom padding row.
		memset(dest, 0, numbytes);
	}
	Box->Owner->Tex->UnlockRect(0);
	return true;
}

//==========================================================================
//
// D3DTex :: GetTexFormat
//
// Returns the texture format that would best fit this texture.
//
//==========================================================================

D3DFORMAT D3DTex::GetTexFormat()
{
	FTextureFormat fmt = GameTex->GetFormat();

	IsGray = false;

	switch (fmt)
	{
	case TEX_Pal:	return D3DFMT_L8;
	case TEX_Gray:	IsGray = true; return D3DFMT_L8;
	case TEX_RGB:	return D3DFMT_A8R8G8B8;
	case TEX_DXT1:	return D3DFMT_DXT1;
	case TEX_DXT2:	return D3DFMT_DXT2;
	case TEX_DXT3:	return D3DFMT_DXT3;
	case TEX_DXT4:	return D3DFMT_DXT4;
	case TEX_DXT5:	return D3DFMT_DXT5;
	default:		I_FatalError ("GameTex->GetFormat() returned invalid format.");
	}
	return D3DFMT_L8;
}

//==========================================================================
//
// D3DTex :: ToTexFmt
//
// Converts a D3DFORMAT constant to something the FTexture system
// understands.
//
//==========================================================================

FTextureFormat D3DTex::ToTexFmt(D3DFORMAT fmt)
{
	switch (fmt)
	{
	case D3DFMT_L8:			return IsGray ? TEX_Gray : TEX_Pal;
	case D3DFMT_A8R8G8B8:	return TEX_RGB;
	case D3DFMT_DXT1:		return TEX_DXT1;
	case D3DFMT_DXT2:		return TEX_DXT2;
	case D3DFMT_DXT3:		return TEX_DXT3;
	case D3DFMT_DXT4:		return TEX_DXT4;
	case D3DFMT_DXT5:		return TEX_DXT5;
	default:
		assert(0);	// LOL WUT?
		return TEX_Pal;
	}
}

//==========================================================================
//
// D3DPal Constructor
//
//==========================================================================

D3DPal::D3DPal(FRemapTable *remap, D3DFB *fb)
	: Tex(NULL), Remap(remap)
{
	int count;

	// Attach to the palette list for the D3DFB
	Next = fb->Palettes;
	if (Next != NULL)
	{
		Next->Prev = &Next;
	}
	Prev = &fb->Palettes;
	fb->Palettes = this;

	// Palette textures must be 256 entries for Shader Model 1.4
	if (fb->SM14)
	{
		count = 256;
		// If the palette isn't big enough, then we don't need to
		// worry about setting the gamma ramp.
		DoColorSkip = (remap->NumEntries >= 256 - 8);
	}
	else
	{
		int pow2count;

		// Round up to the nearest power of 2.
		for (pow2count = 1; pow2count < remap->NumEntries; pow2count <<= 1)
		{ }
		count = pow2count;
		DoColorSkip = false;
	}
	BorderColor = 0;
	RoundedPaletteSize = count;
	if (SUCCEEDED(fb->D3DDevice->CreateTexture(count, 1, 1, 0, 
		D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, &Tex, NULL)))
	{
		if (!Update())
		{
			Tex->Release();
			Tex = NULL;
		}
	}
}

//==========================================================================
//
// D3DPal Destructor
//
//==========================================================================

D3DPal::~D3DPal()
{
	SAFE_RELEASE( Tex );
	// Detach from the palette list
	*Prev = Next;
	if (Next != NULL)
	{
		Next->Prev = Prev;
	}
	// Remove link from the remap table
	if (Remap != NULL)
	{
		Remap->Native = NULL;
	}
}

//==========================================================================
//
// D3DPal :: Update
//
// Copies the palette to the texture.
//
//==========================================================================

bool D3DPal::Update()
{
	D3DLOCKED_RECT lrect;
	D3DCOLOR *buff;
	const PalEntry *pal;
	int skipat, i;

	assert(Tex != NULL);

	if (FAILED(Tex->LockRect(0, &lrect, NULL, 0)))
	{
		return false;
	}
	buff = (D3DCOLOR *)lrect.pBits;
	pal = Remap->Palette;

	// See explanation in UploadPalette() for skipat rationale.
	skipat = MIN(Remap->NumEntries, DoColorSkip ? 256 - 8 : 256);

	for (i = 0; i < skipat; ++i)
	{
		buff[i] = D3DCOLOR_ARGB(pal[i].a, pal[i].r, pal[i].g, pal[i].b);
	}
	for (++i; i < Remap->NumEntries; ++i)
	{
		buff[i] = D3DCOLOR_ARGB(pal[i].a, pal[i-1].r, pal[i-1].g, pal[i-1].b);
	}
	BorderColor = D3DCOLOR_ARGB(pal[i].a, pal[i-1].r, pal[i-1].g, pal[i-1].b);

	Tex->UnlockRect(0);
	return true;
}

//==========================================================================
//
// D3DFB :: Begin2D
//
// Begins 2D mode drawing operations. In particular, DrawTexture is
// rerouted to use Direct3D instead of the software renderer.
//
//==========================================================================

bool D3DFB::Begin2D(bool copy3d)
{
	if (!Accel2D)
	{
		return false;
	}
	if (In2D)
	{
		return true;
	}
	In2D = 2 - copy3d;
	Update();
	In2D = 3;

	return true;
}

//==========================================================================
//
// D3DFB :: DrawBlendingRect
//
// Call after Begin2D to blend the 3D view.
//
//==========================================================================

void D3DFB::DrawBlendingRect()
{
	if (!In2D || !Accel2D)
	{
		return;
	}
	Dim(FlashColor, FlashAmount / 256.f, viewwindowx, viewwindowy, viewwidth, viewheight);
}

//==========================================================================
//
// D3DFB :: CreateTexture
//
// Returns a native texture that wraps a FTexture.
//
//==========================================================================

FNativeTexture *D3DFB::CreateTexture(FTexture *gametex, bool wrapping)
{
	D3DTex *tex = new D3DTex(gametex, this, wrapping);
	if (tex->Box == NULL)
	{
		delete tex;
		return NULL;
	}
	return tex;
}

//==========================================================================
//
// D3DFB :: CreatePalette
//
// Returns a native texture that contains a palette.
//
//==========================================================================

FNativePalette *D3DFB::CreatePalette(FRemapTable *remap)
{
	D3DPal *tex = new D3DPal(remap, this);
	if (tex->Tex == NULL)
	{
		delete tex;
		return NULL;
	}
	return tex;
}

//==========================================================================
//
// D3DFB :: Clear
//
// Fills the specified region with a color.
//
//==========================================================================

void D3DFB::Clear (int left, int top, int right, int bottom, int palcolor, uint32 color)
{
	if (In2D < 2)
	{
		Super::Clear(left, top, right, bottom, palcolor, color);
		return;
	}
	if (!InScene)
	{
		return;
	}
	if (palcolor >= 0 && color == 0)
	{
		color = GPalette.BaseColors[palcolor];
	}
	else if (APART(color) < 255)
	{
		Dim(color, APART(color)/255.f, left, top, right - left, bottom - top);
		return;
	}
	AddColorOnlyQuad(left, top, right - left, bottom - top, color | 0xFF000000);
}

//==========================================================================
//
// D3DFB :: Dim
//
//==========================================================================

void D3DFB::Dim (PalEntry color, float amount, int x1, int y1, int w, int h)
{
	if (amount <= 0)
	{
		return;
	}
	if (In2D < 2)
	{
		Super::Dim(color, amount, x1, y1, w, h);
		return;
	}
	if (!InScene)
	{
		return;
	}
	if (amount > 1)
	{
		amount = 1;
	}
	AddColorOnlyQuad(x1, y1, w, h, color | (int(amount * 255) << 24));
}

//==========================================================================
//
// D3DFB :: BeginLineBatch
//
//==========================================================================

void D3DFB::BeginLineBatch()
{
	if (In2D < 2 || !InScene || BatchType == BATCH_Lines)
	{
		return;
	}
	EndQuadBatch();		// Make sure all quads have been drawn first.
	VertexBuffer->Lock(0, 0, (void **)&VertexData, D3DLOCK_DISCARD);
	VertexPos = 0;
	BatchType = BATCH_Lines;
}

//==========================================================================
//
// D3DFB :: EndLineBatch
//
//==========================================================================

void D3DFB::EndLineBatch()
{
	if (In2D < 2 || !InScene || BatchType != BATCH_Lines)
	{
		return;
	}
	VertexBuffer->Unlock();
	if (VertexPos > 0)
	{
		SetPixelShader(Shaders[SHADER_VertexColor]);
		SetAlphaBlend(D3DBLENDOP_ADD, D3DBLEND_SRCALPHA, D3DBLEND_INVSRCALPHA);
		D3DDevice->SetStreamSource(0, VertexBuffer, 0, sizeof(FBVERTEX));
		D3DDevice->DrawPrimitive(D3DPT_LINELIST, 0, VertexPos / 2);
	}
	VertexPos = -1;
	BatchType = BATCH_None;
}

//==========================================================================
//
// D3DFB :: DrawLine
//
//==========================================================================

void D3DFB::DrawLine(int x0, int y0, int x1, int y1, int palcolor, uint32 color)
{
	if (In2D < 2)
	{
		Super::DrawLine(x0, y0, x1, y1, palcolor, color);
		return;
	}
	if (!InScene)
	{
		return;
	}
	if (BatchType != BATCH_Lines)
	{
		BeginLineBatch();
	}
	if (VertexPos == NUM_VERTS)
	{ // Flush the buffer and refill it.
		EndLineBatch();
		BeginLineBatch();
	}
	// Add the endpoints to the vertex buffer.
	VertexData[VertexPos].x = float(x0);
	VertexData[VertexPos].y = float(y0) + LBOffset;
	VertexData[VertexPos].z = 0;
	VertexData[VertexPos].rhw = 1;
	VertexData[VertexPos].color0 = color;
	VertexData[VertexPos].color1 = 0;
	VertexData[VertexPos].tu = 0;
	VertexData[VertexPos].tv = 0;

	VertexData[VertexPos+1].x = float(x1);
	VertexData[VertexPos+1].y = float(y1) + LBOffset;
	VertexData[VertexPos+1].z = 0;
	VertexData[VertexPos+1].rhw = 1;
	VertexData[VertexPos+1].color0 = color;
	VertexData[VertexPos+1].color1 = 0;
	VertexData[VertexPos+1].tu = 0;
	VertexData[VertexPos+1].tv = 0;

	VertexPos += 2;
}

//==========================================================================
//
// D3DFB :: DrawPixel
//
//==========================================================================

void D3DFB::DrawPixel(int x, int y, int palcolor, uint32 color)
{
	if (In2D < 2)
	{
		Super::DrawPixel(x, y, palcolor, color);
		return;
	}
	if (!InScene)
	{
		return;
	}
	FBVERTEX pt =
	{
		float(x), float(y), 0, 1, color
	};
	EndBatch();		// Draw out any batched operations.
	SetPixelShader(Shaders[SHADER_VertexColor]);
	SetAlphaBlend(D3DBLENDOP_ADD, D3DBLEND_SRCALPHA, D3DBLEND_INVSRCALPHA);
	D3DDevice->DrawPrimitiveUP(D3DPT_POINTLIST, 1, &pt, sizeof(FBVERTEX));
}

//==========================================================================
//
// D3DFB :: DrawTextureV
//
// If not in 2D mode, just call the normal software version.
// If in 2D mode, then use Direct3D calls to perform the drawing.
//
//==========================================================================

void D3DFB::DrawTextureParms (FTexture *img, DrawParms &parms)
{
	if (In2D < 2)
	{
		Super::DrawTextureParms(img, parms);
		return;
	}
	if (!InScene)
	{
		return;
	}

	D3DTex *tex = static_cast<D3DTex *>(img->GetNative(false));

	if (tex == NULL)
	{
		assert(tex != NULL);
		return;
	}

	CheckQuadBatch();

	double xscale = parms.destwidth / parms.texwidth;
	double yscale = parms.destheight / parms.texheight;
	double x0 = parms.x - parms.left * xscale;
	double y0 = parms.y - parms.top * yscale;
	double x1 = x0 + parms.destwidth;
	double y1 = y0 + parms.destheight;
	float u0 = tex->Box->Left;
	float v0 = tex->Box->Top;
	float u1 = tex->Box->Right;
	float v1 = tex->Box->Bottom;
	double uscale = 1.f / tex->Box->Owner->Width;
	bool scissoring = false;
	FBVERTEX *vert;
	float yoffs;

	if (parms.flipX)
	{
		swapvalues(u0, u1);
	}
	if (parms.windowleft > 0 || parms.windowright < parms.texwidth)
	{
		double wi = MIN(parms.windowright, parms.texwidth);
		x0 += parms.windowleft * xscale;
		u0 = float(u0 + parms.windowleft * uscale);
		x1 -= (parms.texwidth - wi) * xscale;
		u1 = float(u1 - (parms.texwidth - wi) * uscale);
	}

#if 0
	float vscale = 1.f / tex->Box->Owner->Height / yscale;
	if (y0 < parms.uclip)
	{
		v0 += (float(parms.uclip) - y0) * vscale;
		y0 = float(parms.uclip);
	}
	if (y1 > parms.dclip)
	{
		v1 -= (y1 - float(parms.dclip)) * vscale;
		y1 = float(parms.dclip);
	}
	if (x0 < parms.lclip)
	{
		u0 += float(parms.lclip - x0) * uscale / xscale * 2;
		x0 = float(parms.lclip);
	}
	if (x1 > parms.rclip)
	{
		u1 -= (x1 - parms.rclip) * uscale / xscale * 2;
		x1 = float(parms.rclip);
	}
#else
	// Use a scissor test because the math above introduces some jitter
	// that is noticeable at low resolutions. Unfortunately, this means this
	// quad has to be in a batch by itself.
	if (y0 < parms.uclip || y1 > parms.dclip || x0 < parms.lclip || x1 > parms.rclip)
	{
		scissoring = true;
		if (QuadBatchPos > 0)
		{
			EndQuadBatch();
			BeginQuadBatch();
		}
		RECT scissor = {
			parms.lclip, parms.uclip + LBOffsetI,
			parms.rclip, parms.dclip + LBOffsetI
		};
		D3DDevice->SetScissorRect(&scissor);
		D3DDevice->SetRenderState(D3DRS_SCISSORTESTENABLE, TRUE);
	}
#endif
	parms.bilinear = false;

	D3DCOLOR color0, color1;
	BufferedTris *quad = &QuadExtra[QuadBatchPos];

	if (!SetStyle(tex, parms, color0, color1, *quad))
	{
		goto done;
	}

	quad->Texture = tex->Box->Owner->Tex;
	if (parms.bilinear)
	{
		quad->Flags |= BQF_Bilinear;
	}
	quad->NumTris = 2;
	quad->NumVerts = 4;

	yoffs = GatheringWipeScreen ? 0.5f : 0.5f - LBOffset;

#if 0
	// Coordinates are truncated to integers, because that's effectively
	// what the software renderer does. The hardware will instead round
	// to nearest, it seems.
	x0 = floorf(x0) - 0.5f;
	y0 = floorf(y0) - yoffs;
	x1 = floorf(x1) - 0.5f;
	y1 = floorf(y1) - yoffs;
#else
	x0 = x0 - 0.5f;
	y0 = y0 - yoffs;
	x1 = x1 - 0.5f;
	y1 = y1 - yoffs;
#endif

	vert = &VertexData[VertexPos];

	// Fill the vertex buffer.
	vert[0].x = float(x0);
	vert[0].y = float(y0);
	vert[0].z = 0;
	vert[0].rhw = 1;
	vert[0].color0 = color0;
	vert[0].color1 = color1;
	vert[0].tu = u0;
	vert[0].tv = v0;

	vert[1].x = float(x1);
	vert[1].y = float(y0);
	vert[1].z = 0;
	vert[1].rhw = 1;
	vert[1].color0 = color0;
	vert[1].color1 = color1;
	vert[1].tu = u1;
	vert[1].tv = v0;

	vert[2].x = float(x1);
	vert[2].y = float(y1);
	vert[2].z = 0;
	vert[2].rhw = 1;
	vert[2].color0 = color0;
	vert[2].color1 = color1;
	vert[2].tu = u1;
	vert[2].tv = v1;

	vert[3].x = float(x0);
	vert[3].y = float(y1);
	vert[3].z = 0;
	vert[3].rhw = 1;
	vert[3].color0 = color0;
	vert[3].color1 = color1;
	vert[3].tu = u0;
	vert[3].tv = v1;

	// Fill the vertex index buffer.
	IndexData[IndexPos    ] = VertexPos;
	IndexData[IndexPos + 1] = VertexPos + 1;
	IndexData[IndexPos + 2] = VertexPos + 2;
	IndexData[IndexPos + 3] = VertexPos;
	IndexData[IndexPos + 4] = VertexPos + 2;
	IndexData[IndexPos + 5] = VertexPos + 3;

	// Batch the quad.
	QuadBatchPos++;
	VertexPos += 4;
	IndexPos += 6;
done:
	if (scissoring)
	{
		EndQuadBatch();
		D3DDevice->SetRenderState(D3DRS_SCISSORTESTENABLE, FALSE);
	}
}

//==========================================================================
//
// D3DFB :: FlatFill
//
// Fills an area with a repeating copy of the texture.
//
//==========================================================================

void D3DFB::FlatFill(int left, int top, int right, int bottom, FTexture *src, bool local_origin)
{
	if (In2D < 2)
	{
		Super::FlatFill(left, top, right, bottom, src, local_origin);
		return;
	}
	if (!InScene)
	{
		return;
	}
	D3DTex *tex = static_cast<D3DTex *>(src->GetNative(true));
	if (tex == NULL)
	{
		return;
	}
	float yoffs = GatheringWipeScreen ? 0.5f : 0.5f - LBOffset;
	float x0 = float(left);
	float y0 = float(top);
	float x1 = float(right);
	float y1 = float(bottom);
	float itw = 1.f / float(src->GetWidth());
	float ith = 1.f / float(src->GetHeight());
	float xo = local_origin ? x0 : 0;
	float yo = local_origin ? y0 : 0;
	float u0 = (x0 - xo) * itw;
	float v0 = (y0 - yo) * ith;
	float u1 = (x1 - xo) * itw;
	float v1 = (y1 - yo) * ith;
	x0 -= 0.5f;
	y0 -= yoffs;
	x1 -= 0.5f;
	y1 -= yoffs;

	CheckQuadBatch();

	BufferedTris *quad = &QuadExtra[QuadBatchPos];
	FBVERTEX *vert = &VertexData[VertexPos];

	quad->Group1 = 0;
	if (tex->GetTexFormat() == D3DFMT_L8 && !tex->IsGray)
	{
		quad->Flags = BQF_WrapUV | BQF_GamePalette; // | BQF_DisableAlphaTest;
		quad->ShaderNum = BQS_PalTex;
	}
	else
	{
		quad->Flags = BQF_WrapUV; // | BQF_DisableAlphaTest;
		quad->ShaderNum = BQS_Plain;
	}
	quad->Palette = NULL;
	quad->Texture = tex->Box->Owner->Tex;
	quad->NumVerts = 4;
	quad->NumTris = 2;

	vert[0].x = x0;
	vert[0].y = y0;
	vert[0].z = 0;
	vert[0].rhw = 1;
	vert[0].color0 = 0;
	vert[0].color1 = 0xFFFFFFFF;
	vert[0].tu = u0;
	vert[0].tv = v0;

	vert[1].x = x1;
	vert[1].y = y0;
	vert[1].z = 0;
	vert[1].rhw = 1;
	vert[1].color0 = 0;
	vert[1].color1 = 0xFFFFFFFF;
	vert[1].tu = u1;
	vert[1].tv = v0;

	vert[2].x = x1;
	vert[2].y = y1;
	vert[2].z = 0;
	vert[2].rhw = 1;
	vert[2].color0 = 0;
	vert[2].color1 = 0xFFFFFFFF;
	vert[2].tu = u1;
	vert[2].tv = v1;

	vert[3].x = x0;
	vert[3].y = y1;
	vert[3].z = 0;
	vert[3].rhw = 1;
	vert[3].color0 = 0;
	vert[3].color1 = 0xFFFFFFFF;
	vert[3].tu = u0;
	vert[3].tv = v1;

	IndexData[IndexPos    ] = VertexPos;
	IndexData[IndexPos + 1] = VertexPos + 1;
	IndexData[IndexPos + 2] = VertexPos + 2;
	IndexData[IndexPos + 3] = VertexPos;
	IndexData[IndexPos + 4] = VertexPos + 2;
	IndexData[IndexPos + 5] = VertexPos + 3;

	QuadBatchPos++;
	VertexPos += 4;
	IndexPos += 6;
}

//==========================================================================
//
// D3DFB :: FillSimplePoly
//
// Here, "simple" means that a simple triangle fan can draw it.
//
// Bottomclip is ignored by this implementation, since the hardware renderer
// will unconditionally draw the status bar border every frame on top of the
// polygons, so there's no need to waste time setting up a special scissor
// rectangle here and needlessly forcing separate batches.
//
//==========================================================================

void D3DFB::FillSimplePoly(FTexture *texture, FVector2 *points, int npoints,
	double originx, double originy, double scalex, double scaley,
	DAngle rotation, FDynamicColormap *colormap, int lightlevel, int bottomclip)
{
	// Use an equation similar to player sprites to determine shade
	double fadelevel = clamp((LIGHT2SHADE(lightlevel)/65536. - 12) / NUMCOLORMAPS, 0.0, 1.0);
	
	BufferedTris *quad;
	FBVERTEX *verts;
	D3DTex *tex;
	float yoffs, uscale, vscale;
	int i, ipos;
	D3DCOLOR color0, color1;
	float ox, oy;
	float cosrot, sinrot;
	bool dorotate = rotation != 0;

	if (npoints < 3)
	{ // This is no polygon.
		return;
	}
	if (In2D < 2)
	{
		Super::FillSimplePoly(texture, points, npoints, originx, originy, scalex, scaley, rotation, colormap, lightlevel, bottomclip);
		return;
	}
	if (!InScene)
	{
		return;
	}
	tex = static_cast<D3DTex *>(texture->GetNative(true));
	if (tex == NULL)
	{
		return;
	}

	cosrot = (float)cos(rotation.Radians());
	sinrot = (float)sin(rotation.Radians());

	CheckQuadBatch(npoints - 2, npoints);
	quad = &QuadExtra[QuadBatchPos];
	verts = &VertexData[VertexPos];

	color0 = 0;
	color1 = 0xFFFFFFFF;

	quad->Group1 = 0;
	if (tex->GetTexFormat() == D3DFMT_L8 && !tex->IsGray)
	{
		quad->Flags = BQF_WrapUV | BQF_GamePalette | BQF_DisableAlphaTest;
		quad->ShaderNum = BQS_PalTex;
		if (colormap != NULL)
		{
			if (colormap->Desaturate != 0)
			{
				quad->Flags |= BQF_Desaturated;
			}
			quad->ShaderNum = BQS_InGameColormap;
			quad->Desat = colormap->Desaturate;
			color0 = D3DCOLOR_ARGB(255, colormap->Color.r, colormap->Color.g, colormap->Color.b);
			color1 = D3DCOLOR_ARGB(DWORD((1 - fadelevel) * 255),
				DWORD(colormap->Fade.r * fadelevel),
				DWORD(colormap->Fade.g * fadelevel),
				DWORD(colormap->Fade.b * fadelevel));
		}
	}
	else
	{
		quad->Flags = BQF_WrapUV | BQF_DisableAlphaTest;
		quad->ShaderNum = BQS_Plain;
	}
	quad->Palette = NULL;
	quad->Texture = tex->Box->Owner->Tex;
	quad->NumVerts = npoints;
	quad->NumTris = npoints - 2;

	yoffs = GatheringWipeScreen ? 0 : LBOffset;
	uscale = float(1.f / (texture->GetScaledWidth() * scalex));
	vscale = float(1.f / (texture->GetScaledHeight() * scaley));
	ox = float(originx);
	oy = float(originy);

	for (i = 0; i < npoints; ++i)
	{
		verts[i].x = points[i].X;
		verts[i].y = points[i].Y + yoffs;
		verts[i].z = 0;
		verts[i].rhw = 1;
		verts[i].color0 = color0;
		verts[i].color1 = color1;
		float u = points[i].X - 0.5f - ox;
		float v = points[i].Y - 0.5f - oy;
		if (dorotate)
		{
			float t = u;
			u = t * cosrot - v * sinrot;
			v = v * cosrot + t * sinrot;
		}
		verts[i].tu = u * uscale;
		verts[i].tv = v * vscale;
	}
	for (ipos = IndexPos, i = 2; i < npoints; ++i, ipos += 3)
	{
		IndexData[ipos    ] = VertexPos;
		IndexData[ipos + 1] = VertexPos + i - 1;
		IndexData[ipos + 2] = VertexPos + i;
	}

	QuadBatchPos++;
	VertexPos += npoints;
	IndexPos = ipos;
}

//==========================================================================
//
// D3DFB :: AddColorOnlyQuad
//
// Adds a single-color, untextured quad to the batch.
//
//==========================================================================

void D3DFB::AddColorOnlyQuad(int left, int top, int width, int height, D3DCOLOR color)
{
	BufferedTris *quad;
	FBVERTEX *verts;

	CheckQuadBatch();
	quad = &QuadExtra[QuadBatchPos];
	verts = &VertexData[VertexPos];

	float x = float(left) - 0.5f;
	float y = float(top) - 0.5f + (GatheringWipeScreen ? 0 : LBOffset);

	quad->Group1 = 0;
	quad->ShaderNum = BQS_ColorOnly;
	if ((color & 0xFF000000) != 0xFF000000)
	{
		quad->BlendOp = D3DBLENDOP_ADD;
		quad->SrcBlend = D3DBLEND_SRCALPHA;
		quad->DestBlend = D3DBLEND_INVSRCALPHA;
	}
	quad->Palette = NULL;
	quad->Texture = NULL;
	quad->NumVerts = 4;
	quad->NumTris = 2;

	verts[0].x = x;
	verts[0].y = y;
	verts[0].z = 0;
	verts[0].rhw = 1;
	verts[0].color0 = color;
	verts[0].color1 = 0;
	verts[0].tu = 0;
	verts[0].tv = 0;

	verts[1].x = x + width;
	verts[1].y = y;
	verts[1].z = 0;
	verts[1].rhw = 1;
	verts[1].color0 = color;
	verts[1].color1 = 0;
	verts[1].tu = 0;
	verts[1].tv = 0;

	verts[2].x = x + width;
	verts[2].y = y + height;
	verts[2].z = 0;
	verts[2].rhw = 1;
	verts[2].color0 = color;
	verts[2].color1 = 0;
	verts[2].tu = 0;
	verts[2].tv = 0;

	verts[3].x = x;
	verts[3].y = y + height;
	verts[3].z = 0;
	verts[3].rhw = 1;
	verts[3].color0 = color;
	verts[3].color1 = 0;
	verts[3].tu = 0;
	verts[3].tv = 0;

	IndexData[IndexPos    ] = VertexPos;
	IndexData[IndexPos + 1] = VertexPos + 1;
	IndexData[IndexPos + 2] = VertexPos + 2;
	IndexData[IndexPos + 3] = VertexPos;
	IndexData[IndexPos + 4] = VertexPos + 2;
	IndexData[IndexPos + 5] = VertexPos + 3;

	QuadBatchPos++;
	VertexPos += 4;
	IndexPos += 6;
}

//==========================================================================
//
// D3DFB :: AddColorOnlyRect
//
// Like AddColorOnlyQuad, except it's hollow.
//
//==========================================================================

void D3DFB::AddColorOnlyRect(int left, int top, int width, int height, D3DCOLOR color)
{
	AddColorOnlyQuad(left, top, width - 1, 1, color);					// top
	AddColorOnlyQuad(left + width - 1, top, 1, height - 1, color);		// right
	AddColorOnlyQuad(left + 1, top + height - 1, width - 1, 1, color);	// bottom
	AddColorOnlyQuad(left, top + 1, 1, height - 1, color);				// left
}

//==========================================================================
//
// D3DFB :: CheckQuadBatch
//
// Make sure there's enough room in the batch for one more set of triangles.
//
//==========================================================================

void D3DFB::CheckQuadBatch(int numtris, int numverts)
{
	if (BatchType == BATCH_Lines)
	{
		EndLineBatch();
	}
	else if (QuadBatchPos == MAX_QUAD_BATCH ||
		VertexPos + numverts > NUM_VERTS ||
		IndexPos + numtris * 3 > NUM_INDEXES)
	{
		EndQuadBatch();
	}
	if (QuadBatchPos < 0)
	{
		BeginQuadBatch();
	}
}

//==========================================================================
//
// D3DFB :: BeginQuadBatch
//
// Locks the vertex buffer for quads and sets the cursor to 0.
//
//==========================================================================

void D3DFB::BeginQuadBatch()
{
	if (In2D < 2 || !InScene || QuadBatchPos >= 0)
	{
		return;
	}
	EndLineBatch();		// Make sure all lines have been drawn first.
	VertexBuffer->Lock(0, 0, (void **)&VertexData, D3DLOCK_DISCARD);
	IndexBuffer->Lock(0, 0, (void **)&IndexData, D3DLOCK_DISCARD);
	VertexPos = 0;
	IndexPos = 0;
	QuadBatchPos = 0;
	BatchType = BATCH_Quads;
}

//==========================================================================
//
// D3DFB :: EndQuadBatch
//
// Draws all the quads that have been batched up.
//
//==========================================================================

void D3DFB::EndQuadBatch()
{
	if (In2D < 2 || !InScene || BatchType != BATCH_Quads)
	{
		return;
	}
	BatchType = BATCH_None;
	VertexBuffer->Unlock();
	IndexBuffer->Unlock();
	if (QuadBatchPos == 0)
	{
		QuadBatchPos = -1;
		VertexPos = -1;
		IndexPos = -1;
		return;
	}
	D3DDevice->SetStreamSource(0, VertexBuffer, 0, sizeof(FBVERTEX));
	D3DDevice->SetIndices(IndexBuffer);
	bool uv_wrapped = false;
	bool uv_should_wrap;
	int indexpos, vertpos;

	indexpos = vertpos = 0;
	for (int i = 0; i < QuadBatchPos; )
	{
		const BufferedTris *quad = &QuadExtra[i];
		int j;

		int startindex = indexpos;
		int startvertex = vertpos;

		indexpos += quad->NumTris * 3;
		vertpos += quad->NumVerts;

		// Quads with matching parameters should be done with a single
		// DrawPrimitive call.
		for (j = i + 1; j < QuadBatchPos; ++j)
		{
			const BufferedTris *q2 = &QuadExtra[j];
			if (quad->Texture != q2->Texture ||
				quad->Group1 != q2->Group1 ||
				quad->Palette != q2->Palette)
			{
				break;
			}
			if (quad->ShaderNum == BQS_InGameColormap && (quad->Flags & BQF_Desaturated) && quad->Desat != q2->Desat)
			{
				break;
			}
			indexpos += q2->NumTris * 3;
			vertpos += q2->NumVerts;
		}

		// Set the palette (if one)
		if ((quad->Flags & BQF_Paletted) == BQF_GamePalette)
		{
			SetPaletteTexture(PaletteTexture, 256, BorderColor);
		}
		else if ((quad->Flags & BQF_Paletted) == BQF_CustomPalette)
		{
			assert(quad->Palette != NULL);
			SetPaletteTexture(quad->Palette->Tex, quad->Palette->RoundedPaletteSize, quad->Palette->BorderColor);
		}
#if 0
		// Set paletted bilinear filtering (IF IT WORKED RIGHT!)
		if ((quad->Flags & (BQF_Paletted | BQF_Bilinear)) == (BQF_Paletted | BQF_Bilinear))
		{
			SetPalTexBilinearConstants(quad->Texture);
		}
#endif

		// Set the alpha blending
		SetAlphaBlend(D3DBLENDOP(quad->BlendOp), D3DBLEND(quad->SrcBlend), D3DBLEND(quad->DestBlend));

		// Set the alpha test
		EnableAlphaTest(!(quad->Flags & BQF_DisableAlphaTest));

		// Set the pixel shader
		if (quad->ShaderNum == BQS_PalTex)
		{
			SetPixelShader(Shaders[(quad->Flags & BQF_InvertSource) ?
				SHADER_NormalColorPalInv : SHADER_NormalColorPal]);
		}
		else if (quad->ShaderNum == BQS_Plain)
		{
			SetPixelShader(Shaders[(quad->Flags & BQF_InvertSource) ?
				SHADER_NormalColorInv : SHADER_NormalColor]);
		}
		else if (quad->ShaderNum == BQS_RedToAlpha)
		{
			SetPixelShader(Shaders[(quad->Flags & BQF_InvertSource) ?
				SHADER_RedToAlphaInv : SHADER_RedToAlpha]);
		}
		else if (quad->ShaderNum == BQS_ColorOnly)
		{
			SetPixelShader(Shaders[SHADER_VertexColor]);
		}
		else if (quad->ShaderNum == BQS_SpecialColormap)
		{
			int select;

			select = !!(quad->Flags & BQF_Paletted);
			SetPixelShader(Shaders[SHADER_SpecialColormap + select]);
		}
		else if (quad->ShaderNum == BQS_InGameColormap)
		{
			int select;

			select = !!(quad->Flags & BQF_Desaturated);
			select |= !!(quad->Flags & BQF_InvertSource) << 1;
			select |= !!(quad->Flags & BQF_Paletted) << 2;
			if (quad->Flags & BQF_Desaturated)
			{
				SetConstant(PSCONST_Desaturation, quad->Desat / 255.f, (255 - quad->Desat) / 255.f, 0, 0);
			}
			SetPixelShader(Shaders[SHADER_InGameColormap + select]);
		}

		// Set the texture clamp addressing mode
		uv_should_wrap = !!(quad->Flags & BQF_WrapUV);
		if (uv_wrapped != uv_should_wrap)
		{
			DWORD mode = uv_should_wrap ? D3DTADDRESS_WRAP : D3DTADDRESS_BORDER;
			uv_wrapped = uv_should_wrap;
			D3DDevice->SetSamplerState(0, D3DSAMP_ADDRESSU, mode);
			D3DDevice->SetSamplerState(0, D3DSAMP_ADDRESSV, mode);
		}

		// Set the texture
		if (quad->Texture != NULL)
		{
			SetTexture(0, quad->Texture);
		}

		// Draw the quad
		D3DDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0,
			startvertex,					// MinIndex
			vertpos - startvertex,			// NumVertices
			startindex,						// StartIndex
			(indexpos - startindex) / 3		// PrimitiveCount
			/*4 * i, 4 * (j - i), 6 * i, 2 * (j - i)*/);
		i = j;
	}
	if (uv_wrapped)
	{
		D3DDevice->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_BORDER);
		D3DDevice->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_BORDER);
	}
	QuadBatchPos = -1;
	VertexPos = -1;
	IndexPos = -1;
}

//==========================================================================
//
// D3DFB :: EndBatch
//
// Draws whichever type of primitive is currently being batched.
//
//==========================================================================

void D3DFB::EndBatch()
{
	if (BatchType == BATCH_Quads)
	{
		EndQuadBatch();
	}
	else if (BatchType == BATCH_Lines)
	{
		EndLineBatch();
	}
}

//==========================================================================
//
// D3DFB :: SetStyle
//
// Patterned after R_SetPatchStyle.
//
//==========================================================================

bool D3DFB::SetStyle(D3DTex *tex, DrawParms &parms, D3DCOLOR &color0, D3DCOLOR &color1, BufferedTris &quad)
{
	D3DFORMAT fmt = tex->GetTexFormat();
	FRenderStyle style = parms.style;
	float alpha;
	bool stencilling;

	if (style.Flags & STYLEF_TransSoulsAlpha)
	{
		alpha = transsouls;
	}
	else if (style.Flags & STYLEF_Alpha1)
	{
		alpha = 1;
	}
	else
	{
		alpha = clamp(parms.Alpha, 0.f, 1.f);
	}

	style.CheckFuzz();
	if (style.BlendOp == STYLEOP_Shadow)
	{
		style = LegacyRenderStyles[STYLE_TranslucentStencil];
		alpha = 0.3f;
		parms.fillcolor = 0;
	}

	// FIXME: Fuzz effect is not written
	if (style.BlendOp == STYLEOP_FuzzOrAdd || style.BlendOp == STYLEOP_Fuzz)
	{
		style.BlendOp = STYLEOP_Add;
	}
	else if (style.BlendOp == STYLEOP_FuzzOrSub)
	{
		style.BlendOp = STYLEOP_Sub;
	}
	else if (style.BlendOp == STYLEOP_FuzzOrRevSub)
	{
		style.BlendOp = STYLEOP_RevSub;
	}

	stencilling = false;
	quad.Palette = NULL;
	quad.Flags = 0;
	quad.Desat = 0;

	switch (style.BlendOp)
	{
	default:
	case STYLEOP_Add:		quad.BlendOp = D3DBLENDOP_ADD;			break;
	case STYLEOP_Sub:		quad.BlendOp = D3DBLENDOP_SUBTRACT;		break;
	case STYLEOP_RevSub:	quad.BlendOp = D3DBLENDOP_REVSUBTRACT;	break;
	case STYLEOP_None:		return false;
	}
	quad.SrcBlend = GetStyleAlpha(style.SrcAlpha);
	quad.DestBlend = GetStyleAlpha(style.DestAlpha);

	if (style.Flags & STYLEF_InvertOverlay)
	{
		// Only the overlay color is inverted, not the overlay alpha.
		parms.colorOverlay = D3DCOLOR_ARGB(APART(parms.colorOverlay),
			255 - RPART(parms.colorOverlay), 255 - GPART(parms.colorOverlay),
			255 - BPART(parms.colorOverlay));
	}

	SetColorOverlay(parms.colorOverlay, alpha, color0, color1);

	if (style.Flags & STYLEF_ColorIsFixed)
	{
		if (style.Flags & STYLEF_InvertSource)
		{ // Since the source color is a constant, we can invert it now
		  // without spending time doing it in the shader.
			parms.fillcolor = D3DCOLOR_XRGB(255 - RPART(parms.fillcolor),
				255 - GPART(parms.fillcolor), 255 - BPART(parms.fillcolor));
		}
		// Set up the color mod to replace the color from the image data.
		color0 = (color0 & D3DCOLOR_RGBA(0,0,0,255)) | (parms.fillcolor & D3DCOLOR_RGBA(255,255,255,0));
		color1 &= D3DCOLOR_RGBA(0,0,0,255);

		if (style.Flags & STYLEF_RedIsAlpha)
		{
			// Note that if the source texture is paletted, the palette is ignored.
			quad.Flags = 0;
			quad.ShaderNum = BQS_RedToAlpha;
		}
		else if (fmt == D3DFMT_L8)
		{
			quad.Flags = BQF_GamePalette;
			quad.ShaderNum = BQS_PalTex;
		}
		else
		{
			quad.Flags = 0;
			quad.ShaderNum = BQS_Plain;
		}
	}
	else
	{
		if (style.Flags & STYLEF_RedIsAlpha)
		{
			quad.Flags = 0;
			quad.ShaderNum = BQS_RedToAlpha;
		}
		else if (fmt == D3DFMT_L8)
		{
			if (parms.remap != NULL)
			{
				quad.Flags = BQF_CustomPalette;
				quad.Palette = reinterpret_cast<D3DPal *>(parms.remap->GetNative());
				quad.ShaderNum = BQS_PalTex;
			}
			else if (tex->IsGray)
			{
				quad.Flags = 0;
				quad.ShaderNum = BQS_Plain;
			}
			else
			{
				quad.Flags = BQF_GamePalette;
				quad.ShaderNum = BQS_PalTex;
			}
		}
		else
		{
			quad.Flags = 0;
			quad.ShaderNum = BQS_Plain;
		}	
		if (style.Flags & STYLEF_InvertSource)
		{
			quad.Flags |= BQF_InvertSource;
		}

		if (parms.specialcolormap != NULL)
		{ // Emulate an invulnerability or similar colormap.
			float *start, *end;
			start = parms.specialcolormap->ColorizeStart;
			end = parms.specialcolormap->ColorizeEnd;
			if (quad.Flags & BQF_InvertSource)
			{
				quad.Flags &= ~BQF_InvertSource;
				swapvalues(start, end);
			}
			quad.ShaderNum = BQS_SpecialColormap;
			color0 = D3DCOLOR_RGBA(DWORD(start[0]/2*255), DWORD(start[1]/2*255), DWORD(start[2]/2*255), color0 >> 24);
			color1 = D3DCOLOR_RGBA(DWORD(end[0]/2*255), DWORD(end[1]/2*255), DWORD(end[2]/2*255), color1 >> 24);
		}
		else if (parms.colormapstyle != NULL)
		{ // Emulate the fading from an in-game colormap (colorized, faded, and desaturated)
			if (parms.colormapstyle->Desaturate != 0)
			{
				quad.Flags |= BQF_Desaturated;
			}
			quad.ShaderNum = BQS_InGameColormap;
			quad.Desat = parms.colormapstyle->Desaturate;
			color0 = D3DCOLOR_ARGB(color1 >> 24,
				parms.colormapstyle->Color.r,
				parms.colormapstyle->Color.g,
				parms.colormapstyle->Color.b);
			double fadelevel = parms.colormapstyle->FadeLevel;
			color1 = D3DCOLOR_ARGB(DWORD((1 - fadelevel) * 255),
				DWORD(parms.colormapstyle->Fade.r * fadelevel),
				DWORD(parms.colormapstyle->Fade.g * fadelevel),
				DWORD(parms.colormapstyle->Fade.b * fadelevel));
		}
	}

	// For unmasked images, force the alpha from the image data to be ignored.
	if (!parms.masked && quad.ShaderNum != BQS_InGameColormap)
	{
		color0 = (color0 & D3DCOLOR_RGBA(255, 255, 255, 0)) | D3DCOLOR_COLORVALUE(0, 0, 0, alpha);
		color1 &= D3DCOLOR_RGBA(255, 255, 255, 0);

		// If our alpha is one and we are doing normal adding, then we can turn the blend off completely.
		if (quad.BlendOp == D3DBLENDOP_ADD &&
			((alpha == 1 && quad.SrcBlend == D3DBLEND_SRCALPHA) || quad.SrcBlend == D3DBLEND_ONE) &&
			((alpha == 1 && quad.DestBlend == D3DBLEND_INVSRCALPHA) || quad.DestBlend == D3DBLEND_ZERO))
		{
			quad.BlendOp = D3DBLENDOP(0);
		}
		quad.Flags |= BQF_DisableAlphaTest;
	}
	return true;
}

D3DBLEND D3DFB::GetStyleAlpha(int type)
{
	switch (type)
	{
	case STYLEALPHA_Zero:		return D3DBLEND_ZERO;
	case STYLEALPHA_One:		return D3DBLEND_ONE;
	case STYLEALPHA_Src:		return D3DBLEND_SRCALPHA;
	case STYLEALPHA_InvSrc:		return D3DBLEND_INVSRCALPHA;
	default:					return D3DBLEND_ZERO;
	}
}


void D3DFB::SetColorOverlay(DWORD color, float alpha, D3DCOLOR &color0, D3DCOLOR &color1)
{
	if (APART(color) != 0)
	{
		int a = APART(color) * 256 / 255;
		color0 = D3DCOLOR_RGBA(
			(RPART(color) * a) >> 8,
			(GPART(color) * a) >> 8,
			(BPART(color) * a) >> 8,
			0);
		a = 256 - a;
		color1 = D3DCOLOR_RGBA(a, a, a, int(alpha * 255));
	}
	else
	{
		color0 = 0;
		color1 = D3DCOLOR_COLORVALUE(1, 1, 1, alpha);
	}
}

void D3DFB::EnableAlphaTest(BOOL enabled)
{
	if (enabled != AlphaTestEnabled)
	{
		AlphaTestEnabled = enabled;
		D3DDevice->SetRenderState(D3DRS_ALPHATESTENABLE, enabled);
	}
}

void D3DFB::SetAlphaBlend(D3DBLENDOP op, D3DBLEND srcblend, D3DBLEND destblend)
{
	if (op == 0)
	{ // Disable alpha blend
		if (AlphaBlendEnabled)
		{
			AlphaBlendEnabled = FALSE;
			D3DDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);
		}
	}
	else
	{ // Enable alpha blend
		assert(srcblend != 0);
		assert(destblend != 0);

		if (!AlphaBlendEnabled)
		{
			AlphaBlendEnabled = TRUE;
			D3DDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
		}
		if (AlphaBlendOp != op)
		{
			AlphaBlendOp = op;
			D3DDevice->SetRenderState(D3DRS_BLENDOP, op);
		}
		if (AlphaSrcBlend != srcblend)
		{
			AlphaSrcBlend = srcblend;
			D3DDevice->SetRenderState(D3DRS_SRCBLEND, srcblend);
		}
		if (AlphaDestBlend != destblend)
		{
			AlphaDestBlend = destblend;
			D3DDevice->SetRenderState(D3DRS_DESTBLEND, destblend);
		}
	}
}

void D3DFB::SetConstant(int cnum, float r, float g, float b, float a)
{
	if (Constant[cnum][0] != r ||
		Constant[cnum][1] != g ||
		Constant[cnum][2] != b ||
		Constant[cnum][3] != a)
	{
		Constant[cnum][0] = r;
		Constant[cnum][1] = g;
		Constant[cnum][2] = b;
		Constant[cnum][3] = a;
		D3DDevice->SetPixelShaderConstantF(cnum, Constant[cnum], 1);
	}
}

void D3DFB::SetPixelShader(IDirect3DPixelShader9 *shader)
{
	if (CurPixelShader != shader)
	{
		CurPixelShader = shader;
		D3DDevice->SetPixelShader(shader);
	}
}

void D3DFB::SetTexture(int tnum, IDirect3DTexture9 *texture)
{
	assert(unsigned(tnum) < countof(Texture));
	if (Texture[tnum] != texture)
	{
		Texture[tnum] = texture;
		D3DDevice->SetTexture(tnum, texture);
	}
}

void D3DFB::SetPaletteTexture(IDirect3DTexture9 *texture, int count, D3DCOLOR border_color)
{
	if (SM14)
	{
		// Shader Model 1.4 only uses 256-color palettes.
		SetConstant(PSCONST_PaletteMod, 1.f, 0.5f / 256.f, 0, 0);
		if (border_color != 0 && CurBorderColor != border_color)
		{
			CurBorderColor = border_color;
			D3DDevice->SetSamplerState(1, D3DSAMP_BORDERCOLOR, border_color);
		}
	}
	else
	{
		// The pixel shader receives color indexes in the range [0.0,1.0].
		// The palette texture is also addressed in the range [0.0,1.0],
		// HOWEVER the coordinate 1.0 is the right edge of the texture and
		// not actually the texture itself. We need to scale and shift
		// the palette indexes so they lie exactly in the center of each
		// texel. For a normal palette with 256 entries, that means the
		// range we use should be [0.5,255.5], adjusted so the coordinate
		// is still within [0.0,1.0].
		//
		// The constant register c2 is used to hold the multiplier in the
		// x part and the adder in the y part.
		float fcount = 1 / float(count);
		SetConstant(PSCONST_PaletteMod, 255 * fcount, 0.5f * fcount, 0, 0);
	}
	SetTexture(1, texture);
}

void D3DFB::SetPalTexBilinearConstants(Atlas *tex)
{
#if 0
	float con[8];

	// Don't bother doing anything if the constants won't be used.
	if (PalTexShader == PalTexBilinearShader)
	{
		return;
	}

	con[0] = float(tex->Width);
	con[1] = float(tex->Height);
	con[2] = 0;
	con[3] = 1 / con[0];
	con[4] = 0;
	con[5] = 1 / con[1];
	con[6] = con[5];
	con[7] = con[3];

	D3DDevice->SetPixelShaderConstantF(3, con, 2);
#endif
}
