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
