/*************************************************************************
 *
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 * 
 * Copyright 2000, 2010 Oracle and/or its affiliates.
 *
 * OpenOffice.org - a multi-platform office productivity suite
 *
 * This file is part of OpenOffice.org.
 *
 * OpenOffice.org is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * OpenOffice.org 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 Lesser General Public License version 3 for more details
 * (a copy is included in the LICENSE file that accompanied this code).
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with OpenOffice.org.  If not, see
 * <http://www.openoffice.org/license.html>
 * for a copy of the LGPLv3 License.
 *
 ************************************************************************/

// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_canvas.hxx"

#if DIRECTX_VERSION == 0x0900

#define MAX_TEXTURE_SIZE (2048)
#define MIN_TEXTURE_SIZE (32)
//#define FAKE_MAX_NUMBER_TEXTURES (2)
//#define FAKE_MAX_TEXTURE_SIZE (4096)

#define VERTEX_BUFFER_SIZE (341*3) // 1023, the size of the internal
                                   // vertex buffer (must be divisable
                                   // by 3, as each triangle primitive
                                   // has 3 vertices)


//////////////////////////////////////////////////////////////////////////////////
// includes
//////////////////////////////////////////////////////////////////////////////////
#include <vcl/syschild.hxx>
#include <vcl/window.hxx>

#include <canvas/debug.hxx>
#include <canvas/verbosetrace.hxx>
#include <tools/diagnose_ex.h>

#include <canvas/elapsedtime.hxx>
#include <canvas/canvastools.hxx>
#include <canvas/rendering/icolorbuffer.hxx>
#include <canvas/rendering/isurface.hxx>
#include <canvas/rendering/irendermodule.hxx>
#include <basegfx/numeric/ftools.hxx>
#include <basegfx/vector/b2dsize.hxx>
#include <basegfx/vector/b2isize.hxx>
#include <basegfx/point/b2ipoint.hxx>
#include <basegfx/range/b2irectangle.hxx>
#include <boost/scoped_ptr.hpp>
#include <com/sun/star/lang/NoSupportException.hpp>

#include "dx_rendermodule.hxx"
#include "dx_config.hxx"

#undef WB_LEFT
#undef WB_RIGHT

#include "dx_impltools.hxx"
#include <vcl/sysdata.hxx>

#if defined(DX_DEBUG_IMAGES) 
# if OSL_DEBUG_LEVEL > 0
#  include <imdebug.h>
#  undef min
#  undef max
# endif
#endif

using namespace ::com::sun::star;

//////////////////////////////////////////////////////////////////////////////////
// 'dxcanvas' namespace
//////////////////////////////////////////////////////////////////////////////////

namespace dxcanvas
{
	namespace
	{
        //////////////////////////////////////////////////////////////////////////////////
        // monitorSupport
        //////////////////////////////////////////////////////////////////////////////////

        class monitorSupport
        {
        public:

            monitorSupport() :
                mhLibrary(LoadLibrary("user32.dll")),
                mpMonitorFromWindow(NULL)
            {
                if(mhLibrary)
                    mpMonitorFromWindow = reinterpret_cast<fMonitorFromWindow>(
                        GetProcAddress(
                            mhLibrary,"MonitorFromWindow"));
            }

            ~monitorSupport()
            {
                if(mhLibrary)
                    FreeLibrary(mhLibrary);
                mhLibrary=0;
            }

            HMONITOR MonitorFromWindow( HWND hwnd )
            {
                // return adapter_default in case something went wrong...
                if(!(mpMonitorFromWindow))
                    return HMONITOR(0);
                // MONITOR_DEFAULTTONEAREST
                const DWORD dwFlags(0x00000002);
                return mpMonitorFromWindow(hwnd,dwFlags);
            }
        private:

            HINSTANCE mhLibrary;
            typedef HMONITOR (WINAPI *fMonitorFromWindow )( HWND hwnd, DWORD dwFlags );
            fMonitorFromWindow mpMonitorFromWindow;
        };

        monitorSupport aMonitorSupport;


		class DXRenderModule;

		//////////////////////////////////////////////////////////////////////////////////
		// DXSurface
		//////////////////////////////////////////////////////////////////////////////////

		/** ISurface implemenation.

			@attention holds the DXRenderModule via non-refcounted
			reference! This is safe with current state of affairs, since
			the canvas::PageManager holds surface and render module via
			shared_ptr (and makes sure all surfaces are deleted before its
			render module member goes out of scope).
		*/
		class DXSurface : public canvas::ISurface
		{
		public:
			DXSurface( DXRenderModule&           rRenderModule,
                       const ::basegfx::B2ISize& rSize );
			~DXSurface();

			virtual bool selectTexture();
			virtual bool isValid();
			virtual bool update( const ::basegfx::B2IPoint& rDestPos,
								const ::basegfx::B2IRange& rSourceRect,
								::canvas::IColorBuffer&    rSource );
			virtual ::basegfx::B2IVector getSize();
			COMReference<IDirect3DTexture9> getTexture() const;

		private:
			/// Guard local methods against concurrent acces to RenderModule
			class ImplRenderModuleGuard : private ::boost::noncopyable
			{
			public:
				explicit inline ImplRenderModuleGuard( DXRenderModule& rRenderModule );
				inline ~ImplRenderModuleGuard();
	                
			private:
				DXRenderModule& mrRenderModule;
			};

			DXRenderModule&                  mrRenderModule;
			COMReference<IDirect3DTexture9>  mpTexture;

			::basegfx::B2IVector             maSize;
		};


		//////////////////////////////////////////////////////////////////////////////////
		// DXRenderModule
		//////////////////////////////////////////////////////////////////////////////////

		/// Default implementation of IDXRenderModule
		class DXRenderModule : public IDXRenderModule
		{
        public:
            explicit DXRenderModule( const ::Window& rWindow );
			~DXRenderModule();
		            
            virtual void lock() const { maMutex.acquire(); }
            virtual void unlock() const { maMutex.release(); }

            virtual COMReference<IDirect3DSurface9>
                createSystemMemorySurface( const ::basegfx::B2IVector& rSize );
			virtual void disposing();
			virtual HWND getHWND() const { return mhWnd; }
			virtual void screenShot();

