Browse Source

Reduce redundant state changes, add gl-fixed-vertex-attrib-locations

rdb 10 years ago
parent
commit
08c29313b2

+ 23 - 8
panda/src/cull/cullBinStateSorted.I

@@ -33,7 +33,8 @@ CullBinStateSorted(const string &name, GraphicsStateGuardianBase *gsg,
 ////////////////////////////////////////////////////////////////////
 INLINE CullBinStateSorted::ObjectData::
 ObjectData(CullableObject *object) :
-  _object(object)
+  _object(object),
+  _format(object->_munged_data->get_format())
 {
 }
 
@@ -45,16 +46,30 @@ ObjectData(CullableObject *object) :
 ////////////////////////////////////////////////////////////////////
 INLINE bool CullBinStateSorted::ObjectData::
 operator < (const ObjectData &other) const {
-  // First group objects by transform, since transform changes are
-  // supposed to be expensive.
+  // Group by state changes, in approximate order from heaviest
+  // change to lightest change.
+  const RenderState *sa = _object->_state;
+  const RenderState *sb = other._object->_state;
+  int compare = sa->compare_sort(*sb);
+  if (compare != 0) {
+    return compare < 0;
+  }
+
+  // Vertex format changes are also fairly slow.
+  if (_format != other._format) {
+    return _format < other._format;
+  }
+
+  // Prevent unnecessary vertex buffer rebinds.
+  if (_object->_munged_data != other._object->_munged_data) {
+    return _object->_munged_data < other._object->_munged_data;
+  }
+
+  // Uniform updates are actually pretty fast.
   if (_object->_internal_transform != other._object->_internal_transform) {
     return _object->_internal_transform < other._object->_internal_transform;
   }
 
-  // Then group by other state changes, in approximate order from
-  // heaviest change to lightest change.
-  const RenderState *sa = _object->_state;
-  const RenderState *sb = other._object->_state;
-  return sa->compare_sort(*sb) < 0;
+  return 0;
 }
 

+ 1 - 0
panda/src/cull/cullBinStateSorted.h

@@ -61,6 +61,7 @@ private:
     INLINE bool operator < (const ObjectData &other) const;
     
     CullableObject *_object;
+    const GeomVertexFormat *_format;
   };
 
   typedef pvector<ObjectData> Objects;

+ 3 - 3
panda/src/glstuff/glCgShaderContext_src.cxx

