Skip to content

Commit

Permalink
opt: add StorageImageReadWithoutFormat to cap trim (#5475)
Browse files Browse the repository at this point in the history
The StorageImageReadWithoutFormat capability is only required when
an image type with the format set to Unknown is used with some specific
OpImageRead or OpImageSparseRead instructions.

This patch adds the required code to the capability trimming pass to
remove the StorageImageReadWithoutFormat capability when not required.

Signed-off-by: Nathan Gauër <[email protected]>
  • Loading branch information
Keenuts authored Nov 14, 2023
1 parent d88742f commit c91e9d0
Show file tree
Hide file tree
Showing 3 changed files with 237 additions and 10 deletions.
66 changes: 56 additions & 10 deletions source/opt/trim_capabilities_pass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,13 @@ constexpr uint32_t kTypeArrayTypeIndex = 0;
constexpr uint32_t kOpTypeScalarBitWidthIndex = 0;
constexpr uint32_t kTypePointerTypeIdInIndex = 1;
constexpr uint32_t kOpTypeIntSizeIndex = 0;
constexpr uint32_t kOpTypeImageArrayedIndex = 3;
constexpr uint32_t kOpTypeImageDimIndex = 1;
constexpr uint32_t kOpTypeImageArrayedIndex = kOpTypeImageDimIndex + 2;
constexpr uint32_t kOpTypeImageMSIndex = kOpTypeImageArrayedIndex + 1;
constexpr uint32_t kOpTypeImageSampledIndex = kOpTypeImageMSIndex + 1;
constexpr uint32_t kOpTypeImageFormatIndex = kOpTypeImageSampledIndex + 1;
constexpr uint32_t kOpImageReadImageIndex = 0;
constexpr uint32_t kOpImageSparseReadImageIndex = 0;

// DFS visit of the type defined by `instruction`.
// If `condition` is true, children of the current node are visited.
Expand Down Expand Up @@ -296,17 +300,59 @@ static std::optional<spv::Capability> Handler_OpTypeImage_ImageMSArray(
: std::nullopt;
}

static std::optional<spv::Capability>
Handler_OpImageRead_StorageImageReadWithoutFormat(
const Instruction* instruction) {
assert(instruction->opcode() == spv::Op::OpImageRead &&
"This handler only support OpImageRead opcodes.");
const auto* def_use_mgr = instruction->context()->get_def_use_mgr();

const uint32_t image_index =
instruction->GetSingleWordInOperand(kOpImageReadImageIndex);
const uint32_t type_index = def_use_mgr->GetDef(image_index)->type_id();
const Instruction* type = def_use_mgr->GetDef(type_index);
const uint32_t dim = type->GetSingleWordInOperand(kOpTypeImageDimIndex);
const uint32_t format = type->GetSingleWordInOperand(kOpTypeImageFormatIndex);

const bool is_unknown = spv::ImageFormat(format) == spv::ImageFormat::Unknown;
const bool requires_capability_for_unknown =
spv::Dim(dim) != spv::Dim::SubpassData;
return is_unknown && requires_capability_for_unknown
? std::optional(spv::Capability::StorageImageReadWithoutFormat)
: std::nullopt;
}

static std::optional<spv::Capability>
Handler_OpImageSparseRead_StorageImageReadWithoutFormat(
const Instruction* instruction) {
assert(instruction->opcode() == spv::Op::OpImageSparseRead &&
"This handler only support OpImageSparseRead opcodes.");
const auto* def_use_mgr = instruction->context()->get_def_use_mgr();

const uint32_t image_index =
instruction->GetSingleWordInOperand(kOpImageSparseReadImageIndex);
const uint32_t type_index = def_use_mgr->GetDef(image_index)->type_id();
const Instruction* type = def_use_mgr->GetDef(type_index);
const uint32_t format = type->GetSingleWordInOperand(kOpTypeImageFormatIndex);

return spv::ImageFormat(format) == spv::ImageFormat::Unknown
? std::optional(spv::Capability::StorageImageReadWithoutFormat)
: std::nullopt;
}

// Opcode of interest to determine capabilities requirements.
constexpr std::array<std::pair<spv::Op, OpcodeHandler>, 8> kOpcodeHandlers{{
constexpr std::array<std::pair<spv::Op, OpcodeHandler>, 10> kOpcodeHandlers{{
// clang-format off
{spv::Op::OpTypeFloat, Handler_OpTypeFloat_Float64 },
{spv::Op::OpTypeImage, Handler_OpTypeImage_ImageMSArray},
{spv::Op::OpTypeInt, Handler_OpTypeInt_Int64 },
{spv::Op::OpTypePointer, Handler_OpTypePointer_StorageInputOutput16},
{spv::Op::OpTypePointer, Handler_OpTypePointer_StoragePushConstant16},
{spv::Op::OpTypePointer, Handler_OpTypePointer_StorageUniform16},
{spv::Op::OpTypePointer, Handler_OpTypePointer_StorageUniform16},
{spv::Op::OpTypePointer, Handler_OpTypePointer_StorageUniformBufferBlock16},
{spv::Op::OpImageRead, Handler_OpImageRead_StorageImageReadWithoutFormat},
{spv::Op::OpImageSparseRead, Handler_OpImageSparseRead_StorageImageReadWithoutFormat},
{spv::Op::OpTypeFloat, Handler_OpTypeFloat_Float64 },
{spv::Op::OpTypeImage, Handler_OpTypeImage_ImageMSArray},
{spv::Op::OpTypeInt, Handler_OpTypeInt_Int64 },
{spv::Op::OpTypePointer, Handler_OpTypePointer_StorageInputOutput16},
{spv::Op::OpTypePointer, Handler_OpTypePointer_StoragePushConstant16},
{spv::Op::OpTypePointer, Handler_OpTypePointer_StorageUniform16},
{spv::Op::OpTypePointer, Handler_OpTypePointer_StorageUniform16},
{spv::Op::OpTypePointer, Handler_OpTypePointer_StorageUniformBufferBlock16},
// clang-format on
}};

Expand Down
1 change: 1 addition & 0 deletions source/opt/trim_capabilities_pass.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ class TrimCapabilitiesPass : public Pass {
spv::Capability::RayTraversalPrimitiveCullingKHR,
spv::Capability::Shader,
spv::Capability::ShaderClockKHR,
spv::Capability::StorageImageReadWithoutFormat,
spv::Capability::StorageInputOutput16,
spv::Capability::StoragePushConstant16,
spv::Capability::StorageUniform16,
Expand Down
180 changes: 180 additions & 0 deletions test/opt/trim_capabilities_pass_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2186,6 +2186,186 @@ TEST_F(TrimCapabilitiesPassTest,
EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithChange);
}

TEST_F(TrimCapabilitiesPassTest,
StorageImageReadWithoutFormat_RemovedIfUnused) {
const std::string kTest = R"(
OpCapability StorageImageReadWithoutFormat
; CHECK-NOT: OpCapability StorageImageReadWithoutFormat
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %PSMain "PSMain" %out_var
OpExecutionMode %PSMain OriginUpperLeft
OpDecorate %out_var Location 0
%float = OpTypeFloat 32
%float4 = OpTypeVector %float 4
%float_0 = OpConstant %float 0
%float4_0000 = OpConstantComposite %float4 %float_0 %float_0 %float_0 %float_0
%ptr_float4 = OpTypePointer Output %float4
%void = OpTypeVoid
%9 = OpTypeFunction %void
%out_var = OpVariable %ptr_float4 Output
%PSMain = OpFunction %void None %9
%10 = OpLabel
OpStore %out_var %float4_0000
OpReturn
OpFunctionEnd
)";
const auto result =
SinglePassRunAndMatch<TrimCapabilitiesPass>(kTest, /* skip_nop= */ false);
EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithChange);
}

TEST_F(TrimCapabilitiesPassTest,
StorageImageReadWithoutFormat_RemovedIfUnusedOpImageFetch) {
const std::string kTest = R"(
OpCapability StorageImageReadWithoutFormat
; CHECK-NOT: OpCapability StorageImageReadWithoutFormat
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %PSMain "PSMain" %out_var
OpExecutionMode %PSMain OriginUpperLeft
OpDecorate %out_var Location 0
OpDecorate %texture DescriptorSet 0
OpDecorate %texture Binding 1
%float = OpTypeFloat 32
%float4 = OpTypeVector %float 4
%int = OpTypeInt 32 1
%int2 = OpTypeVector %int 2
%type_image = OpTypeImage %float 2D 2 0 0 1 Unknown
%ptr_image = OpTypePointer UniformConstant %type_image
%int_0 = OpConstant %int 0
%int2_00 = OpConstantComposite %int2 %int_0 %int_0
%ptr_float4 = OpTypePointer Output %float4
%void = OpTypeVoid
%9 = OpTypeFunction %void
%texture = OpVariable %ptr_image UniformConstant
%out_var = OpVariable %ptr_float4 Output
%PSMain = OpFunction %void None %9
%10 = OpLabel
%11 = OpLoad %type_image %texture
%12 = OpImageFetch %float4 %11 %int2_00 Lod %int_0
OpStore %out_var %12
OpReturn
OpFunctionEnd
)";
const auto result =
SinglePassRunAndMatch<TrimCapabilitiesPass>(kTest, /* skip_nop= */ false);
EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithChange);
}

