Validate ShaderModule limits at pipeline creation time.
A list of errors, `infringingLimits`, is added to EntryPointMetadata. During shader reflection, instead of directly bubbling limit errors up, they are stored in this list and check only later during pipeline creation. Several ShaderModule tests are reworked to create a pipeline to check for the validation of these limits. For the IO variable limits the tests needed to be reworked to check for strings in the error messages because since IO structs have to match between VS and FS, if one failed the other failed too. (so it's no possible to target the validation of one of these in particular) Bug: dawn:986 Change-Id: I689e16454488d4a3c746ece53828555ed72ed561 Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/85501 Reviewed-by: Austin Eng <enga@chromium.org> Commit-Queue: Corentin Wallez <cwallez@chromium.org>
This commit is contained in:
parent
8033af0947
commit
a2f7d02c5e
|
@ -37,6 +37,15 @@ namespace dawn::native {
|
||||||
|
|
||||||
const EntryPointMetadata& metadata = module->GetEntryPoint(entryPoint);
|
const EntryPointMetadata& metadata = module->GetEntryPoint(entryPoint);
|
||||||
|
|
||||||
|
if (!metadata.infringedLimitErrors.empty()) {
|
||||||
|
std::ostringstream out;
|
||||||
|
out << "Entry point \"" << entryPoint << "\" infringes limits:\n";
|
||||||
|
for (const std::string& limit : metadata.infringedLimitErrors) {
|
||||||
|
out << " - " << limit << "\n";
|
||||||
|
}
|
||||||
|
return DAWN_VALIDATION_ERROR(out.str());
|
||||||
|
}
|
||||||
|
|
||||||
DAWN_INVALID_IF(metadata.stage != stage,
|
DAWN_INVALID_IF(metadata.stage != stage,
|
||||||
"The stage (%s) of the entry point \"%s\" isn't the expected one (%s).",
|
"The stage (%s) of the entry point \"%s\" isn't the expected one (%s).",
|
||||||
metadata.stage, entryPoint, stage);
|
metadata.stage, entryPoint, stage);
|
||||||
|
|
|
@ -606,6 +606,17 @@ namespace dawn::native {
|
||||||
|
|
||||||
std::unique_ptr<EntryPointMetadata> metadata = std::make_unique<EntryPointMetadata>();
|
std::unique_ptr<EntryPointMetadata> metadata = std::make_unique<EntryPointMetadata>();
|
||||||
|
|
||||||
|
// Returns the invalid argument, and if it is true additionally store the formatted
|
||||||
|
// error in metadata.infringedLimits. This is to delay the emission of these validation
|
||||||
|
// errors until the entry point is used.
|
||||||
|
#define DelayedInvalidIf(invalid, ...) \
|
||||||
|
([&]() { \
|
||||||
|
if (invalid) { \
|
||||||
|
metadata->infringedLimitErrors.push_back(absl::StrFormat(__VA_ARGS__)); \
|
||||||
|
} \
|
||||||
|
return invalid; \
|
||||||
|
})()
|
||||||
|
|
||||||
if (!entryPoint.overridable_constants.empty()) {
|
if (!entryPoint.overridable_constants.empty()) {
|
||||||
DAWN_INVALID_IF(device->IsToggleEnabled(Toggle::DisallowUnsafeAPIs),
|
DAWN_INVALID_IF(device->IsToggleEnabled(Toggle::DisallowUnsafeAPIs),
|
||||||
"Pipeline overridable constants are disallowed because they "
|
"Pipeline overridable constants are disallowed because they "
|
||||||
|
@ -657,7 +668,7 @@ namespace dawn::native {
|
||||||
DAWN_TRY_ASSIGN(metadata->stage, TintPipelineStageToShaderStage(entryPoint.stage));
|
DAWN_TRY_ASSIGN(metadata->stage, TintPipelineStageToShaderStage(entryPoint.stage));
|
||||||
|
|
||||||
if (metadata->stage == SingleShaderStage::Compute) {
|
if (metadata->stage == SingleShaderStage::Compute) {
|
||||||
DAWN_INVALID_IF(
|
DelayedInvalidIf(
|
||||||
entryPoint.workgroup_size_x > limits.v1.maxComputeWorkgroupSizeX ||
|
entryPoint.workgroup_size_x > limits.v1.maxComputeWorkgroupSizeX ||
|
||||||
entryPoint.workgroup_size_y > limits.v1.maxComputeWorkgroupSizeY ||
|
entryPoint.workgroup_size_y > limits.v1.maxComputeWorkgroupSizeY ||
|
||||||
entryPoint.workgroup_size_z > limits.v1.maxComputeWorkgroupSizeZ,
|
entryPoint.workgroup_size_z > limits.v1.maxComputeWorkgroupSizeZ,
|
||||||
|
@ -671,14 +682,14 @@ namespace dawn::native {
|
||||||
// Cast to uint64_t to avoid overflow in this multiplication.
|
// Cast to uint64_t to avoid overflow in this multiplication.
|
||||||
uint64_t numInvocations = static_cast<uint64_t>(entryPoint.workgroup_size_x) *
|
uint64_t numInvocations = static_cast<uint64_t>(entryPoint.workgroup_size_x) *
|
||||||
entryPoint.workgroup_size_y * entryPoint.workgroup_size_z;
|
entryPoint.workgroup_size_y * entryPoint.workgroup_size_z;
|
||||||
DAWN_INVALID_IF(numInvocations > limits.v1.maxComputeInvocationsPerWorkgroup,
|
DelayedInvalidIf(numInvocations > limits.v1.maxComputeInvocationsPerWorkgroup,
|
||||||
"The total number of workgroup invocations (%u) exceeds the "
|
"The total number of workgroup invocations (%u) exceeds the "
|
||||||
"maximum allowed (%u).",
|
"maximum allowed (%u).",
|
||||||
numInvocations, limits.v1.maxComputeInvocationsPerWorkgroup);
|
numInvocations, limits.v1.maxComputeInvocationsPerWorkgroup);
|
||||||
|
|
||||||
const size_t workgroupStorageSize =
|
const size_t workgroupStorageSize =
|
||||||
inspector->GetWorkgroupStorageSize(entryPoint.name);
|
inspector->GetWorkgroupStorageSize(entryPoint.name);
|
||||||
DAWN_INVALID_IF(workgroupStorageSize > limits.v1.maxComputeWorkgroupStorageSize,
|
DelayedInvalidIf(workgroupStorageSize > limits.v1.maxComputeWorkgroupStorageSize,
|
||||||
"The total use of workgroup storage (%u bytes) is larger than "
|
"The total use of workgroup storage (%u bytes) is larger than "
|
||||||
"the maximum allowed (%u bytes).",
|
"the maximum allowed (%u bytes).",
|
||||||
workgroupStorageSize, limits.v1.maxComputeWorkgroupStorageSize);
|
workgroupStorageSize, limits.v1.maxComputeWorkgroupStorageSize);
|
||||||
|
@ -698,12 +709,15 @@ namespace dawn::native {
|
||||||
inputVar.name);
|
inputVar.name);
|
||||||
|
|
||||||
uint32_t unsanitizedLocation = inputVar.location_decoration;
|
uint32_t unsanitizedLocation = inputVar.location_decoration;
|
||||||
DAWN_INVALID_IF(unsanitizedLocation >= kMaxVertexAttributes,
|
if (DelayedInvalidIf(unsanitizedLocation >= kMaxVertexAttributes,
|
||||||
"Vertex input variable \"%s\" has a location (%u) that "
|
"Vertex input variable \"%s\" has a location (%u) that "
|
||||||
"exceeds the maximum (%u)",
|
"exceeds the maximum (%u)",
|
||||||
inputVar.name, unsanitizedLocation, kMaxVertexAttributes);
|
inputVar.name, unsanitizedLocation,
|
||||||
VertexAttributeLocation location(static_cast<uint8_t>(unsanitizedLocation));
|
kMaxVertexAttributes)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
VertexAttributeLocation location(static_cast<uint8_t>(unsanitizedLocation));
|
||||||
DAWN_TRY_ASSIGN(
|
DAWN_TRY_ASSIGN(
|
||||||
metadata->vertexInputBaseTypes[location],
|
metadata->vertexInputBaseTypes[location],
|
||||||
TintComponentTypeToVertexFormatBaseType(inputVar.component_type));
|
TintComponentTypeToVertexFormatBaseType(inputVar.component_type));
|
||||||
|
@ -714,36 +728,38 @@ namespace dawn::native {
|
||||||
// output variable by Tint so we directly add its components to the total.
|
// output variable by Tint so we directly add its components to the total.
|
||||||
uint32_t totalInterStageShaderComponents = 4;
|
uint32_t totalInterStageShaderComponents = 4;
|
||||||
for (const auto& outputVar : entryPoint.output_variables) {
|
for (const auto& outputVar : entryPoint.output_variables) {
|
||||||
|
EntryPointMetadata::InterStageVariableInfo variable;
|
||||||
|
DAWN_TRY_ASSIGN(variable.baseType, TintComponentTypeToInterStageComponentType(
|
||||||
|
outputVar.component_type));
|
||||||
|
DAWN_TRY_ASSIGN(
|
||||||
|
variable.componentCount,
|
||||||
|
TintCompositionTypeToInterStageComponentCount(outputVar.composition_type));
|
||||||
|
DAWN_TRY_ASSIGN(
|
||||||
|
variable.interpolationType,
|
||||||
|
TintInterpolationTypeToInterpolationType(outputVar.interpolation_type));
|
||||||
|
DAWN_TRY_ASSIGN(variable.interpolationSampling,
|
||||||
|
TintInterpolationSamplingToInterpolationSamplingType(
|
||||||
|
outputVar.interpolation_sampling));
|
||||||
|
totalInterStageShaderComponents += variable.componentCount;
|
||||||
|
|
||||||
DAWN_INVALID_IF(
|
DAWN_INVALID_IF(
|
||||||
!outputVar.has_location_decoration,
|
!outputVar.has_location_decoration,
|
||||||
"Vertex ouput variable \"%s\" doesn't have a location decoration.",
|
"Vertex ouput variable \"%s\" doesn't have a location decoration.",
|
||||||
outputVar.name);
|
outputVar.name);
|
||||||
|
|
||||||
uint32_t location = outputVar.location_decoration;
|
uint32_t location = outputVar.location_decoration;
|
||||||
DAWN_INVALID_IF(location > kMaxInterStageShaderLocation,
|
if (DelayedInvalidIf(location > kMaxInterStageShaderLocation,
|
||||||
"Vertex output variable \"%s\" has a location (%u) that "
|
"Vertex output variable \"%s\" has a location (%u) that "
|
||||||
"exceeds the maximum (%u).",
|
"exceeds the maximum (%u).",
|
||||||
outputVar.name, location, kMaxInterStageShaderLocation);
|
outputVar.name, location, kMaxInterStageShaderLocation)) {
|
||||||
|
continue;
|
||||||
metadata->usedInterStageVariables.set(location);
|
|
||||||
DAWN_TRY_ASSIGN(
|
|
||||||
metadata->interStageVariables[location].baseType,
|
|
||||||
TintComponentTypeToInterStageComponentType(outputVar.component_type));
|
|
||||||
DAWN_TRY_ASSIGN(
|
|
||||||
metadata->interStageVariables[location].componentCount,
|
|
||||||
TintCompositionTypeToInterStageComponentCount(outputVar.composition_type));
|
|
||||||
DAWN_TRY_ASSIGN(
|
|
||||||
metadata->interStageVariables[location].interpolationType,
|
|
||||||
TintInterpolationTypeToInterpolationType(outputVar.interpolation_type));
|
|
||||||
DAWN_TRY_ASSIGN(metadata->interStageVariables[location].interpolationSampling,
|
|
||||||
TintInterpolationSamplingToInterpolationSamplingType(
|
|
||||||
outputVar.interpolation_sampling));
|
|
||||||
|
|
||||||
totalInterStageShaderComponents +=
|
|
||||||
metadata->interStageVariables[location].componentCount;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DAWN_INVALID_IF(
|
metadata->usedInterStageVariables.set(location);
|
||||||
|
metadata->interStageVariables[location] = variable;
|
||||||
|
}
|
||||||
|
|
||||||
|
DelayedInvalidIf(
|
||||||
totalInterStageShaderComponents > kMaxInterStageShaderComponents,
|
totalInterStageShaderComponents > kMaxInterStageShaderComponents,
|
||||||
"Total vertex output components count (%u) exceeds the maximum (%u).",
|
"Total vertex output components count (%u) exceeds the maximum (%u).",
|
||||||
totalInterStageShaderComponents, kMaxInterStageShaderComponents);
|
totalInterStageShaderComponents, kMaxInterStageShaderComponents);
|
||||||
|
@ -752,33 +768,35 @@ namespace dawn::native {
|
||||||
if (metadata->stage == SingleShaderStage::Fragment) {
|
if (metadata->stage == SingleShaderStage::Fragment) {
|
||||||
uint32_t totalInterStageShaderComponents = 0;
|
uint32_t totalInterStageShaderComponents = 0;
|
||||||
for (const auto& inputVar : entryPoint.input_variables) {
|
for (const auto& inputVar : entryPoint.input_variables) {
|
||||||
|
EntryPointMetadata::InterStageVariableInfo variable;
|
||||||
|
DAWN_TRY_ASSIGN(variable.baseType, TintComponentTypeToInterStageComponentType(
|
||||||
|
inputVar.component_type));
|
||||||
|
DAWN_TRY_ASSIGN(
|
||||||
|
variable.componentCount,
|
||||||
|
TintCompositionTypeToInterStageComponentCount(inputVar.composition_type));
|
||||||
|
DAWN_TRY_ASSIGN(
|
||||||
|
variable.interpolationType,
|
||||||
|
TintInterpolationTypeToInterpolationType(inputVar.interpolation_type));
|
||||||
|
DAWN_TRY_ASSIGN(variable.interpolationSampling,
|
||||||
|
TintInterpolationSamplingToInterpolationSamplingType(
|
||||||
|
inputVar.interpolation_sampling));
|
||||||
|
totalInterStageShaderComponents += variable.componentCount;
|
||||||
|
|
||||||
DAWN_INVALID_IF(
|
DAWN_INVALID_IF(
|
||||||
!inputVar.has_location_decoration,
|
!inputVar.has_location_decoration,
|
||||||
"Fragment input variable \"%s\" doesn't have a location decoration.",
|
"Fragment input variable \"%s\" doesn't have a location decoration.",
|
||||||
inputVar.name);
|
inputVar.name);
|
||||||
|
|
||||||
uint32_t location = inputVar.location_decoration;
|
uint32_t location = inputVar.location_decoration;
|
||||||
DAWN_INVALID_IF(location > kMaxInterStageShaderLocation,
|
if (DelayedInvalidIf(location > kMaxInterStageShaderLocation,
|
||||||
"Fragment input variable \"%s\" has a location (%u) that "
|
"Fragment input variable \"%s\" has a location (%u) that "
|
||||||
"exceeds the maximum (%u).",
|
"exceeds the maximum (%u).",
|
||||||
inputVar.name, location, kMaxInterStageShaderLocation);
|
inputVar.name, location, kMaxInterStageShaderLocation)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
metadata->usedInterStageVariables.set(location);
|
metadata->usedInterStageVariables.set(location);
|
||||||
DAWN_TRY_ASSIGN(
|
metadata->interStageVariables[location] = variable;
|
||||||
metadata->interStageVariables[location].baseType,
|
|
||||||
TintComponentTypeToInterStageComponentType(inputVar.component_type));
|
|
||||||
DAWN_TRY_ASSIGN(
|
|
||||||
metadata->interStageVariables[location].componentCount,
|
|
||||||
TintCompositionTypeToInterStageComponentCount(inputVar.composition_type));
|
|
||||||
DAWN_TRY_ASSIGN(
|
|
||||||
metadata->interStageVariables[location].interpolationType,
|
|
||||||
TintInterpolationTypeToInterpolationType(inputVar.interpolation_type));
|
|
||||||
DAWN_TRY_ASSIGN(metadata->interStageVariables[location].interpolationSampling,
|
|
||||||
TintInterpolationSamplingToInterpolationSamplingType(
|
|
||||||
inputVar.interpolation_sampling));
|
|
||||||
|
|
||||||
totalInterStageShaderComponents +=
|
|
||||||
metadata->interStageVariables[location].componentCount;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entryPoint.front_facing_used) {
|
if (entryPoint.front_facing_used) {
|
||||||
|
@ -794,91 +812,77 @@ namespace dawn::native {
|
||||||
totalInterStageShaderComponents += 4;
|
totalInterStageShaderComponents += 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
DAWN_INVALID_IF(
|
DelayedInvalidIf(
|
||||||
totalInterStageShaderComponents > kMaxInterStageShaderComponents,
|
totalInterStageShaderComponents > kMaxInterStageShaderComponents,
|
||||||
"Total fragment input components count (%u) exceeds the maximum (%u).",
|
"Total fragment input components count (%u) exceeds the maximum (%u).",
|
||||||
totalInterStageShaderComponents, kMaxInterStageShaderComponents);
|
totalInterStageShaderComponents, kMaxInterStageShaderComponents);
|
||||||
|
|
||||||
for (const auto& outputVar : entryPoint.output_variables) {
|
for (const auto& outputVar : entryPoint.output_variables) {
|
||||||
|
EntryPointMetadata::FragmentOutputVariableInfo variable;
|
||||||
|
DAWN_TRY_ASSIGN(variable.baseType, TintComponentTypeToTextureComponentType(
|
||||||
|
outputVar.component_type));
|
||||||
|
DAWN_TRY_ASSIGN(
|
||||||
|
variable.componentCount,
|
||||||
|
TintCompositionTypeToInterStageComponentCount(outputVar.composition_type));
|
||||||
|
ASSERT(variable.componentCount <= 4);
|
||||||
|
|
||||||
DAWN_INVALID_IF(
|
DAWN_INVALID_IF(
|
||||||
!outputVar.has_location_decoration,
|
!outputVar.has_location_decoration,
|
||||||
"Fragment input variable \"%s\" doesn't have a location decoration.",
|
"Fragment input variable \"%s\" doesn't have a location decoration.",
|
||||||
outputVar.name);
|
outputVar.name);
|
||||||
|
|
||||||
uint32_t unsanitizedAttachment = outputVar.location_decoration;
|
uint32_t unsanitizedAttachment = outputVar.location_decoration;
|
||||||
DAWN_INVALID_IF(unsanitizedAttachment >= kMaxColorAttachments,
|
if (DelayedInvalidIf(unsanitizedAttachment >= kMaxColorAttachments,
|
||||||
"Fragment output variable \"%s\" has a location (%u) that "
|
"Fragment output variable \"%s\" has a location (%u) that "
|
||||||
"exceeds the maximum (%u).",
|
"exceeds the maximum (%u).",
|
||||||
outputVar.name, unsanitizedAttachment, kMaxColorAttachments);
|
outputVar.name, unsanitizedAttachment,
|
||||||
ColorAttachmentIndex attachment(static_cast<uint8_t>(unsanitizedAttachment));
|
kMaxColorAttachments)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
DAWN_TRY_ASSIGN(
|
ColorAttachmentIndex attachment(static_cast<uint8_t>(unsanitizedAttachment));
|
||||||
metadata->fragmentOutputVariables[attachment].baseType,
|
metadata->fragmentOutputVariables[attachment] = variable;
|
||||||
TintComponentTypeToTextureComponentType(outputVar.component_type));
|
|
||||||
uint32_t componentCount;
|
|
||||||
DAWN_TRY_ASSIGN(componentCount, TintCompositionTypeToInterStageComponentCount(
|
|
||||||
outputVar.composition_type));
|
|
||||||
// componentCount should be no larger than 4u
|
|
||||||
ASSERT(componentCount <= 4u);
|
|
||||||
metadata->fragmentOutputVariables[attachment].componentCount = componentCount;
|
|
||||||
metadata->fragmentOutputsWritten.set(attachment);
|
metadata->fragmentOutputsWritten.set(attachment);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const tint::inspector::ResourceBinding& resource :
|
for (const tint::inspector::ResourceBinding& resource :
|
||||||
inspector->GetResourceBindings(entryPoint.name)) {
|
inspector->GetResourceBindings(entryPoint.name)) {
|
||||||
DAWN_INVALID_IF(resource.bind_group >= kMaxBindGroups,
|
ShaderBindingInfo info;
|
||||||
"The entry-point uses a binding with a group decoration (%u) "
|
|
||||||
"that exceeds the maximum (%u).",
|
|
||||||
resource.bind_group, kMaxBindGroups);
|
|
||||||
|
|
||||||
BindingNumber bindingNumber(resource.binding);
|
info.bindingType = TintResourceTypeToBindingInfoType(resource.resource_type);
|
||||||
BindGroupIndex bindGroupIndex(resource.bind_group);
|
|
||||||
|
|
||||||
DAWN_INVALID_IF(bindingNumber > kMaxBindingNumberTyped,
|
switch (info.bindingType) {
|
||||||
"Binding number (%u) exceeds the maximum binding number (%u).",
|
|
||||||
uint32_t(bindingNumber), uint32_t(kMaxBindingNumberTyped));
|
|
||||||
|
|
||||||
const auto& [binding, inserted] =
|
|
||||||
metadata->bindings[bindGroupIndex].emplace(bindingNumber, ShaderBindingInfo{});
|
|
||||||
DAWN_INVALID_IF(!inserted,
|
|
||||||
"Entry-point has a duplicate binding for (group:%u, binding:%u).",
|
|
||||||
resource.binding, resource.bind_group);
|
|
||||||
|
|
||||||
ShaderBindingInfo* info = &binding->second;
|
|
||||||
info->bindingType = TintResourceTypeToBindingInfoType(resource.resource_type);
|
|
||||||
|
|
||||||
switch (info->bindingType) {
|
|
||||||
case BindingInfoType::Buffer:
|
case BindingInfoType::Buffer:
|
||||||
info->buffer.minBindingSize = resource.size_no_padding;
|
info.buffer.minBindingSize = resource.size_no_padding;
|
||||||
DAWN_TRY_ASSIGN(info->buffer.type, TintResourceTypeToBufferBindingType(
|
DAWN_TRY_ASSIGN(info.buffer.type, TintResourceTypeToBufferBindingType(
|
||||||
resource.resource_type));
|
resource.resource_type));
|
||||||
break;
|
break;
|
||||||
case BindingInfoType::Sampler:
|
case BindingInfoType::Sampler:
|
||||||
switch (resource.resource_type) {
|
switch (resource.resource_type) {
|
||||||
case tint::inspector::ResourceBinding::ResourceType::kSampler:
|
case tint::inspector::ResourceBinding::ResourceType::kSampler:
|
||||||
info->sampler.isComparison = false;
|
info.sampler.isComparison = false;
|
||||||
break;
|
break;
|
||||||
case tint::inspector::ResourceBinding::ResourceType::kComparisonSampler:
|
case tint::inspector::ResourceBinding::ResourceType::kComparisonSampler:
|
||||||
info->sampler.isComparison = true;
|
info.sampler.isComparison = true;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
UNREACHABLE();
|
UNREACHABLE();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case BindingInfoType::Texture:
|
case BindingInfoType::Texture:
|
||||||
info->texture.viewDimension =
|
info.texture.viewDimension =
|
||||||
TintTextureDimensionToTextureViewDimension(resource.dim);
|
TintTextureDimensionToTextureViewDimension(resource.dim);
|
||||||
if (resource.resource_type ==
|
if (resource.resource_type ==
|
||||||
tint::inspector::ResourceBinding::ResourceType::kDepthTexture ||
|
tint::inspector::ResourceBinding::ResourceType::kDepthTexture ||
|
||||||
resource.resource_type == tint::inspector::ResourceBinding::
|
resource.resource_type == tint::inspector::ResourceBinding::
|
||||||
ResourceType::kDepthMultisampledTexture) {
|
ResourceType::kDepthMultisampledTexture) {
|
||||||
info->texture.compatibleSampleTypes = SampleTypeBit::Depth;
|
info.texture.compatibleSampleTypes = SampleTypeBit::Depth;
|
||||||
} else {
|
} else {
|
||||||
info->texture.compatibleSampleTypes =
|
info.texture.compatibleSampleTypes =
|
||||||
TintSampledKindToSampleTypeBit(resource.sampled_kind);
|
TintSampledKindToSampleTypeBit(resource.sampled_kind);
|
||||||
}
|
}
|
||||||
info->texture.multisampled =
|
info.texture.multisampled =
|
||||||
resource.resource_type == tint::inspector::ResourceBinding::
|
resource.resource_type == tint::inspector::ResourceBinding::
|
||||||
ResourceType::kMultisampledTexture ||
|
ResourceType::kMultisampledTexture ||
|
||||||
resource.resource_type == tint::inspector::ResourceBinding::
|
resource.resource_type == tint::inspector::ResourceBinding::
|
||||||
|
@ -887,11 +891,11 @@ namespace dawn::native {
|
||||||
break;
|
break;
|
||||||
case BindingInfoType::StorageTexture:
|
case BindingInfoType::StorageTexture:
|
||||||
DAWN_TRY_ASSIGN(
|
DAWN_TRY_ASSIGN(
|
||||||
info->storageTexture.access,
|
info.storageTexture.access,
|
||||||
TintResourceTypeToStorageTextureAccess(resource.resource_type));
|
TintResourceTypeToStorageTextureAccess(resource.resource_type));
|
||||||
info->storageTexture.format =
|
info.storageTexture.format =
|
||||||
TintImageFormatToTextureFormat(resource.image_format);
|
TintImageFormatToTextureFormat(resource.image_format);
|
||||||
info->storageTexture.viewDimension =
|
info.storageTexture.viewDimension =
|
||||||
TintTextureDimensionToTextureViewDimension(resource.dim);
|
TintTextureDimensionToTextureViewDimension(resource.dim);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
@ -900,6 +904,25 @@ namespace dawn::native {
|
||||||
default:
|
default:
|
||||||
return DAWN_VALIDATION_ERROR("Unknown binding type in Shader");
|
return DAWN_VALIDATION_ERROR("Unknown binding type in Shader");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BindingNumber bindingNumber(resource.binding);
|
||||||
|
BindGroupIndex bindGroupIndex(resource.bind_group);
|
||||||
|
|
||||||
|
if (DelayedInvalidIf(bindGroupIndex >= kMaxBindGroupsTyped,
|
||||||
|
"The entry-point uses a binding with a group decoration (%u) "
|
||||||
|
"that exceeds the maximum (%u).",
|
||||||
|
resource.bind_group, kMaxBindGroups) ||
|
||||||
|
DelayedInvalidIf(bindingNumber > kMaxBindingNumberTyped,
|
||||||
|
"Binding number (%u) exceeds the maximum binding number (%u).",
|
||||||
|
uint32_t(bindingNumber), uint32_t(kMaxBindingNumberTyped))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& [binding, inserted] =
|
||||||
|
metadata->bindings[bindGroupIndex].emplace(bindingNumber, info);
|
||||||
|
DAWN_INVALID_IF(!inserted,
|
||||||
|
"Entry-point has a duplicate binding for (group:%u, binding:%u).",
|
||||||
|
resource.binding, resource.bind_group);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<tint::inspector::SamplerTexturePair> samplerTextureUses =
|
std::vector<tint::inspector::SamplerTexturePair> samplerTextureUses =
|
||||||
|
@ -916,6 +939,7 @@ namespace dawn::native {
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
#undef DelayedInvalidIf
|
||||||
return std::move(metadata);
|
return std::move(metadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -165,6 +165,12 @@ namespace dawn::native {
|
||||||
// pointers to EntryPointMetadata are safe to store as long as you also keep a Ref to the
|
// pointers to EntryPointMetadata are safe to store as long as you also keep a Ref to the
|
||||||
// ShaderModuleBase.
|
// ShaderModuleBase.
|
||||||
struct EntryPointMetadata {
|
struct EntryPointMetadata {
|
||||||
|
// It is valid for a shader to contain entry points that go over limits. To keep this
|
||||||
|
// structure with packed arrays and bitsets, we still validate against limits when
|
||||||
|
// doing reflection, but store the errors in this vector, for later use if the application
|
||||||
|
// tries to use the entry point.
|
||||||
|
std::vector<std::string> infringedLimitErrors;
|
||||||
|
|
||||||
// bindings[G][B] is the reflection data for the binding defined with
|
// bindings[G][B] is the reflection data for the binding defined with
|
||||||
// @group(G) @binding(B) in WGSL / SPIRV.
|
// @group(G) @binding(B) in WGSL / SPIRV.
|
||||||
BindingInfoArray bindings;
|
BindingInfoArray bindings;
|
||||||
|
|
|
@ -12,12 +12,11 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
#include "dawn/common/Constants.h"
|
|
||||||
|
|
||||||
#include "dawn/native/ShaderModule.h"
|
|
||||||
|
|
||||||
#include "dawn/tests/unittests/validation/ValidationTest.h"
|
#include "dawn/tests/unittests/validation/ValidationTest.h"
|
||||||
|
|
||||||
|
#include "dawn/common/Constants.h"
|
||||||
|
#include "dawn/native/ShaderModule.h"
|
||||||
|
#include "dawn/utils/ComboRenderPipelineDescriptor.h"
|
||||||
#include "dawn/utils/WGPUHelpers.h"
|
#include "dawn/utils/WGPUHelpers.h"
|
||||||
|
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
@ -214,85 +213,114 @@ TEST_F(ShaderModuleValidationTest, GetCompilationMessages) {
|
||||||
// Validate the maximum location of effective inter-stage variables cannot be greater than 14
|
// Validate the maximum location of effective inter-stage variables cannot be greater than 14
|
||||||
// (kMaxInterStageShaderComponents / 4 - 1).
|
// (kMaxInterStageShaderComponents / 4 - 1).
|
||||||
TEST_F(ShaderModuleValidationTest, MaximumShaderIOLocations) {
|
TEST_F(ShaderModuleValidationTest, MaximumShaderIOLocations) {
|
||||||
auto generateShaderForTest = [](uint32_t maximumOutputLocation, wgpu::ShaderStage shaderStage) {
|
auto CheckTestPipeline = [&](bool success, uint32_t maximumOutputLocation,
|
||||||
|
wgpu::ShaderStage failingShaderStage) {
|
||||||
|
// Build the ShaderIO struct containing variables up to maximumOutputLocation.
|
||||||
std::ostringstream stream;
|
std::ostringstream stream;
|
||||||
stream << "struct ShaderIO {" << std::endl;
|
stream << "struct ShaderIO {" << std::endl;
|
||||||
for (uint32_t location = 1; location <= maximumOutputLocation; ++location) {
|
for (uint32_t location = 1; location <= maximumOutputLocation; ++location) {
|
||||||
stream << "@location(" << location << ") var" << location << ": f32;" << std::endl;
|
stream << "@location(" << location << ") var" << location << ": f32," << std::endl;
|
||||||
}
|
}
|
||||||
switch (shaderStage) {
|
|
||||||
|
if (failingShaderStage == wgpu::ShaderStage::Vertex) {
|
||||||
|
stream << " @builtin(position) pos: vec4<f32>,";
|
||||||
|
}
|
||||||
|
stream << "}\n";
|
||||||
|
|
||||||
|
std::string ioStruct = stream.str();
|
||||||
|
|
||||||
|
// Build the test pipeline. Note that it's not possible with just ASSERT_DEVICE_ERROR
|
||||||
|
// whether it is the vertex or fragment shader that fails. So instead we will look for the
|
||||||
|
// string "failingVertex" or "failingFragment" in the error message.
|
||||||
|
utils::ComboRenderPipelineDescriptor pDesc;
|
||||||
|
pDesc.cTargets[0].format = wgpu::TextureFormat::RGBA8Unorm;
|
||||||
|
|
||||||
|
const char* errorMatcher = nullptr;
|
||||||
|
switch (failingShaderStage) {
|
||||||
case wgpu::ShaderStage::Vertex: {
|
case wgpu::ShaderStage::Vertex: {
|
||||||
stream << R"(
|
errorMatcher = "failingVertex";
|
||||||
@builtin(position) pos: vec4<f32>;
|
pDesc.vertex.entryPoint = "failingVertex";
|
||||||
};
|
pDesc.vertex.module = utils::CreateShaderModule(device, (ioStruct + R"(
|
||||||
@stage(vertex) fn main() -> ShaderIO {
|
@stage(vertex) fn failingVertex() -> ShaderIO {
|
||||||
var shaderIO : ShaderIO;
|
var shaderIO : ShaderIO;
|
||||||
shaderIO.pos = vec4<f32>(0.0, 0.0, 0.0, 1.0);
|
shaderIO.pos = vec4<f32>(0.0, 0.0, 0.0, 1.0);
|
||||||
return shaderIO;
|
return shaderIO;
|
||||||
})";
|
}
|
||||||
} break;
|
)")
|
||||||
|
.c_str());
|
||||||
|
pDesc.cFragment.module = utils::CreateShaderModule(device, R"(
|
||||||
|
@stage(fragment) fn main() -> @location(0) vec4<f32> {
|
||||||
|
return vec4<f32>(0.0);
|
||||||
|
}
|
||||||
|
)");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case wgpu::ShaderStage::Fragment: {
|
case wgpu::ShaderStage::Fragment: {
|
||||||
stream << R"(
|
errorMatcher = "failingFragment";
|
||||||
};
|
pDesc.cFragment.entryPoint = "failingFragment";
|
||||||
@stage(fragment) fn main(shaderIO: ShaderIO) -> @location(0) vec4<f32> {
|
pDesc.cFragment.module = utils::CreateShaderModule(device, (ioStruct + R"(
|
||||||
return vec4<f32>(0.0, 0.0, 0.0, 1.0);
|
@stage(fragment) fn failingFragment(io : ShaderIO) -> @location(0) vec4<f32> {
|
||||||
})";
|
return vec4<f32>(0.0);
|
||||||
} break;
|
}
|
||||||
|
)")
|
||||||
|
.c_str());
|
||||||
|
pDesc.vertex.module = utils::CreateShaderModule(device, R"(
|
||||||
|
@stage(vertex) fn main() -> @builtin(position) vec4<f32> {
|
||||||
|
return vec4<f32>(0.0);
|
||||||
|
}
|
||||||
|
)");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case wgpu::ShaderStage::Compute:
|
|
||||||
default:
|
default:
|
||||||
UNREACHABLE();
|
UNREACHABLE();
|
||||||
}
|
}
|
||||||
|
|
||||||
return stream.str();
|
if (success) {
|
||||||
|
ASSERT_DEVICE_ERROR(
|
||||||
|
device.CreateRenderPipeline(&pDesc),
|
||||||
|
testing::HasSubstr(
|
||||||
|
"One or more fragment inputs and vertex outputs are not one-to-one matching"));
|
||||||
|
} else {
|
||||||
|
ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&pDesc),
|
||||||
|
testing::HasSubstr(errorMatcher));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
constexpr uint32_t kMaxInterShaderIOLocation = kMaxInterStageShaderComponents / 4 - 1;
|
constexpr uint32_t kMaxInterShaderIOLocation = kMaxInterStageShaderComponents / 4 - 1;
|
||||||
|
|
||||||
// It is allowed to create a shader module with the maximum active vertex output location == 14;
|
// It is allowed to create a shader module with the maximum active vertex output location == 14;
|
||||||
{
|
CheckTestPipeline(true, kMaxInterShaderIOLocation, wgpu::ShaderStage::Vertex);
|
||||||
std::string vertexShader =
|
|
||||||
generateShaderForTest(kMaxInterShaderIOLocation, wgpu::ShaderStage::Vertex);
|
|
||||||
utils::CreateShaderModule(device, vertexShader.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
// It isn't allowed to create a shader module with the maximum active vertex output location >
|
// It isn't allowed to create a shader module with the maximum active vertex output location >
|
||||||
// 14;
|
// 14;
|
||||||
{
|
CheckTestPipeline(false, kMaxInterShaderIOLocation + 1, wgpu::ShaderStage::Vertex);
|
||||||
std::string vertexShader =
|
|
||||||
generateShaderForTest(kMaxInterShaderIOLocation + 1, wgpu::ShaderStage::Vertex);
|
|
||||||
ASSERT_DEVICE_ERROR(utils::CreateShaderModule(device, vertexShader.c_str()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// It is allowed to create a shader module with the maximum active fragment input location ==
|
// It is allowed to create a shader module with the maximum active fragment input location ==
|
||||||
// 14;
|
// 14;
|
||||||
{
|
CheckTestPipeline(true, kMaxInterShaderIOLocation, wgpu::ShaderStage::Fragment);
|
||||||
std::string fragmentShader =
|
|
||||||
generateShaderForTest(kMaxInterShaderIOLocation, wgpu::ShaderStage::Fragment);
|
|
||||||
utils::CreateShaderModule(device, fragmentShader.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
// It is allowed to create a shader module with the maximum active vertex output location > 14;
|
// It is allowed to create a shader module with the maximum active vertex output location > 14;
|
||||||
{
|
CheckTestPipeline(false, kMaxInterShaderIOLocation + 1, wgpu::ShaderStage::Fragment);
|
||||||
std::string fragmentShader =
|
|
||||||
generateShaderForTest(kMaxInterShaderIOLocation + 1, wgpu::ShaderStage::Fragment);
|
|
||||||
ASSERT_DEVICE_ERROR(utils::CreateShaderModule(device, fragmentShader.c_str()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate the maximum number of total inter-stage user-defined variable component count and
|
// Validate the maximum number of total inter-stage user-defined variable component count and
|
||||||
// built-in variables cannot exceed kMaxInterStageShaderComponents.
|
// built-in variables cannot exceed kMaxInterStageShaderComponents.
|
||||||
TEST_F(ShaderModuleValidationTest, MaximumInterStageShaderComponents) {
|
TEST_F(ShaderModuleValidationTest, MaximumInterStageShaderComponents) {
|
||||||
auto generateShaderForTest = [](uint32_t totalUserDefinedInterStageShaderComponentCount,
|
auto CheckTestPipeline = [&](bool success,
|
||||||
wgpu::ShaderStage shaderStage,
|
uint32_t totalUserDefinedInterStageShaderComponentCount,
|
||||||
const char* builtInDeclarations) {
|
wgpu::ShaderStage failingShaderStage,
|
||||||
|
const char* extraBuiltInDeclarations = "") {
|
||||||
|
// Build the ShaderIO struct containing totalUserDefinedInterStageShaderComponentCount
|
||||||
|
// components. Components are added in two parts, a bunch of vec4s, then one additional
|
||||||
|
// variable for the remaining components.
|
||||||
std::ostringstream stream;
|
std::ostringstream stream;
|
||||||
stream << "struct ShaderIO {" << std::endl << builtInDeclarations << std::endl;
|
stream << "struct ShaderIO {" << std::endl << extraBuiltInDeclarations << std::endl;
|
||||||
uint32_t vec4InputLocations = totalUserDefinedInterStageShaderComponentCount / 4;
|
uint32_t vec4InputLocations = totalUserDefinedInterStageShaderComponentCount / 4;
|
||||||
|
|
||||||
for (uint32_t location = 0; location < vec4InputLocations; ++location) {
|
for (uint32_t location = 0; location < vec4InputLocations; ++location) {
|
||||||
stream << "@location(" << location << ") var" << location << ": vec4<f32>;"
|
stream << "@location(" << location << ") var" << location << ": vec4<f32>,"
|
||||||
<< std::endl;
|
<< std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -300,163 +328,161 @@ TEST_F(ShaderModuleValidationTest, MaximumInterStageShaderComponents) {
|
||||||
if (lastComponentCount > 0) {
|
if (lastComponentCount > 0) {
|
||||||
stream << "@location(" << vec4InputLocations << ") var" << vec4InputLocations << ": ";
|
stream << "@location(" << vec4InputLocations << ") var" << vec4InputLocations << ": ";
|
||||||
if (lastComponentCount == 1) {
|
if (lastComponentCount == 1) {
|
||||||
stream << "f32;";
|
stream << "f32,";
|
||||||
} else {
|
} else {
|
||||||
stream << " vec" << lastComponentCount << "<f32>;";
|
stream << " vec" << lastComponentCount << "<f32>,";
|
||||||
}
|
}
|
||||||
stream << std::endl;
|
stream << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (shaderStage) {
|
if (failingShaderStage == wgpu::ShaderStage::Vertex) {
|
||||||
|
stream << " @builtin(position) pos: vec4<f32>,";
|
||||||
|
}
|
||||||
|
stream << "}\n";
|
||||||
|
|
||||||
|
std::string ioStruct = stream.str();
|
||||||
|
|
||||||
|
// Build the test pipeline. Note that it's not possible with just ASSERT_DEVICE_ERROR
|
||||||
|
// whether it is the vertex or fragment shader that fails. So instead we will look for the
|
||||||
|
// string "failingVertex" or "failingFragment" in the error message.
|
||||||
|
utils::ComboRenderPipelineDescriptor pDesc;
|
||||||
|
pDesc.cTargets[0].format = wgpu::TextureFormat::RGBA8Unorm;
|
||||||
|
|
||||||
|
const char* errorMatcher = nullptr;
|
||||||
|
switch (failingShaderStage) {
|
||||||
case wgpu::ShaderStage::Vertex: {
|
case wgpu::ShaderStage::Vertex: {
|
||||||
stream << R"(
|
errorMatcher = "failingVertex";
|
||||||
@builtin(position) pos: vec4<f32>;
|
pDesc.vertex.entryPoint = "failingVertex";
|
||||||
};
|
pDesc.vertex.module = utils::CreateShaderModule(device, (ioStruct + R"(
|
||||||
@stage(vertex) fn main() -> ShaderIO {
|
@stage(vertex) fn failingVertex() -> ShaderIO {
|
||||||
var shaderIO : ShaderIO;
|
var shaderIO : ShaderIO;
|
||||||
shaderIO.pos = vec4<f32>(0.0, 0.0, 0.0, 1.0);
|
shaderIO.pos = vec4<f32>(0.0, 0.0, 0.0, 1.0);
|
||||||
return shaderIO;
|
return shaderIO;
|
||||||
})";
|
}
|
||||||
} break;
|
)")
|
||||||
|
.c_str());
|
||||||
|
pDesc.cFragment.module = utils::CreateShaderModule(device, R"(
|
||||||
|
@stage(fragment) fn main() -> @location(0) vec4<f32> {
|
||||||
|
return vec4<f32>(0.0);
|
||||||
|
}
|
||||||
|
)");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case wgpu::ShaderStage::Fragment: {
|
case wgpu::ShaderStage::Fragment: {
|
||||||
stream << R"(
|
errorMatcher = "failingFragment";
|
||||||
};
|
pDesc.cFragment.entryPoint = "failingFragment";
|
||||||
@stage(fragment) fn main(shaderIO: ShaderIO) -> @location(0) vec4<f32> {
|
pDesc.cFragment.module = utils::CreateShaderModule(device, (ioStruct + R"(
|
||||||
return vec4<f32>(0.0, 0.0, 0.0, 1.0);
|
@stage(fragment) fn failingFragment(io : ShaderIO) -> @location(0) vec4<f32> {
|
||||||
})";
|
return vec4<f32>(0.0);
|
||||||
} break;
|
}
|
||||||
|
)")
|
||||||
|
.c_str());
|
||||||
|
pDesc.vertex.module = utils::CreateShaderModule(device, R"(
|
||||||
|
@stage(vertex) fn main() -> @builtin(position) vec4<f32> {
|
||||||
|
return vec4<f32>(0.0);
|
||||||
|
}
|
||||||
|
)");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case wgpu::ShaderStage::Compute:
|
|
||||||
default:
|
default:
|
||||||
UNREACHABLE();
|
UNREACHABLE();
|
||||||
}
|
}
|
||||||
|
|
||||||
return stream.str();
|
if (success) {
|
||||||
|
ASSERT_DEVICE_ERROR(
|
||||||
|
device.CreateRenderPipeline(&pDesc),
|
||||||
|
testing::HasSubstr(
|
||||||
|
"One or more fragment inputs and vertex outputs are not one-to-one matching"));
|
||||||
|
} else {
|
||||||
|
ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&pDesc),
|
||||||
|
testing::HasSubstr(errorMatcher));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Verify when there is no input builtin variable in a fragment shader, the total user-defined
|
// Verify when there is no input builtin variable in a fragment shader, the total user-defined
|
||||||
// input component count must be less than kMaxInterStageShaderComponents.
|
// input component count must be less than kMaxInterStageShaderComponents.
|
||||||
{
|
{
|
||||||
constexpr uint32_t kInterStageShaderComponentCount = kMaxInterStageShaderComponents;
|
CheckTestPipeline(true, kMaxInterStageShaderComponents, wgpu::ShaderStage::Fragment);
|
||||||
std::string correctFragmentShader =
|
CheckTestPipeline(false, kMaxInterStageShaderComponents + 1, wgpu::ShaderStage::Fragment);
|
||||||
generateShaderForTest(kInterStageShaderComponentCount, wgpu::ShaderStage::Fragment, "");
|
|
||||||
utils::CreateShaderModule(device, correctFragmentShader.c_str());
|
|
||||||
|
|
||||||
std::string errorFragmentShader = generateShaderForTest(kInterStageShaderComponentCount + 1,
|
|
||||||
wgpu::ShaderStage::Fragment, "");
|
|
||||||
ASSERT_DEVICE_ERROR(utils::CreateShaderModule(device, errorFragmentShader.c_str()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// @position should be counted into the maximum inter-stage component count.
|
// @builtin(position) should be counted into the maximum inter-stage component count.
|
||||||
// Note that in vertex shader we always have @position so we don't need to specify it
|
// Note that in vertex shader we always have @position so we don't need to specify it
|
||||||
// again in the parameter "builtInDeclarations" of generateShaderForTest().
|
// again in the parameter "builtInDeclarations" of generateShaderForTest().
|
||||||
{
|
{
|
||||||
constexpr uint32_t kInterStageShaderComponentCount = kMaxInterStageShaderComponents - 4;
|
CheckTestPipeline(true, kMaxInterStageShaderComponents - 4, wgpu::ShaderStage::Vertex);
|
||||||
std::string vertexShader =
|
CheckTestPipeline(false, kMaxInterStageShaderComponents - 3, wgpu::ShaderStage::Vertex);
|
||||||
generateShaderForTest(kInterStageShaderComponentCount, wgpu::ShaderStage::Vertex, "");
|
|
||||||
utils::CreateShaderModule(device, vertexShader.c_str());
|
|
||||||
|
|
||||||
std::string fragmentShader =
|
|
||||||
generateShaderForTest(kInterStageShaderComponentCount, wgpu::ShaderStage::Fragment,
|
|
||||||
"@builtin(position) fragCoord: vec4<f32>;");
|
|
||||||
utils::CreateShaderModule(device, fragmentShader.c_str());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @builtin(position) in fragment shaders should be counted into the maximum inter-stage
|
||||||
|
// component count.
|
||||||
{
|
{
|
||||||
constexpr uint32_t kInterStageShaderComponentCount = kMaxInterStageShaderComponents - 3;
|
CheckTestPipeline(true, kMaxInterStageShaderComponents - 4, wgpu::ShaderStage::Fragment,
|
||||||
std::string vertexShader =
|
"@builtin(position) fragCoord : vec4<f32>,");
|
||||||
generateShaderForTest(kInterStageShaderComponentCount, wgpu::ShaderStage::Vertex, "");
|
CheckTestPipeline(false, kMaxInterStageShaderComponents - 3, wgpu::ShaderStage::Fragment,
|
||||||
ASSERT_DEVICE_ERROR(utils::CreateShaderModule(device, vertexShader.c_str()));
|
"@builtin(position) fragCoord : vec4<f32>,");
|
||||||
|
|
||||||
std::string fragmentShader =
|
|
||||||
generateShaderForTest(kInterStageShaderComponentCount, wgpu::ShaderStage::Fragment,
|
|
||||||
"@builtin(position) fragCoord: vec4<f32>;");
|
|
||||||
ASSERT_DEVICE_ERROR(utils::CreateShaderModule(device, fragmentShader.c_str()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// front_facing should be counted into the maximum inter-stage component count.
|
// @builtin(front_facing) should be counted into the maximum inter-stage component count.
|
||||||
{
|
{
|
||||||
const char* builtinDeclaration = "@builtin(front_facing) frontFacing : bool;";
|
CheckTestPipeline(true, kMaxInterStageShaderComponents - 1, wgpu::ShaderStage::Fragment,
|
||||||
|
"@builtin(front_facing) frontFacing : bool,");
|
||||||
{
|
CheckTestPipeline(false, kMaxInterStageShaderComponents, wgpu::ShaderStage::Fragment,
|
||||||
std::string fragmentShader =
|
"@builtin(front_facing) frontFacing : bool,");
|
||||||
generateShaderForTest(kMaxInterStageShaderComponents - 1,
|
|
||||||
wgpu::ShaderStage::Fragment, builtinDeclaration);
|
|
||||||
utils::CreateShaderModule(device, fragmentShader.c_str());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @builtin(sample_index) should be counted into the maximum inter-stage component count.
|
||||||
{
|
{
|
||||||
std::string fragmentShader = generateShaderForTest(
|
CheckTestPipeline(true, kMaxInterStageShaderComponents - 1, wgpu::ShaderStage::Fragment,
|
||||||
kMaxInterStageShaderComponents, wgpu::ShaderStage::Fragment, builtinDeclaration);
|
"@builtin(sample_index) sampleIndex : u32,");
|
||||||
ASSERT_DEVICE_ERROR(utils::CreateShaderModule(device, fragmentShader.c_str()));
|
CheckTestPipeline(false, kMaxInterStageShaderComponents, wgpu::ShaderStage::Fragment,
|
||||||
}
|
"@builtin(sample_index) sampleIndex : u32,");
|
||||||
}
|
}
|
||||||
|
|
||||||
// @sample_index should be counted into the maximum inter-stage component count.
|
// @builtin(sample_mask) should be counted into the maximum inter-stage component count.
|
||||||
{
|
{
|
||||||
const char* builtinDeclaration = "@builtin(sample_index) sampleIndex: u32;";
|
CheckTestPipeline(true, kMaxInterStageShaderComponents - 1, wgpu::ShaderStage::Fragment,
|
||||||
|
"@builtin(sample_mask) sampleMask : u32,");
|
||||||
{
|
CheckTestPipeline(false, kMaxInterStageShaderComponents, wgpu::ShaderStage::Fragment,
|
||||||
std::string fragmentShader =
|
"@builtin(sample_mask) sampleMask : u32,");
|
||||||
generateShaderForTest(kMaxInterStageShaderComponents - 1,
|
|
||||||
wgpu::ShaderStage::Fragment, builtinDeclaration);
|
|
||||||
utils::CreateShaderModule(device, fragmentShader.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
std::string fragmentShader = generateShaderForTest(
|
|
||||||
kMaxInterStageShaderComponents, wgpu::ShaderStage::Fragment, builtinDeclaration);
|
|
||||||
ASSERT_DEVICE_ERROR(utils::CreateShaderModule(device, fragmentShader.c_str()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// @sample_mask should be counted into the maximum inter-stage component count.
|
|
||||||
{
|
|
||||||
const char* builtinDeclaration = "@builtin(front_facing) frontFacing : bool;";
|
|
||||||
|
|
||||||
{
|
|
||||||
std::string fragmentShader =
|
|
||||||
generateShaderForTest(kMaxInterStageShaderComponents - 1,
|
|
||||||
wgpu::ShaderStage::Fragment, builtinDeclaration);
|
|
||||||
utils::CreateShaderModule(device, fragmentShader.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
std::string fragmentShader = generateShaderForTest(
|
|
||||||
kMaxInterStageShaderComponents, wgpu::ShaderStage::Fragment, builtinDeclaration);
|
|
||||||
ASSERT_DEVICE_ERROR(utils::CreateShaderModule(device, fragmentShader.c_str()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests that we validate workgroup size limits.
|
// Tests that we validate workgroup size limits.
|
||||||
TEST_F(ShaderModuleValidationTest, ComputeWorkgroupSizeLimits) {
|
TEST_F(ShaderModuleValidationTest, ComputeWorkgroupSizeLimits) {
|
||||||
auto MakeShaderWithWorkgroupSize = [this](uint32_t x, uint32_t y, uint32_t z) {
|
auto CheckShaderWithWorkgroupSize = [this](bool success, uint32_t x, uint32_t y, uint32_t z) {
|
||||||
std::ostringstream ss;
|
std::ostringstream ss;
|
||||||
ss << "@stage(compute) @workgroup_size(" << x << "," << y << "," << z << ") fn main() {}";
|
ss << "@stage(compute) @workgroup_size(" << x << "," << y << "," << z << ") fn main() {}";
|
||||||
utils::CreateShaderModule(device, ss.str().c_str());
|
|
||||||
|
wgpu::ComputePipelineDescriptor desc;
|
||||||
|
desc.compute.entryPoint = "main";
|
||||||
|
desc.compute.module = utils::CreateShaderModule(device, ss.str().c_str());
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
device.CreateComputePipeline(&desc);
|
||||||
|
} else {
|
||||||
|
ASSERT_DEVICE_ERROR(device.CreateComputePipeline(&desc));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
wgpu::Limits supportedLimits = GetSupportedLimits().limits;
|
wgpu::Limits supportedLimits = GetSupportedLimits().limits;
|
||||||
|
|
||||||
MakeShaderWithWorkgroupSize(1, 1, 1);
|
CheckShaderWithWorkgroupSize(true, 1, 1, 1);
|
||||||
MakeShaderWithWorkgroupSize(supportedLimits.maxComputeWorkgroupSizeX, 1, 1);
|
CheckShaderWithWorkgroupSize(true, supportedLimits.maxComputeWorkgroupSizeX, 1, 1);
|
||||||
MakeShaderWithWorkgroupSize(1, supportedLimits.maxComputeWorkgroupSizeY, 1);
|
CheckShaderWithWorkgroupSize(true, 1, supportedLimits.maxComputeWorkgroupSizeY, 1);
|
||||||
MakeShaderWithWorkgroupSize(1, 1, supportedLimits.maxComputeWorkgroupSizeZ);
|
CheckShaderWithWorkgroupSize(true, 1, 1, supportedLimits.maxComputeWorkgroupSizeZ);
|
||||||
|
|
||||||
ASSERT_DEVICE_ERROR(
|
CheckShaderWithWorkgroupSize(false, supportedLimits.maxComputeWorkgroupSizeX + 1, 1, 1);
|
||||||
MakeShaderWithWorkgroupSize(supportedLimits.maxComputeWorkgroupSizeX + 1, 1, 1));
|
CheckShaderWithWorkgroupSize(false, 1, supportedLimits.maxComputeWorkgroupSizeY + 1, 1);
|
||||||
ASSERT_DEVICE_ERROR(
|
CheckShaderWithWorkgroupSize(false, 1, 1, supportedLimits.maxComputeWorkgroupSizeZ + 1);
|
||||||
MakeShaderWithWorkgroupSize(1, supportedLimits.maxComputeWorkgroupSizeY + 1, 1));
|
|
||||||
ASSERT_DEVICE_ERROR(
|
|
||||||
MakeShaderWithWorkgroupSize(1, 1, supportedLimits.maxComputeWorkgroupSizeZ + 1));
|
|
||||||
|
|
||||||
// No individual dimension exceeds its limit, but the combined size should definitely exceed the
|
// No individual dimension exceeds its limit, but the combined size should definitely exceed the
|
||||||
// total invocation limit.
|
// total invocation limit.
|
||||||
ASSERT_DEVICE_ERROR(MakeShaderWithWorkgroupSize(supportedLimits.maxComputeWorkgroupSizeX,
|
CheckShaderWithWorkgroupSize(false, supportedLimits.maxComputeWorkgroupSizeX,
|
||||||
supportedLimits.maxComputeWorkgroupSizeY,
|
supportedLimits.maxComputeWorkgroupSizeY,
|
||||||
supportedLimits.maxComputeWorkgroupSizeZ));
|
supportedLimits.maxComputeWorkgroupSizeZ);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests that we validate workgroup storage size limits.
|
// Tests that we validate workgroup storage size limits.
|
||||||
|
@ -468,7 +494,8 @@ TEST_F(ShaderModuleValidationTest, ComputeWorkgroupStorageSizeLimits) {
|
||||||
constexpr uint32_t kMat4Size = 64;
|
constexpr uint32_t kMat4Size = 64;
|
||||||
const uint32_t maxMat4Count = supportedLimits.maxComputeWorkgroupStorageSize / kMat4Size;
|
const uint32_t maxMat4Count = supportedLimits.maxComputeWorkgroupStorageSize / kMat4Size;
|
||||||
|
|
||||||
auto MakeShaderWithWorkgroupStorage = [this](uint32_t vec4_count, uint32_t mat4_count) {
|
auto CheckPipelineWithWorkgroupStorage = [this](bool success, uint32_t vec4_count,
|
||||||
|
uint32_t mat4_count) {
|
||||||
std::ostringstream ss;
|
std::ostringstream ss;
|
||||||
std::ostringstream body;
|
std::ostringstream body;
|
||||||
if (vec4_count > 0) {
|
if (vec4_count > 0) {
|
||||||
|
@ -480,18 +507,28 @@ TEST_F(ShaderModuleValidationTest, ComputeWorkgroupStorageSizeLimits) {
|
||||||
body << "_ = mat4_data;";
|
body << "_ = mat4_data;";
|
||||||
}
|
}
|
||||||
ss << "@stage(compute) @workgroup_size(1) fn main() { " << body.str() << " }";
|
ss << "@stage(compute) @workgroup_size(1) fn main() { " << body.str() << " }";
|
||||||
utils::CreateShaderModule(device, ss.str().c_str());
|
|
||||||
|
wgpu::ComputePipelineDescriptor desc;
|
||||||
|
desc.compute.entryPoint = "main";
|
||||||
|
desc.compute.module = utils::CreateShaderModule(device, ss.str().c_str());
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
device.CreateComputePipeline(&desc);
|
||||||
|
} else {
|
||||||
|
ASSERT_DEVICE_ERROR(device.CreateComputePipeline(&desc));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
MakeShaderWithWorkgroupStorage(1, 1);
|
CheckPipelineWithWorkgroupStorage(true, 1, 1);
|
||||||
MakeShaderWithWorkgroupStorage(maxVec4Count, 0);
|
CheckPipelineWithWorkgroupStorage(true, maxVec4Count, 0);
|
||||||
MakeShaderWithWorkgroupStorage(0, maxMat4Count);
|
CheckPipelineWithWorkgroupStorage(true, 0, maxMat4Count);
|
||||||
MakeShaderWithWorkgroupStorage(maxVec4Count - 4, 1);
|
CheckPipelineWithWorkgroupStorage(true, maxVec4Count - 4, 1);
|
||||||
MakeShaderWithWorkgroupStorage(4, maxMat4Count - 1);
|
CheckPipelineWithWorkgroupStorage(true, 4, maxMat4Count - 1);
|
||||||
ASSERT_DEVICE_ERROR(MakeShaderWithWorkgroupStorage(maxVec4Count + 1, 0));
|
|
||||||
ASSERT_DEVICE_ERROR(MakeShaderWithWorkgroupStorage(maxVec4Count - 3, 1));
|
CheckPipelineWithWorkgroupStorage(false, maxVec4Count + 1, 0);
|
||||||
ASSERT_DEVICE_ERROR(MakeShaderWithWorkgroupStorage(0, maxMat4Count + 1));
|
CheckPipelineWithWorkgroupStorage(false, maxVec4Count - 3, 1);
|
||||||
ASSERT_DEVICE_ERROR(MakeShaderWithWorkgroupStorage(4, maxMat4Count));
|
CheckPipelineWithWorkgroupStorage(false, 0, maxMat4Count + 1);
|
||||||
|
CheckPipelineWithWorkgroupStorage(false, 4, maxMat4Count);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test that numeric ID must be unique
|
// Test that numeric ID must be unique
|
||||||
|
@ -517,21 +554,24 @@ struct Buf {
|
||||||
TEST_F(ShaderModuleValidationTest, MaxBindingNumber) {
|
TEST_F(ShaderModuleValidationTest, MaxBindingNumber) {
|
||||||
static_assert(kMaxBindingNumber == 65535);
|
static_assert(kMaxBindingNumber == 65535);
|
||||||
|
|
||||||
|
wgpu::ComputePipelineDescriptor desc;
|
||||||
|
desc.compute.entryPoint = "main";
|
||||||
|
|
||||||
// kMaxBindingNumber is valid.
|
// kMaxBindingNumber is valid.
|
||||||
utils::CreateShaderModule(device, R"(
|
desc.compute.module = utils::CreateShaderModule(device, R"(
|
||||||
@group(0) @binding(65535) var s : sampler;
|
@group(0) @binding(65535) var s : sampler;
|
||||||
@stage(fragment) fn main() -> @location(0) u32 {
|
@stage(compute) @workgroup_size(1) fn main() {
|
||||||
_ = s;
|
_ = s;
|
||||||
return 0u;
|
|
||||||
}
|
}
|
||||||
)");
|
)");
|
||||||
|
device.CreateComputePipeline(&desc);
|
||||||
|
|
||||||
// kMaxBindingNumber + 1 is an error
|
// kMaxBindingNumber + 1 is an error
|
||||||
ASSERT_DEVICE_ERROR(utils::CreateShaderModule(device, R"(
|
desc.compute.module = utils::CreateShaderModule(device, R"(
|
||||||
@group(0) @binding(65536) var s : sampler;
|
@group(0) @binding(65536) var s : sampler;
|
||||||
@stage(fragment) fn main() -> @location(0) u32 {
|
@stage(compute) @workgroup_size(1) fn main() {
|
||||||
_ = s;
|
_ = s;
|
||||||
return 0u;
|
|
||||||
}
|
}
|
||||||
)"));
|
)");
|
||||||
|
ASSERT_DEVICE_ERROR(device.CreateComputePipeline(&desc));
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue