////////////////////////////////////////////////////////////////////////////////////////
//
// Nestopia - NES/Famicom emulator written in C++
//
// Copyright (C) 2003-2008 Martin Freij
//
// This file is part of Nestopia.
//
// Nestopia is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// Nestopia is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Nestopia; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
////////////////////////////////////////////////////////////////////////////////////////

#include "language/resource.h"
#include "NstResourceString.hpp"
#include "NstIoLog.hpp"
#include "NstApplicationException.hpp"
#include "NstDirect2D.hpp"
#include "NstIoScreen.hpp"

#if NST_MSVC
#pragma comment(lib,"d3d9")
#pragma comment(lib,"d3dx9")
#endif

namespace Nestopia
{
	namespace DirectX
	{
		#ifdef NST_MSVC_OPTIMIZE
		#pragma optimize("t", on)
		#endif

		inline Direct2D::Base::operator IDirect3D9& () const
		{
			return com;
		}

		inline Direct2D::Device::operator IDirect3DDevice9& () const
		{
			return **com;
		}

		inline bool Direct2D::Device::Fonts::Font::CanDraw() const
		{
			return length && com;
		}

		inline uint Direct2D::VertexBuffer::NumVertices() const
		{
			return numVertices;
		}

		inline uint Direct2D::IndexBuffer::NumStrips() const
		{
			return numStrips;
		}

		inline double Direct2D::Textures::ScreenTexture::GetLeftU(uint x) const
		{
			return double(NST_MIN(size.x,x)) / desc.Width;
		}

		inline double Direct2D::Textures::ScreenTexture::GetRightU(uint x) const
		{
			return double(NST_MIN(size.x,x)) / desc.Width;
		}

		inline double Direct2D::Textures::ScreenTexture::GetTopV(uint y) const
		{
			return double(NST_MIN(size.y,y)) / desc.Height;
		}

		inline double Direct2D::Textures::ScreenTexture::GetBottomV(uint y) const
		{
			return double(NST_MIN(size.y,y)) / desc.Height;
		}

		inline double Direct2D::Textures::EffectTexture::GetLeftU(uint) const
		{
			return 0.0;
		}

		inline double Direct2D::Textures::EffectTexture::GetRightU(uint) const
		{
			return 1.0;
		}

		inline double Direct2D::Textures::EffectTexture::GetTopV(uint y) const
		{
			return double(NST_MIN(size.y,y)) / desc.Height * 2;
		}

		inline double Direct2D::Textures::EffectTexture::GetBottomV(uint y) const
		{
			return double(NST_MIN(size.y,y)) / desc.Height * 2;
		}

		inline double Direct2D::Textures::GetScreenLeftU(uint x) const
		{
			return screenTexture.GetLeftU(x);
		}

		inline double Direct2D::Textures::GetScreenRightU(uint x) const
		{
			return screenTexture.GetRightU(x);
		}

		inline double Direct2D::Textures::GetScreenTopV(uint y) const
		{
			return screenTexture.GetTopV(y);
		}

		inline double Direct2D::Textures::GetScreenBottomV(uint y) const
		{
			return screenTexture.GetBottomV(y);
		}

		inline double Direct2D::Textures::GetEffectLeftU(uint x) const
		{
			return effectTexture.GetLeftU(x);
		}

		inline double Direct2D::Textures::GetEffectRightU(uint x) const
		{
			return effectTexture.GetRightU(x);
		}

		inline double Direct2D::Textures::GetEffectTopV(uint y) const
		{
			return effectTexture.GetTopV(y);
		}

		inline double Direct2D::Textures::GetEffectBottomV(uint y) const
		{
			return effectTexture.GetBottomV(y);
		}

		inline void Direct2D::Device::Fonts::Font::Draw(const D3DCOLOR color,const DWORD flags,Rect rect) const
		{
			com->DrawText( NULL, string.Ptr(), length, &rect, flags, color );
		}

		NST_SINGLE_CALL void Direct2D::Device::Fonts::Render(const D3DPRESENT_PARAMETERS& presentation,const uint state) const
		{
			const uint width = presentation.BackBufferWidth;
			const uint height = presentation.BackBufferHeight;

			if (!presentation.Windowed)
			{
				if ((state & RENDER_FPS) && fps.CanDraw())
				{
					for (uint i=0; i < 2; ++i)
					{
						fps.Draw
						(
							i ? D3DCOLOR_ARGB(0xFF,0xA5,0xB5,0x40) : D3DCOLOR_ARGB(0xFF,0x2A,0x35,0x10),
							DT_SINGLELINE|TA_BOTTOM|TA_RIGHT|DT_NOCLIP,
							Rect(width-31,height-31,width-i-3,height-i-3)
						);
					}
				}

				if ((state & RENDER_MSG) && msg.CanDraw())
				{
					for (uint i=0; i < 2; ++i)
					{
						msg.Draw
						(
							i ? D3DCOLOR_ARGB(0xFF,0xFF,0x20,0x20) : D3DCOLOR_ARGB(0xFF,0x20,0x20,0xA0),
							DT_SINGLELINE|TA_BOTTOM|TA_LEFT|DT_NOCLIP,
							Rect(4-i,height-31,width,height-i-3)
						);
					}
				}
			}

			if ((state & RENDER_NFO) && nfo.CanDraw())
			{
				for (uint i=0; i < 2; ++i)
				{
					nfo.Draw
					(
						i ? D3DCOLOR_ARGB(0xFF,0x20,0xFF,0x20) : D3DCOLOR_ARGB(0xFF,0x20,0x60,0x20),
						TA_TOP|TA_LEFT|DT_NOCLIP,
						Rect(16-i,16-i,width,height)
					);
				}
			}
		}

		NST_SINGLE_CALL HRESULT Direct2D::Device::RenderScreen(const uint state,const uint numIndexedStrips,const uint numVertices) const
		{
			HRESULT hResult = com->BeginScene();

			if (SUCCEEDED(hResult))
			{
				if (state & RENDER_PICTURE)
				{
					if (numIndexedStrips)
						hResult = com->DrawIndexedPrimitive( D3DPT_TRIANGLESTRIP, 0, 0, numVertices, 0, numIndexedStrips );
					else
						hResult = com->DrawPrimitive( D3DPT_TRIANGLESTRIP, 0, 2 );
				}

				fonts.Render( presentation, state );
				com->EndScene();
			}

			return hResult;
		}

		#ifdef NST_MSVC_OPTIMIZE
		#pragma optimize("", on)
		#endif

		Direct2D::Direct2D(HWND hWnd)
		:
		device     ( hWnd, base ),
		textures   ( device.GetPresentation().BackBufferFormat ),
		lastResult ( D3D_OK )
		{
			ValidateObjects();
		}

		Direct2D::~Direct2D()
		{
		}

		#ifdef NST_MSVC_OPTIMIZE
		#pragma optimize("t", on)
		#endif

		void Direct2D::InvalidateObjects()
		{
			indexBuffer.Invalidate();
			vertexBuffer.Invalidate();
			textures.Invalidate();
		}

		void Direct2D::FlushObjects()
		{
			textures.Flush();
		}

		void Direct2D::ValidateObjects()
		{
			if (SUCCEEDED(lastResult))
			{
				lastResult = textures.Validate( device, GetAdapter(), device.GetPresentation().BackBufferFormat );

				if (SUCCEEDED(lastResult))
				{
					lastResult = indexBuffer.Validate( device );

					if (SUCCEEDED(lastResult))
						lastResult = vertexBuffer.Validate( device, textures );
				}
			}
		}

		void Direct2D::RenderScreen(uint state)
		{
			if (SUCCEEDED(lastResult))
				lastResult = device.RenderScreen( state, indexBuffer.NumStrips(), vertexBuffer.NumVertices() );
		}

		/**
		 * Selects a video adapter and creates a new video device. Selection is ignored if the adapter is already selected.
		 *
		 * @param adapter The video adapter to select.
		 * @throws Application::Exception If an error occured creating or resetting the new video device.
		 * @see Application::Exception
		 */
		void Direct2D::SelectAdapter(const Adapters::const_iterator adapter)
		{
			if (device.GetOrdinal() != adapter->ordinal)
			{
				InvalidateObjects();
				
				device.Create( base, *adapter );
				if (FAILED(device.Reset()))
					throw Application::Exception( L"Couldn't reset the new video device!" );

				ValidateObjects();
				lastResult = D3D_OK;
				//lastResult = INVALID_RECT;	//removed when implementing support for multiple monitor adapters
			}
			else
			{
				Io::Log() << "Selection of video adapter with ordinal \"" << adapter->ordinal << "\" was ignored as it is already selected.\r\n";
			}
		}

