X-Git-Url: https://git.libre-soc.org/?a=blobdiff_plain;f=src%2Fmesa%2Fmain%2Fglspirv.c;h=87075a547cd7f0fdde8c86ee10f8a306a3cdb11e;hb=38ab39f6501f78ea7048e8a16a97fdb075b9d8c7;hp=3989f4242413e38a47b3d19daa3ec8b0524c6e95;hpb=46b21b8f9060faafdb838aa94f2aef35de03901a;p=mesa.git diff --git a/src/mesa/main/glspirv.c b/src/mesa/main/glspirv.c index 3989f424241..87075a547cd 100644 --- a/src/mesa/main/glspirv.c +++ b/src/mesa/main/glspirv.c @@ -22,8 +22,232 @@ */ #include "glspirv.h" - #include "errors.h" +#include "shaderobj.h" +#include "mtypes.h" + +#include "compiler/nir/nir.h" +#include "compiler/spirv/nir_spirv.h" + +#include "program/program.h" + +#include "util/u_atomic.h" + +void +_mesa_spirv_module_reference(struct gl_spirv_module **dest, + struct gl_spirv_module *src) +{ + struct gl_spirv_module *old = *dest; + + if (old && p_atomic_dec_zero(&old->RefCount)) + free(old); + + *dest = src; + + if (src) + p_atomic_inc(&src->RefCount); +} + +void +_mesa_shader_spirv_data_reference(struct gl_shader_spirv_data **dest, + struct gl_shader_spirv_data *src) +{ + struct gl_shader_spirv_data *old = *dest; + + if (old && p_atomic_dec_zero(&old->RefCount)) { + _mesa_spirv_module_reference(&(*dest)->SpirVModule, NULL); + ralloc_free(old); + } + + *dest = src; + + if (src) + p_atomic_inc(&src->RefCount); +} + +void +_mesa_spirv_shader_binary(struct gl_context *ctx, + unsigned n, struct gl_shader **shaders, + const void* binary, size_t length) +{ + struct gl_spirv_module *module; + struct gl_shader_spirv_data *spirv_data; + + assert(length >= 0); + + module = malloc(sizeof(*module) + length); + if (!module) { + _mesa_error(ctx, GL_OUT_OF_MEMORY, "glShaderBinary"); + return; + } + + p_atomic_set(&module->RefCount, 0); + module->Length = length; + memcpy(&module->Binary[0], binary, length); + + for (int i = 0; i < n; ++i) { + struct gl_shader *sh = shaders[i]; + + spirv_data = rzalloc(NULL, struct gl_shader_spirv_data); + _mesa_shader_spirv_data_reference(&sh->spirv_data, spirv_data); + _mesa_spirv_module_reference(&spirv_data->SpirVModule, module); + + sh->CompileStatus = COMPILE_FAILURE; + + free((void *)sh->Source); + sh->Source = NULL; + free((void *)sh->FallbackSource); + sh->FallbackSource = NULL; + + ralloc_free(sh->ir); + sh->ir = NULL; + ralloc_free(sh->symbols); + sh->symbols = NULL; + } +} + +/** + * This is the equivalent to compiler/glsl/linker.cpp::link_shaders() + * but for SPIR-V programs. + * + * This method just creates the gl_linked_shader structs with a reference to + * the SPIR-V data collected during previous steps. + * + * The real linking happens later in the driver-specifc call LinkShader(). + * This is so backends can implement different linking strategies for + * SPIR-V programs. + */ +void +_mesa_spirv_link_shaders(struct gl_context *ctx, struct gl_shader_program *prog) +{ + prog->data->LinkStatus = LINKING_SUCCESS; + prog->data->Validated = false; + + for (unsigned i = 0; i < prog->NumShaders; i++) { + struct gl_shader *shader = prog->Shaders[i]; + gl_shader_stage shader_type = shader->Stage; + + /* We only support one shader per stage. The gl_spirv spec doesn't seem + * to prevent this, but the way the API is designed, requiring all shaders + * to be specialized with an entry point, makes supporting this quite + * undefined. + * + * TODO: Turn this into a proper error once the spec bug + * is resolved. + */ + if (prog->_LinkedShaders[shader_type]) { + ralloc_strcat(&prog->data->InfoLog, + "\nError trying to link more than one SPIR-V shader " + "per stage.\n"); + prog->data->LinkStatus = LINKING_FAILURE; + return; + } + + assert(shader->spirv_data); + + struct gl_linked_shader *linked = rzalloc(NULL, struct gl_linked_shader); + linked->Stage = shader_type; + + /* Create program and attach it to the linked shader */ + struct gl_program *gl_prog = + ctx->Driver.NewProgram(ctx, + _mesa_shader_stage_to_program(shader_type), + prog->Name, false); + if (!gl_prog) { + prog->data->LinkStatus = LINKING_FAILURE; + _mesa_delete_linked_shader(ctx, linked); + return; + } + + _mesa_reference_shader_program_data(ctx, + &gl_prog->sh.data, + prog->data); + + /* Don't use _mesa_reference_program() just take ownership */ + linked->Program = gl_prog; + + /* Reference the SPIR-V data from shader to the linked shader */ + _mesa_shader_spirv_data_reference(&linked->spirv_data, + shader->spirv_data); + + prog->_LinkedShaders[shader_type] = linked; + prog->data->linked_stages |= 1 << shader_type; + } + + int last_vert_stage = + util_last_bit(prog->data->linked_stages & + ((1 << (MESA_SHADER_GEOMETRY + 1)) - 1)); + + if (last_vert_stage) + prog->last_vert_prog = prog->_LinkedShaders[last_vert_stage - 1]->Program; +} + +nir_shader * +_mesa_spirv_to_nir(struct gl_context *ctx, + const struct gl_shader_program *prog, + gl_shader_stage stage, + const nir_shader_compiler_options *options) +{ + nir_shader *nir = NULL; + + struct gl_linked_shader *linked_shader = prog->_LinkedShaders[stage]; + assert (linked_shader); + + struct gl_shader_spirv_data *spirv_data = linked_shader->spirv_data; + assert(spirv_data); + + struct gl_spirv_module *spirv_module = spirv_data->SpirVModule; + assert (spirv_module != NULL); + + const char *entry_point_name = spirv_data->SpirVEntryPoint; + assert(entry_point_name); + + struct nir_spirv_specialization *spec_entries = + calloc(sizeof(*spec_entries), + spirv_data->NumSpecializationConstants); + + for (unsigned i = 0; i < spirv_data->NumSpecializationConstants; ++i) { + spec_entries[i].id = spirv_data->SpecializationConstantsIndex[i]; + spec_entries[i].data32 = spirv_data->SpecializationConstantsValue[i]; + spec_entries[i].defined_on_module = false; + } + + const struct spirv_to_nir_options spirv_options = { + .lower_workgroup_access_to_offsets = true, + .caps = ctx->Const.SpirVCapabilities + }; + + nir_function *entry_point = + spirv_to_nir((const uint32_t *) &spirv_module->Binary[0], + spirv_module->Length / 4, + spec_entries, spirv_data->NumSpecializationConstants, + stage, entry_point_name, + &spirv_options, + options); + free(spec_entries); + + assert (entry_point); + nir = entry_point->shader; + assert(nir->info.stage == stage); + + nir->options = options; + + nir->info.name = + ralloc_asprintf(nir, "SPIRV:%s:%d", + _mesa_shader_stage_to_abbrev(nir->info.stage), + prog->Name); + nir_validate_shader(nir); + + NIR_PASS_V(nir, nir_copy_prop); + + /* Split member structs. We do this before lower_io_to_temporaries so that + * it doesn't lower system values to temporaries by accident. + */ + NIR_PASS_V(nir, nir_split_var_copies); + NIR_PASS_V(nir, nir_split_per_member_structs); + + return nir; +} void GLAPIENTRY _mesa_SpecializeShaderARB(GLuint shader, @@ -33,7 +257,105 @@ _mesa_SpecializeShaderARB(GLuint shader, const GLuint *pConstantValue) { GET_CURRENT_CONTEXT(ctx); + struct gl_shader *sh; + bool has_entry_point; + struct nir_spirv_specialization *spec_entries = NULL; + + if (!ctx->Extensions.ARB_gl_spirv) { + _mesa_error(ctx, GL_INVALID_OPERATION, "glSpecializeShaderARB"); + return; + } + + sh = _mesa_lookup_shader_err(ctx, shader, "glSpecializeShaderARB"); + if (!sh) + return; + + if (!sh->spirv_data) { + _mesa_error(ctx, GL_INVALID_OPERATION, + "glSpecializeShaderARB(not SPIR-V)"); + return; + } + + if (sh->CompileStatus) { + _mesa_error(ctx, GL_INVALID_OPERATION, + "glSpecializeShaderARB(already specialized)"); + return; + } + + struct gl_shader_spirv_data *spirv_data = sh->spirv_data; + + /* From the GL_ARB_gl_spirv spec: + * + * "The OpenGL API expects the SPIR-V module to have already been + * validated, and can return an error if it discovers anything invalid + * in the module. An invalid SPIR-V module is allowed to result in + * undefined behavior." + * + * However, the following errors still need to be detected (from the same + * spec): + * + * "INVALID_VALUE is generated if does not name a valid + * entry point for . + * + * INVALID_VALUE is generated if any element of + * refers to a specialization constant that does not exist in the + * shader module contained in ." + * + * We cannot flag those errors a-priori because detecting them requires + * parsing the module. However, flagging them during specialization is okay, + * since it makes no difference in terms of application-visible state. + */ + spec_entries = calloc(sizeof(*spec_entries), numSpecializationConstants); + + for (unsigned i = 0; i < numSpecializationConstants; ++i) { + spec_entries[i].id = pConstantIndex[i]; + spec_entries[i].data32 = pConstantValue[i]; + spec_entries[i].defined_on_module = false; + } + + has_entry_point = + gl_spirv_validation((uint32_t *)&spirv_data->SpirVModule->Binary[0], + spirv_data->SpirVModule->Length / 4, + spec_entries, numSpecializationConstants, + sh->Stage, pEntryPoint); + + /* See previous spec comment */ + if (!has_entry_point) { + _mesa_error(ctx, GL_INVALID_VALUE, + "glSpecializeShaderARB(\"%s\" is not a valid entry point" + " for shader)", pEntryPoint); + goto end; + } + + for (unsigned i = 0; i < numSpecializationConstants; ++i) { + if (spec_entries[i].defined_on_module == false) { + _mesa_error(ctx, GL_INVALID_VALUE, + "glSpecializeShaderARB(constant \"%i\" does not exist " + "in shader)", spec_entries[i].id); + goto end; + } + } + + spirv_data->SpirVEntryPoint = ralloc_strdup(spirv_data, pEntryPoint); + + /* Note that we didn't make a real compilation of the module (spirv_to_nir), + * but just checked some error conditions. Real "compilation" will be done + * later, upon linking. + */ + sh->CompileStatus = COMPILE_SUCCESS; + + spirv_data->NumSpecializationConstants = numSpecializationConstants; + spirv_data->SpecializationConstantsIndex = + rzalloc_array_size(spirv_data, sizeof(GLuint), + numSpecializationConstants); + spirv_data->SpecializationConstantsValue = + rzalloc_array_size(spirv_data, sizeof(GLuint), + numSpecializationConstants); + for (unsigned i = 0; i < numSpecializationConstants; ++i) { + spirv_data->SpecializationConstantsIndex[i] = pConstantIndex[i]; + spirv_data->SpecializationConstantsValue[i] = pConstantValue[i]; + } - /* Just return GL_INVALID_OPERATION error while this is boilerplate */ - _mesa_error(ctx, GL_INVALID_OPERATION, "SpecializeShaderARB"); + end: + free(spec_entries); }