diff options
Diffstat (limited to 'src/render')
| -rw-r--r-- | src/render/queue_libplacebo.c | 215 | ||||
| -rw-r--r-- | src/render/queue_libplacebo.h | 19 | ||||
| -rw-r--r-- | src/render/renderer_libplacebo.c | 65 | ||||
| -rw-r--r-- | src/render/renderer_libplacebo.h | 1 |
4 files changed, 209 insertions, 91 deletions
diff --git a/src/render/queue_libplacebo.c b/src/render/queue_libplacebo.c index e3540c3..408af51 100644 --- a/src/render/queue_libplacebo.c +++ b/src/render/queue_libplacebo.c @@ -13,27 +13,63 @@ static bool queue_lp_configure_subtitles(struct camu_frame_queue *queue, u32 wid { struct camu_frame_queue_lp *lq = (struct camu_frame_queue_lp *)queue; #ifdef CAMU_HAVE_SUBTITLES - if (!lq->ass) return false; - lq->ass_renderer = ass_renderer_init(lq->ass); - if (!lq->ass_renderer) { + ASS_Library *ass = lq->subs.ass; + if (!(lq->subs.renderer = ass_renderer_init(ass))) { log_error("Failed to initialize ass renderer."); return false; } - ass_set_shaper(lq->ass_renderer, ASS_SHAPING_COMPLEX); - lq->ass_track = ass_new_track(lq->ass); - if (!lq->ass_track) { + if (!(lq->subs.track = ass_new_track(ass))) { log_error("Failed to create ass track."); return false; } - ass_set_frame_size(lq->ass_renderer, width, height); - ass_set_storage_size(lq->ass_renderer, width, height); - ass_set_fonts(lq->ass_renderer, NULL, NULL, ASS_FONTPROVIDER_AUTODETECT, NULL, 0); - ass_set_hinting(lq->ass_renderer, ASS_HINTING_NONE); + ASS_Renderer *renderer = lq->subs.renderer; + ASS_Track *track = lq->subs.track; + ass_set_shaper(renderer, ASS_SHAPING_COMPLEX); + ass_set_frame_size(renderer, width, height); + ass_set_storage_size(renderer, width, height); + ass_set_fonts(renderer, NULL, NULL, ASS_FONTPROVIDER_AUTODETECT, NULL, 0); + ass_set_hinting(renderer, ASS_HINTING_NONE); AVCodecParameters *codecpar = stream->av.stream->codecpar; - ass_process_codec_private(lq->ass_track, (const char *)codecpar->extradata, - codecpar->extradata_size); - nn_mutex_init(&lq->subtitle_lock); - lq->has_subtitles = true; + lq->subs.conversion_needed = codecpar->codec_id != AV_CODEC_ID_ASS; + if (lq->subs.conversion_needed) { + const AVCodecDescriptor *desc = avcodec_descriptor_get(codecpar->codec_id); + if (!(desc->props & AV_CODEC_PROP_TEXT_SUB)) { + log_error("Cannot convert non-text (bitmap) subtitles."); + return false; + } + const AVCodec *codec = avcodec_find_decoder(codecpar->codec_id); + if (!codec) { + log_error("Failed to find subtitle decoder."); + return false; + } + if (!(lq->subs.converter = camu_ff_alloc_codec_context(codec, codecpar))) { + return false; + } + AVCodecContext *converter = lq->subs.converter; + converter->sub_charenc_mode = FF_SUB_CHARENC_MODE_IGNORE; + if (!camu_ff_open_avcodec(converter, codec, NULL)) { + return false; + } + const AVCodecDescriptor *ass_desc = avcodec_descriptor_get(AV_CODEC_ID_ASS); + log_info("Subtitle conversion: %s -> %s.", desc->long_name, ass_desc->long_name); + // https://github.com/mpv-player/mpv/blob/266cb79f38fd1a5fd448b453dee5971795a145ca/sub/sd_ass.c#L634 +#define MP_ASS_FONT_PLAYRESX 384.0 + track->PlayResX = track->PlayResY * (f64)width / MAX(height, (u32)1); + f64 fix_margins = track->PlayResX / MP_ASS_FONT_PLAYRESX; + f64 font_scale = 1.0; + for (s32 n = 0; n < track->n_styles; n++) { + track->styles[n].MarginL = track->styles[n].MarginL * fix_margins; + track->styles[n].MarginR = track->styles[n].MarginR * fix_margins; + track->styles[n].MarginV = track->styles[n].MarginV * font_scale; + } + ass_process_codec_private(track, (const char *)converter->subtitle_header, + converter->subtitle_header_size); + } else { + ass_process_codec_private(track, (const char *)codecpar->extradata, + codecpar->extradata_size); + } + nn_mutex_init(&lq->subs.lock); + lq->subs.are_present = true; return true; #else (void)lq; @@ -65,11 +101,6 @@ static bool map_frame(pl_gpu gpu, pl_tex *tex, const struct pl_source_frame *src masks[2] = 0x00ff0000; pixel_stride = 3; break; - case CAMU_PIXEL_FORMAT_GREYA: - masks[0] = 0x000000ff; - masks[1] = 0x0000ff00; - pixel_stride = 2; - break; case CAMU_PIXEL_FORMAT_GREY: masks[0] = 0x000000ff; pixel_stride = 1; @@ -79,25 +110,26 @@ static bool map_frame(pl_gpu gpu, pl_tex *tex, const struct pl_source_frame *src al_assert_and_return(false); } - struct pl_plane_data data = { - .type = PL_FMT_UNORM, - .width = frame->video.width, - .height = frame->video.height, - .pixel_stride = pixel_stride, - .row_stride = frame->video.width * pixel_stride, - .pixels = frame->data - }; - pl_plane_data_from_mask(&data, masks); + if (frame->format == CAMU_PIXEL_FORMAT_GREY) { + out_frame->repr.sys = pl_system_from_av(AVCOL_SPC_BT470BG); + out_frame->repr.levels = pl_levels_from_av(AVCOL_RANGE_JPEG); + } else { + out_frame->repr = pl_color_repr_unknown; + } + out_frame->color = pl_color_space_unknown; + out_frame->crop = (pl_rect2df){ 0.f, 0.f, frame->video.width, frame->video.height }; - struct pl_plane plane = { 0 }; - bool ok = pl_upload_plane(gpu, &plane, tex, &data); + struct pl_plane_data data[4] = { 0 }; + data[0].type = PL_FMT_UNORM; + data[0].width = frame->video.width; + data[0].height = frame->video.height; + data[0].pixel_stride = pixel_stride; + data[0].row_stride = frame->video.width * pixel_stride; + data[0].pixels = frame->data; + pl_plane_data_from_mask(&data[0], masks); - out_frame->num_planes = 1; - out_frame->planes[0] = plane; - out_frame->repr = pl_color_repr_unknown; - out_frame->color = pl_color_space_monitor; - out_frame->crop = (pl_rect2df){ 0.f, 0.f, frame->video.width, frame->video.height }; - //out_frame->profile = + struct pl_plane plane = { 0 }; + bool ok = pl_upload_plane(gpu, &plane, tex, &data[0]); camu_codec_frame_discard(frame); @@ -106,6 +138,9 @@ static bool map_frame(pl_gpu gpu, pl_tex *tex, const struct pl_source_frame *src return false; } + out_frame->num_planes = 1; + out_frame->planes[0] = plane; + return true; } @@ -153,7 +188,7 @@ static void free_overlay(pl_gpu gpu, struct camu_overlay *overlay) static struct camu_overlay *first_nonref_overlay(struct camu_frame_queue_lp *lq, u32 *index) { struct camu_overlay *overlay; - al_array_foreach(lq->subtitles, i, overlay) { + al_array_foreach(lq->subs.overlays, i, overlay) { if (!overlay->ref) { *index = i; return overlay; @@ -224,10 +259,10 @@ static struct camu_overlay *create_subtitle_overlay(struct camu_overlay *prev, p ass_frame->dst_y + ass_frame->h }; u32 c = ass_frame->color; - current_part->color[0] = (c >> 24) / 255.0; - current_part->color[1] = ((c >> 16) & 0xff) / 255.0; - current_part->color[2] = ((c >> 8) & 0xff) / 255.0; - current_part->color[3] = 1.0 - (c & 0xff) / 255.0; + current_part->color[0] = (c >> 24) / 255.f; + current_part->color[1] = ((c >> 16) & 0xff) / 255.f; + current_part->color[2] = ((c >> 8) & 0xff) / 255.f; + current_part->color[3] = 1.f - (c & 0xff) / 255.f; current->parts = current_part; current->num_parts = 1; @@ -265,7 +300,7 @@ static bool map_av_frame(pl_gpu gpu, pl_tex *tex, const struct pl_source_frame * // pl_map_avframe_derived() will fail under Xwayland because of a lack of EGL_EXT_image_dma_buf_import. if (!ok && frame->hw_frames_ctx) { if (!lq->copy_frame_fallback) { - log_warn("Falling back to software copy."); + log_warn("Failed to map hwframe, falling back to software copy."); lq->copy_frame_fallback = true; lq->swframe = av_frame_alloc(); enum AVPixelFormat *fmts; @@ -290,27 +325,27 @@ static bool map_av_frame(pl_gpu gpu, pl_tex *tex, const struct pl_source_frame * ((struct pl_source_frame *)src)->frame_data = NULL; #ifdef CAMU_HAVE_SUBTITLES - if (ok && lq->has_subtitles) { + if (ok && lq->subs.are_present) { s64 now = av_rescale_q(frame->best_effort_timestamp, stream->time_base, (AVRational){ 1, 1000 }); s32 change = -1; // 1 = different position, 2 = different content. - nn_mutex_lock(&lq->subtitle_lock); - ASS_Image *ass_frame = ass_render_frame(lq->ass_renderer, lq->ass_track, now, &change); + nn_mutex_lock(&lq->subs.lock); + ASS_Image *ass_frame = ass_render_frame(lq->subs.renderer, lq->subs.track, now, &change); al_assert(change >= 0); - // ass_frame = NULL and lq->ass_track->n_events = 0 can also mean parsing completely failed. + // ass_frame = NULL and lq->subs.track->n_events = 0 can also mean parsing completely failed. if (ass_frame) { - struct camu_overlay *overlay = lq->last_subtitle; + struct camu_overlay *overlay = lq->subs.last; if (change != 0) { u32 reused = AL_ARRAY_NO_INDEX; overlay = create_subtitle_overlay(first_nonref_overlay(lq, &reused), gpu, ass_frame); // If all texture uploads failed, overlay->num will be 0. - lq->last_subtitle = overlay; + lq->subs.last = overlay; if (overlay) { if (reused != AL_ARRAY_NO_INDEX) { // Pointer may have changed due to reallocation. - al_array_at(lq->subtitles, reused) = overlay; + al_array_at(lq->subs.overlays, reused) = overlay; } else { // New overlay object. - overlay->lock = &lq->subtitle_lock; - al_array_push(lq->subtitles, overlay); + overlay->lock = &lq->subs.lock; + al_array_push(lq->subs.overlays, overlay); } } } @@ -322,7 +357,7 @@ static bool map_av_frame(pl_gpu gpu, pl_tex *tex, const struct pl_source_frame * out_frame->overlays = NULL; } } - nn_mutex_unlock(&lq->subtitle_lock); + nn_mutex_unlock(&lq->subs.lock); } #endif @@ -373,15 +408,40 @@ static void queue_lp_push_av_frame(struct camu_frame_queue *queue, AVFrame *fram } #endif +static bool convert_subtitle(struct camu_frame_queue_lp *lq, AVPacket *pkt) +{ + AVSubtitle sub = { 0 }; + s32 got_sub = 0; + s32 ret = avcodec_decode_subtitle2(lq->subs.converter, &sub, &got_sub, pkt); + if (ret < 0) { + log_error("Failed to convert subtitle (%s).", av_err2str(ret)); + } else if (got_sub) { + for (u32 i = 0; i < sub.num_rects; i++) { + AVSubtitleRect *rect = sub.rects[i]; + size_t size = al_strlen(rect->ass); + if (rect->ass[size - 1] == 0x02) size--; // Not sure what this is... + ass_process_chunk(lq->subs.track, rect->ass, size, pkt->pts, pkt->duration); + } + avsubtitle_free(&sub); + return true; + } + return false; +} + static void queue_lp_push_subtitle(struct camu_frame_queue *queue, struct camu_codec_packet *packet) { struct camu_frame_queue_lp *lq = (struct camu_frame_queue_lp *)queue; #ifdef CAMU_HAVE_SUBTITLES + if (!lq->subs.are_present) return; AVPacket *pkt = packet->av.pkt; - nn_mutex_lock(&lq->subtitle_lock); - al_assert(lq->ass_track && lq->ass_renderer); - ass_process_chunk(lq->ass_track, (const char *)pkt->data, pkt->size, pkt->pts, pkt->duration); - nn_mutex_unlock(&lq->subtitle_lock); + nn_mutex_lock(&lq->subs.lock); + al_assert(lq->subs.track && lq->subs.renderer); + if (lq->subs.conversion_needed) { + convert_subtitle(lq, pkt); + } else { + ass_process_chunk(lq->subs.track, (const char *)pkt->data, pkt->size, pkt->pts, pkt->duration); + } + nn_mutex_unlock(&lq->subs.lock); #else (void)lq; (void)packet; @@ -392,6 +452,16 @@ static void queue_lp_flush(struct camu_frame_queue *queue) { struct camu_frame_queue_lp *lq = (struct camu_frame_queue_lp *)queue; pl_queue_push(lq->queue, NULL); +#ifdef CAMU_HAVE_SUBTITLES + if (lq->subs.are_present) { + if (lq->subs.conversion_needed) { + AVPacket *empty_pkt = av_packet_alloc(); + while (convert_subtitle(lq, empty_pkt)) { } + av_packet_free(&empty_pkt); + } + ass_flush_events(lq->subs.track); + } +#endif } static s32 queue_lp_count(struct camu_frame_queue *queue) @@ -422,7 +492,7 @@ static u8 queue_lp_read(struct camu_frame_queue *queue, f64 pts, void *out) static void queue_lp_reset(struct camu_frame_queue *queue) { struct camu_frame_queue_lp *lq = (struct camu_frame_queue_lp *)queue; - pl_queue_push(lq->queue, NULL); + queue_lp_flush(&lq->q); pl_queue_reset(lq->queue); } @@ -436,15 +506,22 @@ static void queue_lp_free(struct camu_frame_queue **queue) } #endif #ifdef CAMU_HAVE_SUBTITLES - if (lq->has_subtitles) { - struct camu_overlay *overlay; - al_array_foreach_rev(lq->subtitles, i, overlay) { - free_overlay(lq->gpu, overlay); - } - al_array_free(lq->subtitles); - ass_free_track(lq->ass_track); - ass_renderer_done(lq->ass_renderer); - nn_mutex_destroy(&lq->subtitle_lock); + struct camu_overlay *overlay; + al_array_foreach_rev(lq->subs.overlays, i, overlay) { + free_overlay(lq->gpu, overlay); + } + al_array_free(lq->subs.overlays); + if (lq->subs.are_present) { + nn_mutex_destroy(&lq->subs.lock); + } + if (lq->subs.converter) { + avcodec_free_context(&lq->subs.converter); + } + if (lq->subs.track) { + ass_free_track(lq->subs.track); + } + if (lq->subs.renderer) { + ass_renderer_done(lq->subs.renderer); } #endif al_free(lq); @@ -455,9 +532,9 @@ struct camu_frame_queue *camu_frame_queue_lp_create(void) { struct camu_frame_queue_lp *lq = al_alloc_object(struct camu_frame_queue_lp); #ifdef CAMU_HAVE_SUBTITLES - lq->last_subtitle = NULL; - al_array_init(lq->subtitles); - lq->has_subtitles = false; + lq->subs.are_present = false; + lq->subs.last = NULL; + al_array_init(lq->subs.overlays); #endif lq->q.configure_subtitles = queue_lp_configure_subtitles; lq->q.push = queue_lp_push; diff --git a/src/render/queue_libplacebo.h b/src/render/queue_libplacebo.h index 0d49000..f72cb88 100644 --- a/src/render/queue_libplacebo.h +++ b/src/render/queue_libplacebo.h @@ -4,7 +4,6 @@ #ifdef CAMU_HAVE_FFMPEG #include <al/lib.h> AL_IGNORE_WARNING("-Wswitch") -AL_IGNORE_WARNING("-Wunused-parameter") // PL_LIBAV_IMPLEMENTATION defined in queue_libplacebo.c. #include <libplacebo/utils/libav.h> AL_IGNORE_WARNING_END @@ -25,13 +24,17 @@ struct camu_frame_queue_lp { AVFrame *swframe; #endif #ifdef CAMU_HAVE_SUBTITLES - ASS_Library *ass; - ASS_Renderer *ass_renderer; - ASS_Track *ass_track; - struct nn_mutex subtitle_lock; - struct camu_overlay *last_subtitle; - array(struct camu_overlay *) subtitles; - bool has_subtitles; + struct { + bool are_present; + bool conversion_needed; + AVCodecContext *converter; + ASS_Library *ass; + ASS_Renderer *renderer; + ASS_Track *track; + struct camu_overlay *last; + array(struct camu_overlay *) overlays; + struct nn_mutex lock; + } subs; #endif }; diff --git a/src/render/renderer_libplacebo.c b/src/render/renderer_libplacebo.c index 0995f6f..48b7ea6 100644 --- a/src/render/renderer_libplacebo.c +++ b/src/render/renderer_libplacebo.c @@ -20,7 +20,14 @@ #define RENDERER_DEBUG 0 #endif +#define CRT_BACKGROUND +#define CRT_BACKGROUND_COLOR (1.f / 7.5f) + +#ifdef CRT_BACKGROUND +static f32 clear_color[4] = { CRT_BACKGROUND_COLOR, CRT_BACKGROUND_COLOR, CRT_BACKGROUND_COLOR, 1.f }; +#else static f32 clear_color[4] = { 0.f, 0.f, 0.f, 1.f }; +#endif static struct pl_render_params default_params; @@ -146,9 +153,11 @@ static bool renderer_lp_create_renderer(struct camu_renderer *renderer, u32 *wid { struct camu_renderer_lp *lr = (struct camu_renderer_lp *)renderer; +#ifndef CRT_BACKGROUND clear_color[0] = ((global_color_palette.colors[0] >> 16) & 0xff) / 255.f; clear_color[1] = ((global_color_palette.colors[0] >> 8) & 0xff) / 255.f; clear_color[2] = ((global_color_palette.colors[0]) & 0xff) / 255.f; +#endif lr->logger = pl_log_create(PL_API_VER, pl_log_params( .log_cb = log_callback, @@ -173,13 +182,13 @@ static bool renderer_lp_create_renderer(struct camu_renderer *renderer, u32 *wid )); if (!lr->vk_inst) { log_error("Failed to create vulkan instance."); - goto err; + return false; } VkResult ret = vk_create_surface(priv, lr->vk_inst->instance, &lr->surface); if (ret != VK_SUCCESS) { log_error("Failed to create vulkan surface."); - goto err; + return false; } lr->vk = pl_vulkan_create(lr->logger, pl_vulkan_params( @@ -190,7 +199,7 @@ static bool renderer_lp_create_renderer(struct camu_renderer *renderer, u32 *wid )); if (!lr->vk) { log_error("Failed to create vulkan device."); - goto err; + return false; } lr->swapchain = pl_vulkan_create_swapchain(lr->vk, pl_vulkan_swapchain_params( @@ -200,7 +209,7 @@ static bool renderer_lp_create_renderer(struct camu_renderer *renderer, u32 *wid )); if (!lr->swapchain) { log_error("Failed to create vulkan swapchain."); - goto err; + return false; } lr->gpu = lr->vk->gpu; @@ -213,18 +222,21 @@ static bool renderer_lp_create_renderer(struct camu_renderer *renderer, u32 *wid )); if (!lr->d3d11) { log_error("Failed to create D3D11 device."); - goto err; + return false; } // https://learn.microsoft.com/en-us/windows/win32/api/dxgi/ne-dxgi-dxgi_swap_chain_flag + // https://learn.microsoft.com/en-us/windows/win32/api/dxgi/ne-dxgi-dxgi_swap_effect + char *FLIP = getenv("SINK_DXGI_FLIP"); + if (!FLIP) FLIP = "0"; lr->swapchain = pl_d3d11_create_swapchain(lr->d3d11, pl_d3d11_swapchain_params( .window = win32_window, - //.blit = true, + .blit = al_strscmp(FLIP, "0") == 0, //.flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING )); if (!lr->swapchain) { log_error("Failed to create D3D11 swapchain."); - goto err; + return false; } lr->gpu = lr->d3d11->gpu; @@ -247,7 +259,7 @@ static bool renderer_lp_create_renderer(struct camu_renderer *renderer, u32 *wid )); if (!lr->gl) { log_error("Failed to create GL device."); - goto err; + return false; } // max_swapchain_depth is returned as-is by pl_swapchain_latency(). @@ -259,7 +271,7 @@ static bool renderer_lp_create_renderer(struct camu_renderer *renderer, u32 *wid )); if (!lr->swapchain) { log_error("Failed to create GL swapchain."); - goto err; + return false; } lr->gpu = lr->gl->gpu; @@ -307,15 +319,16 @@ static bool renderer_lp_create_renderer(struct camu_renderer *renderer, u32 *wid #ifdef CAMU_HAVE_SUBTITLES lr->ass = ass_library_init(); + if (!lr->ass) { + log_error("Failed to initialize libass."); + return false; + } ass_set_message_cb(lr->ass, ass_log_callback, lr); #endif lr->last_swap_tick = nn_get_tick(); return true; -err: - lr->r.free((struct camu_renderer **)&lr); - return false; } static struct camu_frame_queue *renderer_lp_create_queue(struct camu_renderer *renderer) @@ -333,7 +346,7 @@ static struct camu_frame_queue *renderer_lp_create_queue(struct camu_renderer *r lq->copy_frame_fallback = false; #endif #ifdef CAMU_HAVE_SUBTITLES - lq->ass = lr->ass; + lq->subs.ass = lr->ass; #endif return queue; } @@ -385,8 +398,13 @@ static bool renderer_lp_render(struct camu_renderer *renderer, struct camu_scree struct pl_swapchain_frame frame; if (pl_swapchain_start_frame(lr->swapchain, &frame)) { pl_frame_from_swapchain(&lr->target, &frame); - pl_frame_clear_rgba(lr->gpu, &lr->target, clear_color); lr->have_frame = true; +#ifdef CRT_BACKGROUND + lr->frame_cleared = false; +#else + pl_frame_clear_rgba(lr->gpu, &lr->target, clear_color); + lr->frame_cleared = true; +#endif } else { log_error("Failed to start frame, fatal."); return false; @@ -473,6 +491,15 @@ static bool renderer_lp_render(struct camu_renderer *renderer, struct camu_scree target->color.hdr.min_luma = (PL_COLOR_SDR_WHITE - 50.f) / PL_COLOR_SDR_CONTRAST; } */ +#ifdef CRT_BACKGROUND + if (!lr->frame_cleared) { + clear_color[0] = 0.03f; + clear_color[1] = 0.03f; + clear_color[2] = 0.03f; + pl_frame_clear_rgba(lr->gpu, &lr->target, clear_color); + lr->frame_cleared = true; + } +#endif pl_render_image_mix(lr->renderer, &mix, target, &lr->params); } } else { @@ -503,6 +530,16 @@ static bool renderer_lp_render(struct camu_renderer *renderer, struct camu_scree return true; } +#ifdef CRT_BACKGROUND + if (!lr->frame_cleared) { + clear_color[0] = CRT_BACKGROUND_COLOR; + clear_color[1] = CRT_BACKGROUND_COLOR; + clear_color[2] = CRT_BACKGROUND_COLOR; + pl_frame_clear_rgba(lr->gpu, &lr->target, clear_color); + lr->frame_cleared = true; + } +#endif + if (pl_swapchain_submit_frame(lr->swapchain)) { lr->have_frame = false; } else { diff --git a/src/render/renderer_libplacebo.h b/src/render/renderer_libplacebo.h index 921c7ac..532963f 100644 --- a/src/render/renderer_libplacebo.h +++ b/src/render/renderer_libplacebo.h @@ -33,6 +33,7 @@ struct camu_renderer_lp { pl_swapchain swapchain; struct pl_frame target; bool have_frame; + bool frame_cleared; pl_renderer renderer; f64 last_swap_tick; struct pl_render_params params; |