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, &params);
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