opt: split composite from array flattening (#5733)
* opt: split composite from array flattening
DXC has an option to flatten resource arrays. But when this option
is not used, the resource arrays should be kept as-is.
On the other hand, when a struct contains resources, we MUST flatten is
to be compliant with the Vulkan spec.
Because this pass flattens both types of resources, using a struct of
resources automatically implied flattening arrays.
By adding those 2 new settings, we decide if the pass flattens only one type
of resources, or both.
Note: the flatten_arrays flag only impacts resource arrays.
Arrays of composites containing resources are still flattened.
Since the API is considered stable, I added 2 new functions to create
passes with one flag or the other, and kept the original behavior as-is.
Related to https://github.com/microsoft/DirectXShaderCompiler/issues/6745
Signed-off-by: Nathan Gauër <brioche@google.com>
* add commandline options
Signed-off-by: Nathan Gauër <brioche@google.com>
* clang-format
Signed-off-by: Nathan Gauër <brioche@google.com>
---------
Signed-off-by: Nathan Gauër <brioche@google.com>
diff --git a/include/spirv-tools/optimizer.hpp b/include/spirv-tools/optimizer.hpp
index 216e68a..fe02ec7 100644
--- a/include/spirv-tools/optimizer.hpp
+++ b/include/spirv-tools/optimizer.hpp
@@ -827,14 +827,19 @@
// Create descriptor scalar replacement pass.
// This pass replaces every array variable |desc| that has a DescriptorSet and
-// Binding decorations with a new variable for each element of the array.
-// Suppose |desc| was bound at binding |b|. Then the variable corresponding to
-// |desc[i]| will have binding |b+i|. The descriptor set will be the same. It
-// is assumed that no other variable already has a binding that will used by one
-// of the new variables. If not, the pass will generate invalid Spir-V. All
-// accesses to |desc| must be OpAccessChain instructions with a literal index
-// for the first index.
+// Binding decorations with a new variable for each element of the
+// array/composite. Suppose |desc| was bound at binding |b|. Then the variable
+// corresponding to |desc[i]| will have binding |b+i|. The descriptor set will
+// be the same. It is assumed that no other variable already has a binding that
+// will used by one of the new variables. If not, the pass will generate
+// invalid Spir-V. All accesses to |desc| must be OpAccessChain instructions
+// with a literal index for the first index. This variant flattens both
+// composites and arrays.
Optimizer::PassToken CreateDescriptorScalarReplacementPass();
+// This variant flattens only composites.
+Optimizer::PassToken CreateDescriptorCompositeScalarReplacementPass();
+// This variant flattens only arrays.
+Optimizer::PassToken CreateDescriptorArrayScalarReplacementPass();
// Create a pass to replace each OpKill instruction with a function call to a
// function that has a single OpKill. Also replace each OpTerminateInvocation
diff --git a/source/opt/desc_sroa.cpp b/source/opt/desc_sroa.cpp
index 2c0f482..124a3d3 100644
--- a/source/opt/desc_sroa.cpp
+++ b/source/opt/desc_sroa.cpp
@@ -31,11 +31,14 @@
Pass::Status DescriptorScalarReplacement::Process() {
bool modified = false;
-
std::vector<Instruction*> vars_to_kill;
for (Instruction& var : context()->types_values()) {
- if (descsroautil::IsDescriptorArray(context(), &var)) {
+ bool is_candidate =
+ flatten_arrays_ && descsroautil::IsDescriptorArray(context(), &var);
+ is_candidate |= flatten_composites_ &&
+ descsroautil::IsDescriptorStruct(context(), &var);
+ if (is_candidate) {
modified = true;
if (!ReplaceCandidate(&var)) {
return Status::Failure;
diff --git a/source/opt/desc_sroa.h b/source/opt/desc_sroa.h
index 901be3e..d6af4df 100644
--- a/source/opt/desc_sroa.h
+++ b/source/opt/desc_sroa.h
@@ -32,9 +32,16 @@
// Documented in optimizer.hpp
class DescriptorScalarReplacement : public Pass {
public:
- DescriptorScalarReplacement() {}
+ DescriptorScalarReplacement(bool flatten_composites, bool flatten_arrays)
+ : flatten_composites_(flatten_composites),
+ flatten_arrays_(flatten_arrays) {}
- const char* name() const override { return "descriptor-scalar-replacement"; }
+ const char* name() const override {
+ if (flatten_composites_ && flatten_arrays_)
+ return "descriptor-scalar-replacement";
+ if (flatten_composites_) return "descriptor-compososite-scalar-replacement";
+ return "descriptor-array-scalar-replacement";
+ }
Status Process() override;
@@ -141,6 +148,9 @@
// array |var|. If the entry is |0|, then the variable has not been
// created yet.
std::map<Instruction*, std::vector<uint32_t>> replacement_variables_;
+
+ bool flatten_composites_;
+ bool flatten_arrays_;
};
} // namespace opt
diff --git a/source/opt/desc_sroa_util.cpp b/source/opt/desc_sroa_util.cpp
index dba3de9..62d9476 100644
--- a/source/opt/desc_sroa_util.cpp
+++ b/source/opt/desc_sroa_util.cpp
@@ -29,41 +29,58 @@
return length_const->GetU32();
}
-} // namespace
+bool HasDescriptorDecorations(IRContext* context, Instruction* var) {
+ const auto& decoration_mgr = context->get_decoration_mgr();
+ return decoration_mgr->HasDecoration(
+ var->result_id(), uint32_t(spv::Decoration::DescriptorSet)) &&
+ decoration_mgr->HasDecoration(var->result_id(),
+ uint32_t(spv::Decoration::Binding));
+}
-namespace descsroautil {
-
-bool IsDescriptorArray(IRContext* context, Instruction* var) {
+Instruction* GetVariableType(IRContext* context, Instruction* var) {
if (var->opcode() != spv::Op::OpVariable) {
- return false;
+ return nullptr;
}
uint32_t ptr_type_id = var->type_id();
Instruction* ptr_type_inst = context->get_def_use_mgr()->GetDef(ptr_type_id);
if (ptr_type_inst->opcode() != spv::Op::OpTypePointer) {
- return false;
+ return nullptr;
}
uint32_t var_type_id = ptr_type_inst->GetSingleWordInOperand(1);
- Instruction* var_type_inst = context->get_def_use_mgr()->GetDef(var_type_id);
- if (var_type_inst->opcode() != spv::Op::OpTypeArray &&
- var_type_inst->opcode() != spv::Op::OpTypeStruct) {
- return false;
+ return context->get_def_use_mgr()->GetDef(var_type_id);
+}
+
+} // namespace
+
+namespace descsroautil {
+
+bool IsDescriptorArray(IRContext* context, Instruction* var) {
+ Instruction* var_type_inst = GetVariableType(context, var);
+ if (var_type_inst == nullptr) return false;
+ return var_type_inst->opcode() == spv::Op::OpTypeArray &&
+ HasDescriptorDecorations(context, var);
+}
+
+bool IsDescriptorStruct(IRContext* context, Instruction* var) {
+ Instruction* var_type_inst = GetVariableType(context, var);
+ if (var_type_inst == nullptr) return false;
+
+ while (var_type_inst->opcode() == spv::Op::OpTypeArray) {
+ var_type_inst = context->get_def_use_mgr()->GetDef(
+ var_type_inst->GetInOperand(0).AsId());
}
+ if (var_type_inst->opcode() != spv::Op::OpTypeStruct) return false;
+
// All structures with descriptor assignments must be replaced by variables,
// one for each of their members - with the exceptions of buffers.
if (IsTypeOfStructuredBuffer(context, var_type_inst)) {
return false;
}
- if (!context->get_decoration_mgr()->HasDecoration(
- var->result_id(), uint32_t(spv::Decoration::DescriptorSet))) {
- return false;
- }
-
- return context->get_decoration_mgr()->HasDecoration(
- var->result_id(), uint32_t(spv::Decoration::Binding));
+ return HasDescriptorDecorations(context, var);
}
bool IsTypeOfStructuredBuffer(IRContext* context, const Instruction* type) {
diff --git a/source/opt/desc_sroa_util.h b/source/opt/desc_sroa_util.h
index 2f45c0c..0423356 100644
--- a/source/opt/desc_sroa_util.h
+++ b/source/opt/desc_sroa_util.h
@@ -27,6 +27,10 @@
// descriptor array.
bool IsDescriptorArray(IRContext* context, Instruction* var);
+// Returns true if |var| is an OpVariable instruction that represents a
+// struct containing descriptors.
+bool IsDescriptorStruct(IRContext* context, Instruction* var);
+
// Returns true if |type| is a type that could be used for a structured buffer
// as opposed to a type that would be used for a structure of resource
// descriptors.
diff --git a/source/opt/optimizer.cpp b/source/opt/optimizer.cpp
index 4add68a..4523d62 100644
--- a/source/opt/optimizer.cpp
+++ b/source/opt/optimizer.cpp
@@ -364,6 +364,10 @@
RegisterPass(CreateSpreadVolatileSemanticsPass());
} else if (pass_name == "descriptor-scalar-replacement") {
RegisterPass(CreateDescriptorScalarReplacementPass());
+ } else if (pass_name == "descriptor-composite-scalar-replacement") {
+ RegisterPass(CreateDescriptorCompositeScalarReplacementPass());
+ } else if (pass_name == "descriptor-array-scalar-replacement") {
+ RegisterPass(CreateDescriptorArrayScalarReplacementPass());
} else if (pass_name == "eliminate-dead-code-aggressive") {
RegisterPass(CreateAggressiveDCEPass(preserve_interface));
} else if (pass_name == "eliminate-insert-extract") {
@@ -1059,7 +1063,20 @@
Optimizer::PassToken CreateDescriptorScalarReplacementPass() {
return MakeUnique<Optimizer::PassToken::Impl>(
- MakeUnique<opt::DescriptorScalarReplacement>());
+ MakeUnique<opt::DescriptorScalarReplacement>(
+ /* flatten_composites= */ true, /* flatten_arrays= */ true));
+}
+
+Optimizer::PassToken CreateDescriptorCompositeScalarReplacementPass() {
+ return MakeUnique<Optimizer::PassToken::Impl>(
+ MakeUnique<opt::DescriptorScalarReplacement>(
+ /* flatten_composites= */ true, /* flatten_arrays= */ false));
+}
+
+Optimizer::PassToken CreateDescriptorArrayScalarReplacementPass() {
+ return MakeUnique<Optimizer::PassToken::Impl>(
+ MakeUnique<opt::DescriptorScalarReplacement>(
+ /* flatten_composites= */ false, /* flatten_arrays= */ true));
}
Optimizer::PassToken CreateWrapOpKillPass() {
diff --git a/test/opt/desc_sroa_test.cpp b/test/opt/desc_sroa_test.cpp
index 5c166d8..f86fa3a 100644
--- a/test/opt/desc_sroa_test.cpp
+++ b/test/opt/desc_sroa_test.cpp
@@ -198,7 +198,8 @@
)";
- SinglePassRunAndMatch<DescriptorScalarReplacement>(text, true);
+ SinglePassRunAndMatch<DescriptorScalarReplacement>(
+ text, true, /* flatten_composites=*/true, /* flatten_arrays=*/true);
}
TEST_F(DescriptorScalarReplacementTest, ExpandArrayOfSamplers) {
@@ -249,7 +250,8 @@
OpFunctionEnd
)";
- SinglePassRunAndMatch<DescriptorScalarReplacement>(text, true);
+ SinglePassRunAndMatch<DescriptorScalarReplacement>(
+ text, true, /* flatten_composites=*/true, /* flatten_arrays=*/true);
}
TEST_F(DescriptorScalarReplacementTest, ExpandArrayOfSSBOs) {
@@ -308,7 +310,8 @@
OpFunctionEnd
)";
- SinglePassRunAndMatch<DescriptorScalarReplacement>(text, true);
+ SinglePassRunAndMatch<DescriptorScalarReplacement>(
+ text, true, /* flatten_composites=*/true, /* flatten_arrays=*/true);
}
TEST_F(DescriptorScalarReplacementTest, NameNewVariables) {
@@ -370,7 +373,8 @@
OpFunctionEnd
)";
- SinglePassRunAndMatch<DescriptorScalarReplacement>(text, true);
+ SinglePassRunAndMatch<DescriptorScalarReplacement>(
+ text, true, /* flatten_composites=*/true, /* flatten_arrays=*/true);
}
TEST_F(DescriptorScalarReplacementTest, DontExpandCBuffers) {
@@ -430,7 +434,8 @@
OpFunctionEnd
)";
- SinglePassRunAndMatch<DescriptorScalarReplacement>(text, true);
+ SinglePassRunAndMatch<DescriptorScalarReplacement>(
+ text, true, /* flatten_composites=*/true, /* flatten_arrays=*/true);
}
TEST_F(DescriptorScalarReplacementTest, DontExpandStructuredBuffers) {
@@ -497,7 +502,8 @@
OpFunctionEnd
)";
- SinglePassRunAndMatch<DescriptorScalarReplacement>(text, true);
+ SinglePassRunAndMatch<DescriptorScalarReplacement>(
+ text, true, /* flatten_composites=*/true, /* flatten_arrays=*/true);
}
TEST_F(DescriptorScalarReplacementTest, StructureArrayNames) {
@@ -511,7 +517,39 @@
)";
const std::string text = checks + GetStructureArrayTestSpirv();
- SinglePassRunAndMatch<DescriptorScalarReplacement>(text, true);
+ SinglePassRunAndMatch<DescriptorScalarReplacement>(
+ text, true, /* flatten_composites=*/true, /* flatten_arrays=*/true);
+}
+
+TEST_F(DescriptorScalarReplacementTest,
+ FlattensArraysOfStructsButNoResourceArrays) {
+ // Check that only the composite array is flattenned, but internal resource
+ // arrays are left as-is.
+ const std::string checks = R"(
+; CHECK: OpName %globalS_0__0__t "globalS[0][0].t"
+; CHECK: OpName %globalS_0__0__s "globalS[0][0].s"
+; CHECK: OpName %globalS_1__1__t "globalS[1][1].t"
+; CHECK: OpName %globalS_1__1__s "globalS[1][1].s"
+; CHECK-NOT: OpName %globalS_1__1__t_0_
+; CHECK-NOT: OpName %globalS_1__1__s_0_
+ )";
+
+ const std::string text = checks + GetStructureArrayTestSpirv();
+ SinglePassRunAndMatch<DescriptorScalarReplacement>(
+ text, true, /* flatten_composites=*/true, /* flatten_arrays=*/false);
+}
+
+TEST_F(DescriptorScalarReplacementTest, FlattenNothingIfAskedTo) {
+ // Not useful, but checks what happens if both are set to false.
+ // In such case, nothing happens.
+ const std::string checks = R"(
+; CHECK: OpName %globalS
+; CHECK-NOT: OpName %globalS_
+ )";
+
+ const std::string text = checks + GetStructureArrayTestSpirv();
+ SinglePassRunAndMatch<DescriptorScalarReplacement>(
+ text, true, /* flatten_composites=*/false, /* flatten_arrays=*/false);
}
TEST_F(DescriptorScalarReplacementTest, StructureArrayBindings) {
@@ -525,7 +563,8 @@
)";
const std::string text = checks + GetStructureArrayTestSpirv();
- SinglePassRunAndMatch<DescriptorScalarReplacement>(text, true);
+ SinglePassRunAndMatch<DescriptorScalarReplacement>(
+ text, true, /* flatten_composites=*/true, /* flatten_arrays=*/true);
}
TEST_F(DescriptorScalarReplacementTest, StructureArrayReplacements) {
@@ -540,7 +579,8 @@
)";
const std::string text = checks + GetStructureArrayTestSpirv();
- SinglePassRunAndMatch<DescriptorScalarReplacement>(text, true);
+ SinglePassRunAndMatch<DescriptorScalarReplacement>(
+ text, true, /* flatten_composites=*/true, /* flatten_arrays=*/true);
}
TEST_F(DescriptorScalarReplacementTest, ResourceStructAsFunctionParam) {
@@ -724,7 +764,9 @@
; CHECK: OpFAdd %v4float [[sample_3]] [[sample_4]]
)";
- SinglePassRunAndMatch<DescriptorScalarReplacement>(checks + shader, true);
+ SinglePassRunAndMatch<DescriptorScalarReplacement>(
+ checks + shader, true, /* flatten_composites=*/true,
+ /* flatten_arrays=*/true);
}
TEST_F(DescriptorScalarReplacementTest, BindingForResourceArrayOfStructs) {
@@ -765,7 +807,8 @@
OpFunctionEnd
)";
- SinglePassRunAndMatch<DescriptorScalarReplacement>(shader, true);
+ SinglePassRunAndMatch<DescriptorScalarReplacement>(
+ shader, true, /* flatten_composites=*/true, /* flatten_arrays=*/true);
}
TEST_F(DescriptorScalarReplacementTest, MemberDecorationForResourceStruct) {
@@ -828,7 +871,8 @@
OpFunctionEnd
)";
- SinglePassRunAndMatch<DescriptorScalarReplacement>(shader, true);
+ SinglePassRunAndMatch<DescriptorScalarReplacement>(
+ shader, true, /* flatten_composites=*/true, /* flatten_arrays=*/true);
}
TEST_F(DescriptorScalarReplacementTest, DecorateStringForReflect) {
@@ -915,7 +959,8 @@
OpFunctionEnd
)";
- SinglePassRunAndMatch<DescriptorScalarReplacement>(shader, true);
+ SinglePassRunAndMatch<DescriptorScalarReplacement>(
+ shader, true, /* flatten_composites=*/true, /* flatten_arrays=*/true);
}
TEST_F(DescriptorScalarReplacementTest, ExpandArrayInOpEntryPoint) {
@@ -983,7 +1028,161 @@
OpFunctionEnd
)";
- SinglePassRunAndMatch<DescriptorScalarReplacement>(text, false);
+ SinglePassRunAndMatch<DescriptorScalarReplacement>(
+ text, false, /* flatten_composites=*/true, /* flatten_arrays=*/true);
+}
+
+TEST_F(DescriptorScalarReplacementTest,
+ ExpandArrayWhenCompositeExpensionIsOff) {
+ const std::string text = R"(; SPIR-V
+; Version: 1.6
+; Bound: 31
+; Schema: 0
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+
+; CHECK: OpEntryPoint GLCompute %main "main" %output_0_ %output_1_
+
+ OpEntryPoint GLCompute %main "main" %output
+ OpExecutionMode %main LocalSize 1 1 1
+ OpSource HLSL 670
+ OpName %type_RWByteAddressBuffer "type.RWByteAddressBuffer"
+ OpName %output "output"
+ OpName %main "main"
+ OpName %src_main "src.main"
+ OpName %bb_entry "bb.entry"
+
+; CHECK: OpDecorate %output_1_ DescriptorSet 0
+; CHECK: OpDecorate %output_1_ Binding 1
+; CHECK: OpDecorate %output_0_ DescriptorSet 0
+; CHECK: OpDecorate %output_0_ Binding 0
+
+ OpDecorate %output DescriptorSet 0
+ OpDecorate %output Binding 0
+
+ OpDecorate %_runtimearr_uint ArrayStride 4
+ OpMemberDecorate %type_RWByteAddressBuffer 0 Offset 0
+ OpDecorate %type_RWByteAddressBuffer Block
+ %int = OpTypeInt 32 1
+ %int_1 = OpConstant %int 1
+ %uint = OpTypeInt 32 0
+ %uint_0 = OpConstant %uint 0
+ %uint_2 = OpConstant %uint 2
+ %uint_32 = OpConstant %uint 32
+%_runtimearr_uint = OpTypeRuntimeArray %uint
+%type_RWByteAddressBuffer = OpTypeStruct %_runtimearr_uint
+%_arr_type_RWByteAddressBuffer_uint_2 = OpTypeArray %type_RWByteAddressBuffer %uint_2
+%_ptr_StorageBuffer__arr_type_RWByteAddressBuffer_uint_2 = OpTypePointer StorageBuffer %_arr_type_RWByteAddressBuffer_uint_2
+ %void = OpTypeVoid
+ %23 = OpTypeFunction %void
+%_ptr_StorageBuffer_type_RWByteAddressBuffer = OpTypePointer StorageBuffer %type_RWByteAddressBuffer
+%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+
+; CHECK: %output_1_ = OpVariable %_ptr_StorageBuffer_type_RWByteAddressBuffer StorageBuffer
+; CHECK: %output_0_ = OpVariable %_ptr_StorageBuffer_type_RWByteAddressBuffer StorageBuffer
+
+ %output = OpVariable %_ptr_StorageBuffer__arr_type_RWByteAddressBuffer_uint_2 StorageBuffer
+
+ %main = OpFunction %void None %23
+ %26 = OpLabel
+ %27 = OpFunctionCall %void %src_main
+ OpReturn
+ OpFunctionEnd
+ %src_main = OpFunction %void None %23
+ %bb_entry = OpLabel
+ %28 = OpAccessChain %_ptr_StorageBuffer_type_RWByteAddressBuffer %output %int_1
+ %29 = OpShiftRightLogical %uint %uint_0 %uint_2
+ %30 = OpAccessChain %_ptr_StorageBuffer_uint %28 %uint_0 %29
+ OpStore %30 %uint_32
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ SinglePassRunAndMatch<DescriptorScalarReplacement>(
+ text, false, /* flatten_composites=*/false, /* flatten_arrays=*/true);
+}
+
+TEST_F(DescriptorScalarReplacementTest, ExpandStructButNotArray) {
+ const std::string text = R"(; SPIR-V
+; Version: 1.6
+; Generator: Khronos SPIR-V Tools Assembler; 0
+; Bound: 41
+; Schema: 0
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main" %out_var_SV_Target
+ OpExecutionMode %main OriginUpperLeft
+ OpSource HLSL 660
+ OpName %type_2d_image "type.2d.image"
+ OpName %Textures "Textures"
+ OpName %type_sampler "type.sampler"
+ OpName %out_var_SV_Target "out.var.SV_Target"
+ OpName %main "main"
+ OpName %type_sampled_image "type.sampled.image"
+ OpName %TheStruct "TheStruct"
+ OpMemberName %StructOfResources 0 "Texture"
+ OpMemberName %StructOfResources 1 "Sampler"
+; CHECK: OpName %TheStruct_Sampler "TheStruct.Sampler"
+; CHECK: OpName %TheStruct_Texture "TheStruct.Texture"
+ OpDecorate %out_var_SV_Target Location 0
+ OpDecorate %Textures DescriptorSet 0
+ OpDecorate %Textures Binding 0
+ OpDecorate %TheStruct DescriptorSet 0
+ OpDecorate %TheStruct Binding 10
+; CHECK: OpDecorate %TheStruct_Sampler DescriptorSet 0
+; CHECK: OpDecorate %TheStruct_Sampler Binding 11
+; CHECK: OpDecorate %TheStruct_Texture DescriptorSet 0
+; CHECK: OpDecorate %TheStruct_Texture Binding 10
+ %int = OpTypeInt 32 1
+ %int_0 = OpConstant %int 0
+ %int_1 = OpConstant %int 1
+ %float = OpTypeFloat 32
+ %float_0 = OpConstant %float 0
+ %v2float = OpTypeVector %float 2
+ %13 = OpConstantComposite %v2float %float_0 %float_0
+ %uint = OpTypeInt 32 0
+ %uint_10 = OpConstant %uint 10
+ %type_2d_image = OpTypeImage %float 2D 2 0 0 1 Unknown
+ %_arr_type_2d_image_uint_10 = OpTypeArray %type_2d_image %uint_10
+%_ptr_UniformConstant__arr_type_2d_image_uint_10 = OpTypePointer UniformConstant %_arr_type_2d_image_uint_10
+ %type_sampler = OpTypeSampler
+ %StructOfResources = OpTypeStruct %type_2d_image %type_sampler
+%_ptr_UniformConstant__struct_18 = OpTypePointer UniformConstant %StructOfResources
+ %v4float = OpTypeVector %float 4
+ %_ptr_Output_v4float = OpTypePointer Output %v4float
+ %void = OpTypeVoid
+ %23 = OpTypeFunction %void
+%_ptr_UniformConstant_type_2d_image = OpTypePointer UniformConstant %type_2d_image
+ %_ptr_UniformConstant_type_sampler = OpTypePointer UniformConstant %type_sampler
+ %type_sampled_image = OpTypeSampledImage %type_2d_image
+ %Textures = OpVariable %_ptr_UniformConstant__arr_type_2d_image_uint_10 UniformConstant
+ %out_var_SV_Target = OpVariable %_ptr_Output_v4float Output
+ %TheStruct = OpVariable %_ptr_UniformConstant__struct_18 UniformConstant
+ %main = OpFunction %void None %23
+ %26 = OpLabel
+ %27 = OpAccessChain %_ptr_UniformConstant_type_2d_image %Textures %int_0
+ %28 = OpLoad %type_2d_image %27
+ %29 = OpAccessChain %_ptr_UniformConstant_type_sampler %TheStruct %int_1
+ %31 = OpLoad %type_sampler %29
+; CHECK: %31 = OpLoad %type_sampler %TheStruct_Sampler
+ %32 = OpSampledImage %type_sampled_image %28 %31
+ %33 = OpImageSampleImplicitLod %v4float %32 %13 None
+ %34 = OpAccessChain %_ptr_UniformConstant_type_2d_image %TheStruct %int_0
+ %35 = OpLoad %type_2d_image %34
+; CHECK: %35 = OpLoad %type_2d_image %TheStruct_Texture
+ %36 = OpAccessChain %_ptr_UniformConstant_type_sampler %TheStruct %int_1
+ %37 = OpLoad %type_sampler %36
+; CHECK: %37 = OpLoad %type_sampler %TheStruct_Sampler
+ %38 = OpSampledImage %type_sampled_image %35 %37
+ %39 = OpImageSampleImplicitLod %v4float %38 %13 None
+ %40 = OpFAdd %v4float %33 %39
+ OpStore %out_var_SV_Target %40
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ SinglePassRunAndMatch<DescriptorScalarReplacement>(
+ text, false, /* flatten_composites=*/true, /* flatten_arrays=*/false);
}
} // namespace
diff --git a/tools/opt/opt.cpp b/tools/opt/opt.cpp
index f8456d7..3b8d5fc 100644
--- a/tools/opt/opt.cpp
+++ b/tools/opt/opt.cpp
@@ -181,6 +181,14 @@
must be in OpAccessChain instructions with a literal index for
the first index.)");
printf(R"(
+ --descriptor-composite-scalar-replacement
+ Same as descriptor-scalar-replacement, but only impacts composite/structs.
+ For details, see --descriptor-scalar-replacement help.)");
+ printf(R"(
+ --descriptor-array-scalar-replacement
+ Same as descriptor-scalar-replacement, but only impacts arrays.
+ For details, see --descriptor-scalar-replacement help.)");
+ printf(R"(
--eliminate-dead-branches
Convert conditional branches with constant condition to the
indicated unconditional branch. Delete all resulting dead