diff options
Diffstat (limited to 'canvas/source/directx/dx_9rm.cxx')
-rwxr-xr-x | canvas/source/directx/dx_9rm.cxx | 1363 |
1 files changed, 1363 insertions, 0 deletions
diff --git a/canvas/source/directx/dx_9rm.cxx b/canvas/source/directx/dx_9rm.cxx new file mode 100755 index 000000000000..a0f485befa12 --- /dev/null +++ b/canvas/source/directx/dx_9rm.cxx @@ -0,0 +1,1363 @@ +/************************************************************************* + * + * 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( FALSE ); + + mpWindow->SetControlForeground(); + mpWindow->SetControlBackground(); + mpWindow->EnablePaint(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 |