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