@@ -1036,7 +1036,7 @@ disable_shader_texture_bindings() {
     if (p == 0) continue;
 
     int texunit = cgGetParameterResourceIndex(p);
-    _glgsg->_glActiveTexture(GL_TEXTURE0 + texunit);
+    _glgsg->set_active_texture_stage(texunit);
 
     glBindTexture(GL_TEXTURE_1D, 0);
     glBindTexture(GL_TEXTURE_2D, 0);
@@ -1099,7 +1099,7 @@ update_shader_texture_bindings(ShaderContext *prev) {
     if (tex.is_null()) {
       // Apply a white texture in order to make it easier to use a shader
       // that takes a texture on a model that doesn't have a texture applied.
-      _glgsg->_glActiveTexture(GL_TEXTURE0 + i);
+      _glgsg->set_active_texture_stage(i);
       _glgsg->apply_white_texture();
       continue;
     }
@@ -1115,7 +1115,7 @@ update_shader_texture_bindings(ShaderContext *prev) {
       continue;
     }
 
-    _glgsg->_glActiveTexture(GL_TEXTURE0 + texunit);
+    _glgsg->set_active_texture_stage(texunit);
 
     TextureContext *tc = tex->prepare_now(view, _glgsg->_prepared_objects, _glgsg);
     if (tc == (TextureContext*)NULL) {

+ 17 - 0
panda/src/glstuff/glGraphicsStateGuardian_src.I

@@ -288,6 +288,23 @@ set_vertex_attrib_divisor(GLuint index, GLuint divisor) {
 
 #endif
 
+////////////////////////////////////////////////////////////////////
+//     Function: GLGraphicsStateGuardian::set_active_texture_stage
+//       Access: Protected
+//  Description: Calls glActiveTexture.
+////////////////////////////////////////////////////////////////////
+INLINE void CLP(GraphicsStateGuardian)::
+set_active_texture_stage(int i) {
+  if (i != _active_texture_stage) {
+#ifdef OPENGLES_2
+    glActiveTexture(GL_TEXTURE0 + i);
+#else
+    _glActiveTexture(GL_TEXTURE0 + i);
+#endif
+    _active_texture_stage = i;
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: GLGraphicsStateGuardian::enable_multisample_antialias
 //       Access: Protected

+ 175 - 57
panda/src/glstuff/glGraphicsStateGuardian_src.cxx

@@ -1086,9 +1086,7 @@ reset() {
     has_extension("GL_ARB_texture_non_power_of_two");
 #endif
 
-#ifdef OPENGLES_2
-  _glActiveTexture = glActiveTexture;
-#else
+#ifndef OPENGLES_2
   bool supports_multitexture = false;
   if (is_at_least_gl_version(1, 3) || is_at_least_gles_version(1, 1)) {
     supports_multitexture = true;
@@ -1641,6 +1639,28 @@ reset() {
   _glVertexAttribLPointer = NULL;
 #endif
 
+#ifndef OPENGLES
+  _use_vertex_attrib_binding = false;
+  if (is_at_least_gl_version(4, 3) || has_extension("GL_ARB_vertex_attrib_binding")) {
+    _glBindVertexBuffer = (PFNGLBINDVERTEXBUFFERPROC)
+      get_extension_func("glBindVertexBuffer");
+    _glVertexAttribFormat = (PFNGLVERTEXATTRIBFORMATPROC)
+      get_extension_func("glVertexAttribFormat");
+    _glVertexAttribIFormat = (PFNGLVERTEXATTRIBIFORMATPROC)
+      get_extension_func("glVertexAttribIFormat");
+    _glVertexAttribLFormat = (PFNGLVERTEXATTRIBLFORMATPROC)
+      get_extension_func("glVertexAttribLFormat");
+    _glVertexAttribBinding = (PFNGLVERTEXATTRIBBINDINGPROC)
+      get_extension_func("glVertexAttribBinding");
+    _glVertexBindingDivisor = (PFNGLVERTEXBINDINGDIVISORPROC)
+      get_extension_func("glVertexBindingDivisor");
+
+    if (gl_fixed_vertex_attrib_locations) {
+      _use_vertex_attrib_binding = true;
+    }
+  }
+#endif
+
   // We need to have a default shader to apply in case
   // something didn't happen to have a shader applied, or
   // if it failed to compile. This default shader just outputs
@@ -2344,6 +2364,7 @@ reset() {
     }
   }
 
+  _active_texture_stage = -1;
   _num_active_texture_stages = 0;
 
   // Check availability of anisotropic texture filtering.
@@ -2407,18 +2428,22 @@ reset() {
 
   // Check availability of multi-bind functions.
   _supports_multi_bind = false;
+#ifndef OPENGLES
   if (is_at_least_gl_version(4, 4) || has_extension("GL_ARB_multi_bind")) {
     _glBindTextures = (PFNGLBINDTEXTURESPROC)
       get_extension_func("glBindTextures");
     _glBindImageTextures = (PFNGLBINDIMAGETEXTURESPROC)
       get_extension_func("glBindImageTextures");
 
-#ifndef OPENGLES
     if (_supports_sampler_objects) {
       _glBindSamplers = (PFNGLBINDSAMPLERSPROC)
         get_extension_func("glBindSamplers");
     }
-#endif  // OPENGLES
+
+    if (_use_vertex_attrib_binding) {
+      _glBindVertexBuffers = (PFNGLBINDVERTEXBUFFERSPROC)
+        get_extension_func("glBindVertexBuffers");
+    }
 
     if (_glBindTextures != NULL && _glBindImageTextures != NULL) {
       _supports_multi_bind = true;
@@ -2427,6 +2452,7 @@ reset() {
         << "ARB_multi_bind advertised as supported by OpenGL runtime, but could not get pointers to extension function.\n";
     }
   }
+#endif  // OPENGLES
 
   if (is_at_least_gl_version(4, 3) || has_extension("GL_ARB_internalformat_query2")) {
     _glGetInternalformativ = (PFNGLGETINTERNALFORMATIVPROC)
@@ -2616,6 +2642,12 @@ reset() {
   _point_size = 1.0f;
   _point_perspective = false;
 
+#ifndef OPENGLES
+  _current_vertex_buffers.clear();
+  _current_vertex_format.clear();
+  memset(_vertex_attrib_columns, 0, sizeof(const GeomVertexColumn *) * 32);
+#endif
+
   report_my_gl_errors();
 
 #ifdef SUPPORT_FIXED_FUNCTION
@@ -3108,7 +3140,7 @@ clear_before_callback() {
   // Some callbacks may quite reasonably assume that the active
   // texture stage is still set to stage 0.  CEGUI, in particular,
   // makes this assumption.
-  _glActiveTexture(GL_TEXTURE0);
+  set_active_texture_stage(0);
 #ifdef SUPPORT_FIXED_FUNCTION
   _glClientActiveTexture(GL_TEXTURE0);
 #endif
@@ -3375,6 +3407,9 @@ end_frame(Thread *current_thread) {
   }
 #endif
 
+  // Respecify the active texture next frame, for good measure.
+  _active_texture_stage = -1;
+
   // Calling glFlush() at the end of the frame is particularly
   // necessary if this is a single-buffered visual, so that the frame
   // will be finished drawing before we return to the application.
@@ -3636,6 +3671,15 @@ begin_draw_primitives(const GeomPipelineReader *geom_reader,
   _use_sender = !vertex_arrays;
 #endif
 
+#ifndef OPENGLES
+  if (_use_vertex_attrib_binding) {
+    const GeomVertexFormat *format = data_reader->get_format();
+    if (format != _current_vertex_format) {
+      update_shader_vertex_format(format);
+    }
+  }
+#endif
+
   {
     //PStatGPUTimer timer(this, _vertex_array_update_pcollector);
 #ifdef OPENGLES_1
@@ -3908,42 +3952,97 @@ unbind_buffers() {
     _current_ibuffer_index = 0;
   }
 
+#ifndef OPENGLES
+  if (_current_vertex_buffers.size() > 1 && _supports_multi_bind) {
+    _glBindVertexBuffers(0, _current_vertex_buffers.size(), NULL, NULL, NULL);
+  } else {
+    for (int i = 0; i < _current_vertex_buffers.size(); ++i) {
+      if (_current_vertex_buffers[i] != 0) {
+        _glBindVertexBuffer(i, 0, 0, 0);
+      }
+    }
+  }
+  _current_vertex_buffers.clear();
+#endif
+
 #ifdef SUPPORT_FIXED_FUNCTION
   disable_standard_vertex_arrays();
 #endif
 }
 
-#ifdef SUPPORT_FIXED_FUNCTION
+#ifndef OPENGLES
 ////////////////////////////////////////////////////////////////////
-//     Function: GLGraphicsStateGuardian::disable_standard_vertex_arrays
+//     Function: GLGraphicsStateGuardian::update_shader_vertex_format
 //       Access: Protected
-//  Description: Used to disable all the standard vertex arrays that
-//               are currently enabled.  glShaderContexts are
-//               responsible for setting up their own vertex arrays,
-//               but before they can do so, the standard vertex
-//               arrays need to be disabled to get them "out of the
-//               way."  Called only from begin_draw_primitives.
+//  Description: Updates the vertex format used by the shader.  This
+//               is still an experimental feature.
 ////////////////////////////////////////////////////////////////////
 void CLP(GraphicsStateGuardian)::
-disable_standard_vertex_arrays() {
-#ifdef SUPPORT_IMMEDIATE_MODE
-  if (_use_sender) return;
-#endif
+update_shader_vertex_format(const GeomVertexFormat *format) {
+  size_t num_columns = format->get_num_columns();
+  for (size_t ci = 0; ci < num_columns; ++ci) {
+    GLuint binding = format->get_array_with(ci);
+    const GeomVertexColumn *column = format->get_column(ci);
+
+    // Needs improvement, obviously.
+    const InternalName *name = column->get_name();
+    GLuint loc;
+    if (name == InternalName::get_vertex()) {
+      loc = 0;
+    } else if (name == InternalName::get_transform_weight()) {
+      loc = 1;
+    } else if (name == InternalName::get_normal()) {
+      loc = 2;
+    } else if (name == InternalName::get_color()) {
+      loc = 3;
+    } else if (name == InternalName::get_transform_index()) {
+      loc = 7;
+    } else if (name == InternalName::get_texcoord()) {
+      loc = 8;
+    } else {
+      // Not yet supported, ignore for now.  This system will be improved.
+      continue;
+    }
+
+    if (_vertex_attrib_columns[loc] != NULL &&
+        _vertex_attrib_columns[loc]->compare_to(*column) == 0) {
+      continue;
+    }
+    _vertex_attrib_columns[loc] = column;
 
-  glDisableClientState(GL_NORMAL_ARRAY);
-  glDisableClientState(GL_COLOR_ARRAY);
-  GLPf(Color4)(1.0f, 1.0f, 1.0f, 1.0f);
+    GLuint offset = column->get_start();
+    GLenum type = get_numeric_type(column->get_numeric_type());
+    GLboolean normalized = (column->get_contents() == GeomEnums::C_color);
+    GLint size = column->get_num_values();
 
-  for (int stage_index=0; stage_index < _last_max_stage_index; stage_index++) {
-    _glClientActiveTexture(GL_TEXTURE0 + stage_index);
-    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
+    if (column->get_numeric_type() == GeomEnums::NT_packed_dabc) {
+      // GL_BGRA is a special accepted value available since OpenGL 3.2.
+      // It requires us to pass GL_TRUE for normalized.
+      size = GL_BGRA;
+      normalized = GL_TRUE;
+    }
+
+    for (int i = 0; i < column->get_num_elements(); ++i) {
+      if (loc == 7) { // Temp hack
+        _glVertexAttribIFormat(loc, size, type, offset);
+      } else {
+        _glVertexAttribFormat(loc, size, type, normalized, offset);
+      }
+      _glVertexAttribBinding(loc, binding);
+
+      offset += column->get_element_stride();
+      ++loc;
+    }
   }
-  _last_max_stage_index = 0;
 
-  glDisableClientState(GL_VERTEX_ARRAY);
-  report_my_gl_errors();
+  size_t num_arrays = format->get_num_arrays();
+  for (size_t ai = 0; ai < num_arrays; ++ai) {
+    _glVertexBindingDivisor(ai, format->get_array(ai)->get_divisor());
+  }
+
+  _current_vertex_format = format;
 }
-#endif  // SUPPORT_FIXED_FUNCTION
+#endif
 
 ////////////////////////////////////////////////////////////////////
 //     Function: GLGraphicsStateGuardian::draw_triangles
@@ -5112,7 +5211,7 @@ prepare_vertex_buffer(GeomVertexArrayData *data) {
     }
 
     report_my_gl_errors();
-    apply_vertex_buffer(gvbc, data->get_handle(), false);
+    update_vertex_buffer(gvbc, data->get_handle(), false);
     return gvbc;
   }
 
@@ -5120,31 +5219,22 @@ prepare_vertex_buffer(GeomVertexArrayData *data) {
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: GLGraphicsStateGuardian::apply_vertex_buffer
+//     Function: GLGraphicsStateGuardian::update_vertex_buffer
 //       Access: Public
-//  Description: Makes the data the currently available data for
-//               rendering.
+//  Description: Makes sure that the data in the vertex buffer is
+//               up-to-date.  This may bind it to the GL_ARRAY_BUFFER
+//               binding point if necessary.
 ////////////////////////////////////////////////////////////////////
 bool CLP(GraphicsStateGuardian)::
-apply_vertex_buffer(VertexBufferContext *vbc,
-                    const GeomVertexArrayDataHandle *reader, bool force) {
+update_vertex_buffer(CLP(VertexBufferContext) *gvbc,
+                     const GeomVertexArrayDataHandle *reader, bool force) {
   nassertr(_supports_buffers, false);
   if (reader->get_modified() == UpdateSeq::initial()) {
     // No need to re-apply.
     return true;
   }
 
-  CLP(VertexBufferContext) *gvbc = DCAST(CLP(VertexBufferContext), vbc);
-
-  if (_current_vbuffer_index != gvbc->_index) {
-    if (GLCAT.is_spam() && gl_debug_buffers) {
-      GLCAT.spam()
-        << "binding vertex buffer " << (int)gvbc->_index << "\n";
-    }
-    _glBindBuffer(GL_ARRAY_BUFFER, gvbc->_index);
-    _current_vbuffer_index = gvbc->_index;
-    gvbc->set_active(true);
-  }
+  gvbc->set_active(true);
 
   if (gvbc->was_modified(reader)) {
     int num_bytes = reader->get_data_size_bytes();
@@ -5160,6 +5250,15 @@ apply_vertex_buffer(VertexBufferContext *vbc,
       }
 
       PStatGPUTimer timer(this, _load_vertex_buffer_pcollector, reader->get_current_thread());
+      if (_current_vbuffer_index != gvbc->_index) {
+        if (GLCAT.is_spam() && gl_debug_buffers) {
+          GLCAT.spam()
+            << "binding vertex buffer " << (int)gvbc->_index << "\n";
+        }
+        _glBindBuffer(GL_ARRAY_BUFFER, gvbc->_index);
+        _current_vbuffer_index = gvbc->_index;
+      }
+
       if (gvbc->changed_size(reader) || gvbc->changed_usage_hint(reader)) {
         _glBufferData(GL_ARRAY_BUFFER, num_bytes, client_pointer,
                       get_usage(reader->get_usage_hint()));
@@ -5263,12 +5362,23 @@ setup_array_data(const unsigned char *&client_pointer,
   }
 
   // Prepare the buffer object and bind it.
-  VertexBufferContext *vbc = array_reader->prepare_now(get_prepared_objects(), this);
-  nassertr(vbc != (VertexBufferContext *)NULL, false);
-  if (!apply_vertex_buffer(vbc, array_reader, force)) {
+  CLP(VertexBufferContext) *gvbc = DCAST(CLP(VertexBufferContext),
+    array_reader->prepare_now(get_prepared_objects(), this));
+
+  nassertr(gvbc != (CLP(VertexBufferContext) *)NULL, false);
+  if (!update_vertex_buffer(gvbc, array_reader, force)) {
     return false;
   }
 
+  if (_current_vbuffer_index != gvbc->_index) {
+    if (GLCAT.is_spam() && gl_debug_buffers) {
+      GLCAT.spam()
+        << "binding vertex buffer " << (int)gvbc->_index << "\n";
+    }
+    _glBindBuffer(GL_ARRAY_BUFFER, gvbc->_index);
+    _current_vbuffer_index = gvbc->_index;
+  }
+
   // NULL is the OpenGL convention for the first byte of the buffer object.
   client_pointer = NULL;
   return true;
@@ -9316,6 +9426,14 @@ void CLP(GraphicsStateGuardian)::
 reissue_transforms() {
   prepare_lens();
   do_issue_transform();
+
+  _active_texture_stage = -1;
+
+#ifndef OPENGLES
+  // Might also want to reissue the vertex format, for good measure.
+  _current_vertex_format.clear();
+  memset(_vertex_attrib_columns, 0, sizeof(const GeomVertexColumn *) * 32);
+#endif
 }
 
 #ifdef SUPPORT_FIXED_FUNCTION
@@ -9592,7 +9710,7 @@ set_state_and_transform(const RenderState *target,
     _state_mask.clear_bit(TextureAttrib::get_class_slot());
   }
 #ifndef SUPPORT_FIXED_FUNCTION
-  else { // In the case of OpenGL ES 2.x, we need to glUseShader before we draw anything.
+  else if (_current_shader == NULL) { // In the case of OpenGL ES 2.x, we need to glUseShader before we draw anything.
     do_issue_shader();
     _state_mask.clear_bit(TextureAttrib::get_class_slot());
   }
@@ -9945,7 +10063,7 @@ update_standard_texture_bindings() {
     nassertv(texture != (Texture *)NULL);
 
     // Issue the texture on stage i.
-    _glActiveTexture(GL_TEXTURE0 + i);
+    set_active_texture_stage(i);
 
     // First, turn off the previous texture mode.
     glDisable(GL_TEXTURE_2D);
@@ -10114,7 +10232,7 @@ update_standard_texture_bindings() {
 
   // Disable the texture stages that are no longer used.
   for (i = num_stages; i < _num_active_texture_stages; i++) {
-    _glActiveTexture(GL_TEXTURE0 + i);
+    set_active_texture_stage(i);
     glDisable(GL_TEXTURE_2D);
     if (_supports_cube_map) {
       glDisable(GL_TEXTURE_CUBE_MAP);
@@ -10217,7 +10335,7 @@ update_show_usage_texture_bindings(int show_stage_index) {
 #ifdef SUPPORT_FIXED_FUNCTION
   // Disable all texture stages.
   for (i = 0; i < _num_active_texture_stages; i++) {
-    _glActiveTexture(GL_TEXTURE0 + i);
+    set_active_texture_stage(i);
 #ifndef OPENGLES
     glDisable(GL_TEXTURE_1D);
 #endif  // OPENGLES
@@ -10249,7 +10367,7 @@ update_show_usage_texture_bindings(int show_stage_index) {
     nassertv(texture != (Texture *)NULL);
 
     // Choose the corresponding usage texture and apply it.
-    _glActiveTexture(GL_TEXTURE0 + i);
+    set_active_texture_stage(i);
 #ifdef SUPPORT_FIXED_FUNCTION
     glEnable(GL_TEXTURE_2D);
 #endif
@@ -10366,7 +10484,7 @@ void CLP(GraphicsStateGuardian)::
 disable_standard_texture_bindings() {
   // Disable the texture stages that are no longer used.
   for (int i = 0; i < _num_active_texture_stages; i++) {
-    _glActiveTexture(GL_TEXTURE0 + i);
+    set_active_texture_stage(i);
 #ifndef OPENGLES
     glDisable(GL_TEXTURE_1D);
 #endif  // OPENGLES
@@ -10399,7 +10517,7 @@ do_issue_tex_matrix() {
 
   for (int i = 0; i < _num_active_texture_stages; i++) {
     TextureStage *stage = _target_texture->get_on_ff_stage(i);
-    _glActiveTexture(GL_TEXTURE0 + i);
+    set_active_texture_stage(i);
 
     glMatrixMode(GL_TEXTURE);
 
@@ -10452,7 +10570,7 @@ do_issue_tex_gen() {
 
   for (int i = 0; i < _num_active_texture_stages; i++) {
     TextureStage *stage = _target_texture->get_on_ff_stage(i);
-    _glActiveTexture(GL_TEXTURE0 + i);
+    set_active_texture_stage(i);
     if (_supports_point_sprite) {
 #ifdef OPENGLES
       glTexEnvi(GL_POINT_SPRITE_OES, GL_COORD_REPLACE_OES, GL_FALSE);
@@ -10909,7 +11027,7 @@ apply_sampler(GLuint unit, const SamplerState &sampler, CLP(TextureContext) *gtc
     // We don't support sampler objects.  We'll have to bind the
     // texture and change the texture parameters if they don't match.
     if (gtc->_active_sampler != sampler) {
-      _glActiveTexture(GL_TEXTURE0 + unit);
+      set_active_texture_stage(unit);
       apply_texture(gtc);
       specify_texture(gtc, sampler);
     }

+ 26 - 3
panda/src/glstuff/glGraphicsStateGuardian_src.h

@@ -306,9 +306,9 @@ public:
   void record_deleted_display_list(GLuint index);
 
   virtual VertexBufferContext *prepare_vertex_buffer(GeomVertexArrayData *data);
-  bool apply_vertex_buffer(VertexBufferContext *vbc,
-                           const GeomVertexArrayDataHandle *reader,
-                           bool force);
+  bool update_vertex_buffer(CLP(VertexBufferContext) *gvbc,
+                            const GeomVertexArrayDataHandle *reader,
+                            bool force);
   virtual void release_vertex_buffer(VertexBufferContext *vbc);
 
   bool setup_array_data(const unsigned char *&client_pointer,
@@ -457,6 +457,8 @@ protected:
   INLINE void set_vertex_attrib_divisor(GLuint index, GLuint divisor);
 #endif
 
+  INLINE void set_active_texture_stage(int i);
+
   INLINE void enable_multisample_antialias(bool val);
   INLINE void enable_multisample_alpha_one(bool val);
   INLINE void enable_multisample_alpha_mask(bool val);
@@ -533,6 +535,9 @@ protected:
   void disable_standard_texture_bindings();
   void update_standard_texture_bindings();
 #endif
+#ifndef OPENGLES
+  void update_shader_vertex_format(const GeomVertexFormat *format);
+#endif
 
   void apply_white_texture();
   GLuint get_white_texture();
@@ -651,6 +656,15 @@ protected:
   GLuint _current_vbuffer_index;
   GLuint _current_ibuffer_index;
   GLuint _current_fbo;
+
+#ifndef OPENGLES
+  pvector<GLuint> _current_vertex_buffers;
+  bool _use_vertex_attrib_binding;
+  CPT(GeomVertexFormat) _current_vertex_format;
+  const GeomVertexColumn *_vertex_attrib_columns[32];
+#endif
+
+  int _active_texture_stage;
   int _num_active_texture_stages;
   PN_stdfloat _max_anisotropy;
   bool _supports_anisotropy;
@@ -735,7 +749,9 @@ public:
 #endif
 #endif
 
+#ifndef OPENGLES_2
   PFNGLACTIVETEXTUREPROC _glActiveTexture;
+#endif
 #ifdef SUPPORT_FIXED_FUNCTION
   PFNGLCLIENTACTIVETEXTUREPROC _glClientActiveTexture;
 #endif
@@ -897,6 +913,13 @@ public:
   PFNGLDRAWELEMENTSINSTANCEDPROC _glDrawElementsInstanced;
 #endif  // !OPENGLES_1
 #ifndef OPENGLES
+  PFNGLBINDVERTEXBUFFERPROC _glBindVertexBuffer;
+  PFNGLBINDVERTEXBUFFERSPROC _glBindVertexBuffers;
+  PFNGLVERTEXATTRIBFORMATPROC _glVertexAttribFormat;
+  PFNGLVERTEXATTRIBIFORMATPROC _glVertexAttribIFormat;
+  PFNGLVERTEXATTRIBLFORMATPROC _glVertexAttribLFormat;
+  PFNGLVERTEXATTRIBBINDINGPROC _glVertexAttribBinding;
+  PFNGLVERTEXBINDINGDIVISORPROC _glVertexBindingDivisor;
   PFNGLGETACTIVEUNIFORMSIVPROC _glGetActiveUniformsiv;
   PFNGLGETACTIVEUNIFORMBLOCKIVPROC _glGetActiveUniformBlockiv;
   PFNGLGETACTIVEUNIFORMBLOCKNAMEPROC _glGetActiveUniformBlockName;

+ 191 - 81
panda/src/glstuff/glShaderContext_src.cxx

@@ -262,6 +262,7 @@ CLP(ShaderContext)(CLP(GraphicsStateGuardian) *glgsg, Shader *s) : ShaderContext
   _glgsg = glgsg;
   _glsl_program = 0;
   _uses_standard_vertex_arrays = false;
+  _enabled_attribs.clear();
   _color_attrib_index = -1;
   _transform_table_index = -1;
   _slider_table_index = -1;
@@ -279,12 +280,30 @@ CLP(ShaderContext)(CLP(GraphicsStateGuardian) *glgsg, Shader *s) : ShaderContext
     return;
   }
 
+  // Process the vertex attributes first.
+  GLint param_count = 0;
+  GLint name_buflen = 0;
+  _glgsg->_glGetProgramiv(_glsl_program, GL_ACTIVE_ATTRIBUTES, &param_count);
+  _glgsg->_glGetProgramiv(_glsl_program, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, &name_buflen);
+  name_buflen = max(64, name_buflen);
+  char *name_buffer = (char *)alloca(name_buflen);
+
+  _shader->_var_spec.clear();
+  for (int i = 0; i < param_count; ++i) {
+    reflect_attribute(i, name_buffer, name_buflen);
+  }
+
+  /*if (gl_fixed_vertex_attrib_locations) {
+    // Relink the shader for glBindAttribLocation to take effect.
+    _glgsg->_glLinkProgram(_glsl_program);
+  }*/
+
   // Create a buffer the size of the longest uniform name.  Note
   // that Intel HD drivers report values that are too low.
-  GLint name_buflen = 0;
+  name_buflen = 0;
   _glgsg->_glGetProgramiv(_glsl_program, GL_ACTIVE_UNIFORM_MAX_LENGTH, &name_buflen);
   name_buflen = max(64, name_buflen);
-  char *name_buffer = (char *)alloca(name_buflen);
+  name_buffer = (char *)alloca(name_buflen);
 
 #ifndef OPENGLES
   // Get the used uniform blocks.
@@ -316,7 +335,7 @@ CLP(ShaderContext)(CLP(GraphicsStateGuardian) *glgsg, Shader *s) : ShaderContext
   _glgsg->_glUseProgram(_glsl_program);
 
   // Analyze the uniforms.
-  GLint param_count = 0;
+  param_count = 0;
   _glgsg->_glGetProgramiv(_glsl_program, GL_ACTIVE_UNIFORMS, &param_count);
 
   _shader->_ptr_spec.clear();
@@ -326,17 +345,6 @@ CLP(ShaderContext)(CLP(GraphicsStateGuardian) *glgsg, Shader *s) : ShaderContext
     reflect_uniform(i, name_buffer, name_buflen);
   }
 
-  // Now we've processed the uniforms, we'll process the attribs.
-  _glgsg->_glGetProgramiv(_glsl_program, GL_ACTIVE_ATTRIBUTES, &param_count);
-  _glgsg->_glGetProgramiv(_glsl_program, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, &name_buflen);
-  name_buflen = max(64, name_buflen);
-  name_buffer = (char *)alloca(name_buflen);
-
-  _shader->_var_spec.clear();
-  for (int i = 0; i < param_count; ++i) {
-    reflect_attribute(i, name_buffer, name_buflen);
-  }
-
   _glgsg->report_my_gl_errors();
 
   // Restore the active shader.
@@ -470,6 +478,37 @@ reflect_attribute(int i, char *name_buffer, GLsizei name_buflen) {
     break;
   }
 
+  // Experimental feature.
+  if (gl_fixed_vertex_attrib_locations) {
+    GLint loc;
+    if (bind._name == InternalName::get_vertex()) {
+      loc = 0;
+    } else if (bind._name == InternalName::get_transform_weight()) {
+      loc = 1;
+    } else if (bind._name == InternalName::get_normal()) {
+      loc = 2;
+    } else if (bind._name == InternalName::get_color()) {
+      loc = 3;
+    } else if (bind._name == InternalName::get_transform_index()) {
+      loc = 7;
+    } else if (bind._name == InternalName::get_texcoord() &&
+               bind._append_uv >= 0 && bind._append_uv < 8) {
+      loc = 8 + bind._append_uv;
+    } else {
+      GLCAT.error()
+        << "Vertex attrib '" << name_buffer << "' not yet supported with "
+           "gl-fixed-vertex-attrib-locations!\n";
+      return;
+    }
+    if (loc != p) {
+      GLCAT.error()
+        << "Vertex attrib '" << name_buffer << "' was bound to the wrong slot!\n";
+      return;
+    }
+    //_glgsg->_glBindAttribLocation(_glsl_program, loc, name_buffer);
+    _enabled_attribs.set_range(loc, bind._elements);
+  }
+
   _shader->_var_spec.push_back(bind);
 }
 
@@ -2045,91 +2084,152 @@ update_shader_vertex_arrays(ShaderContext *prev, bool force) {
   _state_rs->get_attrib_def(color_attrib);
 
   const GeomVertexArrayDataHandle *array_reader;
-  Geom::NumericType numeric_type;
-  int start, stride, num_values;
-  size_t nvarying = _shader->_var_spec.size();
 
-  GLuint max_p = 0;
+#ifndef OPENGLES
+  if (_glgsg->_use_vertex_attrib_binding) {
+    // Use experimental new separated format/binding state.
+    const GeomVertexDataPipelineReader *data_reader = _glgsg->_data_reader;
 
-  for (size_t i = 0; i < nvarying; ++i) {
-    const Shader::ShaderVarSpec &bind = _shader->_var_spec[i];
-    InternalName *name = bind._name;
-    int texslot = bind._append_uv;
+    for (int ai = 0; ai < data_reader->get_num_arrays(); ++ai) {
+      array_reader = data_reader->get_array_reader(ai);
 
-    if (texslot >= 0 && texslot < _glgsg->_state_texture->get_num_on_stages()) {
-      TextureStage *stage = _glgsg->_state_texture->get_on_stage(texslot);
-      InternalName *texname = stage->get_texcoord_name();
+      // Make sure the vertex buffer is up-to-date.
+      CLP(VertexBufferContext) *gvbc = DCAST(CLP(VertexBufferContext),
+        array_reader->prepare_now(_glgsg->get_prepared_objects(), _glgsg));
+      nassertr(gvbc != (CLP(VertexBufferContext) *)NULL, false);
 
-      if (name == InternalName::get_texcoord()) {
-        name = texname;
-      } else if (texname != InternalName::get_texcoord()) {
-        name = name->append(texname->get_basename());
+      if (!_glgsg->update_vertex_buffer(gvbc, array_reader, force)) {
+        return false;
       }
-    }
 
-    GLuint p = bind._id._seqno;
-    max_p = max(max_p, p + 1);
+      GLintptr stride = array_reader->get_array_format()->get_stride();
 
-    // Don't apply vertex colors if they are disabled with a ColorAttrib.
-    int num_elements, element_stride, divisor;
-    bool normalized;
-    if ((p != _color_attrib_index || color_attrib->get_type() == ColorAttrib::T_vertex) &&
-        _glgsg->_data_reader->get_array_info(name, array_reader,
-                                             num_values, numeric_type,
-                                             normalized, start, stride, divisor,
-                                             num_elements, element_stride)) {
-      const unsigned char *client_pointer;
-      if (!_glgsg->setup_array_data(client_pointer, array_reader, force)) {
-        return false;
+      // Bind the vertex buffer to the binding index.
+      if (ai >= _glgsg->_current_vertex_buffers.size()) {
+        _glgsg->_current_vertex_buffers.resize(ai + 1, 0);
+      }
+      if (_glgsg->_current_vertex_buffers[ai] != gvbc->_index) {
+        _glgsg->_glBindVertexBuffer(ai, gvbc->_index, 0, stride);
+        _glgsg->_current_vertex_buffers[ai] = gvbc->_index;
       }
-      client_pointer += start;
+    }
 
-      for (int i = 0; i < num_elements; ++i) {
-        _glgsg->enable_vertex_attrib_array(p);
+    // Figure out which attributes to enable or disable.
+    BitMask32 enabled_attribs = _enabled_attribs;
+    if (_color_attrib_index != -1 &&
+        color_attrib->get_type() != ColorAttrib::T_vertex) {
+      // Vertex colours are disabled.
+      enabled_attribs.clear_bit(_color_attrib_index);
 
-#ifndef OPENGLES
-        if (bind._integer) {
-          _glgsg->_glVertexAttribIPointer(p, num_values, _glgsg->get_numeric_type(numeric_type),
-                                          stride, client_pointer);
-        } else
+#ifdef STDFLOAT_DOUBLE
+      _glgsg->_glVertexAttrib4dv(_color_attrib_index, color_attrib->get_color().get_data());
+#else
+      _glgsg->_glVertexAttrib4fv(_color_attrib_index, color_attrib->get_color().get_data());
 #endif
-        if (numeric_type == GeomEnums::NT_packed_dabc) {
-          // GL_BGRA is a special accepted value available since OpenGL 3.2.
-          // It requires us to pass GL_TRUE for normalized.
-          _glgsg->_glVertexAttribPointer(p, GL_BGRA, GL_UNSIGNED_BYTE,
-                                         GL_TRUE, stride, client_pointer);
+    }
+
+    BitMask32 changed_attribs = enabled_attribs ^ _glgsg->_enabled_vertex_attrib_arrays;
+
+    for (int i = 0; i < 32; ++i) {
+      if (changed_attribs.get_bit(i)) {
+        if (enabled_attribs.get_bit(i)) {
+          _glgsg->_glEnableVertexAttribArray(i);
         } else {
-          _glgsg->_glVertexAttribPointer(p, num_values,
-                                         _glgsg->get_numeric_type(numeric_type),
-                                         normalized, stride, client_pointer);
+          _glgsg->_glDisableVertexAttribArray(i);
         }
+      }
+    }
+    _glgsg->_enabled_vertex_attrib_arrays = enabled_attribs;
 
-        if (divisor > 0) {
-          _glgsg->set_vertex_attrib_divisor(p, divisor);
+  } else
+#endif
+  {
+    Geom::NumericType numeric_type;
+    int start, stride, num_values;
+    size_t nvarying = _shader->_var_spec.size();
+
+    GLuint max_p = 0;
+
+    for (size_t i = 0; i < nvarying; ++i) {
+      const Shader::ShaderVarSpec &bind = _shader->_var_spec[i];
+      InternalName *name = bind._name;
+      int texslot = bind._append_uv;
+
+      if (texslot >= 0 && texslot < _glgsg->_state_texture->get_num_on_stages()) {
+        TextureStage *stage = _glgsg->_state_texture->get_on_stage(texslot);
+        InternalName *texname = stage->get_texcoord_name();
+
+        if (name == InternalName::get_texcoord()) {
+          name = texname;
+        } else if (texname != InternalName::get_texcoord()) {
+          name = name->append(texname->get_basename());
         }
-
-        ++p;
-        client_pointer += element_stride;
-      }
-    } else {
-      for (int i = 0; i < bind._elements; ++i) {
-        _glgsg->disable_vertex_attrib_array(p + i);
       }
-      if (p == _color_attrib_index) {
-        // Vertex colors are disabled or not present.  Apply flat color.
+
+      GLuint p = bind._id._seqno;
+      max_p = max(max_p, p + 1);
+
+      // Don't apply vertex colors if they are disabled with a ColorAttrib.
+      int num_elements, element_stride, divisor;
+      bool normalized;
+      if ((p != _color_attrib_index || color_attrib->get_type() == ColorAttrib::T_vertex) &&
+          _glgsg->_data_reader->get_array_info(name, array_reader,
+                                               num_values, numeric_type,
+                                               normalized, start, stride, divisor,
+                                               num_elements, element_stride)) {
+        const unsigned char *client_pointer;
+        if (!_glgsg->setup_array_data(client_pointer, array_reader, force)) {
+          return false;
+        }
+        client_pointer += start;
+
+        for (int i = 0; i < num_elements; ++i) {
+          _glgsg->enable_vertex_attrib_array(p);
+
+#ifndef OPENGLES
+          if (bind._integer) {
+            _glgsg->_glVertexAttribIPointer(p, num_values, _glgsg->get_numeric_type(numeric_type),
+                                            stride, client_pointer);
+          } else
+#endif
+          if (numeric_type == GeomEnums::NT_packed_dabc) {
+            // GL_BGRA is a special accepted value available since OpenGL 3.2.
+            // It requires us to pass GL_TRUE for normalized.
+            _glgsg->_glVertexAttribPointer(p, GL_BGRA, GL_UNSIGNED_BYTE,
+                                           GL_TRUE, stride, client_pointer);
+          } else {
+            _glgsg->_glVertexAttribPointer(p, num_values,
+                                           _glgsg->get_numeric_type(numeric_type),
+                                           normalized, stride, client_pointer);
+          }
+
+          if (divisor > 0) {
+            _glgsg->set_vertex_attrib_divisor(p, divisor);
+          }
+
+          ++p;
+          client_pointer += element_stride;
+        }
+      } else {
+        for (int i = 0; i < bind._elements; ++i) {
+          _glgsg->disable_vertex_attrib_array(p + i);
+        }
+        if (p == _color_attrib_index) {
+          // Vertex colors are disabled or not present.  Apply flat color.
 #ifdef STDFLOAT_DOUBLE
-        _glgsg->_glVertexAttrib4dv(p, color_attrib->get_color().get_data());
+          _glgsg->_glVertexAttrib4dv(p, color_attrib->get_color().get_data());
 #else
-        _glgsg->_glVertexAttrib4fv(p, color_attrib->get_color().get_data());
+          _glgsg->_glVertexAttrib4fv(p, color_attrib->get_color().get_data());
 #endif
+        }
       }
     }
-  }
 
-  // Disable attribute arrays we don't use.
-  GLint highest_p = _glgsg->_enabled_vertex_attrib_arrays.get_highest_on_bit() + 1;
-  for (GLint p = max_p; p < highest_p; ++p) {
-    _glgsg->disable_vertex_attrib_array(p);
+    // Disable attribute arrays we don't use.
+    GLint highest_p = _glgsg->_enabled_vertex_attrib_arrays.get_highest_on_bit() + 1;
+    for (GLint p = max_p; p < highest_p; ++p) {
+      _glgsg->disable_vertex_attrib_array(p);
+    }
   }
 
   if (_transform_table_index >= 0) {
@@ -2180,7 +2280,7 @@ disable_shader_texture_bindings() {
     }
 #endif
 
-    _glgsg->_glActiveTexture(GL_TEXTURE0 + i);
+    _glgsg->set_active_texture_stage(i);
 
     switch (_shader->_tex_spec[i]._desired_type) {
     case Texture::TT_1d_texture:
@@ -2371,7 +2471,8 @@ update_shader_texture_bindings(ShaderContext *prev) {
   static const bool multi_bind = false;
 #else
   bool multi_bind = false;
-  if (_glgsg->_supports_multi_bind && _glgsg->_supports_sampler_objects) {
+  if (num_textures > 1 &&
+      _glgsg->_supports_multi_bind && _glgsg->_supports_sampler_objects) {
     // Prepare to multi-bind the textures and samplers.
     multi_bind = true;
     textures = (GLuint *)alloca(sizeof(GLuint) * num_textures);
@@ -2394,7 +2495,7 @@ update_shader_texture_bindings(ShaderContext *prev) {
         textures[i] = _glgsg->get_white_texture();
         samplers[i] = 0;
       } else {
-        _glgsg->_glActiveTexture(GL_TEXTURE0 + i);
+        _glgsg->set_active_texture_stage(i);
         _glgsg->apply_white_texture();
       }
       continue;
@@ -2487,7 +2588,7 @@ update_shader_texture_bindings(ShaderContext *prev) {
 #endif  // !OPENGLES
     {
       // Non-multibind case.
-      _glgsg->_glActiveTexture(GL_TEXTURE0 + i);
+      _glgsg->set_active_texture_stage(i);
       if (!_glgsg->update_texture(gtc, false)) {
         continue;
       }
@@ -2765,6 +2866,15 @@ glsl_compile_and_link() {
   _glgsg->_glBindAttribLocation(_glsl_program, 2, "p3d_Normal");
   _glgsg->_glBindAttribLocation(_glsl_program, 3, "p3d_Color");
 
+  if (gl_fixed_vertex_attrib_locations) {
+    _glgsg->_glBindAttribLocation(_glsl_program, 1, "transform_weight");
+    _glgsg->_glBindAttribLocation(_glsl_program, 2, "normal");
+    _glgsg->_glBindAttribLocation(_glsl_program, 3, "color");
+    _glgsg->_glBindAttribLocation(_glsl_program, 7, "transform_index");
+    _glgsg->_glBindAttribLocation(_glsl_program, 8, "p3d_MultiTexCoord0");
+    _glgsg->_glBindAttribLocation(_glsl_program, 8, "texcoord");
+  }
+
   // If we requested to retrieve the shader, we should indicate that before linking.
 #if !defined(NDEBUG) && !defined(OPENGLES)
   if (gl_dump_compiled_shaders && _glgsg->_supports_get_program_binary) {

+ 1 - 0
panda/src/glstuff/glShaderContext_src.h

@@ -83,6 +83,7 @@ private:
   //typedef pvector<ParamContext> ParamContexts;
   //ParamContexts _params;
 
+  BitMask32 _enabled_attribs;
   GLint _color_attrib_index;
   GLint _transform_table_index;
   GLint _slider_table_index;

+ 4 - 0
panda/src/glstuff/glmisc_src.cxx

@@ -276,6 +276,10 @@ ConfigVariableBool gl_vertex_array_objects
             "and vertex-buffers are both set.  This should usually be "
             "true unless you suspect a bug in the implementation. "));
 
+ConfigVariableBool gl_fixed_vertex_attrib_locations
+  ("gl-fixed-vertex-attrib-locations", false,
+   PRC_DESC("Experimental feature."));
+
 ConfigVariableBool gl_support_primitive_restart_index
   ("gl-support-primitive-restart-index", true,
    PRC_DESC("Setting this causes Panda to make use of primitive "

+ 1 - 0
panda/src/glstuff/glmisc_src.h

@@ -77,6 +77,7 @@ extern ConfigVariableBool gl_immutable_texture_storage;
 extern ConfigVariableBool gl_use_bindless_texture;
 extern ConfigVariableBool gl_enable_memory_barriers;
 extern ConfigVariableBool gl_vertex_array_objects;
+extern ConfigVariableBool gl_fixed_vertex_attrib_locations;
 extern ConfigVariableBool gl_support_primitive_restart_index;
 extern ConfigVariableBool gl_support_sampler_objects;
 extern ConfigVariableBool gl_support_shadow_filter;

+ 7 - 0
panda/src/gobj/geomVertexColumn.cxx

@@ -427,6 +427,13 @@ complete_pointers(TypedWritable **p_list, BamReader *manager) {
 
   _name = DCAST(InternalName, p_list[pi++]);
 
+  // Make sure that old .bam files are corrected to have C_normal
+  // normal columns rather than C_vector.
+  if (manager->get_file_minor_ver() < 38 &&
+      _name == InternalName::get_normal() && _contents == C_vector) {
+    _contents = C_normal;
+  }
+
   return pi;
 }
 

+ 1 - 1
panda/src/grutil/cardMaker.cxx

@@ -64,7 +64,7 @@ generate() {
            (InternalName::get_vertex(), 3,
             GeomEnums::NT_stdfloat, GeomEnums::C_point,
             InternalName::get_normal(), 3,
-            GeomEnums::NT_stdfloat, GeomEnums::C_vector,
+            GeomEnums::NT_stdfloat, GeomEnums::C_normal,
             InternalName::get_texcoord(), 3,
             GeomEnums::NT_stdfloat, GeomEnums::C_texcoord));
       } else {