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 // Make sure |image_variable| is behind its type i.e., avoid the forward
333 // reference.
334 uint32_t type_id = context()->get_type_mgr()->FindPointerToType(
335 sampled_image_type_id, storage_class);
336 MoveInstructionNextToType(image_variable, type_id);
337 return true;
338 }
339
UpdateImageVariableToSampledImage(Instruction * image_variable,const DescriptorSetAndBinding & descriptor_set_binding)340 Pass::Status ConvertToSampledImagePass::UpdateImageVariableToSampledImage(
341 Instruction* image_variable,
342 const DescriptorSetAndBinding& descriptor_set_binding) {
343 std::vector<Instruction*> image_variable_loads;
344 FindUses(image_variable, &image_variable_loads, spv::Op::OpLoad);
345 if (image_variable_loads.empty()) return Status::SuccessWithoutChange;
346
347 const uint32_t sampled_image_type_id =
348 GetSampledImageTypeForImage(image_variable);
349 if (!sampled_image_type_id) return Status::Failure;
350
351 for (auto* load : image_variable_loads) {
352 load->SetResultType(sampled_image_type_id);
353 auto* image_extraction = UpdateImageUses(load);
354 UpdateSampledImageUses(load, image_extraction, descriptor_set_binding);
355 }
356
357 return ConvertImageVariableToSampledImage(image_variable,
358 sampled_image_type_id)
359 ? Status::SuccessWithChange
360 : Status::Failure;
361 }
362
DoesSampledImageReferenceImage(Instruction * sampled_image_inst,Instruction * image_variable)363 bool ConvertToSampledImagePass::DoesSampledImageReferenceImage(
364 Instruction* sampled_image_inst, Instruction* image_variable) {
365 if (sampled_image_inst->opcode() != spv::Op::OpSampledImage) return false;
366 auto* def_use_mgr = context()->get_def_use_mgr();
367 auto* image_load = GetNonCopyObjectDef(
368 def_use_mgr, sampled_image_inst->GetSingleWordInOperand(0u));
369 if (image_load->opcode() != spv::Op::OpLoad) return false;
370 auto* image =
371 GetNonCopyObjectDef(def_use_mgr, image_load->GetSingleWordInOperand(0u));
372 return image->opcode() == spv::Op::OpVariable &&
373 image->result_id() == image_variable->result_id();
374 }
375
CheckUsesOfSamplerVariable(const Instruction * sampler_variable,Instruction * image_to_be_combined_with)376 Pass::Status ConvertToSampledImagePass::CheckUsesOfSamplerVariable(
377 const Instruction* sampler_variable,
378 Instruction* image_to_be_combined_with) {
379 if (image_to_be_combined_with == nullptr) return Status::Failure;
380
381 std::vector<Instruction*> sampler_variable_loads;
382 FindUses(sampler_variable, &sampler_variable_loads, spv::Op::OpLoad);
383 for (auto* load : sampler_variable_loads) {
384 std::vector<Instruction*> sampled_image_users;
385 FindUses(load, &sampled_image_users, spv::Op::OpSampledImage);
386 for (auto* sampled_image_inst : sampled_image_users) {
387 if (!DoesSampledImageReferenceImage(sampled_image_inst,
388 image_to_be_combined_with)) {
389 return Status::Failure;
390 }
391 }
392 }
393 return Status::SuccessWithoutChange;
394 }
395
396 std::unique_ptr<VectorOfDescriptorSetAndBindingPairs>
ParseDescriptorSetBindingPairsString(const char * str)397 ConvertToSampledImagePass::ParseDescriptorSetBindingPairsString(
398 const char* str) {
399 if (!str) return nullptr;
400
401 auto descriptor_set_binding_pairs =
402 MakeUnique<VectorOfDescriptorSetAndBindingPairs>();
403
404 while (std::isspace(*str)) str++; // skip leading spaces.
405
406 // The parsing loop, break when points to the end.
407 while (*str) {
408 // Parse the descriptor set.
409 uint32_t descriptor_set = 0;
410 str = ParseNumberUntilSeparator(str, &descriptor_set);
411 if (str == nullptr) return nullptr;
412
413 // Find the ':', spaces between the descriptor set and the ':' are not
414 // allowed.
415 if (*str++ != ':') {
416 // ':' not found
417 return nullptr;
418 }
419
420 // Parse the binding.
421 uint32_t binding = 0;
422 str = ParseNumberUntilSeparator(str, &binding);
423 if (str == nullptr) return nullptr;
424
425 descriptor_set_binding_pairs->push_back({descriptor_set, binding});
426
427 // Skip trailing spaces.
428 while (std::isspace(*str)) str++;
429 }
430
431 return descriptor_set_binding_pairs;
432 }
433
434 } // namespace opt
435 } // namespace spvtools
436