Changes the command buffer code to check that all
enabled vertex attribs are valid, not just the ones
the current program is using.

TEST=unit tests
BUG=none

Review URL: http://codereview.chromium.org/2021007

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@47040 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder.cc b/gpu/command_buffer/service/gles2_cmd_decoder.cc
index 74b0385..5fa47fc61 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder.cc
@@ -7,9 +7,10 @@
 #include <stdio.h>
 
 #include <algorithm>
-#include <vector>
-#include <string>
+#include <list>
 #include <map>
+#include <string>
+#include <vector>
 
 #include "app/gfx/gl/gl_context.h"
 #include "base/callback.h"
@@ -292,32 +293,27 @@
 GLES2Decoder::~GLES2Decoder() {
 }
 
-// This class implements GLES2Decoder so we don't have to expose all the GLES2
-// cmd stuff to outside this class.
-class GLES2DecoderImpl : public base::SupportsWeakPtr<GLES2DecoderImpl>,
-                         public GLES2Decoder {
+class VertexAttribManager {
  public:
-  explicit GLES2DecoderImpl(ContextGroup* group);
-
   // Info about Vertex Attributes. This is used to track what the user currently
   // has bound on each Vertex Attribute so that checking can be done at
   // glDrawXXX time.
   class VertexAttribInfo {
    public:
+    typedef std::list<VertexAttribInfo*> VertexAttribInfoList;
+
     VertexAttribInfo()
-        : enabled_(false),
-          size_(0),
-          type_(0),
+        : index_(0),
+          enabled_(false),
+          size_(4),
+          type_(GL_FLOAT),
           offset_(0),
-          real_stride_(0) {
+          real_stride_(16),
+          list_(NULL) {
     }
 
     // Returns true if this VertexAttrib can access index.
-    bool CanAccess(GLuint index);
-
-    void set_enabled(bool enabled) {
-      enabled_ = enabled;
-    }
+    bool CanAccess(GLuint index) const;
 
     BufferManager::BufferInfo* buffer() const {
       return buffer_;
@@ -327,6 +323,10 @@
       return offset_;
     }
 
+    GLuint index() const {
+      return index_;
+    }
+
     void SetInfo(
         BufferManager::BufferInfo* buffer,
         GLint size,
@@ -345,7 +345,35 @@
       buffer_ = NULL;
     }
 
+    bool enabled() const {
+      return enabled_;
+    }
+
    private:
+    friend class VertexAttribManager;
+
+    void set_enabled(bool enabled) {
+      enabled_ = enabled;
+    }
+
+    void set_index(GLuint index) {
+      index_ = index;
+    }
+
+    void SetList(VertexAttribInfoList* new_list) {
+      DCHECK(new_list);
+
+      if (list_) {
+        list_->erase(it_);
+      }
+
+      it_ = new_list->insert(new_list->end(), this);
+      list_ = new_list;
+    }
+
+    // The index of this attrib.
+    GLuint index_;
+
     // Whether or not this attribute is enabled.
     bool enabled_;
 
@@ -365,8 +393,97 @@
 
     // The buffer bound to this attribute.
     BufferManager::BufferInfo::Ref buffer_;
+
+    // List this info is on.
+    VertexAttribInfoList* list_;
+
+    // Iterator for list this info is on. Enabled/Disabled
+    VertexAttribInfoList::iterator it_;
   };
 
+  typedef std::list<VertexAttribInfo*> VertexAttribInfoList;
+
+  VertexAttribManager()
+      : max_vertex_attribs_(0) {
+  }
+
+  void Initialize(uint32 num_vertex_attribs);
+
+  bool Enable(GLuint index, bool enable);
+
+  const VertexAttribInfoList& GetEnabledVertexAttribInfos() const {
+    return enabled_vertex_attribs_;
+  }
+
+  VertexAttribInfo* GetVertexAttribInfo(GLuint index) {
+    DCHECK(index < max_vertex_attribs_);
+    return &vertex_attrib_infos_[index];
+  }
+
+ private:
+  uint32 max_vertex_attribs_;
+
+  // Info for each vertex attribute saved so we can check at glDrawXXX time
+  // if it is safe to draw.
+  scoped_array<VertexAttribInfo> vertex_attrib_infos_;
+
+  // Lists for which vertex attribs are enabled, disabled.
+  VertexAttribInfoList enabled_vertex_attribs_;
+  VertexAttribInfoList disabled_vertex_attribs_;
+};
+
+bool VertexAttribManager::VertexAttribInfo::CanAccess(GLuint index) const {
+  if (!enabled_) {
+    return true;
+  }
+
+  if (!buffer_ || buffer_->IsDeleted()) {
+    return false;
+  }
+
+  // The number of elements that can be accessed.
+  GLsizeiptr buffer_size = buffer_->size();
+  if (offset_ > buffer_size || real_stride_ == 0) {
+    return false;
+  }
+
+  uint32 usable_size = buffer_size - offset_;
+  GLuint num_elements = usable_size / real_stride_ +
+      ((usable_size % real_stride_) >=
+       (GLES2Util::GetGLTypeSizeForTexturesAndBuffers(type_) * size_) ? 1 : 0);
+  return index < num_elements;
+}
+
+
+void VertexAttribManager::Initialize(uint32 max_vertex_attribs) {
+  max_vertex_attribs_ = max_vertex_attribs;
+  vertex_attrib_infos_.reset(
+      new VertexAttribInfo[max_vertex_attribs]);
+  for (uint32 vv = 0; vv < max_vertex_attribs; ++vv) {
+    vertex_attrib_infos_[vv].set_index(vv);
+    vertex_attrib_infos_[vv].SetList(&disabled_vertex_attribs_);
+  }
+}
+
+bool VertexAttribManager::Enable(GLuint index, bool enable) {
+  if (index >= max_vertex_attribs_) {
+    return false;
+  }
+  VertexAttribInfo& info = vertex_attrib_infos_[index];
+  if (info.enabled() != enable) {
+    info.set_enabled(enable);
+    info.SetList(enable ? &enabled_vertex_attribs_ : &disabled_vertex_attribs_);
+  }
+  return true;
+}
+
+// This class implements GLES2Decoder so we don't have to expose all the GLES2
+// cmd stuff to outside this class.
+class GLES2DecoderImpl : public base::SupportsWeakPtr<GLES2DecoderImpl>,
+                         public GLES2Decoder {
+ public:
+  explicit GLES2DecoderImpl(ContextGroup* group);
+
   // Overridden from AsyncAPIInterface.
   virtual Error DoCommand(unsigned int command,
                           unsigned int arg_count,
@@ -899,9 +1016,8 @@
   // to call glDrawElements.
   BufferManager::BufferInfo::Ref bound_element_array_buffer_;
 
-  // Info for each vertex attribute saved so we can check at glDrawXXX time
-  // if it is safe to draw.
-  scoped_array<VertexAttribInfo> vertex_attrib_infos_;
+  // Class that manages vertex attribs.
+  VertexAttribManager vertex_attrib_manager_;
 
   // Current active texture by 0 - n index.
   // In other words, if we call glActiveTexture(GL_TEXTURE2) this value would
@@ -1240,8 +1356,8 @@
     return false;
   }
 
-  vertex_attrib_infos_.reset(
-      new VertexAttribInfo[group_->max_vertex_attribs()]);
+  vertex_attrib_manager_.Initialize(group_->max_vertex_attribs());
+
   texture_units_.reset(
       new TextureUnit[group_->max_texture_units()]);
   for (uint32 tt = 0; tt < group_->max_texture_units(); ++tt) {
@@ -1708,7 +1824,7 @@
   error::Error result = error::kNoError;
   if (debug()) {
     // TODO(gman): Change output to something useful for NaCl.
-    printf("cmd: %s\n", GetCommandName(command));
+    DLOG(INFO) << "cmd: " << GetCommandName(command);
   }
   unsigned int command_index = command - kStartPoint - 1;
   if (command_index < arraysize(g_command_info)) {
@@ -1734,7 +1850,8 @@
         while ((error = glGetError()) != GL_NO_ERROR) {
           // TODO(gman): Change output to something useful for NaCl.
           SetGLError(error, NULL);
-          printf("GL ERROR b4: %s\n", GetCommandName(command));
+          DLOG(INFO) << "GL ERROR: " << error
+                     << " : " << GetCommandName(command);
         }
       }
     } else {
@@ -1917,8 +2034,7 @@
 }
 
 void GLES2DecoderImpl::DoDisableVertexAttribArray(GLuint index) {
-  if (index < group_->max_vertex_attribs()) {
-    vertex_attrib_infos_[index].set_enabled(false);
+  if (vertex_attrib_manager_.Enable(index, false)) {
     glDisableVertexAttribArray(index);
   } else {
     SetGLError(GL_INVALID_VALUE,
@@ -1927,8 +2043,7 @@
 }
 
 void GLES2DecoderImpl::DoEnableVertexAttribArray(GLuint index) {
-  if (index < group_->max_vertex_attribs()) {
-    vertex_attrib_infos_[index].set_enabled(true);
+  if (vertex_attrib_manager_.Enable(index, true)) {
     glEnableVertexAttribArray(index);
   } else {
     SetGLError(GL_INVALID_VALUE,
@@ -2590,28 +2705,6 @@
   }
 }
 
-bool GLES2DecoderImpl::VertexAttribInfo::CanAccess(GLuint index) {
-  if (!enabled_) {
-    return true;
-  }
-
-  if (!buffer_ || buffer_->IsDeleted()) {
-    return false;
-  }
-
-  // The number of elements that can be accessed.
-  GLsizeiptr buffer_size = buffer_->size();
-  if (offset_ > buffer_size || real_stride_ == 0) {
-    return false;
-  }
-
-  uint32 usable_size = buffer_size - offset_;
-  GLuint num_elements = usable_size / real_stride_ +
-      ((usable_size % real_stride_) >=
-       (GLES2Util::GetGLTypeSizeForTexturesAndBuffers(type_) * size_) ? 1 : 0);
-  return index < num_elements;
-}
-
 void GLES2DecoderImpl::SetBlackTextureForNonRenderableTextures(
     bool* has_non_renderable_textures) {
   DCHECK(has_non_renderable_textures);
@@ -2686,19 +2779,33 @@
     // But GL says no ERROR.
     return false;
   }
-  // Validate that all attribs current program needs are setup correctly.
-  const ProgramManager::ProgramInfo::AttribInfoVector& infos =
-      current_program_->GetAttribInfos();
-  for (size_t ii = 0; ii < infos.size(); ++ii) {
-    GLint location = infos[ii].location;
-    if (location < 0) {
-      return false;
-    }
-    DCHECK_LT(static_cast<GLuint>(location), group_->max_vertex_attribs());
-    if (!vertex_attrib_infos_[location].CanAccess(max_vertex_accessed)) {
-      SetGLError(GL_INVALID_OPERATION,
-                 "glDrawXXX: attempt to access out of range vertices");
-      return false;
+  // Validate all attribs currently enabled. If they are used by the current
+  // program then check that they have enough elements to handle the draw call.
+  // If they are not used by the current program check that they have a buffer
+  // assigned.
+  const VertexAttribManager::VertexAttribInfoList& infos =
+      vertex_attrib_manager_.GetEnabledVertexAttribInfos();
+  for (VertexAttribManager::VertexAttribInfoList::const_iterator it =
+           infos.begin(); it != infos.end(); ++it) {
+    const VertexAttribManager::VertexAttribInfo* info = *it;
+    const ProgramManager::ProgramInfo::VertexAttribInfo* attrib_info =
+        current_program_->GetAttribInfoByLocation(info->index());
+    if (attrib_info) {
+      // This attrib is used in the current program.
+      if (!info->CanAccess(max_vertex_accessed)) {
+        SetGLError(GL_INVALID_OPERATION,
+                   "glDrawXXX: attempt to access out of range vertices");
+        return false;
+      }
+    } else {
+      // This attrib is not used in the current program.
+      if (!info->buffer() || info->buffer()->IsDeleted()) {
+        SetGLError(
+            GL_INVALID_OPERATION,
+            "glDrawXXX: attempt to render with no buffer attached to enabled "
+            "attrib");
+        return false;
+      }
     }
   }
   return true;
@@ -3053,7 +3160,7 @@
                "glVertexAttribPointer: stride not valid for type");
     return error::kNoError;
   }
-  vertex_attrib_infos_[indx].SetInfo(
+  vertex_attrib_manager_.GetVertexAttribInfo(indx)->SetInfo(
       bound_array_buffer_,
       size,
       type,
@@ -3660,7 +3767,8 @@
     return error::kNoError;
   }
   result->SetNumResults(1);
-  *result->GetData() = vertex_attrib_infos_[index].offset();
+  *result->GetData() =
+      vertex_attrib_manager_.GetVertexAttribInfo(index)->offset();
   return error::kNoError;
 }
 
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_unittest.cc b/gpu/command_buffer/service/gles2_cmd_decoder_unittest.cc
index 759e54c..7a5b7085 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder_unittest.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_unittest.cc
@@ -225,6 +225,19 @@
   EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());
 }
 
+TEST_F(GLES2DecoderWithShaderTest, DrawElementsExtraAttributesFails) {
+  SetupIndexBuffer();
+  DoEnableVertexAttribArray(6);
+
+  EXPECT_CALL(*gl_, DrawElements(_, _, _, _))
+      .Times(0);
+  DrawElements cmd;
+  cmd.Init(GL_TRIANGLES, kValidIndexRangeCount, GL_UNSIGNED_SHORT,
+           kValidIndexRangeStart * 2);
+  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
+  EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());
+}
+
 TEST_F(GLES2DecoderWithShaderTest, DrawElementsValidAttributesSucceeds) {
   SetupTexture();
   SetupVertexBuffer();
diff --git a/gpu/command_buffer/service/program_manager.cc b/gpu/command_buffer/service/program_manager.cc
index 5b86014a..77b0c96 100644
--- a/gpu/command_buffer/service/program_manager.cc
+++ b/gpu/command_buffer/service/program_manager.cc
@@ -25,13 +25,15 @@
   attrib_infos_.clear();
   uniform_infos_.clear();
   sampler_indices_.clear();
-  location_to_index_map_.clear();
+  attrib_location_to_index_map_.clear();
+  uniform_location_to_index_map_.clear();
 }
 
 void ProgramManager::ProgramInfo::Update() {
   Reset();
   GLint num_attribs = 0;
   GLint max_len = 0;
+  GLint max_location = -1;
   glGetProgramiv(service_id_, GL_ACTIVE_ATTRIBUTES, &num_attribs);
   glGetProgramiv(service_id_, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, &max_len);
   // TODO(gman): Should we check for error?
@@ -45,18 +47,31 @@
     if (!IsInvalidPrefix(name_buffer.get(), length)) {
       // TODO(gman): Should we check for error?
       GLint location = glGetAttribLocation(service_id_, name_buffer.get());
+      if (location > max_location) {
+        max_location = location;
+      }
       attrib_infos_.push_back(
           VertexAttribInfo(size, type, name_buffer.get(), location));
       max_attrib_name_length_ = std::max(max_attrib_name_length_, length);
     }
   }
 
+  // Create attrib location to index map.
+  attrib_location_to_index_map_.resize(max_location + 1);
+  for (GLint ii = 0; ii <= max_location; ++ii) {
+    attrib_location_to_index_map_[ii] = -1;
+  }
+  for (size_t ii = 0; ii < attrib_infos_.size(); ++ii) {
+    const VertexAttribInfo& info = attrib_infos_[ii];
+    attrib_location_to_index_map_[info.location] = ii;
+  }
+
   GLint num_uniforms = 0;
   max_len = 0;
   glGetProgramiv(service_id_, GL_ACTIVE_UNIFORMS, &num_uniforms);
   glGetProgramiv(service_id_, GL_ACTIVE_UNIFORM_MAX_LENGTH, &max_len);
   name_buffer.reset(new char[max_len]);
-  GLint max_location = -1;
+  max_location = -1;
   int index = 0;  // this index tracks valid uniforms.
   for (GLint ii = 0; ii < num_uniforms; ++ii) {
     GLsizei length;
@@ -83,15 +98,15 @@
       ++index;
     }
   }
-  // Create location to index map.
-  location_to_index_map_.resize(max_location + 1);
+  // Create uniform location to index map.
+  uniform_location_to_index_map_.resize(max_location + 1);
   for (GLint ii = 0; ii <= max_location; ++ii) {
-    location_to_index_map_[ii] = -1;
+    uniform_location_to_index_map_[ii] = -1;
   }
   for (size_t ii = 0; ii < uniform_infos_.size(); ++ii) {
     const UniformInfo& info = uniform_infos_[ii];
     for (size_t jj = 0; jj < info.element_locations.size(); ++jj) {
-      location_to_index_map_[info.element_locations[jj]] = ii;
+      uniform_location_to_index_map_[info.element_locations[jj]] = ii;
     }
   }
   valid_ = true;
@@ -147,8 +162,8 @@
 bool ProgramManager::ProgramInfo::GetUniformTypeByLocation(
     GLint location, GLenum* type) const {
   if (location >= 0 &&
-      static_cast<size_t>(location) < location_to_index_map_.size()) {
-    GLint index = location_to_index_map_[location];
+      static_cast<size_t>(location) < uniform_location_to_index_map_.size()) {
+    GLint index = uniform_location_to_index_map_[location];
     if (index >= 0) {
       *type = uniform_infos_[index].type;
       return true;
@@ -202,8 +217,8 @@
 bool ProgramManager::ProgramInfo::SetSamplers(
     GLint location, GLsizei count, const GLint* value) {
   if (location >= 0 &&
-      static_cast<size_t>(location) < location_to_index_map_.size()) {
-    GLint index = location_to_index_map_[location];
+      static_cast<size_t>(location) < uniform_location_to_index_map_.size()) {
+    GLint index = uniform_location_to_index_map_[location];
     if (index >= 0) {
       UniformInfo& info = uniform_infos_[index];
       if (info.IsSampler() && count <= info.size) {
diff --git a/gpu/command_buffer/service/program_manager.h b/gpu/command_buffer/service/program_manager.h
index feb263c..71042428 100644
--- a/gpu/command_buffer/service/program_manager.h
+++ b/gpu/command_buffer/service/program_manager.h
@@ -96,6 +96,16 @@
 
     GLint GetAttribLocation(const std::string& name) const;
 
+    const VertexAttribInfo* GetAttribInfoByLocation(GLuint location) const {
+      if (location < attrib_location_to_index_map_.size()) {
+        GLint index = attrib_location_to_index_map_[location];
+        if (index >= 0) {
+          return &attrib_infos_[index];
+        }
+      }
+      return NULL;
+    }
+
     const UniformInfo* GetUniformInfo(GLint index) const {
       return (static_cast<size_t>(index) < uniform_infos_.size()) ?
          &uniform_infos_[index] : NULL;
@@ -137,15 +147,19 @@
 
     GLsizei max_attrib_name_length_;
 
+    // Attrib by index.
     AttribInfoVector attrib_infos_;
 
+    // Attrib by location to index.
+    std::vector<GLint> attrib_location_to_index_map_;
+
     GLsizei max_uniform_name_length_;
 
     // Uniform info by index.
     UniformInfoVector uniform_infos_;
 
     // Uniform location to index.
-    std::vector<GLint> location_to_index_map_;
+    std::vector<GLint> uniform_location_to_index_map_;
 
     // The indices of the uniforms that are samplers.
     SamplerIndices sampler_indices_;