            virtual bool flip( const ::basegfx::B2IRectangle& rUpdateArea,
                               const ::basegfx::B2IRectangle& rCurrWindowArea );

            virtual void resize( const ::basegfx::B2IRange& rect );
            virtual ::basegfx::B2IVector getPageSize();
            virtual ::canvas::ISurfaceSharedPtr createSurface( const ::basegfx::B2IVector& surfaceSize );
            virtual void beginPrimitive( PrimitiveType eType );
            virtual void endPrimitive();
            virtual void pushVertex( const ::canvas::Vertex& vertex );
            virtual bool isError();

            COMReference<IDirect3DDevice9> getDevice() { return mpDevice; }

            void flushVertexCache();
			void commitVertexCache();

        private:

            bool create( const ::Window& rWindow );
			bool createDevice();
            bool verifyDevice( const UINT nAdapter );
			UINT getAdapterFromWindow();
            
            /** This object represents the DirectX state machine.  In order
                to serialize access to DirectX's global state, a global
                mutex is required.
            */
            static ::osl::Mutex                         maMutex;

            HWND                                        mhWnd;
            COMReference<IDirect3DDevice9>              mpDevice;
            COMReference<IDirect3D9>                    mpDirect3D9;
            COMReference<IDirect3DSwapChain9>           mpSwapChain;
            COMReference<IDirect3DVertexBuffer9>        mpVertexBuffer;
            ::canvas::ISurfaceSharedPtr                 mpTexture;
            ::boost::scoped_ptr<SystemChildWindow>      mpWindow;
            ::basegfx::B2IVector                        maSize;
            typedef std::vector<canvas::Vertex>         vertexCache_t;
            vertexCache_t                               maVertexCache;
            std::size_t                                 mnCount;
            int                                         mnBeginSceneCount;
            bool                                        mbCanUseDynamicTextures;
            bool                                        mbError;
            PrimitiveType                               meType;
            ::basegfx::B2IVector                        maPageSize;
            D3DPRESENT_PARAMETERS                       mad3dpp;

			inline bool isDisposed() const { return (mhWnd==NULL); }

			struct dxvertex
			{
				float x,y,z,rhw;
				DWORD diffuse;
				float u,v;
			};

			std::size_t									maNumVertices;
			std::size_t                                 maWriteIndex;
			std::size_t                                 maReadIndex;
		};

		::osl::Mutex DXRenderModule::maMutex;

		//////////////////////////////////////////////////////////////////////////////////
		// DXSurface::ImplRenderModuleGuard
		//////////////////////////////////////////////////////////////////////////////////

		inline DXSurface::ImplRenderModuleGuard::ImplRenderModuleGuard( 
			DXRenderModule& rRenderModule ) :
			mrRenderModule( rRenderModule )
		{
			mrRenderModule.lock();
		}

		inline DXSurface::ImplRenderModuleGuard::~ImplRenderModuleGuard()
		{
			mrRenderModule.unlock();
		}

#ifdef FAKE_MAX_NUMBER_TEXTURES
		static sal_uInt32 gNumSurfaces = 0;
#endif

        void fillRect( sal_uInt32 *pDest,
                       sal_uInt32 dwWidth,
                       sal_uInt32 dwHeight,
                       sal_uInt32 dwPitch,
                       sal_uInt32 dwColor )
        {
            for(sal_uInt32 i=0; i<dwWidth; ++i)
            {
                pDest[i]=dwColor;
                pDest[((dwHeight-1)*dwPitch)+i]=dwColor;
            }

            for(sal_uInt32 j=0; j<dwHeight; ++j)
            {
                pDest[0]=dwColor;
                pDest[dwWidth-1]=dwColor;
                pDest += dwPitch;
            }
        }

		//////////////////////////////////////////////////////////////////////////////////
		// DXSurface::DXSurface
		//////////////////////////////////////////////////////////////////////////////////

		DXSurface::DXSurface( DXRenderModule&           rRenderModule,
							  const ::basegfx::B2ISize& rSize ) : 
            mrRenderModule(rRenderModule),
            mpTexture(NULL),
			maSize()
		{
			ImplRenderModuleGuard aGuard( mrRenderModule );

#ifdef FAKE_MAX_NUMBER_TEXTURES
			++gNumSurfaces;
			if(gNumSurfaces >= FAKE_MAX_NUMBER_TEXTURES)
				return;
#endif

#ifdef FAKE_MAX_TEXTURE_SIZE
			if(rSize.getX() > FAKE_MAX_TEXTURE_SIZE)
				return;
			if(rSize.getY() > FAKE_MAX_TEXTURE_SIZE)
				return;
#endif

			ENSURE_ARG_OR_THROW(rSize.getX() > 0 && rSize.getY() > 0, 
							"DXSurface::DXSurface(): request for zero-sized surface");

			COMReference<IDirect3DDevice9> pDevice(rRenderModule.getDevice());

			IDirect3DTexture9 *pTexture(NULL);
			if(FAILED(pDevice->CreateTexture(
				rSize.getX(),
				rSize.getY(),
				1,0,D3DFMT_A8R8G8B8,
				D3DPOOL_MANAGED,
				&pTexture,NULL)))
				return;

			mpTexture=COMReference<IDirect3DTexture9>(pTexture);
            maSize = rSize;
		}

		//////////////////////////////////////////////////////////////////////////////////
		// DXSurface::~DXSurface
		//////////////////////////////////////////////////////////////////////////////////

		DXSurface::~DXSurface()
		{
			ImplRenderModuleGuard aGuard( mrRenderModule );

#ifdef FAKE_MAX_NUMBER_TEXTURES
			gNumSurfaces--;
#endif
		}

		//////////////////////////////////////////////////////////////////////////////////
		// DXSurface::selectTexture
		//////////////////////////////////////////////////////////////////////////////////

		bool DXSurface::selectTexture()
		{
			ImplRenderModuleGuard aGuard( mrRenderModule );
			mrRenderModule.flushVertexCache();
			COMReference<IDirect3DDevice9> pDevice(mrRenderModule.getDevice());

			if( FAILED(pDevice->SetTexture(0,mpTexture.get())) )
                return false;

			return true;
		}

