From 258487370fd840b018a404225277d74f74899c59 Mon Sep 17 00:00:00 2001 From: Niklas Haas Date: Wed, 13 Sep 2017 03:09:48 +0200 Subject: vo_gpu: vulkan: generalize SPIR-V compiler In addition to the built-in nvidia compiler, we now also support a backend based on libshaderc. shaderc is sort of like glslang except it has a C API and is available as a dynamic library. The generated SPIR-V is now cached alongside the VkPipeline in the cached_program. We use a special cache header to ensure validity of this cache before passing it blindly to the vulkan implementation, since passing invalid SPIR-V can cause all sorts of nasty things. It's also designed to self-invalidate if the compiler gets better, by offering a catch-all `int compiler_version` that implementations can use as a cache invalidation marker. --- video/out/gpu/context.c | 18 +++++-- video/out/gpu/context.h | 1 + video/out/gpu/spirv.c | 78 +++++++++++++++++++++++++++ video/out/gpu/spirv.h | 41 ++++++++++++++ video/out/gpu/spirv_shaderc.c | 123 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 256 insertions(+), 5 deletions(-) create mode 100644 video/out/gpu/spirv.c create mode 100644 video/out/gpu/spirv.h create mode 100644 video/out/gpu/spirv_shaderc.c (limited to 'video/out/gpu') diff --git a/video/out/gpu/context.c b/video/out/gpu/context.c index 25e2a754bf..69f322c422 100644 --- a/video/out/gpu/context.c +++ b/video/out/gpu/context.c @@ -31,6 +31,7 @@ #include "video/out/vo.h" #include "context.h" +#include "spirv.h" extern const struct ra_ctx_fns ra_ctx_glx; extern const struct ra_ctx_fns ra_ctx_glx_probe; @@ -185,10 +186,17 @@ struct ra_ctx *ra_ctx_create(struct vo *vo, const char *context_type, return NULL; } -void ra_ctx_destroy(struct ra_ctx **ctx) +void ra_ctx_destroy(struct ra_ctx **ctx_ptr) { - if (*ctx) - (*ctx)->fns->uninit(*ctx); - talloc_free(*ctx); - *ctx = NULL; + struct ra_ctx *ctx = *ctx_ptr; + if (!ctx) + return; + + if (ctx->spirv && ctx->spirv->fns->uninit) + ctx->spirv->fns->uninit(ctx); + + ctx->fns->uninit(ctx); + talloc_free(ctx); + + *ctx_ptr = NULL; } diff --git a/video/out/gpu/context.h b/video/out/gpu/context.h index f74087592d..78c0441cdf 100644 --- a/video/out/gpu/context.h +++ b/video/out/gpu/context.h @@ -22,6 +22,7 @@ struct ra_ctx { struct ra_ctx_opts opts; const struct ra_ctx_fns *fns; struct ra_swapchain *swapchain; + struct spirv_compiler *spirv; void *priv; }; diff --git a/video/out/gpu/spirv.c b/video/out/gpu/spirv.c new file mode 100644 index 0000000000..9375fd9508 --- /dev/null +++ b/video/out/gpu/spirv.c @@ -0,0 +1,78 @@ +#include "common/msg.h" +#include "options/m_config.h" + +#include "spirv.h" +#include "config.h" + +extern const struct spirv_compiler_fns spirv_shaderc; +extern const struct spirv_compiler_fns spirv_nvidia_builtin; + +// in probe-order +enum { + SPIRV_AUTO = 0, + SPIRV_SHADERC, // generally preferred, but not packaged everywhere + SPIRV_NVIDIA, // can be useful for testing, only available on nvidia +}; + +static const struct spirv_compiler_fns *compilers[] = { +#if HAVE_SHADERC + [SPIRV_SHADERC] = &spirv_shaderc, +#endif +#if HAVE_VULKAN + [SPIRV_NVIDIA] = &spirv_nvidia_builtin, +#endif +}; + +static const struct m_opt_choice_alternatives compiler_choices[] = { + {"auto", SPIRV_AUTO}, +#if HAVE_SHADERC + {"shaderc", SPIRV_SHADERC}, +#endif +#if HAVE_VULKAN + {"nvidia", SPIRV_NVIDIA}, +#endif + {0} +}; + +struct spirv_opts { + int compiler; +}; + +#define OPT_BASE_STRUCT struct spirv_opts +const struct m_sub_options spirv_conf = { + .opts = (const struct m_option[]) { + OPT_CHOICE_C("spirv-compiler", compiler, 0, compiler_choices), + {0} + }, + .size = sizeof(struct spirv_opts), +}; + +bool spirv_compiler_init(struct ra_ctx *ctx) +{ + void *tmp = talloc_new(NULL); + struct spirv_opts *opts = mp_get_config_group(tmp, ctx->global, &spirv_conf); + int compiler = opts->compiler; + talloc_free(tmp); + + for (int i = SPIRV_AUTO+1; i < MP_ARRAY_SIZE(compilers); i++) { + if (compiler != SPIRV_AUTO && i != compiler) + continue; + if (!compilers[i]) + continue; + + ctx->spirv = talloc_zero(NULL, struct spirv_compiler); + ctx->spirv->log = ctx->log, + ctx->spirv->fns = compilers[i]; + + const char *name = m_opt_choice_str(compiler_choices, i); + strncpy(ctx->spirv->name, name, sizeof(ctx->spirv->name)); + MP_VERBOSE(ctx, "Initializing SPIR-V compiler '%s'\n", name); + if (ctx->spirv->fns->init(ctx)) + return true; + talloc_free(ctx->spirv); + ctx->spirv = NULL; + } + + MP_ERR(ctx, "Failed initializing SPIR-V compiler!\n"); + return false; +} diff --git a/video/out/gpu/spirv.h b/video/out/gpu/spirv.h new file mode 100644 index 0000000000..e3dbd4f52a --- /dev/null +++ b/video/out/gpu/spirv.h @@ -0,0 +1,41 @@ +#pragma once + +#include "common/msg.h" +#include "common/common.h" +#include "context.h" + +enum glsl_shader { + GLSL_SHADER_VERTEX, + GLSL_SHADER_FRAGMENT, + GLSL_SHADER_COMPUTE, +}; + +#define SPIRV_NAME_MAX_LEN 32 + +struct spirv_compiler { + char name[SPIRV_NAME_MAX_LEN]; + const struct spirv_compiler_fns *fns; + struct mp_log *log; + void *priv; + + const char *required_ext; // or NULL + int glsl_version; // GLSL version supported + int compiler_version; // for cache invalidation, may be left as 0 + int ra_caps; // RA_CAP_* provided by this implementation, if any +}; + +struct spirv_compiler_fns { + // Compile GLSL to SPIR-V, under GL_KHR_vulkan_glsl semantics. + bool (*compile_glsl)(struct spirv_compiler *spirv, void *tactx, + enum glsl_shader type, const char *glsl, + struct bstr *out_spirv); + + // Called by spirv_compiler_init / ra_ctx_destroy. These don't need to + // allocate/free ctx->spirv, that is done by the caller + bool (*init)(struct ra_ctx *ctx); + void (*uninit)(struct ra_ctx *ctx); // optional +}; + +// Initializes ctx->spirv to a valid SPIR-V compiler, or returns false on +// failure. Cleanup will be handled by ra_ctx_destroy. +bool spirv_compiler_init(struct ra_ctx *ctx); diff --git a/video/out/gpu/spirv_shaderc.c b/video/out/gpu/spirv_shaderc.c new file mode 100644 index 0000000000..9b429ca2c2 --- /dev/null +++ b/video/out/gpu/spirv_shaderc.c @@ -0,0 +1,123 @@ +#include "common/msg.h" + +#include "context.h" +#include "spirv.h" + +#include + +struct priv { + shaderc_compiler_t compiler; + shaderc_compile_options_t opts; +}; + +static void shaderc_uninit(struct ra_ctx *ctx) +{ + struct priv *p = ctx->spirv->priv; + if (!p) + return; + + shaderc_compile_options_release(p->opts); + shaderc_compiler_release(p->compiler); +} + +static bool shaderc_init(struct ra_ctx *ctx) +{ + struct priv *p = ctx->spirv->priv = talloc_zero(ctx->spirv, struct priv); + + p->compiler = shaderc_compiler_initialize(); + if (!p->compiler) + goto error; + p->opts = shaderc_compile_options_initialize(); + if (!p->opts) + goto error; + + shaderc_compile_options_set_optimization_level(p->opts, + shaderc_optimization_level_size); + + int ver, rev; + shaderc_get_spv_version(&ver, &rev); + ctx->spirv->compiler_version = ver * 100 + rev; // forwards compatibility + ctx->spirv->glsl_version = 450; // impossible to query? + return true; + +error: + shaderc_uninit(ctx); + return false; +} + +static shaderc_compilation_result_t compile(struct priv *p, + enum glsl_shader type, + const char *glsl, bool debug) +{ + static const shaderc_shader_kind kinds[] = { + [GLSL_SHADER_VERTEX] = shaderc_glsl_vertex_shader, + [GLSL_SHADER_FRAGMENT] = shaderc_glsl_fragment_shader, + [GLSL_SHADER_COMPUTE] = shaderc_glsl_compute_shader, + }; + + if (debug) { + return shaderc_compile_into_spv_assembly(p->compiler, glsl, strlen(glsl), + kinds[type], "input", "main", p->opts); + } else { + return shaderc_compile_into_spv(p->compiler, glsl, strlen(glsl), + kinds[type], "input", "main", p->opts); + } +} + +static bool shaderc_compile(struct spirv_compiler *spirv, void *tactx, + enum glsl_shader type, const char *glsl, + struct bstr *out_spirv) +{ + struct priv *p = spirv->priv; + + shaderc_compilation_result_t res = compile(p, type, glsl, false); + int errs = shaderc_result_get_num_errors(res), + warn = shaderc_result_get_num_warnings(res), + msgl = errs ? MSGL_ERR : warn ? MSGL_WARN : MSGL_V; + + const char *msg = shaderc_result_get_error_message(res); + if (msg[0]) + MP_MSG(spirv, msgl, "shaderc output:\n%s", msg); + + int s = shaderc_result_get_compilation_status(res); + bool success = s == shaderc_compilation_status_success; + + static const char *results[] = { + [shaderc_compilation_status_success] = "success", + [shaderc_compilation_status_invalid_stage] = "invalid stage", + [shaderc_compilation_status_compilation_error] = "error", + [shaderc_compilation_status_internal_error] = "internal error", + [shaderc_compilation_status_null_result_object] = "no result", + [shaderc_compilation_status_invalid_assembly] = "invalid assembly", + }; + + const char *status = s < MP_ARRAY_SIZE(results) ? results[s] : "unknown"; + MP_MSG(spirv, msgl, "shaderc compile status '%s' (%d errors, %d warnings)\n", + status, errs, warn); + + if (success) { + void *bytes = (void *) shaderc_result_get_bytes(res); + out_spirv->len = shaderc_result_get_length(res); + out_spirv->start = talloc_memdup(tactx, bytes, out_spirv->len); + } + + // Also print SPIR-V disassembly for debugging purposes. Unfortunately + // there doesn't seem to be a way to get this except compiling the shader + // a second time.. + if (mp_msg_test(spirv->log, MSGL_TRACE)) { + shaderc_compilation_result_t dis = compile(p, type, glsl, true); + MP_TRACE(spirv, "Generated SPIR-V:\n%.*s", + (int)shaderc_result_get_length(dis), + shaderc_result_get_bytes(dis)); + shaderc_result_release(dis); + } + + shaderc_result_release(res); + return success; +} + +const struct spirv_compiler_fns spirv_shaderc = { + .compile_glsl = shaderc_compile, + .init = shaderc_init, + .uninit = shaderc_uninit, +}; -- cgit v1.2.3