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