xref: /core/canvas/source/directx/dx_9rm.cxx (revision f896bbcf)
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  *   Licensed to the Apache Software Foundation (ASF) under one or more
12  *   contributor license agreements. See the NOTICE file distributed
13  *   with this work for additional information regarding copyright
14  *   ownership. The ASF licenses this file to you under the Apache
15  *   License, Version 2.0 (the "License"); you may not use this file
16  *   except in compliance with the License. You may obtain a copy of
17  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 #include <sal/config.h>
21 #include <sal/log.hxx>
22 
23 #include <memory>
24 #include <string.h>
25 
26 #include <basegfx/numeric/ftools.hxx>
27 #include <basegfx/point/b2ipoint.hxx>
28 #include <basegfx/range/b2irectangle.hxx>
29 #include <basegfx/vector/b2dsize.hxx>
30 #include <basegfx/vector/b2isize.hxx>
31 #include <com/sun/star/lang/NoSupportException.hpp>
32 #include <osl/thread.hxx>
33 #include <osl/time.h>
34 #include <comphelper/diagnose_ex.hxx>
35 #include <vcl/syschild.hxx>
36 #include <vcl/sysdata.hxx>
37 #include <vcl/window.hxx>
38 
39 #include <canvas/elapsedtime.hxx>
40 #include <canvas/canvastools.hxx>
41 #include <rendering/icolorbuffer.hxx>
42 #include <rendering/irendermodule.hxx>
43 #include <rendering/isurface.hxx>
44 
45 #include "dx_config.hxx"
46 #include "dx_impltools.hxx"
47 #include "dx_rendermodule.hxx"
48 
49 #define MIN_TEXTURE_SIZE (32)
50 //#define FAKE_MAX_NUMBER_TEXTURES (2)
51 //#define FAKE_MAX_TEXTURE_SIZE (4096)
52 
53 #define VERTEX_BUFFER_SIZE (341*3) // 1023, the size of the internal
54                                    // vertex buffer (must be divisible
55                                    // by 3, as each triangle primitive
56                                    // has 3 vertices)
57 
58 
59 using namespace ::com::sun::star;
60 
61 
62 // 'dxcanvas' namespace
63 
64 
65 namespace dxcanvas
66 {
67     namespace
68     {
69         class DXRenderModule;
70 
71 
72         // DXSurface
73 
74 
75         /** ISurface implementation.
76 
77             @attention holds the DXRenderModule via non-refcounted
78             reference! This is safe with current state of affairs, since
79             the canvas::PageManager holds surface and render module via
80             shared_ptr (and makes sure all surfaces are deleted before its
81             render module member goes out of scope).
82         */
83         class DXSurface : public canvas::ISurface
84         {
85         public:
86             DXSurface( DXRenderModule&           rRenderModule,
87                        const ::basegfx::B2ISize& rSize );
88             ~DXSurface() override;
89 
90             virtual bool selectTexture() override;
91             virtual bool isValid() override;
92             virtual bool update( const ::basegfx::B2IPoint& rDestPos,
93                                 const ::basegfx::B2IRange& rSourceRect,
94                                 ::canvas::IColorBuffer&    rSource ) override;
95             virtual ::basegfx::B2ISize getSize();
96 
97         private:
98             /// Guard local methods against concurrent access to RenderModule
99             class ImplRenderModuleGuard
100             {
101             public:
102                 /// make noncopyable
103                 ImplRenderModuleGuard(const ImplRenderModuleGuard&) = delete;
104                 const ImplRenderModuleGuard& operator=(const ImplRenderModuleGuard&) = delete;
105 
106                 explicit ImplRenderModuleGuard( DXRenderModule& rRenderModule );
107                 ~ImplRenderModuleGuard();
108 
109             private:
110                 DXRenderModule& mrRenderModule;
111             };
112 
113             DXRenderModule&                  mrRenderModule;
114             sal::systools::COMReference<IDirect3DTexture9> mpTexture;
115 
116             ::basegfx::B2ISize maSize;
117         };
118 
119 
120         // DXRenderModule
121 
122 
123         /// Default implementation of IDXRenderModule
124         class DXRenderModule final: public IDXRenderModule
125         {
126         public:
127             explicit DXRenderModule( const vcl::Window& rWindow );
128             ~DXRenderModule() override;
129 
lock() const130             virtual void lock() const override { maMutex.acquire(); }
unlock() const131             virtual void unlock() const override { maMutex.release(); }
132 
133             virtual sal::systools::COMReference<IDirect3DSurface9>
134                 createSystemMemorySurface(const ::basegfx::B2ISize& rSize) override;
135             virtual void disposing() override;
getHWND() const136             virtual HWND getHWND() const override { return mhWnd; }
137             virtual void screenShot() override;
138 
139             virtual bool flip( const ::basegfx::B2IRectangle& rUpdateArea,
140                                const ::basegfx::B2IRectangle& rCurrWindowArea ) override;
141 
142             virtual void resize( const ::basegfx::B2IRange& rect ) override;
143             virtual ::basegfx::B2IVector getPageSize() override;
144             virtual std::shared_ptr<canvas::ISurface> createSurface( const ::basegfx::B2IVector& surfaceSize ) override;
145             virtual void beginPrimitive( PrimitiveType eType ) override;
146             virtual void endPrimitive() override;
147             virtual void pushVertex( const ::canvas::Vertex& vertex ) override;
148             virtual bool isError() override;
149 
getDevice()150             sal::systools::COMReference<IDirect3DDevice9> getDevice() { return mpDevice; }
151 
152             void flushVertexCache();
153             void commitVertexCache();
154 
155         private:
156 
157             bool create( const vcl::Window& rWindow );
158             bool createDevice();
159             bool verifyDevice( const UINT nAdapter );
160             UINT getAdapterFromWindow();
161 
162             /** This object represents the DirectX state machine.  In order
163                 to serialize access to DirectX's global state, a global
164                 mutex is required.
165             */
166             static ::osl::Mutex                         maMutex;
167 
168             HWND                                        mhWnd;
169             sal::systools::COMReference<IDirect3DDevice9> mpDevice;
170             sal::systools::COMReference<IDirect3D9>     mpDirect3D9;
171             sal::systools::COMReference<IDirect3DSwapChain9> mpSwapChain;
172             sal::systools::COMReference<IDirect3DVertexBuffer9> mpVertexBuffer;
173             std::shared_ptr<canvas::ISurface>                 mpTexture;
174             VclPtr<SystemChildWindow>                   mpWindow;
175             ::basegfx::B2ISize maSize;
176             typedef std::vector<canvas::Vertex>         vertexCache_t;
177             vertexCache_t                               maVertexCache;
178             std::size_t                                 mnCount;
179             int                                         mnBeginSceneCount;
180             bool                                        mbCanUseDynamicTextures;
181             bool                                        mbError;
182             PrimitiveType                               meType;
183             ::basegfx::B2IVector                        maPageSize;
184             D3DPRESENT_PARAMETERS                       mad3dpp;
185 
isDisposed() const186             bool isDisposed() const { return (mhWnd==nullptr); }
187 
188             struct dxvertex
189             {
190                 float x,y,z,rhw;
191                 DWORD diffuse;
192                 float u,v;
193             };
194 
195             std::size_t                                 maNumVertices;
196             std::size_t                                 maWriteIndex;
197             std::size_t                                 maReadIndex;
198         };
199 
200         ::osl::Mutex DXRenderModule::maMutex;
201 
202 
203         // DXSurface::ImplRenderModuleGuard
204 
205 
ImplRenderModuleGuard(DXRenderModule & rRenderModule)206         DXSurface::ImplRenderModuleGuard::ImplRenderModuleGuard(
207             DXRenderModule& rRenderModule ) :
208             mrRenderModule( rRenderModule )
209         {
210             mrRenderModule.lock();
211         }
212 
~ImplRenderModuleGuard()213         DXSurface::ImplRenderModuleGuard::~ImplRenderModuleGuard()
214         {
215             mrRenderModule.unlock();
216         }
217 
218 #ifdef FAKE_MAX_NUMBER_TEXTURES
219         static sal_uInt32 gNumSurfaces = 0;
220 #endif
221 
222         // DXSurface::DXSurface
223 
224 
DXSurface(DXRenderModule & rRenderModule,const::basegfx::B2ISize & rSize)225         DXSurface::DXSurface( DXRenderModule&           rRenderModule,
226                               const ::basegfx::B2ISize& rSize ) :
227             mrRenderModule(rRenderModule),
228             mpTexture(nullptr)
229         {
230             ImplRenderModuleGuard aGuard( mrRenderModule );
231 
232 #ifdef FAKE_MAX_NUMBER_TEXTURES
233             ++gNumSurfaces;
234             if(gNumSurfaces >= FAKE_MAX_NUMBER_TEXTURES)
235                 return;
236 #endif
237 
238 #ifdef FAKE_MAX_TEXTURE_SIZE
239             if(rSize.getWidth() > FAKE_MAX_TEXTURE_SIZE)
240                 return;
241             if(rSize.getHeight() > FAKE_MAX_TEXTURE_SIZE)
242                 return;
243 #endif
244 
245             ENSURE_ARG_OR_THROW(rSize.getWidth() > 0 && rSize.getHeight() > 0,
246                             "DXSurface::DXSurface(): request for zero-sized surface");
247 
248             sal::systools::COMReference<IDirect3DDevice9> pDevice(rRenderModule.getDevice());
249 
250             IDirect3DTexture9 *pTexture(nullptr);
251             if(FAILED(pDevice->CreateTexture(
252                 rSize.getWidth(),
253                 rSize.getHeight(),
254                 1,0,D3DFMT_A8R8G8B8,
255                 D3DPOOL_MANAGED,
256                 &pTexture,nullptr)))
257                 return;
258 
259             mpTexture = sal::systools::COMReference<IDirect3DTexture9>(pTexture, false);
260             maSize = rSize;
261         }
262 
263 
264         // DXSurface::~DXSurface
265 
266 
~DXSurface()267         DXSurface::~DXSurface()
268         {
269             ImplRenderModuleGuard aGuard( mrRenderModule );
270 
271 #ifdef FAKE_MAX_NUMBER_TEXTURES
272             gNumSurfaces--;
273 #endif
274         }
275 
276 
277         // DXSurface::selectTexture
278 
279 
selectTexture()280         bool DXSurface::selectTexture()
281         {
282             ImplRenderModuleGuard aGuard( mrRenderModule );
283             mrRenderModule.flushVertexCache();
284             sal::systools::COMReference<IDirect3DDevice9> pDevice(mrRenderModule.getDevice());
285 
286             if( FAILED(pDevice->SetTexture(0,mpTexture.get())) )
287                 return false;
288 
289             return true;
290         }
291 
292 
293         // DXSurface::isValid
294 
295 
isValid()296         bool DXSurface::isValid()
297         {
298             ImplRenderModuleGuard aGuard( mrRenderModule );
299 
300             if(!(mpTexture.is()))
301                 return false;
302             return true;
303         }
304 
305 
306         // DXSurface::update
307 
308 
update(const::basegfx::B2IPoint & rDestPos,const::basegfx::B2IRange & rSourceRect,::canvas::IColorBuffer & rSource)309         bool DXSurface::update( const ::basegfx::B2IPoint& rDestPos,
310                                 const ::basegfx::B2IRange& rSourceRect,
311                                 ::canvas::IColorBuffer&    rSource )
312         {
313             ImplRenderModuleGuard aGuard( mrRenderModule );
314 
315             // can't update if surface is not valid, that means
316             // either not existent nor restored...
317             if(!(isValid()))
318                 return false;
319 
320             D3DLOCKED_RECT aLockedRect;
321             RECT rect;
322             rect.left = std::max(sal_Int32(0),rDestPos.getX());
323             rect.top =  std::max(sal_Int32(0),rDestPos.getY());
324             // to avoid interpolation artifacts from other textures,
325             // the surface manager allocates one pixel gap between
326             // them. Clear that to transparent.
327             rect.right = std::min(maSize.getWidth(),
328                                   rect.left + sal_Int32(rSourceRect.getWidth()+1));
329             rect.bottom = std::min(maSize.getHeight(),
330                                    rect.top + sal_Int32(rSourceRect.getHeight()+1));
331             const bool bClearRightColumn( rect.right < maSize.getWidth() );
332             const bool bClearBottomRow( rect.bottom < maSize.getHeight() );
333 
334             if(SUCCEEDED(mpTexture->LockRect(0,&aLockedRect,&rect,D3DLOCK_NOSYSLOCK)))
335             {
336                 if(sal_uInt8* pImage = rSource.lock())
337                 {
338                     switch( rSource.getFormat() )
339                     {
340                         case ::canvas::IColorBuffer::Format::A8R8G8B8:
341                         {
342                             const std::size_t nSourceBytesPerPixel(4);
343                             const std::size_t nSourcePitchInBytes(rSource.getStride());
344                             pImage += rSourceRect.getMinY()*nSourcePitchInBytes;
345                             pImage += rSourceRect.getMinX()*nSourceBytesPerPixel;
346 
347                             // calculate the destination memory address
348                             sal_uInt8 *pDst = static_cast<sal_uInt8*>(aLockedRect.pBits);
349 
350                             const sal_uInt32 nNumBytesToCopy(
351                                 static_cast<sal_uInt32>(
352                                     rSourceRect.getWidth())*
353                                 nSourceBytesPerPixel);
354                             const sal_uInt64 nNumLines(rSourceRect.getHeight());
355 
356                             for(sal_uInt64 i=0; i<nNumLines; ++i)
357                             {
358                                 memcpy(pDst,pImage,nNumBytesToCopy);
359 
360                                 if( bClearRightColumn )
361                                 {
362                                     // to avoid interpolation artifacts
363                                     // from other textures, the surface
364                                     // manager allocates one pixel gap
365                                     // between them. Clear that to
366                                     // transparent.
367                                     pDst[nNumBytesToCopy] =
368                                         pDst[nNumBytesToCopy+1] =
369                                         pDst[nNumBytesToCopy+2] =
370                                         pDst[nNumBytesToCopy+3] = 0x00;
371                                 }
372                                 pDst += aLockedRect.Pitch;
373                                 pImage += nSourcePitchInBytes;
374                             }
375 
376                             if( bClearBottomRow )
377                                 memset(pDst, 0, nNumBytesToCopy+4);
378                         }
379                         break;
380 
381                         case ::canvas::IColorBuffer::Format::X8R8G8B8:
382                         {
383                             const std::size_t nSourceBytesPerPixel(4);
384                             const std::size_t nSourcePitchInBytes(rSource.getStride());
385                             pImage += rSourceRect.getMinY()*nSourcePitchInBytes;
386                             pImage += rSourceRect.getMinX()*nSourceBytesPerPixel;
387 
388                             // calculate the destination memory address
389                             sal_uInt8 *pDst = static_cast<sal_uInt8*>(aLockedRect.pBits);
390 
391                             const sal_Int32 nNumLines(
392                                 sal::static_int_cast<sal_Int32>(rSourceRect.getHeight()));
393                             const sal_Int32 nNumColumns(
394                                 sal::static_int_cast<sal_Int32>(rSourceRect.getWidth()));
395                             for(sal_Int32 i=0; i<nNumLines; ++i)
396                             {
397                                 sal_uInt32 *pSrc32 = reinterpret_cast<sal_uInt32 *>(pImage);
398                                 sal_uInt32 *pDst32 = reinterpret_cast<sal_uInt32 *>(pDst);
399                                 for(sal_Int32 j=0; j<nNumColumns; ++j)
400                                     pDst32[j] = 0xFF000000 | pSrc32[j];
401 
402                                 if( bClearRightColumn )
403                                     pDst32[nNumColumns] = 0xFF000000;
404 
405                                 pDst += aLockedRect.Pitch;
406                                 pImage += nSourcePitchInBytes;
407                             }
408 
409                             if( bClearBottomRow )
410                                 memset(pDst, 0, 4*(nNumColumns+1));
411                         }
412                         break;
413 
414                         default:
415                             ENSURE_OR_RETURN_FALSE(false,
416                                             "DXSurface::update(): Unknown/unimplemented buffer format" );
417                             break;
418                     }
419 
420                     rSource.unlock();
421                 }
422 
423                 return SUCCEEDED(mpTexture->UnlockRect(0));
424             }
425 
426             return true;
427         }
428 
getSize()429         ::basegfx::B2ISize DXSurface::getSize()
430         {
431             return maSize;
432         }
433 
DXRenderModule(const vcl::Window & rWindow)434         DXRenderModule::DXRenderModule( const vcl::Window& rWindow ) :
435             mhWnd(nullptr),
436             mpDevice(),
437             mpDirect3D9(),
438             mpSwapChain(),
439             mpVertexBuffer(),
440             mpTexture(),
441             maVertexCache(),
442             mnCount(0),
443             mnBeginSceneCount(0),
444             mbCanUseDynamicTextures(false),
445             mbError( false ),
446             meType( PrimitiveType::Unknown ),
447             mad3dpp(),
448             maNumVertices( VERTEX_BUFFER_SIZE ),
449             maWriteIndex(0),
450             maReadIndex(0)
451         {
452             // TODO(P2): get rid of those fine-grained locking
453             ::osl::MutexGuard aGuard( maMutex );
454 
455             if(!(create(rWindow)))
456             {
457                 throw lang::NoSupportException( "Could not create DirectX device!" );
458             }
459 
460             // allocate a single texture surface which can be used later.
461             // we also use this to calibrate the page size.
462             basegfx::B2IVector aPageSize(maPageSize);
463             while(true)
464             {
465                 mpTexture = std::make_shared<DXSurface>(*this, basegfx::B2ISize(aPageSize.getX(), aPageSize.getY()));
466                 if(mpTexture->isValid())
467                     break;
468 
469                 aPageSize.setX(aPageSize.getX()>>1);
470                 aPageSize.setY(aPageSize.getY()>>1);
471                 if((aPageSize.getX() < MIN_TEXTURE_SIZE) ||
472                    (aPageSize.getY() < MIN_TEXTURE_SIZE))
473                 {
474                     throw lang::NoSupportException(
475                         "Could not create DirectX device - insufficient texture space!" );
476                 }
477             }
478             maPageSize=aPageSize;
479 
480             IDirect3DVertexBuffer9 *pVB(nullptr);
481             if( FAILED(mpDevice->CreateVertexBuffer(sizeof(dxvertex)*maNumVertices,
482                                                     D3DUSAGE_DYNAMIC|D3DUSAGE_WRITEONLY,
483                                                     D3DFVF_XYZRHW|D3DFVF_DIFFUSE|D3DFVF_TEX1,
484                                                     D3DPOOL_DEFAULT,
485                                                     &pVB,
486                                                     nullptr)) )
487             {
488                 throw lang::NoSupportException(
489                     "Could not create DirectX device - out of memory!" );
490             }
491 
492             mpVertexBuffer = sal::systools::COMReference<IDirect3DVertexBuffer9>(pVB, false);
493         }
494 
495 
496         // DXRenderModule::~DXRenderModule
497 
498 
~DXRenderModule()499         DXRenderModule::~DXRenderModule()
500         {
501             disposing();
502         }
503 
504 
505         // DXRenderModule::disposing
506 
507 
disposing()508         void DXRenderModule::disposing()
509         {
510             if(!mhWnd)
511                 return;
512 
513             mpTexture.reset();
514             mpWindow.disposeAndClear();
515             mhWnd=nullptr;
516 
517             // refrain from releasing the DX9 objects. We're the only
518             // ones holding references to them, and it might be
519             // dangerous to destroy the DX9 device, before all other
520             // objects are dead.
521         }
522 
523 
524         // DXRenderModule::create
525 
526 
create(const vcl::Window & rWindow)527         bool DXRenderModule::create( const vcl::Window& rWindow )
528         {
529             // TODO(P2): get rid of those fine-grained locking
530             ::osl::MutexGuard aGuard( maMutex );
531 
532             // TODO(F2): since we would like to share precious hardware
533             // resources, the direct3d9 object should be global. each new
534             // request for a canvas should only create a new swapchain.
535             mpDirect3D9 = sal::systools::COMReference<IDirect3D9>(
536                 Direct3DCreate9(D3D_SDK_VERSION), false);
537             if(!mpDirect3D9.is())
538                 return false;
539 
540             maVertexCache.reserve( 1024 );
541 
542             mpWindow.disposeAndClear();
543             mpWindow.reset( VclPtr<SystemChildWindow>::Create(
544                               const_cast<vcl::Window *>(&rWindow), 0) );
545 
546             // system child window must not receive mouse events
547             mpWindow->SetMouseTransparent( true );
548 
549             // parent should receive paint messages as well
550             mpWindow->SetParentClipMode(ParentClipMode::NoClip);
551 
552             // the system child window must not clear its background
553             mpWindow->EnableEraseBackground( false );
554 
555             mpWindow->SetControlForeground();
556             mpWindow->SetControlBackground();
557 
558             const SystemEnvData *pData = mpWindow->GetSystemData();
559             const HWND hwnd(reinterpret_cast<HWND>(pData->hWnd));
560             mhWnd = hwnd;
561 
562             ENSURE_OR_THROW( IsWindow( mhWnd ),
563                             "DXRenderModule::create() No valid HWND given." );
564 
565             // retrieve position and size of the parent window
566             const ::Size &rSizePixel(rWindow.GetSizePixel());
567 
568             // remember the size of the parent window, since we
569             // need to use this for our child window.
570             maSize.setWidth(sal_Int32(rSizePixel.Width()));
571             maSize.setHeight(sal_Int32(rSizePixel.Height()));
572 
573             // let the child window cover the same size as the parent window.
574             mpWindow->setPosSizePixel(0, 0, maSize.getWidth(),maSize.getHeight());
575 
576             // create a device from the direct3d9 object.
577             if(!(createDevice()))
578             {
579                 mpWindow.disposeAndClear();
580                 return false;
581             }
582 
583             mpWindow->Show();
584 
585             return true;
586         }
587 
588 
589         // DXRenderModule::verifyDevice
590 
591 
verifyDevice(const UINT nAdapter)592         bool DXRenderModule::verifyDevice( const UINT nAdapter )
593         {
594             ENSURE_OR_THROW( mpDirect3D9.is(),
595                               "DXRenderModule::verifyDevice() No valid device." );
596 
597             // ask direct3d9 about the capabilities of hardware devices on a specific adapter.
598             // here we decide if the underlying hardware of the machine 'is good enough'.
599             // since we only need a tiny little fraction of what could be used, this
600             // is basically a no-op.
601             D3DCAPS9 aCaps;
602             if(FAILED(mpDirect3D9->GetDeviceCaps(nAdapter,D3DDEVTYPE_HAL,&aCaps)))
603                 return false;
604             if(!(aCaps.MaxTextureWidth))
605                 return false;
606             if(!(aCaps.MaxTextureHeight))
607                 return false;
608             maPageSize = ::basegfx::B2IVector(aCaps.MaxTextureWidth,aCaps.MaxTextureHeight);
609 
610             // check device against allow & denylist entries
611             D3DADAPTER_IDENTIFIER9 aIdent;
612             if(FAILED(mpDirect3D9->GetAdapterIdentifier(nAdapter,0,&aIdent)))
613                 return false;
614 
615             DXCanvasItem aConfigItem;
616             DXCanvasItem::DeviceInfo aInfo;
617             aInfo.nVendorId = aIdent.VendorId;
618             aInfo.nDeviceId = aIdent.DeviceId;
619             aInfo.nDeviceSubSysId = aIdent.SubSysId;
620             aInfo.nDeviceRevision = aIdent.Revision;
621 
622             aInfo.nDriverId = HIWORD(aIdent.DriverVersion.HighPart);
623             aInfo.nDriverVersion = LOWORD(aIdent.DriverVersion.HighPart);
624             aInfo.nDriverSubVersion = HIWORD(aIdent.DriverVersion.LowPart);
625             aInfo.nDriverBuildId = LOWORD(aIdent.DriverVersion.LowPart);
626 
627             if( !aConfigItem.isDeviceUsable(aInfo) )
628                 return false;
629 
630             if( aConfigItem.isDenylistCurrentDevice() )
631             {
632                 aConfigItem.denylistDevice(aInfo);
633                 return false;
634             }
635 
636             aConfigItem.adaptMaxTextureSize(maPageSize);
637 
638             mbCanUseDynamicTextures = (aCaps.Caps2 & D3DCAPS2_DYNAMICTEXTURES) != 0;
639 
640             return true;
641         }
642 
643 
644         // DXRenderModule::createDevice
645 
646 
createDevice()647         bool DXRenderModule::createDevice()
648         {
649             // we expect that the caller provides us with a valid HWND
650             ENSURE_OR_THROW( IsWindow(mhWnd),
651                               "DXRenderModule::createDevice() No valid HWND given." );
652 
653             // we expect that the caller already created the direct3d9 object.
654             ENSURE_OR_THROW( mpDirect3D9.is(),
655                               "DXRenderModule::createDevice() no direct3d?." );
656 
657             // find the adapter identifier from the window.
658             const UINT aAdapter(getAdapterFromWindow());
659             if(aAdapter == static_cast<UINT>(-1))
660                 return false;
661 
662             // verify that device possibly works
663             if( !verifyDevice(aAdapter) )
664                 return false;
665 
666             // query the display mode from the selected adapter.
667             // we'll later request the backbuffer format to be same
668             // same as the display format.
669             D3DDISPLAYMODE d3ddm;
670             mpDirect3D9->GetAdapterDisplayMode(aAdapter,&d3ddm);
671 
672             // we need to use D3DSWAPEFFECT_COPY here since the canvas-api has
673             // basically nothing to do with efficient resource handling. it tries
674             // to avoid drawing whenever possible, which is simply not the most
675             // efficient way we could leverage the hardware in this case. it would
676             // be far better to redraw the backbuffer each time we would like to
677             // display the content of the backbuffer, but we need to face reality
678             // here and follow how the canvas was designed.
679 
680             // Strictly speaking, we don't need a full screen worth of
681             // backbuffer here. We could also scale dynamically with
682             // the current window size, but this will make it
683             // necessary to temporarily have two buffers while copying
684             // from the old to the new one. What's more, at the time
685             // we need a larger buffer, DX might not have sufficient
686             // resources available, and we're then left with too small
687             // a back buffer, and no way of falling back to a
688             // different canvas implementation.
689             ZeroMemory( &mad3dpp, sizeof(mad3dpp) );
690             mad3dpp.BackBufferWidth = std::max(maSize.getWidth(), sal_Int32(d3ddm.Width));
691             mad3dpp.BackBufferHeight = std::max(maSize.getHeight(), sal_Int32(d3ddm.Height));
692             mad3dpp.BackBufferCount = 1;
693             mad3dpp.Windowed = TRUE;
694             mad3dpp.SwapEffect = D3DSWAPEFFECT_COPY;
695             mad3dpp.BackBufferFormat = d3ddm.Format;
696             mad3dpp.EnableAutoDepthStencil = FALSE;
697             mad3dpp.hDeviceWindow = mhWnd;
698             mad3dpp.PresentationInterval = D3DPRESENT_INTERVAL_ONE;
699 
700             // now create the device, first try hardware vertex processing,
701             // then software vertex processing. if both queries fail, we give up
702             // and indicate failure.
703             IDirect3DDevice9 *pDevice(nullptr);
704             if(FAILED(mpDirect3D9->CreateDevice(aAdapter,
705                                                 D3DDEVTYPE_HAL,
706                                                 mhWnd,
707                                                 D3DCREATE_HARDWARE_VERTEXPROCESSING|
708                                                 D3DCREATE_MULTITHREADED|D3DCREATE_FPU_PRESERVE,
709                                                 &mad3dpp,
710                                                 &pDevice)))
711                 if(FAILED(mpDirect3D9->CreateDevice(aAdapter,
712                                                     D3DDEVTYPE_HAL,
713                                                     mhWnd,
714                                                     D3DCREATE_SOFTWARE_VERTEXPROCESSING|
715                                                     D3DCREATE_MULTITHREADED|D3DCREATE_FPU_PRESERVE,
716                                                     &mad3dpp,
717                                                     &pDevice)))
718                     return false;
719 
720             // got it, store it in a safe place...
721             mpDevice = sal::systools::COMReference<IDirect3DDevice9>(pDevice, false);
722 
723             // After CreateDevice, the first swap chain already exists, so just get it...
724             IDirect3DSwapChain9 *pSwapChain(nullptr);
725             pDevice->GetSwapChain(0,&pSwapChain);
726             mpSwapChain = sal::systools::COMReference<IDirect3DSwapChain9>(pSwapChain, false);
727             if( !mpSwapChain.is() )
728                 return false;
729 
730             // clear the render target [which is the backbuffer in this case].
731             // we are forced to do this once, and furthermore right now.
732             // please note that this is only possible since we created the
733             // backbuffer with copy semantics [the content is preserved after
734             // calls to Present()], which is an unnecessarily expensive operation.
735             LPDIRECT3DSURFACE9 pBackBuffer = nullptr;
736             mpSwapChain->GetBackBuffer(0,D3DBACKBUFFER_TYPE_MONO,&pBackBuffer);
737             mpDevice->SetRenderTarget( 0, pBackBuffer );
738             mpDevice->Clear(0,nullptr,D3DCLEAR_TARGET,0,1.0f,0);
739             pBackBuffer->Release();
740 
741             return true;
742         }
743 
744 
745         // DXRenderModule::createSystemMemorySurface
746 
747 
createSystemMemorySurface(const::basegfx::B2ISize & rSize)748         sal::systools::COMReference<IDirect3DSurface9> DXRenderModule::createSystemMemorySurface(const ::basegfx::B2ISize& rSize)
749         {
750             if(isDisposed())
751                 return sal::systools::COMReference<IDirect3DSurface9>(nullptr);
752 
753             // please note that D3DFMT_X8R8G8B8 is the only format we're
754             // able to choose here, since GetDC() doesn't support any
755             // other 32bit-format.
756             IDirect3DSurface9 *pSurface(nullptr);
757             if( FAILED(mpDevice->CreateOffscreenPlainSurface(
758                            rSize.getWidth(),
759                            rSize.getHeight(),
760                            D3DFMT_X8R8G8B8,
761                            D3DPOOL_SYSTEMMEM,
762                            &pSurface,
763                            nullptr)) )
764             {
765                 throw lang::NoSupportException(
766                     "Could not create offscreen surface - out of mem!" );
767             }
768 
769             return sal::systools::COMReference<IDirect3DSurface9>(pSurface, false);
770         }
771 
772 
773         // DXRenderModule::flip
774 
775 
flip(const::basegfx::B2IRectangle & rUpdateArea,const::basegfx::B2IRectangle &)776         bool DXRenderModule::flip( const ::basegfx::B2IRectangle& rUpdateArea,
777                                    const ::basegfx::B2IRectangle& /*rCurrWindowArea*/ )
778         {
779             // TODO(P2): get rid of those fine-grained locking
780             ::osl::MutexGuard aGuard( maMutex );
781 
782             if(isDisposed() || !mpSwapChain.is())
783                 return false;
784 
785             flushVertexCache();
786 
787             // TODO(P2): Might be faster to actually pass update area here
788             RECT aRect =
789                 {
790                     rUpdateArea.getMinX(),
791                     rUpdateArea.getMinY(),
792                     rUpdateArea.getMaxX(),
793                     rUpdateArea.getMaxY()
794                 };
795             HRESULT hr(mpSwapChain->Present(&aRect,&aRect,nullptr,nullptr,0));
796             if(FAILED(hr))
797             {
798                 if(hr != D3DERR_DEVICELOST)
799                     return false;
800 
801                 // interestingly enough, sometimes the Reset() below
802                 // *still* causes DeviceLost errors. So, cycle until
803                 // DX was kind enough to really reset the device...
804                 do
805                 {
806                     mpVertexBuffer.clear();
807                     hr = mpDevice->Reset(&mad3dpp);
808                     if(SUCCEEDED(hr))
809                     {
810                         IDirect3DVertexBuffer9 *pVB(nullptr);
811                         if( FAILED(mpDevice->CreateVertexBuffer(sizeof(dxvertex)*maNumVertices,
812                                                                 D3DUSAGE_DYNAMIC|D3DUSAGE_WRITEONLY,
813                                                                 D3DFVF_XYZRHW|D3DFVF_DIFFUSE|D3DFVF_TEX1,
814                                                                 D3DPOOL_DEFAULT,
815                                                                 &pVB,
816                                                                 nullptr)) )
817                         {
818                             throw lang::NoSupportException(
819                                 "Could not create DirectX device - out of memory!" );
820                         }
821                         mpVertexBuffer = sal::systools::COMReference<IDirect3DVertexBuffer9>(pVB, false);
822 
823                         // retry after the restore
824                         if(SUCCEEDED(mpSwapChain->Present(&aRect,&aRect,nullptr,nullptr,0)))
825                             return true;
826                     }
827 
828                     osl::Thread::wait(std::chrono::seconds(1));
829                 }
830                 while(hr == D3DERR_DEVICELOST);
831 
832                 return false;
833             }
834 
835             return true;
836         }
837 
838 
839         // DXRenderModule::screenShot
840 
841 
screenShot()842         void DXRenderModule::screenShot()
843         {
844         }
845 
846 
847         // DXRenderModule::resize
848 
849 
resize(const::basegfx::B2IRange & rect)850         void DXRenderModule::resize( const ::basegfx::B2IRange& rect )
851         {
852             // TODO(P2): get rid of those fine-grained locking
853             ::osl::MutexGuard aGuard( maMutex );
854 
855             if(isDisposed())
856                 return;
857 
858             // don't do anything if the size didn't change.
859             if(maSize.getWidth() == static_cast<sal_Int32>(rect.getWidth()) &&
860                maSize.getHeight() == static_cast<sal_Int32>(rect.getHeight()))
861                return;
862 
863             // TODO(Q2): use numeric cast to prevent overflow
864             maSize.setWidth(sal_Int32(rect.getWidth()));
865             maSize.setHeight(sal_Int32(rect.getHeight()));
866 
867             mpWindow->setPosSizePixel(0, 0, maSize.getWidth(), maSize.getHeight());
868 
869             // resize back buffer, if necessary
870 
871 
872             // don't attempt to create anything if the
873             // requested size is NULL.
874             if(!(maSize.getWidth()))
875                 return;
876             if(!(maSize.getHeight()))
877                 return;
878 
879             // backbuffer too small (might happen, if window is
880             // maximized across multiple monitors)
881             if( sal_Int32(mad3dpp.BackBufferWidth) < maSize.getWidth() ||
882                 sal_Int32(mad3dpp.BackBufferHeight) < maSize.getHeight() )
883             {
884                 mad3dpp.BackBufferWidth = maSize.getWidth();
885                 mad3dpp.BackBufferHeight = maSize.getHeight();
886 
887                 // clear before, save resources
888                 mpSwapChain.clear();
889 
890                 IDirect3DSwapChain9 *pSwapChain(nullptr);
891                 if(FAILED(mpDevice->CreateAdditionalSwapChain(&mad3dpp,&pSwapChain)))
892                     return;
893                 mpSwapChain = sal::systools::COMReference<IDirect3DSwapChain9>(pSwapChain, false);
894 
895                 // clear the render target [which is the backbuffer in this case].
896                 // we are forced to do this once, and furthermore right now.
897                 // please note that this is only possible since we created the
898                 // backbuffer with copy semantics [the content is preserved after
899                 // calls to Present()], which is an unnecessarily expensive operation.
900                 LPDIRECT3DSURFACE9 pBackBuffer = nullptr;
901                 mpSwapChain->GetBackBuffer(0,D3DBACKBUFFER_TYPE_MONO,&pBackBuffer);
902                 mpDevice->SetRenderTarget( 0, pBackBuffer );
903                 mpDevice->Clear(0,nullptr,D3DCLEAR_TARGET,0,1.0f,0);
904                 pBackBuffer->Release();
905             }
906         }
907 
908 
909         // DXRenderModule::getPageSize
910 
911 
getPageSize()912         ::basegfx::B2IVector DXRenderModule::getPageSize()
913         {
914             // TODO(P2): get rid of those fine-grained locking
915             ::osl::MutexGuard aGuard( maMutex );
916             return maPageSize;
917         }
918 
919 
920         // DXRenderModule::createSurface
921 
922 
createSurface(const::basegfx::B2IVector & surfaceSize)923         std::shared_ptr<canvas::ISurface> DXRenderModule::createSurface( const ::basegfx::B2IVector& surfaceSize )
924         {
925             // TODO(P2): get rid of those fine-grained locking
926             ::osl::MutexGuard aGuard( maMutex );
927 
928             if(isDisposed())
929                 return std::shared_ptr<canvas::ISurface>();
930 
931             const ::basegfx::B2IVector& rPageSize( getPageSize() );
932             ::basegfx::B2ISize aSize(surfaceSize);
933             if(!(aSize.getWidth()))
934                 aSize.setWidth(rPageSize.getX());
935             if(!(aSize.getHeight()))
936                 aSize.setHeight(rPageSize.getY());
937 
938             if(mpTexture.use_count() == 1)
939                 return mpTexture;
940 
941             return std::make_shared<DXSurface>(*this,aSize);
942         }
943 
944 
945         // DXRenderModule::beginPrimitive
946 
947 
beginPrimitive(PrimitiveType eType)948         void DXRenderModule::beginPrimitive( PrimitiveType eType )
949         {
950             // TODO(P2): get rid of those fine-grained locking
951             ::osl::MutexGuard aGuard( maMutex );
952 
953             if(isDisposed())
954                 return;
955 
956             ENSURE_OR_THROW( !mnBeginSceneCount,
957                               "DXRenderModule::beginPrimitive(): nested call" );
958 
959             ++mnBeginSceneCount;
960             meType=eType;
961             mnCount=0;
962         }
963 
964 
965         // DXRenderModule::endPrimitive
966 
967 
endPrimitive()968         void DXRenderModule::endPrimitive()
969         {
970             // TODO(P2): get rid of those fine-grained locking
971             ::osl::MutexGuard aGuard( maMutex );
972 
973             if(isDisposed())
974                 return;
975 
976             --mnBeginSceneCount;
977             meType = PrimitiveType::Unknown;
978             mnCount = 0;
979         }
980 
981 
982         // DXRenderModule::pushVertex
983 
984 
pushVertex(const::canvas::Vertex & vertex)985         void DXRenderModule::pushVertex( const ::canvas::Vertex& vertex )
986         {
987             // TODO(P2): get rid of those fine-grained locking
988             ::osl::MutexGuard aGuard( maMutex );
989 
990             if(isDisposed())
991                 return;
992 
993             switch(meType)
994             {
995                 case PrimitiveType::Triangle:
996                 {
997                     maVertexCache.push_back(vertex);
998                     ++mnCount;
999                     mnCount &= 3;
1000                     break;
1001                 }
1002 
1003                 case PrimitiveType::Quad:
1004                 {
1005                     if(mnCount == 3)
1006                     {
1007                         const std::size_t size(maVertexCache.size());
1008                         ::canvas::Vertex v0(maVertexCache[size-1]);
1009                         ::canvas::Vertex v2(maVertexCache[size-3]);
1010                         maVertexCache.push_back(v0);
1011                         maVertexCache.push_back(vertex);
1012                         maVertexCache.push_back(v2);
1013                         mnCount=0;
1014                     }
1015                     else
1016                     {
1017                         maVertexCache.push_back(vertex);
1018                         ++mnCount;
1019                     }
1020                     break;
1021                 }
1022 
1023                 default:
1024                     SAL_WARN("canvas.directx", "DXRenderModule::pushVertex(): unexpected primitive type");
1025                     break;
1026             }
1027         }
1028 
1029 
1030         // DXRenderModule::isError
1031 
1032 
isError()1033         bool DXRenderModule::isError()
1034         {
1035             // TODO(P2): get rid of those fine-grained locking
1036             ::osl::MutexGuard aGuard( maMutex );
1037 
1038             return mbError;
1039         }
1040 
1041 
1042         // DXRenderModule::getAdapterFromWindow
1043 
1044 
getAdapterFromWindow()1045         UINT DXRenderModule::getAdapterFromWindow()
1046         {
1047             HMONITOR hMonitor(MonitorFromWindow(mhWnd, MONITOR_DEFAULTTONEAREST));
1048             UINT aAdapterCount(mpDirect3D9->GetAdapterCount());
1049             for(UINT i=0; i<aAdapterCount; ++i)
1050                 if(hMonitor == mpDirect3D9->GetAdapterMonitor(i))
1051                     return i;
1052             return static_cast<UINT>(-1);
1053         }
1054 
1055 
1056         // DXRenderModule::commitVertexCache
1057 
1058 
commitVertexCache()1059         void DXRenderModule::commitVertexCache()
1060         {
1061             if(maReadIndex != maWriteIndex)
1062             {
1063                 const std::size_t nVertexStride = sizeof(dxvertex);
1064                 const unsigned int nNumVertices = maWriteIndex-maReadIndex;
1065                 const unsigned int nNumPrimitives = nNumVertices / 3;
1066 
1067                 if(FAILED(mpDevice->SetStreamSource(0,mpVertexBuffer.get(),0,nVertexStride)))
1068                     return;
1069 
1070                 if(FAILED(mpDevice->SetFVF(D3DFVF_XYZRHW|D3DFVF_DIFFUSE|D3DFVF_TEX1)))
1071                     return;
1072 
1073                 if(FAILED(mpDevice->BeginScene()))
1074                     return;
1075 
1076                 mbError |= FAILED(mpDevice->DrawPrimitive(D3DPT_TRIANGLELIST,maReadIndex,nNumPrimitives));
1077                 mbError |= FAILED(mpDevice->EndScene());
1078 
1079                 maReadIndex += nNumVertices;
1080             }
1081         }
1082 
1083 
1084         // DXRenderModule::flushVertexCache
1085 
1086 
flushVertexCache()1087         void DXRenderModule::flushVertexCache()
1088         {
1089             if(maVertexCache.empty())
1090                 return;
1091 
1092             mbError=true;
1093 
1094             if( FAILED(mpDevice->SetRenderState(D3DRS_LIGHTING,FALSE)))
1095                 return;
1096 
1097             // enable texture alpha blending
1098             if( FAILED(mpDevice->SetRenderState(D3DRS_ALPHABLENDENABLE,TRUE)))
1099                 return;
1100 
1101             mpDevice->SetSamplerState(0,D3DSAMP_MAGFILTER,D3DTEXF_LINEAR);
1102             mpDevice->SetSamplerState(0,D3DSAMP_MINFILTER,D3DTEXF_LINEAR);
1103             mpDevice->SetSamplerState(0,D3DSAMP_ADDRESSU ,D3DTADDRESS_CLAMP );
1104             mpDevice->SetSamplerState(0,D3DSAMP_ADDRESSV ,D3DTADDRESS_CLAMP );
1105 
1106             // configure the fixed-function pipeline.
1107             // the only 'feature' we need here is to modulate the alpha-channels
1108             // from the texture and the interpolated diffuse color. the result
1109             // will then be blended with the backbuffer.
1110             // fragment color = texture color * diffuse.alpha.
1111             mpDevice->SetTextureStageState(0,D3DTSS_ALPHAOP,D3DTOP_MODULATE);
1112             mpDevice->SetTextureStageState(0,D3DTSS_ALPHAARG1,D3DTA_TEXTURE);
1113             mpDevice->SetTextureStageState(0,D3DTSS_ALPHAARG2,D3DTA_DIFFUSE);
1114 
1115             // normal combination of object...
1116             if( FAILED(mpDevice->SetRenderState(D3DRS_SRCBLEND,D3DBLEND_SRCALPHA)) )
1117                 return;
1118 
1119             // ..and background color
1120             if( FAILED(mpDevice->SetRenderState(D3DRS_DESTBLEND,D3DBLEND_INVSRCALPHA)) )
1121                 return;
1122 
1123             // disable backface culling; this enables us to mirror sprites
1124             // by simply reverting the triangles, which, with enabled
1125             // culling, would be invisible otherwise
1126             if( FAILED(mpDevice->SetRenderState(D3DRS_CULLMODE,D3DCULL_NONE)) )
1127                 return;
1128 
1129             mbError=false;
1130 
1131             std::size_t nSize(maVertexCache.size());
1132             const std::size_t nVertexStride = sizeof(dxvertex);
1133 
1134             const ::basegfx::B2IVector aPageSize(getPageSize());
1135             const float nHalfPixelSizeX(0.5f/aPageSize.getX());
1136             const float nHalfPixelSizeY(0.5f/aPageSize.getY());
1137             vertexCache_t::const_iterator it(maVertexCache.begin());
1138 
1139             while( nSize )
1140             {
1141                 DWORD dwLockFlags(D3DLOCK_NOOVERWRITE);
1142 
1143                 // Check to see if there's space for the current set of
1144                 // vertices in the buffer.
1145                 if( maNumVertices - maWriteIndex < nSize )
1146                 {
1147                     commitVertexCache();
1148                     dwLockFlags = D3DLOCK_DISCARD;
1149                     maWriteIndex = 0;
1150                     maReadIndex = 0;
1151                 }
1152 
1153                 dxvertex *vertices(nullptr);
1154                 const std::size_t nNumVertices(
1155                     std::min(maNumVertices - maWriteIndex,
1156                              nSize));
1157                 if(FAILED(mpVertexBuffer->Lock(maWriteIndex*nVertexStride,
1158                                                nNumVertices*nVertexStride,
1159                                                reinterpret_cast<void **>(&vertices),
1160                                                dwLockFlags)))
1161                     return;
1162 
1163                 std::size_t nIndex(0);
1164                 while( nIndex < nNumVertices )
1165                 {
1166                     dxvertex &dest = vertices[nIndex++];
1167                     dest.x=it->x;
1168                     dest.y=it->y;
1169                     dest.z=it->z;
1170                     dest.rhw=1;
1171                     const sal_uInt32 alpha(static_cast<sal_uInt32>(it->a*255.0f));
1172                     dest.diffuse=D3DCOLOR_ARGB(alpha,255,255,255);
1173                     dest.u=static_cast<float>(it->u + nHalfPixelSizeX);
1174                     dest.v=static_cast<float>(it->v + nHalfPixelSizeY);
1175                     ++it;
1176                 }
1177 
1178                 mpVertexBuffer->Unlock();
1179 
1180                 // Advance to the next position in the vertex buffer.
1181                 maWriteIndex += nNumVertices;
1182                 nSize -= nNumVertices;
1183 
1184                 commitVertexCache();
1185             }
1186 
1187             maVertexCache.clear();
1188         }
1189     }
1190 
1191 
1192     // createRenderModule
1193 
1194 
createRenderModule(const vcl::Window & rParent)1195     IDXRenderModuleSharedPtr createRenderModule( const vcl::Window& rParent )
1196     {
1197         return std::make_shared<DXRenderModule>(rParent);
1198     }
1199 }
1200 
1201 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
1202