		/**
		 * Verifies if a switch to a fullscreen mode can be made or if the mode is already set to fullscreen.
		 * 
		 * @param mode The video mode to set to fullscreen.
		 * @return True if switched to the fullscreen mode can be made. False if the mode is already set to fullscreen.
		 */
		bool Direct2D::CanSwitchFullscreen(const Adapter::Modes::const_iterator mode) const
		{
			return device.CanSwitchFullscreen( *mode );
		}

		/**
		 * Switches to a fullscreen mode. Switch is ignored if the specific mode is already set to fullscreen.
		 * 
		 * @param mode The video mode to set to fullscreen.
		 * @return True if switched to fullscreen. False if the mode is already set in fullscreen.
		 */
		bool Direct2D::SwitchFullscreen(const Adapter::Modes::const_iterator mode)
		{
			if (CanSwitchFullscreen( mode ))
			{
				FlushObjects();
				device.SwitchFullscreen( *mode );
				lastResult = D3D_OK;
				ValidateObjects();
				return true;
			}

			return false;
		}

		/**
		 * Switches to window mode. Switch is ignored if already in window mode.
		 * 
		 * @return True if switched to window mode. False if the mode is already in window mode.
		 */
		bool Direct2D::SwitchWindowed()
		{
			if (!device.GetPresentation().Windowed)
			{
				FlushObjects();
				device.SwitchWindowed();
				lastResult = D3D_OK;
				ValidateObjects();
				return true;
			}

			return false;
		}

		void Direct2D::EnableDialogBoxMode(const bool enable)
		{
			if (device.CanToggleDialogBoxMode( enable ))
			{
				FlushObjects();
				lastResult = device.ToggleDialogBoxMode();
				ValidateObjects();
			}
		}

		bool Direct2D::Repair()
		{
			if (FAILED(lastResult) && lastResult != INVALID_RECT)
			{
				FlushObjects();
				lastResult = device.Repair( lastResult );
				ValidateObjects();
			}

			return SUCCEEDED(lastResult);
		}

		void Direct2D::UpdateWindowView()
		{
			const Point::Picture picture( device.GetPresentation().hDeviceWindow );

			if (picture.x > 0 && picture.y > 0)
			{
				const Point::Client client( device.GetPresentation().hDeviceWindow );
				NST_ASSERT( client.x >= picture.x && client.y >= picture.y );

				if
				(
					client.x != device.GetPresentation().BackBufferWidth ||
					client.y != device.GetPresentation().BackBufferHeight ||
					lastResult == INVALID_RECT
				)
				{
					FlushObjects();
					lastResult = device.ResetWindowClient( client, lastResult );
				}

				ValidateObjects();
			}
			else
			{
				lastResult = INVALID_RECT;
			}
		}

		void Direct2D::UpdateWindowView
		(
			const Point& screen,
			const Rect& clipping,
			const uint scanlines,
			const int screenCurvature,
			const Adapter::Filter filter,
			const bool useVidMem
		)
		{
			const Point::Picture picture( device.GetPresentation().hDeviceWindow );

			if (picture.x > 0 && picture.y > 0)
			{
				textures.Update( base.GetAdapter(device.GetOrdinal()), screen, scanlines, filter, useVidMem );
				vertexBuffer.Update( picture, clipping, screenCurvature );
				indexBuffer.Update( screenCurvature );

				const Point::Client client( device.GetPresentation().hDeviceWindow );
				NST_ASSERT( client.x >= picture.x && client.y >= picture.y );

				if
				(
					client.x != device.GetPresentation().BackBufferWidth ||
					client.y != device.GetPresentation().BackBufferHeight ||
					lastResult == INVALID_RECT
				)
				{
					FlushObjects();
					lastResult = device.ResetWindowClient( client, lastResult );
				}

				ValidateObjects();
			}
			else
			{
				lastResult = INVALID_RECT;
			}
		}

		void Direct2D::UpdateFullscreenView
		(
			const Rect& picture,
			const Point& screen,
			const Rect& clipping,
			const uint scanlines,
			const int screenCurvature,
			const Adapter::Filter filter,
			const bool useVidMem
		)
		{
			NST_ASSERT( picture.Width() && picture.Height() );

			textures.Update( base.GetAdapter(device.GetOrdinal()), screen, scanlines, filter, useVidMem );
			vertexBuffer.Update( picture, clipping, screenCurvature );
			indexBuffer.Update( screenCurvature );
			ValidateObjects();
		}

		void Direct2D::UpdateFrameRate(const uint frameRate,const bool vsync,const bool tripleBuffering)
		{
			if (device.ResetFrameRate( frameRate, vsync, tripleBuffering, base ))
			{
				FlushObjects();

				if (SUCCEEDED(lastResult))
				{
					lastResult = device.Reset();
					ValidateObjects();
				}
			}
		}

		Direct2D::ScreenShotResult Direct2D::SaveScreenShot(wcstring const file,const uint ext) const
		{
			NST_ASSERT( file && *file );

			if (SUCCEEDED(lastResult))
			{
				D3DXIMAGE_FILEFORMAT format;

				switch (ext)
				{
					case MAKEFOURCC('p','n','g','\0'): format = D3DXIFF_PNG; break;
					case MAKEFOURCC('j','p','g','\0'): format = D3DXIFF_JPG; break;
					case MAKEFOURCC('b','m','p','\0'): format = D3DXIFF_BMP; break;
					default: return SCREENSHOT_UNSUPPORTED;
				}

				if (textures.SaveToFile( file, format ))
					return SCREENSHOT_OK;
			}

			return SCREENSHOT_ERROR;
		}

		Direct2D::Mode::Mode(uint w,uint h,uint b)
		: width(w), height(h), bpp(b) {}

		bool Direct2D::Mode::operator == (const Mode& mode) const
		{
			return width == mode.width && height == mode.height && bpp == mode.bpp;
		}

		bool Direct2D::Mode::operator < (const Mode& mode) const
		{
			if ( width  < mode.width  ) return true;
			if ( width  > mode.width  ) return false;
			if ( height < mode.height ) return true;
			if ( height > mode.height ) return false;
			if ( bpp    < mode.bpp    ) return true;
			if ( bpp    > mode.bpp    ) return false;

			return false;
		}

		#ifdef NST_MSVC_OPTIMIZE
		#pragma optimize("", on)
		#endif

		Direct2D::Base::Base()
		: com(Create()), adapters(EnumerateAdapters(com)) {}

		Direct2D::Base::~Base()
		{
			com.Release();
		}

		IDirect3D9& Direct2D::Base::Create()
		{
			IDirect3D9* com;

			if (NULL != (com = ::Direct3DCreate9( D3D_SDK_VERSION )))
			{
				return *com;
			}
			else if (NULL != (com = ::Direct3DCreate9( D3D9b_SDK_VERSION ))) // unofficial, it may work, it may not work
			{
				return *com;
			}
			else
			{
				throw Application::Exception( IDS_ERR_D3D_FAILED );
			}
		}

