From 4010dd0b1a27e399606299b428d1f9bc645cf8fd Mon Sep 17 00:00:00 2001 From: wm4 Date: Thu, 27 Oct 2011 12:07:10 +0200 Subject: libvo, vo_vdpau: make the EOSD packer code from vo_vdpau generic The code in eosd_packer.c/.h is taken from vo_vdpau.c and has been made independent from vdpau API specifics. This allows other VOs, which need to pack the small EOSD images into a large surface for efficiency, to use this code. --- Makefile | 1 + libvo/eosd_packer.c | 254 +++++++++++++++++++++++++++++++++++++++++++++++++++ libvo/eosd_packer.h | 71 +++++++++++++++ libvo/vo_vdpau.c | 255 +++++++++++----------------------------------------- 4 files changed, 380 insertions(+), 201 deletions(-) create mode 100644 libvo/eosd_packer.c create mode 100644 libvo/eosd_packer.h diff --git a/Makefile b/Makefile index 8cb43cb50a..2b8ae28d20 100644 --- a/Makefile +++ b/Makefile @@ -405,6 +405,7 @@ SRCS_COMMON = asxparser.c \ libmpdemux/yuv4mpeg.c \ libmpdemux/yuv4mpeg_ratio.c \ libvo/osd.c \ + libvo/eosd_packer.c \ osdep/numcores.c \ osdep/$(GETCH) \ osdep/$(TIMER) \ diff --git a/libvo/eosd_packer.c b/libvo/eosd_packer.c new file mode 100644 index 0000000000..103648b7c4 --- /dev/null +++ b/libvo/eosd_packer.c @@ -0,0 +1,254 @@ +/* + * Common code for packing EOSD images into larger surfaces. + * + * This file is part of mplayer2. + * + * mplayer2 is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * mplayer2 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with mplayer2; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include "talloc.h" +#include "mp_msg.h" +#include "eosd_packer.h" + +// Initial size of EOSD surface in pixels (x*x) +#define EOSD_SURFACE_INITIAL_SIZE 256 + +// Allocate an eosd_packer, which can be used to layout and cache the list of +// EOSD images contained in a mp_eosd_images_t into a flat surface. +// It can be free'd with talloc_free(). +// Don't forget to call eosd_init() before using it. +struct eosd_packer *eosd_packer_create(void *talloc_ctx) { + return talloc_zero(talloc_ctx, struct eosd_packer); +} + +// Call this when you need to completely reinitialize the EOSD state, e.g. when +// when your EOSD surface was deleted. +// max_width and max_height are the maximum surface sizes that should be +// allowed. +void eosd_packer_reinit(struct eosd_packer *state, uint32_t max_width, + uint32_t max_height) +{ + state->max_surface_width = max_width; + state->max_surface_height = max_height; + state->surface.w = 0; + state->surface.h = 0; + state->targets_count = 0; +} + +#define HEIGHT_SORT_BITS 4 +static int size_index(struct eosd_target *r) +{ + unsigned int h = r->source.y1; + int n = av_log2_16bit(h); + return (n << HEIGHT_SORT_BITS) + + (- 1 - (h << HEIGHT_SORT_BITS >> n) & (1 << HEIGHT_SORT_BITS) - 1); +} + +/* Pack the given rectangles into an area of size w * h. + * The size of each rectangle is read from .source.x1/.source.y1. + * The height of each rectangle must be at least 1 and less than 65536. + * The .source rectangle is then set corresponding to the packed position. + * 'scratch' must point to work memory for num_rects+16 ints. + * Return 0 on success, -1 if the rectangles did not fit in w*h. + * + * The rectangles are placed in rows in order approximately sorted by + * height (the approximate sorting is simpler than a full one would be, + * and allows the algorithm to work in linear time). Additionally, to + * reduce wasted space when there are a few tall rectangles, empty + * lower-right parts of rows are filled recursively when the size of + * rectangles in the row drops past a power-of-two threshold. So if a + * row starts with rectangles of size 3x50, 10x40 and 5x20 then the + * free rectangle with corners (13, 20)-(w, 50) is filled recursively. + */ +static int pack_rectangles(struct eosd_target *rects, int num_rects, + int w, int h, int *scratch) +{ + int bins[16 << HEIGHT_SORT_BITS]; + int sizes[16 << HEIGHT_SORT_BITS] = {}; + for (int i = 0; i < num_rects; i++) + sizes[size_index(rects + i)]++; + int idx = 0; + for (int i = 0; i < 16 << HEIGHT_SORT_BITS; i += 1 << HEIGHT_SORT_BITS) { + for (int j = 0; j < 1 << HEIGHT_SORT_BITS; j++) { + bins[i + j] = idx; + idx += sizes[i + j]; + } + scratch[idx++] = -1; + } + for (int i = 0; i < num_rects; i++) + scratch[bins[size_index(rects + i)]++] = i; + for (int i = 0; i < 16; i++) + bins[i] = bins[i << HEIGHT_SORT_BITS] - sizes[i << HEIGHT_SORT_BITS]; + struct { + int size, x, bottom; + } stack[16] = {{15, 0, h}}, s = {}; + int stackpos = 1; + int y; + while (stackpos) { + y = s.bottom; + s = stack[--stackpos]; + s.size++; + while (s.size--) { + int maxy = -1; + int obj; + while ((obj = scratch[bins[s.size]]) >= 0) { + int bottom = y + rects[obj].source.y1; + if (bottom > s.bottom) + break; + int right = s.x + rects[obj].source.x1; + if (right > w) + break; + bins[s.size]++; + rects[obj].source.x0 = s.x; + rects[obj].source.x1 += s.x; + rects[obj].source.y0 = y; + rects[obj].source.y1 += y; + num_rects--; + if (maxy <= 0) + stack[stackpos++] = s; + s.x = right; + maxy = FFMAX(maxy, bottom); + } + if (maxy > 0) + s.bottom = maxy; + } + } + return num_rects ? -1 : 0; +} + +// padding to reduce interpolation artifacts when doing scaling & filtering +#define EOSD_PADDING 0 + +// Release all previous images, and packs the images in imgs into state. The +// caller must check the change variables: +// *out_need_reposition == true: sub-image positions changed +// *out_need_upload == true: upload all sub-images again +// *out_need_reallocate == true: resize the EOSD texture to state->surface.w/h +// Logical implications: need_reallocate => need_upload => need_reposition +void eosd_packer_generate(struct eosd_packer *state, mp_eosd_images_t *imgs, + bool *out_need_reposition, bool *out_need_upload, + bool *out_need_reallocate) +{ + int i; + ASS_Image *img = imgs->imgs; + ASS_Image *p; + struct eosd_surface *sfc = &state->surface; + + *out_need_reposition = false; + *out_need_upload = false; + *out_need_reallocate = false; + + int change_state = imgs->changed; + + // eosd_reinit() was probably called, force full reupload. + if (state->targets_count == 0 && img) + change_state = 2; + + if (change_state == 0) + return; // Nothing changed, no need to redraw + + state->targets_count = 0; + + *out_need_reposition = true; + + if (!img) + return; // There's nothing to render! + + if (change_state == 1) + goto eosd_skip_upload; + + *out_need_upload = true; + while (1) { + for (p = img, i = 0; p; p = p->next) { + if (p->w <= 0 || p->h <= 0) + continue; + // Allocate new space for surface/target arrays + if (i >= state->targets_size) { + state->targets_size = FFMAX(state->targets_size * 2, 512); + state->targets = + talloc_realloc_size(state, state->targets, + state->targets_size + * sizeof(*state->targets)); + state->scratch = + talloc_realloc_size(state, state->scratch, + (state->targets_size + 16) + * sizeof(*state->scratch)); + } + state->targets[i].source.x1 = p->w + EOSD_PADDING; + state->targets[i].source.y1 = p->h + EOSD_PADDING; + i++; + } + if (pack_rectangles(state->targets, i, sfc->w, sfc->h, + state->scratch) >= 0) + break; + int w = FFMIN(FFMAX(sfc->w * 2, EOSD_SURFACE_INITIAL_SIZE), + state->max_surface_width); + int h = FFMIN(FFMAX(sfc->h * 2, EOSD_SURFACE_INITIAL_SIZE), + state->max_surface_height); + if (w == sfc->w && h == sfc->h) { + mp_msg(MSGT_VO, MSGL_ERR, "[eosd] EOSD bitmaps do not fit on " + "a surface with the maximum supported size\n"); + return; + } + sfc->w = w; + sfc->h = h; + *out_need_reallocate = true; + } + if (*out_need_reallocate) { + mp_msg(MSGT_VO, MSGL_V, "[eosd] Allocate a %dx%d surface for " + "EOSD bitmaps.\n", sfc->w, sfc->h); + } + +eosd_skip_upload: + for (p = img; p; p = p->next) { + if (p->w <= 0 || p->h <= 0) + continue; + struct eosd_target *target = &state->targets[state->targets_count]; + target->source.x1 -= EOSD_PADDING; + target->source.y1 -= EOSD_PADDING; + target->dest.x0 = p->dst_x; + target->dest.y0 = p->dst_y; + target->dest.x1 = p->w + p->dst_x; + target->dest.y1 = p->h + p->dst_y; + target->color = p->color; + target->ass_img = p; + state->targets_count++; + } +} + +// Calculate the bounding box of all sub-rectangles in the EOSD surface that +// will be used for EOSD rendering. +// If the bounding box is empty, return false. +bool eosd_packer_calculate_source_bb(struct eosd_packer *state, + struct eosd_rect *out_bb) +{ + struct eosd_rect bb = { state->surface.w, state->surface.h, 0, 0 }; + + for (int n = 0; n < state->targets_count; n++) { + struct eosd_rect s = state->targets[n].source; + bb.x0 = FFMIN(bb.x0, s.x0); + bb.y0 = FFMIN(bb.y0, s.y0); + bb.x1 = FFMAX(bb.x1, s.x1); + bb.y1 = FFMAX(bb.y1, s.y1); + } + + // avoid degenerate bounding box if empty + bb.x0 = FFMIN(bb.x0, bb.x1); + bb.y0 = FFMIN(bb.y0, bb.y1); + + *out_bb = bb; + return state->targets_count > 0; +} diff --git a/libvo/eosd_packer.h b/libvo/eosd_packer.h new file mode 100644 index 0000000000..e207a4a2dd --- /dev/null +++ b/libvo/eosd_packer.h @@ -0,0 +1,71 @@ +/* + * This file is part of mplayer2. + * + * mplayer2 is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * mplayer2 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with mplayer2; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPLAYER_EOSD_PACKER_H +#define MPLAYER_EOSD_PACKER_H + +#include +#include + +#include "sub/ass_mp.h" + +// Pool of surfaces +struct eosd_surface { + //void *native_surface; + int w; + int h; +}; + +struct eosd_rect { + int x0, y0, x1, y1; +}; + +// List of surfaces to be rendered +struct eosd_target { + struct eosd_rect source; // position in EOSD surface + struct eosd_rect dest; // position on screen + uint32_t color; // libass-style color of the image + // NOTE: This must not be accessed after you return from your VO's + // VOCTRL_DRAW_EOSD call - libass will free or reuse the associated + // memory. Feel free to set this to NULL to make erroneous accesses to + // this member fail early. + ASS_Image *ass_img; +}; + +struct eosd_packer { + struct eosd_surface surface; + struct eosd_target *targets; + int targets_count; // number of valid elements in targets + int targets_size; // number of allocated elements in targets + + uint32_t max_surface_width; + uint32_t max_surface_height; + + int *scratch; +}; + +struct eosd_packer *eosd_packer_create(void *talloc_ctx); +void eosd_packer_reinit(struct eosd_packer *state, uint32_t max_width, + uint32_t max_height); +void eosd_packer_generate(struct eosd_packer *state, mp_eosd_images_t *imgs, + bool *out_need_reposition, bool *out_need_upload, + bool *out_need_reallocate); +bool eosd_packer_calculate_source_bb(struct eosd_packer *state, + struct eosd_rect *out_bb); + +#endif /* MPLAYER_EOSD_PACKER_H */ diff --git a/libvo/vo_vdpau.c b/libvo/vo_vdpau.c index f95937c122..101291a558 100644 --- a/libvo/vo_vdpau.c +++ b/libvo/vo_vdpau.c @@ -56,6 +56,7 @@ #include "libavutil/mathematics.h" #include "sub/ass_mp.h" +#include "eosd_packer.h" #define WRAP_ADD(x, a, m) ((a) < 0 \ ? ((x)+(a)+(m) < (m) ? (x)+(a)+(m) : (x)+(a)) \ @@ -85,9 +86,6 @@ /* number of palette entries */ #define PALETTE_SIZE 256 -/* Initial size of EOSD surface in pixels (x*x) */ -#define EOSD_SURFACE_INITIAL_SIZE 256 - /* Pixelformat used for output surfaces */ #define OUTPUT_RGBA_FORMAT VDP_RGBA_FORMAT_B8G8R8A8 @@ -173,24 +171,8 @@ struct vdpctx { // EOSD // Pool of surfaces - struct eosd_bitmap_surface { - VdpBitmapSurface surface; - int w; - int h; - uint32_t max_width; - uint32_t max_height; - } eosd_surface; - - // List of surfaces to be rendered - struct eosd_target { - VdpRect source; - VdpRect dest; - VdpColor color; - } *eosd_targets; - int eosd_targets_size; - int *eosd_scratch; - - int eosd_render_count; + VdpBitmapSurface eosd_surface; + struct eosd_packer *eosd_packer; // Video equalizer struct mp_csp_equalizer video_eq; @@ -792,13 +774,16 @@ static int initialize_vdpau_objects(struct vo *vo) if (create_vdp_mixer(vo, vc->vdp_chroma_type) < 0) return -1; + int max_width = 0, max_height = 0; vdp_st = vdp-> bitmap_surface_query_capabilities(vc->vdp_device, VDP_RGBA_FORMAT_A8, &(VdpBool){0}, - &vc->eosd_surface.max_width, - &vc->eosd_surface.max_height); + &max_width, + &max_height); CHECK_ST_WARNING("Query to get max EOSD surface size failed"); + eosd_packer_reinit(vc->eosd_packer, max_width, max_height); + forget_frames(vo); resize(vo); return 0; @@ -818,11 +803,9 @@ static void mark_vdpau_objects_uninitialized(struct vo *vo) for (int i = 0; i <= MAX_OUTPUT_SURFACES; i++) vc->output_surfaces[i] = VDP_INVALID_HANDLE; vc->vdp_device = VDP_INVALID_HANDLE; - vc->eosd_surface = (struct eosd_bitmap_surface){ - .surface = VDP_INVALID_HANDLE, - }; + vc->eosd_surface = VDP_INVALID_HANDLE; + eosd_packer_reinit(vc->eosd_packer, 0, 0); vc->output_surface_width = vc->output_surface_height = -1; - vc->eosd_render_count = 0; } static int handle_preemption(struct vo *vo) @@ -1018,6 +1001,8 @@ static void draw_osd_I8A8(void *ctx, int x0, int y0, int w, int h, "vdp_output_surface_render_output_surface"); } +#define EOSD_VDP_RC(r) &(VdpRect){r.x0, r.y0, r.x1, r.y1} + static void draw_eosd(struct vo *vo) { struct vdpctx *vc = vo->priv; @@ -1043,200 +1028,66 @@ static void draw_eosd(struct vo *vo) .blend_equation_alpha = VDP_OUTPUT_SURFACE_RENDER_BLEND_EQUATION_ADD, }; - for (i = 0; i < vc->eosd_render_count; i++) { + for (i = 0; i < vc->eosd_packer->targets_count; i++) { + struct eosd_target *target = &vc->eosd_packer->targets[i]; + VdpColor color = { + .alpha = 1.0 - ((target->color >> 0) & 0xff) / 255.0, + .blue = ((target->color >> 8) & 0xff) / 255.0, + .green = ((target->color >> 16) & 0xff) / 255.0, + .red = ((target->color >> 24) & 0xff) / 255.0, + }; vdp_st = vdp-> output_surface_render_bitmap_surface(output_surface, - &vc->eosd_targets[i].dest, - vc->eosd_surface.surface, - &vc->eosd_targets[i].source, - &vc->eosd_targets[i].color, + EOSD_VDP_RC(target->dest), + vc->eosd_surface, + EOSD_VDP_RC(target->source), + &color, &blend_state, VDP_OUTPUT_SURFACE_RENDER_ROTATE_0); CHECK_ST_WARNING("EOSD: Error when rendering"); } } -#define HEIGHT_SORT_BITS 4 -static int size_index(struct eosd_target *r) -{ - unsigned int h = r->source.y1; - int n = av_log2_16bit(h); - return (n << HEIGHT_SORT_BITS) - + (- 1 - (h << HEIGHT_SORT_BITS >> n) & (1 << HEIGHT_SORT_BITS) - 1); -} - -/* Pack the given rectangles into an area of size w * h. - * The size of each rectangle is read from .source.x1/.source.y1. - * The height of each rectangle must be at least 1 and less than 65536. - * The .source rectangle is then set corresponding to the packed position. - * 'scratch' must point to work memory for num_rects+16 ints. - * Return 0 on success, -1 if the rectangles did not fit in w*h. - * - * The rectangles are placed in rows in order approximately sorted by - * height (the approximate sorting is simpler than a full one would be, - * and allows the algorithm to work in linear time). Additionally, to - * reduce wasted space when there are a few tall rectangles, empty - * lower-right parts of rows are filled recursively when the size of - * rectangles in the row drops past a power-of-two threshold. So if a - * row starts with rectangles of size 3x50, 10x40 and 5x20 then the - * free rectangle with corners (13, 20)-(w, 50) is filled recursively. - */ -static int pack_rectangles(struct eosd_target *rects, int num_rects, - int w, int h, int *scratch) -{ - int bins[16 << HEIGHT_SORT_BITS]; - int sizes[16 << HEIGHT_SORT_BITS] = {}; - for (int i = 0; i < num_rects; i++) - sizes[size_index(rects + i)]++; - int idx = 0; - for (int i = 0; i < 16 << HEIGHT_SORT_BITS; i += 1 << HEIGHT_SORT_BITS) { - for (int j = 0; j < 1 << HEIGHT_SORT_BITS; j++) { - bins[i + j] = idx; - idx += sizes[i + j]; - } - scratch[idx++] = -1; - } - for (int i = 0; i < num_rects; i++) - scratch[bins[size_index(rects + i)]++] = i; - for (int i = 0; i < 16; i++) - bins[i] = bins[i << HEIGHT_SORT_BITS] - sizes[i << HEIGHT_SORT_BITS]; - struct { - int size, x, bottom; - } stack[16] = {{15, 0, h}}, s = {}; - int stackpos = 1; - int y; - while (stackpos) { - y = s.bottom; - s = stack[--stackpos]; - s.size++; - while (s.size--) { - int maxy = -1; - int obj; - while ((obj = scratch[bins[s.size]]) >= 0) { - int bottom = y + rects[obj].source.y1; - if (bottom > s.bottom) - break; - int right = s.x + rects[obj].source.x1; - if (right > w) - break; - bins[s.size]++; - rects[obj].source.x0 = s.x; - rects[obj].source.x1 += s.x; - rects[obj].source.y0 = y; - rects[obj].source.y1 += y; - num_rects--; - if (maxy <= 0) - stack[stackpos++] = s; - s.x = right; - maxy = FFMAX(maxy, bottom); - } - if (maxy > 0) - s.bottom = maxy; - } - } - return num_rects ? -1 : 0; -} - static void generate_eosd(struct vo *vo, mp_eosd_images_t *imgs) { struct vdpctx *vc = vo->priv; struct vdp_functions *vdp = vc->vdp; VdpStatus vdp_st; + struct eosd_packer *packer = vc->eosd_packer; int i; - ASS_Image *img = imgs->imgs; - ASS_Image *p; - struct eosd_bitmap_surface *sfc = &vc->eosd_surface; - bool need_upload = false; - - if (imgs->changed == 0) - return; // Nothing changed, no need to redraw - - vc->eosd_render_count = 0; - - if (!img) - return; // There's nothing to render! - - if (imgs->changed == 1) - goto eosd_skip_upload; - - need_upload = true; - bool reallocate = false; - while (1) { - for (p = img, i = 0; p; p = p->next) { - if (p->w <= 0 || p->h <= 0) - continue; - // Allocate new space for surface/target arrays - if (i >= vc->eosd_targets_size) { - vc->eosd_targets_size = FFMAX(vc->eosd_targets_size * 2, 512); - vc->eosd_targets = - talloc_realloc_size(vc, vc->eosd_targets, - vc->eosd_targets_size - * sizeof(*vc->eosd_targets)); - vc->eosd_scratch = - talloc_realloc_size(vc, vc->eosd_scratch, - (vc->eosd_targets_size + 16) - * sizeof(*vc->eosd_scratch)); - } - vc->eosd_targets[i].source.x1 = p->w; - vc->eosd_targets[i].source.y1 = p->h; - i++; - } - if (pack_rectangles(vc->eosd_targets, i, sfc->w, sfc->h, - vc->eosd_scratch) >= 0) - break; - int w = FFMIN(FFMAX(sfc->w * 2, EOSD_SURFACE_INITIAL_SIZE), - sfc->max_width); - int h = FFMIN(FFMAX(sfc->h * 2, EOSD_SURFACE_INITIAL_SIZE), - sfc->max_height); - if (w == sfc->w && h == sfc->h) { - mp_msg(MSGT_VO, MSGL_ERR, "[vdpau] EOSD bitmaps do not fit on " - "a surface with the maximum supported size\n"); - return; - } else { - sfc->w = w; - sfc->h = h; - } - reallocate = true; - } - if (reallocate) { - if (sfc->surface != VDP_INVALID_HANDLE) { - vdp_st = vdp->bitmap_surface_destroy(sfc->surface); + + bool need_repos, need_upload, need_resize; + eosd_packer_generate(packer, imgs, &need_repos, &need_upload, &need_resize); + + if (!need_upload) + return; + + if (need_resize) { + if (vc->eosd_surface != VDP_INVALID_HANDLE) { + vdp_st = vdp->bitmap_surface_destroy(vc->eosd_surface); CHECK_ST_WARNING("Error when calling vdp_bitmap_surface_destroy"); } - mp_msg(MSGT_VO, MSGL_V, "[vdpau] Allocating a %dx%d surface for " - "EOSD bitmaps.\n", sfc->w, sfc->h); vdp_st = vdp->bitmap_surface_create(vc->vdp_device, VDP_RGBA_FORMAT_A8, - sfc->w, sfc->h, true, - &sfc->surface); + packer->surface.w, packer->surface.h, + true, &vc->eosd_surface); if (vdp_st != VDP_STATUS_OK) - sfc->surface = VDP_INVALID_HANDLE; + vc->eosd_surface = VDP_INVALID_HANDLE; CHECK_ST_WARNING("EOSD: error when creating surface"); } -eosd_skip_upload: - if (sfc->surface == VDP_INVALID_HANDLE) + if (vc->eosd_surface == VDP_INVALID_HANDLE) return; - for (p = img; p; p = p->next) { - if (p->w <= 0 || p->h <= 0) - continue; - struct eosd_target *target = &vc->eosd_targets[vc->eosd_render_count]; - if (need_upload) { - vdp_st = vdp-> - bitmap_surface_put_bits_native(sfc->surface, - (const void *) &p->bitmap, - &p->stride, &target->source); - CHECK_ST_WARNING("EOSD: putbits failed"); - } - // Render dest, color, etc. - target->color.alpha = 1.0 - ((p->color >> 0) & 0xff) / 255.0; - target->color.blue = ((p->color >> 8) & 0xff) / 255.0; - target->color.green = ((p->color >> 16) & 0xff) / 255.0; - target->color.red = ((p->color >> 24) & 0xff) / 255.0; - target->dest.x0 = p->dst_x; - target->dest.y0 = p->dst_y; - target->dest.x1 = p->w + p->dst_x; - target->dest.y1 = p->h + p->dst_y; - vc->eosd_render_count++; + + for (i = 0; i < vc->eosd_packer->targets_count; i++) { + struct eosd_target *target = &vc->eosd_packer->targets[i]; + ASS_Image *p = target->ass_img; + vdp_st = vdp-> + bitmap_surface_put_bits_native(vc->eosd_surface, + (const void *) &p->bitmap, + &p->stride, + EOSD_VDP_RC(target->source)); + CHECK_ST_WARNING("EOSD: putbits failed"); + target->ass_img = NULL; } } @@ -1607,8 +1458,8 @@ static void destroy_vdpau_objects(struct vo *vo) CHECK_ST_WARNING("Error when calling vdp_output_surface_destroy"); } - if (vc->eosd_surface.surface != VDP_INVALID_HANDLE) { - vdp_st = vdp->bitmap_surface_destroy(vc->eosd_surface.surface); + if (vc->eosd_surface != VDP_INVALID_HANDLE) { + vdp_st = vdp->bitmap_surface_destroy(vc->eosd_surface); CHECK_ST_WARNING("Error when calling vdp_bitmap_surface_destroy"); } @@ -1643,6 +1494,8 @@ static int preinit(struct vo *vo, const char *arg) struct vdpctx *vc = talloc_zero(vo, struct vdpctx); vo->priv = vc; + vc->eosd_packer = eosd_packer_create(vo); + // Mark everything as invalid first so uninit() can tell what has been // allocated mark_vdpau_objects_uninitialized(vo); -- cgit v1.2.3 From adc85b0031867bfc2296f2749e8544e8147b5b60 Mon Sep 17 00:00:00 2001 From: wm4 Date: Sat, 29 Oct 2011 19:38:33 +0200 Subject: vo_gl: use generic eosd code This is an experiment. The change probably doesn't matter much. One issue with the old code was that "large" images caused each sub-image to be created and rendered as a new texture. "Large" in this case means larger than 32x32 pixels, which actually isn't very large with screen sizes beyond 1500x1000 pixels. This means rendering a simple subtitle for a fullscreened video may allocate many small textures, one for each glyph. On the other hand, the old code could be fixed by tuning the texture sizes for "modern" work loads. Also, the new code uses less deprecated OpenGL features and draws all sub-images in one batch. There are two possible issues the new code could cause: - Drivers could have performance issues with the larger texture sizes and the number of glTexSubImage2D calls on it - There is only one EOSD texture, which could become full (it's enlarged on demand, but restricted by driver texture size limitations) It has been reported that this is faster on OSX with ATI GPUs than the old code. --- libvo/gl_common.c | 6 ++ libvo/gl_common.h | 7 ++ libvo/vo_gl.c | 256 +++++++++++++++++++++++------------------------------- 3 files changed, 124 insertions(+), 145 deletions(-) diff --git a/libvo/gl_common.c b/libvo/gl_common.c index 7cca800d40..aa34521802 100644 --- a/libvo/gl_common.c +++ b/libvo/gl_common.c @@ -360,6 +360,12 @@ static const extfunc_desc_t extfuncs[] = { DEF_FUNC_DESC(ColorMask), DEF_FUNC_DESC(ReadPixels), DEF_FUNC_DESC(ReadBuffer), + DEF_FUNC_DESC(VertexPointer), + DEF_FUNC_DESC(ColorPointer), + DEF_FUNC_DESC(TexCoordPointer), + DEF_FUNC_DESC(DrawArrays), + DEF_FUNC_DESC(EnableClientState), + DEF_FUNC_DESC(DisableClientState), DEF_EXT_DESC(GenBuffers, NULL, ("glGenBuffers", "glGenBuffersARB")), diff --git a/libvo/gl_common.h b/libvo/gl_common.h index 8e3294f441..5a0fd9b8a8 100644 --- a/libvo/gl_common.h +++ b/libvo/gl_common.h @@ -463,6 +463,13 @@ struct GL { void (GLAPIENTRY *ReadPixels)(GLint, GLint, GLsizei, GLsizei, GLenum, GLenum, GLvoid *); void (GLAPIENTRY *ReadBuffer)(GLenum); + void (GLAPIENTRY *VertexPointer)(GLint, GLenum, GLsizei, const GLvoid *); + void (GLAPIENTRY *ColorPointer)(GLint, GLenum, GLsizei, const GLvoid *); + void (GLAPIENTRY *TexCoordPointer)(GLint, GLenum, GLsizei, const GLvoid *); + void (GLAPIENTRY *DrawArrays)(GLenum, GLint, GLsizei); + void (GLAPIENTRY *EnableClientState)(GLenum); + void (GLAPIENTRY *DisableClientState)(GLenum); + // OpenGL extension functions void (GLAPIENTRY *GenBuffers)(GLsizei, GLuint *); diff --git a/libvo/vo_gl.c b/libvo/vo_gl.c index e68465f009..7bf5cf2ec1 100644 --- a/libvo/vo_gl.c +++ b/libvo/vo_gl.c @@ -38,6 +38,7 @@ #include "osd.h" #include "sub/font_load.h" #include "sub/sub.h" +#include "eosd_packer.h" #include "gl_common.h" #include "aspect.h" @@ -49,19 +50,17 @@ static int preinit_nosw(struct vo *vo, const char *arg); //! How many parts the OSD may consist of at most #define MAX_OSD_PARTS 20 -#define LARGE_EOSD_TEX_SIZE 512 -#define TINYTEX_SIZE 16 -#define TINYTEX_COLS (LARGE_EOSD_TEX_SIZE / TINYTEX_SIZE) -#define TINYTEX_MAX (TINYTEX_COLS * TINYTEX_COLS) -#define SMALLTEX_SIZE 32 -#define SMALLTEX_COLS (LARGE_EOSD_TEX_SIZE / SMALLTEX_SIZE) -#define SMALLTEX_MAX (SMALLTEX_COLS * SMALLTEX_COLS) - //for gl_priv.use_yuv #define MASK_ALL_YUV (~(1 << YUV_CONVERSION_NONE)) #define MASK_NOT_COMBINERS (~((1 << YUV_CONVERSION_NONE) | (1 << YUV_CONVERSION_COMBINERS))) #define MASK_GAMMA_SUPPORT (MASK_NOT_COMBINERS & ~(1 << YUV_CONVERSION_FRAGMENT)) +struct vertex_eosd { + float x, y; + uint8_t color[4]; + float u, v; +}; + struct gl_priv { MPGLContext *glctx; GL *gl; @@ -74,17 +73,17 @@ struct gl_priv { //! Alpha textures for OSD GLuint osdatex[MAX_OSD_PARTS]; #endif - GLuint *eosdtex; - GLuint largeeosdtex[2]; + GLuint eosd_texture; + int eosd_texture_width, eosd_texture_height; + struct eosd_packer *eosd; + struct vertex_eosd *eosd_va; //! Display lists that draw the OSD parts GLuint osdDispList[MAX_OSD_PARTS]; #ifndef FAST_OSD GLuint osdaDispList[MAX_OSD_PARTS]; #endif - GLuint eosdDispList; //! How many parts the OSD currently consists of int osdtexCnt; - int eosdtexCnt; int osd_color; int use_ycbcr; @@ -285,150 +284,111 @@ static void clearOSD(struct vo *vo) } /** - * \brief remove textures, display list and free memory used by EOSD + * \brief construct display list from ass image list + * \param img image list to create OSD from. */ -static void clearEOSD(struct vo *vo) +static void genEOSD(struct vo *vo, mp_eosd_images_t *imgs) { struct gl_priv *p = vo->priv; GL *gl = p->gl; - if (p->eosdDispList) - gl->DeleteLists(p->eosdDispList, 1); - p->eosdDispList = 0; - if (p->eosdtexCnt) - gl->DeleteTextures(p->eosdtexCnt, p->eosdtex); - p->eosdtexCnt = 0; - free(p->eosdtex); - p->eosdtex = NULL; -} + bool need_repos, need_upload, need_allocate; + eosd_packer_generate(p->eosd, imgs, &need_repos, &need_upload, + &need_allocate); -static inline int is_tinytex(ASS_Image *i, int tinytexcur) -{ - return i->w < TINYTEX_SIZE && i->h < TINYTEX_SIZE - && tinytexcur < TINYTEX_MAX; -} + if (!need_repos) + return; -static inline int is_smalltex(ASS_Image *i, int smalltexcur) -{ - return i->w < SMALLTEX_SIZE && i->h < SMALLTEX_SIZE - && smalltexcur < SMALLTEX_MAX; -} + if (!p->eosd_texture) + gl->GenTextures(1, &p->eosd_texture); -static inline void tinytex_pos(int tinytexcur, int *x, int *y) -{ - *x = (tinytexcur % TINYTEX_COLS) * TINYTEX_SIZE; - *y = (tinytexcur / TINYTEX_COLS) * TINYTEX_SIZE; -} + gl->BindTexture(p->target, p->eosd_texture); -static inline void smalltex_pos(int smalltexcur, int *x, int *y) -{ - *x = (smalltexcur % SMALLTEX_COLS) * SMALLTEX_SIZE; - *y = (smalltexcur / SMALLTEX_COLS) * SMALLTEX_SIZE; + if (need_allocate) { + texSize(vo, p->eosd->surface.w, p->eosd->surface.h, + &p->eosd_texture_width, &p->eosd_texture_height); + // xxx it doesn't need to be cleared, that's a waste of time + glCreateClearTex(gl, p->target, GL_ALPHA, GL_ALPHA, GL_UNSIGNED_BYTE, + GL_NEAREST, p->eosd_texture_width, + p->eosd_texture_height, 0); + } + + // 2 triangles primitives per quad = 6 vertices per quad + // not using GL_QUADS, as it is deprecated in OpenGL 3.x and later + p->eosd_va = talloc_realloc_size(p->eosd, p->eosd_va, + p->eosd->targets_count + * sizeof(struct vertex_eosd) * 6); + + float eosd_w = p->eosd_texture_width; + float eosd_h = p->eosd_texture_height; + + if (p->use_rectangle == 1) + eosd_w = eosd_h = 1.0f; + + for (int n = 0; n < p->eosd->targets_count; n++) { + struct eosd_target *target = &p->eosd->targets[n]; + ASS_Image *i = target->ass_img; + + if (need_upload) { + glUploadTex(gl, p->target, GL_ALPHA, GL_UNSIGNED_BYTE, i->bitmap, + i->stride, target->source.x0, target->source.y0, + i->w, i->h, 0); + } + + uint8_t color[4] = { i->color >> 24, (i->color >> 16) & 0xff, + (i->color >> 8) & 0xff, 255 - (i->color & 0xff) }; + + float x0 = target->dest.x0; + float y0 = target->dest.y0; + float x1 = target->dest.x1; + float y1 = target->dest.y1; + float tx0 = target->source.x0 / eosd_w; + float ty0 = target->source.y0 / eosd_h; + float tx1 = target->source.x1 / eosd_w; + float ty1 = target->source.y1 / eosd_h; + +#define COLOR_INIT {color[0], color[1], color[2], color[3]} + struct vertex_eosd *va = &p->eosd_va[n * 6]; + va[0] = (struct vertex_eosd) { x0, y0, COLOR_INIT, tx0, ty0 }; + va[1] = (struct vertex_eosd) { x0, y1, COLOR_INIT, tx0, ty1 }; + va[2] = (struct vertex_eosd) { x1, y0, COLOR_INIT, tx1, ty0 }; + va[3] = (struct vertex_eosd) { x1, y1, COLOR_INIT, tx1, ty1 }; + va[4] = va[2]; + va[5] = va[1]; +#undef COLOR_INIT + } + + gl->BindTexture(p->target, 0); } -/** - * \brief construct display list from ass image list - * \param img image list to create OSD from. - * A value of NULL has the same effect as clearEOSD() - */ -static void genEOSD(struct vo *vo, mp_eosd_images_t *imgs) +// Note: relies on state being setup, like projection matrix and blending +static void drawEOSD(struct vo *vo) { struct gl_priv *p = vo->priv; GL *gl = p->gl; - int sx, sy; - int tinytexcur = 0; - int smalltexcur = 0; - GLuint *curtex; - GLint scale_type = p->scaled_osd ? GL_LINEAR : GL_NEAREST; - ASS_Image *img = imgs->imgs; - ASS_Image *i; - - if (imgs->changed == 0) // there are elements, but they are unchanged + if (p->eosd->targets_count == 0) return; - if (img && imgs->changed == 1) // there are elements, but they just moved - goto skip_upload; - clearEOSD(vo); - if (!img) - return; - if (!p->largeeosdtex[0]) { - gl->GenTextures(2, p->largeeosdtex); - for (int n = 0; n < 2; n++) { - gl->BindTexture(p->target, p->largeeosdtex[n]); - glCreateClearTex(gl, p->target, GL_ALPHA, GL_ALPHA, - GL_UNSIGNED_BYTE, scale_type, - LARGE_EOSD_TEX_SIZE, LARGE_EOSD_TEX_SIZE, 0); - } - } - for (i = img; i; i = i->next) { - if (i->w <= 0 || i->h <= 0 || i->stride < i->w) - continue; - if (is_tinytex(i, tinytexcur)) - tinytexcur++; - else if (is_smalltex(i, smalltexcur)) - smalltexcur++; - else - p->eosdtexCnt++; - } - mp_msg(MSGT_VO, MSGL_DBG2, "EOSD counts (tiny, small, all): %i, %i, %i\n", - tinytexcur, smalltexcur, p->eosdtexCnt); - if (p->eosdtexCnt) { - p->eosdtex = calloc(p->eosdtexCnt, sizeof(GLuint)); - gl->GenTextures(p->eosdtexCnt, p->eosdtex); - } - tinytexcur = smalltexcur = 0; - for (i = img, curtex = p->eosdtex; i; i = i->next) { - int x = 0, y = 0; - if (i->w <= 0 || i->h <= 0 || i->stride < i->w) { - mp_msg(MSGT_VO, MSGL_V, "Invalid dimensions OSD for part!\n"); - continue; - } - if (is_tinytex(i, tinytexcur)) { - tinytex_pos(tinytexcur, &x, &y); - gl->BindTexture(p->target, p->largeeosdtex[0]); - tinytexcur++; - } else if (is_smalltex(i, smalltexcur)) { - smalltex_pos(smalltexcur, &x, &y); - gl->BindTexture(p->target, p->largeeosdtex[1]); - smalltexcur++; - } else { - texSize(vo, i->w, i->h, &sx, &sy); - gl->BindTexture(p->target, *curtex++); - glCreateClearTex(gl, p->target, GL_ALPHA, GL_ALPHA, - GL_UNSIGNED_BYTE, scale_type, sx, sy, 0); - } - glUploadTex(gl, p->target, GL_ALPHA, GL_UNSIGNED_BYTE, i->bitmap, - i->stride, x, y, i->w, i->h, 0); - } - p->eosdDispList = gl->GenLists(1); -skip_upload: - gl->NewList(p->eosdDispList, GL_COMPILE); - tinytexcur = smalltexcur = 0; - for (i = img, curtex = p->eosdtex; i; i = i->next) { - int x = 0, y = 0; - if (i->w <= 0 || i->h <= 0 || i->stride < i->w) - continue; - gl->Color4ub(i->color >> 24, (i->color >> 16) & 0xff, - (i->color >> 8) & 0xff, 255 - (i->color & 0xff)); - if (is_tinytex(i, tinytexcur)) { - tinytex_pos(tinytexcur, &x, &y); - sx = sy = LARGE_EOSD_TEX_SIZE; - gl->BindTexture(p->target, p->largeeosdtex[0]); - tinytexcur++; - } else if (is_smalltex(i, smalltexcur)) { - smalltex_pos(smalltexcur, &x, &y); - sx = sy = LARGE_EOSD_TEX_SIZE; - gl->BindTexture(p->target, p->largeeosdtex[1]); - smalltexcur++; - } else { - texSize(vo, i->w, i->h, &sx, &sy); - gl->BindTexture(p->target, *curtex++); - } - glDrawTex(gl, i->dst_x, i->dst_y, i->w, i->h, x, y, i->w, i->h, sx, sy, - p->use_rectangle == 1, 0, 0); - } - gl->EndList(); + gl->BindTexture(p->target, p->eosd_texture); + + struct vertex_eosd *va = p->eosd_va; + size_t stride = sizeof(struct vertex_eosd); + + gl->VertexPointer(2, GL_FLOAT, stride, &va[0].x); + gl->ColorPointer(4, GL_UNSIGNED_BYTE, stride, &va[0].color[0]); + gl->TexCoordPointer(2, GL_FLOAT, stride, &va[0].u); + + gl->EnableClientState(GL_VERTEX_ARRAY); + gl->EnableClientState(GL_TEXTURE_COORD_ARRAY); + gl->EnableClientState(GL_COLOR_ARRAY); + + gl->DrawArrays(GL_TRIANGLES, 0, p->eosd->targets_count * 6); + + gl->DisableClientState(GL_VERTEX_ARRAY); + gl->DisableClientState(GL_TEXTURE_COORD_ARRAY); + gl->DisableClientState(GL_COLOR_ARRAY); + gl->BindTexture(p->target, 0); } @@ -450,10 +410,10 @@ static void uninitGl(struct vo *vo) gl->DeleteTextures(i, p->default_texs); p->default_texs[0] = 0; clearOSD(vo); - clearEOSD(vo); - if (p->largeeosdtex[0]) - gl->DeleteTextures(2, p->largeeosdtex); - p->largeeosdtex[0] = 0; + if (p->eosd_texture) + gl->DeleteTextures(1, &p->eosd_texture); + eosd_packer_reinit(p->eosd, 0, 0); + p->eosd_texture = 0; if (gl->DeleteBuffers && p->buffer) gl->DeleteBuffers(1, &p->buffer); p->buffer = 0; @@ -633,6 +593,10 @@ static int initGl(struct vo *vo, uint32_t d_width, uint32_t d_height) update_yuvconv(vo); } + GLint max_texture_size; + gl->GetIntegerv(GL_MAX_TEXTURE_SIZE, &max_texture_size); + eosd_packer_reinit(p->eosd, max_texture_size, max_texture_size); + resize(vo, d_width, d_height); gl->ClearColor(0.0f, 0.0f, 0.0f, 0.0f); @@ -786,7 +750,7 @@ static void do_render_osd(struct vo *vo, int type) GL *gl = p->gl; int draw_osd = (type & RENDER_OSD) && p->osdtexCnt > 0; - int draw_eosd = (type & RENDER_EOSD) && p->eosdDispList; + int draw_eosd = (type & RENDER_EOSD); if (!draw_osd && !draw_eosd) return; // set special rendering parameters @@ -799,7 +763,7 @@ static void do_render_osd(struct vo *vo, int type) gl->Enable(GL_BLEND); if (draw_eosd) { gl->BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - gl->CallList(p->eosdDispList); + drawEOSD(vo); } if (draw_osd) { gl->Color4ub((p->osd_color >> 16) & 0xff, (p->osd_color >> 8) & 0xff, @@ -1239,6 +1203,8 @@ static int preinit_internal(struct vo *vo, const char *arg, int allow_sw, .osd_color = 0xffffff, }; + p->eosd = eosd_packer_create(vo); + //essentially unused; for legacy warnings only int user_colorspace = 0; int levelconv = -1; -- cgit v1.2.3