Compat: Reject non-matching blend state / write mask

Bug: dawn:1839
Change-Id: I1284fb0c5b6a0d21603d9a9806d31e931b17b615
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/134061
Reviewed-by: Stephen White <senorblanco@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Austin Eng <enga@chromium.org>
Commit-Queue: Gregg Tavares <gman@chromium.org>
This commit is contained in:
Gregg Tavares 2023-05-25 04:37:12 +00:00 committed by Dawn LUCI CQ
parent daffe42b6a
commit 110358f2e6
4 changed files with 165 additions and 9 deletions

View File

@ -1450,7 +1450,7 @@ bool DeviceBase::IsRobustnessEnabled() const {
} }
bool DeviceBase::IsCompatibilityMode() const { bool DeviceBase::IsCompatibilityMode() const {
return mAdapter->GetFeatureLevel() == FeatureLevel::Compatibility; return mAdapter != nullptr && mAdapter->GetFeatureLevel() == FeatureLevel::Compatibility;
} }
bool DeviceBase::AllowUnsafeAPIs() const { bool DeviceBase::AllowUnsafeAPIs() const {

View File

@ -352,6 +352,61 @@ MaybeError ValidateColorTargetState(
return {}; return {};
} }
MaybeError ValidateCompatibilityColorTargetState(
const uint8_t firstColorTargetIndex,
const ColorTargetState* const firstColorTargetState,
const uint8_t targetIndex,
const ColorTargetState* target) {
DAWN_INVALID_IF(firstColorTargetState->writeMask != target->writeMask,
"targets[%u].writeMask (%s) does not match targets[%u].writeMask (%s).",
targetIndex, target->writeMask, firstColorTargetIndex,
firstColorTargetState->writeMask);
if (!firstColorTargetState->blend) {
DAWN_INVALID_IF(target->blend,
"targets[%u].blend has a blend state but targets[%u].blend does not.",
targetIndex, firstColorTargetIndex);
} else {
DAWN_INVALID_IF(!target->blend,
"targets[%u].blend has a blend state but targets[%u].blend does not.",
firstColorTargetIndex, targetIndex);
const BlendState& currBlendState = *target->blend;
const BlendState& firstBlendState = *firstColorTargetState->blend;
DAWN_INVALID_IF(
firstBlendState.color.operation != currBlendState.color.operation,
"targets[%u].color.operation (%s) does not match targets[%u].color.operation (%s).",
firstColorTargetIndex, firstBlendState.color.operation, targetIndex,
currBlendState.color.operation);
DAWN_INVALID_IF(
firstBlendState.color.srcFactor != currBlendState.color.srcFactor,
"targets[%u].color.srcFactor (%s) does not match targets[%u].color.srcFactor (%s).",
firstColorTargetIndex, firstBlendState.color.srcFactor, targetIndex,
currBlendState.color.srcFactor);
DAWN_INVALID_IF(
firstBlendState.color.dstFactor != currBlendState.color.dstFactor,
"targets[%u].color.dstFactor (%s) does not match targets[%u].color.dstFactor (%s).",
firstColorTargetIndex, firstBlendState.color.dstFactor, targetIndex,
currBlendState.color.dstFactor);
DAWN_INVALID_IF(
firstBlendState.alpha.operation != currBlendState.alpha.operation,
"targets[%u].alpha.operation (%s) does not match targets[%u].alpha.operation (%s).",
firstColorTargetIndex, firstBlendState.alpha.operation, targetIndex,
currBlendState.alpha.operation);
DAWN_INVALID_IF(
firstBlendState.alpha.srcFactor != currBlendState.alpha.srcFactor,
"targets[%u].alpha.srcFactor (%s) does not match targets[%u].alpha.srcFactor (%s).",
firstColorTargetIndex, firstBlendState.alpha.srcFactor, targetIndex,
currBlendState.alpha.srcFactor);
DAWN_INVALID_IF(
firstBlendState.alpha.dstFactor != currBlendState.alpha.dstFactor,
"targets[%u].alpha.dstFactor (%s) does not match targets[%u].alpha.dstFactor (%s).",
firstColorTargetIndex, firstBlendState.alpha.dstFactor, targetIndex,
currBlendState.alpha.dstFactor);
}
return {};
}
MaybeError ValidateFragmentState(DeviceBase* device, MaybeError ValidateFragmentState(DeviceBase* device,
const FragmentState* descriptor, const FragmentState* descriptor,
const PipelineLayoutBase* layout, const PipelineLayoutBase* layout,
@ -388,20 +443,36 @@ MaybeError ValidateFragmentState(DeviceBase* device,
depthStencil->format, descriptor->module, descriptor->entryPoint); depthStencil->format, descriptor->module, descriptor->entryPoint);
} }
uint8_t firstColorTargetIndex = 0;
const ColorTargetState* firstColorTargetState = nullptr;
ColorAttachmentFormats colorAttachmentFormats; ColorAttachmentFormats colorAttachmentFormats;
for (ColorAttachmentIndex i(uint8_t(0));
i < ColorAttachmentIndex(static_cast<uint8_t>(descriptor->targetCount)); ++i) { for (ColorAttachmentIndex attachmentIndex(uint8_t(0));
const ColorTargetState* target = &descriptor->targets[static_cast<uint8_t>(i)]; attachmentIndex < ColorAttachmentIndex(static_cast<uint8_t>(descriptor->targetCount));
++attachmentIndex) {
const uint8_t i = static_cast<uint8_t>(attachmentIndex);
const ColorTargetState* target = &descriptor->targets[i];
if (target->format != wgpu::TextureFormat::Undefined) { if (target->format != wgpu::TextureFormat::Undefined) {
DAWN_TRY_CONTEXT( DAWN_TRY_CONTEXT(
ValidateColorTargetState(device, target, fragmentMetadata.fragmentOutputsWritten[i], ValidateColorTargetState(device, target,
fragmentMetadata.fragmentOutputVariables[i]), fragmentMetadata.fragmentOutputsWritten[attachmentIndex],
"validating targets[%u].", static_cast<uint8_t>(i)); fragmentMetadata.fragmentOutputVariables[attachmentIndex]),
"validating targets[%u].", i);
colorAttachmentFormats->push_back(&device->GetValidInternalFormat(target->format)); colorAttachmentFormats->push_back(&device->GetValidInternalFormat(target->format));
if (device->IsCompatibilityMode()) {
if (!firstColorTargetState) {
firstColorTargetState = target;
firstColorTargetIndex = i;
} else {
DAWN_TRY_CONTEXT(ValidateCompatibilityColorTargetState(
firstColorTargetIndex, firstColorTargetState, i, target),
"validating targets[%u] in compatibility mode.", i);
}
}
} else { } else {
DAWN_INVALID_IF(target->blend, DAWN_INVALID_IF(target->blend,
"Color target[%u] blend state is set when the format is undefined.", "Color target[%u] blend state is set when the format is undefined.", i);
static_cast<uint8_t>(i));
} }
} }
DAWN_TRY(ValidateColorAttachmentBytesPerSample(device, colorAttachmentFormats)); DAWN_TRY(ValidateColorAttachmentBytesPerSample(device, colorAttachmentFormats));

