summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTimothy Arceri <tarceri@itsqueeze.com>2024-04-02 15:03:03 +1100
committerTimothy Arceri <tarceri@itsqueeze.com>2024-04-12 12:15:48 +1000
commit0dcbd8a8a4355f8f149f6cc5ff704908ec6464e6 (patch)
tree84ad24f77307d7934c54b4161560398ec0f9f559
parent0d8ee7534e5a0c861b26904087a9db2c04a66a87 (diff)
glsl: move cross_validate_uniforms() to the nir linker
Reviewed-by: Alejandro Piñeiro <apinheiro@igalia.com> Reviewed-by: Marek Olšák <marek.olsak@amd.com> Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/28690>
-rw-r--r--src/compiler/glsl/gl_nir_linker.c345
-rw-r--r--src/compiler/glsl/linker.cpp26
2 files changed, 345 insertions, 26 deletions
diff --git a/src/compiler/glsl/gl_nir_linker.c b/src/compiler/glsl/gl_nir_linker.c
index b0ad48f9a77..668fae2bad3 100644
--- a/src/compiler/glsl/gl_nir_linker.c
+++ b/src/compiler/glsl/gl_nir_linker.c
@@ -1564,6 +1564,343 @@ gl_nir_validate_intrastage_arrays(struct gl_shader_program *prog,
return false;
}
+static bool
+nir_constant_compare(const nir_constant *c1, const nir_constant *c2)
+{
+ bool match = true;
+
+ match &= memcmp(c1->values, c2->values, sizeof(c1->values)) == 0;
+ match &= c1->is_null_constant == c2->is_null_constant;
+ match &= c1->num_elements == c2->num_elements;
+ if (!match)
+ return false;
+
+ for (unsigned i = 0; i < c1->num_elements; i++) {
+ match &= nir_constant_compare(c1->elements[i], c2->elements[i]);
+ }
+
+ return match;
+}
+
+struct ifc_var {
+ unsigned stage;
+ nir_variable *var;
+};
+
+/**
+ * Perform validation of global variables used across multiple shaders
+ */
+static void
+cross_validate_globals(void *mem_ctx, const struct gl_constants *consts,
+ struct gl_shader_program *prog,
+ nir_shader *shader, struct hash_table *variables,
+ bool uniforms_only)
+{
+ nir_foreach_variable_in_shader(var, shader) {
+ if (uniforms_only &&
+ (var->data.mode != nir_var_uniform &&
+ var->data.mode != nir_var_mem_ubo &&
+ var->data.mode != nir_var_image &&
+ var->data.mode != nir_var_mem_ssbo))
+ continue;
+
+ /* don't cross validate subroutine uniforms */
+ if (glsl_contains_subroutine(var->type))
+ continue;
+
+ /* Don't cross validate interface instances. These are only relevant
+ * inside a shader. The cross validation is done at the Interface Block
+ * name level.
+ */
+ if (glsl_without_array(var->type) == var->interface_type)
+ continue;
+
+ /* Don't cross validate temporaries that are at global scope. These
+ * will eventually get pulled into the shaders 'main'.
+ */
+ if (var->data.mode == nir_var_function_temp ||
+ var->data.mode == nir_var_shader_temp)
+ continue;
+
+ /* If a global with this name has already been seen, verify that the
+ * new instance has the same type. In addition, if the globals have
+ * initializers, the values of the initializers must be the same.
+ */
+ struct hash_entry *entry =
+ _mesa_hash_table_search(variables, var->name);
+ if (entry != NULL) {
+ struct ifc_var *existing_ifc = (struct ifc_var *) entry->data;
+ nir_variable *existing = existing_ifc->var;
+
+ /* Check if types match. */
+ if (var->type != existing->type) {
+ if (!gl_nir_validate_intrastage_arrays(prog, var, existing,
+ existing_ifc->stage, true)) {
+ /* If it is an unsized array in a Shader Storage Block,
+ * two different shaders can access to different elements.
+ * Because of that, they might be converted to different
+ * sized arrays, then check that they are compatible but
+ * ignore the array size.
+ */
+ if (!(var->data.mode == nir_var_mem_ssbo &&
+ var->data.from_ssbo_unsized_array &&
+ existing->data.mode == nir_var_mem_ssbo &&
+ existing->data.from_ssbo_unsized_array &&
+ glsl_get_gl_type(var->type) == glsl_get_gl_type(existing->type))) {
+ linker_error(prog, "%s `%s' declared as type "
+ "`%s' and type `%s'\n",
+ gl_nir_mode_string(var),
+ var->name, glsl_get_type_name(var->type),
+ glsl_get_type_name(existing->type));
+ return;
+ }
+ }
+ }
+
+ if (var->data.explicit_location) {
+ if (existing->data.explicit_location
+ && (var->data.location != existing->data.location)) {
+ linker_error(prog, "explicit locations for %s "
+ "`%s' have differing values\n",
+ gl_nir_mode_string(var), var->name);
+ return;
+ }
+
+ if (var->data.location_frac != existing->data.location_frac) {
+ linker_error(prog, "explicit components for %s `%s' have "
+ "differing values\n", gl_nir_mode_string(var),
+ var->name);
+ return;
+ }
+
+ existing->data.location = var->data.location;
+ existing->data.explicit_location = true;
+ } else {
+ /* Check if uniform with implicit location was marked explicit
+ * by earlier shader stage. If so, mark it explicit in this stage
+ * too to make sure later processing does not treat it as
+ * implicit one.
+ */
+ if (existing->data.explicit_location) {
+ var->data.location = existing->data.location;
+ var->data.explicit_location = true;
+ }
+ }
+
+ /* From the GLSL 4.20 specification:
+ * "A link error will result if two compilation units in a program
+ * specify different integer-constant bindings for the same
+ * opaque-uniform name. However, it is not an error to specify a
+ * binding on some but not all declarations for the same name"
+ */
+ if (var->data.explicit_binding) {
+ if (existing->data.explicit_binding &&
+ var->data.binding != existing->data.binding) {
+ linker_error(prog, "explicit bindings for %s "
+ "`%s' have differing values\n",
+ gl_nir_mode_string(var), var->name);
+ return;
+ }
+
+ existing->data.binding = var->data.binding;
+ existing->data.explicit_binding = true;
+ }
+
+ if (glsl_contains_atomic(var->type) &&
+ var->data.offset != existing->data.offset) {
+ linker_error(prog, "offset specifications for %s "
+ "`%s' have differing values\n",
+ gl_nir_mode_string(var), var->name);
+ return;
+ }
+
+ /* Validate layout qualifiers for gl_FragDepth.
+ *
+ * From the AMD/ARB_conservative_depth specs:
+ *
+ * "If gl_FragDepth is redeclared in any fragment shader in a
+ * program, it must be redeclared in all fragment shaders in
+ * that program that have static assignments to
+ * gl_FragDepth. All redeclarations of gl_FragDepth in all
+ * fragment shaders in a single program must have the same set
+ * of qualifiers."
+ */
+ if (strcmp(var->name, "gl_FragDepth") == 0) {
+ bool layout_declared = var->data.depth_layout != nir_depth_layout_none;
+ bool layout_differs =
+ var->data.depth_layout != existing->data.depth_layout;
+
+ if (layout_declared && layout_differs) {
+ linker_error(prog,
+ "All redeclarations of gl_FragDepth in all "
+ "fragment shaders in a single program must have "
+ "the same set of qualifiers.\n");
+ }
+
+ if (var->data.used && layout_differs) {
+ linker_error(prog,
+ "If gl_FragDepth is redeclared with a layout "
+ "qualifier in any fragment shader, it must be "
+ "redeclared with the same layout qualifier in "
+ "all fragment shaders that have assignments to "
+ "gl_FragDepth\n");
+ }
+ }
+
+ /* Page 35 (page 41 of the PDF) of the GLSL 4.20 spec says:
+ *
+ * "If a shared global has multiple initializers, the
+ * initializers must all be constant expressions, and they
+ * must all have the same value. Otherwise, a link error will
+ * result. (A shared global having only one initializer does
+ * not require that initializer to be a constant expression.)"
+ *
+ * Previous to 4.20 the GLSL spec simply said that initializers
+ * must have the same value. In this case of non-constant
+ * initializers, this was impossible to determine. As a result,
+ * no vendor actually implemented that behavior. The 4.20
+ * behavior matches the implemented behavior of at least one other
+ * vendor, so we'll implement that for all GLSL versions.
+ * If (at least) one of these constant expressions is implicit,
+ * because it was added by glsl_zero_init, we skip the verification.
+ */
+ if (var->constant_initializer != NULL) {
+ if (existing->constant_initializer != NULL &&
+ !existing->data.is_implicit_initializer &&
+ !var->data.is_implicit_initializer) {
+ if (!nir_constant_compare(var->constant_initializer,
+ existing->constant_initializer)) {
+ linker_error(prog, "initializers for %s "
+ "`%s' have differing values\n",
+ gl_nir_mode_string(var), var->name);
+ return;
+ }
+ } else {
+ /* If the first-seen instance of a particular uniform did
+ * not have an initializer but a later instance does,
+ * replace the former with the later.
+ */
+ if (!var->data.is_implicit_initializer)
+ _mesa_hash_table_insert(variables, existing->name, var);
+ }
+ }
+
+ if (var->data.has_initializer) {
+ if (existing->data.has_initializer
+ && (var->constant_initializer == NULL
+ || existing->constant_initializer == NULL)) {
+ linker_error(prog,
+ "shared global variable `%s' has multiple "
+ "non-constant initializers.\n",
+ var->name);
+ return;
+ }
+ }
+
+ if (existing->data.explicit_invariant != var->data.explicit_invariant) {
+ linker_error(prog, "declarations for %s `%s' have "
+ "mismatching invariant qualifiers\n",
+ gl_nir_mode_string(var), var->name);
+ return;
+ }
+ if (existing->data.centroid != var->data.centroid) {
+ linker_error(prog, "declarations for %s `%s' have "
+ "mismatching centroid qualifiers\n",
+ gl_nir_mode_string(var), var->name);
+ return;
+ }
+ if (existing->data.sample != var->data.sample) {
+ linker_error(prog, "declarations for %s `%s` have "
+ "mismatching sample qualifiers\n",
+ gl_nir_mode_string(var), var->name);
+ return;
+ }
+ if (existing->data.image.format != var->data.image.format) {
+ linker_error(prog, "declarations for %s `%s` have "
+ "mismatching image format qualifiers\n",
+ gl_nir_mode_string(var), var->name);
+ return;
+ }
+
+ /* Check the precision qualifier matches for uniform variables on
+ * GLSL ES.
+ */
+ if (!consts->AllowGLSLRelaxedES &&
+ prog->IsES && !var->interface_type &&
+ existing->data.precision != var->data.precision) {
+ if ((existing->data.used && var->data.used) ||
+ prog->GLSL_Version >= 300) {
+ linker_error(prog, "declarations for %s `%s` have "
+ "mismatching precision qualifiers\n",
+ gl_nir_mode_string(var), var->name);
+ return;
+ } else {
+ linker_warning(prog, "declarations for %s `%s` have "
+ "mismatching precision qualifiers\n",
+ gl_nir_mode_string(var), var->name);
+ }
+ }
+
+ /* In OpenGL GLSL 3.20 spec, section 4.3.9:
+ *
+ * "It is a link-time error if any particular shader interface
+ * contains:
+ *
+ * - two different blocks, each having no instance name, and each
+ * having a member of the same name, or
+ *
+ * - a variable outside a block, and a block with no instance name,
+ * where the variable has the same name as a member in the block."
+ */
+ const glsl_type *var_itype = var->interface_type;
+ const glsl_type *existing_itype = existing->interface_type;
+ if (var_itype != existing_itype) {
+ if (!var_itype || !existing_itype) {
+ linker_error(prog, "declarations for %s `%s` are inside block "
+ "`%s` and outside a block",
+ gl_nir_mode_string(var), var->name,
+ glsl_get_type_name(var_itype ? var_itype : existing_itype));
+ return;
+ } else if (strcmp(glsl_get_type_name(var_itype), glsl_get_type_name(existing_itype)) != 0) {
+ linker_error(prog, "declarations for %s `%s` are inside blocks "
+ "`%s` and `%s`",
+ gl_nir_mode_string(var), var->name,
+ glsl_get_type_name(existing_itype),
+ glsl_get_type_name(var_itype));
+ return;
+ }
+ }
+ } else {
+ struct ifc_var *ifc_var = ralloc(mem_ctx, struct ifc_var);
+ ifc_var->var = var;
+ ifc_var->stage = shader->info.stage;
+ _mesa_hash_table_insert(variables, var->name, ifc_var);
+ }
+ }
+}
+
+/**
+ * Perform validation of uniforms used across multiple shader stages
+ */
+static void
+cross_validate_uniforms(const struct gl_constants *consts,
+ struct gl_shader_program *prog)
+{
+ void *mem_ctx = ralloc_context(NULL);
+ struct hash_table *variables =
+ _mesa_hash_table_create(mem_ctx, _mesa_hash_string, _mesa_key_string_equal);
+ for (unsigned i = 0; i < MESA_SHADER_STAGES; i++) {
+ if (prog->_LinkedShaders[i] == NULL)
+ continue;
+
+ cross_validate_globals(mem_ctx, consts, prog,
+ prog->_LinkedShaders[i]->Program->nir,
+ variables, true);
+ }
+
+ ralloc_free(mem_ctx);
+}
+
/**
* Initializes explicit location slots to INACTIVE_UNIFORM_EXPLICIT_LOCATION
* for a variable, checks for overlaps between other uniforms using explicit
@@ -2070,6 +2407,14 @@ gl_nir_link_glsl(const struct gl_constants *consts,
MESA_TRACE_FUNC();
+ /* Here begins the inter-stage linking phase. Some initial validation is
+ * performed, then locations are assigned for uniforms, attributes, and
+ * varyings.
+ */
+ cross_validate_uniforms(consts, prog);
+ if (!prog->data->LinkStatus)
+ return false;
+
check_explicit_uniform_locations(exts, prog);
link_assign_subroutine_types(prog);
diff --git a/src/compiler/glsl/linker.cpp b/src/compiler/glsl/linker.cpp
index 720b0f10003..ebb5d46a914 100644
--- a/src/compiler/glsl/linker.cpp
+++ b/src/compiler/glsl/linker.cpp
@@ -942,24 +942,6 @@ cross_validate_globals(const struct gl_constants *consts,
}
}
-
-/**
- * Perform validation of uniforms used across multiple shader stages
- */
-static void
-cross_validate_uniforms(const struct gl_constants *consts,
- struct gl_shader_program *prog)
-{
- glsl_symbol_table variables;
- for (unsigned i = 0; i < MESA_SHADER_STAGES; i++) {
- if (prog->_LinkedShaders[i] == NULL)
- continue;
-
- cross_validate_globals(consts, prog, prog->_LinkedShaders[i]->ir,
- &variables, true);
- }
-}
-
/**
* Populates a shaders symbol table with all global declarations
*/
@@ -2378,14 +2360,6 @@ link_shaders(struct gl_context *ctx, struct gl_shader_program *prog)
}
}
- /* Here begins the inter-stage linking phase. Some initial validation is
- * performed, then locations are assigned for uniforms, attributes, and
- * varyings.
- */
- cross_validate_uniforms(consts, prog);
- if (!prog->data->LinkStatus)
- goto done;
-
done:
for (unsigned i = 0; i < MESA_SHADER_STAGES; i++) {
free(shader_list[i]);