Implements maxColorAttachmentBytesPerSample

- Adds the limit
- Adds relevant format-specific data into format table
- Adds deprecation validations regarding the limit
- Adds deprecation validation unit tests and helpful utils
- Moves deprecated api tests from end2end to unittests, allowing tests
  to be cross-files for ease after deprecation.
- Updates some validation messages to include helpful contexts.

Bug: dawn:1522
Change-Id: Ib05f9adb60808ff4d68061d9646e76c729a23643
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/113543
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Austin Eng <enga@chromium.org>
Commit-Queue: Loko Kung <lokokung@google.com>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
diff --git a/dawn.json b/dawn.json
index 115ccc5..2b83e89 100644
--- a/dawn.json
+++ b/dawn.json
@@ -1271,6 +1271,7 @@
             {"name": "max inter stage shader components", "type": "uint32_t", "default": "WGPU_LIMIT_U32_UNDEFINED"},
             {"name": "max inter stage shader variables", "type": "uint32_t", "default": "WGPU_LIMIT_U32_UNDEFINED"},
             {"name": "max color attachments", "type": "uint32_t", "default": "WGPU_LIMIT_U32_UNDEFINED"},
+            {"name": "max color attachment bytes per sample", "type": "uint32_t", "default": "WGPU_LIMIT_U32_UNDEFINED"},
             {"name": "max compute workgroup storage size", "type": "uint32_t", "default": "WGPU_LIMIT_U32_UNDEFINED"},
             {"name": "max compute invocations per workgroup", "type": "uint32_t", "default": "WGPU_LIMIT_U32_UNDEFINED"},
             {"name": "max compute workgroup size x", "type": "uint32_t", "default": "WGPU_LIMIT_U32_UNDEFINED"},
diff --git a/src/dawn/native/CommandEncoder.cpp b/src/dawn/native/CommandEncoder.cpp
index 95157ca..f082047 100644
--- a/src/dawn/native/CommandEncoder.cpp
+++ b/src/dawn/native/CommandEncoder.cpp
@@ -458,6 +458,7 @@
         descriptor->colorAttachmentCount, maxColorAttachments);
 
     bool isAllColorAttachmentNull = true;
+    ColorAttachmentFormats colorAttachmentFormats;
     for (uint32_t i = 0; i < descriptor->colorAttachmentCount; ++i) {
         DAWN_TRY_CONTEXT(
             ValidateRenderPassColorAttachment(device, descriptor->colorAttachments[i], width,
@@ -465,8 +466,11 @@
             "validating colorAttachments[%u].", i);
         if (descriptor->colorAttachments[i].view) {
             isAllColorAttachmentNull = false;
+            colorAttachmentFormats->push_back(&descriptor->colorAttachments[i].view->GetFormat());
         }
     }
+    DAWN_TRY_CONTEXT(ValidateColorAttachmentBytesPerSample(device, colorAttachmentFormats),
+                     "validating color attachment bytes per sample.");
 
     if (descriptor->depthStencilAttachment != nullptr) {
         DAWN_TRY_CONTEXT(ValidateRenderPassDepthStencilAttachment(
@@ -854,7 +858,8 @@
 
     // If descriptor is invalid, make pass invalid and stop immediately
     if (device->ConsumedError(ValidateRenderPassDescriptor(device, descriptor, &width, &height,
-                                                           &sampleCount, mUsageValidationMode))) {
+                                                           &sampleCount, mUsageValidationMode),
+                              "validating render pass descriptor.")) {
         return RenderPassEncoder::MakeError(device, this, &mEncodingContext);
     }
 
diff --git a/src/dawn/native/CommandValidation.cpp b/src/dawn/native/CommandValidation.cpp
index 836c811..e7874e6 100644
--- a/src/dawn/native/CommandValidation.cpp
+++ b/src/dawn/native/CommandValidation.cpp
@@ -16,6 +16,8 @@
 
 #include <algorithm>
 #include <limits>
+#include <sstream>
+#include <string>
 #include <utility>
 
 #include "dawn/common/BitSetIterator.h"
@@ -491,4 +493,34 @@
     return {};
 }
 
+namespace {
+std::string TextureFormatsToString(const ColorAttachmentFormats& formats) {
+    std::ostringstream ss;
+    ss << "[ ";
+    for (const Format* format : formats) {
+        ss << absl::StrFormat("%s", format->format) << " ";
+    }
+    ss << "]";
+    return ss.str();
+}
+}  // anonymous namespace
+
+MaybeError ValidateColorAttachmentBytesPerSample(DeviceBase* device,
+                                                 const ColorAttachmentFormats& formats) {
+    uint32_t totalByteSize = 0;
+    for (const Format* format : formats) {
+        totalByteSize = Align(totalByteSize, format->renderTargetComponentAlignment);
+        totalByteSize += format->renderTargetPixelByteCost;
+    }
+    uint32_t maxColorAttachmentBytesPerSample =
+        device->GetLimits().v1.maxColorAttachmentBytesPerSample;
+    // TODO(dawn:1522) Promote to DAWN_INVALID_IF after deprecation period.
+    DAWN_DEPRECATED_IF(
+        device, totalByteSize > maxColorAttachmentBytesPerSample,
+        "Total color attachment bytes per sample (%u) exceeds maximum (%u) with formats (%s).",
+        totalByteSize, maxColorAttachmentBytesPerSample, TextureFormatsToString(formats));
+
+    return {};
+}
+
 }  // namespace dawn::native
diff --git a/src/dawn/native/CommandValidation.h b/src/dawn/native/CommandValidation.h
index d4f66d7..53ecfc2 100644
--- a/src/dawn/native/CommandValidation.h
+++ b/src/dawn/native/CommandValidation.h
@@ -17,6 +17,8 @@
 
 #include <vector>
 
+#include "dawn/common/Constants.h"
+#include "dawn/common/StackContainer.h"
 #include "dawn/native/CommandAllocator.h"
 #include "dawn/native/Error.h"
 #include "dawn/native/Features.h"
@@ -90,6 +92,10 @@
                             UsageValidationMode mode);
 MaybeError ValidateCanUseAs(const BufferBase* buffer, wgpu::BufferUsage usage);
 
+using ColorAttachmentFormats = StackVector<const Format*, kMaxColorAttachments>;
+MaybeError ValidateColorAttachmentBytesPerSample(DeviceBase* device,
+                                                 const ColorAttachmentFormats& formats);
+
 }  // namespace dawn::native
 
 #endif  // SRC_DAWN_NATIVE_COMMANDVALIDATION_H_
diff --git a/src/dawn/native/Device.cpp b/src/dawn/native/Device.cpp
index c713c26..23607a1 100644
--- a/src/dawn/native/Device.cpp
+++ b/src/dawn/native/Device.cpp
@@ -1585,7 +1585,8 @@
     const RenderBundleEncoderDescriptor* descriptor) {
     DAWN_TRY(ValidateIsAlive());
     if (IsValidationEnabled()) {
-        DAWN_TRY(ValidateRenderBundleEncoderDescriptor(this, descriptor));
+        DAWN_TRY_CONTEXT(ValidateRenderBundleEncoderDescriptor(this, descriptor),
+                         "validating render bundle encoder descriptor.");
     }
     return RenderBundleEncoder::Create(this, descriptor);
 }
diff --git a/src/dawn/native/Error.h b/src/dawn/native/Error.h
index 6ab93c7..4523a54 100644
--- a/src/dawn/native/Error.h
+++ b/src/dawn/native/Error.h
@@ -90,6 +90,14 @@
         ? MaybeError(DAWN_VALIDATION_ERROR(__VA_ARGS__))    \
         : (device->EmitDeprecationWarning(absl::StrFormat(__VA_ARGS__)), MaybeError{})
 
+// DAWN_DEPRECATED_IF is used analogous to DAWN_INVALID_IF at deprecation paths.
+#define DAWN_DEPRECATED_IF(device, EXPR, ...)                    \
+    if (DAWN_UNLIKELY(EXPR)) {                                   \
+        return DAWN_MAKE_DEPRECATION_ERROR(device, __VA_ARGS__); \
+    }                                                            \
+    for (;;)                                                     \
+    break
+
 // DAWN_DEVICE_LOST_ERROR means that there was a real unrecoverable native device lost error.
 // We can't even do a graceful shutdown because the Device is gone.
 #define DAWN_DEVICE_LOST_ERROR(MESSAGE) DAWN_MAKE_ERROR(InternalErrorType::DeviceLost, MESSAGE)
diff --git a/src/dawn/native/Format.cpp b/src/dawn/native/Format.cpp
index fdfe477..7969515 100644
--- a/src/dawn/native/Format.cpp
+++ b/src/dawn/native/Format.cpp
@@ -160,6 +160,8 @@
         [&AddFormat](wgpu::TextureFormat format, bool renderable, bool supportsStorageUsage,
                      bool supportsMultisample, bool supportsResolveTarget, uint32_t byteSize,
                      SampleTypeBit sampleTypes, uint8_t componentCount,
+                     uint8_t renderTargetPixelByteCost = 0,
+                     uint8_t renderTargetComponentAlignment = 0,
                      wgpu::TextureFormat baseFormat = wgpu::TextureFormat::Undefined) {
             Format internalFormat;
             internalFormat.format = format;
@@ -175,6 +177,13 @@
             internalFormat.supportsResolveTarget = supportsResolveTarget;
             internalFormat.aspects = Aspect::Color;
             internalFormat.componentCount = componentCount;
+            if (renderable) {
+                // If the color format is renderable, it must have a pixel byte size and component
+                // alignment specified.
+                ASSERT(renderTargetPixelByteCost != 0 && renderTargetComponentAlignment != 0);
+                internalFormat.renderTargetPixelByteCost = renderTargetPixelByteCost;
+                internalFormat.renderTargetComponentAlignment = renderTargetComponentAlignment;
+            }
 
             // Default baseFormat of each color formats should be themselves.
             if (baseFormat == wgpu::TextureFormat::Undefined) {
@@ -333,52 +342,52 @@
 
     // clang-format off
         // 1 byte color formats
-        AddColorFormat(wgpu::TextureFormat::R8Unorm, true, false, true, true, 1, kAnyFloat, 1);
+        AddColorFormat(wgpu::TextureFormat::R8Unorm, true, false, true, true, 1, kAnyFloat, 1, 1, 1);
         AddColorFormat(wgpu::TextureFormat::R8Snorm, false, false, false, false, 1, kAnyFloat, 1);
-        AddColorFormat(wgpu::TextureFormat::R8Uint, true, false, true, false, 1, SampleTypeBit::Uint, 1);
-        AddColorFormat(wgpu::TextureFormat::R8Sint, true, false, true, false, 1, SampleTypeBit::Sint, 1);
+        AddColorFormat(wgpu::TextureFormat::R8Uint, true, false, true, false, 1, SampleTypeBit::Uint, 1, 1, 1);
+        AddColorFormat(wgpu::TextureFormat::R8Sint, true, false, true, false, 1, SampleTypeBit::Sint, 1, 1, 1);
 
         // 2 bytes color formats
-        AddColorFormat(wgpu::TextureFormat::R16Uint, true, false, true, false, 2, SampleTypeBit::Uint, 1);
-        AddColorFormat(wgpu::TextureFormat::R16Sint, true, false, true, false, 2, SampleTypeBit::Sint, 1);
-        AddColorFormat(wgpu::TextureFormat::R16Float, true, false, true, true, 2, kAnyFloat, 1);
-        AddColorFormat(wgpu::TextureFormat::RG8Unorm, true, false, true, true, 2, kAnyFloat, 2);
+        AddColorFormat(wgpu::TextureFormat::R16Uint, true, false, true, false, 2, SampleTypeBit::Uint, 1, 2, 2);
+        AddColorFormat(wgpu::TextureFormat::R16Sint, true, false, true, false, 2, SampleTypeBit::Sint, 1, 2, 2);
+        AddColorFormat(wgpu::TextureFormat::R16Float, true, false, true, true, 2, kAnyFloat, 1, 2, 2);
+        AddColorFormat(wgpu::TextureFormat::RG8Unorm, true, false, true, true, 2, kAnyFloat, 2, 2, 1);
         AddColorFormat(wgpu::TextureFormat::RG8Snorm, false, false, false, false, 2, kAnyFloat, 2);
-        AddColorFormat(wgpu::TextureFormat::RG8Uint, true, false, true, false, 2, SampleTypeBit::Uint, 2);
-        AddColorFormat(wgpu::TextureFormat::RG8Sint, true, false, true, false, 2, SampleTypeBit::Sint, 2);
+        AddColorFormat(wgpu::TextureFormat::RG8Uint, true, false, true, false, 2, SampleTypeBit::Uint, 2, 2, 1);
+        AddColorFormat(wgpu::TextureFormat::RG8Sint, true, false, true, false, 2, SampleTypeBit::Sint, 2, 2, 1);
 
         // 4 bytes color formats
-        AddColorFormat(wgpu::TextureFormat::R32Uint, true, true, false, false, 4, SampleTypeBit::Uint, 1);
-        AddColorFormat(wgpu::TextureFormat::R32Sint, true, true, false, false, 4, SampleTypeBit::Sint, 1);
-        AddColorFormat(wgpu::TextureFormat::R32Float, true, true, true, false, 4, SampleTypeBit::UnfilterableFloat, 1);
-        AddColorFormat(wgpu::TextureFormat::RG16Uint, true, false, true, false, 4, SampleTypeBit::Uint, 2);
-        AddColorFormat(wgpu::TextureFormat::RG16Sint, true, false, true, false, 4, SampleTypeBit::Sint, 2);
-        AddColorFormat(wgpu::TextureFormat::RG16Float, true, false, true, true, 4, kAnyFloat, 2);
-        AddColorFormat(wgpu::TextureFormat::RGBA8Unorm, true, true, true, true, 4, kAnyFloat, 4);
-        AddColorFormat(wgpu::TextureFormat::RGBA8UnormSrgb, true, false, true, true, 4, kAnyFloat, 4, wgpu::TextureFormat::RGBA8Unorm);
+        AddColorFormat(wgpu::TextureFormat::R32Uint, true, true, false, false, 4, SampleTypeBit::Uint, 1, 4, 4);
+        AddColorFormat(wgpu::TextureFormat::R32Sint, true, true, false, false, 4, SampleTypeBit::Sint, 1, 4, 4);
+        AddColorFormat(wgpu::TextureFormat::R32Float, true, true, true, false, 4, SampleTypeBit::UnfilterableFloat, 1, 4, 4);
+        AddColorFormat(wgpu::TextureFormat::RG16Uint, true, false, true, false, 4, SampleTypeBit::Uint, 2, 4, 2);
+        AddColorFormat(wgpu::TextureFormat::RG16Sint, true, false, true, false, 4, SampleTypeBit::Sint, 2, 4, 2);
+        AddColorFormat(wgpu::TextureFormat::RG16Float, true, false, true, true, 4, kAnyFloat, 2, 4, 2);
+        AddColorFormat(wgpu::TextureFormat::RGBA8Unorm, true, true, true, true, 4, kAnyFloat, 4, 8, 1);
+        AddColorFormat(wgpu::TextureFormat::RGBA8UnormSrgb, true, false, true, true, 4, kAnyFloat, 4, 8, 1, wgpu::TextureFormat::RGBA8Unorm);
         AddColorFormat(wgpu::TextureFormat::RGBA8Snorm, false, true, false, false, 4, kAnyFloat, 4);
-        AddColorFormat(wgpu::TextureFormat::RGBA8Uint, true, true, true, false, 4, SampleTypeBit::Uint, 4);
-        AddColorFormat(wgpu::TextureFormat::RGBA8Sint, true, true, true, false, 4, SampleTypeBit::Sint, 4);
-        AddColorFormat(wgpu::TextureFormat::BGRA8Unorm, true, false, true, true, 4, kAnyFloat, 4);
-        AddColorFormat(wgpu::TextureFormat::BGRA8UnormSrgb, true, false, true, true, 4, kAnyFloat, 4, wgpu::TextureFormat::BGRA8Unorm);
-        AddColorFormat(wgpu::TextureFormat::RGB10A2Unorm, true, false, true, true, 4, kAnyFloat, 4);
+        AddColorFormat(wgpu::TextureFormat::RGBA8Uint, true, true, true, false, 4, SampleTypeBit::Uint, 4, 4, 1);
+        AddColorFormat(wgpu::TextureFormat::RGBA8Sint, true, true, true, false, 4, SampleTypeBit::Sint, 4, 4, 1);
+        AddColorFormat(wgpu::TextureFormat::BGRA8Unorm, true, false, true, true, 4, kAnyFloat, 4, 8, 1);
+        AddColorFormat(wgpu::TextureFormat::BGRA8UnormSrgb, true, false, true, true, 4, kAnyFloat, 4, 8, 1, wgpu::TextureFormat::BGRA8Unorm);
+        AddColorFormat(wgpu::TextureFormat::RGB10A2Unorm, true, false, true, true, 4, kAnyFloat, 4, 8, 4);
 
         bool isRG11B10UfloatRenderable = device->HasFeature(Feature::RG11B10UfloatRenderable);
-        AddColorFormat(wgpu::TextureFormat::RG11B10Ufloat, isRG11B10UfloatRenderable, false, isRG11B10UfloatRenderable, false, 4, kAnyFloat, 3);
+        AddColorFormat(wgpu::TextureFormat::RG11B10Ufloat, isRG11B10UfloatRenderable, false, isRG11B10UfloatRenderable, false, 4, kAnyFloat, 3, 8, 4);
         AddColorFormat(wgpu::TextureFormat::RGB9E5Ufloat, false, false, false, false, 4, kAnyFloat, 3);
 
         // 8 bytes color formats
-        AddColorFormat(wgpu::TextureFormat::RG32Uint, true, true, false, false, 8, SampleTypeBit::Uint, 2);
-        AddColorFormat(wgpu::TextureFormat::RG32Sint, true, true, false, false, 8, SampleTypeBit::Sint, 2);
-        AddColorFormat(wgpu::TextureFormat::RG32Float, true, true, false, false, 8, SampleTypeBit::UnfilterableFloat, 2);
-        AddColorFormat(wgpu::TextureFormat::RGBA16Uint, true, true, true, false, 8, SampleTypeBit::Uint, 4);
-        AddColorFormat(wgpu::TextureFormat::RGBA16Sint, true, true, true, false, 8, SampleTypeBit::Sint, 4);
-        AddColorFormat(wgpu::TextureFormat::RGBA16Float, true, true, true, true, 8, kAnyFloat, 4);
+        AddColorFormat(wgpu::TextureFormat::RG32Uint, true, true, false, false, 8, SampleTypeBit::Uint, 2, 8, 4);
+        AddColorFormat(wgpu::TextureFormat::RG32Sint, true, true, false, false, 8, SampleTypeBit::Sint, 2, 8, 4);
+        AddColorFormat(wgpu::TextureFormat::RG32Float, true, true, false, false, 8, SampleTypeBit::UnfilterableFloat, 2, 8, 4);
+        AddColorFormat(wgpu::TextureFormat::RGBA16Uint, true, true, true, false, 8, SampleTypeBit::Uint, 4, 8, 2);
+        AddColorFormat(wgpu::TextureFormat::RGBA16Sint, true, true, true, false, 8, SampleTypeBit::Sint, 4, 8, 2);
+        AddColorFormat(wgpu::TextureFormat::RGBA16Float, true, true, true, true, 8, kAnyFloat, 4, 8, 2);
 
         // 16 bytes color formats
-        AddColorFormat(wgpu::TextureFormat::RGBA32Uint, true, true, false, false, 16, SampleTypeBit::Uint, 4);
-        AddColorFormat(wgpu::TextureFormat::RGBA32Sint, true, true, false, false, 16, SampleTypeBit::Sint, 4);
-        AddColorFormat(wgpu::TextureFormat::RGBA32Float, true, true, false, false, 16, SampleTypeBit::UnfilterableFloat, 4);
+        AddColorFormat(wgpu::TextureFormat::RGBA32Uint, true, true, false, false, 16, SampleTypeBit::Uint, 4, 16, 4);
+        AddColorFormat(wgpu::TextureFormat::RGBA32Sint, true, true, false, false, 16, SampleTypeBit::Sint, 4, 16, 4);
+        AddColorFormat(wgpu::TextureFormat::RGBA32Float, true, true, false, false, 16, SampleTypeBit::UnfilterableFloat, 4, 16, 4);
 
         // Depth-stencil formats
         AddStencilFormat(wgpu::TextureFormat::Stencil8, true);
diff --git a/src/dawn/native/Format.h b/src/dawn/native/Format.h
index 0a5f8f2..8a895c7 100644
--- a/src/dawn/native/Format.h
+++ b/src/dawn/native/Format.h
@@ -97,8 +97,10 @@
     bool supportsMultisample = false;
     bool supportsResolveTarget = false;
     Aspect aspects{};
-    // Only used for renderable color formats, number of color channels.
-    uint8_t componentCount = 0;
+    // Only used for renderable color formats:
+    uint8_t componentCount = 0;                  // number of color channels
+    uint8_t renderTargetPixelByteCost = 0;       // byte cost of pixel in render targets
+    uint8_t renderTargetComponentAlignment = 0;  // byte alignment for components in render targets
 
     bool IsColor() const;
     bool HasDepth() const;
diff --git a/src/dawn/native/Limits.cpp b/src/dawn/native/Limits.cpp
index dfa0650..433ab3c 100644
--- a/src/dawn/native/Limits.cpp
+++ b/src/dawn/native/Limits.cpp
@@ -59,6 +59,7 @@
     X(Maximum,               maxInterStageShaderComponents,        60,         60) \
     X(Maximum,               maxInterStageShaderVariables,         16,         16) \
     X(Maximum,                         maxColorAttachments,         8,          8) \
+    X(Maximum,            maxColorAttachmentBytesPerSample,        32,         32) \
     X(Maximum,           maxComputeInvocationsPerWorkgroup,       256,        256) \
     X(Maximum,                    maxComputeWorkgroupSizeX,       256,        256) \
     X(Maximum,                    maxComputeWorkgroupSizeY,       256,        256) \
diff --git a/src/dawn/native/RenderBundleEncoder.cpp b/src/dawn/native/RenderBundleEncoder.cpp
index 01e48fc..7649a3b0 100644
--- a/src/dawn/native/RenderBundleEncoder.cpp
+++ b/src/dawn/native/RenderBundleEncoder.cpp
@@ -16,6 +16,7 @@
 
 #include <utility>
 
+#include "dawn/common/StackContainer.h"
 #include "dawn/native/CommandValidation.h"
 #include "dawn/native/Commands.h"
 #include "dawn/native/Device.h"
@@ -57,7 +58,7 @@
     return {};
 }
 
-MaybeError ValidateRenderBundleEncoderDescriptor(const DeviceBase* device,
+MaybeError ValidateRenderBundleEncoderDescriptor(DeviceBase* device,
                                                  const RenderBundleEncoderDescriptor* descriptor) {
     DAWN_INVALID_IF(!IsValidSampleCount(descriptor->sampleCount),
                     "Sample count (%u) is not supported.", descriptor->sampleCount);
@@ -68,14 +69,18 @@
                     descriptor->colorFormatsCount, maxColorAttachments);
 
     bool allColorFormatsUndefined = true;
+    ColorAttachmentFormats colorAttachmentFormats;
     for (uint32_t i = 0; i < descriptor->colorFormatsCount; ++i) {
         wgpu::TextureFormat format = descriptor->colorFormats[i];
         if (format != wgpu::TextureFormat::Undefined) {
             DAWN_TRY_CONTEXT(ValidateColorAttachmentFormat(device, format),
                              "validating colorFormats[%u]", i);
+            colorAttachmentFormats->push_back(&device->GetValidInternalFormat(format));
             allColorFormatsUndefined = false;
         }
     }
+    DAWN_TRY_CONTEXT(ValidateColorAttachmentBytesPerSample(device, colorAttachmentFormats),
+                     "validating color attachment bytes per sample.");
 
     if (descriptor->depthStencilFormat != wgpu::TextureFormat::Undefined) {
         DAWN_TRY_CONTEXT(ValidateDepthStencilAttachmentFormat(
diff --git a/src/dawn/native/RenderBundleEncoder.h b/src/dawn/native/RenderBundleEncoder.h
index 24ee19e..b8a9d61 100644
--- a/src/dawn/native/RenderBundleEncoder.h
+++ b/src/dawn/native/RenderBundleEncoder.h
@@ -23,7 +23,7 @@
 
 namespace dawn::native {
 
-MaybeError ValidateRenderBundleEncoderDescriptor(const DeviceBase* device,
+MaybeError ValidateRenderBundleEncoderDescriptor(DeviceBase* device,
                                                  const RenderBundleEncoderDescriptor* descriptor);
 
 class RenderBundleEncoder final : public RenderEncoderBase {
diff --git a/src/dawn/native/RenderPipeline.cpp b/src/dawn/native/RenderPipeline.cpp
index 9b0ff96..60e8125 100644
--- a/src/dawn/native/RenderPipeline.cpp
+++ b/src/dawn/native/RenderPipeline.cpp
@@ -16,10 +16,10 @@
 
 #include <algorithm>
 #include <cmath>
-#include <sstream>
 
 #include "dawn/common/BitSetIterator.h"
 #include "dawn/native/ChainUtils_autogen.h"
+#include "dawn/native/CommandValidation.h"
 #include "dawn/native/Commands.h"
 #include "dawn/native/Device.h"
 #include "dawn/native/InternalPipelineStore.h"
@@ -351,6 +351,7 @@
 
     const EntryPointMetadata& fragmentMetadata =
         descriptor->module->GetEntryPoint(descriptor->entryPoint);
+    ColorAttachmentFormats colorAttachmentFormats;
     for (ColorAttachmentIndex i(uint8_t(0));
          i < ColorAttachmentIndex(static_cast<uint8_t>(descriptor->targetCount)); ++i) {
         const ColorTargetState* target = &descriptor->targets[static_cast<uint8_t>(i)];
@@ -359,12 +360,14 @@
                 ValidateColorTargetState(device, target, fragmentMetadata.fragmentOutputsWritten[i],
                                          fragmentMetadata.fragmentOutputVariables[i]),
                 "validating targets[%u].", static_cast<uint8_t>(i));
+            colorAttachmentFormats->push_back(&device->GetValidInternalFormat(target->format));
         } else {
             DAWN_INVALID_IF(target->blend,
                             "Color target[%u] blend state is set when the format is undefined.",
                             static_cast<uint8_t>(i));
         }
     }
+    DAWN_TRY(ValidateColorAttachmentBytesPerSample(device, colorAttachmentFormats));
 
     DAWN_INVALID_IF(fragmentMetadata.usesSampleMaskOutput && alphaToCoverageEnabled,
                     "alphaToCoverageEnabled is true when the sample_mask builtin is a "
diff --git a/src/dawn/tests/BUILD.gn b/src/dawn/tests/BUILD.gn
index 80bfe3f..21a1b36 100644
--- a/src/dawn/tests/BUILD.gn
+++ b/src/dawn/tests/BUILD.gn
@@ -317,6 +317,8 @@
     "unittests/validation/CopyCommandsValidationTests.cpp",
     "unittests/validation/CopyTextureForBrowserTests.cpp",
     "unittests/validation/DebugMarkerValidationTests.cpp",
+    "unittests/validation/DeprecatedAPITests.cpp",
+    "unittests/validation/DeprecatedAPITests.h",
     "unittests/validation/DeviceValidationTests.cpp",
     "unittests/validation/DrawIndirectValidationTests.cpp",
     "unittests/validation/DrawVertexAndIndexBufferOOBValidationTests.cpp",
@@ -486,7 +488,6 @@
     "end2end/CreatePipelineAsyncTests.cpp",
     "end2end/CullingTests.cpp",
     "end2end/DebugMarkerTests.cpp",
-    "end2end/DeprecatedAPITests.cpp",
     "end2end/DepthBiasTests.cpp",
     "end2end/DepthStencilCopyTests.cpp",
     "end2end/DepthStencilLoadOpTests.cpp",
diff --git a/src/dawn/tests/end2end/RenderPassLoadOpTests.cpp b/src/dawn/tests/end2end/RenderPassLoadOpTests.cpp
index ca70dbb..2b16c34 100644
--- a/src/dawn/tests/end2end/RenderPassLoadOpTests.cpp
+++ b/src/dawn/tests/end2end/RenderPassLoadOpTests.cpp
@@ -465,6 +465,7 @@
 }
 
 // Test clearing multiple color attachments with different big integers can still work correctly.
+// TODO(dawn:1522) Refactor and fix this test to avoid deprecation warnings.
 TEST_P(RenderPassLoadOpTests, LoadOpClearWithBigInt32ValuesOnMultipleColorAttachments) {
     constexpr int32_t kMaxInt32RepresentableInFloat = 1 << std::numeric_limits<float>::digits;
     constexpr int32_t kMinInt32RepresentableInFloat = -kMaxInt32RepresentableInFloat;
@@ -523,7 +524,14 @@
     wgpu::RenderPassDescriptor renderPassDescriptor = {};
     renderPassDescriptor.colorAttachmentCount = kMaxColorAttachments;
     renderPassDescriptor.colorAttachments = colorAttachmentsInfo.data();
-    wgpu::RenderPassEncoder renderPass = encoder.BeginRenderPass(&renderPassDescriptor);
+    wgpu::RenderPassEncoder renderPass;
+    if (HasToggleEnabled("apply_clear_big_integer_color_value_with_draw")) {
+        // When the toggle is enabled, an extra internal pipeline is created which will hit the same
+        // deprecation issue again, hence we need to check for 2 warnings instead of 1.
+        EXPECT_DEPRECATION_WARNINGS(renderPass = encoder.BeginRenderPass(&renderPassDescriptor), 2);
+    } else {
+        EXPECT_DEPRECATION_WARNING(renderPass = encoder.BeginRenderPass(&renderPassDescriptor));
+    }
     renderPass.End();
 
     std::array<wgpu::Buffer, kMaxColorAttachments> outputBuffers;
@@ -553,6 +561,7 @@
 
 // Test clearing multiple color attachments with different big unsigned integers can still work
 // correctly.
+// TODO(dawn:1522) Refactor and fix this test to avoid deprecation warnings.
 TEST_P(RenderPassLoadOpTests, LoadOpClearWithBigUInt32ValuesOnMultipleColorAttachments) {
     constexpr int32_t kMaxUInt32RepresentableInFloat = 1 << std::numeric_limits<float>::digits;
 
@@ -628,7 +637,14 @@
     wgpu::RenderPassDescriptor renderPassDescriptor = {};
     renderPassDescriptor.colorAttachmentCount = kMaxColorAttachments;
     renderPassDescriptor.colorAttachments = colorAttachmentsInfo.data();
-    wgpu::RenderPassEncoder renderPass = encoder.BeginRenderPass(&renderPassDescriptor);
+    wgpu::RenderPassEncoder renderPass;
+    if (HasToggleEnabled("apply_clear_big_integer_color_value_with_draw")) {
+        // When the toggle is enabled, an extra internal pipeline is created which will hit the same
+        // deprecation issue again, hence we need to check for 2 warnings instead of 1.
+        EXPECT_DEPRECATION_WARNINGS(renderPass = encoder.BeginRenderPass(&renderPassDescriptor), 2);
+    } else {
+        EXPECT_DEPRECATION_WARNING(renderPass = encoder.BeginRenderPass(&renderPassDescriptor));
+    }
     renderPass.End();
 
     std::array<wgpu::Buffer, kMaxColorAttachments> outputBuffers;
diff --git a/src/dawn/tests/end2end/TextureViewTests.cpp b/src/dawn/tests/end2end/TextureViewTests.cpp
index e00f08f..2955338 100644
--- a/src/dawn/tests/end2end/TextureViewTests.cpp
+++ b/src/dawn/tests/end2end/TextureViewTests.cpp
@@ -832,7 +832,7 @@
         wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0),
                                                          {{0, sampledTexture.CreateView()}});
 
-        utils::ComboRenderPassDescriptor renderPassInfo{textureView};
+        utils::ComboRenderPassDescriptor renderPassInfo({textureView});
 
         wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPassInfo);
         pass.SetPipeline(pipeline);
@@ -947,7 +947,7 @@
         wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0),
                                                          {{0, sampledTexture.CreateView()}});
 
-        utils::ComboRenderPassDescriptor renderPassInfo{multisampledTextureView};
+        utils::ComboRenderPassDescriptor renderPassInfo({multisampledTextureView});
         renderPassInfo.cColorAttachments[0].resolveTarget = resolveView;
 
         wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPassInfo);
diff --git a/src/dawn/tests/end2end/DeprecatedAPITests.cpp b/src/dawn/tests/unittests/validation/DeprecatedAPITests.cpp
similarity index 79%
rename from src/dawn/tests/end2end/DeprecatedAPITests.cpp
rename to src/dawn/tests/unittests/validation/DeprecatedAPITests.cpp
index 20d3fdd..152be1b 100644
--- a/src/dawn/tests/end2end/DeprecatedAPITests.cpp
+++ b/src/dawn/tests/unittests/validation/DeprecatedAPITests.cpp
@@ -12,38 +12,27 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// This file contains test for deprecated parts of Dawn's API while following WebGPU's evolution.
-// It contains test for the "old" behavior that will be deleted once users are migrated, tests that
-// a deprecation warning is emitted when the "old" behavior is used, and tests that an error is
-// emitted when both the old and the new behavior are used (when applicable).
-
 #include <cmath>
 
-#include "dawn/tests/DawnTest.h"
+#include "dawn/tests/unittests/validation/DeprecatedAPITests.h"
 
 #include "dawn/common/Constants.h"
 #include "dawn/utils/ComboRenderPipelineDescriptor.h"
 #include "dawn/utils/WGPUHelpers.h"
 
-constexpr char kDisallowDeprecatedAPIsToggleName[] = "disallow_deprecated_apis";
+WGPUDevice DeprecationTests::CreateTestDevice(dawn::native::Adapter dawnAdapter) {
+    wgpu::DeviceDescriptor descriptor = {};
 
-#define EXPECT_DEPRECATION_ERROR_OR_WARNING(statement)         \
-    if (HasToggleEnabled(kDisallowDeprecatedAPIsToggleName)) { \
-        ASSERT_DEVICE_ERROR(statement);                        \
-    } else {                                                   \
-        EXPECT_DEPRECATION_WARNING(statement);                 \
-    }                                                          \
-    for (;;)                                                   \
-    break
+    wgpu::DawnTogglesDeviceDescriptor togglesDesc = {};
+    const char* forceEnabledToggles[1] = {kDisallowDeprecatedAPIsToggleName};
+    togglesDesc.forceEnabledToggles = forceEnabledToggles;
+    togglesDesc.forceEnabledTogglesCount = 1;
 
-class DeprecationTests : public DawnTest {
-  protected:
-    void SetUp() override {
-        DawnTest::SetUp();
-        // Skip when validation is off because warnings might be emitted during validation calls
-        DAWN_TEST_UNSUPPORTED_IF(HasToggleEnabled("skip_validation"));
+    if (GetParam()) {
+        descriptor.nextInChain = &togglesDesc;
     }
-};
+    return dawnAdapter.CreateDevice(&descriptor);
+}
 
 // Test that setting attachment rather than view for render pass color and depth/stencil attachments
 // is deprecated.
@@ -208,16 +197,9 @@
                 }));
 }
 
