Refactor ValidateDrawBase.

Split the parameter-dependent and parameter-independent validation.
This will more easily let us cache independent validation in Context.

Bug: angleproject:2747
Change-Id: I78a12798cd03a398392ca213bf51e2079295b97e
Reviewed-on: https://chromium-review.googlesource.com/1158610
Commit-Queue: Jamie Madill <jmadill@chromium.org>
Reviewed-by: Frank Henigman <fjhenigman@chromium.org>
Reviewed-by: Geoff Lang <geofflang@chromium.org>
diff --git a/src/libANGLE/validationES.cpp b/src/libANGLE/validationES.cpp
index 99b7009..d05cb75 100644
--- a/src/libANGLE/validationES.cpp
+++ b/src/libANGLE/validationES.cpp
@@ -79,33 +79,6 @@
     return !checkedA.IsValid();
 }
 
-bool ValidateDrawClientAttribs(Context *context)
-{
-    ASSERT(context->getStateCache().hasAnyEnabledClientAttrib());
-
-    const State &state = context->getGLState();
-
-    if (context->getExtensions().webglCompatibility || !state.areClientArraysEnabled())
-    {
-        // [WebGL 1.0] Section 6.5 Enabled Vertex Attributes and Range Checking
-        // If a vertex attribute is enabled as an array via enableVertexAttribArray but no
-        // buffer is bound to that attribute via bindBuffer and vertexAttribPointer, then calls
-        // to drawArrays or drawElements will generate an INVALID_OPERATION error.
-        ANGLE_VALIDATION_ERR(context, InvalidOperation(), VertexArrayNoBuffer);
-        return false;
-    }
-
-    if (state.getVertexArray()->hasEnabledNullPointerClientArray())
-    {
-        // This is an application error that would normally result in a crash, but we catch it
-        // and return an error
-        ANGLE_VALIDATION_ERR(context, InvalidOperation(), VertexArrayNoBufferPointer);
-        return false;
-    }
-
-    return true;
-}
-
 bool ValidateDrawAttribs(Context *context, GLint primcount, GLint maxVertex)
 {
     // If we're drawing zero vertices, we have enough data.
@@ -408,16 +381,10 @@
     const Program *program         = context->getGLState().getProgram();
     const Framebuffer *framebuffer = context->getGLState().getDrawFramebuffer();
 
-    if (!ComponentTypeMask::Validate(program->getDrawBufferTypeMask().to_ulong(),
-                                     framebuffer->getDrawBufferTypeMask().to_ulong(),
-                                     program->getActiveOutputVariables().to_ulong(),
-                                     framebuffer->getDrawBufferMask().to_ulong()))
-    {
-        ANGLE_VALIDATION_ERR(context, InvalidOperation(), DrawBufferTypeMismatch);
-        return false;
-    }
-
-    return true;
+    return ComponentTypeMask::Validate(program->getDrawBufferTypeMask().to_ulong(),
+                                       framebuffer->getDrawBufferTypeMask().to_ulong(),
+                                       program->getActiveOutputVariables().to_ulong(),
+                                       framebuffer->getDrawBufferMask().to_ulong());
 }
 
 bool ValidateVertexShaderAttributeTypeMatch(Context *context)
@@ -434,13 +401,9 @@
     vaoAttribTypeBits = (vaoAttribEnabledMask & vaoAttribTypeBits);
     vaoAttribTypeBits |= (~vaoAttribEnabledMask & stateCurrentValuesTypeBits);
 
-    if (!ComponentTypeMask::Validate(program->getAttributesTypeMask().to_ulong(), vaoAttribTypeBits,
-                                     program->getAttributesMask().to_ulong(), 0xFFFF))
-    {
-        ANGLE_VALIDATION_ERR(context, InvalidOperation(), VertexShaderTypeMismatch);
-        return false;
-    }
-    return true;
+    return ComponentTypeMask::Validate(program->getAttributesTypeMask().to_ulong(),
+                                       vaoAttribTypeBits, program->getAttributesMask().to_ulong(),
+                                       0xFFFF);
 }
 
 bool IsCompatibleDrawModeWithGeometryShader(PrimitiveMode drawMode,
@@ -488,7 +451,6 @@
             return false;
     }
 }
-
 }  // anonymous namespace
 
 void SetRobustLengthParam(GLsizei *length, GLsizei value)
@@ -2588,6 +2550,188 @@
     return true;
 }
 
+ErrorAndMessage ValidateDrawStates(Context *context)
+{
+    const Extensions &extensions = context->getExtensions();
+    const State &state = context->getGLState();
+
+    // WebGL buffers cannot be mapped/unmapped because the MapBufferRange, FlushMappedBufferRange,
+    // and UnmapBuffer entry points are removed from the WebGL 2.0 API.
+    // https://www.khronos.org/registry/webgl/specs/latest/2.0/#5.14
+    if (!extensions.webglCompatibility && state.getVertexArray()->hasMappedEnabledArrayBuffer())
+    {
+        return {GL_INVALID_OPERATION, nullptr};
+    }
+
+    // Note: these separate values are not supported in WebGL, due to D3D's limitations. See
+    // Section 6.10 of the WebGL 1.0 spec.
+    Framebuffer *framebuffer = state.getDrawFramebuffer();
+    if (context->getLimitations().noSeparateStencilRefsAndMasks || extensions.webglCompatibility)
+    {
+        ASSERT(framebuffer);
+        const FramebufferAttachment *dsAttachment =
+            framebuffer->getStencilOrDepthStencilAttachment();
+        const GLuint stencilBits = dsAttachment ? dsAttachment->getStencilSize() : 0;
+        ASSERT(stencilBits <= 8);
+
+        const DepthStencilState &depthStencilState = state.getDepthStencilState();
+        if (depthStencilState.stencilTest && stencilBits > 0)
+        {
+            GLuint maxStencilValue = (1 << stencilBits) - 1;
+
+            bool differentRefs =
+                clamp(state.getStencilRef(), 0, static_cast<GLint>(maxStencilValue)) !=
+                clamp(state.getStencilBackRef(), 0, static_cast<GLint>(maxStencilValue));
+            bool differentWritemasks = (depthStencilState.stencilWritemask & maxStencilValue) !=
+                                       (depthStencilState.stencilBackWritemask & maxStencilValue);
+            bool differentMasks = (depthStencilState.stencilMask & maxStencilValue) !=
+                                  (depthStencilState.stencilBackMask & maxStencilValue);
+
+            if (differentRefs || differentWritemasks || differentMasks)
+            {
+                if (!extensions.webglCompatibility)
+                {
+                    WARN() << "This ANGLE implementation does not support separate front/back "
+                              "stencil writemasks, reference values, or stencil mask values.";
+                }
+                return {GL_INVALID_OPERATION, kErrorStencilReferenceMaskOrMismatch};
+            }
+        }
+    }
+
+    if (!framebuffer->isComplete(context))
+    {
+        return {GL_INVALID_FRAMEBUFFER_OPERATION, nullptr};
+    }
+
+    if (context->getStateCache().hasAnyEnabledClientAttrib())
+    {
+        if (context->getExtensions().webglCompatibility || !state.areClientArraysEnabled())
+        {
+            // [WebGL 1.0] Section 6.5 Enabled Vertex Attributes and Range Checking
+            // If a vertex attribute is enabled as an array via enableVertexAttribArray but no
+            // buffer is bound to that attribute via bindBuffer and vertexAttribPointer, then calls
+            // to drawArrays or drawElements will generate an INVALID_OPERATION error.
+            return {GL_INVALID_OPERATION, kErrorVertexArrayNoBuffer};
+        }
+
+        if (state.getVertexArray()->hasEnabledNullPointerClientArray())
+        {
+            // This is an application error that would normally result in a crash, but we catch it
+            // and return an error
+            return {GL_INVALID_OPERATION, kErrorVertexArrayNoBufferPointer};
+        }
+    }
+
+    // If we are running GLES1, there is no current program.
+    if (context->getClientVersion() >= Version(2, 0))
+    {
+        Program *program = state.getProgram();
+        if (!program)
+        {
+            return {GL_INVALID_OPERATION, kErrorProgramNotBound};
+        }
+
+        // In OpenGL ES spec for UseProgram at section 7.3, trying to render without
+        // vertex shader stage or fragment shader stage is a undefined behaviour.
+        // But ANGLE should clearly generate an INVALID_OPERATION error instead of
+        // produce undefined result.
+        if (!program->hasLinkedShaderStage(ShaderType::Vertex) ||
+            !program->hasLinkedShaderStage(ShaderType::Fragment))
+        {
+            return {GL_INVALID_OPERATION, kErrorNoActiveGraphicsShaderStage};
+        }
+
+        if (!program->validateSamplers(nullptr, context->getCaps()))
+        {
+            return {GL_INVALID_OPERATION, nullptr};
+        }
+
+        if (extensions.multiview)
+        {
+            const int programNumViews     = program->usesMultiview() ? program->getNumViews() : 1;
+            const int framebufferNumViews = framebuffer->getNumViews();
+            if (framebufferNumViews != programNumViews)
+            {
+                return {GL_INVALID_OPERATION, kErrorMultiviewMismatch};
+            }
+
+            const TransformFeedback *transformFeedbackObject = state.getCurrentTransformFeedback();
+            if (transformFeedbackObject != nullptr && transformFeedbackObject->isActive() &&
+                framebufferNumViews > 1)
+            {
+                return {GL_INVALID_OPERATION, kErrorMultiviewTransformFeedback};
+            }
+
+            if (extensions.disjointTimerQuery && framebufferNumViews > 1 &&
+                state.isQueryActive(QueryType::TimeElapsed))
+            {
+                return {GL_INVALID_OPERATION, kErrorMultiviewTimerQuery};
+            }
+        }
+
+        // Uniform buffer validation
+        for (unsigned int uniformBlockIndex = 0;
+             uniformBlockIndex < program->getActiveUniformBlockCount(); uniformBlockIndex++)
+        {
+            const InterfaceBlock &uniformBlock = program->getUniformBlockByIndex(uniformBlockIndex);
+            GLuint blockBinding = program->getUniformBlockBinding(uniformBlockIndex);
+            const OffsetBindingPointer<Buffer> &uniformBuffer =
+                state.getIndexedUniformBuffer(blockBinding);
+
+            if (uniformBuffer.get() == nullptr)
+            {
+                // undefined behaviour
+                return {GL_INVALID_OPERATION, kErrorUniformBufferUnbound};
+            }
+
+            size_t uniformBufferSize = GetBoundBufferAvailableSize(uniformBuffer);
+            if (uniformBufferSize < uniformBlock.dataSize)
+            {
+                // undefined behaviour
+                return {GL_INVALID_OPERATION, kErrorUniformBufferTooSmall};
+            }
+
+            if (extensions.webglCompatibility &&
+                uniformBuffer->isBoundForTransformFeedbackAndOtherUse())
+            {
+                return {GL_INVALID_OPERATION, kErrorUniformBufferBoundForTransformFeedback};
+            }
+        }
+
+        // Do some additonal WebGL-specific validation
+        if (extensions.webglCompatibility)
+        {
+            const TransformFeedback *transformFeedbackObject = state.getCurrentTransformFeedback();
+            if (transformFeedbackObject != nullptr && transformFeedbackObject->isActive() &&
+                transformFeedbackObject->buffersBoundForOtherUse())
+            {
+                return {GL_INVALID_OPERATION, kErrorTransformFeedbackBufferDoubleBound};
+            }
+
+            // Detect rendering feedback loops for WebGL.
+            if (framebuffer->formsRenderingFeedbackLoopWith(state))
+            {
+                return {GL_INVALID_OPERATION, kErrorFeedbackLoop};
+            }
+
+            // Detect that the vertex shader input types match the attribute types
+            if (!ValidateVertexShaderAttributeTypeMatch(context))
+            {
+                return {GL_INVALID_OPERATION, kErrorVertexShaderTypeMismatch};
+            }
+
+            // Detect that the color buffer types match the fragment shader output types
+            if (!ValidateFragmentShaderColorBufferTypeMatch(context))
+            {
+                return {GL_INVALID_OPERATION, kErrorDrawBufferTypeMismatch};
+            }
+        }
+    }
+
+    return {GL_NO_ERROR, nullptr};
+}
+
 bool ValidateDrawBase(Context *context, PrimitiveMode mode, GLsizei count)
 {
     const Extensions &extensions = context->getExtensions();
@@ -2626,128 +2770,25 @@
 
     const State &state = context->getGLState();
 
-    // WebGL buffers cannot be mapped/unmapped because the MapBufferRange, FlushMappedBufferRange,
-    // and UnmapBuffer entry points are removed from the WebGL 2.0 API.
-    // https://www.khronos.org/registry/webgl/specs/latest/2.0/#5.14
-    if (!extensions.webglCompatibility && state.getVertexArray()->hasMappedEnabledArrayBuffer())
+    const ErrorAndMessage &errorAndMessage = ValidateDrawStates(context);
+    if (errorAndMessage.errorType != GL_NO_ERROR)
     {
-        context->handleError(InvalidOperation());
-        return false;
-    }
-
-    // Note: these separate values are not supported in WebGL, due to D3D's limitations. See
-    // Section 6.10 of the WebGL 1.0 spec.
-    Framebuffer *framebuffer = state.getDrawFramebuffer();
-    if (context->getLimitations().noSeparateStencilRefsAndMasks || extensions.webglCompatibility)
-    {
-        ASSERT(framebuffer);
-        const FramebufferAttachment *dsAttachment =
-            framebuffer->getStencilOrDepthStencilAttachment();
-        const GLuint stencilBits = dsAttachment ? dsAttachment->getStencilSize() : 0;
-        ASSERT(stencilBits <= 8);
-
-        const DepthStencilState &depthStencilState = state.getDepthStencilState();
-        if (depthStencilState.stencilTest && stencilBits > 0)
+        if (errorAndMessage.message)
         {
-            GLuint maxStencilValue = (1 << stencilBits) - 1;
-
-            bool differentRefs =
-                clamp(state.getStencilRef(), 0, static_cast<GLint>(maxStencilValue)) !=
-                clamp(state.getStencilBackRef(), 0, static_cast<GLint>(maxStencilValue));
-            bool differentWritemasks = (depthStencilState.stencilWritemask & maxStencilValue) !=
-                                       (depthStencilState.stencilBackWritemask & maxStencilValue);
-            bool differentMasks = (depthStencilState.stencilMask & maxStencilValue) !=
-                                  (depthStencilState.stencilBackMask & maxStencilValue);
-
-            if (differentRefs || differentWritemasks || differentMasks)
-            {
-                if (!extensions.webglCompatibility)
-                {
-                    WARN() << "This ANGLE implementation does not support separate front/back "
-                              "stencil writemasks, reference values, or stencil mask values.";
-                }
-                ANGLE_VALIDATION_ERR(context, InvalidOperation(), StencilReferenceMaskOrMismatch);
-                return false;
-            }
+            context->handleError(Error(errorAndMessage.errorType, errorAndMessage.message));
         }
-    }
-
-    if (!ValidateFramebufferComplete(context, framebuffer))
-    {
-        return false;
-    }
-
-    if (context->getStateCache().hasAnyEnabledClientAttrib())
-    {
-        if (!ValidateDrawClientAttribs(context))
+        else
         {
-            return false;
+            context->handleError(Error(errorAndMessage.errorType));
         }
+        return false;
     }
 
     // If we are running GLES1, there is no current program.
     if (context->getClientVersion() >= Version(2, 0))
     {
         Program *program = state.getProgram();
-        if (!program)
-        {
-            ANGLE_VALIDATION_ERR(context, InvalidOperation(), ProgramNotBound);
-            return false;
-        }
-
-        // In OpenGL ES spec for UseProgram at section 7.3, trying to render without
-        // vertex shader stage or fragment shader stage is a undefined behaviour.
-        // But ANGLE should clearly generate an INVALID_OPERATION error instead of
-        // produce undefined result.
-        if (!program->hasLinkedShaderStage(ShaderType::Vertex) ||
-            !program->hasLinkedShaderStage(ShaderType::Fragment))
-        {
-            context->handleError(InvalidOperation()
-                                 << "It is a undefined behaviour to render without "
-                                    "vertex shader stage or fragment shader stage.");
-            return false;
-        }
-
-        if (!program->validateSamplers(nullptr, context->getCaps()))
-        {
-            context->handleError(InvalidOperation());
-            return false;
-        }
-
-        if (extensions.multiview)
-        {
-            const int programNumViews     = program->usesMultiview() ? program->getNumViews() : 1;
-            const int framebufferNumViews = framebuffer->getNumViews();
-            if (framebufferNumViews != programNumViews)
-            {
-                context->handleError(InvalidOperation()
-                                     << "The number of views in the active program "
-                                        "and draw framebuffer does not match.");
-                return false;
-            }
-
-            const TransformFeedback *transformFeedbackObject = state.getCurrentTransformFeedback();
-            if (transformFeedbackObject != nullptr && transformFeedbackObject->isActive() &&
-                framebufferNumViews > 1)
-            {
-                context->handleError(InvalidOperation()
-                                     << "There is an active transform feedback object "
-                                        "when the number of views in the active draw "
-                                        "framebuffer is greater than 1.");
-                return false;
-            }
-
-            if (extensions.disjointTimerQuery && framebufferNumViews > 1 &&
-                state.isQueryActive(QueryType::TimeElapsed))
-            {
-                context->handleError(InvalidOperation()
-                                     << "There is an active query for target "
-                                        "GL_TIME_ELAPSED_EXT when the number of "
-                                        "views in the active draw framebuffer is "
-                                        "greater than 1.");
-                return false;
-            }
-        }
+        ASSERT(program);
 
         // Do geometry shader specific validations
         if (program->hasLinkedShaderStage(ShaderType::Geometry))
@@ -2761,84 +2802,16 @@
             }
         }
 