		//////////////////////////////////////////////////////////////////////////////////
		// DXSurface::isValid
		//////////////////////////////////////////////////////////////////////////////////

		bool DXSurface::isValid()
		{
			ImplRenderModuleGuard aGuard( mrRenderModule );

			if(!(mpTexture.is()))
				return false;
			return true;
		}

		//////////////////////////////////////////////////////////////////////////////////
		// DXSurface::update
		//////////////////////////////////////////////////////////////////////////////////

		bool DXSurface::update( const ::basegfx::B2IPoint& rDestPos,
								const ::basegfx::B2IRange& rSourceRect,
								::canvas::IColorBuffer&    rSource )
		{
			ImplRenderModuleGuard aGuard( mrRenderModule );

			// can't update if surface is not valid, that means
			// either not existent nor restored...
			if(!(isValid()))
				return false;

			D3DLOCKED_RECT aLockedRect;
			RECT rect;
			rect.left = std::max(sal_Int32(0),rDestPos.getX());
			rect.top =  std::max(sal_Int32(0),rDestPos.getY());
            // to avoid interpolation artifacts from other textures,
            // the surface manager allocates one pixel gap between
            // them. Clear that to transparent.
			rect.right = std::min(maSize.getX(),
                                  rect.left + sal_Int32(rSourceRect.getWidth()+1));
            rect.bottom = std::min(maSize.getY(),
                                   rect.top + sal_Int32(rSourceRect.getHeight()+1));
            const bool bClearRightColumn( rect.right < maSize.getX() );
            const bool bClearBottomRow( rect.bottom < maSize.getY() );            

			if(SUCCEEDED(mpTexture->LockRect(0,&aLockedRect,&rect,D3DLOCK_NOSYSLOCK)))
			{
				if(sal_uInt8* pImage = rSource.lock())
				{
					switch( rSource.getFormat() )
					{
						case ::canvas::IColorBuffer::FMT_A8R8G8B8:
						{
							const std::size_t nSourceBytesPerPixel(4);
							const std::size_t nSourcePitchInBytes(rSource.getStride());
							pImage += rSourceRect.getMinY()*nSourcePitchInBytes;
							pImage += rSourceRect.getMinX()*nSourceBytesPerPixel;

							// calculate the destination memory address
							sal_uInt8 *pDst = (sal_uInt8*)aLockedRect.pBits;

							const sal_uInt32 nNumBytesToCopy(
								static_cast<sal_uInt32>(
									rSourceRect.getWidth())*
								nSourceBytesPerPixel);
							const sal_uInt64 nNumLines(rSourceRect.getHeight());

							for(sal_uInt32 i=0; i<nNumLines; ++i)
							{
                                rtl_copyMemory(pDst,pImage,nNumBytesToCopy);

                                if( bClearRightColumn )
                                {
                                    // to avoid interpolation artifacts
                                    // from other textures, the surface
                                    // manager allocates one pixel gap
                                    // between them. Clear that to
                                    // transparent.
                                    pDst[nNumBytesToCopy] = 
                                        pDst[nNumBytesToCopy+1] = 
                                        pDst[nNumBytesToCopy+2] = 
                                        pDst[nNumBytesToCopy+3] = 0x00;
                                }
                                pDst += aLockedRect.Pitch;
								pImage += nSourcePitchInBytes;
							}

                            if( bClearBottomRow )
                                rtl_zeroMemory(pDst,nNumBytesToCopy+4);
						}
						break;

						case ::canvas::IColorBuffer::FMT_R8G8B8:
						{
							const std::size_t nSourceBytesPerPixel(3);
							const std::size_t nSourcePitchInBytes(rSource.getStride());
							pImage += rSourceRect.getMinY()*nSourcePitchInBytes;
							pImage += rSourceRect.getMinX()*nSourceBytesPerPixel;

							// calculate the destination memory address
							sal_uInt8 *pDst = (sal_uInt8*)aLockedRect.pBits;

							const sal_Int32 nNumColumns(
                                sal::static_int_cast<sal_Int32>(rSourceRect.getWidth()));
							const sal_Int32 nNumLines(
                                sal::static_int_cast<sal_Int32>(rSourceRect.getHeight()));
							for(sal_Int32 i=0; i<nNumLines; ++i)
							{
								sal_uInt32 *pDstScanline = reinterpret_cast<sal_uInt32 *>(pDst);
								sal_uInt8 *pSrcScanline = reinterpret_cast<sal_uInt8 *>(pImage);

                                for(sal_Int32 x=0; x<nNumColumns; ++x)
                                {
                                    sal_uInt32 color(0xFF000000);
                                    color |= pSrcScanline[2]<<16;
                                    color |= pSrcScanline[1]<<8;
                                    color |= pSrcScanline[0];
                                    pSrcScanline += 3;
                                    *pDstScanline++ = color;
                                }
                                if( bClearRightColumn )
                                    *pDstScanline++ = 0xFF000000;

								pDst += aLockedRect.Pitch;
								pImage += nSourcePitchInBytes;
							}

                            if( bClearBottomRow )
                                rtl_zeroMemory(pDst,4*(nNumColumns+1));
						}
						break;

						case ::canvas::IColorBuffer::FMT_X8R8G8B8:
						{
							const std::size_t nSourceBytesPerPixel(4);
							const std::size_t nSourcePitchInBytes(rSource.getStride());
							pImage += rSourceRect.getMinY()*nSourcePitchInBytes;
							pImage += rSourceRect.getMinX()*nSourceBytesPerPixel;

							// calculate the destination memory address
							sal_uInt8 *pDst = (sal_uInt8*)aLockedRect.pBits;

							const sal_Int32 nNumLines(
                                sal::static_int_cast<sal_Int32>(rSourceRect.getHeight()));
							const sal_Int32 nNumColumns(
                                sal::static_int_cast<sal_Int32>(rSourceRect.getWidth()));
							for(sal_Int32 i=0; i<nNumLines; ++i)
							{
								sal_uInt32 *pSrc32 = reinterpret_cast<sal_uInt32 *>(pImage);
								sal_uInt32 *pDst32 = reinterpret_cast<sal_uInt32 *>(pDst);
								for(sal_Int32 j=0; j<nNumColumns; ++j)
									pDst32[j] = 0xFF000000 | pSrc32[j];

                                if( bClearRightColumn )
                                    pDst32[nNumColumns] = 0xFF000000;

								pDst += aLockedRect.Pitch;
								pImage += nSourcePitchInBytes;
							}

                            if( bClearBottomRow )
                                rtl_zeroMemory(pDst,4*(nNumColumns+1));
						}
						break;

						default:
							ENSURE_OR_RETURN_FALSE(false, 
											"DXSurface::update(): Unknown/unimplemented buffer format" );
							break;
					}

					rSource.unlock();
				}

				return SUCCEEDED(mpTexture->UnlockRect(0));
			}

			return true;
		}

