From deebc55014074fef121c1df6b117e9c0bf97d516 Mon Sep 17 00:00:00 2001 From: Niklas Haas Date: Sat, 29 Aug 2015 01:10:30 +0200 Subject: vo_opengl: move gl_* files to their own subdir This is mainly just to keep things a bit more organized and separated inside the codebase. --- video/out/opengl/gl_utils.c | 951 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 951 insertions(+) create mode 100644 video/out/opengl/gl_utils.c (limited to 'video/out/opengl/gl_utils.c') diff --git a/video/out/opengl/gl_utils.c b/video/out/opengl/gl_utils.c new file mode 100644 index 0000000000..b88e04c6c0 --- /dev/null +++ b/video/out/opengl/gl_utils.c @@ -0,0 +1,951 @@ +/* + * 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 . + * + * 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 +#include +#include +#include +#include +#include + +#include "stream/stream.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); + } +} + +// 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. +// iformat: texture internal format +// Returns success. +bool fbotex_init(struct fbotex *fbo, GL *gl, struct mp_log *log, int w, int h, + GLenum iformat) +{ + assert(!fbo->fbo); + assert(!fbo->texture); + return fbotex_change(fbo, gl, log, w, h, iformat, 0); +} + +// Like fbotex_init(), except it can be called on an already initialized FBO; +// and if the parameters are the same as the previous call, do not touch it. +// flags can be 0, or a combination of FBOTEX_FUZZY_W and FBOTEX_FUZZY_H. +// Enabling FUZZY for W or H means the w or h does not need to be exact. +bool fbotex_change(struct fbotex *fbo, GL *gl, struct mp_log *log, int w, int h, + GLenum iformat, int flags) +{ + bool res = true; + + int cw = w, ch = h; + + if ((flags & FBOTEX_FUZZY_W) && cw < fbo->w) + cw = fbo->w; + if ((flags & FBOTEX_FUZZY_H) && ch < fbo->h) + ch = fbo->h; + + if (fbo->w == cw && fbo->h == ch && fbo->iformat == iformat) + return true; + + if (flags & FBOTEX_FUZZY_W) + w = MP_ALIGN_UP(w, 256); + if (flags & FBOTEX_FUZZY_H) + h = MP_ALIGN_UP(h, 256); + + GLenum filter = fbo->tex_filter; + + *fbo = (struct fbotex) { + .gl = gl, + .w = w, + .h = h, + .iformat = iformat, + }; + + mp_verbose(log, "Create FBO: %dx%d\n", fbo->w, fbo->h); + + if (!(gl->mpgl_caps & MPGL_CAP_FB)) + return false; + + gl->GenFramebuffers(1, &fbo->fbo); + gl->GenTextures(1, &fbo->texture); + gl->BindTexture(GL_TEXTURE_2D, fbo->texture); + gl->TexImage2D(GL_TEXTURE_2D, 0, iformat, fbo->w, fbo->h, 0, + GL_RGBA, GL_UNSIGNED_BYTE, NULL); + 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->BindTexture(GL_TEXTURE_2D, 0); + + fbotex_set_filter(fbo, filter ? filter : GL_LINEAR); + + glCheckError(gl, log, "after creating framebuffer texture"); + + gl->BindFramebuffer(GL_FRAMEBUFFER, fbo->fbo); + gl->FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D, 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_set_filter(struct fbotex *fbo, GLenum tex_filter) +{ + GL *gl = fbo->gl; + + if (fbo->tex_filter != tex_filter && fbo->texture) { + gl->BindTexture(GL_TEXTURE_2D, fbo->texture); + gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, tex_filter); + gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, tex_filter); + gl->BindTexture(GL_TEXTURE_2D, 0); + } + fbo->tex_filter = tex_filter; +} + +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}; + } +} + +// Standard parallel 2D projection, except y1 < y0 means that the coordinate +// system is flipped, not the projection. +void gl_transform_ortho(struct gl_transform *t, float x0, float x1, + float y0, float y1) +{ + if (y1 < y0) { + float tmp = y0; + y0 = tmp - y1; + y1 = tmp; + } + + t->m[0][0] = 2.0f / (x1 - x0); + t->m[0][1] = 0.0f; + t->m[1][0] = 0.0f; + t->m[1][1] = 2.0f / (y1 - y0); + t->t[0] = -(x1 + x0) / (x1 - x0); + t->t[1] = -(y1 + y0) / (y1 - y0); +} + +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); + } + } +} + +#define SC_ENTRIES 16 +#define SC_UNIFORM_ENTRIES 20 +#define SC_FILE_ENTRIES 10 + +enum uniform_type { + UT_invalid, + UT_i, + UT_f, + UT_m, +}; + +struct sc_uniform { + char *name; + enum uniform_type type; + const char *glsl_type; + int size; + GLint loc; + union { + GLfloat f[9]; + GLint i[4]; + } v; +}; + +struct sc_file { + char *path; + char *body; +}; + +struct sc_entry { + GLuint gl_shader; + // the following fields define the shader's contents + char *key; // vertex+frag shader (mangled) + struct gl_vao *vao; +}; + +struct gl_shader_cache { + GL *gl; + struct mp_log *log; + struct mpv_global *global; + + // this is modified during use (gl_sc_add() etc.) + char *text; + char *header_text; + struct gl_vao *vao; + + struct sc_entry entries[SC_ENTRIES]; + int num_entries; + + struct sc_uniform uniforms[SC_UNIFORM_ENTRIES]; + int num_uniforms; + + struct sc_file files[SC_FILE_ENTRIES]; + int num_files; +}; + +struct gl_shader_cache *gl_sc_create(GL *gl, struct mp_log *log, + struct mpv_global *global) +{ + struct gl_shader_cache *sc = talloc_ptrtype(NULL, sc); + *sc = (struct gl_shader_cache){ + .gl = gl, + .log = log, + .global = global, + .text = talloc_strdup(sc, ""), + .header_text = talloc_strdup(sc, ""), + }; + return sc; +} + +void gl_sc_reset(struct gl_shader_cache *sc) +{ + sc->text[0] = '\0'; + sc->header_text[0] = '\0'; + for (int n = 0; n < sc->num_uniforms; n++) + talloc_free(sc->uniforms[n].name); + sc->num_uniforms = 0; +} + +static void sc_flush_cache(struct gl_shader_cache *sc) +{ + for (int n = 0; n < sc->num_entries; n++) { + struct sc_entry *e = &sc->entries[n]; + sc->gl->DeleteProgram(e->gl_shader); + talloc_free(e->key); + } + sc->num_entries = 0; +} + +void gl_sc_destroy(struct gl_shader_cache *sc) +{ + if (!sc) + return; + gl_sc_reset(sc); + sc_flush_cache(sc); + talloc_free(sc); +} + +void gl_sc_add(struct gl_shader_cache *sc, const char *text) +{ + sc->text = talloc_strdup_append(sc->text, text); +} + +void gl_sc_addf(struct gl_shader_cache *sc, const char *textf, ...) +{ + va_list ap; + va_start(ap, textf); + ta_xvasprintf_append(&sc->text, textf, ap); + va_end(ap); +} + +void gl_sc_hadd(struct gl_shader_cache *sc, const char *text) +{ + sc->header_text = talloc_strdup_append(sc->header_text, text); +} + +const char *gl_sc_loadfile(struct gl_shader_cache *sc, const char *path) +{ + if (!path || !path[0] || !sc->global) + return NULL; + for (int n = 0; n < sc->num_files; n++) { + if (strcmp(sc->files[n].path, path) == 0) + return sc->files[n].body; + } + // not found -> load it + if (sc->num_files == SC_FILE_ENTRIES) { + // empty cache when it overflows + for (int n = 0; n < sc->num_files; n++) { + talloc_free(sc->files[n].path); + talloc_free(sc->files[n].body); + } + sc->num_files = 0; + } + struct bstr s = stream_read_file(path, sc, sc->global, 100000); // 100 kB + if (s.len) { + struct sc_file *new = &sc->files[sc->num_files++]; + *new = (struct sc_file) { + .path = talloc_strdup(sc, path), + .body = s.start + }; + return new->body; + } + return NULL; +} + +static struct sc_uniform *find_uniform(struct gl_shader_cache *sc, + const char *name) +{ + for (int n = 0; n < sc->num_uniforms; n++) { + if (strcmp(sc->uniforms[n].name, name) == 0) + return &sc->uniforms[n]; + } + // not found -> add it + assert(sc->num_uniforms < SC_UNIFORM_ENTRIES); // just don't have too many + struct sc_uniform *new = &sc->uniforms[sc->num_uniforms++]; + *new = (struct sc_uniform) { .loc = -1, .name = talloc_strdup(NULL, name) }; + return new; +} + +void gl_sc_uniform_sampler(struct gl_shader_cache *sc, char *name, GLenum target, + int unit) +{ + struct sc_uniform *u = find_uniform(sc, name); + u->type = UT_i; + u->size = 1; + switch (target) { + case GL_TEXTURE_1D: u->glsl_type = "sampler1D"; break; + case GL_TEXTURE_2D: u->glsl_type = "sampler2D"; break; + case GL_TEXTURE_RECTANGLE: u->glsl_type = "sampler2DRect"; break; + case GL_TEXTURE_3D: u->glsl_type = "sampler3D"; break; + default: abort(); + } + u->v.i[0] = unit; +} + +void gl_sc_uniform_f(struct gl_shader_cache *sc, char *name, GLfloat f) +{ + struct sc_uniform *u = find_uniform(sc, name); + u->type = UT_f; + u->size = 1; + u->glsl_type = "float"; + u->v.f[0] = f; +} + +void gl_sc_uniform_i(struct gl_shader_cache *sc, char *name, GLint i) +{ + struct sc_uniform *u = find_uniform(sc, name); + u->type = UT_i; + u->size = 1; + u->glsl_type = "int"; + u->v.i[0] = i; +} + +void gl_sc_uniform_vec2(struct gl_shader_cache *sc, char *name, GLfloat f[2]) +{ + struct sc_uniform *u = find_uniform(sc, name); + u->type = UT_f; + u->size = 2; + u->glsl_type = "vec2"; + u->v.f[0] = f[0]; + u->v.f[1] = f[1]; +} + +void gl_sc_uniform_vec3(struct gl_shader_cache *sc, char *name, GLfloat f[3]) +{ + struct sc_uniform *u = find_uniform(sc, name); + u->type = UT_f; + u->size = 3; + u->glsl_type = "vec3"; + u->v.f[0] = f[0]; + u->v.f[1] = f[1]; + u->v.f[2] = f[2]; +} + +static void transpose2x2(float r[2 * 2]) +{ + MPSWAP(float, r[0+2*1], r[1+2*0]); +} + +void gl_sc_uniform_mat2(struct gl_shader_cache *sc, char *name, + bool transpose, GLfloat *v) +{ + struct sc_uniform *u = find_uniform(sc, name); + u->type = UT_m; + u->size = 2; + u->glsl_type = "mat2"; + for (int n = 0; n < 4; n++) + u->v.f[n] = v[n]; + if (transpose) + transpose2x2(&u->v.f[0]); +} + +static void transpose3x3(float r[3 * 3]) +{ + MPSWAP(float, r[0+3*1], r[1+3*0]); + MPSWAP(float, r[0+3*2], r[2+3*0]); + MPSWAP(float, r[1+3*2], r[2+3*1]); +} + +void gl_sc_uniform_mat3(struct gl_shader_cache *sc, char *name, + bool transpose, GLfloat *v) +{ + struct sc_uniform *u = find_uniform(sc, name); + u->type = UT_m; + u->size = 3; + u->glsl_type = "mat3"; + for (int n = 0; n < 9; n++) + u->v.f[n] = v[n]; + if (transpose) + transpose3x3(&u->v.f[0]); +} + +// This will call glBindAttribLocation() on the shader before it's linked +// (OpenGL requires this to happen before linking). Basically, it associates +// the input variable names with the fields in the vao. +// The vertex shader is setup such that the elements are available as fragment +// shader variables using the names in the vao entries, which "position" being +// set to gl_Position. +void gl_sc_set_vao(struct gl_shader_cache *sc, struct gl_vao *vao) +{ + sc->vao = vao; +} + +static const char *vao_glsl_type(const struct gl_vao_entry *e) +{ + // pretty dumb... too dumb, but works for us + switch (e->num_elems) { + case 1: return "float"; + case 2: return "vec2"; + case 3: return "vec3"; + case 4: return "vec4"; + default: abort(); + } +} + +// Assumes program is current (gl->UseProgram(program)). +static void update_uniform(GL *gl, GLuint program, struct sc_uniform *u) +{ + GLint loc = gl->GetUniformLocation(program, u->name); + if (loc < 0) + return; + switch (u->type) { + case UT_i: + assert(u->size == 1); + gl->Uniform1i(loc, u->v.i[0]); + break; + case UT_f: + switch (u->size) { + case 1: gl->Uniform1f(loc, u->v.f[0]); break; + case 2: gl->Uniform2f(loc, u->v.f[0], u->v.f[1]); break; + case 3: gl->Uniform3f(loc, u->v.f[0], u->v.f[1], u->v.f[2]); break; + case 4: gl->Uniform4f(loc, u->v.f[0], u->v.f[1], u->v.f[2], u->v.f[3]); break; + default: abort(); + } + break; + case UT_m: + switch (u->size) { + case 2: gl->UniformMatrix2fv(loc, 1, GL_FALSE, &u->v.f[0]); break; + case 3: gl->UniformMatrix3fv(loc, 1, GL_FALSE, &u->v.f[0]); break; + default: abort(); + } + break; + default: + abort(); + } +} + +static void compile_attach_shader(struct gl_shader_cache *sc, GLuint program, + GLenum type, const char *source) +{ + GL *gl = sc->gl; + + GLuint shader = gl->CreateShader(type); + gl->ShaderSource(shader, 1, &source, NULL); + gl->CompileShader(shader); + GLint status; + gl->GetShaderiv(shader, GL_COMPILE_STATUS, &status); + GLint log_length; + gl->GetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_length); + + int pri = status ? (log_length > 1 ? MSGL_V : MSGL_DEBUG) : MSGL_ERR; + const char *typestr = type == GL_VERTEX_SHADER ? "vertex" : "fragment"; + if (mp_msg_test(sc->log, pri)) { + MP_MSG(sc, pri, "%s shader source:\n", typestr); + mp_log_source(sc->log, pri, source); + } + if (log_length > 1) { + GLchar *logstr = talloc_zero_size(NULL, log_length + 1); + gl->GetShaderInfoLog(shader, log_length, NULL, logstr); + MP_MSG(sc, pri, "%s shader compile log (status=%d):\n%s\n", + typestr, status, logstr); + talloc_free(logstr); + } + + gl->AttachShader(program, shader); + gl->DeleteShader(shader); +} + +static void link_shader(struct gl_shader_cache *sc, GLuint program) +{ + GL *gl = sc->gl; + gl->LinkProgram(program); + GLint status; + gl->GetProgramiv(program, GL_LINK_STATUS, &status); + GLint log_length; + gl->GetProgramiv(program, GL_INFO_LOG_LENGTH, &log_length); + + int pri = status ? (log_length > 1 ? MSGL_V : MSGL_DEBUG) : MSGL_ERR; + if (mp_msg_test(sc->log, pri)) { + GLchar *logstr = talloc_zero_size(NULL, log_length + 1); + gl->GetProgramInfoLog(program, log_length, NULL, logstr); + MP_MSG(sc, pri, "shader link log (status=%d): %s\n", status, logstr); + talloc_free(logstr); + } +} + +static GLuint create_program(struct gl_shader_cache *sc, const char *vertex, + const char *frag) +{ + GL *gl = sc->gl; + MP_VERBOSE(sc, "recompiling a shader program:\n"); + if (sc->header_text[0]) { + MP_VERBOSE(sc, "header:\n"); + mp_log_source(sc->log, MSGL_V, sc->header_text); + MP_VERBOSE(sc, "body:\n"); + } + mp_log_source(sc->log, MSGL_V, sc->text); + GLuint prog = gl->CreateProgram(); + compile_attach_shader(sc, prog, GL_VERTEX_SHADER, vertex); + compile_attach_shader(sc, prog, GL_FRAGMENT_SHADER, frag); + for (int n = 0; sc->vao->entries[n].name; n++) { + char vname[80]; + snprintf(vname, sizeof(vname), "vertex_%s", sc->vao->entries[n].name); + gl->BindAttribLocation(prog, n, vname); + } + link_shader(sc, prog); + return prog; +} + +#define ADD(x, ...) (x) = talloc_asprintf_append(x, __VA_ARGS__) + +// 1. Generate vertex and fragment shaders from the fragment shader text added +// with gl_sc_add(). The generated shader program is cached (based on the +// text), so actual compilation happens only the first time. +// 2. Update the uniforms set with gl_sc_uniform_*. +// 3. Make the new shader program current (glUseProgram()). +// 4. Reset the sc state and prepare for a new shader program. (All uniforms +// and fragment operations needed for the next program have to be re-added.) +void gl_sc_gen_shader_and_reset(struct gl_shader_cache *sc) +{ + GL *gl = sc->gl; + void *tmp = talloc_new(NULL); + + assert(sc->vao); + + // set up shader text (header + uniforms + body) + char *header = talloc_asprintf(tmp, "#version %d%s\n", gl->glsl_version, + gl->es >= 300 ? " es" : ""); + if (gl->es) + ADD(header, "precision mediump float;\n"); + char *vert_in = gl->glsl_version >= 130 ? "in" : "attribute"; + char *vert_out = gl->glsl_version >= 130 ? "out" : "varying"; + char *frag_in = gl->glsl_version >= 130 ? "in" : "varying"; + + // vertex shader: we don't use the vertex shader, so just setup a dummy, + // which passes through the vertex array attributes. + char *vert_head = talloc_strdup(tmp, header); + char *vert_body = talloc_strdup(tmp, "void main() {\n"); + char *frag_vaos = talloc_strdup(tmp, ""); + for (int n = 0; sc->vao->entries[n].name; n++) { + const struct gl_vao_entry *e = &sc->vao->entries[n]; + const char *glsl_type = vao_glsl_type(e); + if (strcmp(e->name, "position") == 0) { + // setting raster pos. requires setting gl_Position magic variable + assert(e->num_elems == 2 && e->type == GL_FLOAT); + ADD(vert_head, "%s vec2 vertex_position;\n", vert_in); + ADD(vert_body, "gl_Position = vec4(vertex_position, 1.0, 1.0);\n"); + } else { + ADD(vert_head, "%s %s vertex_%s;\n", vert_in, glsl_type, e->name); + ADD(vert_head, "%s %s %s;\n", vert_out, glsl_type, e->name); + ADD(vert_body, "%s = vertex_%s;\n", e->name, e->name); + ADD(frag_vaos, "%s %s %s;\n", frag_in, glsl_type, e->name); + } + } + ADD(vert_body, "}\n"); + char *vert = talloc_asprintf(tmp, "%s%s", vert_head, vert_body); + + // fragment shader; still requires adding used uniforms and VAO elements + char *frag = talloc_strdup(tmp, header); + ADD(frag, "#define RG %s\n", gl->mpgl_caps & MPGL_CAP_TEX_RG ? "rg" : "ra"); + if (gl->glsl_version >= 130) { + ADD(frag, "#define texture1D texture\n"); + ADD(frag, "#define texture3D texture\n"); + ADD(frag, "out vec4 out_color;\n"); + } else { + ADD(frag, "#define texture texture2D\n"); + } + ADD(frag, "%s", frag_vaos); + for (int n = 0; n < sc->num_uniforms; n++) { + struct sc_uniform *u = &sc->uniforms[n]; + ADD(frag, "uniform %s %s;\n", u->glsl_type, u->name); + } + // custom shader header + if (sc->header_text[0]) { + ADD(frag, "// header\n"); + ADD(frag, "%s\n", sc->header_text); + ADD(frag, "// body\n"); + } + ADD(frag, "void main() {\n"); + ADD(frag, "%s", sc->text); + // we require _all_ frag shaders to write to a "vec4 color" + if (gl->glsl_version >= 130) { + ADD(frag, "out_color = color;\n"); + } else { + ADD(frag, "gl_FragColor = color;\n"); + } + ADD(frag, "}\n"); + + char *key = talloc_asprintf(tmp, "%s%s", vert, frag); + struct sc_entry *entry = NULL; + for (int n = 0; n < sc->num_entries; n++) { + if (strcmp(key, sc->entries[n].key) == 0) { + entry = &sc->entries[n]; + break; + } + } + if (!entry) { + if (sc->num_entries == SC_ENTRIES) + sc_flush_cache(sc); + entry = &sc->entries[sc->num_entries++]; + *entry = (struct sc_entry){.key = talloc_strdup(NULL, key)}; + } + // build vertex shader from vao + if (!entry->gl_shader) + entry->gl_shader = create_program(sc, vert, frag); + + gl->UseProgram(entry->gl_shader); + + // For now we set the uniforms every time. This is probably bad, and we + // should switch to caching them. + for (int n = 0; n < sc->num_uniforms; n++) + update_uniform(gl, entry->gl_shader, &sc->uniforms[n]); + + talloc_free(tmp); + + gl_sc_reset(sc); +} -- cgit v1.2.3