Skip to content

[spirv-val] Add the validation checks for SPV_QCOM_tile_shading #6130

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion source/val/validate_annotation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,8 @@ spv_result_t ValidateDecorationTarget(ValidationState_t& _, spv::Decoration dec,
case spv::Decoration::DescriptorSet:
if (sc != spv::StorageClass::StorageBuffer &&
sc != spv::StorageClass::Uniform &&
sc != spv::StorageClass::UniformConstant) {
sc != spv::StorageClass::UniformConstant &&
sc != spv::StorageClass::TileAttachmentQCOM) {
return fail(6491) << "must be in the StorageBuffer, Uniform, or "
"UniformConstant storage class";
}
Expand Down
17 changes: 9 additions & 8 deletions source/val/validate_decorations.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -404,8 +404,7 @@ bool IsAlignedTo(uint32_t offset, uint32_t alignment) {
// or row major-ness.
spv_result_t checkLayout(uint32_t struct_id, const char* storage_class_str,
const char* decoration_str, bool blockRules,
bool scalar_block_layout,
uint32_t incoming_offset,
bool scalar_block_layout, uint32_t incoming_offset,
MemberConstraints& constraints,
ValidationState_t& vstate) {
if (vstate.options()->skip_block_layout) return SPV_SUCCESS;
Expand Down Expand Up @@ -1023,7 +1022,7 @@ spv_result_t CheckDecorationsOfEntryPoints(ValidationState_t& vstate) {
}
if (num_workgroup_variables_with_block > 1 &&
num_workgroup_variables_with_block !=
num_workgroup_variables_with_aliased) {
num_workgroup_variables_with_aliased) {
return vstate.diag(SPV_ERROR_INVALID_BINARY,
vstate.FindDef(entry_point))
<< "When declaring WorkgroupMemoryExplicitLayoutKHR, "
Expand Down Expand Up @@ -1246,10 +1245,10 @@ spv_result_t CheckDecorationsOfBuffers(ValidationState_t& vstate) {
}
// Prepare for messages
const char* sc_str =
uniform ? "Uniform"
: (push_constant ? "PushConstant"
: (workgroup ? "Workgroup"
: "StorageBuffer"));
uniform
? "Uniform"
: (push_constant ? "PushConstant"
: (workgroup ? "Workgroup" : "StorageBuffer"));

if (spvIsVulkanEnv(vstate.context()->target_env)) {
const bool block = hasDecoration(id, spv::Decoration::Block, vstate);
Expand Down Expand Up @@ -1765,6 +1764,7 @@ spv_result_t CheckNonWritableDecoration(ValidationState_t& vstate,
var_storage_class == spv::StorageClass::Private) &&
vstate.features().nonwritable_var_in_function_or_private) {
// New permitted feature in SPIR-V 1.4.
} else if (var_storage_class == spv::StorageClass::TileAttachmentQCOM) {
} else if (
// It may point to a UBO, SSBO, storage image, or raw access chain.
vstate.IsPointerToUniformBlock(type_id) ||
Expand Down Expand Up @@ -2030,7 +2030,8 @@ spv_result_t CheckRelaxPrecisionDecoration(ValidationState_t& vstate,
{ \
spv_result_t e##LINE = (X); \
if (e##LINE != SPV_SUCCESS) return e##LINE; \
} static_assert(true, "require extra semicolon")
} \
static_assert(true, "require extra semicolon")
#define PASS_OR_BAIL(X) PASS_OR_BAIL_AT_LINE(X, __LINE__)

// Check rules for decorations where we start from the decoration rather
Expand Down
59 changes: 59 additions & 0 deletions source/val/validate_memory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -937,6 +937,65 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) {
}
}

if (_.HasCapability(spv::Capability::TileShadingQCOM) &&
storage_class == spv::StorageClass::TileAttachmentQCOM) {
if (result_type->opcode() == spv::Op::OpTypePointer) {
const auto pointee_type =
_.FindDef(result_type->GetOperandAs<uint32_t>(2));
if (pointee_type && pointee_type->opcode() == spv::Op::OpTypeImage) {
spv::Dim dim = static_cast<spv::Dim>(pointee_type->word(3));
if (dim != spv::Dim::Dim2D) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Any OpTypeImage variable in the TileAttachmentQCOM "
"Storage Class must "
"have 2D as its dimension";
}
unsigned sampled = pointee_type->word(7);
if (sampled != 1 && sampled != 2) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Any OpyTpeImage variable in the TileAttachmentQCOM "
"Storage Class must "
"have 1 or 2 as Image 'Sampled' parameter";
}
for (const auto& pair_o : inst->uses()) {
const auto* use_inst_o = pair_o.first;
if (use_inst_o->opcode() == spv::Op::OpLoad) {
for (const auto& pair_i : use_inst_o->uses()) {
const auto* use_inst_i = pair_i.first;
switch (use_inst_i->opcode()) {
case spv::Op::OpImageQueryFormat:
case spv::Op::OpImageQueryOrder:
case spv::Op::OpImageQuerySizeLod:
case spv::Op::OpImageQuerySize:
case spv::Op::OpImageQueryLod:
case spv::Op::OpImageQueryLevels:
case spv::Op::OpImageQuerySamples:
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Any variable in the TileAttachmentQCOM Storage "
"Class must "
"not be consumed by an OpImageQuery* instruction";
default:
break;
}
}
}
}
}
}

if (!(_.HasDecoration(inst->id(), spv::Decoration::DescriptorSet) &&
_.HasDecoration(inst->id(), spv::Decoration::Binding))) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Any variable in the TileAttachmentQCOM Storage Class must "
"be decorated with DescriptorSet and Binding";
}
if (_.HasDecoration(inst->id(), spv::Decoration::Component)) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Any variable in the TileAttachmentQCOM Storage Class must "
"not be decorated with Component decoration";
}
}

