office-gobmx/canvas/source/directx/dx_9rm.cxx
Mike Kaganski 7f0b5f8eb2 Use sal::systools::COMReference and drop dxcanvas::COMReference
Change-Id: I352147dd86d8ba73da1706c42c885bef0f11b2cd
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/115071
Tested-by: Jenkins
Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
2021-05-04 15:29:53 +02:00

1213 lines
44 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* This file incorporates work covered by the following license notice:
*
* 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 .
*/
#include <sal/config.h>
#include <sal/log.hxx>
#include <memory>
#include <string.h>
#include <basegfx/numeric/ftools.hxx>
#include <basegfx/point/b2ipoint.hxx>
#include <basegfx/range/b2irectangle.hxx>
#include <basegfx/vector/b2dsize.hxx>
#include <basegfx/vector/b2isize.hxx>
#include <com/sun/star/lang/NoSupportException.hpp>
#include <osl/thread.hxx>
#include <osl/time.h>
#include <tools/diagnose_ex.h>
#include <vcl/syschild.hxx>
#include <vcl/sysdata.hxx>
#include <vcl/window.hxx>
#include <canvas/elapsedtime.hxx>
#include <canvas/canvastools.hxx>
#include <rendering/icolorbuffer.hxx>
#include <rendering/irendermodule.hxx>
#include <rendering/isurface.hxx>
#include "dx_config.hxx"
#include "dx_impltools.hxx"
#include "dx_rendermodule.hxx"
#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 divisible
// by 3, as each triangle primitive
// has 3 vertices)
using namespace ::com::sun::star;
// 'dxcanvas' namespace
namespace dxcanvas
{
namespace
{
class DXRenderModule;
// DXSurface
/** ISurface implementation.
@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() override;
virtual bool selectTexture() override;
virtual bool isValid() override;
virtual bool update( const ::basegfx::B2IPoint& rDestPos,
const ::basegfx::B2IRange& rSourceRect,
::canvas::IColorBuffer& rSource ) override;
virtual ::basegfx::B2IVector getSize();
private:
/// Guard local methods against concurrent access to RenderModule
class ImplRenderModuleGuard
{
public:
/// make noncopyable
ImplRenderModuleGuard(const ImplRenderModuleGuard&) = delete;
const ImplRenderModuleGuard& operator=(const ImplRenderModuleGuard&) = delete;
explicit ImplRenderModuleGuard( DXRenderModule& rRenderModule );
~ImplRenderModuleGuard();
private:
DXRenderModule& mrRenderModule;
};
DXRenderModule& mrRenderModule;
sal::systools::COMReference<IDirect3DTexture9> mpTexture;
::basegfx::B2IVector maSize;
};
// DXRenderModule
/// Default implementation of IDXRenderModule
class DXRenderModule final: public IDXRenderModule
{
public:
explicit DXRenderModule( const vcl::Window& rWindow );
~DXRenderModule() override;
virtual void lock() const override { maMutex.acquire(); }
virtual void unlock() const override { maMutex.release(); }
virtual sal::systools::COMReference<IDirect3DSurface9>
createSystemMemorySurface( const ::basegfx::B2IVector& rSize ) override;
virtual void disposing() override;
virtual HWND getHWND() const override { return mhWnd; }
virtual void screenShot() override;
virtual bool flip( const ::basegfx::B2IRectangle& rUpdateArea,
const ::basegfx::B2IRectangle& rCurrWindowArea ) override;
virtual void resize( const ::basegfx::B2IRange& rect ) override;
virtual ::basegfx::B2IVector getPageSize() override;
virtual std::shared_ptr<canvas::ISurface> createSurface( const ::basegfx::B2IVector& surfaceSize ) override;
virtual void beginPrimitive( PrimitiveType eType ) override;
virtual void endPrimitive() override;
virtual void pushVertex( const ::canvas::Vertex& vertex ) override;
virtual bool isError() override;
sal::systools::COMReference<IDirect3DDevice9> getDevice() { return mpDevice; }
void flushVertexCache();
void commitVertexCache();
private:
bool create( const vcl::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;
sal::systools::COMReference<IDirect3DDevice9> mpDevice;
sal::systools::COMReference<IDirect3D9> mpDirect3D9;
sal::systools::COMReference<IDirect3DSwapChain9> mpSwapChain;
sal::systools::COMReference<IDirect3DVertexBuffer9> mpVertexBuffer;
std::shared_ptr<canvas::ISurface> mpTexture;
VclPtr<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;
bool isDisposed() const { return (mhWnd==nullptr); }
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
DXSurface::ImplRenderModuleGuard::ImplRenderModuleGuard(
DXRenderModule& rRenderModule ) :
mrRenderModule( rRenderModule )
{
mrRenderModule.lock();
}
DXSurface::ImplRenderModuleGuard::~ImplRenderModuleGuard()
{
mrRenderModule.unlock();
}
#ifdef FAKE_MAX_NUMBER_TEXTURES
static sal_uInt32 gNumSurfaces = 0;
#endif
// DXSurface::DXSurface
DXSurface::DXSurface( DXRenderModule& rRenderModule,
const ::basegfx::B2ISize& rSize ) :
mrRenderModule(rRenderModule),
mpTexture(nullptr),
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");
sal::systools::COMReference<IDirect3DDevice9> pDevice(rRenderModule.getDevice());
IDirect3DTexture9 *pTexture(nullptr);
if(FAILED(pDevice->CreateTexture(
rSize.getX(),
rSize.getY(),
1,0,D3DFMT_A8R8G8B8,
D3DPOOL_MANAGED,
&pTexture,nullptr)))
return;
mpTexture = sal::systools::COMReference<IDirect3DTexture9>(pTexture, false);
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();
sal::systools::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::Format::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 = static_cast<sal_uInt8*>(aLockedRect.pBits);
const sal_uInt32 nNumBytesToCopy(
static_cast<sal_uInt32>(
rSourceRect.getWidth())*
nSourceBytesPerPixel);
const sal_uInt64 nNumLines(rSourceRect.getHeight());
for(sal_uInt64 i=0; i<nNumLines; ++i)
{
memcpy(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 )
memset(pDst, 0, nNumBytesToCopy+4);
}
break;
case ::canvas::IColorBuffer::Format::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 = static_cast<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 )
memset(pDst, 0, 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;
}
// DXRenderModule::DXRenderModule
DXRenderModule::DXRenderModule( const vcl::Window& rWindow ) :
mhWnd(nullptr),
mpDevice(),
mpDirect3D9(),
mpSwapChain(),
mpVertexBuffer(),
mpTexture(),
maSize(),
maVertexCache(),
mnCount(0),
mnBeginSceneCount(0),
mbCanUseDynamicTextures(false),
mbError( false ),
meType( PrimitiveType::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( "Could not create DirectX device!" );
}
// 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 = std::make_shared<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(
"Could not create DirectX device - insufficient texture space!" );
}
}
maPageSize=aPageSize;
IDirect3DVertexBuffer9 *pVB(nullptr);
if( FAILED(mpDevice->CreateVertexBuffer(sizeof(dxvertex)*maNumVertices,
D3DUSAGE_DYNAMIC|D3DUSAGE_WRITEONLY,
D3DFVF_XYZRHW|D3DFVF_DIFFUSE|D3DFVF_TEX1,
D3DPOOL_DEFAULT,
&pVB,
nullptr)) )
{
throw lang::NoSupportException(
"Could not create DirectX device - out of memory!" );
}
mpVertexBuffer = sal::systools::COMReference<IDirect3DVertexBuffer9>(pVB, false);
}
// DXRenderModule::~DXRenderModule
DXRenderModule::~DXRenderModule()
{
disposing();
}
// DXRenderModule::disposing
void DXRenderModule::disposing()
{
if(!mhWnd)
return;
mpTexture.reset();
mpWindow.disposeAndClear();
mhWnd=nullptr;
// 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 vcl::Window& rWindow )
{
// TODO(P2): get rid of those fine-grained locking
::osl::MutexGuard aGuard( maMutex );
// 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 = sal::systools::COMReference<IDirect3D9>(
Direct3DCreate9(D3D_SDK_VERSION), false);
if(!mpDirect3D9.is())
return false;
maVertexCache.reserve( 1024 );
mpWindow.disposeAndClear();
mpWindow.reset( VclPtr<SystemChildWindow>::Create(
const_cast<vcl::Window *>(&rWindow), 0) );
// system child window must not receive mouse events
mpWindow->SetMouseTransparent( true );
// parent should receive paint messages as well
mpWindow->SetParentClipMode(ParentClipMode::NoClip);
// the system child window must not clear its background
mpWindow->EnableEraseBackground( false );
mpWindow->SetControlForeground();
mpWindow->SetControlBackground();
const SystemEnvData *pData = mpWindow->GetSystemData();
const HWND hwnd(reinterpret_cast<HWND>(pData->hWnd));
mhWnd = 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());
// create a device from the direct3d9 object.
if(!(createDevice()))
{
mpWindow.disposeAndClear();
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 allow & denylist 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.isDenylistCurrentDevice() )
{
aConfigItem.denylistDevice(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 whenever 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(maSize.getX(),
sal_Int32(d3ddm.Width));
mad3dpp.BackBufferHeight = std::max(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(nullptr);
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 = sal::systools::COMReference<IDirect3DDevice9>(pDevice, false);
// After CreateDevice, the first swap chain already exists, so just get it...
IDirect3DSwapChain9 *pSwapChain(nullptr);
pDevice->GetSwapChain(0,&pSwapChain);
mpSwapChain = sal::systools::COMReference<IDirect3DSwapChain9>(pSwapChain, false);
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 = nullptr;
mpSwapChain->GetBackBuffer(0,D3DBACKBUFFER_TYPE_MONO,&pBackBuffer);
mpDevice->SetRenderTarget( 0, pBackBuffer );
mpDevice->Clear(0,nullptr,D3DCLEAR_TARGET,0,1.0f,0);
pBackBuffer->Release();
return true;
}
// DXRenderModule::createSystemMemorySurface
sal::systools::COMReference<IDirect3DSurface9> DXRenderModule::createSystemMemorySurface( const ::basegfx::B2IVector& rSize )
{
if(isDisposed())
return sal::systools::COMReference<IDirect3DSurface9>(nullptr);
// 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(nullptr);
if( FAILED(mpDevice->CreateOffscreenPlainSurface(
rSize.getX(),
rSize.getY(),
D3DFMT_X8R8G8B8,
D3DPOOL_SYSTEMMEM,
&pSurface,
nullptr)) )
{
throw lang::NoSupportException(
"Could not create offscreen surface - out of mem!" );
}
return sal::systools::COMReference<IDirect3DSurface9>(pSurface, false);
}
// 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,nullptr,nullptr,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.clear();
hr = mpDevice->Reset(&mad3dpp);
if(SUCCEEDED(hr))
{
IDirect3DVertexBuffer9 *pVB(nullptr);
if( FAILED(mpDevice->CreateVertexBuffer(sizeof(dxvertex)*maNumVertices,
D3DUSAGE_DYNAMIC|D3DUSAGE_WRITEONLY,
D3DFVF_XYZRHW|D3DFVF_DIFFUSE|D3DFVF_TEX1,
D3DPOOL_DEFAULT,
&pVB,
nullptr)) )
{
throw lang::NoSupportException(
"Could not create DirectX device - out of memory!" );
}
mpVertexBuffer = sal::systools::COMReference<IDirect3DVertexBuffer9>(pVB, false);
// retry after the restore
if(SUCCEEDED(mpSwapChain->Present(&aRect,&aRect,nullptr,nullptr,0)))
return true;
}
osl::Thread::wait(std::chrono::seconds(1));
}
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.clear();
IDirect3DSwapChain9 *pSwapChain(nullptr);
if(FAILED(mpDevice->CreateAdditionalSwapChain(&mad3dpp,&pSwapChain)))
return;
mpSwapChain = sal::systools::COMReference<IDirect3DSwapChain9>(pSwapChain, 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 = nullptr;
mpSwapChain->GetBackBuffer(0,D3DBACKBUFFER_TYPE_MONO,&pBackBuffer);
mpDevice->SetRenderTarget( 0, pBackBuffer );
mpDevice->Clear(0,nullptr,D3DCLEAR_TARGET,0,1.0f,0);
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
std::shared_ptr<canvas::ISurface> DXRenderModule::createSurface( const ::basegfx::B2IVector& surfaceSize )
{
// TODO(P2): get rid of those fine-grained locking
::osl::MutexGuard aGuard( maMutex );
if(isDisposed())
return std::shared_ptr<canvas::ISurface>();
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 std::make_shared<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 = PrimitiveType::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 PrimitiveType::Triangle:
{
maVertexCache.push_back(vertex);
++mnCount;
mnCount &= 3;
break;
}
case PrimitiveType::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:
SAL_WARN("canvas.directx", "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(MonitorFromWindow(mhWnd, MONITOR_DEFAULTTONEAREST));
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.empty())
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(nullptr);
const std::size_t nNumVertices(
std::min(maNumVertices - maWriteIndex,
nSize));
if(FAILED(mpVertexBuffer->Lock(maWriteIndex*nVertexStride,
nNumVertices*nVertexStride,
reinterpret_cast<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 vcl::Window& rParent )
{
return std::make_shared<DXRenderModule>(rParent);
}
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */