Improve validation errors for encoders

Improves the validation messages in ComputePassEncoder.cpp,
ProgrammablePassEncoder.cpp, RenderBundleEncoder.cpp, and
EncodingContext.cpp/h to give them more contextual information.

Bug: dawn:563
Change-Id: I87c46c4bfda1375809fae93239029ea4e3b9c0a2
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/67000
Commit-Queue: Brandon Jones <bajones@chromium.org>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
diff --git a/src/dawn_native/CommandEncoder.cpp b/src/dawn_native/CommandEncoder.cpp
index ab0857f..c9510d0 100644
--- a/src/dawn_native/CommandEncoder.cpp
+++ b/src/dawn_native/CommandEncoder.cpp
@@ -891,8 +891,7 @@
                 if (GetDevice()->IsValidationEnabled()) {
                     DAWN_INVALID_IF(
                         mDebugGroupStackSize == 0,
-                        "Every call to PopDebugGroup must be balanced by a corresponding call to "
-                        "PushDebugGroup.");
+                        "PopDebugGroup called when no debug groups are currently pushed.");
                 }
                 allocator->Allocate<PopDebugGroupCmd>(Command::PopDebugGroup);
                 mDebugGroupStackSize--;
diff --git a/src/dawn_native/ComputePassEncoder.cpp b/src/dawn_native/ComputePassEncoder.cpp
index 46277b1..1aa4845 100644
--- a/src/dawn_native/ComputePassEncoder.cpp
+++ b/src/dawn_native/ComputePassEncoder.cpp
@@ -26,18 +26,6 @@
 
 namespace dawn_native {
 
-    namespace {
-
-        MaybeError ValidatePerDimensionDispatchSizeLimit(const DeviceBase* device, uint32_t size) {
-            if (size > device->GetLimits().v1.maxComputeWorkgroupsPerDimension) {
-                return DAWN_VALIDATION_ERROR("Dispatch size exceeds defined limits");
-            }
-
-            return {};
-        }
-
-    }  // namespace
-
     ComputePassEncoder::ComputePassEncoder(DeviceBase* device,
                                            CommandEncoder* commandEncoder,
                                            EncodingContext* encodingContext)
@@ -85,9 +73,24 @@
             [&](CommandAllocator* allocator) -> MaybeError {
                 if (IsValidationEnabled()) {
                     DAWN_TRY(mCommandBufferState.ValidateCanDispatch());
-                    DAWN_TRY(ValidatePerDimensionDispatchSizeLimit(GetDevice(), x));
-                    DAWN_TRY(ValidatePerDimensionDispatchSizeLimit(GetDevice(), y));
-                    DAWN_TRY(ValidatePerDimensionDispatchSizeLimit(GetDevice(), z));
+
+                    uint32_t workgroupsPerDimension =
+                        GetDevice()->GetLimits().v1.maxComputeWorkgroupsPerDimension;
+
+                    DAWN_INVALID_IF(
+                        x > workgroupsPerDimension,
+                        "Dispatch size X (%u) exceeds max compute workgroups per dimension (%u).",
+                        x, workgroupsPerDimension);
+
+                    DAWN_INVALID_IF(
+                        y > workgroupsPerDimension,
+                        "Dispatch size Y (%u) exceeds max compute workgroups per dimension (%u).",
+                        y, workgroupsPerDimension);
+
+                    DAWN_INVALID_IF(
+                        z > workgroupsPerDimension,
+                        "Dispatch size Z (%u) exceeds max compute workgroups per dimension (%u).",
+                        z, workgroupsPerDimension);
                 }
 
                 // Record the synchronization scope for Dispatch, which is just the current
@@ -117,21 +120,20 @@
                     // Indexed dispatches need a compute-shader based validation to check that the
                     // dispatch sizes aren't too big. Disallow them as unsafe until the validation
                     // is implemented.
-                    if (GetDevice()->IsToggleEnabled(Toggle::DisallowUnsafeAPIs)) {
-                        return DAWN_VALIDATION_ERROR(
-                            "DispatchIndirect is disallowed because it doesn't validate that the "
-                            "dispatch "
-                            "size is valid yet.");
-                    }
+                    DAWN_INVALID_IF(
+                        GetDevice()->IsToggleEnabled(Toggle::DisallowUnsafeAPIs),
+                        "DispatchIndirect is disallowed because it doesn't validate that the "
+                        "dispatch size is valid yet.");
 
-                    if (indirectOffset % 4 != 0) {
-                        return DAWN_VALIDATION_ERROR("Indirect offset must be a multiple of 4");
-                    }
+                    DAWN_INVALID_IF(indirectOffset % 4 != 0,
+                                    "Indirect offset (%u) is not a multiple of 4.", indirectOffset);
 
-                    if (indirectOffset >= indirectBuffer->GetSize() ||
-                        indirectOffset + kDispatchIndirectSize > indirectBuffer->GetSize()) {
-                        return DAWN_VALIDATION_ERROR("Indirect offset out of bounds");
-                    }
+                    DAWN_INVALID_IF(
+                        indirectOffset >= indirectBuffer->GetSize() ||
+                            indirectOffset + kDispatchIndirectSize > indirectBuffer->GetSize(),
+                        "Indirect offset (%u) and dispatch size (%u) exceeds the indirect buffer "
+                        "size (%u).",
+                        indirectOffset, kDispatchIndirectSize, indirectBuffer->GetSize());
                 }
 
                 // Record the synchronization scope for Dispatch, both the bindgroups and the
diff --git a/src/dawn_native/EncodingContext.cpp b/src/dawn_native/EncodingContext.cpp
index 2724291..7b14f5e 100644
--- a/src/dawn_native/EncodingContext.cpp
+++ b/src/dawn_native/EncodingContext.cpp
@@ -24,7 +24,7 @@
 
 namespace dawn_native {
 
-    EncodingContext::EncodingContext(DeviceBase* device, const ObjectBase* initialEncoder)
+    EncodingContext::EncodingContext(DeviceBase* device, const ApiObjectBase* initialEncoder)
         : mDevice(device), mTopLevelEncoder(initialEncoder), mCurrentEncoder(initialEncoder) {
     }
 
@@ -87,7 +87,7 @@
         }
     }
 
