/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 | } |