		//////////////////////////////////////////////////////////////////////////////////
		// DXSurface::getSize
		//////////////////////////////////////////////////////////////////////////////////
		
		::basegfx::B2IVector DXSurface::getSize()
		{
			return maSize;
		}

        COMReference<IDirect3DTexture9> DXSurface::getTexture() const
        {
            return mpTexture;
        }

		//////////////////////////////////////////////////////////////////////////////////
		// DXRenderModule::DXRenderModule
		//////////////////////////////////////////////////////////////////////////////////

        DXRenderModule::DXRenderModule( const ::Window& rWindow ) :
	        mhWnd(0),
			mpDevice(),
			mpDirect3D9(),
			mpSwapChain(),
            mpVertexBuffer(),
			mpTexture(),
			maSize(),
            maVertexCache(),
			mnCount(0),
			mnBeginSceneCount(0),
			mbCanUseDynamicTextures(false),
			mbError( false ),
			meType( PRIMITIVE_TYPE_UNKNOWN ),
			maPageSize(),
            mad3dpp(),
			maNumVertices( VERTEX_BUFFER_SIZE ),
			maWriteIndex(0),
			maReadIndex(0)
		{
            // TODO(P2): get rid of those fine-grained locking
			::osl::MutexGuard aGuard( maMutex );

			if(!(create(rWindow)))
			{
				throw lang::NoSupportException(
					::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM(
                                         "Could not create DirectX device!") ),NULL);
			}

			// allocate a single texture surface which can be used later.
			// we also use this to calibrate the page size.
			::basegfx::B2IVector aPageSize(maPageSize);
			while(true)
			{
                mpTexture = ::canvas::ISurfaceSharedPtr(
                    new DXSurface(*this,aPageSize));
                if(mpTexture->isValid())
					break;

				aPageSize.setX(aPageSize.getX()>>1);
				aPageSize.setY(aPageSize.getY()>>1);
				if((aPageSize.getX() < MIN_TEXTURE_SIZE) ||
				   (aPageSize.getY() < MIN_TEXTURE_SIZE))
				{
					throw lang::NoSupportException(
						::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM(
											"Could not create DirectX device - "
                                            "insufficient texture space!") ),NULL);
				}
			}
			maPageSize=aPageSize;
			
