1 /*
2  * This file is part of mpv.
3  *
4  * mpv is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * mpv is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with mpv.  If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 #include <windows.h>
19 #include <d3d9.h>
20 #include <d3d11.h>
21 #include <dxva2api.h>
22 
23 #include "common/common.h"
24 #include "osdep/windows_utils.h"
25 #include "video/hwdec.h"
26 #include "video/d3d.h"
27 #include "video/out/d3d11/ra_d3d11.h"
28 #include "video/out/gpu/hwdec.h"
29 
30 struct priv_owner {
31     struct mp_hwdec_ctx hwctx;
32     ID3D11Device *dev11;
33     IDirect3DDevice9Ex *dev9;
34 };
35 
36 struct queue_surf {
37     ID3D11Texture2D *tex11;
38     ID3D11Query *idle11;
39     ID3D11Texture2D *stage11;
40     IDirect3DTexture9 *tex9;
41     IDirect3DSurface9 *surf9;
42     IDirect3DSurface9 *stage9;
43     struct ra_tex *tex;
44 
45     bool busy11; // The surface is currently being used by D3D11
46 };
47 
48 struct priv {
49     ID3D11Device *dev11;
50     ID3D11DeviceContext *ctx11;
51     IDirect3DDevice9Ex *dev9;
52 
53     // Surface queue stuff. Following Microsoft recommendations, a queue of
54     // surfaces is used to share images between D3D9 and D3D11. This allows
55     // multiple D3D11 frames to be in-flight at once.
56     struct queue_surf **queue;
57     int queue_len;
58     int queue_pos;
59 };
60 
uninit(struct ra_hwdec * hw)61 static void uninit(struct ra_hwdec *hw)
62 {
63     struct priv_owner *p = hw->priv;
64     hwdec_devices_remove(hw->devs, &p->hwctx);
65     av_buffer_unref(&p->hwctx.av_device_ref);
66     SAFE_RELEASE(p->dev11);
67     SAFE_RELEASE(p->dev9);
68 }
69 
init(struct ra_hwdec * hw)70 static int init(struct ra_hwdec *hw)
71 {
72     struct priv_owner *p = hw->priv;
73     IDirect3D9Ex *d3d9ex = NULL;
74     int ret = -1;
75     HRESULT hr;
76 
77     if (!ra_is_d3d11(hw->ra))
78         goto done;
79     p->dev11 = ra_d3d11_get_device(hw->ra);
80     if (!p->dev11)
81         goto done;
82 
83     d3d_load_dlls();
84     if (!d3d9_dll) {
85         MP_FATAL(hw, "Failed to load \"d3d9.dll\": %s\n", mp_LastError_to_str());
86         goto done;
87     }
88     if (!dxva2_dll) {
89         MP_FATAL(hw, "Failed to load \"dxva2.dll\": %s\n", mp_LastError_to_str());
90         goto done;
91     }
92 
93     HRESULT (WINAPI *Direct3DCreate9Ex)(UINT SDKVersion, IDirect3D9Ex **ppD3D);
94     Direct3DCreate9Ex = (void *)GetProcAddress(d3d9_dll, "Direct3DCreate9Ex");
95     if (!Direct3DCreate9Ex) {
96         MP_FATAL(hw, "Direct3D 9Ex not supported\n");
97         goto done;
98     }
99 
100     hr = Direct3DCreate9Ex(D3D_SDK_VERSION, &d3d9ex);
101     if (FAILED(hr)) {
102         MP_FATAL(hw, "Couldn't create Direct3D9Ex: %s\n", mp_HRESULT_to_str(hr));
103         goto done;
104     }
105 
106     D3DPRESENT_PARAMETERS pparams = {
107         .BackBufferWidth = 16,
108         .BackBufferHeight = 16,
109         .BackBufferCount = 1,
110         .SwapEffect = D3DSWAPEFFECT_DISCARD,
111         .hDeviceWindow = GetDesktopWindow(),
112         .Windowed = TRUE,
113         .Flags = D3DPRESENTFLAG_VIDEO,
114     };
115     hr = IDirect3D9Ex_CreateDeviceEx(d3d9ex, D3DADAPTER_DEFAULT,
116         D3DDEVTYPE_HAL, GetDesktopWindow(), D3DCREATE_NOWINDOWCHANGES |
117         D3DCREATE_FPU_PRESERVE | D3DCREATE_HARDWARE_VERTEXPROCESSING |
118         D3DCREATE_DISABLE_PSGP_THREADING | D3DCREATE_MULTITHREADED, &pparams,
119         NULL, &p->dev9);
120     if (FAILED(hr)) {
121         MP_FATAL(hw, "Failed to create Direct3D9Ex device: %s\n",
122                  mp_HRESULT_to_str(hr));
123         goto done;
124     }
125 
126     // Check if it's possible to StretchRect() from NV12 to XRGB surfaces
127     hr = IDirect3D9Ex_CheckDeviceFormatConversion(d3d9ex, D3DADAPTER_DEFAULT,
128         D3DDEVTYPE_HAL, MAKEFOURCC('N', 'V', '1', '2'), D3DFMT_X8R8G8B8);
129     if (hr != S_OK) {
130         MP_FATAL(hw, "Can't StretchRect from NV12 to XRGB surfaces\n");
131         goto done;
132     }
133 
134     p->hwctx = (struct mp_hwdec_ctx){
135         .driver_name = hw->driver->name,
136         .av_device_ref = d3d9_wrap_device_ref((IDirect3DDevice9 *)p->dev9),
137     };
138     hwdec_devices_add(hw->devs, &p->hwctx);
139 
140     ret = 0;
141 done:
142     SAFE_RELEASE(d3d9ex);
143     return ret;
144 }
145 
mapper_init(struct ra_hwdec_mapper * mapper)146 static int mapper_init(struct ra_hwdec_mapper *mapper)
147 {
148     struct priv_owner *o = mapper->owner->priv;
149     struct priv *p = mapper->priv;
150 
151     ID3D11Device_AddRef(o->dev11);
152     p->dev11 = o->dev11;
153     IDirect3DDevice9Ex_AddRef(o->dev9);
154     p->dev9 = o->dev9;
155     ID3D11Device_GetImmediateContext(o->dev11, &p->ctx11);
156 
157     mapper->dst_params = mapper->src_params;
158     mapper->dst_params.imgfmt = IMGFMT_RGB0;
159     mapper->dst_params.hw_subfmt = 0;
160     return 0;
161 }
162 
surf_destroy(struct ra_hwdec_mapper * mapper,struct queue_surf * surf)163 static void surf_destroy(struct ra_hwdec_mapper *mapper,
164                          struct queue_surf *surf)
165 {
166     if (!surf)
167         return;
168     SAFE_RELEASE(surf->tex11);
169     SAFE_RELEASE(surf->idle11);
170     SAFE_RELEASE(surf->stage11);
171     SAFE_RELEASE(surf->tex9);
172     SAFE_RELEASE(surf->surf9);
173     SAFE_RELEASE(surf->stage9);
174     ra_tex_free(mapper->ra, &surf->tex);
175     talloc_free(surf);
176 }
177 
surf_create(struct ra_hwdec_mapper * mapper)178 static struct queue_surf *surf_create(struct ra_hwdec_mapper *mapper)
179 {
180     struct priv *p = mapper->priv;
181     IDXGIResource *res11 = NULL;
182     bool success = false;
183     HRESULT hr;
184 
185     struct queue_surf *surf = talloc_ptrtype(p, surf);
186 
187     D3D11_TEXTURE2D_DESC desc11 = {
188         .Width = mapper->src->w,
189         .Height = mapper->src->h,
190         .MipLevels = 1,
191         .ArraySize = 1,
192         .Format = DXGI_FORMAT_B8G8R8X8_UNORM,
193         .SampleDesc.Count = 1,
194         .Usage = D3D11_USAGE_DEFAULT,
195         .BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET,
196         .MiscFlags = D3D11_RESOURCE_MISC_SHARED,
197     };
198     hr = ID3D11Device_CreateTexture2D(p->dev11, &desc11, NULL, &surf->tex11);
199     if (FAILED(hr)) {
200         MP_ERR(mapper, "Failed to create D3D11 texture: %s\n",
201                mp_HRESULT_to_str(hr));
202         goto done;
203     }
204 
205     // Try to use a 16x16 staging texture, unless the source surface is
206     // smaller. Ideally, a 1x1 texture would be sufficient, but Microsoft's
207     // D3D9ExDXGISharedSurf example uses 16x16 to avoid driver bugs.
208     D3D11_TEXTURE2D_DESC sdesc11 = {
209         .Width = MPMIN(16, desc11.Width),
210         .Height = MPMIN(16, desc11.Height),
211         .MipLevels = 1,
212         .ArraySize = 1,
213         .Format = DXGI_FORMAT_B8G8R8X8_UNORM,
214         .SampleDesc.Count = 1,
215         .Usage = D3D11_USAGE_STAGING,
216         .CPUAccessFlags = D3D11_CPU_ACCESS_READ,
217     };
218     hr = ID3D11Device_CreateTexture2D(p->dev11, &sdesc11, NULL, &surf->stage11);
219     if (FAILED(hr)) {
220         MP_ERR(mapper, "Failed to create D3D11 staging texture: %s\n",
221                mp_HRESULT_to_str(hr));
222         goto done;
223     }
224 
225     hr = ID3D11Texture2D_QueryInterface(surf->tex11, &IID_IDXGIResource,
226                                         (void**)&res11);
227     if (FAILED(hr)) {
228         MP_ERR(mapper, "Failed to get share handle: %s\n",
229                mp_HRESULT_to_str(hr));
230         goto done;
231     }
232 
233     HANDLE share_handle;
234     hr = IDXGIResource_GetSharedHandle(res11, &share_handle);
235     if (FAILED(hr)) {
236         MP_ERR(mapper, "Failed to get share handle: %s\n",
237                mp_HRESULT_to_str(hr));
238         goto done;
239     }
240 
241     hr = ID3D11Device_CreateQuery(p->dev11,
242         &(D3D11_QUERY_DESC) { D3D11_QUERY_EVENT }, &surf->idle11);
243     if (FAILED(hr)) {
244         MP_ERR(mapper, "Failed to create D3D11 query: %s\n",
245                mp_HRESULT_to_str(hr));
246         goto done;
247     }
248 
249     // Share the D3D11 texture with D3D9Ex
250     hr = IDirect3DDevice9Ex_CreateTexture(p->dev9, desc11.Width, desc11.Height,
251         1, D3DUSAGE_RENDERTARGET, D3DFMT_X8R8G8B8, D3DPOOL_DEFAULT,
252         &surf->tex9, &share_handle);
253     if (FAILED(hr)) {
254         MP_ERR(mapper, "Failed to create D3D9 texture: %s\n",
255                mp_HRESULT_to_str(hr));
256         goto done;
257     }
258 
259     hr = IDirect3DTexture9_GetSurfaceLevel(surf->tex9, 0, &surf->surf9);
260     if (FAILED(hr)) {
261         MP_ERR(mapper, "Failed to get D3D9 surface: %s\n",
262                mp_HRESULT_to_str(hr));
263         goto done;
264     }
265 
266     // As above, try to use a 16x16 staging texture to avoid driver bugs
267     hr = IDirect3DDevice9Ex_CreateRenderTarget(p->dev9,
268         MPMIN(16, desc11.Width), MPMIN(16, desc11.Height), D3DFMT_X8R8G8B8,
269         D3DMULTISAMPLE_NONE, 0, TRUE, &surf->stage9, NULL);
270     if (FAILED(hr)) {
271         MP_ERR(mapper, "Failed to create D3D9 staging surface: %s\n",
272                mp_HRESULT_to_str(hr));
273         goto done;
274     }
275 
276     surf->tex = ra_d3d11_wrap_tex(mapper->ra, (ID3D11Resource *)surf->tex11);
277     if (!surf->tex)
278         goto done;
279 
280     success = true;
281 done:
282     if (!success)
283         surf_destroy(mapper, surf);
284     SAFE_RELEASE(res11);
285     return success ? surf : NULL;
286 }
287 
288 // true if the surface is currently in-use by the D3D11 graphics pipeline
surf_is_idle11(struct ra_hwdec_mapper * mapper,struct queue_surf * surf)289 static bool surf_is_idle11(struct ra_hwdec_mapper *mapper,
290                            struct queue_surf *surf)
291 {
292     struct priv *p = mapper->priv;
293     HRESULT hr;
294     BOOL idle;
295 
296     if (!surf->busy11)
297         return true;
298 
299     hr = ID3D11DeviceContext_GetData(p->ctx11,
300         (ID3D11Asynchronous *)surf->idle11, &idle, sizeof(idle),
301         D3D11_ASYNC_GETDATA_DONOTFLUSH);
302     if (FAILED(hr) || hr == S_FALSE || !idle)
303         return false;
304 
305     surf->busy11 = false;
306     return true;
307 }
308 
309 // If the surface is currently in-use by the D3D11 graphics pipeline, wait for
310 // it to become idle. Should only be called in the queue-underflow case.
surf_wait_idle11(struct ra_hwdec_mapper * mapper,struct queue_surf * surf)311 static bool surf_wait_idle11(struct ra_hwdec_mapper *mapper,
312                              struct queue_surf *surf)
313 {
314     struct priv *p = mapper->priv;
315     HRESULT hr;
316 
317     ID3D11DeviceContext_CopySubresourceRegion(p->ctx11,
318         (ID3D11Resource *)surf->stage11, 0, 0, 0, 0,
319         (ID3D11Resource *)surf->tex11, 0, (&(D3D11_BOX){
320             .right = MPMIN(16, mapper->src->w),
321             .bottom = MPMIN(16, mapper->src->h),
322             .back = 1,
323         }));
324 
325     // Block until the surface becomes idle (see surf_wait_idle9())
326     D3D11_MAPPED_SUBRESOURCE map = {0};
327     hr = ID3D11DeviceContext_Map(p->ctx11, (ID3D11Resource *)surf->stage11, 0,
328                                  D3D11_MAP_READ, 0, &map);
329     if (FAILED(hr)) {
330         MP_ERR(mapper, "Couldn't map D3D11 staging texture: %s\n",
331                mp_HRESULT_to_str(hr));
332         return false;
333     }
334 
335     ID3D11DeviceContext_Unmap(p->ctx11, (ID3D11Resource *)surf->stage11, 0);
336     surf->busy11 = false;
337     return true;
338 }
339 
surf_wait_idle9(struct ra_hwdec_mapper * mapper,struct queue_surf * surf)340 static bool surf_wait_idle9(struct ra_hwdec_mapper *mapper,
341                             struct queue_surf *surf)
342 {
343     struct priv *p = mapper->priv;
344     HRESULT hr;
345 
346     // Rather than polling for the surface to become idle, copy part of the
347     // surface to a staging texture and map it. This should block until the
348     // surface becomes idle. Microsoft's ISurfaceQueue does this as well.
349     RECT rc = {0, 0, MPMIN(16, mapper->src->w), MPMIN(16, mapper->src->h)};
350     hr = IDirect3DDevice9Ex_StretchRect(p->dev9, surf->surf9, &rc, surf->stage9,
351                                         &rc, D3DTEXF_NONE);
352     if (FAILED(hr)) {
353         MP_ERR(mapper, "Couldn't copy to D3D9 staging texture: %s\n",
354                mp_HRESULT_to_str(hr));
355         return false;
356     }
357 
358     D3DLOCKED_RECT lock;
359     hr = IDirect3DSurface9_LockRect(surf->stage9, &lock, NULL, D3DLOCK_READONLY);
360     if (FAILED(hr)) {
361         MP_ERR(mapper, "Couldn't map D3D9 staging texture: %s\n",
362                mp_HRESULT_to_str(hr));
363         return false;
364     }
365 
366     IDirect3DSurface9_UnlockRect(surf->stage9);
367     p->queue[p->queue_pos]->busy11 = true;
368     return true;
369 }
370 
surf_acquire(struct ra_hwdec_mapper * mapper)371 static struct queue_surf *surf_acquire(struct ra_hwdec_mapper *mapper)
372 {
373     struct priv *p = mapper->priv;
374 
375     if (!p->queue_len || !surf_is_idle11(mapper, p->queue[p->queue_pos])) {
376         if (p->queue_len < 16) {
377             struct queue_surf *surf = surf_create(mapper);
378             if (!surf)
379                 return NULL;
380 
381             // The next surface is busy, so grow the queue
382             MP_TARRAY_INSERT_AT(p, p->queue, p->queue_len, p->queue_pos, surf);
383             MP_DBG(mapper, "Queue grew to %d surfaces\n", p->queue_len);
384         } else {
385             // For sanity, don't let the queue grow beyond 16 surfaces. It
386             // should never get this big. If it does, wait for the surface to
387             // become idle rather than polling it.
388             if (!surf_wait_idle11(mapper, p->queue[p->queue_pos]))
389                 return NULL;
390             MP_WARN(mapper, "Queue underflow!\n");
391         }
392     }
393     return p->queue[p->queue_pos];
394 }
395 
surf_release(struct ra_hwdec_mapper * mapper)396 static void surf_release(struct ra_hwdec_mapper *mapper)
397 {
398     struct priv *p = mapper->priv;
399     ID3D11DeviceContext_End(p->ctx11,
400         (ID3D11Asynchronous *)p->queue[p->queue_pos]->idle11);
401 
402     // The current surface is now in-flight, move to the next surface
403     p->queue_pos++;
404     if (p->queue_pos >= p->queue_len)
405         p->queue_pos = 0;
406 }
407 
mapper_uninit(struct ra_hwdec_mapper * mapper)408 static void mapper_uninit(struct ra_hwdec_mapper *mapper)
409 {
410     struct priv *p = mapper->priv;
411 
412     for (int i = 0; i < p->queue_len; i++)
413         surf_destroy(mapper, p->queue[i]);
414 
415     SAFE_RELEASE(p->ctx11);
416     SAFE_RELEASE(p->dev9);
417     SAFE_RELEASE(p->dev11);
418 }
419 
mapper_map(struct ra_hwdec_mapper * mapper)420 static int mapper_map(struct ra_hwdec_mapper *mapper)
421 {
422     struct priv *p = mapper->priv;
423     HRESULT hr;
424 
425     struct queue_surf *surf = surf_acquire(mapper);
426     if (!surf)
427         return -1;
428 
429     RECT rc = {0, 0, mapper->src->w, mapper->src->h};
430     IDirect3DSurface9* hw_surface = (IDirect3DSurface9 *)mapper->src->planes[3];
431 
432     hr = IDirect3DDevice9Ex_StretchRect(p->dev9, hw_surface, &rc, surf->surf9,
433                                         &rc, D3DTEXF_NONE);
434     if (FAILED(hr)) {
435         MP_ERR(mapper, "StretchRect() failed: %s\n", mp_HRESULT_to_str(hr));
436         return -1;
437     }
438 
439     if (!surf_wait_idle9(mapper, surf))
440         return -1;
441 
442     mapper->tex[0] = surf->tex;
443     return 0;
444 }
445 
mapper_unmap(struct ra_hwdec_mapper * mapper)446 static void mapper_unmap(struct ra_hwdec_mapper *mapper)
447 {
448     struct priv *p = mapper->priv;
449 
450     if (p->queue_pos < p->queue_len &&
451         p->queue[p->queue_pos]->tex == mapper->tex[0])
452     {
453         surf_release(mapper);
454         mapper->tex[0] = NULL;
455     }
456 }
457 
458 const struct ra_hwdec_driver ra_hwdec_dxva2dxgi = {
459     .name = "dxva2-dxgi",
460     .priv_size = sizeof(struct priv_owner),
461     .imgfmts = {IMGFMT_DXVA2, 0},
462     .init = init,
463     .uninit = uninit,
464     .mapper = &(const struct ra_hwdec_mapper_driver){
465         .priv_size = sizeof(struct priv),
466         .init = mapper_init,
467         .uninit = mapper_uninit,
468         .map = mapper_map,
469         .unmap = mapper_unmap,
470     },
471 };
472