		/**
		 * Lists all video adapters and stores their information and properties.
		 *
		 * @param d3d The Direct3D9 object to fetch video adapters from.
		 */
		const Direct2D::Adapters Direct2D::Base::EnumerateAdapters(IDirect3D9& d3d)
		{
			NST_COMPILE_ASSERT( D3DADAPTER_DEFAULT == 0 );

			Io::Log() << "Direct3D: initializing..\r\n";

			Adapters adapters;
			for (uint ordinal=0, numAdapters=d3d.GetAdapterCount(); ordinal < NST_MIN(numAdapters,255); ++ordinal)
			{
				D3DADAPTER_IDENTIFIER9 identifier;

				if (SUCCEEDED(d3d.GetAdapterIdentifier( ordinal, 0, &identifier )))
				{
					uint guidIndex = 1;	//the index of adapters of the same GUID
					if (!adapters.empty())
					{
						for (Adapters::iterator adapterIterator = adapters.begin(); adapterIterator != adapters.end(); adapterIterator++)	//for each previous found adapter
						{
							if (adapterIterator->guid == identifier.DeviceIdentifier)	//if the adapter is of the same GUID
							{
								guidIndex++;
							}
						}
					}

					Io::Log() << "Direct3D: enumerating device - name: "
										<< (*identifier.Description ? identifier.Description : "unknown")
										<< ", GUID: "
										<< System::Guid( identifier.DeviceIdentifier ).GetString()
										<< ", index: "
										<< guidIndex
										<< "\r\n";

					Adapter::Modes modes;

					for (uint format=0; format < 2; ++format)
					{
						const D3DFORMAT type = (format ? D3DFMT_X8R8G8B8 : D3DFMT_R5G6B5);

						for (uint mode=0, numModes=d3d.GetAdapterModeCount( ordinal, type ); mode < numModes; ++mode)
						{
							D3DDISPLAYMODE display;

							if (FAILED(d3d.EnumAdapterModes( ordinal, type, mode, &display )))
								continue;

							if (display.Width < Mode::MIN_WIDTH || display.Height < Mode::MIN_HEIGHT || display.RefreshRate > Mode::MAX_RATE)
								continue;

							// C++ standard vagueness, sometimes set::iterator == set::const_iterator
							const_cast<Mode::Rates&>(modes.insert(Mode( display.Width, display.Height, format ? 32 : 16 )).first->rates).insert( display.RefreshRate );
						}
					}

					if (modes.empty())
					{
						Io::Log() << "Direct3D: found no valid display mode, continuing enumeration..\r\n";
					}
					else
					{
						D3DCAPS9 caps;

						if (FAILED(d3d.GetDeviceCaps( ordinal, D3DDEVTYPE_HAL, &caps )))
						{
							if (FAILED(d3d.GetDeviceCaps( ordinal, D3DDEVTYPE_REF, &caps )))
							{
								Io::Log() << "Direct3D: warning, bogus device, continuing enumeration..\r\n";
								continue;
							}
							else
							{
								Io::Log() << "Direct3D: performance warning, this is a REF device only!\r\n";
							}
						}

						adapters.push_back( Adapter() );
						Adapter& adapter = adapters.back();

						adapter.guid                = identifier.DeviceIdentifier;
						adapter.guidIndex           = guidIndex;
						adapter.name                = (*identifier.Description ? identifier.Description : "Unknown");
						adapter.name.Trim();
						adapter.name                << Resource::String( IDS_DIALOG_VIDEO_ADAPTER_INDEX_SUFFIX ).Invoke( ValueString(guidIndex) );	//appends the GUID index suffix
						adapter.ordinal             = ordinal;
						adapter.deviceType          = (caps.DeviceType != D3DDEVTYPE_REF ? Adapter::DEVICE_HAL : Adapter::DEVICE_HEL);
						adapter.maxScreenSize       = Point(caps.MaxTextureWidth,caps.MaxTextureHeight);
						adapter.videoMemScreen      = caps.Caps2 & D3DCAPS2_DYNAMICTEXTURES;
						adapter.anyTextureSize      = (caps.TextureCaps & (D3DPTEXTURECAPS_SQUAREONLY|D3DPTEXTURECAPS_POW2|D3DPTEXTURECAPS_NONPOW2CONDITIONAL)) == 0;
						adapter.canDoScanlineEffect = (caps.MaxSimultaneousTextures >= 2) && (caps.TextureOpCaps & D3DTEXOPCAPS_MODULATE) && (caps.TextureAddressCaps & D3DPTADDRESSCAPS_WRAP);
						adapter.intervalTwo         = caps.PresentationIntervals & D3DPRESENT_INTERVAL_TWO;
						adapter.intervalThree       = caps.PresentationIntervals & D3DPRESENT_INTERVAL_THREE;
						adapter.intervalFour        = caps.PresentationIntervals & D3DPRESENT_INTERVAL_FOUR;
						adapter.filters             = 0;
						adapter.modern              = (caps.PixelShaderVersion >= D3DPS_VERSION(2,0));
						adapter.modes               = modes;

						if ((caps.TextureFilterCaps & (D3DPTFILTERCAPS_MINFLINEAR|D3DPTFILTERCAPS_MAGFLINEAR)) == (D3DPTFILTERCAPS_MINFLINEAR|D3DPTFILTERCAPS_MAGFLINEAR))
							adapter.filters |= Adapter::FILTER_BILINEAR;

						Io::Log log;

						log << "Direct3D: dynamic textures: " << (adapter.videoMemScreen ? "supported\r\n" : "unsupported\r\n")
								<< "Direct3D: texture bilinear filtering: " << ((adapter.filters & Adapter::FILTER_BILINEAR) ? "supported\r\n" : "unsupported\r\n")
								<< "Direct3D: max texture dimensions: " << caps.MaxTextureWidth << 'x' << caps.MaxTextureHeight
								<< "\r\nDirect3D: scanline effect: " << (adapter.canDoScanlineEffect ? "supported\r\n" : "unsupported\r\n")
								<< "Direct3D: vsync on every second refresh: " << (adapter.intervalTwo ? "supported\r\n" : "unsupported\r\n")
								<< "Direct3D: vsync on every third refresh: " << (adapter.intervalThree ? "supported\r\n" : "unsupported\r\n")
								<< "Direct3D: found " << modes.size() << " display modes\r\n"
								<< "Direct3D: supported monitor frequencies: ";

						Mode::Rates rates;

						for (Adapter::Modes::const_iterator it(modes.begin()), end(modes.end()); it != end; ++it)
							rates.insert( it->rates.begin(), it->rates.end() );

						for (Mode::Rates::const_iterator it(rates.begin()), end(rates.end());; )
						{
							log << uint(*it);

							if (++it != end)
							{
								log << "hz, ";
							}
							else
							{
								log << "hz\r\n";
								break;
							}
						}
					}
				}
			}

			if (adapters.empty())
				throw Application::Exception( L"Found no valid display adapter!" );

			return adapters;
		}

		#ifdef NST_MSVC_OPTIMIZE
		#pragma optimize("t", on)
		#endif

		uint Direct2D::Base::FormatToBpp(const D3DFORMAT format)
		{
			switch (format)
			{
				case D3DFMT_X8R8G8B8:
				case D3DFMT_X8B8G8R8:
				case D3DFMT_A8R8G8B8:
				case D3DFMT_A8B8G8R8:
				case D3DFMT_A2R10G10B10:
				case D3DFMT_A2B10G10R10:
					return 32;

				case D3DFMT_R5G6B5:
				case D3DFMT_X1R5G5B5:
				case D3DFMT_X4R4G4B4:
				case D3DFMT_A1R5G5B5:
				case D3DFMT_A4R4G4B4:
				case D3DFMT_A8R3G3B2:
					return 16;
			}

			return 0;
		}

		void Direct2D::Base::FormatToMask(const D3DFORMAT format,ulong& r,ulong& g,ulong& b)
		{
			switch (format)
			{
				case D3DFMT_X8R8G8B8:
				case D3DFMT_A8R8G8B8:    r = 0x00FF0000; g = 0x0000FF00; b = 0x000000FF; break;
				case D3DFMT_X8B8G8R8:
				case D3DFMT_A8B8G8R8:    r = 0x000000FF; g = 0x0000FF00; b = 0x00FF0000; break;
				case D3DFMT_A2R10G10B10: r = 0x3FF00000; g = 0x000FFC00; b = 0x000003FF; break;
				case D3DFMT_A2B10G10R10: r = 0x000003FF; g = 0x000FFC00; b = 0x3FF00000; break;
				case D3DFMT_R5G6B5:      r = 0xF800;     g = 0x07E0;     b = 0x001F;     break;
				case D3DFMT_X1R5G5B5:
				case D3DFMT_A1R5G5B5:    r = 0x7C00;     g = 0x03E0;     b = 0x001F;     break;
				case D3DFMT_X4R4G4B4:
				case D3DFMT_A4R4G4B4:    r = 0x0F00;     g = 0x00F0;     b = 0x000F;     break;
				case D3DFMT_A8R3G3B2:    r = 0x00E0;     g = 0x001C;     b = 0x0003;     break;
				default:                 r = 0;          g = 0;          b = 0;          break;
			}
		}

		#ifdef NST_MSVC_OPTIMIZE
		#pragma optimize("", on)
		#endif

		Direct2D::Device::Device(HWND const hWnd,const Base& base)
		{
			NST_ASSERT( hWnd );

			presentation.BackBufferWidth            = 0;
			presentation.BackBufferHeight           = 0;
			presentation.BackBufferFormat           = D3DFMT_UNKNOWN;
			presentation.BackBufferCount            = 1;
			presentation.MultiSampleType            = D3DMULTISAMPLE_NONE;
			presentation.MultiSampleQuality         = 0;
			presentation.SwapEffect                 = D3DSWAPEFFECT_DISCARD;
			presentation.hDeviceWindow              = hWnd;
			presentation.Windowed                   = true;
			presentation.EnableAutoDepthStencil     = false;
			presentation.AutoDepthStencilFormat     = D3DFMT_UNKNOWN;
			presentation.Flags                      = 0;
			presentation.FullScreen_RefreshRateInHz = 0;
			presentation.PresentationInterval       = D3DPRESENT_INTERVAL_IMMEDIATE;

			dialogBoxMode = false;

			Create( base, base.GetAdapter(0) );	//always connects to first video device at start up
		}

