1 /*
2 * Copyright © 2020, 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 "dp_renderer.h"
28
29 #if HAVE_RENDERER_PLACEBO
30 #include <assert.h>
31
32 #include <libplacebo/renderer.h>
33 #include <libplacebo/utils/dav1d.h>
34
35 #if HAVE_PLACEBO_VULKAN
36 # include <libplacebo/vulkan.h>
37 # include <SDL_vulkan.h>
38 #endif
39 #if HAVE_PLACEBO_OPENGL
40 # include <libplacebo/opengl.h>
41 # include <SDL_opengl.h>
42 #endif
43
44
45 /**
46 * Renderer context for libplacebo
47 */
48 typedef struct renderer_priv_ctx
49 {
50 // SDL window
51 SDL_Window *win;
52 // Placebo log
53 pl_log log;
54 // Placebo renderer
55 pl_renderer renderer;
56 #if HAVE_PLACEBO_VULKAN
57 // Placebo Vulkan handle
58 pl_vulkan vk;
59 // Placebo Vulkan instance
60 pl_vk_inst vk_inst;
61 // Vulkan surface
62 VkSurfaceKHR surf;
63 #endif
64 #if HAVE_PLACEBO_OPENGL
65 // Placebo OpenGL handle
66 pl_opengl gl;
67 // SDL OpenGL context
68 SDL_GLContext gl_context;
69 #endif
70 // Placebo GPU
71 pl_gpu gpu;
72 // Placebo swapchain
73 pl_swapchain swapchain;
74 // Lock protecting access to the texture
75 SDL_mutex *lock;
76 // Image to render, and planes backing them
77 struct pl_frame image;
78 pl_tex plane_tex[3];
79 } Dav1dPlayRendererPrivateContext;
80
81 static Dav1dPlayRendererPrivateContext*
placebo_renderer_create_common(const Dav1dPlaySettings * settings,int window_flags)82 placebo_renderer_create_common(const Dav1dPlaySettings *settings, int window_flags)
83 {
84 if (settings->fullscreen)
85 window_flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
86
87 // Create Window
88 SDL_Window *sdlwin = dp_create_sdl_window(window_flags | SDL_WINDOW_RESIZABLE);
89 if (sdlwin == NULL)
90 return NULL;
91
92 SDL_ShowCursor(0);
93
94 // Alloc
95 Dav1dPlayRendererPrivateContext *const rd_priv_ctx =
96 calloc(1, sizeof(Dav1dPlayRendererPrivateContext));
97 if (rd_priv_ctx == NULL)
98 return NULL;
99
100 rd_priv_ctx->win = sdlwin;
101
102 // Init libplacebo
103 rd_priv_ctx->log = pl_log_create(PL_API_VER, pl_log_params(
104 .log_cb = pl_log_color,
105 #ifndef NDEBUG
106 .log_level = PL_LOG_DEBUG,
107 #else
108 .log_level = PL_LOG_WARN,
109 #endif
110 ));
111 if (rd_priv_ctx->log == NULL) {
112 free(rd_priv_ctx);
113 return NULL;
114 }
115
116 // Create Mutex
117 rd_priv_ctx->lock = SDL_CreateMutex();
118 if (rd_priv_ctx->lock == NULL) {
119 fprintf(stderr, "SDL_CreateMutex failed: %s\n", SDL_GetError());
120 pl_log_destroy(&rd_priv_ctx->log);
121 free(rd_priv_ctx);
122 return NULL;
123 }
124
125 return rd_priv_ctx;
126 }
127
128 #if HAVE_PLACEBO_OPENGL
placebo_renderer_create_gl(const Dav1dPlaySettings * settings)129 static void *placebo_renderer_create_gl(const Dav1dPlaySettings *settings)
130 {
131 SDL_Window *sdlwin = NULL;
132 SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG);
133
134 // Common init
135 Dav1dPlayRendererPrivateContext *rd_priv_ctx =
136 placebo_renderer_create_common(settings, SDL_WINDOW_OPENGL);
137
138 if (rd_priv_ctx == NULL)
139 return NULL;
140 sdlwin = rd_priv_ctx->win;
141
142 rd_priv_ctx->gl_context = SDL_GL_CreateContext(sdlwin);
143 SDL_GL_MakeCurrent(sdlwin, rd_priv_ctx->gl_context);
144
145 rd_priv_ctx->gl = pl_opengl_create(rd_priv_ctx->log, pl_opengl_params(
146 .allow_software = true,
147 #ifndef NDEBUG
148 .debug = true,
149 #endif
150 ));
151 if (!rd_priv_ctx->gl) {
152 fprintf(stderr, "Failed creating opengl device!\n");
153 exit(2);
154 }
155
156 rd_priv_ctx->swapchain = pl_opengl_create_swapchain(rd_priv_ctx->gl,
157 pl_opengl_swapchain_params(
158 .swap_buffers = (void (*)(void *)) SDL_GL_SwapWindow,
159 .priv = sdlwin,
160 ));
161
162 if (!rd_priv_ctx->swapchain) {
163 fprintf(stderr, "Failed creating opengl swapchain!\n");
164 exit(2);
165 }
166
167 int w = WINDOW_WIDTH, h = WINDOW_HEIGHT;
168 SDL_GL_GetDrawableSize(sdlwin, &w, &h);
169
170 if (!pl_swapchain_resize(rd_priv_ctx->swapchain, &w, &h)) {
171 fprintf(stderr, "Failed resizing vulkan swapchain!\n");
172 exit(2);
173 }
174
175 rd_priv_ctx->gpu = rd_priv_ctx->gl->gpu;
176
177 if (w != WINDOW_WIDTH || h != WINDOW_HEIGHT)
178 printf("Note: window dimensions differ (got %dx%d)\n", w, h);
179
180 return rd_priv_ctx;
181 }
182 #endif
183
184 #if HAVE_PLACEBO_VULKAN
placebo_renderer_create_vk(const Dav1dPlaySettings * settings)185 static void *placebo_renderer_create_vk(const Dav1dPlaySettings *settings)
186 {
187 SDL_Window *sdlwin = NULL;
188
189 // Common init
190 Dav1dPlayRendererPrivateContext *rd_priv_ctx =
191 placebo_renderer_create_common(settings, SDL_WINDOW_VULKAN);
192
193 if (rd_priv_ctx == NULL)
194 return NULL;
195 sdlwin = rd_priv_ctx->win;
196
197 // Init Vulkan
198 unsigned num = 0;
199 if (!SDL_Vulkan_GetInstanceExtensions(sdlwin, &num, NULL)) {
200 fprintf(stderr, "Failed enumerating Vulkan extensions: %s\n", SDL_GetError());
201 exit(1);
202 }
203
204 const char **extensions = malloc(num * sizeof(const char *));
205 assert(extensions);
206
207 SDL_bool ok = SDL_Vulkan_GetInstanceExtensions(sdlwin, &num, extensions);
208 if (!ok) {
209 fprintf(stderr, "Failed getting Vk instance extensions\n");
210 exit(1);
211 }
212
213 if (num > 0) {
214 printf("Requesting %d additional Vulkan extensions:\n", num);
215 for (unsigned i = 0; i < num; i++)
216 printf(" %s\n", extensions[i]);
217 }
218
219 rd_priv_ctx->vk_inst = pl_vk_inst_create(rd_priv_ctx->log, pl_vk_inst_params(
220 .extensions = extensions,
221 .num_extensions = num,
222 ));
223 if (!rd_priv_ctx->vk_inst) {
224 fprintf(stderr, "Failed creating Vulkan instance!\n");
225 exit(1);
226 }
227 free(extensions);
228
229 if (!SDL_Vulkan_CreateSurface(sdlwin, rd_priv_ctx->vk_inst->instance, &rd_priv_ctx->surf)) {
230 fprintf(stderr, "Failed creating vulkan surface: %s\n", SDL_GetError());
231 exit(1);
232 }
233
234 rd_priv_ctx->vk = pl_vulkan_create(rd_priv_ctx->log, pl_vulkan_params(
235 .instance = rd_priv_ctx->vk_inst->instance,
236 .surface = rd_priv_ctx->surf,
237 .allow_software = true,
238 ));
239 if (!rd_priv_ctx->vk) {
240 fprintf(stderr, "Failed creating vulkan device!\n");
241 exit(2);
242 }
243
244 // Create swapchain
245 rd_priv_ctx->swapchain = pl_vulkan_create_swapchain(rd_priv_ctx->vk,
246 pl_vulkan_swapchain_params(
247 .surface = rd_priv_ctx->surf,
248 .present_mode = VK_PRESENT_MODE_IMMEDIATE_KHR,
249 ));
250
251 if (!rd_priv_ctx->swapchain) {
252 fprintf(stderr, "Failed creating vulkan swapchain!\n");
253 exit(2);
254 }
255
256 int w = WINDOW_WIDTH, h = WINDOW_HEIGHT;
257 if (!pl_swapchain_resize(rd_priv_ctx->swapchain, &w, &h)) {
258 fprintf(stderr, "Failed resizing vulkan swapchain!\n");
259 exit(2);
260 }
261
262 rd_priv_ctx->gpu = rd_priv_ctx->vk->gpu;
263
264 if (w != WINDOW_WIDTH || h != WINDOW_HEIGHT)
265 printf("Note: window dimensions differ (got %dx%d)\n", w, h);
266
267 return rd_priv_ctx;
268 }
269 #endif
270
placebo_renderer_destroy(void * cookie)271 static void placebo_renderer_destroy(void *cookie)
272 {
273 Dav1dPlayRendererPrivateContext *rd_priv_ctx = cookie;
274 assert(rd_priv_ctx != NULL);
275
276 pl_renderer_destroy(&(rd_priv_ctx->renderer));
277 pl_swapchain_destroy(&(rd_priv_ctx->swapchain));
278 for (int i = 0; i < 3; i++)
279 pl_tex_destroy(rd_priv_ctx->gpu, &(rd_priv_ctx->plane_tex[i]));
280
281 #if HAVE_PLACEBO_VULKAN
282 if (rd_priv_ctx->vk) {
283 pl_vulkan_destroy(&(rd_priv_ctx->vk));
284 vkDestroySurfaceKHR(rd_priv_ctx->vk_inst->instance, rd_priv_ctx->surf, NULL);
285 pl_vk_inst_destroy(&(rd_priv_ctx->vk_inst));
286 }
287 #endif
288 #if HAVE_PLACEBO_OPENGL
289 if (rd_priv_ctx->gl)
290 pl_opengl_destroy(&(rd_priv_ctx->gl));
291 if (rd_priv_ctx->gl_context)
292 SDL_GL_DeleteContext(rd_priv_ctx->gl_context);
293 #endif
294
295 SDL_DestroyWindow(rd_priv_ctx->win);
296
297 pl_log_destroy(&rd_priv_ctx->log);
298 }
299
placebo_render(void * cookie,const Dav1dPlaySettings * settings)300 static void placebo_render(void *cookie, const Dav1dPlaySettings *settings)
301 {
302 Dav1dPlayRendererPrivateContext *rd_priv_ctx = cookie;
303 assert(rd_priv_ctx != NULL);
304
305 SDL_LockMutex(rd_priv_ctx->lock);
306 if (!rd_priv_ctx->image.num_planes) {
307 SDL_UnlockMutex(rd_priv_ctx->lock);
308 return;
309 }
310
311 // Prepare rendering
312 if (rd_priv_ctx->renderer == NULL) {
313 rd_priv_ctx->renderer = pl_renderer_create(rd_priv_ctx->log, rd_priv_ctx->gpu);
314 }
315
316 struct pl_swapchain_frame frame;
317 bool ok = pl_swapchain_start_frame(rd_priv_ctx->swapchain, &frame);
318 if (!ok) {
319 SDL_UnlockMutex(rd_priv_ctx->lock);
320 return;
321 }
322
323 struct pl_frame target;
324 pl_frame_from_swapchain(&target, &frame);
325 pl_rect2df_aspect_copy(&target.crop, &rd_priv_ctx->image.crop, 0.0);
326 if (pl_frame_is_cropped(&target))
327 pl_tex_clear(rd_priv_ctx->gpu, frame.fbo, (float[4]){ 0.0 });
328
329 if (!pl_render_image(rd_priv_ctx->renderer, &rd_priv_ctx->image, &target,
330 settings->highquality ? &pl_render_default_params
331 : &pl_render_fast_params))
332 {
333 fprintf(stderr, "Failed rendering frame!\n");
334 pl_tex_clear(rd_priv_ctx->gpu, frame.fbo, (float[4]){ 1.0 });
335 }
336
337 ok = pl_swapchain_submit_frame(rd_priv_ctx->swapchain);
338 if (!ok) {
339 fprintf(stderr, "Failed submitting frame!\n");
340 SDL_UnlockMutex(rd_priv_ctx->lock);
341 return;
342 }
343
344 pl_swapchain_swap_buffers(rd_priv_ctx->swapchain);
345 SDL_UnlockMutex(rd_priv_ctx->lock);
346 }
347
placebo_upload_image(void * cookie,Dav1dPicture * dav1d_pic,const Dav1dPlaySettings * settings)348 static int placebo_upload_image(void *cookie, Dav1dPicture *dav1d_pic,
349 const Dav1dPlaySettings *settings)
350 {
351 Dav1dPlayRendererPrivateContext *p = cookie;
352 assert(p != NULL);
353 int ret = 0;
354
355 if (!dav1d_pic)
356 return ret;
357
358 SDL_LockMutex(p->lock);
359 if (!pl_upload_dav1dpicture(p->gpu, &p->image, p->plane_tex, pl_dav1d_upload_params(
360 .picture = dav1d_pic,
361 .film_grain = settings->gpugrain,
362 .gpu_allocated = settings->zerocopy,
363 .asynchronous = true,
364 )))
365 {
366 fprintf(stderr, "Failed uploading planes!\n");
367 p->image = (struct pl_frame) {0};
368 ret = -1;
369 }
370 SDL_UnlockMutex(p->lock);
371 return ret;
372 }
373
placebo_alloc_pic(Dav1dPicture * const pic,void * cookie)374 static int placebo_alloc_pic(Dav1dPicture *const pic, void *cookie)
375 {
376 Dav1dPlayRendererPrivateContext *rd_priv_ctx = cookie;
377 assert(rd_priv_ctx != NULL);
378
379 SDL_LockMutex(rd_priv_ctx->lock);
380 int ret = pl_allocate_dav1dpicture(pic, (void *) rd_priv_ctx->gpu);
381 SDL_UnlockMutex(rd_priv_ctx->lock);
382 return ret;
383 }
384
placebo_release_pic(Dav1dPicture * pic,void * cookie)385 static void placebo_release_pic(Dav1dPicture *pic, void *cookie)
386 {
387 Dav1dPlayRendererPrivateContext *rd_priv_ctx = cookie;
388 assert(rd_priv_ctx != NULL);
389
390 SDL_LockMutex(rd_priv_ctx->lock);
391 pl_release_dav1dpicture(pic, (void *) rd_priv_ctx->gpu);
392 SDL_UnlockMutex(rd_priv_ctx->lock);
393 }
394
395 #if HAVE_PLACEBO_VULKAN
396 const Dav1dPlayRenderInfo rdr_placebo_vk = {
397 .name = "placebo-vk",
398 .create_renderer = placebo_renderer_create_vk,
399 .destroy_renderer = placebo_renderer_destroy,
400 .render = placebo_render,
401 .update_frame = placebo_upload_image,
402 .alloc_pic = placebo_alloc_pic,
403 .release_pic = placebo_release_pic,
404 .supports_gpu_grain = 1,
405 };
406 #else
407 const Dav1dPlayRenderInfo rdr_placebo_vk = { NULL };
408 #endif
409
410 #if HAVE_PLACEBO_OPENGL
411 const Dav1dPlayRenderInfo rdr_placebo_gl = {
412 .name = "placebo-gl",
413 .create_renderer = placebo_renderer_create_gl,
414 .destroy_renderer = placebo_renderer_destroy,
415 .render = placebo_render,
416 .update_frame = placebo_upload_image,
417 .supports_gpu_grain = 1,
418 };
419 #else
420 const Dav1dPlayRenderInfo rdr_placebo_gl = { NULL };
421 #endif
422
423 #else
424 const Dav1dPlayRenderInfo rdr_placebo_vk = { NULL };
425 const Dav1dPlayRenderInfo rdr_placebo_gl = { NULL };
426 #endif
427