2015-01-28 19:40:46 +01:00
|
|
|
/*
|
|
|
|
* 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>
|
2015-01-28 22:22:29 +01:00
|
|
|
#include <assert.h>
|
2015-01-28 19:40:46 +01:00
|
|
|
|
|
|
|
#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;
|
|
|
|
}
|
|
|
|
}
|
2015-01-28 22:22:29 +01:00
|
|
|
|
|
|
|
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*)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);
|
|
|
|
}
|