1 /*
2 * Copyright (c) 2008 Georgi Petrov (gogothebee) <gogothebee@gmail.com>
3 *
4 * This file is part of mpv.
5 *
6 * mpv is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * mpv is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with mpv. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include <windows.h>
21 #include <errno.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <math.h>
25 #include <stdbool.h>
26 #include <assert.h>
27 #include <d3d9.h>
28 #include <inttypes.h>
29 #include <limits.h>
30 #include "config.h"
31 #include "options/options.h"
32 #include "options/m_option.h"
33 #include "sub/draw_bmp.h"
34 #include "mpv_talloc.h"
35 #include "vo.h"
36 #include "video/csputils.h"
37 #include "video/mp_image.h"
38 #include "video/img_format.h"
39 #include "common/msg.h"
40 #include "common/common.h"
41 #include "w32_common.h"
42 #include "sub/osd.h"
43
44 #include "config.h"
45 #if !HAVE_GPL
46 #error GPL only
47 #endif
48
49 #define DEVTYPE D3DDEVTYPE_HAL
50 //#define DEVTYPE D3DDEVTYPE_REF
51
52 #define D3DFVF_OSD_VERTEX (D3DFVF_XYZ | D3DFVF_TEX1)
53
54 typedef struct {
55 float x, y, z;
56 float tu, tv;
57 } vertex_osd;
58
59 struct d3dtex {
60 // user-requested size
61 int w, h;
62 // allocated texture size
63 int tex_w, tex_h;
64 // D3DPOOL_SYSTEMMEM (or others) texture:
65 // - can be locked in order to write (and even read) data
66 // - can _not_ (probably) be used as texture for rendering
67 // This is always non-NULL if d3dtex_allocate succeeds.
68 IDirect3DTexture9 *system;
69 // D3DPOOL_DEFAULT texture:
70 // - can't be locked (Probably.)
71 // - must be used for rendering
72 // This can be NULL if the system one can be both locked and mapped.
73 IDirect3DTexture9 *device;
74 };
75
76 #define MAX_OSD_RECTS 64
77
78 /* Global variables "priv" structure. I try to keep their count low.
79 */
80 typedef struct d3d_priv {
81 struct mp_log *log;
82
83 int opt_disable_texture_align;
84 // debugging
85 int opt_force_power_of_2;
86 int opt_texture_memory;
87 int opt_swap_discard;
88 int opt_exact_backbuffer;
89
90 struct vo *vo;
91
92 bool have_image;
93 double osd_pts;
94
95 D3DLOCKED_RECT locked_rect; /**< The locked offscreen surface */
96 RECT fs_movie_rect; /**< Rect (upscaled) of the movie when displayed
97 in fullscreen */
98 RECT fs_panscan_rect; /**< PanScan source surface cropping in
99 fullscreen */
100 int src_width; /**< Source (movie) width */
101 int src_height; /**< Source (movie) heigth */
102 struct mp_osd_res osd_res;
103 int image_format; /**< mplayer image format */
104 struct mp_image_params params;
105
106 D3DFORMAT movie_src_fmt; /**< Movie colorspace format (depends on
107 the movie's codec) */
108 D3DFORMAT desktop_fmt; /**< Desktop (screen) colorspace format.
109 Usually XRGB */
110
111 HANDLE d3d9_dll; /**< d3d9 Library HANDLE */
112 IDirect3D9 * (WINAPI *pDirect3DCreate9)(UINT); /**< pointer to Direct3DCreate9 function */
113
114 LPDIRECT3D9 d3d_handle; /**< Direct3D Handle */
115 LPDIRECT3DDEVICE9 d3d_device; /**< The Direct3D Adapter */
116 bool d3d_in_scene; /**< BeginScene was called, EndScene not */
117 IDirect3DSurface9 *d3d_surface; /**< Offscreen Direct3D Surface. MPlayer
118 renders inside it. Uses colorspace
119 priv->movie_src_fmt */
120 IDirect3DSurface9 *d3d_backbuf; /**< Video card's back buffer (used to
121 display next frame) */
122 int cur_backbuf_width; /**< Current backbuffer width */
123 int cur_backbuf_height; /**< Current backbuffer height */
124 int device_caps_power2_only; /**< 1 = texture sizes have to be power 2
125 0 = texture sizes can be anything */
126 int device_caps_square_only; /**< 1 = textures have to be square
127 0 = textures do not have to be square */
128 int device_texture_sys; /**< 1 = device can texture from system memory
129 0 = device requires shadow */
130 int max_texture_width; /**< from the device capabilities */
131 int max_texture_height; /**< from the device capabilities */
132
133 D3DMATRIX d3d_colormatrix;
134
135 struct mp_draw_sub_cache *osd_cache;
136 struct d3dtex osd_texture;
137 int osd_num_vertices;
138 vertex_osd osd_vertices[MAX_OSD_RECTS * 6];
139 } d3d_priv;
140
141 struct fmt_entry {
142 const unsigned int mplayer_fmt; /**< Given by MPlayer */
143 const D3DFORMAT fourcc; /**< Required by D3D's test function */
144 };
145
146 /* Map table from reported MPlayer format to the required
147 fourcc. This is needed to perform the format query. */
148
149 static const struct fmt_entry fmt_table[] = {
150 // planar YUV
151 {IMGFMT_420P, MAKEFOURCC('Y','V','1','2')},
152 {IMGFMT_420P, MAKEFOURCC('I','4','2','0')},
153 {IMGFMT_420P, MAKEFOURCC('I','Y','U','V')},
154 {IMGFMT_NV12, MAKEFOURCC('N','V','1','2')},
155 // packed YUV
156 {IMGFMT_UYVY, D3DFMT_UYVY},
157 // packed RGB
158 {IMGFMT_BGR0, D3DFMT_X8R8G8B8},
159 {IMGFMT_RGB0, D3DFMT_X8B8G8R8},
160 {IMGFMT_BGR24, D3DFMT_R8G8B8}, //untested
161 {IMGFMT_RGB565, D3DFMT_R5G6B5},
162 {0},
163 };
164
165
166 static bool resize_d3d(d3d_priv *priv);
167 static void uninit(struct vo *vo);
168 static void flip_page(struct vo *vo);
169 static mp_image_t *get_window_screenshot(d3d_priv *priv);
170 static void draw_osd(struct vo *vo);
171 static bool change_d3d_backbuffer(d3d_priv *priv);
172
d3d_matrix_identity(D3DMATRIX * m)173 static void d3d_matrix_identity(D3DMATRIX *m)
174 {
175 memset(m, 0, sizeof(D3DMATRIX));
176 m->_11 = m->_22 = m->_33 = m->_44 = 1.0f;
177 }
178
d3d_matrix_ortho(D3DMATRIX * m,float left,float right,float bottom,float top)179 static void d3d_matrix_ortho(D3DMATRIX *m, float left, float right,
180 float bottom, float top)
181 {
182 d3d_matrix_identity(m);
183 m->_11 = 2.0f / (right - left);
184 m->_22 = 2.0f / (top - bottom);
185 m->_33 = 1.0f;
186 m->_41 = -(right + left) / (right - left);
187 m->_42 = -(top + bottom) / (top - bottom);
188 m->_43 = 0;
189 m->_44 = 1.0f;
190 }
191
192 /****************************************************************************
193 * *
194 * *
195 * *
196 * Direct3D specific implementation functions *
197 * *
198 * *
199 * *
200 ****************************************************************************/
201
d3d_begin_scene(d3d_priv * priv)202 static bool d3d_begin_scene(d3d_priv *priv)
203 {
204 if (!priv->d3d_in_scene) {
205 if (FAILED(IDirect3DDevice9_BeginScene(priv->d3d_device))) {
206 MP_ERR(priv, "BeginScene failed.\n");
207 return false;
208 }
209 priv->d3d_in_scene = true;
210 }
211 return true;
212 }
213
214 /** @brief Calculate scaled fullscreen movie rectangle with
215 * preserved aspect ratio.
216 */
calc_fs_rect(d3d_priv * priv)217 static void calc_fs_rect(d3d_priv *priv)
218 {
219 struct mp_rect src_rect;
220 struct mp_rect dst_rect;
221 vo_get_src_dst_rects(priv->vo, &src_rect, &dst_rect, &priv->osd_res);
222
223 priv->fs_movie_rect.left = dst_rect.x0;
224 priv->fs_movie_rect.right = dst_rect.x1;
225 priv->fs_movie_rect.top = dst_rect.y0;
226 priv->fs_movie_rect.bottom = dst_rect.y1;
227 priv->fs_panscan_rect.left = src_rect.x0;
228 priv->fs_panscan_rect.right = src_rect.x1;
229 priv->fs_panscan_rect.top = src_rect.y0;
230 priv->fs_panscan_rect.bottom = src_rect.y1;
231 }
232
233 // Adjust the texture size *width/*height to fit the requirements of the D3D
234 // device. The texture size is only increased.
d3d_fix_texture_size(d3d_priv * priv,int * width,int * height)235 static void d3d_fix_texture_size(d3d_priv *priv, int *width, int *height)
236 {
237 int tex_width = *width;
238 int tex_height = *height;
239
240 // avoid nasty special cases with 0-sized textures and texture sizes
241 tex_width = MPMAX(tex_width, 1);
242 tex_height = MPMAX(tex_height, 1);
243
244 if (priv->device_caps_power2_only) {
245 tex_width = 1;
246 tex_height = 1;
247 while (tex_width < *width) tex_width <<= 1;
248 while (tex_height < *height) tex_height <<= 1;
249 }
250 if (priv->device_caps_square_only)
251 /* device only supports square textures */
252 tex_width = tex_height = MPMAX(tex_width, tex_height);
253 // better round up to a multiple of 16
254 if (!priv->opt_disable_texture_align) {
255 tex_width = (tex_width + 15) & ~15;
256 tex_height = (tex_height + 15) & ~15;
257 }
258
259 *width = tex_width;
260 *height = tex_height;
261 }
262
d3dtex_release(d3d_priv * priv,struct d3dtex * tex)263 static void d3dtex_release(d3d_priv *priv, struct d3dtex *tex)
264 {
265 if (tex->system)
266 IDirect3DTexture9_Release(tex->system);
267 tex->system = NULL;
268
269 if (tex->device)
270 IDirect3DTexture9_Release(tex->device);
271 tex->device = NULL;
272
273 tex->tex_w = tex->tex_h = 0;
274 }
275
d3dtex_allocate(d3d_priv * priv,struct d3dtex * tex,D3DFORMAT fmt,int w,int h)276 static bool d3dtex_allocate(d3d_priv *priv, struct d3dtex *tex, D3DFORMAT fmt,
277 int w, int h)
278 {
279 d3dtex_release(priv, tex);
280
281 tex->w = w;
282 tex->h = h;
283
284 int tw = w, th = h;
285 d3d_fix_texture_size(priv, &tw, &th);
286
287 bool use_sh = !priv->device_texture_sys;
288 int memtype = D3DPOOL_SYSTEMMEM;
289 switch (priv->opt_texture_memory) {
290 case 1: memtype = D3DPOOL_MANAGED; use_sh = false; break;
291 case 2: memtype = D3DPOOL_DEFAULT; use_sh = false; break;
292 case 3: memtype = D3DPOOL_DEFAULT; use_sh = true; break;
293 case 4: memtype = D3DPOOL_SCRATCH; use_sh = true; break;
294 }
295
296 if (FAILED(IDirect3DDevice9_CreateTexture(priv->d3d_device, tw, th, 1,
297 D3DUSAGE_DYNAMIC, fmt, memtype, &tex->system, NULL)))
298 {
299 MP_ERR(priv, "Allocating %dx%d texture in system RAM failed.\n", w, h);
300 goto error_exit;
301 }
302
303 if (use_sh) {
304 if (FAILED(IDirect3DDevice9_CreateTexture(priv->d3d_device, tw, th, 1,
305 D3DUSAGE_DYNAMIC, fmt, D3DPOOL_DEFAULT, &tex->device, NULL)))
306 {
307 MP_ERR(priv, "Allocating %dx%d texture in video RAM failed.\n", w, h);
308 goto error_exit;
309 }
310 }
311
312 tex->tex_w = tw;
313 tex->tex_h = th;
314
315 return true;
316
317 error_exit:
318 d3dtex_release(priv, tex);
319 return false;
320 }
321
d3dtex_get_render_texture(d3d_priv * priv,struct d3dtex * tex)322 static IDirect3DBaseTexture9 *d3dtex_get_render_texture(d3d_priv *priv,
323 struct d3dtex *tex)
324 {
325 return (IDirect3DBaseTexture9 *)(tex->device ? tex->device : tex->system);
326 }
327
328 // Copy system texture contents to device texture.
d3dtex_update(d3d_priv * priv,struct d3dtex * tex)329 static bool d3dtex_update(d3d_priv *priv, struct d3dtex *tex)
330 {
331 if (!tex->device)
332 return true;
333 return !FAILED(IDirect3DDevice9_UpdateTexture(priv->d3d_device,
334 (IDirect3DBaseTexture9 *)tex->system,
335 (IDirect3DBaseTexture9 *)tex->device));
336 }
337
d3d_unlock_video_objects(d3d_priv * priv)338 static void d3d_unlock_video_objects(d3d_priv *priv)
339 {
340 if (priv->locked_rect.pBits) {
341 if (FAILED(IDirect3DSurface9_UnlockRect(priv->d3d_surface)))
342 MP_VERBOSE(priv, "Unlocking video objects failed.\n");
343 }
344 priv->locked_rect.pBits = NULL;
345 }
346
347 // Free video surface/textures, etc.
d3d_destroy_video_objects(d3d_priv * priv)348 static void d3d_destroy_video_objects(d3d_priv *priv)
349 {
350 d3d_unlock_video_objects(priv);
351
352 if (priv->d3d_surface)
353 IDirect3DSurface9_Release(priv->d3d_surface);
354 priv->d3d_surface = NULL;
355 }
356
357 /** @brief Destroy D3D Offscreen and Backbuffer surfaces.
358 */
destroy_d3d_surfaces(d3d_priv * priv)359 static void destroy_d3d_surfaces(d3d_priv *priv)
360 {
361 MP_VERBOSE(priv, "destroy_d3d_surfaces called.\n");
362
363 d3d_destroy_video_objects(priv);
364 d3dtex_release(priv, &priv->osd_texture);
365
366 if (priv->d3d_backbuf)
367 IDirect3DSurface9_Release(priv->d3d_backbuf);
368 priv->d3d_backbuf = NULL;
369
370 priv->d3d_in_scene = false;
371 }
372
373 // Allocate video surface.
d3d_configure_video_objects(d3d_priv * priv)374 static bool d3d_configure_video_objects(d3d_priv *priv)
375 {
376 assert(priv->image_format != 0);
377
378 if (!priv->d3d_surface &&
379 FAILED(IDirect3DDevice9_CreateOffscreenPlainSurface(
380 priv->d3d_device, priv->src_width, priv->src_height,
381 priv->movie_src_fmt, D3DPOOL_DEFAULT, &priv->d3d_surface, NULL)))
382 {
383 MP_ERR(priv, "Allocating offscreen surface failed.\n");
384 return false;
385 }
386
387 return true;
388 }
389
390 // Recreate and initialize D3D objects if necessary. The amount of work that
391 // needs to be done can be quite different: it could be that full initialization
392 // is required, or that some objects need to be created, or that nothing is
393 // done.
create_d3d_surfaces(d3d_priv * priv)394 static bool create_d3d_surfaces(d3d_priv *priv)
395 {
396 MP_VERBOSE(priv, "create_d3d_surfaces called.\n");
397
398 if (!priv->d3d_backbuf &&
399 FAILED(IDirect3DDevice9_GetBackBuffer(priv->d3d_device, 0, 0,
400 D3DBACKBUFFER_TYPE_MONO,
401 &priv->d3d_backbuf))) {
402 MP_ERR(priv, "Allocating backbuffer failed.\n");
403 return 0;
404 }
405
406 if (!d3d_configure_video_objects(priv))
407 return 0;
408
409 /* setup default renderstate */
410 IDirect3DDevice9_SetRenderState(priv->d3d_device,
411 D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
412 IDirect3DDevice9_SetRenderState(priv->d3d_device,
413 D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
414 IDirect3DDevice9_SetRenderState(priv->d3d_device,
415 D3DRS_ALPHAFUNC, D3DCMP_GREATER);
416 IDirect3DDevice9_SetRenderState(priv->d3d_device,
417 D3DRS_ALPHAREF, (DWORD)0x0);
418 IDirect3DDevice9_SetRenderState(priv->d3d_device,
419 D3DRS_LIGHTING, FALSE);
420
421 // we use up to 3 samplers for up to 3 YUV planes
422 // TODO
423 /*
424 for (int n = 0; n < 3; n++) {
425 IDirect3DDevice9_SetSamplerState(priv->d3d_device, n, D3DSAMP_MINFILTER,
426 D3DTEXF_LINEAR);
427 IDirect3DDevice9_SetSamplerState(priv->d3d_device, n, D3DSAMP_MAGFILTER,
428 D3DTEXF_LINEAR);
429 IDirect3DDevice9_SetSamplerState(priv->d3d_device, n, D3DSAMP_ADDRESSU,
430 D3DTADDRESS_CLAMP);
431 IDirect3DDevice9_SetSamplerState(priv->d3d_device, n, D3DSAMP_ADDRESSV,
432 D3DTADDRESS_CLAMP);
433 }
434 */
435
436 return 1;
437 }
438
init_d3d(d3d_priv * priv)439 static bool init_d3d(d3d_priv *priv)
440 {
441 D3DDISPLAYMODE disp_mode;
442 D3DCAPS9 disp_caps;
443 DWORD texture_caps;
444 DWORD dev_caps;
445
446 priv->d3d_handle = priv->pDirect3DCreate9(D3D_SDK_VERSION);
447 if (!priv->d3d_handle) {
448 MP_ERR(priv, "Initializing Direct3D failed.\n");
449 return false;
450 }
451
452 if (FAILED(IDirect3D9_GetAdapterDisplayMode(priv->d3d_handle,
453 D3DADAPTER_DEFAULT,
454 &disp_mode))) {
455 MP_ERR(priv, "Reading display mode failed.\n");
456 return false;
457 }
458
459 priv->desktop_fmt = disp_mode.Format;
460 priv->cur_backbuf_width = disp_mode.Width;
461 priv->cur_backbuf_height = disp_mode.Height;
462
463 MP_VERBOSE(priv, "Setting backbuffer dimensions to (%dx%d).\n",
464 disp_mode.Width, disp_mode.Height);
465
466 if (FAILED(IDirect3D9_GetDeviceCaps(priv->d3d_handle,
467 D3DADAPTER_DEFAULT,
468 DEVTYPE,
469 &disp_caps)))
470 {
471 MP_ERR(priv, "Reading display capabilities failed.\n");
472 return false;
473 }
474
475 /* Store relevant information reguarding caps of device */
476 texture_caps = disp_caps.TextureCaps;
477 dev_caps = disp_caps.DevCaps;
478 priv->device_caps_power2_only = (texture_caps & D3DPTEXTURECAPS_POW2) &&
479 !(texture_caps & D3DPTEXTURECAPS_NONPOW2CONDITIONAL);
480 priv->device_caps_square_only = texture_caps & D3DPTEXTURECAPS_SQUAREONLY;
481 priv->device_texture_sys = dev_caps & D3DDEVCAPS_TEXTURESYSTEMMEMORY;
482 priv->max_texture_width = disp_caps.MaxTextureWidth;
483 priv->max_texture_height = disp_caps.MaxTextureHeight;
484
485 if (priv->opt_force_power_of_2)
486 priv->device_caps_power2_only = 1;
487
488 if (FAILED(IDirect3D9_CheckDeviceFormat(priv->d3d_handle,
489 D3DADAPTER_DEFAULT,
490 DEVTYPE,
491 priv->desktop_fmt,
492 D3DUSAGE_DYNAMIC | D3DUSAGE_QUERY_FILTER,
493 D3DRTYPE_TEXTURE,
494 D3DFMT_A8R8G8B8)))
495 {
496 MP_ERR(priv, "OSD texture format not supported.\n");
497 return false;
498 }
499
500 if (!change_d3d_backbuffer(priv))
501 return false;
502
503 MP_VERBOSE(priv, "device_caps_power2_only %d, device_caps_square_only %d\n"
504 "device_texture_sys %d\n"
505 "max_texture_width %d, max_texture_height %d\n",
506 priv->device_caps_power2_only, priv->device_caps_square_only,
507 priv->device_texture_sys, priv->max_texture_width,
508 priv->max_texture_height);
509
510 return true;
511 }
512
513 /** @brief Fill D3D Presentation parameters
514 */
fill_d3d_presentparams(d3d_priv * priv,D3DPRESENT_PARAMETERS * present_params)515 static void fill_d3d_presentparams(d3d_priv *priv,
516 D3DPRESENT_PARAMETERS *present_params)
517 {
518 /* Prepare Direct3D initialization parameters. */
519 memset(present_params, 0, sizeof(D3DPRESENT_PARAMETERS));
520 present_params->Windowed = TRUE;
521 present_params->SwapEffect =
522 priv->opt_swap_discard ? D3DSWAPEFFECT_DISCARD : D3DSWAPEFFECT_COPY;
523 present_params->Flags = D3DPRESENTFLAG_VIDEO;
524 present_params->hDeviceWindow = vo_w32_hwnd(priv->vo);
525 present_params->BackBufferWidth = priv->cur_backbuf_width;
526 present_params->BackBufferHeight = priv->cur_backbuf_height;
527 present_params->MultiSampleType = D3DMULTISAMPLE_NONE;
528 present_params->PresentationInterval = D3DPRESENT_INTERVAL_ONE;
529 present_params->BackBufferFormat = priv->desktop_fmt;
530 present_params->BackBufferCount = 1;
531 present_params->EnableAutoDepthStencil = FALSE;
532 }
533
534
535 // Create a new backbuffer. Create or Reset the D3D device.
change_d3d_backbuffer(d3d_priv * priv)536 static bool change_d3d_backbuffer(d3d_priv *priv)
537 {
538 int window_w = priv->vo->dwidth;
539 int window_h = priv->vo->dheight;
540
541 /* Grow the backbuffer in the required dimension. */
542 if (window_w > priv->cur_backbuf_width)
543 priv->cur_backbuf_width = window_w;
544
545 if (window_h > priv->cur_backbuf_height)
546 priv->cur_backbuf_height = window_h;
547
548 if (priv->opt_exact_backbuffer) {
549 priv->cur_backbuf_width = window_w;
550 priv->cur_backbuf_height = window_h;
551 }
552
553 /* The grown backbuffer dimensions are ready and fill_d3d_presentparams
554 * will use them, so we can reset the device.
555 */
556 D3DPRESENT_PARAMETERS present_params;
557 fill_d3d_presentparams(priv, &present_params);
558
559 if (!priv->d3d_device) {
560 if (FAILED(IDirect3D9_CreateDevice(priv->d3d_handle,
561 D3DADAPTER_DEFAULT,
562 DEVTYPE, vo_w32_hwnd(priv->vo),
563 D3DCREATE_SOFTWARE_VERTEXPROCESSING
564 | D3DCREATE_FPU_PRESERVE
565 | D3DCREATE_MULTITHREADED,
566 &present_params, &priv->d3d_device)))
567 {
568 MP_VERBOSE(priv, "Creating Direct3D device failed.\n");
569 return 0;
570 }
571 } else {
572 if (FAILED(IDirect3DDevice9_Reset(priv->d3d_device, &present_params))) {
573 MP_ERR(priv, "Reseting Direct3D device failed.\n");
574 return 0;
575 }
576 }
577
578 MP_VERBOSE(priv, "New backbuffer (%dx%d), VO (%dx%d)\n",
579 present_params.BackBufferWidth, present_params.BackBufferHeight,
580 window_w, window_h);
581
582 return 1;
583 }
584
destroy_d3d(d3d_priv * priv)585 static void destroy_d3d(d3d_priv *priv)
586 {
587 destroy_d3d_surfaces(priv);
588
589 if (priv->d3d_device)
590 IDirect3DDevice9_Release(priv->d3d_device);
591 priv->d3d_device = NULL;
592
593 if (priv->d3d_handle) {
594 MP_VERBOSE(priv, "Stopping Direct3D.\n");
595 IDirect3D9_Release(priv->d3d_handle);
596 }
597 priv->d3d_handle = NULL;
598 }
599
600 /** @brief Reconfigure the whole Direct3D. Called only
601 * when the video adapter becomes uncooperative. ("Lost" devices)
602 * @return 1 on success, 0 on failure
603 */
reconfigure_d3d(d3d_priv * priv)604 static int reconfigure_d3d(d3d_priv *priv)
605 {
606 MP_VERBOSE(priv, "reconfigure_d3d called.\n");
607
608 // Force complete destruction of the D3D state.
609 // Note: this step could be omitted. The resize_d3d call below would detect
610 // that d3d_device is NULL, and would properly recreate it. I'm not sure why
611 // the following code to release and recreate the d3d_handle exists.
612 destroy_d3d(priv);
613 if (!init_d3d(priv))
614 return 0;
615
616 // Proper re-initialization.
617 if (!resize_d3d(priv))
618 return 0;
619
620 return 1;
621 }
622
623 // Resize Direct3D context on window resize.
624 // This function also is called when major initializations need to be done.
resize_d3d(d3d_priv * priv)625 static bool resize_d3d(d3d_priv *priv)
626 {
627 D3DVIEWPORT9 vp = {0, 0, priv->vo->dwidth, priv->vo->dheight, 0, 1};
628
629 MP_VERBOSE(priv, "resize_d3d %dx%d called.\n",
630 priv->vo->dwidth, priv->vo->dheight);
631
632 /* Make sure that backbuffer is large enough to accommodate the new
633 viewport dimensions. Grow it if necessary. */
634
635 bool backbuf_resize = priv->vo->dwidth > priv->cur_backbuf_width ||
636 priv->vo->dheight > priv->cur_backbuf_height;
637
638 if (priv->opt_exact_backbuffer) {
639 backbuf_resize = priv->vo->dwidth != priv->cur_backbuf_width ||
640 priv->vo->dheight != priv->cur_backbuf_height;
641 }
642
643 if (backbuf_resize || !priv->d3d_device)
644 {
645 destroy_d3d_surfaces(priv);
646 if (!change_d3d_backbuffer(priv))
647 return 0;
648 }
649
650 if (!priv->d3d_device || !priv->image_format)
651 return 1;
652
653 if (!create_d3d_surfaces(priv))
654 return 0;
655
656 if (FAILED(IDirect3DDevice9_SetViewport(priv->d3d_device, &vp))) {
657 MP_ERR(priv, "Setting viewport failed.\n");
658 return 0;
659 }
660
661 // so that screen coordinates map to D3D ones
662 D3DMATRIX view;
663 d3d_matrix_ortho(&view, 0.5f, vp.Width + 0.5f, vp.Height + 0.5f, 0.5f);
664 IDirect3DDevice9_SetTransform(priv->d3d_device, D3DTS_VIEW, &view);
665
666 calc_fs_rect(priv);
667 priv->vo->want_redraw = true;
668
669 return 1;
670 }
671
672 /** @brief Uninitialize Direct3D and close the window.
673 */
uninit_d3d(d3d_priv * priv)674 static void uninit_d3d(d3d_priv *priv)
675 {
676 MP_VERBOSE(priv, "uninit_d3d called.\n");
677
678 destroy_d3d(priv);
679 }
680
d3d_draw_frame(d3d_priv * priv)681 static uint32_t d3d_draw_frame(d3d_priv *priv)
682 {
683 if (!priv->d3d_device)
684 return VO_TRUE;
685
686 if (!d3d_begin_scene(priv))
687 return VO_ERROR;
688
689 IDirect3DDevice9_Clear(priv->d3d_device, 0, NULL, D3DCLEAR_TARGET, 0, 0, 0);
690
691 if (!priv->have_image)
692 goto render_osd;
693
694 RECT rm = priv->fs_movie_rect;
695 RECT rs = priv->fs_panscan_rect;
696
697 rs.left &= ~(ULONG)1;
698 rs.top &= ~(ULONG)1;
699 rs.right &= ~(ULONG)1;
700 rs.bottom &= ~(ULONG)1;
701 if (FAILED(IDirect3DDevice9_StretchRect(priv->d3d_device,
702 priv->d3d_surface,
703 &rs,
704 priv->d3d_backbuf,
705 &rm,
706 D3DTEXF_LINEAR))) {
707 MP_ERR(priv, "Copying frame to the backbuffer failed.\n");
708 return VO_ERROR;
709 }
710
711 render_osd:
712
713 draw_osd(priv->vo);
714
715 return VO_TRUE;
716 }
717
check_format(d3d_priv * priv,uint32_t movie_fmt)718 static D3DFORMAT check_format(d3d_priv *priv, uint32_t movie_fmt)
719 {
720 const struct fmt_entry *cur = &fmt_table[0];
721
722 while (cur->mplayer_fmt) {
723 if (cur->mplayer_fmt == movie_fmt) {
724 HRESULT res;
725 /* Test conversion from Movie colorspace to
726 * display's target colorspace. */
727 res = IDirect3D9_CheckDeviceFormatConversion(priv->d3d_handle,
728 D3DADAPTER_DEFAULT,
729 DEVTYPE,
730 cur->fourcc,
731 priv->desktop_fmt);
732 if (FAILED(res)) {
733 MP_VERBOSE(priv, "Rejected image format: %s\n",
734 vo_format_name(cur->mplayer_fmt));
735 return 0;
736 }
737
738 MP_DBG(priv, "Accepted image format: %s\n",
739 vo_format_name(cur->mplayer_fmt));
740
741 return cur->fourcc;
742 }
743 cur++;
744 }
745
746 return 0;
747 }
748
749 // Return if the image format can be used. If it can, decide which rendering
750 // and conversion mode to use.
751 // If initialize is true, actually setup all variables to use the picked
752 // rendering mode.
init_rendering_mode(d3d_priv * priv,uint32_t fmt,bool initialize)753 static bool init_rendering_mode(d3d_priv *priv, uint32_t fmt, bool initialize)
754 {
755 int blit_d3dfmt = check_format(priv, fmt);
756
757 if (!blit_d3dfmt)
758 return false;
759
760 MP_VERBOSE(priv, "Accepted rendering methods for "
761 "format='%s': StretchRect=%#x.\n",
762 vo_format_name(fmt), blit_d3dfmt);
763
764 if (!initialize)
765 return true;
766
767 // initialization doesn't fail beyond this point
768
769 priv->movie_src_fmt = 0;
770 priv->image_format = fmt;
771
772 priv->movie_src_fmt = blit_d3dfmt;
773
774 return true;
775 }
776
777 /** @brief Query if movie colorspace is supported by the HW.
778 * @return 0 on failure, device capabilities (not probed
779 * currently) on success.
780 */
query_format(struct vo * vo,int movie_fmt)781 static int query_format(struct vo *vo, int movie_fmt)
782 {
783 d3d_priv *priv = vo->priv;
784 if (!init_rendering_mode(priv, movie_fmt, false))
785 return 0;
786
787 return 1;
788 }
789
790 /****************************************************************************
791 * *
792 * *
793 * *
794 * libvo Control / Callback functions *
795 * *
796 * *
797 * *
798 ****************************************************************************/
799
800
801 /** @brief libvo Callback: Preinitialize the video card.
802 * Preinit the hardware just enough to be queried about
803 * supported formats.
804 *
805 * @return 0 on success, -1 on failure
806 */
807
preinit(struct vo * vo)808 static int preinit(struct vo *vo)
809 {
810 d3d_priv *priv = vo->priv;
811 priv->vo = vo;
812 priv->log = vo->log;
813
814 priv->d3d9_dll = LoadLibraryA("d3d9.dll");
815 if (!priv->d3d9_dll) {
816 MP_ERR(priv, "Unable to dynamically load d3d9.dll\n");
817 goto err_out;
818 }
819
820 priv->pDirect3DCreate9 = (void *)GetProcAddress(priv->d3d9_dll,
821 "Direct3DCreate9");
822 if (!priv->pDirect3DCreate9) {
823 MP_ERR(priv, "Unable to find entry point of Direct3DCreate9\n");
824 goto err_out;
825 }
826
827 /* w32_common framework call. Configures window on the screen, gets
828 * fullscreen dimensions and does other useful stuff.
829 */
830 if (!vo_w32_init(vo)) {
831 MP_VERBOSE(priv, "Configuring onscreen window failed.\n");
832 goto err_out;
833 }
834
835 if (!init_d3d(priv))
836 goto err_out;
837
838 return 0;
839
840 err_out:
841 uninit(vo);
842 return -1;
843 }
844
845 /** @brief libvo Callback: Handle control requests.
846 * @return VO_TRUE on success, VO_NOTIMPL when not implemented
847 */
control(struct vo * vo,uint32_t request,void * data)848 static int control(struct vo *vo, uint32_t request, void *data)
849 {
850 d3d_priv *priv = vo->priv;
851
852 switch (request) {
853 case VOCTRL_REDRAW_FRAME:
854 d3d_draw_frame(priv);
855 return VO_TRUE;
856 case VOCTRL_SET_PANSCAN:
857 calc_fs_rect(priv);
858 priv->vo->want_redraw = true;
859 return VO_TRUE;
860 case VOCTRL_SCREENSHOT_WIN:
861 *(struct mp_image **)data = get_window_screenshot(priv);
862 return VO_TRUE;
863 }
864
865 int events = 0;
866 int r = vo_w32_control(vo, &events, request, data);
867
868 if (events & VO_EVENT_RESIZE)
869 resize_d3d(priv);
870
871 if (events & VO_EVENT_EXPOSE)
872 vo->want_redraw = true;
873
874 vo_event(vo, events);
875
876 return r;
877 }
878
reconfig(struct vo * vo,struct mp_image_params * params)879 static int reconfig(struct vo *vo, struct mp_image_params *params)
880 {
881 d3d_priv *priv = vo->priv;
882
883 priv->have_image = false;
884
885 vo_w32_config(vo);
886
887 if ((priv->image_format != params->imgfmt)
888 || (priv->src_width != params->w)
889 || (priv->src_height != params->h))
890 {
891 d3d_destroy_video_objects(priv);
892
893 priv->src_width = params->w;
894 priv->src_height = params->h;
895 priv->params = *params;
896 init_rendering_mode(priv, params->imgfmt, true);
897 }
898
899 if (!resize_d3d(priv))
900 return VO_ERROR;
901
902 return 0; /* Success */
903 }
904
905 /** @brief libvo Callback: Flip next already drawn frame on the
906 * screen.
907 */
flip_page(struct vo * vo)908 static void flip_page(struct vo *vo)
909 {
910 d3d_priv *priv = vo->priv;
911
912 if (priv->d3d_device && priv->d3d_in_scene) {
913 if (FAILED(IDirect3DDevice9_EndScene(priv->d3d_device))) {
914 MP_ERR(priv, "EndScene failed.\n");
915 }
916 }
917 priv->d3d_in_scene = false;
918
919 RECT rect = {0, 0, vo->dwidth, vo->dheight};
920 if (!priv->d3d_device ||
921 FAILED(IDirect3DDevice9_Present(priv->d3d_device, &rect, 0, 0, 0))) {
922 MP_VERBOSE(priv, "Trying to reinitialize uncooperative video adapter.\n");
923 if (!reconfigure_d3d(priv)) {
924 MP_VERBOSE(priv, "Reinitialization failed.\n");
925 return;
926 } else {
927 MP_VERBOSE(priv, "Video adapter reinitialized.\n");
928 }
929 }
930 }
931
932 /** @brief libvo Callback: Uninitializes all pointers and closes
933 * all D3D related stuff,
934 */
uninit(struct vo * vo)935 static void uninit(struct vo *vo)
936 {
937 d3d_priv *priv = vo->priv;
938
939 MP_VERBOSE(priv, "uninit called.\n");
940
941 uninit_d3d(priv);
942 vo_w32_uninit(vo);
943 if (priv->d3d9_dll)
944 FreeLibrary(priv->d3d9_dll);
945 priv->d3d9_dll = NULL;
946 }
947
948 // Lock buffers and fill out to point to them.
949 // Must call d3d_unlock_video_objects() to unlock the buffers again.
get_video_buffer(d3d_priv * priv,struct mp_image * out)950 static bool get_video_buffer(d3d_priv *priv, struct mp_image *out)
951 {
952 *out = (struct mp_image) {0};
953 mp_image_set_size(out, priv->src_width, priv->src_height);
954 mp_image_setfmt(out, priv->image_format);
955
956 if (!priv->d3d_device)
957 return false;
958
959 if (!priv->locked_rect.pBits) {
960 if (FAILED(IDirect3DSurface9_LockRect(priv->d3d_surface,
961 &priv->locked_rect, NULL, 0)))
962 {
963 MP_ERR(priv, "Surface lock failed.\n");
964 return false;
965 }
966 }
967
968 uint8_t *base = priv->locked_rect.pBits;
969 size_t stride = priv->locked_rect.Pitch;
970
971 out->planes[0] = base;
972 out->stride[0] = stride;
973
974 if (out->num_planes == 2) {
975 // NV12, NV21
976 out->planes[1] = base + stride * out->h;
977 out->stride[1] = stride;
978 }
979
980 if (out->num_planes == 3) {
981 bool swap = priv->movie_src_fmt == MAKEFOURCC('Y','V','1','2');
982
983 size_t uv_stride = stride / 2;
984 uint8_t *u = base + out->h * stride;
985 uint8_t *v = u + (out->h / 2) * uv_stride;
986
987 out->planes[1] = swap ? v : u;
988 out->planes[2] = swap ? u : v;
989
990 out->stride[1] = out->stride[2] = uv_stride;
991 }
992
993 return true;
994 }
995
draw_image(struct vo * vo,mp_image_t * mpi)996 static void draw_image(struct vo *vo, mp_image_t *mpi)
997 {
998 d3d_priv *priv = vo->priv;
999 if (!priv->d3d_device)
1000 goto done;
1001
1002 struct mp_image buffer;
1003 if (!get_video_buffer(priv, &buffer))
1004 goto done;
1005
1006 mp_image_copy(&buffer, mpi);
1007
1008 d3d_unlock_video_objects(priv);
1009
1010 priv->have_image = true;
1011 priv->osd_pts = mpi->pts;
1012
1013 d3d_draw_frame(priv);
1014
1015 done:
1016 talloc_free(mpi);
1017 }
1018
get_window_screenshot(d3d_priv * priv)1019 static mp_image_t *get_window_screenshot(d3d_priv *priv)
1020 {
1021 D3DDISPLAYMODE mode;
1022 mp_image_t *image = NULL;
1023 RECT window_rc;
1024 RECT screen_rc;
1025 RECT visible;
1026 POINT pt;
1027 D3DLOCKED_RECT locked_rect;
1028 int width, height;
1029 IDirect3DSurface9 *surface = NULL;
1030
1031 if (FAILED(IDirect3DDevice9_GetDisplayMode(priv->d3d_device, 0, &mode))) {
1032 MP_ERR(priv, "GetDisplayMode failed.\n");
1033 goto error_exit;
1034 }
1035
1036 if (FAILED(IDirect3DDevice9_CreateOffscreenPlainSurface(priv->d3d_device,
1037 mode.Width, mode.Height, D3DFMT_A8R8G8B8, D3DPOOL_SYSTEMMEM, &surface,
1038 NULL)))
1039 {
1040 MP_ERR(priv, "Couldn't create surface.\n");
1041 goto error_exit;
1042 }
1043
1044 if (FAILED(IDirect3DDevice9_GetFrontBufferData(priv->d3d_device, 0,
1045 surface)))
1046 {
1047 MP_ERR(priv, "Couldn't copy frontbuffer.\n");
1048 goto error_exit;
1049 }
1050
1051 GetClientRect(vo_w32_hwnd(priv->vo), &window_rc);
1052 pt = (POINT) { 0, 0 };
1053 ClientToScreen(vo_w32_hwnd(priv->vo), &pt);
1054 window_rc.left = pt.x;
1055 window_rc.top = pt.y;
1056 window_rc.right += window_rc.left;
1057 window_rc.bottom += window_rc.top;
1058
1059 screen_rc = (RECT) { 0, 0, mode.Width, mode.Height };
1060
1061 if (!IntersectRect(&visible, &screen_rc, &window_rc))
1062 goto error_exit;
1063 width = visible.right - visible.left;
1064 height = visible.bottom - visible.top;
1065 if (width < 1 || height < 1)
1066 goto error_exit;
1067
1068 image = mp_image_alloc(IMGFMT_BGR0, width, height);
1069 if (!image)
1070 goto error_exit;
1071
1072 IDirect3DSurface9_LockRect(surface, &locked_rect, NULL, 0);
1073
1074 memcpy_pic(image->planes[0], (char*)locked_rect.pBits + visible.top *
1075 locked_rect.Pitch + visible.left * 4, width * 4, height,
1076 image->stride[0], locked_rect.Pitch);
1077
1078 IDirect3DSurface9_UnlockRect(surface);
1079 IDirect3DSurface9_Release(surface);
1080
1081 return image;
1082
1083 error_exit:
1084 if (image)
1085 talloc_free(image);
1086 if (surface)
1087 IDirect3DSurface9_Release(surface);
1088 return NULL;
1089 }
1090
update_osd(d3d_priv * priv)1091 static void update_osd(d3d_priv *priv)
1092 {
1093 if (!priv->osd_cache)
1094 priv->osd_cache = mp_draw_sub_alloc(priv, priv->vo->global);
1095
1096 struct sub_bitmap_list *sbs = osd_render(priv->vo->osd, priv->osd_res,
1097 priv->osd_pts, 0, mp_draw_sub_formats);
1098
1099 struct mp_rect act_rc[MAX_OSD_RECTS], mod_rc[64];
1100 int num_act_rc = 0, num_mod_rc = 0;
1101
1102 struct mp_image *osd = mp_draw_sub_overlay(priv->osd_cache, sbs,
1103 act_rc, MP_ARRAY_SIZE(act_rc), &num_act_rc,
1104 mod_rc, MP_ARRAY_SIZE(mod_rc), &num_mod_rc);
1105
1106 talloc_free(sbs);
1107
1108 if (!osd) {
1109 MP_ERR(priv, "Failed to render OSD.\n");
1110 return;
1111 }
1112
1113 if (!num_mod_rc && priv->osd_texture.system)
1114 return; // nothing changed
1115
1116 priv->osd_num_vertices = 0;
1117
1118 if (osd->w > priv->osd_texture.tex_w || osd->h > priv->osd_texture.tex_h) {
1119 int new_w = osd->w;
1120 int new_h = osd->h;
1121 d3d_fix_texture_size(priv, &new_w, &new_h);
1122
1123 MP_DBG(priv, "reallocate OSD surface to %dx%d.\n", new_w, new_h);
1124
1125 d3dtex_release(priv, &priv->osd_texture);
1126 if (!d3dtex_allocate(priv, &priv->osd_texture, D3DFMT_A8R8G8B8,
1127 new_w, new_h))
1128 return;
1129 }
1130
1131 // Lazy; could/should use the bounding rect, or perform multiple lock calls.
1132 // The previous approach (fully packed texture) was more efficient.
1133 RECT dirty_rc = { 0, 0, priv->osd_texture.w, priv->osd_texture.h };
1134
1135 D3DLOCKED_RECT locked_rect;
1136
1137 if (FAILED(IDirect3DTexture9_LockRect(priv->osd_texture.system, 0, &locked_rect,
1138 &dirty_rc, 0)))
1139 {
1140 MP_ERR(priv, "OSD texture lock failed.\n");
1141 return;
1142 }
1143
1144 for (int n = 0; n < num_mod_rc; n++) {
1145 struct mp_rect rc = mod_rc[n];
1146 int w = mp_rect_w(rc);
1147 int h = mp_rect_h(rc);
1148 void *src = mp_image_pixel_ptr(osd, 0, rc.x0, rc.y0);
1149 void *dst = (char *)locked_rect.pBits + locked_rect.Pitch * rc.y0 +
1150 rc.x0 * 4;
1151 memcpy_pic(dst, src, w * 4, h, locked_rect.Pitch, osd->stride[0]);
1152 }
1153
1154 if (FAILED(IDirect3DTexture9_UnlockRect(priv->osd_texture.system, 0))) {
1155 MP_ERR(priv, "OSD texture unlock failed.\n");
1156 return;
1157 }
1158
1159 if (!d3dtex_update(priv, &priv->osd_texture))
1160 return;
1161
1162 // We need 2 primitives per quad which makes 6 vertices.
1163 priv->osd_num_vertices = num_act_rc * 6;
1164
1165 float tex_w = priv->osd_texture.tex_w;
1166 float tex_h = priv->osd_texture.tex_h;
1167
1168 for (int n = 0; n < num_act_rc; n++) {
1169 struct mp_rect rc = act_rc[n];
1170
1171 float tx0 = rc.x0 / tex_w;
1172 float ty0 = rc.y0 / tex_h;
1173 float tx1 = rc.x1 / tex_w;
1174 float ty1 = rc.y1 / tex_h;
1175
1176 vertex_osd *v = &priv->osd_vertices[n * 6];
1177 v[0] = (vertex_osd) { rc.x0, rc.y0, 0, tx0, ty0 };
1178 v[1] = (vertex_osd) { rc.x1, rc.y0, 0, tx1, ty0 };
1179 v[2] = (vertex_osd) { rc.x0, rc.y1, 0, tx0, ty1 };
1180 v[3] = (vertex_osd) { rc.x1, rc.y1, 0, tx1, ty1 };
1181 v[4] = v[2];
1182 v[5] = v[1];
1183 }
1184 }
1185
draw_osd(struct vo * vo)1186 static void draw_osd(struct vo *vo)
1187 {
1188 d3d_priv *priv = vo->priv;
1189 if (!priv->d3d_device)
1190 return;
1191
1192 update_osd(priv);
1193
1194 if (!priv->osd_num_vertices)
1195 return;
1196
1197 d3d_begin_scene(priv);
1198
1199 IDirect3DDevice9_SetRenderState(priv->d3d_device,
1200 D3DRS_ALPHABLENDENABLE, TRUE);
1201
1202 IDirect3DDevice9_SetTexture(priv->d3d_device, 0,
1203 d3dtex_get_render_texture(priv, &priv->osd_texture));
1204
1205 IDirect3DDevice9_SetRenderState(priv->d3d_device, D3DRS_SRCBLEND,
1206 D3DBLEND_ONE);
1207
1208 IDirect3DDevice9_SetFVF(priv->d3d_device, D3DFVF_OSD_VERTEX);
1209 IDirect3DDevice9_DrawPrimitiveUP(priv->d3d_device, D3DPT_TRIANGLELIST,
1210 priv->osd_num_vertices / 3,
1211 priv->osd_vertices, sizeof(vertex_osd));
1212
1213 IDirect3DDevice9_SetRenderState(priv->d3d_device,
1214 D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
1215
1216 IDirect3DDevice9_SetTexture(priv->d3d_device, 0, NULL);
1217
1218 IDirect3DDevice9_SetRenderState(priv->d3d_device,
1219 D3DRS_ALPHABLENDENABLE, FALSE);
1220 }
1221
1222 #define OPT_BASE_STRUCT d3d_priv
1223
1224 static const struct m_option opts[] = {
1225 {"force-power-of-2", OPT_FLAG(opt_force_power_of_2)},
1226 {"disable-texture-align", OPT_FLAG(opt_disable_texture_align)},
1227 {"texture-memory", OPT_CHOICE(opt_texture_memory,
1228 {"default", 0},
1229 {"managed", 1},
1230 {"default-pool", 2},
1231 {"default-pool-shadow", 3},
1232 {"scratch", 4})},
1233 {"swap-discard", OPT_FLAG(opt_swap_discard)},
1234 {"exact-backbuffer", OPT_FLAG(opt_exact_backbuffer)},
1235 {0}
1236 };
1237
1238 const struct vo_driver video_out_direct3d = {
1239 .description = "Direct3D 9 Renderer",
1240 .name = "direct3d",
1241 .preinit = preinit,
1242 .query_format = query_format,
1243 .reconfig = reconfig,
1244 .control = control,
1245 .draw_image = draw_image,
1246 .flip_page = flip_page,
1247 .uninit = uninit,
1248 .priv_size = sizeof(d3d_priv),
1249 .options = opts,
1250 .options_prefix = "vo-direct3d",
1251 };
1252