From 759ac6cc93bd1895a8f9233b8e9256889bdef6aa Mon Sep 17 00:00:00 2001 From: wm4 Date: Sat, 8 Apr 2017 16:38:56 +0200 Subject: vo_opengl: add option for caching shaders on disk Mostly because of ANGLE (sadly). The implementation became unpleasantly big, but at least it's relatively self-contained. I'm not sure to what degree shaders from different drivers are compatible as in whether a driver would randomly misbehave if it's fed a binary created by another driver. The useless binayFormat parameter won't help it, as they can probably easily clash. As usual, OpenGL is pretty shit here. --- video/out/opengl/common.c | 10 ++++ video/out/opengl/common.h | 4 ++ video/out/opengl/utils.c | 133 ++++++++++++++++++++++++++++++++++++++++++---- video/out/opengl/utils.h | 3 ++ video/out/opengl/video.c | 3 +- video/out/opengl/video.h | 1 + 6 files changed, 142 insertions(+), 12 deletions(-) (limited to 'video') diff --git a/video/out/opengl/common.c b/video/out/opengl/common.c index 5762f44440..271214954a 100644 --- a/video/out/opengl/common.c +++ b/video/out/opengl/common.c @@ -316,6 +316,16 @@ static const struct gl_functions gl_functions[] = { {0} }, }, + { + .ver_core = 410, + .ver_es_core = 300, + .extension = "GL_ARB_get_program_binary", + .functions = (const struct gl_function[]) { + DEF_FN(GetProgramBinary), + DEF_FN(ProgramBinary), + {0} + }, + }, // Swap control, always an OS specific extension // The OSX code loads this manually. { diff --git a/video/out/opengl/common.h b/video/out/opengl/common.h index 11cc37ba8a..3eb2a8ecf8 100644 --- a/video/out/opengl/common.h +++ b/video/out/opengl/common.h @@ -150,6 +150,10 @@ struct GL { void (GLAPIENTRY *GetShaderiv)(GLuint, GLenum, GLint *); void (GLAPIENTRY *GetProgramInfoLog)(GLuint, GLsizei, GLsizei *, GLchar *); void (GLAPIENTRY *GetProgramiv)(GLenum, GLenum, GLint *); + void (GLAPIENTRY *GetProgramBinary)(GLuint, GLsizei, GLsizei *, GLenum *, + void *); + void (GLAPIENTRY *ProgramBinary)(GLuint, GLenum, const void *, GLsizei); + const GLubyte* (GLAPIENTRY *GetStringi)(GLenum, GLuint); void (GLAPIENTRY *BindAttribLocation)(GLuint, GLuint, const GLchar *); void (GLAPIENTRY *BindFramebuffer)(GLenum, GLuint); diff --git a/video/out/opengl/utils.c b/video/out/opengl/utils.c index c080f71299..7e8680fff2 100644 --- a/video/out/opengl/utils.c +++ b/video/out/opengl/utils.c @@ -23,7 +23,15 @@ #include #include +#include +#include +#include + +#include "osdep/io.h" + #include "common/common.h" +#include "options/path.h" +#include "stream/stream.h" #include "formats.h" #include "utils.h" @@ -488,6 +496,10 @@ struct gl_shader_cache { // temporary buffers (avoids frequent reallocations) bstr tmp[5]; + + // For the disk-cache. + char *cache_dir; + struct mpv_global *global; // can be NULL }; struct gl_shader_cache *gl_sc_create(GL *gl, struct mp_log *log) @@ -817,6 +829,14 @@ static void update_uniform(GL *gl, struct sc_entry *e, struct sc_uniform *u, int } } +void gl_sc_set_cache_dir(struct gl_shader_cache *sc, struct mpv_global *global, + const char *dir) +{ + talloc_free(sc->cache_dir); + sc->cache_dir = talloc_strdup(sc, dir); + sc->global = global; +} + static void compile_attach_shader(struct gl_shader_cache *sc, GLuint program, GLenum type, const char *source) { @@ -882,18 +902,10 @@ static void link_shader(struct gl_shader_cache *sc, GLuint program) sc->error_state = true; } -static GLuint create_program(struct gl_shader_cache *sc, const char *vertex, - const char *frag) +static GLuint compile_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.len) { - MP_VERBOSE(sc, "header:\n"); - mp_log_source(sc->log, MSGL_V, sc->header_text.start); - MP_VERBOSE(sc, "body:\n"); - } - if (sc->text.len) - mp_log_source(sc->log, MSGL_V, sc->text.start); GLuint prog = gl->CreateProgram(); compile_attach_shader(sc, prog, GL_VERTEX_SHADER, vertex); compile_attach_shader(sc, prog, GL_FRAGMENT_SHADER, frag); @@ -906,6 +918,105 @@ static GLuint create_program(struct gl_shader_cache *sc, const char *vertex, return prog; } +static GLuint load_program(struct gl_shader_cache *sc, const char *vertex, + const char *frag) +{ + GL *gl = sc->gl; + + MP_VERBOSE(sc, "new shader program:\n"); + if (sc->header_text.len) { + MP_VERBOSE(sc, "header:\n"); + mp_log_source(sc->log, MSGL_V, sc->header_text.start); + MP_VERBOSE(sc, "body:\n"); + } + if (sc->text.len) + mp_log_source(sc->log, MSGL_V, sc->text.start); + + if (!sc->cache_dir || !sc->cache_dir[0] || !gl->ProgramBinary) + return compile_program(sc, vertex, frag); + + // Try to load it from a disk cache, or compiling + saving it. + + GLuint prog = 0; + void *tmp = talloc_new(NULL); + char *dir = mp_get_user_path(tmp, sc->global, sc->cache_dir); + + struct AVSHA *sha = av_sha_alloc(); + if (!sha) + abort(); + av_sha_init(sha, 256); + + av_sha_update(sha, vertex, strlen(vertex) + 1); + av_sha_update(sha, frag, strlen(frag) + 1); + + // In theory, the array could change order, breaking old binaries. + for (int n = 0; sc->vao->entries[n].name; n++) { + av_sha_update(sha, sc->vao->entries[n].name, + strlen(sc->vao->entries[n].name) + 1); + } + + uint8_t hash[256 / 8]; + av_sha_final(sha, hash); + av_free(sha); + + char hashstr[256 / 8 * 2 + 1]; + for (int n = 0; n < 256 / 8; n++) + snprintf(hashstr + n * 2, sizeof(hashstr) - n * 2, "%02X", hash[n]); + + const char *header = "mpv shader cache v1\n"; + size_t header_size = strlen(header) + 4; + + char *filename = mp_path_join(tmp, dir, hashstr); + if (stat(filename, &(struct stat){0}) == 0) { + MP_VERBOSE(sc, "Trying to load shader from disk...\n"); + struct bstr cachedata = stream_read_file(filename, tmp, sc->global, + 1000000000); // 1 GB + if (cachedata.len > header_size) { + GLenum format = AV_RL32(cachedata.start + header_size - 4); + prog = gl->CreateProgram(); + gl_check_error(gl, sc->log, "before loading program"); + gl->ProgramBinary(prog, format, cachedata.start + header_size, + cachedata.len - header_size); + gl->GetError(); // discard potential useless error + GLint status = 0; + gl->GetProgramiv(prog, GL_LINK_STATUS, &status); + if (!status) { + gl->DeleteProgram(prog); + prog = 0; + } + } + MP_VERBOSE(sc, "Loading cached shader %s.\n", prog ? "ok" : "failed"); + } + + if (!prog) { + prog = compile_program(sc, vertex, frag); + + GLint size = 0; + gl->GetProgramiv(prog, GL_PROGRAM_BINARY_LENGTH, &size); + uint8_t *buffer = talloc_size(tmp, size + header_size); + GLsizei actual_size = 0; + GLenum binary_format = 0; + gl->GetProgramBinary(prog, size, &actual_size, &binary_format, + buffer + header_size); + memcpy(buffer, header, header_size - 4); + AV_WL32(buffer + header_size - 4, binary_format); + + if (actual_size) { + mp_mkdirp(dir); + + MP_VERBOSE(sc, "Writing shader cache file: %s\n", filename); + FILE *out = fopen(filename, "wb"); + if (out) { + fwrite(buffer, header_size + actual_size, 1, out); + fclose(out); + } + } + } + + talloc_free(tmp); + return prog; +} + #define ADD(x, ...) bstr_xappend_asprintf(sc, (x), __VA_ARGS__) #define ADD_BSTR(x, s) bstr_xappend(sc, (x), (s)) @@ -1030,7 +1141,7 @@ void gl_sc_generate(struct gl_shader_cache *sc) } // build vertex shader from vao and cache the locations of the uniform variables if (!entry->gl_shader) { - entry->gl_shader = create_program(sc, vert->start, frag->start); + entry->gl_shader = load_program(sc, vert->start, frag->start); entry->num_uniforms = 0; for (int n = 0; n < sc->num_uniforms; n++) { struct sc_cached_uniform un = { diff --git a/video/out/opengl/utils.h b/video/out/opengl/utils.h index aa936f6e35..95eb1c4fea 100644 --- a/video/out/opengl/utils.h +++ b/video/out/opengl/utils.h @@ -171,6 +171,9 @@ void gl_sc_set_vao(struct gl_shader_cache *sc, struct gl_vao *vao); void gl_sc_enable_extension(struct gl_shader_cache *sc, char *name); void gl_sc_generate(struct gl_shader_cache *sc); void gl_sc_reset(struct gl_shader_cache *sc); +struct mpv_global; +void gl_sc_set_cache_dir(struct gl_shader_cache *sc, struct mpv_global *global, + const char *dir); struct gl_timer; diff --git a/video/out/opengl/video.c b/video/out/opengl/video.c index 38d3d413e2..251a3d4f8b 100644 --- a/video/out/opengl/video.c +++ b/video/out/opengl/video.c @@ -389,7 +389,7 @@ const struct m_sub_options gl_video_conf = { OPT_SUBSTRUCT("", icc_opts, mp_icc_conf, 0), OPT_CHOICE("opengl-early-flush", early_flush, 0, ({"no", 0}, {"yes", 1}, {"auto", -1})), - + OPT_STRING("opengl-shader-cache-dir", shader_cache_dir, 0), {0} }, .size = sizeof(struct gl_video_opts), @@ -3362,6 +3362,7 @@ static void reinit_from_options(struct gl_video *p) check_gl_features(p); uninit_rendering(p); + gl_sc_set_cache_dir(p->sc, p->global, p->opts.shader_cache_dir); gl_video_setup_hooks(p); reinit_osd(p); diff --git a/video/out/opengl/video.h b/video/out/opengl/video.h index 3b5f452bf4..236ab067c4 100644 --- a/video/out/opengl/video.h +++ b/video/out/opengl/video.h @@ -134,6 +134,7 @@ struct gl_video_opts { int tex_pad_x, tex_pad_y; struct mp_icc_opts *icc_opts; int early_flush; + char *shader_cache_dir; }; extern const struct m_sub_options gl_video_conf; -- cgit v1.2.3