1 /*
2  * Copyright ©2019 Collabora Ltd.
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a
5  * copy of this software and associated documentation files (the "Software"),
6  * to deal in the Software without restriction, including without limitation
7  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8  * and/or sell copies of the Software, and to permit persons to whom the
9  * Software is furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice (including the next
12  * paragraph) shall be included in all copies or substantial portions of the
13  * Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
18  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21  * DEALINGS IN THE SOFTWARE.
22  */
23 
24 /**
25  * \file lower_xfb_varying.cpp
26  *
27  */
28 
29 #include "ir.h"
30 #include "main/mtypes.h"
31 #include "glsl_symbol_table.h"
32 #include "util/strndup.h"
33 
34 namespace {
35 
36 /**
37  * Visitor that splices varying packing code before every return.
38  */
39 class lower_xfb_var_splicer : public ir_hierarchical_visitor
40 {
41 public:
42    explicit lower_xfb_var_splicer(void *mem_ctx,
43                                   gl_shader_stage stage,
44                                   const exec_list *instructions);
45 
46    ir_visitor_status append_instructions(exec_node *node);
47    virtual ir_visitor_status visit_leave(ir_return *ret);
48    virtual ir_visitor_status visit_leave(ir_function_signature *sig);
49    virtual ir_visitor_status visit_leave(ir_emit_vertex *emit);
50 
51 private:
52    /**
53     * Memory context used to allocate new instructions for the shader.
54     */
55    void * const mem_ctx;
56 
57    gl_shader_stage stage;
58 
59    /**
60     * Instructions that should be spliced into place before each return and EmitVertex().
61     */
62    const exec_list *instructions;
63 };
64 
65 } /* anonymous namespace */
66 
67 
lower_xfb_var_splicer(void * mem_ctx,gl_shader_stage stage,const exec_list * instructions)68 lower_xfb_var_splicer::lower_xfb_var_splicer(void *mem_ctx, gl_shader_stage stage,
69                                              const exec_list *instructions)
70    : mem_ctx(mem_ctx), stage(stage), instructions(instructions)
71 {
72 }
73 
74 ir_visitor_status
append_instructions(exec_node * node)75 lower_xfb_var_splicer::append_instructions(exec_node *node)
76 {
77    foreach_in_list(ir_instruction, ir, this->instructions) {
78       node->insert_before(ir->clone(this->mem_ctx, NULL));
79    }
80    return visit_continue;
81 }
82 
83 ir_visitor_status
visit_leave(ir_return * ret)84 lower_xfb_var_splicer::visit_leave(ir_return *ret)
85 {
86    if (stage != MESA_SHADER_VERTEX)
87       return visit_continue;
88    return append_instructions(ret);
89 }
90 
91 ir_visitor_status
visit_leave(ir_emit_vertex * emit)92 lower_xfb_var_splicer::visit_leave(ir_emit_vertex *emit)
93 {
94    return append_instructions(emit);
95 }
96 
97 /** Insert a copy-back assignment at the end of the main() function */
98 ir_visitor_status
visit_leave(ir_function_signature * sig)99 lower_xfb_var_splicer::visit_leave(ir_function_signature *sig)
100 {
101    if (strcmp(sig->function_name(), "main") != 0)
102       return visit_continue;
103 
104    if (this->stage == MESA_SHADER_VERTEX) {
105       if (((ir_instruction*)sig->body.get_tail())->ir_type == ir_type_return)
106          return visit_continue;
107 
108       foreach_in_list(ir_instruction, ir, this->instructions) {
109          sig->body.push_tail(ir->clone(this->mem_ctx, NULL));
110       }
111    }
112 
113    return visit_continue;
114 }
115 
116 static char*
get_field_name(const char * name)117 get_field_name(const char *name)
118 {
119    const char *first_dot = strchr(name, '.');
120    const char *first_square_bracket = strchr(name, '[');
121    int name_size = 0;
122 
123    if (!first_square_bracket && !first_dot)
124       name_size = strlen(name);
125    else if ((!first_square_bracket ||
126             (first_dot && first_dot < first_square_bracket)))
127       name_size = first_dot - name;
128    else
129       name_size = first_square_bracket - name;
130 
131    return strndup(name, name_size);
132 }
133 
134 /* Generate a new name given the old xfb declaration string by replacing dots
135  * with '_', brackets with '@' and appending "-xfb" */
136 static char *
generate_new_name(void * mem_ctx,const char * name)137 generate_new_name(void *mem_ctx, const char *name)
138 {
139    char *new_name;
140    unsigned i = 0;
141 
142    new_name = ralloc_strdup(mem_ctx, name);
143    while (new_name[i]) {
144       if (new_name[i] == '.') {
145          new_name[i] = '_';
146       } else if (new_name[i] == '[' || new_name[i] == ']') {
147          new_name[i] = '@';
148       }
149       i++;
150    }
151 
152    if (!ralloc_strcat(&new_name, "-xfb")) {
153       ralloc_free(new_name);
154       return NULL;
155    }
156 
157    return new_name;
158 }
159 
160 /* Get the dereference for the given variable name. The method is called
161  * recursively to parse array indices and struct members. */
162 static bool
get_deref(void * ctx,const char * name,struct gl_linked_shader * shader,ir_dereference ** deref,const glsl_type ** type)163 get_deref(void *ctx,
164           const char *name,
165           struct gl_linked_shader *shader,
166           ir_dereference **deref,
167           const glsl_type **type)
168 {
169    if (name[0] == '\0') {
170       /* End */
171       return (*deref != NULL);
172    } else if (name[0] == '[') {
173       /* Array index */
174       char *endptr = NULL;
175       unsigned index;
176 
177       index = strtol(name + 1, &endptr, 10);
178       assert(*type != NULL && (*type)->is_array() && endptr[0] == ']');
179       *deref = new(ctx) ir_dereference_array(*deref, new(ctx) ir_constant(index));
180       *type = (*type)->without_array();
181       return get_deref(ctx, endptr + 1, shader, deref, type);
182    } else if (name[0] == '.') {
183       /* Struct member */
184       char *field = get_field_name(name + 1);
185 
186       assert(*type != NULL && (*type)->is_struct() && field != NULL);
187       *deref = new(ctx) ir_dereference_record(*deref, field);
188       *type = (*type)->field_type(field);
189       assert(*type != glsl_type::error_type);
190       name += 1 + strlen(field);
191       free(field);
192       return get_deref(ctx, name, shader, deref, type);
193    } else {
194       /* Top level variable */
195       char *field = get_field_name(name);
196       ir_variable *toplevel_var;
197 
198       toplevel_var = shader->symbols->get_variable(field);
199       name += strlen(field);
200       free(field);
201       if (toplevel_var == NULL) {
202          return false;
203       }
204 
205       *deref = new (ctx) ir_dereference_variable(toplevel_var);
206       *type = toplevel_var->type;
207       return get_deref(ctx, name, shader, deref, type);
208    }
209 }
210 
211 ir_variable *
lower_xfb_varying(void * mem_ctx,struct gl_linked_shader * shader,const char * old_var_name)212 lower_xfb_varying(void *mem_ctx,
213                   struct gl_linked_shader *shader,
214                   const char *old_var_name)
215 {
216    exec_list new_instructions;
217    char *new_var_name;
218    ir_dereference *deref = NULL;
219    const glsl_type *type = NULL;
220 
221    if (!get_deref(mem_ctx, old_var_name, shader, &deref, &type)) {
222       if (deref) {
223          delete deref;
224       }
225       return NULL;
226    }
227 
228    new_var_name = generate_new_name(mem_ctx, old_var_name);
229    ir_variable *new_variable
230       = new(mem_ctx) ir_variable(type, new_var_name, ir_var_shader_out);
231    new_variable->data.assigned = true;
232    new_variable->data.used = true;
233    shader->ir->push_head(new_variable);
234    ralloc_free(new_var_name);
235 
236    ir_dereference *lhs = new(mem_ctx) ir_dereference_variable(new_variable);
237    ir_assignment *new_assignment = new(mem_ctx) ir_assignment(lhs, deref);
238    new_instructions.push_tail(new_assignment);
239 
240    lower_xfb_var_splicer splicer(mem_ctx, shader->Stage, &new_instructions);
241    visit_list_elements(&splicer, shader->ir);
242 
243    return new_variable;
244 }
245