mirror of
https://github.com/mpv-player/mpv
synced 2024-12-19 05:15:12 +00:00
775e08ba65
When blending OSD and subtitles onto the video, we write bogus alpha values. This doesn't normally matter, because these values are normally unused and discarded. But at least on Wayland, the alpha values are used by the compositor and leads to transparent windows even with opaque video on places where the OSD happens to use transparency. (Also see github issue #338.) Until now, the alpha basically contained garbage. The source factor GL_SRC_ALPHA meant that alpha was multiplied with itself. Use GL_ONE instead (which is why we have to use glBlendFuncSeparate()). This should give correct results, even with video that has alpha. (Or at least it's something close to correct, I haven't thought too hard how the compositor will blend it, and in fact I couldn't manage to test it.) If glBlendFuncSeparate() is not available, fall back to glBlendFunc(), which does the same as the code did before this commit. Technically, we support GL 1.1, but glBlendFuncSeparate is 1.4, and I guess we should try not to crash if vo_opengl_old runs on a system with GL 1.1 drivers only.
381 lines
12 KiB
C
381 lines
12 KiB
C
/*
|
|
* This file is part of mplayer.
|
|
*
|
|
* mplayer 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.
|
|
*
|
|
* mplayer 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 mplayer. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <assert.h>
|
|
#include <libavutil/common.h>
|
|
|
|
#include "bitmap_packer.h"
|
|
|
|
#include "gl_osd.h"
|
|
|
|
struct osd_fmt_entry {
|
|
GLint internal_format;
|
|
GLint format;
|
|
GLenum type;
|
|
};
|
|
|
|
// 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},
|
|
};
|
|
|
|
static const struct osd_fmt_entry osd_to_gl3_formats[SUBBITMAP_COUNT] = {
|
|
[SUBBITMAP_LIBASS] = {GL_RED, GL_RED, GL_UNSIGNED_BYTE},
|
|
[SUBBITMAP_RGBA] = {GL_RGBA, GL_BGRA, GL_UNSIGNED_BYTE},
|
|
};
|
|
|
|
static const struct osd_fmt_entry osd_to_gl_legacy_formats[SUBBITMAP_COUNT] = {
|
|
[SUBBITMAP_LIBASS] = {GL_ALPHA, GL_ALPHA, GL_UNSIGNED_BYTE},
|
|
[SUBBITMAP_RGBA] = {GL_RGBA, GL_BGRA, GL_UNSIGNED_BYTE},
|
|
};
|
|
|
|
struct mpgl_osd *mpgl_osd_init(GL *gl, struct mp_log *log, bool legacy)
|
|
{
|
|
GLint max_texture_size;
|
|
gl->GetIntegerv(GL_MAX_TEXTURE_SIZE, &max_texture_size);
|
|
|
|
struct mpgl_osd *ctx = talloc_ptrtype(NULL, ctx);
|
|
*ctx = (struct mpgl_osd) {
|
|
.log = log,
|
|
.gl = gl,
|
|
.fmt_table = legacy ? osd_to_gl_legacy_formats : osd_to_gl3_formats,
|
|
.scratch = talloc_zero_size(ctx, 1),
|
|
};
|
|
|
|
for (int n = 0; n < MAX_OSD_PARTS; n++) {
|
|
struct mpgl_osd_part *p = talloc_ptrtype(ctx, p);
|
|
*p = (struct mpgl_osd_part) {
|
|
.packer = talloc_struct(p, struct bitmap_packer, {
|
|
.w_max = max_texture_size,
|
|
.h_max = max_texture_size,
|
|
}),
|
|
};
|
|
ctx->parts[n] = p;
|
|
}
|
|
|
|
for (int n = 0; n < SUBBITMAP_COUNT; n++)
|
|
ctx->formats[n] = ctx->fmt_table[n].type != 0;
|
|
|
|
return ctx;
|
|
}
|
|
|
|
void mpgl_osd_destroy(struct mpgl_osd *ctx)
|
|
{
|
|
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);
|
|
if (gl->DeleteBuffers)
|
|
gl->DeleteBuffers(1, &p->buffer);
|
|
}
|
|
talloc_free(ctx);
|
|
}
|
|
|
|
static bool upload_pbo(struct mpgl_osd *ctx, struct mpgl_osd_part *osd,
|
|
struct sub_bitmaps *imgs)
|
|
{
|
|
GL *gl = ctx->gl;
|
|
bool success = true;
|
|
struct osd_fmt_entry fmt = ctx->fmt_table[imgs->format];
|
|
int pix_stride = glFmt2bpp(fmt.format, fmt.type);
|
|
|
|
if (!osd->buffer) {
|
|
gl->GenBuffers(1, &osd->buffer);
|
|
gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, osd->buffer);
|
|
gl->BufferData(GL_PIXEL_UNPACK_BUFFER, osd->w * osd->h * pix_stride,
|
|
NULL, GL_DYNAMIC_COPY);
|
|
gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
|
}
|
|
|
|
gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, osd->buffer);
|
|
char *data = gl->MapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY);
|
|
if (!data) {
|
|
success = false;
|
|
} else {
|
|
struct pos bb[2];
|
|
packer_get_bb(osd->packer, bb);
|
|
size_t stride = osd->w * pix_stride;
|
|
packer_copy_subbitmaps(osd->packer, imgs, data, pix_stride, stride);
|
|
if (!gl->UnmapBuffer(GL_PIXEL_UNPACK_BUFFER))
|
|
success = false;
|
|
glUploadTex(gl, GL_TEXTURE_2D, fmt.format, fmt.type, NULL, stride,
|
|
bb[0].x, bb[0].y, bb[1].x - bb[0].x, bb[1].y - bb[0].y,
|
|
0);
|
|
}
|
|
gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
|
|
|
if (!success) {
|
|
MP_FATAL(ctx, "Error: can't upload subtitles! "
|
|
"Remove the 'pbo' suboption.\n");
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
static void upload_tex(struct mpgl_osd *ctx, struct mpgl_osd_part *osd,
|
|
struct sub_bitmaps *imgs)
|
|
{
|
|
struct osd_fmt_entry fmt = ctx->fmt_table[imgs->format];
|
|
if (osd->packer->padding) {
|
|
struct pos bb[2];
|
|
packer_get_bb(osd->packer, bb);
|
|
glClearTex(ctx->gl, GL_TEXTURE_2D, fmt.format, fmt.type,
|
|
bb[0].x, bb[0].y, bb[1].x - bb[0].y, bb[1].y - bb[0].y,
|
|
0, &ctx->scratch);
|
|
}
|
|
for (int n = 0; n < osd->packer->count; n++) {
|
|
struct sub_bitmap *s = &imgs->parts[n];
|
|
struct pos p = osd->packer->result[n];
|
|
|
|
glUploadTex(ctx->gl, GL_TEXTURE_2D, fmt.format, fmt.type,
|
|
s->bitmap, s->stride, p.x, p.y, s->w, s->h, 0);
|
|
}
|
|
}
|
|
|
|
static bool upload_osd(struct mpgl_osd *ctx, struct mpgl_osd_part *osd,
|
|
struct sub_bitmaps *imgs)
|
|
{
|
|
GL *gl = ctx->gl;
|
|
|
|
// assume 2x2 filter on scaling
|
|
osd->packer->padding = ctx->scaled || imgs->scaled;
|
|
int r = packer_pack_from_subbitmaps(osd->packer, imgs);
|
|
if (r < 0) {
|
|
MP_ERR(ctx, "OSD bitmaps do not fit on a surface with the maximum "
|
|
"supported size %dx%d.\n", osd->packer->w_max, osd->packer->h_max);
|
|
return false;
|
|
}
|
|
|
|
struct osd_fmt_entry fmt = ctx->fmt_table[imgs->format];
|
|
assert(fmt.type != 0);
|
|
|
|
if (!osd->texture)
|
|
gl->GenTextures(1, &osd->texture);
|
|
|
|
gl->BindTexture(GL_TEXTURE_2D, osd->texture);
|
|
|
|
if (osd->packer->w > osd->w || osd->packer->h > osd->h
|
|
|| osd->format != imgs->format)
|
|
{
|
|
osd->format = imgs->format;
|
|
osd->w = FFMAX(32, osd->packer->w);
|
|
osd->h = FFMAX(32, osd->packer->h);
|
|
|
|
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);
|
|
|
|
if (gl->DeleteBuffers)
|
|
gl->DeleteBuffers(1, &osd->buffer);
|
|
osd->buffer = 0;
|
|
}
|
|
|
|
bool uploaded = false;
|
|
if (ctx->use_pbo)
|
|
uploaded = upload_pbo(ctx, osd, imgs);
|
|
if (!uploaded)
|
|
upload_tex(ctx, osd, imgs);
|
|
|
|
gl->BindTexture(GL_TEXTURE_2D, 0);
|
|
|
|
return true;
|
|
}
|
|
|
|
static struct mpgl_osd_part *osd_generate(struct mpgl_osd *ctx,
|
|
struct sub_bitmaps *imgs)
|
|
{
|
|
if (imgs->num_parts == 0 || !ctx->formats[imgs->format])
|
|
return NULL;
|
|
|
|
struct mpgl_osd_part *osd = ctx->parts[imgs->render_index];
|
|
|
|
if (imgs->bitmap_pos_id != osd->bitmap_pos_id) {
|
|
if (imgs->bitmap_id != osd->bitmap_id) {
|
|
if (!upload_osd(ctx, osd, imgs))
|
|
osd->packer->count = 0;
|
|
}
|
|
|
|
osd->bitmap_id = imgs->bitmap_id;
|
|
osd->bitmap_pos_id = imgs->bitmap_pos_id;
|
|
osd->num_vertices = 0;
|
|
}
|
|
|
|
return osd->packer->count ? osd : NULL;
|
|
}
|
|
|
|
void mpgl_osd_set_gl_state(struct mpgl_osd *ctx, struct mpgl_osd_part *p)
|
|
{
|
|
GL *gl = ctx->gl;
|
|
|
|
gl->BindTexture(GL_TEXTURE_2D, p->texture);
|
|
gl->Enable(GL_BLEND);
|
|
|
|
const int *factors = &blend_factors[p->format][0];
|
|
if (gl->BlendFuncSeparate) {
|
|
gl->BlendFuncSeparate(factors[0], factors[1], factors[2], factors[3]);
|
|
} else {
|
|
gl->BlendFunc(factors[0], factors[1]);
|
|
}
|
|
}
|
|
|
|
void mpgl_osd_unset_gl_state(struct mpgl_osd *ctx, struct mpgl_osd_part *p)
|
|
{
|
|
GL *gl = ctx->gl;
|
|
|
|
gl->Disable(GL_BLEND);
|
|
gl->BindTexture(GL_TEXTURE_2D, 0);
|
|
}
|
|
|
|
static void reset(struct mpgl_osd *ctx)
|
|
{
|
|
for (int n = 0; n < MAX_OSD_PARTS; n++) {
|
|
struct mpgl_osd_part *p = ctx->parts[n];
|
|
p->active = false;
|
|
}
|
|
}
|
|
|
|
struct draw_cb_closure {
|
|
struct mpgl_osd *ctx;
|
|
void (*cb)(void *ctx, struct mpgl_osd_part *part, struct sub_bitmaps *imgs);
|
|
void *cb_ctx;
|
|
};
|
|
|
|
static void draw_cb(void *pctx, struct sub_bitmaps *imgs)
|
|
{
|
|
struct draw_cb_closure *c = pctx;
|
|
struct mpgl_osd_part *part = osd_generate(c->ctx, imgs);
|
|
if (!part)
|
|
return;
|
|
part->active = true;
|
|
c->cb(c->cb_ctx, part, imgs);
|
|
}
|
|
|
|
void mpgl_osd_draw_cb(struct mpgl_osd *ctx,
|
|
struct osd_state *osd,
|
|
struct mp_osd_res res,
|
|
void (*cb)(void *ctx, struct mpgl_osd_part *part,
|
|
struct sub_bitmaps *imgs),
|
|
void *cb_ctx)
|
|
{
|
|
struct draw_cb_closure c = {ctx, cb, cb_ctx};
|
|
reset(ctx);
|
|
osd_draw(osd, res, osd->vo_pts, 0, ctx->formats, draw_cb, &c);
|
|
}
|
|
|
|
void mpgl_osd_redraw_cb(struct mpgl_osd *ctx,
|
|
void (*cb)(void *ctx, struct mpgl_osd_part *part,
|
|
struct sub_bitmaps *imgs),
|
|
void *cb_ctx)
|
|
{
|
|
for (int n = 0; n < MAX_OSD_PARTS; n++) {
|
|
struct mpgl_osd_part *p = ctx->parts[n];
|
|
if (p->active)
|
|
cb(cb_ctx, p, NULL);
|
|
}
|
|
}
|
|
|
|
struct vertex {
|
|
float position[2];
|
|
uint8_t color[4];
|
|
float texcoord[2];
|
|
};
|
|
|
|
static void draw_legacy_cb(void *pctx, struct sub_bitmaps *imgs)
|
|
{
|
|
struct mpgl_osd *ctx = pctx;
|
|
struct mpgl_osd_part *osd = osd_generate(ctx, imgs);
|
|
if (!osd)
|
|
return;
|
|
|
|
if (!osd->num_vertices) {
|
|
// 2 triangles primitives per quad = 6 vertices per quad
|
|
// not using GL_QUADS, as it is deprecated in OpenGL 3.x and later
|
|
osd->vertices = talloc_realloc(osd, osd->vertices, struct vertex,
|
|
osd->packer->count * 6);
|
|
|
|
struct vertex *va = osd->vertices;
|
|
float tex_w = osd->w;
|
|
float tex_h = osd->h;
|
|
|
|
for (int n = 0; n < osd->packer->count; n++) {
|
|
struct sub_bitmap *b = &imgs->parts[n];
|
|
struct pos p = osd->packer->result[n];
|
|
|
|
uint32_t c = imgs->format == SUBBITMAP_LIBASS
|
|
? b->libass.color : 0xFFFFFF00;
|
|
uint8_t color[4] = { c >> 24, (c >> 16) & 0xff,
|
|
(c >> 8) & 0xff, 255 - (c & 0xff) };
|
|
|
|
float x0 = b->x;
|
|
float y0 = b->y;
|
|
float x1 = b->x + b->dw;
|
|
float y1 = b->y + b->dh;
|
|
float tx0 = p.x / tex_w;
|
|
float ty0 = p.y / tex_h;
|
|
float tx1 = (p.x + b->w) / tex_w;
|
|
float ty1 = (p.y + b->h) / tex_h;
|
|
|
|
#define COLOR_INIT {color[0], color[1], color[2], color[3]}
|
|
struct vertex *v = &va[osd->num_vertices];
|
|
v[0] = (struct vertex) { {x0, y0}, COLOR_INIT, {tx0, ty0} };
|
|
v[1] = (struct vertex) { {x0, y1}, COLOR_INIT, {tx0, ty1} };
|
|
v[2] = (struct vertex) { {x1, y0}, COLOR_INIT, {tx1, ty0} };
|
|
v[3] = (struct vertex) { {x1, y1}, COLOR_INIT, {tx1, ty1} };
|
|
v[4] = v[2];
|
|
v[5] = v[1];
|
|
#undef COLOR_INIT
|
|
osd->num_vertices += 6;
|
|
}
|
|
}
|
|
|
|
GL *gl = ctx->gl;
|
|
|
|
struct vertex *va = osd->vertices;
|
|
size_t stride = sizeof(va[0]);
|
|
|
|
gl->VertexPointer(2, GL_FLOAT, stride, &va[0].position[0]);
|
|
gl->ColorPointer(4, GL_UNSIGNED_BYTE, stride, &va[0].color[0]);
|
|
gl->TexCoordPointer(2, GL_FLOAT, stride, &va[0].texcoord[0]);
|
|
|
|
gl->EnableClientState(GL_VERTEX_ARRAY);
|
|
gl->EnableClientState(GL_TEXTURE_COORD_ARRAY);
|
|
gl->EnableClientState(GL_COLOR_ARRAY);
|
|
|
|
mpgl_osd_set_gl_state(ctx, osd);
|
|
gl->DrawArrays(GL_TRIANGLES, 0, osd->num_vertices);
|
|
mpgl_osd_unset_gl_state(ctx, osd);
|
|
|
|
gl->DisableClientState(GL_VERTEX_ARRAY);
|
|
gl->DisableClientState(GL_TEXTURE_COORD_ARRAY);
|
|
gl->DisableClientState(GL_COLOR_ARRAY);
|
|
}
|
|
|
|
void mpgl_osd_draw_legacy(struct mpgl_osd *ctx, struct osd_state *osd,
|
|
struct mp_osd_res res)
|
|
{
|
|
osd_draw(osd, res, osd->vo_pts, 0, ctx->formats, draw_legacy_cb, ctx);
|
|
}
|