From 4430eb7d7544810b2d8f52cbfae2d6073192c504 Mon Sep 17 00:00:00 2001
From: Quantum <quantum2048@gmail.com>
Date: Wed, 29 Aug 2018 00:25:32 -0400
Subject: [PATCH] Use VAOs for everything.

---
 punyverse/entity.py | 42 +++++++++++++++++---------------
 punyverse/model.py  | 58 +++++++++++++++++++++++++++------------------
 punyverse/shader.py | 26 --------------------
 3 files changed, 58 insertions(+), 68 deletions(-)

diff --git a/punyverse/entity.py b/punyverse/entity.py
index b04a2d5..fcf5eff 100644
--- a/punyverse/entity.py
+++ b/punyverse/entity.py
@@ -80,7 +80,8 @@ class AsteroidManager(object):
     __nonzero__ = __bool__
 
     def load(self, file):
-        self.asteroids.append(WavefrontVBO(load_model(file), 5, 5, 5))
+        shader = self.world.activate_shader('model')
+        self.asteroids.append(WavefrontVBO(load_model(file), shader, 5, 5, 5))
 
     def new(self, location, direction):
         return Asteroid(self.world, random.choice(self.asteroids), location, direction)
@@ -104,12 +105,27 @@ class Belt(Entity):
         self.render = gl_info.have_version(3, 3)
 
         if self.render:
+            shader = world.activate_shader('belt')
             if not isinstance(models, list):
                 models = [models]
 
-            self.objects = [WavefrontVBO(load_model(model), info.get('sx', scale), info.get('sy', scale),
-                                         info.get('sz', scale)) for model in models]
-            self.belt = BeltVBO(radius, cross, len(self.objects), count)
+            self.belt = BeltVBO(radius, cross, len(models), count)
+            self.objects = [
+                WavefrontVBO(load_model(model), shader, info.get('sx', scale),
+                             info.get('sy', scale), info.get('sz', scale))
+                for model in models
+            ]
+
+            def callback():
+                glBindBuffer(GL_ARRAY_BUFFER, vbo)
+                shader.vertex_attribute('a_translate', self.belt.location_size, self.belt.type, GL_FALSE,
+                                        self.belt.stride, self.belt.location_offset, divisor=1)
+                shader.vertex_attribute('a_scale', self.belt.scale_size, self.belt.type, GL_FALSE,
+                                        self.belt.stride, self.belt.scale_offset, divisor=1)
+                glBindBuffer(GL_ARRAY_BUFFER, 0)
+
+            for model, vbo, count in zip(self.objects, self.belt.vbo, self.belt.sizes):
+                model.additional_attributes(callback)
 
         super(Belt, self).__init__(world, name, (x, y, z), (inclination, longitude, argument))
 
@@ -128,14 +144,7 @@ class Belt(Entity):
         shader.uniform_mat4('u_modelMatrix', self.model_matrix)
 
         for object, vbo, count in zip(self.objects, self.belt.vbo, self.belt.sizes):
-            glBindBuffer(GL_ARRAY_BUFFER, vbo)
-            shader.vertex_attribute('a_translate', self.belt.location_size, self.belt.type, GL_FALSE,
-                                    self.belt.stride, self.belt.location_offset, divisor=1)
-            shader.vertex_attribute('a_scale', self.belt.scale_size, self.belt.type, GL_FALSE,
-                                    self.belt.stride, self.belt.scale_offset, divisor=1)
-            glBindBuffer(GL_ARRAY_BUFFER, 0)
             object.draw(shader, instances=count)
-        shader.deactivate_all_attributes()
 
 
 class Sky(Entity):
@@ -159,7 +168,6 @@ class Sky(Entity):
             shader.vertex_attribute('a_direction', self.cube.direction_size, self.cube.type, GL_FALSE,
                                     self.cube.stride, self.cube.direction_offset)
             glBindBuffer(GL_ARRAY_BUFFER, 0)
-        shader.reset_all_attributes()
 
     def draw(self, options):
         cam = self.world.cam
@@ -276,7 +284,6 @@ class Body(Entity):
             glBindBuffer(GL_ARRAY_BUFFER, self.orbit_vbo.vbo)
             shader.vertex_attribute('a_position', self.orbit_vbo.position_size, self.orbit_vbo.type, GL_FALSE,
                                     self.orbit_vbo.stride, self.orbit_vbo.position_offset)
