blob: 16d4b7056eca9e9342e36ebbbb9313dd22cf953a [file] [log] [blame]
/**************************************************************
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the 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