-        // Uniform buffer validation
-        for (unsigned int uniformBlockIndex = 0;
-             uniformBlockIndex < program->getActiveUniformBlockCount(); uniformBlockIndex++)
+        if (extensions.webglCompatibility && count > 0)
         {
-            const InterfaceBlock &uniformBlock = program->getUniformBlockByIndex(uniformBlockIndex);
-            GLuint blockBinding = program->getUniformBlockBinding(uniformBlockIndex);
-            const OffsetBindingPointer<Buffer> &uniformBuffer =
-                state.getIndexedUniformBuffer(blockBinding);
-
-            if (uniformBuffer.get() == nullptr)
-            {
-                // undefined behaviour
-                context->handleError(
-                    InvalidOperation()
-                    << "It is undefined behaviour to have a used but unbound uniform buffer.");
-                return false;
-            }
-
-            size_t uniformBufferSize = GetBoundBufferAvailableSize(uniformBuffer);
-            if (uniformBufferSize < uniformBlock.dataSize)
-            {
-                // undefined behaviour
-                context->handleError(
-                    InvalidOperation()
-                    << "It is undefined behaviour to use a uniform buffer that is too small.");
-                return false;
-            }
-
-            if (extensions.webglCompatibility &&
-                uniformBuffer->isBoundForTransformFeedbackAndOtherUse())
+            const VertexArray *vao = context->getGLState().getVertexArray();
+            if (vao->hasTransformFeedbackBindingConflict(context))
             {
                 ANGLE_VALIDATION_ERR(context, InvalidOperation(),
-                                     UniformBufferBoundForTransformFeedback);
+                                     VertexBufferBoundForTransformFeedback);
                 return false;
             }
         }