			IDirect3DVertexBuffer9 *pVB(NULL);
			DWORD aFVF(D3DFVF_XYZRHW|D3DFVF_DIFFUSE|D3DFVF_TEX1);
            if( FAILED(mpDevice->CreateVertexBuffer(sizeof(dxvertex)*maNumVertices,
                                                    D3DUSAGE_DYNAMIC|D3DUSAGE_WRITEONLY,
                                                    aFVF,
                                                    D3DPOOL_DEFAULT,
                                                    &pVB,
                                                    NULL)) )
            {
                throw lang::NoSupportException(
                    ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM(
                                         "Could not create DirectX device - out of memory!")),NULL);
            }

			mpVertexBuffer=COMReference<IDirect3DVertexBuffer9>(pVB);
		}

		//////////////////////////////////////////////////////////////////////////////////
		// DXRenderModule::~DXRenderModule
		//////////////////////////////////////////////////////////////////////////////////

		DXRenderModule::~DXRenderModule()
		{
			disposing();
		}

		//////////////////////////////////////////////////////////////////////////////////
		// DXRenderModule::disposing
		//////////////////////////////////////////////////////////////////////////////////

		void DXRenderModule::disposing()
		{
			if(!(mhWnd))
				return;

			mpTexture.reset();
			mpWindow.reset();
			mhWnd=NULL;

            // refrain from releasing the DX9 objects. We're the only
            // ones holding references to them, and it might be
            // dangerous to destroy the DX9 device, before all other
            // objects are dead.
		}

		//////////////////////////////////////////////////////////////////////////////////
		// DXRenderModule::create
		//////////////////////////////////////////////////////////////////////////////////

		bool DXRenderModule::create( const ::Window& rWindow )
		{
            // TODO(P2): get rid of those fine-grained locking
			::osl::MutexGuard aGuard( maMutex );

			maVertexCache.reserve(1024);

			mpWindow.reset( 
				new SystemChildWindow(
				const_cast<Window *>(&rWindow), 0) );

			// system child window must not receive mouse events
			mpWindow->SetMouseTransparent( TRUE );
	        
			// parent should receive paint messages as well
			// [PARENTCLIPMODE_NOCLIP], the argument is here
			// passed as plain numeric value since the stupid
			// define utilizes a USHORT cast.
			mpWindow->SetParentClipMode(0x0002);
	        
			// the system child window must not clear its background
			mpWindow->EnableEraseBackground( sal_False );

			mpWindow->SetControlForeground();
			mpWindow->SetControlBackground();
			mpWindow->EnablePaint(sal_False);

			const SystemEnvData *pData = mpWindow->GetSystemData();
			const HWND hwnd(reinterpret_cast<HWND>(pData->hWnd));
			mhWnd = const_cast<HWND>(hwnd);

			ENSURE_OR_THROW( IsWindow( reinterpret_cast<HWND>(mhWnd) ),
							"DXRenderModule::create() No valid HWND given." );

			// retrieve position and size of the parent window
			const ::Size &rSizePixel(rWindow.GetSizePixel());

			// remember the size of the parent window, since we
			// need to use this for our child window.
			maSize.setX(static_cast<sal_Int32>(rSizePixel.Width()));
			maSize.setY(static_cast<sal_Int32>(rSizePixel.Height()));

			// let the child window cover the same size as the parent window.
			mpWindow->SetPosSizePixel(0,0,maSize.getX(),maSize.getY());

			// TODO(F2): since we would like to share precious hardware
			// resources, the direct3d9 object should be global. each new
			// request for a canvas should only create a new swapchain.
			mpDirect3D9 = COMReference<IDirect3D9>(
				Direct3DCreate9(D3D_SDK_VERSION));
			if(!mpDirect3D9.is())
				return false;

			// create a device from the direct3d9 object.
			if(!(createDevice()))
				return false;

			mpWindow->Show();

			return true;
		}

		//////////////////////////////////////////////////////////////////////////////////
		// DXRenderModule::verifyDevice
		//////////////////////////////////////////////////////////////////////////////////

		bool DXRenderModule::verifyDevice( const UINT nAdapter )
		{
			ENSURE_OR_THROW( mpDirect3D9.is(),
                              "DXRenderModule::verifyDevice() No valid device." );

			// ask direct3d9 about the capabilities of hardware devices on a specific adapter.
			// here we decide if the underlying hardware of the machine 'is good enough'.
			// since we only need a tiny little fraction of what could be used, this
			// is basically a no-op.
			D3DCAPS9 aCaps;
			if(FAILED(mpDirect3D9->GetDeviceCaps(nAdapter,D3DDEVTYPE_HAL,&aCaps)))
				return false;
			if(!(aCaps.MaxTextureWidth))
				return false;
			if(!(aCaps.MaxTextureHeight))
				return false;
			maPageSize = ::basegfx::B2IVector(aCaps.MaxTextureWidth,aCaps.MaxTextureHeight);

            // check device against white & blacklist entries
            D3DADAPTER_IDENTIFIER9 aIdent;
			if(FAILED(mpDirect3D9->GetAdapterIdentifier(nAdapter,0,&aIdent)))
				return false;
            
            DXCanvasItem aConfigItem;
            DXCanvasItem::DeviceInfo aInfo;
            aInfo.nVendorId = aIdent.VendorId;
            aInfo.nDeviceId = aIdent.DeviceId;
            aInfo.nDeviceSubSysId = aIdent.SubSysId;
            aInfo.nDeviceRevision = aIdent.Revision;

            aInfo.nDriverId = HIWORD(aIdent.DriverVersion.HighPart);
            aInfo.nDriverVersion = LOWORD(aIdent.DriverVersion.HighPart);
            aInfo.nDriverSubVersion = HIWORD(aIdent.DriverVersion.LowPart);
            aInfo.nDriverBuildId = LOWORD(aIdent.DriverVersion.LowPart);

            if( !aConfigItem.isDeviceUsable(aInfo) )
                return false;

            if( aConfigItem.isBlacklistCurrentDevice() )
            {
                aConfigItem.blacklistDevice(aInfo);
                return false;
            }

            aConfigItem.adaptMaxTextureSize(maPageSize);

			mbCanUseDynamicTextures = (aCaps.Caps2 & D3DCAPS2_DYNAMICTEXTURES) != 0;

            return true;
        }


		//////////////////////////////////////////////////////////////////////////////////
		// DXRenderModule::createDevice
		//////////////////////////////////////////////////////////////////////////////////

		bool DXRenderModule::createDevice()
		{
			// we expect that the caller provides us with a valid HWND
			ENSURE_OR_THROW( IsWindow(mhWnd),
                              "DXRenderModule::createDevice() No valid HWND given." );

			// we expect that the caller already created the direct3d9 object.
			ENSURE_OR_THROW( mpDirect3D9.is(),
                              "DXRenderModule::createDevice() no direct3d?." );

			// find the adapter identifier from the window.
			const UINT aAdapter(getAdapterFromWindow());
			if(aAdapter == static_cast<UINT>(-1))
				return false;

            // verify that device possibly works
            if( !verifyDevice(aAdapter) )
                return false;

			// query the display mode from the selected adapter.
			// we'll later request the backbuffer format to be same
			// same as the display format.
			D3DDISPLAYMODE d3ddm;
			mpDirect3D9->GetAdapterDisplayMode(aAdapter,&d3ddm);

			// we need to use D3DSWAPEFFECT_COPY here since the canvas-api has
			// basically nothing to do with efficient resource handling. it tries
			// to avoid drawing whenevery possible, which is simply not the most
			// efficient way we could leverage the hardware in this case. it would
			// be far better to redraw the backbuffer each time we would like to
			// display the content of the backbuffer, but we need to face reality
			// here and follow how the canvas was designed.

			// Strictly speaking, we don't need a full screen worth of
			// backbuffer here. We could also scale dynamically with
			// the current window size, but this will make it
			// necessary to temporarily have two buffers while copying
			// from the old to the new one. What's more, at the time
			// we need a larger buffer, DX might not have sufficient
			// resources available, and we're then left with too small
			// a back buffer, and no way of falling back to a
			// different canvas implementation.
			ZeroMemory( &mad3dpp, sizeof(mad3dpp) );
            mad3dpp.BackBufferWidth = std::max(sal_Int32(maSize.getX()),
                                               sal_Int32(d3ddm.Width));
            mad3dpp.BackBufferHeight = std::max(sal_Int32(maSize.getY()),
                                                sal_Int32(d3ddm.Height));
            mad3dpp.BackBufferCount = 1;
			mad3dpp.Windowed = TRUE;
			mad3dpp.SwapEffect = D3DSWAPEFFECT_COPY;
			mad3dpp.BackBufferFormat = d3ddm.Format;
			mad3dpp.EnableAutoDepthStencil = FALSE;
			mad3dpp.hDeviceWindow = mhWnd;
			mad3dpp.PresentationInterval = D3DPRESENT_INTERVAL_ONE;

			// now create the device, first try hardware vertex processing,
			// then software vertex processing. if both queries fail, we give up
			// and indicate failure.
			IDirect3DDevice9 *pDevice(NULL);
			if(FAILED(mpDirect3D9->CreateDevice(aAdapter,
												D3DDEVTYPE_HAL,
												mhWnd,
												D3DCREATE_HARDWARE_VERTEXPROCESSING|
                                                D3DCREATE_MULTITHREADED|D3DCREATE_FPU_PRESERVE,
												&mad3dpp,
												&pDevice)))
				if(FAILED(mpDirect3D9->CreateDevice(aAdapter,
													D3DDEVTYPE_HAL,
													mhWnd,
													D3DCREATE_SOFTWARE_VERTEXPROCESSING|
                                                    D3DCREATE_MULTITHREADED|D3DCREATE_FPU_PRESERVE,
													&mad3dpp,
													&pDevice)))
					return false;

			// got it, store it in a safe place...
			mpDevice=COMReference<IDirect3DDevice9>(pDevice);

			// After CreateDevice, the first swap chain already exists, so just get it...
			IDirect3DSwapChain9 *pSwapChain(NULL);
			pDevice->GetSwapChain(0,&pSwapChain);
			mpSwapChain=COMReference<IDirect3DSwapChain9>(pSwapChain);
            if( !mpSwapChain.is() )
                return false;

			// clear the render target [which is the backbuffer in this case].
			// we are forced to do this once, and furthermore right now.
			// please note that this is only possible since we created the
			// backbuffer with copy semantics [the content is preserved after
			// calls to Present()], which is an unnecessarily expensive operation.
			LPDIRECT3DSURFACE9 pBackBuffer = NULL;
			mpSwapChain->GetBackBuffer(0,D3DBACKBUFFER_TYPE_MONO,&pBackBuffer);
			mpDevice->SetRenderTarget( 0, pBackBuffer );
			mpDevice->Clear(0,NULL,D3DCLEAR_TARGET,0,1.0f,0L);
			pBackBuffer->Release();

			return true;
		}

		//////////////////////////////////////////////////////////////////////////////////
		// DXRenderModule::createSystemMemorySurface
		//////////////////////////////////////////////////////////////////////////////////

		COMReference<IDirect3DSurface9> DXRenderModule::createSystemMemorySurface( const ::basegfx::B2IVector& rSize )
		{
			if(isDisposed())
				return COMReference<IDirect3DSurface9>(NULL);

			// please note that D3DFMT_X8R8G8B8 is the only format we're
			// able to choose here, since GetDC() doesn't support any
			// other 32bit-format.
			IDirect3DSurface9 *pSurface(NULL);
			if( FAILED(mpDevice->CreateOffscreenPlainSurface(
                           rSize.getX(),
                           rSize.getY(),
                           D3DFMT_X8R8G8B8,
                           D3DPOOL_SYSTEMMEM,
                           &pSurface,
                           NULL)) )
            {
				throw lang::NoSupportException(
					::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM(
                                         "Could not create offscreen surface - out of mem!") ),NULL);
            }

			return COMReference<IDirect3DSurface9>(pSurface);
		}

		//////////////////////////////////////////////////////////////////////////////////
		// DXRenderModule::flip
		//////////////////////////////////////////////////////////////////////////////////

		bool DXRenderModule::flip( const ::basegfx::B2IRectangle& rUpdateArea,
								   const ::basegfx::B2IRectangle& /*rCurrWindowArea*/ )
		{
            // TODO(P2): get rid of those fine-grained locking
			::osl::MutexGuard aGuard( maMutex );

			if(isDisposed() || !mpSwapChain.is())
				return false;

			flushVertexCache();

            // TODO(P2): Might be faster to actually pass update area here 
            RECT aRect = 
                {
                    rUpdateArea.getMinX(),
                    rUpdateArea.getMinY(),
                    rUpdateArea.getMaxX(),
                    rUpdateArea.getMaxY() 
                };
			HRESULT hr(mpSwapChain->Present(&aRect,&aRect,NULL,NULL,0));
			if(FAILED(hr))
			{
				if(hr != D3DERR_DEVICELOST)
					return false;

                // interestingly enough, sometimes the Reset() below
                // *still* causes DeviceLost errors. So, cycle until
                // DX was kind enough to really reset the device...
                do
                {
                    mpVertexBuffer.reset();
                    hr = mpDevice->Reset(&mad3dpp);
                    if(SUCCEEDED(hr))
                    {
                        IDirect3DVertexBuffer9 *pVB(NULL);
                        DWORD aFVF(D3DFVF_XYZRHW|D3DFVF_DIFFUSE|D3DFVF_TEX1);
                        if( FAILED(mpDevice->CreateVertexBuffer(sizeof(dxvertex)*maNumVertices,
                                                                D3DUSAGE_DYNAMIC|D3DUSAGE_WRITEONLY,
                                                                aFVF,
                                                                D3DPOOL_DEFAULT,
                                                                &pVB,
                                                                NULL)) )
                        {
                            throw lang::NoSupportException(
                                ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM(
                                                     "Could not create DirectX device - out of memory!")),NULL);
                        }
                        mpVertexBuffer=COMReference<IDirect3DVertexBuffer9>(pVB);

                        // retry after the restore
                        if(SUCCEEDED(mpSwapChain->Present(&aRect,&aRect,NULL,NULL,0)))
                            return true;
                    }

                    TimeValue aTimeout;
                    aTimeout.Seconds=1;
                    aTimeout.Nanosec=0;
                    osl_waitThread(&aTimeout);
                }
                while(hr == D3DERR_DEVICELOST);

				return false;
			}

			return true;
		}

		//////////////////////////////////////////////////////////////////////////////////
		// DXRenderModule::screenShot
		//////////////////////////////////////////////////////////////////////////////////

		void DXRenderModule::screenShot()
		{
		}

		//////////////////////////////////////////////////////////////////////////////////
		// DXRenderModule::resize
		//////////////////////////////////////////////////////////////////////////////////

		void DXRenderModule::resize( const ::basegfx::B2IRange& rect )
		{
            // TODO(P2): get rid of those fine-grained locking
			::osl::MutexGuard aGuard( maMutex );

			if(isDisposed())
				return;

			// don't do anything if the size didn't change.
			if(maSize.getX() == static_cast<sal_Int32>(rect.getWidth()) &&
			   maSize.getY() == static_cast<sal_Int32>(rect.getHeight()))
			   return;

			// TODO(Q2): use numeric cast to prevent overflow
			maSize.setX(static_cast<sal_Int32>(rect.getWidth()));
			maSize.setY(static_cast<sal_Int32>(rect.getHeight()));

			mpWindow->SetPosSizePixel(0,0,maSize.getX(),maSize.getY());

            // resize back buffer, if necessary
            // -------------------------------------------------------------

			// don't attempt to create anything if the
			// requested size is NULL.
			if(!(maSize.getX()))
				return;
			if(!(maSize.getY()))
				return;

            // backbuffer too small (might happen, if window is
            // maximized across multiple monitors)
            if( sal_Int32(mad3dpp.BackBufferWidth) < maSize.getX() ||
                sal_Int32(mad3dpp.BackBufferHeight) < maSize.getY() )
            {
                mad3dpp.BackBufferWidth = maSize.getX();
                mad3dpp.BackBufferHeight = maSize.getY();

                // clear before, save resources
                mpSwapChain.reset();

                IDirect3DSwapChain9 *pSwapChain(NULL);
                if(FAILED(mpDevice->CreateAdditionalSwapChain(&mad3dpp,&pSwapChain)))
                    return;
                mpSwapChain=COMReference<IDirect3DSwapChain9>(pSwapChain);

                // clear the render target [which is the backbuffer in this case].
                // we are forced to do this once, and furthermore right now.
                // please note that this is only possible since we created the
                // backbuffer with copy semantics [the content is preserved after
                // calls to Present()], which is an unnecessarily expensive operation.
                LPDIRECT3DSURFACE9 pBackBuffer = NULL;
                mpSwapChain->GetBackBuffer(0,D3DBACKBUFFER_TYPE_MONO,&pBackBuffer);
                mpDevice->SetRenderTarget( 0, pBackBuffer );
                mpDevice->Clear(0,NULL,D3DCLEAR_TARGET,0,1.0f,0L);
                pBackBuffer->Release();
            }
		}

		//////////////////////////////////////////////////////////////////////////////////
		// DXRenderModule::getPageSize
		//////////////////////////////////////////////////////////////////////////////////

		::basegfx::B2IVector DXRenderModule::getPageSize()
		{
            // TODO(P2): get rid of those fine-grained locking
			::osl::MutexGuard aGuard( maMutex );
			return maPageSize;
		}

		//////////////////////////////////////////////////////////////////////////////////
		// DXRenderModule::createSurface
		//////////////////////////////////////////////////////////////////////////////////

		::canvas::ISurfaceSharedPtr DXRenderModule::createSurface( const ::basegfx::B2IVector& surfaceSize )
		{
            // TODO(P2): get rid of those fine-grained locking
			::osl::MutexGuard aGuard( maMutex );

			if(isDisposed())
				return ::canvas::ISurfaceSharedPtr();

			const ::basegfx::B2IVector& rPageSize( getPageSize() );
			::basegfx::B2ISize aSize(surfaceSize);
			if(!(aSize.getX()))
				aSize.setX(rPageSize.getX());
			if(!(aSize.getY()))
				aSize.setY(rPageSize.getY());

			if(mpTexture.use_count() == 1)
				return mpTexture;

			return ::canvas::ISurfaceSharedPtr( new DXSurface(*this,aSize) );
		}

		//////////////////////////////////////////////////////////////////////////////////
		// DXRenderModule::beginPrimitive
		//////////////////////////////////////////////////////////////////////////////////

		void DXRenderModule::beginPrimitive( PrimitiveType eType )
		{
            // TODO(P2): get rid of those fine-grained locking
			::osl::MutexGuard aGuard( maMutex );

			if(isDisposed())
				return;

			ENSURE_OR_THROW( !mnBeginSceneCount,
                              "DXRenderModule::beginPrimitive(): nested call" );

			++mnBeginSceneCount;
			meType=eType;
			mnCount=0;
		}

		//////////////////////////////////////////////////////////////////////////////////
		// DXRenderModule::endPrimitive
		//////////////////////////////////////////////////////////////////////////////////

		void DXRenderModule::endPrimitive()
		{
            // TODO(P2): get rid of those fine-grained locking
			::osl::MutexGuard aGuard( maMutex );

			if(isDisposed())
				return;

			--mnBeginSceneCount;
			meType=PRIMITIVE_TYPE_UNKNOWN;
			mnCount=0;
		}

		//////////////////////////////////////////////////////////////////////////////////
		// DXRenderModule::pushVertex
		//////////////////////////////////////////////////////////////////////////////////

		void DXRenderModule::pushVertex( const ::canvas::Vertex& vertex )
		{
            // TODO(P2): get rid of those fine-grained locking
			::osl::MutexGuard aGuard( maMutex );

			if(isDisposed())
				return;

			switch(meType)
			{
				case PRIMITIVE_TYPE_TRIANGLE:
                {
					maVertexCache.push_back(vertex);
					++mnCount;
					mnCount &= 3;
					break;
                }

				case PRIMITIVE_TYPE_QUAD:
                {
					if(mnCount == 3)
					{
						const std::size_t size(maVertexCache.size());
						::canvas::Vertex v0(maVertexCache[size-1]);
						::canvas::Vertex v2(maVertexCache[size-3]);
						maVertexCache.push_back(v0);
						maVertexCache.push_back(vertex);
						maVertexCache.push_back(v2);
						mnCount=0;
					}
					else
					{
						maVertexCache.push_back(vertex);
						++mnCount;
					}
					break;
                }

                default:
                    OSL_ENSURE(false, 
                               "DXRenderModule::pushVertex(): unexpected primitive type");
                    break;
			}
		}

		//////////////////////////////////////////////////////////////////////////////////
		// DXRenderModule::isError
		//////////////////////////////////////////////////////////////////////////////////

		bool DXRenderModule::isError()
		{
            // TODO(P2): get rid of those fine-grained locking
			::osl::MutexGuard aGuard( maMutex );

			return mbError;
		}

		//////////////////////////////////////////////////////////////////////////////////
		// DXRenderModule::getAdapterFromWindow
		//////////////////////////////////////////////////////////////////////////////////

		UINT DXRenderModule::getAdapterFromWindow()
		{
			HMONITOR hMonitor(aMonitorSupport.MonitorFromWindow(mhWnd));
			UINT aAdapterCount(mpDirect3D9->GetAdapterCount());
			for(UINT i=0; i<aAdapterCount; ++i)
				if(hMonitor == mpDirect3D9->GetAdapterMonitor(i))
					return i;
			return static_cast<UINT>(-1);
		}

		//////////////////////////////////////////////////////////////////////////////////
		// DXRenderModule::commitVertexCache
		//////////////////////////////////////////////////////////////////////////////////
		
		void DXRenderModule::commitVertexCache()
		{
			if(maReadIndex != maWriteIndex)
			{
				const std::size_t nVertexStride = sizeof(dxvertex);
				const unsigned int nNumVertices = maWriteIndex-maReadIndex;
				const unsigned int nNumPrimitives = nNumVertices / 3;

				if(FAILED(mpDevice->SetStreamSource(0,mpVertexBuffer.get(),0,nVertexStride)))
					return;

				if(FAILED(mpDevice->SetFVF(D3DFVF_XYZRHW|D3DFVF_DIFFUSE|D3DFVF_TEX1)))
					return;

				if(FAILED(mpDevice->BeginScene()))
					return;

				mbError |= FAILED(mpDevice->DrawPrimitive(D3DPT_TRIANGLELIST,maReadIndex,nNumPrimitives));
				mbError |= FAILED(mpDevice->EndScene());

				maReadIndex += nNumVertices;
			}
		}

		//////////////////////////////////////////////////////////////////////////////////
		// DXRenderModule::flushVertexCache
		//////////////////////////////////////////////////////////////////////////////////

		void DXRenderModule::flushVertexCache()
		{
			if(!(maVertexCache.size()))
				return;

			mbError=true;

			if( FAILED(mpDevice->SetRenderState(D3DRS_LIGHTING,FALSE)))
				return;

			// enable texture alpha blending
			if( FAILED(mpDevice->SetRenderState(D3DRS_ALPHABLENDENABLE,TRUE)))
				return;

			mpDevice->SetSamplerState(0,D3DSAMP_MAGFILTER,D3DTEXF_LINEAR);
			mpDevice->SetSamplerState(0,D3DSAMP_MINFILTER,D3DTEXF_LINEAR);
			mpDevice->SetSamplerState(0,D3DSAMP_ADDRESSU ,D3DTADDRESS_CLAMP );
			mpDevice->SetSamplerState(0,D3DSAMP_ADDRESSV ,D3DTADDRESS_CLAMP );

			// configure the fixed-function pipeline.
			// the only 'feature' we need here is to modulate the alpha-channels
			// from the texture and the interpolated diffuse color. the result
			// will then be blended with the backbuffer.
			// fragment color = texture color * diffuse.alpha.
			mpDevice->SetTextureStageState(0,D3DTSS_ALPHAOP,D3DTOP_MODULATE);
			mpDevice->SetTextureStageState(0,D3DTSS_ALPHAARG1,D3DTA_TEXTURE);
			mpDevice->SetTextureStageState(0,D3DTSS_ALPHAARG2,D3DTA_DIFFUSE);

			// normal combination of object...
			if( FAILED(mpDevice->SetRenderState(D3DRS_SRCBLEND,D3DBLEND_SRCALPHA)) )
				return;

			// ..and background color
			if( FAILED(mpDevice->SetRenderState(D3DRS_DESTBLEND,D3DBLEND_INVSRCALPHA)) )
				return;

			// disable backface culling; this enables us to mirror sprites
			// by simply reverting the triangles, which, with enabled
			// culling, would be invisible otherwise
			if( FAILED(mpDevice->SetRenderState(D3DRS_CULLMODE,D3DCULL_NONE)) )
				return;

			mbError=false;

			std::size_t nSize(maVertexCache.size());
			const std::size_t nVertexStride = sizeof(dxvertex);

			const ::basegfx::B2IVector aPageSize(getPageSize());
			const float nHalfPixelSizeX(0.5f/aPageSize.getX());
			const float nHalfPixelSizeY(0.5f/aPageSize.getY());
            vertexCache_t::const_iterator it(maVertexCache.begin());

            while( nSize )
            {
                DWORD dwLockFlags(D3DLOCK_NOOVERWRITE);

                // Check to see if there's space for the current set of
                // vertices in the buffer.
                if( maNumVertices - maWriteIndex < nSize )
                {
                    commitVertexCache();
                    dwLockFlags = D3DLOCK_DISCARD;
                    maWriteIndex = 0;
                    maReadIndex = 0;
                }

                dxvertex *vertices(NULL);
                const std::size_t nNumVertices(
                    std::min(maNumVertices - maWriteIndex,
                             nSize));
                if(FAILED(mpVertexBuffer->Lock(maWriteIndex*nVertexStride,
                                               nNumVertices*nVertexStride,
                                               (void **)&vertices,
                                               dwLockFlags)))
                    return;

                std::size_t nIndex(0);
                while( nIndex < nNumVertices )
                {
                    dxvertex &dest = vertices[nIndex++];
                    dest.x=it->x;
                    dest.y=it->y;
                    dest.z=it->z;
                    dest.rhw=1;
                    const sal_uInt32 alpha(static_cast<sal_uInt32>(it->a*255.0f));
                    dest.diffuse=D3DCOLOR_ARGB(alpha,255,255,255);
                    dest.u=static_cast<float>(it->u + nHalfPixelSizeX);
                    dest.v=static_cast<float>(it->v + nHalfPixelSizeY);
                    ++it;
                }

                mpVertexBuffer->Unlock();

                // Advance to the next position in the vertex buffer.
                maWriteIndex += nNumVertices;
                nSize -= nNumVertices;

                commitVertexCache();
            }

            maVertexCache.clear();
		}
	}

	//////////////////////////////////////////////////////////////////////////////////
	// createRenderModule
	//////////////////////////////////////////////////////////////////////////////////

	IDXRenderModuleSharedPtr createRenderModule( const ::Window& rParent )
	{
		return IDXRenderModuleSharedPtr( new DXRenderModule(rParent) );
	}
}

#endif