return SPV_SUCCESS;
}

Expand Down
83 changes: 79 additions & 4 deletions source/val/validate_mode_setting.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -311,17 +311,84 @@ spv_result_t ValidateEntryPoint(ValidationState_t& _, const Instruction* inst) {
}
}
}
if (!ok && _.HasCapability(spv::Capability::TileShadingQCOM)) {
ok =
execution_modes &&
execution_modes->count(spv::ExecutionMode::TileShadingRateQCOM);
}
if (!ok) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< _.VkErrorID(6426)
<< (_.HasCapability(spv::Capability::TileShadingQCOM)
? _.VkErrorID(10685)
: _.VkErrorID(6426))
<< "In the Vulkan environment, GLCompute execution model "
"entry points require either the LocalSize or "
"LocalSizeId execution mode or an object decorated with "
"WorkgroupSize must be specified.";
"entry points require either the "
<< (_.HasCapability(spv::Capability::TileShadingQCOM)
? "TileShadingRateQCOM, "
: "")
<< "LocalSize or LocalSizeId execution mode or an object "
"decorated with WorkgroupSize must be specified.";
}
}

if (_.HasCapability(spv::Capability::TileShadingQCOM)) {
if (execution_modes) {
if (execution_modes->count(
spv::ExecutionMode::TileShadingRateQCOM) &&
(execution_modes->count(spv::ExecutionMode::LocalSize) ||
execution_modes->count(spv::ExecutionMode::LocalSizeId))) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "If the TileShadingRateQCOM execution mode is used, "
<< "LocalSize and LocalSizeId must not be specified.";
}
if (execution_modes->count(
spv::ExecutionMode::NonCoherentTileAttachmentReadQCOM)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "The NonCoherentTileAttachmentQCOM execution mode must "
"not be used in any stage other than fragment.";
}
}
} else {
if (execution_modes &&
execution_modes->count(spv::ExecutionMode::TileShadingRateQCOM)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "If the TileShadingRateQCOM execution mode is used, the "
"TileShadingQCOM capability must be enabled.";
}
}
break;
default:
if (execution_modes &&
execution_modes->count(spv::ExecutionMode::TileShadingRateQCOM)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "The TileShadingRateQCOM execution mode must not be used "
"in any stage other than compute.";
}
if (execution_model != spv::ExecutionModel::Fragment) {
if (execution_modes &&
execution_modes->count(
spv::ExecutionMode::NonCoherentTileAttachmentReadQCOM)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "The NonCoherentTileAttachmentQCOM execution mode must "
"not be used in any stage other than fragment.";
}
if (_.HasCapability(spv::Capability::TileShadingQCOM)) {
return _.diag(SPV_ERROR_INVALID_CAPABILITY, inst)
<< "The TileShadingQCOM capability must not be enabled in "
"any stage other than compute or fragment.";
}
} else {
if (execution_modes &&
execution_modes->count(
spv::ExecutionMode::NonCoherentTileAttachmentReadQCOM)) {
if (!_.HasCapability(spv::Capability::TileShadingQCOM)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "If the NonCoherentTileAttachmentReadQCOM execution "
"mode is used, the TileShadingQCOM capability must be "
"enabled.";
}
}
}
break;
}
}
Expand Down Expand Up @@ -758,6 +825,14 @@ spv_result_t ValidateExecutionMode(ValidationState_t& _,
<< "In the Vulkan environment, the PixelCenterInteger execution "
"mode must not be used.";
}
if (mode == spv::ExecutionMode::TileShadingRateQCOM) {
const auto rateX = inst->GetOperandAs<int>(2);
const auto rateY = inst->GetOperandAs<int>(3);
if ((rateX & (rateX - 1)) != 0 || (rateY & (rateY - 1)) != 0)
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "The TileShadingRateQCOM execution mode's x and y values "
"must be powers of 2.";
}
}