-DAWN_INSTANTIATE_TEST(DeprecationTests,
-                      D3D12Backend(),
-                      MetalBackend(),
-                      NullBackend(),
-                      OpenGLBackend(),
-                      OpenGLESBackend(),
-                      VulkanBackend(),
-                      D3D12Backend({kDisallowDeprecatedAPIsToggleName}),
-                      MetalBackend({kDisallowDeprecatedAPIsToggleName}),
-                      NullBackend({kDisallowDeprecatedAPIsToggleName}),
-                      OpenGLBackend({kDisallowDeprecatedAPIsToggleName}),
-                      OpenGLESBackend({kDisallowDeprecatedAPIsToggleName}),
-                      VulkanBackend({kDisallowDeprecatedAPIsToggleName}));
+INSTANTIATE_TEST_SUITE_P(DeprecatedAPITest,
+                         DeprecationTests,
+                         testing::Values(true, false),
+                         [](const testing::TestParamInfo<DeprecationTests::ParamType>& info) {
+                             return info.param ? "Disallowed" : "Allowed";
+                         });
diff --git a/src/dawn/tests/unittests/validation/DeprecatedAPITests.h b/src/dawn/tests/unittests/validation/DeprecatedAPITests.h
new file mode 100644
index 0000000..e00f217
--- /dev/null
+++ b/src/dawn/tests/unittests/validation/DeprecatedAPITests.h
@@ -0,0 +1,45 @@
+// Copyright 2022 The Dawn Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_DAWN_TESTS_UNITTESTS_VALIDATION_DEPRECATEDAPITESTS_H_
+#define SRC_DAWN_TESTS_UNITTESTS_VALIDATION_DEPRECATEDAPITESTS_H_
+
+#include "dawn/tests/unittests/validation/ValidationTest.h"
+
+// This test header should be included when testing for deprecated parts of Dawn's API while
+// following WebGPU's evolution. Tests in this suite test that a deprecation warning is emitted when
+// the "old" behavior is used, and tests that an error is emitted when both the old and the new
+// behavior are used (when applicable). Note that implementations of tests in this suite may be
+// scattered in other files as well for organizational purposes so that similar tests can live
+// together.
+
+static constexpr char kDisallowDeprecatedAPIsToggleName[] = "disallow_deprecated_apis";
+
+#define EXPECT_DEPRECATION_ERROR_OR_WARNING(statement)         \
+    if (HasToggleEnabled(kDisallowDeprecatedAPIsToggleName)) { \
+        ASSERT_DEVICE_ERROR(statement);                        \
+    } else {                                                   \
+        EXPECT_DEPRECATION_WARNING(statement);                 \
+    }                                                          \
+    for (;;)                                                   \
+    break
+
+// Parameter is a single bool. When true, deprecated APIs are strictly disallowed (i.e. generate
+// errors). Otherwise, deprecated APIs only generate a warning message.
+class DeprecationTests : public ValidationTest, public testing::WithParamInterface<bool> {
+  protected:
+    WGPUDevice CreateTestDevice(dawn::native::Adapter dawnAdapter) override;
+};
+
+#endif  // SRC_DAWN_TESTS_UNITTESTS_VALIDATION_DEPRECATEDAPITESTS_H_
diff --git a/src/dawn/tests/unittests/validation/RenderBundleValidationTests.cpp b/src/dawn/tests/unittests/validation/RenderBundleValidationTests.cpp
index aebdc0e..f00a0bb 100644
--- a/src/dawn/tests/unittests/validation/RenderBundleValidationTests.cpp
+++ b/src/dawn/tests/unittests/validation/RenderBundleValidationTests.cpp
@@ -12,6 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include <vector>
+
+#include "dawn/tests/unittests/validation/DeprecatedAPITests.h"
 #include "dawn/tests/unittests/validation/ValidationTest.h"
 
 #include "dawn/common/Constants.h"
