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