blob: 45bbd963d93919aee3256a27e896dbe87e187fc9 [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
// Nvidia GeForce Go 6800 crashes with a bluescreen if we take the
// maximum texture size, which would be twice as large. this behaviors
// has only been observed on directx5.
// This value is simply the maximum size for textures we request from
// the system, it has absolutely nothing to do with the size of primitives
// we're able to render, both concepts are totally independent from each other.
#define MAX_TEXTURE_SIZE (2048)
#define MIN_TEXTURE_SIZE (32)
//#define FAKE_MAX_NUMBER_TEXTURES (2)
//#define FAKE_MAX_TEXTURE_SIZE (512)
//////////////////////////////////////////////////////////////////////////////////
// includes
//////////////////////////////////////////////////////////////////////////////////
#include <vcl/syschild.hxx>
#include <vcl/window.hxx>
#include <canvas/debug.hxx>
#include <canvas/verbosetrace.hxx>
#include <canvas/elapsedtime.hxx>
#include <canvas/canvastools.hxx>
#include <canvas/rendering/icolorbuffer.hxx>
#include <canvas/rendering/isurface.hxx>
#include <canvas/rendering/irendermodule.hxx>
#include <tools/diagnose_ex.h>
#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>
#define COMPILE_MULTIMON_STUBS
#include "dx_rendermodule.hxx"
#include "dx_surfacegraphics.hxx"
#include <vcl/sysdata.hxx>
#undef WB_LEFT
#undef WB_RIGHT
#include "dx_impltools.hxx"
#include <malloc.h>
#if defined(DX_DEBUG_IMAGES)
# if OSL_DEBUG_LEVEL > 0
# include <imdebug.h>
# undef min
# undef max
# endif
#endif
#undef COMPILE_MULTIMON_STUBS
#include <stdio.h>
#define MONITOR_DEFAULTTONULL 0x00000000
#define MONITOR_DEFAULTTOPRIMARY 0x00000001
#define MONITOR_DEFAULTTONEAREST 0x00000002
using namespace ::com::sun::star;
//////////////////////////////////////////////////////////////////////////////////
// 'dxcanvas' namespace
//////////////////////////////////////////////////////////////////////////////////
namespace dxcanvas
{
namespace
{
bool doBlit( const ::basegfx::B2IPoint& rDestPos,
IDirectDrawSurface& rOutSurface,
const ::basegfx::B2IRange& rSourceArea,
IDirectDrawSurface& rSourceSurface,
DDBLTFX* pBltFx,
bool bForceSoftware )
{
if( !bForceSoftware )
{
// blit surface to backbuffer
RECT aOutRect =
{
rDestPos.getX(),
rDestPos.getY(),
rDestPos.getX() + static_cast<sal_Int32>(rSourceArea.getWidth()),
rDestPos.getY() + static_cast<sal_Int32>(rSourceArea.getHeight()),
};
RECT aSourceRect =
{
rSourceArea.getMinX(),
rSourceArea.getMinY(),
rSourceArea.getMaxX(),
rSourceArea.getMaxY()
};
if( SUCCEEDED(rOutSurface.Blt( &aOutRect,
&rSourceSurface,
&aSourceRect,
DDBLT_WAIT,
pBltFx )) )
{
return true;
}
}
// failed, or forced to use SW copy. attempt manual copy.
bool bResult = false;
// lock source surface
DDSURFACEDESC aDescSrc;
rtl_fillMemory(&aDescSrc,sizeof(DDSURFACEDESC),0);
aDescSrc.dwSize = sizeof(DDSURFACEDESC);
const DWORD dwSrcFlags = DDLOCK_NOSYSLOCK|
DDLOCK_SURFACEMEMORYPTR|
DDLOCK_WAIT|
DDLOCK_READONLY;
if(SUCCEEDED(rSourceSurface.Lock(NULL,
&aDescSrc,
dwSrcFlags,
NULL)))
{
// lock destination surface
DDSURFACEDESC aDescDst;
rtl_fillMemory(&aDescDst,sizeof(DDSURFACEDESC),0);
aDescDst.dwSize = sizeof(DDSURFACEDESC);
const DWORD dwDstFlags = DDLOCK_NOSYSLOCK|
DDLOCK_SURFACEMEMORYPTR|
DDLOCK_WAIT|
DDLOCK_WRITEONLY;
if(SUCCEEDED(rOutSurface.Lock(NULL,
&aDescDst,
dwDstFlags,
NULL)))
{
sal_uInt32 nSrcFormat;
nSrcFormat = ::canvas::tools::bitcount32(aDescSrc.ddpfPixelFormat.dwRGBAlphaBitMask)<<12;
nSrcFormat |= ::canvas::tools::bitcount32(aDescSrc.ddpfPixelFormat.dwRBitMask)<<8;
nSrcFormat |= ::canvas::tools::bitcount32(aDescSrc.ddpfPixelFormat.dwGBitMask)<<4;
nSrcFormat |= ::canvas::tools::bitcount32(aDescSrc.ddpfPixelFormat.dwBBitMask);
sal_uInt32 nDstFormat;
nDstFormat = ::canvas::tools::bitcount32(aDescDst.ddpfPixelFormat.dwRGBAlphaBitMask)<<12;
nDstFormat |= ::canvas::tools::bitcount32(aDescDst.ddpfPixelFormat.dwRBitMask)<<8;
nDstFormat |= ::canvas::tools::bitcount32(aDescDst.ddpfPixelFormat.dwGBitMask)<<4;
nDstFormat |= ::canvas::tools::bitcount32(aDescDst.ddpfPixelFormat.dwBBitMask);
// TODO(E1): Use numeric_cast to catch overflow here
const sal_uInt32 nWidth( static_cast<sal_uInt32>(
rSourceArea.getWidth() ) );
const sal_uInt32 nHeight( static_cast<sal_uInt32>(
rSourceArea.getHeight() ) );
if((nSrcFormat == 0x8888) && (nDstFormat == 0x0565))
{
// medium range 8888 to 0565 pixel format conversion.
bResult = true;
sal_uInt8 *pSrcSurface = (sal_uInt8 *)aDescSrc.lpSurface +
rSourceArea.getMinY()*aDescSrc.lPitch +
(rSourceArea.getMinX()<<2);
sal_uInt8 *pDstSurface = (sal_uInt8 *)aDescDst.lpSurface +
rDestPos.getY()*aDescDst.lPitch +
(rDestPos.getX()<<1);
for(sal_uInt32 y=0; y<nHeight; ++y)
{
sal_uInt32 *pSrcScanline = (sal_uInt32 *)pSrcSurface;
sal_uInt16 *pDstScanline = (sal_uInt16 *)pDstSurface;
for(sal_uInt32 x=0; x<nWidth; ++x)
{
sal_uInt32 srcPixel = *pSrcScanline++;
sal_uInt16 dstPixel;
dstPixel = (sal_uInt16)((srcPixel & 0x0000F8) >> 3);
dstPixel |= (srcPixel & 0x00FC00) >> 5;
dstPixel |= (srcPixel & 0xF80000) >> 8;
*pDstScanline++ = dstPixel;
}
pSrcSurface += aDescSrc.lPitch;
pDstSurface += aDescDst.lPitch;
}
}
else if((nSrcFormat == 0x8888) && (nDstFormat == 0x0888))
{
// medium range 8888 to 0888 pixel format conversion.
bResult = true;
sal_uInt8 *pSrcSurface = (sal_uInt8 *)aDescSrc.lpSurface +
rSourceArea.getMinY()*aDescSrc.lPitch +
(rSourceArea.getMinX()<<2);
sal_uInt8 *pDstSurface = (sal_uInt8 *)aDescDst.lpSurface +
rDestPos.getY()*aDescDst.lPitch +
(rDestPos.getX()<<2);
for(sal_uInt32 y=0; y<nHeight; ++y)
{
sal_uInt32 *pSrcScanline = (sal_uInt32 *)pSrcSurface;
sal_uInt16 *pDstScanline = (sal_uInt16 *)pDstSurface;
for(sal_uInt32 x=0; x<nWidth; ++x)
{
*pDstScanline++ = (sal_uInt16)*pSrcScanline++;
}
pSrcSurface += aDescSrc.lPitch;
pDstSurface += aDescDst.lPitch;
}
}
else if((nSrcFormat == 0x8888) && (nDstFormat == 0x1555))
{
// medium range 8888 to 1555 pixel format conversion.
bResult = true;
sal_uInt8 *pSrcSurface = (sal_uInt8 *)aDescSrc.lpSurface +
rSourceArea.getMinY()*aDescSrc.lPitch +
(rSourceArea.getMinX()<<2);
sal_uInt8 *pDstSurface = (sal_uInt8 *)aDescDst.lpSurface +
rDestPos.getY()*aDescDst.lPitch +
(rDestPos.getX()<<1);
for(sal_uInt32 y=0; y<nHeight; ++y)
{
sal_uInt32 *pSrcScanline = (sal_uInt32*)pSrcSurface;
sal_uInt16 *pDstScanline = (sal_uInt16 *)pDstSurface;
for(sal_uInt32 x=0; x<nWidth; ++x)
{
sal_uInt32 srcPixel = *pSrcScanline++;
sal_uInt16 dstPixel;
dstPixel = (sal_uInt16)((srcPixel & 0x000000F8) >> 3);
dstPixel |= (srcPixel & 0x0000F800) >> 6;
dstPixel |= (srcPixel & 0x00F80000) >> 9;
dstPixel |= (srcPixel & 0x80000000) >> 16;
*pDstScanline++ = dstPixel;
}
pSrcSurface += aDescSrc.lPitch;
pDstSurface += aDescDst.lPitch;
}
}
// unlock destination surface
rOutSurface.Unlock(NULL);
}
// unlock source surface
rSourceSurface.Unlock(NULL);
}
return bResult;
}
void dumpSurface( const COMReference<IDirectDrawSurface> &pSurface, const char *szFilename )
{
if(!(pSurface.get()))
return;
DDSURFACEDESC aSurfaceDesc;
rtl_fillMemory( &aSurfaceDesc,sizeof(DDSURFACEDESC),0 );
aSurfaceDesc.dwSize = sizeof(DDSURFACEDESC);
if( FAILED(pSurface->Lock( NULL,
&aSurfaceDesc,
DDLOCK_NOSYSLOCK|DDLOCK_SURFACEMEMORYPTR|DDLOCK_WAIT|DDLOCK_READONLY,
NULL)) )
return;
const std::size_t dwBitmapSize(aSurfaceDesc.dwWidth*aSurfaceDesc.dwHeight*4);
sal_uInt8 *pBuffer = static_cast<sal_uInt8 *>(_alloca(dwBitmapSize));
if(pBuffer)
{
sal_uInt8 *pSource = reinterpret_cast<sal_uInt8 *>(aSurfaceDesc.lpSurface);
sal_uInt8 *pDest = reinterpret_cast<sal_uInt8 *>(pBuffer);
const std::size_t dwDestPitch(aSurfaceDesc.dwWidth<<2);
pDest += aSurfaceDesc.dwHeight*dwDestPitch;
for(sal_uInt32 y=0; y<aSurfaceDesc.dwHeight; ++y)
{
pDest -= dwDestPitch;
rtl_copyMemory( pDest, pSource, dwDestPitch );
pSource += aSurfaceDesc.lPitch;
}
if(FILE *fp = fopen(szFilename,"wb"))
{
BITMAPINFOHEADER bitmapInfo;
bitmapInfo.biSize = sizeof(BITMAPINFOHEADER);
bitmapInfo.biWidth = aSurfaceDesc.dwWidth;
bitmapInfo.biHeight = aSurfaceDesc.dwHeight;
bitmapInfo.biPlanes = 1;
bitmapInfo.biBitCount = 32;
bitmapInfo.biCompression = BI_RGB;
bitmapInfo.biSizeImage = 0;
bitmapInfo.biXPelsPerMeter = 0;
bitmapInfo.biYPelsPerMeter = 0;
bitmapInfo.biClrUsed = 0;
bitmapInfo.biClrImportant = 0;
const std::size_t dwFileSize(sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER)+dwBitmapSize);
BITMAPFILEHEADER header;
header.bfType = 'MB';
header.bfSize = dwFileSize;
header.bfReserved1 = 0;
header.bfReserved2 = 0;
header.bfOffBits = sizeof(BITMAPFILEHEADER) + bitmapInfo.biSize;
fwrite(&header,1,sizeof(BITMAPFILEHEADER),fp);
fwrite(&bitmapInfo,1,sizeof(BITMAPINFOHEADER),fp);
fwrite(pBuffer,1,dwBitmapSize,fp);
fclose(fp);
}
}
pSurface->Unlock(NULL);
}
void clearSurface( const COMReference<IDirectDrawSurface>& pSurface )
{
if(!(pSurface.is()))
return;
DDBLTFX aBltFx;
rtl_fillMemory( &aBltFx,
sizeof(DDBLTFX), 0 );
aBltFx.dwSize = sizeof(DDBLTFX);
aBltFx.dwFillColor = 0;
pSurface->Blt( NULL,
NULL,
NULL,
DDBLT_COLORFILL | DDBLT_WAIT,
&aBltFx );
}
// Define struct for MonitorEntry
struct MonitorEntry
{
GUID mnGUID;
HMONITOR mhMonitor;
MONITORINFO maMonitorInfo;
};
// define type for MonitorList
typedef ::std::vector< MonitorEntry > MonitorList;
// Win32 system callback for DirectDrawEnumerateExA call
BOOL WINAPI EnumerateExA_Callback( GUID FAR* lpGUID,
LPSTR /*lpDriverDescription*/,
LPSTR /*lpDriverName*/,
LPVOID lpContext,
HMONITOR hMonitor )
{
if(lpGUID)
{
MonitorList* pMonitorList = (MonitorList*)lpContext;
MonitorEntry aEntry;
aEntry.mnGUID = *lpGUID;
aEntry.mhMonitor = hMonitor;
aEntry.maMonitorInfo.cbSize = sizeof(MONITORINFO);
GetMonitorInfo( hMonitor,
&aEntry.maMonitorInfo );
pMonitorList->push_back(aEntry);
}
return DDENUMRET_OK;
}
void fillMonitorList( MonitorList& rMonitorList )
{
// Try to fill MonitorList. If neither lib or call to
// DirectDrawEnumerateExA does not exist, it's an old
// DX version (< 5.0), or system does not support
// multiple monitors.
HINSTANCE hInstance = LoadLibrary("ddraw.dll");
if(hInstance)
{
LPDIRECTDRAWENUMERATEEX lpDDEnumEx =
(LPDIRECTDRAWENUMERATEEX)GetProcAddress(hInstance,"DirectDrawEnumerateExA");
if(lpDDEnumEx)
lpDDEnumEx( (LPDDENUMCALLBACKEXA) EnumerateExA_Callback,
&rMonitorList,
DDENUM_ATTACHEDSECONDARYDEVICES );
FreeLibrary(hInstance);
}
}
IDirectDraw2* createDirectDraw( const MonitorList& rMonitorList,
MONITORINFO& rMonitorInfo,
HWND renderWindow )
{
GUID* gpSelectedDriverGUID = NULL;
// if we have multiple monitors, choose a gpSelectedDriverGUID from monitor list
HMONITOR hMonitor = MonitorFromWindow(renderWindow,
MONITOR_DEFAULTTONEAREST);
MonitorList::const_iterator aCurr = rMonitorList.begin();
const MonitorList::const_iterator aEnd = rMonitorList.end();
while( !gpSelectedDriverGUID && aCurr != aEnd )
{
if(hMonitor == aCurr->mhMonitor)
{
// This is the monitor we are running on
gpSelectedDriverGUID = const_cast<GUID*>(&aCurr->mnGUID);
rMonitorInfo = aCurr->maMonitorInfo;
}
++aCurr;
}
IDirectDraw* pDirectDraw;
if( FAILED( DirectDrawCreate( gpSelectedDriverGUID, &pDirectDraw, NULL )))
return NULL;
IDirectDraw2* pDirectDraw2;
if( FAILED( pDirectDraw->QueryInterface( IID_IDirectDraw2, (LPVOID*)&pDirectDraw2 )))
return NULL;
// queryInterface bumped up the refcount, so release the
// reference to the original IDirectDraw interface.
pDirectDraw->Release();
return pDirectDraw2;
}
HRESULT WINAPI EnumTextureFormatsCallback( LPDDSURFACEDESC pSurfaceDesc,
LPVOID pContext )
{
// dirty cast of given context back to result ModeSelectContext
DDPIXELFORMAT* pResult = (DDPIXELFORMAT*)pContext;
if( pResult == NULL || pSurfaceDesc == NULL )
return DDENUMRET_CANCEL;
VERBOSE_TRACE( "EnumTextureFormatsCallback: advertised texture format has dwRGBBitCount %d, dwRBitMask %x, "
"dwGBitMask %x, dwBBitMask %x and dwRGBAlphaBitMask %x. The format uses %s alpha.",
pSurfaceDesc->ddpfPixelFormat.dwRGBBitCount,
pSurfaceDesc->ddpfPixelFormat.dwRBitMask,
pSurfaceDesc->ddpfPixelFormat.dwGBitMask,
pSurfaceDesc->ddpfPixelFormat.dwBBitMask,
pSurfaceDesc->ddpfPixelFormat.dwRGBAlphaBitMask,
pSurfaceDesc->ddpfPixelFormat.dwFlags & DDPF_ALPHAPREMULT ? "premultiplied" : "non-premultiplied" );
// Only accept RGB surfaces with alpha channel
if( (DDPF_ALPHAPIXELS | DDPF_RGB) ==
(pSurfaceDesc->ddpfPixelFormat.dwFlags & (DDPF_ALPHAPIXELS | DDPF_RGB)) )
{
// ignore formats with the DDPF_ALPHAPREMULT flag
if(!(pSurfaceDesc->ddpfPixelFormat.dwFlags & DDPF_ALPHAPREMULT))
{
// take widest alpha channel available
if( pSurfaceDesc->ddpfPixelFormat.dwAlphaBitDepth > pResult->dwAlphaBitDepth )
{
// take new format
rtl_copyMemory( pResult, &pSurfaceDesc->ddpfPixelFormat, sizeof(DDPIXELFORMAT) );
}
else if( pSurfaceDesc->ddpfPixelFormat.dwAlphaBitDepth == pResult->dwAlphaBitDepth )
{
// tie-breaking: take highest bitcount
if( pSurfaceDesc->ddpfPixelFormat.dwRGBBitCount > pResult->dwRGBBitCount )
{
// take new format
rtl_copyMemory( pResult, &pSurfaceDesc->ddpfPixelFormat, sizeof(DDPIXELFORMAT) );
}
}
}
}
return DDENUMRET_OK;
}
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();
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<IDirectDrawSurface> mpSurface;
COMReference<IDirect3DTexture2> mpTexture;
::basegfx::B2IVector maSize;
};
//////////////////////////////////////////////////////////////////////////////////
// DXRenderModule
//////////////////////////////////////////////////////////////////////////////////
/// Default implementation of IDXRenderModule
class DXRenderModule : public IDXRenderModule
{
public:
explicit DXRenderModule( const ::Window& rWindow );
virtual void lock() const { maMutex.acquire(); }
virtual void unlock() const { maMutex.release(); }
virtual COMReference<IDirectDrawSurface>
createSystemMemorySurface( const ::basegfx::B2IVector& rSize );
virtual bool flip( const ::basegfx::B2IRectangle& rUpdateArea,
const ::basegfx::B2IRectangle& rCurrWindowArea );
virtual void resize( const ::basegfx::B2IRange& rect );
virtual HWND getHWND() const { return mhWnd; }
virtual void disposing();
virtual void screenShot();
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();
const D3DDEVICEDESC& getDeviceDesc() const { return maDeviceDesc; }
const DDPIXELFORMAT& getTextureFormat() const { return maTextureFormat; }
COMReference<IDirectDraw2> getDirectDraw() { return mpDirectDraw; }
COMReference< IDirect3DDevice2 > getDevice() { return mpDirect3DDevice; }
void flushVertexCache();
struct ModeSelectContext
{
DDSURFACEDESC selectedDesc;
::basegfx::B2ISize requestedSize;
};
/** Query actual size of the device
This is especially interesting for fullscreen devices
*/
::basegfx::B2ISize getFramebufferSize() const;
/** Query the amount of memory available for new surfaces
This might differ from getAvailableTextureMem()
@see getAvailableTextureMem()
@param bWithAGPMema
When true, returned value includes non-local,
i.e. AGP-able memory, too.
@return the amount of free surface mem
*/
std::size_t getAvailableSurfaceMem( bool bWithAGPMem=true ) const;
/** Query the amount of memory available for new textures
This might differ from getAvailableSurfaceMem()
@see getAvailableSurfaceMem()
@param bWithAGPMema
When true, returned value includes non-local,
i.e. AGP-able memory, too.
@return the amount of free texture mem
*/
std::size_t getAvailableTextureMem( bool bWithAGPMem=true ) const;
private:
bool queryCaps();
bool validateCaps();
bool setup3DDevice();
unsigned int getDisplayFormat() const;
void convert2Screen( ::basegfx::B2IPoint& io_rDestPos,
::basegfx::B2IRange& io_rDestArea );
void renderInfoText( const ::rtl::OUString& rStr,
const Gdiplus::PointF& rPos ) const;
void renderFPSCounter() const;
void renderMemAvailable() const;
bool create( const ::Window& rWindow );
bool validateMainSurfaces();
/** 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;
::boost::scoped_ptr<SystemChildWindow> mpWindow;
::basegfx::B2IVector maSize;
ModeSelectContext maSelectedFullscreenMode;
DDPIXELFORMAT maTextureFormat;
MONITORINFO maMonitorInfo; // monitor info for mpDirectDraw's monitor
COMReference<IDirectDraw2> mpDirectDraw;
COMReference<IDirectDrawSurface> mpPrimarySurface;
COMReference<IDirectDrawSurface> mpBackBufferSurface;
COMReference< IDirect3D2 > mpDirect3D;
COMReference< IDirect3DDevice2 > mpDirect3DDevice;
mutable ::canvas::tools::ElapsedTime maLastUpdate; // for the frame counter
D3DDEVICEDESC maDeviceDesc;
typedef std::vector<canvas::Vertex> vertexCache_t;
vertexCache_t maVertexCache;
std::size_t mnCount;
int mnBeginSceneCount;
const bool mbPageFlipping;
bool mbHasNoTearingBlt;
bool mbError;
PrimitiveType meType;
::canvas::ISurfaceSharedPtr mpTexture;
::basegfx::B2IVector maPageSize;
};
::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),
mpSurface(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");
const D3DDEVICEDESC &deviceDesc = rRenderModule.getDeviceDesc();
DDSURFACEDESC aSurfaceDesc;
rtl_fillMemory( &aSurfaceDesc,sizeof(DDSURFACEDESC),0 );
aSurfaceDesc.dwSize = sizeof(DDSURFACEDESC);
aSurfaceDesc.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT;
aSurfaceDesc.dwWidth = ::std::min(deviceDesc.dwMaxTextureWidth,::canvas::tools::nextPow2(rSize.getX()));
aSurfaceDesc.dwHeight = ::std::min(deviceDesc.dwMaxTextureHeight,::canvas::tools::nextPow2(rSize.getY()));
aSurfaceDesc.ddsCaps.dwCaps = DDSCAPS_TEXTURE |
DDSCAPS_VIDEOMEMORY |
DDSCAPS_LOCALVIDMEM;
rtl_copyMemory(&aSurfaceDesc.ddpfPixelFormat,&rRenderModule.getTextureFormat(),sizeof(DDPIXELFORMAT));
IDirectDrawSurface *pSurface;
COMReference<IDirectDraw2> pDirectDraw(rRenderModule.getDirectDraw());
HRESULT hr = pDirectDraw->CreateSurface(&aSurfaceDesc, &pSurface, NULL);
if(FAILED(hr))
{
// if the call failed due to 'out of videomemory',
// retry with request for AGP memory.
if(DDERR_OUTOFVIDEOMEMORY == hr)
{
aSurfaceDesc.ddsCaps.dwCaps = DDSCAPS_TEXTURE |
DDSCAPS_VIDEOMEMORY |
DDSCAPS_NONLOCALVIDMEM;
hr = pDirectDraw->CreateSurface(&aSurfaceDesc, &pSurface, NULL);
}
}
if(SUCCEEDED(hr))
{
IDirect3DTexture2* pTexture;
if( FAILED(pSurface->QueryInterface(IID_IDirect3DTexture2, (LPVOID *)&pTexture)) )
{
pSurface->Release();
return;
}
maSize.setX(aSurfaceDesc.dwWidth);
maSize.setY(aSurfaceDesc.dwHeight);
mpSurface=COMReference<IDirectDrawSurface>(pSurface);
mpTexture=COMReference<IDirect3DTexture2>(pTexture);
// #122683# Clear texture, to avoid ugly artifacts at the
// border to invisible sprite areas (note that the textures
// are usually only partly utilized).
clearSurface( mpSurface );
}
}
//////////////////////////////////////////////////////////////////////////////////
// DXSurface::~DXSurface
//////////////////////////////////////////////////////////////////////////////////
DXSurface::~DXSurface()
{
ImplRenderModuleGuard aGuard( mrRenderModule );
#ifdef FAKE_MAX_NUMBER_TEXTURES
gNumSurfaces--;
#endif
}
//////////////////////////////////////////////////////////////////////////////////
// DXSurface::selectTexture
//////////////////////////////////////////////////////////////////////////////////
bool DXSurface::selectTexture()
{
ImplRenderModuleGuard aGuard( mrRenderModule );
mrRenderModule.flushVertexCache();
D3DTEXTUREHANDLE aTextureHandle;
if(FAILED(mpTexture->GetHandle(
mrRenderModule.getDevice().get(),
&aTextureHandle)))
{
return false;
}
// select texture for next primitive
if(FAILED(mrRenderModule.getDevice()->SetRenderState(
D3DRENDERSTATE_TEXTUREHANDLE,aTextureHandle)))
{
return false;
}
#if defined(DX_DEBUG_IMAGES)
# if OSL_DEBUG_LEVEL > 0
if( mpSurface.is() )
{
DDSURFACEDESC aSurfaceDesc;
rtl_fillMemory( &aSurfaceDesc,sizeof(DDSURFACEDESC),0 );
aSurfaceDesc.dwSize = sizeof(DDSURFACEDESC);
if( SUCCEEDED(mpSurface->Lock( NULL,
&aSurfaceDesc,
DDLOCK_NOSYSLOCK|DDLOCK_SURFACEMEMORYPTR|DDLOCK_WAIT|DDLOCK_READONLY,
NULL)) )
{
imdebug( "rgba w=%d h=%d %p",
aSurfaceDesc.dwWidth,
aSurfaceDesc.dwHeight,
aSurfaceDesc.lpSurface );
mpSurface->Unlock(NULL);
}
}
# endif
#endif
return true;
}
//////////////////////////////////////////////////////////////////////////////////
// DXSurface::isValid
//////////////////////////////////////////////////////////////////////////////////
bool DXSurface::isValid()
{
ImplRenderModuleGuard aGuard( mrRenderModule );
if(!(mpSurface.is()))
return false;
if(mpSurface->IsLost() == DDERR_SURFACELOST)
{
mpSurface->Restore();
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;
DDSURFACEDESC aSurfaceDesc;
rtl_fillMemory( &aSurfaceDesc,sizeof(DDSURFACEDESC),0 );
aSurfaceDesc.dwSize = sizeof(DDSURFACEDESC);
// TODO(P2): only lock the region we want to update
if( FAILED(mpSurface->Lock( NULL,
&aSurfaceDesc,
DDLOCK_NOSYSLOCK|DDLOCK_SURFACEMEMORYPTR|DDLOCK_WAIT|DDLOCK_WRITEONLY,
NULL)) )
return false;
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*)aSurfaceDesc.lpSurface+
(rDestPos.getY()*aSurfaceDesc.lPitch) +
(4*rDestPos.getX()));
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);
pDst += aSurfaceDesc.lPitch;
pImage += nSourcePitchInBytes;
}
}
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*)aSurfaceDesc.lpSurface+
(rDestPos.getY()*aSurfaceDesc.lPitch) +
(4*rDestPos.getX()));
const sal_uInt64 nNumColumns(rSourceRect.getWidth());
const sal_uInt64 nNumLines(rSourceRect.getHeight());
for(sal_uInt32 i=0; i<nNumLines; ++i)
{
sal_uInt32 *pDstScanline = reinterpret_cast<sal_uInt32 *>(pDst);
sal_uInt8 *pSrcScanline = reinterpret_cast<sal_uInt8 *>(pImage);
for(sal_uInt32 x=0; x<nNumColumns; ++x)
{
sal_uInt32 color(0xFF000000);
color |= pSrcScanline[2]<<16;
color |= pSrcScanline[1]<<8;
color |= pSrcScanline[0];
pSrcScanline += 3;
*pDstScanline++ = color;
}
pDst += aSurfaceDesc.lPitch;
pImage += nSourcePitchInBytes;
}
}
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*)aSurfaceDesc.lpSurface+
(rDestPos.getY()*aSurfaceDesc.lPitch) +
(4*rDestPos.getX()));
const sal_uInt64 nNumLines(rSourceRect.getHeight());
for(sal_uInt32 i=0; i<nNumLines; ++i)
{
sal_uInt32 *pSrc32 = reinterpret_cast<sal_uInt32 *>(pImage);
sal_uInt32 *pDst32 = reinterpret_cast<sal_uInt32 *>(pDst);
for(sal_uInt32 j=0; j<rSourceRect.getWidth(); ++j)
pDst32[j] = 0xFF000000 | pSrc32[j];
pDst += aSurfaceDesc.lPitch;
pImage += nSourcePitchInBytes;
}
}
break;
default:
ENSURE_OR_RETURN_FALSE(false,
"DXSurface::update(): Unknown/unimplemented buffer format" );
break;
}
rSource.unlock();
}
return SUCCEEDED(mpSurface->Unlock(NULL));
}
//////////////////////////////////////////////////////////////////////////////////
// DXSurface::getSize
//////////////////////////////////////////////////////////////////////////////////
::basegfx::B2IVector DXSurface::getSize()
{
return maSize;
}
//////////////////////////////////////////////////////////////////////////////////
// DXRenderModule::DXRenderModule
//////////////////////////////////////////////////////////////////////////////////
DXRenderModule::DXRenderModule( const ::Window& rWindow ) :
mhWnd(0),
mpWindow(),
maSize(),
maSelectedFullscreenMode(),
maTextureFormat(),
maMonitorInfo(),
mpDirectDraw(),
mpPrimarySurface(),
mpBackBufferSurface(),
mpDirect3D(),
mpDirect3DDevice(),
maLastUpdate(),
maDeviceDesc(),
maVertexCache(),
mnCount(0),
mnBeginSceneCount(0),
mbPageFlipping( false ),
mbHasNoTearingBlt( false ),
mbError( false ),
meType( PRIMITIVE_TYPE_UNKNOWN ),
mpTexture(),
maPageSize()
{
// 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(
::std::min(
static_cast<sal_uInt32>(maDeviceDesc.dwMaxTextureWidth),
static_cast<sal_uInt32>(MAX_TEXTURE_SIZE)),
::std::min(
static_cast<sal_uInt32>(maDeviceDesc.dwMaxTextureHeight),
static_cast<sal_uInt32>(MAX_TEXTURE_SIZE)));
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!") ),NULL);
}
}
maPageSize=aPageSize;
}
//////////////////////////////////////////////////////////////////////////////////
// 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( sal_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) ),
"DXRenderModuleDXRenderModuleWin32() 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());
MonitorList aMonitorList;
fillMonitorList( aMonitorList );
mpDirectDraw = COMReference<IDirectDraw2>(
createDirectDraw(aMonitorList, maMonitorInfo, mhWnd));
if(!mpDirectDraw.is())
return false;
if( !queryCaps() )
{
// go defunct, and exit
VERBOSE_TRACE( "Device::Device(): GetCaps failed" );
mpDirectDraw.reset();
return false;
}
if( !validateCaps() )
{
// go defunct, and exit
VERBOSE_TRACE( "Device::Device(): Insufficient DirectX capabilities, failed" );
mpDirectDraw.reset();
return false;
}
if( FAILED( mpDirectDraw->SetCooperativeLevel( mhWnd,
DDSCL_NORMAL|DDSCL_MULTITHREADED|DDSCL_FPUPRESERVE ) ) )
{
// go defunct, and exit
VERBOSE_TRACE( "Device::Device(): SetCooperativeLevel failed" );
mpDirectDraw.reset();
return false;
}
// setup query struct
rtl_fillMemory( &maSelectedFullscreenMode.selectedDesc,
sizeof(DDSURFACEDESC), 0 );
maSelectedFullscreenMode.selectedDesc.dwSize = sizeof(DDSURFACEDESC);
// read current display mode, e.g. for screen dimension
if( FAILED( mpDirectDraw->GetDisplayMode( &maSelectedFullscreenMode.selectedDesc )) )
{
// go defunct, and exit
VERBOSE_TRACE( "Device::Device(): GetDisplayMode failed" );
mpDirectDraw.reset();
return false;
}
// check for supported primary surface formats...
unsigned int nDisplayFormat = getDisplayFormat() & 0x00000FFF;
if(nDisplayFormat != 0x888 && nDisplayFormat != 0x565)
{
// go defunct, and exit
VERBOSE_TRACE( "Device::Device(): Unsupported DisplayFormat" );
mpDirectDraw.reset();
return false;
}
// create primary surface reference
DDSURFACEDESC aSurfaceDesc;
IDirectDrawSurface* pPrimarySurface;
rtl_fillMemory( &aSurfaceDesc,
sizeof(DDSURFACEDESC), 0 );
aSurfaceDesc.dwSize = sizeof(aSurfaceDesc);
aSurfaceDesc.dwFlags = DDSD_CAPS;
aSurfaceDesc.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_3DDEVICE;
if( FAILED(mpDirectDraw->CreateSurface(&aSurfaceDesc, &pPrimarySurface, NULL)) )
{
// go defunct, and exit
VERBOSE_TRACE( "Device::Device(): CreateSurface failed" );
mpDirectDraw.reset();
return false;
}
mpPrimarySurface = COMReference< IDirectDrawSurface >(pPrimarySurface);
// create a Clipper and associate it with the primary surface
// and the render window
LPDIRECTDRAWCLIPPER pClipper;
if( FAILED(mpDirectDraw->CreateClipper( 0, &pClipper, NULL )) )
{
// go defunct, and exit
VERBOSE_TRACE( "Device::Device(): CreateClipper failed" );
mpPrimarySurface.reset();
mpDirectDraw.reset();
return false;
}
if( FAILED(pClipper->SetHWnd(0, mhWnd)) )
{
// go defunct, and exit
VERBOSE_TRACE( "Device::Device(): Clipper->SetHWnd failed" );
pClipper->Release();
mpPrimarySurface.reset();
mpDirectDraw.reset();
return false;
}
if( FAILED(mpPrimarySurface->SetClipper( pClipper )) )
{
// go defunct, and exit
VERBOSE_TRACE( "Device::Device(): SetClipper failed" );
pClipper->Release();
mpPrimarySurface.reset();
mpDirectDraw.reset();
return false;
}
// clipper is now owned by mpPrimarySurface, release our reference
pClipper->Release();
// TODO(F3): Check whether palette needs any setup here
// get us a backbuffer for simulated flipping
IDirectDrawSurface* pSurface;
// 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.
const ::basegfx::B2ISize aSize( getFramebufferSize() );
rtl_fillMemory( &aSurfaceDesc,
sizeof(DDSURFACEDESC), 0 );
aSurfaceDesc.dwSize = sizeof(DDSURFACEDESC);
aSurfaceDesc.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH;
aSurfaceDesc.dwHeight= aSize.getY();
aSurfaceDesc.dwWidth = aSize.getX();
aSurfaceDesc.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_3DDEVICE | DDSCAPS_VIDEOMEMORY | DDSCAPS_LOCALVIDMEM;
HRESULT nRes = mpDirectDraw->CreateSurface(&aSurfaceDesc, &pSurface, NULL);
if( FAILED( nRes ) )
{
if( nRes == DDERR_OUTOFVIDEOMEMORY )
{
// local vid mem failed. Maybe AGP mem works?
aSurfaceDesc.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_3DDEVICE | DDSCAPS_VIDEOMEMORY | DDSCAPS_NONLOCALVIDMEM;
if( FAILED(mpDirectDraw->CreateSurface(&aSurfaceDesc, &pSurface, NULL)) )
{
// no chance, go defunct, and exit
VERBOSE_TRACE( "Device::Device(): CreateSurface for backbuffer failed" );
mpPrimarySurface.reset();
mpDirectDraw.reset();
return false;
}
VERBOSE_TRACE( "Device::Device(): CreateSurface for backbuffer reverted to non-local video mem" );
}
else
{
// no chance, go defunct, and exit
VERBOSE_TRACE( "Device::Device(): CreateSurface for backbuffer failed" );
mpPrimarySurface.reset();
mpDirectDraw.reset();
return false;
}
}
VERBOSE_TRACE( "Device::Device(): created backbuffer of size %d times %d pixel",
aSurfaceDesc.dwWidth,
aSurfaceDesc.dwHeight );
mpBackBufferSurface = COMReference< IDirectDrawSurface >(pSurface);
clearSurface(mpBackBufferSurface);
if( !setup3DDevice() )
{
// go defunct, and exit
VERBOSE_TRACE( "Device::Device(): setup3DDevice failed" );
mpBackBufferSurface.reset();
mpPrimarySurface.reset();
mpDirectDraw.reset();
return false;
}
mpWindow->Show();
return true;
}
//////////////////////////////////////////////////////////////////////////////////
// DXRenderModule::getSize
//////////////////////////////////////////////////////////////////////////////////
::basegfx::B2ISize DXRenderModule::getFramebufferSize() const
{
return mpDirectDraw.is() ?
::basegfx::B2ISize( maSelectedFullscreenMode.selectedDesc.dwWidth,
maSelectedFullscreenMode.selectedDesc.dwHeight ) :
::basegfx::B2ISize();
}
//////////////////////////////////////////////////////////////////////////////////
// DXRenderModule::setup3DDevice
//////////////////////////////////////////////////////////////////////////////////
bool DXRenderModule::setup3DDevice()
{
// create and setup 3D device
// ==========================
LPDIRECT3D2 pDirect3D;
if( FAILED( mpDirectDraw->QueryInterface( IID_IDirect3D2, (LPVOID*)&pDirect3D ) ) )
{
// go defunct, and exit
VERBOSE_TRACE( "Device::setup3DDevice(): QueryInterface() for Direct3D failed" );
return false;
}
mpDirect3D = COMReference< IDirect3D2 >(pDirect3D);
LPDIRECT3DDEVICE2 pDirect3DDevice;
// try HW-accelerated device first
if( FAILED(mpDirect3D->CreateDevice( IID_IDirect3DHALDevice,
mpBackBufferSurface.get(),
&pDirect3DDevice )) )
{
// no HW 3D support - go defunct, and exit
VERBOSE_TRACE( "Device::setup3DDevice(): CreateDevice() for HW Direct3D rendering failed" );
mpDirect3D.reset();
return false;
}
D3DDEVICEDESC aHELDeviceDesc;
rtl_fillMemory(&maDeviceDesc,sizeof(maDeviceDesc),0);
rtl_fillMemory(&aHELDeviceDesc,sizeof(aHELDeviceDesc),0);
maDeviceDesc.dwSize = sizeof(maDeviceDesc);
aHELDeviceDesc.dwSize = sizeof(aHELDeviceDesc);
if(FAILED(pDirect3DDevice->GetCaps(&maDeviceDesc,&aHELDeviceDesc)))
{
// go defunct, and exit
VERBOSE_TRACE( "Device::setup3DDevice(): GetCaps() for Direct3DDevice failed" );
mpDirect3D.reset();
return false;
}
mpDirect3DDevice = COMReference< IDirect3DDevice2 >(pDirect3DDevice);
// select appropriate texture format (_need_ alpha channel here)
rtl_fillMemory( &maTextureFormat,
sizeof(DDPIXELFORMAT), 0 );
maTextureFormat.dwSize = sizeof(DDPIXELFORMAT);
if( SUCCEEDED(mpDirect3DDevice->EnumTextureFormats( EnumTextureFormatsCallback, &maTextureFormat )) )
{
bool bSupportedFormat = true;
if((maTextureFormat.dwFlags & (DDPF_ALPHAPIXELS | DDPF_RGB)) != (DDPF_ALPHAPIXELS | DDPF_RGB))
bSupportedFormat = false;
else if(maTextureFormat.dwRGBAlphaBitMask != 0xFF000000)
bSupportedFormat = false;
else if(maTextureFormat.dwRBitMask != 0x00FF0000)
bSupportedFormat = false;
else if(maTextureFormat.dwGBitMask != 0x0000FF00)
bSupportedFormat = false;
else if(maTextureFormat.dwBBitMask != 0x000000FF)
bSupportedFormat = false;
if(bSupportedFormat)
{
VERBOSE_TRACE( "Device::setup3DDevice(): chose texture format dwRGBBitCount %d, dwRBitMask %x, "
"dwGBitMask %x, dwBBitMask %x and dwRGBAlphaBitMask %x. The texture uses %s alpha.",
maTextureFormat.dwRGBBitCount,
maTextureFormat.dwRBitMask,
maTextureFormat.dwGBitMask,
maTextureFormat.dwBBitMask,
maTextureFormat.dwRGBAlphaBitMask,
maTextureFormat.dwFlags & DDPF_ALPHAPREMULT ? "premultiplied" : "non-premultiplied" );
// setup the device (with as much as we can possibly do here)
// ==========================================================
LPDIRECT3DVIEWPORT2 pViewport;
if( SUCCEEDED(mpDirect3D->CreateViewport( &pViewport, NULL )) )
{
if( SUCCEEDED(mpDirect3DDevice->AddViewport( pViewport )) )
{
// setup viewport (to whole backbuffer)
D3DVIEWPORT2 aViewport;
aViewport.dwSize = sizeof(D3DVIEWPORT2);
aViewport.dwX = 0;
aViewport.dwY = 0;
aViewport.dwWidth = maSelectedFullscreenMode.selectedDesc.dwWidth;
aViewport.dwHeight = maSelectedFullscreenMode.selectedDesc.dwHeight;
aViewport.dvClipX = -1.0;
aViewport.dvClipY = -1.0;
aViewport.dvClipWidth = 2.0;
aViewport.dvClipHeight = 2.0;
aViewport.dvMinZ = 0.0;
aViewport.dvMaxZ = 1.0;
if( SUCCEEDED(pViewport->SetViewport2( &aViewport )) )
{
if( SUCCEEDED(mpDirect3DDevice->SetCurrentViewport( pViewport )) )
{
// Viewport was handed over to 3DDevice, thus we can release now
pViewport->Release();
// currently, no need for any
// matrix or light source
// setup, since we only render
// transformed&lighted
// vertices
// done; successfully
return true;
}
else
{
VERBOSE_TRACE( "Device::setup3DDevice(): SetCurrentViewport failed" );
}
}
else
{
VERBOSE_TRACE( "Device::setup3DDevice(): SetViewport2 failed" );
}
}
else
{
VERBOSE_TRACE( "Device::setup3DDevice(): AddViewport failed" );
}
pViewport->Release();
}
else
{
VERBOSE_TRACE( "Device::setup3DDevice(): CreateViewport failed" );
}
}
else
{
VERBOSE_TRACE( "Device::setup3DDevice(): No supported pixelformat" );
}
}
else
{
VERBOSE_TRACE( "Device::setup3DDevice(): EnumTextureFormats failed" );
}
// go defunct, and exit
mpDirect3DDevice.reset();
mpDirect3D.reset();
return false;
}
//////////////////////////////////////////////////////////////////////////////////
// DXRenderModule::queryCaps
//////////////////////////////////////////////////////////////////////////////////
bool DXRenderModule::queryCaps()
{
DDCAPS aHWCaps;
DDCAPS aHELCaps;
rtl_fillMemory( &aHWCaps,
sizeof(aHWCaps), 0 );
rtl_fillMemory( &aHELCaps,
sizeof(aHELCaps), 0 );
aHWCaps.dwSize = sizeof( aHWCaps );
aHELCaps.dwSize = sizeof( aHELCaps );
if( FAILED( mpDirectDraw->GetCaps( &aHWCaps,
&aHELCaps ) ) )
{
return false;
}
mbHasNoTearingBlt = aHWCaps.dwFXCaps & DDBLTFX_NOTEARING;
VERBOSE_TRACE( "dxcanvas initialization: %d bytes VRAM free for surfaces (%d with AGP mem), "
"%d bytes VRAM free for textures (%d with AGP mem)",
getAvailableSurfaceMem( false ),
getAvailableSurfaceMem( true ),
getAvailableTextureMem( false ),
getAvailableTextureMem( true ) );
return true;
}
//////////////////////////////////////////////////////////////////////////////////
// DXRenderModule::validateCaps
//////////////////////////////////////////////////////////////////////////////////
bool DXRenderModule::validateCaps()
{
// TODO(E3): Validate HW capabilities. Depending on primary
// surface size, reject HW e.g. on the grounds of insufficient
// VRAM.
// setup query struct
DDSURFACEDESC desc;
rtl_fillMemory(&desc,sizeof(DDSURFACEDESC),0);
desc.dwSize = sizeof(DDSURFACEDESC);
// read current display mode, e.g. for screen dimension
if(FAILED( mpDirectDraw->GetDisplayMode(&desc)))
return false;
// simple heuristic: we need at least 3 times the desktop
// resolution based on ARGB color values...
std::size_t nMinimumVRAMSize = ((desc.dwWidth*desc.dwHeight)<<2)*3;
if(getAvailableSurfaceMem() < nMinimumVRAMSize)
return false;
return true;
}
//////////////////////////////////////////////////////////////////////////////////
// DXRenderModule::getDisplayFormat
//////////////////////////////////////////////////////////////////////////////////
unsigned int DXRenderModule::getDisplayFormat() const
{
unsigned int nFormat;
nFormat = ::canvas::tools::bitcount32(maSelectedFullscreenMode.selectedDesc.ddpfPixelFormat.dwRGBAlphaBitMask)<<12;
nFormat |= ::canvas::tools::bitcount32(maSelectedFullscreenMode.selectedDesc.ddpfPixelFormat.dwRBitMask)<<8;
nFormat |= ::canvas::tools::bitcount32(maSelectedFullscreenMode.selectedDesc.ddpfPixelFormat.dwGBitMask)<<4;
nFormat |= ::canvas::tools::bitcount32(maSelectedFullscreenMode.selectedDesc.ddpfPixelFormat.dwBBitMask);
return nFormat;
}
//////////////////////////////////////////////////////////////////////////////////
// DXRenderModule::getAvailableSurfaceMem
//////////////////////////////////////////////////////////////////////////////////
std::size_t DXRenderModule::getAvailableSurfaceMem( bool bWithAGPMem ) const
{
if( !mpDirectDraw.is() )
return 0;
std::size_t nRes( 0 );
DDSCAPS aSurfaceCaps;
DWORD nTotal, nFree;
// real VRAM (const_cast, since GetAvailableVidMem is non-const)
aSurfaceCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_VIDEOMEMORY | DDSCAPS_LOCALVIDMEM;
if( FAILED(const_cast<IDirectDraw2&>(*mpDirectDraw).GetAvailableVidMem( &aSurfaceCaps, &nTotal, &nFree )) )
return 0;
nRes += nFree;
if( bWithAGPMem )
{
// AGP RAM (const_cast, since GetAvailableVidMem is non-const)
aSurfaceCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_VIDEOMEMORY | DDSCAPS_NONLOCALVIDMEM;
if( FAILED(const_cast<IDirectDraw2&>(*mpDirectDraw).GetAvailableVidMem( &aSurfaceCaps, &nTotal, &nFree )) )
return 0;
nRes += nFree;
}
return nRes;
}
//////////////////////////////////////////////////////////////////////////////////
// DXRenderModule::getAvailableTextureMem
//////////////////////////////////////////////////////////////////////////////////
std::size_t DXRenderModule::getAvailableTextureMem( bool bWithAGPMem ) const
{
if( !mpDirectDraw.is() )
return 0;
std::size_t nRes( 0 );
DDSCAPS aSurfaceCaps;
DWORD nTotal, nFree;
// TODO(F1): Check if flags are applicable
// real VRAM (const_cast, since GetAvailableVidMem is non-const)
aSurfaceCaps.dwCaps = DDSCAPS_TEXTURE | DDSCAPS_VIDEOMEMORY | DDSCAPS_LOCALVIDMEM;
if( FAILED(const_cast<IDirectDraw2&>(*mpDirectDraw).GetAvailableVidMem( &aSurfaceCaps, &nTotal, &nFree )) )
return 0;
nRes += nFree;
if( bWithAGPMem )
{
// AGP RAM (const_cast, since GetAvailableVidMem is non-const)
aSurfaceCaps.dwCaps = DDSCAPS_TEXTURE | DDSCAPS_VIDEOMEMORY | DDSCAPS_NONLOCALVIDMEM;
if( FAILED(const_cast<IDirectDraw2&>(*mpDirectDraw).GetAvailableVidMem( &aSurfaceCaps, &nTotal, &nFree )) )
return 0;
nRes += nFree;
}
// TODO(F1): Add pool mem
return nRes;
}
//////////////////////////////////////////////////////////////////////////////////
// DXRenderModule::convert2Screen
//////////////////////////////////////////////////////////////////////////////////
void DXRenderModule::convert2Screen( ::basegfx::B2IPoint& io_rDestPos,
::basegfx::B2IRange& io_rDestArea )
{
POINT aPoint = { 0, 0 };
ClientToScreen( mhWnd, &aPoint );
// i52230 make sure given screen coordinate is relative to
// this monitor's area (the device rendering is always
// contained to a single monitor)
aPoint.x -= maMonitorInfo.rcMonitor.left;
aPoint.y -= maMonitorInfo.rcMonitor.top;
io_rDestPos.setX( io_rDestPos.getX() + aPoint.x );
io_rDestPos.setY( io_rDestPos.getY() + aPoint.y );
const ::basegfx::B2ISize& rSize( getFramebufferSize() );
// calc output bounds (clip against framebuffer bounds)
io_rDestArea = ::basegfx::B2IRange(
::std::max( sal_Int32(0),
::std::min( sal_Int32(rSize.getX()),
sal_Int32(io_rDestArea.getMinX() + aPoint.x) ) ),
::std::max( sal_Int32(0),
::std::min( sal_Int32(rSize.getY()),
sal_Int32(io_rDestArea.getMinY() + aPoint.y) ) ),
::std::max( sal_Int32(0),
::std::min( sal_Int32(rSize.getX()),
sal_Int32(io_rDestArea.getMaxX() + aPoint.x) ) ),
::std::max( sal_Int32(0),
::std::min( sal_Int32(rSize.getY()),
sal_Int32(io_rDestArea.getMaxY() + aPoint.y) ) ) );
}
//////////////////////////////////////////////////////////////////////////////////
// DXRenderModule::createSystemMemorySurface
//////////////////////////////////////////////////////////////////////////////////
COMReference<IDirectDrawSurface> DXRenderModule::createSystemMemorySurface( const ::basegfx::B2IVector& rSize )
{
DDSURFACEDESC aSurfaceDesc;
IDirectDrawSurface* pSurface;
aSurfaceDesc.dwSize = sizeof(DDSURFACEDESC);
aSurfaceDesc.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT;;
aSurfaceDesc.dwWidth = rSize.getX();
aSurfaceDesc.dwHeight= rSize.getY();
rtl_copyMemory( &aSurfaceDesc.ddpfPixelFormat, &maTextureFormat, sizeof(DDPIXELFORMAT) );
aSurfaceDesc.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_SYSTEMMEMORY;
HRESULT nRes = mpDirectDraw->CreateSurface(&aSurfaceDesc, &pSurface, NULL);
if(FAILED(nRes))
return COMReference<IDirectDrawSurface>(NULL);
return COMReference<IDirectDrawSurface>(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 );
// see if the main surfaces got lost. if so, try to
// restore them. bail out if this operation fails.
if(!(validateMainSurfaces()))
return false;
flushVertexCache();
ENSURE_OR_THROW( !mnBeginSceneCount,
"Device::flip(): within 3D scene" );
// TODO(E3): handle DX errors more thoroughly. For fullscreen
// exclusive mode, actually even our primary surface can get
// lost and needs restore!
if( mpDirectDraw.is() &&
mpPrimarySurface.is() &&
mpBackBufferSurface.is() )
{
// ignore area and offset for page flipping device
if( mbPageFlipping )
{
#if defined(VERBOSE) && defined(DBG_UTIL)
renderFPSCounter();
renderMemAvailable();
#endif
VERBOSE_TRACE( "Device::flip(): Using true page flipping" );
// use true page flipping. Hopefully, the 3D hardware
// is flushed on this flip call (rumours have it that
// way), otherwise, perform the Lock hack as for the
// Blt below.
if( SUCCEEDED(mpPrimarySurface->Flip( NULL, DDFLIP_WAIT )) )
return true;
}
else
{
VERBOSE_TRACE( "Device::flip(): Using blt for page flipping" );
// determine actual window position
::basegfx::B2IPoint aDestPoint( rUpdateArea.getMinimum() );
::basegfx::B2IRange aSourceArea( rUpdateArea );
::basegfx::B2IRange aDestArea( 0,0,
static_cast<sal_Int32>(rCurrWindowArea.getWidth()),
static_cast<sal_Int32>(rCurrWindowArea.getHeight()) );
convert2Screen( aDestPoint, aDestArea );
// perform clipping
if( !::canvas::tools::clipBlit( aSourceArea,
aDestPoint,
rUpdateArea,
aDestArea ) )
return true; // fully clipped, but still, in a way,
// successful.
// TODO(P1): Rumours have it that the 3D hardware
// _might_ still be rendering with flaky drivers,
// which don't flush properly on Blt(). It was said,
// that 'usually', it works to lock the 3D render
// target (the backbuffer in this case). OTOH, I've
// found that this tends to degrade performance
// significantly on complying cards...
// TODO(P1): Up until rev. 1.3, this method contained
// code to make sure the blit will start _immediately_
// after the Blt call. If this is not warranted, wait
// for the next vsync. As this case was found to be
// extremely seldom, kicked out (what's more, there's
// simply no guarantee that the blitter will be
// available at any point in the code - Windows still
// is a preemptive multi-processing environment. And
// _if_ we're competing with someone over the blitter,
// we will do so the next VBLANK interval, and the
// following...)
// screen update seems to be smoother when waiting
// for vblank in every case - even when blitter
// supports the DDBLTFX_NOTEARING flag.
if( FAILED(mpDirectDraw->WaitForVerticalBlank(DDWAITVB_BLOCKBEGIN,
NULL)) )
return false;
DDBLTFX aBltFx;
DDBLTFX* pBltFX = NULL;
if( mbHasNoTearingBlt )
{
// Blt can internally schedule for no-tearing
// ===========================================
rtl_fillMemory( &aBltFx,
sizeof(aBltFx), 0 );
aBltFx.dwSize = sizeof(aBltFx);
aBltFx.dwDDFX = DDBLTFX_NOTEARING;
pBltFX = &aBltFx;
}
if( doBlit( aDestPoint,
*mpPrimarySurface,
aSourceArea,
*mpBackBufferSurface,
pBltFX,false ) )
{
#if defined(VERBOSE) && defined(DBG_UTIL)
renderFPSCounter();
renderMemAvailable();
#endif
return true;
}
}
}
return false;
}
//////////////////////////////////////////////////////////////////////////////////
// DXRenderModule::disposing
//////////////////////////////////////////////////////////////////////////////////
void DXRenderModule::disposing()
{
if(!(mhWnd))
return;
mpTexture.reset();
mpWindow.reset();
mhWnd=NULL;
// refrain from releasing the DX5 objects - deleting the
// DX5 device seems to kill the whole engine, including
// all objects we might still hold references to
// (surfaces, e.g.)
}
//////////////////////////////////////////////////////////////////////////////////
// DXRenderModule::screenshot
//////////////////////////////////////////////////////////////////////////////////
void DXRenderModule::screenShot()
{
if(!(mpBackBufferSurface.get()))
return;
char filename[256];
static sal_uInt32 counter = 0;
sprintf(filename,"c:\\shot%d.bmp",counter++);
dumpSurface(mpBackBufferSurface,filename);
}
//////////////////////////////////////////////////////////////////////////////////
// DXRenderModule::validateMainSurfaces
//////////////////////////////////////////////////////////////////////////////////
bool DXRenderModule::validateMainSurfaces()
{
if(mpPrimarySurface.get()) {
if(mpPrimarySurface->IsLost() == DDERR_SURFACELOST) {
if(FAILED(mpPrimarySurface->Restore()))
return false;
}
}
if(mpBackBufferSurface.get()) {
if(mpBackBufferSurface->IsLost() == DDERR_SURFACELOST)
{
// TODO(F1): simply restoring the backbuffer does not
// work as expected, we need to re-create everything
// from scratch. find out why...
//if(SUCCEEDED(mpBackBufferSurface->Restore()))
// return setup3DDevice();
mpBackBufferSurface.reset();
// get us a backbuffer for simulated flipping
IDirectDrawSurface* pSurface;
// TODO(P2): 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. YMMV.
const ::basegfx::B2ISize aSize( getFramebufferSize() );
DDSURFACEDESC aSurfaceDesc;
rtl_fillMemory( &aSurfaceDesc, sizeof(DDSURFACEDESC), 0 );
aSurfaceDesc.dwSize = sizeof(DDSURFACEDESC);
aSurfaceDesc.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH;
aSurfaceDesc.dwHeight= aSize.getY();
aSurfaceDesc.dwWidth = aSize.getX();
aSurfaceDesc.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_3DDEVICE | DDSCAPS_VIDEOMEMORY | DDSCAPS_LOCALVIDMEM;
HRESULT nRes = mpDirectDraw->CreateSurface(&aSurfaceDesc, &pSurface, NULL);
if( FAILED( nRes ) )
{
if( nRes == DDERR_OUTOFVIDEOMEMORY )
{
// local vid mem failed. Maybe AGP mem works?
aSurfaceDesc.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_3DDEVICE | DDSCAPS_VIDEOMEMORY | DDSCAPS_NONLOCALVIDMEM;
if( FAILED(mpDirectDraw->CreateSurface(&aSurfaceDesc, &pSurface, NULL)) )
{
// no chance
return false;
}
VERBOSE_TRACE( "Device::Device(): CreateSurface for backbuffer reverted to non-local video mem" );
}
else
{
// no chance
VERBOSE_TRACE( "Device::Device(): CreateSurface for backbuffer failed" );
return false;
}
}
VERBOSE_TRACE( "Device::Device(): created backbuffer of size %d times %d pixel",
aSurfaceDesc.dwWidth,
aSurfaceDesc.dwHeight );
mpBackBufferSurface = COMReference< IDirectDrawSurface >(pSurface);
return setup3DDevice();
}
}
return true;
}
void DXRenderModule::renderInfoText( const ::rtl::OUString& rStr,
const Gdiplus::PointF& rPos ) const
{
ENSURE_OR_THROW( !mnBeginSceneCount,
"Device::renderInfoText(): within 3D scene" );
// render text directly to primary surface
GraphicsSharedPtr pGraphics;
if( mbPageFlipping )
{
// render on top of backbuffer. We have
// page flipping, anyway, thus this will
// cost us nothing.
pGraphics = createSurfaceGraphics( mpBackBufferSurface );
}
else
{
// render FPS directly to front buffer.
// That saves us another explicit blit,
// and for me, the FPS counter can blink,
// if it likes to...
pGraphics = createSurfaceGraphics( mpPrimarySurface );
}
if( !mbPageFlipping )
{
// clear background. We might be doing optimized redraws,
// and the background under the FPS count will then not be
// cleared.
Gdiplus::SolidBrush aBrush(
Gdiplus::Color( 255, 255, 255 ) );
pGraphics->FillRectangle( &aBrush,
rPos.X, rPos.Y, 80.0, 20.0 );
}
Gdiplus::SolidBrush aBrush(
Gdiplus::Color( 255, 0, 255 ) );
Gdiplus::Font aFont( NULL,
16,
Gdiplus::FontStyleRegular,
Gdiplus::UnitWorld,
NULL );
pGraphics->DrawString( reinterpret_cast<LPCWSTR>(rStr.getStr()),
rStr.getLength(),
&aFont,
rPos,
&aBrush );
}
//////////////////////////////////////////////////////////////////////////////////
// DXRenderModule::renderMemAvailable
//////////////////////////////////////////////////////////////////////////////////
void DXRenderModule::renderMemAvailable() const
{
ENSURE_OR_THROW( !mnBeginSceneCount,
"DXRenderModule::renderMemAvailable(): within 3D scene" );
const double nSurfaceMem( getAvailableSurfaceMem()/1024 );
::rtl::OUString text( ::rtl::math::doubleToUString( nSurfaceMem,
rtl_math_StringFormat_F,
2,'.',NULL,' ') );
// pad with leading space
while( text.getLength() < 6 )
text = ::rtl::OUString(RTL_CONSTASCII_USTRINGPARAM (" ")) + text;
text = ::rtl::OUString(RTL_CONSTASCII_USTRINGPARAM ("S: ")) + text;
renderInfoText( text,
Gdiplus::PointF( 0.0, 20) );
const double nTexMem( getAvailableTextureMem()/1024 );
text = ::rtl::math::doubleToUString( nTexMem,
rtl_math_StringFormat_F,
2,'.',NULL,' ');
// pad with leading space
while( text.getLength() < 6 )
text = ::rtl::OUString(RTL_CONSTASCII_USTRINGPARAM (" ")) + text;
text = ::rtl::OUString(RTL_CONSTASCII_USTRINGPARAM ("T: ")) + text;
renderInfoText( text,
Gdiplus::PointF( 0.0, 40) );
VERBOSE_TRACE( "dxcanvas: %f free surface mem, %f free texture mem",
nSurfaceMem, nTexMem );
}
//////////////////////////////////////////////////////////////////////////////////
// DXRenderModule::renderFPSCounter
//////////////////////////////////////////////////////////////////////////////////
void DXRenderModule::renderFPSCounter() const
{
ENSURE_OR_THROW( !mnBeginSceneCount,
"DXRenderModule::ren derFPSCounter(): within 3D scene" );
const double denominator( maLastUpdate.getElapsedTime() );
maLastUpdate.reset();
::rtl::OUString text( ::rtl::math::doubleToUString( denominator == 0.0 ? 100.0 : 1.0/denominator,
rtl_math_StringFormat_F,
2,'.',NULL,' ') );
// pad with leading space
while( text.getLength() < 6 )
text = ::rtl::OUString(RTL_CONSTASCII_USTRINGPARAM (" ")) + text;
text += ::rtl::OUString(RTL_CONSTASCII_USTRINGPARAM (" fps"));
renderInfoText( text,
Gdiplus::PointF() );
VERBOSE_TRACE( "dxcanvas: %f FPS",
denominator == 0.0 ? 100.0 : 1.0/denominator );
}
//////////////////////////////////////////////////////////////////////////////////
// DXRenderModule::resize
//////////////////////////////////////////////////////////////////////////////////
void DXRenderModule::resize( const ::basegfx::B2IRange& rect )
{
// TODO(P2): get rid of those fine-grained locking
::osl::MutexGuard aGuard( maMutex );
if( mhWnd==0 )
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());
}
//////////////////////////////////////////////////////////////////////////////////
// DXRenderModule::getPageSize
//////////////////////////////////////////////////////////////////////////////////
::basegfx::B2IVector DXRenderModule::getPageSize()
{
// TODO(P2): get rid of those fine-grained locking
::osl::MutexGuard aGuard( maMutex );
return maPageSize;
}
::canvas::ISurfaceSharedPtr DXRenderModule::createSurface( const ::basegfx::B2IVector& surfaceSize )
{
// TODO(P2): get rid of those fine-grained locking
::osl::MutexGuard aGuard( maMutex );
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) );
}
void DXRenderModule::beginPrimitive( PrimitiveType eType )
{
// TODO(P2): get rid of those fine-grained locking
::osl::MutexGuard aGuard( maMutex );
ENSURE_OR_THROW( !mnBeginSceneCount,
"DXRenderModule::beginPrimitive(): nested call" );
++mnBeginSceneCount;
meType=eType;
mnCount=0;
}
void DXRenderModule::endPrimitive()
{
// TODO(P2): get rid of those fine-grained locking
::osl::MutexGuard aGuard( maMutex );
--mnBeginSceneCount;
meType=PRIMITIVE_TYPE_UNKNOWN;
mnCount=0;
}
void DXRenderModule::pushVertex( const ::canvas::Vertex& vertex )
{
// TODO(P2): get rid of those fine-grained locking
::osl::MutexGuard aGuard( maMutex );
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 types" );
break;
}
}
bool DXRenderModule::isError()
{
// TODO(P2): get rid of those fine-grained locking
::osl::MutexGuard aGuard( maMutex );
return mbError;
}
void DXRenderModule::flushVertexCache()
{
if(!(maVertexCache.size()))
return;
mbError=true;
if( FAILED(mpDirect3DDevice->BeginScene()) )
return;
// enable texture alpha blending
if( FAILED(mpDirect3DDevice->SetRenderState(D3DRENDERSTATE_ALPHABLENDENABLE,TRUE)))
return;
// enable texture alpha modulation, for honoring fAlpha
if( FAILED(mpDirect3DDevice->SetRenderState(D3DRENDERSTATE_TEXTUREMAPBLEND,
D3DTBLEND_MODULATEALPHA)) )
return;
// enable texture magnification filtering (don't care if this
// fails, it's just visually more pleasant)
mpDirect3DDevice->SetRenderState(D3DRENDERSTATE_TEXTUREMAG,
D3DFILTER_LINEAR);
// enable texture minification filtering (don't care if this
// fails, it's just visually more pleasant)
mpDirect3DDevice->SetRenderState(D3DRENDERSTATE_TEXTUREMIN,
D3DFILTER_LINEAR);
// enable subpixel texture output (don't care if this
// fails, it's just visually more pleasant)
mpDirect3DDevice->SetRenderState(D3DRENDERSTATE_SUBPIXEL,
TRUE);
// normal combination of object...
if( FAILED(mpDirect3DDevice->SetRenderState(D3DRENDERSTATE_SRCBLEND,
D3DBLEND_SRCALPHA)) )
return;
// ..and background color
if( FAILED(mpDirect3DDevice->SetRenderState(D3DRENDERSTATE_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(mpDirect3DDevice->SetRenderState(D3DRENDERSTATE_CULLMODE,
D3DCULL_NONE)) )
return;
mbError=false;
const float nHalfPixelSizeX(0.5f/maPageSize.getX());
const float nHalfPixelSizeY(0.5f/maPageSize.getY());
sal_uInt32 nIndex(0);
const std::size_t size(maVertexCache.size());
D3DTLVERTEX *vertices = static_cast<D3DTLVERTEX *>(_alloca(sizeof(D3DTLVERTEX)*size));
vertexCache_t::const_iterator it(maVertexCache.begin());
while(it != maVertexCache.end())
{
vertices[nIndex++] = D3DTLVERTEX(
D3DVECTOR(static_cast<D3DVALUE>(it->x),
static_cast<D3DVALUE>(it->y),
static_cast<D3DVALUE>(it->z)),
1,
D3DRGBA(1,1,1,it->a),
D3DRGBA(0,0,0,0),
static_cast<float>(it->u + nHalfPixelSizeX),
static_cast<float>(it->v + nHalfPixelSizeY));
++it;
}
maVertexCache.clear();
mbError |= FAILED(mpDirect3DDevice->DrawPrimitive(D3DPT_TRIANGLELIST,
D3DVT_TLVERTEX,
(LPVOID)vertices,
size,
0));
mbError |= FAILED(mpDirect3DDevice->EndScene());
}
}
IDXRenderModuleSharedPtr createRenderModule( const ::Window& rParent )
{
return IDXRenderModuleSharedPtr( new DXRenderModule(rParent) );
}
}
#endif