-    void EncodingContext::EnterPass(const ObjectBase* passEncoder) {
+    void EncodingContext::EnterPass(const ApiObjectBase* passEncoder) {
         // Assert we're at the top level.
         ASSERT(mCurrentEncoder == mTopLevelEncoder);
         ASSERT(passEncoder != nullptr);
@@ -95,7 +95,7 @@
         mCurrentEncoder = passEncoder;
     }
 
-    MaybeError EncodingContext::ExitRenderPass(const ObjectBase* passEncoder,
+    MaybeError EncodingContext::ExitRenderPass(const ApiObjectBase* passEncoder,
                                                RenderPassResourceUsageTracker usageTracker,
                                                CommandEncoder* commandEncoder,
                                                IndirectDrawMetadata indirectDrawMetadata) {
@@ -121,7 +121,7 @@
         return {};
     }
 
-    void EncodingContext::ExitComputePass(const ObjectBase* passEncoder,
+    void EncodingContext::ExitComputePass(const ApiObjectBase* passEncoder,
                                           ComputePassResourceUsage usages) {
         ASSERT(mCurrentEncoder != mTopLevelEncoder);
         ASSERT(mCurrentEncoder == passEncoder);
@@ -161,12 +161,10 @@
     }
 
     MaybeError EncodingContext::Finish() {
-        if (IsFinished()) {
-            return DAWN_VALIDATION_ERROR("Command encoding already finished");
-        }
+        DAWN_INVALID_IF(IsFinished(), "Command encoding already finished.");
 
-        const void* currentEncoder = mCurrentEncoder;
-        const void* topLevelEncoder = mTopLevelEncoder;
+        const ApiObjectBase* currentEncoder = mCurrentEncoder;
+        const ApiObjectBase* topLevelEncoder = mTopLevelEncoder;
 
         // Even if finish validation fails, it is now invalid to call any encoding commands,
         // so we clear the encoders. Note: mTopLevelEncoder == nullptr is used as a flag for
@@ -178,9 +176,8 @@
         if (mError != nullptr) {
             return std::move(mError);
         }
-        if (currentEncoder != topLevelEncoder) {
-            return DAWN_VALIDATION_ERROR("Command buffer recording ended mid-pass");
-        }
+        DAWN_INVALID_IF(currentEncoder != topLevelEncoder,
+                        "Command buffer recording ended before %s was ended.", currentEncoder);
         return {};
     }
 
diff --git a/src/dawn_native/EncodingContext.h b/src/dawn_native/EncodingContext.h
index b058e2b..c395cf2 100644
--- a/src/dawn_native/EncodingContext.h
+++ b/src/dawn_native/EncodingContext.h
@@ -28,13 +28,13 @@
 
     class CommandEncoder;
     class DeviceBase;
-    class ObjectBase;
+    class ApiObjectBase;
 
     // Base class for allocating/iterating commands.
     // It performs error tracking as well as encoding state for render/compute passes.
     class EncodingContext {
       public:
-        EncodingContext(DeviceBase* device, const ObjectBase* initialEncoder);
+        EncodingContext(DeviceBase* device, const ApiObjectBase* initialEncoder);
         ~EncodingContext();
 
         CommandIterator AcquireCommands();
@@ -70,14 +70,15 @@
             return false;
         }
 
-        inline bool CheckCurrentEncoder(const ObjectBase* encoder) {
+        inline bool CheckCurrentEncoder(const ApiObjectBase* encoder) {
             if (DAWN_UNLIKELY(encoder != mCurrentEncoder)) {
                 if (mCurrentEncoder != mTopLevelEncoder) {
                     // The top level encoder was used when a pass encoder was current.
-                    HandleError(DAWN_VALIDATION_ERROR("Command cannot be recorded inside a pass"));
+                    HandleError(DAWN_FORMAT_VALIDATION_ERROR(
+                        "Command cannot be recorded while %s is active.", mCurrentEncoder));
                 } else {
-                    HandleError(DAWN_VALIDATION_ERROR(
-                        "Recording in an error or already ended pass encoder"));
+                    HandleError(DAWN_FORMAT_VALIDATION_ERROR(
+                        "Recording in an error or already ended %s.", encoder));
                 }
                 return false;
             }
@@ -85,7 +86,7 @@
         }
 
         template <typename EncodeFunction>
-        inline bool TryEncode(const ObjectBase* encoder, EncodeFunction&& encodeFunction) {
+        inline bool TryEncode(const ApiObjectBase* encoder, EncodeFunction&& encodeFunction) {
             if (!CheckCurrentEncoder(encoder)) {
                 return false;
             }
@@ -94,7 +95,7 @@
         }
 
         template <typename EncodeFunction, typename... Args>
-        inline bool TryEncode(const ObjectBase* encoder,
+        inline bool TryEncode(const ApiObjectBase* encoder,
                               EncodeFunction&& encodeFunction,
                               const char* formatStr,
                               const Args&... args) {
@@ -111,12 +112,12 @@
         void WillBeginRenderPass();
 
         // Functions to set current encoder state
-        void EnterPass(const ObjectBase* passEncoder);
-        MaybeError ExitRenderPass(const ObjectBase* passEncoder,
+        void EnterPass(const ApiObjectBase* passEncoder);
+        MaybeError ExitRenderPass(const ApiObjectBase* passEncoder,
                                   RenderPassResourceUsageTracker usageTracker,
                                   CommandEncoder* commandEncoder,
                                   IndirectDrawMetadata indirectDrawMetadata);
-        void ExitComputePass(const ObjectBase* passEncoder, ComputePassResourceUsage usages);
+        void ExitComputePass(const ApiObjectBase* passEncoder, ComputePassResourceUsage usages);
         MaybeError Finish();
 
         const RenderPassUsages& GetRenderPassUsages() const;
@@ -138,12 +139,12 @@
         // There can only be two levels of encoders. Top-level and render/compute pass.
         // The top level encoder is the encoder the EncodingContext is created with.
         // It doubles as flag to check if encoding has been Finished.
-        const ObjectBase* mTopLevelEncoder;
+        const ApiObjectBase* mTopLevelEncoder;
         // The current encoder must be the same as the encoder provided to TryEncode,
         // otherwise an error is produced. It may be nullptr if the EncodingContext is an error.
         // The current encoder changes with Enter/ExitPass which should be called by
         // CommandEncoder::Begin/EndPass.
-        const ObjectBase* mCurrentEncoder;
+        const ApiObjectBase* mCurrentEncoder;
 
         RenderPassUsages mRenderPassUsages;
         bool mWereRenderPassUsagesAcquired = false;
diff --git a/src/dawn_native/ProgrammablePassEncoder.cpp b/src/dawn_native/ProgrammablePassEncoder.cpp
index c6e2e28..8cf1b82 100644
--- a/src/dawn_native/ProgrammablePassEncoder.cpp
+++ b/src/dawn_native/ProgrammablePassEncoder.cpp
@@ -48,9 +48,9 @@
     }
 
     MaybeError ProgrammablePassEncoder::ValidateProgrammableEncoderEnd() const {
-        if (mDebugGroupStackSize != 0) {
-            return DAWN_VALIDATION_ERROR("Each Push must be balanced by a corresponding Pop.");
-        }
+        DAWN_INVALID_IF(mDebugGroupStackSize != 0,
+                        "PushDebugGroup called %u time(s) without a corresponding PopDebugGroup.",
+                        mDebugGroupStackSize);
         return {};
     }
 
@@ -75,10 +75,9 @@
             this,
             [&](CommandAllocator* allocator) -> MaybeError {
                 if (IsValidationEnabled()) {
-                    if (mDebugGroupStackSize == 0) {
-                        return DAWN_VALIDATION_ERROR(
-                            "Pop must be balanced by a corresponding Push.");
-                    }
+                    DAWN_INVALID_IF(
+                        mDebugGroupStackSize == 0,
+                        "PopDebugGroup called when no debug groups are currently pushed.");
                 }
                 allocator->Allocate<PopDebugGroupCmd>(Command::PopDebugGroup);
                 mDebugGroupStackSize--;
@@ -115,18 +114,21 @@
         const uint32_t* dynamicOffsetsIn) const {
         DAWN_TRY(GetDevice()->ValidateObject(group));
 
-        if (index >= kMaxBindGroupsTyped) {
-            return DAWN_VALIDATION_ERROR("Setting bind group over the max");
-        }
+        DAWN_INVALID_IF(index >= kMaxBindGroupsTyped,
+                        "Bind group index (%u) exceeds the maximum (%u).",
+                        static_cast<uint32_t>(index), kMaxBindGroups);
 
         ityp::span<BindingIndex, const uint32_t> dynamicOffsets(dynamicOffsetsIn,
                                                                 BindingIndex(dynamicOffsetCountIn));
 
         // Dynamic offsets count must match the number required by the layout perfectly.
         const BindGroupLayoutBase* layout = group->GetLayout();
-        if (layout->GetDynamicBufferCount() != dynamicOffsets.size()) {
-            return DAWN_VALIDATION_ERROR("dynamicOffset count mismatch");
-        }
+        DAWN_INVALID_IF(
+            layout->GetDynamicBufferCount() != dynamicOffsets.size(),
+            "The number of dynamic offsets (%u) does not match the number of dynamic buffers (%u) "
+            "in %s.",
+            static_cast<uint32_t>(dynamicOffsets.size()),
+            static_cast<uint32_t>(layout->GetDynamicBufferCount()), layout);
 
         for (BindingIndex i{0}; i < dynamicOffsets.size(); ++i) {
             const BindingInfo& bindingInfo = layout->GetBindingInfo(i);
@@ -150,9 +152,9 @@
                     UNREACHABLE();
             }
 
-            if (!IsAligned(dynamicOffsets[i], requiredAlignment)) {
-                return DAWN_VALIDATION_ERROR("Dynamic Buffer Offset need to be aligned");
-            }
+            DAWN_INVALID_IF(!IsAligned(dynamicOffsets[i], requiredAlignment),
+                            "Dynamic Offset[%u] (%u) is not %u byte aligned.",
+                            static_cast<uint32_t>(i), dynamicOffsets[i], requiredAlignment);
 
             BufferBinding bufferBinding = group->GetBindingAsBufferBinding(i);
 
@@ -163,15 +165,20 @@
 
             if ((dynamicOffsets[i] >
                  bufferBinding.buffer->GetSize() - bufferBinding.offset - bufferBinding.size)) {
-                if ((bufferBinding.buffer->GetSize() - bufferBinding.offset) ==
-                    bufferBinding.size) {
-                    return DAWN_VALIDATION_ERROR(
-                        "Dynamic offset out of bounds. The binding goes to the end of the "
-                        "buffer even with a dynamic offset of 0. Did you forget to specify "
-                        "the binding's size?");
-                } else {
-                    return DAWN_VALIDATION_ERROR("Dynamic offset out of bounds");
-                }
+                DAWN_INVALID_IF(
+                    (bufferBinding.buffer->GetSize() - bufferBinding.offset) == bufferBinding.size,
+                    "Dynamic Offset[%u] (%u) is out of bounds of %s with a size of %u and a bound "
+                    "range of (offset: %u, size: %u). The binding goes to the end of the buffer "
+                    "even with a dynamic offset of 0. Did you forget to specify "
+                    "the binding's size?",
+                    static_cast<uint32_t>(i), dynamicOffsets[i], bufferBinding.buffer,
+                    bufferBinding.buffer->GetSize(), bufferBinding.offset, bufferBinding.size);
+
+                return DAWN_FORMAT_VALIDATION_ERROR(
+                    "Dynamic Offset[%u] (%u) is out of bounds of "
+                    "%s with a size of %u and a bound range of (offset: %u, size: %u).",
+                    static_cast<uint32_t>(i), dynamicOffsets[i], bufferBinding.buffer,
+                    bufferBinding.buffer->GetSize(), bufferBinding.offset, bufferBinding.size);
             }
         }
 
diff --git a/src/dawn_native/RenderBundleEncoder.cpp b/src/dawn_native/RenderBundleEncoder.cpp
index 19f8c64..b21a095 100644
--- a/src/dawn_native/RenderBundleEncoder.cpp
+++ b/src/dawn_native/RenderBundleEncoder.cpp
@@ -123,7 +123,8 @@
     RenderBundleBase* RenderBundleEncoder::APIFinish(const RenderBundleDescriptor* descriptor) {
         RenderBundleBase* result = nullptr;
 
-        if (GetDevice()->ConsumedError(FinishImpl(descriptor), &result)) {
+        if (GetDevice()->ConsumedError(FinishImpl(descriptor), &result, "calling Finish(%s).",
+                                       descriptor)) {
             return RenderBundleBase::MakeError(GetDevice());
         }