-        shader.reset_all_attributes()
 
         self.orbit_cache = cache
         return self.orbit_vbo, self.orbit_vao
@@ -370,7 +377,6 @@ class SphericalBody(Body):
                 glBindBuffer(GL_ARRAY_BUFFER, 0)
         else:
             raise ValueError('Invalid type: %s' % self.type)
-        shader.reset_all_attributes()
 
         self.atmosphere = None
         self.clouds = None
@@ -403,7 +409,6 @@ class SphericalBody(Body):
                     shader.vertex_attribute('a_uv', self.clouds.uv_size, self.clouds.type, GL_FALSE,
                                             self.clouds.stride, self.clouds.uv_offset)
                     glBindBuffer(GL_ARRAY_BUFFER, 0)
-                shader.reset_all_attributes()
 
             if atm_texture is not None:
                 self.atm_texture = load_texture_1d(atm_texture, clamp=True)
@@ -417,7 +422,6 @@ class SphericalBody(Body):
                     shader.vertex_attribute('a_u', self.atmosphere.u_size, self.atmosphere.type, GL_FALSE,
                                             self.atmosphere.stride, self.atmosphere.u_offset)
                     glBindBuffer(GL_ARRAY_BUFFER, 0)
-                shader.reset_all_attributes()
 
         if 'ring' in info:
             distance = world.evaluate(info['ring'].get('distance', self.radius * 1.2))
@@ -441,7 +445,6 @@ class SphericalBody(Body):
                 shader.vertex_attribute('a_u', self.ring.u_size, self.ring.type, GL_FALSE,
                                         self.ring.stride, self.ring.u_offset)
                 glBindBuffer(GL_ARRAY_BUFFER, 0)
-            shader.reset_all_attributes()
 
     def _draw_planet(self):
         shader = self.world.activate_shader('planet')
@@ -584,8 +587,9 @@ class ModelBody(Body):
         super(ModelBody, self).__init__(name, world, info, parent)
 
         scale = info.get('scale', 1)
-        self.vbo = WavefrontVBO(load_model(info['model']), info.get('sx', scale), info.get('sy', scale),
-                                info.get('sz', scale))
+        shader = world.activate_shader('model')
+        self.vbo = WavefrontVBO(load_model(info['model']), shader, info.get('sx', scale),
+                                info.get('sy', scale), info.get('sz', scale))
 
     def _draw(self, options):
         shader = self.world.activate_shader('model')
diff --git a/punyverse/model.py b/punyverse/model.py
index b0bf896..ab57462 100644
--- a/punyverse/model.py
+++ b/punyverse/model.py
@@ -9,7 +9,7 @@ from pyglet.gl import *
 # noinspection PyUnresolvedReferences
 from six.moves import range, zip
 
-from punyverse.glgeom import list_to_gl_buffer
+from punyverse.glgeom import list_to_gl_buffer, VAO
 from punyverse.texture import load_texture
 
 
@@ -197,36 +197,41 @@ def load_model(path):
 
 
 class ModelVBO(object):
-    __slots__ = ('has_normal', 'has_texture', 'data_buf', 'index_buf', 'offset_type', 'vertex_count')
+    __slots__ = ('has_normal', 'has_texture', 'data_buf', 'index_buf', 'offset_type', 'vertex_count', 'vao')
 
-    def draw(self, shader, instances=None):
+    def __init__(self):
+        self.vao = VAO()
+
+    def build_vao(self, shader):
         stride = (3 + self.has_normal * 3 + self.has_texture * 2) * 4
 
-        glBindBuffer(GL_ARRAY_BUFFER, self.data_buf)
-        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, self.index_buf)
+        with self.vao:
+            glBindBuffer(GL_ARRAY_BUFFER, self.data_buf)
+            glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, self.index_buf)
 