@@ -615,7 +618,7 @@
 TEST_F(RenderBundleValidationTest, ColorFormatsCountOutOfBounds) {
     std::array<wgpu::TextureFormat, kMaxColorAttachments + 1> colorFormats;
     for (uint32_t i = 0; i < colorFormats.size(); ++i) {
-        colorFormats[i] = wgpu::TextureFormat::RGBA8Unorm;
+        colorFormats[i] = wgpu::TextureFormat::R8Unorm;
     }
 
     // colorFormatsCount <= kMaxColorAttachments is valid.
@@ -1139,3 +1142,68 @@
 
     // Don't test non-renerable depth/stencil formats because we don't have any.
 }
+
+// Tests validation for per-pixel accounting for render targets. The tests currently assume that the
+// default maxColorAttachmentBytesPerSample limit of 32 is used.
+TEST_P(DeprecationTests, RenderBundleColorFormatsBytesPerSample) {
+    struct TestCase {
+        std::vector<wgpu::TextureFormat> formats;
+        bool success;
+    };
+    static std::vector<TestCase> kTestCases = {
+        // Simple 1 format cases.
+
+        // R8Unorm take 1 byte and are aligned to 1 byte so we can have 8 (max).
+        {{wgpu::TextureFormat::R8Unorm, wgpu::TextureFormat::R8Unorm, wgpu::TextureFormat::R8Unorm,
+          wgpu::TextureFormat::R8Unorm, wgpu::TextureFormat::R8Unorm, wgpu::TextureFormat::R8Unorm,
+          wgpu::TextureFormat::R8Unorm, wgpu::TextureFormat::R8Unorm},
+         true},
+        // RGBA8Uint takes 4 bytes and are aligned to 1 byte so we can have 8 (max).
+        {{wgpu::TextureFormat::RGBA8Uint, wgpu::TextureFormat::RGBA8Uint,
+          wgpu::TextureFormat::RGBA8Uint, wgpu::TextureFormat::RGBA8Uint,
+          wgpu::TextureFormat::RGBA8Uint, wgpu::TextureFormat::RGBA8Uint,
+          wgpu::TextureFormat::RGBA8Uint, wgpu::TextureFormat::RGBA8Uint},
+         true},
+        // RGBA8Unorm takes 8 bytes (special case) and are aligned to 1 byte so only 4 allowed.
+        {{wgpu::TextureFormat::RGBA8Unorm, wgpu::TextureFormat::RGBA8Unorm,
+          wgpu::TextureFormat::RGBA8Unorm, wgpu::TextureFormat::RGBA8Unorm},
+         true},
+        {{wgpu::TextureFormat::RGBA8Unorm, wgpu::TextureFormat::RGBA8Unorm,
+          wgpu::TextureFormat::RGBA8Unorm, wgpu::TextureFormat::RGBA8Unorm,
+          wgpu::TextureFormat::RGBA8Unorm},
+         false},
+        // RGBA32Float takes 16 bytes and are aligned to 4 bytes so only 2 are allowed.
+        {{wgpu::TextureFormat::RGBA32Float, wgpu::TextureFormat::RGBA32Float}, true},
+        {{wgpu::TextureFormat::RGBA32Float, wgpu::TextureFormat::RGBA32Float,
+          wgpu::TextureFormat::RGBA32Float},
+         false},
+
+        // Different format alignment cases.
+
+        // Alignment causes the first 1 byte R8Unorm to become 4 bytes. So even though 1+4+8+16+1 <
+        // 32, the 4 byte alignment requirement of R32Float makes the first R8Unorm become 4 and
+        // 4+4+8+16+1 > 32. Re-ordering this so the R8Unorm's are at the end, however is allowed:
+        // 4+8+16+1+1 < 32.
+        {{wgpu::TextureFormat::R8Unorm, wgpu::TextureFormat::R32Float,
+          wgpu::TextureFormat::RGBA8Unorm, wgpu::TextureFormat::RGBA32Float,
+          wgpu::TextureFormat::R8Unorm},
+         false},
+        {{wgpu::TextureFormat::R32Float, wgpu::TextureFormat::RGBA8Unorm,
+          wgpu::TextureFormat::RGBA32Float, wgpu::TextureFormat::R8Unorm,
+          wgpu::TextureFormat::R8Unorm},
+         true},
+    };
+
+    for (const TestCase& testCase : kTestCases) {
+        utils::ComboRenderBundleEncoderDescriptor descriptor;
+        descriptor.colorFormatsCount = testCase.formats.size();
+        for (size_t i = 0; i < testCase.formats.size(); i++) {
+            descriptor.cColorFormats[i] = testCase.formats.at(i);
+        }
+        if (testCase.success) {
+            device.CreateRenderBundleEncoder(&descriptor);
+        } else {
+            EXPECT_DEPRECATION_ERROR_OR_WARNING(device.CreateRenderBundleEncoder(&descriptor));
+        }
+    }
+}
diff --git a/src/dawn/tests/unittests/validation/RenderPassDescriptorValidationTests.cpp b/src/dawn/tests/unittests/validation/RenderPassDescriptorValidationTests.cpp
index 87b292f..ef401da 100644
--- a/src/dawn/tests/unittests/validation/RenderPassDescriptorValidationTests.cpp
+++ b/src/dawn/tests/unittests/validation/RenderPassDescriptorValidationTests.cpp
@@ -13,7 +13,9 @@
 // limitations under the License.
 
 #include <cmath>