		void Direct2D::Device::Create(IDirect3D9& d3d,const Adapter& adapter)
		{
			ordinal = adapter.ordinal;
			intervalTwo = adapter.intervalTwo;
			intervalThree = adapter.intervalThree;
			intervalFour = adapter.intervalFour;

			fonts.Destroy( true );
			com.Release();

			NST_VERIFY( !!Point::Client(presentation.hDeviceWindow) );

			uint buffers = (timing.tripleBuffering ? 2 : 1);
			presentation.BackBufferCount = buffers;
			presentation.Flags = GetPresentationFlags();
			presentation.SwapEffect = GetSwapEffect();
			DWORD flags = D3DCREATE_PUREDEVICE|D3DCREATE_HARDWARE_VERTEXPROCESSING;

			for (;;)
			{
				const HRESULT hResult = d3d.CreateDevice
				(
					adapter.ordinal,
					adapter.deviceType == Adapter::DEVICE_HAL ? D3DDEVTYPE_HAL : D3DDEVTYPE_REF,
					presentation.hDeviceWindow,
					flags,
					&presentation,
					&com
				);

				if (SUCCEEDED(hResult))
				{
					break;
				}
				else if (hResult == D3DERR_DEVICELOST)
				{
					throw Application::Exception( L"Can't start! Direct3D is busy!" );
				}
				else if (buffers != presentation.BackBufferCount)
				{
					buffers = presentation.BackBufferCount;
					Io::Log() << "Direct3D: Warning! IDirect3D9::CreateDevice() failed, retrying with one back-buffer only..\r\n";
				}
				else if (flags == (D3DCREATE_PUREDEVICE|D3DCREATE_HARDWARE_VERTEXPROCESSING))
				{
					flags = D3DCREATE_HARDWARE_VERTEXPROCESSING;
					Io::Log() << "Direct3D: Warning! IDirect3D9::CreateDevice() failed, retrying without a pure device..\r\n";
				}
				else if (flags == D3DCREATE_HARDWARE_VERTEXPROCESSING)
				{
					flags = D3DCREATE_SOFTWARE_VERTEXPROCESSING;
					Io::Log() << "Direct3D: Warning! IDirect3D9::CreateDevice() failed, retrying with software vertex processing mode..\r\n";
				}
				else
				{
					if (HDC const hdc = ::GetDC( NULL ))
					{
						const int bits = ::GetDeviceCaps( hdc, BITSPIXEL );
						::ReleaseDC( NULL, hdc );

						if (bits && bits != 16 && bits != 32)
							throw Application::Exception( IDS_ERR_BAD_BPP );
					}

					throw Application::Exception( IDS_ERR_D3D_DEVICE_FAILED );
				}
			}

			Prepare();
			fonts.Create( *this );

			Io::Log() << "Direct3D: creating "
                      << (adapter.deviceType == Adapter::DEVICE_HAL ? "HAL device #" : "REF device #")
                      << adapter.ordinal
                      << "\r\n";

			LogDisplaySwitch();
		}

		bool Direct2D::Device::GetDisplayMode(D3DDISPLAYMODE& displayMode) const
		{
			IDirect3D9* base;

			if (SUCCEEDED(com->GetDirect3D( &base )))
			{
				const HRESULT hResult = base->GetAdapterDisplayMode( ordinal, &displayMode );
				base->Release();

				if (SUCCEEDED(hResult))
					return true;
			}

			displayMode.Width = presentation.BackBufferWidth;
			displayMode.Height = presentation.BackBufferHeight;

			return false;
		}

		#ifdef NST_MSVC_OPTIMIZE
		#pragma optimize("t", on)
		#endif

		DWORD Direct2D::Device::GetPresentationFlags() const
		{
			return (dialogBoxMode && !presentation.Windowed) ? D3DPRESENTFLAG_LOCKABLE_BACKBUFFER : 0;
		}

		D3DSWAPEFFECT Direct2D::Device::GetSwapEffect() const
		{
			return (dialogBoxMode && !presentation.Windowed) || (presentation.BackBufferCount > 1) ? D3DSWAPEFFECT_DISCARD : D3DSWAPEFFECT_COPY;
		}

		uint Direct2D::Device::GetDesiredPresentationRate(const Mode& mode) const
		{
			if (presentation.Windowed)
			{
				return 0;
			}
			else if (timing.autoHz)
			{
				int match = INT_MAX;
				Mode::Rates::const_iterator close(mode.rates.begin());

				for (Mode::Rates::const_iterator it(mode.rates.end()), begin(mode.rates.begin());; )
				{
					--it;

					for (uint i=5; --i; )
					{
						int diff = int(timing.frameRate * i) - int(*it);

						if (diff == 0)
							return *it;

						if (diff < 0)
							diff = int(*it) - int(timing.frameRate * i);

						if (match > diff)
						{
							match = diff;
							close = it;
						}
					}

					if (it == begin)
						break;
				}

				return *close;
			}
			else for (Mode::Rates::const_iterator it(mode.rates.begin()), end(mode.rates.end()); it != end; ++it)
			{
				if (*it == Mode::DEFAULT_RATE)
					return Mode::DEFAULT_RATE;
			}

			return 0;
		}

		DWORD Direct2D::Device::GetDesiredPresentationInterval(const uint rate) const
		{
			if (!timing.vsync || rate % timing.frameRate)
			{
				return D3DPRESENT_INTERVAL_IMMEDIATE;
			}
			else if (!presentation.Windowed)
			{
				if (timing.frameRate * 4 == rate && intervalFour)
				{
					return D3DPRESENT_INTERVAL_FOUR;
				}
				else if (timing.frameRate * 3 == rate && intervalThree)
				{
					return D3DPRESENT_INTERVAL_THREE;
				}
				else if (timing.frameRate * 2 == rate && intervalTwo)
				{
					return D3DPRESENT_INTERVAL_TWO;
				}
			}

			return timing.frameRate == rate ? D3DPRESENT_INTERVAL_ONE : D3DPRESENT_INTERVAL_IMMEDIATE;
		}

		uint Direct2D::Device::GetRefreshRate() const
		{
			if (presentation.Windowed)
			{
				D3DDISPLAYMODE mode;
				return GetDisplayMode( mode ) ? mode.RefreshRate : 0;
			}
			else
			{
				return presentation.FullScreen_RefreshRateInHz;
			}
		}

		DWORD Direct2D::Device::GetDesiredPresentationInterval() const
		{
			return GetDesiredPresentationInterval( GetRefreshRate() );
		}

		HRESULT Direct2D::Device::Reset()
		{
			fonts.OnLost();

			const uint oldInterval = presentation.PresentationInterval;
			presentation.PresentationInterval = GetDesiredPresentationInterval();
			uint buffers = timing.tripleBuffering ? 2 : 1;
			presentation.BackBufferCount = buffers;
			presentation.Flags = GetPresentationFlags();
			presentation.SwapEffect = GetSwapEffect();

			for (;;)
			{
				const HRESULT hResult = com->Reset( &presentation );

				if (SUCCEEDED(hResult))
				{
					break;
				}
				else if (hResult == D3DERR_DEVICELOST)
				{
					return D3DERR_DEVICELOST;
				}
				else if (buffers != presentation.BackBufferCount)
				{
					buffers = presentation.BackBufferCount;
					Io::Log() << "Direct3D: Warning! IDirect3DDevice9::Reset() failed, retrying with one back-buffer only..\r\n";
				}
				else throw Application::Exception
				(
					IDS_ERR_FAILED,
					hResult == D3DERR_INVALIDCALL         ? L"IDirect3DDevice9::Reset() (code: D3DERR_INVALIDCALL)"         :
					hResult == D3DERR_OUTOFVIDEOMEMORY    ? L"IDirect3DDevice9::Reset() (code: D3DERR_OUTOFVIDEOMEMORY)"    :
					hResult == D3DERR_DRIVERINTERNALERROR ? L"IDirect3DDevice9::Reset() (code: D3DERR_DRIVERINTERNALERROR)" :
					hResult == E_OUTOFMEMORY              ? L"IDirect3DDevice9::Reset() (code: E_OUTOFMEMORY)"              :
															L"IDirect3DDevice9::Reset()"
				);
			}

			if (!presentation.Windowed && dialogBoxMode && FAILED(com->SetDialogBoxMode( true )))
				throw Application::Exception( IDS_ERR_FAILED, L"IDirect3DDevice9::SetDialogBoxMode()" );

			Prepare();
			fonts.OnReset();

			if (presentation.PresentationInterval != oldInterval)
			{
				Io::Log() <<
				(
					presentation.PresentationInterval == D3DPRESENT_INTERVAL_IMMEDIATE ? "Direct3D: disabling VSYNC\r\n" :
					presentation.PresentationInterval == D3DPRESENT_INTERVAL_TWO       ? "Direct3D: enabling VSYNC on second refresh\r\n" :
					presentation.PresentationInterval == D3DPRESENT_INTERVAL_THREE     ? "Direct3D: enabling VSYNC on third refresh\r\n" :
                                                                                         "Direct3D: enabling VSYNC\r\n"
				);
			}

			if (!presentation.Windowed && ::GetMenu( presentation.hDeviceWindow ))
				::DrawMenuBar( presentation.hDeviceWindow );

			return D3D_OK;
		}

