/* * This file is part of mpv. * * mpv is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * mpv 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with mpv. If not, see . */ #include #include #include #include #include "formats.h" #include "utils.h" #include "osd.h" #define GLSL(x) gl_sc_add(sc, #x "\n"); // glBlendFuncSeparate() arguments static const int blend_factors[SUBBITMAP_COUNT][4] = { [SUBBITMAP_LIBASS] = {GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA}, [SUBBITMAP_RGBA] = {GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA}, }; struct vertex { float position[2]; float texcoord[2]; uint8_t ass_color[4]; }; static const struct gl_vao_entry vertex_vao[] = { {"position", 2, GL_FLOAT, false, offsetof(struct vertex, position)}, {"texcoord" , 2, GL_FLOAT, false, offsetof(struct vertex, texcoord)}, {"ass_color", 4, GL_UNSIGNED_BYTE, true, offsetof(struct vertex, ass_color)}, {0} }; struct mpgl_osd_part { enum sub_bitmap_format format; int change_id; GLuint texture; int w, h; struct gl_pbo_upload pbo; int num_subparts; int prev_num_subparts; struct sub_bitmap *subparts; int num_vertices; struct vertex *vertices; }; struct mpgl_osd { struct mp_log *log; struct osd_state *osd; GL *gl; bool use_pbo; struct mpgl_osd_part *parts[MAX_OSD_PARTS]; const struct gl_format *fmt_table[SUBBITMAP_COUNT]; bool formats[SUBBITMAP_COUNT]; int64_t change_counter; // temporary int stereo_mode; struct mp_osd_res osd_res; void *scratch; }; struct mpgl_osd *mpgl_osd_init(GL *gl, struct mp_log *log, struct osd_state *osd) { struct mpgl_osd *ctx = talloc_ptrtype(NULL, ctx); *ctx = (struct mpgl_osd) { .log = log, .osd = osd, .gl = gl, .scratch = talloc_zero_size(ctx, 1), }; ctx->fmt_table[SUBBITMAP_LIBASS] = gl_find_unorm_format(gl, 1, 1); ctx->fmt_table[SUBBITMAP_RGBA] = gl_find_unorm_format(gl, 1, 4); for (int n = 0; n < MAX_OSD_PARTS; n++) ctx->parts[n] = talloc_zero(ctx, struct mpgl_osd_part); for (int n = 0; n < SUBBITMAP_COUNT; n++) ctx->formats[n] = !!ctx->fmt_table[n]; return ctx; } void mpgl_osd_destroy(struct mpgl_osd *ctx) { if (!ctx) return; GL *gl = ctx->gl; for (int n = 0; n < MAX_OSD_PARTS; n++) { struct mpgl_osd_part *p = ctx->parts[n]; gl->DeleteTextures(1, &p->texture); gl_pbo_upload_uninit(&p->pbo); } talloc_free(ctx); } void mpgl_osd_set_options(struct mpgl_osd *ctx, bool pbo) { ctx->use_pbo = pbo; } static int next_pow2(int v) { for (int x = 0; x < 30; x++) { if ((1 << x) >= v) return 1 << x; } return INT_MAX; } static bool upload_osd(struct mpgl_osd *ctx, struct mpgl_osd_part *osd, struct sub_bitmaps *imgs) { GL *gl = ctx->gl; bool ok = false; assert(imgs->packed); int req_w = next_pow2(imgs->packed_w); int req_h = next_pow2(imgs->packed_h); const struct gl_format *fmt = ctx->fmt_table[imgs->format]; assert(fmt); if (!osd->texture) gl->GenTextures(1, &osd->texture); gl->BindTexture(GL_TEXTURE_2D, osd->texture); if (req_w > osd->w || req_h > osd->h || osd->format != imgs->format) { osd->format = imgs->format; osd->w = FFMAX(32, req_w); osd->h = FFMAX(32, req_h); MP_VERBOSE(ctx, "Reallocating OSD texture to %dx%d.\n", osd->w, osd->h); GLint max_wh; gl->GetIntegerv(GL_MAX_TEXTURE_SIZE, &max_wh); if (osd->w > max_wh || osd->h > max_wh) { MP_ERR(ctx, "OSD bitmaps do not fit on a surface with the maximum " "supported size %dx%d.\n", max_wh, max_wh); goto done; } gl->TexImage2D(GL_TEXTURE_2D, 0, fmt->internal_format, osd->w, osd->h, 0, fmt->format, fmt->type, NULL); gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); } gl_pbo_upload_tex(&osd->pbo, gl, ctx->use_pbo, GL_TEXTURE_2D, fmt->format, fmt->type, osd->w, osd->h, imgs->packed->planes[0], imgs->packed->stride[0], 0, 0, imgs->packed_w, imgs->packed_h); ok = true; done: gl->BindTexture(GL_TEXTURE_2D, 0); return ok; } static void gen_osd_cb(void *pctx, struct sub_bitmaps *imgs) { struct mpgl_osd *ctx = pctx; if (imgs->num_parts == 0 || !ctx->formats[imgs->format]) return; struct mpgl_osd_part *osd = ctx->parts[imgs->render_index]; bool ok = true; if (imgs->change_id != osd->change_id) { if (!upload_osd(ctx, osd, imgs)) ok = false; osd->change_id = imgs->change_id; ctx->change_counter += 1; } osd->num_subparts = ok ? imgs->num_parts : 0; MP_TARRAY_GROW(osd, osd->subparts, osd->num_subparts); memcpy(osd->subparts, imgs->parts, osd->num_subparts * sizeof(osd->subparts[0])); } bool mpgl_osd_draw_prepare(struct mpgl_osd *ctx, int index, struct gl_shader_cache *sc) { assert(index >= 0 && index < MAX_OSD_PARTS); struct mpgl_osd_part *part = ctx->parts[index]; enum sub_bitmap_format fmt = part->format; if (!fmt || !part->num_subparts) return false; gl_sc_uniform_tex(sc, "osdtex", GL_TEXTURE_2D, part->texture); switch (fmt) { case SUBBITMAP_RGBA: { GLSL(color = texture(osdtex, texcoord).bgra;) break; } case SUBBITMAP_LIBASS: { GLSL(color = vec4(ass_color.rgb, ass_color.a * texture(osdtex, texcoord).r);) break; } default: abort(); } gl_sc_set_vertex_format(sc, vertex_vao, sizeof(struct vertex)); return true; } static void write_quad(struct vertex *va, struct gl_transform t, float x0, float y0, float x1, float y1, float tx0, float ty0, float tx1, float ty1, float tex_w, float tex_h, const uint8_t color[4]) { gl_transform_vec(t, &x0, &y0); gl_transform_vec(t, &x1, &y1); #define COLOR_INIT {color[0], color[1], color[2], color[3]} va[0] = (struct vertex){ {x0, y0}, {tx0 / tex_w, ty0 / tex_h}, COLOR_INIT }; va[1] = (struct vertex){ {x0, y1}, {tx0 / tex_w, ty1 / tex_h}, COLOR_INIT }; va[2] = (struct vertex){ {x1, y0}, {tx1 / tex_w, ty0 / tex_h}, COLOR_INIT }; va[3] = (struct vertex){ {x1, y1}, {tx1 / tex_w, ty1 / tex_h}, COLOR_INIT }; va[4] = va[2]; va[5] = va[1]; #undef COLOR_INIT } static void generate_verts(struct mpgl_osd_part *part, struct gl_transform t) { int num_vertices = part->num_subparts * 6; MP_TARRAY_GROW(part, part->vertices, part->num_vertices + num_vertices); for (int n = 0; n < part->num_subparts; n++) { struct sub_bitmap *b = &part->subparts[n]; struct vertex *va = &part->vertices[part->num_vertices]; // NOTE: the blend color is used with SUBBITMAP_LIBASS only, so it // doesn't matter that we upload garbage for the other formats uint32_t c = b->libass.color; uint8_t color[4] = { c >> 24, (c >> 16) & 0xff, (c >> 8) & 0xff, 255 - (c & 0xff) }; write_quad(&va[n * 6], t, b->x, b->y, b->x + b->dw, b->y + b->dh, b->src_x, b->src_y, b->src_x + b->w, b->src_y + b->h, part->w, part->h, color); } part->num_vertices += num_vertices; } // number of screen divisions per axis (x=0, y=1) for the current 3D mode static void get_3d_side_by_side(int stereo_mode, int div[2]) { div[0] = div[1] = 1; switch (stereo_mode) { case MP_STEREO3D_SBS2L: case MP_STEREO3D_SBS2R: div[0] = 2; break; case MP_STEREO3D_AB2R: case MP_STEREO3D_AB2L: div[1] = 2; break; } } void mpgl_osd_draw_finish(struct mpgl_osd *ctx, int vp_w, int vp_h, int index, struct gl_shader_cache *sc) { GL *gl = ctx->gl; struct mpgl_osd_part *part = ctx->parts[index]; int div[2]; get_3d_side_by_side(ctx->stereo_mode, div); part->num_vertices = 0; for (int x = 0; x < div[0]; x++) { for (int y = 0; y < div[1]; y++) { struct gl_transform t; gl_transform_ortho(&t, 0, vp_w, 0, vp_h); float a_x = ctx->osd_res.w * x; float a_y = ctx->osd_res.h * y; t.t[0] += a_x * t.m[0][0] + a_y * t.m[1][0]; t.t[1] += a_x * t.m[0][1] + a_y * t.m[1][1]; generate_verts(part, t); } } if (!part->num_vertices) return; gl->Enable(GL_BLEND); const int *factors = &blend_factors[part->format][0]; gl->BlendFuncSeparate(factors[0], factors[1], factors[2], factors[3]); ctx->gl->Viewport(0, 0, vp_w, abs(vp_h)); gl_sc_draw_data(sc, GL_TRIANGLES, part->vertices, part->num_vertices); gl->BindTexture(GL_TEXTURE_2D, 0); gl->Disable(GL_BLEND); } static void set_res(struct mpgl_osd *ctx, struct mp_osd_res res, int stereo_mode) { int div[2]; get_3d_side_by_side(stereo_mode, div); res.w /= div[0]; res.h /= div[1]; ctx->osd_res = res; } void mpgl_osd_generate(struct mpgl_osd *ctx, struct mp_osd_res res, double pts, int stereo_mode, int draw_flags) { for (int n = 0; n < MAX_OSD_PARTS; n++) ctx->parts[n]->num_subparts = 0; set_res(ctx, res, stereo_mode); osd_draw(ctx->osd, ctx->osd_res, pts, draw_flags, ctx->formats, gen_osd_cb, ctx); ctx->stereo_mode = stereo_mode; // Parts going away does not necessarily result in gen_osd_cb() being called // (not even with num_parts==0), so check this separately. for (int n = 0; n < MAX_OSD_PARTS; n++) { struct mpgl_osd_part *part = ctx->parts[n]; if (part->num_subparts != part->prev_num_subparts) ctx->change_counter += 1; part->prev_num_subparts = part->num_subparts; } } // See osd_resize() for remarks. This function is an optional optimization too. void mpgl_osd_resize(struct mpgl_osd *ctx, struct mp_osd_res res, int stereo_mode) { set_res(ctx, res, stereo_mode); osd_resize(ctx->osd, ctx->osd_res); } int64_t mpgl_get_change_counter(struct mpgl_osd *ctx) { return ctx->change_counter; }