return SPV_SUCCESS;
Expand Down
3 changes: 3 additions & 0 deletions source/val/validation_state.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1883,6 +1883,7 @@ bool ValidationState_t::IsValidStorageClass(
case spv::StorageClass::HitObjectAttributeNV:
case spv::StorageClass::TileImageEXT:
case spv::StorageClass::NodePayloadAMDX:
case spv::StorageClass::TileAttachmentQCOM:
return true;
default:
return false;
Expand Down Expand Up @@ -2573,6 +2574,8 @@ std::string ValidationState_t::VkErrorID(uint32_t id,
return VUID_WRAP(VUID-StandaloneSpirv-Component-10583);
case 10684:
return VUID_WRAP(VUID-StandaloneSpirv-None-10684);
case 10685:
return VUID_WRAP(VUID-StandaloneSpirv-None-10685);
default:
return ""; // unknown id
}
Expand Down
20 changes: 19 additions & 1 deletion test/val/val_capability_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3214,7 +3214,8 @@ std::string MinimalShaderModuleWithCapability(std::string cap) {
: "";
return std::string("OpCapability ") + cap + extra_cap + R"(
OpCapability Shader
OpMemoryModel Logical )" + mem_model + R"(
OpMemoryModel Logical )" +
mem_model + R"(
OpEntryPoint Vertex %main "main"
%void = OpTypeVoid
%void_fn = OpTypeFunction %void
Expand Down Expand Up @@ -3383,6 +3384,23 @@ OpMemoryModel Logical GLSL450
"the VulkanMemoryModel capability must also be declared"));
}

TEST_F(ValidateCapability, TileShadingQCOM) {
const auto spirv = R"(
OpCapability Shader
OpCapability TileShadingQCOM
OpExtension "SPV_QCOM_tile_shading"
OpMemoryModel Logical GLSL450
OpEntryPoint Vertex %func "main"
)" + std::string(kVoidFVoid);

spv_target_env env = SPV_ENV_VULKAN_1_4;
CompileSuccessfully(spirv, env);
EXPECT_THAT(SPV_ERROR_INVALID_CAPABILITY, ValidateInstructions(env));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("The TileShadingQCOM capability must not be enabled "
"in any stage other than compute or fragment"));
}

} // namespace
} // namespace val
} // namespace spvtools
Loading