/************************************************************************* * * 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 * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dx_rendermodule.hxx" #include "dx_config.hxx" #undef WB_LEFT #undef WB_RIGHT #include "dx_impltools.hxx" #include #if defined(DX_DEBUG_IMAGES) # if OSL_DEBUG_LEVEL > 0 # include # 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( 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 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 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 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 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 mpDevice; COMReference mpDirect3D9; COMReference mpSwapChain; COMReference mpVertexBuffer; ::canvas::ISurfaceSharedPtr mpTexture; ::boost::scoped_ptr mpWindow; ::basegfx::B2IVector maSize; typedef std::vector 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= 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 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(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 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( rSourceRect.getWidth())* nSourceBytesPerPixel); const sal_uInt64 nNumLines(rSourceRect.getHeight()); for(sal_uInt32 i=0; i(rSourceRect.getWidth())); const sal_Int32 nNumLines( sal::static_int_cast(rSourceRect.getHeight())); for(sal_Int32 i=0; i(pDst); sal_uInt8 *pSrcScanline = reinterpret_cast(pImage); for(sal_Int32 x=0; x(rSourceRect.getHeight())); const sal_Int32 nNumColumns( sal::static_int_cast(rSourceRect.getWidth())); for(sal_Int32 i=0; i(pImage); sal_uInt32 *pDst32 = reinterpret_cast(pDst); for(sal_Int32 j=0; jUnlockRect(0)); } return true; } ////////////////////////////////////////////////////////////////////////////////// // DXSurface::getSize ////////////////////////////////////////////////////////////////////////////////// ::basegfx::B2IVector DXSurface::getSize() { return maSize; } COMReference 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(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(&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(pData->hWnd)); mhWnd = const_cast(hwnd); ENSURE_OR_THROW( IsWindow( reinterpret_cast(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(rSizePixel.Width())); maSize.setY(static_cast(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( 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(-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(pDevice); // After CreateDevice, the first swap chain already exists, so just get it... IDirect3DSwapChain9 *pSwapChain(NULL); pDevice->GetSwapChain(0,&pSwapChain); mpSwapChain=COMReference(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 DXRenderModule::createSystemMemorySurface( const ::basegfx::B2IVector& rSize ) { if(isDisposed()) return COMReference(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(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(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(rect.getWidth()) && maSize.getY() == static_cast(rect.getHeight())) return; // TODO(Q2): use numeric cast to prevent overflow maSize.setX(static_cast(rect.getWidth())); maSize.setY(static_cast(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(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; iGetAdapterMonitor(i)) return i; return static_cast(-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(it->a*255.0f)); dest.diffuse=D3DCOLOR_ARGB(alpha,255,255,255); dest.u=static_cast(it->u + nHalfPixelSizeX); dest.v=static_cast(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