Coverage Report

Created: 2025-06-12 07:21

/src/mpv/video/out/dr_helper.c
Line
Count
Source (jump to first uncovered line)
1
#include <assert.h>
2
#include <stdatomic.h>
3
#include <stdlib.h>
4
5
#include <libavutil/buffer.h>
6
7
#include "misc/dispatch.h"
8
#include "mpv_talloc.h"
9
#include "osdep/threads.h"
10
#include "video/mp_image.h"
11
12
#include "dr_helper.h"
13
14
struct dr_helper {
15
    mp_mutex thread_lock;
16
    mp_thread_id thread_id;
17
    bool thread_valid; // (POSIX defines no "unset" mp_thread value yet)
18
19
    struct mp_dispatch_queue *dispatch;
20
    atomic_ullong dr_in_flight;
21
22
    struct mp_image *(*get_image)(void *ctx, int imgfmt, int w, int h,
23
                                  int stride_align, int flags);
24
    void *get_image_ctx;
25
};
26
27
static void dr_helper_destroy(void *ptr)
28
3.57k
{
29
3.57k
    struct dr_helper *dr = ptr;
30
31
    // All references must have been freed on destruction, or we'll have
32
    // dangling pointers.
33
3.57k
    mp_assert(atomic_load(&dr->dr_in_flight) == 0);
34
35
3.57k
    mp_mutex_destroy(&dr->thread_lock);
36
3.57k
}
37
38
struct dr_helper *dr_helper_create(struct mp_dispatch_queue *dispatch,
39
            struct mp_image *(*get_image)(void *ctx, int imgfmt, int w, int h,
40
                                          int stride_align, int flags),
41
            void *get_image_ctx)
42
3.57k
{
43
3.57k
    struct dr_helper *dr = talloc_ptrtype(NULL, dr);
44
3.57k
    talloc_set_destructor(dr, dr_helper_destroy);
45
3.57k
    *dr = (struct dr_helper){
46
3.57k
        .dispatch = dispatch,
47
3.57k
        .dr_in_flight = 0,
48
3.57k
        .get_image = get_image,
49
3.57k
        .get_image_ctx = get_image_ctx,
50
3.57k
    };
51
3.57k
    mp_mutex_init(&dr->thread_lock);
52
3.57k
    return dr;
53
3.57k
}
54
55
void dr_helper_acquire_thread(struct dr_helper *dr)
56
3.57k
{
57
3.57k
    mp_mutex_lock(&dr->thread_lock);
58
3.57k
    mp_assert(!dr->thread_valid); // fails on API user errors
59
3.57k
    dr->thread_valid = true;
60
3.57k
    dr->thread_id = mp_thread_current_id();
61
3.57k
    mp_mutex_unlock(&dr->thread_lock);
62
3.57k
}
63
64
void dr_helper_release_thread(struct dr_helper *dr)
65
0
{
66
0
    mp_mutex_lock(&dr->thread_lock);
67
    // Fails on API user errors.
68
0
    mp_assert(dr->thread_valid);
69
0
    mp_assert(mp_thread_id_equal(dr->thread_id, mp_thread_current_id()));
70
0
    dr->thread_valid = false;
71
0
    mp_mutex_unlock(&dr->thread_lock);
72
0
}
73
74
struct free_dr_context {
75
    struct dr_helper *dr;
76
    AVBufferRef *ref;
77
};
78
79
static void dr_thread_free(void *ptr)
80
0
{
81
0
    struct free_dr_context *ctx = ptr;
82
83
0
    unsigned long long v = atomic_fetch_add(&ctx->dr->dr_in_flight, -1);
84
0
    mp_assert(v); // value before sub is 0 - unexpected underflow.
85
86
0
    av_buffer_unref(&ctx->ref);
87
0
    talloc_free(ctx);
88
0
}
89
90
static void free_dr_buffer_on_dr_thread(void *opaque, uint8_t *data)
91
0
{
92
0
    struct free_dr_context *ctx = opaque;
93
0
    struct dr_helper *dr = ctx->dr;
94
95
0
    mp_mutex_lock(&dr->thread_lock);
96
0
    bool on_this_thread =
97
0
        dr->thread_valid && mp_thread_id_equal(ctx->dr->thread_id, mp_thread_current_id());
98
0
    mp_mutex_unlock(&dr->thread_lock);
99
100
    // The image could be unreffed even on the DR thread. In practice, this
101
    // matters most on DR destruction.
102
0
    if (on_this_thread) {
103
0
        dr_thread_free(ctx);
104
0
    } else {
105
0
        mp_dispatch_enqueue(dr->dispatch, dr_thread_free, ctx);
106
0
    }
107
0
}
108
109
struct get_image_cmd {
110
    struct dr_helper *dr;
111
    int imgfmt, w, h, stride_align, flags;
112
    struct mp_image *res;
113
};
114
115
static void sync_get_image(void *ptr)
116
0
{
117
0
    struct get_image_cmd *cmd = ptr;
118
0
    struct dr_helper *dr = cmd->dr;
119
120
0
    cmd->res = dr->get_image(dr->get_image_ctx, cmd->imgfmt, cmd->w, cmd->h,
121
0
                             cmd->stride_align, cmd->flags);
122
0
    if (!cmd->res)
123
0
        return;
124
125
    // We require exactly 1 AVBufferRef.
126
0
    mp_assert(cmd->res->bufs[0]);
127
0
    mp_assert(!cmd->res->bufs[1]);
128
129
    // Apply some magic to get it free'd on the DR thread as well. For this to
130
    // work, we create a dummy-ref that aliases the original ref, which is why
131
    // the original ref must be writable in the first place. (A newly allocated
132
    // image should be always writable of course.)
133
0
    mp_assert(mp_image_is_writeable(cmd->res));
134
135
0
    struct free_dr_context *ctx = talloc_zero(NULL, struct free_dr_context);
136
0
    *ctx = (struct free_dr_context){
137
0
        .dr = dr,
138
0
        .ref = cmd->res->bufs[0],
139
0
    };
140
141
0
    AVBufferRef *new_ref = av_buffer_create(ctx->ref->data, ctx->ref->size,
142
0
                                            free_dr_buffer_on_dr_thread, ctx, 0);
143
0
    MP_HANDLE_OOM(new_ref);
144
145
0
    cmd->res->bufs[0] = new_ref;
146
147
0
    atomic_fetch_add(&dr->dr_in_flight, 1);
148
0
}
149
150
struct mp_image *dr_helper_get_image(struct dr_helper *dr, int imgfmt,
151
                                     int w, int h, int stride_align, int flags)
152
0
{
153
0
    struct get_image_cmd cmd = {
154
0
        .dr = dr,
155
0
        .imgfmt = imgfmt,
156
0
        .w = w, .h = h,
157
0
        .stride_align = stride_align,
158
0
        .flags = flags,
159
0
    };
160
0
    mp_dispatch_run(dr->dispatch, sync_get_image, &cmd);
161
0
    return cmd.res;
162
0
}