diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 20:38:23 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 20:38:23 +0000 |
commit | ff6e3c025658a5fa1affd094f220b623e7e1b24b (patch) | |
tree | 9faab72d69c92d24e349d184f5869b9796f17e0c /demos/plplay.c | |
parent | Initial commit. (diff) | |
download | libplacebo-ff6e3c025658a5fa1affd094f220b623e7e1b24b.tar.xz libplacebo-ff6e3c025658a5fa1affd094f220b623e7e1b24b.zip |
Adding upstream version 6.338.2.upstream/6.338.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'demos/plplay.c')
-rw-r--r-- | demos/plplay.c | 766 |
1 files changed, 766 insertions, 0 deletions
diff --git a/demos/plplay.c b/demos/plplay.c new file mode 100644 index 0000000..901653e --- /dev/null +++ b/demos/plplay.c @@ -0,0 +1,766 @@ +/* Example video player based on ffmpeg. Designed to expose every libplacebo + * option for testing purposes. Not a serious video player, no real error + * handling. Simply infinitely loops its input. + * + * License: CC0 / Public Domain + */ + +#include <stdatomic.h> + +#include <libavutil/cpu.h> + +#include "common.h" +#include "window.h" +#include "utils.h" +#include "plplay.h" +#include "pl_clock.h" +#include "pl_thread.h" + +#ifdef HAVE_NUKLEAR +#include "ui.h" +#else +struct ui; +static void ui_destroy(struct ui **ui) {} +static bool ui_draw(struct ui *ui, const struct pl_swapchain_frame *frame) { return true; }; +#endif + +#include <libplacebo/utils/libav.h> + +static inline void log_time(struct timing *t, double ts) +{ + t->sum += ts; + t->sum2 += ts * ts; + t->peak = fmax(t->peak, ts); + t->count++; +} + +static void uninit(struct plplay *p) +{ + if (p->decoder_thread_created) { + p->exit_thread = true; + pl_queue_push(p->queue, NULL); // Signal EOF to wake up thread + pl_thread_join(p->decoder_thread); + } + + pl_queue_destroy(&p->queue); + pl_renderer_destroy(&p->renderer); + pl_options_free(&p->opts); + + for (int i = 0; i < p->shader_num; i++) { + pl_mpv_user_shader_destroy(&p->shader_hooks[i]); + free(p->shader_paths[i]); + } + + for (int i = 0; i < MAX_FRAME_PASSES; i++) + pl_shader_info_deref(&p->frame_info[i].shader); + for (int j = 0; j < MAX_BLEND_FRAMES; j++) { + for (int i = 0; i < MAX_BLEND_PASSES; i++) + pl_shader_info_deref(&p->blend_info[j][i].shader); + } + + free(p->shader_hooks); + free(p->shader_paths); + free(p->icc_name); + pl_icc_close(&p->icc); + + if (p->cache) { + FILE *file = fopen(p->cache_file, "wb"); + if (file) { + pl_cache_save_file(p->cache, file); + fclose(file); + } + pl_cache_destroy(&p->cache); + } + + // Free this before destroying the window to release associated GPU buffers + avcodec_free_context(&p->codec); + avformat_free_context(p->format); + + ui_destroy(&p->ui); + window_destroy(&p->win); + + pl_log_destroy(&p->log); + memset(p, 0, sizeof(*p)); +} + +static bool open_file(struct plplay *p, const char *filename) +{ + static const int av_log_level[] = { + [PL_LOG_NONE] = AV_LOG_QUIET, + [PL_LOG_FATAL] = AV_LOG_PANIC, + [PL_LOG_ERR] = AV_LOG_ERROR, + [PL_LOG_WARN] = AV_LOG_WARNING, + [PL_LOG_INFO] = AV_LOG_INFO, + [PL_LOG_DEBUG] = AV_LOG_VERBOSE, + [PL_LOG_TRACE] = AV_LOG_DEBUG, + }; + + av_log_set_level(av_log_level[p->args.verbosity]); + + printf("Opening file: '%s'\n", filename); + if (avformat_open_input(&p->format, filename, NULL, NULL) != 0) { + fprintf(stderr, "libavformat: Failed opening file!\n"); + return false; + } + + printf("Format: %s\n", p->format->iformat->name); + + if (p->format->duration != AV_NOPTS_VALUE) + printf("Duration: %.3f s\n", p->format->duration / 1e6); + + if (avformat_find_stream_info(p->format, NULL) < 0) { + fprintf(stderr, "libavformat: Failed finding stream info!\n"); + return false; + } + + // Find "best" video stream + int stream_idx = + av_find_best_stream(p->format, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0); + + if (stream_idx < 0) { + fprintf(stderr, "plplay: File contains no video streams?\n"); + return false; + } + + const AVStream *stream = p->format->streams[stream_idx]; + const AVCodecParameters *par = stream->codecpar; + printf("Found video track (stream %d)\n", stream_idx); + printf("Resolution: %d x %d\n", par->width, par->height); + + if (stream->avg_frame_rate.den && stream->avg_frame_rate.num) + printf("FPS: %f\n", av_q2d(stream->avg_frame_rate)); + + if (stream->r_frame_rate.den && stream->r_frame_rate.num) + printf("TBR: %f\n", av_q2d(stream->r_frame_rate)); + + if (stream->time_base.den && stream->time_base.num) + printf("TBN: %f\n", av_q2d(stream->time_base)); + + if (par->bit_rate) + printf("Bitrate: %"PRIi64" kbps\n", par->bit_rate / 1000); + + printf("Format: %s\n", av_get_pix_fmt_name(par->format)); + + p->stream = stream; + return true; +} + +static bool init_codec(struct plplay *p) +{ + assert(p->stream); + assert(p->win->gpu); + + const AVCodec *codec = avcodec_find_decoder(p->stream->codecpar->codec_id); + if (!codec) { + fprintf(stderr, "libavcodec: Failed finding matching codec\n"); + return false; + } + + p->codec = avcodec_alloc_context3(codec); + if (!p->codec) { + fprintf(stderr, "libavcodec: Failed allocating codec\n"); + return false; + } + + if (avcodec_parameters_to_context(p->codec, p->stream->codecpar) < 0) { + fprintf(stderr, "libavcodec: Failed copying codec parameters to codec\n"); + return false; + } + + printf("Codec: %s (%s)\n", codec->name, codec->long_name); + + const AVCodecHWConfig *hwcfg = 0; + if (p->args.hwdec) { + for (int i = 0; (hwcfg = avcodec_get_hw_config(codec, i)); i++) { + if (!pl_test_pixfmt(p->win->gpu, hwcfg->pix_fmt)) + continue; + if (!(hwcfg->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX)) + continue; + + int ret = av_hwdevice_ctx_create(&p->codec->hw_device_ctx, + hwcfg->device_type, + NULL, NULL, 0); + if (ret < 0) { + fprintf(stderr, "libavcodec: Failed opening HW device context, skipping\n"); + continue; + } + + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(hwcfg->pix_fmt); + printf("Using hardware frame format: %s\n", desc->name); + p->codec->extra_hw_frames = 4; + break; + } + } + + if (!hwcfg) + printf("Using software decoding\n"); + + p->codec->thread_count = FFMIN(av_cpu_count() + 1, 16); + p->codec->get_buffer2 = pl_get_buffer2; + p->codec->opaque = &p->win->gpu; +#if LIBAVCODEC_VERSION_MAJOR < 60 + AV_NOWARN_DEPRECATED({ + p->codec->thread_safe_callbacks = 1; + }); +#endif +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(58, 113, 100) + p->codec->export_side_data |= AV_CODEC_EXPORT_DATA_FILM_GRAIN; +#endif + + if (avcodec_open2(p->codec, codec, NULL) < 0) { + fprintf(stderr, "libavcodec: Failed opening codec\n"); + return false; + } + + return true; +} + +static bool map_frame(pl_gpu gpu, pl_tex *tex, + const struct pl_source_frame *src, + struct pl_frame *out_frame) +{ + AVFrame *frame = src->frame_data; + struct plplay *p = frame->opaque; + bool ok = pl_map_avframe_ex(gpu, out_frame, pl_avframe_params( + .frame = frame, + .tex = tex, + .map_dovi = !p->ignore_dovi, + )); + + av_frame_free(&frame); // references are preserved by `out_frame` + if (!ok) { + fprintf(stderr, "Failed mapping AVFrame!\n"); + return false; + } + + p->stats.mapped++; + pl_frame_copy_stream_props(out_frame, p->stream); + return true; +} + +static void unmap_frame(pl_gpu gpu, struct pl_frame *frame, + const struct pl_source_frame *src) +{ + pl_unmap_avframe(gpu, frame); +} + +static void discard_frame(const struct pl_source_frame *src) +{ + AVFrame *frame = src->frame_data; + struct plplay *p = frame->opaque; + p->stats.dropped++; + av_frame_free(&frame); + printf("Dropped frame with PTS %.3f\n", src->pts); +} + +static PL_THREAD_VOID decode_loop(void *arg) +{ + int ret; + struct plplay *p = arg; + AVPacket *packet = av_packet_alloc(); + AVFrame *frame = av_frame_alloc(); + if (!frame || !packet) + goto done; + + float frame_duration = av_q2d(av_inv_q(p->stream->avg_frame_rate)); + double first_pts = 0.0, base_pts = 0.0, last_pts = 0.0; + uint64_t num_frames = 0; + + while (!p->exit_thread) { + switch ((ret = av_read_frame(p->format, packet))) { + case 0: + if (packet->stream_index != p->stream->index) { + // Ignore unrelated packets + av_packet_unref(packet); + continue; + } + ret = avcodec_send_packet(p->codec, packet); + av_packet_unref(packet); + break; + case AVERROR_EOF: + // Send empty input to flush decoder + ret = avcodec_send_packet(p->codec, NULL); + break; + default: + fprintf(stderr, "libavformat: Failed reading packet: %s\n", + av_err2str(ret)); + goto done; + } + + if (ret < 0) { + fprintf(stderr, "libavcodec: Failed sending packet to decoder: %s\n", + av_err2str(ret)); + goto done; + } + + // Decode all frames from this packet + while ((ret = avcodec_receive_frame(p->codec, frame)) == 0) { + last_pts = frame->pts * av_q2d(p->stream->time_base); + if (num_frames++ == 0) + first_pts = last_pts; + frame->opaque = p; + (void) atomic_fetch_add(&p->stats.decoded, 1); + pl_queue_push_block(p->queue, UINT64_MAX, &(struct pl_source_frame) { + .pts = last_pts - first_pts + base_pts, + .duration = frame_duration, + .map = map_frame, + .unmap = unmap_frame, + .discard = discard_frame, + .frame_data = frame, + + // allow soft-disabling deinterlacing at the source frame level + .first_field = p->opts->params.deinterlace_params + ? pl_field_from_avframe(frame) + : PL_FIELD_NONE, + }); + frame = av_frame_alloc(); + } + + switch (ret) { + case AVERROR(EAGAIN): + continue; + case AVERROR_EOF: + if (num_frames <= 1) + goto done; // still image or empty file + // loop infinitely + ret = av_seek_frame(p->format, p->stream->index, 0, AVSEEK_FLAG_BACKWARD); + if (ret < 0) { + fprintf(stderr, "libavformat: Failed seeking in stream: %s\n", + av_err2str(ret)); + goto done; + } + avcodec_flush_buffers(p->codec); + base_pts += last_pts; + num_frames = 0; + continue; + default: + fprintf(stderr, "libavcodec: Failed decoding frame: %s\n", + av_err2str(ret)); + goto done; + } + } + +done: + pl_queue_push(p->queue, NULL); // Signal EOF to flush queue + av_packet_free(&packet); + av_frame_free(&frame); + PL_THREAD_RETURN(); +} + +static void update_colorspace_hint(struct plplay *p, const struct pl_frame_mix *mix) +{ + const struct pl_frame *frame = NULL; + + for (int i = 0; i < mix->num_frames; i++) { + if (mix->timestamps[i] > 0.0) + break; + frame = mix->frames[i]; + } + + if (!frame) + return; + + struct pl_color_space hint = {0}; + if (p->colorspace_hint) + hint = frame->color; + if (p->target_override) + apply_csp_overrides(p, &hint); + pl_swapchain_colorspace_hint(p->win->swapchain, &hint); +} + +static bool render_frame(struct plplay *p, const struct pl_swapchain_frame *frame, + const struct pl_frame_mix *mix) +{ + struct pl_frame target; + pl_options opts = p->opts; + pl_frame_from_swapchain(&target, frame); + update_settings(p, &target); + + if (p->target_override) { + target.repr = p->force_repr; + pl_color_repr_merge(&target.repr, &frame->color_repr); + apply_csp_overrides(p, &target.color); + + // Update ICC profile parameters dynamically + float target_luma = 0.0f; + if (!p->use_icc_luma) { + pl_color_space_nominal_luma_ex(pl_nominal_luma_params( + .metadata = PL_HDR_METADATA_HDR10, // use only static HDR nits + .scaling = PL_HDR_NITS, + .color = &target.color, + .out_max = &target_luma, + )); + } + pl_icc_update(p->log, &p->icc, NULL, pl_icc_params( + .max_luma = target_luma, + .force_bpc = p->force_bpc, + )); + target.icc = p->icc; + } + + assert(mix->num_frames); + pl_rect2df crop = mix->frames[0]->crop; + if (p->stream->sample_aspect_ratio.num && p->target_zoom != ZOOM_RAW) { + float sar = av_q2d(p->stream->sample_aspect_ratio); + pl_rect2df_stretch(&crop, fmaxf(1.0f, sar), fmaxf(1.0f, 1.0 / sar)); + } + + // Apply target rotation and un-rotate crop relative to target + target.rotation = p->target_rot; + pl_rect2df_rotate(&crop, mix->frames[0]->rotation - target.rotation); + + switch (p->target_zoom) { + case ZOOM_PAD: + pl_rect2df_aspect_copy(&target.crop, &crop, 0.0); + break; + case ZOOM_CROP: + pl_rect2df_aspect_copy(&target.crop, &crop, 1.0); + break; + case ZOOM_STRETCH: + break; // target.crop already covers full image + case ZOOM_FIT: + pl_rect2df_aspect_fit(&target.crop, &crop, 0.0); + break; + case ZOOM_RAW: ; + // Ensure pixels are exactly aligned, to avoid fractional scaling + int w = roundf(fabsf(pl_rect_w(crop))); + int h = roundf(fabsf(pl_rect_h(crop))); + target.crop.x0 = roundf((pl_rect_w(target.crop) - w) / 2.0f); + target.crop.y0 = roundf((pl_rect_h(target.crop) - h) / 2.0f); + target.crop.x1 = target.crop.x0 + w; + target.crop.y1 = target.crop.y0 + h; + break; + case ZOOM_400: + case ZOOM_200: + case ZOOM_100: + case ZOOM_50: + case ZOOM_25: ; + const float z = powf(2.0f, (int) ZOOM_100 - p->target_zoom); + const float sx = z * fabsf(pl_rect_w(crop)) / pl_rect_w(target.crop); + const float sy = z * fabsf(pl_rect_h(crop)) / pl_rect_h(target.crop); + pl_rect2df_stretch(&target.crop, sx, sy); + break; + } + + struct pl_color_map_params *cpars = &opts->color_map_params; + if (cpars->visualize_lut) { + cpars->visualize_rect = (pl_rect2df) {0, 0, 1, 1}; + float tar = pl_rect2df_aspect(&target.crop); + pl_rect2df_aspect_set(&cpars->visualize_rect, 1.0f / tar, 0.0f); + } + + pl_clock_t ts_pre = pl_clock_now(); + if (!pl_render_image_mix(p->renderer, mix, &target, &opts->params)) + return false; + pl_clock_t ts_rendered = pl_clock_now(); + if (!ui_draw(p->ui, frame)) + return false; + pl_clock_t ts_ui_drawn = pl_clock_now(); + + log_time(&p->stats.render, pl_clock_diff(ts_rendered, ts_pre)); + log_time(&p->stats.draw_ui, pl_clock_diff(ts_ui_drawn, ts_rendered)); + + p->stats.rendered++; + return true; +} + +static bool render_loop(struct plplay *p) +{ + pl_options opts = p->opts; + + struct pl_queue_params qparams = { + .interpolation_threshold = 0.01, + .timeout = UINT64_MAX, + }; + + // Initialize the frame queue, blocking indefinitely until done + struct pl_frame_mix mix; + switch (pl_queue_update(p->queue, &mix, &qparams)) { + case PL_QUEUE_OK: break; + case PL_QUEUE_EOF: return true; + case PL_QUEUE_ERR: goto error; + default: abort(); + } + + struct pl_swapchain_frame frame; + update_colorspace_hint(p, &mix); + if (!pl_swapchain_start_frame(p->win->swapchain, &frame)) + goto error; + + // Disable background transparency by default if the swapchain does not + // appear to support alpha transaprency + if (frame.color_repr.alpha == PL_ALPHA_UNKNOWN) + opts->params.background_transparency = 0.0; + + if (!render_frame(p, &frame, &mix)) + goto error; + if (!pl_swapchain_submit_frame(p->win->swapchain)) + goto error; + + // Wait until rendering is complete. Do this before measuring the time + // start, to ensure we don't count initialization overhead as part of the + // first vsync. + pl_gpu_finish(p->win->gpu); + p->stats.render = p->stats.draw_ui = (struct timing) {0}; + + pl_clock_t ts_start = 0, ts_prev = 0; + pl_swapchain_swap_buffers(p->win->swapchain); + window_poll(p->win, false); + + double pts_target = 0.0, prev_pts = 0.0; + + while (!p->win->window_lost) { + if (window_get_key(p->win, KEY_ESC)) + break; + + if (p->toggle_fullscreen) + window_toggle_fullscreen(p->win, !window_is_fullscreen(p->win)); + + update_colorspace_hint(p, &mix); + pl_clock_t ts_acquire = pl_clock_now(); + if (!pl_swapchain_start_frame(p->win->swapchain, &frame)) { + // Window stuck/invisible? Block for events and try again. + window_poll(p->win, true); + continue; + } + + pl_clock_t ts_pre_update = pl_clock_now(); + log_time(&p->stats.acquire, pl_clock_diff(ts_pre_update, ts_acquire)); + if (!ts_start) + ts_start = ts_pre_update; + + qparams.timeout = 0; // non-blocking update + qparams.radius = pl_frame_mix_radius(&p->opts->params); + qparams.pts = fmax(pts_target, pl_clock_diff(ts_pre_update, ts_start)); + p->stats.current_pts = qparams.pts; + if (qparams.pts != prev_pts) + log_time(&p->stats.pts_interval, qparams.pts - prev_pts); + prev_pts = qparams.pts; + +retry: + switch (pl_queue_update(p->queue, &mix, &qparams)) { + case PL_QUEUE_ERR: goto error; + case PL_QUEUE_EOF: + printf("End of file reached\n"); + return true; + case PL_QUEUE_OK: + break; + case PL_QUEUE_MORE: + qparams.timeout = UINT64_MAX; // retry in blocking mode + goto retry; + } + + pl_clock_t ts_post_update = pl_clock_now(); + log_time(&p->stats.update, pl_clock_diff(ts_post_update, ts_pre_update)); + + if (qparams.timeout) { + double stuck_ms = 1e3 * pl_clock_diff(ts_post_update, ts_pre_update); + fprintf(stderr, "Stalled for %.4f ms due to frame queue underrun!\n", stuck_ms); + ts_start += ts_post_update - ts_pre_update; // subtract time spent waiting + p->stats.stalled++; + p->stats.stalled_ms += stuck_ms; + } + + if (!render_frame(p, &frame, &mix)) + goto error; + + if (pts_target) { + pl_gpu_flush(p->win->gpu); + pl_clock_t ts_wait = pl_clock_now(); + double pts_now = pl_clock_diff(ts_wait, ts_start); + if (pts_target >= pts_now) { + log_time(&p->stats.sleep, pts_target - pts_now); + pl_thread_sleep(pts_target - pts_now); + } else { + double missed_ms = 1e3 * (pts_now - pts_target); + fprintf(stderr, "Missed PTS target %.3f (%.3f ms in the past)\n", + pts_target, missed_ms); + p->stats.missed++; + p->stats.missed_ms += missed_ms; + } + + pts_target = 0.0; + } + + pl_clock_t ts_pre_submit = pl_clock_now(); + if (!pl_swapchain_submit_frame(p->win->swapchain)) { + fprintf(stderr, "libplacebo: failed presenting frame!\n"); + goto error; + } + pl_clock_t ts_post_submit = pl_clock_now(); + log_time(&p->stats.submit, pl_clock_diff(ts_post_submit, ts_pre_submit)); + + if (ts_prev) + log_time(&p->stats.vsync_interval, pl_clock_diff(ts_post_submit, ts_prev)); + ts_prev = ts_post_submit; + + pl_swapchain_swap_buffers(p->win->swapchain); + pl_clock_t ts_post_swap = pl_clock_now(); + log_time(&p->stats.swap, pl_clock_diff(ts_post_swap, ts_post_submit)); + + window_poll(p->win, false); + + // In content-timed mode (frame mixing disabled), delay rendering + // until the next frame should become visible + if (!opts->params.frame_mixer) { + struct pl_source_frame next; + for (int i = 0;; i++) { + if (!pl_queue_peek(p->queue, i, &next)) + break; + if (next.pts > qparams.pts) { + pts_target = next.pts; + break; + } + } + } + + if (p->fps_override) + pts_target = fmax(pts_target, qparams.pts + 1.0 / p->fps); + } + + return true; + +error: + fprintf(stderr, "Render loop failed, exiting early...\n"); + return false; +} + +static void info_callback(void *priv, const struct pl_render_info *info) +{ + struct plplay *p = priv; + switch (info->stage) { + case PL_RENDER_STAGE_FRAME: + if (info->index >= MAX_FRAME_PASSES) + return; + p->num_frame_passes = info->index + 1; + pl_dispatch_info_move(&p->frame_info[info->index], info->pass); + return; + + case PL_RENDER_STAGE_BLEND: + if (info->index >= MAX_BLEND_PASSES || info->count >= MAX_BLEND_FRAMES) + return; + p->num_blend_passes[info->count] = info->index + 1; + pl_dispatch_info_move(&p->blend_info[info->count][info->index], info->pass); + return; + + case PL_RENDER_STAGE_COUNT: + break; + } + + abort(); +} + +static struct plplay state; + +int main(int argc, char *argv[]) +{ + state = (struct plplay) { + .target_override = true, + .use_icc_luma = true, + .fps = 60.0, + .args = { + .preset = &pl_render_default_params, + .verbosity = PL_LOG_INFO, + }, + }; + + if (!parse_args(&state.args, argc, argv)) + return -1; + + state.log = pl_log_create(PL_API_VER, pl_log_params( + .log_cb = pl_log_color, + .log_level = state.args.verbosity, + )); + + pl_options opts = state.opts = pl_options_alloc(state.log); + pl_options_reset(opts, state.args.preset); + + // Enable this by default to save one click + opts->params.cone_params = &opts->cone_params; + + // Enable dynamic parameters by default, due to plplay's heavy reliance on + // GUI controls for dynamically adjusting render parameters. + opts->params.dynamic_constants = true; + + // Hook up our pass info callback + opts->params.info_callback = info_callback; + opts->params.info_priv = &state; + + struct plplay *p = &state; + if (!open_file(p, state.args.filename)) + goto error; + + const AVCodecParameters *par = p->stream->codecpar; + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(par->format); + if (!desc) + goto error; + + struct window_params params = { + .title = "plplay", + .width = par->width, + .height = par->height, + .forced_impl = state.args.window_impl, + }; + + if (desc->flags & AV_PIX_FMT_FLAG_ALPHA) { + params.alpha = true; + opts->params.background_transparency = 1.0; + } + + p->win = window_create(p->log, ¶ms); + if (!p->win) + goto error; + + // Test the AVPixelFormat against the GPU capabilities + if (!pl_test_pixfmt(p->win->gpu, par->format)) { + fprintf(stderr, "Unsupported AVPixelFormat: %s\n", desc->name); + goto error; + } + +#ifdef HAVE_NUKLEAR + p->ui = ui_create(p->win->gpu); + if (!p->ui) + goto error; +#endif + + if (!init_codec(p)) + goto error; + + const char *cache_dir = get_cache_dir(&(char[512]) {0}); + if (cache_dir) { + int ret = snprintf(p->cache_file, sizeof(p->cache_file), "%s/plplay.cache", cache_dir); + if (ret > 0 && ret < sizeof(p->cache_file)) { + p->cache = pl_cache_create(pl_cache_params( + .log = p->log, + .max_total_size = 50 << 20, // 50 MB + )); + pl_gpu_set_cache(p->win->gpu, p->cache); + FILE *file = fopen(p->cache_file, "rb"); + if (file) { + pl_cache_load_file(p->cache, file); + fclose(file); + } + } + } + + p->queue = pl_queue_create(p->win->gpu); + int ret = pl_thread_create(&p->decoder_thread, decode_loop, p); + if (ret != 0) { + fprintf(stderr, "Failed creating decode thread: %s\n", strerror(errno)); + goto error; + } + + p->decoder_thread_created = true; + + p->renderer = pl_renderer_create(p->log, p->win->gpu); + if (!render_loop(p)) + goto error; + + printf("Exiting...\n"); + uninit(p); + return 0; + +error: + uninit(p); + return 1; +} |