		void Direct2D::Device::Prepare() const
		{
			com->SetRenderState( D3DRS_ZWRITEENABLE, false        );
			com->SetRenderState( D3DRS_COLORVERTEX,  false        );
			com->SetRenderState( D3DRS_CULLMODE,     D3DCULL_NONE );
			com->SetRenderState( D3DRS_LIGHTING,     false        );
		}

		void Direct2D::Device::LogDisplaySwitch() const
		{
			Io::Log log;
			log << "Direct3D: entering ";

			D3DDISPLAYMODE mode;

			if (GetDisplayMode( mode ))
			{
				log << mode.Width
					<< 'x'
					<< mode.Height
					<< 'x'
					<< Base::FormatToBpp(mode.Format)
					<< ' '
					<< mode.RefreshRate
					<< "hz ";
			}

			log << (presentation.Windowed ? "window mode\r\n" : "full-screen mode\r\n");
		}

		uint Direct2D::Device::GetMaxMessageLength() const
		{
			return fonts.Width() ? (presentation.BackBufferWidth - fonts.Width() * 7) / fonts.Width() : 64;
		}

		#ifdef NST_MSVC_OPTIMIZE
		#pragma optimize("", on)
		#endif

		HRESULT Direct2D::Device::Repair(const HRESULT lastError)
		{
			NST_ASSERT( FAILED(lastError) );

			uint id = 0;
			wcstring msg;

			switch (lastError)
			{
				case D3DERR_DEVICELOST:
				case D3DERR_DEVICENOTRESET:

					switch (com->TestCooperativeLevel())
					{
						case D3DERR_DEVICELOST:

							return D3DERR_DEVICELOST;

						case D3DERR_DEVICENOTRESET:

							return Reset();

						case D3DERR_DRIVERINTERNALERROR:

							msg = L"Internal video driver error! Try upgrading it!";
							break;

						default:

							id = IDS_ERR_FAILED;
							msg = L"IDirect3DDevice9::TestCooperativeLevel()";
							break;
					}

				case D3DERR_DRIVERINTERNALERROR:

					msg = L"Internal video driver error! Try upgrading it!";
					break;

				case E_OUTOFMEMORY:

					msg = L"Out of memory!";
					break;

				case D3DERR_OUTOFVIDEOMEMORY:

					msg = L"Out of video memory!";
					break;

				default:

					msg = L"Unknown Direct3D error!";
					break;
			}

			throw Application::Exception( id, msg );
		}

		#ifdef NST_MSVC_OPTIMIZE
		#pragma optimize("t", on)
		#endif

		bool Direct2D::Device::CanToggleDialogBoxMode(bool enable) const
		{
			return !presentation.Windowed && dialogBoxMode != enable;
		}

		/**
		 * Verifies if a switch to a fullscreen mode can be made or if the mode is already set to fullscreen.
		 * 
		 * @param mode The video mode to set to fullscreen.
		 * @return True if switched to the fullscreen mode can be made. False if the mode is already set to fullscreen.
		 */
		bool Direct2D::Device::CanSwitchFullscreen(const Mode& mode) const
		{
			return
			(
				presentation.Windowed ||
				presentation.BackBufferWidth != mode.width ||
				presentation.BackBufferHeight != mode.height ||
				presentation.BackBufferFormat != (mode.bpp == 16 ? D3DFMT_R5G6B5 : D3DFMT_X8R8G8B8) ||
				presentation.FullScreen_RefreshRateInHz != GetDesiredPresentationRate( mode )
			);
		}

		/**
		 * Switches to fullscreen mode. Creates fonts to be displayed in fullscreen.
		 * 
		 * @param mode The video mode to set to fullscreen.
		 * @throws Application::Exception If failed switch to fullscreen.
		 * @see Application::Exception
		 */
		void Direct2D::Device::SwitchFullscreen(const Mode& mode)
		{
			presentation.Windowed = false;
			presentation.BackBufferWidth = mode.width;
			presentation.BackBufferHeight = mode.height;
			presentation.BackBufferFormat = (mode.bpp == 16 ? D3DFMT_R5G6B5 : D3DFMT_X8R8G8B8);
			presentation.FullScreen_RefreshRateInHz = GetDesiredPresentationRate( mode );

			if (FAILED(Reset()))
				throw Application::Exception( L"Couldn't switch display mode!" );

			fonts.Create( *this );
			LogDisplaySwitch();
		}

		HRESULT Direct2D::Device::ToggleDialogBoxMode()
		{
			NST_ASSERT( !presentation.Windowed );

			if (dialogBoxMode)
			{
				dialogBoxMode = false;
				com->SetDialogBoxMode( false );
			}
			else
			{
				dialogBoxMode = true;
			}

			return Reset();
		}

		/**
		 * Switches to window mode. Destroys any fullscreen fonts and creates fonts to be displayed in status bar.
		 * 
		 * @throws Application::Exception If failed switch to window mode.
		 * @see Application::Exception
		 */
		void Direct2D::Device::SwitchWindowed()
		{
			fonts.Destroy( false );

			presentation.Windowed = true;
			presentation.BackBufferWidth = 0;
			presentation.BackBufferHeight = 0;
			presentation.BackBufferFormat = D3DFMT_UNKNOWN;
			presentation.FullScreen_RefreshRateInHz = 0;

			if (dialogBoxMode)
			{
				dialogBoxMode = false;
				com->SetDialogBoxMode( false );
			}

			if (FAILED(Reset()))
				throw Application::Exception( L"Couldn't switch display mode!" );

			fonts.Create( *this );
			LogDisplaySwitch();
		}

		HRESULT Direct2D::Device::ResetWindowClient(const Point& client,HRESULT hResult)
		{
			NST_ASSERT( presentation.Windowed && client.x > 0 && client.y > 0 );

			presentation.BackBufferWidth = client.x;
			presentation.BackBufferHeight = client.y;

			if (SUCCEEDED(hResult) || hResult == INVALID_RECT)
				hResult = Reset();

			return hResult;
		}

		bool Direct2D::Device::ResetFrameRate(uint frameRate,bool vsync,bool tripleBuffering,const Base& base)
		{
			timing.frameRate = frameRate;
			timing.vsync = vsync;

			bool update = false;

			if (timing.tripleBuffering != tripleBuffering)
			{
				timing.tripleBuffering = tripleBuffering;
				update = true;
			}

			if (!presentation.Windowed)
			{
				const Mode mode
				(
					presentation.BackBufferWidth,
					presentation.BackBufferHeight,
					presentation.BackBufferFormat == D3DFMT_X8R8G8B8 ? 32 : 16
				);

				frameRate = GetDesiredPresentationRate( *base.GetAdapter(ordinal).modes.find(mode) );

				if (presentation.FullScreen_RefreshRateInHz != frameRate)
				{
					presentation.FullScreen_RefreshRateInHz = frameRate;
					update = true;
				}
			}

			return update || presentation.PresentationInterval != GetDesiredPresentationInterval();
		}

		Direct2D::Device::Fonts::Fonts()
		: width(0) {}

