• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2021 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "source/opt/convert_to_sampled_image_pass.h"
16 
17 #include <cctype>
18 #include <cstring>
19 
20 #include "source/opt/ir_builder.h"
21 #include "source/util/make_unique.h"
22 #include "source/util/parse_number.h"
23 
24 namespace spvtools {
25 namespace opt {
26 
27 using VectorOfDescriptorSetAndBindingPairs =
28     std::vector<DescriptorSetAndBinding>;
29 using DescriptorSetBindingToInstruction =
30     ConvertToSampledImagePass::DescriptorSetBindingToInstruction;
31 
32 namespace {
33 
34 using utils::ParseNumber;
35 
36 // Returns true if the given char is ':', '\0' or considered as blank space
37 // (i.e.: '\n', '\r', '\v', '\t', '\f' and ' ').
IsSeparator(char ch)38 bool IsSeparator(char ch) {
39   return std::strchr(":\0", ch) || std::isspace(ch) != 0;
40 }
41 
42 // Reads characters starting from |str| until it meets a separator. Parses a
43 // number from the characters and stores it into |number|. Returns the pointer
44 // to the separator if it succeeds. Otherwise, returns nullptr.
ParseNumberUntilSeparator(const char * str,uint32_t * number)45 const char* ParseNumberUntilSeparator(const char* str, uint32_t* number) {
46   const char* number_begin = str;
47   while (!IsSeparator(*str)) str++;
48   const char* number_end = str;
49   std::string number_in_str(number_begin, number_end - number_begin);
50   if (!utils::ParseNumber(number_in_str.c_str(), number)) {
51     // The descriptor set is not a valid uint32 number.
52     return nullptr;
53   }
54   return str;
55 }
56 
57 // Returns id of the image type used for the sampled image type of
58 // |sampled_image|.
GetImageTypeOfSampledImage(analysis::TypeManager * type_mgr,Instruction * sampled_image)59 uint32_t GetImageTypeOfSampledImage(analysis::TypeManager* type_mgr,
60                                     Instruction* sampled_image) {
61   auto* sampled_image_type =
62       type_mgr->GetType(sampled_image->type_id())->AsSampledImage();
63   return type_mgr->GetTypeInstruction(sampled_image_type->image_type());
64 }
65 
66 // Finds the instruction whose id is |inst_id|. Follows the operand of
67 // OpCopyObject recursively if the opcode of the instruction is OpCopyObject
68 // and returns the first instruction that does not have OpCopyObject as opcode.
GetNonCopyObjectDef(analysis::DefUseManager * def_use_mgr,uint32_t inst_id)69 Instruction* GetNonCopyObjectDef(analysis::DefUseManager* def_use_mgr,
70                                  uint32_t inst_id) {
71   Instruction* inst = def_use_mgr->GetDef(inst_id);
72   while (inst->opcode() == spv::Op::OpCopyObject) {
73     inst_id = inst->GetSingleWordInOperand(0u);
74     inst = def_use_mgr->GetDef(inst_id);
75   }
76   return inst;
77 }
78 
79 }  // namespace
80 
GetDescriptorSetBinding(const Instruction & inst,DescriptorSetAndBinding * descriptor_set_binding) const81 bool ConvertToSampledImagePass::GetDescriptorSetBinding(
82     const Instruction& inst,
83     DescriptorSetAndBinding* descriptor_set_binding) const {
84   auto* decoration_manager = context()->get_decoration_mgr();
85   bool found_descriptor_set_to_convert = false;
86   bool found_binding_to_convert = false;
87   for (auto decorate :
88        decoration_manager->GetDecorationsFor(inst.result_id(), false)) {
89     spv::Decoration decoration =
90         spv::Decoration(decorate->GetSingleWordInOperand(1u));
91     if (decoration == spv::Decoration::DescriptorSet) {
92       if (found_descriptor_set_to_convert) {
93         assert(false && "A resource has two OpDecorate for the descriptor set");
94         return false;
95       }
96       descriptor_set_binding->descriptor_set =
97           decorate->GetSingleWordInOperand(2u);
98       found_descriptor_set_to_convert = true;
99     } else if (decoration == spv::Decoration::Binding) {
100       if (found_binding_to_convert) {
101         assert(false && "A resource has two OpDecorate for the binding");
102         return false;
103       }
104       descriptor_set_binding->binding = decorate->GetSingleWordInOperand(2u);
105       found_binding_to_convert = true;
106     }
107   }
108   return found_descriptor_set_to_convert && found_binding_to_convert;
109 }
110 
ShouldResourceBeConverted(const DescriptorSetAndBinding & descriptor_set_binding) const111 bool ConvertToSampledImagePass::ShouldResourceBeConverted(
112     const DescriptorSetAndBinding& descriptor_set_binding) const {
113   return descriptor_set_binding_pairs_.find(descriptor_set_binding) !=
114          descriptor_set_binding_pairs_.end();
115 }
116 
GetVariableType(const Instruction & variable) const117 const analysis::Type* ConvertToSampledImagePass::GetVariableType(
118     const Instruction& variable) const {
119   if (variable.opcode() != spv::Op::OpVariable) return nullptr;
120   auto* type = context()->get_type_mgr()->GetType(variable.type_id());
121   auto* pointer_type = type->AsPointer();
122   if (!pointer_type) return nullptr;
123 
124   return pointer_type->pointee_type();
125 }
126 
GetStorageClass(const Instruction & variable) const127 spv::StorageClass ConvertToSampledImagePass::GetStorageClass(
128     const Instruction& variable) const {
129   assert(variable.opcode() == spv::Op::OpVariable);
130   auto* type = context()->get_type_mgr()->GetType(variable.type_id());
131   auto* pointer_type = type->AsPointer();
132   if (!pointer_type) return spv::StorageClass::Max;
133 
134   return pointer_type->storage_class();
135 }
136 
CollectResourcesToConvert(DescriptorSetBindingToInstruction * descriptor_set_binding_pair_to_sampler,DescriptorSetBindingToInstruction * descriptor_set_binding_pair_to_image) const137 bool ConvertToSampledImagePass::CollectResourcesToConvert(
138     DescriptorSetBindingToInstruction* descriptor_set_binding_pair_to_sampler,
139     DescriptorSetBindingToInstruction* descriptor_set_binding_pair_to_image)
140     const {
141   for (auto& inst : context()->types_values()) {
142     const auto* variable_type = GetVariableType(inst);
143     if (variable_type == nullptr) continue;
144 
145     DescriptorSetAndBinding descriptor_set_binding;
146     if (!GetDescriptorSetBinding(inst, &descriptor_set_binding)) continue;
147 
148     if (!ShouldResourceBeConverted(descriptor_set_binding)) {
149       continue;
150     }
151 
152     if (variable_type->AsImage()) {
153       if (!descriptor_set_binding_pair_to_image
154                ->insert({descriptor_set_binding, &inst})
155                .second) {
156         return false;
157       }
158     } else if (variable_type->AsSampler()) {
159       if (!descriptor_set_binding_pair_to_sampler
160                ->insert({descriptor_set_binding, &inst})
161                .second) {
162         return false;
163       }
164     }
165   }
166   return true;
167 }
168 
Process()169 Pass::Status ConvertToSampledImagePass::Process() {
170   Status status = Status::SuccessWithoutChange;
171 
172   DescriptorSetBindingToInstruction descriptor_set_binding_pair_to_sampler,
173       descriptor_set_binding_pair_to_image;
174   if (!CollectResourcesToConvert(&descriptor_set_binding_pair_to_sampler,
175                                  &descriptor_set_binding_pair_to_image)) {
176     return Status::Failure;
177   }
178 
179   for (auto& image : descriptor_set_binding_pair_to_image) {
180     status = CombineStatus(
181         status, UpdateImageVariableToSampledImage(image.second, image.first));
182     if (status == Status::Failure) {
183       return status;
184     }
185   }
186 
187   for (const auto& sampler : descriptor_set_binding_pair_to_sampler) {
188     // Converting only a Sampler to Sampled Image is not allowed. It must have a
189     // corresponding image to combine the sampler with.
190     auto image_itr = descriptor_set_binding_pair_to_image.find(sampler.first);
191     if (image_itr == descriptor_set_binding_pair_to_image.end() ||
192         image_itr->second == nullptr) {
193       return Status::Failure;
194     }
195 
196     status = CombineStatus(
197         status, CheckUsesOfSamplerVariable(sampler.second, image_itr->second));
198     if (status == Status::Failure) {
199       return status;
200     }
201   }
202 
203   return status;
204 }
205 
FindUses(const Instruction * inst,std::vector<Instruction * > * uses,spv::Op user_opcode) const206 void ConvertToSampledImagePass::FindUses(const Instruction* inst,
207                                          std::vector<Instruction*>* uses,
208                                          spv::Op user_opcode) const {
209   auto* def_use_mgr = context()->get_def_use_mgr();
210   def_use_mgr->ForEachUser(inst, [uses, user_opcode, this](Instruction* user) {
211     if (user->opcode() == user_opcode) {
212       uses->push_back(user);
213     } else if (user->opcode() == spv::Op::OpCopyObject) {
214       FindUses(user, uses, user_opcode);
215     }
216   });
217 }
218 
FindUsesOfImage(const Instruction * image,std::vector<Instruction * > * uses) const219 void ConvertToSampledImagePass::FindUsesOfImage(
220     const Instruction* image, std::vector<Instruction*>* uses) const {
221   auto* def_use_mgr = context()->get_def_use_mgr();
222   def_use_mgr->ForEachUser(image, [uses, this](Instruction* user) {
223     switch (user->opcode()) {
224       case spv::Op::OpImageFetch:
225       case spv::Op::OpImageRead:
226       case spv::Op::OpImageWrite:
227       case spv::Op::OpImageQueryFormat:
228       case spv::Op::OpImageQueryOrder:
229       case spv::Op::OpImageQuerySizeLod:
230       case spv::Op::OpImageQuerySize:
231       case spv::Op::OpImageQueryLevels:
232       case spv::Op::OpImageQuerySamples:
233       case spv::Op::OpImageSparseFetch:
234         uses->push_back(user);
235       default:
236         break;
237     }
238     if (user->opcode() == spv::Op::OpCopyObject) {
239       FindUsesOfImage(user, uses);
240     }
241   });
242 }
243 
CreateImageExtraction(Instruction * sampled_image)244 Instruction* ConvertToSampledImagePass::CreateImageExtraction(
245     Instruction* sampled_image) {
246   InstructionBuilder builder(
247       context(), sampled_image->NextNode(),
248       IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
249   return builder.AddUnaryOp(
250       GetImageTypeOfSampledImage(context()->get_type_mgr(), sampled_image),
251       spv::Op::OpImage, sampled_image->result_id());
252 }
253 
GetSampledImageTypeForImage(Instruction * image_variable)254 uint32_t ConvertToSampledImagePass::GetSampledImageTypeForImage(
255     Instruction* image_variable) {
256   const auto* variable_type = GetVariableType(*image_variable);
257   if (variable_type == nullptr) return 0;
258   const auto* image_type = variable_type->AsImage();
259   if (image_type == nullptr) return 0;
260 
261   analysis::Image image_type_for_sampled_image(*image_type);
262   analysis::SampledImage sampled_image_type(&image_type_for_sampled_image);
263   return context()->get_type_mgr()->GetTypeInstruction(&sampled_image_type);
264 }
265 
UpdateImageUses(Instruction * sampled_image_load)266 Instruction* ConvertToSampledImagePass::UpdateImageUses(
267     Instruction* sampled_image_load) {
268   std::vector<Instruction*> uses_of_load;
269   FindUsesOfImage(sampled_image_load, &uses_of_load);
270   if (uses_of_load.empty()) return nullptr;
271 
272   auto* extracted_image = CreateImageExtraction(sampled_image_load);
273   for (auto* user : uses_of_load) {
274     user->SetInOperand(0, {extracted_image->result_id()});
275     context()->get_def_use_mgr()->AnalyzeInstUse(user);
276   }
277   return extracted_image;
278 }
279 
280 bool ConvertToSampledImagePass::
IsSamplerOfSampledImageDecoratedByDescriptorSetBinding(Instruction * sampled_image_inst,const DescriptorSetAndBinding & descriptor_set_binding)281     IsSamplerOfSampledImageDecoratedByDescriptorSetBinding(
282         Instruction* sampled_image_inst,
283         const DescriptorSetAndBinding& descriptor_set_binding) {
284   auto* def_use_mgr = context()->get_def_use_mgr();
285   uint32_t sampler_id = sampled_image_inst->GetSingleWordInOperand(1u);
286   auto* sampler_load = def_use_mgr->GetDef(sampler_id);
287   if (sampler_load->opcode() != spv::Op::OpLoad) return false;
288   auto* sampler = def_use_mgr->GetDef(sampler_load->GetSingleWordInOperand(0u));
289   DescriptorSetAndBinding sampler_descriptor_set_binding;
290   return GetDescriptorSetBinding(*sampler, &sampler_descriptor_set_binding) &&
291          sampler_descriptor_set_binding == descriptor_set_binding;
292 }
293 
UpdateSampledImageUses(Instruction * image_load,Instruction * image_extraction,const DescriptorSetAndBinding & image_descriptor_set_binding)294 void ConvertToSampledImagePass::UpdateSampledImageUses(
295     Instruction* image_load, Instruction* image_extraction,
296     const DescriptorSetAndBinding& image_descriptor_set_binding) {
297   std::vector<Instruction*> sampled_image_users;
298   FindUses(image_load, &sampled_image_users, spv::Op::OpSampledImage);
299 
300   auto* def_use_mgr = context()->get_def_use_mgr();
301   for (auto* sampled_image_inst : sampled_image_users) {
302     if (IsSamplerOfSampledImageDecoratedByDescriptorSetBinding(
303             sampled_image_inst, image_descriptor_set_binding)) {
304       context()->ReplaceAllUsesWith(sampled_image_inst->result_id(),
305                                     image_load->result_id());
306       def_use_mgr->AnalyzeInstUse(image_load);
307       context()->KillInst(sampled_image_inst);
308     } else {
309       if (!image_extraction)
310         image_extraction = CreateImageExtraction(image_load);
311       sampled_image_inst->SetInOperand(0, {image_extraction->result_id()});
312       def_use_mgr->AnalyzeInstUse(sampled_image_inst);
313     }
314   }
315 }
316 
MoveInstructionNextToType(Instruction * inst,uint32_t type_id)317 void ConvertToSampledImagePass::MoveInstructionNextToType(Instruction* inst,
318                                                           uint32_t type_id) {
319   auto* type_inst = context()->get_def_use_mgr()->GetDef(type_id);
320   inst->SetResultType(type_id);
321   inst->RemoveFromList();
322   inst->InsertAfter(type_inst);
323 }
324 
ConvertImageVariableToSampledImage(Instruction * image_variable,uint32_t sampled_image_type_id)325 bool ConvertToSampledImagePass::ConvertImageVariableToSampledImage(
326     Instruction* image_variable, uint32_t sampled_image_type_id) {
327   auto* sampled_image_type =
328       context()->get_type_mgr()->GetType(sampled_image_type_id);
329   if (sampled_image_type == nullptr) return false;
330   auto storage_class = GetStorageClass(*image_variable);
331   if (storage_class == spv::StorageClass::Max) return false;
332   analysis::Pointer sampled_image_pointer(sampled_image_type, storage_class);
333 
334   // Make sure |image_variable| is behind its type i.e., avoid the forward
335   // reference.
336   uint32_t type_id =
337       context()->get_type_mgr()->GetTypeInstruction(&sampled_image_pointer);
338   MoveInstructionNextToType(image_variable, type_id);
339   return true;
340 }
341 
UpdateImageVariableToSampledImage(Instruction * image_variable,const DescriptorSetAndBinding & descriptor_set_binding)342 Pass::Status ConvertToSampledImagePass::UpdateImageVariableToSampledImage(
343     Instruction* image_variable,
344     const DescriptorSetAndBinding& descriptor_set_binding) {
345   std::vector<Instruction*> image_variable_loads;
346   FindUses(image_variable, &image_variable_loads, spv::Op::OpLoad);
347   if (image_variable_loads.empty()) return Status::SuccessWithoutChange;
348 
349   const uint32_t sampled_image_type_id =
350       GetSampledImageTypeForImage(image_variable);
351   if (!sampled_image_type_id) return Status::Failure;
352 
353   for (auto* load : image_variable_loads) {
354     load->SetResultType(sampled_image_type_id);
355     auto* image_extraction = UpdateImageUses(load);
356     UpdateSampledImageUses(load, image_extraction, descriptor_set_binding);
357   }
358 
359   return ConvertImageVariableToSampledImage(image_variable,
360                                             sampled_image_type_id)
361              ? Status::SuccessWithChange
362              : Status::Failure;
363 }
364 
DoesSampledImageReferenceImage(Instruction * sampled_image_inst,Instruction * image_variable)365 bool ConvertToSampledImagePass::DoesSampledImageReferenceImage(
366     Instruction* sampled_image_inst, Instruction* image_variable) {
367   if (sampled_image_inst->opcode() != spv::Op::OpSampledImage) return false;
368   auto* def_use_mgr = context()->get_def_use_mgr();
369   auto* image_load = GetNonCopyObjectDef(
370       def_use_mgr, sampled_image_inst->GetSingleWordInOperand(0u));
371   if (image_load->opcode() != spv::Op::OpLoad) return false;
372   auto* image =
373       GetNonCopyObjectDef(def_use_mgr, image_load->GetSingleWordInOperand(0u));
374   return image->opcode() == spv::Op::OpVariable &&
375          image->result_id() == image_variable->result_id();
376 }
377 
CheckUsesOfSamplerVariable(const Instruction * sampler_variable,Instruction * image_to_be_combined_with)378 Pass::Status ConvertToSampledImagePass::CheckUsesOfSamplerVariable(
379     const Instruction* sampler_variable,
380     Instruction* image_to_be_combined_with) {
381   if (image_to_be_combined_with == nullptr) return Status::Failure;
382 
383   std::vector<Instruction*> sampler_variable_loads;
384   FindUses(sampler_variable, &sampler_variable_loads, spv::Op::OpLoad);
385   for (auto* load : sampler_variable_loads) {
386     std::vector<Instruction*> sampled_image_users;
387     FindUses(load, &sampled_image_users, spv::Op::OpSampledImage);
388     for (auto* sampled_image_inst : sampled_image_users) {
389       if (!DoesSampledImageReferenceImage(sampled_image_inst,
390                                           image_to_be_combined_with)) {
391         return Status::Failure;
392       }
393     }
394   }
395   return Status::SuccessWithoutChange;
396 }
397 
398 std::unique_ptr<VectorOfDescriptorSetAndBindingPairs>
ParseDescriptorSetBindingPairsString(const char * str)399 ConvertToSampledImagePass::ParseDescriptorSetBindingPairsString(
400     const char* str) {
401   if (!str) return nullptr;
402 
403   auto descriptor_set_binding_pairs =
404       MakeUnique<VectorOfDescriptorSetAndBindingPairs>();
405 
406   while (std::isspace(*str)) str++;  // skip leading spaces.
407 
408   // The parsing loop, break when points to the end.
409   while (*str) {
410     // Parse the descriptor set.
411     uint32_t descriptor_set = 0;
412     str = ParseNumberUntilSeparator(str, &descriptor_set);
413     if (str == nullptr) return nullptr;
414 
415     // Find the ':', spaces between the descriptor set and the ':' are not
416     // allowed.
417     if (*str++ != ':') {
418       // ':' not found
419       return nullptr;
420     }
421 
422     // Parse the binding.
423     uint32_t binding = 0;
424     str = ParseNumberUntilSeparator(str, &binding);
425     if (str == nullptr) return nullptr;
426 
427     descriptor_set_binding_pairs->push_back({descriptor_set, binding});
428 
429     // Skip trailing spaces.
430     while (std::isspace(*str)) str++;
431   }
432 
433   return descriptor_set_binding_pairs;
434 }
435 
436 }  // namespace opt
437 }  // namespace spvtools
438