1 // Copyright (c) 2019 The Khronos Group Inc.
2 // Copyright (c) 2019 Valve Corporation
3 // Copyright (c) 2019 LunarG Inc.
4 //
5 // Licensed under the Apache License, Version 2.0 (the "License");
6 // you may not use this file except in compliance with the License.
7 // You may obtain a copy of the License at
8 //
9 // http://www.apache.org/licenses/LICENSE-2.0
10 //
11 // Unless required by applicable law or agreed to in writing, software
12 // distributed under the License is distributed on an "AS IS" BASIS,
13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 // See the License for the specific language governing permissions and
15 // limitations under the License.
16
17 #include "convert_to_half_pass.h"
18
19 #include "source/opt/ir_builder.h"
20
21 namespace spvtools {
22 namespace opt {
23 namespace {
24 // Indices of operands in SPIR-V instructions
25 constexpr int kImageSampleDrefIdInIdx = 2;
26 } // namespace
27
IsArithmetic(Instruction * inst)28 bool ConvertToHalfPass::IsArithmetic(Instruction* inst) {
29 return target_ops_core_.count(inst->opcode()) != 0 ||
30 (inst->opcode() == spv::Op::OpExtInst &&
31 inst->GetSingleWordInOperand(0) ==
32 context()->get_feature_mgr()->GetExtInstImportId_GLSLstd450() &&
33 target_ops_450_.count(inst->GetSingleWordInOperand(1)) != 0);
34 }
35
IsFloat(Instruction * inst,uint32_t width)36 bool ConvertToHalfPass::IsFloat(Instruction* inst, uint32_t width) {
37 uint32_t ty_id = inst->type_id();
38 if (ty_id == 0) return false;
39 return Pass::IsFloat(ty_id, width);
40 }
41
IsStruct(Instruction * inst)42 bool ConvertToHalfPass::IsStruct(Instruction* inst) {
43 uint32_t ty_id = inst->type_id();
44 if (ty_id == 0) return false;
45 Instruction* ty_inst = Pass::GetBaseType(ty_id);
46 return (ty_inst->opcode() == spv::Op::OpTypeStruct);
47 }
48
IsDecoratedRelaxed(Instruction * inst)49 bool ConvertToHalfPass::IsDecoratedRelaxed(Instruction* inst) {
50 uint32_t r_id = inst->result_id();
51 for (auto r_inst : get_decoration_mgr()->GetDecorationsFor(r_id, false))
52 if (r_inst->opcode() == spv::Op::OpDecorate &&
53 spv::Decoration(r_inst->GetSingleWordInOperand(1)) ==
54 spv::Decoration::RelaxedPrecision) {
55 return true;
56 }
57 return false;
58 }
59
IsRelaxed(uint32_t id)60 bool ConvertToHalfPass::IsRelaxed(uint32_t id) {
61 return relaxed_ids_set_.count(id) > 0;
62 }
63
AddRelaxed(uint32_t id)64 void ConvertToHalfPass::AddRelaxed(uint32_t id) { relaxed_ids_set_.insert(id); }
65
CanRelaxOpOperands(Instruction * inst)66 bool ConvertToHalfPass::CanRelaxOpOperands(Instruction* inst) {
67 return image_ops_.count(inst->opcode()) == 0;
68 }
69
FloatScalarType(uint32_t width)70 analysis::Type* ConvertToHalfPass::FloatScalarType(uint32_t width) {
71 analysis::Float float_ty(width);
72 return context()->get_type_mgr()->GetRegisteredType(&float_ty);
73 }
74
FloatVectorType(uint32_t v_len,uint32_t width)75 analysis::Type* ConvertToHalfPass::FloatVectorType(uint32_t v_len,
76 uint32_t width) {
77 analysis::Type* reg_float_ty = FloatScalarType(width);
78 analysis::Vector vec_ty(reg_float_ty, v_len);
79 return context()->get_type_mgr()->GetRegisteredType(&vec_ty);
80 }
81
FloatMatrixType(uint32_t v_cnt,uint32_t vty_id,uint32_t width)82 analysis::Type* ConvertToHalfPass::FloatMatrixType(uint32_t v_cnt,
83 uint32_t vty_id,
84 uint32_t width) {
85 Instruction* vty_inst = get_def_use_mgr()->GetDef(vty_id);
86 uint32_t v_len = vty_inst->GetSingleWordInOperand(1);
87 analysis::Type* reg_vec_ty = FloatVectorType(v_len, width);
88 analysis::Matrix mat_ty(reg_vec_ty, v_cnt);
89 return context()->get_type_mgr()->GetRegisteredType(&mat_ty);
90 }
91
EquivFloatTypeId(uint32_t ty_id,uint32_t width)92 uint32_t ConvertToHalfPass::EquivFloatTypeId(uint32_t ty_id, uint32_t width) {
93 analysis::Type* reg_equiv_ty;
94 Instruction* ty_inst = get_def_use_mgr()->GetDef(ty_id);
95 if (ty_inst->opcode() == spv::Op::OpTypeMatrix)
96 reg_equiv_ty = FloatMatrixType(ty_inst->GetSingleWordInOperand(1),
97 ty_inst->GetSingleWordInOperand(0), width);
98 else if (ty_inst->opcode() == spv::Op::OpTypeVector)
99 reg_equiv_ty = FloatVectorType(ty_inst->GetSingleWordInOperand(1), width);
100 else // spv::Op::OpTypeFloat
101 reg_equiv_ty = FloatScalarType(width);
102 return context()->get_type_mgr()->GetTypeInstruction(reg_equiv_ty);
103 }
104
GenConvert(uint32_t * val_idp,uint32_t width,Instruction * inst)105 void ConvertToHalfPass::GenConvert(uint32_t* val_idp, uint32_t width,
106 Instruction* inst) {
107 Instruction* val_inst = get_def_use_mgr()->GetDef(*val_idp);
108 uint32_t ty_id = val_inst->type_id();
109 uint32_t nty_id = EquivFloatTypeId(ty_id, width);
110 if (nty_id == ty_id) return;
111 Instruction* cvt_inst;
112 InstructionBuilder builder(
113 context(), inst,
114 IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
115 if (val_inst->opcode() == spv::Op::OpUndef)
116 cvt_inst = builder.AddNullaryOp(nty_id, spv::Op::OpUndef);
117 else
118 cvt_inst = builder.AddUnaryOp(nty_id, spv::Op::OpFConvert, *val_idp);
119 *val_idp = cvt_inst->result_id();
120 }
121
MatConvertCleanup(Instruction * inst)122 bool ConvertToHalfPass::MatConvertCleanup(Instruction* inst) {
123 if (inst->opcode() != spv::Op::OpFConvert) return false;
124 uint32_t mty_id = inst->type_id();
125 Instruction* mty_inst = get_def_use_mgr()->GetDef(mty_id);
126 if (mty_inst->opcode() != spv::Op::OpTypeMatrix) return false;
127 uint32_t vty_id = mty_inst->GetSingleWordInOperand(0);
128 uint32_t v_cnt = mty_inst->GetSingleWordInOperand(1);
129 Instruction* vty_inst = get_def_use_mgr()->GetDef(vty_id);
130 uint32_t cty_id = vty_inst->GetSingleWordInOperand(0);
131 Instruction* cty_inst = get_def_use_mgr()->GetDef(cty_id);
132 InstructionBuilder builder(
133 context(), inst,
134 IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
135 // Convert each component vector, combine them with OpCompositeConstruct
136 // and replace original instruction.
137 uint32_t orig_width = (cty_inst->GetSingleWordInOperand(0) == 16) ? 32 : 16;
138 uint32_t orig_mat_id = inst->GetSingleWordInOperand(0);
139 uint32_t orig_vty_id = EquivFloatTypeId(vty_id, orig_width);
140 std::vector<Operand> opnds = {};
141 for (uint32_t vidx = 0; vidx < v_cnt; ++vidx) {
142 Instruction* ext_inst = builder.AddIdLiteralOp(
143 orig_vty_id, spv::Op::OpCompositeExtract, orig_mat_id, vidx);
144 Instruction* cvt_inst =
145 builder.AddUnaryOp(vty_id, spv::Op::OpFConvert, ext_inst->result_id());
146 opnds.push_back({SPV_OPERAND_TYPE_ID, {cvt_inst->result_id()}});
147 }
148 uint32_t mat_id = TakeNextId();
149 std::unique_ptr<Instruction> mat_inst(new Instruction(
150 context(), spv::Op::OpCompositeConstruct, mty_id, mat_id, opnds));
151 (void)builder.AddInstruction(std::move(mat_inst));
152 context()->ReplaceAllUsesWith(inst->result_id(), mat_id);
153 // Turn original instruction into copy so it is valid.
154 inst->SetOpcode(spv::Op::OpCopyObject);
155 inst->SetResultType(EquivFloatTypeId(mty_id, orig_width));
156 get_def_use_mgr()->AnalyzeInstUse(inst);
157 return true;
158 }
159
RemoveRelaxedDecoration(uint32_t id)160 bool ConvertToHalfPass::RemoveRelaxedDecoration(uint32_t id) {
161 return context()->get_decoration_mgr()->RemoveDecorationsFrom(
162 id, [](const Instruction& dec) {
163 if (dec.opcode() == spv::Op::OpDecorate &&
164 spv::Decoration(dec.GetSingleWordInOperand(1u)) ==
165 spv::Decoration::RelaxedPrecision) {
166 return true;
167 } else
168 return false;
169 });
170 }
171
GenHalfArith(Instruction * inst)172 bool ConvertToHalfPass::GenHalfArith(Instruction* inst) {
173 bool modified = false;
174 // If this is a OpCompositeExtract instruction and has a struct operand, we
175 // should not relax this instruction. Doing so could cause a mismatch between
176 // the result type and the struct member type.
177 bool hasStructOperand = false;
178 if (inst->opcode() == spv::Op::OpCompositeExtract) {
179 inst->ForEachInId([&hasStructOperand, this](uint32_t* idp) {
180 Instruction* op_inst = get_def_use_mgr()->GetDef(*idp);
181 if (IsStruct(op_inst)) hasStructOperand = true;
182 });
183 if (hasStructOperand) {
184 return false;
185 }
186 }
187 // Convert all float32 based operands to float16 equivalent and change
188 // instruction type to float16 equivalent.
189 inst->ForEachInId([&inst, &modified, this](uint32_t* idp) {
190 Instruction* op_inst = get_def_use_mgr()->GetDef(*idp);
191 if (!IsFloat(op_inst, 32)) return;
192 GenConvert(idp, 16, inst);
193 modified = true;
194 });
195 if (IsFloat(inst, 32)) {
196 inst->SetResultType(EquivFloatTypeId(inst->type_id(), 16));
197 converted_ids_.insert(inst->result_id());
198 modified = true;
199 }
200 if (modified) get_def_use_mgr()->AnalyzeInstUse(inst);
201 return modified;
202 }
203
ProcessPhi(Instruction * inst,uint32_t from_width,uint32_t to_width)204 bool ConvertToHalfPass::ProcessPhi(Instruction* inst, uint32_t from_width,
205 uint32_t to_width) {
206 // Add converts of any float operands to to_width if they are of from_width.
207 // If converting to 16, change type of phi to float16 equivalent and remember
208 // result id. Converts need to be added to preceding blocks.
209 uint32_t ocnt = 0;
210 uint32_t* prev_idp;
211 bool modified = false;
212 inst->ForEachInId([&ocnt, &prev_idp, &from_width, &to_width, &modified,
213 this](uint32_t* idp) {
214 if (ocnt % 2 == 0) {
215 prev_idp = idp;
216 } else {
217 Instruction* val_inst = get_def_use_mgr()->GetDef(*prev_idp);
218 if (IsFloat(val_inst, from_width)) {
219 BasicBlock* bp = context()->get_instr_block(*idp);
220 auto insert_before = bp->tail();
221 if (insert_before != bp->begin()) {
222 --insert_before;
223 if (insert_before->opcode() != spv::Op::OpSelectionMerge &&
224 insert_before->opcode() != spv::Op::OpLoopMerge)
225 ++insert_before;
226 }
227 GenConvert(prev_idp, to_width, &*insert_before);
228 modified = true;
229 }
230 }
231 ++ocnt;
232 });
233 if (to_width == 16u) {
234 inst->SetResultType(EquivFloatTypeId(inst->type_id(), 16u));
235 converted_ids_.insert(inst->result_id());
236 modified = true;
237 }
238 if (modified) get_def_use_mgr()->AnalyzeInstUse(inst);
239 return modified;
240 }
241
ProcessConvert(Instruction * inst)242 bool ConvertToHalfPass::ProcessConvert(Instruction* inst) {
243 // If float32 and relaxed, change to float16 convert
244 if (IsFloat(inst, 32) && IsRelaxed(inst->result_id())) {
245 inst->SetResultType(EquivFloatTypeId(inst->type_id(), 16));
246 get_def_use_mgr()->AnalyzeInstUse(inst);
247 converted_ids_.insert(inst->result_id());
248 }
249 // If operand and result types are the same, change FConvert to CopyObject to
250 // keep validator happy; simplification and DCE will clean it up
251 // One way this can happen is if an FConvert generated during this pass
252 // (likely by ProcessPhi) is later encountered here and its operand has been
253 // changed to half.
254 uint32_t val_id = inst->GetSingleWordInOperand(0);
255 Instruction* val_inst = get_def_use_mgr()->GetDef(val_id);
256 if (inst->type_id() == val_inst->type_id())
257 inst->SetOpcode(spv::Op::OpCopyObject);
258 return true; // modified
259 }
260
ProcessImageRef(Instruction * inst)261 bool ConvertToHalfPass::ProcessImageRef(Instruction* inst) {
262 bool modified = false;
263 // If image reference, only need to convert dref args back to float32
264 if (dref_image_ops_.count(inst->opcode()) != 0) {
265 uint32_t dref_id = inst->GetSingleWordInOperand(kImageSampleDrefIdInIdx);
266 if (converted_ids_.count(dref_id) > 0) {
267 GenConvert(&dref_id, 32, inst);
268 inst->SetInOperand(kImageSampleDrefIdInIdx, {dref_id});
269 get_def_use_mgr()->AnalyzeInstUse(inst);
270 modified = true;
271 }
272 }
273 return modified;
274 }
275
ProcessDefault(Instruction * inst)276 bool ConvertToHalfPass::ProcessDefault(Instruction* inst) {
277 // If non-relaxed instruction has changed operands, need to convert
278 // them back to float32
279 if (inst->opcode() == spv::Op::OpPhi) return ProcessPhi(inst, 16u, 32u);
280 bool modified = false;
281 inst->ForEachInId([&inst, &modified, this](uint32_t* idp) {
282 if (converted_ids_.count(*idp) == 0) return;
283 uint32_t old_id = *idp;
284 GenConvert(idp, 32, inst);
285 if (*idp != old_id) modified = true;
286 });
287 if (modified) get_def_use_mgr()->AnalyzeInstUse(inst);
288 return modified;
289 }
290
GenHalfInst(Instruction * inst)291 bool ConvertToHalfPass::GenHalfInst(Instruction* inst) {
292 bool modified = false;
293 // Remember id for later deletion of RelaxedPrecision decoration
294 bool inst_relaxed = IsRelaxed(inst->result_id());
295 if (IsArithmetic(inst) && inst_relaxed)
296 modified = GenHalfArith(inst);
297 else if (inst->opcode() == spv::Op::OpPhi && inst_relaxed)
298 modified = ProcessPhi(inst, 32u, 16u);
299 else if (inst->opcode() == spv::Op::OpFConvert)
300 modified = ProcessConvert(inst);
301 else if (image_ops_.count(inst->opcode()) != 0)
302 modified = ProcessImageRef(inst);
303 else
304 modified = ProcessDefault(inst);
305 return modified;
306 }
307
CloseRelaxInst(Instruction * inst)308 bool ConvertToHalfPass::CloseRelaxInst(Instruction* inst) {
309 if (inst->result_id() == 0) return false;
310 if (IsRelaxed(inst->result_id())) return false;
311 if (!IsFloat(inst, 32)) return false;
312 if (IsDecoratedRelaxed(inst)) {
313 AddRelaxed(inst->result_id());
314 return true;
315 }
316 if (closure_ops_.count(inst->opcode()) == 0) return false;
317 // Can relax if all float operands are relaxed
318 bool relax = true;
319 bool hasStructOperand = false;
320 inst->ForEachInId([&relax, &hasStructOperand, this](uint32_t* idp) {
321 Instruction* op_inst = get_def_use_mgr()->GetDef(*idp);
322 if (IsStruct(op_inst)) hasStructOperand = true;
323 if (!IsFloat(op_inst, 32)) return;
324 if (!IsRelaxed(*idp)) relax = false;
325 });
326 // If the instruction has a struct operand, we should not relax it, even if
327 // all its uses are relaxed. Doing so could cause a mismatch between the
328 // result type and the struct member type.
329 if (hasStructOperand) {
330 return false;
331 }
332 if (relax) {
333 AddRelaxed(inst->result_id());
334 return true;
335 }
336 // Can relax if all uses are relaxed
337 relax = true;
338 get_def_use_mgr()->ForEachUser(inst, [&relax, this](Instruction* uinst) {
339 if (uinst->result_id() == 0 || !IsFloat(uinst, 32) ||
340 (!IsDecoratedRelaxed(uinst) && !IsRelaxed(uinst->result_id())) ||
341 !CanRelaxOpOperands(uinst)) {
342 relax = false;
343 return;
344 }
345 });
346 if (relax) {
347 AddRelaxed(inst->result_id());
348 return true;
349 }
350 return false;
351 }
352
ProcessFunction(Function * func)353 bool ConvertToHalfPass::ProcessFunction(Function* func) {
354 // Do a closure of Relaxed on composite and phi instructions
355 bool changed = true;
356 while (changed) {
357 changed = false;
358 cfg()->ForEachBlockInReversePostOrder(
359 func->entry().get(), [&changed, this](BasicBlock* bb) {
360 for (auto ii = bb->begin(); ii != bb->end(); ++ii)
361 changed |= CloseRelaxInst(&*ii);
362 });
363 }
364 // Do convert of relaxed instructions to half precision
365 bool modified = false;
366 cfg()->ForEachBlockInReversePostOrder(
367 func->entry().get(), [&modified, this](BasicBlock* bb) {
368 for (auto ii = bb->begin(); ii != bb->end(); ++ii)
369 modified |= GenHalfInst(&*ii);
370 });
371 // Replace invalid converts of matrix into equivalent vector extracts,
372 // converts and finally a composite construct
373 cfg()->ForEachBlockInReversePostOrder(
374 func->entry().get(), [&modified, this](BasicBlock* bb) {
375 for (auto ii = bb->begin(); ii != bb->end(); ++ii)
376 modified |= MatConvertCleanup(&*ii);
377 });
378 return modified;
379 }
380
ProcessImpl()381 Pass::Status ConvertToHalfPass::ProcessImpl() {
382 Pass::ProcessFunction pfn = [this](Function* fp) {
383 return ProcessFunction(fp);
384 };
385 bool modified = context()->ProcessReachableCallTree(pfn);
386 // If modified, make sure module has Float16 capability
387 if (modified) context()->AddCapability(spv::Capability::Float16);
388 // Remove all RelaxedPrecision decorations from instructions and globals
389 for (auto c_id : relaxed_ids_set_) {
390 modified |= RemoveRelaxedDecoration(c_id);
391 }
392 for (auto& val : get_module()->types_values()) {
393 uint32_t v_id = val.result_id();
394 if (v_id != 0) {
395 modified |= RemoveRelaxedDecoration(v_id);
396 }
397 }
398 return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
399 }
400
Process()401 Pass::Status ConvertToHalfPass::Process() {
402 Initialize();
403 return ProcessImpl();
404 }
405
Initialize()406 void ConvertToHalfPass::Initialize() {
407 target_ops_core_ = {
408 spv::Op::OpVectorExtractDynamic,
409 spv::Op::OpVectorInsertDynamic,
410 spv::Op::OpVectorShuffle,
411 spv::Op::OpCompositeConstruct,
412 spv::Op::OpCompositeInsert,
413 spv::Op::OpCompositeExtract,
414 spv::Op::OpCopyObject,
415 spv::Op::OpTranspose,
416 spv::Op::OpConvertSToF,
417 spv::Op::OpConvertUToF,
418 // spv::Op::OpFConvert,
419 // spv::Op::OpQuantizeToF16,
420 spv::Op::OpFNegate,
421 spv::Op::OpFAdd,
422 spv::Op::OpFSub,
423 spv::Op::OpFMul,
424 spv::Op::OpFDiv,
425 spv::Op::OpFMod,
426 spv::Op::OpVectorTimesScalar,
427 spv::Op::OpMatrixTimesScalar,
428 spv::Op::OpVectorTimesMatrix,
429 spv::Op::OpMatrixTimesVector,
430 spv::Op::OpMatrixTimesMatrix,
431 spv::Op::OpOuterProduct,
432 spv::Op::OpDot,
433 spv::Op::OpSelect,
434 spv::Op::OpFOrdEqual,
435 spv::Op::OpFUnordEqual,
436 spv::Op::OpFOrdNotEqual,
437 spv::Op::OpFUnordNotEqual,
438 spv::Op::OpFOrdLessThan,
439 spv::Op::OpFUnordLessThan,
440 spv::Op::OpFOrdGreaterThan,
441 spv::Op::OpFUnordGreaterThan,
442 spv::Op::OpFOrdLessThanEqual,
443 spv::Op::OpFUnordLessThanEqual,
444 spv::Op::OpFOrdGreaterThanEqual,
445 spv::Op::OpFUnordGreaterThanEqual,
446 };
447 target_ops_450_ = {
448 GLSLstd450Round, GLSLstd450RoundEven, GLSLstd450Trunc, GLSLstd450FAbs,
449 GLSLstd450FSign, GLSLstd450Floor, GLSLstd450Ceil, GLSLstd450Fract,
450 GLSLstd450Radians, GLSLstd450Degrees, GLSLstd450Sin, GLSLstd450Cos,
451 GLSLstd450Tan, GLSLstd450Asin, GLSLstd450Acos, GLSLstd450Atan,
452 GLSLstd450Sinh, GLSLstd450Cosh, GLSLstd450Tanh, GLSLstd450Asinh,
453 GLSLstd450Acosh, GLSLstd450Atanh, GLSLstd450Atan2, GLSLstd450Pow,
454 GLSLstd450Exp, GLSLstd450Log, GLSLstd450Exp2, GLSLstd450Log2,
455 GLSLstd450Sqrt, GLSLstd450InverseSqrt, GLSLstd450Determinant,
456 GLSLstd450MatrixInverse,
457 // TODO(greg-lunarg): GLSLstd450ModfStruct,
458 GLSLstd450FMin, GLSLstd450FMax, GLSLstd450FClamp, GLSLstd450FMix,
459 GLSLstd450Step, GLSLstd450SmoothStep, GLSLstd450Fma,
460 // TODO(greg-lunarg): GLSLstd450FrexpStruct,
461 GLSLstd450Ldexp, GLSLstd450Length, GLSLstd450Distance, GLSLstd450Cross,
462 GLSLstd450Normalize, GLSLstd450FaceForward, GLSLstd450Reflect,
463 GLSLstd450Refract, GLSLstd450NMin, GLSLstd450NMax, GLSLstd450NClamp};
464 image_ops_ = {spv::Op::OpImageSampleImplicitLod,
465 spv::Op::OpImageSampleExplicitLod,
466 spv::Op::OpImageSampleDrefImplicitLod,
467 spv::Op::OpImageSampleDrefExplicitLod,
468 spv::Op::OpImageSampleProjImplicitLod,
469 spv::Op::OpImageSampleProjExplicitLod,
470 spv::Op::OpImageSampleProjDrefImplicitLod,
471 spv::Op::OpImageSampleProjDrefExplicitLod,
472 spv::Op::OpImageFetch,
473 spv::Op::OpImageGather,
474 spv::Op::OpImageDrefGather,
475 spv::Op::OpImageRead,
476 spv::Op::OpImageSparseSampleImplicitLod,
477 spv::Op::OpImageSparseSampleExplicitLod,
478 spv::Op::OpImageSparseSampleDrefImplicitLod,
479 spv::Op::OpImageSparseSampleDrefExplicitLod,
480 spv::Op::OpImageSparseSampleProjImplicitLod,
481 spv::Op::OpImageSparseSampleProjExplicitLod,
482 spv::Op::OpImageSparseSampleProjDrefImplicitLod,
483 spv::Op::OpImageSparseSampleProjDrefExplicitLod,
484 spv::Op::OpImageSparseFetch,
485 spv::Op::OpImageSparseGather,
486 spv::Op::OpImageSparseDrefGather,
487 spv::Op::OpImageSparseTexelsResident,
488 spv::Op::OpImageSparseRead};
489 dref_image_ops_ = {
490 spv::Op::OpImageSampleDrefImplicitLod,
491 spv::Op::OpImageSampleDrefExplicitLod,
492 spv::Op::OpImageSampleProjDrefImplicitLod,
493 spv::Op::OpImageSampleProjDrefExplicitLod,
494 spv::Op::OpImageDrefGather,
495 spv::Op::OpImageSparseSampleDrefImplicitLod,
496 spv::Op::OpImageSparseSampleDrefExplicitLod,
497 spv::Op::OpImageSparseSampleProjDrefImplicitLod,
498 spv::Op::OpImageSparseSampleProjDrefExplicitLod,
499 spv::Op::OpImageSparseDrefGather,
500 };
501 closure_ops_ = {
502 spv::Op::OpVectorExtractDynamic,
503 spv::Op::OpVectorInsertDynamic,
504 spv::Op::OpVectorShuffle,
505 spv::Op::OpCompositeConstruct,
506 spv::Op::OpCompositeInsert,
507 spv::Op::OpCompositeExtract,
508 spv::Op::OpCopyObject,
509 spv::Op::OpTranspose,
510 spv::Op::OpPhi,
511 };
512 relaxed_ids_set_.clear();
513 converted_ids_.clear();
514 }
515
516 } // namespace opt
517 } // namespace spvtools
518