		void Direct2D::Device::Fonts::Font::Create(const Device& device)
		{
			wcstring fontName = L"System";
			uint fontHeight = 12;

			D3DDISPLAYMODE mode;
			device.GetDisplayMode( mode );

			if (mode.Width > 320 && mode.Height > 240)
			{
				fontHeight = mode.Height / (device.presentation.Windowed ? 32 : 16);

				switch (PRIMARYLANGID(::GetUserDefaultLangID()))
				{
					case LANG_JAPANESE: fontName = L"MS Gothic"; break;
					case LANG_CHINESE:  fontName = L"MS Hei";    break;
					case LANG_KOREAN:   fontName = L"GulimChe";  break;
					default:            fontName = L"Arial";     break;
				}
			}

			if (com)
			{
				Object::Pod<D3DXFONT_DESC> desc;
				com->GetDesc( &desc );

				if (desc.Height == int(fontHeight) && bool(desc.Width > 320 && desc.Height > 240) == bool(mode.Width > 320 && mode.Height > 240))
					return;

				com.Release();
			}

			::D3DXCreateFont
			(
				*device.com,
				fontHeight,
				0,
				FW_NORMAL,
				1,
				false,
				DEFAULT_CHARSET,
				OUT_DEFAULT_PRECIS,
				DEFAULT_QUALITY,
				DEFAULT_PITCH|FF_DONTCARE,
				fontName,
				&com
			);
		}

		uint Direct2D::Device::Fonts::Font::Width() const
		{
			TEXTMETRIC metric;

			if (com && SUCCEEDED(com->GetTextMetrics( &metric )))
				return metric.tmAveCharWidth;
			else
				return 0;
		}

		void Direct2D::Device::Fonts::Font::Destroy()
		{
			length = 0;
			com.Release();
		}

		void Direct2D::Device::Fonts::Font::OnReset() const
		{
			if (com)
				com->OnResetDevice();
		}

		void Direct2D::Device::Fonts::Font::OnLost() const
		{
			if (com)
				com->OnLostDevice();
		}

		void Direct2D::Device::Fonts::Font::Update(const GenericString& newstring)
		{
			string = newstring;
			length = newstring.Length();

			if (length && com)
				com->PreloadText( string.Ptr(), string.Length() );
		}

		void Direct2D::Device::Fonts::Create(const Device& device)
		{
			nfo.Create( device );

			if (!device.presentation.Windowed)
			{
				fps.Create( device );
				msg.Create( device );
			}

			width = nfo.Width();
		}

		void Direct2D::Device::Fonts::Destroy(const bool newDevice)
		{
			width = 0;

			fps.Destroy();
			msg.Destroy();

			if (newDevice)
				nfo.Destroy();
			else
				nfo.OnReset();
		}

		void Direct2D::Device::Fonts::OnReset() const
		{
			fps.OnReset();
			msg.OnReset();
			nfo.OnReset();
		}

		void Direct2D::Device::Fonts::OnLost() const
		{
			fps.OnLost();
			msg.OnLost();
			nfo.OnLost();
		}

		Direct2D::Device::Timing::Timing()
		:
		autoHz          (false),
		vsync           (false),
		tripleBuffering (false),
		frameRate       (Mode::DEFAULT_RATE)
		{
		}

		Direct2D::VertexBuffer::VertexBuffer()
		: numVertices(4), screenCurvature(0), dirty(false) {}

		Direct2D::VertexBuffer::~VertexBuffer()
		{
			Invalidate();
		}

		void Direct2D::VertexBuffer::Update(const Rect& picture,const Rect& c,const int s)
		{
			NST_ASSERT( picture.Width() > 0 && picture.Height() > 0 && c.Width() > 0 && c.Height() > 0 );

			dirty = true;

			rect = picture;
			clip = c;
			screenCurvature = s;

			const uint n = (s ? (TSL_PATCHES+1) * (TSL_PATCHES+1) : 4);

			if (numVertices != n)
			{
				numVertices = n;
				Invalidate();
			}
		}

		void Direct2D::VertexBuffer::Invalidate()
		{
			if (com)
			{
				IDirect3DDevice9* device;

				if (SUCCEEDED(com->GetDevice( &device )))
				{
					device->SetStreamSource( 0, NULL, 0, 0 );
					device->Release();
				}

				com.Release();
			}
		}

		HRESULT Direct2D::VertexBuffer::Validate(IDirect3DDevice9& device,const Textures& textures)
		{
			if (!com)
			{
				const HRESULT hResult = device.CreateVertexBuffer
				(
					numVertices * sizeof(Vertex),
					D3DUSAGE_WRITEONLY,
					FVF,
					D3DPOOL_MANAGED,
					&com,
					NULL
				);

				if (FAILED(hResult))
				{
					if (hResult == D3DERR_DEVICELOST)
						return D3DERR_DEVICELOST;
					else
						throw Application::Exception( IDS_ERR_FAILED, L"IDirect3DDevice9::CreateVertexBuffer()" );
				}

				dirty = true;
			}

			if (dirty)
			{
				dirty = false;

				void* ptr;
				const HRESULT hResult = com->Lock( 0, 0, &ptr, D3DLOCK_NOSYSLOCK );

				if (SUCCEEDED(hResult))
				{
					Vertex* NST_RESTRICT v = static_cast<Vertex*>(ptr);

					dirty = true;

					if (screenCurvature)
					{
						const float z = 1.f - screenCurvature / 40.f;
						const D3DXVECTOR2 p0( rect.left - 0.499f, rect.top - 0.499f );
						const D3DXVECTOR2 p1( rect.right - 0.499f, rect.bottom - 0.499f );
						const D3DXVECTOR2 t( rect.Width() * z, rect.Height() * z );

						for (uint y=0; y <= TSL_PATCHES; ++y)
						{
							D3DXVECTOR2 vy;

							float weight = y / float(TSL_PATCHES);
							::D3DXVec2Hermite( &vy, &p0, &t, &p1, &t, weight );

							vy.x = textures.GetScreenBottomV(clip.top + clip.Height() * weight);
							float x1 = textures.GetEffectBottomV(clip.top + clip.Height() * weight);

							for (uint x=0; x <= TSL_PATCHES; ++x, ++v)
							{
								D3DXVECTOR2 vx;

								weight = x / float(TSL_PATCHES);
								::D3DXVec2Hermite( &vx, &p0, &t, &p1, &t, weight );

								v->x   = vx.x;
								v->y   = vy.y;
								v->z   = 0.f;
								v->rhw = 1.f;
								v->u0  = textures.GetScreenLeftU(clip.left + clip.Width() * weight);
								v->u1  = textures.GetEffectLeftU(clip.left + clip.Width() * weight);
								v->v0  = vy.x;
								v->v1  = x1;
							}
						}
					}
					else
					{
						v[0].x   = rect.left - 0.499f;
						v[0].y   = rect.top - 0.499f;
						v[0].z   = 0.f;
						v[0].rhw = 1.f;
						v[0].u0  = textures.GetScreenLeftU( clip.left );
						v[0].u1  = textures.GetEffectLeftU( clip.left );
						v[0].v0  = textures.GetScreenTopV( clip.top );
						v[0].v1  = textures.GetEffectTopV( clip.top );
						v[1].x   = rect.left - 0.499f;
						v[1].y   = rect.bottom - 0.499f;
						v[1].z   = 0.f;
						v[1].rhw = 1.f;
						v[1].u0  = textures.GetScreenLeftU( clip.left );
						v[1].u1  = textures.GetEffectLeftU( clip.left );
						v[1].v0  = textures.GetScreenBottomV( clip.bottom );
						v[1].v1  = textures.GetEffectBottomV( clip.bottom );
						v[2].x   = rect.right - 0.499f;
						v[2].y   = rect.top - 0.499f;
						v[2].z   = 0.f;
						v[2].rhw = 1.f;
						v[2].u0  = textures.GetScreenRightU( clip.right );
						v[2].u1  = textures.GetEffectRightU( clip.right );
						v[2].v0  = textures.GetScreenTopV( clip.top );
						v[2].v1  = textures.GetEffectTopV( clip.top );
						v[3].x   = rect.right - 0.499f;
						v[3].y   = rect.bottom - 0.499f;
						v[3].z   = 0.f;
						v[3].rhw = 1.f;
						v[3].u0  = textures.GetScreenRightU( clip.right );
						v[3].u1  = textures.GetEffectRightU( clip.right );
						v[3].v0  = textures.GetScreenBottomV( clip.bottom );
						v[3].v1  = textures.GetEffectBottomV( clip.bottom );
					}

					com->Unlock();
				}
				else if (hResult == D3DERR_DEVICELOST)
				{
					return D3DERR_DEVICELOST;
				}
				else
				{
					throw Application::Exception( IDS_ERR_FAILED, L"IDirect3DVertexBuffer9::Lock()" );
				}

				com->PreLoad();
			}

			if (FAILED(device.SetFVF( FVF )))
				throw Application::Exception( IDS_ERR_FAILED, L"IDirect3DDevice9::SetFVF()" );

			if (FAILED(device.SetStreamSource( 0, *com, 0, sizeof(Vertex) )))
				throw Application::Exception( IDS_ERR_FAILED, L"IDirect3DDevice9::SetStreamSource()" );

			return D3D_OK;
		}

