diff options
Diffstat (limited to 'src/compiler')
-rw-r--r-- | src/compiler/Makefile.sources | 1 | ||||
-rw-r--r-- | src/compiler/glsl/gl_nir_link_uniform_blocks.c | 643 | ||||
-rw-r--r-- | src/compiler/glsl/gl_nir_linker.h | 3 | ||||
-rw-r--r-- | src/compiler/glsl/meson.build | 1 |
4 files changed, 648 insertions, 0 deletions
diff --git a/src/compiler/Makefile.sources b/src/compiler/Makefile.sources index d6b12fcea1b..c94ec24b2e6 100644 --- a/src/compiler/Makefile.sources +++ b/src/compiler/Makefile.sources @@ -31,6 +31,7 @@ LIBGLSL_FILES = \ glsl/gl_nir_lower_samplers.c \ glsl/gl_nir_lower_samplers_as_deref.c \ glsl/gl_nir_link_atomics.c \ + glsl/gl_nir_link_uniform_blocks.c \ glsl/gl_nir_link_uniform_initializers.c \ glsl/gl_nir_link_uniforms.c \ glsl/gl_nir_link_xfb.c \ diff --git a/src/compiler/glsl/gl_nir_link_uniform_blocks.c b/src/compiler/glsl/gl_nir_link_uniform_blocks.c new file mode 100644 index 00000000000..28faa7e7626 --- /dev/null +++ b/src/compiler/glsl/gl_nir_link_uniform_blocks.c @@ -0,0 +1,643 @@ +/* + * Copyright © 2019 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include "nir.h" +#include "gl_nir_linker.h" +#include "ir_uniform.h" /* for gl_uniform_storage */ +#include "linker_util.h" +#include "main/mtypes.h" + +/* Summary: This file contains code to do a nir-based linking for uniform + * blocks. This includes ubos and ssbos. Note that it is tailored to + * ARB_gl_spirv needs and particularities. + * + * More details: + * + * 1. Linking doesn't use names: GLSL linking use names as core concept. But + * on SPIR-V, uniform block name, fields names, and other names are + * considered optional debug infor so could not be present. So the linking + * should work without it, and it is optional to not handle them at + * all. From ARB_gl_spirv spec. + * + * "19. How should the program interface query operations behave for program + * objects created from SPIR-V shaders? + * + * DISCUSSION: we previously said we didn't need reflection to work for + * SPIR-V shaders (at least for the first version), however we are left + * with specifying how it should "not work". The primary issue is that + * SPIR-V binaries are not required to have names associated with + * variables. They can be associated in debug information, but there is no + * requirement for that to be present, and it should not be relied upon. + * + * Options: + * + * <skip> + * + * C) Allow as much as possible to work "naturally". You can query for the + * number of active resources, and for details about them. Anything that + * doesn't query by name will work as expected. Queries for maximum length + * of names return one. Queries for anything "by name" return INVALID_INDEX + * (or -1). Querying the name property of a resource returns an empty + * string. This may allow many queries to work, but it's not clear how + * useful it would be if you can't actually know which specific variable + * you are retrieving information on. If everything is specified a-priori + * by location/binding/offset/index/component in the shader, this may be + * sufficient. + * + * RESOLVED. Pick (c), but also allow debug names to be returned if an + * implementation wants to." + * + * This implemention doesn't care for the names, as the main objective is + * functional, and not support optional debug features. + * + * 2. Terminology: this file handles both UBO and SSBO, including both as + * "uniform blocks" analogously to what is done in the GLSL (IR) path. + * + * From ARB_gl_spirv spec: + * "Mapping of Storage Classes: + * <skip> + * uniform blockN { ... } ...; -> Uniform, with Block decoration + * <skip> + * buffer blockN { ... } ...; -> Uniform, with BufferBlock decoration" + * + * 3. Explicit data: The code assumes that all structure members have an + * Offset decoration, all arrays have an ArrayStride and all matrices have + * a MatrixStride, even for nested structures. That way we don’t have to + * worry about the different layout modes. This is explicitly required in + * the SPIR-V spec: + * + * "Composite objects in the UniformConstant, Uniform, and PushConstant + * Storage Classes must be explicitly laid out. The following apply to all + * the aggregate and matrix types describing such an object, recursively + * through their nested types: + * + * – Each structure-type member must have an Offset Decoration. + * – Each array type must have an ArrayStride Decoration. + * – Each structure-type member that is a matrix or array-of-matrices must + * have be decorated with a MatrixStride Decoration, and one of the + * RowMajor or ColMajor Decorations." + * + * Additionally, the structure members are expected to be presented in + * increasing offset order: + * + * "a structure has lower-numbered members appearing at smaller offsets than + * higher-numbered members" + */ + +enum block_type { + BLOCK_UBO, + BLOCK_SSBO +}; + +/* + * It is worth to note that ARB_gl_spirv spec doesn't require us to do this + * validation, but at the same time, it allow us to do it. The following + * validation is easy and a nice-to-have. +*/ +static bool +link_blocks_are_compatible(const struct gl_uniform_block *a, + const struct gl_uniform_block *b) +{ + /* + * Names on ARB_gl_spirv are optional, so we are ignoring them. So + * meanwhile on the equivalent GLSL method the matching is done using the + * name, here we use the binding, that for SPIR-V binaries is explicit, and + * mandatory, from OpenGL 4.6 spec, section "7.4.2. SPIR-V Shader Interface + * Matching": + * "Uniform and shader storage block variables must also be decorated + * with a Binding" + */ + if (a->Binding != b->Binding) + return false; + + /* We are explicitly ignoring the names, so it would be good to check that + * this is happening. + */ + assert(a->Name == NULL); + assert(b->Name == NULL); + + if (a->NumUniforms != b->NumUniforms) + return false; + + if (a->_Packing != b->_Packing) + return false; + + if (a->_RowMajor != b->_RowMajor) + return false; + + for (unsigned i = 0; i < a->NumUniforms; i++) { + if (a->Uniforms[i].Type != b->Uniforms[i].Type) + return false; + + if (a->Uniforms[i].RowMajor != b->Uniforms[i].RowMajor) + return false; + + if (a->Uniforms[i].Offset != b->Uniforms[i].Offset) + return false; + + /* See comment on previous assert */ + assert(a->Uniforms[i].Name == NULL); + assert(b->Uniforms[i].Name == NULL); + } + + return true; +} + +/** + * Merges a buffer block into an array of buffer blocks that may or may not + * already contain a copy of it. + * + * Returns the index of the block in the array (new if it was needed, or the + * index of the copy of it). -1 if there are two incompatible block + * definitions with the same binding. + * + */ +static int +link_cross_validate_uniform_block(void *mem_ctx, + struct gl_uniform_block **linked_blocks, + unsigned int *num_linked_blocks, + struct gl_uniform_block *new_block) +{ + /* We first check if new_block was already linked */ + for (unsigned int i = 0; i < *num_linked_blocks; i++) { + struct gl_uniform_block *old_block = &(*linked_blocks)[i]; + + if (old_block->Binding == new_block->Binding) + return link_blocks_are_compatible(old_block, new_block) ? i : -1; + } + + *linked_blocks = reralloc(mem_ctx, *linked_blocks, + struct gl_uniform_block, + *num_linked_blocks + 1); + int linked_block_index = (*num_linked_blocks)++; + struct gl_uniform_block *linked_block = &(*linked_blocks)[linked_block_index]; + + memcpy(linked_block, new_block, sizeof(*new_block)); + linked_block->Uniforms = ralloc_array(*linked_blocks, + struct gl_uniform_buffer_variable, + linked_block->NumUniforms); + + memcpy(linked_block->Uniforms, + new_block->Uniforms, + sizeof(*linked_block->Uniforms) * linked_block->NumUniforms); + + return linked_block_index; +} + + +/** + * Accumulates the array of buffer blocks and checks that all definitions of + * blocks agree on their contents. + */ +static bool +nir_interstage_cross_validate_uniform_blocks(struct gl_shader_program *prog, + enum block_type block_type) +{ + int *interfaceBlockStageIndex[MESA_SHADER_STAGES]; + struct gl_uniform_block *blks = NULL; + unsigned *num_blks = block_type == BLOCK_SSBO ? &prog->data->NumShaderStorageBlocks : + &prog->data->NumUniformBlocks; + + unsigned max_num_buffer_blocks = 0; + for (unsigned i = 0; i < MESA_SHADER_STAGES; i++) { + if (prog->_LinkedShaders[i]) { + if (block_type == BLOCK_SSBO) { + max_num_buffer_blocks += + prog->_LinkedShaders[i]->Program->info.num_ssbos; + } else { + max_num_buffer_blocks += + prog->_LinkedShaders[i]->Program->info.num_ubos; + } + } + } + + for (unsigned i = 0; i < MESA_SHADER_STAGES; i++) { + struct gl_linked_shader *sh = prog->_LinkedShaders[i]; + + interfaceBlockStageIndex[i] = malloc(max_num_buffer_blocks * sizeof(int)); + for (unsigned int j = 0; j < max_num_buffer_blocks; j++) + interfaceBlockStageIndex[i][j] = -1; + + if (sh == NULL) + continue; + + unsigned sh_num_blocks; + struct gl_uniform_block **sh_blks; + if (block_type == BLOCK_SSBO) { + sh_num_blocks = prog->_LinkedShaders[i]->Program->info.num_ssbos; + sh_blks = sh->Program->sh.ShaderStorageBlocks; + } else { + sh_num_blocks = prog->_LinkedShaders[i]->Program->info.num_ubos; + sh_blks = sh->Program->sh.UniformBlocks; + } + + for (unsigned int j = 0; j < sh_num_blocks; j++) { + int index = link_cross_validate_uniform_block(prog->data, &blks, + num_blks, sh_blks[j]); + + if (index == -1) { + /* We use the binding as we are ignoring the names */ + linker_error(prog, "buffer block with binding `%i' has mismatching " + "definitions\n", sh_blks[j]->Binding); + + for (unsigned k = 0; k <= i; k++) { + free(interfaceBlockStageIndex[k]); + } + + /* Reset the block count. This will help avoid various segfaults + * from api calls that assume the array exists due to the count + * being non-zero. + */ + *num_blks = 0; + return false; + } + + interfaceBlockStageIndex[i][index] = j; + } + } + + /* Update per stage block pointers to point to the program list. + */ + for (unsigned i = 0; i < MESA_SHADER_STAGES; i++) { + for (unsigned j = 0; j < *num_blks; j++) { + int stage_index = interfaceBlockStageIndex[i][j]; + + if (stage_index != -1) { + struct gl_linked_shader *sh = prog->_LinkedShaders[i]; + + struct gl_uniform_block **sh_blks = block_type == BLOCK_SSBO ? + sh->Program->sh.ShaderStorageBlocks : + sh->Program->sh.UniformBlocks; + + blks[j].stageref |= sh_blks[stage_index]->stageref; + sh_blks[stage_index] = &blks[j]; + } + } + } + + for (unsigned i = 0; i < MESA_SHADER_STAGES; i++) { + free(interfaceBlockStageIndex[i]); + } + + if (block_type == BLOCK_SSBO) + prog->data->ShaderStorageBlocks = blks; + else + prog->data->UniformBlocks = blks; + + return true; +} + +/* + * Iterates @type in order to compute how many individual leaf variables + * contains. + */ +static void +iterate_type_count_variables(const struct glsl_type *type, + unsigned int *num_variables) +{ + for (unsigned i = 0; i < glsl_get_length(type); i++) { + const struct glsl_type *field_type; + + if (glsl_type_is_struct_or_ifc(type)) + field_type = glsl_get_struct_field(type, i); + else + field_type = glsl_get_array_element(type); + + if (glsl_type_is_leaf(field_type)) + (*num_variables)++; + else + iterate_type_count_variables(field_type, num_variables); + } +} + + +static void +fill_individual_variable(const struct glsl_type *type, + struct gl_uniform_buffer_variable *variables, + unsigned int *variable_index, + unsigned int *offset, + struct gl_shader_program *prog, + struct gl_uniform_block *block) +{ + /* ARB_gl_spirv: allowed to ignore names. Thus, we don't need to initialize + * the variable's Name or IndexName. + */ + variables[*variable_index].Type = type; + + if (glsl_type_is_matrix(type)) { + variables[*variable_index].RowMajor = glsl_matrix_type_is_row_major(type); + } else { + /* default value, better that potential meaningless garbage */ + variables[*variable_index].RowMajor = false; + } + + /** + * Although ARB_gl_spirv points that the offsets need to be included (see + * "Mappings of layouts"), in the end those are only valid for + * root-variables, and we would need to recompute offsets when we iterate + * over non-trivial types, like aoa. So we compute the offset always. + */ + variables[*variable_index].Offset = *offset; + (*offset) += glsl_get_explicit_size(type, true); + + (*variable_index)++; +} + +static void +iterate_type_fill_variables(const struct glsl_type *type, + struct gl_uniform_buffer_variable *variables, + unsigned int *variable_index, + unsigned int *offset, + struct gl_shader_program *prog, + struct gl_uniform_block *block) +{ + unsigned int struct_base_offset; + + for (unsigned i = 0; i < glsl_get_length(type); i++) { + const struct glsl_type *field_type; + + if (glsl_type_is_struct_or_ifc(type)) { + field_type = glsl_get_struct_field(type, i); + + if (i == 0) { + struct_base_offset = *offset; + } + + *offset = struct_base_offset + glsl_get_struct_field_offset(type, i); + } else { + field_type = glsl_get_array_element(type); + } + + if (glsl_type_is_leaf(field_type)) { + fill_individual_variable(field_type, variables, variable_index, + offset, prog, block); + } else { + iterate_type_fill_variables(field_type, variables, variable_index, + offset, prog, block); + } + } +} + +/* + * In opposite to the equivalent glsl one, this one only allocates the needed + * space. We do a initial count here, just to avoid re-allocating for each one + * we find. + */ +static void +allocate_uniform_blocks(void *mem_ctx, + struct gl_linked_shader *shader, + struct gl_uniform_block **out_blks, unsigned *num_blocks, + struct gl_uniform_buffer_variable **out_variables, + unsigned *num_variables, + enum block_type block_type) +{ + *num_variables = 0; + *num_blocks = 0; + + nir_foreach_variable(var, &shader->Program->nir->uniforms) { + if (block_type == BLOCK_UBO && !nir_variable_is_in_ubo(var)) + continue; + + if (block_type == BLOCK_SSBO && !nir_variable_is_in_ssbo(var)) + continue; + + const struct glsl_type *type = glsl_without_array(var->type); + unsigned aoa_size = glsl_get_aoa_size(var->type); + unsigned buffer_count = aoa_size == 0 ? 1 : aoa_size; + + *num_blocks += buffer_count; + + unsigned int block_variables = 0; + iterate_type_count_variables(type, &block_variables); + + *num_variables += block_variables * buffer_count; + } + + if (*num_blocks == 0) { + assert(*num_variables == 0); + return; + } + + assert(*num_variables != 0); + + struct gl_uniform_block *blocks = + rzalloc_array(mem_ctx, struct gl_uniform_block, *num_blocks); + + struct gl_uniform_buffer_variable *variables = + rzalloc_array(blocks, struct gl_uniform_buffer_variable, *num_variables); + + *out_blks = blocks; + *out_variables = variables; +} + +static void +fill_block(struct gl_uniform_block *block, + nir_variable *var, + struct gl_uniform_buffer_variable *variables, + unsigned *variable_index, + unsigned array_index, + struct gl_shader_program *prog, + const gl_shader_stage stage) +{ + const struct glsl_type *type = glsl_without_array(var->type); + + block->Name = NULL; /* ARB_gl_spirv: allowed to ignore names */ + /* From ARB_gl_spirv spec: + * "Vulkan uses only one binding point for a resource array, + * while OpenGL still uses multiple binding points, so binding + * numbers are counted differently for SPIR-V used in Vulkan + * and OpenGL + */ + block->Binding = var->data.binding + array_index; + block->Uniforms = &variables[*variable_index]; + block->stageref = 1U << stage; + + /* From SPIR-V 1.0 spec, 3.20, Decoration: + * "RowMajor + * Applies only to a member of a structure type. + * Only valid on a matrix or array whose most basic + * element is a matrix. Indicates that components + * within a row are contiguous in memory." + * + * So the SPIR-V binary doesn't report if the block was defined as RowMajor + * or not. In any case, for the components it is mandatory to set it, so it + * is not needed a default RowMajor value to know it. + * + * Setting to the default, but it should be ignored. + */ + block->_RowMajor = false; + + /* From ARB_gl_spirv spec: + * "Mapping of layouts + * + * std140/std430 -> explicit *Offset*, *ArrayStride*, and + * *MatrixStride* Decoration on struct members + * shared/packed -> not allowed" + * + * So we would not have a value for _Packing, and in fact it would be + * useless so far. Using a default value. It should be ignored. + */ + block->_Packing = 0; + block->linearized_array_index = array_index; + + unsigned old_variable_index = *variable_index; + unsigned offset = 0; + iterate_type_fill_variables(type, variables, variable_index, &offset, prog, block); + block->NumUniforms = *variable_index - old_variable_index; + + block->UniformBufferSize = glsl_get_explicit_size(type, false); + + /* From OpenGL 4.6 spec, section 7.6.2.3, "SPIR-V Uniform Offsets and + * strides" + * + * "If the variable is decorated as a BufferBlock , its offsets and + * strides must not contradict std430 alignment and minimum offset + * requirements. Otherwise, its offsets and strides must not contradict + * std140 alignment and minimum offset requirements." + * + * So although we are computing the size based on the offsets and + * array/matrix strides, at the end we need to ensure that the alignment is + * the same that with std140. From ARB_uniform_buffer_object spec: + * + * "For uniform blocks laid out according to [std140] rules, the minimum + * buffer object size returned by the UNIFORM_BLOCK_DATA_SIZE query is + * derived by taking the offset of the last basic machine unit consumed + * by the last uniform of the uniform block (including any end-of-array + * or end-of-structure padding), adding one, and rounding up to the next + * multiple of the base alignment required for a vec4." + */ + block->UniformBufferSize = glsl_align(block->UniformBufferSize, 16); +} + +/* + * Link ubos/ssbos for a given linked_shader/stage. + */ +static void +link_linked_shader_uniform_blocks(void *mem_ctx, + struct gl_context *ctx, + struct gl_shader_program *prog, + struct gl_linked_shader *shader, + struct gl_uniform_block **blocks, + unsigned *num_blocks, + enum block_type block_type) +{ + struct gl_uniform_buffer_variable *variables = NULL; + unsigned num_variables = 0; + + allocate_uniform_blocks(mem_ctx, shader, + blocks, num_blocks, + &variables, &num_variables, + block_type); + + /* Fill the content of uniforms and variables */ + unsigned block_index = 0; + unsigned variable_index = 0; + struct gl_uniform_block *blks = *blocks; + + nir_foreach_variable(var, &shader->Program->nir->uniforms) { + if (block_type == BLOCK_UBO && !nir_variable_is_in_ubo(var)) + continue; + + if (block_type == BLOCK_SSBO && !nir_variable_is_in_ssbo(var)) + continue; + + unsigned aoa_size = glsl_get_aoa_size(var->type); + unsigned buffer_count = aoa_size == 0 ? 1 : aoa_size; + + for (unsigned array_index = 0; array_index < buffer_count; array_index++) { + fill_block(&blks[block_index], var, variables, &variable_index, + array_index, prog, shader->Stage); + block_index++; + } + } + + assert(block_index == *num_blocks); + assert(variable_index == num_variables); +} + +bool +gl_nir_link_uniform_blocks(struct gl_context *ctx, + struct gl_shader_program *prog) +{ + void *mem_ctx = ralloc_context(NULL); + + for (int stage = 0; stage < MESA_SHADER_STAGES; stage++) { + struct gl_linked_shader *const linked = prog->_LinkedShaders[stage]; + struct gl_uniform_block *ubo_blocks = NULL; + unsigned num_ubo_blocks = 0; + struct gl_uniform_block *ssbo_blocks = NULL; + unsigned num_ssbo_blocks = 0; + + if (!linked) + continue; + + link_linked_shader_uniform_blocks(mem_ctx, ctx, prog, linked, + &ubo_blocks, &num_ubo_blocks, + BLOCK_UBO); + + link_linked_shader_uniform_blocks(mem_ctx, ctx, prog, linked, + &ssbo_blocks, &num_ssbo_blocks, + BLOCK_SSBO); + + if (!prog->data->LinkStatus) { + return false; + } + + prog->data->linked_stages |= 1 << stage; + + /* Copy ubo blocks to linked shader list */ + linked->Program->sh.UniformBlocks = + ralloc_array(linked, struct gl_uniform_block *, num_ubo_blocks); + ralloc_steal(linked, ubo_blocks); + for (unsigned i = 0; i < num_ubo_blocks; i++) { + linked->Program->sh.UniformBlocks[i] = &ubo_blocks[i]; + } + + /* We need to set it twice to avoid the value being overwritten by the + * one from nir in brw_shader_gather_info. TODO: get a way to set the + * info once, and being able to gather properly the info. + */ + linked->Program->nir->info.num_ubos = num_ubo_blocks; + linked->Program->info.num_ubos = num_ubo_blocks; + + /* Copy ssbo blocks to linked shader list */ + linked->Program->sh.ShaderStorageBlocks = + ralloc_array(linked, struct gl_uniform_block *, num_ssbo_blocks); + ralloc_steal(linked, ssbo_blocks); + for (unsigned i = 0; i < num_ssbo_blocks; i++) { + linked->Program->sh.ShaderStorageBlocks[i] = &ssbo_blocks[i]; + } + + /* See previous comment on num_ubo_blocks */ + linked->Program->nir->info.num_ssbos = num_ssbo_blocks; + linked->Program->info.num_ssbos = num_ssbo_blocks; + } + + if (!nir_interstage_cross_validate_uniform_blocks(prog, BLOCK_UBO)) + return false; + + if (!nir_interstage_cross_validate_uniform_blocks(prog, BLOCK_SSBO)) + return false; + + return true; +} diff --git a/src/compiler/glsl/gl_nir_linker.h b/src/compiler/glsl/gl_nir_linker.h index 29ca27d3df8..20ed35fa0e4 100644 --- a/src/compiler/glsl/gl_nir_linker.h +++ b/src/compiler/glsl/gl_nir_linker.h @@ -46,6 +46,9 @@ void gl_nir_link_assign_atomic_counter_resources(struct gl_context *ctx, void gl_nir_link_assign_xfb_resources(struct gl_context *ctx, struct gl_shader_program *prog); +bool gl_nir_link_uniform_blocks(struct gl_context *ctx, + struct gl_shader_program *prog); + #ifdef __cplusplus } /* extern "C" */ #endif diff --git a/src/compiler/glsl/meson.build b/src/compiler/glsl/meson.build index 615c1ad1761..fe1dc8ffe6a 100644 --- a/src/compiler/glsl/meson.build +++ b/src/compiler/glsl/meson.build @@ -79,6 +79,7 @@ files_libglsl = files( 'gl_nir_lower_samplers.c', 'gl_nir_lower_samplers_as_deref.c', 'gl_nir_link_atomics.c', + 'gl_nir_link_uniform_blocks.c', 'gl_nir_link_uniform_initializers.c', 'gl_nir_link_uniforms.c', 'gl_nir_link_xfb.c', |