/src/mpv/video/out/gpu/context.c
Line | Count | Source |
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 <stddef.h> |
19 | | #include <stdlib.h> |
20 | | #include <stdio.h> |
21 | | #include <string.h> |
22 | | #include <stdbool.h> |
23 | | #include <math.h> |
24 | | #include <assert.h> |
25 | | |
26 | | #include "config.h" |
27 | | #include "common/common.h" |
28 | | #include "common/msg.h" |
29 | | #include "options/options.h" |
30 | | #include "options/m_option.h" |
31 | | #include "video/out/vo.h" |
32 | | |
33 | | #include "context.h" |
34 | | #include "spirv.h" |
35 | | |
36 | | /* OpenGL */ |
37 | | extern const struct ra_ctx_fns ra_ctx_glx; |
38 | | extern const struct ra_ctx_fns ra_ctx_x11_egl; |
39 | | extern const struct ra_ctx_fns ra_ctx_drm_egl; |
40 | | extern const struct ra_ctx_fns ra_ctx_wayland_egl; |
41 | | extern const struct ra_ctx_fns ra_ctx_wgl; |
42 | | extern const struct ra_ctx_fns ra_ctx_angle; |
43 | | extern const struct ra_ctx_fns ra_ctx_dxgl; |
44 | | extern const struct ra_ctx_fns ra_ctx_android; |
45 | | |
46 | | /* Vulkan */ |
47 | | extern const struct ra_ctx_fns ra_ctx_vulkan_wayland; |
48 | | extern const struct ra_ctx_fns ra_ctx_vulkan_win; |
49 | | extern const struct ra_ctx_fns ra_ctx_vulkan_xlib; |
50 | | extern const struct ra_ctx_fns ra_ctx_vulkan_android; |
51 | | extern const struct ra_ctx_fns ra_ctx_vulkan_display; |
52 | | extern const struct ra_ctx_fns ra_ctx_vulkan_mac; |
53 | | |
54 | | /* Direct3D 11 */ |
55 | | extern const struct ra_ctx_fns ra_ctx_d3d11; |
56 | | |
57 | | /* No API */ |
58 | | extern const struct ra_ctx_fns ra_ctx_wldmabuf; |
59 | | |
60 | | /* Autoprobe dummy. Always fails to create. */ |
61 | | static bool dummy_init(struct ra_ctx *ctx) |
62 | 5.69k | { |
63 | 5.69k | return false; |
64 | 5.69k | } |
65 | | |
66 | | static void dummy_uninit(struct ra_ctx *ctx) |
67 | 0 | { |
68 | 0 | } |
69 | | |
70 | | static const struct ra_ctx_fns ra_ctx_dummy = { |
71 | | .type = "auto", |
72 | | .name = "auto", |
73 | | .description = "Auto detect", |
74 | | .init = dummy_init, |
75 | | .uninit = dummy_uninit, |
76 | | }; |
77 | | |
78 | | static const struct ra_ctx_fns *const contexts[] = { |
79 | | &ra_ctx_dummy, |
80 | | // Direct3D contexts: |
81 | | #if HAVE_D3D11 |
82 | | &ra_ctx_d3d11, |
83 | | #endif |
84 | | |
85 | | // Vulkan contexts: |
86 | | #if HAVE_VULKAN |
87 | | #if HAVE_WIN32_DESKTOP |
88 | | &ra_ctx_vulkan_win, |
89 | | #endif |
90 | | #if HAVE_WAYLAND |
91 | | &ra_ctx_vulkan_wayland, |
92 | | #endif |
93 | | #if HAVE_X11 |
94 | | &ra_ctx_vulkan_xlib, |
95 | | #endif |
96 | | #if HAVE_COCOA && HAVE_SWIFT |
97 | | &ra_ctx_vulkan_mac, |
98 | | #endif |
99 | | #endif |
100 | | |
101 | | // OpenGL contexts: |
102 | | #if HAVE_EGL_ANDROID |
103 | | &ra_ctx_android, |
104 | | #endif |
105 | | #if HAVE_EGL_ANGLE_WIN32 |
106 | | &ra_ctx_angle, |
107 | | #endif |
108 | | #if HAVE_GL_WIN32 |
109 | | &ra_ctx_wgl, |
110 | | #endif |
111 | | #if HAVE_GL_DXINTEROP |
112 | | &ra_ctx_dxgl, |
113 | | #endif |
114 | | #if HAVE_EGL_WAYLAND |
115 | | &ra_ctx_wayland_egl, |
116 | | #endif |
117 | | #if HAVE_EGL_X11 |
118 | | &ra_ctx_x11_egl, |
119 | | #endif |
120 | | #if HAVE_GL_X11 |
121 | | &ra_ctx_glx, |
122 | | #endif |
123 | | #if HAVE_EGL_DRM |
124 | | &ra_ctx_drm_egl, |
125 | | #endif |
126 | | |
127 | | // Vulkan contexts (fallbacks): |
128 | | #if HAVE_VULKAN |
129 | | #if HAVE_ANDROID |
130 | | &ra_ctx_vulkan_android, |
131 | | #endif |
132 | | #if HAVE_VK_KHR_DISPLAY |
133 | | &ra_ctx_vulkan_display, |
134 | | #endif |
135 | | #endif |
136 | | }; |
137 | | |
138 | | static const struct ra_ctx_fns *const no_api_contexts[] = { |
139 | | &ra_ctx_dummy, |
140 | | /* No API contexts: */ |
141 | | #if HAVE_DMABUF_WAYLAND |
142 | | &ra_ctx_wldmabuf, |
143 | | #endif |
144 | | }; |
145 | | |
146 | | static bool get_desc(struct m_obj_desc *dst, int index) |
147 | 280k | { |
148 | 280k | if (index >= MP_ARRAY_SIZE(contexts)) |
149 | 139k | return false; |
150 | 140k | const struct ra_ctx_fns *ctx = contexts[index]; |
151 | 140k | *dst = (struct m_obj_desc) { |
152 | 140k | .name = ctx->name, |
153 | 140k | .description = ctx->description, |
154 | 140k | }; |
155 | 140k | return true; |
156 | 280k | } |
157 | | |
158 | | static bool check_unknown_entry(const char *name) |
159 | 117 | { |
160 | 117 | return false; |
161 | 117 | } |
162 | | |
163 | | const struct m_obj_list ra_ctx_obj_list = { |
164 | | .get_desc = get_desc, |
165 | | .check_unknown_entry = check_unknown_entry, |
166 | | .description = "GPU contexts", |
167 | | .allow_trailer = true, |
168 | | .disallow_positional_parameters = true, |
169 | | .use_global_options = true, |
170 | | }; |
171 | | |
172 | | static bool get_type_desc(struct m_obj_desc *dst, int index) |
173 | 280k | { |
174 | 280k | int api_index = 0; |
175 | | |
176 | 420k | for (int i = 0; i < MP_ARRAY_SIZE(contexts); i++) { |
177 | 280k | if (i && strcmp(contexts[i - 1]->type, contexts[i]->type)) |
178 | 0 | api_index++; |
179 | | |
180 | 280k | if (api_index == index) { |
181 | 140k | *dst = (struct m_obj_desc) { |
182 | 140k | .name = contexts[i]->type, |
183 | 140k | .description = "", |
184 | 140k | }; |
185 | 140k | return true; |
186 | 140k | } |
187 | 280k | } |
188 | | |
189 | 140k | return false; |
190 | 280k | } |
191 | | |
192 | | static void print_context_apis(struct mp_log *log) |
193 | 36 | { |
194 | 36 | mp_info(log, "Available GPU APIs and contexts:\n"); |
195 | 72 | for (int n = 0; n < MP_ARRAY_SIZE(contexts); n++) { |
196 | 36 | mp_info(log, " %s %s\n", contexts[n]->type, contexts[n]->name); |
197 | 36 | } |
198 | 36 | } |
199 | | |
200 | | const struct m_obj_list ra_ctx_type_obj_list = { |
201 | | .get_desc = get_type_desc, |
202 | | .check_unknown_entry = check_unknown_entry, |
203 | | .description = "GPU APIs", |
204 | | .allow_trailer = true, |
205 | | .disallow_positional_parameters = true, |
206 | | .use_global_options = true, |
207 | | .print_help_list = print_context_apis, |
208 | | }; |
209 | | |
210 | | #define OPT_BASE_STRUCT struct ra_ctx_opts |
211 | | const struct m_sub_options ra_ctx_conf = { |
212 | | .opts = (const m_option_t[]) { |
213 | | {"gpu-context", |
214 | | OPT_SETTINGSLIST(context_list, &ra_ctx_obj_list)}, |
215 | | {"gpu-api", |
216 | | OPT_SETTINGSLIST(context_type_list, &ra_ctx_type_obj_list)}, |
217 | | {"gpu-debug", OPT_BOOL(debug)}, |
218 | | {"gpu-sw", OPT_BOOL(allow_sw)}, |
219 | | {0} |
220 | | }, |
221 | | .size = sizeof(struct ra_ctx_opts), |
222 | | .change_flags = UPDATE_VO, |
223 | | }; |
224 | | |
225 | | static struct ra_ctx *create_in_contexts(struct vo *vo, const char *name, |
226 | | struct m_obj_settings *context_type_list, |
227 | | const struct ra_ctx_fns *const ctxs[], size_t size, |
228 | | struct ra_ctx_opts opts) |
229 | 5.69k | { |
230 | 11.3k | for (int i = 0; i < size; i++) { |
231 | 5.69k | if (strcmp(name, ctxs[i]->name) != 0) |
232 | 0 | continue; |
233 | 5.69k | if (context_type_list) { |
234 | 5.69k | bool found = false; |
235 | 5.69k | for (int j = 0; context_type_list[j].name; j++) { |
236 | 5.69k | if (strcmp(context_type_list[j].name, "auto") == 0 || |
237 | 5.69k | strcmp(context_type_list[j].name, ctxs[i]->type) == 0) { |
238 | 5.69k | found = true; |
239 | 5.69k | break; |
240 | 5.69k | } |
241 | 5.69k | } |
242 | 5.69k | if (!found) |
243 | 0 | continue; |
244 | 5.69k | } |
245 | 5.69k | struct ra_ctx *ctx = talloc_ptrtype(NULL, ctx); |
246 | 5.69k | *ctx = (struct ra_ctx) { |
247 | 5.69k | .vo = vo, |
248 | 5.69k | .global = vo->global, |
249 | 5.69k | .log = mp_log_new(ctx, vo->log, ctxs[i]->type), |
250 | 5.69k | .opts = opts, |
251 | 5.69k | .fns = ctxs[i], |
252 | 5.69k | }; |
253 | | |
254 | 5.69k | MP_VERBOSE(ctx, "Initializing GPU context '%s'\n", ctx->fns->name); |
255 | 5.69k | if (ctxs[i]->init(ctx)) |
256 | 0 | return ctx; |
257 | 5.69k | talloc_free(ctx); |
258 | 5.69k | } |
259 | 5.69k | return NULL; |
260 | 5.69k | } |
261 | | |
262 | | struct ra_ctx *ra_ctx_create_by_name(struct vo *vo, const char *name) |
263 | 0 | { |
264 | 0 | struct ra_ctx_opts dummy = {0}; |
265 | 0 | struct ra_ctx *ctx = create_in_contexts(vo, name, NULL, contexts, |
266 | 0 | MP_ARRAY_SIZE(contexts), dummy); |
267 | 0 | if (ctx) |
268 | 0 | return ctx; |
269 | 0 | return create_in_contexts(vo, name, NULL, no_api_contexts, |
270 | 0 | MP_ARRAY_SIZE(no_api_contexts), dummy); |
271 | 0 | } |
272 | | |
273 | | // Create a VO window and create a RA context on it. |
274 | | // vo_flags: passed to the backend's create window function |
275 | | struct ra_ctx *ra_ctx_create(struct vo *vo, struct ra_ctx_opts opts) |
276 | 5.69k | { |
277 | 5.69k | bool ctx_auto = !opts.context_list || |
278 | 0 | (opts.context_list[0].name && |
279 | 0 | strcmp(opts.context_list[0].name, "auto") == 0); |
280 | | |
281 | 5.69k | if (ctx_auto) { |
282 | 5.69k | MP_VERBOSE(vo, "Probing for best GPU context.\n"); |
283 | 5.69k | opts.probing = true; |
284 | 5.69k | } |
285 | | |
286 | | // Hack to silence backend (X11/Wayland/etc.) errors. Kill it once backends |
287 | | // are separate from `struct vo` |
288 | 5.69k | bool old_probing = vo->probing; |
289 | 5.69k | vo->probing = opts.probing; |
290 | | |
291 | 5.69k | struct ra_ctx *ctx = NULL; |
292 | 5.69k | if (opts.probing) { |
293 | 5.69k | struct m_obj_settings context_type_list[2] = {{.name = "auto"}, {0}}; |
294 | | |
295 | 5.69k | for (int i = 0; |
296 | 11.3k | opts.context_type_list ? opts.context_type_list[i].name != NULL : i == 0; |
297 | 5.69k | i++) { |
298 | 11.3k | for (int j = 0; j < MP_ARRAY_SIZE(contexts); j++) { |
299 | 5.69k | if (opts.context_type_list) |
300 | 0 | context_type_list[0].name = opts.context_type_list[i].name; |
301 | | |
302 | 5.69k | ctx = create_in_contexts(vo, contexts[j]->name, context_type_list, |
303 | 5.69k | contexts, MP_ARRAY_SIZE(contexts), |
304 | 5.69k | opts); |
305 | 5.69k | if (ctx) |
306 | 0 | goto done; |
307 | 5.69k | } |
308 | 5.69k | } |
309 | 5.69k | } |
310 | 5.69k | for (int i = 0; opts.context_list && opts.context_list[i].name; i++) { |
311 | 0 | ctx = create_in_contexts(vo, opts.context_list[i].name, |
312 | 0 | opts.context_type_list, contexts, |
313 | 0 | MP_ARRAY_SIZE(contexts), opts); |
314 | 0 | if (ctx) |
315 | 0 | goto done; |
316 | 0 | } |
317 | | |
318 | 5.69k | done: |
319 | 5.69k | if (ctx) { |
320 | 0 | vo->probing = old_probing; |
321 | 0 | vo->context_name = ctx->fns->name; |
322 | 0 | return ctx; |
323 | 0 | } |
324 | | |
325 | 5.69k | vo->probing = old_probing; |
326 | | |
327 | | // If we've reached this point, then none of the contexts matched the name |
328 | | // requested, or the backend creation failed for all of them. |
329 | 5.69k | if (!vo->probing) |
330 | 5.69k | MP_ERR(vo, "Failed initializing any suitable GPU context!\n"); |
331 | 5.69k | return NULL; |
332 | 5.69k | } |
333 | | |
334 | | void ra_ctx_destroy(struct ra_ctx **ctx_ptr) |
335 | 5.69k | { |
336 | 5.69k | struct ra_ctx *ctx = *ctx_ptr; |
337 | 5.69k | if (!ctx) |
338 | 5.69k | return; |
339 | | |
340 | 0 | if (ctx->spirv && ctx->spirv->fns->uninit) |
341 | 0 | ctx->spirv->fns->uninit(ctx); |
342 | |
|
343 | 0 | ctx->fns->uninit(ctx); |
344 | 0 | talloc_free(ctx); |
345 | |
|
346 | | *ctx_ptr = NULL; |
347 | 0 | } |