mpv/video/out/gl_utils.c

425 lines
12 KiB
C
Raw Normal View History

/*
* This file is part of mpv.
* Parts based on MPlayer code by Reimar Döffinger.
*
* mpv 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.
*
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with mpv. If not, see <http://www.gnu.org/licenses/>.
*
* You can alternatively redistribute this file 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.
*/
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "common/common.h"
#include "gl_utils.h"
// GLU has this as gluErrorString (we don't use GLU, as it is legacy-OpenGL)
static const char *gl_error_to_string(GLenum error)
{
switch (error) {
case GL_INVALID_ENUM: return "INVALID_ENUM";
case GL_INVALID_VALUE: return "INVALID_VALUE";
case GL_INVALID_OPERATION: return "INVALID_OPERATION";
case GL_INVALID_FRAMEBUFFER_OPERATION: return "INVALID_FRAMEBUFFER_OPERATION";
case GL_OUT_OF_MEMORY: return "OUT_OF_MEMORY";
default: return "unknown";
}
}
void glCheckError(GL *gl, struct mp_log *log, const char *info)
{
for (;;) {
GLenum error = gl->GetError();
if (error == GL_NO_ERROR)
break;
mp_msg(log, MSGL_ERR, "%s: OpenGL error %s.\n", info,
gl_error_to_string(error));
}
}
// return the number of bytes per pixel for the given format
// does not handle all possible variants, just those used by mpv
int glFmt2bpp(GLenum format, GLenum type)
{
int component_size = 0;
switch (type) {
case GL_UNSIGNED_BYTE_3_3_2:
case GL_UNSIGNED_BYTE_2_3_3_REV:
return 1;
case GL_UNSIGNED_SHORT_5_5_5_1:
case GL_UNSIGNED_SHORT_1_5_5_5_REV:
case GL_UNSIGNED_SHORT_5_6_5:
case GL_UNSIGNED_SHORT_5_6_5_REV:
return 2;
case GL_UNSIGNED_BYTE:
component_size = 1;
break;
case GL_UNSIGNED_SHORT:
component_size = 2;
break;
}
switch (format) {
case GL_LUMINANCE:
case GL_ALPHA:
return component_size;
case GL_RGB_422_APPLE:
return 2;
case GL_RGB:
case GL_BGR:
case GL_RGB_INTEGER:
return 3 * component_size;
case GL_RGBA:
case GL_BGRA:
case GL_RGBA_INTEGER:
return 4 * component_size;
case GL_RED:
case GL_RED_INTEGER:
return component_size;
case GL_RG:
case GL_LUMINANCE_ALPHA:
case GL_RG_INTEGER:
return 2 * component_size;
}
abort(); // unknown
}
static int get_alignment(int stride)
{
if (stride % 8 == 0)
return 8;
if (stride % 4 == 0)
return 4;
if (stride % 2 == 0)
return 2;
return 1;
}
// upload a texture, handling things like stride and slices
// target: texture target, usually GL_TEXTURE_2D
// format, type: texture parameters
// dataptr, stride: image data
// x, y, width, height: part of the image to upload
// slice: height of an upload slice, 0 for all at once
void glUploadTex(GL *gl, GLenum target, GLenum format, GLenum type,
const void *dataptr, int stride,
int x, int y, int w, int h, int slice)
{
const uint8_t *data = dataptr;
int y_max = y + h;
if (w <= 0 || h <= 0)
return;
if (slice <= 0)
slice = h;
if (stride < 0) {
data += (h - 1) * stride;
stride = -stride;
}
gl->PixelStorei(GL_UNPACK_ALIGNMENT, get_alignment(stride));
bool use_rowlength = slice > 1 && (gl->mpgl_caps & MPGL_CAP_ROW_LENGTH);
if (use_rowlength) {
// this is not always correct, but should work for MPlayer
gl->PixelStorei(GL_UNPACK_ROW_LENGTH, stride / glFmt2bpp(format, type));
} else {
if (stride != glFmt2bpp(format, type) * w)
slice = 1; // very inefficient, but at least it works
}
for (; y + slice <= y_max; y += slice) {
gl->TexSubImage2D(target, 0, x, y, w, slice, format, type, data);
data += stride * slice;
}
if (y < y_max)
gl->TexSubImage2D(target, 0, x, y, w, y_max - y, format, type, data);
if (use_rowlength)
gl->PixelStorei(GL_UNPACK_ROW_LENGTH, 0);
gl->PixelStorei(GL_UNPACK_ALIGNMENT, 4);
}
// Like glUploadTex, but upload a byte array with all elements set to val.
// If scratch is not NULL, points to a resizeable talloc memory block than can
// be freely used by the function (for avoiding temporary memory allocations).
void glClearTex(GL *gl, GLenum target, GLenum format, GLenum type,
int x, int y, int w, int h, uint8_t val, void **scratch)
{
int bpp = glFmt2bpp(format, type);
int stride = w * bpp;
int size = h * stride;
if (size < 1)
return;
void *data = scratch ? *scratch : NULL;
if (talloc_get_size(data) < size)
data = talloc_realloc(NULL, data, char *, size);
memset(data, val, size);
gl->PixelStorei(GL_UNPACK_ALIGNMENT, get_alignment(stride));
gl->TexSubImage2D(target, 0, x, y, w, h, format, type, data);
gl->PixelStorei(GL_UNPACK_ALIGNMENT, 4);
if (scratch) {
*scratch = data;
} else {
talloc_free(data);
}
}
mp_image_t *glGetWindowScreenshot(GL *gl)
{
if (gl->es)
return NULL; // ES can't read from front buffer
GLint vp[4]; //x, y, w, h
gl->GetIntegerv(GL_VIEWPORT, vp);
mp_image_t *image = mp_image_alloc(IMGFMT_RGB24, vp[2], vp[3]);
if (!image)
return NULL;
gl->PixelStorei(GL_PACK_ALIGNMENT, 1);
gl->ReadBuffer(GL_FRONT);
//flip image while reading (and also avoid stride-related trouble)
for (int y = 0; y < vp[3]; y++) {
gl->ReadPixels(vp[0], vp[1] + vp[3] - y - 1, vp[2], 1,
GL_RGB, GL_UNSIGNED_BYTE,
image->planes[0] + y * image->stride[0]);
}
gl->PixelStorei(GL_PACK_ALIGNMENT, 4);
return image;
}
void mp_log_source(struct mp_log *log, int lev, const char *src)
{
int line = 1;
if (!src)
return;
while (*src) {
const char *end = strchr(src, '\n');
const char *next = end + 1;
if (!end)
next = end = src + strlen(src);
mp_msg(log, lev, "[%3d] %.*s\n", line, (int)(end - src), src);
line++;
src = next;
}
}
static void gl_vao_enable_attribs(struct gl_vao *vao)
{
GL *gl = vao->gl;
for (int n = 0; vao->entries[n].name; n++) {
const struct gl_vao_entry *e = &vao->entries[n];
gl->EnableVertexAttribArray(n);
gl->VertexAttribPointer(n, e->num_elems, e->type, e->normalized,
vao->stride, (void *)(intptr_t)e->offset);
}
}
void gl_vao_init(struct gl_vao *vao, GL *gl, int stride,
const struct gl_vao_entry *entries)
{
assert(!vao->vao);
assert(!vao->buffer);
*vao = (struct gl_vao){
.gl = gl,
.stride = stride,
.entries = entries,
};
gl->GenBuffers(1, &vao->buffer);
if (gl->BindVertexArray) {
gl->BindBuffer(GL_ARRAY_BUFFER, vao->buffer);
gl->GenVertexArrays(1, &vao->vao);
gl->BindVertexArray(vao->vao);
gl_vao_enable_attribs(vao);
gl->BindVertexArray(0);
gl->BindBuffer(GL_ARRAY_BUFFER, 0);
}
}
void gl_vao_uninit(struct gl_vao *vao)
{
GL *gl = vao->gl;
if (!gl)
return;
if (gl->DeleteVertexArrays)
gl->DeleteVertexArrays(1, &vao->vao);
gl->DeleteBuffers(1, &vao->buffer);
*vao = (struct gl_vao){0};
}
void gl_vao_bind(struct gl_vao *vao)
{
GL *gl = vao->gl;
if (gl->BindVertexArray) {
gl->BindVertexArray(vao->vao);
} else {
gl->BindBuffer(GL_ARRAY_BUFFER, vao->buffer);
gl_vao_enable_attribs(vao);
gl->BindBuffer(GL_ARRAY_BUFFER, 0);
}
}
void gl_vao_unbind(struct gl_vao *vao)
{
GL *gl = vao->gl;
if (gl->BindVertexArray) {
gl->BindVertexArray(0);
} else {
for (int n = 0; vao->entries[n].name; n++)
gl->DisableVertexAttribArray(n);
}
}
void gl_vao_bind_attribs(struct gl_vao *vao, GLuint program)
{
GL *gl = vao->gl;
for (int n = 0; vao->entries[n].name; n++)
gl->BindAttribLocation(program, n, vao->entries[n].name);
}
// Draw the vertex data (as described by the gl_vao_entry entries) in ptr
// to the screen. num is the number of vertexes. prim is usually GL_TRIANGLES.
// If ptr is NULL, then skip the upload, and use the data uploaded with the
// previous call.
void gl_vao_draw_data(struct gl_vao *vao, GLenum prim, void *ptr, size_t num)
{
GL *gl = vao->gl;
if (ptr) {
gl->BindBuffer(GL_ARRAY_BUFFER, vao->buffer);
gl->BufferData(GL_ARRAY_BUFFER, num * vao->stride, ptr, GL_DYNAMIC_DRAW);
gl->BindBuffer(GL_ARRAY_BUFFER, 0);
}
gl_vao_bind(vao);
gl->DrawArrays(prim, 0, num);
gl_vao_unbind(vao);
}
// Create a texture and a FBO using the texture as color attachments.
// gl_target: GL_TEXTURE_2D
// gl_filter: GL_LINEAR
// iformat: texture internal format
// Returns success.
bool fbotex_init(struct fbotex *fbo, GL *gl, struct mp_log *log, int w, int h,
GLenum gl_target, GLenum gl_filter, GLenum iformat)
{
bool res = true;
assert(!fbo->fbo);
assert(!fbo->texture);
*fbo = (struct fbotex) {
.gl = gl,
.vp_w = w,
.vp_h = h,
.tex_w = w,
.tex_h = h,
};
mp_verbose(log, "Create FBO: %dx%d\n", fbo->tex_w, fbo->tex_h);
if (!(gl->mpgl_caps & MPGL_CAP_FB))
return false;
gl->GenFramebuffers(1, &fbo->fbo);
gl->GenTextures(1, &fbo->texture);
gl->BindTexture(gl_target, fbo->texture);
gl->TexImage2D(gl_target, 0, iformat, fbo->tex_w, fbo->tex_h, 0,
GL_RGBA, GL_UNSIGNED_BYTE, NULL);
gl->TexParameteri(gl_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
gl->TexParameteri(gl_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
gl->TexParameteri(gl_target, GL_TEXTURE_MIN_FILTER, gl_filter);
gl->TexParameteri(gl_target, GL_TEXTURE_MAG_FILTER, gl_filter);
glCheckError(gl, log, "after creating framebuffer texture");
gl->BindFramebuffer(GL_FRAMEBUFFER, fbo->fbo);
gl->FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
gl_target, fbo->texture, 0);
GLenum err = gl->CheckFramebufferStatus(GL_FRAMEBUFFER);
if (err != GL_FRAMEBUFFER_COMPLETE) {
mp_err(log, "Error: framebuffer completeness check failed (error=%d).\n",
(int)err);
res = false;
}
gl->BindFramebuffer(GL_FRAMEBUFFER, 0);
glCheckError(gl, log, "after creating framebuffer");
return res;
}
void fbotex_uninit(struct fbotex *fbo)
{
GL *gl = fbo->gl;
if (gl && (gl->mpgl_caps & MPGL_CAP_FB)) {
gl->DeleteFramebuffers(1, &fbo->fbo);
gl->DeleteTextures(1, &fbo->texture);
*fbo = (struct fbotex) {0};
}
}
void gl_matrix_ortho2d(float m[3][3], float x0, float x1, float y0, float y1)
{
memset(m, 0, 9 * sizeof(float));
m[0][0] = 2.0f / (x1 - x0);
m[1][1] = 2.0f / (y1 - y0);
m[2][0] = -(x1 + x0) / (x1 - x0);
m[2][1] = -(y1 + y0) / (y1 - y0);
m[2][2] = 1.0f;
}
static void GLAPIENTRY gl_debug_cb(GLenum source, GLenum type, GLuint id,
GLenum severity, GLsizei length,
const GLchar *message, const void *userParam)
{
// keep in mind that the debug callback can be asynchronous
struct mp_log *log = (void *)userParam;
int level = MSGL_ERR;
switch (severity) {
case GL_DEBUG_SEVERITY_NOTIFICATION:level = MSGL_V; break;
case GL_DEBUG_SEVERITY_LOW: level = MSGL_INFO; break;
case GL_DEBUG_SEVERITY_MEDIUM: level = MSGL_WARN; break;
case GL_DEBUG_SEVERITY_HIGH: level = MSGL_ERR; break;
}
mp_msg(log, level, "GL: %s\n", message);
}
void gl_set_debug_logger(GL *gl, struct mp_log *log)
{
if (gl->DebugMessageCallback) {
if (log) {
gl->DebugMessageCallback(gl_debug_cb, log);
} else {
gl->DebugMessageCallback(NULL, NULL);
}
}
}