View File

@ -460,6 +460,9 @@ TEST_P(SinglePipelineCachingTests, RenderPipelineBlobCacheShaderNegativeCases) {
// Tests that pipeline creation wouldn't hit the cache if the pipelines are not exactly the same // Tests that pipeline creation wouldn't hit the cache if the pipelines are not exactly the same
// (fragment color targets differences). // (fragment color targets differences).
TEST_P(SinglePipelineCachingTests, RenderPipelineBlobCacheNegativeCasesFragmentColorTargets) { TEST_P(SinglePipelineCachingTests, RenderPipelineBlobCacheNegativeCasesFragmentColorTargets) {
// In compat, all targets must have the same writeMask
DAWN_TEST_UNSUPPORTED_IF(IsCompatibilityMode());
// First time should create and write out to the cache. // First time should create and write out to the cache.
{ {
wgpu::Device device = CreateDevice(); wgpu::Device device = CreateDevice();

View File

@ -54,5 +54,87 @@ TEST_F(CompatValidationTest, CanNotCreateCubeArrayTextureView) {
cubeTexture.Destroy(); cubeTexture.Destroy();
} }
TEST_F(CompatValidationTest, CanNotCreatePipelineWithDifferentPerTargetBlendStateOrWriteMask) {
wgpu::ShaderModule module = utils::CreateShaderModule(device, R"(
@vertex fn vs() -> @builtin(position) vec4f {
return vec4f(0);
}
struct FragmentOut {
@location(0) fragColor0 : vec4f,
@location(1) fragColor1 : vec4f,
@location(2) fragColor2 : vec4f,
}
@fragment fn fs() -> FragmentOut {
var output : FragmentOut;
output.fragColor0 = vec4f(0);
output.fragColor1 = vec4f(0);
output.fragColor2 = vec4f(0);
return output;
}
)");
utils::ComboRenderPipelineDescriptor testDescriptor;
testDescriptor.layout = {};
testDescriptor.vertex.module = module;
testDescriptor.vertex.entryPoint = "vs";
testDescriptor.cFragment.module = module;
testDescriptor.cFragment.entryPoint = "fs";
testDescriptor.cFragment.targetCount = 3;
testDescriptor.cTargets[1].format = wgpu::TextureFormat::Undefined;
for (int i = 0; i < 10; ++i) {
wgpu::BlendState blend0;
wgpu::BlendState blend2;
// Blend state intentionally omitted for target 1
testDescriptor.cTargets[0].blend = &blend0;
testDescriptor.cTargets[2].blend = &blend2;
bool expectError = true;
switch (i) {
case 0: // default
expectError = false;
break;
case 1: // no blend
testDescriptor.cTargets[0].blend = nullptr;
break;
case 2: // no blend second target
testDescriptor.cTargets[2].blend = nullptr;
break;
case 3: // color.operation
blend2.color.operation = wgpu::BlendOperation::Subtract;
break;
case 4: // color.srcFactor
blend2.color.srcFactor = wgpu::BlendFactor::SrcAlpha;
break;
case 5: // color.dstFactor
blend2.color.dstFactor = wgpu::BlendFactor::DstAlpha;
break;
case 6: // alpha.operation
blend2.alpha.operation = wgpu::BlendOperation::Subtract;
break;
case 7: // alpha.srcFactor
blend2.alpha.srcFactor = wgpu::BlendFactor::SrcAlpha;
break;
case 8: // alpha.dstFactor
blend2.alpha.dstFactor = wgpu::BlendFactor::DstAlpha;
break;
case 9: // writeMask
testDescriptor.cTargets[2].writeMask = wgpu::ColorWriteMask::Green;
break;
default:
UNREACHABLE();
}
if (expectError) {
ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&testDescriptor));
} else {
device.CreateRenderPipeline(&testDescriptor);
}
}
}
} // anonymous namespace } // anonymous namespace
} // namespace dawn } // namespace dawn