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
34namespace {
35
36/**
37 * Visitor that splices varying packing code before every return.
38 */
39class lower_xfb_var_splicer : public ir_hierarchical_visitor
40{
41public:
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
51private:
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
68lower_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
74ir_visitor_status
75lower_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
83ir_visitor_status
84lower_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
91ir_visitor_status
92lower_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 */
98ir_visitor_status
99lower_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
116static char*
117get_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" */
136static char *
137generate_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. */
162static bool
163get_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
211ir_variable *
212lower_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