+#include <vector>
 
+#include "dawn/tests/unittests/validation/DeprecatedAPITests.h"
 #include "dawn/tests/unittests/validation/ValidationTest.h"
 
 #include "dawn/common/Constants.h"
@@ -98,8 +100,7 @@
 TEST_F(RenderPassDescriptorValidationTest, ColorAttachmentOutOfBounds) {
     std::array<wgpu::RenderPassColorAttachment, kMaxColorAttachments + 1> colorAttachments;
     for (uint32_t i = 0; i < colorAttachments.size(); i++) {
-        colorAttachments[i].view =
-            Create2DAttachment(device, 1, 1, wgpu::TextureFormat::RGBA8Unorm);
+        colorAttachments[i].view = Create2DAttachment(device, 1, 1, wgpu::TextureFormat::R8Unorm);
         colorAttachments[i].resolveTarget = nullptr;
         colorAttachments[i].clearValue = {0.0f, 0.0f, 0.0f, 0.0f};
         colorAttachments[i].loadOp = wgpu::LoadOp::Clear;
@@ -1400,6 +1401,74 @@
     }
 }
 
+// Tests validation for per-pixel accounting for render targets. The tests currently assume that the
+// default maxColorAttachmentBytesPerSample limit of 32 is used.
+TEST_P(DeprecationTests, RenderPassColorAttachmentBytesPerSample) {
+    struct TestCase {
+        std::vector<wgpu::TextureFormat> formats;
+        bool success;
+    };
+    static std::vector<TestCase> kTestCases = {
+        // Simple 1 format cases.
+
+        // R8Unorm take 1 byte and are aligned to 1 byte so we can have 8 (max).
+        {{wgpu::TextureFormat::R8Unorm, wgpu::TextureFormat::R8Unorm, wgpu::TextureFormat::R8Unorm,
+          wgpu::TextureFormat::R8Unorm, wgpu::TextureFormat::R8Unorm, wgpu::TextureFormat::R8Unorm,
+          wgpu::TextureFormat::R8Unorm, wgpu::TextureFormat::R8Unorm},
+         true},
+        // RGBA8Uint takes 4 bytes and are aligned to 1 byte so we can have 8 (max).
+        {{wgpu::TextureFormat::RGBA8Uint, wgpu::TextureFormat::RGBA8Uint,
+          wgpu::TextureFormat::RGBA8Uint, wgpu::TextureFormat::RGBA8Uint,
+          wgpu::TextureFormat::RGBA8Uint, wgpu::TextureFormat::RGBA8Uint,
+          wgpu::TextureFormat::RGBA8Uint, wgpu::TextureFormat::RGBA8Uint},
+         true},
+        // RGBA8Unorm takes 8 bytes (special case) and are aligned to 1 byte so only 4 allowed.
+        {{wgpu::TextureFormat::RGBA8Unorm, wgpu::TextureFormat::RGBA8Unorm,
+          wgpu::TextureFormat::RGBA8Unorm, wgpu::TextureFormat::RGBA8Unorm},
+         true},
+        {{wgpu::TextureFormat::RGBA8Unorm, wgpu::TextureFormat::RGBA8Unorm,
+          wgpu::TextureFormat::RGBA8Unorm, wgpu::TextureFormat::RGBA8Unorm,
+          wgpu::TextureFormat::RGBA8Unorm},
+         false},
+        // RGBA32Float takes 16 bytes and are aligned to 4 bytes so only 2 are allowed.
+        {{wgpu::TextureFormat::RGBA32Float, wgpu::TextureFormat::RGBA32Float}, true},
+        {{wgpu::TextureFormat::RGBA32Float, wgpu::TextureFormat::RGBA32Float,
+          wgpu::TextureFormat::RGBA32Float},
+         false},
+
+        // Different format alignment cases.
+
+        // Alignment causes the first 1 byte R8Unorm to become 4 bytes. So even though 1+4+8+16+1 <
+        // 32, the 4 byte alignment requirement of R32Float makes the first R8Unorm become 4 and
+        // 4+4+8+16+1 > 32. Re-ordering this so the R8Unorm's are at the end, however is allowed:
+        // 4+8+16+1+1 < 32.
+        {{wgpu::TextureFormat::R8Unorm, wgpu::TextureFormat::R32Float,
+          wgpu::TextureFormat::RGBA8Unorm, wgpu::TextureFormat::RGBA32Float,
+          wgpu::TextureFormat::R8Unorm},
+         false},
+        {{wgpu::TextureFormat::R32Float, wgpu::TextureFormat::RGBA8Unorm,
+          wgpu::TextureFormat::RGBA32Float, wgpu::TextureFormat::R8Unorm,
+          wgpu::TextureFormat::R8Unorm},
+         true},
+    };
+
+    for (const TestCase& testCase : kTestCases) {
+        std::vector<wgpu::TextureView> colorAttachmentInfo;
+        for (size_t i = 0; i < testCase.formats.size(); i++) {
+            colorAttachmentInfo.push_back(Create2DAttachment(device, 1, 1, testCase.formats.at(i)));
+        }
+        utils::ComboRenderPassDescriptor descriptor(colorAttachmentInfo);
+        wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder();
+        if (testCase.success) {
+            wgpu::RenderPassEncoder renderPassEncoder = commandEncoder.BeginRenderPass(&descriptor);
+            renderPassEncoder.End();
+            commandEncoder.Finish();
+        } else {
+            EXPECT_DEPRECATION_ERROR_OR_WARNING(commandEncoder.BeginRenderPass(&descriptor));
+        }
+    }
+}
+
 // TODO(cwallez@chromium.org): Constraints on attachment aliasing?
 
 }  // anonymous namespace