TEST_F(TrimCapabilitiesPassTest,
StorageImageReadWithoutFormat_RemainsWhenRequiredWithRead) {
const std::string kTest = R"(
OpCapability StorageImageReadWithoutFormat
; CHECK: OpCapability StorageImageReadWithoutFormat
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %PSMain "PSMain" %out_var
OpExecutionMode %PSMain OriginUpperLeft
OpDecorate %out_var Location 0
OpDecorate %texture DescriptorSet 0
OpDecorate %texture Binding 1
%float = OpTypeFloat 32
%float4 = OpTypeVector %float 4
%int = OpTypeInt 32 1
%int2 = OpTypeVector %int 2
%type_image = OpTypeImage %float 2D 2 0 0 1 Unknown
%ptr_image = OpTypePointer UniformConstant %type_image
%int_0 = OpConstant %int 0
%int2_00 = OpConstantComposite %int2 %int_0 %int_0
%ptr_float4 = OpTypePointer Output %float4
%void = OpTypeVoid
%9 = OpTypeFunction %void
%texture = OpVariable %ptr_image UniformConstant
%out_var = OpVariable %ptr_float4 Output
%PSMain = OpFunction %void None %9
%10 = OpLabel
%11 = OpLoad %type_image %texture
%12 = OpImageRead %float4 %11 %int2_00 Lod %int_0
OpStore %out_var %12
OpReturn
OpFunctionEnd
)";
const auto result =
SinglePassRunAndMatch<TrimCapabilitiesPass>(kTest, /* skip_nop= */ false);
EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithoutChange);
}