		Direct2D::IndexBuffer::IndexBuffer()
		: numStrips(0) {}

		Direct2D::IndexBuffer::~IndexBuffer()
		{
			Invalidate();
		}

		void Direct2D::IndexBuffer::Update(bool s)
		{
			if (bool(numStrips) != s)
			{
				numStrips = (s ? (TSL_PATCHES * 2 + 1) * TSL_PATCHES - 1 : 0);
				Invalidate();
			}
		}

		void Direct2D::IndexBuffer::Invalidate()
		{
			if (com)
			{
				IDirect3DDevice9* device;

				if (SUCCEEDED(com->GetDevice( &device )))
				{
					device->SetIndices( NULL );
					device->Release();
				}

				com.Release();
			}
		}

		HRESULT Direct2D::IndexBuffer::Validate(IDirect3DDevice9& device)
		{
			NST_ASSERT( !com || numStrips );

			if (numStrips)
			{
				if (!com)
				{
					const HRESULT hResult = device.CreateIndexBuffer
					(
						(((TSL_PATCHES * 2 + 1) * TSL_PATCHES) + 1) * sizeof(WORD),
						D3DUSAGE_WRITEONLY,
						D3DFMT_INDEX16,
						D3DPOOL_MANAGED,
						&com,
						NULL
					);

					if (SUCCEEDED(hResult))
					{
						void* ptr;
						const HRESULT hResult = com->Lock( 0, 0, &ptr, D3DLOCK_NOSYSLOCK );

						if (SUCCEEDED(hResult))
						{
							WORD* NST_RESTRICT data = static_cast<WORD*>(ptr);

							for (uint p=0, i=0, n=TSL_PATCHES+1;;)
							{
								uint j = i + n;

								do
								{
									*data++ = i;
									*data++ = i++ + n;
								}
								while (i < j);

								i += n-1;

								do
								{
									*data++ = i-- + n;
									*data++ = i;
								}
								while (i > j);

								i += n;
								p += 2;

								if (p == n-1)
								{
									*data++ = i;
									break;
								}
							}

							com->Unlock();
						}
						else if (hResult == D3DERR_DEVICELOST)
						{
							return D3DERR_DEVICELOST;
						}
						else
						{
							throw Application::Exception( IDS_ERR_FAILED, L"IDirect3DIndexBuffer9::Lock()" );
						}
					}
					else if (hResult == D3DERR_DEVICELOST)
					{
						return D3DERR_DEVICELOST;
					}
					else
					{
						throw Application::Exception( IDS_ERR_FAILED, L"IDirect3DDevice9::CreateIndexBuffer()" );
					}
				}

				com->PreLoad();

				if (FAILED(device.SetIndices( *com )))
					throw Application::Exception( IDS_ERR_FAILED, L"IDirect3DDevice9::SetIndices()" );
			}

			return D3D_OK;
		}

		Direct2D::Textures::Textures(D3DFORMAT backBufferFormat)
		:
		screenTexture (backBufferFormat),
		effectTexture (backBufferFormat),
		filter        (Adapter::FILTER_NONE)
		{}

		void Direct2D::Textures::Update(const Adapter& adapter,const Point& screen,const uint scanlines,const Adapter::Filter f,const bool useVidMem)
		{
			NST_ASSERT( screen.x > 0 && screen.y > 0 );

			filter = f;

			effectTexture.Update( adapter, screen, scanlines );
			screenTexture.Update( adapter, screen, useVidMem );
		}

		void Direct2D::Textures::Flush()
		{
			screenTexture.Flush();
		}

		void Direct2D::Textures::Invalidate()
		{
			effectTexture.Invalidate();
			screenTexture.Invalidate();
		}

		HRESULT Direct2D::Textures::Validate(IDirect3DDevice9& device,const Adapter& adapter,const D3DFORMAT backBufferFormat)
		{
			if (screenTexture.Validate( device, backBufferFormat, adapter ) && effectTexture.Validate( device, backBufferFormat ))
			{
				const D3DTEXTUREFILTERTYPE type = (filter == Adapter::FILTER_NONE ? D3DTEXF_POINT : D3DTEXF_LINEAR);

				for (uint i=0, n = (effectTexture ? 2 : 1); i < n; ++i)
				{
					device.SetSamplerState( i, D3DSAMP_MINFILTER, type );
					device.SetSamplerState( i, D3DSAMP_MAGFILTER, type );
				}

				return D3D_OK;
			}

			return D3DERR_DEVICELOST;
		}

		bool Direct2D::Textures::SaveToFile(wcstring const file,const D3DXIMAGE_FILEFORMAT type) const
		{
			return screenTexture.SaveToFile( file, type );
		}

		Direct2D::Textures::Texture::Texture(uint s,D3DFORMAT f)
		: size(256,256), stage(s)
		{
			desc.Width = size.x;
			desc.Height = size.y;
			desc.Format = f;
		}

		Direct2D::Textures::Texture::~Texture()
		{
			Invalidate();
		}

		uint Direct2D::Textures::Texture::GetSquared(const Point& p)
		{
			uint squared = NST_MAX(p.x,p.y);

			squared--;
			squared |= squared >> 1;
			squared |= squared >> 2;
			squared |= squared >> 4;
			squared |= squared >> 8;
			squared |= squared >> 16;
			squared++;

			return squared;
		}

		void Direct2D::Textures::Texture::Invalidate()
		{
			if (com)
			{
				IDirect3DDevice9* device;

				if (SUCCEEDED(com->GetDevice( &device )))
				{
					device->SetTextureStageState( stage, D3DTSS_COLOROP, D3DTOP_DISABLE );
					device->SetTexture( stage, NULL );
					device->Release();
				}

				com.Release();
			}
		}

		bool Direct2D::Textures::Texture::Validate(IDirect3DDevice9& device,const D3DFORMAT desiredFormat,const bool dynamicUsage)
		{
			if (!com)
			{
				const HRESULT hResult = ::D3DXCreateTexture
				(
					&device,
					desc.Width,
					desc.Height,
					1,
					dynamicUsage ? D3DUSAGE_DYNAMIC : 0,
					desiredFormat,
					dynamicUsage ? D3DPOOL_DEFAULT : D3DPOOL_MANAGED,
					&com
				);

				if (SUCCEEDED(hResult))
				{
					if (FAILED(com->GetLevelDesc( 0, &desc )))
						throw Application::Exception( IDS_ERR_FAILED, L"IDirect3DDevice9::GetLevelDesc()" );

					if (desc.Width >= size.x && desc.Height >= size.y)
					{
						if (!GetBitsPerPixel())
							throw Application::Exception( L"Unsupported bits-per-pixel format!" );

						return true;
					}
					else
					{
						throw Application::Exception( L"Maximum texture dimension too small!" );
					}
				}
				else if (hResult == D3DERR_DEVICELOST)
				{
					return false;
				}
				else
				{
					throw Application::Exception( IDS_ERR_FAILED, L"IDirect3DDevice9::CreateTexture()" );
				}
			}

			return false;
		}

		Direct2D::Textures::ScreenTexture::ScreenTexture(D3DFORMAT f)
		: Texture(0,f), useVidMem(false) {}

		void Direct2D::Textures::ScreenTexture::Update(const Adapter& adapter,Point newSize,const bool wantVidMem)
		{
			size = newSize;

			if (!adapter.anyTextureSize)
				newSize = GetSquared(newSize);

			if (desc.Width != newSize.x || desc.Height != newSize.y)
			{
				desc.Width = newSize.x;
				desc.Height = newSize.y;
				Invalidate();
			}

			if (useVidMem != wantVidMem)
			{
				useVidMem = wantVidMem;
				Invalidate();
			}
		}

		void Direct2D::Textures::ScreenTexture::Flush()
		{
			if (desc.Pool == D3DPOOL_DEFAULT)
				Invalidate();
		}

