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