-        shader.vertex_attribute('a_position', 3, GL_FLOAT, GL_FALSE, stride, 0)
-        if self.has_normal:
-            shader.vertex_attribute('a_normal', 3, GL_FLOAT, GL_FALSE, stride, 3 * 4)
-        else:
-            shader.vertex_attribute_vec3('a_normal', 0, 0, 0)
-        if self.has_texture:
-            shader.vertex_attribute('a_uv', 2, GL_FLOAT, GL_FALSE, stride, (6 if self.has_normal else 3) * 4)
-        else:
-            shader.vertex_attribute_vec2('a_uv', 0, 0)
+            shader.vertex_attribute('a_position', 3, GL_FLOAT, GL_FALSE, stride, 0)
+            if self.has_normal:
+                shader.vertex_attribute('a_normal', 3, GL_FLOAT, GL_FALSE, stride, 3 * 4)
+            if self.has_texture:
+                shader.vertex_attribute('a_uv', 2, GL_FLOAT, GL_FALSE, stride, (6 if self.has_normal else 3) * 4)
 
-        if instances:
-            glDrawElementsInstanced(GL_TRIANGLES, self.vertex_count, self.offset_type, 0, instances)
-        else:
-            glDrawElements(GL_TRIANGLES, self.vertex_count, self.offset_type, 0)
+            glBindBuffer(GL_ARRAY_BUFFER, 0)
 
-        shader.deactivate_attributes('a_position', 'a_normal', 'a_uv')
-        glBindBuffer(GL_ARRAY_BUFFER, 0)
-        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)
+    def draw(self, shader, instances=None):
+        with self.vao:
+            if not self.has_normal:
+                shader.vertex_attribute_vec3('a_normal', 0, 0, 0)
+
+            if not self.has_texture:
+                shader.vertex_attribute_vec2('a_uv', 0, 0)
+            if instances:
+                glDrawElementsInstanced(GL_TRIANGLES, self.vertex_count, self.offset_type, 0, instances)
+            else:
+                glDrawElements(GL_TRIANGLES, self.vertex_count, self.offset_type, 0)
 
 
 class WavefrontVBO(object):
-    def __init__(self, model, sx=1, sy=1, sz=1):
+    def __init__(self, model, shader, sx=1, sy=1, sz=1):
         self._tex_cache = {}
         self.vbos = []
         self.scale = (sx, sy, sz)
@@ -240,7 +245,14 @@ class WavefrontVBO(object):
         normals = model.normals
 
         for group in self.merge_groups(model):
-            self.vbos.append((group.material, self.process_group(group, vertices, normals, textures)))
+            processed = self.process_group(group, vertices, normals, textures)
+            self.vbos.append((group.material, processed))
+            processed.build_vao(shader)
+
+    def additional_attributes(self, callback):
+        for _, group in self.vbos:
+            with group.vao:
+                callback()
 
     def draw(self, shader, instances=None):
         for mat, vbo in self.vbos:
diff --git a/punyverse/shader.py b/punyverse/shader.py
index d007348..625efcc 100644
--- a/punyverse/shader.py
+++ b/punyverse/shader.py
@@ -78,8 +78,6 @@ class Program(object):
         self.program = program
         self.attributes = self._variable_locations(GL_ACTIVE_ATTRIBUTES, glGetActiveAttrib, glGetAttribLocation)
         self.uniforms = self._variable_locations(GL_ACTIVE_UNIFORMS, glGetActiveUniform, glGetUniformLocation)
-        self.active_attributes = set()
-        self.divisor_attributes = set()
 
     def vertex_attribute(self, name, size, type, normalized, stride, offset, divisor=None):
         location = self.attributes[name]
@@ -87,30 +85,6 @@ class Program(object):
         glEnableVertexAttribArray(location)
         if divisor:
             glVertexAttribDivisor(location, divisor)
-            self.divisor_attributes.add(location)
-        self.active_attributes.add(location)
-
-    def deactivate_all_attributes(self):
-        for location in self.active_attributes:
-            glDisableVertexAttribArray(location)
-        for location in self.divisor_attributes:
-            glVertexAttribDivisor(location, 0)
-        self.reset_all_attributes()
-
-    def deactivate_attributes(self, *attributes):
-        for attr in attributes:
-            location = self.attributes[attr]
-            if location in self.active_attributes:
-                glDisableVertexAttribArray(location)
-            if location in self.divisor_attributes:
-                glVertexAttribDivisor(location, 0)
-            self.active_attributes.discard(location)
-            self.divisor_attributes.discard(location)
-
-    def reset_all_attributes(self):
-        # Call this when you bound to a VAO.
-        self.active_attributes.clear()
-        self.divisor_attributes.clear()
 
     def vertex_attribute_vec2(self, name, a, b):
         glVertexAttrib2f(self.attributes[name], a, b)