summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorwm4 <wm4@nowhere>2020-05-11 19:51:32 +0200
committerwm4 <wm4@nowhere>2020-05-11 19:57:34 +0200
commit55e1f15cdb9d73af56715ffe030a626697c1b917 (patch)
treeb5c8a68ccbcc6fb0b4f7555f10d31ce1236196d0
parent6db890ebab2839443ddbfc34a4a0246d464bd14f (diff)
downloadmpv-55e1f15cdb9d73af56715ffe030a626697c1b917.tar.bz2
mpv-55e1f15cdb9d73af56715ffe030a626697c1b917.tar.xz
draw_bmp: add a function to return a single-texture OSD overlay
Maybe this is useful for some of the lesser VOs. It's preferable over bad ad-hoc solutions based on the more complex sub_bitmap data structures (as observed e.g. in vo_vaapi.c), and does not use that much more code since draw_bmp already created such an overlay internally. But I still wanted something that avoids having to upload/render a full screen-sized overlay if for example there's only a tiny subtitle line on the bottom of the screen. So the new API can return a list of modified pixels (for upload) and non-transparent pixels (for display). The way these pixel rectangles are computed is a bit dumb and returns dumb results, but it should be usable, and the implementation can change.
-rw-r--r--sub/draw_bmp.c263
-rw-r--r--sub/draw_bmp.h43
-rw-r--r--sub/osd.c5
-rw-r--r--test/repack.c4
4 files changed, 265 insertions, 50 deletions
diff --git a/sub/draw_bmp.c b/sub/draw_bmp.c
index c411c539b5..9566582428 100644
--- a/sub/draw_bmp.c
+++ b/sub/draw_bmp.c
@@ -100,6 +100,8 @@ struct mp_draw_sub_cache
// Function that works on the _f32 data.
void (*blend_line)(void *dst, void *src, void *src_a, int w);
+
+ struct mp_image res_overlay; // returned by mp_draw_sub_overlay()
};
static void blend_line_f32(void *dst, void *src, void *src_a, int w)
@@ -112,7 +114,7 @@ static void blend_line_f32(void *dst, void *src, void *src_a, int w)
dst_f[x] = src_f[x] + dst_f[x] * (1.0f - src_a_f[x]);
}
-static void blend_slice(struct mp_draw_sub_cache *p, int rgb_y)
+static void blend_slice(struct mp_draw_sub_cache *p)
{
struct mp_image *ov = p->overlay_tmp;
struct mp_image *ca = p->calpha_tmp;
@@ -164,7 +166,7 @@ static bool blend_overlay_with_video(struct mp_draw_sub_cache *p,
if (p->calpha_to_f32)
repack_line(p->calpha_to_f32, 0, 0, x >> xs, y >> ys, w >> xs);
- blend_slice(p, y);
+ blend_slice(p);
repack_line(p->video_from_f32, x, y, 0, 0, w);
}
@@ -478,10 +480,21 @@ static void clear_rgba_overlay(struct mp_draw_sub_cache *p)
p->any_osd = false;
}
-static bool reinit(struct mp_draw_sub_cache *p, struct mp_image_params *params)
+static void init_general(struct mp_draw_sub_cache *p)
+{
+ p->sub_scale = mp_sws_alloc(p);
+
+ p->s_w = MP_ALIGN_UP(p->rgba_overlay->w, SLICE_W) / SLICE_W;
+
+ p->slices = talloc_zero_array(p, struct slice, p->s_w * p->rgba_overlay->h);
+
+ mp_image_clear(p->rgba_overlay, 0, 0, p->w, p->h);
+ clear_rgba_overlay(p);
+}
+
+static bool reinit_to_video(struct mp_draw_sub_cache *p)
{
- talloc_free_children(p);
- *p = (struct mp_draw_sub_cache){.params = *params};
+ struct mp_image_params *params = &p->params;
bool need_premul = params->alpha != MP_ALPHA_PREMUL &&
(mp_imgfmt_get_desc(params->imgfmt).flags & MP_IMGFLAG_ALPHA);
@@ -683,15 +696,6 @@ static bool reinit(struct mp_draw_sub_cache *p, struct mp_image_params *params)
}
}
- p->sub_scale = mp_sws_alloc(p);
-
- p->s_w = MP_ALIGN_UP(p->rgba_overlay->w, SLICE_W) / SLICE_W;
-
- p->slices = talloc_zero_array(p, struct slice, p->s_w * p->rgba_overlay->h);
-
- mp_image_clear(p->rgba_overlay, 0, 0, w, h);
- clear_rgba_overlay(p);
-
if (need_premul) {
p->premul = mp_sws_alloc(p);
p->unpremul = mp_sws_alloc(p);
@@ -707,6 +711,56 @@ static bool reinit(struct mp_draw_sub_cache *p, struct mp_image_params *params)
p->unpremul->force_scaler = MP_SWS_ZIMG;
}
+ init_general(p);
+
+ return true;
+}
+
+static bool reinit_to_overlay(struct mp_draw_sub_cache *p)
+{
+ p->align_x = 1;
+ p->align_y = 1;
+
+ p->w = p->params.w;
+ p->h = p->params.h;
+
+ p->rgba_overlay = talloc_steal(p, mp_image_alloc(IMGFMT_BGRA, p->w, p->h));
+ if (!p->rgba_overlay)
+ return false;
+
+ mp_image_params_guess_csp(&p->rgba_overlay->params);
+ p->rgba_overlay->params.alpha = MP_ALPHA_PREMUL;
+
+ // Some non-sense with the intention to somewhat isolate the returned image.
+ mp_image_setfmt(&p->res_overlay, p->rgba_overlay->imgfmt);
+ mp_image_set_size(&p->res_overlay, p->rgba_overlay->w, p->rgba_overlay->h);
+ mp_image_copy_attributes(&p->res_overlay, p->rgba_overlay);
+ p->res_overlay.planes[0] = p->rgba_overlay->planes[0];
+ p->res_overlay.stride[0] = p->rgba_overlay->stride[0];
+
+ init_general(p);
+
+ // Mark all dirty (for full reinit of user state).
+ for (int y = 0; y < p->rgba_overlay->h; y++) {
+ for (int sx = 0; sx < p->s_w; sx++)
+ p->slices[y * p->s_w + sx] = (struct slice){0, SLICE_W};
+ }
+
+ return true;
+}
+
+static bool check_reinit(struct mp_draw_sub_cache *p,
+ struct mp_image_params *params, bool to_video)
+{
+ if (!mp_image_params_equal(&p->params, params) || !p->rgba_overlay) {
+ talloc_free_children(p);
+ *p = (struct mp_draw_sub_cache){.params = *params};
+ if (!(to_video ? reinit_to_video(p) : reinit_to_overlay(p))) {
+ talloc_free_children(p);
+ *p = (struct mp_draw_sub_cache){0};
+ return false;
+ }
+ }
return true;
}
@@ -725,10 +779,12 @@ char *mp_draw_sub_get_dbg_info(struct mp_draw_sub_cache *p)
mp_imgfmt_to_name(p->calpha_tmp ? p->calpha_tmp->imgfmt : 0));
}
-// p_cache: if not NULL, the function will set *p to a talloc-allocated p
-// containing scaled versions of sbs contents - free the p with
-// talloc_free()
-bool mp_draw_sub_bitmaps(struct mp_draw_sub_cache **p_cache, struct mp_image *dst,
+struct mp_draw_sub_cache *mp_draw_sub_alloc(void *ta_parent)
+{
+ return talloc_zero(ta_parent, struct mp_draw_sub_cache);
+}
+
+bool mp_draw_sub_bitmaps(struct mp_draw_sub_cache *p, struct mp_image *dst,
struct sub_bitmap_list *sbs_list)
{
bool ok = false;
@@ -738,18 +794,8 @@ bool mp_draw_sub_bitmaps(struct mp_draw_sub_cache **p_cache, struct mp_image *ds
assert(dst->w >= sbs_list->w);
assert(dst->h >= sbs_list->h);
- struct mp_draw_sub_cache *p = p_cache ? *p_cache : NULL;
- if (!p)
- p = talloc_zero(NULL, struct mp_draw_sub_cache);
-
- if (!mp_image_params_equal(&p->params, &dst->params) || !p->video_tmp)
- {
- if (!reinit(p, &dst->params)) {
- talloc_free_children(p);
- *p = (struct mp_draw_sub_cache){0};
- goto done;
- }
- }
+ if (!check_reinit(p, &dst->params, true))
+ return false;
if (p->change_id != sbs_list->change_id) {
p->change_id = sbs_list->change_id;
@@ -765,31 +811,156 @@ bool mp_draw_sub_bitmaps(struct mp_draw_sub_cache **p_cache, struct mp_image *ds
goto done;
}
- struct mp_image *target = dst;
- if (p->any_osd && p->premul_tmp) {
- if (mp_sws_scale(p->premul, p->premul_tmp, dst) < 0)
- goto done;
- target = p->premul_tmp;
- }
-
- if (!blend_overlay_with_video(p, target))
- goto done;
+ if (p->any_osd) {
+ struct mp_image *target = dst;
+ if (p->premul_tmp) {
+ if (mp_sws_scale(p->premul, p->premul_tmp, dst) < 0)
+ goto done;
+ target = p->premul_tmp;
+ }
- if (p->any_osd && p->premul_tmp) {
- if (mp_sws_scale(p->unpremul, dst, p->premul_tmp) < 0)
+ if (!blend_overlay_with_video(p, target))
goto done;
+
+ if (target != dst) {
+ if (mp_sws_scale(p->unpremul, dst, p->premul_tmp) < 0)
+ goto done;
+ }
}
ok = true;
done:
- if (p_cache) {
- *p_cache = p;
- } else {
- talloc_free(p);
+ return ok;
+}
+
+// Bounding boxes for mp_draw_sub_overlay() API. For simplicity, each rectangle
+// covers a fixed tile on the screen, starts out empty, but is not extended
+// beyond the tile. In the simplest case, there's only 1 rect/tile for everything.
+struct rc_grid {
+ unsigned w, h; // size in grid tiles
+ unsigned r_w, r_h; // size of a grid tile in pixels
+ struct mp_rect *rcs; // rcs[x * w + y]
+};
+
+static void init_rc_grid(struct rc_grid *gr, struct mp_draw_sub_cache *p,
+ struct mp_rect *rcs, int max_rcs)
+{
+ *gr = (struct rc_grid){ .w = max_rcs ? 1 : 0, .h = max_rcs ? 1 : 0,
+ .rcs = rcs, .r_w = p->s_w * SLICE_W, .r_h = p->h, };
+
+ // Dumb iteration to figure out max. size because I'm stupid.
+ bool more = true;
+ while (more) {
+ more = false;
+ if (gr->r_h >= 128) {
+ if (gr->w * gr->h * 2 > max_rcs)
+ break;
+ gr->h *= 2;
+ gr->r_h = (p->h + gr->h - 1) / gr->h;
+ more = true;
+ }
+ if (gr->r_w >= SLICE_W * 2) {
+ if (gr->w * gr->h * 2 > max_rcs)
+ break;
+ gr->w *= 2;
+ gr->r_w = (p->s_w + gr->w - 1) / gr->w * SLICE_W;
+ more = true;
+ }
}
- return ok;
+ assert(gr->r_h * gr->h >= p->h);
+ assert(!(gr->r_w & (SLICE_W - 1)));
+ assert(gr->r_w * gr->w >= p->w);
+
+ // Init with empty (degenerate) rectangles.
+ for (int y = 0; y < gr->h; y++) {
+ for (int x = 0; x < gr->w; x++) {
+ struct mp_rect *rc = &gr->rcs[y * gr->w + x];
+ rc->x1 = x * gr->r_w;
+ rc->y1 = y * gr->r_h;
+ rc->x0 = rc->x1 + gr->r_w;
+ rc->y0 = rc->y1 + gr->r_h;
+ }
+ }
+}
+
+// Extend given grid with contents of p->slices.
+static void mark_rcs(struct mp_draw_sub_cache *p, struct rc_grid *gr)
+{
+ for (int y = 0; y < p->h; y++) {
+ struct slice *line = &p->slices[y * p->s_w];
+ struct mp_rect *rcs = &gr->rcs[y / gr->r_h * gr->w];
+
+ for (int sx = 0; sx < p->s_w; sx++) {
+ struct slice *s = &line[sx];
+ if (s->x0 < s->x1) {
+ unsigned xpos = sx * SLICE_W;
+ struct mp_rect *rc = &rcs[xpos / gr->r_w];
+ rc->y0 = MPMIN(rc->y0, y);
+ rc->y1 = MPMAX(rc->y1, y + 1);
+ rc->x0 = MPMIN(rc->x0, xpos + s->x0);
+ rc->x1 = MPMAX(rc->x1, xpos + s->x1);
+ }
+ }
+ }
+}
+
+// Remove empty RCs, and return rc count.
+static int return_rcs(struct rc_grid *gr)
+{
+ int num = 0, cnt = gr->w * gr->h;
+ for (int n = 0; n < cnt; n++) {
+ struct mp_rect *rc = &gr->rcs[n];
+ if (rc->x0 < rc->x1 && rc->y0 < rc->y1)
+ gr->rcs[num++] = *rc;
+ }
+ return num;
+}
+
+struct mp_image *mp_draw_sub_overlay(struct mp_draw_sub_cache *p,
+ struct sub_bitmap_list *sbs_list,
+ struct mp_rect *act_rcs,
+ int max_act_rcs,
+ int *num_act_rcs,
+ struct mp_rect *mod_rcs,
+ int max_mod_rcs,
+ int *num_mod_rcs)
+{
+ *num_act_rcs = 0;
+ *num_mod_rcs = 0;
+
+ struct mp_image_params params = {.w = sbs_list->w, .h = sbs_list->h};
+ if (!check_reinit(p, &params, false))
+ return NULL;
+
+ struct rc_grid gr_act, gr_mod;
+ init_rc_grid(&gr_act, p, act_rcs, max_act_rcs);
+ init_rc_grid(&gr_mod, p, mod_rcs, max_mod_rcs);
+
+ if (p->change_id != sbs_list->change_id) {
+ p->change_id = sbs_list->change_id;
+
+ mark_rcs(p, &gr_mod);
+
+ clear_rgba_overlay(p);
+
+ for (int n = 0; n < sbs_list->num_items; n++) {
+ if (!render_sb(p, sbs_list->items[n])) {
+ p->change_id = 0;
+ return NULL;
+ }
+ }
+
+ mark_rcs(p, &gr_mod);
+ }
+
+ mark_rcs(p, &gr_act);
+
+ *num_act_rcs = return_rcs(&gr_act);
+ *num_mod_rcs = return_rcs(&gr_mod);
+
+ return &p->res_overlay;
}
// vim: ts=4 sw=4 et tw=80
diff --git a/sub/draw_bmp.h b/sub/draw_bmp.h
index 6833d42cf7..d54a1c7685 100644
--- a/sub/draw_bmp.h
+++ b/sub/draw_bmp.h
@@ -3,12 +3,53 @@
#include "osd.h"
+struct mp_rect;
struct mp_image;
struct mp_draw_sub_cache;
-bool mp_draw_sub_bitmaps(struct mp_draw_sub_cache **cache, struct mp_image *dst,
+
+struct mp_draw_sub_cache *mp_draw_sub_alloc(void *ta_parent);
+
+// Render the sub-bitmaps in sbs_list to dst. sbs_list must have been rendered
+// for an OSD resolution equivalent to dst's size (UB if not).
+// Warning: if dst is a format with alpha, and dst is not set to MP_ALPHA_PREMUL
+// (not done by default), this will be extremely slow.
+// Warning: the caller is responsible for ensuring that dst is writable.
+// cache: allocated instance; caches non-changing OSD parts etc.
+// dst: image to draw to
+// sbs_list: source sub-bitmaps
+// returns: success
+bool mp_draw_sub_bitmaps(struct mp_draw_sub_cache *cache, struct mp_image *dst,
struct sub_bitmap_list *sbs_list);
+
char *mp_draw_sub_get_dbg_info(struct mp_draw_sub_cache *c);
+// Return a RGBA overlay with subtitles. The returned image uses IMGFMT_BGRA and
+// premultiplied alpha, and the size specified by sbs_list.w/h.
+// This can return a list of active (act_) and modified (mod_) rectangles.
+// Active rectangles are regions that contain visible OSD pixels. Modified
+// rectangles are regions that were changed since the last call. This function
+// always makes the act region a subset of the mod region. Rectangles within a
+// list never overlap with rectangles within the same list.
+// If the user-provided lists are too small (max_*_rcs too small), multiple
+// rectangles are merged until they fit in the list.
+// You can pass max_act_rcs=0, which implies you render the whole overlay.
+// cache: allocated instance; keeps track of changed regions
+// sbs_list: source sub-bitmaps
+// act_rcs: caller allocated list of non-transparent rectangles
+// max_act_rcs: number of allocated items in act_rcs
+// num_act_rcs: set to the number of valid items in act_rcs
+// mod_rcs, max_mod_rcs, num_mod_rcs: modified rectangles
+// returns: internal OSD overlay owned by cache, NULL on error
+// read only, valid until the next call on cache
+struct mp_image *mp_draw_sub_overlay(struct mp_draw_sub_cache *cache,
+ struct sub_bitmap_list *sbs_list,
+ struct mp_rect *act_rcs,
+ int max_act_rcs,
+ int *num_act_rcs,
+ struct mp_rect *mod_rcs,
+ int max_mod_rcs,
+ int *num_mod_rcs);
+
extern const bool mp_draw_sub_formats[SUBBITMAP_COUNT];
#endif /* MPLAYER_DRAW_BMP_H */
diff --git a/sub/osd.c b/sub/osd.c
index 52b69461ff..d6f284c398 100644
--- a/sub/osd.c
+++ b/sub/osd.c
@@ -425,9 +425,12 @@ void osd_draw_on_image_p(struct osd_state *osd, struct mp_osd_res res,
// Need to lock for the dumb osd->draw_cache thing.
pthread_mutex_lock(&osd->lock);
+ if (!osd->draw_cache)
+ osd->draw_cache = mp_draw_sub_alloc(osd);
+
stats_time_start(osd->stats, "draw-bmp");
- if (!mp_draw_sub_bitmaps(&osd->draw_cache, dest, list))
+ if (!mp_draw_sub_bitmaps(osd->draw_cache, dest, list))
MP_WARN(osd, "Failed rendering OSD.\n");
talloc_steal(osd, osd->draw_cache);
diff --git a/test/repack.c b/test/repack.c
index aa9c804308..a2f4a97b83 100644
--- a/test/repack.c
+++ b/test/repack.c
@@ -365,8 +365,8 @@ static bool try_draw_bmp(FILE *f, int imgfmt)
.num_items = 1,
};
- struct mp_draw_sub_cache *c = NULL;
- if (mp_draw_sub_bitmaps(&c, dst, &sbs_list)) {
+ struct mp_draw_sub_cache *c = mp_draw_sub_alloc(NULL);
+ if (mp_draw_sub_bitmaps(c, dst, &sbs_list)) {
char *info = mp_draw_sub_get_dbg_info(c);
fprintf(f, "%s\n", info);
talloc_free(info);