TEST_F(TrimCapabilitiesPassTest,
StorageImageReadWithoutFormat_RemainsWhenRequiredWithSparseRead) {
const std::string kTest = R"(
OpCapability StorageImageReadWithoutFormat
; CHECK: OpCapability StorageImageReadWithoutFormat
OpCapability SparseResidency
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %PSMain "PSMain"
OpExecutionMode %PSMain OriginUpperLeft
OpDecorate %texture DescriptorSet 0
OpDecorate %texture Binding 1
%float = OpTypeFloat 32
%float4 = OpTypeVector %float 4
%int = OpTypeInt 32 1
%int2 = OpTypeVector %int 2
%type_image = OpTypeImage %float 2D 2 0 0 2 Unknown
%struct = OpTypeStruct %int %float4
%ptr_image = OpTypePointer UniformConstant %type_image
%int_0 = OpConstant %int 0
%int2_00 = OpConstantComposite %int2 %int_0 %int_0
%void = OpTypeVoid
%9 = OpTypeFunction %void
%texture = OpVariable %ptr_image UniformConstant
%PSMain = OpFunction %void None %9
%10 = OpLabel
%11 = OpLoad %type_image %texture
%12 = OpImageSparseRead %struct %11 %int2_00
OpReturn
OpFunctionEnd
)";
const auto result =
SinglePassRunAndMatch<TrimCapabilitiesPass>(kTest, /* skip_nop= */ false);
EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithoutChange);
}

TEST_F(TrimCapabilitiesPassTest,
StorageImageReadWithoutFormat_RemovedWithReadOnSubpassData) {
const std::string kTest = R"(
OpCapability StorageImageReadWithoutFormat
; CHECK-NOT: OpCapability StorageImageReadWithoutFormat
OpCapability InputAttachment
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %PSMain "PSMain" %out_var
OpExecutionMode %PSMain OriginUpperLeft
OpDecorate %out_var Location 0
OpDecorate %texture DescriptorSet 0
OpDecorate %texture Binding 1
%float = OpTypeFloat 32
%float4 = OpTypeVector %float 4
%int = OpTypeInt 32 1
%int2 = OpTypeVector %int 2
%type_image = OpTypeImage %float SubpassData 2 0 0 2 Unknown
%ptr_image = OpTypePointer UniformConstant %type_image
%int_0 = OpConstant %int 0
%int2_00 = OpConstantComposite %int2 %int_0 %int_0
%ptr_float4 = OpTypePointer Output %float4
%void = OpTypeVoid
%9 = OpTypeFunction %void
%texture = OpVariable %ptr_image UniformConstant
%out_var = OpVariable %ptr_float4 Output
%PSMain = OpFunction %void None %9
%10 = OpLabel
%11 = OpLoad %type_image %texture
%12 = OpImageRead %float4 %11 %int2_00
OpStore %out_var %12
OpReturn
OpFunctionEnd
)";
const auto result =
SinglePassRunAndMatch<TrimCapabilitiesPass>(kTest, /* skip_nop= */ false);
EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithChange);
}

} // namespace
} // namespace opt
} // namespace spvtools

0 comments on commit c91e9d0

Please sign in to comment.