1 /**
2     \author mean fixounet@free.fr 2016
3     \brief d3d/dxva renderer
4 
5     Inspired stronly by mplayer vo_direct3d
6 */
7 
8 /***************************************************************************
9  *                                                                         *
10  *   This program is free software; you can redistribute it and/or modify  *
11  *   it under the terms of the GNU General Public License as published by  *
12  *   the Free Software Foundation; either version 2 of the License, or     *
13  *   (at your option) any later version.                                   *
14  *                                                                         *
15  ***************************************************************************/
16 #include "ADM_coreD3DApi.h"
17 #include "config.h"
18 #include "ADM_default.h"
19 #include "DIA_coreToolkit.h"
20 #include "GUI_render.h"
21 #include "GUI_renderInternal.h"
22 #include "GUI_accelRender.h"
23 #include "ADM_threads.h"
24 
25 #include "ADM_coreD3D.h"
26 #include <dxva2api.h>
27 #include <d3d9types.h>
28 #include "ADM_coreDxva2.h"
29 #include "../../qt4/ADM_userInterfaces/ADM_gui/T_preview.h"
30 
31 
32 
33 #if 0
34 #define aprintf printf
35 #else
36 #define aprintf(...) {}
37 #endif
38 
39 /**
40  */
ADM_FAILED(HRESULT hr)41 static bool ADM_FAILED(HRESULT hr)
42 {
43     int r=(int)hr;
44     if(r<0)
45     {
46         ADM_warning("Failed with error code=0x%x\n",r);
47         return true;
48     }
49     return false;
50 }
51 
52 /**
53     \class sdlRender
54 */
55 class dxvaRender: public VideoRenderBase,public ADM_QvideoDrawer
56 {
57   protected:
58               bool     useYV12;
59   public:
60                              dxvaRender( void ) ;
61               virtual       ~dxvaRender();
62               virtual	  bool init( GUI_WindowInfo *window, uint32_t w, uint32_t h, float zoom);
63               virtual	  bool stop(void);
64               virtual   bool displayImage(ADMImage *pic);
65               virtual   bool changeZoom(float newZoom);
usingUIRedraw(void)66               virtual   bool usingUIRedraw(void) {return false;};
67               virtual   bool refresh(void) ;
getName()68                         const char *getName() {return "DXVA2";}
69                         bool draw(QWidget *widget, QPaintEvent *ev);
70                         bool displayImage_argb(ADMImage *pic);
71                         bool displayImage_yv12(ADMImage *pic);
72                         bool displayImage_surface(ADMImage *pic,admDx2Surface *image);
73             //  virtual   ADM_HW_IMAGE getPreferedImage(void ) {return ADM_HW_DXVA;}
74                         bool displayImage_surface(admDx2Surface *pic);
getPreferedImage(void)75                virtual   ADM_HW_IMAGE getPreferedImage(void ) {return ADM_HW_DXVA;}
76 
77 
78   protected:
79                         admMutex        lock;
80                         GUI_WindowInfo  info;
81                         IDirect3DSurface9 *mySurface;
82                         IDirect3DSurface9 *myYV12Surface;
83                         uint8_t           *videoBuffer;
84                         D3DDISPLAYMODE     displayMode;
85                         IDirect3DDevice9  *d3dDevice;
86                         IDirect3D9        *d3dHandle;
87                         D3DLOCKED_RECT     d3dLock;
88                         RECT               panScan;
89                         RECT               targetRect;
90                         ADM_Qvideo        *videoWidget;
91                         HWND              windowId;
92 protected:
93                         bool cleanup();
94                         bool setup();
95 };
96 
97 
98 /**
99         \fn RenderSpawnDxva2
100 */
RenderSpawnDxva2()101 VideoRenderBase *RenderSpawnDxva2()
102 {
103     return new dxvaRender();
104 }
105 
106 /**
107     \fn dxvaRender
108 */
dxvaRender()109 dxvaRender::dxvaRender()
110 {
111     ADM_info("creating Dxva2/D3D render.\n");
112     useYV12=false;
113     mySurface=NULL;
114     myYV12Surface=NULL;
115     videoBuffer=NULL;
116     videoWidget=NULL;
117     d3dHandle=admD3D::getHandle();
118 }
119 /**
120     \fn dxvaRender
121 */
~dxvaRender()122 dxvaRender::~dxvaRender()
123 {
124     if(videoWidget)
125     {
126         videoWidget->useExternalRedraw(false); // reactivate Qt double buffering
127         videoWidget->setDrawer(NULL);
128         videoWidget=NULL;
129     }
130     ADM_info("dxva2/D3D clean up\n");
131     cleanup();
132     ADM_info("dxva2/D3D cleaned up\n");
133 }
134 
135 /**
136     \fn stop
137 */
stop(void)138 bool dxvaRender::stop(void)
139 {
140     ADM_info("stopping dxva2/D3D render.\n");
141     return true;
142 }
143 /**
144     \fn changeZoom
145 */
changeZoom(float newZoom)146 bool dxvaRender::changeZoom(float newZoom)
147 {
148         ADM_info("changing zoom, dxva/d3D render.\n");
149         calcDisplayFromZoom(newZoom);
150         currentZoom=newZoom;
151         cleanup();
152         setup();
153         return false;
154 }
155 
156 
157 /**
158     \fn changeZoom
159 */
init(GUI_WindowInfo * window,uint32_t w,uint32_t h,float zoom)160 bool dxvaRender::init( GUI_WindowInfo *window, uint32_t w, uint32_t h, float zoom)
161 {
162     ADM_info("Initializing dxva2/D3D render\n");
163     info=*window;
164     baseInit(w,h,zoom);
165 
166     windowId=(HWND)window->systemWindowId;
167 
168     if(!d3dHandle)
169     {
170         ADM_warning("No D3DHandle\n");
171         return false;
172     }
173 
174     if (ADM_FAILED(D3DCall(IDirect3D9,GetAdapterDisplayMode,d3dHandle,
175                                                 D3DADAPTER_DEFAULT,
176                                                 &displayMode)))
177     {
178         ADM_warning("Dxv2/D3D Render: Cannot get display mode\n");
179         return 0;
180     }
181 
182     D3DCAPS9 deviceCapabilities;
183     ADM_info("D3D Checking device capabilities\n");
184     if (ADM_FAILED(D3DCall(IDirect3D9,GetDeviceCaps,d3dHandle,
185                                         D3DADAPTER_DEFAULT,
186                                         D3DDEVTYPE_HAL,
187                                         &deviceCapabilities)))
188     {
189       ADM_warning("Cannot get device capabilities");
190       return false;
191     }
192     int texture = deviceCapabilities.TextureCaps;
193     ADM_info("Power of 2 : %d\n",  (texture & D3DPTEXTURECAPS_POW2) &&   !(texture & D3DPTEXTURECAPS_NONPOW2CONDITIONAL));
194     ADM_info("Square only: %d\n",  (texture & D3DPTEXTURECAPS_SQUAREONLY));
195 
196 
197 
198       // Check if we support YV12
199     D3DFORMAT fmt=displayMode.Format;
200     D3DFORMAT yv12=(D3DFORMAT)MAKEFOURCC('Y','V','1','2');
201     if (ADM_FAILED(D3DCall(IDirect3D9,CheckDeviceFormatConversion, d3dHandle, // adapter
202                                                          D3DADAPTER_DEFAULT, // device type
203                                                          D3DDEVTYPE_HAL, // adapter format
204                                                          yv12, // render target format
205                                                          fmt)))  // depth stencil format
206     {
207         useYV12=false;
208         ADM_info("D3D YV12 not supported\n");
209     }
210     else
211     {
212         useYV12=true;
213         ADM_info("D3D YV12 is supported\n");
214     }
215 
216 
217 
218     if(!setup())
219     {
220       ADM_warning("Dxva/D3D setup failed\n");
221       return false;
222     }
223     videoWidget=(ADM_Qvideo *)info.widget;
224     videoWidget->useExternalRedraw(true); // deactivate Qt Double buffering
225     videoWidget->setDrawer(this);
226 
227     ADM_info("Dxva (D3D) init successful, dxva render. w=%d, h=%d, zoom=%.4f, displayWidth=%d, displayHeight=%d\n",(int)w,(int)h,zoom,(int)displayWidth,(int)displayHeight);
228     return true;
229 }
230 
231 /**
232   \fn setup
233   \brief Allocate stuff for a given display with/height. It is called again each time the zoom is changed
234 */
setup()235 bool dxvaRender::setup()
236 {
237   D3DVIEWPORT9 viewPort = {0, 0, displayWidth, displayHeight, 0, 1};
238 
239 
240      ADM_info("D3D (re)Setting up \n");
241      D3DPRESENT_PARAMETERS presentationParameters;
242      memset(&presentationParameters, 0, sizeof(presentationParameters));
243      presentationParameters.Windowed               = TRUE;
244      presentationParameters.SwapEffect             = D3DSWAPEFFECT_DISCARD; // We could use copy, but discard seems faster according to ms
245      presentationParameters.Flags                  = D3DPRESENTFLAG_VIDEO;
246      presentationParameters.hDeviceWindow          = windowId;
247      presentationParameters.BackBufferWidth        = displayWidth;
248      presentationParameters.BackBufferHeight       = displayHeight;
249      presentationParameters.MultiSampleType        = D3DMULTISAMPLE_NONE;
250      presentationParameters.PresentationInterval   = D3DPRESENT_INTERVAL_ONE;
251      presentationParameters.BackBufferFormat       = displayMode.Format;
252      presentationParameters.BackBufferCount        = 1;
253      presentationParameters.EnableAutoDepthStencil = FALSE;
254 
255      if( admD3D::isDirect9Ex())
256      {
257         IDirect3DDevice9Ex *d3d9deviceex = NULL;
258 
259         if(ADM_FAILED(D3DCall(IDirect3D9Ex,CreateDeviceEx,  ((IDirect3D9Ex *)d3dHandle),
260                                          D3DADAPTER_DEFAULT,
261                                          D3DDEVTYPE_HAL,  presentationParameters.hDeviceWindow,
262                                          D3DCREATE_SOFTWARE_VERTEXPROCESSING,
263                                          &presentationParameters,NULL, &d3d9deviceex)))
264         {
265           ADM_warning("Failed to create D3D9Ex render device\n");
266           d3dDevice=NULL;
267         }
268         else
269         {
270            d3dDevice=(IDirect3DDevice9 *)d3d9deviceex;
271            ADM_info("DXVA2Render : device created in D3D9Ex mode\n");
272         }
273      }
274 
275      if(!d3dDevice) // fallback if D3D9Ex fails or not available
276      {
277        if(ADM_FAILED(D3DCall(IDirect3D9,CreateDevice,  d3dHandle,
278                                          D3DADAPTER_DEFAULT,
279                                          D3DDEVTYPE_HAL,  presentationParameters.hDeviceWindow,
280                                          D3DCREATE_SOFTWARE_VERTEXPROCESSING,
281                                          &presentationParameters, &d3dDevice)))
282         {
283           ADM_warning("dxvaRender::Failed to create D3D render device\n");
284           return false;
285         }
286      }
287 
288       //
289 
290       D3DFORMAT yv12=(D3DFORMAT)MAKEFOURCC('Y','V','1','2');
291 
292       //
293        if( ADM_FAILED(D3DCall(IDirect3DDevice9,CreateOffscreenPlainSurface,
294                  d3dDevice, displayWidth,displayHeight,
295                  displayMode.Format, D3DPOOL_DEFAULT, &mySurface, NULL)))
296        {
297                   ADM_warning("D3D Cannot create surface\n");
298                   return false;
299        }
300        if( ADM_FAILED(D3DCall(IDirect3DDevice9,CreateOffscreenPlainSurface,
301                  d3dDevice, imageWidth,imageHeight,
302                  yv12, D3DPOOL_DEFAULT, &myYV12Surface, NULL)))
303        {
304                   ADM_warning("D3D Cannot create surface\n");
305                   return false;
306        }
307       // put some defaults
308       D3DCall(IDirect3DDevice9,SetRenderState,d3dDevice, D3DRS_SRCBLEND, D3DBLEND_ONE);
309       D3DCall(IDirect3DDevice9,SetRenderState,d3dDevice, D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
310       D3DCall(IDirect3DDevice9,SetRenderState,d3dDevice, D3DRS_ALPHAFUNC, D3DCMP_GREATER);
311       D3DCall(IDirect3DDevice9,SetRenderState,d3dDevice, D3DRS_ALPHAREF, (DWORD)0x0);
312       D3DCall(IDirect3DDevice9,SetRenderState,d3dDevice, D3DRS_LIGHTING, FALSE);
313       D3DCall(IDirect3DDevice9,SetSamplerState,d3dDevice, 0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
314       D3DCall(IDirect3DDevice9,SetSamplerState,d3dDevice, 0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
315       //
316 
317 
318   if(ADM_FAILED(D3DCall(IDirect3DDevice9,SetViewport,d3dDevice, &viewPort)))
319   {
320       ADM_warning("D3D Cannot set D3D viewport\n");
321       return false;
322   }
323 
324   scaler=new ADMColorScalerFull(ADM_CS_BICUBIC,imageWidth,imageHeight,displayWidth,displayHeight,
325           ADM_COLOR_YV12,
326           ADM_COLOR_RGB32A
327       );
328   videoBuffer=new uint8_t[displayWidth*displayHeight*4];
329   panScan.left  =0;
330   panScan.right =imageWidth-1;
331   panScan.top   =0;
332   panScan.bottom=imageHeight-1;
333 
334   targetRect.left  =0;
335   targetRect.right =displayWidth-1;
336   targetRect.top   =0;
337   targetRect.bottom=displayHeight-1;
338   ADM_info("Setup done\n");
339   return true;
340 }
341 /**
342   \fn cleanup
343   \brief cleanup all display dependant stuff, called again before switching zoom and at exit
344 
345 */
cleanup()346 bool dxvaRender::cleanup()
347 {
348     ADM_info("D3D cleanup\n");
349     if(scaler)
350     {
351       delete scaler;
352       scaler=NULL;
353     }
354     if(mySurface)
355     {
356         D3DCallNoArg(IDirect3DSurface9,Release,mySurface);
357         mySurface=NULL;
358     }
359     if(myYV12Surface)
360     {
361         D3DCallNoArg(IDirect3DSurface9,Release,myYV12Surface);
362         myYV12Surface=NULL;
363     }
364     if(d3dDevice)
365     {
366        D3DCallNoArg(IDirect3DDevice9,Release,d3dDevice);
367        d3dDevice=NULL;
368     }
369     if(videoBuffer)
370     {
371         delete [] videoBuffer;
372         videoBuffer=NULL;
373     }
374     return true;
375 }
376  /**
377  \fn refresh
378  \brief does not work correctly
379  */
refresh(void)380  bool dxvaRender::refresh(void)
381  {
382    ADM_info("Refresh**\n");
383 #if 0
384    if( ADM_FAILED(IDirect3DDevice9_Present(d3dDevice, &targetRect, 0, 0, 0)))
385    {
386         ADM_warning("D3D Present failed\n");
387    }
388 #else
389     D3DCallNoArg(IDirect3DDevice9,BeginScene,d3dDevice);
390     D3DCallNoArg(IDirect3DDevice9,EndScene,d3dDevice);
391     if( ADM_FAILED(D3DCall(IDirect3DDevice9,Present,d3dDevice, &targetRect, 0, 0, 0)))
392     {
393         ADM_warning("D3D Present failed\n");
394     }
395 #endif
396   return true;
397  }
398 /**
399   \fn draw
400   \brief callback from Qt saying we need to refresh
401 */
draw(QWidget * widget,QPaintEvent * ev)402 bool dxvaRender::draw(QWidget *widget, QPaintEvent *ev)
403 {
404   ADM_info("D3D : Draw!\n");
405   refresh();
406   return true;
407 }
408 
409 
410 /**
411     \fn d3dBlit
412     \brief blit one plane at a time from ADMImahe to surface
413 */
414 
d3dBlit(ADMImage * pic,ADM_PLANE plane,uint8_t * target,int targetPitch,int w,int h)415 static bool d3dBlit(ADMImage *pic,ADM_PLANE plane,uint8_t *target,int targetPitch,int w, int h)
416 {
417   uint8_t *y=pic->GetReadPtr(plane);
418   int pitch=pic->GetPitch(plane);
419     BitBlit(target,targetPitch,
420           y,pitch,
421           w,h);
422     return true;
423 }
424 /**
425   \fn ADMImage_To_yv12Surface
426 */
ADMImage_To_yv12Surface(ADMImage * pic,IDirect3DSurface9 * surface)427 static bool  ADMImage_To_yv12Surface(ADMImage *pic, IDirect3DSurface9 *surface)
428 {
429   D3DLOCKED_RECT     lock;;
430   if (ADM_FAILED(D3DCall(IDirect3DSurface9,LockRect,surface,&lock, NULL, 0)))
431   {
432       ADM_warning("D3D Cannot lock surface\n");
433       return false;
434   }
435 
436   // copy
437   uint8_t *dst=(uint8_t *)lock.pBits;
438   int  dStride=lock.Pitch;
439 
440   int width=pic->GetWidth(PLANAR_Y);
441   int height=pic->GetHeight(PLANAR_Y);
442 
443   d3dBlit(pic, PLANAR_Y,dst,dStride,width,height);
444 
445   dst+=height*dStride;
446   d3dBlit(pic, PLANAR_U,dst,dStride>>1,width>>1,height>>1);
447 
448   dst+=(height/2)*(dStride/2);
449   d3dBlit(pic, PLANAR_V,dst,dStride>>1,width>>1,height>>1);
450 
451   if (ADM_FAILED(D3DCallNoArg(IDirect3DSurface9,UnlockRect,surface)))
452   {
453       ADM_warning("D3D Cannot unlock surface\n");
454       return false;
455   }
456   return true;
457 }
458 /**
459   \fn ADMImage_To_argbSurface
460 */
ADMImage_To_argbSurface(ADMImage * pic,IDirect3DSurface9 * surface,ADMColorScalerFull * scaler)461 static bool  ADMImage_To_argbSurface(ADMImage *pic, IDirect3DSurface9 *surface,ADMColorScalerFull *scaler)
462 {
463     D3DLOCKED_RECT     lock;
464 
465     if (ADM_FAILED(D3DCall(IDirect3DSurface9,LockRect,surface,&lock, NULL, 0)))
466     {
467         ADM_warning("D3D Cannot lock surface\n");
468         return false;
469     }
470     // RGB
471     uint8_t *src[3];
472     uint8_t *dst[3];
473     pic->GetReadPlanes(src);
474     dst[0]=(uint8_t *)lock.pBits;
475     dst[1]=dst[2]=NULL;
476     int sourcePitch[3],dstPitch[3];
477     pic->GetPitches(sourcePitch);
478     dstPitch[0]=lock.Pitch;
479     dstPitch[1]=dstPitch[2]=0;
480     scaler-> convertPlanes(sourcePitch,dstPitch, src, dst);
481 
482     if (ADM_FAILED(IDirect3DSurface9_UnlockRect(surface)))
483     {
484         ADM_warning("D3D Cannot unlock surface\n");
485         return false;
486     }
487     return true;
488 }
489 
490 /**
491   \fn displayImage_yv12
492   \brief copy image to myV12 surface then convert from yv12 to display format in mySurface
493 
494 */
displayImage_yv12(ADMImage * pic)495 bool dxvaRender::displayImage_yv12(ADMImage *pic)
496 {
497     IDirect3DSurface9 *bBuffer;
498     // 1 upload to myYV12 surface
499     if(!ADMImage_To_yv12Surface(pic,myYV12Surface))
500     {
501       return false;
502     }
503    // upload....
504     if( ADM_FAILED(IDirect3DDevice9_GetBackBuffer(d3dDevice, 0, 0,
505                                               D3DBACKBUFFER_TYPE_MONO,
506                                               &bBuffer)))
507     {
508           ADM_warning("D3D Cannot create backBuffer\n");
509           return false;
510     }
511 
512 
513     // data are in YV12 surface, blit it to mySurface
514     // zoom and color conversion happen there
515 
516     if (ADM_FAILED(IDirect3DDevice9_StretchRect(d3dDevice,
517                     myYV12Surface,
518                     NULL,
519                     bBuffer,
520                     NULL,
521                     D3DTEXF_LINEAR)))
522                     {
523                            ADM_warning("StretchRec yv12 failed\n");
524                     }
525     IDirect3DDevice9_BeginScene(d3dDevice);
526     IDirect3DDevice9_EndScene(d3dDevice);
527     if( ADM_FAILED(IDirect3DDevice9_Present(d3dDevice, &targetRect, 0, 0, 0)))
528     {
529       ADM_warning("D3D Present failed\n");
530     }
531 
532     return true;
533 }
534 /**
535   \fn displayImage_argb
536   \brief manually do the yv12-> RGB conversion + rescale and the upload to backbuffer
537 */
displayImage_argb(ADMImage * pic)538 bool dxvaRender::displayImage_argb(ADMImage *pic)
539 {
540   IDirect3DSurface9 *bBuffer;
541   // 1 upload to myYV12 surface
542   if( ADM_FAILED(IDirect3DDevice9_GetBackBuffer(d3dDevice, 0, 0,
543                                             D3DBACKBUFFER_TYPE_MONO,
544                                             &bBuffer)))
545   {
546         ADM_warning("D3D Cannot create backBuffer\n");
547         return false;
548   }
549 
550   if(!ADMImage_To_argbSurface(pic,bBuffer,scaler))
551   {
552     ADM_warning("Image to argb surface failed\n");
553     return false;
554   }
555 
556   IDirect3DDevice9_BeginScene(d3dDevice);
557 
558   IDirect3DDevice9_EndScene(d3dDevice);
559   if( ADM_FAILED(IDirect3DDevice9_Present(d3dDevice, &targetRect, 0, 0, 0)))
560   {
561     ADM_warning("D3D Present failed\n");
562   }
563 
564   return true;
565 }
566 /**
567   \fn brief input is already a surface, in yv12 format
568 */
displayImage_surface(ADMImage * pic,admDx2Surface * surface)569 bool dxvaRender::displayImage_surface(ADMImage *pic,admDx2Surface *surface)
570 {
571 #if 0
572   // this does not work, both surfaces are coming from different device
573     IDirect3DSurface9 *bBuffer;
574     if( ADM_FAILED(D3DCall(IDirect3DDevice9,GetBackBuffer,d3dDevice, 0, 0,
575                                D3DBACKBUFFER_TYPE_MONO,
576                                &bBuffer)))
577     {
578         ADM_warning("D3D Cannot create backBuffer\n");
579         return false;
580     }
581     // OK
582     ADM_info("surface duplicated\n");
583 
584     // can we directly use the surface from dxva ? (can we at all ?)
585     if (ADM_FAILED(D3DCall(IDirect3DDevice9,StretchRect,d3dDevice,
586                                surface->surface,
587                                NULL,
588                                bBuffer,
589                                NULL,
590                                D3DTEXF_LINEAR)))
591     {
592         ADM_warning("StretchRec yv12 failed\n");
593     }else
594     {
595         IDirect3DDevice9_BeginScene(d3dDevice);
596         IDirect3DDevice9_EndScene(d3dDevice);
597         if( ADM_FAILED(IDirect3DDevice9_Present(d3dDevice, &targetRect, 0, 0, 0)))
598         {
599             ADM_warning("D3D Present failed\n");
600         }
601         return true;
602     }
603 #endif
604     // go to indirect route
605     if(!pic->hwDownloadFromRef())
606     {
607         ADM_warning("Failed to download yv12 from dxva\n");
608         return false;
609     }
610     // workaround : use default non bridged path
611     if(useYV12)
612         return displayImage_yv12(pic);
613     return displayImage_argb(pic);
614 }
615 
616 /**
617   \fn displayImage
618   \brief display an image
619 */
displayImage(ADMImage * pic)620 bool dxvaRender::displayImage(ADMImage *pic)
621 {
622   if(pic->refType==ADM_HW_DXVA)
623   {
624       aprintf("Source is already a surface (dxva2 input)\n");
625       admDx2Surface *surface=(admDx2Surface *)pic->refDescriptor.refHwImage;
626       return displayImage_surface(pic,surface);
627   }
628 
629 
630 #if 1
631   if(useYV12)
632 #else
633  if(0)
634 #endif
635   {
636       return displayImage_yv12(pic);
637   }
638   return displayImage_argb(pic);
639 }
640 
641 // EOF
642