1 /*
2 * Copyright © 2019, VideoLAN and dav1d authors
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice, this
9 * list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation
13 * and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
19 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
22 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27 #include "config.h"
28 #include "vcs_version.h"
29
30 #include <getopt.h>
31 #include <stdbool.h>
32 #include <stdint.h>
33 #include <stdio.h>
34 #include <string.h>
35
36 #include <SDL.h>
37
38 #include "common/attributes.h"
39
40 #include "dav1d/dav1d.h"
41
42 #include "tools/input/input.h"
43
44 /**
45 * Settings structure
46 * Hold all settings available for the player,
47 * this is usually filled by parsing arguments
48 * from the console.
49 */
50 typedef struct {
51 const char *inputfile;
52 int highquality;
53 int untimed;
54 int zerocopy;
55 } Dav1dPlaySettings;
56
57 #define WINDOW_WIDTH 910
58 #define WINDOW_HEIGHT 512
59
60 #define DAV1D_EVENT_NEW_FRAME 1
61 #define DAV1D_EVENT_DEC_QUIT 2
62
63 /*
64 * Fifo helper functions
65 */
66 typedef struct dp_fifo
67 {
68 SDL_mutex *lock;
69 SDL_cond *cond_change;
70 size_t capacity;
71 size_t count;
72 void **entries;
73 } Dav1dPlayPtrFifo;
74
dp_fifo_destroy(Dav1dPlayPtrFifo * fifo)75 static void dp_fifo_destroy(Dav1dPlayPtrFifo *fifo)
76 {
77 assert(fifo->count == 0);
78 SDL_DestroyMutex(fifo->lock);
79 SDL_DestroyCond(fifo->cond_change);
80 free(fifo->entries);
81 free(fifo);
82 }
83
dp_fifo_create(size_t capacity)84 static Dav1dPlayPtrFifo *dp_fifo_create(size_t capacity)
85 {
86 Dav1dPlayPtrFifo *fifo;
87
88 assert(capacity > 0);
89 if (capacity <= 0)
90 return NULL;
91
92 fifo = malloc(sizeof(*fifo));
93 if (fifo == NULL)
94 return NULL;
95
96 fifo->capacity = capacity;
97 fifo->count = 0;
98
99 fifo->lock = SDL_CreateMutex();
100 if (fifo->lock == NULL) {
101 free(fifo);
102 return NULL;
103 }
104 fifo->cond_change = SDL_CreateCond();
105 if (fifo->cond_change == NULL) {
106 SDL_DestroyMutex(fifo->lock);
107 free(fifo);
108 return NULL;
109 }
110
111 fifo->entries = calloc(capacity, sizeof(void*));
112 if (fifo->entries == NULL) {
113 dp_fifo_destroy(fifo);
114 return NULL;
115 }
116
117 return fifo;
118 }
119
dp_fifo_push(Dav1dPlayPtrFifo * fifo,void * element)120 static void dp_fifo_push(Dav1dPlayPtrFifo *fifo, void *element)
121 {
122 SDL_LockMutex(fifo->lock);
123 while (fifo->count == fifo->capacity)
124 SDL_CondWait(fifo->cond_change, fifo->lock);
125 fifo->entries[fifo->count++] = element;
126 if (fifo->count == 1)
127 SDL_CondSignal(fifo->cond_change);
128 SDL_UnlockMutex(fifo->lock);
129 }
130
dp_fifo_array_shift(void ** arr,size_t len)131 static void *dp_fifo_array_shift(void **arr, size_t len)
132 {
133 void *shifted_element = arr[0];
134 for (size_t i = 1; i < len; ++i)
135 arr[i-1] = arr[i];
136 return shifted_element;
137 }
138
dp_fifo_shift(Dav1dPlayPtrFifo * fifo)139 static void *dp_fifo_shift(Dav1dPlayPtrFifo *fifo)
140 {
141 SDL_LockMutex(fifo->lock);
142 while (fifo->count == 0)
143 SDL_CondWait(fifo->cond_change, fifo->lock);
144 void *res = dp_fifo_array_shift(fifo->entries, fifo->count--);
145 if (fifo->count == fifo->capacity - 1)
146 SDL_CondSignal(fifo->cond_change);
147 SDL_UnlockMutex(fifo->lock);
148 return res;
149 }
150
151 /**
152 * Renderer info
153 */
154 typedef struct rdr_info
155 {
156 // Cookie passed to the renderer implementation callbacks
157 void *cookie;
158 // Callback to create the renderer
159 void* (*create_renderer)(void *data);
160 // Callback to destroy the renderer
161 void (*destroy_renderer)(void *cookie);
162 // Callback to the render function that renders a prevously sent frame
163 void (*render)(void *cookie, const Dav1dPlaySettings *settings);
164 // Callback to the send frame function
165 int (*update_frame)(void *cookie, Dav1dPicture *dav1d_pic,
166 const Dav1dPlaySettings *settings);
167 // Callback for alloc/release pictures (optional)
168 int (*alloc_pic)(Dav1dPicture *pic, void *cookie);
169 void (*release_pic)(Dav1dPicture *pic, void *cookie);
170 } Dav1dPlayRenderInfo;
171
172 #ifdef HAVE_PLACEBO_VULKAN
173
174 #include <libplacebo/renderer.h>
175 #include <libplacebo/utils/upload.h>
176 #include <libplacebo/vulkan.h>
177 #include <SDL_vulkan.h>
178
179
180 /**
181 * Renderer context for libplacebo
182 */
183 typedef struct renderer_priv_ctx
184 {
185 // Placebo context
186 struct pl_context *ctx;
187 // Placebo renderer
188 struct pl_renderer *renderer;
189 // Placebo Vulkan handle
190 const struct pl_vulkan *vk;
191 // Placebo Vulkan instance
192 const struct pl_vk_inst *vk_inst;
193 // Vulkan surface
194 VkSurfaceKHR surf;
195 // Placebo swapchain
196 const struct pl_swapchain *swapchain;
197 // Lock protecting access to the texture
198 SDL_mutex *lock;
199 // Planes to render
200 struct pl_plane y_plane;
201 struct pl_plane u_plane;
202 struct pl_plane v_plane;
203 // Textures to render
204 const struct pl_tex *y_tex;
205 const struct pl_tex *u_tex;
206 const struct pl_tex *v_tex;
207 } Dav1dPlayRendererPrivateContext;
208
placebo_renderer_create(void * data)209 static void *placebo_renderer_create(void *data)
210 {
211 // Alloc
212 Dav1dPlayRendererPrivateContext *rd_priv_ctx = malloc(sizeof(Dav1dPlayRendererPrivateContext));
213 if (rd_priv_ctx == NULL) {
214 return NULL;
215 }
216
217 // Init libplacebo
218 rd_priv_ctx->ctx = pl_context_create(PL_API_VER, &(struct pl_context_params) {
219 .log_cb = pl_log_color,
220 #ifndef NDEBUG
221 .log_level = PL_LOG_DEBUG,
222 #else
223 .log_level = PL_LOG_WARN,
224 #endif
225 });
226 if (rd_priv_ctx->ctx == NULL) {
227 free(rd_priv_ctx);
228 return NULL;
229 }
230
231 // Create Mutex
232 rd_priv_ctx->lock = SDL_CreateMutex();
233 if (rd_priv_ctx->lock == NULL) {
234 fprintf(stderr, "SDL_CreateMutex failed: %s\n", SDL_GetError());
235 pl_context_destroy(&(rd_priv_ctx->ctx));
236 free(rd_priv_ctx);
237 return NULL;
238 }
239
240 // Init Vulkan
241 struct pl_vk_inst_params iparams = pl_vk_inst_default_params;
242
243 SDL_Window *sdlwin = data;
244
245 unsigned num = 0;
246 if (!SDL_Vulkan_GetInstanceExtensions(sdlwin, &num, NULL)) {
247 fprintf(stderr, "Failed enumerating Vulkan extensions: %s\n", SDL_GetError());
248 exit(1);
249 }
250
251 iparams.extensions = malloc(num * sizeof(const char *));
252 iparams.num_extensions = num;
253 assert(iparams.extensions);
254
255 SDL_bool ok = SDL_Vulkan_GetInstanceExtensions(sdlwin, &num, iparams.extensions);
256 if (!ok) {
257 fprintf(stderr, "Failed getting Vk instance extensions\n");
258 exit(1);
259 }
260
261 if (num > 0) {
262 printf("Requesting %d additional Vulkan extensions:\n", num);
263 for (unsigned i = 0; i < num; i++)
264 printf(" %s\n", iparams.extensions[i]);
265 }
266
267 rd_priv_ctx->vk_inst = pl_vk_inst_create(rd_priv_ctx->ctx, &iparams);
268 if (!rd_priv_ctx->vk_inst) {
269 fprintf(stderr, "Failed creating Vulkan instance!\n");
270 exit(1);
271 }
272 free(iparams.extensions);
273
274 if (!SDL_Vulkan_CreateSurface(sdlwin, rd_priv_ctx->vk_inst->instance, &rd_priv_ctx->surf)) {
275 fprintf(stderr, "Failed creating vulkan surface: %s\n", SDL_GetError());
276 exit(1);
277 }
278
279 struct pl_vulkan_params params = pl_vulkan_default_params;
280 params.instance = rd_priv_ctx->vk_inst->instance;
281 params.surface = rd_priv_ctx->surf;
282 params.allow_software = true;
283
284 rd_priv_ctx->vk = pl_vulkan_create(rd_priv_ctx->ctx, ¶ms);
285 if (!rd_priv_ctx->vk) {
286 fprintf(stderr, "Failed creating vulkan device!\n");
287 exit(2);
288 }
289
290 // Create swapchain
291 rd_priv_ctx->swapchain = pl_vulkan_create_swapchain(rd_priv_ctx->vk,
292 &(struct pl_vulkan_swapchain_params) {
293 .surface = rd_priv_ctx->surf,
294 .present_mode = VK_PRESENT_MODE_IMMEDIATE_KHR,
295 });
296
297 if (!rd_priv_ctx->swapchain) {
298 fprintf(stderr, "Failed creating vulkan swapchain!\n");
299 exit(2);
300 }
301
302 int w = WINDOW_WIDTH, h = WINDOW_HEIGHT;
303 if (!pl_swapchain_resize(rd_priv_ctx->swapchain, &w, &h)) {
304 fprintf(stderr, "Failed resizing vulkan swapchain!\n");
305 exit(2);
306 }
307
308 if (w != WINDOW_WIDTH || h != WINDOW_HEIGHT)
309 printf("Note: window dimensions differ (got %dx%d)\n", w, h);
310
311 rd_priv_ctx->y_tex = NULL;
312 rd_priv_ctx->u_tex = NULL;
313 rd_priv_ctx->v_tex = NULL;
314
315 rd_priv_ctx->renderer = NULL;
316
317 return rd_priv_ctx;
318 }
319
placebo_renderer_destroy(void * cookie)320 static void placebo_renderer_destroy(void *cookie)
321 {
322 Dav1dPlayRendererPrivateContext *rd_priv_ctx = cookie;
323 assert(rd_priv_ctx != NULL);
324
325 pl_renderer_destroy(&(rd_priv_ctx->renderer));
326 pl_tex_destroy(rd_priv_ctx->vk->gpu, &(rd_priv_ctx->y_tex));
327 pl_tex_destroy(rd_priv_ctx->vk->gpu, &(rd_priv_ctx->u_tex));
328 pl_tex_destroy(rd_priv_ctx->vk->gpu, &(rd_priv_ctx->v_tex));
329 pl_swapchain_destroy(&(rd_priv_ctx->swapchain));
330 pl_vulkan_destroy(&(rd_priv_ctx->vk));
331 vkDestroySurfaceKHR(rd_priv_ctx->vk_inst->instance, rd_priv_ctx->surf, NULL);
332 pl_vk_inst_destroy(&(rd_priv_ctx->vk_inst));
333 pl_context_destroy(&(rd_priv_ctx->ctx));
334 }
335
placebo_render(void * cookie,const Dav1dPlaySettings * settings)336 static void placebo_render(void *cookie, const Dav1dPlaySettings *settings)
337 {
338 Dav1dPlayRendererPrivateContext *rd_priv_ctx = cookie;
339 assert(rd_priv_ctx != NULL);
340
341 SDL_LockMutex(rd_priv_ctx->lock);
342 if (rd_priv_ctx->y_tex == NULL) {
343 SDL_UnlockMutex(rd_priv_ctx->lock);
344 return;
345 }
346
347 // Prepare rendering
348 if (rd_priv_ctx->renderer == NULL) {
349 rd_priv_ctx->renderer = pl_renderer_create(rd_priv_ctx->ctx, rd_priv_ctx->vk->gpu);
350 }
351
352 struct pl_swapchain_frame frame;
353 bool ok = pl_swapchain_start_frame(rd_priv_ctx->swapchain, &frame);
354 if (!ok) {
355 SDL_UnlockMutex(rd_priv_ctx->lock);
356 return;
357 }
358
359 const struct pl_tex *img = rd_priv_ctx->y_plane.texture;
360 struct pl_image image = {
361 .num_planes = 3,
362 .planes = { rd_priv_ctx->y_plane, rd_priv_ctx->u_plane, rd_priv_ctx->v_plane },
363 .repr = pl_color_repr_hdtv,
364 .color = pl_color_space_unknown,
365 .width = img->params.w,
366 .height = img->params.h,
367 };
368
369 struct pl_render_params render_params = {0};
370 if (settings->highquality)
371 render_params = pl_render_default_params;
372
373 struct pl_render_target target;
374 pl_render_target_from_swapchain(&target, &frame);
375 target.profile = (struct pl_icc_profile) {
376 .data = NULL,
377 .len = 0,
378 };
379
380 if (!pl_render_image(rd_priv_ctx->renderer, &image, &target, &render_params)) {
381 fprintf(stderr, "Failed rendering frame!\n");
382 SDL_UnlockMutex(rd_priv_ctx->lock);
383 return;
384 }
385
386 ok = pl_swapchain_submit_frame(rd_priv_ctx->swapchain);
387 if (!ok) {
388 fprintf(stderr, "Failed submitting frame!\n");
389 SDL_UnlockMutex(rd_priv_ctx->lock);
390 return;
391 }
392
393 pl_swapchain_swap_buffers(rd_priv_ctx->swapchain);
394 SDL_UnlockMutex(rd_priv_ctx->lock);
395 }
396
placebo_upload_planes(void * cookie,Dav1dPicture * dav1d_pic,const Dav1dPlaySettings * settings)397 static int placebo_upload_planes(void *cookie, Dav1dPicture *dav1d_pic,
398 const Dav1dPlaySettings *settings)
399 {
400 Dav1dPlayRendererPrivateContext *rd_priv_ctx = cookie;
401 assert(rd_priv_ctx != NULL);
402
403 SDL_LockMutex(rd_priv_ctx->lock);
404
405 if (dav1d_pic == NULL) {
406 SDL_UnlockMutex(rd_priv_ctx->lock);
407 return 0;
408 }
409
410 int width = dav1d_pic->p.w;
411 int height = dav1d_pic->p.h;
412
413 enum Dav1dPixelLayout dav1d_layout = dav1d_pic->p.layout;
414
415 if (DAV1D_PIXEL_LAYOUT_I420 != dav1d_layout || dav1d_pic->p.bpc != 8) {
416 fprintf(stderr, "Unsupported pixel format, only 8bit 420 supported so far.\n");
417 exit(50);
418 }
419
420 struct pl_plane_data data_y = {
421 .type = PL_FMT_UNORM,
422 .width = width,
423 .height = height,
424 .pixel_stride = 1,
425 .row_stride = dav1d_pic->stride[0],
426 .component_size = {8},
427 .component_map = {0},
428 };
429
430 struct pl_plane_data data_u = {
431 .type = PL_FMT_UNORM,
432 .width = width/2,
433 .height = height/2,
434 .pixel_stride = 1,
435 .row_stride = dav1d_pic->stride[1],
436 .component_size = {8},
437 .component_map = {1},
438 };
439
440 struct pl_plane_data data_v = {
441 .type = PL_FMT_UNORM,
442 .width = width/2,
443 .height = height/2,
444 .pixel_stride = 1,
445 .row_stride = dav1d_pic->stride[1],
446 .component_size = {8},
447 .component_map = {2},
448 };
449
450 if (settings->zerocopy) {
451 const struct pl_buf *buf = dav1d_pic->allocator_data;
452 assert(buf);
453 data_y.buf = data_u.buf = data_v.buf = buf;
454 data_y.buf_offset = (uintptr_t) dav1d_pic->data[0] - (uintptr_t) buf->data;
455 data_u.buf_offset = (uintptr_t) dav1d_pic->data[1] - (uintptr_t) buf->data;
456 data_v.buf_offset = (uintptr_t) dav1d_pic->data[2] - (uintptr_t) buf->data;
457 } else {
458 data_y.pixels = dav1d_pic->data[0];
459 data_u.pixels = dav1d_pic->data[1];
460 data_v.pixels = dav1d_pic->data[2];
461 }
462
463 bool ok = true;
464 ok &= pl_upload_plane(rd_priv_ctx->vk->gpu, &(rd_priv_ctx->y_plane), &(rd_priv_ctx->y_tex), &data_y);
465 ok &= pl_upload_plane(rd_priv_ctx->vk->gpu, &(rd_priv_ctx->u_plane), &(rd_priv_ctx->u_tex), &data_u);
466 ok &= pl_upload_plane(rd_priv_ctx->vk->gpu, &(rd_priv_ctx->v_plane), &(rd_priv_ctx->v_tex), &data_v);
467
468 pl_chroma_location_offset(PL_CHROMA_LEFT, &rd_priv_ctx->u_plane.shift_x, &rd_priv_ctx->u_plane.shift_y);
469 pl_chroma_location_offset(PL_CHROMA_LEFT, &rd_priv_ctx->v_plane.shift_x, &rd_priv_ctx->v_plane.shift_y);
470
471 if (!ok) {
472 fprintf(stderr, "Failed uploading planes!\n");
473 }
474
475 SDL_UnlockMutex(rd_priv_ctx->lock);
476 return !ok;
477 }
478
479 // Align to power of 2
480 #define ALIGN2(x, align) (((x) + (align) - 1) & ~((align) - 1))
481
placebo_alloc_pic(Dav1dPicture * const p,void * cookie)482 static int placebo_alloc_pic(Dav1dPicture *const p, void *cookie)
483 {
484 Dav1dPlayRendererPrivateContext *rd_priv_ctx = cookie;
485 assert(rd_priv_ctx != NULL);
486 SDL_LockMutex(rd_priv_ctx->lock);
487
488 const struct pl_gpu *gpu = rd_priv_ctx->vk->gpu;
489 int ret = DAV1D_ERR(ENOMEM);
490
491 // Copied from dav1d_default_picture_alloc
492 const int hbd = p->p.bpc > 8;
493 const int aligned_w = ALIGN2(p->p.w, 128);
494 const int aligned_h = ALIGN2(p->p.h, 128);
495 const int has_chroma = p->p.layout != DAV1D_PIXEL_LAYOUT_I400;
496 const int ss_ver = p->p.layout == DAV1D_PIXEL_LAYOUT_I420;
497 const int ss_hor = p->p.layout != DAV1D_PIXEL_LAYOUT_I444;
498 p->stride[0] = aligned_w << hbd;
499 p->stride[1] = has_chroma ? (aligned_w >> ss_hor) << hbd : 0;
500
501 // Align strides up to multiples of the GPU performance hints
502 p->stride[0] = ALIGN2(p->stride[0], gpu->limits.align_tex_xfer_stride);
503 p->stride[1] = ALIGN2(p->stride[1], gpu->limits.align_tex_xfer_stride);
504
505 // Aligning offsets to 4 also implicity aligns to the texel size (1 or 2)
506 size_t off_align = ALIGN2(gpu->limits.align_tex_xfer_offset, 4);
507 const size_t y_sz = ALIGN2(p->stride[0] * aligned_h, off_align);
508 const size_t uv_sz = ALIGN2(p->stride[1] * (aligned_h >> ss_ver), off_align);
509
510 // The extra DAV1D_PICTURE_ALIGNMENTs are to brute force plane alignment,
511 // even in the case that the driver gives us insane alignments
512 const size_t pic_size = y_sz + 2 * uv_sz;
513 const size_t total_size = pic_size + DAV1D_PICTURE_ALIGNMENT * 4;
514
515 // Validate size limitations
516 if (total_size > gpu->limits.max_xfer_size) {
517 printf("alloc of %zu bytes exceeds limits\n", total_size);
518 goto err;
519 }
520
521 const struct pl_buf *buf = pl_buf_create(gpu, &(struct pl_buf_params) {
522 .type = PL_BUF_TEX_TRANSFER,
523 .host_mapped = true,
524 .size = total_size,
525 .memory_type = PL_BUF_MEM_HOST,
526 .user_data = p,
527 });
528
529 if (!buf) {
530 printf("alloc of GPU mapped buffer failed\n");
531 goto err;
532 }
533
534 assert(buf->data);
535 uintptr_t base = (uintptr_t) buf->data, data[3];
536 data[0] = ALIGN2(base, DAV1D_PICTURE_ALIGNMENT);
537 data[1] = ALIGN2(data[0] + y_sz, DAV1D_PICTURE_ALIGNMENT);
538 data[2] = ALIGN2(data[1] + uv_sz, DAV1D_PICTURE_ALIGNMENT);
539
540 // Sanity check offset alignment for the sake of debugging
541 if (data[0] - base != ALIGN2(data[0] - base, off_align) ||
542 data[1] - base != ALIGN2(data[1] - base, off_align) ||
543 data[2] - base != ALIGN2(data[2] - base, off_align))
544 {
545 printf("GPU buffer horribly misaligned, expect slowdown!\n");
546 }
547
548 p->allocator_data = (void *) buf;
549 p->data[0] = (void *) data[0];
550 p->data[1] = (void *) data[1];
551 p->data[2] = (void *) data[2];
552 ret = 0;
553
554 // fall through
555 err:
556 SDL_UnlockMutex(rd_priv_ctx->lock);
557 return ret;
558 }
559
placebo_release_pic(Dav1dPicture * pic,void * cookie)560 static void placebo_release_pic(Dav1dPicture *pic, void *cookie)
561 {
562 Dav1dPlayRendererPrivateContext *rd_priv_ctx = cookie;
563 assert(rd_priv_ctx != NULL);
564 assert(pic->allocator_data);
565
566 SDL_LockMutex(rd_priv_ctx->lock);
567 const struct pl_gpu *gpu = rd_priv_ctx->vk->gpu;
568 pl_buf_destroy(gpu, (const struct pl_buf **) &pic->allocator_data);
569 SDL_UnlockMutex(rd_priv_ctx->lock);
570 }
571
572 static const Dav1dPlayRenderInfo renderer_info = {
573 .create_renderer = placebo_renderer_create,
574 .destroy_renderer = placebo_renderer_destroy,
575 .render = placebo_render,
576 .update_frame = placebo_upload_planes,
577 .alloc_pic = placebo_alloc_pic,
578 .release_pic = placebo_release_pic,
579 };
580
581 #else
582
583 /**
584 * Renderer context for SDL
585 */
586 typedef struct renderer_priv_ctx
587 {
588 // SDL renderer
589 SDL_Renderer *renderer;
590 // Lock protecting access to the texture
591 SDL_mutex *lock;
592 // Texture to render
593 SDL_Texture *tex;
594 } Dav1dPlayRendererPrivateContext;
595
sdl_renderer_create(void * data)596 static void *sdl_renderer_create(void *data)
597 {
598 SDL_Window *win = data;
599
600 // Alloc
601 Dav1dPlayRendererPrivateContext *rd_priv_ctx = malloc(sizeof(Dav1dPlayRendererPrivateContext));
602 if (rd_priv_ctx == NULL) {
603 return NULL;
604 }
605
606 // Create renderer
607 rd_priv_ctx->renderer = SDL_CreateRenderer(win, -1, SDL_RENDERER_ACCELERATED);
608 // Set scale quality
609 SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear");
610
611 // Create Mutex
612 rd_priv_ctx->lock = SDL_CreateMutex();
613 if (rd_priv_ctx->lock == NULL) {
614 fprintf(stderr, "SDL_CreateMutex failed: %s\n", SDL_GetError());
615 free(rd_priv_ctx);
616 return NULL;
617 }
618
619 rd_priv_ctx->tex = NULL;
620
621 return rd_priv_ctx;
622 }
623
sdl_renderer_destroy(void * cookie)624 static void sdl_renderer_destroy(void *cookie)
625 {
626 Dav1dPlayRendererPrivateContext *rd_priv_ctx = cookie;
627 assert(rd_priv_ctx != NULL);
628
629 SDL_DestroyRenderer(rd_priv_ctx->renderer);
630 SDL_DestroyMutex(rd_priv_ctx->lock);
631 free(rd_priv_ctx);
632 }
633
sdl_render(void * cookie,const Dav1dPlaySettings * settings)634 static void sdl_render(void *cookie, const Dav1dPlaySettings *settings)
635 {
636 Dav1dPlayRendererPrivateContext *rd_priv_ctx = cookie;
637 assert(rd_priv_ctx != NULL);
638
639 SDL_LockMutex(rd_priv_ctx->lock);
640
641 if (rd_priv_ctx->tex == NULL) {
642 SDL_UnlockMutex(rd_priv_ctx->lock);
643 return;
644 }
645
646 // Display the frame
647 SDL_RenderClear(rd_priv_ctx->renderer);
648 SDL_RenderCopy(rd_priv_ctx->renderer, rd_priv_ctx->tex, NULL, NULL);
649 SDL_RenderPresent(rd_priv_ctx->renderer);
650
651 SDL_UnlockMutex(rd_priv_ctx->lock);
652 }
653
sdl_update_texture(void * cookie,Dav1dPicture * dav1d_pic,const Dav1dPlaySettings * settings)654 static int sdl_update_texture(void *cookie, Dav1dPicture *dav1d_pic,
655 const Dav1dPlaySettings *settings)
656 {
657 Dav1dPlayRendererPrivateContext *rd_priv_ctx = cookie;
658 assert(rd_priv_ctx != NULL);
659
660 SDL_LockMutex(rd_priv_ctx->lock);
661
662 if (dav1d_pic == NULL) {
663 rd_priv_ctx->tex = NULL;
664 SDL_UnlockMutex(rd_priv_ctx->lock);
665 return 0;
666 }
667
668 int width = dav1d_pic->p.w;
669 int height = dav1d_pic->p.h;
670 int tex_w = width;
671 int tex_h = height;
672
673 enum Dav1dPixelLayout dav1d_layout = dav1d_pic->p.layout;
674
675 if (DAV1D_PIXEL_LAYOUT_I420 != dav1d_layout || dav1d_pic->p.bpc != 8) {
676 fprintf(stderr, "Unsupported pixel format, only 8bit 420 supported so far.\n");
677 exit(50);
678 }
679
680 SDL_Texture *texture = rd_priv_ctx->tex;
681 if (texture != NULL) {
682 SDL_QueryTexture(texture, NULL, NULL, &tex_w, &tex_h);
683 if (tex_w != width || tex_h != height) {
684 SDL_DestroyTexture(texture);
685 texture = NULL;
686 }
687 }
688
689 if (texture == NULL) {
690 texture = SDL_CreateTexture(rd_priv_ctx->renderer, SDL_PIXELFORMAT_IYUV,
691 SDL_TEXTUREACCESS_STREAMING, width, height);
692 }
693
694 SDL_UpdateYUVTexture(texture, NULL,
695 dav1d_pic->data[0], (int)dav1d_pic->stride[0], // Y
696 dav1d_pic->data[1], (int)dav1d_pic->stride[1], // U
697 dav1d_pic->data[2], (int)dav1d_pic->stride[1] // V
698 );
699
700 rd_priv_ctx->tex = texture;
701 SDL_UnlockMutex(rd_priv_ctx->lock);
702 return 0;
703 }
704
705 static const Dav1dPlayRenderInfo renderer_info = {
706 .create_renderer = sdl_renderer_create,
707 .destroy_renderer = sdl_renderer_destroy,
708 .render = sdl_render,
709 .update_frame = sdl_update_texture
710 };
711
712 #endif
713
714 /**
715 * Render context structure
716 * This structure contains informations necessary
717 * to be shared between the decoder and the renderer
718 * threads.
719 */
720 typedef struct render_context
721 {
722 Dav1dPlaySettings settings;
723 Dav1dSettings lib_settings;
724
725 // Renderer callbacks
726 Dav1dPlayRenderInfo *renderer_info;
727 // Renderer private data (passed to callbacks)
728 void *rd_priv;
729
730 // Lock to protect access to the context structure
731 SDL_mutex *lock;
732
733 // Timestamp of previous decoded frame
734 int64_t last_pts;
735 // Timestamp of current decoded frame
736 int64_t current_pts;
737 // Ticks when last frame was received
738 uint32_t last_ticks;
739 // PTS time base
740 double timebase;
741
742 // Fifo
743 Dav1dPlayPtrFifo *fifo;
744
745 // Custom SDL2 event type
746 uint32_t renderer_event_type;
747
748 // Indicates if termination of the decoder thread was requested
749 uint8_t dec_should_terminate;
750 } Dav1dPlayRenderContext;
751
dp_settings_print_usage(const char * const app,const char * const reason,...)752 static void dp_settings_print_usage(const char *const app,
753 const char *const reason, ...)
754 {
755 if (reason) {
756 va_list args;
757
758 va_start(args, reason);
759 vfprintf(stderr, reason, args);
760 va_end(args);
761 fprintf(stderr, "\n\n");
762 }
763 fprintf(stderr, "Usage: %s [options]\n\n", app);
764 fprintf(stderr, "Supported options:\n"
765 " --input/-i $file: input file\n"
766 " --untimed/-u: ignore PTS, render as fast as possible\n"
767 " --framethreads $num: number of frame threads (default: 1)\n"
768 " --tilethreads $num: number of tile threads (default: 1)\n"
769 " --highquality: enable high quality rendering\n"
770 " --zerocopy/-z: enable zero copy upload path\n"
771 " --version/-v: print version and exit\n");
772 exit(1);
773 }
774
parse_unsigned(const char * const optarg,const int option,const char * const app)775 static unsigned parse_unsigned(const char *const optarg, const int option,
776 const char *const app)
777 {
778 char *end;
779 const unsigned res = (unsigned) strtoul(optarg, &end, 0);
780 if (*end || end == optarg)
781 dp_settings_print_usage(app, "Invalid argument \"%s\" for option %s; should be an integer",
782 optarg, option);
783 return res;
784 }
785
dp_rd_ctx_parse_args(Dav1dPlayRenderContext * rd_ctx,const int argc,char * const * const argv)786 static void dp_rd_ctx_parse_args(Dav1dPlayRenderContext *rd_ctx,
787 const int argc, char *const *const argv)
788 {
789 int o;
790 Dav1dPlaySettings *settings = &rd_ctx->settings;
791 Dav1dSettings *lib_settings = &rd_ctx->lib_settings;
792
793 // Short options
794 static const char short_opts[] = "i:vuz";
795
796 enum {
797 ARG_FRAME_THREADS = 256,
798 ARG_TILE_THREADS,
799 ARG_HIGH_QUALITY,
800 };
801
802 // Long options
803 static const struct option long_opts[] = {
804 { "input", 1, NULL, 'i' },
805 { "version", 0, NULL, 'v' },
806 { "untimed", 0, NULL, 'u' },
807 { "framethreads", 1, NULL, ARG_FRAME_THREADS },
808 { "tilethreads", 1, NULL, ARG_TILE_THREADS },
809 { "highquality", 0, NULL, ARG_HIGH_QUALITY },
810 { "zerocopy", 0, NULL, 'z' },
811 { NULL, 0, NULL, 0 },
812 };
813
814 while ((o = getopt_long(argc, argv, short_opts, long_opts, NULL)) != -1) {
815 switch (o) {
816 case 'i':
817 settings->inputfile = optarg;
818 break;
819 case 'v':
820 fprintf(stderr, "%s\n", dav1d_version());
821 exit(0);
822 case 'u':
823 settings->untimed = true;
824 break;
825 case ARG_HIGH_QUALITY:
826 settings->highquality = true;
827 #ifndef HAVE_PLACEBO_VULKAN
828 fprintf(stderr, "warning: --highquality requires libplacebo\n");
829 #endif
830 break;
831 case 'z':
832 settings->zerocopy = true;
833 #ifndef HAVE_PLACEBO_VULKAN
834 fprintf(stderr, "warning: --zerocopy requires libplacebo\n");
835 #endif
836 break;
837 case ARG_FRAME_THREADS:
838 lib_settings->n_frame_threads =
839 parse_unsigned(optarg, ARG_FRAME_THREADS, argv[0]);
840 break;
841 case ARG_TILE_THREADS:
842 lib_settings->n_tile_threads =
843 parse_unsigned(optarg, ARG_TILE_THREADS, argv[0]);
844 break;
845 default:
846 dp_settings_print_usage(argv[0], NULL);
847 }
848 }
849
850 if (optind < argc)
851 dp_settings_print_usage(argv[0],
852 "Extra/unused arguments found, e.g. '%s'\n", argv[optind]);
853 if (!settings->inputfile)
854 dp_settings_print_usage(argv[0], "Input file (-i/--input) is required");
855 }
856
857 /**
858 * Destroy a Dav1dPlayRenderContext
859 */
dp_rd_ctx_destroy(Dav1dPlayRenderContext * rd_ctx)860 static void dp_rd_ctx_destroy(Dav1dPlayRenderContext *rd_ctx)
861 {
862 assert(rd_ctx != NULL);
863
864 renderer_info.destroy_renderer(rd_ctx->rd_priv);
865 dp_fifo_destroy(rd_ctx->fifo);
866 SDL_DestroyMutex(rd_ctx->lock);
867 free(rd_ctx);
868 }
869
870 /**
871 * Create a Dav1dPlayRenderContext
872 *
873 * \note The Dav1dPlayRenderContext must be destroyed
874 * again by using dp_rd_ctx_destroy.
875 */
dp_rd_ctx_create(void * rd_data)876 static Dav1dPlayRenderContext *dp_rd_ctx_create(void *rd_data)
877 {
878 Dav1dPlayRenderContext *rd_ctx;
879
880 // Alloc
881 rd_ctx = malloc(sizeof(Dav1dPlayRenderContext));
882 if (rd_ctx == NULL) {
883 return NULL;
884 }
885
886 // Register a custom event to notify our SDL main thread
887 // about new frames
888 rd_ctx->renderer_event_type = SDL_RegisterEvents(1);
889 if (rd_ctx->renderer_event_type == UINT32_MAX) {
890 fprintf(stderr, "Failure to create custom SDL event type!\n");
891 free(rd_ctx);
892 return NULL;
893 }
894
895 rd_ctx->fifo = dp_fifo_create(5);
896 if (rd_ctx->fifo == NULL) {
897 fprintf(stderr, "Failed to create FIFO for output pictures!\n");
898 free(rd_ctx);
899 return NULL;
900 }
901
902 rd_ctx->lock = SDL_CreateMutex();
903 if (rd_ctx->lock == NULL) {
904 fprintf(stderr, "SDL_CreateMutex failed: %s\n", SDL_GetError());
905 dp_fifo_destroy(rd_ctx->fifo);
906 free(rd_ctx);
907 return NULL;
908 }
909
910 rd_ctx->rd_priv = renderer_info.create_renderer(rd_data);
911 if (rd_ctx->rd_priv == NULL) {
912 SDL_DestroyMutex(rd_ctx->lock);
913 dp_fifo_destroy(rd_ctx->fifo);
914 free(rd_ctx);
915 return NULL;
916 }
917
918 dav1d_default_settings(&rd_ctx->lib_settings);
919 memset(&rd_ctx->settings, 0, sizeof(rd_ctx->settings));
920
921 rd_ctx->last_pts = 0;
922 rd_ctx->last_ticks = 0;
923 rd_ctx->current_pts = 0;
924 rd_ctx->timebase = 0;
925 rd_ctx->dec_should_terminate = 0;
926
927 return rd_ctx;
928 }
929
930 /**
931 * Notify about new available frame
932 */
dp_rd_ctx_post_event(Dav1dPlayRenderContext * rd_ctx,uint32_t code)933 static void dp_rd_ctx_post_event(Dav1dPlayRenderContext *rd_ctx, uint32_t code)
934 {
935 SDL_Event event;
936 SDL_zero(event);
937 event.type = rd_ctx->renderer_event_type;
938 event.user.code = code;
939 SDL_PushEvent(&event);
940 }
941
942 /**
943 * Update the decoder context with a new dav1d picture
944 *
945 * Once the decoder decoded a new picture, this call can be used
946 * to update the internal texture of the render context with the
947 * new picture.
948 */
dp_rd_ctx_update_with_dav1d_picture(Dav1dPlayRenderContext * rd_ctx,Dav1dPicture * dav1d_pic)949 static void dp_rd_ctx_update_with_dav1d_picture(Dav1dPlayRenderContext *rd_ctx,
950 Dav1dPicture *dav1d_pic)
951 {
952 renderer_info.update_frame(rd_ctx->rd_priv, dav1d_pic, &rd_ctx->settings);
953 rd_ctx->current_pts = dav1d_pic->m.timestamp;
954 }
955
956 /**
957 * Terminate decoder thread (async)
958 */
dp_rd_ctx_request_shutdown(Dav1dPlayRenderContext * rd_ctx)959 static void dp_rd_ctx_request_shutdown(Dav1dPlayRenderContext *rd_ctx)
960 {
961 SDL_LockMutex(rd_ctx->lock);
962 rd_ctx->dec_should_terminate = 1;
963 SDL_UnlockMutex(rd_ctx->lock);
964 }
965
966 /**
967 * Query state of decoder shutdown request
968 */
dp_rd_ctx_should_terminate(Dav1dPlayRenderContext * rd_ctx)969 static int dp_rd_ctx_should_terminate(Dav1dPlayRenderContext *rd_ctx)
970 {
971 int ret = 0;
972 SDL_LockMutex(rd_ctx->lock);
973 ret = rd_ctx->dec_should_terminate;
974 SDL_UnlockMutex(rd_ctx->lock);
975 return ret;
976 }
977
978 /**
979 * Render the currently available texture
980 *
981 * Renders the currently available texture, if any.
982 */
dp_rd_ctx_render(Dav1dPlayRenderContext * rd_ctx)983 static void dp_rd_ctx_render(Dav1dPlayRenderContext *rd_ctx)
984 {
985 // Calculate time since last frame was received
986 uint32_t ticks_now = SDL_GetTicks();
987 uint32_t ticks_diff = (rd_ctx->last_ticks != 0) ? ticks_now - rd_ctx->last_ticks : 0;
988
989 // Calculate when to display the frame
990 int64_t pts_diff = rd_ctx->current_pts - rd_ctx->last_pts;
991 int32_t wait_time = (pts_diff * rd_ctx->timebase) * 1000 - ticks_diff;
992 rd_ctx->last_pts = rd_ctx->current_pts;
993
994 // In untimed mode, simply don't wait
995 if (rd_ctx->settings.untimed)
996 wait_time = 0;
997
998 // This way of timing the playback is not accurate, as there is no guarantee
999 // that SDL_Delay will wait for exactly the requested amount of time so in a
1000 // accurate player this would need to be done in a better way.
1001 if (wait_time > 0) {
1002 SDL_Delay(wait_time);
1003 } else if (wait_time < -10) { // Do not warn for minor time drifts
1004 fprintf(stderr, "Frame displayed %f seconds too late\n", wait_time/(float)1000);
1005 }
1006
1007 renderer_info.render(rd_ctx->rd_priv, &rd_ctx->settings);
1008
1009 rd_ctx->last_ticks = SDL_GetTicks();
1010 }
1011
1012 /* Decoder thread "main" function */
decoder_thread_main(void * cookie)1013 static int decoder_thread_main(void *cookie)
1014 {
1015 Dav1dPlayRenderContext *rd_ctx = cookie;
1016
1017 Dav1dPicture *p;
1018 Dav1dContext *c = NULL;
1019 Dav1dData data;
1020 DemuxerContext *in_ctx = NULL;
1021 int res = 0;
1022 unsigned n_out = 0, total, timebase[2], fps[2];
1023
1024 // Store current ticks for stats calculation
1025 uint32_t decoder_start = SDL_GetTicks();
1026
1027 Dav1dPlaySettings settings = rd_ctx->settings;
1028
1029 if ((res = input_open(&in_ctx, "ivf",
1030 settings.inputfile,
1031 fps, &total, timebase)) < 0)
1032 {
1033 fprintf(stderr, "Failed to open demuxer\n");
1034 res = 1;
1035 goto cleanup;
1036 }
1037
1038 double timebase_d = timebase[1]/(double)timebase[0];
1039 rd_ctx->timebase = timebase_d;
1040
1041 if ((res = dav1d_open(&c, &rd_ctx->lib_settings))) {
1042 fprintf(stderr, "Failed opening dav1d decoder\n");
1043 res = 1;
1044 goto cleanup;
1045 }
1046
1047 if ((res = input_read(in_ctx, &data)) < 0) {
1048 fprintf(stderr, "Failed demuxing input\n");
1049 res = 1;
1050 goto cleanup;
1051 }
1052
1053 // Decoder loop
1054 do {
1055 if (dp_rd_ctx_should_terminate(rd_ctx))
1056 break;
1057
1058 // Send data packets we got from the demuxer to dav1d
1059 if ((res = dav1d_send_data(c, &data)) < 0) {
1060 // On EAGAIN, dav1d can not consume more data and
1061 // dav1d_get_picture needs to be called first, which
1062 // will happen below, so just keep going in that case
1063 // and do not error out.
1064 if (res != DAV1D_ERR(EAGAIN)) {
1065 dav1d_data_unref(&data);
1066 fprintf(stderr, "Error decoding frame: %s\n",
1067 strerror(-res));
1068 break;
1069 }
1070 }
1071
1072 p = calloc(1, sizeof(*p));
1073
1074 // Try to get a decoded frame
1075 if ((res = dav1d_get_picture(c, p)) < 0) {
1076 // In all error cases, even EAGAIN, p needs to be freed as
1077 // it is never added to the queue and would leak.
1078 free(p);
1079
1080 // On EAGAIN, it means dav1d has not enough data to decode
1081 // therefore this is not a decoding error but just means
1082 // we need to feed it more data, which happens in the next
1083 // run of this decoder loop.
1084 if (res != DAV1D_ERR(EAGAIN)) {
1085 fprintf(stderr, "Error decoding frame: %s\n",
1086 strerror(-res));
1087 break;
1088 }
1089 res = 0;
1090 } else {
1091
1092 // Queue frame
1093 dp_fifo_push(rd_ctx->fifo, p);
1094 dp_rd_ctx_post_event(rd_ctx, DAV1D_EVENT_NEW_FRAME);
1095
1096 n_out++;
1097 }
1098 } while ((data.sz > 0 || !input_read(in_ctx, &data)));
1099
1100 // Release remaining data
1101 if (data.sz > 0) dav1d_data_unref(&data);
1102
1103 // Do not drain in case an error occured and caused us to leave the
1104 // decoding loop early.
1105 if (res < 0)
1106 goto cleanup;
1107
1108 // Drain decoder
1109 // When there is no more data to feed to the decoder, for example
1110 // because the file ended, we still need to request pictures, as
1111 // even though we do not have more data, there can be frames decoded
1112 // from data we sent before. So we need to call dav1d_get_picture until
1113 // we get an EAGAIN error.
1114 do {
1115 if (dp_rd_ctx_should_terminate(rd_ctx))
1116 break;
1117
1118 p = calloc(1, sizeof(*p));
1119 res = dav1d_get_picture(c, p);
1120 if (res < 0) {
1121 free(p);
1122 if (res != DAV1D_ERR(EAGAIN)) {
1123 fprintf(stderr, "Error decoding frame: %s\n",
1124 strerror(-res));
1125 break;
1126 }
1127 } else {
1128 // Queue frame
1129 dp_fifo_push(rd_ctx->fifo, p);
1130 dp_rd_ctx_post_event(rd_ctx, DAV1D_EVENT_NEW_FRAME);
1131
1132 n_out++;
1133 }
1134 } while (res != DAV1D_ERR(EAGAIN));
1135
1136 // Print stats
1137 uint32_t decoding_time_ms = SDL_GetTicks() - decoder_start;
1138 printf("Decoded %u frames in %d seconds, avg %.02f fps\n",
1139 n_out, decoding_time_ms/1000, n_out / (decoding_time_ms / 1000.0));
1140
1141 cleanup:
1142 dp_rd_ctx_post_event(rd_ctx, DAV1D_EVENT_DEC_QUIT);
1143
1144 if (in_ctx)
1145 input_close(in_ctx);
1146 if (c)
1147 dav1d_close(&c);
1148
1149 return (res != DAV1D_ERR(EAGAIN) && res < 0);
1150 }
1151
main(int argc,char ** argv)1152 int main(int argc, char **argv)
1153 {
1154 SDL_Thread *decoder_thread;
1155 SDL_Window *win = NULL;
1156
1157 // Check for version mismatch between library and tool
1158 const char *version = dav1d_version();
1159 if (strcmp(version, DAV1D_VERSION)) {
1160 fprintf(stderr, "Version mismatch (library: %s, executable: %s)\n",
1161 version, DAV1D_VERSION);
1162 return 1;
1163 }
1164
1165 // Init SDL2 library
1166 if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) < 0)
1167 return 10;
1168
1169 // Create Window and Renderer
1170 int window_flags = SDL_WINDOW_SHOWN | SDL_WINDOW_ALLOW_HIGHDPI;
1171 #ifdef HAVE_PLACEBO_VULKAN
1172 window_flags |= SDL_WINDOW_VULKAN;
1173 #endif
1174 win = SDL_CreateWindow("Dav1dPlay", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
1175 WINDOW_WIDTH, WINDOW_HEIGHT, window_flags);
1176 SDL_SetWindowResizable(win, SDL_TRUE);
1177
1178 // Create render context
1179 Dav1dPlayRenderContext *rd_ctx = dp_rd_ctx_create(win);
1180 if (rd_ctx == NULL) {
1181 fprintf(stderr, "Failed creating render context\n");
1182 return 5;
1183 }
1184
1185 // Parse and validate arguments
1186 dp_rd_ctx_parse_args(rd_ctx, argc, argv);
1187
1188 if (rd_ctx->settings.zerocopy) {
1189 if (renderer_info.alloc_pic) {
1190 rd_ctx->lib_settings.allocator = (Dav1dPicAllocator) {
1191 .cookie = rd_ctx->rd_priv,
1192 .alloc_picture_callback = renderer_info.alloc_pic,
1193 .release_picture_callback = renderer_info.release_pic,
1194 };
1195 } else {
1196 fprintf(stderr, "--zerocopy unsupported by compiled renderer\n");
1197 }
1198 }
1199
1200 // Start decoder thread
1201 decoder_thread = SDL_CreateThread(decoder_thread_main, "Decoder thread", rd_ctx);
1202
1203 // Main loop
1204 while (1) {
1205
1206 SDL_Event e;
1207 if (SDL_WaitEvent(&e)) {
1208 if (e.type == SDL_QUIT) {
1209 dp_rd_ctx_request_shutdown(rd_ctx);
1210 } else if (e.type == rd_ctx->renderer_event_type) {
1211 if (e.user.code == DAV1D_EVENT_NEW_FRAME) {
1212 // Dequeue frame and update the render context with it
1213 Dav1dPicture *p = dp_fifo_shift(rd_ctx->fifo);
1214
1215 // Do not update textures during termination
1216 if (!dp_rd_ctx_should_terminate(rd_ctx))
1217 dp_rd_ctx_update_with_dav1d_picture(rd_ctx, p);
1218 dav1d_picture_unref(p);
1219 free(p);
1220 } else if (e.user.code == DAV1D_EVENT_DEC_QUIT) {
1221 break;
1222 }
1223 }
1224 }
1225
1226 // Do not render during termination
1227 if (!dp_rd_ctx_should_terminate(rd_ctx))
1228 dp_rd_ctx_render(rd_ctx);
1229 }
1230
1231 int decoder_ret = 0;
1232 SDL_WaitThread(decoder_thread, &decoder_ret);
1233
1234 dp_rd_ctx_destroy(rd_ctx);
1235 SDL_DestroyWindow(win);
1236
1237 return decoder_ret;
1238 }
1239