-
-        // Do some additonal WebGL-specific validation
-        if (extensions.webglCompatibility)
-        {
-            const TransformFeedback *transformFeedbackObject = state.getCurrentTransformFeedback();
-            if (transformFeedbackObject != nullptr && transformFeedbackObject->isActive() &&
-                transformFeedbackObject->buffersBoundForOtherUse())
-            {
-                ANGLE_VALIDATION_ERR(context, InvalidOperation(),
-                                     TransformFeedbackBufferDoubleBound);
-                return false;
-            }
-            // Detect rendering feedback loops for WebGL.
-            if (framebuffer->formsRenderingFeedbackLoopWith(state))
-            {
-                ANGLE_VALIDATION_ERR(context, InvalidOperation(), FeedbackLoop);
-                return false;
-            }
-
-            // Detect that the vertex shader input types match the attribute types
-            if (!ValidateVertexShaderAttributeTypeMatch(context))
-            {
-                return false;
-            }
-
-            // Detect that the color buffer types match the fragment shader output types
-            if (!ValidateFragmentShaderColorBufferTypeMatch(context))
-            {
-                return false;
-            }
-
-            if (count > 0)
-            {
-                const VertexArray *vao = context->getGLState().getVertexArray();
-                if (vao->hasTransformFeedbackBindingConflict(context))
-                {
-                    ANGLE_VALIDATION_ERR(context, InvalidOperation(),
-                                         VertexBufferBoundForTransformFeedback);
-                    return false;
-                }
-            }
-        }
     }
 
     return true;