/**
- * Perform validation of uniforms used across multiple shader stages
+ * Generate a string describing the mode of a variable
+ */
+static const char *
+mode_string(const ir_variable *var)
+{
+ switch (var->mode) {
+ case ir_var_auto:
+ return (var->read_only) ? "global constant" : "global variable";
+
+ case ir_var_uniform: return "uniform";
+ case ir_var_in: return "shader input";
+ case ir_var_out: return "shader output";
+ case ir_var_inout: return "shader inout";
+ default:
+ assert(!"Should not get here.");
+ return "invalid variable";
+ }
+}
+
+
+/**
+ * Perform validation of global variables used across multiple shaders
*/
bool
-cross_validate_uniforms(struct gl_shader_program *prog)
+cross_validate_globals(struct gl_shader_program *prog,
+ struct gl_shader **shader_list,
+ unsigned num_shaders,
+ bool uniforms_only)
{
/* Examine all of the uniforms in all of the shaders and cross validate
* them.
*/
- glsl_symbol_table uniforms;
- for (unsigned i = 0; i < prog->_NumLinkedShaders; i++) {
- foreach_list(node, prog->_LinkedShaders[i]->ir) {
+ glsl_symbol_table variables;
+ for (unsigned i = 0; i < num_shaders; i++) {
+ foreach_list(node, shader_list[i]->ir) {
ir_variable *const var = ((ir_instruction *) node)->as_variable();
- if ((var == NULL) || (var->mode != ir_var_uniform))
+ if (var == NULL)
continue;
- /* If a uniform with this name has already been seen, verify that the
- * new instance has the same type. In addition, if the uniforms have
+ if (uniforms_only && (var->mode != ir_var_uniform))
+ 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.
*/
- ir_variable *const existing = uniforms.get_variable(var->name);
+ ir_variable *const existing = variables.get_variable(var->name);
if (existing != NULL) {
if (var->type != existing->type) {
- linker_error_printf(prog, "uniform `%s' declared as type "
+ linker_error_printf(prog, "%s `%s' declared as type "
"`%s' and type `%s'\n",
+ mode_string(var),
var->name, var->type->name,
existing->type->name);
return false;
}
+ /* FINISHME: Handle non-constant initializers.
+ */
if (var->constant_value != NULL) {
if (existing->constant_value != NULL) {
if (!var->constant_value->has_value(existing->constant_value)) {
- linker_error_printf(prog, "initializers for uniform "
+ linker_error_printf(prog, "initializers for %s "
"`%s' have differing values\n",
- var->name);
+ mode_string(var), var->name);
return false;
}
} else
* have an initializer but a later instance does, copy the
* initializer to the version stored in the symbol table.
*/
- existing->constant_value =
- (ir_constant *)var->constant_value->clone(NULL);
+ existing->constant_value = var->constant_value->clone(NULL);
}
} else
- uniforms.add_variable(var->name, var);
+ variables.add_variable(var->name, var);
}
}
}
+/**
+ * Perform validation of uniforms used across multiple shader stages
+ */
+bool
+cross_validate_uniforms(struct gl_shader_program *prog)
+{
+ return cross_validate_globals(prog, prog->_LinkedShaders,
+ prog->_NumLinkedShaders, true);
+}
+
+
/**
* Validate that outputs from one stage match inputs of another
*/
}
+/**
+ * Remap variables referenced in an instruction tree
+ *
+ * This is used when instruction trees are cloned from one shader and placed in
+ * another. These trees will contain references to \c ir_variable nodes that
+ * do not exist in the target shader. This function finds these \c ir_variable
+ * references and replaces the references with matching variables in the target
+ * shader.
+ *
+ * If there is no matching variable in the target shader, a clone of the
+ * \c ir_variable is made and added to the target shader. The new variable is
+ * added to \b both the instruction stream and the symbol table.
+ *
+ * \param inst IR tree that is to be processed.
+ * \param symbols Symbol table containing global scope symbols in the
+ * linked shader.
+ * \param instructions Instruction stream where new variable declarations
+ * should be added.
+ */
+void
+remap_variables(ir_instruction *inst, glsl_symbol_table *symbols,
+ exec_list *instructions)
+{
+ class remap_visitor : public ir_hierarchical_visitor {
+ public:
+ remap_visitor(glsl_symbol_table *symbols, exec_list *instructions)
+ {
+ this->symbols = symbols;
+ this->instructions = instructions;
+ }
+
+ virtual ir_visitor_status visit(ir_dereference_variable *ir)
+ {
+ ir_variable *const existing =
+ this->symbols->get_variable(ir->var->name);
+ if (existing != NULL)
+ ir->var = existing;
+ else {
+ ir_variable *copy = ir->var->clone(NULL);
+
+ this->symbols->add_variable(copy->name, copy);
+ this->instructions->push_head(copy);
+ }
+
+ return visit_continue;
+ }
+
+ private:
+ glsl_symbol_table *symbols;
+ exec_list *instructions;
+ };
+
+ remap_visitor v(symbols, instructions);
+
+ inst->accept(&v);
+}
+
+
+/**
+ * Move non-declarations from one instruction stream to another
+ *
+ * The intended usage pattern of this function is to pass the pointer to the
+ * head sentinal of a list (i.e., a pointer to the list cast to an \c exec_node
+ * pointer) for \c last and \c false for \c make_copies on the first
+ * call. Successive calls pass the return value of the previous call for
+ * \c last and \c true for \c make_copies.
+ *
+ * \param instructions Source instruction stream
+ * \param last Instruction after which new instructions should be
+ * inserted in the target instruction stream
+ * \param make_copies Flag selecting whether instructions in \c instructions
+ * should be copied (via \c ir_instruction::clone) into the
+ * target list or moved.
+ *
+ * \return
+ * The new "last" instruction in the target instruction stream. This pointer
+ * is suitable for use as the \c last parameter of a later call to this
+ * function.
+ */
+exec_node *
+move_non_declarations(exec_list *instructions, exec_node *last,
+ bool make_copies, gl_shader *target)
+{
+ foreach_list(node, instructions) {
+ ir_instruction *inst = (ir_instruction *) node;
+
+ if (inst->as_variable() || inst->as_function())
+ continue;
+
+ assert(inst->as_assignment());
+
+ if (make_copies) {
+ inst = inst->clone(NULL);
+ remap_variables(inst, target->symbols, target->ir);
+ } else {
+ inst->remove();
+ }
+
+ last->insert_after(inst);
+ last = inst;
+ }
+
+ return last;
+}
+
+/**
+ * Get the function signature for main from a shader
+ */
+static ir_function_signature *
+get_main_function_signature(gl_shader *sh)
+{
+ ir_function *const f = sh->symbols->get_function("main");
+ if (f != NULL) {
+ exec_list void_parameters;
+
+ /* Look for the 'void main()' signature and ensure that it's defined.
+ * This keeps the linker from accidentally pick a shader that just
+ * contains a prototype for main.
+ *
+ * We don't have to check for multiple definitions of main (in multiple
+ * shaders) because that would have already been caught above.
+ */
+ ir_function_signature *sig = f->matching_signature(&void_parameters);
+ if ((sig != NULL) && sig->is_defined) {
+ return sig;
+ }
+ }
+
+ return NULL;
+}
+
+
/**
* Combine a group of shaders for a single stage to generate a linked shader
*
struct gl_shader **shader_list,
unsigned num_shaders)
{
- (void) prog;
- assert(num_shaders == 1);
+ /* Check that global variables defined in multiple shaders are consistent.
+ */
+ if (!cross_validate_globals(prog, shader_list, num_shaders, false))
+ return NULL;
+
+ /* Check that there is only a single definition of each function signature
+ * across all shaders.
+ */
+ for (unsigned i = 0; i < (num_shaders - 1); i++) {
+ foreach_list(node, shader_list[i]->ir) {
+ ir_function *const f = ((ir_instruction *) node)->as_function();
+
+ if (f == NULL)
+ continue;
+
+ for (unsigned j = i + 1; j < num_shaders; j++) {
+ ir_function *const other =
+ shader_list[j]->symbols->get_function(f->name);
+
+ /* If the other shader has no function (and therefore no function
+ * signatures) with the same name, skip to the next shader.
+ */
+ if (other == NULL)
+ continue;
+
+ foreach_iter (exec_list_iterator, iter, *f) {
+ ir_function_signature *sig =
+ (ir_function_signature *) iter.get();
+
+ if (!sig->is_defined || sig->is_built_in)
+ continue;
+
+ ir_function_signature *other_sig =
+ other->exact_matching_signature(& sig->parameters);
+
+ if ((other_sig != NULL) && other_sig->is_defined
+ && !other_sig->is_built_in) {
+ linker_error_printf(prog,
+ "function `%s' is multiply defined",
+ f->name);
+ return NULL;
+ }
+ }
+ }
+ }
+ }
+
+ /* Find the shader that defines main, and make a clone of it.
+ *
+ * Starting with the clone, search for undefined references. If one is
+ * found, find the shader that defines it. Clone the reference and add
+ * it to the shader. Repeat until there are no undefined references or
+ * until a reference cannot be resolved.
+ */
+ gl_shader *main = NULL;
+ for (unsigned i = 0; i < num_shaders; i++) {
+ if (get_main_function_signature(shader_list[i]) != NULL) {
+ main = shader_list[i];
+ break;
+ }
+ }
+
+ if (main == NULL) {
+ linker_error_printf(prog, "%s shader lacks `main'\n",
+ (shader_list[0]->Type == GL_VERTEX_SHADER)
+ ? "vertex" : "fragment");
+ return NULL;
+ }
- gl_shader *const linked = _mesa_new_shader(NULL, 0, shader_list[0]->Type);
+ gl_shader *const linked = _mesa_new_shader(NULL, 0, main->Type);
linked->ir = new(linked) exec_list;
- clone_ir_list(linked->ir, shader_list[0]->ir);
+ clone_ir_list(linked->ir, main->ir);
populate_symbol_table(linked);
+ /* The a pointer to the main function in the final linked shader (i.e., the
+ * copy of the original shader that contained the main function).
+ */
+ ir_function_signature *const main_sig = get_main_function_signature(linked);
+
+ /* Move any instructions other than variable declarations or function
+ * declarations into main.
+ */
+ exec_node *insertion_point = (exec_node *) &main_sig->body;
+ for (unsigned i = 0; i < num_shaders; i++) {
+ insertion_point = move_non_declarations(shader_list[i]->ir,
+ insertion_point,
+ (shader_list[i] != main),
+ linked);
+ }
+
+ /* Resolve initializers for global variables in the linked shader.
+ */
+
return linked;
}
/* An 'out' variable is only really a shader output if its value is read
* by the following stage.
*/
- var->shader_out = (var->location != -1);
+ if (var->location == -1) {
+ var->shader_out = false;
+ var->mode = ir_var_auto;
+ }
}
foreach_list(node, consumer->ir) {