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>
This commit is contained in:
Nathan Gauër 2024-07-19 17:48:21 +02:00 committed by GitHub
parent 4c7e1fa5c3
commit 2ea4003633
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 306 additions and 43 deletions

View File

@ -827,14 +827,19 @@ Optimizer::PassToken CreateReplaceDescArrayAccessUsingVarIndexPass();
// 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

View File

@ -31,11 +31,14 @@ bool IsDecorationBinding(Instruction* inst) {
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;

View File

@ -32,9 +32,16 @@ namespace opt {
// 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 @@ class DescriptorScalarReplacement : public Pass {
// 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

View File

@ -29,41 +29,58 @@ uint32_t GetLengthOfArrayType(IRContext* context, Instruction* type) {
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) {

View File

@ -27,6 +27,10 @@ namespace descsroautil {
// 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.

View File

@ -364,6 +364,10 @@ bool Optimizer::RegisterPassFromFlag(const std::string& flag,
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 CreateSpreadVolatileSemanticsPass() {
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() {

View File

@ -198,7 +198,8 @@ TEST_F(DescriptorScalarReplacementTest, ExpandArrayOfTextures) {
)";
SinglePassRunAndMatch<DescriptorScalarReplacement>(text, true);
SinglePassRunAndMatch<DescriptorScalarReplacement>(
text, true, /* flatten_composites=*/true, /* flatten_arrays=*/true);
}
TEST_F(DescriptorScalarReplacementTest, ExpandArrayOfSamplers) {
@ -249,7 +250,8 @@ TEST_F(DescriptorScalarReplacementTest, ExpandArrayOfSamplers) {
OpFunctionEnd
)";
SinglePassRunAndMatch<DescriptorScalarReplacement>(text, true);
SinglePassRunAndMatch<DescriptorScalarReplacement>(
text, true, /* flatten_composites=*/true, /* flatten_arrays=*/true);
}
TEST_F(DescriptorScalarReplacementTest, ExpandArrayOfSSBOs) {
@ -308,7 +310,8 @@ TEST_F(DescriptorScalarReplacementTest, ExpandArrayOfSSBOs) {
OpFunctionEnd
)";
SinglePassRunAndMatch<DescriptorScalarReplacement>(text, true);
SinglePassRunAndMatch<DescriptorScalarReplacement>(
text, true, /* flatten_composites=*/true, /* flatten_arrays=*/true);
}
TEST_F(DescriptorScalarReplacementTest, NameNewVariables) {
@ -370,7 +373,8 @@ TEST_F(DescriptorScalarReplacementTest, NameNewVariables) {
OpFunctionEnd
)";
SinglePassRunAndMatch<DescriptorScalarReplacement>(text, true);
SinglePassRunAndMatch<DescriptorScalarReplacement>(
text, true, /* flatten_composites=*/true, /* flatten_arrays=*/true);
}
TEST_F(DescriptorScalarReplacementTest, DontExpandCBuffers) {
@ -430,7 +434,8 @@ TEST_F(DescriptorScalarReplacementTest, DontExpandCBuffers) {
OpFunctionEnd
)";
SinglePassRunAndMatch<DescriptorScalarReplacement>(text, true);
SinglePassRunAndMatch<DescriptorScalarReplacement>(
text, true, /* flatten_composites=*/true, /* flatten_arrays=*/true);
}
TEST_F(DescriptorScalarReplacementTest, DontExpandStructuredBuffers) {
@ -497,7 +502,8 @@ TEST_F(DescriptorScalarReplacementTest, DontExpandStructuredBuffers) {
OpFunctionEnd
)";
SinglePassRunAndMatch<DescriptorScalarReplacement>(text, true);
SinglePassRunAndMatch<DescriptorScalarReplacement>(
text, true, /* flatten_composites=*/true, /* flatten_arrays=*/true);
}
TEST_F(DescriptorScalarReplacementTest, StructureArrayNames) {
@ -511,7 +517,39 @@ TEST_F(DescriptorScalarReplacementTest, StructureArrayNames) {
)";
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 @@ TEST_F(DescriptorScalarReplacementTest, StructureArrayBindings) {
)";
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 @@ TEST_F(DescriptorScalarReplacementTest, StructureArrayReplacements) {
)";
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 @@ TEST_F(DescriptorScalarReplacementTest, ResourceStructAsFunctionParam) {
; 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 @@ TEST_F(DescriptorScalarReplacementTest, BindingForResourceArrayOfStructs) {
OpFunctionEnd
)";
SinglePassRunAndMatch<DescriptorScalarReplacement>(shader, true);
SinglePassRunAndMatch<DescriptorScalarReplacement>(
shader, true, /* flatten_composites=*/true, /* flatten_arrays=*/true);
}
TEST_F(DescriptorScalarReplacementTest, MemberDecorationForResourceStruct) {
@ -828,7 +871,8 @@ TEST_F(DescriptorScalarReplacementTest, MemberDecorationForResourceStruct) {
OpFunctionEnd
)";
SinglePassRunAndMatch<DescriptorScalarReplacement>(shader, true);
SinglePassRunAndMatch<DescriptorScalarReplacement>(
shader, true, /* flatten_composites=*/true, /* flatten_arrays=*/true);
}
TEST_F(DescriptorScalarReplacementTest, DecorateStringForReflect) {
@ -915,7 +959,8 @@ TEST_F(DescriptorScalarReplacementTest, DecorateStringForReflect) {
OpFunctionEnd
)";
SinglePassRunAndMatch<DescriptorScalarReplacement>(shader, true);
SinglePassRunAndMatch<DescriptorScalarReplacement>(
shader, true, /* flatten_composites=*/true, /* flatten_arrays=*/true);
}
TEST_F(DescriptorScalarReplacementTest, ExpandArrayInOpEntryPoint) {
@ -983,7 +1028,161 @@ TEST_F(DescriptorScalarReplacementTest, ExpandArrayInOpEntryPoint) {
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

View File

@ -181,6 +181,14 @@ Options (in lexicographical order):)",
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