diff --git a/src/dawn/tests/unittests/validation/RenderPipelineValidationTests.cpp b/src/dawn/tests/unittests/validation/RenderPipelineValidationTests.cpp
index 7e8a19f..66fe44a 100644
--- a/src/dawn/tests/unittests/validation/RenderPipelineValidationTests.cpp
+++ b/src/dawn/tests/unittests/validation/RenderPipelineValidationTests.cpp
@@ -18,6 +18,7 @@
 #include <vector>
 
 #include "dawn/common/Constants.h"
+#include "dawn/tests/unittests/validation/DeprecatedAPITests.h"
 #include "dawn/tests/unittests/validation/ValidationTest.h"
 #include "dawn/utils/ComboRenderPipelineDescriptor.h"
 #include "dawn/utils/WGPUHelpers.h"
@@ -750,7 +751,7 @@
 
     // Vertex-only render pipeline must have a depth stencil attachment
     {
-        utils::ComboRenderPassDescriptor renderPassDescriptor({});
+        utils::ComboRenderPassDescriptor renderPassDescriptor;
 
         wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
         ASSERT_DEVICE_ERROR(encoder.BeginRenderPass(&renderPassDescriptor));
@@ -1380,6 +1381,103 @@
     ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
 }
 
+// Tests validation for per-pixel accounting for render targets. The tests currently assume that the
+// default maxColorAttachmentBytesPerSample limit of 32 is used.
+TEST_P(DeprecationTests, RenderPipelineColorAttachmentBytesPerSample) {
+    // Creates a fragment shader with maximum number of color attachments to enable testing.
+    auto CreateShader = [&](const std::vector<wgpu::TextureFormat>& formats) -> wgpu::ShaderModule {
+        // Default type to use when formats.size() < kMaxColorAttachments.
+        static constexpr std::string_view kDefaultWgslType = "vec4<f32>";
+
+        std::ostringstream bindings;
+        std::ostringstream outputs;
+        for (size_t i = 0; i < kMaxColorAttachments; i++) {
+            if (i < formats.size()) {
+                std::ostringstream type;
+                type << "vec4<" << utils::GetWGSLColorTextureComponentType(formats.at(i)) << ">";
+                bindings << "@location(" << i << ") o" << i << " : " << type.str() << ", ";
+                outputs << type.str() << "(1), ";
+            } else {
+                bindings << "@location(" << i << ") o" << i << " : " << kDefaultWgslType << ", ";
+                outputs << kDefaultWgslType << "(1), ";
+            }
+        }
+
+        std::ostringstream fsShader;
+        fsShader << "struct Outputs { " << bindings.str() << "}\n";
+        fsShader << "@fragment fn main() -> Outputs {\n";
+        fsShader << "    return Outputs(" << outputs.str() << ");\n";
+        fsShader << "}";
+        return utils::CreateShaderModule(device, fsShader.str().c_str());
+    };
+
+    struct TestCase {
+        std::vector<wgpu::TextureFormat> formats;
+        bool success;
+    };
+    static std::vector<TestCase> kTestCases = {
+        // Simple 1 format cases.
+
+        // R8Unorm take 1 byte and are aligned to 1 byte so we can have 8 (max).
+        {{wgpu::TextureFormat::R8Unorm, wgpu::TextureFormat::R8Unorm, wgpu::TextureFormat::R8Unorm,
+          wgpu::TextureFormat::R8Unorm, wgpu::TextureFormat::R8Unorm, wgpu::TextureFormat::R8Unorm,
+          wgpu::TextureFormat::R8Unorm, wgpu::TextureFormat::R8Unorm},
+         true},
+        // RGBA8Uint takes 4 bytes and are aligned to 1 byte so we can have 8 (max).
+        {{wgpu::TextureFormat::RGBA8Uint, wgpu::TextureFormat::RGBA8Uint,
+          wgpu::TextureFormat::RGBA8Uint, wgpu::TextureFormat::RGBA8Uint,
+          wgpu::TextureFormat::RGBA8Uint, wgpu::TextureFormat::RGBA8Uint,
+          wgpu::TextureFormat::RGBA8Uint, wgpu::TextureFormat::RGBA8Uint},
+         true},
+        // RGBA8Unorm takes 8 bytes (special case) and are aligned to 1 byte so only 4 allowed.
+        {{wgpu::TextureFormat::RGBA8Unorm, wgpu::TextureFormat::RGBA8Unorm,
+          wgpu::TextureFormat::RGBA8Unorm, wgpu::TextureFormat::RGBA8Unorm},
+         true},
+        {{wgpu::TextureFormat::RGBA8Unorm, wgpu::TextureFormat::RGBA8Unorm,
+          wgpu::TextureFormat::RGBA8Unorm, wgpu::TextureFormat::RGBA8Unorm,
+          wgpu::TextureFormat::RGBA8Unorm},
+         false},
+        // RGBA32Float takes 16 bytes and are aligned to 4 bytes so only 2 are allowed.
+        {{wgpu::TextureFormat::RGBA32Float, wgpu::TextureFormat::RGBA32Float}, true},
+        {{wgpu::TextureFormat::RGBA32Float, wgpu::TextureFormat::RGBA32Float,
+          wgpu::TextureFormat::RGBA32Float},
+         false},
+
+        // Different format alignment cases.
+
+        // Alignment causes the first 1 byte R8Unorm to become 4 bytes. So even though 1+4+8+16+1 <
+        // 32, the 4 byte alignment requirement of R32Float makes the first R8Unorm become 4 and
+        // 4+4+8+16+1 > 32. Re-ordering this so the R8Unorm's are at the end, however is allowed:
+        // 4+8+16+1+1 < 32.
+        {{wgpu::TextureFormat::R8Unorm, wgpu::TextureFormat::R32Float,
+          wgpu::TextureFormat::RGBA8Unorm, wgpu::TextureFormat::RGBA32Float,
+          wgpu::TextureFormat::R8Unorm},
+         false},
+        {{wgpu::TextureFormat::R32Float, wgpu::TextureFormat::RGBA8Unorm,
+          wgpu::TextureFormat::RGBA32Float, wgpu::TextureFormat::R8Unorm,
+          wgpu::TextureFormat::R8Unorm},
+         true},
+    };
+
+    for (const TestCase& testCase : kTestCases) {
+        utils::ComboRenderPipelineDescriptor descriptor;
+        descriptor.vertex.module = utils::CreateShaderModule(device, R"(
+            @vertex fn main() -> @builtin(position) vec4<f32> {
+                return vec4<f32>(0.0, 0.0, 0.0, 1.0);
+            })");
+        descriptor.cFragment.module = CreateShader(testCase.formats);
+        descriptor.cFragment.targetCount = testCase.formats.size();
+        for (size_t i = 0; i < testCase.formats.size(); i++) {
+            descriptor.cTargets[i].format = testCase.formats.at(i);
+        }
+        if (testCase.success) {
+            device.CreateRenderPipeline(&descriptor);
+        } else {
+            EXPECT_DEPRECATION_ERROR_OR_WARNING(device.CreateRenderPipeline(&descriptor));
+        }
+    }
+}
+
 class DepthClipControlValidationTest : public RenderPipelineValidationTest {
   protected:
     WGPUDevice CreateTestDevice(dawn::native::Adapter dawnAdapter) override {
diff --git a/src/dawn/utils/WGPUHelpers.cpp b/src/dawn/utils/WGPUHelpers.cpp
index d016620..8cdf718 100644
--- a/src/dawn/utils/WGPUHelpers.cpp
+++ b/src/dawn/utils/WGPUHelpers.cpp
@@ -96,7 +96,7 @@
 }
 
 ComboRenderPassDescriptor::ComboRenderPassDescriptor(
-    std::initializer_list<wgpu::TextureView> colorAttachmentInfo,
+    const std::vector<wgpu::TextureView>& colorAttachmentInfo,
     wgpu::TextureView depthStencil) {
     for (uint32_t i = 0; i < kMaxColorAttachments; ++i) {
         cColorAttachments[i].loadOp = wgpu::LoadOp::Clear;
@@ -174,7 +174,7 @@
       height(0),
       color(nullptr),
       colorFormat(wgpu::TextureFormat::RGBA8Unorm),
-      renderPassInfo({}) {}
+      renderPassInfo() {}
 
 BasicRenderPass::BasicRenderPass(uint32_t texWidth,
                                  uint32_t texHeight,
diff --git a/src/dawn/utils/WGPUHelpers.h b/src/dawn/utils/WGPUHelpers.h
index 9217ca9..b33f9d7 100644
--- a/src/dawn/utils/WGPUHelpers.h
+++ b/src/dawn/utils/WGPUHelpers.h
@@ -57,7 +57,7 @@
 
 struct ComboRenderPassDescriptor : public wgpu::RenderPassDescriptor {
   public:
-    ComboRenderPassDescriptor(std::initializer_list<wgpu::TextureView> colorAttachmentInfo,
+    ComboRenderPassDescriptor(const std::vector<wgpu::TextureView>& colorAttachmentInfo = {},
                               wgpu::TextureView depthStencil = wgpu::TextureView());
     ~ComboRenderPassDescriptor();