Coverage Report

Created: 2026-01-26 07:36

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}