1/*
2 * Copyright 2021 Advanced Micro Devices, Inc.
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 DEALINGS
21 * IN THE SOFTWARE.
22 */
23
24#include "si_pipe.h"
25#include "nir.h"
26#include "nir_builder.h"
27#include "nir_worklist.h"
28
29
30static bool
31add_src_instr_to_worklist(nir_src *src, void *wl)
32{
33   if (!src->is_ssa)
34      return false;
35
36   nir_instr_worklist_push_tail(wl, src->ssa->parent_instr);
37   return true;
38}
39
40static int
41get_tex_unit(nir_tex_instr *tex)
42{
43   int tex_index = nir_tex_instr_src_index(tex, nir_tex_src_texture_deref);
44   if (tex_index >= 0) {
45      nir_deref_instr *deref = nir_src_as_deref(tex->src[tex_index].src);
46      nir_variable *var = nir_deref_instr_get_variable(deref);
47      return var ? var->data.binding : 0;
48   }
49   return -1;
50}
51
52static int
53check_instr_depends_on_tex(nir_intrinsic_instr *store)
54{
55   int texunit = -1;
56   struct set *instrs = _mesa_set_create(NULL, _mesa_hash_pointer,
57                                         _mesa_key_pointer_equal);
58   nir_instr_worklist *work = nir_instr_worklist_create();
59
60   _mesa_set_add(instrs, &store->instr);
61   add_src_instr_to_worklist(&store->src[0], work);
62
63   nir_foreach_instr_in_worklist(instr, work) {
64      /* Don't process an instruction twice */
65      if (_mesa_set_search(instrs, instr))
66         continue;
67
68      _mesa_set_add(instrs, instr);
69
70      if (instr->type == nir_instr_type_alu ||
71          instr->type == nir_instr_type_load_const) {
72         /* TODO: ubo, etc */
73         if (!nir_foreach_src(instr, add_src_instr_to_worklist, work))
74            break;
75         continue;
76      } else if (instr->type == nir_instr_type_tex) {
77         if (texunit != -1) {
78            /* We can only depend on a single tex */
79            texunit = -1;
80            break;
81         } else {
82            texunit = get_tex_unit(nir_instr_as_tex(instr));
83            continue;
84         }
85      } else {
86         break;
87      }
88   }
89
90   nir_instr_worklist_destroy(work);
91   _mesa_set_destroy(instrs, NULL);
92   return texunit;
93}
94
95static bool
96get_output_as_const_value(nir_shader *shader, float values[4])
97{
98   nir_foreach_function(function, shader) {
99      nir_foreach_block_reverse(block, function->impl) {
100         nir_foreach_instr_reverse_safe(instr, block) {
101            switch (instr->type) {
102               case nir_instr_type_intrinsic: {
103                  nir_intrinsic_instr *intrin = nir_instr_as_intrinsic(instr);
104                  if (intrin->intrinsic == nir_intrinsic_store_output) {
105                     nir_const_value *c = nir_src_as_const_value(intrin->src[0]);
106                     if (c) {
107                        nir_const_value_to_array(values, c, 4, f32);
108                        return true;
109                     }
110                     return false;
111                  }
112                  FALLTHROUGH;
113               }
114               default:
115                  continue;
116            }
117         }
118      }
119   }
120   return false;
121}
122
123struct replace_param {
124   float value[4];
125   int *texunit;
126};
127
128static bool
129store_instr_depends_on_tex(nir_builder *b, nir_instr *instr, void *state)
130{
131   if (instr->type != nir_instr_type_intrinsic)
132      return false;
133
134   nir_intrinsic_instr *intrin = nir_instr_as_intrinsic(instr);
135   if (intrin->intrinsic != nir_intrinsic_store_output)
136      return false;
137
138   struct replace_param *p = (struct replace_param*) state;
139   *(p->texunit) = check_instr_depends_on_tex(intrin);
140
141   return *(p->texunit) != -1;
142}
143
144
145static bool
146replace_tex_by_imm(nir_builder *b, nir_instr *instr, void *state)
147{
148   if (instr->type != nir_instr_type_tex)
149      return false;
150
151   nir_tex_instr *tex = nir_instr_as_tex(instr);
152   struct replace_param *p = (struct replace_param*) state;
153
154   if (get_tex_unit(tex) != *(p->texunit))
155      return false;
156
157   b->cursor = nir_instr_remove(&tex->instr);
158   nir_ssa_def *imm = nir_imm_vec4(b, p->value[0], p->value[1], p->value[2], p->value[3]);
159   nir_ssa_def_rewrite_uses(&tex->dest.ssa, imm);
160   return true;
161}
162
163
164/* This function returns true if a shader' sole output becomes constant when
165 * a given texunit is replaced by a constant value.
166 * The input constant value is passed as 'in' and the determined constant
167 * value is stored in 'out'. The texunit is also remembered.
168 */
169bool
170si_nir_is_output_const_if_tex_is_const(nir_shader *shader, float *in, float *out, int *texunit)
171{
172   assert(shader->info.stage == MESA_SHADER_FRAGMENT);
173
174   if (BITSET_COUNT(shader->info.textures_used) == 0 ||
175       util_bitcount64(shader->info.outputs_written) != 1)
176      return false;
177
178   struct replace_param p;
179   memcpy(p.value, in, 4 * sizeof(float));
180   p.texunit = texunit;
181
182   /* Test if the single store_output only depends on constants and a single texture op */
183   if (nir_shader_instructions_pass(shader, store_instr_depends_on_tex, nir_metadata_all, &p)) {
184      assert(*p.texunit != -1);
185
186      /* Replace nir_tex_instr using texunit by vec4(v) */
187      nir_shader_instructions_pass(shader, replace_tex_by_imm,
188                                   nir_metadata_block_index |
189                                   nir_metadata_dominance, &p);
190
191      /* Optimize the cloned shader */
192      bool progress;
193      do {
194         progress = false;
195         NIR_PASS(progress, shader, nir_copy_prop);
196         NIR_PASS(progress, shader, nir_opt_remove_phis);
197         NIR_PASS(progress, shader, nir_opt_dce);
198         NIR_PASS(progress, shader, nir_opt_dead_cf);
199         NIR_PASS(progress, shader, nir_opt_algebraic);
200         NIR_PASS(progress, shader, nir_opt_constant_folding);
201      } while (progress);
202
203      /* Is the output a constant value? */
204      if (get_output_as_const_value(shader, out))
205         return true;
206   }
207
208   return false;
209}
210