		bool Direct2D::Textures::ScreenTexture::Validate(IDirect3DDevice9& device,const D3DFORMAT desiredFormat,const Adapter& adapter)
		{
			if (!Texture::Validate( device, desiredFormat, useVidMem && adapter.videoMemScreen ) && !com)
				return false;

			device.SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_SELECTARG1 );
			device.SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE );
			device.SetTextureStageState( 0, D3DTSS_ALPHAOP, D3DTOP_DISABLE );

			device.SetSamplerState( 0, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP );
			device.SetSamplerState( 0, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP );

			if (FAILED(device.SetTexture( 0, *com )))
				throw Application::Exception( IDS_ERR_FAILED, L"IDirect3DDevice9::SetTexture()" );

			return true;
		}

		bool Direct2D::Textures::ScreenTexture::SaveToFile(wcstring const file,const D3DXIMAGE_FILEFORMAT type) const
		{
			NST_ASSERT( file && *file && size.x && size.y );

			if (!com)
				return false;

			ComInterface<IDirect3DSurface9> surface;

			{
				ComInterface<IDirect3DDevice9> device;

				if (FAILED(com->GetDevice( &device )))
					return false;

				if (FAILED(device->CreateOffscreenPlainSurface( size.x, size.y, D3DFMT_R8G8B8, D3DPOOL_SCRATCH, &surface, NULL )))
					return false;
			}

			ulong masks[3];
			GetBitMask(masks[0],masks[1],masks[2]);

			uint shifts[3];

			for (uint i=0; i < 3; ++i)
			{
				NST_ASSERT( masks[i] );

				for (shifts[i]=0; !(masks[i] & 0x1); ++shifts[i])
					masks[i] >>= 1;
			}

			D3DLOCKED_RECT dstLock;

			if (FAILED(surface->LockRect( &dstLock, NULL, D3DLOCK_NOSYSLOCK )))
				return false;

			D3DLOCKED_RECT srcLock;
			const RECT rect = {0,0,size.x,size.y};

			if (FAILED(com->LockRect( 0, &srcLock, &rect, D3DLOCK_READONLY|D3DLOCK_NOSYSLOCK )))
			{
				surface->UnlockRect();
				return false;
			}

			BYTE* NST_RESTRICT dst = static_cast<BYTE*>(dstLock.pBits);
			const uint dstPitch = dstLock.Pitch - size.x * 3;

			if (GetBitsPerPixel() == 16)
			{
				const WORD* NST_RESTRICT src = static_cast<WORD*>(srcLock.pBits);
				const uint srcPitch = srcLock.Pitch - size.x * sizeof(WORD);

				for (uint y=size.y; y; --y)
				{
					for (const BYTE* const end=dst+size.x*3; dst != end; dst += 3)
					{
						const uint pixel = *src++;

						dst[2] = ((pixel >> shifts[0] & masks[0]) * 0xFF + masks[0] / 2) / masks[0];
						dst[1] = ((pixel >> shifts[1] & masks[1]) * 0xFF + masks[1] / 2) / masks[1];
						dst[0] = ((pixel >> shifts[2] & masks[2]) * 0xFF + masks[2] / 2) / masks[2];
					}

					src = reinterpret_cast<const WORD*>(reinterpret_cast<const BYTE*>(src) + srcPitch);
					dst += dstPitch;
				}
			}
			else
			{
				const DWORD* NST_RESTRICT src = static_cast<DWORD*>(srcLock.pBits);
				const uint srcPitch = srcLock.Pitch - size.x * sizeof(DWORD);

				for (uint y=size.y; y; --y)
				{
					for (const BYTE* const end=dst+size.x*3; dst != end; dst += 3)
					{
						const uint pixel = *src++;

						dst[2] = ((pixel >> shifts[0] & masks[0]) * 0xFF + masks[0] / 2) / masks[0];
						dst[1] = ((pixel >> shifts[1] & masks[1]) * 0xFF + masks[1] / 2) / masks[1];
						dst[0] = ((pixel >> shifts[2] & masks[2]) * 0xFF + masks[2] / 2) / masks[2];
					}

					src = reinterpret_cast<const DWORD*>(reinterpret_cast<const BYTE*>(src) + srcPitch);
					dst += dstPitch;
				}
			}

			com->UnlockRect( 0 );
			surface->UnlockRect();

			return SUCCEEDED(::D3DXSaveSurfaceToFile( file, type, *surface, NULL, &rect ));
		}

		Direct2D::Textures::EffectTexture::EffectTexture(D3DFORMAT f)
		: Texture(1,f), scanlines(0), dirty(false) {}

		void Direct2D::Textures::EffectTexture::Update(const Adapter& adapter,Point newSize,const uint newScanlines)
		{
			NST_ASSERT( newScanlines <= MAX_SCANLINES );

			newSize.x = 1;
			size = newSize;

			if (!adapter.anyTextureSize)
				newSize = GetSquared(newSize);

			if (desc.Width != newSize.x || desc.Height != newSize.y)
			{
				desc.Width = newSize.x;
				desc.Height = newSize.y;
				Invalidate();
			}

			if (scanlines != newScanlines)
			{
				dirty = true;

				if ((scanlines > 0) != (newScanlines > 0))
					Invalidate();

				scanlines = newScanlines;
			}
		}

		bool Direct2D::Textures::EffectTexture::Validate(IDirect3DDevice9& device,const D3DFORMAT desiredFormat)
		{
			NST_ASSERT( !com || scanlines );

			if (scanlines)
			{
				if (Texture::Validate( device, desiredFormat, false ))
				{
					dirty = true;
				}
				else if (!com)
				{
					return false;
				}

				if (dirty)
				{
					dirty = false;

					ulong mask[3];
					GetBitMask(mask[0],mask[1],mask[2]);

					const uint intensity[2] =
					{
						mask[0] | mask[1] | mask[2],
						((mask[0] * (100 - scanlines) / 100) & mask[0]) |
						((mask[1] * (100 - scanlines) / 100) & mask[1]) |
						((mask[2] * (100 - scanlines) / 100) & mask[2])
					};

					D3DLOCKED_RECT lockedRect;
					const RECT rect = {0,0,desc.Width,desc.Height};
					const HRESULT hResult = com->LockRect( 0, &lockedRect, &rect, D3DLOCK_NOSYSLOCK );

					if (SUCCEEDED(hResult))
					{
						if (GetBitsPerPixel() == 32)
						{
							DWORD* NST_RESTRICT dst = static_cast<DWORD*>(lockedRect.pBits);
							const uint pitch = lockedRect.Pitch - desc.Width * sizeof(DWORD);

							for (uint y=desc.Height/2; y; --y)
							{
								for (uint i=0; i < 2; ++i)
								{
									for (uint x=desc.Width; x; --x)
										*dst++ = intensity[i];

									dst = reinterpret_cast<DWORD*>(reinterpret_cast<BYTE*>(dst) + pitch);
								}
							}
						}
						else
						{
							WORD* NST_RESTRICT dst = static_cast<WORD*>(lockedRect.pBits);
							const uint pitch = lockedRect.Pitch - desc.Width * sizeof(WORD);

							for (uint y=desc.Height/2; y; --y)
							{
								for (uint i=0; i < 2; ++i)
								{
									for (uint x=desc.Width; x; --x)
										*dst++ = intensity[i];

									dst = reinterpret_cast<WORD*>(reinterpret_cast<BYTE*>(dst) + pitch);
								}
							}
						}

						com->UnlockRect(0);
					}
					else if (hResult == D3DERR_DEVICELOST)
					{
						return false;
					}
					else
					{
						throw Application::Exception( IDS_ERR_FAILED, L"IDirect3DTexture9::LockRect()" );
					}

					com->PreLoad();
				}

				device.SetTextureStageState( 1, D3DTSS_COLOROP, D3DTOP_MODULATE );
				device.SetTextureStageState( 1, D3DTSS_COLORARG1, D3DTA_TEXTURE );
				device.SetTextureStageState( 1, D3DTSS_COLORARG2, D3DTA_CURRENT );

				device.SetSamplerState( 1, D3DSAMP_ADDRESSU, D3DTADDRESS_WRAP );
				device.SetSamplerState( 1, D3DSAMP_ADDRESSV, D3DTADDRESS_WRAP );

				if (FAILED(device.SetTexture( 1, *com )))
					throw Application::Exception( IDS_ERR_FAILED, L"IDirect3DDevice9::SetTexture()" );
			}

			return true;
		}

		#ifdef NST_MSVC_OPTIMIZE
		#pragma optimize("", on)
		#endif
	}
}
