[vispy] 01/10: Imported Upstream version 0.2.1

Frédéric-Emmanuel Picca picca at moszumanska.debian.org
Sat Aug 30 17:17:30 UTC 2014


This is an automated email from the git hooks/post-receive script.

picca pushed a commit to branch master
in repository vispy.

commit c568db47739f607192f5a6d624e1bc073172b26f
Author: Picca Frédéric-Emmanuel <picca at debian.org>
Date:   Wed Feb 19 15:15:44 2014 +0100

    Imported Upstream version 0.2.1
---
 .gitignore                                         |    9 +
 LICENSE.txt                                        |   49 +
 README.md                                          |   44 +
 _link_vispy.py                                     |   47 +
 codegen/createglapi.py                             |  140 +
 codegen/headerparser.py                            |  338 +
 codegen/headers/gl2.h                              |  620 ++
 codegen/headers/gl2ext.h                           | 2051 +++++
 dev/disc_collection.py                             |  113 +
 dev/visual_experiments.py                          |   33 +
 doc/.requirements                                  |    3 +
 doc/Makefile                                       |  160 +
 doc/_templates/localtoc.html                       |   10 +
 doc/_templates/navbar.html                         |    4 +
 doc/_templates/navigation.html                     |   12 +
 doc/_templates/versions.html                       |    9 +
 doc/app.rst                                        |   32 +
 doc/architecture.svg                               |  689 ++
 doc/conf.py                                        |  282 +
 doc/event.rst                                      |   36 +
 doc/ext/docscrape.py                               |  502 ++
 doc/ext/docscrape_sphinx.py                        |  227 +
 doc/ext/examplesgenerator.py                       |  139 +
 doc/ext/glapigenerator.py                          |   77 +
 doc/ext/gloooverviewgenerator.py                   |   94 +
 doc/ext/numpydoc.py                                |  166 +
 doc/ext/scriptnamemangler.py                       |   30 +
 doc/ext/vispy_ext.py                               |   26 +
 doc/gloo.rst                                       |   83 +
 doc/index.rst                                      |   37 +
 doc/make.bat                                       |  190 +
 doc/releasenotes.rst                               |   30 +
 doc/shaders.svg                                    | 1318 +++
 doc/themes/scikit-image/layout.html                |  113 +
 doc/themes/scikit-image/search.html                |   46 +
 .../static/css/bootstrap-responsive.min.css        |    9 +
 .../scikit-image/static/css/bootstrap.min.css      |    9 +
 doc/themes/scikit-image/static/css/custom.css      |  232 +
 doc/themes/scikit-image/static/img/favicon.ico     |  Bin 0 -> 10134 bytes
 .../static/img/glyphicons-halflings-white.png      |  Bin 0 -> 8777 bytes
 .../static/img/glyphicons-halflings.png            |  Bin 0 -> 12799 bytes
 doc/themes/scikit-image/static/js/bootstrap.min.js |    6 +
 doc/themes/scikit-image/static/js/docversions.js   |   21 +
 doc/themes/scikit-image/theme.conf                 |    4 +
 doc/util.rst                                       |   39 +
 doc/visuals.rst                                    |    8 +
 examples/app/app-event.py                          |   62 +
 examples/app/app-glut.py                           |   68 +
 examples/app/app-pyglet.py                         |   63 +
 examples/app/app-qt.py                             |   62 +
 examples/app/simple.py                             |   37 +
 examples/benchmark/simple-glut.py                  |   43 +
 examples/benchmark/simple-vispy.py                 |   33 +
 examples/demo/atom.py                              |  192 +
 examples/demo/boids.py                             |  187 +
 examples/demo/cloud.py                             |  303 +
 examples/demo/fireworks.py                         |  150 +
 examples/demo/galaxy.py                            |  197 +
 examples/demo/game_of_life.py                      |  156 +
 examples/demo/markers.py                           |  229 +
 examples/demo/show-markers.py                      |  117 +
 examples/glsl-sandbox-cube.py                      |  184 +
 examples/howto/animate-images.py                   |  117 +
 examples/howto/animate-shape.py                    |  114 +
 examples/howto/client-buffers.py                   |  187 +
 examples/howto/display-lines.py                    |  138 +
 examples/howto/display-points.py                   |  104 +
 examples/howto/display-shape.py                    |   66 +
 examples/howto/hello-fbo.py                        |  130 +
 examples/howto/rotate-cube.py                      |  173 +
 examples/howto/split-screen.py                     |  181 +
 examples/howto/start.py                            |   17 +
 examples/rawgl/rawgl-cube.py                       |  202 +
 examples/rawgl/rawgl-fireworks.py                  |  222 +
 examples/spinning-cube2.py                         |  121 +
 examples/texturing.py                              |  112 +
 make/__init__.py                                   |    6 +
 make/__main__.py                                   |   13 +
 make/make.py                                       |  406 +
 nosetests.py                                       |    6 +
 setup.py                                           |  105 +
 stdeb.cfg                                          |    7 +
 vispy/__init__.py                                  |  104 +
 vispy/app/__init__.py                              |   65 +
 vispy/app/application.py                           |  204 +
 vispy/app/backends/__init__.py                     |   28 +
 vispy/app/backends/glut.py                         |  432 +
 vispy/app/backends/pyglet.py                       |  343 +
 vispy/app/backends/qt.py                           |  322 +
 vispy/app/backends/template.py                     |  170 +
 vispy/app/canvas.py                                |  616 ++
 vispy/app/tests/qt-designer.ui                     |   58 +
 vispy/app/tests/test_backends.py                   |  140 +
 vispy/app/tests/test_qt.py                         |   53 +
 vispy/app/timer.py                                 |  155 +
 vispy/data/crate.bz2                               |  Bin 0 -> 55642 bytes
 vispy/data/cube.obj                                |   36 +
 vispy/data/triceratops.obj                         | 8492 ++++++++++++++++++++
 vispy/gloo/__init__.py                             |   88 +
 vispy/gloo/buffer.py                               |  870 ++
 vispy/gloo/framebuffer.py                          |  351 +
 vispy/gloo/gl/__init__.py                          |   76 +
 vispy/gloo/gl/_constants.py                        |  315 +
 vispy/gloo/gl/_constants_ext.py                    |   96 +
 vispy/gloo/gl/_desktop.py                          |  158 +
 vispy/gloo/gl/_desktop_ext.py                      |   30 +
 vispy/gloo/gl/desktop.py                           |  165 +
 vispy/gloo/gl/ext.py                               |    9 +
 vispy/gloo/globject.py                             |  153 +
 vispy/gloo/program.py                              |  651 ++
 vispy/gloo/shader.py                               |  343 +
 vispy/gloo/tests/test_buffer.py                    |  410 +
 vispy/gloo/tests/test_framebuffer.py               |  206 +
 vispy/gloo/tests/test_globject.py                  |  217 +
 vispy/gloo/tests/test_program.py                   |  155 +
 vispy/gloo/tests/test_shader.py                    |   89 +
 vispy/gloo/tests/test_texture.py                   |  260 +
 vispy/gloo/tests/test_variable.py                  |  127 +
 vispy/gloo/texture.py                              |  724 ++
 vispy/gloo/variable.py                             |  458 ++
 vispy/shaders/__init__.py                          |    0
 vispy/shaders/shaders.py                           |  393 +
 vispy/shaders/transforms.py                        |  334 +
 vispy/util/__init__.py                             |   11 +
 vispy/util/color.py                                |    5 +
 vispy/util/dataio/__init__.py                      |  132 +
 vispy/util/dataio/wavefront.py                     |  365 +
 vispy/util/event.py                                |  543 ++
 vispy/util/keys.py                                 |  105 +
 vispy/util/ordereddict.py                          |  127 +
 vispy/util/ptime.py                                |   32 +
 vispy/util/six.py                                  |  404 +
 vispy/util/test/test_emitter_group.py              |  205 +
 vispy/util/test/test_event_emitter.py              |  421 +
 vispy/util/test/test_vispy.py                      |    6 +
 vispy/util/transforms.py                           |  148 +
 vispy/visuals/README                               |    4 +
 vispy/visuals/__init__.py                          |    2 +
 vispy/visuals/base.py                              |   17 +
 vispy/visuals/box.py                               |    6 +
 vispy/visuals/camera.py                            |    8 +
 vispy/visuals/curve.py                             |    0
 vispy/visuals/data.py                              |  116 +
 vispy/visuals/image.py                             |    0
 vispy/visuals/layout.py                            |    4 +
 vispy/visuals/light.py                             |    0
 vispy/visuals/line.py                              |  369 +
 vispy/visuals/mesh.py                              |    7 +
 vispy/visuals/particle.py                          |    5 +
 vispy/visuals/text.py                              |    0
 vispy/visuals/view_box.py                          |    9 +
 vispy/visuals/visual.py                            |  149 +
 vispy/visuals/volume.py                            |    0
 153 files changed, 33693 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..39789f1
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,9 @@
+*.pyc
+*.pyo
+doc/_build
+/_website
+/_gh-pages
+/_images
+build
+dist
+MANIFEST
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..22c7511
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,49 @@
+Vispy licensing terms
+---------------------
+
+Vispy is licensed under the terms of the (new) BSD license:
+
+Copyright (c) 2013, Vispy Development Team
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+* Redistributions of source code must retain the above copyright
+  notice, this list of conditions and the following disclaimer.
+* Redistributions in binary form must reproduce the above copyright
+  notice, this list of conditions and the following disclaimer in the
+  documentation and/or other materials provided with the distribution.
+* Neither the name of Vispy Development Team nor the names of its
+  contributors may be used to endorse or promote products
+  derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+Exceptions
+----------
+
+The examples code in the examples directory can be considered public 
+domain, unless otherwise indicated in the corresponding source file.
+
+
+The Vispy Development Team
+--------------------------
+
+The Vispy Development Team is the set of all contributors to the Vispy
+project. The core team that coordinates development consists of: 
+
+  * Luke Campagnola
+  * Nicolas Rougier
+  * Cyrille Rossant
+  * Almar Klein
+ 
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..84192f2
--- /dev/null
+++ b/README.md
@@ -0,0 +1,44 @@
+## Vispy: interactive scientific visualization in Python
+
+Main website: http://vispy.org
+
+
+### Description
+
+Vispy is a collaborative project that has two main goals: 
+1) To make it easier to use OpenGL (also in the browser) 
+and allow more sharing of code between visualization projects. 
+2) To create fast, interactive and beautiful (i.e. high quality) 
+visualizations, using a high level interface (also for big data).
+
+
+### Installation
+
+Vispy runs on Python 2.6 and higher, including Python 3. Vispy depends on Numpy and PyOpenGL.
+Since Vispy is pure Python, installation is easy, for instance via `pip install vispy`. 
+
+
+### About us
+
+The core development team consists of Luke Campagnola, Almar Klein, 
+Nicolas Rougier and Cyrille Rossant. We have each written our own 
+Python visualization toolkit (PyQtGraph, Visvis, Glumpy and Galry, 
+respectively), and decided to team-up.
+Vispy will eventually replace all of our visualization libraries, so 
+you can expect vispy to have all the features of our respective 
+toolkits combined, and more.
+
+
+### Contributions
+
+You want to help out? Fork our repo and come up with a pull request! Or discuss new ideas in the mailing list.
+
+
+### More information
+
+  * Have a look at the code at http://github.com/vispy/vispy
+  * Our mailing list is at: https://groups.google.com/d/forum/vispy
+  * API documentation is at http://vispy.readthedocs.org
+  * Visit our gallery for examples: http://vispy.org/gallery.html
+  * View the [wiki](http://github.com/vispy/vispy/wiki) for more information about this project.
+
diff --git a/_link_vispy.py b/_link_vispy.py
new file mode 100644
index 0000000..7094098
--- /dev/null
+++ b/_link_vispy.py
@@ -0,0 +1,47 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2013, Almar Klein
+# (new) BSD License.
+
+"""
+This module provides an easy way to enable importing a package event
+if it's not on sys.path. The main use case is to import a package 
+from its developmment repository without having to install it.
+
+This module is sort of like a symlink for Python modules.
+
+To install: 
+  1) Copy this file to a directory that is on the PYTHONPATH
+  2) Rename the file to "yourpackage.py".
+
+If the real package is in "yourpackage/yourpackage" relative to this file, 
+you're done. Otherwise modify PARENT_DIR_OF_MODULE.
+
+""" 
+
+import os
+import sys
+
+# Determine directory and package name
+THIS_DIR = os.path.abspath(os.path.dirname(__file__))
+MODULE_NAME = __name__
+
+# Override if necessary
+PARENT_DIR_OF_MODULE = os.path.join(THIS_DIR, MODULE_NAME)
+
+
+# Insert in sys.path, so we can import the *real* package
+if not PARENT_DIR_OF_MODULE in sys.path:
+    sys.path.insert(0, PARENT_DIR_OF_MODULE)
+
+# Remove *this* module from sys.modules, so we can import that name again
+# (keep reference to avoid premature cleanup)
+_old = sys.modules.pop(MODULE_NAME, None)
+
+# Import the *real* package. This will put the new package in
+# sys.modules. Note that the new package is injected in the namespace
+# from which "import package" is called; we do not need to import *.
+__import__(MODULE_NAME, level=0)
+
+# Clean up after ourselves
+if PARENT_DIR_OF_MODULE in sys.path:
+    sys.path.remove(PARENT_DIR_OF_MODULE)
diff --git a/codegen/createglapi.py b/codegen/createglapi.py
new file mode 100644
index 0000000..1e6e7e3
--- /dev/null
+++ b/codegen/createglapi.py
@@ -0,0 +1,140 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2013, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+""" Script to create an OpenGL ES 2.0 API
+
+The header file for the OpenGL ES standard is parsed to obtain a list
+of constants and functions.
+
+At this point, the constants are fully parsed by ourselves, but for the
+functions we inject the corresponding function of PyOpenGL. Later on, 
+we may create functions that hook into the OpenGL library themselves,
+or that pipe the OpenGL commands to elsewhere, such as a WebGL instance.
+
+"""
+
+import os 
+import sys
+
+from OpenGL import GL
+
+THISDIR = os.path.abspath(os.path.dirname(__file__))
+GLDIR = os.path.join(THISDIR, '..', 'vispy', 'gloo', 'gl')
+
+
+# We need header parser
+sys.path.insert(0, THISDIR)
+
+# Load parser
+from headerparser import Parser
+
+PREAMBLE = '''""" 
+
+THIS CODE IS AUTO-GENERATED. DO NOT EDIT.
+
+%s
+
+"""
+'''
+
+
+def create_constants_module(parser, extension=False):
+    
+    # Initialize 
+    lines = []
+    lines.append(PREAMBLE % 'Constants for OpenGL ES 2.0.')
+    
+    # __future__ import
+    lines.append('from __future__ import print_function, division, absolute_import\n')
+    
+    # Import enum
+    lines.append('from . import _GL_ENUM')
+    lines.append('\n')
+    
+    # For extensions, we only take the OES ones, and remove the OES
+    if extension:
+        constantDefs = []
+        for c in parser.constantDefs:
+            if 'OES' in c.cname:
+                c.cname = c.cname.replace('OES', '').replace('__', '_').strip('_')
+                constantDefs.append(c)
+    else:
+        constantDefs = parser.constantDefs
+    
+    # Insert constants
+    for c in sorted(constantDefs, key=lambda x:x.cname):
+        if isinstance(c.value, int):
+            lines.append('%s = _GL_ENUM(%r, %r)' % (c.cname, c.cname, c.value))
+        else:
+            lines.append('%s = %r' % (c.cname, c.value))
+    lines.append('')
+    
+    # Write the file
+    fname = '_constants_ext.py' if extension else '_constants.py'
+    with open(os.path.join(GLDIR, fname), 'w') as f:
+        f.write('\n'.join(lines))
+    print('wrote %s' % fname)
+
+
+def create_desktop_module(parser, extension=False):
+    
+    # Initialize
+    lines = []
+    doc = 'OpenGL ES 2.0 API based on desktop OpenGL (via pyOpenGL).'
+    lines.append(PREAMBLE % doc)
+    
+    # __future__ import
+    lines.append('from __future__ import print_function, division, absolute_import\n')
+    
+    # Import constants and ext
+    if extension:
+        lines.append('from ._constants_ext import *')
+    else:
+        lines.append('from ._constants import *')
+    
+    lines.append('\n')
+    
+    # For extensions, we only take the OES ones, and remove the OES
+    if extension:
+        functionDefs = []
+        for f in parser.functionDefs:
+            if 'OES' in f.cname:
+                f.cname = f.cname.replace('OES', '')
+                functionDefs.append(f)
+    else:
+        functionDefs = parser.functionDefs
+    
+    # Insert functions
+    lines.append('_glfunctions = [')
+    for f in sorted(functionDefs, key=lambda x:x.cname):
+        # Add "super-function" if this is a group of functions
+        if isinstance(f.group, list):
+            if hasattr(GL, f.keyname):
+                lines.append('    "%s",' % f.keyname)
+        # Add line
+        if hasattr(GL, f.cname):
+            lines.append('    "%s",' % f.cname)
+        else:
+            print('WARNING: %s seems not available in PyOpenGL' % f.cname)
+    lines.append('    ]')
+    
+    lines.append('')
+    
+    # Write the file
+    fname = '_desktop_ext.py' if extension else '_desktop.py'
+    with open(os.path.join(GLDIR, fname), 'w') as f:
+        f.write('\n'.join(lines))
+    print('wrote %s' % fname)
+
+
+if __name__ == '__main__':
+    # Create code  for normal ES 2.0
+    parser = Parser(os.path.join(THISDIR, 'headers', 'gl2.h'))
+    create_constants_module(parser)
+    create_desktop_module(parser)
+    
+    # Create code for extensions
+    parser = Parser(os.path.join(THISDIR, 'headers', 'gl2ext.h'))
+    create_constants_module(parser, True)
+    create_desktop_module(parser, True)
diff --git a/codegen/headerparser.py b/codegen/headerparser.py
new file mode 100644
index 0000000..9ec52c0
--- /dev/null
+++ b/codegen/headerparser.py
@@ -0,0 +1,338 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2013, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+""" Code to parse a header file.
+"""
+
+import os
+import sys
+
+
+TYPEMAP = {
+    'GLenum': 'int (GLenum)',
+    }
+
+
+def getwords(line):
+    """ Get words on a line.
+    """
+    line = line.replace('\t', ' ').strip()
+    return [w for w in line.split(' ') if w]
+
+
+# Keep track of all constants in case they are "reused" in a header file
+CONSTANTS = {}
+
+
+class Parser:
+    """ Class to parse header files.
+    """
+    
+    def __init__(self, header_file, parse_now=True):
+        # Get filenames for C and Py
+        self._c_fname = c_fname =os.path.split(header_file)[1]
+        
+        # Get absolute filenames
+        self._c_filename = header_file#os.path.join(SCRIPT_DIR, c_fname)
+        
+        # Init output 
+        self.definitions = []
+        self.functionDefs = []
+        self.constantDefs = []
+        
+        # We are aware of the line number
+        self._linenr = 0
+        
+        # Some stats
+        self.stat_types = set()
+        
+        if parse_now:
+            self.parse()
+    
+    
+    def __iadd__(self, definition):
+        """ Add an output line. Can be multiple lines.
+        """
+        # Create comment
+        definition.comment = 'line %i of %s' % (self._linenr, self._c_fname)
+        # Add to lists
+        self.definitions.append(definition)
+        if isinstance(definition, FunctionDefinition):
+            self.functionDefs.append(definition)
+        elif isinstance(definition, ConstantDefinition):
+            self.constantDefs.append(definition)
+        
+        return self
+    
+    
+    def _get_line(self):
+        # Get a stripped line, and keep track of line nr, skip empty lines
+        line = ''
+        while not line:
+            line = self._file.readline()
+            if not line:
+                raise StopIteration()
+            line = line.strip()
+            self._linenr += 1
+        return line
+    
+    
+    def _get_lines(self):
+        # Easy iterator
+        while True:
+            yield self._get_line()
+    
+    
+    def parse(self):
+        """ Parse the header file!
+        """
+        
+        # Open file
+        self._file = open(self._c_filename, 'rt', encoding='utf-8')
+        
+        # Parse the file
+        for line in self._get_lines():
+            if line.startswith('#define'):
+                self += ConstantDefinition(line)
+            elif (  line.startswith('GLAPI') or
+                    line.startswith('GL_APICALL') or 
+                    line.startswith('WINGDIAPI') ):
+                while ')' not in line:
+                    line += self._get_line()
+                #self += self.handle_function(line)
+                self += FunctionDefinition(line)
+        
+        # Remove invalid defs
+        self.definitions = [d for d in self.definitions if d.isvalid]
+        self.functionDefs = [d for d in self.functionDefs if d.isvalid]
+        self.constantDefs = [d for d in self.constantDefs if d.isvalid]
+        
+        # Resolve multipe functions that are really the same
+        self.functionDefs.sort(key=lambda x:x.cname)
+        keyDef = None
+        for funcDef in self.functionDefs:
+            if keyDef is not None:
+                extrachars = funcDef.matchKeyName(keyDef.keyname)
+                if extrachars:
+                    funcDef.group = True
+                    if not keyDef.group:
+                        keyDef.group = [keyDef]
+                    keyDef.group.append(funcDef)
+                    continue
+            if funcDef.extrachars:
+                # This may be a key def
+                keyDef = funcDef
+            else:
+                keyDef = None
+        
+        # Process all definitions
+        for definition in self.definitions:
+            if definition.isvalid:
+                definition.process()
+        
+        # Get some stats
+        for funcDef in self.functionDefs:
+            for arg in funcDef.args:
+                self.stat_types.add(arg.ctype)
+        
+        # Show stats
+        n1 = len([d for d in self.constantDefs])
+        n2 = len([d for d in self.functionDefs if d.group is not True])
+        n3 = len([d for d in self.functionDefs if isinstance(d.group, list)])
+        n4 = len([d for d in self.functionDefs if d.group is True])
+        print('Found %i constants and %i unique functions (%i groups contain %i functions)").' % (
+               n1, n2, n3, n4))
+        
+        print('C-types found in args:', self.stat_types)
+    
+    
+    @property
+    def constant_names(self):
+        return set([d.cname for d in self.constantDefs])
+    
+    @property
+    def function_names(self):
+        return set([d.cname for d in self.functionDefs])
+    
+    def show_groups(self):
+        for d in self.functionDefs:
+            if isinstance(d.group, list):
+                print(d.keyname)
+                for d2 in d.group:
+                    print('  ', d2.cname)
+
+
+
+class Definition:
+    """ Abstract class to represent a constant or function definition.
+    """
+    def __init__(self, line):
+        self.line = line
+        self.isvalid = True
+        self.comment = ''
+        self.cname = ''
+        self.parse_line(line)
+    
+    def parse_line(self, line):
+        # Do initial parsing of the incoming line 
+        # (which may be multiline actually)
+        pass
+    
+    def process(self):
+        # Do more parsing of this definition
+        pass
+
+
+
+class ConstantDefinition(Definition):
+    
+    def parse_line(self, line):
+        """ Set cname and value attributes.
+        """
+        self.value = None
+        line = line.split('/*',1)[0]
+        _, *args = getwords(line)
+        self.isvalid = False
+        if len(args) == 1:
+            pass
+        elif len(args) == 2:
+            # Set name
+            self.cname, val = args
+            self.isvalid = bool(self.cname)
+            # Set value
+            if val.startswith('0x'):
+                self.value = int(val[2:].rstrip('ul'), 16)
+            elif val[0] in '0123456789':
+                self.value = int(val)
+            elif val.startswith("'"):
+                self.value = val
+            elif val in CONSTANTS:
+                self.value = CONSTANTS[val]
+            else:
+                print('Warning: Dont know what to do with "%s"' % line)
+        else:
+            print('Dont know what to do with "%s"' % line)
+        
+        if self.value is not None:
+            CONSTANTS[self.cname] = self.value
+    
+    def process(self):
+        pass  # We did all that we needed to do
+
+
+
+class FunctionDefinition(Definition):
+    
+    def parse_line(self, line):
+        """ Set cname, keyname, cargs attributes.
+        """
+        # Parse components
+        beforeBrace, args = line.split('(', 1)
+        betweenBraces, _ = args.split(')', 1)
+        *prefix, name = getwords(beforeBrace)
+        
+        # Store name
+        self.cname = name
+        
+        # Possibly, this function belongs to a collection of similar functions,
+        # which we are going to replace with one function in Python.
+        self.keyname = self.cname.rstrip('v').rstrip('bsifd').rstrip('1234')
+        self.extrachars = self.matchKeyName(self.keyname)
+        
+        # If this is a list, this instance represents the group
+        # If this is True, this instance is in a group (but not the representative)
+        self.group = None
+        
+        # Create list of Argument instances
+        self.cargs = [arg.strip() for arg in betweenBraces.split(',')]
+        self.args = []
+        # Set output arg
+        self.args.append( Argument(' '.join(prefix), False) )
+        # Parse input arguments,
+        for arg in self.cargs:
+            if arg and arg != 'void':
+                self.args.append( Argument(arg) )
+    
+    
+    def matchKeyName(self, keyname):
+        if self.cname.startswith(keyname):
+            extrachars = self.cname[len(keyname):]
+            if all([(c in 'vbsuifd1234') for c in extrachars]):
+                return extrachars
+    
+    
+    def count_input_args(self):
+        return len([arg for arg in self.args if arg.pyinput])
+    
+    def count_output_args(self):
+        return len([arg for arg in self.args if (not arg.pyinput)])
+    
+    
+    def process(self):
+        
+        # Is one of the inputs really an output?
+        if self.cname.lower().startswith('glget'):
+            if not self.count_output_args():
+                args = [arg for arg in args if arg.isptr]
+                if len(args) == 1:
+                    args[0].pyinput = False
+                else:
+                    print('Warning: cannot determine py-output for %s' % self.name)
+        
+        # Build Python function signature
+        pyargs = ', '.join([arg.name for arg in self.args if arg.pyinput])
+        #defline = 'def %s(%s):' % (self.pyname, pyargs)
+        # ... not here
+         
+
+
+class Argument:
+    """ Input or output argument.
+    """
+    def __init__(self, argAsString, cinput=True):
+        # Parse string
+        components = [c for c in argAsString.split(' ') if c]
+        if len(components) == 1:
+            name = components[0]
+            type = 'unknown'
+        else:
+            name = components[-1]
+            type = components[-2]
+        # Store stuff
+        self.orig = tuple(components)
+        self.name = name.lstrip('*')
+        self.isptr = len(name) - len(self.name) # Number of stars
+        self.ctype = type
+        self.typedes = TYPEMAP.get(type, type)
+        self.pytype = self.typedes.split(' ')[0]
+        # Status flags
+        self.cinput = cinput
+        self.pyinput = cinput  # May be modified    
+
+
+
+if __name__ == '__main__':
+    THISDIR = os.path.abspath(os.path.dirname(__file__))
+    
+    # Some tests ...
+    gl2 = Parser(os.path.join(THISDIR, 'headers', 'gl2.h'))
+    import OpenGL.GL
+    pygl = set(dir(OpenGL.GL))
+    
+    # Test if all functions are in pyopengl
+    print('Not in pyopengl:', gl2.function_names.difference(pygl))
+    
+    # Test if constant match with these in pyopengl
+    for d in gl2.constantDefs:
+        v1 = d.value
+        try:
+            v2 = getattr(OpenGL.GL, d.cname)
+        except AttributeError:
+            print(d.cname, 'is not in pyopengl')
+        else:
+            if v1 != v2:
+                print(d.cname, 'does not match: %r vs %r' % (v1, int(v2)))
+    
+    
+    
\ No newline at end of file
diff --git a/codegen/headers/gl2.h b/codegen/headers/gl2.h
new file mode 100644
index 0000000..46bc0a7
--- /dev/null
+++ b/codegen/headers/gl2.h
@@ -0,0 +1,620 @@
+#ifndef __gl2_h_
+#define __gl2_h_
+
+/* $Revision: 20555 $ on $Date:: 2013-02-12 14:32:47 -0800 #$ */
+
+#include <GLES2/gl2platform.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * This document is licensed under the SGI Free Software B License Version
+ * 2.0. For details, see http://oss.sgi.com/projects/FreeB/ .
+ */
+
+/*-------------------------------------------------------------------------
+ * Data type definitions
+ *-----------------------------------------------------------------------*/
+
+typedef void             GLvoid;
+typedef char             GLchar;
+typedef unsigned int     GLenum;
+typedef unsigned char    GLboolean;
+typedef unsigned int     GLbitfield;
+typedef khronos_int8_t   GLbyte;
+typedef short            GLshort;
+typedef int              GLint;
+typedef int              GLsizei;
+typedef khronos_uint8_t  GLubyte;
+typedef unsigned short   GLushort;
+typedef unsigned int     GLuint;
+typedef khronos_float_t  GLfloat;
+typedef khronos_float_t  GLclampf;
+typedef khronos_int32_t  GLfixed;
+
+/* GL types for handling large vertex buffer objects */
+typedef khronos_intptr_t GLintptr;
+typedef khronos_ssize_t  GLsizeiptr;
+
+/* OpenGL ES core versions */
+#define GL_ES_VERSION_2_0                 1
+
+/* ClearBufferMask */
+#define GL_DEPTH_BUFFER_BIT               0x00000100
+#define GL_STENCIL_BUFFER_BIT             0x00000400
+#define GL_COLOR_BUFFER_BIT               0x00004000
+
+/* Boolean */
+#define GL_FALSE                          0
+#define GL_TRUE                           1
+
+/* BeginMode */
+#define GL_POINTS                         0x0000
+#define GL_LINES                          0x0001
+#define GL_LINE_LOOP                      0x0002
+#define GL_LINE_STRIP                     0x0003
+#define GL_TRIANGLES                      0x0004
+#define GL_TRIANGLE_STRIP                 0x0005
+#define GL_TRIANGLE_FAN                   0x0006
+
+/* AlphaFunction (not supported in ES20) */
+/*      GL_NEVER */
+/*      GL_LESS */
+/*      GL_EQUAL */
+/*      GL_LEQUAL */
+/*      GL_GREATER */
+/*      GL_NOTEQUAL */
+/*      GL_GEQUAL */
+/*      GL_ALWAYS */
+
+/* BlendingFactorDest */
+#define GL_ZERO                           0
+#define GL_ONE                            1
+#define GL_SRC_COLOR                      0x0300
+#define GL_ONE_MINUS_SRC_COLOR            0x0301
+#define GL_SRC_ALPHA                      0x0302
+#define GL_ONE_MINUS_SRC_ALPHA            0x0303
+#define GL_DST_ALPHA                      0x0304
+#define GL_ONE_MINUS_DST_ALPHA            0x0305
+
+/* BlendingFactorSrc */
+/*      GL_ZERO */
+/*      GL_ONE */
+#define GL_DST_COLOR                      0x0306
+#define GL_ONE_MINUS_DST_COLOR            0x0307
+#define GL_SRC_ALPHA_SATURATE             0x0308
+/*      GL_SRC_ALPHA */
+/*      GL_ONE_MINUS_SRC_ALPHA */
+/*      GL_DST_ALPHA */
+/*      GL_ONE_MINUS_DST_ALPHA */
+
+/* BlendEquationSeparate */
+#define GL_FUNC_ADD                       0x8006
+#define GL_BLEND_EQUATION                 0x8009
+#define GL_BLEND_EQUATION_RGB             0x8009    /* same as BLEND_EQUATION */
+#define GL_BLEND_EQUATION_ALPHA           0x883D
+
+/* BlendSubtract */
+#define GL_FUNC_SUBTRACT                  0x800A
+#define GL_FUNC_REVERSE_SUBTRACT          0x800B
+
+/* Separate Blend Functions */
+#define GL_BLEND_DST_RGB                  0x80C8
+#define GL_BLEND_SRC_RGB                  0x80C9
+#define GL_BLEND_DST_ALPHA                0x80CA
+#define GL_BLEND_SRC_ALPHA                0x80CB
+#define GL_CONSTANT_COLOR                 0x8001
+#define GL_ONE_MINUS_CONSTANT_COLOR       0x8002
+#define GL_CONSTANT_ALPHA                 0x8003
+#define GL_ONE_MINUS_CONSTANT_ALPHA       0x8004
+#define GL_BLEND_COLOR                    0x8005
+
+/* Buffer Objects */
+#define GL_ARRAY_BUFFER                   0x8892
+#define GL_ELEMENT_ARRAY_BUFFER           0x8893
+#define GL_ARRAY_BUFFER_BINDING           0x8894
+#define GL_ELEMENT_ARRAY_BUFFER_BINDING   0x8895
+
+#define GL_STREAM_DRAW                    0x88E0
+#define GL_STATIC_DRAW                    0x88E4
+#define GL_DYNAMIC_DRAW                   0x88E8
+
+#define GL_BUFFER_SIZE                    0x8764
+#define GL_BUFFER_USAGE                   0x8765
+
+#define GL_CURRENT_VERTEX_ATTRIB          0x8626
+
+/* CullFaceMode */
+#define GL_FRONT                          0x0404
+#define GL_BACK                           0x0405
+#define GL_FRONT_AND_BACK                 0x0408
+
+/* DepthFunction */
+/*      GL_NEVER */
+/*      GL_LESS */
+/*      GL_EQUAL */
+/*      GL_LEQUAL */
+/*      GL_GREATER */
+/*      GL_NOTEQUAL */
+/*      GL_GEQUAL */
+/*      GL_ALWAYS */
+
+/* EnableCap */
+#define GL_TEXTURE_2D                     0x0DE1
+#define GL_CULL_FACE                      0x0B44
+#define GL_BLEND                          0x0BE2
+#define GL_DITHER                         0x0BD0
+#define GL_STENCIL_TEST                   0x0B90
+#define GL_DEPTH_TEST                     0x0B71
+#define GL_SCISSOR_TEST                   0x0C11
+#define GL_POLYGON_OFFSET_FILL            0x8037
+#define GL_SAMPLE_ALPHA_TO_COVERAGE       0x809E
+#define GL_SAMPLE_COVERAGE                0x80A0
+
+/* ErrorCode */
+#define GL_NO_ERROR                       0
+#define GL_INVALID_ENUM                   0x0500
+#define GL_INVALID_VALUE                  0x0501
+#define GL_INVALID_OPERATION              0x0502
+#define GL_OUT_OF_MEMORY                  0x0505
+
+/* FrontFaceDirection */
+#define GL_CW                             0x0900
+#define GL_CCW                            0x0901
+
+/* GetPName */
+#define GL_LINE_WIDTH                     0x0B21
+#define GL_ALIASED_POINT_SIZE_RANGE       0x846D
+#define GL_ALIASED_LINE_WIDTH_RANGE       0x846E
+#define GL_CULL_FACE_MODE                 0x0B45
+#define GL_FRONT_FACE                     0x0B46
+#define GL_DEPTH_RANGE                    0x0B70
+#define GL_DEPTH_WRITEMASK                0x0B72
+#define GL_DEPTH_CLEAR_VALUE              0x0B73
+#define GL_DEPTH_FUNC                     0x0B74
+#define GL_STENCIL_CLEAR_VALUE            0x0B91
+#define GL_STENCIL_FUNC                   0x0B92
+#define GL_STENCIL_FAIL                   0x0B94
+#define GL_STENCIL_PASS_DEPTH_FAIL        0x0B95
+#define GL_STENCIL_PASS_DEPTH_PASS        0x0B96
+#define GL_STENCIL_REF                    0x0B97
+#define GL_STENCIL_VALUE_MASK             0x0B93
+#define GL_STENCIL_WRITEMASK              0x0B98
+#define GL_STENCIL_BACK_FUNC              0x8800
+#define GL_STENCIL_BACK_FAIL              0x8801
+#define GL_STENCIL_BACK_PASS_DEPTH_FAIL   0x8802
+#define GL_STENCIL_BACK_PASS_DEPTH_PASS   0x8803
+#define GL_STENCIL_BACK_REF               0x8CA3
+#define GL_STENCIL_BACK_VALUE_MASK        0x8CA4
+#define GL_STENCIL_BACK_WRITEMASK         0x8CA5
+#define GL_VIEWPORT                       0x0BA2
+#define GL_SCISSOR_BOX                    0x0C10
+/*      GL_SCISSOR_TEST */
+#define GL_COLOR_CLEAR_VALUE              0x0C22
+#define GL_COLOR_WRITEMASK                0x0C23
+#define GL_UNPACK_ALIGNMENT               0x0CF5
+#define GL_PACK_ALIGNMENT                 0x0D05
+#define GL_MAX_TEXTURE_SIZE               0x0D33
+#define GL_MAX_VIEWPORT_DIMS              0x0D3A
+#define GL_SUBPIXEL_BITS                  0x0D50
+#define GL_RED_BITS                       0x0D52
+#define GL_GREEN_BITS                     0x0D53
+#define GL_BLUE_BITS                      0x0D54
+#define GL_ALPHA_BITS                     0x0D55
+#define GL_DEPTH_BITS                     0x0D56
+#define GL_STENCIL_BITS                   0x0D57
+#define GL_POLYGON_OFFSET_UNITS           0x2A00
+/*      GL_POLYGON_OFFSET_FILL */
+#define GL_POLYGON_OFFSET_FACTOR          0x8038
+#define GL_TEXTURE_BINDING_2D             0x8069
+#define GL_SAMPLE_BUFFERS                 0x80A8
+#define GL_SAMPLES                        0x80A9
+#define GL_SAMPLE_COVERAGE_VALUE          0x80AA
+#define GL_SAMPLE_COVERAGE_INVERT         0x80AB
+
+/* GetTextureParameter */
+/*      GL_TEXTURE_MAG_FILTER */
+/*      GL_TEXTURE_MIN_FILTER */
+/*      GL_TEXTURE_WRAP_S */
+/*      GL_TEXTURE_WRAP_T */
+
+#define GL_NUM_COMPRESSED_TEXTURE_FORMATS 0x86A2
+#define GL_COMPRESSED_TEXTURE_FORMATS     0x86A3
+
+/* HintMode */
+#define GL_DONT_CARE                      0x1100
+#define GL_FASTEST                        0x1101
+#define GL_NICEST                         0x1102
+
+/* HintTarget */
+#define GL_GENERATE_MIPMAP_HINT            0x8192
+
+/* DataType */
+#define GL_BYTE                           0x1400
+#define GL_UNSIGNED_BYTE                  0x1401
+#define GL_SHORT                          0x1402
+#define GL_UNSIGNED_SHORT                 0x1403
+#define GL_INT                            0x1404
+#define GL_UNSIGNED_INT                   0x1405
+#define GL_FLOAT                          0x1406
+#define GL_FIXED                          0x140C
+
+/* PixelFormat */
+#define GL_DEPTH_COMPONENT                0x1902
+#define GL_ALPHA                          0x1906
+#define GL_RGB                            0x1907
+#define GL_RGBA                           0x1908
+#define GL_LUMINANCE                      0x1909
+#define GL_LUMINANCE_ALPHA                0x190A
+
+/* PixelType */
+/*      GL_UNSIGNED_BYTE */
+#define GL_UNSIGNED_SHORT_4_4_4_4         0x8033
+#define GL_UNSIGNED_SHORT_5_5_5_1         0x8034
+#define GL_UNSIGNED_SHORT_5_6_5           0x8363
+
+/* Shaders */
+#define GL_FRAGMENT_SHADER                  0x8B30
+#define GL_VERTEX_SHADER                    0x8B31
+#define GL_MAX_VERTEX_ATTRIBS               0x8869
+#define GL_MAX_VERTEX_UNIFORM_VECTORS       0x8DFB
+#define GL_MAX_VARYING_VECTORS              0x8DFC
+#define GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS 0x8B4D
+#define GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS   0x8B4C
+#define GL_MAX_TEXTURE_IMAGE_UNITS          0x8872
+#define GL_MAX_FRAGMENT_UNIFORM_VECTORS     0x8DFD
+#define GL_SHADER_TYPE                      0x8B4F
+#define GL_DELETE_STATUS                    0x8B80
+#define GL_LINK_STATUS                      0x8B82
+#define GL_VALIDATE_STATUS                  0x8B83
+#define GL_ATTACHED_SHADERS                 0x8B85
+#define GL_ACTIVE_UNIFORMS                  0x8B86
+#define GL_ACTIVE_UNIFORM_MAX_LENGTH        0x8B87
+#define GL_ACTIVE_ATTRIBUTES                0x8B89
+#define GL_ACTIVE_ATTRIBUTE_MAX_LENGTH      0x8B8A
+#define GL_SHADING_LANGUAGE_VERSION         0x8B8C
+#define GL_CURRENT_PROGRAM                  0x8B8D
+
+/* StencilFunction */
+#define GL_NEVER                          0x0200
+#define GL_LESS                           0x0201
+#define GL_EQUAL                          0x0202
+#define GL_LEQUAL                         0x0203
+#define GL_GREATER                        0x0204
+#define GL_NOTEQUAL                       0x0205
+#define GL_GEQUAL                         0x0206
+#define GL_ALWAYS                         0x0207
+
+/* StencilOp */
+/*      GL_ZERO */
+#define GL_KEEP                           0x1E00
+#define GL_REPLACE                        0x1E01
+#define GL_INCR                           0x1E02
+#define GL_DECR                           0x1E03
+#define GL_INVERT                         0x150A
+#define GL_INCR_WRAP                      0x8507
+#define GL_DECR_WRAP                      0x8508
+
+/* StringName */
+#define GL_VENDOR                         0x1F00
+#define GL_RENDERER                       0x1F01
+#define GL_VERSION                        0x1F02
+#define GL_EXTENSIONS                     0x1F03
+
+/* TextureMagFilter */
+#define GL_NEAREST                        0x2600
+#define GL_LINEAR                         0x2601
+
+/* TextureMinFilter */
+/*      GL_NEAREST */
+/*      GL_LINEAR */
+#define GL_NEAREST_MIPMAP_NEAREST         0x2700
+#define GL_LINEAR_MIPMAP_NEAREST          0x2701
+#define GL_NEAREST_MIPMAP_LINEAR          0x2702
+#define GL_LINEAR_MIPMAP_LINEAR           0x2703
+
+/* TextureParameterName */
+#define GL_TEXTURE_MAG_FILTER             0x2800
+#define GL_TEXTURE_MIN_FILTER             0x2801
+#define GL_TEXTURE_WRAP_S                 0x2802
+#define GL_TEXTURE_WRAP_T                 0x2803
+
+/* TextureTarget */
+/*      GL_TEXTURE_2D */
+#define GL_TEXTURE                        0x1702
+
+#define GL_TEXTURE_CUBE_MAP               0x8513
+#define GL_TEXTURE_BINDING_CUBE_MAP       0x8514
+#define GL_TEXTURE_CUBE_MAP_POSITIVE_X    0x8515
+#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X    0x8516
+#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y    0x8517
+#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y    0x8518
+#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z    0x8519
+#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z    0x851A
+#define GL_MAX_CUBE_MAP_TEXTURE_SIZE      0x851C
+
+/* TextureUnit */
+#define GL_TEXTURE0                       0x84C0
+#define GL_TEXTURE1                       0x84C1
+#define GL_TEXTURE2                       0x84C2
+#define GL_TEXTURE3                       0x84C3
+#define GL_TEXTURE4                       0x84C4
+#define GL_TEXTURE5                       0x84C5
+#define GL_TEXTURE6                       0x84C6
+#define GL_TEXTURE7                       0x84C7
+#define GL_TEXTURE8                       0x84C8
+#define GL_TEXTURE9                       0x84C9
+#define GL_TEXTURE10                      0x84CA
+#define GL_TEXTURE11                      0x84CB
+#define GL_TEXTURE12                      0x84CC
+#define GL_TEXTURE13                      0x84CD
+#define GL_TEXTURE14                      0x84CE
+#define GL_TEXTURE15                      0x84CF
+#define GL_TEXTURE16                      0x84D0
+#define GL_TEXTURE17                      0x84D1
+#define GL_TEXTURE18                      0x84D2
+#define GL_TEXTURE19                      0x84D3
+#define GL_TEXTURE20                      0x84D4
+#define GL_TEXTURE21                      0x84D5
+#define GL_TEXTURE22                      0x84D6
+#define GL_TEXTURE23                      0x84D7
+#define GL_TEXTURE24                      0x84D8
+#define GL_TEXTURE25                      0x84D9
+#define GL_TEXTURE26                      0x84DA
+#define GL_TEXTURE27                      0x84DB
+#define GL_TEXTURE28                      0x84DC
+#define GL_TEXTURE29                      0x84DD
+#define GL_TEXTURE30                      0x84DE
+#define GL_TEXTURE31                      0x84DF
+#define GL_ACTIVE_TEXTURE                 0x84E0
+
+/* TextureWrapMode */
+#define GL_REPEAT                         0x2901
+#define GL_CLAMP_TO_EDGE                  0x812F
+#define GL_MIRRORED_REPEAT                0x8370
+
+/* Uniform Types */
+#define GL_FLOAT_VEC2                     0x8B50
+#define GL_FLOAT_VEC3                     0x8B51
+#define GL_FLOAT_VEC4                     0x8B52
+#define GL_INT_VEC2                       0x8B53
+#define GL_INT_VEC3                       0x8B54
+#define GL_INT_VEC4                       0x8B55
+#define GL_BOOL                           0x8B56
+#define GL_BOOL_VEC2                      0x8B57
+#define GL_BOOL_VEC3                      0x8B58
+#define GL_BOOL_VEC4                      0x8B59
+#define GL_FLOAT_MAT2                     0x8B5A
+#define GL_FLOAT_MAT3                     0x8B5B
+#define GL_FLOAT_MAT4                     0x8B5C
+#define GL_SAMPLER_2D                     0x8B5E
+#define GL_SAMPLER_CUBE                   0x8B60
+
+/* Vertex Arrays */
+#define GL_VERTEX_ATTRIB_ARRAY_ENABLED        0x8622
+#define GL_VERTEX_ATTRIB_ARRAY_SIZE           0x8623
+#define GL_VERTEX_ATTRIB_ARRAY_STRIDE         0x8624
+#define GL_VERTEX_ATTRIB_ARRAY_TYPE           0x8625
+#define GL_VERTEX_ATTRIB_ARRAY_NORMALIZED     0x886A
+#define GL_VERTEX_ATTRIB_ARRAY_POINTER        0x8645
+#define GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING 0x889F
+
+/* Read Format */
+#define GL_IMPLEMENTATION_COLOR_READ_TYPE   0x8B9A
+#define GL_IMPLEMENTATION_COLOR_READ_FORMAT 0x8B9B
+
+/* Shader Source */
+#define GL_COMPILE_STATUS                 0x8B81
+#define GL_INFO_LOG_LENGTH                0x8B84
+#define GL_SHADER_SOURCE_LENGTH           0x8B88
+#define GL_SHADER_COMPILER                0x8DFA
+
+/* Shader Binary */
+#define GL_SHADER_BINARY_FORMATS          0x8DF8
+#define GL_NUM_SHADER_BINARY_FORMATS      0x8DF9
+
+/* Shader Precision-Specified Types */
+#define GL_LOW_FLOAT                      0x8DF0
+#define GL_MEDIUM_FLOAT                   0x8DF1
+#define GL_HIGH_FLOAT                     0x8DF2
+#define GL_LOW_INT                        0x8DF3
+#define GL_MEDIUM_INT                     0x8DF4
+#define GL_HIGH_INT                       0x8DF5
+
+/* Framebuffer Object. */
+#define GL_FRAMEBUFFER                    0x8D40
+#define GL_RENDERBUFFER                   0x8D41
+
+#define GL_RGBA4                          0x8056
+#define GL_RGB5_A1                        0x8057
+#define GL_RGB565                         0x8D62
+#define GL_DEPTH_COMPONENT16              0x81A5
+#define GL_STENCIL_INDEX8                 0x8D48
+
+#define GL_RENDERBUFFER_WIDTH             0x8D42
+#define GL_RENDERBUFFER_HEIGHT            0x8D43
+#define GL_RENDERBUFFER_INTERNAL_FORMAT   0x8D44
+#define GL_RENDERBUFFER_RED_SIZE          0x8D50
+#define GL_RENDERBUFFER_GREEN_SIZE        0x8D51
+#define GL_RENDERBUFFER_BLUE_SIZE         0x8D52
+#define GL_RENDERBUFFER_ALPHA_SIZE        0x8D53
+#define GL_RENDERBUFFER_DEPTH_SIZE        0x8D54
+#define GL_RENDERBUFFER_STENCIL_SIZE      0x8D55
+
+#define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE           0x8CD0
+#define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME           0x8CD1
+#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL         0x8CD2
+#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE 0x8CD3
+
+#define GL_COLOR_ATTACHMENT0              0x8CE0
+#define GL_DEPTH_ATTACHMENT               0x8D00
+#define GL_STENCIL_ATTACHMENT             0x8D20
+
+#define GL_NONE                           0
+
+#define GL_FRAMEBUFFER_COMPLETE                      0x8CD5
+#define GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT         0x8CD6
+#define GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT 0x8CD7
+#define GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS         0x8CD9
+#define GL_FRAMEBUFFER_UNSUPPORTED                   0x8CDD
+
+#define GL_FRAMEBUFFER_BINDING            0x8CA6
+#define GL_RENDERBUFFER_BINDING           0x8CA7
+#define GL_MAX_RENDERBUFFER_SIZE          0x84E8
+
+#define GL_INVALID_FRAMEBUFFER_OPERATION  0x0506
+
+/*-------------------------------------------------------------------------
+ * GL core functions.
+ *-----------------------------------------------------------------------*/
+
+GL_APICALL void         GL_APIENTRY glActiveTexture (GLenum texture);
+GL_APICALL void         GL_APIENTRY glAttachShader (GLuint program, GLuint shader);
+GL_APICALL void         GL_APIENTRY glBindAttribLocation (GLuint program, GLuint index, const GLchar* name);
+GL_APICALL void         GL_APIENTRY glBindBuffer (GLenum target, GLuint buffer);
+GL_APICALL void         GL_APIENTRY glBindFramebuffer (GLenum target, GLuint framebuffer);
+GL_APICALL void         GL_APIENTRY glBindRenderbuffer (GLenum target, GLuint renderbuffer);
+GL_APICALL void         GL_APIENTRY glBindTexture (GLenum target, GLuint texture);
+GL_APICALL void         GL_APIENTRY glBlendColor (GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha);
+GL_APICALL void         GL_APIENTRY glBlendEquation ( GLenum mode );
+GL_APICALL void         GL_APIENTRY glBlendEquationSeparate (GLenum modeRGB, GLenum modeAlpha);
+GL_APICALL void         GL_APIENTRY glBlendFunc (GLenum sfactor, GLenum dfactor);
+GL_APICALL void         GL_APIENTRY glBlendFuncSeparate (GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha);
+GL_APICALL void         GL_APIENTRY glBufferData (GLenum target, GLsizeiptr size, const GLvoid* data, GLenum usage);
+GL_APICALL void         GL_APIENTRY glBufferSubData (GLenum target, GLintptr offset, GLsizeiptr size, const GLvoid* data);
+GL_APICALL GLenum       GL_APIENTRY glCheckFramebufferStatus (GLenum target);
+GL_APICALL void         GL_APIENTRY glClear (GLbitfield mask);
+GL_APICALL void         GL_APIENTRY glClearColor (GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha);
+GL_APICALL void         GL_APIENTRY glClearDepthf (GLclampf depth);
+GL_APICALL void         GL_APIENTRY glClearStencil (GLint s);
+GL_APICALL void         GL_APIENTRY glColorMask (GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha);
+GL_APICALL void         GL_APIENTRY glCompileShader (GLuint shader);
+GL_APICALL void         GL_APIENTRY glCompressedTexImage2D (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const GLvoid* data);
+GL_APICALL void         GL_APIENTRY glCompressedTexSubImage2D (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const GLvoid* data);
+GL_APICALL void         GL_APIENTRY glCopyTexImage2D (GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border);
+GL_APICALL void         GL_APIENTRY glCopyTexSubImage2D (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height);
+GL_APICALL GLuint       GL_APIENTRY glCreateProgram (void);
+GL_APICALL GLuint       GL_APIENTRY glCreateShader (GLenum type);
+GL_APICALL void         GL_APIENTRY glCullFace (GLenum mode);
+GL_APICALL void         GL_APIENTRY glDeleteBuffers (GLsizei n, const GLuint* buffers);
+GL_APICALL void         GL_APIENTRY glDeleteFramebuffers (GLsizei n, const GLuint* framebuffers);
+GL_APICALL void         GL_APIENTRY glDeleteProgram (GLuint program);
+GL_APICALL void         GL_APIENTRY glDeleteRenderbuffers (GLsizei n, const GLuint* renderbuffers);
+GL_APICALL void         GL_APIENTRY glDeleteShader (GLuint shader);
+GL_APICALL void         GL_APIENTRY glDeleteTextures (GLsizei n, const GLuint* textures);
+GL_APICALL void         GL_APIENTRY glDepthFunc (GLenum func);
+GL_APICALL void         GL_APIENTRY glDepthMask (GLboolean flag);
+GL_APICALL void         GL_APIENTRY glDepthRangef (GLclampf zNear, GLclampf zFar);
+GL_APICALL void         GL_APIENTRY glDetachShader (GLuint program, GLuint shader);
+GL_APICALL void         GL_APIENTRY glDisable (GLenum cap);
+GL_APICALL void         GL_APIENTRY glDisableVertexAttribArray (GLuint index);
+GL_APICALL void         GL_APIENTRY glDrawArrays (GLenum mode, GLint first, GLsizei count);
+GL_APICALL void         GL_APIENTRY glDrawElements (GLenum mode, GLsizei count, GLenum type, const GLvoid* indices);
+GL_APICALL void         GL_APIENTRY glEnable (GLenum cap);
+GL_APICALL void         GL_APIENTRY glEnableVertexAttribArray (GLuint index);
+GL_APICALL void         GL_APIENTRY glFinish (void);
+GL_APICALL void         GL_APIENTRY glFlush (void);
+GL_APICALL void         GL_APIENTRY glFramebufferRenderbuffer (GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer);
+GL_APICALL void         GL_APIENTRY glFramebufferTexture2D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
+GL_APICALL void         GL_APIENTRY glFrontFace (GLenum mode);
+GL_APICALL void         GL_APIENTRY glGenBuffers (GLsizei n, GLuint* buffers);
+GL_APICALL void         GL_APIENTRY glGenerateMipmap (GLenum target);
+GL_APICALL void         GL_APIENTRY glGenFramebuffers (GLsizei n, GLuint* framebuffers);
+GL_APICALL void         GL_APIENTRY glGenRenderbuffers (GLsizei n, GLuint* renderbuffers);
+GL_APICALL void         GL_APIENTRY glGenTextures (GLsizei n, GLuint* textures);
+GL_APICALL void         GL_APIENTRY glGetActiveAttrib (GLuint program, GLuint index, GLsizei bufsize, GLsizei* length, GLint* size, GLenum* type, GLchar* name);
+GL_APICALL void         GL_APIENTRY glGetActiveUniform (GLuint program, GLuint index, GLsizei bufsize, GLsizei* length, GLint* size, GLenum* type, GLchar* name);
+GL_APICALL void         GL_APIENTRY glGetAttachedShaders (GLuint program, GLsizei maxcount, GLsizei* count, GLuint* shaders);
+GL_APICALL GLint        GL_APIENTRY glGetAttribLocation (GLuint program, const GLchar* name);
+GL_APICALL void         GL_APIENTRY glGetBooleanv (GLenum pname, GLboolean* params);
+GL_APICALL void         GL_APIENTRY glGetBufferParameteriv (GLenum target, GLenum pname, GLint* params);
+GL_APICALL GLenum       GL_APIENTRY glGetError (void);
+GL_APICALL void         GL_APIENTRY glGetFloatv (GLenum pname, GLfloat* params);
+GL_APICALL void         GL_APIENTRY glGetFramebufferAttachmentParameteriv (GLenum target, GLenum attachment, GLenum pname, GLint* params);
+GL_APICALL void         GL_APIENTRY glGetIntegerv (GLenum pname, GLint* params);
+GL_APICALL void         GL_APIENTRY glGetProgramiv (GLuint program, GLenum pname, GLint* params);
+GL_APICALL void         GL_APIENTRY glGetProgramInfoLog (GLuint program, GLsizei bufsize, GLsizei* length, GLchar* infolog);
+GL_APICALL void         GL_APIENTRY glGetRenderbufferParameteriv (GLenum target, GLenum pname, GLint* params);
+GL_APICALL void         GL_APIENTRY glGetShaderiv (GLuint shader, GLenum pname, GLint* params);
+GL_APICALL void         GL_APIENTRY glGetShaderInfoLog (GLuint shader, GLsizei bufsize, GLsizei* length, GLchar* infolog);
+GL_APICALL void         GL_APIENTRY glGetShaderPrecisionFormat (GLenum shadertype, GLenum precisiontype, GLint* range, GLint* precision);
+GL_APICALL void         GL_APIENTRY glGetShaderSource (GLuint shader, GLsizei bufsize, GLsizei* length, GLchar* source);
+GL_APICALL const GLubyte* GL_APIENTRY glGetString (GLenum name);
+GL_APICALL void         GL_APIENTRY glGetTexParameterfv (GLenum target, GLenum pname, GLfloat* params);
+GL_APICALL void         GL_APIENTRY glGetTexParameteriv (GLenum target, GLenum pname, GLint* params);
+GL_APICALL void         GL_APIENTRY glGetUniformfv (GLuint program, GLint location, GLfloat* params);
+GL_APICALL void         GL_APIENTRY glGetUniformiv (GLuint program, GLint location, GLint* params);
+GL_APICALL GLint        GL_APIENTRY glGetUniformLocation (GLuint program, const GLchar* name);
+GL_APICALL void         GL_APIENTRY glGetVertexAttribfv (GLuint index, GLenum pname, GLfloat* params);
+GL_APICALL void         GL_APIENTRY glGetVertexAttribiv (GLuint index, GLenum pname, GLint* params);
+GL_APICALL void         GL_APIENTRY glGetVertexAttribPointerv (GLuint index, GLenum pname, GLvoid** pointer);
+GL_APICALL void         GL_APIENTRY glHint (GLenum target, GLenum mode);
+GL_APICALL GLboolean    GL_APIENTRY glIsBuffer (GLuint buffer);
+GL_APICALL GLboolean    GL_APIENTRY glIsEnabled (GLenum cap);
+GL_APICALL GLboolean    GL_APIENTRY glIsFramebuffer (GLuint framebuffer);
+GL_APICALL GLboolean    GL_APIENTRY glIsProgram (GLuint program);
+GL_APICALL GLboolean    GL_APIENTRY glIsRenderbuffer (GLuint renderbuffer);
+GL_APICALL GLboolean    GL_APIENTRY glIsShader (GLuint shader);
+GL_APICALL GLboolean    GL_APIENTRY glIsTexture (GLuint texture);
+GL_APICALL void         GL_APIENTRY glLineWidth (GLfloat width);
+GL_APICALL void         GL_APIENTRY glLinkProgram (GLuint program);
+GL_APICALL void         GL_APIENTRY glPixelStorei (GLenum pname, GLint param);
+GL_APICALL void         GL_APIENTRY glPolygonOffset (GLfloat factor, GLfloat units);
+GL_APICALL void         GL_APIENTRY glReadPixels (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid* pixels);
+GL_APICALL void         GL_APIENTRY glReleaseShaderCompiler (void);
+GL_APICALL void         GL_APIENTRY glRenderbufferStorage (GLenum target, GLenum internalformat, GLsizei width, GLsizei height);
+GL_APICALL void         GL_APIENTRY glSampleCoverage (GLclampf value, GLboolean invert);
+GL_APICALL void         GL_APIENTRY glScissor (GLint x, GLint y, GLsizei width, GLsizei height);
+GL_APICALL void         GL_APIENTRY glShaderBinary (GLsizei n, const GLuint* shaders, GLenum binaryformat, const GLvoid* binary, GLsizei length);
+GL_APICALL void         GL_APIENTRY glShaderSource (GLuint shader, GLsizei count, const GLchar* const* string, const GLint* length);
+GL_APICALL void         GL_APIENTRY glStencilFunc (GLenum func, GLint ref, GLuint mask);
+GL_APICALL void         GL_APIENTRY glStencilFuncSeparate (GLenum face, GLenum func, GLint ref, GLuint mask);
+GL_APICALL void         GL_APIENTRY glStencilMask (GLuint mask);
+GL_APICALL void         GL_APIENTRY glStencilMaskSeparate (GLenum face, GLuint mask);
+GL_APICALL void         GL_APIENTRY glStencilOp (GLenum fail, GLenum zfail, GLenum zpass);
+GL_APICALL void         GL_APIENTRY glStencilOpSeparate (GLenum face, GLenum fail, GLenum zfail, GLenum zpass);
+GL_APICALL void         GL_APIENTRY glTexImage2D (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid* pixels);
+GL_APICALL void         GL_APIENTRY glTexParameterf (GLenum target, GLenum pname, GLfloat param);
+GL_APICALL void         GL_APIENTRY glTexParameterfv (GLenum target, GLenum pname, const GLfloat* params);
+GL_APICALL void         GL_APIENTRY glTexParameteri (GLenum target, GLenum pname, GLint param);
+GL_APICALL void         GL_APIENTRY glTexParameteriv (GLenum target, GLenum pname, const GLint* params);
+GL_APICALL void         GL_APIENTRY glTexSubImage2D (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid* pixels);
+GL_APICALL void         GL_APIENTRY glUniform1f (GLint location, GLfloat x);
+GL_APICALL void         GL_APIENTRY glUniform1fv (GLint location, GLsizei count, const GLfloat* v);
+GL_APICALL void         GL_APIENTRY glUniform1i (GLint location, GLint x);
+GL_APICALL void         GL_APIENTRY glUniform1iv (GLint location, GLsizei count, const GLint* v);
+GL_APICALL void         GL_APIENTRY glUniform2f (GLint location, GLfloat x, GLfloat y);
+GL_APICALL void         GL_APIENTRY glUniform2fv (GLint location, GLsizei count, const GLfloat* v);
+GL_APICALL void         GL_APIENTRY glUniform2i (GLint location, GLint x, GLint y);
+GL_APICALL void         GL_APIENTRY glUniform2iv (GLint location, GLsizei count, const GLint* v);
+GL_APICALL void         GL_APIENTRY glUniform3f (GLint location, GLfloat x, GLfloat y, GLfloat z);
+GL_APICALL void         GL_APIENTRY glUniform3fv (GLint location, GLsizei count, const GLfloat* v);
+GL_APICALL void         GL_APIENTRY glUniform3i (GLint location, GLint x, GLint y, GLint z);
+GL_APICALL void         GL_APIENTRY glUniform3iv (GLint location, GLsizei count, const GLint* v);
+GL_APICALL void         GL_APIENTRY glUniform4f (GLint location, GLfloat x, GLfloat y, GLfloat z, GLfloat w);
+GL_APICALL void         GL_APIENTRY glUniform4fv (GLint location, GLsizei count, const GLfloat* v);
+GL_APICALL void         GL_APIENTRY glUniform4i (GLint location, GLint x, GLint y, GLint z, GLint w);
+GL_APICALL void         GL_APIENTRY glUniform4iv (GLint location, GLsizei count, const GLint* v);
+GL_APICALL void         GL_APIENTRY glUniformMatrix2fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);
+GL_APICALL void         GL_APIENTRY glUniformMatrix3fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);
+GL_APICALL void         GL_APIENTRY glUniformMatrix4fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);
+GL_APICALL void         GL_APIENTRY glUseProgram (GLuint program);
+GL_APICALL void         GL_APIENTRY glValidateProgram (GLuint program);
+GL_APICALL void         GL_APIENTRY glVertexAttrib1f (GLuint indx, GLfloat x);
+GL_APICALL void         GL_APIENTRY glVertexAttrib1fv (GLuint indx, const GLfloat* values);
+GL_APICALL void         GL_APIENTRY glVertexAttrib2f (GLuint indx, GLfloat x, GLfloat y);
+GL_APICALL void         GL_APIENTRY glVertexAttrib2fv (GLuint indx, const GLfloat* values);
+GL_APICALL void         GL_APIENTRY glVertexAttrib3f (GLuint indx, GLfloat x, GLfloat y, GLfloat z);
+GL_APICALL void         GL_APIENTRY glVertexAttrib3fv (GLuint indx, const GLfloat* values);
+GL_APICALL void         GL_APIENTRY glVertexAttrib4f (GLuint indx, GLfloat x, GLfloat y, GLfloat z, GLfloat w);
+GL_APICALL void         GL_APIENTRY glVertexAttrib4fv (GLuint indx, const GLfloat* values);
+GL_APICALL void         GL_APIENTRY glVertexAttribPointer (GLuint indx, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid* ptr);
+GL_APICALL void         GL_APIENTRY glViewport (GLint x, GLint y, GLsizei width, GLsizei height);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __gl2_h_ */
\ No newline at end of file
diff --git a/codegen/headers/gl2ext.h b/codegen/headers/gl2ext.h
new file mode 100644
index 0000000..6aecb15
--- /dev/null
+++ b/codegen/headers/gl2ext.h
@@ -0,0 +1,2051 @@
+#ifndef __gl2ext_h_
+#define __gl2ext_h_
+
+/* $Revision: 22161 $ on $Date:: 2013-06-25 08:17:27 -0700 #$ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * This document is licensed under the SGI Free Software B License Version
+ * 2.0. For details, see http://oss.sgi.com/projects/FreeB/ .
+ */
+
+#ifndef GL_APIENTRYP
+#   define GL_APIENTRYP GL_APIENTRY*
+#endif
+
+/* New types shared by several extensions */
+
+#ifndef __gl3_h_
+/* These are defined with respect to <inttypes.h> in the
+ * Apple extension spec, but they are also used by non-APPLE
+ * extensions, and in the Khronos header we use the Khronos
+ * portable types in khrplatform.h, which must be defined.
+ */
+typedef khronos_int64_t GLint64;
+typedef khronos_uint64_t GLuint64;
+typedef struct __GLsync *GLsync;
+#endif
+
+
+/*------------------------------------------------------------------------*
+ * OES extension tokens
+ *------------------------------------------------------------------------*/
+
+/* GL_OES_compressed_ETC1_RGB8_texture */
+#ifndef GL_OES_compressed_ETC1_RGB8_texture
+#define GL_ETC1_RGB8_OES                                        0x8D64
+#endif
+
+/* GL_OES_compressed_paletted_texture */
+#ifndef GL_OES_compressed_paletted_texture
+#define GL_PALETTE4_RGB8_OES                                    0x8B90
+#define GL_PALETTE4_RGBA8_OES                                   0x8B91
+#define GL_PALETTE4_R5_G6_B5_OES                                0x8B92
+#define GL_PALETTE4_RGBA4_OES                                   0x8B93
+#define GL_PALETTE4_RGB5_A1_OES                                 0x8B94
+#define GL_PALETTE8_RGB8_OES                                    0x8B95
+#define GL_PALETTE8_RGBA8_OES                                   0x8B96
+#define GL_PALETTE8_R5_G6_B5_OES                                0x8B97
+#define GL_PALETTE8_RGBA4_OES                                   0x8B98
+#define GL_PALETTE8_RGB5_A1_OES                                 0x8B99
+#endif
+
+/* GL_OES_depth24 */
+#ifndef GL_OES_depth24
+#define GL_DEPTH_COMPONENT24_OES                                0x81A6
+#endif
+
+/* GL_OES_depth32 */
+#ifndef GL_OES_depth32
+#define GL_DEPTH_COMPONENT32_OES                                0x81A7
+#endif
+
+/* GL_OES_depth_texture */
+/* No new tokens introduced by this extension. */
+
+/* GL_OES_EGL_image */
+#ifndef GL_OES_EGL_image
+typedef void* GLeglImageOES;
+#endif
+
+/* GL_OES_EGL_image_external */
+#ifndef GL_OES_EGL_image_external
+/* GLeglImageOES defined in GL_OES_EGL_image already. */
+#define GL_TEXTURE_EXTERNAL_OES                                 0x8D65
+#define GL_SAMPLER_EXTERNAL_OES                                 0x8D66
+#define GL_TEXTURE_BINDING_EXTERNAL_OES                         0x8D67
+#define GL_REQUIRED_TEXTURE_IMAGE_UNITS_OES                     0x8D68
+#endif
+
+/* GL_OES_element_index_uint */
+#ifndef GL_OES_element_index_uint
+#define GL_UNSIGNED_INT                                         0x1405
+#endif
+
+/* GL_OES_get_program_binary */
+#ifndef GL_OES_get_program_binary
+#define GL_PROGRAM_BINARY_LENGTH_OES                            0x8741
+#define GL_NUM_PROGRAM_BINARY_FORMATS_OES                       0x87FE
+#define GL_PROGRAM_BINARY_FORMATS_OES                           0x87FF
+#endif
+
+/* GL_OES_mapbuffer */
+#ifndef GL_OES_mapbuffer
+#define GL_WRITE_ONLY_OES                                       0x88B9
+#define GL_BUFFER_ACCESS_OES                                    0x88BB
+#define GL_BUFFER_MAPPED_OES                                    0x88BC
+#define GL_BUFFER_MAP_POINTER_OES                               0x88BD
+#endif
+
+/* GL_OES_packed_depth_stencil */
+#ifndef GL_OES_packed_depth_stencil
+#define GL_DEPTH_STENCIL_OES                                    0x84F9
+#define GL_UNSIGNED_INT_24_8_OES                                0x84FA
+#define GL_DEPTH24_STENCIL8_OES                                 0x88F0
+#endif
+
+/* GL_OES_required_internalformat */
+#ifndef GL_OES_required_internalformat
+#define GL_ALPHA8_OES                                           0x803C
+#define GL_DEPTH_COMPONENT16_OES                                0x81A5
+/* reuse GL_DEPTH_COMPONENT24_OES */
+/* reuse GL_DEPTH24_STENCIL8_OES */
+/* reuse GL_DEPTH_COMPONENT32_OES */
+#define GL_LUMINANCE4_ALPHA4_OES                                0x8043
+#define GL_LUMINANCE8_ALPHA8_OES                                0x8045
+#define GL_LUMINANCE8_OES                                       0x8040
+#define GL_RGBA4_OES                                            0x8056
+#define GL_RGB5_A1_OES                                          0x8057
+#define GL_RGB565_OES                                           0x8D62
+/* reuse GL_RGB8_OES */
+/* reuse GL_RGBA8_OES */
+/* reuse GL_RGB10_EXT */
+/* reuse GL_RGB10_A2_EXT */
+#endif
+
+/* GL_OES_rgb8_rgba8 */
+#ifndef GL_OES_rgb8_rgba8
+#define GL_RGB8_OES                                             0x8051
+#define GL_RGBA8_OES                                            0x8058
+#endif
+
+/* GL_OES_standard_derivatives */
+#ifndef GL_OES_standard_derivatives
+#define GL_FRAGMENT_SHADER_DERIVATIVE_HINT_OES                  0x8B8B
+#endif
+
+/* GL_OES_stencil1 */
+#ifndef GL_OES_stencil1
+#define GL_STENCIL_INDEX1_OES                                   0x8D46
+#endif
+
+/* GL_OES_stencil4 */
+#ifndef GL_OES_stencil4
+#define GL_STENCIL_INDEX4_OES                                   0x8D47
+#endif
+
+#ifndef GL_OES_surfaceless_context
+#define GL_FRAMEBUFFER_UNDEFINED_OES                            0x8219
+#endif
+
+/* GL_OES_texture_3D */
+#ifndef GL_OES_texture_3D
+#define GL_TEXTURE_WRAP_R_OES                                   0x8072
+#define GL_TEXTURE_3D_OES                                       0x806F
+#define GL_TEXTURE_BINDING_3D_OES                               0x806A
+#define GL_MAX_3D_TEXTURE_SIZE_OES                              0x8073
+#define GL_SAMPLER_3D_OES                                       0x8B5F
+#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_3D_ZOFFSET_OES        0x8CD4
+#endif
+
+/* GL_OES_texture_float */
+/* No new tokens introduced by this extension. */
+
+/* GL_OES_texture_float_linear */
+/* No new tokens introduced by this extension. */
+
+/* GL_OES_texture_half_float */
+#ifndef GL_OES_texture_half_float
+#define GL_HALF_FLOAT_OES                                       0x8D61
+#endif
+
+/* GL_OES_texture_half_float_linear */
+/* No new tokens introduced by this extension. */
+
+/* GL_OES_texture_npot */
+/* No new tokens introduced by this extension. */
+
+/* GL_OES_vertex_array_object */
+#ifndef GL_OES_vertex_array_object
+#define GL_VERTEX_ARRAY_BINDING_OES                             0x85B5
+#endif
+
+/* GL_OES_vertex_half_float */
+/* GL_HALF_FLOAT_OES defined in GL_OES_texture_half_float already. */
+
+/* GL_OES_vertex_type_10_10_10_2 */
+#ifndef GL_OES_vertex_type_10_10_10_2
+#define GL_UNSIGNED_INT_10_10_10_2_OES                          0x8DF6
+#define GL_INT_10_10_10_2_OES                                   0x8DF7
+#endif
+
+/*------------------------------------------------------------------------*
+ * KHR extension tokens
+ *------------------------------------------------------------------------*/
+
+#ifndef GL_KHR_debug
+typedef void (GL_APIENTRYP GLDEBUGPROCKHR)(GLenum source,GLenum type,GLuint id,GLenum severity,GLsizei length,const GLchar *message,const GLvoid *userParam);
+#define GL_DEBUG_OUTPUT_SYNCHRONOUS_KHR                         0x8242
+#define GL_DEBUG_NEXT_LOGGED_MESSAGE_LENGTH_KHR                 0x8243
+#define GL_DEBUG_CALLBACK_FUNCTION_KHR                          0x8244
+#define GL_DEBUG_CALLBACK_USER_PARAM_KHR                        0x8245
+#define GL_DEBUG_SOURCE_API_KHR                                 0x8246
+#define GL_DEBUG_SOURCE_WINDOW_SYSTEM_KHR                       0x8247
+#define GL_DEBUG_SOURCE_SHADER_COMPILER_KHR                     0x8248
+#define GL_DEBUG_SOURCE_THIRD_PARTY_KHR                         0x8249
+#define GL_DEBUG_SOURCE_APPLICATION_KHR                         0x824A
+#define GL_DEBUG_SOURCE_OTHER_KHR                               0x824B
+#define GL_DEBUG_TYPE_ERROR_KHR                                 0x824C
+#define GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_KHR                   0x824D
+#define GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_KHR                    0x824E
+#define GL_DEBUG_TYPE_PORTABILITY_KHR                           0x824F
+#define GL_DEBUG_TYPE_PERFORMANCE_KHR                           0x8250
+#define GL_DEBUG_TYPE_OTHER_KHR                                 0x8251
+#define GL_DEBUG_TYPE_MARKER_KHR                                0x8268
+#define GL_DEBUG_TYPE_PUSH_GROUP_KHR                            0x8269
+#define GL_DEBUG_TYPE_POP_GROUP_KHR                             0x826A
+#define GL_DEBUG_SEVERITY_NOTIFICATION_KHR                      0x826B
+#define GL_MAX_DEBUG_GROUP_STACK_DEPTH_KHR                      0x826C
+#define GL_DEBUG_GROUP_STACK_DEPTH_KHR                          0x826D
+#define GL_BUFFER_KHR                                           0x82E0
+#define GL_SHADER_KHR                                           0x82E1
+#define GL_PROGRAM_KHR                                          0x82E2
+#define GL_QUERY_KHR                                            0x82E3
+/* PROGRAM_PIPELINE only in GL */
+#define GL_SAMPLER_KHR                                          0x82E6
+/* DISPLAY_LIST only in GL */
+#define GL_MAX_LABEL_LENGTH_KHR                                 0x82E8
+#define GL_MAX_DEBUG_MESSAGE_LENGTH_KHR                         0x9143
+#define GL_MAX_DEBUG_LOGGED_MESSAGES_KHR                        0x9144
+#define GL_DEBUG_LOGGED_MESSAGES_KHR                            0x9145
+#define GL_DEBUG_SEVERITY_HIGH_KHR                              0x9146
+#define GL_DEBUG_SEVERITY_MEDIUM_KHR                            0x9147
+#define GL_DEBUG_SEVERITY_LOW_KHR                               0x9148
+#define GL_DEBUG_OUTPUT_KHR                                     0x92E0
+#define GL_CONTEXT_FLAG_DEBUG_BIT_KHR                           0x00000002
+#define GL_STACK_OVERFLOW_KHR                                   0x0503
+#define GL_STACK_UNDERFLOW_KHR                                  0x0504
+#endif
+
+#ifndef GL_KHR_texture_compression_astc_ldr
+#define GL_COMPRESSED_RGBA_ASTC_4x4_KHR                         0x93B0
+#define GL_COMPRESSED_RGBA_ASTC_5x4_KHR                         0x93B1
+#define GL_COMPRESSED_RGBA_ASTC_5x5_KHR                         0x93B2
+#define GL_COMPRESSED_RGBA_ASTC_6x5_KHR                         0x93B3
+#define GL_COMPRESSED_RGBA_ASTC_6x6_KHR                         0x93B4
+#define GL_COMPRESSED_RGBA_ASTC_8x5_KHR                         0x93B5
+#define GL_COMPRESSED_RGBA_ASTC_8x6_KHR                         0x93B6
+#define GL_COMPRESSED_RGBA_ASTC_8x8_KHR                         0x93B7
+#define GL_COMPRESSED_RGBA_ASTC_10x5_KHR                        0x93B8
+#define GL_COMPRESSED_RGBA_ASTC_10x6_KHR                        0x93B9
+#define GL_COMPRESSED_RGBA_ASTC_10x8_KHR                        0x93BA
+#define GL_COMPRESSED_RGBA_ASTC_10x10_KHR                       0x93BB
+#define GL_COMPRESSED_RGBA_ASTC_12x10_KHR                       0x93BC
+#define GL_COMPRESSED_RGBA_ASTC_12x12_KHR                       0x93BD
+#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR                 0x93D0
+#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR                 0x93D1
+#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR                 0x93D2
+#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR                 0x93D3
+#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR                 0x93D4
+#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR                 0x93D5
+#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR                 0x93D6
+#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR                 0x93D7
+#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR                0x93D8
+#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR                0x93D9
+#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR                0x93DA
+#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR               0x93DB
+#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR               0x93DC
+#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR               0x93DD
+#endif
+
+/*------------------------------------------------------------------------*
+ * AMD extension tokens
+ *------------------------------------------------------------------------*/
+
+/* GL_AMD_compressed_3DC_texture */
+#ifndef GL_AMD_compressed_3DC_texture
+#define GL_3DC_X_AMD                                            0x87F9
+#define GL_3DC_XY_AMD                                           0x87FA
+#endif
+
+/* GL_AMD_compressed_ATC_texture */
+#ifndef GL_AMD_compressed_ATC_texture
+#define GL_ATC_RGB_AMD                                          0x8C92
+#define GL_ATC_RGBA_EXPLICIT_ALPHA_AMD                          0x8C93
+#define GL_ATC_RGBA_INTERPOLATED_ALPHA_AMD                      0x87EE
+#endif
+
+/* GL_AMD_performance_monitor */
+#ifndef GL_AMD_performance_monitor
+#define GL_COUNTER_TYPE_AMD                                     0x8BC0
+#define GL_COUNTER_RANGE_AMD                                    0x8BC1
+#define GL_UNSIGNED_INT64_AMD                                   0x8BC2
+#define GL_PERCENTAGE_AMD                                       0x8BC3
+#define GL_PERFMON_RESULT_AVAILABLE_AMD                         0x8BC4
+#define GL_PERFMON_RESULT_SIZE_AMD                              0x8BC5
+#define GL_PERFMON_RESULT_AMD                                   0x8BC6
+#endif
+
+/* GL_AMD_program_binary_Z400 */
+#ifndef GL_AMD_program_binary_Z400
+#define GL_Z400_BINARY_AMD                                      0x8740
+#endif
+
+/*------------------------------------------------------------------------*
+ * ANGLE extension tokens
+ *------------------------------------------------------------------------*/
+
+/* GL_ANGLE_depth_texture */
+#ifndef GL_ANGLE_depth_texture
+#define GL_DEPTH_COMPONENT                                      0x1902
+#define GL_DEPTH_STENCIL_OES                                    0x84F9
+#define GL_UNSIGNED_SHORT                                       0x1403
+#define GL_UNSIGNED_INT                                         0x1405
+#define GL_UNSIGNED_INT_24_8_OES                                0x84FA
+#define GL_DEPTH_COMPONENT16                                    0x81A5
+#define GL_DEPTH_COMPONENT32_OES                                0x81A7
+#define GL_DEPTH24_STENCIL8_OES                                 0x88F0
+#endif
+
+/* GL_ANGLE_framebuffer_blit */
+#ifndef GL_ANGLE_framebuffer_blit
+#define GL_READ_FRAMEBUFFER_ANGLE                               0x8CA8
+#define GL_DRAW_FRAMEBUFFER_ANGLE                               0x8CA9
+#define GL_DRAW_FRAMEBUFFER_BINDING_ANGLE                       0x8CA6
+#define GL_READ_FRAMEBUFFER_BINDING_ANGLE                       0x8CAA
+#endif
+
+/* GL_ANGLE_framebuffer_multisample */
+#ifndef GL_ANGLE_framebuffer_multisample
+#define GL_RENDERBUFFER_SAMPLES_ANGLE                           0x8CAB
+#define GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_ANGLE             0x8D56
+#define GL_MAX_SAMPLES_ANGLE                                    0x8D57
+#endif
+
+/* GL_ANGLE_instanced_arrays */
+#ifndef GL_ANGLE_instanced_arrays
+#define GL_VERTEX_ATTRIB_ARRAY_DIVISOR_ANGLE                    0x88FE
+#endif
+
+/* GL_ANGLE_pack_reverse_row_order */
+#ifndef GL_ANGLE_pack_reverse_row_order
+#define GL_PACK_REVERSE_ROW_ORDER_ANGLE                         0x93A4
+#endif
+
+/* GL_ANGLE_program_binary */
+#ifndef GL_ANGLE_program_binary
+#define GL_PROGRAM_BINARY_ANGLE                                 0x93A6
+#endif
+
+/* GL_ANGLE_texture_compression_dxt3 */
+#ifndef GL_ANGLE_texture_compression_dxt3
+#define GL_COMPRESSED_RGBA_S3TC_DXT3_ANGLE                      0x83F2
+#endif
+
+/* GL_ANGLE_texture_compression_dxt5 */
+#ifndef GL_ANGLE_texture_compression_dxt5
+#define GL_COMPRESSED_RGBA_S3TC_DXT5_ANGLE                      0x83F3
+#endif
+
+/* GL_ANGLE_texture_usage */
+#ifndef GL_ANGLE_texture_usage
+#define GL_TEXTURE_USAGE_ANGLE                                  0x93A2
+#define GL_FRAMEBUFFER_ATTACHMENT_ANGLE                         0x93A3
+#endif
+
+/* GL_ANGLE_translated_shader_source */
+#ifndef GL_ANGLE_translated_shader_source
+#define GL_TRANSLATED_SHADER_SOURCE_LENGTH_ANGLE                0x93A0
+#endif
+
+/*------------------------------------------------------------------------*
+ * APPLE extension tokens
+ *------------------------------------------------------------------------*/
+
+/* GL_APPLE_copy_texture_levels */
+/* No new tokens introduced by this extension. */
+
+/* GL_APPLE_framebuffer_multisample */
+#ifndef GL_APPLE_framebuffer_multisample
+#define GL_RENDERBUFFER_SAMPLES_APPLE                           0x8CAB
+#define GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_APPLE             0x8D56
+#define GL_MAX_SAMPLES_APPLE                                    0x8D57
+#define GL_READ_FRAMEBUFFER_APPLE                               0x8CA8
+#define GL_DRAW_FRAMEBUFFER_APPLE                               0x8CA9
+#define GL_DRAW_FRAMEBUFFER_BINDING_APPLE                       0x8CA6
+#define GL_READ_FRAMEBUFFER_BINDING_APPLE                       0x8CAA
+#endif
+
+/* GL_APPLE_rgb_422 */
+#ifndef GL_APPLE_rgb_422
+#define GL_RGB_422_APPLE                                        0x8A1F
+#define GL_UNSIGNED_SHORT_8_8_APPLE                             0x85BA
+#define GL_UNSIGNED_SHORT_8_8_REV_APPLE                         0x85BB
+#endif
+
+/* GL_APPLE_sync */
+#ifndef GL_APPLE_sync
+
+#define GL_SYNC_OBJECT_APPLE                                    0x8A53
+#define GL_MAX_SERVER_WAIT_TIMEOUT_APPLE                        0x9111
+#define GL_OBJECT_TYPE_APPLE                                    0x9112
+#define GL_SYNC_CONDITION_APPLE                                 0x9113
+#define GL_SYNC_STATUS_APPLE                                    0x9114
+#define GL_SYNC_FLAGS_APPLE                                     0x9115
+#define GL_SYNC_FENCE_APPLE                                     0x9116
+#define GL_SYNC_GPU_COMMANDS_COMPLETE_APPLE                     0x9117
+#define GL_UNSIGNALED_APPLE                                     0x9118
+#define GL_SIGNALED_APPLE                                       0x9119
+#define GL_ALREADY_SIGNALED_APPLE                               0x911A
+#define GL_TIMEOUT_EXPIRED_APPLE                                0x911B
+#define GL_CONDITION_SATISFIED_APPLE                            0x911C
+#define GL_WAIT_FAILED_APPLE                                    0x911D
+#define GL_SYNC_FLUSH_COMMANDS_BIT_APPLE                        0x00000001
+#define GL_TIMEOUT_IGNORED_APPLE                                0xFFFFFFFFFFFFFFFFull
+#endif
+
+/* GL_APPLE_texture_format_BGRA8888 */
+#ifndef GL_APPLE_texture_format_BGRA8888
+#define GL_BGRA_EXT                                             0x80E1
+#endif
+
+/* GL_APPLE_texture_max_level */
+#ifndef GL_APPLE_texture_max_level
+#define GL_TEXTURE_MAX_LEVEL_APPLE                              0x813D
+#endif
+
+/*------------------------------------------------------------------------*
+ * ARM extension tokens
+ *------------------------------------------------------------------------*/
+
+/* GL_ARM_mali_program_binary */
+#ifndef GL_ARM_mali_program_binary
+#define GL_MALI_PROGRAM_BINARY_ARM                              0x8F61
+#endif
+
+/* GL_ARM_mali_shader_binary */
+#ifndef GL_ARM_mali_shader_binary
+#define GL_MALI_SHADER_BINARY_ARM                               0x8F60
+#endif
+
+/* GL_ARM_rgba8 */
+/* No new tokens introduced by this extension. */
+
+/*------------------------------------------------------------------------*
+ * EXT extension tokens
+ *------------------------------------------------------------------------*/
+
+/* GL_EXT_blend_minmax */
+#ifndef GL_EXT_blend_minmax
+#define GL_MIN_EXT                                              0x8007
+#define GL_MAX_EXT                                              0x8008
+#endif
+
+/* GL_EXT_color_buffer_half_float */
+#ifndef GL_EXT_color_buffer_half_float
+#define GL_RGBA16F_EXT                                          0x881A
+#define GL_RGB16F_EXT                                           0x881B
+#define GL_RG16F_EXT                                            0x822F
+#define GL_R16F_EXT                                             0x822D
+#define GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE_EXT            0x8211
+#define GL_UNSIGNED_NORMALIZED_EXT                              0x8C17
+#endif
+
+/* GL_EXT_debug_label */
+#ifndef GL_EXT_debug_label
+#define GL_PROGRAM_PIPELINE_OBJECT_EXT                          0x8A4F
+#define GL_PROGRAM_OBJECT_EXT                                   0x8B40
+#define GL_SHADER_OBJECT_EXT                                    0x8B48
+#define GL_BUFFER_OBJECT_EXT                                    0x9151
+#define GL_QUERY_OBJECT_EXT                                     0x9153
+#define GL_VERTEX_ARRAY_OBJECT_EXT                              0x9154
+#endif
+
+/* GL_EXT_debug_marker */
+/* No new tokens introduced by this extension. */
+
+/* GL_EXT_discard_framebuffer */
+#ifndef GL_EXT_discard_framebuffer
+#define GL_COLOR_EXT                                            0x1800
+#define GL_DEPTH_EXT                                            0x1801
+#define GL_STENCIL_EXT                                          0x1802
+#endif
+
+#ifndef GL_EXT_disjoint_timer_query
+#define GL_QUERY_COUNTER_BITS_EXT                               0x8864
+#define GL_CURRENT_QUERY_EXT                                    0x8865
+#define GL_QUERY_RESULT_EXT                                     0x8866
+#define GL_QUERY_RESULT_AVAILABLE_EXT                           0x8867
+#define GL_TIME_ELAPSED_EXT                                     0x88BF
+#define GL_TIMESTAMP_EXT                                        0x8E28
+#define GL_GPU_DISJOINT_EXT                                     0x8FBB
+#endif
+
+#ifndef GL_EXT_draw_buffers
+#define GL_EXT_draw_buffers 1
+#define GL_MAX_COLOR_ATTACHMENTS_EXT                            0x8CDF
+#define GL_MAX_DRAW_BUFFERS_EXT                                 0x8824
+#define GL_DRAW_BUFFER0_EXT                                     0x8825
+#define GL_DRAW_BUFFER1_EXT                                     0x8826
+#define GL_DRAW_BUFFER2_EXT                                     0x8827
+#define GL_DRAW_BUFFER3_EXT                                     0x8828
+#define GL_DRAW_BUFFER4_EXT                                     0x8829
+#define GL_DRAW_BUFFER5_EXT                                     0x882A
+#define GL_DRAW_BUFFER6_EXT                                     0x882B
+#define GL_DRAW_BUFFER7_EXT                                     0x882C
+#define GL_DRAW_BUFFER8_EXT                                     0x882D
+#define GL_DRAW_BUFFER9_EXT                                     0x882E
+#define GL_DRAW_BUFFER10_EXT                                    0x882F
+#define GL_DRAW_BUFFER11_EXT                                    0x8830
+#define GL_DRAW_BUFFER12_EXT                                    0x8831
+#define GL_DRAW_BUFFER13_EXT                                    0x8832
+#define GL_DRAW_BUFFER14_EXT                                    0x8833
+#define GL_DRAW_BUFFER15_EXT                                    0x8834
+#define GL_COLOR_ATTACHMENT0_EXT                                0x8CE0
+#define GL_COLOR_ATTACHMENT1_EXT                                0x8CE1
+#define GL_COLOR_ATTACHMENT2_EXT                                0x8CE2
+#define GL_COLOR_ATTACHMENT3_EXT                                0x8CE3
+#define GL_COLOR_ATTACHMENT4_EXT                                0x8CE4
+#define GL_COLOR_ATTACHMENT5_EXT                                0x8CE5
+#define GL_COLOR_ATTACHMENT6_EXT                                0x8CE6
+#define GL_COLOR_ATTACHMENT7_EXT                                0x8CE7
+#define GL_COLOR_ATTACHMENT8_EXT                                0x8CE8
+#define GL_COLOR_ATTACHMENT9_EXT                                0x8CE9
+#define GL_COLOR_ATTACHMENT10_EXT                               0x8CEA
+#define GL_COLOR_ATTACHMENT11_EXT                               0x8CEB
+#define GL_COLOR_ATTACHMENT12_EXT                               0x8CEC
+#define GL_COLOR_ATTACHMENT13_EXT                               0x8CED
+#define GL_COLOR_ATTACHMENT14_EXT                               0x8CEE
+#define GL_COLOR_ATTACHMENT15_EXT                               0x8CEF
+#endif
+
+/* GL_EXT_map_buffer_range */
+#ifndef GL_EXT_map_buffer_range
+#define GL_MAP_READ_BIT_EXT                                     0x0001
+#define GL_MAP_WRITE_BIT_EXT                                    0x0002
+#define GL_MAP_INVALIDATE_RANGE_BIT_EXT                         0x0004
+#define GL_MAP_INVALIDATE_BUFFER_BIT_EXT                        0x0008
+#define GL_MAP_FLUSH_EXPLICIT_BIT_EXT                           0x0010
+#define GL_MAP_UNSYNCHRONIZED_BIT_EXT                           0x0020
+#endif
+
+/* GL_EXT_multisampled_render_to_texture */
+#ifndef GL_EXT_multisampled_render_to_texture
+#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_SAMPLES_EXT           0x8D6C
+/* reuse values from GL_EXT_framebuffer_multisample (desktop extension) */
+#define GL_RENDERBUFFER_SAMPLES_EXT                             0x8CAB
+#define GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_EXT               0x8D56
+#define GL_MAX_SAMPLES_EXT                                      0x8D57
+#endif
+
+/* GL_EXT_multiview_draw_buffers */
+#ifndef GL_EXT_multiview_draw_buffers
+#define GL_COLOR_ATTACHMENT_EXT                                 0x90F0
+#define GL_MULTIVIEW_EXT                                        0x90F1
+#define GL_DRAW_BUFFER_EXT                                      0x0C01
+#define GL_READ_BUFFER_EXT                                      0x0C02
+#define GL_MAX_MULTIVIEW_BUFFERS_EXT                            0x90F2
+#endif
+
+/* GL_EXT_multi_draw_arrays */
+/* No new tokens introduced by this extension. */
+
+/* GL_EXT_occlusion_query_boolean */
+#ifndef GL_EXT_occlusion_query_boolean
+#define GL_ANY_SAMPLES_PASSED_EXT                               0x8C2F
+#define GL_ANY_SAMPLES_PASSED_CONSERVATIVE_EXT                  0x8D6A
+#define GL_CURRENT_QUERY_EXT                                    0x8865
+#define GL_QUERY_RESULT_EXT                                     0x8866
+#define GL_QUERY_RESULT_AVAILABLE_EXT                           0x8867
+#endif
+
+/* GL_EXT_read_format_bgra */
+#ifndef GL_EXT_read_format_bgra
+#define GL_BGRA_EXT                                             0x80E1
+#define GL_UNSIGNED_SHORT_4_4_4_4_REV_EXT                       0x8365
+#define GL_UNSIGNED_SHORT_1_5_5_5_REV_EXT                       0x8366
+#endif
+
+/* GL_EXT_robustness */
+#ifndef GL_EXT_robustness
+/* reuse GL_NO_ERROR */
+#define GL_GUILTY_CONTEXT_RESET_EXT                             0x8253
+#define GL_INNOCENT_CONTEXT_RESET_EXT                           0x8254
+#define GL_UNKNOWN_CONTEXT_RESET_EXT                            0x8255
+#define GL_CONTEXT_ROBUST_ACCESS_EXT                            0x90F3
+#define GL_RESET_NOTIFICATION_STRATEGY_EXT                      0x8256
+#define GL_LOSE_CONTEXT_ON_RESET_EXT                            0x8252
+#define GL_NO_RESET_NOTIFICATION_EXT                            0x8261
+#endif
+
+/* GL_EXT_separate_shader_objects */
+#ifndef GL_EXT_separate_shader_objects
+#define GL_VERTEX_SHADER_BIT_EXT                                0x00000001
+#define GL_FRAGMENT_SHADER_BIT_EXT                              0x00000002
+#define GL_ALL_SHADER_BITS_EXT                                  0xFFFFFFFF
+#define GL_PROGRAM_SEPARABLE_EXT                                0x8258
+#define GL_ACTIVE_PROGRAM_EXT                                   0x8259
+#define GL_PROGRAM_PIPELINE_BINDING_EXT                         0x825A
+#endif
+
+/* GL_EXT_shader_framebuffer_fetch */
+#ifndef GL_EXT_shader_framebuffer_fetch
+#define GL_FRAGMENT_SHADER_DISCARDS_SAMPLES_EXT                 0x8A52
+#endif
+
+/* GL_EXT_shader_texture_lod */
+/* No new tokens introduced by this extension. */
+
+/* GL_EXT_shadow_samplers */
+#ifndef GL_EXT_shadow_samplers
+#define GL_TEXTURE_COMPARE_MODE_EXT                             0x884C
+#define GL_TEXTURE_COMPARE_FUNC_EXT                             0x884D
+#define GL_COMPARE_REF_TO_TEXTURE_EXT                           0x884E
+#define GL_SAMPLER_2D_SHADOW_EXT                                0x8B62
+#endif
+
+/* GL_EXT_sRGB */
+#ifndef GL_EXT_sRGB
+#define GL_SRGB_EXT                                             0x8C40
+#define GL_SRGB_ALPHA_EXT                                       0x8C42
+#define GL_SRGB8_ALPHA8_EXT                                     0x8C43
+#define GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING_EXT            0x8210
+#endif
+
+/* GL_EXT_texture_compression_dxt1 */
+#ifndef GL_EXT_texture_compression_dxt1
+#define GL_COMPRESSED_RGB_S3TC_DXT1_EXT                         0x83F0
+#define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT                        0x83F1
+#endif
+
+/* GL_EXT_texture_filter_anisotropic */
+#ifndef GL_EXT_texture_filter_anisotropic
+#define GL_TEXTURE_MAX_ANISOTROPY_EXT                           0x84FE
+#define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT                       0x84FF
+#endif
+
+/* GL_EXT_texture_format_BGRA8888 */
+#ifndef GL_EXT_texture_format_BGRA8888
+#define GL_BGRA_EXT                                             0x80E1
+#endif
+
+/* GL_EXT_texture_rg */
+#ifndef GL_EXT_texture_rg
+#define GL_RED_EXT                                              0x1903
+#define GL_RG_EXT                                               0x8227
+#define GL_R8_EXT                                               0x8229
+#define GL_RG8_EXT                                              0x822B
+#endif
+
+/* GL_EXT_texture_storage */
+#ifndef GL_EXT_texture_storage
+#define GL_TEXTURE_IMMUTABLE_FORMAT_EXT                         0x912F
+#define GL_ALPHA8_EXT                                           0x803C
+#define GL_LUMINANCE8_EXT                                       0x8040
+#define GL_LUMINANCE8_ALPHA8_EXT                                0x8045
+#define GL_RGBA32F_EXT                                          0x8814
+#define GL_RGB32F_EXT                                           0x8815
+#define GL_ALPHA32F_EXT                                         0x8816
+#define GL_LUMINANCE32F_EXT                                     0x8818
+#define GL_LUMINANCE_ALPHA32F_EXT                               0x8819
+/* reuse GL_RGBA16F_EXT */
+/* reuse GL_RGB16F_EXT */
+#define GL_ALPHA16F_EXT                                         0x881C
+#define GL_LUMINANCE16F_EXT                                     0x881E
+#define GL_LUMINANCE_ALPHA16F_EXT                               0x881F
+#define GL_RGB10_A2_EXT                                         0x8059
+#define GL_RGB10_EXT                                            0x8052
+#define GL_BGRA8_EXT                                            0x93A1
+#define GL_R8_EXT                                               0x8229
+#define GL_RG8_EXT                                              0x822B
+#define GL_R32F_EXT                                             0x822E
+#define GL_RG32F_EXT                                            0x8230
+#define GL_R16F_EXT                                             0x822D
+#define GL_RG16F_EXT                                            0x822F
+#endif
+
+/* GL_EXT_texture_type_2_10_10_10_REV */
+#ifndef GL_EXT_texture_type_2_10_10_10_REV
+#define GL_UNSIGNED_INT_2_10_10_10_REV_EXT                      0x8368
+#endif
+
+/* GL_EXT_unpack_subimage */
+#ifndef GL_EXT_unpack_subimage
+#define GL_UNPACK_ROW_LENGTH_EXT                                0x0CF2
+#define GL_UNPACK_SKIP_ROWS_EXT                                 0x0CF3
+#define GL_UNPACK_SKIP_PIXELS_EXT                               0x0CF4
+#endif
+
+/*------------------------------------------------------------------------*
+ * DMP extension tokens
+ *------------------------------------------------------------------------*/
+
+/* GL_DMP_shader_binary */
+#ifndef GL_DMP_shader_binary
+#define GL_SHADER_BINARY_DMP                                    0x9250
+#endif
+
+/*------------------------------------------------------------------------*
+ * FJ extension tokens
+ *------------------------------------------------------------------------*/
+
+/* GL_FJ_shader_binary_GCCSO */
+#ifndef GL_FJ_shader_binary_GCCSO
+#define GL_GCCSO_SHADER_BINARY_FJ                               0x9260
+#endif
+
+/*------------------------------------------------------------------------*
+ * IMG extension tokens
+ *------------------------------------------------------------------------*/
+
+/* GL_IMG_program_binary */
+#ifndef GL_IMG_program_binary
+#define GL_SGX_PROGRAM_BINARY_IMG                               0x9130
+#endif
+
+/* GL_IMG_read_format */
+#ifndef GL_IMG_read_format
+#define GL_BGRA_IMG                                             0x80E1
+#define GL_UNSIGNED_SHORT_4_4_4_4_REV_IMG                       0x8365
+#endif
+
+/* GL_IMG_shader_binary */
+#ifndef GL_IMG_shader_binary
+#define GL_SGX_BINARY_IMG                                       0x8C0A
+#endif
+
+/* GL_IMG_texture_compression_pvrtc */
+#ifndef GL_IMG_texture_compression_pvrtc
+#define GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG                      0x8C00
+#define GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG                      0x8C01
+#define GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG                     0x8C02
+#define GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG                     0x8C03
+#endif
+
+/* GL_IMG_texture_compression_pvrtc2 */
+#ifndef GL_IMG_texture_compression_pvrtc2
+#define GL_COMPRESSED_RGBA_PVRTC_2BPPV2_IMG                     0x9137
+#define GL_COMPRESSED_RGBA_PVRTC_4BPPV2_IMG                     0x9138
+#endif
+
+/* GL_IMG_multisampled_render_to_texture */
+#ifndef GL_IMG_multisampled_render_to_texture
+#define GL_RENDERBUFFER_SAMPLES_IMG                             0x9133
+#define GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_IMG               0x9134
+#define GL_MAX_SAMPLES_IMG                                      0x9135
+#define GL_TEXTURE_SAMPLES_IMG                                  0x9136
+#endif
+
+/*------------------------------------------------------------------------*
+ * NV extension tokens
+ *------------------------------------------------------------------------*/
+
+/* GL_NV_coverage_sample */
+#ifndef GL_NV_coverage_sample
+#define GL_COVERAGE_COMPONENT_NV                                0x8ED0
+#define GL_COVERAGE_COMPONENT4_NV                               0x8ED1
+#define GL_COVERAGE_ATTACHMENT_NV                               0x8ED2
+#define GL_COVERAGE_BUFFERS_NV                                  0x8ED3
+#define GL_COVERAGE_SAMPLES_NV                                  0x8ED4
+#define GL_COVERAGE_ALL_FRAGMENTS_NV                            0x8ED5
+#define GL_COVERAGE_EDGE_FRAGMENTS_NV                           0x8ED6
+#define GL_COVERAGE_AUTOMATIC_NV                                0x8ED7
+#define GL_COVERAGE_BUFFER_BIT_NV                               0x00008000
+#endif
+
+/* GL_NV_depth_nonlinear */
+#ifndef GL_NV_depth_nonlinear
+#define GL_DEPTH_COMPONENT16_NONLINEAR_NV                       0x8E2C
+#endif
+
+/* GL_NV_draw_buffers */
+#ifndef GL_NV_draw_buffers
+#define GL_MAX_DRAW_BUFFERS_NV                                  0x8824
+#define GL_DRAW_BUFFER0_NV                                      0x8825
+#define GL_DRAW_BUFFER1_NV                                      0x8826
+#define GL_DRAW_BUFFER2_NV                                      0x8827
+#define GL_DRAW_BUFFER3_NV                                      0x8828
+#define GL_DRAW_BUFFER4_NV                                      0x8829
+#define GL_DRAW_BUFFER5_NV                                      0x882A
+#define GL_DRAW_BUFFER6_NV                                      0x882B
+#define GL_DRAW_BUFFER7_NV                                      0x882C
+#define GL_DRAW_BUFFER8_NV                                      0x882D
+#define GL_DRAW_BUFFER9_NV                                      0x882E
+#define GL_DRAW_BUFFER10_NV                                     0x882F
+#define GL_DRAW_BUFFER11_NV                                     0x8830
+#define GL_DRAW_BUFFER12_NV                                     0x8831
+#define GL_DRAW_BUFFER13_NV                                     0x8832
+#define GL_DRAW_BUFFER14_NV                                     0x8833
+#define GL_DRAW_BUFFER15_NV                                     0x8834
+#define GL_COLOR_ATTACHMENT0_NV                                 0x8CE0
+#define GL_COLOR_ATTACHMENT1_NV                                 0x8CE1
+#define GL_COLOR_ATTACHMENT2_NV                                 0x8CE2
+#define GL_COLOR_ATTACHMENT3_NV                                 0x8CE3
+#define GL_COLOR_ATTACHMENT4_NV                                 0x8CE4
+#define GL_COLOR_ATTACHMENT5_NV                                 0x8CE5
+#define GL_COLOR_ATTACHMENT6_NV                                 0x8CE6
+#define GL_COLOR_ATTACHMENT7_NV                                 0x8CE7
+#define GL_COLOR_ATTACHMENT8_NV                                 0x8CE8
+#define GL_COLOR_ATTACHMENT9_NV                                 0x8CE9
+#define GL_COLOR_ATTACHMENT10_NV                                0x8CEA
+#define GL_COLOR_ATTACHMENT11_NV                                0x8CEB
+#define GL_COLOR_ATTACHMENT12_NV                                0x8CEC
+#define GL_COLOR_ATTACHMENT13_NV                                0x8CED
+#define GL_COLOR_ATTACHMENT14_NV                                0x8CEE
+#define GL_COLOR_ATTACHMENT15_NV                                0x8CEF
+#endif
+
+/* GL_NV_draw_instanced */
+/* No new tokens introduced by this extension. */
+
+/* GL_NV_fbo_color_attachments */
+#ifndef GL_NV_fbo_color_attachments
+#define GL_MAX_COLOR_ATTACHMENTS_NV                             0x8CDF
+/* GL_COLOR_ATTACHMENT{0-15}_NV defined in GL_NV_draw_buffers already. */
+#endif
+
+/* GL_NV_fence */
+#ifndef GL_NV_fence
+#define GL_ALL_COMPLETED_NV                                     0x84F2
+#define GL_FENCE_STATUS_NV                                      0x84F3
+#define GL_FENCE_CONDITION_NV                                   0x84F4
+#endif
+
+/* GL_NV_framebuffer_blit */
+#ifndef GL_NV_framebuffer_blit
+#define GL_READ_FRAMEBUFFER_NV                                  0x8CA8
+#define GL_DRAW_FRAMEBUFFER_NV                                  0x8CA9
+#define GL_DRAW_FRAMEBUFFER_BINDING_NV                          0x8CA6
+#define GL_READ_FRAMEBUFFER_BINDING_NV                          0x8CAA
+#endif
+
+/* GL_NV_framebuffer_multisample */
+#ifndef GL_NV_framebuffer_multisample
+#define GL_RENDERBUFFER_SAMPLES_NV                              0x8CAB
+#define GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_NV                0x8D56
+#define GL_MAX_SAMPLES_NV                                       0x8D57
+#endif
+
+/* GL_NV_generate_mipmap_sRGB */
+/* No new tokens introduced by this extension. */
+
+/* GL_NV_instanced_arrays */
+#ifndef GL_NV_instanced_arrays
+#define GL_VERTEX_ATTRIB_ARRAY_DIVISOR_NV                       0x88FE
+#endif
+
+/* GL_NV_read_buffer */
+#ifndef GL_NV_read_buffer
+#define GL_READ_BUFFER_NV                                       0x0C02
+#endif
+
+/* GL_NV_read_buffer_front */
+/* No new tokens introduced by this extension. */
+
+/* GL_NV_read_depth */
+/* No new tokens introduced by this extension. */
+
+/* GL_NV_read_depth_stencil */
+/* No new tokens introduced by this extension. */
+
+/* GL_NV_read_stencil */
+/* No new tokens introduced by this extension. */
+
+/* GL_NV_shadow_samplers_array */
+#ifndef GL_NV_shadow_samplers_array
+#define GL_SAMPLER_2D_ARRAY_SHADOW_NV                           0x8DC4
+#endif
+
+/* GL_NV_shadow_samplers_cube */
+#ifndef GL_NV_shadow_samplers_cube
+#define GL_SAMPLER_CUBE_SHADOW_NV                               0x8DC5
+#endif
+
+/* GL_NV_sRGB_formats */
+#ifndef GL_NV_sRGB_formats
+#define GL_SLUMINANCE_NV                                        0x8C46
+#define GL_SLUMINANCE_ALPHA_NV                                  0x8C44
+#define GL_SRGB8_NV                                             0x8C41
+#define GL_SLUMINANCE8_NV                                       0x8C47
+#define GL_SLUMINANCE8_ALPHA8_NV                                0x8C45
+#define GL_COMPRESSED_SRGB_S3TC_DXT1_NV                         0x8C4C
+#define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_NV                   0x8C4D
+#define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_NV                   0x8C4E
+#define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_NV                   0x8C4F
+#define GL_ETC1_SRGB8_NV                                        0x88EE
+#endif
+
+/* GL_NV_texture_border_clamp */
+#ifndef GL_NV_texture_border_clamp
+#define GL_TEXTURE_BORDER_COLOR_NV                              0x1004
+#define GL_CLAMP_TO_BORDER_NV                                   0x812D
+#endif
+
+/* GL_NV_texture_compression_s3tc_update */
+/* No new tokens introduced by this extension. */
+
+/* GL_NV_texture_npot_2D_mipmap */
+/* No new tokens introduced by this extension. */
+
+/*------------------------------------------------------------------------*
+ * QCOM extension tokens
+ *------------------------------------------------------------------------*/
+
+/* GL_QCOM_alpha_test */
+#ifndef GL_QCOM_alpha_test
+#define GL_ALPHA_TEST_QCOM                                      0x0BC0
+#define GL_ALPHA_TEST_FUNC_QCOM                                 0x0BC1
+#define GL_ALPHA_TEST_REF_QCOM                                  0x0BC2
+#endif
+
+/* GL_QCOM_binning_control */
+#ifndef GL_QCOM_binning_control
+#define GL_BINNING_CONTROL_HINT_QCOM                            0x8FB0
+#define GL_CPU_OPTIMIZED_QCOM                                   0x8FB1
+#define GL_GPU_OPTIMIZED_QCOM                                   0x8FB2
+#define GL_RENDER_DIRECT_TO_FRAMEBUFFER_QCOM                    0x8FB3
+#endif
+
+/* GL_QCOM_driver_control */
+/* No new tokens introduced by this extension. */
+
+/* GL_QCOM_extended_get */
+#ifndef GL_QCOM_extended_get
+#define GL_TEXTURE_WIDTH_QCOM                                   0x8BD2
+#define GL_TEXTURE_HEIGHT_QCOM                                  0x8BD3
+#define GL_TEXTURE_DEPTH_QCOM                                   0x8BD4
+#define GL_TEXTURE_INTERNAL_FORMAT_QCOM                         0x8BD5
+#define GL_TEXTURE_FORMAT_QCOM                                  0x8BD6
+#define GL_TEXTURE_TYPE_QCOM                                    0x8BD7
+#define GL_TEXTURE_IMAGE_VALID_QCOM                             0x8BD8
+#define GL_TEXTURE_NUM_LEVELS_QCOM                              0x8BD9
+#define GL_TEXTURE_TARGET_QCOM                                  0x8BDA
+#define GL_TEXTURE_OBJECT_VALID_QCOM                            0x8BDB
+#define GL_STATE_RESTORE                                        0x8BDC
+#endif
+
+/* GL_QCOM_extended_get2 */
+/* No new tokens introduced by this extension. */
+
+/* GL_QCOM_perfmon_global_mode */
+#ifndef GL_QCOM_perfmon_global_mode
+#define GL_PERFMON_GLOBAL_MODE_QCOM                             0x8FA0
+#endif
+
+/* GL_QCOM_writeonly_rendering */
+#ifndef GL_QCOM_writeonly_rendering
+#define GL_WRITEONLY_RENDERING_QCOM                             0x8823
+#endif
+
+/* GL_QCOM_tiled_rendering */
+#ifndef GL_QCOM_tiled_rendering
+#define GL_COLOR_BUFFER_BIT0_QCOM                               0x00000001
+#define GL_COLOR_BUFFER_BIT1_QCOM                               0x00000002
+#define GL_COLOR_BUFFER_BIT2_QCOM                               0x00000004
+#define GL_COLOR_BUFFER_BIT3_QCOM                               0x00000008
+#define GL_COLOR_BUFFER_BIT4_QCOM                               0x00000010
+#define GL_COLOR_BUFFER_BIT5_QCOM                               0x00000020
+#define GL_COLOR_BUFFER_BIT6_QCOM                               0x00000040
+#define GL_COLOR_BUFFER_BIT7_QCOM                               0x00000080
+#define GL_DEPTH_BUFFER_BIT0_QCOM                               0x00000100
+#define GL_DEPTH_BUFFER_BIT1_QCOM                               0x00000200
+#define GL_DEPTH_BUFFER_BIT2_QCOM                               0x00000400
+#define GL_DEPTH_BUFFER_BIT3_QCOM                               0x00000800
+#define GL_DEPTH_BUFFER_BIT4_QCOM                               0x00001000
+#define GL_DEPTH_BUFFER_BIT5_QCOM                               0x00002000
+#define GL_DEPTH_BUFFER_BIT6_QCOM                               0x00004000
+#define GL_DEPTH_BUFFER_BIT7_QCOM                               0x00008000
+#define GL_STENCIL_BUFFER_BIT0_QCOM                             0x00010000
+#define GL_STENCIL_BUFFER_BIT1_QCOM                             0x00020000
+#define GL_STENCIL_BUFFER_BIT2_QCOM                             0x00040000
+#define GL_STENCIL_BUFFER_BIT3_QCOM                             0x00080000
+#define GL_STENCIL_BUFFER_BIT4_QCOM                             0x00100000
+#define GL_STENCIL_BUFFER_BIT5_QCOM                             0x00200000
+#define GL_STENCIL_BUFFER_BIT6_QCOM                             0x00400000
+#define GL_STENCIL_BUFFER_BIT7_QCOM                             0x00800000
+#define GL_MULTISAMPLE_BUFFER_BIT0_QCOM                         0x01000000
+#define GL_MULTISAMPLE_BUFFER_BIT1_QCOM                         0x02000000
+#define GL_MULTISAMPLE_BUFFER_BIT2_QCOM                         0x04000000
+#define GL_MULTISAMPLE_BUFFER_BIT3_QCOM                         0x08000000
+#define GL_MULTISAMPLE_BUFFER_BIT4_QCOM                         0x10000000
+#define GL_MULTISAMPLE_BUFFER_BIT5_QCOM                         0x20000000
+#define GL_MULTISAMPLE_BUFFER_BIT6_QCOM                         0x40000000
+#define GL_MULTISAMPLE_BUFFER_BIT7_QCOM                         0x80000000
+#endif
+
+/*------------------------------------------------------------------------*
+ * VIV extension tokens
+ *------------------------------------------------------------------------*/
+
+/* GL_VIV_shader_binary */
+#ifndef GL_VIV_shader_binary
+#define GL_SHADER_BINARY_VIV                                    0x8FC4
+#endif
+
+/*------------------------------------------------------------------------*
+ * End of extension tokens, start of corresponding extension functions
+ *------------------------------------------------------------------------*/
+
+/*------------------------------------------------------------------------*
+ * OES extension functions
+ *------------------------------------------------------------------------*/
+
+/* GL_OES_compressed_ETC1_RGB8_texture */
+#ifndef GL_OES_compressed_ETC1_RGB8_texture
+#define GL_OES_compressed_ETC1_RGB8_texture 1
+#endif
+
+/* GL_OES_compressed_paletted_texture */
+#ifndef GL_OES_compressed_paletted_texture
+#define GL_OES_compressed_paletted_texture 1
+#endif
+
+/* GL_OES_depth24 */
+#ifndef GL_OES_depth24
+#define GL_OES_depth24 1
+#endif
+
+/* GL_OES_depth32 */
+#ifndef GL_OES_depth32
+#define GL_OES_depth32 1
+#endif
+
+/* GL_OES_depth_texture */
+#ifndef GL_OES_depth_texture
+#define GL_OES_depth_texture 1
+#endif
+
+/* GL_OES_EGL_image */
+#ifndef GL_OES_EGL_image
+#define GL_OES_EGL_image 1
+#ifdef GL_GLEXT_PROTOTYPES
+GL_APICALL void GL_APIENTRY glEGLImageTargetTexture2DOES (GLenum target, GLeglImageOES image);
+GL_APICALL void GL_APIENTRY glEGLImageTargetRenderbufferStorageOES (GLenum target, GLeglImageOES image);
+#endif
+typedef void (GL_APIENTRYP PFNGLEGLIMAGETARGETTEXTURE2DOESPROC) (GLenum target, GLeglImageOES image);
+typedef void (GL_APIENTRYP PFNGLEGLIMAGETARGETRENDERBUFFERSTORAGEOESPROC) (GLenum target, GLeglImageOES image);
+#endif
+
+/* GL_OES_EGL_image_external */
+#ifndef GL_OES_EGL_image_external
+#define GL_OES_EGL_image_external 1
+/* glEGLImageTargetTexture2DOES defined in GL_OES_EGL_image already. */
+#endif
+
+/* GL_OES_element_index_uint */
+#ifndef GL_OES_element_index_uint
+#define GL_OES_element_index_uint 1
+#endif
+
+/* GL_OES_fbo_render_mipmap */
+#ifndef GL_OES_fbo_render_mipmap
+#define GL_OES_fbo_render_mipmap 1
+#endif
+
+/* GL_OES_fragment_precision_high */
+#ifndef GL_OES_fragment_precision_high
+#define GL_OES_fragment_precision_high 1
+#endif
+
+/* GL_OES_get_program_binary */
+#ifndef GL_OES_get_program_binary
+#define GL_OES_get_program_binary 1
+#ifdef GL_GLEXT_PROTOTYPES
+GL_APICALL void GL_APIENTRY glGetProgramBinaryOES (GLuint program, GLsizei bufSize, GLsizei *length, GLenum *binaryFormat, GLvoid *binary);
+GL_APICALL void GL_APIENTRY glProgramBinaryOES (GLuint program, GLenum binaryFormat, const GLvoid *binary, GLint length);
+#endif
+typedef void (GL_APIENTRYP PFNGLGETPROGRAMBINARYOESPROC) (GLuint program, GLsizei bufSize, GLsizei *length, GLenum *binaryFormat, GLvoid *binary);
+typedef void (GL_APIENTRYP PFNGLPROGRAMBINARYOESPROC) (GLuint program, GLenum binaryFormat, const GLvoid *binary, GLint length);
+#endif
+
+/* GL_OES_mapbuffer */
+#ifndef GL_OES_mapbuffer
+#define GL_OES_mapbuffer 1
+#ifdef GL_GLEXT_PROTOTYPES
+GL_APICALL void* GL_APIENTRY glMapBufferOES (GLenum target, GLenum access);
+GL_APICALL GLboolean GL_APIENTRY glUnmapBufferOES (GLenum target);
+GL_APICALL void GL_APIENTRY glGetBufferPointervOES (GLenum target, GLenum pname, GLvoid** params);
+#endif
+typedef void* (GL_APIENTRYP PFNGLMAPBUFFEROESPROC) (GLenum target, GLenum access);
+typedef GLboolean (GL_APIENTRYP PFNGLUNMAPBUFFEROESPROC) (GLenum target);
+typedef void (GL_APIENTRYP PFNGLGETBUFFERPOINTERVOESPROC) (GLenum target, GLenum pname, GLvoid** params);
+#endif
+
+/* GL_OES_packed_depth_stencil */
+#ifndef GL_OES_packed_depth_stencil
+#define GL_OES_packed_depth_stencil 1
+#endif
+
+/* GL_OES_required_internalformat */
+#ifndef GL_OES_required_internalformat
+#define GL_OES_required_internalformat 1
+#endif
+
+/* GL_OES_rgb8_rgba8 */
+#ifndef GL_OES_rgb8_rgba8
+#define GL_OES_rgb8_rgba8 1
+#endif
+
+/* GL_OES_standard_derivatives */
+#ifndef GL_OES_standard_derivatives
+#define GL_OES_standard_derivatives 1
+#endif
+
+/* GL_OES_stencil1 */
+#ifndef GL_OES_stencil1
+#define GL_OES_stencil1 1
+#endif
+
+/* GL_OES_stencil4 */
+#ifndef GL_OES_stencil4
+#define GL_OES_stencil4 1
+#endif
+
+#ifndef GL_OES_surfaceless_context
+#define GL_OES_surfaceless_context 1
+#endif
+
+/* GL_OES_texture_3D */
+#ifndef GL_OES_texture_3D
+#define GL_OES_texture_3D 1
+#ifdef GL_GLEXT_PROTOTYPES
+GL_APICALL void GL_APIENTRY glTexImage3DOES (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid* pixels);
+GL_APICALL void GL_APIENTRY glTexSubImage3DOES (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid* pixels);
+GL_APICALL void GL_APIENTRY glCopyTexSubImage3DOES (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height);
+GL_APICALL void GL_APIENTRY glCompressedTexImage3DOES (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const GLvoid* data);
+GL_APICALL void GL_APIENTRY glCompressedTexSubImage3DOES (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const GLvoid* data);
+GL_APICALL void GL_APIENTRY glFramebufferTexture3DOES (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset);
+#endif
+typedef void (GL_APIENTRYP PFNGLTEXIMAGE3DOESPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid* pixels);
+typedef void (GL_APIENTRYP PFNGLTEXSUBIMAGE3DOESPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid* pixels);
+typedef void (GL_APIENTRYP PFNGLCOPYTEXSUBIMAGE3DOESPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height);
+typedef void (GL_APIENTRYP PFNGLCOMPRESSEDTEXIMAGE3DOESPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const GLvoid* data);
+typedef void (GL_APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE3DOESPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const GLvoid* data);
+typedef void (GL_APIENTRYP PFNGLFRAMEBUFFERTEXTURE3DOES) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset);
+#endif
+
+/* GL_OES_texture_float */
+#ifndef GL_OES_texture_float
+#define GL_OES_texture_float 1
+#endif
+
+/* GL_OES_texture_float_linear */
+#ifndef GL_OES_texture_float_linear
+#define GL_OES_texture_float_linear 1
+#endif
+
+/* GL_OES_texture_half_float */
+#ifndef GL_OES_texture_half_float
+#define GL_OES_texture_half_float 1
+#endif
+
+/* GL_OES_texture_half_float_linear */
+#ifndef GL_OES_texture_half_float_linear
+#define GL_OES_texture_half_float_linear 1
+#endif
+
+/* GL_OES_texture_npot */
+#ifndef GL_OES_texture_npot
+#define GL_OES_texture_npot 1
+#endif
+
+/* GL_OES_vertex_array_object */
+#ifndef GL_OES_vertex_array_object
+#define GL_OES_vertex_array_object 1
+#ifdef GL_GLEXT_PROTOTYPES
+GL_APICALL void GL_APIENTRY glBindVertexArrayOES (GLuint array);
+GL_APICALL void GL_APIENTRY glDeleteVertexArraysOES (GLsizei n, const GLuint *arrays);
+GL_APICALL void GL_APIENTRY glGenVertexArraysOES (GLsizei n, GLuint *arrays);
+GL_APICALL GLboolean GL_APIENTRY glIsVertexArrayOES (GLuint array);
+#endif
+typedef void (GL_APIENTRYP PFNGLBINDVERTEXARRAYOESPROC) (GLuint array);
+typedef void (GL_APIENTRYP PFNGLDELETEVERTEXARRAYSOESPROC) (GLsizei n, const GLuint *arrays);
+typedef void (GL_APIENTRYP PFNGLGENVERTEXARRAYSOESPROC) (GLsizei n, GLuint *arrays);
+typedef GLboolean (GL_APIENTRYP PFNGLISVERTEXARRAYOESPROC) (GLuint array);
+#endif
+
+/* GL_OES_vertex_half_float */
+#ifndef GL_OES_vertex_half_float
+#define GL_OES_vertex_half_float 1
+#endif
+
+/* GL_OES_vertex_type_10_10_10_2 */
+#ifndef GL_OES_vertex_type_10_10_10_2
+#define GL_OES_vertex_type_10_10_10_2 1
+#endif
+
+/*------------------------------------------------------------------------*
+ * KHR extension functions
+ *------------------------------------------------------------------------*/
+
+#ifndef GL_KHR_debug
+#define GL_KHR_debug 1
+#ifdef GL_GLEXT_PROTOTYPES
+GL_APICALL void GL_APIENTRY glDebugMessageControlKHR (GLenum source, GLenum type, GLenum severity, GLsizei count, const GLuint *ids, GLboolean enabled);
+GL_APICALL void GL_APIENTRY glDebugMessageInsertKHR (GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *buf);
+GL_APICALL void GL_APIENTRY glDebugMessageCallbackKHR (GLDEBUGPROCKHR callback, const void *userParam);
+GL_APICALL GLuint GL_APIENTRY glGetDebugMessageLogKHR (GLuint count, GLsizei bufsize, GLenum *sources, GLenum *types, GLuint *ids, GLenum *severities, GLsizei *lengths, GLchar *messageLog);
+GL_APICALL void GL_APIENTRY glPushDebugGroupKHR (GLenum source, GLuint id, GLsizei length, const GLchar *message);
+GL_APICALL void GL_APIENTRY glPopDebugGroupKHR (void);
+GL_APICALL void GL_APIENTRY glObjectLabelKHR (GLenum identifier, GLuint name, GLsizei length, const GLchar *label);
+GL_APICALL void GL_APIENTRY glGetObjectLabelKHR (GLenum identifier, GLuint name, GLsizei bufSize, GLsizei *length, GLchar *label);
+GL_APICALL void GL_APIENTRY glObjectPtrLabelKHR (const void *ptr, GLsizei length, const GLchar *label);
+GL_APICALL void GL_APIENTRY glGetObjectPtrLabelKHR (const void *ptr, GLsizei bufSize, GLsizei *length, GLchar *label);
+GL_APICALL void GL_APIENTRY glGetPointervKHR (GLenum pname, void **params);
+#endif
+typedef void (GL_APIENTRYP PFNGLDEBUGMESSAGECONTROLKHRPROC) (GLenum source, GLenum type, GLenum severity, GLsizei count, const GLuint *ids, GLboolean enabled);
+typedef void (GL_APIENTRYP PFNGLDEBUGMESSAGEINSERTKHRPROC) (GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *buf);
+typedef void (GL_APIENTRYP PFNGLDEBUGMESSAGECALLBACKKHRPROC) (GLDEBUGPROCKHR callback, const void *userParam);
+typedef GLuint (GL_APIENTRYP PFNGLGETDEBUGMESSAGELOGKHRPROC) (GLuint count, GLsizei bufsize, GLenum *sources, GLenum *types, GLuint *ids, GLenum *severities, GLsizei *lengths, GLchar *messageLog);
+typedef void (GL_APIENTRYP PFNGLPUSHDEBUGGROUPKHRPROC) (GLenum source, GLuint id, GLsizei length, const GLchar *message);
+typedef void (GL_APIENTRYP PFNGLPOPDEBUGGROUPKHRPROC) (void);
+typedef void (GL_APIENTRYP PFNGLOBJECTLABELKHRPROC) (GLenum identifier, GLuint name, GLsizei length, const GLchar *label);
+typedef void (GL_APIENTRYP PFNGLGETOBJECTLABELKHRPROC) (GLenum identifier, GLuint name, GLsizei bufSize, GLsizei *length, GLchar *label);
+typedef void (GL_APIENTRYP PFNGLOBJECTPTRLABELKHRPROC) (const void *ptr, GLsizei length, const GLchar *label);
+typedef void (GL_APIENTRYP PFNGLGETOBJECTPTRLABELKHRPROC) (const void *ptr, GLsizei bufSize, GLsizei *length, GLchar *label);
+typedef void (GL_APIENTRYP PFNGLGETPOINTERVKHRPROC) (GLenum pname, void **params);
+#endif
+
+#ifndef GL_KHR_texture_compression_astc_ldr
+#define GL_KHR_texture_compression_astc_ldr 1
+#endif
+
+
+/*------------------------------------------------------------------------*
+ * AMD extension functions
+ *------------------------------------------------------------------------*/
+
+/* GL_AMD_compressed_3DC_texture */
+#ifndef GL_AMD_compressed_3DC_texture
+#define GL_AMD_compressed_3DC_texture 1
+#endif
+
+/* GL_AMD_compressed_ATC_texture */
+#ifndef GL_AMD_compressed_ATC_texture
+#define GL_AMD_compressed_ATC_texture 1
+#endif
+
+/* AMD_performance_monitor */
+#ifndef GL_AMD_performance_monitor
+#define GL_AMD_performance_monitor 1
+#ifdef GL_GLEXT_PROTOTYPES
+GL_APICALL void GL_APIENTRY glGetPerfMonitorGroupsAMD (GLint *numGroups, GLsizei groupsSize, GLuint *groups);
+GL_APICALL void GL_APIENTRY glGetPerfMonitorCountersAMD (GLuint group, GLint *numCounters, GLint *maxActiveCounters, GLsizei counterSize, GLuint *counters);
+GL_APICALL void GL_APIENTRY glGetPerfMonitorGroupStringAMD (GLuint group, GLsizei bufSize, GLsizei *length, GLchar *groupString);
+GL_APICALL void GL_APIENTRY glGetPerfMonitorCounterStringAMD (GLuint group, GLuint counter, GLsizei bufSize, GLsizei *length, GLchar *counterString);
+GL_APICALL void GL_APIENTRY glGetPerfMonitorCounterInfoAMD (GLuint group, GLuint counter, GLenum pname, GLvoid *data);
+GL_APICALL void GL_APIENTRY glGenPerfMonitorsAMD (GLsizei n, GLuint *monitors);
+GL_APICALL void GL_APIENTRY glDeletePerfMonitorsAMD (GLsizei n, GLuint *monitors);
+GL_APICALL void GL_APIENTRY glSelectPerfMonitorCountersAMD (GLuint monitor, GLboolean enable, GLuint group, GLint numCounters, GLuint *countersList);
+GL_APICALL void GL_APIENTRY glBeginPerfMonitorAMD (GLuint monitor);
+GL_APICALL void GL_APIENTRY glEndPerfMonitorAMD (GLuint monitor);
+GL_APICALL void GL_APIENTRY glGetPerfMonitorCounterDataAMD (GLuint monitor, GLenum pname, GLsizei dataSize, GLuint *data, GLint *bytesWritten);
+#endif
+typedef void (GL_APIENTRYP PFNGLGETPERFMONITORGROUPSAMDPROC) (GLint *numGroups, GLsizei groupsSize, GLuint *groups);
+typedef void (GL_APIENTRYP PFNGLGETPERFMONITORCOUNTERSAMDPROC) (GLuint group, GLint *numCounters, GLint *maxActiveCounters, GLsizei counterSize, GLuint *counters);
+typedef void (GL_APIENTRYP PFNGLGETPERFMONITORGROUPSTRINGAMDPROC) (GLuint group, GLsizei bufSize, GLsizei *length, GLchar *groupString);
+typedef void (GL_APIENTRYP PFNGLGETPERFMONITORCOUNTERSTRINGAMDPROC) (GLuint group, GLuint counter, GLsizei bufSize, GLsizei *length, GLchar *counterString);
+typedef void (GL_APIENTRYP PFNGLGETPERFMONITORCOUNTERINFOAMDPROC) (GLuint group, GLuint counter, GLenum pname, GLvoid *data);
+typedef void (GL_APIENTRYP PFNGLGENPERFMONITORSAMDPROC) (GLsizei n, GLuint *monitors);
+typedef void (GL_APIENTRYP PFNGLDELETEPERFMONITORSAMDPROC) (GLsizei n, GLuint *monitors);
+typedef void (GL_APIENTRYP PFNGLSELECTPERFMONITORCOUNTERSAMDPROC) (GLuint monitor, GLboolean enable, GLuint group, GLint numCounters, GLuint *countersList);
+typedef void (GL_APIENTRYP PFNGLBEGINPERFMONITORAMDPROC) (GLuint monitor);
+typedef void (GL_APIENTRYP PFNGLENDPERFMONITORAMDPROC) (GLuint monitor);
+typedef void (GL_APIENTRYP PFNGLGETPERFMONITORCOUNTERDATAAMDPROC) (GLuint monitor, GLenum pname, GLsizei dataSize, GLuint *data, GLint *bytesWritten);
+#endif
+
+/* GL_AMD_program_binary_Z400 */
+#ifndef GL_AMD_program_binary_Z400
+#define GL_AMD_program_binary_Z400 1
+#endif
+
+/*------------------------------------------------------------------------*
+ * ANGLE extension functions
+ *------------------------------------------------------------------------*/
+
+/* GL_ANGLE_depth_texture */
+#ifndef GL_ANGLE_depth_texture
+#define GL_ANGLE_depth_texture 1
+#endif
+
+/* GL_ANGLE_framebuffer_blit */
+#ifndef GL_ANGLE_framebuffer_blit
+#define GL_ANGLE_framebuffer_blit 1
+#ifdef GL_GLEXT_PROTOTYPES
+GL_APICALL void GL_APIENTRY glBlitFramebufferANGLE (GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter);
+#endif
+typedef void (GL_APIENTRYP PFNGLBLITFRAMEBUFFERANGLEPROC) (GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter);
+#endif
+
+/* GL_ANGLE_framebuffer_multisample */
+#ifndef GL_ANGLE_framebuffer_multisample
+#define GL_ANGLE_framebuffer_multisample 1
+#ifdef GL_GLEXT_PROTOTYPES
+GL_APICALL void GL_APIENTRY glRenderbufferStorageMultisampleANGLE (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height);
+#endif
+typedef void (GL_APIENTRYP PFNGLRENDERBUFFERSTORAGEMULTISAMPLEANGLEPROC) (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height);
+#endif
+
+#ifndef GL_ANGLE_instanced_arrays
+#define GL_ANGLE_instanced_arrays 1
+#ifdef GL_GLEXT_PROTOTYPES
+GL_APICALL void GL_APIENTRY glDrawArraysInstancedANGLE (GLenum mode, GLint first, GLsizei count, GLsizei primcount);
+GL_APICALL void GL_APIENTRY glDrawElementsInstancedANGLE (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei primcount);
+GL_APICALL void GL_APIENTRY glVertexAttribDivisorANGLE (GLuint index, GLuint divisor);
+#endif
+typedef void (GL_APIENTRYP PFNGLDRAWARRAYSINSTANCEDANGLEPROC) (GLenum mode, GLint first, GLsizei count, GLsizei primcount);
+typedef void (GL_APIENTRYP PFNGLDRAWELEMENTSINSTANCEDANGLEPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei primcount);
+typedef void (GL_APIENTRYP PFNGLVERTEXATTRIBDIVISORANGLEPROC) (GLuint index, GLuint divisor);
+#endif
+
+/* GL_ANGLE_pack_reverse_row_order */
+#ifndef GL_ANGLE_pack_reverse_row_order
+#define GL_ANGLE_pack_reverse_row_order 1
+#endif
+
+/* GL_ANGLE_program_binary */
+#ifndef GL_ANGLE_program_binary
+#define GL_ANGLE_program_binary 1
+#endif
+
+/* GL_ANGLE_texture_compression_dxt3 */
+#ifndef GL_ANGLE_texture_compression_dxt3
+#define GL_ANGLE_texture_compression_dxt3 1
+#endif
+
+/* GL_ANGLE_texture_compression_dxt5 */
+#ifndef GL_ANGLE_texture_compression_dxt5
+#define GL_ANGLE_texture_compression_dxt5 1
+#endif
+
+/* GL_ANGLE_texture_usage */
+#ifndef GL_ANGLE_texture_usage
+#define GL_ANGLE_texture_usage 1
+#endif
+
+#ifndef GL_ANGLE_translated_shader_source
+#define GL_ANGLE_translated_shader_source 1
+#ifdef GL_GLEXT_PROTOTYPES
+GL_APICALL void GL_APIENTRY glGetTranslatedShaderSourceANGLE (GLuint shader, GLsizei bufsize, GLsizei *length, GLchar *source);
+#endif
+typedef void (GL_APIENTRYP PFNGLGETTRANSLATEDSHADERSOURCEANGLEPROC) (GLuint shader, GLsizei bufsize, GLsizei *length, GLchar *source);
+#endif
+
+/*------------------------------------------------------------------------*
+ * APPLE extension functions
+ *------------------------------------------------------------------------*/
+
+/* GL_APPLE_copy_texture_levels */
+#ifndef GL_APPLE_copy_texture_levels
+#define GL_APPLE_copy_texture_levels 1
+#ifdef GL_GLEXT_PROTOTYPES
+GL_APICALL void GL_APIENTRY glCopyTextureLevelsAPPLE (GLuint destinationTexture, GLuint sourceTexture, GLint sourceBaseLevel, GLsizei sourceLevelCount);
+#endif
+typedef void (GL_APIENTRYP PFNGLCOPYTEXTURELEVELSAPPLEPROC) (GLuint destinationTexture, GLuint sourceTexture, GLint sourceBaseLevel, GLsizei sourceLevelCount);
+#endif
+
+/* GL_APPLE_framebuffer_multisample */
+#ifndef GL_APPLE_framebuffer_multisample
+#define GL_APPLE_framebuffer_multisample 1
+#ifdef GL_GLEXT_PROTOTYPES
+GL_APICALL void GL_APIENTRY glRenderbufferStorageMultisampleAPPLE (GLenum, GLsizei, GLenum, GLsizei, GLsizei);
+GL_APICALL void GL_APIENTRY glResolveMultisampleFramebufferAPPLE (void);
+#endif /* GL_GLEXT_PROTOTYPES */
+typedef void (GL_APIENTRYP PFNGLRENDERBUFFERSTORAGEMULTISAMPLEAPPLEPROC) (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height);
+typedef void (GL_APIENTRYP PFNGLRESOLVEMULTISAMPLEFRAMEBUFFERAPPLEPROC) (void);
+#endif
+
+/* GL_APPLE_rgb_422 */
+#ifndef GL_APPLE_rgb_422
+#define GL_APPLE_rgb_422 1
+#endif
+
+/* GL_APPLE_sync */
+#ifndef GL_APPLE_sync
+#define GL_APPLE_sync 1
+#ifdef GL_GLEXT_PROTOTYPES
+GL_APICALL GLsync GL_APIENTRY glFenceSyncAPPLE (GLenum condition, GLbitfield flags);
+GL_APICALL GLboolean GL_APIENTRY glIsSyncAPPLE (GLsync sync);
+GL_APICALL void GL_APIENTRY glDeleteSyncAPPLE (GLsync sync);
+GL_APICALL GLenum GL_APIENTRY glClientWaitSyncAPPLE (GLsync sync, GLbitfield flags, GLuint64 timeout);
+GL_APICALL void GL_APIENTRY glWaitSyncAPPLE (GLsync sync, GLbitfield flags, GLuint64 timeout);
+GL_APICALL void GL_APIENTRY glGetInteger64vAPPLE (GLenum pname, GLint64 *params);
+GL_APICALL void GL_APIENTRY glGetSyncivAPPLE (GLsync sync, GLenum pname, GLsizei bufSize, GLsizei *length, GLint *values);
+#endif
+typedef GLsync (GL_APIENTRYP PFNGLFENCESYNCAPPLEPROC) (GLenum condition, GLbitfield flags);
+typedef GLboolean (GL_APIENTRYP PFNGLISSYNCAPPLEPROC) (GLsync sync);
+typedef void (GL_APIENTRYP PFNGLDELETESYNCAPPLEPROC) (GLsync sync);
+typedef GLenum (GL_APIENTRYP PFNGLCLIENTWAITSYNCAPPLEPROC) (GLsync sync, GLbitfield flags, GLuint64 timeout);
+typedef void (GL_APIENTRYP PFNGLWAITSYNCAPPLEPROC) (GLsync sync, GLbitfield flags, GLuint64 timeout);
+typedef void (GL_APIENTRYP PFNGLGETINTEGER64VAPPLEPROC) (GLenum pname, GLint64 *params);
+typedef void (GL_APIENTRYP PFNGLGETSYNCIVAPPLEPROC) (GLsync sync, GLenum pname, GLsizei bufSize, GLsizei *length, GLint *values);
+#endif
+
+/* GL_APPLE_texture_format_BGRA8888 */
+#ifndef GL_APPLE_texture_format_BGRA8888
+#define GL_APPLE_texture_format_BGRA8888 1
+#endif
+
+/* GL_APPLE_texture_max_level */
+#ifndef GL_APPLE_texture_max_level
+#define GL_APPLE_texture_max_level 1
+#endif
+
+/*------------------------------------------------------------------------*
+ * ARM extension functions
+ *------------------------------------------------------------------------*/
+
+/* GL_ARM_mali_program_binary */
+#ifndef GL_ARM_mali_program_binary
+#define GL_ARM_mali_program_binary 1
+#endif
+
+/* GL_ARM_mali_shader_binary */
+#ifndef GL_ARM_mali_shader_binary
+#define GL_ARM_mali_shader_binary 1
+#endif
+
+/* GL_ARM_rgba8 */
+#ifndef GL_ARM_rgba8
+#define GL_ARM_rgba8 1
+#endif
+
+/*------------------------------------------------------------------------*
+ * EXT extension functions
+ *------------------------------------------------------------------------*/
+
+/* GL_EXT_blend_minmax */
+#ifndef GL_EXT_blend_minmax
+#define GL_EXT_blend_minmax 1
+#endif
+
+/* GL_EXT_color_buffer_half_float */
+#ifndef GL_EXT_color_buffer_half_float
+#define GL_EXT_color_buffer_half_float 1
+#endif
+
+/* GL_EXT_debug_label */
+#ifndef GL_EXT_debug_label
+#define GL_EXT_debug_label 1
+#ifdef GL_GLEXT_PROTOTYPES
+GL_APICALL void GL_APIENTRY glLabelObjectEXT (GLenum type, GLuint object, GLsizei length, const GLchar *label);
+GL_APICALL void GL_APIENTRY glGetObjectLabelEXT (GLenum type, GLuint object, GLsizei bufSize, GLsizei *length, GLchar *label);
+#endif
+typedef void (GL_APIENTRYP PFNGLLABELOBJECTEXTPROC) (GLenum type, GLuint object, GLsizei length, const GLchar *label);
+typedef void (GL_APIENTRYP PFNGLGETOBJECTLABELEXTPROC) (GLenum type, GLuint object, GLsizei bufSize, GLsizei *length, GLchar *label);
+#endif
+
+/* GL_EXT_debug_marker */
+#ifndef GL_EXT_debug_marker
+#define GL_EXT_debug_marker 1
+#ifdef GL_GLEXT_PROTOTYPES
+GL_APICALL void GL_APIENTRY glInsertEventMarkerEXT (GLsizei length, const GLchar *marker);
+GL_APICALL void GL_APIENTRY glPushGroupMarkerEXT (GLsizei length, const GLchar *marker);
+GL_APICALL void GL_APIENTRY glPopGroupMarkerEXT (void);
+#endif
+typedef void (GL_APIENTRYP PFNGLINSERTEVENTMARKEREXTPROC) (GLsizei length, const GLchar *marker);
+typedef void (GL_APIENTRYP PFNGLPUSHGROUPMARKEREXTPROC) (GLsizei length, const GLchar *marker);
+typedef void (GL_APIENTRYP PFNGLPOPGROUPMARKEREXTPROC) (void);
+#endif
+
+/* GL_EXT_discard_framebuffer */
+#ifndef GL_EXT_discard_framebuffer
+#define GL_EXT_discard_framebuffer 1
+#ifdef GL_GLEXT_PROTOTYPES
+GL_APICALL void GL_APIENTRY glDiscardFramebufferEXT (GLenum target, GLsizei numAttachments, const GLenum *attachments);
+#endif
+typedef void (GL_APIENTRYP PFNGLDISCARDFRAMEBUFFEREXTPROC) (GLenum target, GLsizei numAttachments, const GLenum *attachments);
+#endif
+
+#ifndef GL_EXT_disjoint_timer_query
+#define GL_EXT_disjoint_timer_query 1
+#ifdef GL_GLEXT_PROTOTYPES
+GL_APICALL void GL_APIENTRY glGenQueriesEXT (GLsizei n, GLuint *ids);
+GL_APICALL void GL_APIENTRY glDeleteQueriesEXT (GLsizei n, const GLuint *ids);
+GL_APICALL GLboolean GL_APIENTRY glIsQueryEXT (GLuint id);
+GL_APICALL void GL_APIENTRY glBeginQueryEXT (GLenum target, GLuint id);
+GL_APICALL void GL_APIENTRY glEndQueryEXT (GLenum target);
+GL_APICALL void GL_APIENTRY glQueryCounterEXT (GLuint id, GLenum target);
+GL_APICALL void GL_APIENTRY glGetQueryivEXT (GLenum target, GLenum pname, GLint *params);
+GL_APICALL void GL_APIENTRY glGetQueryObjectivEXT (GLuint id, GLenum pname, GLint *params);
+GL_APICALL void GL_APIENTRY glGetQueryObjectuivEXT (GLuint id, GLenum pname, GLuint *params);
+GL_APICALL void GL_APIENTRY glGetQueryObjecti64vEXT (GLuint id, GLenum pname, GLint64 *params);
+GL_APICALL void GL_APIENTRY glGetQueryObjectui64vEXT (GLuint id, GLenum pname, GLuint64 *params);
+#endif
+typedef void (GL_APIENTRYP PFNGLGENQUERIESEXTPROC) (GLsizei n, GLuint *ids);
+typedef void (GL_APIENTRYP PFNGLDELETEQUERIESEXTPROC) (GLsizei n, const GLuint *ids);
+typedef GLboolean (GL_APIENTRYP PFNGLISQUERYEXTPROC) (GLuint id);
+typedef void (GL_APIENTRYP PFNGLBEGINQUERYEXTPROC) (GLenum target, GLuint id);
+typedef void (GL_APIENTRYP PFNGLENDQUERYEXTPROC) (GLenum target);
+typedef void (GL_APIENTRYP PFNGLQUERYCOUNTEREXTPROC) (GLuint id, GLenum target);
+typedef void (GL_APIENTRYP PFNGLGETQUERYIVEXTPROC) (GLenum target, GLenum pname, GLint *params);
+typedef void (GL_APIENTRYP PFNGLGETQUERYOBJECTIVEXTPROC) (GLuint id, GLenum pname, GLint *params);
+typedef void (GL_APIENTRYP PFNGLGETQUERYOBJECTUIVEXTPROC) (GLuint id, GLenum pname, GLuint *params);
+typedef void (GL_APIENTRYP PFNGLGETQUERYOBJECTI64VEXTPROC) (GLuint id, GLenum pname, GLint64 *params);
+typedef void (GL_APIENTRYP PFNGLGETQUERYOBJECTUI64VEXTPROC) (GLuint id, GLenum pname, GLuint64 *params);
+#endif /* GL_EXT_disjoint_timer_query */
+
+#ifndef GL_EXT_draw_buffers
+#define GL_EXT_draw_buffers 1
+#ifdef GL_GLEXT_PROTOTYPES
+GL_APICALL void GL_APIENTRY glDrawBuffersEXT (GLsizei n, const GLenum *bufs);
+#endif
+typedef void (GL_APIENTRYP PFNGLDRAWBUFFERSEXTPROC) (GLsizei n, const GLenum *bufs);
+#endif /* GL_EXT_draw_buffers */
+
+/* GL_EXT_map_buffer_range */
+#ifndef GL_EXT_map_buffer_range
+#define GL_EXT_map_buffer_range 1
+#ifdef GL_GLEXT_PROTOTYPES
+GL_APICALL void* GL_APIENTRY glMapBufferRangeEXT (GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access);
+GL_APICALL void GL_APIENTRY glFlushMappedBufferRangeEXT (GLenum target, GLintptr offset, GLsizeiptr length);
+#endif
+typedef void* (GL_APIENTRYP PFNGLMAPBUFFERRANGEEXTPROC) (GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access);
+typedef void (GL_APIENTRYP PFNGLFLUSHMAPPEDBUFFERRANGEEXTPROC) (GLenum target, GLintptr offset, GLsizeiptr length);
+#endif
+
+/* GL_EXT_multisampled_render_to_texture */
+#ifndef GL_EXT_multisampled_render_to_texture
+#define GL_EXT_multisampled_render_to_texture 1
+#ifdef GL_GLEXT_PROTOTYPES
+GL_APICALL void GL_APIENTRY glRenderbufferStorageMultisampleEXT (GLenum, GLsizei, GLenum, GLsizei, GLsizei);
+GL_APICALL void GL_APIENTRY glFramebufferTexture2DMultisampleEXT (GLenum, GLenum, GLenum, GLuint, GLint, GLsizei);
+#endif
+typedef void (GL_APIENTRYP PFNGLRENDERBUFFERSTORAGEMULTISAMPLEEXTPROC) (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height);
+typedef void (GL_APIENTRYP PFNGLFRAMEBUFFERTEXTURE2DMULTISAMPLEEXTPROC) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLsizei samples);
+#endif
+
+/* GL_EXT_multiview_draw_buffers */
+#ifndef GL_EXT_multiview_draw_buffers
+#define GL_EXT_multiview_draw_buffers 1
+#ifdef GL_GLEXT_PROTOTYPES
+GL_APICALL void GL_APIENTRY glReadBufferIndexedEXT (GLenum src, GLint index);
+GL_APICALL void GL_APIENTRY glDrawBuffersIndexedEXT (GLint n, const GLenum *location, const GLint *indices);
+GL_APICALL void GL_APIENTRY glGetIntegeri_vEXT (GLenum target, GLuint index, GLint *data);
+#endif
+typedef void (GL_APIENTRYP PFNGLREADBUFFERINDEXEDEXTPROC) (GLenum src, GLint index);
+typedef void (GL_APIENTRYP PFNGLDRAWBUFFERSINDEXEDEXTPROC) (GLint n, const GLenum *location, const GLint *indices);
+typedef void (GL_APIENTRYP PFNGLGETINTEGERI_VEXTPROC) (GLenum target, GLuint index, GLint *data);
+#endif
+
+#ifndef GL_EXT_multi_draw_arrays
+#define GL_EXT_multi_draw_arrays 1
+#ifdef GL_GLEXT_PROTOTYPES
+GL_APICALL void GL_APIENTRY glMultiDrawArraysEXT (GLenum, const GLint *, const GLsizei *, GLsizei);
+GL_APICALL void GL_APIENTRY glMultiDrawElementsEXT (GLenum, const GLsizei *, GLenum, const GLvoid* *, GLsizei);
+#endif /* GL_GLEXT_PROTOTYPES */
+typedef void (GL_APIENTRYP PFNGLMULTIDRAWARRAYSEXTPROC) (GLenum mode, const GLint *first, const GLsizei *count, GLsizei primcount);
+typedef void (GL_APIENTRYP PFNGLMULTIDRAWELEMENTSEXTPROC) (GLenum mode, const GLsizei *count, GLenum type, const GLvoid* *indices, GLsizei primcount);
+#endif
+
+/* GL_EXT_occlusion_query_boolean */
+#ifndef GL_EXT_occlusion_query_boolean
+#define GL_EXT_occlusion_query_boolean 1
+#ifdef GL_GLEXT_PROTOTYPES
+GL_APICALL void GL_APIENTRY glGenQueriesEXT (GLsizei n, GLuint *ids);
+GL_APICALL void GL_APIENTRY glDeleteQueriesEXT (GLsizei n, const GLuint *ids);
+GL_APICALL GLboolean GL_APIENTRY glIsQueryEXT (GLuint id);
+GL_APICALL void GL_APIENTRY glBeginQueryEXT (GLenum target, GLuint id);
+GL_APICALL void GL_APIENTRY glEndQueryEXT (GLenum target);
+GL_APICALL void GL_APIENTRY glGetQueryivEXT (GLenum target, GLenum pname, GLint *params);
+GL_APICALL void GL_APIENTRY glGetQueryObjectuivEXT (GLuint id, GLenum pname, GLuint *params);
+#endif
+typedef void (GL_APIENTRYP PFNGLGENQUERIESEXTPROC) (GLsizei n, GLuint *ids);
+typedef void (GL_APIENTRYP PFNGLDELETEQUERIESEXTPROC) (GLsizei n, const GLuint *ids);
+typedef GLboolean (GL_APIENTRYP PFNGLISQUERYEXTPROC) (GLuint id);
+typedef void (GL_APIENTRYP PFNGLBEGINQUERYEXTPROC) (GLenum target, GLuint id);
+typedef void (GL_APIENTRYP PFNGLENDQUERYEXTPROC) (GLenum target);
+typedef void (GL_APIENTRYP PFNGLGETQUERYIVEXTPROC) (GLenum target, GLenum pname, GLint *params);
+typedef void (GL_APIENTRYP PFNGLGETQUERYOBJECTUIVEXTPROC) (GLuint id, GLenum pname, GLuint *params);
+#endif
+
+/* GL_EXT_read_format_bgra */
+#ifndef GL_EXT_read_format_bgra
+#define GL_EXT_read_format_bgra 1
+#endif
+
+/* GL_EXT_robustness */
+#ifndef GL_EXT_robustness
+#define GL_EXT_robustness 1
+#ifdef GL_GLEXT_PROTOTYPES
+GL_APICALL GLenum GL_APIENTRY glGetGraphicsResetStatusEXT (void);
+GL_APICALL void GL_APIENTRY glReadnPixelsEXT (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLsizei bufSize, void *data);
+GL_APICALL void GL_APIENTRY glGetnUniformfvEXT (GLuint program, GLint location, GLsizei bufSize, float *params);
+GL_APICALL void GL_APIENTRY glGetnUniformivEXT (GLuint program, GLint location, GLsizei bufSize, GLint *params);
+#endif
+typedef GLenum (GL_APIENTRYP PFNGLGETGRAPHICSRESETSTATUSEXTPROC) (void);
+typedef void (GL_APIENTRYP PFNGLREADNPIXELSEXTPROC) (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLsizei bufSize, void *data);
+typedef void (GL_APIENTRYP PFNGLGETNUNIFORMFVEXTPROC) (GLuint program, GLint location, GLsizei bufSize, float *params);
+typedef void (GL_APIENTRYP PFNGLGETNUNIFORMIVEXTPROC) (GLuint program, GLint location, GLsizei bufSize, GLint *params);
+#endif
+
+/* GL_EXT_separate_shader_objects */
+#ifndef GL_EXT_separate_shader_objects
+#define GL_EXT_separate_shader_objects 1
+#ifdef GL_GLEXT_PROTOTYPES
+GL_APICALL void GL_APIENTRY glUseProgramStagesEXT (GLuint pipeline, GLbitfield stages, GLuint program);
+GL_APICALL void GL_APIENTRY glActiveShaderProgramEXT (GLuint pipeline, GLuint program);
+GL_APICALL GLuint GL_APIENTRY glCreateShaderProgramvEXT (GLenum type, GLsizei count, const GLchar **strings);
+GL_APICALL void GL_APIENTRY glBindProgramPipelineEXT (GLuint pipeline);
+GL_APICALL void GL_APIENTRY glDeleteProgramPipelinesEXT (GLsizei n, const GLuint *pipelines);
+GL_APICALL void GL_APIENTRY glGenProgramPipelinesEXT (GLsizei n, GLuint *pipelines);
+GL_APICALL GLboolean GL_APIENTRY glIsProgramPipelineEXT (GLuint pipeline);
+GL_APICALL void GL_APIENTRY glProgramParameteriEXT (GLuint program, GLenum pname, GLint value);
+GL_APICALL void GL_APIENTRY glGetProgramPipelineivEXT (GLuint pipeline, GLenum pname, GLint *params);
+GL_APICALL void GL_APIENTRY glProgramUniform1iEXT (GLuint program, GLint location, GLint x);
+GL_APICALL void GL_APIENTRY glProgramUniform2iEXT (GLuint program, GLint location, GLint x, GLint y);
+GL_APICALL void GL_APIENTRY glProgramUniform3iEXT (GLuint program, GLint location, GLint x, GLint y, GLint z);
+GL_APICALL void GL_APIENTRY glProgramUniform4iEXT (GLuint program, GLint location, GLint x, GLint y, GLint z, GLint w);
+GL_APICALL void GL_APIENTRY glProgramUniform1fEXT (GLuint program, GLint location, GLfloat x);
+GL_APICALL void GL_APIENTRY glProgramUniform2fEXT (GLuint program, GLint location, GLfloat x, GLfloat y);
+GL_APICALL void GL_APIENTRY glProgramUniform3fEXT (GLuint program, GLint location, GLfloat x, GLfloat y, GLfloat z);
+GL_APICALL void GL_APIENTRY glProgramUniform4fEXT (GLuint program, GLint location, GLfloat x, GLfloat y, GLfloat z, GLfloat w);
+GL_APICALL void GL_APIENTRY glProgramUniform1ivEXT (GLuint program, GLint location, GLsizei count, const GLint *value);
+GL_APICALL void GL_APIENTRY glProgramUniform2ivEXT (GLuint program, GLint location, GLsizei count, const GLint *value);
+GL_APICALL void GL_APIENTRY glProgramUniform3ivEXT (GLuint program, GLint location, GLsizei count, const GLint *value);
+GL_APICALL void GL_APIENTRY glProgramUniform4ivEXT (GLuint program, GLint location, GLsizei count, const GLint *value);
+GL_APICALL void GL_APIENTRY glProgramUniform1fvEXT (GLuint program, GLint location, GLsizei count, const GLfloat *value);
+GL_APICALL void GL_APIENTRY glProgramUniform2fvEXT (GLuint program, GLint location, GLsizei count, const GLfloat *value);
+GL_APICALL void GL_APIENTRY glProgramUniform3fvEXT (GLuint program, GLint location, GLsizei count, const GLfloat *value);
+GL_APICALL void GL_APIENTRY glProgramUniform4fvEXT (GLuint program, GLint location, GLsizei count, const GLfloat *value);
+GL_APICALL void GL_APIENTRY glProgramUniformMatrix2fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
+GL_APICALL void GL_APIENTRY glProgramUniformMatrix3fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
+GL_APICALL void GL_APIENTRY glProgramUniformMatrix4fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
+GL_APICALL void GL_APIENTRY glValidateProgramPipelineEXT (GLuint pipeline);
+GL_APICALL void GL_APIENTRY glGetProgramPipelineInfoLogEXT (GLuint pipeline, GLsizei bufSize, GLsizei *length, GLchar *infoLog);
+#endif
+typedef void (GL_APIENTRYP PFNGLUSEPROGRAMSTAGESEXTPROC) (GLuint pipeline, GLbitfield stages, GLuint program);
+typedef void (GL_APIENTRYP PFNGLACTIVESHADERPROGRAMEXTPROC) (GLuint pipeline, GLuint program);
+typedef GLuint (GL_APIENTRYP PFNGLCREATESHADERPROGRAMVEXTPROC) (GLenum type, GLsizei count, const GLchar **strings);
+typedef void (GL_APIENTRYP PFNGLBINDPROGRAMPIPELINEEXTPROC) (GLuint pipeline);
+typedef void (GL_APIENTRYP PFNGLDELETEPROGRAMPIPELINESEXTPROC) (GLsizei n, const GLuint *pipelines);
+typedef void (GL_APIENTRYP PFNGLGENPROGRAMPIPELINESEXTPROC) (GLsizei n, GLuint *pipelines);
+typedef GLboolean (GL_APIENTRYP PFNGLISPROGRAMPIPELINEEXTPROC) (GLuint pipeline);
+typedef void (GL_APIENTRYP PFNGLPROGRAMPARAMETERIEXTPROC) (GLuint program, GLenum pname, GLint value);
+typedef void (GL_APIENTRYP PFNGLGETPROGRAMPIPELINEIVEXTPROC) (GLuint pipeline, GLenum pname, GLint *params);
+typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORM1IEXTPROC) (GLuint program, GLint location, GLint x);
+typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORM2IEXTPROC) (GLuint program, GLint location, GLint x, GLint y);
+typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORM3IEXTPROC) (GLuint program, GLint location, GLint x, GLint y, GLint z);
+typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORM4IEXTPROC) (GLuint program, GLint location, GLint x, GLint y, GLint z, GLint w);
+typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORM1FEXTPROC) (GLuint program, GLint location, GLfloat x);
+typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORM2FEXTPROC) (GLuint program, GLint location, GLfloat x, GLfloat y);
+typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORM3FEXTPROC) (GLuint program, GLint location, GLfloat x, GLfloat y, GLfloat z);
+typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORM4FEXTPROC) (GLuint program, GLint location, GLfloat x, GLfloat y, GLfloat z, GLfloat w);
+typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORM1IVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLint *value);
+typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORM2IVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLint *value);
+typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORM3IVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLint *value);
+typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORM4IVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLint *value);
+typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORM1FVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLfloat *value);
+typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORM2FVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLfloat *value);
+typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORM3FVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLfloat *value);
+typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORM4FVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLfloat *value);
+typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
+typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
+typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
+typedef void (GL_APIENTRYP PFNGLVALIDATEPROGRAMPIPELINEEXTPROC) (GLuint pipeline);
+typedef void (GL_APIENTRYP PFNGLGETPROGRAMPIPELINEINFOLOGEXTPROC) (GLuint pipeline, GLsizei bufSize, GLsizei *length, GLchar *infoLog);
+#endif
+
+/* GL_EXT_shader_framebuffer_fetch */
+#ifndef GL_EXT_shader_framebuffer_fetch
+#define GL_EXT_shader_framebuffer_fetch 1
+#endif
+
+/* GL_EXT_shader_texture_lod */
+#ifndef GL_EXT_shader_texture_lod
+#define GL_EXT_shader_texture_lod 1
+#endif
+
+/* GL_EXT_shadow_samplers */
+#ifndef GL_EXT_shadow_samplers
+#define GL_EXT_shadow_samplers 1
+#endif
+
+/* GL_EXT_sRGB */
+#ifndef GL_EXT_sRGB
+#define GL_EXT_sRGB 1
+#endif
+
+/* GL_EXT_texture_compression_dxt1 */
+#ifndef GL_EXT_texture_compression_dxt1
+#define GL_EXT_texture_compression_dxt1 1
+#endif
+
+/* GL_EXT_texture_filter_anisotropic */
+#ifndef GL_EXT_texture_filter_anisotropic
+#define GL_EXT_texture_filter_anisotropic 1
+#endif
+
+/* GL_EXT_texture_format_BGRA8888 */
+#ifndef GL_EXT_texture_format_BGRA8888
+#define GL_EXT_texture_format_BGRA8888 1
+#endif
+
+/* GL_EXT_texture_rg */
+#ifndef GL_EXT_texture_rg
+#define GL_EXT_texture_rg 1
+#endif
+
+/* GL_EXT_texture_storage */
+#ifndef GL_EXT_texture_storage
+#define GL_EXT_texture_storage 1
+#ifdef GL_GLEXT_PROTOTYPES
+GL_APICALL void GL_APIENTRY glTexStorage1DEXT (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width);
+GL_APICALL void GL_APIENTRY glTexStorage2DEXT (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height);
+GL_APICALL void GL_APIENTRY glTexStorage3DEXT (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth);
+GL_APICALL void GL_APIENTRY glTextureStorage1DEXT (GLuint texture, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width);
+GL_APICALL void GL_APIENTRY glTextureStorage2DEXT (GLuint texture, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height);
+GL_APICALL void GL_APIENTRY glTextureStorage3DEXT (GLuint texture, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth);
+#endif
+typedef void (GL_APIENTRYP PFNGLTEXSTORAGE1DEXTPROC) (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width);
+typedef void (GL_APIENTRYP PFNGLTEXSTORAGE2DEXTPROC) (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height);
+typedef void (GL_APIENTRYP PFNGLTEXSTORAGE3DEXTPROC) (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth);
+typedef void (GL_APIENTRYP PFNGLTEXTURESTORAGE1DEXTPROC) (GLuint texture, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width);
+typedef void (GL_APIENTRYP PFNGLTEXTURESTORAGE2DEXTPROC) (GLuint texture, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height);
+typedef void (GL_APIENTRYP PFNGLTEXTURESTORAGE3DEXTPROC) (GLuint texture, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth);
+#endif
+
+/* GL_EXT_texture_type_2_10_10_10_REV */
+#ifndef GL_EXT_texture_type_2_10_10_10_REV
+#define GL_EXT_texture_type_2_10_10_10_REV 1
+#endif
+
+/* GL_EXT_unpack_subimage */
+#ifndef GL_EXT_unpack_subimage
+#define GL_EXT_unpack_subimage 1
+#endif
+
+/*------------------------------------------------------------------------*
+ * DMP extension functions
+ *------------------------------------------------------------------------*/
+
+/* GL_DMP_shader_binary */
+#ifndef GL_DMP_shader_binary
+#define GL_DMP_shader_binary 1
+#endif
+
+/*------------------------------------------------------------------------*
+ * FJ extension functions
+ *------------------------------------------------------------------------*/
+
+/* GL_FJ_shader_binary_GCCSO */
+#ifndef GL_FJ_shader_binary_GCCSO
+#define GL_FJ_shader_binary_GCCSO 1
+#endif
+
+/*------------------------------------------------------------------------*
+ * IMG extension functions
+ *------------------------------------------------------------------------*/
+
+/* GL_IMG_program_binary */
+#ifndef GL_IMG_program_binary
+#define GL_IMG_program_binary 1
+#endif
+
+/* GL_IMG_read_format */
+#ifndef GL_IMG_read_format
+#define GL_IMG_read_format 1
+#endif
+
+/* GL_IMG_shader_binary */
+#ifndef GL_IMG_shader_binary
+#define GL_IMG_shader_binary 1
+#endif
+
+/* GL_IMG_texture_compression_pvrtc */
+#ifndef GL_IMG_texture_compression_pvrtc
+#define GL_IMG_texture_compression_pvrtc 1
+#endif
+
+/* GL_IMG_texture_compression_pvrtc2 */
+#ifndef GL_IMG_texture_compression_pvrtc2
+#define GL_IMG_texture_compression_pvrtc2 1
+#endif
+
+/* GL_IMG_multisampled_render_to_texture */
+#ifndef GL_IMG_multisampled_render_to_texture
+#define GL_IMG_multisampled_render_to_texture 1
+#ifdef GL_GLEXT_PROTOTYPES
+GL_APICALL void GL_APIENTRY glRenderbufferStorageMultisampleIMG (GLenum, GLsizei, GLenum, GLsizei, GLsizei);
+GL_APICALL void GL_APIENTRY glFramebufferTexture2DMultisampleIMG (GLenum, GLenum, GLenum, GLuint, GLint, GLsizei);
+#endif
+typedef void (GL_APIENTRYP PFNGLRENDERBUFFERSTORAGEMULTISAMPLEIMGPROC) (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height);
+typedef void (GL_APIENTRYP PFNGLFRAMEBUFFERTEXTURE2DMULTISAMPLEIMGPROC) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLsizei samples);
+#endif
+
+/*------------------------------------------------------------------------*
+ * NV extension functions
+ *------------------------------------------------------------------------*/
+
+/* GL_NV_coverage_sample */
+#ifndef GL_NV_coverage_sample
+#define GL_NV_coverage_sample 1
+#ifdef GL_GLEXT_PROTOTYPES
+GL_APICALL void GL_APIENTRY glCoverageMaskNV (GLboolean mask);
+GL_APICALL void GL_APIENTRY glCoverageOperationNV (GLenum operation);
+#endif
+typedef void (GL_APIENTRYP PFNGLCOVERAGEMASKNVPROC) (GLboolean mask);
+typedef void (GL_APIENTRYP PFNGLCOVERAGEOPERATIONNVPROC) (GLenum operation);
+#endif
+
+/* GL_NV_depth_nonlinear */
+#ifndef GL_NV_depth_nonlinear
+#define GL_NV_depth_nonlinear 1
+#endif
+
+/* GL_NV_draw_buffers */
+#ifndef GL_NV_draw_buffers
+#define GL_NV_draw_buffers 1
+#ifdef GL_GLEXT_PROTOTYPES
+GL_APICALL void GL_APIENTRY glDrawBuffersNV (GLsizei n, const GLenum *bufs);
+#endif
+typedef void (GL_APIENTRYP PFNGLDRAWBUFFERSNVPROC) (GLsizei n, const GLenum *bufs);
+#endif
+
+/* GL_NV_draw_instanced */
+#ifndef GL_NV_draw_instanced
+#define GL_NV_draw_instanced 1
+#ifdef GL_GLEXT_PROTOTYPES
+GL_APICALL void GL_APIENTRY glDrawArraysInstancedNV (GLenum mode, GLint first, GLsizei count, GLsizei primcount);
+GL_APICALL void GL_APIENTRY glDrawElementsInstancedNV (GLenum mode, GLsizei count, GLenum type, const GLvoid *indices, GLsizei primcount);
+#endif
+typedef void (GL_APIENTRYP PFNDRAWARRAYSINSTANCEDNVPROC) (GLenum mode, GLint first, GLsizei count, GLsizei primcount);
+typedef void (GL_APIENTRYP PFNDRAWELEMENTSINSTANCEDNVPROC) (GLenum mode, GLsizei count, GLenum type, const GLvoid *indices, GLsizei primcount);
+#endif
+
+/* GL_NV_fbo_color_attachments */
+#ifndef GL_NV_fbo_color_attachments
+#define GL_NV_fbo_color_attachments 1
+#endif
+
+/* GL_NV_fence */
+#ifndef GL_NV_fence
+#define GL_NV_fence 1
+#ifdef GL_GLEXT_PROTOTYPES
+GL_APICALL void GL_APIENTRY glDeleteFencesNV (GLsizei, const GLuint *);
+GL_APICALL void GL_APIENTRY glGenFencesNV (GLsizei, GLuint *);
+GL_APICALL GLboolean GL_APIENTRY glIsFenceNV (GLuint);
+GL_APICALL GLboolean GL_APIENTRY glTestFenceNV (GLuint);
+GL_APICALL void GL_APIENTRY glGetFenceivNV (GLuint, GLenum, GLint *);
+GL_APICALL void GL_APIENTRY glFinishFenceNV (GLuint);
+GL_APICALL void GL_APIENTRY glSetFenceNV (GLuint, GLenum);
+#endif
+typedef void (GL_APIENTRYP PFNGLDELETEFENCESNVPROC) (GLsizei n, const GLuint *fences);
+typedef void (GL_APIENTRYP PFNGLGENFENCESNVPROC) (GLsizei n, GLuint *fences);
+typedef GLboolean (GL_APIENTRYP PFNGLISFENCENVPROC) (GLuint fence);
+typedef GLboolean (GL_APIENTRYP PFNGLTESTFENCENVPROC) (GLuint fence);
+typedef void (GL_APIENTRYP PFNGLGETFENCEIVNVPROC) (GLuint fence, GLenum pname, GLint *params);
+typedef void (GL_APIENTRYP PFNGLFINISHFENCENVPROC) (GLuint fence);
+typedef void (GL_APIENTRYP PFNGLSETFENCENVPROC) (GLuint fence, GLenum condition);
+#endif
+
+/* GL_NV_framebuffer_blit */
+#ifndef GL_NV_framebuffer_blit
+#define GL_NV_framebuffer_blit 1
+#ifdef GL_GLEXT_PROTOTYPES
+GL_APICALL void GL_APIENTRY glBlitFramebufferNV (int srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter);
+#endif
+typedef void (GL_APIENTRYP PFNBLITFRAMEBUFFERNVPROC) (GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter);
+#endif
+
+/* GL_NV_framebuffer_multisample */
+#ifndef GL_NV_framebuffer_multisample
+#define GL_NV_framebuffer_multisample 1
+#ifdef GL_GLEXT_PROTOTYPES
+GL_APICALL void GL_APIENTRY glRenderbufferStorageMultisampleNV ( GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height);
+#endif
+typedef void (GL_APIENTRYP PFNRENDERBUFFERSTORAGEMULTISAMPLENVPROC) ( GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height);
+#endif
+
+/* GL_NV_generate_mipmap_sRGB */
+#ifndef GL_NV_generate_mipmap_sRGB
+#define GL_NV_generate_mipmap_sRGB 1
+#endif
+
+/* GL_NV_instanced_arrays */
+#ifndef GL_NV_instanced_arrays
+#define GL_NV_instanced_arrays 1
+#ifdef GL_GLEXT_PROTOTYPES
+GL_APICALL void GL_APIENTRY glVertexAttribDivisorNV (GLuint index, GLuint divisor);
+#endif
+typedef void (GL_APIENTRYP PFNVERTEXATTRIBDIVISORNVPROC) (GLuint index, GLuint divisor);
+#endif
+
+/* GL_NV_read_buffer */
+#ifndef GL_NV_read_buffer
+#define GL_NV_read_buffer 1
+#ifdef GL_GLEXT_PROTOTYPES
+GL_APICALL void GL_APIENTRY glReadBufferNV (GLenum mode);
+#endif
+typedef void (GL_APIENTRYP PFNGLREADBUFFERNVPROC) (GLenum mode);
+#endif
+
+/* GL_NV_read_buffer_front */
+#ifndef GL_NV_read_buffer_front
+#define GL_NV_read_buffer_front 1
+#endif
+
+/* GL_NV_read_depth */
+#ifndef GL_NV_read_depth
+#define GL_NV_read_depth 1
+#endif
+
+/* GL_NV_read_depth_stencil */
+#ifndef GL_NV_read_depth_stencil
+#define GL_NV_read_depth_stencil 1
+#endif
+
+/* GL_NV_read_stencil */
+#ifndef GL_NV_read_stencil
+#define GL_NV_read_stencil 1
+#endif
+
+/* GL_NV_shadow_samplers_array */
+#ifndef GL_NV_shadow_samplers_array
+#define GL_NV_shadow_samplers_array 1
+#endif
+
+/* GL_NV_shadow_samplers_cube */
+#ifndef GL_NV_shadow_samplers_cube
+#define GL_NV_shadow_samplers_cube 1
+#endif
+
+/* GL_NV_sRGB_formats */
+#ifndef GL_NV_sRGB_formats
+#define GL_NV_sRGB_formats 1
+#endif
+
+/* GL_NV_texture_border_clamp */
+#ifndef GL_NV_texture_border_clamp
+#define GL_NV_texture_border_clamp 1
+#endif
+
+/* GL_NV_texture_compression_s3tc_update */
+#ifndef GL_NV_texture_compression_s3tc_update
+#define GL_NV_texture_compression_s3tc_update 1
+#endif
+
+/* GL_NV_texture_npot_2D_mipmap */
+#ifndef GL_NV_texture_npot_2D_mipmap
+#define GL_NV_texture_npot_2D_mipmap 1
+#endif
+
+/*------------------------------------------------------------------------*
+ * QCOM extension functions
+ *------------------------------------------------------------------------*/
+
+/* GL_QCOM_alpha_test */
+#ifndef GL_QCOM_alpha_test
+#define GL_QCOM_alpha_test 1
+#ifdef GL_GLEXT_PROTOTYPES
+GL_APICALL void GL_APIENTRY glAlphaFuncQCOM (GLenum func, GLclampf ref);
+#endif
+typedef void (GL_APIENTRYP PFNGLALPHAFUNCQCOMPROC) (GLenum func, GLclampf ref);
+#endif
+
+/* GL_QCOM_binning_control */
+#ifndef GL_QCOM_binning_control
+#define GL_QCOM_binning_control 1
+#endif
+
+/* GL_QCOM_driver_control */
+#ifndef GL_QCOM_driver_control
+#define GL_QCOM_driver_control 1
+#ifdef GL_GLEXT_PROTOTYPES
+GL_APICALL void GL_APIENTRY glGetDriverControlsQCOM (GLint *num, GLsizei size, GLuint *driverControls);
+GL_APICALL void GL_APIENTRY glGetDriverControlStringQCOM (GLuint driverControl, GLsizei bufSize, GLsizei *length, GLchar *driverControlString);
+GL_APICALL void GL_APIENTRY glEnableDriverControlQCOM (GLuint driverControl);
+GL_APICALL void GL_APIENTRY glDisableDriverControlQCOM (GLuint driverControl);
+#endif
+typedef void (GL_APIENTRYP PFNGLGETDRIVERCONTROLSQCOMPROC) (GLint *num, GLsizei size, GLuint *driverControls);
+typedef void (GL_APIENTRYP PFNGLGETDRIVERCONTROLSTRINGQCOMPROC) (GLuint driverControl, GLsizei bufSize, GLsizei *length, GLchar *driverControlString);
+typedef void (GL_APIENTRYP PFNGLENABLEDRIVERCONTROLQCOMPROC) (GLuint driverControl);
+typedef void (GL_APIENTRYP PFNGLDISABLEDRIVERCONTROLQCOMPROC) (GLuint driverControl);
+#endif
+
+/* GL_QCOM_extended_get */
+#ifndef GL_QCOM_extended_get
+#define GL_QCOM_extended_get 1
+#ifdef GL_GLEXT_PROTOTYPES
+GL_APICALL void GL_APIENTRY glExtGetTexturesQCOM (GLuint *textures, GLint maxTextures, GLint *numTextures);
+GL_APICALL void GL_APIENTRY glExtGetBuffersQCOM (GLuint *buffers, GLint maxBuffers, GLint *numBuffers);
+GL_APICALL void GL_APIENTRY glExtGetRenderbuffersQCOM (GLuint *renderbuffers, GLint maxRenderbuffers, GLint *numRenderbuffers);
+GL_APICALL void GL_APIENTRY glExtGetFramebuffersQCOM (GLuint *framebuffers, GLint maxFramebuffers, GLint *numFramebuffers);
+GL_APICALL void GL_APIENTRY glExtGetTexLevelParameterivQCOM (GLuint texture, GLenum face, GLint level, GLenum pname, GLint *params);
+GL_APICALL void GL_APIENTRY glExtTexObjectStateOverrideiQCOM (GLenum target, GLenum pname, GLint param);
+GL_APICALL void GL_APIENTRY glExtGetTexSubImageQCOM (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, GLvoid *texels);
+GL_APICALL void GL_APIENTRY glExtGetBufferPointervQCOM (GLenum target, GLvoid **params);
+#endif
+typedef void (GL_APIENTRYP PFNGLEXTGETTEXTURESQCOMPROC) (GLuint *textures, GLint maxTextures, GLint *numTextures);
+typedef void (GL_APIENTRYP PFNGLEXTGETBUFFERSQCOMPROC) (GLuint *buffers, GLint maxBuffers, GLint *numBuffers);
+typedef void (GL_APIENTRYP PFNGLEXTGETRENDERBUFFERSQCOMPROC) (GLuint *renderbuffers, GLint maxRenderbuffers, GLint *numRenderbuffers);
+typedef void (GL_APIENTRYP PFNGLEXTGETFRAMEBUFFERSQCOMPROC) (GLuint *framebuffers, GLint maxFramebuffers, GLint *numFramebuffers);
+typedef void (GL_APIENTRYP PFNGLEXTGETTEXLEVELPARAMETERIVQCOMPROC) (GLuint texture, GLenum face, GLint level, GLenum pname, GLint *params);
+typedef void (GL_APIENTRYP PFNGLEXTTEXOBJECTSTATEOVERRIDEIQCOMPROC) (GLenum target, GLenum pname, GLint param);
+typedef void (GL_APIENTRYP PFNGLEXTGETTEXSUBIMAGEQCOMPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, GLvoid *texels);
+typedef void (GL_APIENTRYP PFNGLEXTGETBUFFERPOINTERVQCOMPROC) (GLenum target, GLvoid **params);
+#endif
+
+/* GL_QCOM_extended_get2 */
+#ifndef GL_QCOM_extended_get2
+#define GL_QCOM_extended_get2 1
+#ifdef GL_GLEXT_PROTOTYPES
+GL_APICALL void GL_APIENTRY glExtGetShadersQCOM (GLuint *shaders, GLint maxShaders, GLint *numShaders);
+GL_APICALL void GL_APIENTRY glExtGetProgramsQCOM (GLuint *programs, GLint maxPrograms, GLint *numPrograms);
+GL_APICALL GLboolean GL_APIENTRY glExtIsProgramBinaryQCOM (GLuint program);
+GL_APICALL void GL_APIENTRY glExtGetProgramBinarySourceQCOM (GLuint program, GLenum shadertype, GLchar *source, GLint *length);
+#endif
+typedef void (GL_APIENTRYP PFNGLEXTGETSHADERSQCOMPROC) (GLuint *shaders, GLint maxShaders, GLint *numShaders);
+typedef void (GL_APIENTRYP PFNGLEXTGETPROGRAMSQCOMPROC) (GLuint *programs, GLint maxPrograms, GLint *numPrograms);
+typedef GLboolean (GL_APIENTRYP PFNGLEXTISPROGRAMBINARYQCOMPROC) (GLuint program);
+typedef void (GL_APIENTRYP PFNGLEXTGETPROGRAMBINARYSOURCEQCOMPROC) (GLuint program, GLenum shadertype, GLchar *source, GLint *length);
+#endif
+
+/* GL_QCOM_perfmon_global_mode */
+#ifndef GL_QCOM_perfmon_global_mode
+#define GL_QCOM_perfmon_global_mode 1
+#endif
+
+/* GL_QCOM_writeonly_rendering */
+#ifndef GL_QCOM_writeonly_rendering
+#define GL_QCOM_writeonly_rendering 1
+#endif
+
+/* GL_QCOM_tiled_rendering */
+#ifndef GL_QCOM_tiled_rendering
+#define GL_QCOM_tiled_rendering 1
+#ifdef GL_GLEXT_PROTOTYPES
+GL_APICALL void GL_APIENTRY glStartTilingQCOM (GLuint x, GLuint y, GLuint width, GLuint height, GLbitfield preserveMask);
+GL_APICALL void GL_APIENTRY glEndTilingQCOM (GLbitfield preserveMask);
+#endif
+typedef void (GL_APIENTRYP PFNGLSTARTTILINGQCOMPROC) (GLuint x, GLuint y, GLuint width, GLuint height, GLbitfield preserveMask);
+typedef void (GL_APIENTRYP PFNGLENDTILINGQCOMPROC) (GLbitfield preserveMask);
+#endif
+
+/*------------------------------------------------------------------------*
+ * VIV extension tokens
+ *------------------------------------------------------------------------*/
+
+/* GL_VIV_shader_binary */
+#ifndef GL_VIV_shader_binary
+#define GL_VIV_shader_binary 1
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __gl2ext_h_ */
\ No newline at end of file
diff --git a/dev/disc_collection.py b/dev/disc_collection.py
new file mode 100644
index 0000000..5cc6e09
--- /dev/null
+++ b/dev/disc_collection.py
@@ -0,0 +1,113 @@
+from vispy import gloo
+from vispy import app
+from vispy.gloo import gl
+import numpy as np
+
+VERT_SHADER = """
+attribute vec3  a_position;
+attribute vec3  a_color;
+attribute float a_size;
+
+uniform float u_line_width;
+
+uniform vec2 u_pan;
+uniform vec2 u_zoom;
+
+varying vec4 v_fg_color;
+varying vec4 v_bg_color;
+varying float v_radius;
+varying float v_linewidth;
+varying float v_antialias;
+
+void main (void) {
+    v_radius = a_size;
+    v_linewidth = u_line_width;
+    v_antialias = 1.0;
+    v_fg_color  = vec4(0.0,0.0,0.0,1.0);
+    v_bg_color  = vec4(a_color,    1.0);
+
+    gl_Position = vec4(u_zoom * (a_position.xy + u_pan), a_position.z, 1.0);
+    gl_PointSize = 2.0*(v_radius + v_linewidth + 1.5*v_antialias);
+}
+"""
+
+FRAG_SHADER = """
+varying vec4 v_fg_color;
+varying vec4 v_bg_color;
+varying float v_radius;
+varying float v_linewidth;
+varying float v_antialias;
+void main()
+{    
+    float size = 2*(v_radius + v_linewidth + 1.5*v_antialias);
+    float t = v_linewidth/2.0-v_antialias;
+    float r = length((gl_PointCoord.xy - vec2(0.5,0.5))*size);
+    float d = abs(r - v_radius) - t;
+    if( d < 0.0 )
+        gl_FragColor = v_fg_color;
+    else
+    {
+        float alpha = d/v_antialias;
+        alpha = exp(-alpha*alpha);
+        if (r > v_radius)
+            gl_FragColor = vec4(v_fg_color.rgb, alpha*v_fg_color.a);
+        else
+            gl_FragColor = mix(v_bg_color, v_fg_color, alpha);
+    }
+}
+"""
+
+class DiscCollection(object):
+    def __init__(self):
+        self.program = gloo.Program(VERT_SHADER, FRAG_SHADER)
+        
+        self._vbo_position = gloo.VertexBuffer('2f4')
+        self._vbo_color = gloo.VertexBuffer('3f4')
+        self._vbo_size = gloo.VertexBuffer('f4')
+        
+        self.program['a_position'] = self._vbo_position
+        self.program['a_color'] = self._vbo_color
+        self.program['a_size'] = self._vbo_size
+        self.program['u_line_width'] = 1.0
+        self.program['u_zoom'] = (1.0, 1.0)
+        self.program['u_pan'] = (0., 0.)
+        
+    @property
+    def line_width(self):
+        return
+        
+    @line_width.setter
+    def line_width(self, value):
+        self.program['u_line_width'] = value
+
+        
+        
+    @property
+    def position(self):
+        return
+    @position.setter
+    def position(self, value):
+        self._vbo_position.set_data(value)
+
+        
+        
+    @property
+    def color(self):
+        return
+    @color.setter
+    def color(self, value):
+        self._vbo_color.set_data(value)
+
+        
+        
+    @property
+    def size(self):
+        return
+    @size.setter
+    def size(self, value):
+        self._vbo_size.set_data(value)
+    
+    
+    
+    def draw(self):
+        self.program.draw(gl.GL_POINTS)
diff --git a/dev/visual_experiments.py b/dev/visual_experiments.py
new file mode 100644
index 0000000..65eb906
--- /dev/null
+++ b/dev/visual_experiments.py
@@ -0,0 +1,33 @@
+from vispy import gloo
+from vispy import app
+from vispy.gloo import gl
+from disc_collection import DiscCollection
+import numpy as np
+
+
+class Canvas(app.Canvas):
+    def on_initialize(self, event):
+        gl.glClearColor(1,1,1,1)
+        gl.glEnable(gl.GL_BLEND)
+        gl.glBlendFunc (gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA)
+        
+        n = 10000
+        self.discs = DiscCollection()
+        self.discs.line_width = 2.
+        self.discs.position = 0.25 * np.random.randn(n, 2).astype(np.float32)
+        self.discs.color = np.random.uniform(0,1,(n,3)).astype(np.float32)
+        self.discs.size = np.random.uniform(2,12,(n,1)).astype(np.float32)
+        
+    def on_resize(self, event):
+        width, height = event.size
+        gl.glViewport(0, 0, width, height)
+    
+    def on_paint(self, event):
+        gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
+        
+        self.discs.draw()
+        
+if __name__ == '__main__':
+    c = Canvas()
+    c.show()
+    app.run()
diff --git a/doc/.requirements b/doc/.requirements
new file mode 100644
index 0000000..59a31d3
--- /dev/null
+++ b/doc/.requirements
@@ -0,0 +1,3 @@
+PyOpenGL>=3.0.2
+numpy>=1.6
+
diff --git a/doc/Makefile b/doc/Makefile
new file mode 100644
index 0000000..b79ef5c
--- /dev/null
+++ b/doc/Makefile
@@ -0,0 +1,160 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS    =
+SPHINXBUILD   = sphinx-build
+PAPER         =
+BUILDDIR      = _build
+
+# Internal variables.
+PAPEROPT_a4     = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+# the i18n builder cannot share the environment and doctrees with the others
+I18NSPHINXOPTS  = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+
+.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
+
+help:
+	@echo "Please use \`make <target>' where <target> is one of"
+	@echo "  html       to make standalone HTML files"
+	@echo "  dirhtml    to make HTML files named index.html in directories"
+	@echo "  singlehtml to make a single large HTML file"
+	@echo "  pickle     to make pickle files"
+	@echo "  json       to make JSON files"
+	@echo "  htmlhelp   to make HTML files and a HTML help project"
+	@echo "  qthelp     to make HTML files and a qthelp project"
+	@echo "  devhelp    to make HTML files and a Devhelp project"
+	@echo "  epub       to make an epub"
+	@echo "  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+	@echo "  latexpdf   to make LaTeX files and run them through pdflatex"
+	@echo "  text       to make text files"
+	@echo "  man        to make manual pages"
+	@echo "  texinfo    to make Texinfo files"
+	@echo "  info       to make Texinfo files and run them through makeinfo"
+	@echo "  gettext    to make PO message catalogs"
+	@echo "  changes    to make an overview of all changed/added/deprecated items"
+	@echo "  linkcheck  to check all external links for integrity"
+	@echo "  doctest    to run all doctests embedded in the documentation (if enabled)"
+
+clean:
+	-rm -rf $(BUILDDIR)/*
+
+html:
+	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+dirhtml:
+	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+singlehtml:
+	$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
+	@echo
+	@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
+
+pickle:
+	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+	@echo
+	@echo "Build finished; now you can process the pickle files."
+
+json:
+	$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+	@echo
+	@echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+	@echo
+	@echo "Build finished; now you can run HTML Help Workshop with the" \
+	      ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+qthelp:
+	$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+	@echo
+	@echo "Build finished; now you can run "qcollectiongenerator" with the" \
+	      ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+	@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/vispy.qhcp"
+	@echo "To view the help file:"
+	@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/vispy.qhc"
+
+devhelp:
+	$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
+	@echo
+	@echo "Build finished."
+	@echo "To view the help file:"
+	@echo "# mkdir -p $$HOME/.local/share/devhelp/vispy"
+	@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/vispy"
+	@echo "# devhelp"
+
+epub:
+	$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
+	@echo
+	@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
+
+latex:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo
+	@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+	@echo "Run \`make' in that directory to run these through (pdf)latex" \
+	      "(use \`make latexpdf' here to do that automatically)."
+
+latexpdf:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo "Running LaTeX files through pdflatex..."
+	$(MAKE) -C $(BUILDDIR)/latex all-pdf
+	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+text:
+	$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
+	@echo
+	@echo "Build finished. The text files are in $(BUILDDIR)/text."
+
+man:
+	$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
+	@echo
+	@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
+
+texinfo:
+	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+	@echo
+	@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
+	@echo "Run \`make' in that directory to run these through makeinfo" \
+	      "(use \`make info' here to do that automatically)."
+
+info:
+	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+	@echo "Running Texinfo files through makeinfo..."
+	make -C $(BUILDDIR)/texinfo info
+	@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
+
+gettext:
+	$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
+	@echo
+	@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
+
+changes:
+	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+	@echo
+	@echo "The overview file is in $(BUILDDIR)/changes."
+
+linkcheck:
+	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+	@echo
+	@echo "Link check complete; look for any errors in the above output " \
+	      "or in $(BUILDDIR)/linkcheck/output.txt."
+
+doctest:
+	$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+	@echo "Testing of doctests in the sources finished, look at the " \
+	      "results in $(BUILDDIR)/doctest/output.txt."
+
+showinchrome:
+	google-chrome _build/html/index.html
+
+showinff:
+	firefox _build/html/index.html
+
diff --git a/doc/_templates/localtoc.html b/doc/_templates/localtoc.html
new file mode 100644
index 0000000..c7e0f04
--- /dev/null
+++ b/doc/_templates/localtoc.html
@@ -0,0 +1,10 @@
+{% if pagename != 'index' %}
+
+    {%- if display_toc %}
+        <h4 class="sidebar-box-heading">Contents</h4>
+        <div class="well sidebar-box toc">
+            {{ toc|replace('<ul>', '<ul class="nav nav-list">') }}
+        </div>
+    {%- endif %}
+
+{% endif %}
\ No newline at end of file
diff --git a/doc/_templates/navbar.html b/doc/_templates/navbar.html
new file mode 100644
index 0000000..78c883b
--- /dev/null
+++ b/doc/_templates/navbar.html
@@ -0,0 +1,4 @@
+<li><a href="http://vispy.org">Home</a></li>
+<li><a href="http://vispy.org/gallery.html">Gallery</a></li>
+<li><a href="http://api.vispy.org">Documentation</a></li>
+<li><a href="https://github.com/vispy/vispy">Source</a></li>
diff --git a/doc/_templates/navigation.html b/doc/_templates/navigation.html
new file mode 100644
index 0000000..127bdd2
--- /dev/null
+++ b/doc/_templates/navigation.html
@@ -0,0 +1,12 @@
+<h4 class="sidebar-box-heading">{{ _('Navigation') }}</h4>
+<div class="well sidebar-box">
+    <ul class="nav nav-list">
+        <li><a href="{{ pathto(master_doc) }}">Documentation Home</a></li>
+        {% if prev %}
+        <li>Previous: <a href="{{ prev.link|e }}" title="{{ _('previous chapter') }}">{{ prev.title }}</a></li>
+        {% endif %}
+        {% if next %}
+        <li>Next: <a href="{{ next.link|e }}" title="{{ _('next chapter') }}">{{ next.title }}</a></li>
+        {% endif %}
+    </ul>
+</div>
diff --git a/doc/_templates/versions.html b/doc/_templates/versions.html
new file mode 100644
index 0000000..ed6d9f7
--- /dev/null
+++ b/doc/_templates/versions.html
@@ -0,0 +1,9 @@
+<h4 class="sidebar-box-heading">{{ _('Versions') }}</h4>
+<div class="well sidebar-box">
+    <ul class="nav nav-list">
+        <script src="{{ pathto('_static/', 1) }}js/docversions.js"></script>
+        <script type="text/javascript">
+            insert_version_links();
+        </script>
+    </ul>
+</div>
\ No newline at end of file
diff --git a/doc/app.rst b/doc/app.rst
new file mode 100644
index 0000000..af89ab6
--- /dev/null
+++ b/doc/app.rst
@@ -0,0 +1,32 @@
+==============
+The app module
+==============
+
+.. automodule:: vispy.app
+
+----
+
+.. autofunction:: vispy.app.use
+
+.. autofunction:: vispy.app.create
+
+.. autofunction:: vispy.app.run
+
+.. autofunction:: vispy.app.quit
+
+.. autofunction:: vispy.app.process_events
+
+----
+
+.. autoclass:: vispy.app.Application
+    :members:
+
+----
+
+.. autoclass:: vispy.app.Canvas
+    :members:
+
+----
+
+.. autoclass:: vispy.app.Timer
+    :members:
diff --git a/doc/architecture.svg b/doc/architecture.svg
new file mode 100644
index 0000000..53258f8
--- /dev/null
+++ b/doc/architecture.svg
@@ -0,0 +1,689 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="744.09448819"
+   height="1052.3622047"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48.3.1 r9886"
+   sodipodi:docname="architecture.svg">
+  <defs
+     id="defs4" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="1.979899"
+     inkscape:cx="427.61449"
+     inkscape:cy="861.96923"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     inkscape:showpageshadow="true"
+     showborder="true"
+     inkscape:connector-spacing="6"
+     inkscape:window-width="1920"
+     inkscape:window-height="1132"
+     inkscape:window-x="-3"
+     inkscape:window-y="-3"
+     inkscape:window-maximized="1" />
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1">
+    <rect
+       style="fill:#b0b0b0;fill-opacity:0.39130435;stroke:#000000;stroke-width:0.11621177;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+       id="rect10341"
+       width="386.21841"
+       height="207.19621"
+       x="212.30661"
+       y="178.5072"
+       ry="7.7275972" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:1.74317658;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="M 409.23262,269.34117 429.23195,258.1912"
+       id="path10339"
+       inkscape:connector-type="polyline"
+       inkscape:connector-curvature="0"
+       inkscape:connection-start="#g10234"
+       inkscape:connection-start-point="d4"
+       inkscape:connection-end="#g10226"
+       inkscape:connection-end-point="d4" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:5.81058836;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="M 205.22535,139.04001 296.9516,269.32918"
+       id="path10327"
+       inkscape:connector-type="polyline"
+       inkscape:connector-curvature="0"
+       inkscape:connection-start="#g10242"
+       inkscape:connection-start-point="d4"
+       inkscape:connection-end="#g10234"
+       inkscape:connection-end-point="d4" />
+    <g
+       id="g10213"
+       transform="matrix(0.58105886,0,0,0.58105886,319.8837,257.28555)"
+       inkscape:connector-avoid="true">
+      <rect
+         ry="16.162441"
+         y="150.29596"
+         x="231.32494"
+         height="49.497475"
+         width="221.2234"
+         id="rect10207"
+         style="fill:#b0b0b0;fill-opacity:1;stroke:#000000;stroke-width:0.2;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
+      <text
+         sodipodi:linespacing="125%"
+         id="text10209"
+         y="179.31813"
+         x="341.19055"
+         style="font-size:16px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+         xml:space="preserve"><tspan
+           y="179.31813"
+           x="341.19055"
+           id="tspan10211"
+           sodipodi:role="line">PyOpenGL</tspan></text>
+    </g>
+    <g
+       transform="matrix(0.58105886,0,0,0.58105886,321.05762,215.02457)"
+       id="g10218"
+       inkscape:connector-avoid="true">
+      <rect
+         style="fill:#b0b0b0;fill-opacity:1;stroke:#000000;stroke-width:0.2;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+         id="rect10220"
+         width="221.2234"
+         height="49.497475"
+         x="231.32494"
+         y="150.29596"
+         ry="16.162441" />
+      <text
+         xml:space="preserve"
+         style="font-size:16px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+         x="341.19055"
+         y="179.31813"
+         id="text10222"
+         sodipodi:linespacing="125%"><tspan
+           sodipodi:role="line"
+           id="tspan10224"
+           x="341.19055"
+           y="179.31813">GL Wrapper</tspan></text>
+    </g>
+    <g
+       id="g10226"
+       transform="matrix(0.88592237,0,0,0.88592237,191.94978,71.798389)"
+       inkscape:connector-avoid="true">
+      <rect
+         ry="26.979406"
+         y="127.76964"
+         x="231.32494"
+         height="82.624428"
+         width="221.2234"
+         id="rect10228"
+         style="fill:#b0b0b0;fill-opacity:1;stroke:#000000;stroke-width:0.13117604;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
+      <text
+         sodipodi:linespacing="125%"
+         id="text10230"
+         y="147.51627"
+         x="341.19055"
+         style="font-size:16px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+         xml:space="preserve"><tspan
+           y="147.51627"
+           x="341.19055"
+           id="tspan10232"
+           sodipodi:role="line">Shaders</tspan></text>
+      <text
+         xml:space="preserve"
+         style="font-size:12px;font-style:normal;font-weight:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+         x="581.21094"
+         y="392.73257"
+         id="text4070"
+         sodipodi:linespacing="125%"
+         transform="matrix(0.65588011,0,0,0.65588011,-137.74158,-97.38374)"><tspan
+           sodipodi:role="line"
+           id="tspan4072"
+           x="581.21094"
+           y="392.73257">AGG line drawing, point sprites, transforms,</tspan><tspan
+           sodipodi:role="line"
+           x="581.21094"
+           y="407.73257"
+           id="tspan4074">clipping boxes, etc. </tspan><tspan
+           sodipodi:role="line"
+           x="581.21094"
+           y="422.73257"
+           id="tspan4076" /><tspan
+           sodipodi:role="line"
+           x="581.21094"
+           y="437.73257"
+           id="tspan4078">Shaders will have a standard set of hooks allowing</tspan><tspan
+           sodipodi:role="line"
+           x="581.21094"
+           y="452.73257"
+           id="tspan4080">them to be recombined</tspan></text>
+    </g>
+    <g
+       transform="matrix(0.98667942,0,0,0.98667942,-8.775649,151.55707)"
+       id="g10234"
+       inkscape:connector-avoid="true">
+      <rect
+         style="fill:#b0b0b0;fill-opacity:1;stroke:#000000;stroke-width:0.11778068;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+         id="rect10236"
+         width="221.2234"
+         height="91.139244"
+         x="231.32494"
+         y="119.36208"
+         ry="29.759754" />
+      <text
+         xml:space="preserve"
+         style="font-size:16px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+         x="339.40591"
+         y="137.08148"
+         id="text10238"
+         sodipodi:linespacing="125%"><tspan
+           sodipodi:role="line"
+           id="tspan10240"
+           x="339.40591"
+           y="137.08148">GL Visuals</tspan></text>
+    </g>
+    <path
+       style="fill:none;stroke:#000000;stroke-width:1.74317658;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="M 307.29281,54.703654 249.03023,81.019258"
+       id="path10319"
+       inkscape:connector-type="polyline"
+       inkscape:connector-curvature="0"
+       inkscape:connection-start="#g10292"
+       inkscape:connection-start-point="d4"
+       inkscape:connection-end="#g10242"
+       inkscape:connection-end-point="d4" />
+    <g
+       id="g10242"
+       transform="matrix(1.1721962,0,0,1.1721962,-216.01538,-95.157095)"
+       inkscape:connector-avoid="true">
+      <rect
+         ry="16.162441"
+         y="150.29596"
+         x="231.32494"
+         height="49.497475"
+         width="221.2234"
+         id="rect10244"
+         style="fill:#b0b0b0;fill-opacity:1;stroke:#000000;stroke-width:0.0991402;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
+      <text
+         sodipodi:linespacing="125%"
+         id="text10246"
+         y="179.31813"
+         x="341.19055"
+         style="font-size:16px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+         xml:space="preserve"><tspan
+           y="179.31813"
+           x="341.19055"
+           id="tspan10248"
+           sodipodi:role="line">High-Level Visuals</tspan></text>
+    </g>
+    <path
+       style="fill:none;stroke:#000000;stroke-width:1.16211772;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 401.01109,54.703654 34.01988,13.614415"
+       id="path10323"
+       inkscape:connector-type="polyline"
+       inkscape:connector-curvature="0"
+       inkscape:connection-start="#g10292"
+       inkscape:connection-start-point="d4"
+       inkscape:connection-end="#g10260"
+       inkscape:connection-end-point="d4" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:1.74317658;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 367.76498,54.703654 47.58869,57.546036 10.45218,9.8539"
+       id="path10321"
+       inkscape:connector-type="polyline"
+       inkscape:connector-curvature="0"
+       inkscape:connection-start="#g10292"
+       inkscape:connection-start-point="d4"
+       inkscape:connection-end="#g10252"
+       inkscape:connection-end-point="d4" />
+    <g
+       transform="matrix(0.84349205,0,0,0.84349205,164.19728,-4.6698546)"
+       id="g10252"
+       inkscape:connector-avoid="true">
+      <rect
+         style="fill:#b0b0b0;fill-opacity:1;stroke:#000000;stroke-width:0.13777459;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+         id="rect10254"
+         width="221.2234"
+         height="59.935478"
+         x="231.32494"
+         y="150.29596"
+         ry="19.570768" />
+      <text
+         xml:space="preserve"
+         style="font-size:16px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+         x="341.88641"
+         y="169.57599"
+         id="text10256"
+         sodipodi:linespacing="125%"><tspan
+           sodipodi:role="line"
+           id="tspan10258"
+           x="341.88641"
+           y="169.57599">Scenegraph</tspan></text>
+      <text
+         xml:space="preserve"
+         style="font-size:8.26647568px;font-style:normal;font-weight:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+         x="257.73676"
+         y="182.9706"
+         id="text4955"
+         sodipodi:linespacing="125%"><tspan
+           sodipodi:role="line"
+           id="tspan4957"
+           x="257.73676"
+           y="182.9706">Visual hierarchies, inherited transforms,</tspan><tspan
+           sodipodi:role="line"
+           x="257.73676"
+           y="193.3037"
+           id="tspan4959">Automatic depth-ordering and visual </tspan><tspan
+           sodipodi:role="line"
+           x="257.73676"
+           y="203.63678"
+           id="tspan4961">stacking, </tspan></text>
+    </g>
+    <path
+       style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       d="m 483.86307,47.007869 96.21703,13.384521 0,32.324881"
+       id="path3089"
+       inkscape:connector-curvature="0" />
+    <g
+       id="g10260"
+       transform="matrix(0.67118742,0,0,0.67118742,260.23557,-32.558688)"
+       inkscape:connector-avoid="true">
+      <rect
+         ry="21.302401"
+         y="150.29596"
+         x="231.32494"
+         height="65.238602"
+         width="221.2234"
+         id="rect10262"
+         style="fill:#b0b0b0;fill-opacity:1;stroke:#000000;stroke-width:0.17314355;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
+      <text
+         sodipodi:linespacing="125%"
+         id="text10264"
+         y="172.32207"
+         x="340.31604"
+         style="font-size:16px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+         xml:space="preserve"><tspan
+           y="172.32207"
+           x="340.31604"
+           id="tspan10266"
+           sodipodi:role="line">Event System</tspan></text>
+      <text
+         xml:space="preserve"
+         style="font-size:10.38861275px;font-style:normal;font-weight:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+         x="255.79767"
+         y="193.13338"
+         id="text4090"
+         sodipodi:linespacing="125%"><tspan
+           sodipodi:role="line"
+           id="tspan4092"
+           x="255.79767"
+           y="193.13338">Backend-independent windowing</tspan><tspan
+           sodipodi:role="line"
+           x="255.79767"
+           y="206.11914"
+           id="tspan4094">and input events.</tspan></text>
+    </g>
+    <g
+       transform="matrix(0.58105886,0,0,0.58105886,461.45006,-18.78988)"
+       id="g10268"
+       inkscape:connector-avoid="true">
+      <rect
+         style="fill:#b0b0b0;fill-opacity:1;stroke:#000000;stroke-width:0.2;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+         id="rect10270"
+         width="251.64659"
+         height="168.25185"
+         x="231.32494"
+         y="150.29596"
+         ry="31.66519" />
+      <text
+         xml:space="preserve"
+         style="font-size:16px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+         x="369.87527"
+         y="174.79507"
+         id="text10272"
+         sodipodi:linespacing="125%"><tspan
+           sodipodi:role="line"
+           id="tspan10274"
+           x="369.87527"
+           y="174.79507">Backends</tspan></text>
+      <text
+         xml:space="preserve"
+         style="font-size:12px;font-style:normal;font-weight:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+         x="288.09787"
+         y="196.58607"
+         id="text4082"
+         sodipodi:linespacing="125%"><tspan
+           sodipodi:role="line"
+           id="tspan4084"
+           x="288.09787"
+           y="196.58607">Qt, gtk, tkinter, pyglet, . . . </tspan><tspan
+           sodipodi:role="line"
+           x="288.09787"
+           y="211.58607"
+           id="tspan4086">Each provides GL context,</tspan><tspan
+           sodipodi:role="line"
+           x="288.09787"
+           y="226.58607"
+           id="tspan4088">window handles, resize events,</tspan><tspan
+           sodipodi:role="line"
+           x="288.09787"
+           y="241.58607"
+           id="tspan4098">input events. . . </tspan><tspan
+           sodipodi:role="line"
+           x="288.09787"
+           y="256.58606"
+           id="tspan4096" /></text>
+      <path
+         style="fill:#909090;fill-opacity:1;stroke:#000000;stroke-width:0.11621177;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+         d="m 655.5625,78.125 c -10.19323,0 -18.375,8.213016 -18.375,18.40625 l 0,60.96875 c 0,10.19323 8.18177,18.40625 18.375,18.40625 l 9.125,0 0,-97.78125 -9.125,0 z"
+         transform="matrix(1.720996,0,0,1.720996,-865.2562,15.821858)"
+         id="rect3857"
+         inkscape:connector-curvature="0" />
+      <text
+         xml:space="preserve"
+         style="font-size:17.20996094px;font-style:normal;font-weight:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+         x="-302.3027"
+         y="261.94058"
+         id="text3853"
+         sodipodi:linespacing="125%"
+         transform="matrix(0,-1,1,0,0,0)"><tspan
+           sodipodi:role="line"
+           id="tspan3855"
+           x="-302.3027"
+           y="261.94058">CanvasBackend</tspan></text>
+    </g>
+    <g
+       transform="matrix(0.58105886,0,0,0.58105886,-82.182581,157.50267)"
+       id="g10276"
+       inkscape:connector-avoid="true">
+      <rect
+         style="fill:#b0b0b0;fill-opacity:1;stroke:#000000;stroke-width:0.2;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+         id="rect10278"
+         width="221.2234"
+         height="49.497475"
+         x="231.32494"
+         y="150.29596"
+         ry="16.162441" />
+      <text
+         xml:space="preserve"
+         style="font-size:16px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+         x="341.19055"
+         y="179.31813"
+         id="text10280"
+         sodipodi:linespacing="125%"><tspan
+           sodipodi:role="line"
+           id="tspan10282"
+           x="341.19055"
+           y="179.31813">SVG Visuals</tspan></text>
+    </g>
+    <g
+       id="g10284"
+       transform="matrix(0.58105886,0,0,0.58105886,-130.31314,86.480739)"
+       inkscape:connector-avoid="true">
+      <rect
+         ry="16.162441"
+         y="150.29596"
+         x="231.32494"
+         height="49.497475"
+         width="221.2234"
+         id="rect10286"
+         style="fill:#b0b0b0;fill-opacity:1;stroke:#000000;stroke-width:0.2;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
+      <text
+         sodipodi:linespacing="125%"
+         id="text10288"
+         y="179.31813"
+         x="341.19055"
+         style="font-size:16px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+         xml:space="preserve"><tspan
+           y="179.31813"
+           x="341.19055"
+           id="tspan10290"
+           sodipodi:role="line">WebGL Visuals</tspan></text>
+    </g>
+    <g
+       transform="matrix(0.80351061,0,0,0.80351061,145.5621,-105.83249)"
+       id="g10292"
+       inkscape:connector-avoid="true">
+      <rect
+         style="fill:#b0b0b0;fill-opacity:1;stroke:#000000;stroke-width:0.14463004;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+         id="rect10294"
+         width="392.94934"
+         height="49.497475"
+         x="59.598995"
+         y="150.29596"
+         ry="16.162441" />
+      <text
+         xml:space="preserve"
+         style="font-size:16px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+         x="260.37836"
+         y="180.32828"
+         id="text10296"
+         sodipodi:linespacing="125%"><tspan
+           sodipodi:role="line"
+           id="tspan10298"
+           x="260.37836"
+           y="180.32828">Functional interafce (mlab/gg-like)</tspan></text>
+    </g>
+    <path
+       style="fill:none;stroke:#000000;stroke-width:0.58105886;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:1.74317657, 1.74317657;stroke-dashoffset:0;display:inline"
+       d="m 263.72641,139.04001 103.30542,37.97187 38.52484,13.43348"
+       id="path10329"
+       inkscape:connector-type="polyline"
+       inkscape:connector-curvature="0"
+       inkscape:connection-end="#g10226"
+       inkscape:connection-end-point="d4"
+       inkscape:connection-start="#g10242"
+       inkscape:connection-start-point="d4" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:0.58105886;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:1.74317657, 1.74317657;stroke-dashoffset:0"
+       d="m 227.61875,139.04001 171.92937,116.48878 9.56189,6.43634 13.07838,4.01801 69.91305,36.37223"
+       id="path10331"
+       inkscape:connector-type="polyline"
+       inkscape:connector-curvature="0"
+       inkscape:connection-start="#g10242"
+       inkscape:connection-start-point="d4"
+       inkscape:connection-end="#g10218"
+       inkscape:connection-end-point="d4" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:1.74317658;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 519.34347,331.11632 -0.375,13.50003"
+       id="path10333"
+       inkscape:connector-type="polyline"
+       inkscape:connector-curvature="0"
+       inkscape:connection-start="#g10218"
+       inkscape:connection-start-point="d4"
+       inkscape:connection-end="#g10213"
+       inkscape:connection-end-point="d4" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:1.74317658;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 437.74448,315.68733 17.72654,0.22667"
+       id="path10337"
+       inkscape:connector-type="polyline"
+       inkscape:connector-curvature="0"
+       inkscape:connection-start="#g10234"
+       inkscape:connection-start-point="d4"
+       inkscape:connection-end="#g10218"
+       inkscape:connection-end-point="d4" />
+    <text
+       xml:space="preserve"
+       style="font-size:9.29694176px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+       x="288.0242"
+       y="375.7251"
+       id="text11111"
+       sodipodi:linespacing="125%"><tspan
+         sodipodi:role="line"
+         id="tspan11113"
+         x="288.0242"
+         y="375.7251"
+         style="font-size:12.78329468px">OpenGL Visualization</tspan></text>
+    <path
+       style="fill:none;stroke:#000000;stroke-width:0.58105886px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       d="M 515.98491,302.35537 504.44356,258.1912"
+       id="path11115"
+       inkscape:connector-type="polyline"
+       inkscape:connector-curvature="0"
+       inkscape:connection-start="#g10218"
+       inkscape:connection-start-point="d4"
+       inkscape:connection-end="#g10226"
+       inkscape:connection-end-point="d4" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:0.58105886px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       d="M 171.52008,139.04001 123.08632,244.83347"
+       id="path11117"
+       inkscape:connector-type="polyline"
+       inkscape:connector-curvature="0"
+       inkscape:connection-start="#g10242"
+       inkscape:connection-start-point="d4"
+       inkscape:connection-end="#g10276"
+       inkscape:connection-end-point="d4" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:0.58105886px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       d="M 141.58811,139.04001 89.793068,173.81154"
+       id="path11119"
+       inkscape:connector-type="polyline"
+       inkscape:connector-curvature="0"
+       inkscape:connection-start="#g10242"
+       inkscape:connection-start-point="d4"
+       inkscape:connection-end="#g10284"
+       inkscape:connection-end-point="d4" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:1.74317658;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 312.8022,127.88148 47.04254,6.56086"
+       id="path11121"
+       inkscape:connector-type="polyline"
+       inkscape:connector-curvature="0"
+       inkscape:connection-start="#g10242"
+       inkscape:connection-start-point="d4"
+       inkscape:connection-end="#g10252"
+       inkscape:connection-end-point="d4" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:0.58105886px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       d="m 469.03124,122.10359 6.49199,-9.99819"
+       id="path11123"
+       inkscape:connector-type="polyline"
+       inkscape:connector-curvature="0"
+       inkscape:connection-start="#g10252"
+       inkscape:connection-start-point="d4"
+       inkscape:connection-end="#g10260"
+       inkscape:connection-end-point="d4" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:0.58105886;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:1.74317657, 1.74317657;stroke-dashoffset:0"
+       d="m 171.83998,273.58361 50.19941,13.0355"
+       id="path11125"
+       inkscape:connector-type="polyline"
+       inkscape:connector-curvature="0"
+       inkscape:connection-start="#g10276"
+       inkscape:connection-start-point="d4"
+       inkscape:connection-end="#g10234"
+       inkscape:connection-end-point="d4" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:0.58105886;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:1.74317657, 1.74317657;stroke-dashoffset:0"
+       d="m 98.681078,202.57248 78.721022,37.35027 62.54688,30.7634"
+       id="path11127"
+       inkscape:connector-type="polyline"
+       inkscape:connector-curvature="0"
+       inkscape:connection-start="#g10284"
+       inkscape:connection-start-point="d4"
+       inkscape:connection-end="#g10234"
+       inkscape:connection-end-point="d4" />
+    <text
+       xml:space="preserve"
+       style="font-size:6.97270632px;font-style:normal;font-weight:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+       x="241.06754"
+       y="297.07275"
+       id="text11129"
+       sodipodi:linespacing="125%"><tspan
+         sodipodi:role="line"
+         id="tspan11131"
+         x="241.06754"
+         y="297.07275">Lines, triangles, images, particles, volumes</tspan><tspan
+         sodipodi:role="line"
+         x="241.06754"
+         y="305.78864"
+         id="tspan11133" /><tspan
+         sodipodi:role="line"
+         x="241.06754"
+         y="314.50452"
+         id="tspan11135">Each visual can work with multiple shaders</tspan><tspan
+         sodipodi:role="line"
+         x="241.06754"
+         y="323.2204"
+         id="tspan11137" /><tspan
+         sodipodi:role="line"
+         x="241.06754"
+         y="331.93628"
+         id="tspan11139">Some visuals can 'stack' together to render with </tspan><tspan
+         sodipodi:role="line"
+         x="241.06754"
+         y="340.65216"
+         id="tspan11141">fewer passes.</tspan></text>
+    <g
+       inkscape:connector-avoid="true"
+       transform="matrix(0,-0.67118742,0.67118742,0,468.23652,284.6125)"
+       id="g3862">
+      <rect
+         style="fill:#b0b0b0;fill-opacity:1;stroke:#000000;stroke-width:0.17314355;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+         id="rect3864"
+         width="90.2864"
+         height="34.385628"
+         x="231.32494"
+         y="150.29596"
+         ry="16.064047" />
+      <text
+         xml:space="preserve"
+         style="font-size:16px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+         x="275.60004"
+         y="173.82709"
+         id="text3866"
+         sodipodi:linespacing="125%"><tspan
+           sodipodi:role="line"
+           id="tspan3868"
+           x="275.60004"
+           y="173.82709">Canvas</tspan></text>
+    </g>
+    <path
+       style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       d="m 563.98032,97.429623 5.13296,0.499037"
+       id="path3876"
+       inkscape:connector-type="polyline"
+       inkscape:connector-curvature="0"
+       inkscape:connection-start="#g10260"
+       inkscape:connection-start-point="d4"
+       inkscape:connection-end="#g3862"
+       inkscape:connection-end-point="d4" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       d="m 592.19248,101.45051 1.39251,0.28961"
+       id="path3878"
+       inkscape:connector-type="polyline"
+       inkscape:connector-curvature="0"
+       inkscape:connection-start="#g3862"
+       inkscape:connection-start-point="d4"
+       inkscape:connection-end="#g10268"
+       inkscape:connection-end-point="d4" />
+  </g>
+</svg>
diff --git a/doc/conf.py b/doc/conf.py
new file mode 100644
index 0000000..ab0fae1
--- /dev/null
+++ b/doc/conf.py
@@ -0,0 +1,282 @@
+# -*- coding: utf-8 -*-
+#
+# vispy documentation build configuration file, created by
+# sphinx-quickstart on Sat May  4 16:52:02 2013.
+#
+# This file is execfile()d with the current directory set to its containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+import sys, os
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+sys.path.insert(0, os.path.abspath('..'))
+#sys.path.insert(0, os.path.abspath('.'))
+curpath = os.path.dirname(__file__)
+sys.path.append(os.path.abspath('ext'))
+
+# -- General configuration -----------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+#needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be extensions
+# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = [  'sphinx.ext.autodoc', 'sphinx.ext.pngmath', 
+                'sphinx.ext.autosummary', #'plot2rst',
+                'sphinx.ext.intersphinx',
+                'numpydoc',
+                'vispy_ext', ]#'scriptnamemangler',]
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+#source_encoding = 'utf-8-sig'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'vispy'
+copyright = u'2013, vispy contributers'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+import vispy
+# The short X.Y version.
+version = vispy.__version__[:3]
+# The full version, including alpha/beta/rc tags.
+release = vispy.__version__
+
+
+# -- General configuration -----------------------------------------------------
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+#today_fmt = '%B %d, %Y'
+
+# List of documents that shouldn't be included in the build.
+#unused_docs = []
+
+# List of directories, relative to source directory, that shouldn't be searched
+# for source files.
+exclude_trees = []
+
+# The reST default role (used for this markup: `text`) to use for all documents.
+#default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+#modindex_common_prefix = []
+
+
+# -- Options for HTML output ---------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. Major themes that come with
+# Sphinx are currently 'default' and 'sphinxdoc'.
+html_theme = 'scikit-image'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further. For a list of options available for each theme, see the
+# documentation.
+#html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+html_theme_path = ['themes']
+
+# The name for this set of Sphinx documents. If None, it defaults to
+# "<project> v<release> documentation".
+html_title = 'vispy v%s docs' % version
+
+# A shorter title for the navigation bar. Default is the same as html_title.
+#html_short_title = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+#html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+html_sidebars = {
+   '**': ['navigation.html',
+          'localtoc.html',
+          'versions.html'],
+}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+# html_additional_pages = {}
+
+# If false, no module index is generated.
+#html_use_modindex = True
+
+# If false, no index is generated.
+#html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+#html_show_sourcelink = True
+
+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
+#html_show_sphinx = True
+
+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
+#html_show_copyright = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it. The value of this option must be the
+# base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+
+# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = ''
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'vispydoc'
+
+
+# -- Options for LaTeX output --------------------------------------------------
+
+# The paper size ('letter' or 'a4').
+#latex_paper_size = 'letter'
+
+# The font size ('10pt', '11pt' or '12pt').
+latex_font_size = '10pt'
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, documentclass [howto/manual]).
+latex_documents = [
+  ('contents', 'vispy.tex', u'The visoy Documentation',
+   u'vispy development team', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+#latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#latex_use_parts = False
+
+# Additional stuff for the LaTeX preamble.
+latex_preamble = r'''
+\usepackage{enumitem}
+\setlistdepth{100}
+
+\usepackage{amsmath}
+\DeclareUnicodeCharacter{00A0}{\nobreakspace}
+
+% In the parameters section, place a newline after the Parameters header
+\usepackage{expdlist}
+\let\latexdescription=\description
+\def\description{\latexdescription{}{} \breaklabel}
+
+% Make Examples/etc section headers smaller and more compact
+\makeatletter
+\titleformat{\paragraph}{\normalsize\py at HeaderFamily}%
+{\py at TitleColor}{0em}{\py at TitleColor}{\py at NormalColor}
+\titlespacing*{\paragraph}{0pt}{1ex}{0pt}
+\makeatother
+
+'''
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+latex_use_modindex = False
+
+# -----------------------------------------------------------------------------
+# Numpy extensions
+# -----------------------------------------------------------------------------
+numpydoc_show_class_members = False
+
+# -----------------------------------------------------------------------------
+# Plots
+# -----------------------------------------------------------------------------
+plot_basedir = os.path.join(curpath, "plots")
+plot_pre_code = """
+import numpy as np
+import matplotlib.pyplot as plt
+np.random.seed(0)
+
+import matplotlib
+matplotlib.rcParams.update({
+'font.size': 14,
+'axes.titlesize': 12,
+'axes.labelsize': 10,
+'xtick.labelsize': 8,
+'ytick.labelsize': 8,
+'legend.fontsize': 10,
+'figure.subplot.bottom': 0.2,
+'figure.subplot.left': 0.2,
+'figure.subplot.right': 0.9,
+'figure.subplot.top': 0.85,
+'figure.subplot.wspace': 0.4,
+'text.usetex': False,
+})
+
+"""
+plot_include_source = True
+plot_formats = [('png', 100), ('pdf', 100)]
+
+plot2rst_index_name = 'README'
+plot2rst_rcparams = {'image.cmap' : 'gray',
+                     'image.interpolation' : 'none'}
+
+# -----------------------------------------------------------------------------
+# intersphinx
+# -----------------------------------------------------------------------------
+_python_doc_base = 'http://docs.python.org/2.7'
+intersphinx_mapping = {
+    _python_doc_base: None,
+    'http://docs.scipy.org/doc/numpy': None,
+    'http://docs.scipy.org/doc/scipy/reference': None,
+    'http://vispy.org/stable': None
+}
diff --git a/doc/event.rst b/doc/event.rst
new file mode 100644
index 0000000..e9c1997
--- /dev/null
+++ b/doc/event.rst
@@ -0,0 +1,36 @@
+================
+The event module
+================
+
+.. automodule:: vispy.event
+
+----
+
+.. autoclass:: vispy.event.Event
+    :members:
+
+.. autoclass:: vispy.app.canvas.MouseEvent
+    :members:
+
+.. autoclass:: vispy.app.canvas.KeyEvent
+    :members:
+
+.. autoclass:: vispy.app.canvas.ResizeEvent
+    :members:
+
+.. autoclass:: vispy.app.canvas.PaintEvent
+    :members:
+
+
+----
+
+.. autoclass:: vispy.event.EventEmitter
+    :members:
+    
+    .. automethod:: vispy.event.EventEmitter.__call__
+
+----
+
+.. autoclass:: vispy.event.EmitterGroup
+    :members:
+    
diff --git a/doc/ext/docscrape.py b/doc/ext/docscrape.py
new file mode 100644
index 0000000..92b17a4
--- /dev/null
+++ b/doc/ext/docscrape.py
@@ -0,0 +1,502 @@
+"""Extract reference documentation from the NumPy source tree.
+
+"""
+
+import inspect
+import textwrap
+import re
+import pydoc
+try:
+    from StringIO import StringIO
+except ImportError:
+    from io import StringIO
+from warnings import warn
+
+
+class Reader(object):
+    """A line-based string reader.
+
+    """
+    def __init__(self, data):
+        """
+        Parameters
+        ----------
+        data : str
+           String with lines separated by '\n'.
+
+        """
+        if isinstance(data,list):
+            self._str = data
+        else:
+            self._str = data.split('\n') # store string as list of lines
+
+        self.reset()
+
+    def __getitem__(self, n):
+        return self._str[n]
+
+    def reset(self):
+        self._l = 0 # current line nr
+
+    def read(self):
+        if not self.eof():
+            out = self[self._l]
+            self._l += 1
+            return out
+        else:
+            return ''
+
+    def seek_next_non_empty_line(self):
+        for l in self[self._l:]:
+            if l.strip():
+                break
+            else:
+                self._l += 1
+
+    def eof(self):
+        return self._l >= len(self._str)
+
+    def read_to_condition(self, condition_func):
+        start = self._l
+        for line in self[start:]:
+            if condition_func(line):
+                return self[start:self._l]
+            self._l += 1
+            if self.eof():
+                return self[start:self._l+1]
+        return []
+
+    def read_to_next_empty_line(self):
+        self.seek_next_non_empty_line()
+        def is_empty(line):
+            return not line.strip()
+        return self.read_to_condition(is_empty)
+
+    def read_to_next_unindented_line(self):
+        def is_unindented(line):
+            return (line.strip() and (len(line.lstrip()) == len(line)))
+        return self.read_to_condition(is_unindented)
+
+    def peek(self,n=0):
+        if self._l + n < len(self._str):
+            return self[self._l + n]
+        else:
+            return ''
+
+    def is_empty(self):
+        return not ''.join(self._str).strip()
+
+
+class NumpyDocString(object):
+    def __init__(self, docstring, config={}):
+        docstring = textwrap.dedent(docstring).split('\n')
+
+        self._doc = Reader(docstring)
+        self._parsed_data = {
+            'Signature': '',
+            'Summary': [''],
+            'Extended Summary': [],
+            'Parameters': [],
+            'Returns': [],
+            'Raises': [],
+            'Warns': [],
+            'Other Parameters': [],
+            'Attributes': [],
+            'Methods': [],
+            'See Also': [],
+            'Notes': [],
+            'Warnings': [],
+            'References': '',
+            'Examples': '',
+            'index': {}
+            }
+
+        self._parse()
+
+    def __getitem__(self,key):
+        return self._parsed_data[key]
+
+    def __setitem__(self,key,val):
+        if not key in self._parsed_data:
+            warn("Unknown section %s" % key)
+        else:
+            self._parsed_data[key] = val
+
+    def _is_at_section(self):
+        self._doc.seek_next_non_empty_line()
+
+        if self._doc.eof():
+            return False
+
+        l1 = self._doc.peek().strip()  # e.g. Parameters
+
+        if l1.startswith('.. index::'):
+            return True
+
+        l2 = self._doc.peek(1).strip() #    ---------- or ==========
+        return l2.startswith('-'*len(l1)) or l2.startswith('='*len(l1))
+
+    def _strip(self,doc):
+        i = 0
+        j = 0
+        for i,line in enumerate(doc):
+            if line.strip(): break
+
+        for j,line in enumerate(doc[::-1]):
+            if line.strip(): break
+
+        return doc[i:len(doc)-j]
+
+    def _read_to_next_section(self):
+        section = self._doc.read_to_next_empty_line()
+
+        while not self._is_at_section() and not self._doc.eof():
+            if not self._doc.peek(-1).strip(): # previous line was empty
+                section += ['']
+
+            section += self._doc.read_to_next_empty_line()
+
+        return section
+
+    def _read_sections(self):
+        while not self._doc.eof():
+            data = self._read_to_next_section()
+            name = data[0].strip()
+
+            if name.startswith('..'): # index section
+                yield name, data[1:]
+            elif len(data) < 2:
+                yield StopIteration
+            else:
+                yield name, self._strip(data[2:])
+
+    def _parse_param_list(self,content):
+        r = Reader(content)
+        params = []
+        while not r.eof():
+            header = r.read().strip()
+            if ' : ' in header:
+                arg_name, arg_type = header.split(' : ')[:2]
+            else:
+                arg_name, arg_type = header, ''
+
+            desc = r.read_to_next_unindented_line()
+            desc = dedent_lines(desc)
+
+            params.append((arg_name,arg_type,desc))
+
+        return params
+
+
+    _name_rgx = re.compile(r"^\s*(:(?P<role>\w+):`(?P<name>[a-zA-Z0-9_.-]+)`|"
+                           r" (?P<name2>[a-zA-Z0-9_.-]+))\s*", re.X)
+    def _parse_see_also(self, content):
+        """
+        func_name : Descriptive text
+            continued text
+        another_func_name : Descriptive text
+        func_name1, func_name2, :meth:`func_name`, func_name3
+
+        """
+        items = []
+
+        def parse_item_name(text):
+            """Match ':role:`name`' or 'name'"""
+            m = self._name_rgx.match(text)
+            if m:
+                g = m.groups()
+                if g[1] is None:
+                    return g[3], None
+                else:
+                    return g[2], g[1]
+            raise ValueError("%s is not a item name" % text)
+
+        def push_item(name, rest):
+            if not name:
+                return
+            name, role = parse_item_name(name)
+            items.append((name, list(rest), role))
+            del rest[:]
+
+        current_func = None
+        rest = []
+
+        for line in content:
+            if not line.strip(): continue
+
+            m = self._name_rgx.match(line)
+            if m and line[m.end():].strip().startswith(':'):
+                push_item(current_func, rest)
+                current_func, line = line[:m.end()], line[m.end():]
+                rest = [line.split(':', 1)[1].strip()]
+                if not rest[0]:
+                    rest = []
+            elif not line.startswith(' '):
+                push_item(current_func, rest)
+                current_func = None
+                if ',' in line:
+                    for func in line.split(','):
+                        push_item(func, [])
+                elif line.strip():
+                    current_func = line
+            elif current_func is not None:
+                rest.append(line.strip())
+        push_item(current_func, rest)
+        return items
+
+    def _parse_index(self, section, content):
+        """
+        .. index: default
+           :refguide: something, else, and more
+
+        """
+        def strip_each_in(lst):
+            return [s.strip() for s in lst]
+
+        out = {}
+        section = section.split('::')
+        if len(section) > 1:
+            out['default'] = strip_each_in(section[1].split(','))[0]
+        for line in content:
+            line = line.split(':')
+            if len(line) > 2:
+                out[line[1]] = strip_each_in(line[2].split(','))
+        return out
+
+    def _parse_summary(self):
+        """Grab signature (if given) and summary"""
+        if self._is_at_section():
+            return
+
+        summary = self._doc.read_to_next_empty_line()
+        summary_str = " ".join([s.strip() for s in summary]).strip()
+        if re.compile('^([\w., ]+=)?\s*[\w\.]+\(.*\)$').match(summary_str):
+            self['Signature'] = summary_str
+            if not self._is_at_section():
+                self['Summary'] = self._doc.read_to_next_empty_line()
+        else:
+            self['Summary'] = summary
+
+        if not self._is_at_section():
+            self['Extended Summary'] = self._read_to_next_section()
+
+    def _parse(self):
+        self._doc.reset()
+        self._parse_summary()
+
+        for (section,content) in self._read_sections():
+            if not section.startswith('..'):
+                section = ' '.join([s.capitalize() for s in section.split(' ')])
+            if section in ('Parameters', 'Attributes', 'Methods',
+                           'Returns', 'Raises', 'Warns'):
+                self[section] = self._parse_param_list(content)
+            elif section.startswith('.. index::'):
+                self['index'] = self._parse_index(section, content)
+            elif section == 'See Also':
+                self['See Also'] = self._parse_see_also(content)
+            else:
+                self[section] = content
+
+    # string conversion routines
+
+    def _str_header(self, name, symbol='-'):
+        return [name, len(name)*symbol]
+
+    def _str_indent(self, doc, indent=4):
+        out = []
+        for line in doc:
+            out += [' '*indent + line]
+        return out
+
+    def _str_signature(self):
+        if self['Signature']:
+            return [self['Signature'].replace('*','\*')] + ['']
+        else:
+            return ['']
+
+    def _str_summary(self):
+        if self['Summary']:
+            return self['Summary'] + ['']
+        else:
+            return []
+
+    def _str_extended_summary(self):
+        if self['Extended Summary']:
+            return self['Extended Summary'] + ['']
+        else:
+            return []
+
+    def _str_param_list(self, name):
+        out = []
+        if self[name]:
+            out += self._str_header(name)
+            for param,param_type,desc in self[name]:
+                out += ['%s : %s' % (param, param_type)]
+                out += self._str_indent(desc)
+            out += ['']
+        return out
+
+    def _str_section(self, name):
+        out = []
+        if self[name]:
+            out += self._str_header(name)
+            out += self[name]
+            out += ['']
+        return out
+
+    def _str_see_also(self, func_role):
+        if not self['See Also']: return []
+        out = []
+        out += self._str_header("See Also")
+        last_had_desc = True
+        for func, desc, role in self['See Also']:
+            if role:
+                link = ':%s:`%s`' % (role, func)
+            elif func_role:
+                link = ':%s:`%s`' % (func_role, func)
+            else:
+                link = "`%s`_" % func
+            if desc or last_had_desc:
+                out += ['']
+                out += [link]
+            else:
+                out[-1] += ", %s" % link
+            if desc:
+                out += self._str_indent([' '.join(desc)])
+                last_had_desc = True
+            else:
+                last_had_desc = False
+        out += ['']
+        return out
+
+    def _str_index(self):
+        idx = self['index']
+        out = []
+        out += ['.. index:: %s' % idx.get('default','')]
+        for section, references in idx.items():
+            if section == 'default':
+                continue
+            out += ['   :%s: %s' % (section, ', '.join(references))]
+        return out
+
+    def __str__(self, func_role=''):
+        out = []
+        out += self._str_signature()
+        out += self._str_summary()
+        out += self._str_extended_summary()
+        for param_list in ('Parameters','Returns','Raises'):
+            out += self._str_param_list(param_list)
+        out += self._str_section('Warnings')
+        out += self._str_see_also(func_role)
+        for s in ('Notes','References','Examples'):
+            out += self._str_section(s)
+        for param_list in ('Attributes', 'Methods'):
+            out += self._str_param_list(param_list)
+        out += self._str_index()
+        return '\n'.join(out)
+
+
+def indent(str,indent=4):
+    indent_str = ' '*indent
+    if str is None:
+        return indent_str
+    lines = str.split('\n')
+    return '\n'.join(indent_str + l for l in lines)
+
+def dedent_lines(lines):
+    """Deindent a list of lines maximally"""
+    return textwrap.dedent("\n".join(lines)).split("\n")
+
+def header(text, style='-'):
+    return text + '\n' + style*len(text) + '\n'
+
+
+class FunctionDoc(NumpyDocString):
+    def __init__(self, func, role='func', doc=None, config={}):
+        self._f = func
+        self._role = role # e.g. "func" or "meth"
+
+        if doc is None:
+            if func is None:
+                raise ValueError("No function or docstring given")
+            doc = inspect.getdoc(func) or ''
+        NumpyDocString.__init__(self, doc)
+
+        if not self['Signature'] and func is not None:
+            func, func_name = self.get_func()
+            try:
+                # try to read signature
+                argspec = inspect.getargspec(func)
+                argspec = inspect.formatargspec(*argspec)
+                argspec = argspec.replace('*','\*')
+                signature = '%s%s' % (func_name, argspec)
+            except TypeError as e:
+                signature = '%s()' % func_name
+            self['Signature'] = signature
+
+    def get_func(self):
+        func_name = getattr(self._f, '__name__', self.__class__.__name__)
+        if inspect.isclass(self._f):
+            func = getattr(self._f, '__call__', self._f.__init__)
+        else:
+            func = self._f
+        return func, func_name
+
+    def __str__(self):
+        out = ''
+
+        func, func_name = self.get_func()
+        signature = self['Signature'].replace('*', '\*')
+
+        roles = {'func': 'function',
+                 'meth': 'method'}
+
+        if self._role:
+            if not self._role in roles:
+                print("Warning: invalid role %s" % self._role)
+            out += '.. %s:: %s\n    \n\n' % (roles.get(self._role,''),
+                                             func_name)
+
+        out += super(FunctionDoc, self).__str__(func_role=self._role)
+        return out
+
+
+class ClassDoc(NumpyDocString):
+    def __init__(self, cls, doc=None, modulename='', func_doc=FunctionDoc,
+                 config={}):
+        if not inspect.isclass(cls) and cls is not None:
+            raise ValueError("Expected a class or None, but got %r" % cls)
+        self._cls = cls
+
+        if modulename and not modulename.endswith('.'):
+            modulename += '.'
+        self._mod = modulename
+
+        if doc is None:
+            if cls is None:
+                raise ValueError("No class or documentation string given")
+            doc = pydoc.getdoc(cls)
+
+        NumpyDocString.__init__(self, doc)
+
+        if config.get('show_class_members', True):
+            if not self['Methods']:
+                self['Methods'] = [(name, '', '')
+                                   for name in sorted(self.methods)]
+            if not self['Attributes']:
+                self['Attributes'] = [(name, '', '')
+                                      for name in sorted(self.properties)]
+
+    @property
+    def methods(self):
+        if self._cls is None:
+            return []
+        return [name for name,func in inspect.getmembers(self._cls)
+                if not name.startswith('_') and callable(func)]
+
+    @property
+    def properties(self):
+        if self._cls is None:
+            return []
+        return [name for name,func in inspect.getmembers(self._cls)
+                if not name.startswith('_') and func is None]
diff --git a/doc/ext/docscrape_sphinx.py b/doc/ext/docscrape_sphinx.py
new file mode 100644
index 0000000..9e66c4b
--- /dev/null
+++ b/doc/ext/docscrape_sphinx.py
@@ -0,0 +1,227 @@
+import re, inspect, textwrap, pydoc
+import sphinx
+from docscrape import NumpyDocString, FunctionDoc, ClassDoc
+
+
+class SphinxDocString(NumpyDocString):
+    def __init__(self, docstring, config={}):
+        self.use_plots = config.get('use_plots', False)
+        NumpyDocString.__init__(self, docstring, config=config)
+
+    # string conversion routines
+    def _str_header(self, name, symbol='`'):
+        return ['.. rubric:: ' + name, '']
+
+    def _str_field_list(self, name):
+        return [':' + name + ':']
+
+    def _str_indent(self, doc, indent=4):
+        out = []
+        for line in doc:
+            out += [' '*indent + line]
+        return out
+
+    def _str_signature(self):
+        return ['']
+        if self['Signature']:
+            return ['``%s``' % self['Signature']] + ['']
+        else:
+            return ['']
+
+    def _str_summary(self):
+        return self['Summary'] + ['']
+
+    def _str_extended_summary(self):
+        return self['Extended Summary'] + ['']
+
+    def _str_param_list(self, name):
+        out = []
+        if self[name]:
+            out += self._str_field_list(name)
+            out += ['']
+            for param,param_type,desc in self[name]:
+                out += self._str_indent(['**%s** : %s' % (param.strip(),
+                                                          param_type)])
+                out += ['']
+                out += self._str_indent(desc,8)
+                out += ['']
+        return out
+
+    @property
+    def _obj(self):
+        if hasattr(self, '_cls'):
+            return self._cls
+        elif hasattr(self, '_f'):
+            return self._f
+        return None
+
+    def _str_member_list(self, name):
+        """
+        Generate a member listing, autosummary:: table where possible,
+        and a table where not.
+
+        """
+        out = []
+        if self[name]:
+            out += ['.. rubric:: %s' % name, '']
+            prefix = getattr(self, '_name', '')
+
+            if prefix:
+                prefix = '~%s.' % prefix
+
+            autosum = []
+            others = []
+            for param, param_type, desc in self[name]:
+                param = param.strip()
+                if not self._obj or hasattr(self._obj, param):
+                    autosum += ["   %s%s" % (prefix, param)]
+                else:
+                    others.append((param, param_type, desc))
+
+            if autosum:
+                out += ['.. autosummary::', '   :toctree:', '']
+                out += autosum
+
+            if others:
+                maxlen_0 = max([len(x[0]) for x in others])
+                maxlen_1 = max([len(x[1]) for x in others])
+                hdr = "="*maxlen_0 + "  " + "="*maxlen_1 + "  " + "="*10
+                fmt = '%%%ds  %%%ds  ' % (maxlen_0, maxlen_1)
+                n_indent = maxlen_0 + maxlen_1 + 4
+                out += [hdr]
+                for param, param_type, desc in others:
+                    out += [fmt % (param.strip(), param_type)]
+                    out += self._str_indent(desc, n_indent)
+                out += [hdr]
+            out += ['']
+        return out
+
+    def _str_section(self, name):
+        out = []
+        if self[name]:
+            out += self._str_header(name)
+            out += ['']
+            content = textwrap.dedent("\n".join(self[name])).split("\n")
+            out += content
+            out += ['']
+        return out
+
+    def _str_see_also(self, func_role):
+        out = []
+        if self['See Also']:
+            see_also = super(SphinxDocString, self)._str_see_also(func_role)
+            out = ['.. seealso::', '']
+            out += self._str_indent(see_also[2:])
+        return out
+
+    def _str_warnings(self):
+        out = []
+        if self['Warnings']:
+            out = ['.. warning::', '']
+            out += self._str_indent(self['Warnings'])
+        return out
+
+    def _str_index(self):
+        idx = self['index']
+        out = []
+        if len(idx) == 0:
+            return out
+
+        out += ['.. index:: %s' % idx.get('default','')]
+        for section, references in idx.items():
+            if section == 'default':
+                continue
+            elif section == 'refguide':
+                out += ['   single: %s' % (', '.join(references))]
+            else:
+                out += ['   %s: %s' % (section, ','.join(references))]
+        return out
+
+    def _str_references(self):
+        out = []
+        if self['References']:
+            out += self._str_header('References')
+            if isinstance(self['References'], str):
+                self['References'] = [self['References']]
+            out.extend(self['References'])
+            out += ['']
+            # Latex collects all references to a separate bibliography,
+            # so we need to insert links to it
+            if sphinx.__version__ >= "0.6":
+                out += ['.. only:: latex','']
+            else:
+                out += ['.. latexonly::','']
+            items = []
+            for line in self['References']:
+                m = re.match(r'.. \[([a-z0-9._-]+)\]', line, re.I)
+                if m:
+                    items.append(m.group(1))
+            out += ['   ' + ", ".join(["[%s]_" % item for item in items]), '']
+        return out
+
+    def _str_examples(self):
+        examples_str = "\n".join(self['Examples'])
+
+        if (self.use_plots and 'import matplotlib' in examples_str
+                and 'plot::' not in examples_str):
+            out = []
+            out += self._str_header('Examples')
+            out += ['.. plot::', '']
+            out += self._str_indent(self['Examples'])
+            out += ['']
+            return out
+        else:
+            return self._str_section('Examples')
+
+    def __str__(self, indent=0, func_role="obj"):
+        out = []
+        out += self._str_signature()
+        out += self._str_index() + ['']
+        out += self._str_summary()
+        out += self._str_extended_summary()
+        for param_list in ('Parameters', 'Returns', 'Raises'):
+            out += self._str_param_list(param_list)
+        out += self._str_warnings()
+        out += self._str_see_also(func_role)
+        out += self._str_section('Notes')
+        out += self._str_references()
+        out += self._str_examples()
+        for param_list in ('Attributes', 'Methods'):
+            out += self._str_member_list(param_list)
+        out = self._str_indent(out,indent)
+        return '\n'.join(out)
+
+class SphinxFunctionDoc(SphinxDocString, FunctionDoc):
+    def __init__(self, obj, doc=None, config={}):
+        self.use_plots = config.get('use_plots', False)
+        FunctionDoc.__init__(self, obj, doc=doc, config=config)
+
+class SphinxClassDoc(SphinxDocString, ClassDoc):
+    def __init__(self, obj, doc=None, func_doc=None, config={}):
+        self.use_plots = config.get('use_plots', False)
+        ClassDoc.__init__(self, obj, doc=doc, func_doc=None, config=config)
+
+class SphinxObjDoc(SphinxDocString):
+    def __init__(self, obj, doc=None, config={}):
+        self._f = obj
+        SphinxDocString.__init__(self, doc, config=config)
+
+def get_doc_object(obj, what=None, doc=None, config={}):
+    if what is None:
+        if inspect.isclass(obj):
+            what = 'class'
+        elif inspect.ismodule(obj):
+            what = 'module'
+        elif callable(obj):
+            what = 'function'
+        else:
+            what = 'object'
+    if what == 'class':
+        return SphinxClassDoc(obj, func_doc=SphinxFunctionDoc, doc=doc,
+                              config=config)
+    elif what in ('function', 'method'):
+        return SphinxFunctionDoc(obj, doc=doc, config=config)
+    else:
+        if doc is None:
+            doc = pydoc.getdoc(obj)
+        return SphinxObjDoc(obj, doc, config=config)
diff --git a/doc/ext/examplesgenerator.py b/doc/ext/examplesgenerator.py
new file mode 100644
index 0000000..20c32f7
--- /dev/null
+++ b/doc/ext/examplesgenerator.py
@@ -0,0 +1,139 @@
+""" Called from vispy_conf.py to generate the examples for the docs from the 
+example Python files.
+"""
+
+from __future__ import print_function, division
+
+import os
+import sys
+import shutil
+
+try:
+    from urllib2 import urlopen
+except ImportError:
+    from urllib.request import urlopen # Py3k
+
+
+THIS_DIR = os.path.dirname(os.path.abspath(__file__))
+DOC_DIR = os.path.abspath(os.path.join(THIS_DIR, '..'))
+EXAMPLES_DIR = os.path.abspath(os.path.join(DOC_DIR, '..', 'examples'))
+OUTPUT_DIR = os.path.join(DOC_DIR, 'examples')
+
+
+
+def clean():
+    # Clear examples dir
+    if os.path.isdir(OUTPUT_DIR):
+        shutil.rmtree(OUTPUT_DIR)
+    # Clean examples file
+    fname = os.path.join(DOC_DIR, 'examples.rst')
+    if os.path.isfile(fname):
+        os.remove(fname)
+
+
+def main():
+    
+    clean()
+    
+    if not os.path.isdir(OUTPUT_DIR):
+        os.mkdir(OUTPUT_DIR)
+    
+    # Get examples and sort
+    examples = list( get_example_filenames(EXAMPLES_DIR) )
+    examples.sort(key=lambda x: x[1])
+    
+    create_examples(examples)
+    create_examples_list(examples)
+
+
+
+def get_example_filenames(examples_dir):
+    """ Yield (filename, name) elements for all examples. The examples
+    are organized in directories, therefore the name can contain a 
+    forward slash.
+    """
+    for (dirpath, dirnames, filenames) in os.walk(examples_dir):
+        for fname in filenames:
+            if not fname.endswith('.py'): continue
+            filename = os.path.join(dirpath, fname)
+            name = filename[len(examples_dir):].lstrip('/\\')[:-3]
+            name = name.replace('\\', '/')
+            yield filename, name
+
+
+
+def create_examples(examples):
+    
+    # Create doc file for each example
+    for filename, name in examples:
+        print('Writing example %s' % name)
+        
+        # Create title
+        lines = []
+        lines.append(name)
+        lines.append('-'*len(lines[-1]))
+        lines.append('')
+        
+        # Get source
+        in_gallery = False
+        doclines = []
+        sourcelines = []
+        with open(os.path.join(EXAMPLES_DIR, name+'.py')) as f:
+            for line in f.readlines():
+                line = line.rstrip()
+                if line.startswith('# vispy:') and 'gallery' in line:
+                    in_gallery = True
+                if not doclines:
+                    if line.startswith('"""'):
+                        doclines.append(line.lstrip('" '))
+                        sourcelines = []
+                    else:
+                        sourcelines.append('    ' + line)
+                elif not sourcelines:
+                    if '"""' in line:
+                        sourcelines.append('    ' + line.partition('"""')[0])
+                    else:
+                        doclines.append(line)
+                else:
+                    sourcelines.append('    ' + line)
+        
+        # Add desciprion 
+        lines.extend(doclines)
+        lines.append('')
+        
+        # Add source code
+        lines.append('.. code-block:: python')
+        lines.append('    ')
+        lines.extend(sourcelines)
+        lines.append('')
+        
+        # Write
+        output_filename = os.path.join(OUTPUT_DIR, name+'.rst')
+        output_dir = os.path.dirname(output_filename)
+        if not os.path.isdir(output_dir):
+            os.mkdir(output_dir)
+        with open(output_filename, 'w') as f:
+            f.write('\n'.join(lines))
+    
+
+
+def create_examples_list(examples):
+    
+    # Create TOC
+    lines = []
+    lines.append('List of examples')
+    lines.append('='*len(lines[-1]))
+    lines.append('')
+    
+    # Add entry for each example that we know
+    for _, name in examples:    
+        lines.append('* :doc:`examples/%s`' % name)
+    
+    # Write file
+    with open(os.path.join(DOC_DIR, 'examples.rst'), 'w') as f:
+        f.write('\n'.join(lines))
+
+
+if __name__ == '__main__':
+    main()
+
diff --git a/doc/ext/glapigenerator.py b/doc/ext/glapigenerator.py
new file mode 100644
index 0000000..dc08d44
--- /dev/null
+++ b/doc/ext/glapigenerator.py
@@ -0,0 +1,77 @@
+""" Called from vispy_ext.py to generate the GL API.
+"""
+
+import os
+import sys
+from vispy.gloo import gl
+
+THISDIR = os.path.dirname(os.path.abspath(__file__))
+DOCSDIR = os.path.join(THISDIR, '..')
+
+def clean():
+    fname = os.path.join(DOCSDIR, 'gl.rst')
+    if os.path.isfile(fname):
+        os.remove(fname)
+
+    
+def parse_api(ob):
+    # Parse the namespace
+    const_names = []
+    func_names = []
+    for name in dir(ob):
+        if name.startswith('GL_'):
+            const_names.append(name)
+        elif name.startswith('gl'):
+            func_names.append(name)
+    return const_names, func_names
+    
+
+def main():
+    
+    lines = []
+    
+    # Get info
+    gl_const_names, gl_func_names = parse_api(gl)
+    glext_const_names, glext_func_names = parse_api(gl.ext)
+    
+    # Write title
+    lines.append('OpenGL ES 2.0 API')
+    lines.append('='*len(lines[-1]))
+    lines.append('')
+    
+    # Some info
+    lines.append('The ``vispy.gloo.gl`` namespace provides the OpenGL ES 2.0 API,')
+    lines.append('Consisting of %i constants and %i functions ' %
+                        (len(gl_const_names), len(gl_func_names)) )
+    lines.append('(with %i and %i more in the extensions) ' %
+                        (len(glext_const_names), len(glext_func_names)) )
+            
+    lines.append('At this moment, the functions are taken from OpenGL.GL (provided by the PyOpenGL package).')
+    lines.append('')
+    
+    # Write class header
+    lines.append('**vispy.gloo.gl**\n')
+    
+    # Write constants and functions
+    for name in sorted(gl_func_names): 
+        lines.append('  * %s()' % name)
+    for name in sorted(gl_const_names):
+        lines.append('  * %s' % name)
+    
+    # Write class header
+    lines.append('**vispy.gloo.gl.ext**\n')
+    
+    # Write constants and functions
+    for name in sorted(glext_func_names): 
+        lines.append('  * %s()' % name)
+    for name in sorted(glext_const_names):
+        lines.append('  * %s' % name)
+    
+    
+    # Write file
+    with open(os.path.join(DOCSDIR, 'gl.rst'), 'w') as f:
+        f.write('\n'.join(lines))
+    
+
+if __name__ == '__main__':
+    main()
diff --git a/doc/ext/gloooverviewgenerator.py b/doc/ext/gloooverviewgenerator.py
new file mode 100644
index 0000000..8de9981
--- /dev/null
+++ b/doc/ext/gloooverviewgenerator.py
@@ -0,0 +1,94 @@
+""" Called from vispy_ext.py to generate the overview section of the
+gloo docs. This section is simply added to gloo.__doc__.
+"""
+
+from vispy import gloo
+
+def main():
+    gloo.__doc__ += generate_overview_docs()
+
+
+def clean():
+    pass
+
+    
+def get_docs_for_class(klass):
+    """ Get props and methods for a class.
+    """
+    
+    # Prepare
+    baseatts = dir(gloo.GLObject)
+    functype = type(gloo.GLObject.activate)
+    proptype = type(gloo.GLObject.handle)
+    props, funcs = set(), set()
+    
+    for att in sorted(dir(klass)):
+        if klass is not gloo.GLObject and att in baseatts:
+            continue
+        if att.startswith('_') or att.lower() != att:
+            continue
+        # Get ob and module name
+        attob = getattr(klass, att)
+        modulename = klass.__module__.split('.')[-1]
+        # Get actual klass
+        actualklass = klass
+        while True:
+            tmp = actualklass.__base__
+            if att in dir(tmp):
+                actualklass = tmp
+            else:
+                break
+        if actualklass == klass:
+            modulename = ''
+        # Append
+        if isinstance(attob, functype):
+            funcs.add(' :meth:`~%s.%s.%s`,' % (
+                                    modulename, actualklass.__name__, att))
+        elif isinstance(attob, proptype):
+            props.add(' :attr:`~%s.%s.%s`,' % (
+                                    modulename, actualklass.__name__, att))
+    # Done
+    return props, funcs
+
+
+def generate_overview_docs():
+    """ Generate the overview section for the gloo docs.
+    """
+    
+    lines = []
+    lines.append('Overview')
+    lines.append('='*len(lines[-1]))
+    
+    for klasses in [(gloo.GLObject,),
+                    (gloo.Program,),
+                    (gloo.VertexShader, gloo.FragmentShader), 
+                    (gloo.VertexBuffer, gloo.ElementBuffer), 
+                    (gloo.Texture2D, gloo.Texture3D, gloo.TextureCubeMap),
+                    (gloo.RenderBuffer,), 
+                    (gloo.FrameBuffer,),
+                ]:
+        # Init line
+        line = '*'
+        for klass in klasses:
+            line += ' :class:`%s`,' % klass.__name__
+        line = line[:-1]
+        # Get atts for these classes, sort by name, prop/func
+        funcs, props = set(), set()
+        for klass in klasses:
+            props_, funcs_ = get_docs_for_class(klass)
+            props.update(props_)
+            funcs.update(funcs_)
+        # Add props and funcs
+        if props:
+            line += '\n\n  * properties:'
+            for item in sorted(props):
+                line += item
+        if funcs:
+            line += '\n\n  * methods:'
+            for item in sorted(funcs):
+                line += item
+            # Add line, strip last char
+            lines.append(line[:-1]) 
+    
+    return '\n'.join(lines)
+
diff --git a/doc/ext/numpydoc.py b/doc/ext/numpydoc.py
new file mode 100644
index 0000000..933e212
--- /dev/null
+++ b/doc/ext/numpydoc.py
@@ -0,0 +1,166 @@
+"""
+========
+numpydoc
+========
+
+Sphinx extension that handles docstrings in the Numpy standard format. [1]
+
+It will:
+
+- Convert Parameters etc. sections to field lists.
+- Convert See Also section to a See also entry.
+- Renumber references.
+- Extract the signature from the docstring, if it can't be determined otherwise.
+
+.. [1] http://projects.scipy.org/numpy/wiki/CodingStyleGuidelines#docstring-standard
+
+"""
+
+import os, re, pydoc, sys
+from docscrape_sphinx import get_doc_object, SphinxDocString
+from sphinx.util.compat import Directive
+import inspect
+if sys.version_info > (3,):
+    unicode = str
+
+def mangle_docstrings(app, what, name, obj, options, lines,
+                      reference_offset=[0]):
+
+    cfg = dict(use_plots=app.config.numpydoc_use_plots,
+               show_class_members=app.config.numpydoc_show_class_members)
+
+    if what == 'module':
+        # Strip top title
+        title_re = re.compile(r'^\s*[#*=]{4,}\n[a-z0-9 -]+\n[#*=]{4,}\s*',
+                              re.I|re.S)
+        lines[:] = title_re.sub(u'', u"\n".join(lines)).split(u"\n")
+    else:
+        doc = get_doc_object(obj, what, u"\n".join(lines), config=cfg)
+        lines[:] = unicode(doc).split(u"\n")
+
+    if app.config.numpydoc_edit_link and hasattr(obj, '__name__') and \
+           obj.__name__:
+        if hasattr(obj, '__module__'):
+            v = dict(full_name=u"%s.%s" % (obj.__module__, obj.__name__))
+        else:
+            v = dict(full_name=obj.__name__)
+        lines += [u'', u'.. htmlonly::', '']
+        lines += [u'    %s' % x for x in
+                  (app.config.numpydoc_edit_link % v).split("\n")]
+
+    # replace reference numbers so that there are no duplicates
+    references = []
+    for line in lines:
+        line = line.strip()
+        m = re.match(r'^.. \[([a-z0-9_.-])\]', line, re.I)
+        if m:
+            references.append(m.group(1))
+
+    # start renaming from the longest string, to avoid overwriting parts
+    references.sort(key=lambda x: -len(x))
+    if references:
+        for i, line in enumerate(lines):
+            for r in references:
+                if re.match(r'^\d+$', r):
+                    new_r = u"R%d" % (reference_offset[0] + int(r))
+                else:
+                    new_r = u"%s%d" % (r, reference_offset[0])
+                lines[i] = lines[i].replace(u'[%s]_' % r,
+                                            u'[%s]_' % new_r)
+                lines[i] = lines[i].replace(u'.. [%s]' % r,
+                                            u'.. [%s]' % new_r)
+
+    reference_offset[0] += len(references)
+
+def mangle_signature(app, what, name, obj, options, sig, retann):
+    # Do not try to inspect classes that don't define `__init__`
+    if (inspect.isclass(obj) and
+        (not hasattr(obj, '__init__') or
+        'initializes x; see ' in pydoc.getdoc(obj.__init__))):
+        return '', ''
+
+    if not (callable(obj) or hasattr(obj, '__argspec_is_invalid_')): return
+    if not hasattr(obj, '__doc__'): return
+
+    doc = SphinxDocString(pydoc.getdoc(obj))
+    if doc['Signature']:
+        sig = re.sub(u"^[^(]*", u"", doc['Signature'])
+        return sig, u''
+
+def setup(app, get_doc_object_=get_doc_object):
+    global get_doc_object
+    get_doc_object = get_doc_object_
+
+    app.connect('autodoc-process-docstring', mangle_docstrings)
+    app.connect('autodoc-process-signature', mangle_signature)
+    app.add_config_value('numpydoc_edit_link', None, False)
+    app.add_config_value('numpydoc_use_plots', None, False)
+    app.add_config_value('numpydoc_show_class_members', True, True)
+
+    # Extra mangling domains
+    app.add_domain(NumpyPythonDomain)
+    app.add_domain(NumpyCDomain)
+
+#------------------------------------------------------------------------------
+# Docstring-mangling domains
+#------------------------------------------------------------------------------
+
+from docutils.statemachine import ViewList
+from sphinx.domains.c import CDomain
+from sphinx.domains.python import PythonDomain
+
+class ManglingDomainBase(object):
+    directive_mangling_map = {}
+
+    def __init__(self, *a, **kw):
+        super(ManglingDomainBase, self).__init__(*a, **kw)
+        self.wrap_mangling_directives()
+
+    def wrap_mangling_directives(self):
+        for name, objtype in self.directive_mangling_map.items():
+            self.directives[name] = wrap_mangling_directive(
+                self.directives[name], objtype)
+
+class NumpyPythonDomain(ManglingDomainBase, PythonDomain):
+    name = 'np'
+    directive_mangling_map = {
+        'function': 'function',
+        'class': 'class',
+        'exception': 'class',
+        'method': 'function',
+        'classmethod': 'function',
+        'staticmethod': 'function',
+        'attribute': 'attribute',
+    }
+
+class NumpyCDomain(ManglingDomainBase, CDomain):
+    name = 'np-c'
+    directive_mangling_map = {
+        'function': 'function',
+        'member': 'attribute',
+        'macro': 'function',
+        'type': 'class',
+        'var': 'object',
+    }
+
+def wrap_mangling_directive(base_directive, objtype):
+    class directive(base_directive):
+        def run(self):
+            env = self.state.document.settings.env
+
+            name = None
+            if self.arguments:
+                m = re.match(r'^(.*\s+)?(.*?)(\(.*)?', self.arguments[0])
+                name = m.group(2).strip()
+
+            if not name:
+                name = self.arguments[0]
+
+            lines = list(self.content)
+            mangle_docstrings(env.app, objtype, name, None, None, lines)
+            self.content = ViewList(lines, self.content.parent)
+
+            return base_directive.run(self)
+
+    return directive
+
diff --git a/doc/ext/scriptnamemangler.py b/doc/ext/scriptnamemangler.py
new file mode 100644
index 0000000..5e02bd3
--- /dev/null
+++ b/doc/ext/scriptnamemangler.py
@@ -0,0 +1,30 @@
+"""
+Readthedocs replaces some javascript sources to pull them from their own
+special server. Somehow their docstools.js is incompatibe, breaking
+the search tool of the docs. This hopefully fixes that.
+"""
+
+import os
+import shutil
+
+THISDIR = os.path.abspath(os.path.dirname(__file__))
+HTMLDIR = os.path.join(THISDIR, '..', '_build', 'html')
+
+
+def init():
+    pass
+
+def clean(app, *args):
+    # Rename doctools.js
+    shutil.copy(os.path.join(HTMLDIR, '_static', 'doctools.js'),
+                os.path.join(HTMLDIR, '_static', 'doctools_.js'))
+    # Change reference
+    search_html = os.path.join(HTMLDIR, 'search.html')
+    text = open(search_html, 'rt').read()
+    text = text.replace('/doctools.js', '/doctools_.js')
+    open(search_html, 'wt').write(text)
+
+
+def setup(app):
+    init()
+    app.connect('build-finished', clean)
diff --git a/doc/ext/vispy_ext.py b/doc/ext/vispy_ext.py
new file mode 100644
index 0000000..a7127a0
--- /dev/null
+++ b/doc/ext/vispy_ext.py
@@ -0,0 +1,26 @@
+""" Invoke various functionality for vispy docs.
+"""
+
+import examplesgenerator
+import glapigenerator
+import gloooverviewgenerator
+
+
+def init():
+    print('Generating examples.')
+    examplesgenerator.main()
+    print('Generating GL API.')
+    glapigenerator.main()
+    print('Generating gloo overview section.')
+    gloooverviewgenerator.main()
+
+
+def clean(app, *args):
+    examplesgenerator.clean()
+    glapigenerator.clean()
+    gloooverviewgenerator.clean()
+
+
+def setup(app):
+    init()
+    app.connect('build-finished', clean)
diff --git a/doc/gloo.rst b/doc/gloo.rst
new file mode 100644
index 0000000..fc70237
--- /dev/null
+++ b/doc/gloo.rst
@@ -0,0 +1,83 @@
+=====================================
+The object oriented OpenGL API (gloo)
+=====================================
+
+.. automodule:: vispy.gloo
+
+
+Base class
+==========
+
+
+.. autoclass:: vispy.gloo.GLObject
+    :members:
+
+
+
+Classes related to shaders
+==========================
+
+.. autoclass:: vispy.gloo.Program
+    :members:
+
+
+.. autoclass:: vispy.gloo.VertexShader
+    :members:
+
+.. autoclass:: vispy.gloo.FragmentShader
+    :members:
+
+.. autoclass:: vispy.gloo.shader.Shader
+    :members:
+
+
+
+Buffer classes
+==============
+
+
+.. autoclass:: vispy.gloo.VertexBuffer
+    :members:
+
+.. autoclass:: vispy.gloo.ElementBuffer
+    :members:
+
+.. autoclass:: vispy.gloo.buffer.DataBuffer
+    :members:
+
+.. autoclass:: vispy.gloo.buffer.Buffer
+    :members:
+
+
+Texture classes
+===============
+
+.. autoclass:: vispy.gloo.Texture2D
+
+.. autoclass:: vispy.gloo.Texture3D
+
+.. autoclass:: vispy.gloo.TextureCubeMap
+
+.. autoclass:: vispy.gloo.texture.Texture
+    :members:
+
+
+Classes related to FBO
+======================
+
+.. autoclass:: vispy.gloo.FrameBuffer
+    :members:
+
+.. autoclass:: vispy.gloo.RenderBuffer
+    :members:
+
+
+vispy.gloo.gl - low level GL API
+================================
+
+Vispy also exposes a (low level) functional GL API. At this point gloo
+is not yet fully independenat since it does not cover functions like
+glClear().
+
+ `vispy.gloo.gl docs <gl.html>`_
+ 
\ No newline at end of file
diff --git a/doc/index.rst b/doc/index.rst
new file mode 100644
index 0000000..a3aa7e6
--- /dev/null
+++ b/doc/index.rst
@@ -0,0 +1,37 @@
+.. vispy documentation master file, created by
+   sphinx-quickstart on Sat May  4 16:52:02 2013.
+   You can adapt this file completely to your liking, but it should at least
+   contain the root `toctree` directive.
+
+Welcome to vispy's documentation!
+=================================
+
+Vispy is a collaborative project that has the goal to allow more sharing
+of code between visualization projects based on OpenGL. It does this
+by providing powerful interfaces to OpenGL, at different levels of
+abstraction and generality.
+
+This is the auto-generated API documentation for the vispy library. 
+See http://vispy.org for more information on the vispy project.
+
+Contents:
+
+.. toctree::
+   :maxdepth: 2
+   
+   vispy.util - Utilities <util>
+   vispy.app - Application API <app>
+   vispy.gloo - object oriented GL API <gloo>
+   vispy.visuals <visuals>
+   
+   examples
+   releasenotes
+
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
+
diff --git a/doc/make.bat b/doc/make.bat
new file mode 100644
index 0000000..9cad97b
--- /dev/null
+++ b/doc/make.bat
@@ -0,0 +1,190 @@
+ at ECHO OFF
+
+REM Command file for Sphinx documentation
+
+if "%SPHINXBUILD%" == "" (
+	set SPHINXBUILD=sphinx-build
+)
+set BUILDDIR=_build
+set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
+set I18NSPHINXOPTS=%SPHINXOPTS% .
+if NOT "%PAPER%" == "" (
+	set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
+	set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
+)
+
+if "%1" == "" goto help
+
+if "%1" == "help" (
+	:help
+	echo.Please use `make ^<target^>` where ^<target^> is one of
+	echo.  html       to make standalone HTML files
+	echo.  dirhtml    to make HTML files named index.html in directories
+	echo.  singlehtml to make a single large HTML file
+	echo.  pickle     to make pickle files
+	echo.  json       to make JSON files
+	echo.  htmlhelp   to make HTML files and a HTML help project
+	echo.  qthelp     to make HTML files and a qthelp project
+	echo.  devhelp    to make HTML files and a Devhelp project
+	echo.  epub       to make an epub
+	echo.  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter
+	echo.  text       to make text files
+	echo.  man        to make manual pages
+	echo.  texinfo    to make Texinfo files
+	echo.  gettext    to make PO message catalogs
+	echo.  changes    to make an overview over all changed/added/deprecated items
+	echo.  linkcheck  to check all external links for integrity
+	echo.  doctest    to run all doctests embedded in the documentation if enabled
+	goto end
+)
+
+if "%1" == "clean" (
+	for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
+	del /q /s %BUILDDIR%\*
+	goto end
+)
+
+if "%1" == "html" (
+	%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished. The HTML pages are in %BUILDDIR%/html.
+	goto end
+)
+
+if "%1" == "dirhtml" (
+	%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
+	goto end
+)
+
+if "%1" == "singlehtml" (
+	%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
+	goto end
+)
+
+if "%1" == "pickle" (
+	%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished; now you can process the pickle files.
+	goto end
+)
+
+if "%1" == "json" (
+	%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished; now you can process the JSON files.
+	goto end
+)
+
+if "%1" == "htmlhelp" (
+	%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished; now you can run HTML Help Workshop with the ^
+.hhp project file in %BUILDDIR%/htmlhelp.
+	goto end
+)
+
+if "%1" == "qthelp" (
+	%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished; now you can run "qcollectiongenerator" with the ^
+.qhcp project file in %BUILDDIR%/qthelp, like this:
+	echo.^> qcollectiongenerator %BUILDDIR%\qthelp\vispy.qhcp
+	echo.To view the help file:
+	echo.^> assistant -collectionFile %BUILDDIR%\qthelp\vispy.ghc
+	goto end
+)
+
+if "%1" == "devhelp" (
+	%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished.
+	goto end
+)
+
+if "%1" == "epub" (
+	%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished. The epub file is in %BUILDDIR%/epub.
+	goto end
+)
+
+if "%1" == "latex" (
+	%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
+	goto end
+)
+
+if "%1" == "text" (
+	%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished. The text files are in %BUILDDIR%/text.
+	goto end
+)
+
+if "%1" == "man" (
+	%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished. The manual pages are in %BUILDDIR%/man.
+	goto end
+)
+
+if "%1" == "texinfo" (
+	%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
+	goto end
+)
+
+if "%1" == "gettext" (
+	%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
+	goto end
+)
+
+if "%1" == "changes" (
+	%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.The overview file is in %BUILDDIR%/changes.
+	goto end
+)
+
+if "%1" == "linkcheck" (
+	%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Link check complete; look for any errors in the above output ^
+or in %BUILDDIR%/linkcheck/output.txt.
+	goto end
+)
+
+if "%1" == "doctest" (
+	%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Testing of doctests in the sources finished, look at the ^
+results in %BUILDDIR%/doctest/output.txt.
+	goto end
+)
+
+:end
diff --git a/doc/releasenotes.rst b/doc/releasenotes.rst
new file mode 100644
index 0000000..3ebbb4f
--- /dev/null
+++ b/doc/releasenotes.rst
@@ -0,0 +1,30 @@
+=============
+Release notes
+=============
+
+**Vispy 0.2**
+
+
+In this release we focussed on improving and finalizing the object
+oriented OpenGL interface ``vispy.gloo``. Some major (backward
+incompatible) changes were done. However, from this release we consider
+the ``vispy.gloo`` package relatively stable and we try to minimize
+backward incompatibilities.
+
+Changes in more detail:
+
+  * ``vispy.oogl`` is renamed to ``vispy.gloo``
+  * ``vispy.gl`` is moved to ``vispy.gloo.gl`` since most users will
+    use gloo to interface with OpenGL.
+  * Improved (and thus changed) several parts of the gloo API.
+  * Some parts of gloo were refactored and should be more robust.
+  * Much better coverage of the test suite.
+  * Compatibility with Python 2.6 (Jerome Kieffer)
+  * More examples and a gallery on the website to show them off. 
+
+
+**Vispy 0.1.0**
+
+
+First release. We have an initial version of the object oriented interface
+to OpenGL, called `vispy.oogl`.
diff --git a/doc/shaders.svg b/doc/shaders.svg
new file mode 100644
index 0000000..b912bef
--- /dev/null
+++ b/doc/shaders.svg
@@ -0,0 +1,1318 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="1304.6836"
+   height="806.07904"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48+devel r"
+   sodipodi:docname="shaders.svg">
+  <defs
+     id="defs4">
+    <marker
+       inkscape:isstock="true"
+       style="overflow:visible"
+       id="marker12083"
+       refX="0"
+       refY="0"
+       orient="auto"
+       inkscape:stockid="Arrow1Mend">
+      <path
+         transform="matrix(-0.4,0,0,-0.4,-4,0)"
+         style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
+         d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
+         id="path12085"
+         inkscape:connector-curvature="0" />
+    </marker>
+    <marker
+       inkscape:isstock="true"
+       style="overflow:visible"
+       id="marker11953"
+       refX="0"
+       refY="0"
+       orient="auto"
+       inkscape:stockid="Arrow1Mend">
+      <path
+         transform="matrix(-0.4,0,0,-0.4,-4,0)"
+         style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
+         d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
+         id="path11955"
+         inkscape:connector-curvature="0" />
+    </marker>
+    <marker
+       inkscape:isstock="true"
+       style="overflow:visible"
+       id="marker11403"
+       refX="0"
+       refY="0"
+       orient="auto"
+       inkscape:stockid="Arrow1Mend">
+      <path
+         transform="matrix(-0.4,0,0,-0.4,-4,0)"
+         style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
+         d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
+         id="path11405"
+         inkscape:connector-curvature="0" />
+    </marker>
+    <marker
+       inkscape:stockid="Arrow1Mend"
+       orient="auto"
+       refY="0"
+       refX="0"
+       id="marker11259"
+       style="overflow:visible"
+       inkscape:isstock="true"
+       inkscape:collect="always">
+      <path
+         id="path11261"
+         d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
+         style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
+         transform="matrix(-0.4,0,0,-0.4,-4,0)"
+         inkscape:connector-curvature="0" />
+    </marker>
+    <marker
+       inkscape:isstock="true"
+       style="overflow:visible"
+       id="marker10947"
+       refX="0"
+       refY="0"
+       orient="auto"
+       inkscape:stockid="Arrow1Mend"
+       inkscape:collect="always">
+      <path
+         transform="matrix(-0.4,0,0,-0.4,-4,0)"
+         style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
+         d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
+         id="path10949"
+         inkscape:connector-curvature="0" />
+    </marker>
+    <marker
+       inkscape:stockid="Arrow1Mend"
+       orient="auto"
+       refY="0"
+       refX="0"
+       id="marker10423"
+       style="overflow:visible"
+       inkscape:isstock="true">
+      <path
+         id="path10425"
+         d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
+         style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
+         transform="matrix(-0.4,0,0,-0.4,-4,0)"
+         inkscape:connector-curvature="0" />
+    </marker>
+    <marker
+       inkscape:isstock="true"
+       style="overflow:visible"
+       id="marker10053"
+       refX="0"
+       refY="0"
+       orient="auto"
+       inkscape:stockid="Arrow1Mend">
+      <path
+         transform="matrix(-0.4,0,0,-0.4,-4,0)"
+         style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
+         d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
+         id="path10055"
+         inkscape:connector-curvature="0" />
+    </marker>
+    <marker
+       inkscape:stockid="Arrow1Mend"
+       orient="auto"
+       refY="0"
+       refX="0"
+       id="marker9893"
+       style="overflow:visible"
+       inkscape:isstock="true"
+       inkscape:collect="always">
+      <path
+         id="path9895"
+         d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
+         style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
+         transform="matrix(-0.4,0,0,-0.4,-4,0)"
+         inkscape:connector-curvature="0" />
+    </marker>
+    <marker
+       inkscape:stockid="Arrow1Mend"
+       orient="auto"
+       refY="0"
+       refX="0"
+       id="marker9331"
+       style="overflow:visible"
+       inkscape:isstock="true"
+       inkscape:collect="always">
+      <path
+         id="path9333"
+         d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
+         style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
+         transform="matrix(-0.4,0,0,-0.4,-4,0)"
+         inkscape:connector-curvature="0" />
+    </marker>
+    <marker
+       inkscape:isstock="true"
+       style="overflow:visible"
+       id="marker9019"
+       refX="0"
+       refY="0"
+       orient="auto"
+       inkscape:stockid="Arrow1Mend">
+      <path
+         transform="matrix(-0.4,0,0,-0.4,-4,0)"
+         style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
+         d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
+         id="path9021"
+         inkscape:connector-curvature="0" />
+    </marker>
+    <marker
+       inkscape:stockid="Arrow1Mend"
+       orient="auto"
+       refY="0"
+       refX="0"
+       id="marker8497"
+       style="overflow:visible"
+       inkscape:isstock="true"
+       inkscape:collect="always">
+      <path
+         id="path8499"
+         d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
+         style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
+         transform="matrix(-0.4,0,0,-0.4,-4,0)"
+         inkscape:connector-curvature="0" />
+    </marker>
+    <marker
+       inkscape:isstock="true"
+       style="overflow:visible"
+       id="marker7363"
+       refX="0"
+       refY="0"
+       orient="auto"
+       inkscape:stockid="Arrow1Mend"
+       inkscape:collect="always">
+      <path
+         transform="matrix(-0.4,0,0,-0.4,-4,0)"
+         style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
+         d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
+         id="path7365"
+         inkscape:connector-curvature="0" />
+    </marker>
+    <marker
+       inkscape:stockid="Arrow1Mend"
+       orient="auto"
+       refY="0"
+       refX="0"
+       id="marker6943"
+       style="overflow:visible"
+       inkscape:isstock="true"
+       inkscape:collect="always">
+      <path
+         id="path6945"
+         d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
+         style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
+         transform="matrix(-0.4,0,0,-0.4,-4,0)"
+         inkscape:connector-curvature="0" />
+    </marker>
+    <marker
+       inkscape:isstock="true"
+       style="overflow:visible"
+       id="marker6510"
+       refX="0"
+       refY="0"
+       orient="auto"
+       inkscape:stockid="Arrow1Mend"
+       inkscape:collect="always">
+      <path
+         transform="matrix(-0.4,0,0,-0.4,-4,0)"
+         style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
+         d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
+         id="path6512"
+         inkscape:connector-curvature="0" />
+    </marker>
+    <marker
+       inkscape:isstock="true"
+       style="overflow:visible"
+       id="marker6141"
+       refX="0"
+       refY="0"
+       orient="auto"
+       inkscape:stockid="Arrow1Mend"
+       inkscape:collect="always">
+      <path
+         transform="matrix(-0.4,0,0,-0.4,-4,0)"
+         style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
+         d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
+         id="path6143"
+         inkscape:connector-curvature="0" />
+    </marker>
+    <marker
+       inkscape:stockid="Arrow1Mend"
+       orient="auto"
+       refY="0"
+       refX="0"
+       id="marker5781"
+       style="overflow:visible"
+       inkscape:isstock="true"
+       inkscape:collect="always">
+      <path
+         id="path5783"
+         d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
+         style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
+         transform="matrix(-0.4,0,0,-0.4,-4,0)"
+         inkscape:connector-curvature="0" />
+    </marker>
+    <marker
+       inkscape:isstock="true"
+       style="overflow:visible"
+       id="marker5323"
+       refX="0"
+       refY="0"
+       orient="auto"
+       inkscape:stockid="Arrow1Mend">
+      <path
+         transform="matrix(-0.4,0,0,-0.4,-4,0)"
+         style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
+         d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
+         id="path5325"
+         inkscape:connector-curvature="0" />
+    </marker>
+    <marker
+       inkscape:stockid="Arrow1Mend"
+       orient="auto"
+       refY="0"
+       refX="0"
+       id="marker5053"
+       style="overflow:visible"
+       inkscape:isstock="true"
+       inkscape:collect="always">
+      <path
+         id="path5055"
+         d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
+         style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
+         transform="matrix(-0.4,0,0,-0.4,-4,0)"
+         inkscape:connector-curvature="0" />
+    </marker>
+    <marker
+       inkscape:isstock="true"
+       style="overflow:visible"
+       id="marker4332"
+       refX="0"
+       refY="0"
+       orient="auto"
+       inkscape:stockid="Arrow1Mend"
+       inkscape:collect="always">
+      <path
+         transform="matrix(-0.4,0,0,-0.4,-4,0)"
+         style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
+         d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
+         id="path4334"
+         inkscape:connector-curvature="0" />
+    </marker>
+    <marker
+       inkscape:stockid="Arrow1Mend"
+       orient="auto"
+       refY="0"
+       refX="0"
+       id="Arrow1Mend"
+       style="overflow:visible"
+       inkscape:isstock="true"
+       inkscape:collect="always">
+      <path
+         id="path4053"
+         d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
+         style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
+         transform="matrix(-0.4,0,0,-0.4,-4,0)"
+         inkscape:connector-curvature="0" />
+    </marker>
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="0.70710678"
+     inkscape:cx="850.65144"
+     inkscape:cy="516.02871"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     inkscape:window-width="1920"
+     inkscape:window-height="1132"
+     inkscape:window-x="-3"
+     inkscape:window-y="-3"
+     inkscape:window-maximized="1"
+     fit-margin-top="0"
+     fit-margin-left="0"
+     fit-margin-right="0"
+     fit-margin-bottom="0" />
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(-11.440706,-8.398777)">
+    <path
+       inkscape:connector-curvature="0"
+       id="path4330"
+       d="m 181.42858,439.50504 53.57143,0"
+       style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#marker4332)" />
+    <rect
+       style="fill:#ececec;fill-opacity:1;stroke:#000000;stroke-width:0.81;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+       id="rect6458"
+       width="806.10175"
+       height="189.50459"
+       x="48.162441"
+       y="465.46356" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#marker9331)"
+       d="m 756.14287,438.09082 53.57143,0 0,84.35714"
+       id="path9329"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#Arrow1Mend)"
+       d="m 12.142858,439.50504 53.571428,0"
+       id="path4038"
+       inkscape:connector-curvature="0" />
+    <text
+       xml:space="preserve"
+       style="font-size:10px;font-style:normal;font-weight:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+       x="11.142855"
+       y="408.21933"
+       id="text4320"
+       sodipodi:linespacing="125%"><tspan
+         sodipodi:role="line"
+         id="tspan4322"
+         x="11.142855"
+         y="408.21933">vec1,</tspan><tspan
+         sodipodi:role="line"
+         x="11.142855"
+         y="420.71933"
+         id="tspan4912">vec2,</tspan><tspan
+         sodipodi:role="line"
+         x="11.142855"
+         y="433.21933"
+         id="tspan4914">or vec3</tspan></text>
+    <g
+       id="g6817"
+       transform="translate(0,-22)">
+      <rect
+         y="420.93359"
+         x="66.428574"
+         height="89.285713"
+         width="127.14286"
+         id="rect4032"
+         style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-opacity:1" />
+      <text
+         sodipodi:linespacing="125%"
+         id="text4034"
+         y="440.93359"
+         x="77.857147"
+         style="font-size:15px;font-style:normal;font-weight:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+         xml:space="preserve"><tspan
+           y="440.93359"
+           x="77.857147"
+           id="tspan4036"
+           sodipodi:role="line">Position Input</tspan></text>
+      <text
+         sodipodi:linespacing="125%"
+         id="text4916"
+         y="461.64789"
+         x="83.571434"
+         style="font-size:10px;font-style:normal;font-weight:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+         xml:space="preserve"><tspan
+           y="461.64789"
+           x="83.571434"
+           id="tspan4918"
+           sodipodi:role="line">* Converts vec2/3</tspan><tspan
+           id="tspan4920"
+           y="474.14789"
+           x="83.571434"
+           sodipodi:role="line">to vec4</tspan><tspan
+           id="tspan4922"
+           y="486.64789"
+           x="83.571434"
+           sodipodi:role="line">* Or uses vec1 as </tspan><tspan
+           id="tspan4926"
+           y="499.14789"
+           x="83.571434"
+           sodipodi:role="line">index into texture</tspan></text>
+    </g>
+    <g
+       id="g4992"
+       transform="translate(-12.727922,12.727922)">
+      <rect
+         y="20.007307"
+         x="306.7818"
+         height="254.06822"
+         width="335.44757"
+         id="rect4954"
+         style="fill:#dadada;fill-opacity:1;stroke:#000000;stroke-width:0.81;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
+      <text
+         sodipodi:linespacing="125%"
+         id="text4956"
+         y="15.996433"
+         x="307.25101"
+         style="font-size:10px;font-style:normal;font-weight:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+         xml:space="preserve"><tspan
+           y="15.996433"
+           x="307.25101"
+           id="tspan4958"
+           sodipodi:role="line">Device coordinate system (eg, screen)</tspan></text>
+      <rect
+         y="72.230667"
+         x="348.38007"
+         height="187.16176"
+         width="231.44745"
+         id="rect4960"
+         style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.81;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
+      <text
+         sodipodi:linespacing="125%"
+         id="text4962"
+         y="52.036324"
+         x="348.43085"
+         style="font-size:10px;font-style:normal;font-weight:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+         xml:space="preserve"><tspan
+           y="52.036324"
+           x="348.43085"
+           id="tspan4964"
+           sodipodi:role="line">Document/figure coordinate system</tspan><tspan
+           id="tspan4966"
+           y="64.536324"
+           x="348.43085"
+           sodipodi:role="line">(physical units--px, cm, pt)</tspan></text>
+      <rect
+         y="179.61069"
+         x="358.58386"
+         height="72.73098"
+         width="100.0051"
+         id="rect4968"
+         style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.81;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
+      <rect
+         style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.81;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+         id="rect4970"
+         width="100.0051"
+         height="72.73098"
+         x="470.58386"
+         y="179.61069" />
+      <rect
+         style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.81;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+         id="rect4972"
+         width="100.0051"
+         height="72.73098"
+         x="358.58386"
+         y="97.610687" />
+      <rect
+         y="97.610687"
+         x="470.58386"
+         height="72.73098"
+         width="100.0051"
+         id="rect4974"
+         style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.81;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
+      <text
+         sodipodi:linespacing="125%"
+         id="text4976"
+         y="95.933609"
+         x="357.85715"
+         style="font-size:10px;font-style:normal;font-weight:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+         xml:space="preserve"><tspan
+           id="tspan4980"
+           y="95.933609"
+           x="357.85715"
+           sodipodi:role="line">View coordinates + clipping</tspan></text>
+      <path
+         sodipodi:nodetypes="cccscccccccccccccccsccccscc"
+         inkscape:connector-curvature="0"
+         id="path4986"
+         d="m 360,157.36218 3.57143,-16.42857 2.14286,10.71429 c 0,0 2.14285,-13.57143 2.14285,-10.71429 0,2.85714 2.85715,7.14286 2.85715,7.14286 L 375,128.79075 l 3.57143,16.42857 8.57143,12.14286 5.71428,-27.14286 2.85715,15 5.71428,-7.14285 2.14286,8.57143 6.42857,12.85714 1.42857,-11.42857 4.28572,-12.85715 2.85714,-9.28571 2.14286,13.57143 5,-13.57143 0.71428,12.85714 c 0,0 3.57143,-18.57143 3.57143,-6.42857 0,12.14286 2.14286,20 2.14286,20 l 6.42857,-10.71428 2.14286,15.71428 7.85 [...]
+         style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+      <text
+         sodipodi:linespacing="125%"
+         id="text4988"
+         y="124.50504"
+         x="366.42856"
+         style="font-size:10px;font-style:normal;font-weight:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+         xml:space="preserve"><tspan
+           y="124.50504"
+           x="366.42856"
+           id="tspan4990"
+           sodipodi:role="line">Data coordinates</tspan></text>
+    </g>
+    <flowRoot
+       xml:space="preserve"
+       id="flowRoot5010"
+       style="font-size:10px;font-style:normal;font-weight:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><flowRegion
+         id="flowRegion5012"><rect
+           id="rect5014"
+           width="60"
+           height="52.142857"
+           x="255"
+           y="349.50504" /></flowRegion><flowPara
+         id="flowPara5016"></flowPara></flowRoot>    <path
+       style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#marker5781)"
+       d="m 414.14287,439.50504 53.57143,0"
+       id="path5779"
+       inkscape:connector-curvature="0" />
+    <g
+       id="g5603"
+       transform="translate(-12,-30)">
+      <rect
+         y="395.93359"
+         x="248.57143"
+         height="122.85714"
+         width="205.71428"
+         id="rect4930"
+         style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.81;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
+      <text
+         sodipodi:linespacing="125%"
+         id="text4932"
+         y="427.36218"
+         x="272.14285"
+         style="font-size:15px;font-style:normal;font-weight:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+         xml:space="preserve"><tspan
+           y="427.36218"
+           x="272.14285"
+           id="tspan4934"
+           sodipodi:role="line">View Transform Chain</tspan></text>
+      <text
+         sodipodi:linespacing="125%"
+         id="text4936"
+         y="446.64789"
+         x="267.14285"
+         style="font-size:10px;font-style:normal;font-weight:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+         xml:space="preserve"><tspan
+           y="446.64789"
+           x="267.14285"
+           id="tspan4938"
+           sodipodi:role="line">Transforms vertex from the Visual's</tspan><tspan
+           id="tspan4940"
+           y="459.14789"
+           x="267.14285"
+           sodipodi:role="line">data coordinate system to its</tspan><tspan
+           id="tspan4942"
+           y="471.64789"
+           x="267.14285"
+           sodipodi:role="line">position within the enclosing </tspan><tspan
+           id="tspan4944"
+           y="484.14789"
+           x="267.14285"
+           sodipodi:role="line">view box. Clipping takes place at</tspan><tspan
+           y="496.64789"
+           x="267.14285"
+           sodipodi:role="line"
+           id="tspan5673">this coordinate system.</tspan></text>
+      <path
+         inkscape:connector-curvature="0"
+         id="path5051"
+         d="m 265.57144,371.6479 53.57143,0"
+         style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#marker5053)" />
+      <g
+         id="g5024">
+        <rect
+           style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.81;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+           id="rect5018"
+           width="55"
+           height="57.857143"
+           x="252.85715"
+           y="343.07648" />
+        <text
+           xml:space="preserve"
+           style="font-size:10px;font-style:normal;font-weight:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+           x="255.71428"
+           y="358.79077"
+           id="text5020"
+           sodipodi:linespacing="125%"><tspan
+             sodipodi:role="line"
+             id="tspan5022"
+             x="255.71428"
+             y="358.79077">Transform</tspan></text>
+      </g>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#marker5323)"
+         d="m 334.85715,371.6479 53.57143,0"
+         id="path5321"
+         inkscape:connector-curvature="0" />
+      <g
+         transform="translate(68,0)"
+         id="g5029">
+        <rect
+           y="343.07648"
+           x="252.85715"
+           height="57.857143"
+           width="55"
+           id="rect5031"
+           style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.81;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
+        <text
+           sodipodi:linespacing="125%"
+           id="text5033"
+           y="358.79077"
+           x="255.71428"
+           style="font-size:10px;font-style:normal;font-weight:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+           xml:space="preserve"><tspan
+             y="358.79077"
+             x="255.71428"
+             id="tspan5035"
+             sodipodi:role="line">Transform</tspan></text>
+      </g>
+      <g
+         id="g5037"
+         transform="translate(136,0)">
+        <rect
+           style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.81;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+           id="rect5039"
+           width="55"
+           height="57.857143"
+           x="252.85715"
+           y="343.07648" />
+        <text
+           xml:space="preserve"
+           style="font-size:10px;font-style:normal;font-weight:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+           x="255.71428"
+           y="358.79077"
+           id="text5041"
+           sodipodi:linespacing="125%"><tspan
+             sodipodi:role="line"
+             id="tspan5043"
+             x="255.71428"
+             y="358.79077">Transform</tspan></text>
+      </g>
+    </g>
+    <path
+       inkscape:connector-curvature="0"
+       id="path6139"
+       d="m 650.14287,439.50504 53.57143,0"
+       style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#marker6141)" />
+    <g
+       id="g5627"
+       transform="translate(220,-30)">
+      <rect
+         style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.81;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+         id="rect5629"
+         width="205.71428"
+         height="122.85714"
+         x="248.57143"
+         y="395.93359" />
+      <text
+         xml:space="preserve"
+         style="font-size:15px;font-style:normal;font-weight:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+         x="248.14285"
+         y="427.36218"
+         id="text5631"
+         sodipodi:linespacing="125%"><tspan
+           sodipodi:role="line"
+           id="tspan5633"
+           x="248.14285"
+           y="427.36218">Document Transform Chain</tspan></text>
+      <text
+         xml:space="preserve"
+         style="font-size:10px;font-style:normal;font-weight:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+         x="263.57141"
+         y="445.21933"
+         id="text5635"
+         sodipodi:linespacing="125%"><tspan
+           sodipodi:role="line"
+           x="263.57141"
+           y="445.21933"
+           id="tspan5643">Transforms to document or </tspan><tspan
+           sodipodi:role="line"
+           x="263.57141"
+           y="457.71933"
+           id="tspan5684">'figure' coordinate system. This</tspan><tspan
+           sodipodi:role="line"
+           x="263.57141"
+           y="470.21933"
+           id="tspan5686">system is expressed in physical</tspan><tspan
+           sodipodi:role="line"
+           x="263.57141"
+           y="482.71933"
+           id="tspan5688">units such as px, pt, or cm. Line</tspan><tspan
+           sodipodi:role="line"
+           x="263.57141"
+           y="495.21933"
+           id="tspan5690">widths and sprite sizes are often</tspan><tspan
+           sodipodi:role="line"
+           x="263.57141"
+           y="507.71933"
+           id="tspan5692">defined in this coordinate system.</tspan></text>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#marker5053)"
+         d="m 265.57144,371.6479 53.57143,0"
+         id="path5645"
+         inkscape:connector-curvature="0" />
+      <g
+         id="g5647">
+        <rect
+           y="343.07648"
+           x="252.85715"
+           height="57.857143"
+           width="55"
+           id="rect5649"
+           style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.81;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
+        <text
+           sodipodi:linespacing="125%"
+           id="text5651"
+           y="358.79077"
+           x="255.71428"
+           style="font-size:10px;font-style:normal;font-weight:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+           xml:space="preserve"><tspan
+             y="358.79077"
+             x="255.71428"
+             id="tspan5653"
+             sodipodi:role="line">Transform</tspan></text>
+      </g>
+      <path
+         inkscape:connector-curvature="0"
+         id="path5655"
+         d="m 334.85715,371.6479 53.57143,0"
+         style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#marker5323)" />
+      <g
+         id="g5657"
+         transform="translate(68,0)">
+        <rect
+           style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.81;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+           id="rect5659"
+           width="55"
+           height="57.857143"
+           x="252.85715"
+           y="343.07648" />
+        <text
+           xml:space="preserve"
+           style="font-size:10px;font-style:normal;font-weight:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+           x="255.71428"
+           y="358.79077"
+           id="text5661"
+           sodipodi:linespacing="125%"><tspan
+             sodipodi:role="line"
+             id="tspan5663"
+             x="255.71428"
+             y="358.79077">Transform</tspan></text>
+      </g>
+      <g
+         transform="translate(136,0)"
+         id="g5665">
+        <rect
+           y="343.07648"
+           x="252.85715"
+           height="57.857143"
+           width="55"
+           id="rect5667"
+           style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.81;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
+        <text
+           sodipodi:linespacing="125%"
+           id="text5669"
+           y="358.79077"
+           x="255.71428"
+           style="font-size:10px;font-style:normal;font-weight:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+           xml:space="preserve"><tspan
+             y="358.79077"
+             x="255.71428"
+             id="tspan5671"
+             sodipodi:role="line">Transform</tspan></text>
+      </g>
+    </g>
+    <g
+       transform="translate(452.5,51.071419)"
+       id="g5665-8">
+      <rect
+         y="343.07648"
+         x="252.85715"
+         height="92.14286"
+         width="95"
+         id="rect5667-5"
+         style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.81;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
+      <text
+         sodipodi:linespacing="125%"
+         id="text5669-8"
+         y="361.64792"
+         x="264.28571"
+         style="font-size:15px;font-style:normal;font-weight:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+         xml:space="preserve"><tspan
+           y="361.64792"
+           x="264.28571"
+           id="tspan5671-9"
+           sodipodi:role="line">Device</tspan><tspan
+           y="380.39792"
+           x="264.28571"
+           sodipodi:role="line"
+           id="tspan5771">Transform</tspan></text>
+    </g>
+    <text
+       xml:space="preserve"
+       style="font-size:25px;font-style:normal;font-weight:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+       x="346.88638"
+       y="585.87372"
+       id="text6460"
+       sodipodi:linespacing="125%"><tspan
+         sodipodi:role="line"
+         id="tspan6462"
+         x="346.88638"
+         y="585.87372">Main Vertex Shader</tspan></text>
+    <path
+       inkscape:connector-curvature="0"
+       id="path6500"
+       d="m 155.82818,383.01772 77.57143,0"
+       style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#marker6510)"
+       sodipodi:nodetypes="cc" />
+    <text
+       sodipodi:linespacing="125%"
+       id="text6502"
+       y="350.44629"
+       x="155.11389"
+       style="font-size:10px;font-style:normal;font-weight:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+       xml:space="preserve"><tspan
+         id="tspan6508"
+         y="350.44629"
+         x="155.11389"
+         sodipodi:role="line">Per-transform</tspan><tspan
+         y="362.94629"
+         x="155.11389"
+         sodipodi:role="line"
+         id="tspan6813">uniforms and</tspan><tspan
+         y="375.44629"
+         x="155.11389"
+         sodipodi:role="line"
+         id="tspan6815">attributes</tspan></text>
+    <path
+       style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#marker6943)"
+       d="m 211.13446,439.35754 0,81.59174"
+       id="path6941"
+       inkscape:connector-curvature="0"
+       sodipodi:nodetypes="cc" />
+    <path
+       sodipodi:nodetypes="cc"
+       inkscape:connector-curvature="0"
+       id="path7361"
+       d="m 451.13446,439.35754 0,81.59174"
+       style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#marker7363)" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#marker8497)"
+       d="m 685.13446,439.35754 0,81.59174"
+       id="path8495"
+       inkscape:connector-curvature="0"
+       sodipodi:nodetypes="cc" />
+    <path
+       inkscape:connector-curvature="0"
+       id="path10945"
+       d="m 514.14287,705.68221 53.57143,0 0,-84.35714"
+       style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#marker10947)" />
+    <g
+       transform="translate(97.923882,249.13918)"
+       id="g9797">
+      <rect
+         style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.81;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+         id="rect9799"
+         width="205.71428"
+         height="122.85714"
+         x="248.57143"
+         y="395.93359" />
+      <text
+         xml:space="preserve"
+         style="font-size:15px;font-style:normal;font-weight:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+         x="272.14285"
+         y="427.36218"
+         id="text9801"
+         sodipodi:linespacing="125%"><tspan
+           sodipodi:role="line"
+           id="tspan9803"
+           x="272.14285"
+           y="427.36218">Color Transform Chain</tspan></text>
+      <text
+         xml:space="preserve"
+         style="font-size:10px;font-style:normal;font-weight:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+         x="267.14285"
+         y="446.64789"
+         id="text9805"
+         sodipodi:linespacing="125%"><tspan
+           id="tspan9815"
+           sodipodi:role="line"
+           x="267.14285"
+           y="446.64789">Adjusts vertex colors based on</tspan><tspan
+           sodipodi:role="line"
+           x="267.14285"
+           y="459.14789"
+           id="tspan10694">Vertex position or other uniform/</tspan><tspan
+           sodipodi:role="line"
+           x="267.14285"
+           y="471.64789"
+           id="tspan10696">attribute inputs</tspan></text>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#marker5053)"
+         d="m 266.98565,535.6479 53.57143,0"
+         id="path9817"
+         inkscape:connector-curvature="0" />
+      <g
+         id="g9819"
+         transform="translate(1.4142136,164)">
+        <rect
+           y="343.07648"
+           x="252.85715"
+           height="57.857143"
+           width="55"
+           id="rect9821"
+           style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.81;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
+        <text
+           sodipodi:linespacing="125%"
+           id="text9823"
+           y="358.79077"
+           x="255.71428"
+           style="font-size:10px;font-style:normal;font-weight:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+           xml:space="preserve"><tspan
+             y="358.79077"
+             x="255.71428"
+             id="tspan9825"
+             sodipodi:role="line">Transform</tspan></text>
+      </g>
+      <path
+         inkscape:connector-curvature="0"
+         id="path9827"
+         d="m 336.27136,535.6479 53.57143,0"
+         style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#marker5323)" />
+      <g
+         id="g9829"
+         transform="translate(69.414214,164)">
+        <rect
+           style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.81;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+           id="rect9831"
+           width="55"
+           height="57.857143"
+           x="252.85715"
+           y="343.07648" />
+        <text
+           xml:space="preserve"
+           style="font-size:10px;font-style:normal;font-weight:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+           x="255.71428"
+           y="358.79077"
+           id="text9833"
+           sodipodi:linespacing="125%"><tspan
+             sodipodi:role="line"
+             id="tspan9835"
+             x="255.71428"
+             y="358.79077">Transform</tspan></text>
+      </g>
+      <g
+         transform="translate(137.41421,164)"
+         id="g9837">
+        <rect
+           y="343.07648"
+           x="252.85715"
+           height="57.857143"
+           width="55"
+           id="rect9839"
+           style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.81;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
+        <text
+           sodipodi:linespacing="125%"
+           id="text9841"
+           y="358.79077"
+           x="255.71428"
+           style="font-size:10px;font-style:normal;font-weight:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+           xml:space="preserve"><tspan
+             y="358.79077"
+             x="255.71428"
+             id="tspan9843"
+             sodipodi:role="line">Transform</tspan></text>
+      </g>
+    </g>
+    <path
+       style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#marker9893)"
+       d="m 289.42858,702.50504 53.57143,0"
+       id="path9851"
+       inkscape:connector-curvature="0" />
+    <path
+       inkscape:connector-curvature="0"
+       id="path9853"
+       d="m 120.14286,702.50504 53.57143,0"
+       style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#Arrow1Mend)" />
+    <text
+       sodipodi:linespacing="125%"
+       id="text9855"
+       y="671.21936"
+       x="119.14285"
+       style="font-size:10px;font-style:normal;font-weight:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+       xml:space="preserve"><tspan
+         y="671.21936"
+         x="119.14285"
+         id="tspan9857"
+         sodipodi:role="line">uniform,</tspan><tspan
+         id="tspan9859"
+         y="683.71936"
+         x="119.14285"
+         sodipodi:role="line">RGB,</tspan><tspan
+         id="tspan9861"
+         y="696.21936"
+         x="119.14285"
+         sodipodi:role="line">RGBA</tspan></text>
+    <g
+       transform="translate(108,223)"
+       id="g9863">
+      <rect
+         style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-opacity:1"
+         id="rect9865"
+         width="127.14286"
+         height="89.285713"
+         x="66.428574"
+         y="420.93359" />
+      <text
+         xml:space="preserve"
+         style="font-size:15px;font-style:normal;font-weight:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+         x="77.857147"
+         y="440.93359"
+         id="text9867"
+         sodipodi:linespacing="125%"><tspan
+           sodipodi:role="line"
+           id="tspan9869"
+           x="77.857147"
+           y="440.93359">Color Input</tspan></text>
+      <text
+         xml:space="preserve"
+         style="font-size:10px;font-style:normal;font-weight:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+         x="76.500366"
+         y="467.30475"
+         id="text9871"
+         sodipodi:linespacing="125%"><tspan
+           sodipodi:role="line"
+           x="76.500366"
+           y="467.30475"
+           id="tspan9879">* Convert uniform</tspan><tspan
+           sodipodi:role="line"
+           x="76.500366"
+           y="479.80475"
+           id="tspan10690">or attribute color</tspan><tspan
+           sodipodi:role="line"
+           x="76.500366"
+           y="492.30475"
+           id="tspan10692">inputs to RGBA vec4</tspan></text>
+    </g>
+    <g
+       id="g10698"
+       transform="translate(49,-54)">
+      <path
+         inkscape:connector-curvature="0"
+         id="path9881"
+         d="m 216.63936,802.6609 77.57143,0"
+         style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#marker10423)"
+         sodipodi:nodetypes="cc" />
+      <text
+         sodipodi:linespacing="125%"
+         id="text9883"
+         y="816.08948"
+         x="215.92508"
+         style="font-size:10px;font-style:normal;font-weight:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+         xml:space="preserve"><tspan
+           id="tspan9885"
+           y="816.08948"
+           x="215.92508"
+           sodipodi:role="line">Per-transform</tspan><tspan
+           y="828.58948"
+           x="215.92508"
+           sodipodi:role="line"
+           id="tspan9887">uniforms and</tspan><tspan
+           y="841.08948"
+           x="215.92508"
+           sodipodi:role="line"
+           id="tspan9889">attributes</tspan></text>
+    </g>
+    <path
+       sodipodi:nodetypes="cc"
+       inkscape:connector-curvature="0"
+       id="path9891"
+       d="m 319.13446,701.76931 0,-81.59174"
+       style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#marker10053)" />
+    <rect
+       y="465.46356"
+       x="897.11212"
+       height="189.50458"
+       width="418.60724"
+       id="rect11245"
+       style="fill:#e9e9e9;fill-opacity:1;stroke:#000000;stroke-width:0.81;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
+    <text
+       sodipodi:linespacing="125%"
+       id="text11247"
+       y="569.38843"
+       x="973.48236"
+       style="font-size:25px;font-style:normal;font-weight:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+       xml:space="preserve"><tspan
+         y="569.38843"
+         x="973.48236"
+         id="tspan11249"
+         sodipodi:role="line">Main Fragment Shader</tspan></text>
+    <path
+       style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#marker11259)"
+       d="m 210.71782,523.44631 679.52962,0"
+       id="path11251"
+       inkscape:connector-curvature="0" />
+    <path
+       inkscape:connector-curvature="0"
+       id="path11401"
+       d="m 318.49599,617.44631 571.75145,0"
+       style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#marker11403)"
+       sodipodi:nodetypes="cc" />
+    <g
+       id="g11869"
+       transform="translate(768.8213,236.41126)">
+      <rect
+         y="395.93359"
+         x="248.57143"
+         height="122.85714"
+         width="205.71428"
+         id="rect11871"
+         style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.81;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
+      <text
+         sodipodi:linespacing="125%"
+         id="text11873"
+         y="427.36218"
+         x="272.14285"
+         style="font-size:15px;font-style:normal;font-weight:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+         xml:space="preserve"><tspan
+           y="427.36218"
+           x="272.14285"
+           id="tspan11875"
+           sodipodi:role="line">Color Transform Chain</tspan></text>
+      <text
+         sodipodi:linespacing="125%"
+         id="text11877"
+         y="448.0621"
+         x="254.41493"
+         style="font-size:10px;font-style:normal;font-weight:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+         xml:space="preserve"><tspan
+           y="448.0621"
+           x="254.41493"
+           sodipodi:role="line"
+           id="tspan11879">Adjusts fragment colors based on</tspan><tspan
+           id="tspan11881"
+           y="460.5621"
+           x="254.41493"
+           sodipodi:role="line">fragment position or other uniform/</tspan><tspan
+           id="tspan11883"
+           y="473.0621"
+           x="254.41493"
+           sodipodi:role="line">attribute inputs. View clipping happens</tspan><tspan
+           y="485.5621"
+           x="254.41493"
+           sodipodi:role="line"
+           id="tspan12219">here.</tspan></text>
+      <path
+         inkscape:connector-curvature="0"
+         id="path11885"
+         d="m 266.98565,535.6479 53.57143,0"
+         style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#marker5053)" />
+      <g
+         transform="translate(1.4142136,164)"
+         id="g11887">
+        <rect
+           style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.81;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+           id="rect11889"
+           width="55"
+           height="57.857143"
+           x="252.85715"
+           y="343.07648" />
+        <text
+           xml:space="preserve"
+           style="font-size:10px;font-style:normal;font-weight:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+           x="255.71428"
+           y="358.79077"
+           id="text11891"
+           sodipodi:linespacing="125%"><tspan
+             sodipodi:role="line"
+             id="tspan11893"
+             x="255.71428"
+             y="358.79077">Transform</tspan></text>
+      </g>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#marker5323)"
+         d="m 336.27136,535.6479 53.57143,0"
+         id="path11895"
+         inkscape:connector-curvature="0" />
+      <g
+         transform="translate(69.414214,164)"
+         id="g11897">
+        <rect
+           y="343.07648"
+           x="252.85715"
+           height="57.857143"
+           width="55"
+           id="rect11899"
+           style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.81;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
+        <text
+           sodipodi:linespacing="125%"
+           id="text11901"
+           y="358.79077"
+           x="255.71428"
+           style="font-size:10px;font-style:normal;font-weight:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+           xml:space="preserve"><tspan
+             y="358.79077"
+             x="255.71428"
+             id="tspan11903"
+             sodipodi:role="line">Transform</tspan></text>
+      </g>
+      <g
+         id="g11905"
+         transform="translate(137.41421,164)">
+        <rect
+           style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.81;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+           id="rect11907"
+           width="55"
+           height="57.857143"
+           x="252.85715"
+           y="343.07648" />
+        <text
+           xml:space="preserve"
+           style="font-size:10px;font-style:normal;font-weight:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+           x="255.71428"
+           y="358.79077"
+           id="text11909"
+           sodipodi:linespacing="125%"><tspan
+             sodipodi:role="line"
+             id="tspan11911"
+             x="255.71428"
+             y="358.79077">Transform</tspan></text>
+      </g>
+    </g>
+    <g
+       transform="translate(719.8974,-66.727922)"
+       id="g11941">
+      <path
+         sodipodi:nodetypes="cc"
+         style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#marker10423)"
+         d="m 216.63936,802.6609 77.57143,0"
+         id="path11943"
+         inkscape:connector-curvature="0" />
+      <text
+         xml:space="preserve"
+         style="font-size:10px;font-style:normal;font-weight:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+         x="215.92508"
+         y="816.08948"
+         id="text11945"
+         sodipodi:linespacing="125%"><tspan
+           sodipodi:role="line"
+           x="215.92508"
+           y="816.08948"
+           id="tspan11947">Per-transform</tspan><tspan
+           id="tspan11949"
+           sodipodi:role="line"
+           x="215.92508"
+           y="828.58948">uniforms and</tspan><tspan
+           id="tspan11951"
+           sodipodi:role="line"
+           x="215.92508"
+           y="841.08948">attributes</tspan></text>
+    </g>
+  </g>
+</svg>
diff --git a/doc/themes/scikit-image/layout.html b/doc/themes/scikit-image/layout.html
new file mode 100644
index 0000000..1c6163a
--- /dev/null
+++ b/doc/themes/scikit-image/layout.html
@@ -0,0 +1,113 @@
+{#
+    scikit-image/layout.html
+    ~~~~~~~~~~~~~~~~~
+
+    Sphinx layout template for the scikit-image theme
+
+#}
+
+{%- set url_root = pathto('', 1) %}
+{# XXX necessary? #}
+{%- if url_root == '#' %}{% set url_root = '' %}{% endif %}
+{%- if not embedded and docstitle %}
+  {%- set titlesuffix = " — "|safe + docstitle|e %}
+{%- else %}
+  {%- set titlesuffix = "" %}
+{%- endif %}
+
+{%- macro script() %}
+    <script src="http://code.jquery.com/jquery-latest.js"></script>
+    <script src="{{ pathto('_static/', 1) }}js/bootstrap.min.js"></script>
+    <script type="text/javascript">
+      var DOCUMENTATION_OPTIONS = {
+        URL_ROOT:    '{{ url_root }}',
+        VERSION:     '{{ release|e }}',
+        COLLAPSE_INDEX: false,
+        FILE_SUFFIX: '{{ '' if no_search_suffix else file_suffix }}',
+        HAS_SOURCE:  {{ has_source|lower }}
+      };
+    </script>
+    {%- for scriptfile in script_files %}
+        <script type="text/javascript" src="{{ pathto(scriptfile, 1) }}"></script>
+    {%- endfor %}
+{%- endmacro %}
+
+{%- macro css() %}
+    <link rel="stylesheet" href="{{ pathto('_static/pygments.css', 1) }}" type="text/css" />
+    <link href="{{ pathto('_static/', 1) }}css/bootstrap.min.css" rel="stylesheet" type="text/css">
+    <link href="{{ pathto('_static/', 1) }}css/custom.css" rel="stylesheet" type="text/css">
+    <link href="http://fonts.googleapis.com/css?family=Raleway" rel="stylesheet" type="text/css">
+    {%- for cssfile in css_files %}
+        <link rel="stylesheet" href="{{ pathto(cssfile, 1) }}" type="text/css" />
+    {%- endfor %}
+{%- endmacro %}
+
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    {%- block htmltitle %}
+        <title>{{ title|striptags|e }}{{ titlesuffix }}</title>
+    {%- endblock %}
+    {{ metatags }}
+    {{ css() }}
+    {{ script() }}
+    {%- if hasdoc('about') %}
+        <link rel="author" title="{{ _('About these documents') }}" href="{{ pathto('about') }}" />
+    {%- endif %}
+    {%- if hasdoc('genindex') %}
+        <link rel="index" title="{{ _('Index') }}" href="{{ pathto('genindex') }}" />
+    {%- endif %}
+    {%- if hasdoc('search') %}
+        <link rel="search" title="{{ _('Search') }}" href="{{ pathto('search') }}" />
+    {%- endif %}
+    {%- if hasdoc('copyright') %}
+        <link rel="copyright" title="{{ _('Copyright') }}" href="{{ pathto('copyright') }}" />
+    {%- endif %}
+        <link rel="top" title="{{ docstitle|e }}" href="{{ pathto('index') }}" />
+    {%- if parents %}
+        <link rel="up" title="{{ parents[-1].title|striptags|e }}" href="{{ parents[-1].link|e }}" />
+    {%- endif %}
+    {%- if next %}
+        <link rel="next" title="{{ next.title|striptags|e }}" href="{{ next.link|e }}" />
+    {%- endif %}
+    {%- if prev %}
+        <link rel="prev" title="{{ prev.title|striptags|e }}" href="{{ prev.link|e }}" />
+    {%- endif %}
+    <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
+    <link rel="shortcut icon" href="http://vispy.org/_static/favicon.ico">
+    {%- block extrahead %}{% endblock %}
+</head>
+<body class="container">
+    <a href="http://vispy.org" class="logo"><img src="http://vispy.org/_static/img/logo.png" alt=""></a>
+    <div class="clearfix"></div>
+    <div class="navbar">
+        <div class="navbar-inner">
+            <ul class="nav">
+                {% include 'navbar.html' %}
+            </ul>
+            <form class="navbar-form pull-right" action="search.html" method="get">
+                <input type="text" class="search span3" name="q" placeholder="Search documentation ...">
+                <input type="hidden" name="check_keywords" value="yes" >
+                <input type="hidden" name="area" value="default" >
+            </form>
+        </div>
+    </div>
+    <div class="row">
+        <div class="span9">
+            {% block body %}{% endblock %}
+        </div>
+        <div class="span3">
+            {%- for sidebartemplate in sidebars %}
+                {%- include sidebartemplate %}
+            {%- endfor %}
+        </div>
+    </div>
+    <div class="well footer">
+        <small>
+            © Copyright the vispy development team.
+            Created using <a href="http://twitter.github.com/bootstrap/">Bootstrap</a> and <a href="http://sphinx.pocoo.org/">Sphinx</a>.
+            Theme by <a href="http://scikit-image.org/">scikit-image</a>.
+        </small>
+    </div>
+</body>
+</html>
diff --git a/doc/themes/scikit-image/search.html b/doc/themes/scikit-image/search.html
new file mode 100644
index 0000000..3a723ef
--- /dev/null
+++ b/doc/themes/scikit-image/search.html
@@ -0,0 +1,46 @@
+{#
+    basic/search.html
+    ~~~~~~~~~~~~~~~~~
+
+    Template for the search page.
+
+    :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
+    :license: BSD, see LICENSE for details.
+#}
+{% extends "layout.html" %}
+{% set title = _('Search') %}
+{% set script_files = script_files + ['_static/searchtools.js'] %}
+{% set script_files = script_files + ['searchindex.js'] %}
+{% block body %}
+  <h1 id="search-documentation">{{ _('Search') }}</h1>
+  <div id="fallback" class="admonition warning">
+  <script type="text/javascript">
+    $('#fallback').hide();
+    $("input.search").focus();
+  </script>
+  <p>
+    {% trans %}Please activate JavaScript to enable the search
+    functionality.{% endtrans %}
+  </p>
+  </div>
+  <p>
+    {% trans %}From here you can search these documents. Enter your search
+    words into the box in the navigation bar and press "Enter". Note that the
+    search function will automatically search for all of the words. Pages
+    containing fewer words won't appear in the result list.{% endtrans %}
+  </p>
+  {% if search_performed %}
+    {% if not search_results %}
+      <p>{{ _('Your search did not match any results.') }}</p>
+    {% endif %}
+  {% endif %}
+  <div id="search-results">
+  {% if search_results %}
+    <ul>
+    {% for href, caption, context in search_results %}
+      <li class="well"><a href="{{ pathto(item.href) }}">{{ caption }}</a></li>
+    {% endfor %}
+    </ul>
+  {% endif %}
+  </div>
+{% endblock %}
\ No newline at end of file
diff --git a/doc/themes/scikit-image/static/css/bootstrap-responsive.min.css b/doc/themes/scikit-image/static/css/bootstrap-responsive.min.css
new file mode 100644
index 0000000..7b0158d
--- /dev/null
+++ b/doc/themes/scikit-image/static/css/bootstrap-responsive.min.css
@@ -0,0 +1,9 @@
+/*!
+ * Bootstrap Responsive v2.1.1
+ *
+ * Copyright 2012 Twitter, Inc
+ * Licensed under the Apache License v2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Designed and built with all the love in the world @twitter by @mdo and @fat.
+ */.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.hidden{display:none;visibility:hidden}.visible-phone{display:none!important}.visible-tablet{display:none!important}.hidden-desktop{displa [...]
diff --git a/doc/themes/scikit-image/static/css/bootstrap.min.css b/doc/themes/scikit-image/static/css/bootstrap.min.css
new file mode 100644
index 0000000..31d8b96
--- /dev/null
+++ b/doc/themes/scikit-image/static/css/bootstrap.min.css
@@ -0,0 +1,9 @@
+/*!
+ * Bootstrap v2.1.1
+ *
+ * Copyright 2012 Twitter, Inc
+ * Licensed under the Apache License v2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Designed and built with all the love in the world @twitter by @mdo and @fat.
+ */article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}audio:not([controls]){display:none}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}a:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}a:hover,a:active{outline:0}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-0.5em}sub{bottom:- [...]
diff --git a/doc/themes/scikit-image/static/css/custom.css b/doc/themes/scikit-image/static/css/custom.css
new file mode 100644
index 0000000..2d64dbb
--- /dev/null
+++ b/doc/themes/scikit-image/static/css/custom.css
@@ -0,0 +1,232 @@
+body {
+    font-family: "Raleway";
+}
+a {
+    color: #CE5C00;
+}
+input,
+button,
+select,
+textarea {
+    font-family: "Raleway";
+}
+pre {
+    font-size: 11px;
+}
+
+h1, h2, h3, h4, h5, h6 {
+    clear: left;
+}
+h1 {
+    font-size: 30px;
+    line-height: 36px;
+}
+h2 {
+    font-size: 24px;
+    line-height: 30px;
+}
+h3 {
+    font-size: 19px;
+    line-height: 21px;
+}
+h4 {
+    font-size: 17px;
+    line-height: 19px;
+}
+h5 {
+    font-size: 15px;
+    line-height: 17px;
+}
+h6 {
+    font-size: 13px;
+    line-height: 15px;
+}
+
+.logo {
+    float: left;
+    margin: 20px 0 20px 22px;
+}
+.logo img {
+    height: 70px;
+}
+
+.hero {
+    padding: 10px 25px 15px 25px;
+}
+
+.gallery-random {
+    float: right;
+    line-height: 180px;
+    margin-left: 10px;
+}
+.gallery-random img {
+    max-height: 180px;
+    max-width: 180px;
+}
+
+.coins-sample {
+    float: left;
+    padding: 5px;
+}
+
+.sidebar-box {
+    padding: 0;
+}
+.sidebar-box-heading {
+    padding-left: 15px;
+}
+
+.headerlink {
+    margin-left: 10px;
+    color: #ddd;
+    display: none;
+}
+h1:hover .headerlink,
+h2:hover .headerlink,
+h3:hover .headerlink,
+h4:hover .headerlink,
+h5:hover .headerlink,
+h6:hover .headerlink {
+    display: inline;
+}
+.headerlink:hover {
+    color: #CE5C00;
+    text-decoration: none;
+}
+
+.footer {
+    margin-top: 30px;
+    padding: 5px 10px;
+    color: #999;
+}
+.footer a {
+    color: #999;
+    text-decoration: underline;
+}
+
+.ohloh-use, .gplus-use {
+    float: left;
+    margin: 0 0 10px 15px;
+}
+
+/* Documentation */
+
+/* general table settings */
+table.docutils {
+    margin-bottom: 10px;
+    border-color: #ccc;
+}
+table.docutils td, table.docutils th {
+    padding: 5px;
+    border-color: #ccc;
+    text-align: left;
+}
+
+.toc ul ul {
+    font-size: 13px;
+    margin-right: -15px;
+}
+
+/* master content table */
+.contentstable.docutils, .contentstable.docutils td {
+    border-color: transparent;
+}
+.contentstable.docutils .first {
+    font-weight: bold;
+}
+.contentstable.docutils .last {
+    padding-left: 10px;
+}
+
+.docutils .label, .docutils .badge {
+    background: transparent;
+    text-shadow: none;
+    font-size: 13px;
+    padding: 5px;
+    line-height: 20px;
+    color: #333;
+}
+
+/* module summary table */
+.longtable.docutils {
+    font-size: 12px;
+    margin-bottom: 30px;
+}
+.longtable.docutils, .longtable.docutils td {
+    border-color: #ccc;
+}
+
+/* function and class description */
+dl.class, dl.function, dl.method, dl.attribute {
+    border-top: 1px solid #ccc;
+    padding-top: 10px;
+}
+.descclassname {
+    color: #aaa;
+    font-weight: normal;
+    font-family: monospace;
+}
+.descname {
+    font-family: monospace;
+}
+dl.class em, dl.function em, dl.class big, dl.function big {
+    font-weight: normal;
+    font-family: monospace;
+}
+dl.class dd, dl.function dd {
+    padding: 10px;
+}
+.docutils.field-list th {
+    background-color: #eee;
+    padding: 10px;
+    text-align: left;
+    vertical-align: top;
+    width: 100px;
+}
+.docutils.field-list td {
+    padding: 10px 10px 10px 20px;
+    text-align: left;
+    vertical-align: top;
+}
+.docutils.field-list td blockquote p {
+    font-size: 13px;
+    line-height: 18px;
+}
+p.rubric {
+    font-weight: bold;
+    font-size: 19px;
+    margin: 15px 0 10px 0;
+}
+p.admonition-title {
+    font-weight: bold;
+    text-decoration: underline;
+}
+
+/* example gallery */
+
+.gallery {
+    height: 200px;
+}
+
+.figure {
+    float: left;
+    margin: 1em;
+}
+
+.figure img {
+    display: block;
+    margin-left: auto;
+    margin-right: auto;
+    max-height: 150px;
+    max-width: 200px;
+}
+
+.figure .caption {
+    width: 200px;
+    text-align: center !important;
+}
+
+.summary-box {
+    /* Should derive width from span8 */
+    width: 640px;
+}
diff --git a/doc/themes/scikit-image/static/img/favicon.ico b/doc/themes/scikit-image/static/img/favicon.ico
new file mode 100644
index 0000000..d7ae601
Binary files /dev/null and b/doc/themes/scikit-image/static/img/favicon.ico differ
diff --git a/doc/themes/scikit-image/static/img/glyphicons-halflings-white.png b/doc/themes/scikit-image/static/img/glyphicons-halflings-white.png
new file mode 100644
index 0000000..3bf6484
Binary files /dev/null and b/doc/themes/scikit-image/static/img/glyphicons-halflings-white.png differ
diff --git a/doc/themes/scikit-image/static/img/glyphicons-halflings.png b/doc/themes/scikit-image/static/img/glyphicons-halflings.png
new file mode 100644
index 0000000..a996999
Binary files /dev/null and b/doc/themes/scikit-image/static/img/glyphicons-halflings.png differ
diff --git a/doc/themes/scikit-image/static/js/bootstrap.min.js b/doc/themes/scikit-image/static/js/bootstrap.min.js
new file mode 100644
index 0000000..0e33fb1
--- /dev/null
+++ b/doc/themes/scikit-image/static/js/bootstrap.min.js
@@ -0,0 +1,6 @@
+/*!
+* Bootstrap.js by @fat & @mdo
+* Copyright 2012 Twitter, Inc.
+* http://www.apache.org/licenses/LICENSE-2.0.txt
+*/
+!function(e){e(function(){"use strict";e.support.transition=function(){var e=function(){var e=document.createElement("bootstrap"),t={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"},n;for(n in t)if(e.style[n]!==undefined)return t[n]}();return e&&{end:e}}()})}(window.jQuery),!function(e){"use strict";var t='[data-dismiss="alert"]',n=function(n){e(n).on("click",t,this.close)};n.prototype.close=funct [...]
\ No newline at end of file
diff --git a/doc/themes/scikit-image/static/js/docversions.js b/doc/themes/scikit-image/static/js/docversions.js
new file mode 100644
index 0000000..951ce3e
--- /dev/null
+++ b/doc/themes/scikit-image/static/js/docversions.js
@@ -0,0 +1,21 @@
+var versions = ['latest', 'v0.1.0'];
+
+function insert_version_links() {
+    for (i = 0; i < versions.length; i++){
+        open_list = '<li>'
+        if (typeof(DOCUMENTATION_OPTIONS) !== 'undefined') {
+            if ((DOCUMENTATION_OPTIONS['VERSION'] == versions[i]) ||
+                (DOCUMENTATION_OPTIONS['VERSION'].match(/dev$/) && (i == 0))) {
+                open_list = '<li id="current">'
+            }
+        }
+        document.write(open_list);
+        document.write('<a href="URL">vispy VERSION</a> </li>\n'
+                        .replace('VERSION', versions[i])
+                        .replace('URL', 'http://api.vispy.org/en/' + versions[i]));
+    }
+}
+
+function stable_version() {
+    return versions[1];
+}
diff --git a/doc/themes/scikit-image/theme.conf b/doc/themes/scikit-image/theme.conf
new file mode 100644
index 0000000..f83ec96
--- /dev/null
+++ b/doc/themes/scikit-image/theme.conf
@@ -0,0 +1,4 @@
+[theme]
+inherit = basic
+stylesheet = none
+pygments_style = default
diff --git a/doc/util.rst b/doc/util.rst
new file mode 100644
index 0000000..93ac41b
--- /dev/null
+++ b/doc/util.rst
@@ -0,0 +1,39 @@
+===============
+The util module
+===============
+
+.. automodule:: vispy.util
+
+
+----
+
+vispy.util.keys
+----------------
+
+Constants for keys like control and alt.
+
+
+----
+
+vispy.util.transforms
+---------------------
+.. automodule:: vispy.util.transforms
+    :members:
+
+
+----
+
+vispy.util.event
+----------------
+
+.. automodule:: vispy.util.event
+
+
+.. autoclass:: vispy.util.event.EventEmitter
+    :members:
+    
+    .. automethod:: vispy.util.event.EventEmitter.__call__
+
+
+.. autoclass:: vispy.util.event.EmitterGroup
+    :members:
diff --git a/doc/visuals.rst b/doc/visuals.rst
new file mode 100644
index 0000000..fb7b99c
--- /dev/null
+++ b/doc/visuals.rst
@@ -0,0 +1,8 @@
+Visuals layer
+=============
+
+The visuals layer implements several visuals, object that represent a 
+certain kind of visualisation. Examples are lines, markers, images, volumes,
+meshes, barplots, axes, grids, etc. 
+
+This layer is planned; it does not exist yet.
diff --git a/examples/app/app-event.py b/examples/app/app-event.py
new file mode 100644
index 0000000..0433c83
--- /dev/null
+++ b/examples/app/app-event.py
@@ -0,0 +1,62 @@
+# #!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+This example shows how to retrieve event information from a callback.
+You should see information displayed for any event you triggered.
+"""
+
+import vispy.gloo.gl as gl
+import vispy.app as app
+
+class Canvas(app.Canvas):
+    def __init__(self, *args, **kwargs):
+        app.Canvas.__init__(self, *args, **kwargs)
+        self.title = 'App demo'
+    
+    def on_initialize(self, event):
+        print('initializing!')
+    
+    def on_close(self, event):
+        print('closing!')
+    
+    def on_resize(self, event):
+        print('Resize %r' % (event.size, ))
+    
+    def on_key_press(self, event):
+        modifiers = [key.name for key in event.modifiers]
+        print('Key pressed - text: %r, key: %s, modifiers: %r' % (
+                event.text, event.key.name, modifiers))
+    
+    def on_key_release(self, event):
+        modifiers = [key.name for key in event.modifiers]
+        print('Key released - text: %r, key: %s, modifiers: %r' % (
+                event.text, event.key.name, modifiers))
+    
+    def on_mouse_press(self, event):
+        self.print_mouse_event(event, 'Mouse press')
+    
+    def on_mouse_release(self, event):
+        self.print_mouse_event(event, 'Mouse release')
+    
+    def on_mouse_move(self, event):
+        if (    event.pos[0] < self.size[0]*0.5 
+            and event.pos[1] < self.size[1]*0.5):
+            self.print_mouse_event(event, 'Mouse move')
+    
+    def on_mouse_wheel(self, event):
+        self.print_mouse_event(event, 'Mouse wheel')
+    
+    def print_mouse_event(self, event, what):
+        modifiers = ', '.join([key.name for key in event.modifiers])
+        print('%s - pos: %r, button: %i, modifiers: %s, delta: %r' %
+              (what, event.pos, event.button, modifiers, event.delta))
+    
+    
+    def on_paint(self, event):
+        gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
+
+
+if __name__ == '__main__':
+    canvas = Canvas()
+    canvas.show()
+    app.run()
diff --git a/examples/app/app-glut.py b/examples/app/app-glut.py
new file mode 100644
index 0000000..1500082
--- /dev/null
+++ b/examples/app/app-glut.py
@@ -0,0 +1,68 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+This example shows how to actively select and test the glut backend.
+
+You should see a black window and any mouse or keyboard event should be
+detected. A timer is also ran every second and it should print "tick !"
+every second.
+
+Note:
+====
+Depending on on your glut implementation (native or freeglut), the mouse wheel
+event may or may not be detected. Furthermore, glut has no utf-8 support and
+non ascii-key will most likely produces garbage.
+"""
+
+from vispy import app
+from vispy.gloo import gl
+app.use('glut')
+
+class Canvas(app.Canvas):
+    def __init__(self, *args, **kwargs):
+        app.Canvas.__init__(self, *args, **kwargs)
+        timer = app.Timer(1.0)
+        timer.connect(self.on_timer)
+        timer.start()
+
+    def on_initialize(self, event):
+        gl.glClearColor(0,0,0,1)
+        print('on_initialize')
+
+    def on_close(self, event):
+        print('on_close')
+
+    def on_resize(self, event):
+        print('on_resize (%dx%d)' % event.size)
+
+    def on_key_press(self, event):
+        print('on_key_press: %s' % event.text)
+
+    def on_key_release(self, event):
+        print('on_key_release')
+
+    def on_mouse_press(self, event):
+        print('on_mouse_press: %d' % event.button)
+
+    def on_mouse_release(self, event):
+        print('on_mouse_release')
+        print(event.trail())
+
+    def on_mouse_move(self, event):
+        print('on_mouse_move (%dx%d)' % event.pos)
+
+    def on_mouse_wheel(self, event):
+        print('on_mouse_wheel: %r' % (event.delta,))
+        
+    def on_paint(self, event):
+        print('on_paint')
+        gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
+
+    def on_timer(self, event):
+        print('tick !')
+
+# -----------------------------------------------------------------------------    
+if __name__ == '__main__':
+    canvas = Canvas()
+    canvas.show()
+    app.run()
diff --git a/examples/app/app-pyglet.py b/examples/app/app-pyglet.py
new file mode 100644
index 0000000..bd508bf
--- /dev/null
+++ b/examples/app/app-pyglet.py
@@ -0,0 +1,63 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+This example shows how to actively select and test the pyglet backend.
+
+You should see a black window and any mouse or keyboard event should be
+detected. A timer is also ran every second and it should print "tick !"
+every second.
+"""
+
+from vispy import app
+from vispy.gloo import gl
+app.use('pyglet')
+
+class Canvas(app.Canvas):
+    def __init__(self, *args, **kwargs):
+        app.Canvas.__init__(self, *args, **kwargs)
+        timer = app.Timer(1.0)
+        timer.connect(self.on_timer)
+        timer.start()
+
+    def on_initialize(self, event):
+        gl.glClearColor(0,0,0,1)
+        print('on_initialize')
+
+    def on_close(self, event):
+        print('on_close')
+
+    def on_resize(self, event):
+        print('on_resize (%dx%d)' % event.size)
+
+    def on_key_press(self, event):
+        print('on_key_press: %s' % event.text)
+
+    def on_key_release(self, event):
+        print('on_key_release')
+
+    def on_mouse_press(self, event):
+        print('on_mouse_press: %d' % event.button)
+
+    def on_mouse_release(self, event):
+        print('on_mouse_release')
+        print(event.trail())
+
+    def on_mouse_move(self, event):
+        print('on_mouse_move (%dx%d)' % event.pos)
+
+    def on_mouse_wheel(self, event):
+        print('on_mouse_wheel: %r' % (event.delta,))
+        
+    def on_paint(self, event):
+        print('on_paint')
+        gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
+
+
+    def on_timer(self, event):
+        print('tick !')
+    
+# -----------------------------------------------------------------------------
+if __name__ == '__main__':
+    canvas = Canvas()
+    canvas.show()
+    app.run()
diff --git a/examples/app/app-qt.py b/examples/app/app-qt.py
new file mode 100644
index 0000000..983cdee
--- /dev/null
+++ b/examples/app/app-qt.py
@@ -0,0 +1,62 @@
+# #!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+This example shows how to actively select and test the qt backend.
+
+You should see a black window and any mouse or keyboard event should be
+detected. A timer is also ran every second and it should print "tick !"
+every second.
+"""
+
+from vispy import app
+from vispy.gloo import gl
+app.use('qt')
+
+class Canvas(app.Canvas):
+    def __init__(self, *args, **kwargs):
+        app.Canvas.__init__(self, *args, **kwargs)
+        timer = app.Timer(1.0)
+        timer.connect(self.on_timer)
+        timer.start()
+
+    def on_initialize(self, event):
+        gl.glClearColor(0,0,0,1)
+        print('on_initialize')
+
+    def on_close(self, event):
+        print('on_close')
+
+    def on_resize(self, event):
+        print('on_resize (%dx%d)' % event.size)
+
+    def on_key_press(self, event):
+        print('on_key_press: %s' % event.text)
+
+    def on_key_release(self, event):
+        print('on_key_release')
+
+    def on_mouse_press(self, event):
+        print('on_mouse_press: %d' % event.button)
+
+    def on_mouse_release(self, event):
+        print('on_mouse_release')
+        print(event.trail())
+
+    def on_mouse_move(self, event):
+        print('on_mouse_move (%dx%d)' % event.pos)
+
+    def on_mouse_wheel(self, event):
+        print('on_mouse_wheel: %r' % (event.delta,))
+        
+    def on_paint(self, event):
+        print('on_paint')
+        gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
+        
+    def on_timer(self, event):
+        print('tick ! (%s)' % event.dt)
+    
+# -----------------------------------------------------------------------------
+if __name__ == '__main__':
+    canvas = Canvas()
+    canvas.show()
+    app.run()
diff --git a/examples/app/simple.py b/examples/app/simple.py
new file mode 100644
index 0000000..2eff1d6
--- /dev/null
+++ b/examples/app/simple.py
@@ -0,0 +1,37 @@
+# #!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# vispy: gallery 20
+
+"""
+This is a very minimal example that opens a window and makes the background
+color to change from black to white to black ...
+
+The backend (one of 'qt', 'glut', 'pyglet') is chosen automatically depending
+on what is available on your machine.
+"""
+
+import math
+from vispy import app
+from vispy.gloo import gl
+
+class Canvas(app.Canvas):
+    def __init__(self, *args, **kwargs):
+        app.Canvas.__init__(self, *args, **kwargs)
+        timer = app.Timer(1/60.0)
+        timer.connect(self.on_timer)
+        timer.start()
+        self.tick = 0
+        
+    def on_paint(self, event):
+        gl.glClear(gl.GL_COLOR_BUFFER_BIT)
+        
+    def on_timer(self, event):
+        self.tick += 1/60.0
+        c = abs(math.sin(self.tick))
+        gl.glClearColor(c,c,c,1)
+        self.update()
+
+if __name__ == '__main__':
+    canvas = Canvas()
+    canvas.show()
+    app.run()
diff --git a/examples/benchmark/simple-glut.py b/examples/benchmark/simple-glut.py
new file mode 100755
index 0000000..9896145
--- /dev/null
+++ b/examples/benchmark/simple-glut.py
@@ -0,0 +1,43 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# VisPy - Copyright (c) 2013, Vispy Development Team All rights reserved.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+# -----------------------------------------------------------------------------
+import numpy as np
+from vispy.gloo import gl
+
+def on_display():
+    gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
+    glut.glutSwapBuffers()
+    
+def on_keyboard(key, x, y):
+    if key == '\033':
+        sys.exit()
+
+def on_idle():
+    global t, t0, frames
+    t = glut.glutGet( glut.GLUT_ELAPSED_TIME )
+    frames = frames + 1
+    elapsed = (t-t0)/1000.0
+    if elapsed > 2.5:
+        print( "FPS : %.2f (%d frames in %.2f second)"
+               % (frames/elapsed, frames, elapsed))
+        t0, frames = t,0
+    glut.glutPostRedisplay()
+
+
+if __name__ == '__main__':
+    import sys
+    import OpenGL.GLUT as glut
+
+    glut.glutInit(sys.argv)
+    glut.glutInitDisplayMode(glut.GLUT_DOUBLE | glut.GLUT_RGB | glut.GLUT_DEPTH)
+    glut.glutInitWindowSize(512,512)
+    glut.glutCreateWindow("Do nothing benchmark (GLUT)")
+    glut.glutDisplayFunc(on_display)
+    glut.glutKeyboardFunc(on_keyboard)
+
+    t0, frames, t = glut.glutGet(glut.GLUT_ELAPSED_TIME),0,0
+    glut.glutIdleFunc(on_idle)
+    glut.glutMainLoop()
diff --git a/examples/benchmark/simple-vispy.py b/examples/benchmark/simple-vispy.py
new file mode 100755
index 0000000..813a3ed
--- /dev/null
+++ b/examples/benchmark/simple-vispy.py
@@ -0,0 +1,33 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# VisPy - Copyright (c) 2013, Vispy Development Team All rights reserved.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+# -----------------------------------------------------------------------------
+import time
+from vispy import app
+from vispy.gloo import gl
+
+app.use('qt')
+# app.use('glut')
+# app.use('pyglet')
+
+canvas = app.Canvas(size=(512,512), title = "Do nothing benchmark (vispy)")
+
+ at canvas.connect
+def on_paint(event):
+    global t, t0, frames
+    gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
+        
+    t = time.time()
+    frames = frames + 1
+    elapsed = (t-t0) # seconds
+    if elapsed > 2.5:
+        print( "FPS : %.2f (%d frames in %.2f second)"
+               % (frames/elapsed, frames, elapsed))
+        t0, frames = t,0
+    canvas.update()
+
+t0, frames, t = time.time(),0,0
+canvas.show()
+app.run()
diff --git a/examples/demo/atom.py b/examples/demo/atom.py
new file mode 100644
index 0000000..6f2bb2a
--- /dev/null
+++ b/examples/demo/atom.py
@@ -0,0 +1,192 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# vispy: gallery 60
+
+"""
+Particles orbiting around a central point (with traces)
+"""
+
+import numpy as np
+from vispy import gloo
+from vispy import app
+from vispy.gloo import gl
+from vispy.util.transforms import perspective, translate, rotate
+
+
+n,p = 250, 50
+T = np.random.uniform(0,2*np.pi,n)
+dT = np.random.uniform(50,100,n)/3000
+position = np.zeros((n,2),dtype=np.float32)
+position[:,0] = np.cos(T)
+position[:,1] = np.sin(T)
+rot = np.random.uniform(0,2*np.pi,(n,4)).astype(np.float32)
+color = np.ones((n,4),dtype=np.float32) * (1,1,1,1)
+u_size = 6
+
+data = np.zeros(n*p, [ ('a_position', np.float32, 2),
+                       ('a_color',    np.float32, 4),
+                       ('a_rot',      np.float32, 4)])
+data['a_position'] = np.repeat(position, p, axis=0)
+data['a_color'] = np.repeat(color, p, axis=0)
+data['a_rot'] = np.repeat(rot, p, axis=0)
+
+
+VERT_SHADER = """
+// Uniforms
+// --------
+uniform mat4 u_model;
+uniform mat4 u_view;
+uniform mat4 u_projection;
+uniform float u_size;
+
+// Attributes
+// ----------
+attribute vec2  a_position;
+attribute vec4  a_rot;
+attribute vec4  a_color;
+attribute mat4  a_model;
+
+// Varyings
+// --------
+varying vec4 v_color;
+varying float v_size;
+
+mat4 rotation(vec3 axis, float angle) {
+    axis = normalize(axis);
+    float s = sin(angle);
+    float c = cos(angle);
+    float oc = 1.0 - c;
+    return mat4(oc * axis.x * axis.x + c,
+                oc * axis.x * axis.y - axis.z * s,
+                oc * axis.z * axis.x + axis.y * s,
+                0.0,
+                oc * axis.x * axis.y + axis.z * s,
+                oc * axis.y * axis.y + c,
+                oc * axis.y * axis.z - axis.x * s,
+                0.0,
+                oc * axis.z * axis.x - axis.y * s,
+                oc * axis.y * axis.z + axis.x * s,
+                oc * axis.z * axis.z + c,
+                0.0,
+                0.0, 0.0, 0.0, 1.0);
+}
+
+void main (void) {
+    v_size = u_size;
+    v_color = a_color;
+
+    mat4 R = rotation(a_rot.xyz, a_rot.w);
+    gl_Position = u_projection * u_view * u_model * R * vec4(a_position, 0.0, 1.0);
+    gl_PointSize = v_size;
+}
+"""
+
+FRAG_SHADER = """
+
+// Varyings
+// ------------------------------------
+varying vec4 v_color;
+varying float v_size;
+
+// Main
+// ------------------------------------
+void main()
+{    
+    float d = 2*(length(gl_PointCoord.xy - vec2(0.5,0.5)));
+    gl_FragColor = vec4(v_color.rgb, v_color.a*(1-d));
+}
+"""
+
+
+
+
+class Canvas(app.Canvas):
+
+    def __init__(self, **kwargs):
+        
+        self.program = gloo.Program(VERT_SHADER, FRAG_SHADER)
+        self.view       = np.eye(4,dtype=np.float32)
+        self.model      = np.eye(4,dtype=np.float32)
+        self.projection = np.eye(4,dtype=np.float32)
+        self.translate = 3
+        translate(self.view, 0,0, -self.translate)
+        
+        self.vbo = gloo.VertexBuffer(data)
+        self.program.set_vars(self.vbo )
+        self.program['u_model'] = self.model
+        self.program['u_view'] = self.view
+        self.program['u_size'] = u_size
+
+        self.theta = 0
+        self.phi = 0
+        self.index = 0
+
+        self.timer = app.Timer(1.0/400)
+        self.timer.connect(self.on_timer)
+        self.timer.start()
+        
+        # Initialize for real
+        app.Canvas.__init__(self, **kwargs)
+
+
+    def on_initialize(self, event):
+        gl.glClearColor(0,0,0,1)
+        gl.glDisable(gl.GL_DEPTH_TEST)
+        gl.glEnable(gl.GL_BLEND)
+        gl.glBlendFunc (gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA)
+
+
+    def on_key_press(self,event):
+        if event.text == ' ':
+            if self.timer.running:
+                self.timer.stop()
+            else:
+                self.timer.start()
+
+
+    def on_timer(self,event):
+        self.theta += .017
+        self.phi += .013
+        self.model = np.eye(4, dtype=np.float32)
+        rotate(self.model, self.theta, 0,0,1)
+        rotate(self.model, self.phi,   0,1,0)
+        self.program['u_model'] = self.model
+        self.update()
+
+
+    def on_resize(self, event):
+        width, height = event.size
+        gl.glViewport(0, 0, width, height)
+        self.projection = perspective( 45.0, width/float(height), 1.0, 1000.0 )
+        self.program['u_projection'] = self.projection
+
+
+    def on_mouse_wheel(self, event):
+        global u_size
+
+        self.translate +=event.delta[1]
+        self.translate = max(2,self.translate)
+        self.view       = np.eye(4,dtype=np.float32)
+        translate(self.view, 0,0, -self.translate)
+        self.program['u_view'] = self.view
+        self.update()
+
+
+    def on_paint(self, event):
+        global T,dT,p,n
+        gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
+
+        T += dT
+        self.index = (self.index+1)%p
+        data['a_position'][self.index::p,0] = np.cos(T)
+        data['a_position'][self.index::p,1] = .5*np.sin(T)
+        data['a_color'][:,3] -= 1.0/p
+        data['a_color'][self.index::p,3] = 1
+        self.vbo.set_data(data)
+        self.program.draw(gl.GL_POINTS)
+
+
+if __name__ == '__main__':
+    c = Canvas(show=True, size=(600,600), title="Atom [zoom with mouse scroll]")
+    #c.show()
+    app.run()
diff --git a/examples/demo/boids.py b/examples/demo/boids.py
new file mode 100644
index 0000000..e9d4367
--- /dev/null
+++ b/examples/demo/boids.py
@@ -0,0 +1,187 @@
+# #!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# vispy: gallery 30
+
+""" 
+Demonstration of boids simulation. Boids is an artificial life
+program, developed by Craig Reynolds in 1986, which simulates the
+flocking behaviour of birds.
+Based on code from glumpy by Nicolas Rougier.
+"""
+
+import time
+
+import numpy as np
+from scipy.spatial import cKDTree
+
+from vispy.gloo import gl
+from vispy import gloo
+from vispy import app
+
+# Create boids
+n = 1000
+particles = np.zeros(2+n, [ ('position',   'f4', 3),
+                            ('position_1', 'f4', 3),
+                            ('position_2', 'f4', 3),
+                            ('velocity',   'f4', 3),
+                            ('color',      'f4', 4),
+                            ('size',       'f4', 1)] )
+boids    = particles[2:]
+target   = particles[0]
+predator = particles[1]
+
+boids['position'] = np.random.uniform(-0.25, +0.25, (n,3))
+boids['velocity'] = np.random.uniform(-0.00, +0.00, (n,3))
+boids['size'] = 4
+boids['color'] = 1,1,1,1
+
+target['size'] = 16
+target['color'][:] = 1,1,0,1
+predator['size'] = 16
+predator['color'][:] = 1,0,0,1
+target['position'][:] = 0.25, 0.0, 0
+
+VERT_SHADER = """
+attribute vec3 position;
+attribute vec4 color;
+attribute float size;
+
+varying vec4 v_color;
+void main (void) {
+    gl_Position = vec4(position, 1.0);
+    v_color = color;
+    gl_PointSize = size;
+}
+"""
+
+FRAG_SHADER = """
+varying vec4 v_color;
+void main()
+{    
+    float x = 2.0*gl_PointCoord.x - 1.0;
+    float y = 2.0*gl_PointCoord.y - 1.0;
+    float a = 1.0 - (x*x + y*y);
+    gl_FragColor = vec4(v_color.rgb, a*v_color.a);
+}
+
+"""
+
+
+class Canvas(app.Canvas):
+    
+    def __init__(self):
+        app.Canvas.__init__(self)
+        
+        # Time
+        self._t = time.time()
+        self._pos = 0.0, 0.0
+        self._button = None
+        
+        # Create program
+        self.program = gloo.Program(VERT_SHADER, FRAG_SHADER)
+    
+        # Create vertex buffers
+        self.vbo_position = gloo.VertexBuffer(particles['position'])
+        self.vbo_color = gloo.VertexBuffer(particles['color'])
+        self.vbo_size = gloo.VertexBuffer(particles['size'])
+
+        # Bind vertex buffers
+        self.program['color'] = self.vbo_color
+        self.program['size'] = self.vbo_size
+        self.program['position'] = self.vbo_position
+
+
+    def on_initialize(self, event):
+        gl.glClearColor(0,0,0,1);
+        gl.glEnable(gl.GL_BLEND)
+        gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE)
+    
+    def on_resize(self, event):
+        width, height = event.size
+        gl.glViewport(0, 0, width, height)
+        
+    def on_mouse_press(self, event):
+        self._button = event.button
+        self.on_mouse_move(event)
+    
+    def on_mouse_release(self, event):
+        self._button = None
+        self.on_mouse_move(event)
+    
+    def on_mouse_move(self, event):
+        if not self._button:
+            return
+        w, h = self.size
+        x, y = event.pos
+        sx = 2*x/float(w) -1.0
+        sy = - (2*y/float(h) -1.0)
+        
+        if self._button == 1:
+            target['position'][:] = sx, sy, 0
+        elif self._button  == 2:
+            predator['position'][:] = sx, sy, 0
+
+    
+    def on_paint(self, event):
+        gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
+        
+        # Draw
+        self.program.draw(gl.GL_POINTS)
+        
+        # Next iteration
+        self._t = self.iteration(time.time() - self._t)
+
+        # Invoke a new draw
+        self.update()
+    
+    
+    def iteration(self, dt):
+        t = self._t 
+        
+        t += 0.5*dt
+        #target[...] = np.array([np.sin(t),np.sin(2*t),np.cos(3*t)])*.1
+    
+        t += 0.5*dt
+        #predator[...] = np.array([np.sin(t),np.sin(2*t),np.cos(3*t)])*.2
+    
+        boids['position_2'] = boids['position_1']
+        boids['position_1'] = boids['position']
+        n = len(boids)
+        P = boids['position']
+        V = boids['velocity']
+    
+        # Cohesion: steer to move toward the average position of local flockmates
+        C = -(P - P.sum(axis=0)/n)
+    
+        # Alignment: steer towards the average heading of local flockmates
+        A = -(V - V.sum(axis=0)/n)
+    
+        # Repulsion: steer to avoid crowding local flockmates
+        D,I = cKDTree(P).query(P,5)
+        M = np.repeat(D < 0.05, 3, axis=1).reshape(n,5,3)
+        Z = np.repeat(P,5,axis=0).reshape(n,5,3)
+        R = -((P[I]-Z)*M).sum(axis=1)
+    
+        # Target : Follow target
+        T = target['position'] - P
+    
+        # Predator : Move away from predator
+        dP = P - predator['position']
+        D = np.maximum(0, 0.3 - np.sqrt(dP[:,0]**2 +dP[:,1]**2+dP[:,2]**2) )
+        D = np.repeat(D,3,axis=0).reshape(n,3)
+        dP *= D
+    
+        #boids['velocity'] += 0.0005*C + 0.01*A + 0.01*R + 0.0005*T + 0.0025*dP
+        boids['velocity'] += 0.0005*C + 0.01*A + 0.01*R + 0.0005*T + 0.025*dP
+        boids['position'] += boids['velocity']
+        
+        self.vbo_position.set_data(particles['position'])
+
+        return t
+
+
+if __name__ == '__main__':
+    c = Canvas()
+    c.show()
+    app.run()
+    
diff --git a/examples/demo/cloud.py b/examples/demo/cloud.py
new file mode 100644
index 0000000..0c86d6c
--- /dev/null
+++ b/examples/demo/cloud.py
@@ -0,0 +1,303 @@
+# #!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# vispy: gallery 1
+
+"""
+Demonstrating a cloud of points.
+"""
+
+import numpy as np
+
+from vispy import gloo
+from vispy import app
+from vispy.gloo import gl
+from vispy.util.transforms import perspective, translate, rotate
+
+
+# Create vertices 
+n = 1000000
+data = np.zeros(n, [ ('a_position', np.float32, 3),
+                     ('a_bg_color', np.float32, 4),
+                     ('a_fg_color', np.float32, 4),
+                     ('a_size',     np.float32, 1)])
+data['a_position'] = 0.45 * np.random.randn(n,3)
+data['a_bg_color'] = np.random.uniform(0.85,1.00,(n,4))
+data['a_fg_color'] = 0,0,0,1
+data['a_size'] = np.random.uniform(5,10,n)
+u_linewidth = 1.0
+u_antialias = 1.0
+u_size = 1
+
+
+vert = """
+// Uniforms
+// ------------------------------------
+uniform mat4 u_model;
+uniform mat4 u_view;
+uniform mat4 u_projection;
+uniform float u_linewidth;
+uniform float u_antialias;
+uniform float u_size;
+
+// Attributes
+// ------------------------------------
+attribute vec3  a_position;
+attribute vec4  a_fg_color;
+attribute vec4  a_bg_color;
+attribute float a_size;
+
+// Varyings
+// ------------------------------------
+varying vec4 v_fg_color;
+varying vec4 v_bg_color;
+varying float v_size;
+varying float v_linewidth;
+varying float v_antialias;
+
+void main (void) {
+    v_size = a_size * u_size;
+    v_linewidth = u_linewidth;
+    v_antialias = u_antialias;
+    v_fg_color  = a_fg_color;
+    v_bg_color  = a_bg_color;
+    gl_Position = u_projection * u_view * u_model * vec4(a_position,1.0);
+    gl_PointSize = v_size + 2*(v_linewidth + 1.5*v_antialias);
+}
+"""
+
+frag = """
+// Constants
+// ------------------------------------
+
+
+// Varyings
+// ------------------------------------
+varying vec4 v_fg_color;
+varying vec4 v_bg_color;
+varying float v_size;
+varying float v_linewidth;
+varying float v_antialias;
+
+// Functions
+// ------------------------------------
+
+// ----------------
+float disc(vec2 P, float size)
+{
+    float r = length((P.xy - vec2(0.5,0.5))*size);
+    r -= v_size/2;
+    return r;
+}
+
+// ----------------
+float arrow_right(vec2 P, float size)
+{
+    float r1 = abs(P.x -.50)*size + abs(P.y -.5)*size - v_size/2; 
+    float r2 = abs(P.x -.25)*size + abs(P.y -.5)*size - v_size/2; 
+    float r = max(r1,-r2);
+    return r;
+}
+
+// ----------------
+float ring(vec2 P, float size)
+{
+    float r1 = length((gl_PointCoord.xy - vec2(0.5,0.5))*size) - v_size/2;
+    float r2 = length((gl_PointCoord.xy - vec2(0.5,0.5))*size) - v_size/4;
+    float r = max(r1,-r2);
+    return r;
+}
+
+// ----------------
+float clober(vec2 P, float size)
+{
+    const float PI = 3.14159265358979323846264;
+    const float t1 = -PI/2;
+    const vec2  c1 = 0.2*vec2(cos(t1),sin(t1));
+    const float t2 = t1+2*PI/3;
+    const vec2  c2 = 0.2*vec2(cos(t2),sin(t2));
+    const float t3 = t2+2*PI/3;
+    const vec2  c3 = 0.2*vec2(cos(t3),sin(t3));
+
+    float r1 = length((gl_PointCoord.xy- vec2(0.5,0.5) - c1)*size);
+    r1 -= v_size/3;
+    float r2 = length((gl_PointCoord.xy- vec2(0.5,0.5) - c2)*size);
+    r2 -= v_size/3;
+    float r3 = length((gl_PointCoord.xy- vec2(0.5,0.5) - c3)*size);
+    r3 -= v_size/3;
+    float r = min(min(r1,r2),r3);
+    return r;
+}
+
+// ----------------
+float square(vec2 P, float size)
+{
+    float r = max(abs(gl_PointCoord.x -.5)*size, abs(gl_PointCoord.y -.5)*size);
+    r -= v_size/2;
+    return r;
+}
+
+// ----------------
+float diamond(vec2 P, float size)
+{
+    float r = abs(gl_PointCoord.x -.5)*size + abs(gl_PointCoord.y -.5)*size; 
+    r -= v_size/2;
+    return r;
+}
+
+// ----------------
+float vbar(vec2 P, float size)
+{
+    float r1 = max(abs(gl_PointCoord.x -.75)*size, abs(gl_PointCoord.x -.25)*size); 
+    float r3 = max(abs(gl_PointCoord.x -.5)*size, abs(gl_PointCoord.y -.5)*size); 
+    float r = max(r1,r3);
+    r -= v_size/2;
+    return r;
+}
+
+// ----------------
+float hbar(vec2 P, float size)
+{
+    float r2 = max(abs(gl_PointCoord.y -.75)*size, abs(gl_PointCoord.y -.25)*size); 
+    float r3 = max(abs(gl_PointCoord.x -.5)*size, abs(gl_PointCoord.y -.5)*size); 
+    float r = max(r2,r3);
+    r -= v_size/2;
+    return r;
+}
+
+// ----------------
+float cross(vec2 P, float size)
+{
+    float r1 = max(abs(gl_PointCoord.x -.75)*size, abs(gl_PointCoord.x -.25)*size); 
+    float r2 = max(abs(gl_PointCoord.y -.75)*size, abs(gl_PointCoord.y -.25)*size); 
+    float r3 = max(abs(gl_PointCoord.x -.5)*size, abs(gl_PointCoord.y -.5)*size); 
+    float r = max(min(r1,r2),r3);
+    r -= v_size/2;
+    return r;
+}
+
+
+// Main
+// ------------------------------------
+void main()
+{    
+    float size = v_size +2*(v_linewidth + 1.5*v_antialias);
+    float t = v_linewidth/2.0-v_antialias;
+
+    float r = disc(gl_PointCoord, size);
+    // float r = square(gl_PointCoord, size);
+    // float r = ring(gl_PointCoord, size);
+    // float r = arrow_right(gl_PointCoord, size);
+    // float r = diamond(gl_PointCoord, size);
+    // float r = cross(gl_PointCoord, size);
+    // float r = clober(gl_PointCoord, size);
+    // float r = hbar(gl_PointCoord, size);
+    // float r = vbar(gl_PointCoord, size);
+
+
+    float d = abs(r) - t;
+    if( r > (v_linewidth/2.0+v_antialias))
+    {
+        discard;
+    }
+    else if( d < 0.0 )
+    {
+       gl_FragColor = v_fg_color;
+    }
+    else
+    {
+        float alpha = d/v_antialias;
+        alpha = exp(-alpha*alpha);
+        if (r > 0)
+            gl_FragColor = vec4(v_fg_color.rgb, alpha*v_fg_color.a);
+        else
+            gl_FragColor = mix(v_bg_color, v_fg_color, alpha);
+    }
+}
+"""
+
+
+
+# ------------------------------------------------------------ Canvas class ---
+class Canvas(app.Canvas):
+
+
+    def __init__(self):
+        app.Canvas.__init__(self)
+        self.size = 800, 600
+        
+        self.program = gloo.Program(vert,frag)
+        self.view = np.eye(4,dtype=np.float32)
+        self.model = np.eye(4,dtype=np.float32)
+        self.projection = np.eye(4,dtype=np.float32)
+        self.translate = 5
+        translate(self.view, 0,0, -self.translate)
+
+        self.program.set_vars(gloo.VertexBuffer(data))
+        self.program['u_linewidth'] = u_linewidth
+        self.program['u_antialias'] = u_antialias
+        self.program['u_model'] = self.model
+        self.program['u_view'] = self.view
+        self.program['u_size'] = 5/self.translate
+        
+        self.theta = 0
+        self.phi = 0
+
+        self.timer = app.Timer(1.0/60)
+        self.timer.connect(self.on_timer)
+        self.timer.start()
+
+
+    def on_initialize(self, event):
+        gl.glClearColor(1,1,1,1)
+        gl.glEnable(gl.GL_DEPTH_TEST)
+        gl.glEnable(gl.GL_BLEND)
+        gl.glBlendFunc (gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA)
+
+
+    def on_key_press(self,event):
+        if event.text == ' ':
+            if self.timer.running:
+                self.timer.stop()
+            else:
+                self.timer.start()
+
+
+    def on_timer(self,event):
+        self.theta += .5
+        self.phi += .5
+        self.model = np.eye(4, dtype=np.float32)
+        rotate(self.model, self.theta, 0,0,1)
+        rotate(self.model, self.phi,   0,1,0)
+        self.program['u_model'] = self.model
+        self.update()
+
+
+    def on_resize(self, event):
+        width, height = event.size
+        gl.glViewport(0, 0, width, height)
+        self.projection = perspective( 45.0, width/float(height), 1.0, 1000.0 )
+        self.program['u_projection'] = self.projection
+
+
+    def on_mouse_wheel(self, event):
+        self.translate +=event.delta[1]
+        self.translate = max(2,self.translate)
+        self.view       = np.eye(4,dtype=np.float32)
+        translate(self.view, 0,0, -self.translate)
+
+        self.program['u_view'] = self.view
+        self.program['u_size'] = 5/self.translate
+        self.update()
+
+
+    def on_paint(self, event):
+        gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
+        self.program.draw(gl.GL_POINTS)
+
+
+
+
+if __name__ == '__main__':
+    c = Canvas()
+    c.show()
+    app.run()
diff --git a/examples/demo/fireworks.py b/examples/demo/fireworks.py
new file mode 100644
index 0000000..b71d676
--- /dev/null
+++ b/examples/demo/fireworks.py
@@ -0,0 +1,150 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# vispy: gallery 20
+
+"""
+Example demonstrating simulation of fireworks using point sprites.
+(adapted from the "OpenGL ES 2.0 Programming Guide")
+
+This example demonstrates a series of explosions that last one second. The
+visualization during the explosion is highly optimized using a Vertex Buffer
+Object (VBO). After each explosion, vertex data for the next explosion are
+calculated, such that each explostion is unique.
+"""
+
+import time
+import numpy as np
+import vispy
+from vispy import gloo
+from vispy import app
+from vispy.gloo import gl
+
+# Create a texture
+radius = 32
+im1 = np.random.normal(0.8, 0.3, (radius*2+1, radius*2+1))
+
+# Mask it with a disk
+L = np.linspace(-radius, radius, 2 * radius + 1)
+(X, Y) = np.meshgrid(L, L)
+im1 *= np.array((X**2 + Y**2) <= radius * radius, dtype='float32')
+
+# Set number of particles, you should be able to scale this to 100000
+N = 10000
+
+# Create vertex data container
+data = np.zeros(N, [ ('a_lifetime', np.float32, 1),
+                     ('a_startPosition', np.float32, 3),
+                     ('a_endPosition', np.float32, 3) ])
+
+
+VERT_SHADER = """
+uniform float u_time;
+uniform vec3 u_centerPosition;
+attribute float a_lifetime;
+attribute vec3 a_startPosition;
+attribute vec3 a_endPosition;
+varying float v_lifetime;
+
+void main () {
+    if (u_time <= a_lifetime)
+    {
+        gl_Position.xyz = a_startPosition + (u_time * a_endPosition);
+        gl_Position.xyz += u_centerPosition;
+        gl_Position.y -= 1.0 * u_time * u_time;
+        gl_Position.w = 1.0;
+    }
+    else
+        gl_Position = vec4(-1000, -1000, 0, 0);
+    
+    v_lifetime = 1.0 - (u_time / a_lifetime);
+    v_lifetime = clamp(v_lifetime, 0.0, 1.0);
+    gl_PointSize = (v_lifetime * v_lifetime) * 40.0;
+}
+"""
+
+FRAG_SHADER = """
+uniform sampler2D texture1;
+uniform vec4 u_color;
+varying float v_lifetime;
+uniform sampler2D s_texture;
+
+void main()
+{    
+    vec4 texColor;
+    texColor = texture2D(s_texture, gl_PointCoord);
+    gl_FragColor = vec4(u_color) * texColor;
+    gl_FragColor.a *= v_lifetime;
+}
+"""
+
+
+class Canvas(app.Canvas):
+    
+    def __init__(self):
+        app.Canvas.__init__(self)
+        self.size = 800, 600
+
+        # Create program
+        self._program = gloo.Program( VERT_SHADER, FRAG_SHADER)        
+        self._program.set_vars(gloo.VertexBuffer(data))
+        self._program['s_texture'] = gloo.Texture2D(im1)
+        
+        # Create first explosion
+        self._new_explosion()
+    
+    
+    def on_initialize(self, event):
+        gl.glClearColor(0,0,0,1);
+        
+        # Enable blending
+        gl.glEnable(gl.GL_BLEND)
+        gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE)
+    
+    
+    def on_resize(self, event):
+        width, height = event.size
+        gl.glViewport(0, 0, width, height)
+    
+    
+    def on_paint(self, event):
+        
+        # Clear
+        gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
+        
+        # Draw
+        self._program['u_time'] = time.time() - self._starttime
+        self._program.draw(gl.GL_POINTS)
+        
+        # Invoke a new draw
+        self.update()
+        
+        # New explosion?
+        if time.time() - self._starttime > 1.5:
+            self._new_explosion()
+    
+    
+    def _new_explosion(self):
+        
+        # New centerpos
+        centerpos = np.random.uniform(-0.5, 0.5, (3,))
+        self._program['u_centerPosition'] = centerpos
+        
+        # New color, scale alpha with N
+        alpha = 1.0 / N**0.08
+        color = np.random.uniform(0.1, 0.9, (3,))
+        
+        self._program['u_color'] = tuple(color)+ (alpha,)
+        
+        # Create new vertex data
+        data['a_lifetime'] = np.random.normal(2.0, 0.5, (N,))
+        data['a_startPosition'] = np.random.normal(0.0, 0.2, (N,3))
+        data['a_endPosition'] = np.random.normal(0.0, 1.2, (N,3))
+        
+        # Set time to zero
+        self._starttime = time.time()
+
+
+if __name__ == '__main__':
+    c = Canvas()
+    c.show()
+    app.run()
diff --git a/examples/demo/galaxy.py b/examples/demo/galaxy.py
new file mode 100644
index 0000000..a4d68e4
--- /dev/null
+++ b/examples/demo/galaxy.py
@@ -0,0 +1,197 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# vispy: gallery 2
+# Copyright (c) 2013, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+"""
+Just a very fake galaxy.
+Astronomers and cosmologists will kill me !
+"""
+
+import os
+import numpy as np
+
+from vispy import gloo
+from vispy import app
+from vispy.gloo import gl
+from vispy.util.transforms import perspective, translate, rotate
+
+# Manual galaxy creation
+# (did you really expect a simulation in less than 250 python lines ?)
+def make_arm(n,angle):
+    R = np.linspace(10,450+50*np.random.uniform(.5,1.),n)
+    R += 40*np.random.normal(0,2.,n) * np.linspace(1,.1,n)
+    T = angle+np.linspace(0,2.5*np.pi,n) + np.pi/6*np.random.normal(0,.5,n)
+    S = 8+2*np.abs(np.random.normal(0,1,n))
+    S *= np.linspace(1,.85,n)
+    P = np.zeros((n,3), dtype=np.float32)
+    X, Y, Z =  P[:,0],P[:,1],P[:,2]
+    X[...] = R*np.cos(T)
+    Y[...] = R*np.sin(T)*1.1
+    D = np.sqrt(X*X+Y*Y)
+    Z[...] = 8*np.random.normal(0, 2-D/512., n)
+    X += (D*np.random.uniform(0,1,n) > 250)*(.05*D*np.random.uniform(-1,1,n))
+    Y += (D*np.random.uniform(0,1,n) > 250)*(.05*D*np.random.uniform(-1,1,n))
+    Z += (D*np.random.uniform(0,1,n) > 250)*(.05*D*np.random.uniform(-1,1,n))
+    D = (D - D.min())/(D.max() - D.min())
+
+    return P/256,S/2,D
+p = 50000
+n = 3*p
+
+data = np.zeros(n, [('a_position', np.float32, 3),
+                    ('a_size',     np.float32, 1),
+                    ('a_dist',     np.float32, 1)])
+for i in range(3):
+    P,S,D = make_arm(p, i * 2*np.pi/3)
+    data['a_dist'][(i+0)*p:(i+1)*p] = D 
+    data['a_position'][(i+0)*p:(i+1)*p] = P
+    data['a_size'][(i+0)*p:(i+1)*p] = S
+
+
+
+# Very simple colormap
+cmap = np.array([[255, 124,   0], [255, 163,  76],
+                 [255, 192, 130], [255, 214, 173],
+                 [255, 232, 212], [246, 238, 237],
+                 [237, 240, 253], [217, 228, 255],
+                 [202, 219, 255], [191, 212, 255],
+                 [182, 206, 255], [174, 202, 255],
+                 [168, 198, 255], [162, 195, 255],
+                 [158, 192, 255], [155, 189, 255],
+                 [151, 187, 255], [148, 185, 255],
+                 [145, 183, 255], [143, 182, 255],
+                 [141, 181, 255], [140, 179, 255],
+                 [139, 179, 255],
+                 [137, 177, 255]], dtype=np.uint8).reshape(1,24,3)
+
+
+VERT_SHADER = """
+// Uniforms
+// ------------------------------------
+uniform mat4  u_model;
+uniform mat4  u_view;
+uniform mat4  u_projection;
+uniform float u_size;
+
+
+// Attributes
+// ------------------------------------
+attribute vec3  a_position;
+attribute float a_size;
+attribute float a_dist;
+
+// Varyings
+// ------------------------------------
+varying float v_size;
+varying float v_dist;
+
+void main (void) {
+    v_size  = a_size*u_size*.75;
+    v_dist  = a_dist;
+    gl_Position = u_projection * u_view * u_model * vec4(a_position,1.0);
+    gl_PointSize = v_size;
+}
+"""
+
+FRAG_SHADER = """
+// Uniforms
+// ------------------------------------
+uniform sampler2D u_colormap;
+
+// Varyings
+// ------------------------------------
+varying float v_size;
+varying float v_dist;
+
+// Main
+// ------------------------------------
+void main()
+{    
+    float a = 2*(length(gl_PointCoord.xy - vec2(0.5,0.5)) / sqrt(2.0));
+    vec3 color = texture2D(u_colormap, vec2(v_dist,.5)).rgb;
+    gl_FragColor = vec4(color,(1-a)*.25);
+}
+"""
+
+
+
+class Canvas(app.Canvas):
+
+    def __init__(self):
+        app.Canvas.__init__(self)
+        self.size = 800, 600
+        self.title = "A very fake galaxy [mouse scroll to zoom]"
+
+        self.program = gloo.Program(VERT_SHADER, FRAG_SHADER)
+        self.view = np.eye(4,dtype=np.float32)
+        self.model = np.eye(4,dtype=np.float32)
+        self.projection = np.eye(4,dtype=np.float32)
+        self.theta, self.phi = 0,0
+
+        self.translate = 5
+        translate(self.view, 0,0, -self.translate)
+
+        self.program.set_vars(gloo.VertexBuffer(data),
+                              u_colormap = gloo.Texture2D(cmap),
+                              u_size = 5./self.translate,
+                              u_model = self.model,
+                              u_view = self.view)
+
+        self.timer = app.Timer(1.0/60)
+        self.timer.connect(self.on_timer)
+
+
+    def on_initialize(self, event):
+        gl.glClearColor(0,0,0,1)
+        gl.glDisable(gl.GL_DEPTH_TEST)
+        gl.glEnable(gl.GL_BLEND)
+        gl.glBlendFunc (gl.GL_SRC_ALPHA, gl.GL_ONE) #_MINUS_SRC_ALPHA)
+        # Start the timer upon initialization.
+        self.timer.start()
+
+
+    def on_key_press(self,event):
+        if event.text == ' ':
+            if self.timer.running:
+                self.timer.stop()
+            else:
+                self.timer.start()
+
+
+    def on_timer(self,event):
+        self.theta += .11
+        self.phi += .13
+        self.model = np.eye(4, dtype=np.float32)
+        rotate(self.model, self.theta, 0,0,1)
+        rotate(self.model, self.phi,   0,1,0)
+        self.program['u_model'] = self.model
+        self.update()
+
+
+    def on_resize(self, event):
+        width, height = event.size
+        gl.glViewport(0, 0, width, height)
+        self.projection = perspective( 45.0, width/float(height), 1.0, 1000.0 )
+        self.program['u_projection'] = self.projection
+
+
+    def on_mouse_wheel(self, event):
+        self.translate +=event.delta[1]
+        self.translate = max(2,self.translate)
+        self.view       = np.eye(4,dtype=np.float32)
+        translate(self.view, 0,0, -self.translate)
+        self.program['u_view'] = self.view
+        self.program['u_size'] = 5/self.translate
+        self.update()
+
+
+    def on_paint(self, event):
+        gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
+        self.program.draw(gl.GL_POINTS)
+
+if __name__ == '__main__':
+    c = Canvas()
+    c.show()
+    app.run()
diff --git a/examples/demo/game_of_life.py b/examples/demo/game_of_life.py
new file mode 100644
index 0000000..748d83b
--- /dev/null
+++ b/examples/demo/game_of_life.py
@@ -0,0 +1,156 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# vispy: gallery 200
+
+""" 
+Example demonstrating the Game of Life in shaders.
+See http://en.wikipedia.org/wiki/Conway%27s_Game_of_Life
+This examples uses textures and FBO's.
+"""
+
+import time
+
+import numpy as np
+
+from vispy.gloo import gl
+from vispy import gloo
+from vispy import app
+
+
+# Given initial world state
+init_gun = [1,5, 2,5, 1,6, 2,6, 
+        11,5, 11,6, 11,7, 12,4, 12,8, 13,3, 13,9, 14,3, 14,9, 15,6,
+        16,4, 16,8, 17,5, 17,6, 17,7, 18,6,
+        21,3, 21,4, 21,5, 22,3, 22,4, 22,5, 23,2, 23,6, 25,1, 25,2, 25,6, 25,7,
+        35,3, 35,4, 36,3, 36,4]
+
+# Create texture to initialize the game
+im1 = np.zeros((100,120,3), 'uint8')
+X, Y = np.array(init_gun[::2]), (im1.shape[0]-1)-np.array(init_gun[1::2])
+im1[Y,X] = 255
+
+# Create vertex data
+vertex_data = np.zeros(4, dtype=[   ('a_position', np.float32, 3), 
+                                    ('a_texcoord', np.float32, 2) ])
+vertex_data['a_position'] = np.array([  [-1.0, -1.0, 0.0],  [+1.0, -1.0, 0.0],  
+                                        [-1.0, +1.0, 0.0],  [+1.0, +1.0, 0.0,] 
+                                    ], np.float32)
+vertex_data['a_texcoord'] = np.array([  [0.0, 0.0], [1.0, 0.0], 
+                                        [0.0, 1.0], [1.0, 1.0] ], np.float32)
+
+
+VERT_SHADER = """ // Life vertex shader
+attribute vec3 a_position;
+attribute vec2 a_texcoord;
+varying vec2 v_texcoord;
+void main (void) {
+    // Calculate position (note that we flip y here)
+    gl_Position = vec4(a_position.x, a_position.y,  a_position.z, 1.0);
+    // Pass texture coords
+    v_texcoord = a_texcoord;
+}
+"""
+
+FRAG_SHADER = """ // Life fragment shader
+uniform sampler2D u_texture;
+uniform vec2 u_texsize;
+varying vec2 v_texcoord;
+void main()
+{   
+    float dx = 1.0 / u_texsize.x;
+    float dy = 1.0 / u_texsize.y;
+    
+    // Count alive neigghbours
+    int count = 0;
+    if (texture2D(u_texture, v_texcoord.xy+vec2(-dx, -dy)).r > 0.0)
+        count += 1;
+    if (texture2D(u_texture, v_texcoord.xy+vec2(-dx, 0.0)).r > 0.0)
+        count += 1;
+    if (texture2D(u_texture, v_texcoord.xy+vec2(-dx, +dy)).r > 0.0)
+        count += 1;
+    if (texture2D(u_texture, v_texcoord.xy+vec2(0.0, -dy)).r > 0.0)
+        count += 1;
+    if (texture2D(u_texture, v_texcoord.xy+vec2(0.0, +dy)).r > 0.0)
+        count += 1;
+    if (texture2D(u_texture, v_texcoord.xy+vec2(+dx, -dy)).r > 0.0)
+        count += 1;
+    if (texture2D(u_texture, v_texcoord.xy+vec2(+dx, 0.0)).r > 0.0)
+        count += 1;
+    if (texture2D(u_texture, v_texcoord.xy+vec2(+dx, +dy)).r > 0.0)
+        count += 1;
+    
+    // Calculate if we stay alive or reproduce
+    float am_alive = texture2D(u_texture, v_texcoord.xy).r;
+    float survive = float(count==3) + am_alive*float(count==2);
+    survive = min(survive, 1.0);
+    
+    // Set color
+    gl_FragColor = vec4(survive, survive, survive, 1.0);
+}
+"""
+
+
+class Canvas(app.Canvas):
+    
+    def __init__(self):
+        app.Canvas.__init__(self)
+        
+        # Create program
+        self._program = gloo.Program( VERT_SHADER, FRAG_SHADER)
+        
+        # Creat FBO
+        self._fbo = gloo.FrameBuffer()
+        self._fbo.attach_depth(gloo.RenderBuffer(im1.shape))
+        
+        # Create vbo
+        self._vbo = gloo.VertexBuffer(vertex_data)
+        
+        # Create textures 
+        self._tex1 = gloo.Texture2D(im1)
+        self._tex2 = gloo.Texture2D(im1.shape)
+        for tex in (self._tex1, self._tex2):
+            tex.set_filter('NEAREST', 'NEAREST')
+        
+        
+        # Set uniforms and attributes
+        self._program.set_vars(self._vbo)
+        self._program['u_texsize'] = im1.shape[1], im1.shape[0]
+    
+    
+    def on_initialize(self, event):
+        gl.glClearColor(0,0,0,1)
+    
+    
+    def on_paint(self, event):
+        
+        # Set framebuffer input output
+        self._program['u_texture'] = self._tex1
+        self._fbo.attach_color(self._tex2)
+        
+        with self._fbo:
+            # Init
+            gl.glViewport(0, 0, im1.shape[1], im1.shape[0])
+            gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
+            # Draw
+            self._program.draw(gl.GL_TRIANGLE_STRIP)
+        
+        # Draw to the normal color buffer (i.e. the screen)
+        self._program['u_texture'] = self._tex2
+        # Init
+        gl.glViewport(0, 0, self.size[0], self.size[1])
+        gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
+        # Draw
+        self._program.draw(gl.GL_TRIANGLE_STRIP)
+        
+        # Prepare for next round
+        self._tex1, self._tex2 = self._tex2, self._tex1
+        
+        # Force redraw
+        self.update()
+
+
+if __name__ == '__main__':
+    c = Canvas()
+    c.show()
+    app.run()
+    
diff --git a/examples/demo/markers.py b/examples/demo/markers.py
new file mode 100644
index 0000000..f46630f
--- /dev/null
+++ b/examples/demo/markers.py
@@ -0,0 +1,229 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# VisPy - Copyright (c) 2013, Vispy Development Team All rights reserved.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+# -----------------------------------------------------------------------------
+"""
+Marker shader definitions. You need to combine marker_frag with one of the
+available marker function (marker_disc, marker_diamond, ...)
+"""
+
+
+vert = """
+// Uniforms
+// ------------------------------------
+uniform mat4 u_model;
+uniform mat4 u_view;
+uniform mat4 u_projection;
+uniform float u_antialias;
+uniform float u_size;
+
+// Attributes
+// ------------------------------------
+attribute vec3  a_position;
+attribute vec4  a_fg_color;
+attribute vec4  a_bg_color;
+attribute float a_linewidth;
+attribute float a_size;
+
+// Varyings
+// ------------------------------------
+varying vec4 v_fg_color;
+varying vec4 v_bg_color;
+varying float v_size;
+varying float v_linewidth;
+varying float v_antialias;
+
+void main (void) {
+    v_size = a_size * u_size;
+    v_linewidth = a_linewidth;
+    v_antialias = u_antialias;
+    v_fg_color  = a_fg_color;
+    v_bg_color  = a_bg_color;
+    gl_Position = u_projection * u_view * u_model * vec4(a_position*u_size,1.0);
+    gl_PointSize = v_size + 2*(v_linewidth + 1.5*v_antialias);
+}
+"""
+
+
+frag = """
+// Constants
+// ------------------------------------
+
+// Varyings
+// ------------------------------------
+varying vec4 v_fg_color;
+varying vec4 v_bg_color;
+varying float v_size;
+varying float v_linewidth;
+varying float v_antialias;
+
+// Functions
+// ------------------------------------
+float marker(vec2 P, float size);
+
+
+// Main
+// ------------------------------------
+void main()
+{    
+    float size = v_size +2*(v_linewidth + 1.5*v_antialias);
+    float t = v_linewidth/2.0-v_antialias;
+
+    // The marker function needs to be linked with this shader
+    float r = marker(gl_PointCoord, size);
+
+    float d = abs(r) - t;
+    if( r > (v_linewidth/2.0+v_antialias))
+    {
+        discard;
+    }
+    else if( d < 0.0 )
+    {
+       gl_FragColor = v_fg_color;
+    }
+    else
+    {
+        float alpha = d/v_antialias;
+        alpha = exp(-alpha*alpha);
+        if (r > 0)
+            gl_FragColor = vec4(v_fg_color.rgb, alpha*v_fg_color.a);
+        else
+            gl_FragColor = mix(v_bg_color, v_fg_color, alpha);
+    }
+}
+"""
+
+
+disc = """
+float marker(vec2 P, float size)
+{
+    float r = length((P.xy - vec2(0.5,0.5))*size);
+    r -= v_size/2;
+    return r;
+}
+"""
+
+
+arrow = """
+float marker(vec2 P, float size)
+{
+    float r1 = abs(P.x -.50)*size + abs(P.y -.5)*size - v_size/2; 
+    float r2 = abs(P.x -.25)*size + abs(P.y -.5)*size - v_size/2; 
+    float r = max(r1,-r2);
+    return r;
+}
+"""
+
+
+ring = """
+float marker(vec2 P, float size)
+{
+    float r1 = length((P.xy - vec2(0.5,0.5))*size) - v_size/2;
+    float r2 = length((P.xy - vec2(0.5,0.5))*size) - v_size/4;
+    float r = max(r1,-r2);
+    return r;
+}
+"""
+
+
+clobber = """
+float marker(vec2 P, float size)
+{
+    const float PI = 3.14159265358979323846264;
+    const float t1 = -PI/2;
+    const vec2  c1 = 0.2*vec2(cos(t1),sin(t1));
+    const float t2 = t1+2*PI/3;
+    const vec2  c2 = 0.2*vec2(cos(t2),sin(t2));
+    const float t3 = t2+2*PI/3;
+    const vec2  c3 = 0.2*vec2(cos(t3),sin(t3));
+
+    float r1 = length((P.xy- vec2(0.5,0.5) - c1)*size);
+    r1 -= v_size/3;
+    float r2 = length((P.xy- vec2(0.5,0.5) - c2)*size);
+    r2 -= v_size/3;
+    float r3 = length((P.xy- vec2(0.5,0.5) - c3)*size);
+    r3 -= v_size/3;
+    float r = min(min(r1,r2),r3);
+    return r;
+}
+"""
+
+
+square = """
+float marker(vec2 P, float size)
+{
+    float r = max(abs(P.x -.5)*size, abs(P.y -.5)*size);
+    r -= v_size/2;
+    return r;
+}
+"""
+
+
+diamond = """
+float marker(vec2 P, float size)
+{
+    float r = abs(P.x -.5)*size + abs(P.y -.5)*size; 
+    r -= v_size/2;
+    return r;
+}
+"""
+
+
+vbar = """
+float marker(vec2 P, float size)
+{
+    float r1 = max(abs(P.x - 0.75)*size, abs(P.x - 0.25)*size); 
+    float r3 = max(abs(P.x - 0.50)*size, abs(P.y - 0.50)*size); 
+    float r = max(r1,r3);
+    r -= v_size/2;
+    return r;
+}
+"""
+
+
+hbar = """
+float marker(vec2 P, float size)
+{
+    float r2 = max(abs(P.y - 0.75)*size, abs(P.y - 0.25)*size); 
+    float r3 = max(abs(P.x - 0.50)*size, abs(P.y - 0.50)*size); 
+    float r = max(r2,r3);
+    r -= v_size/2;
+    return r;
+}
+"""
+
+
+cross = """
+float marker(vec2 P, float size)
+{
+    float r1 = max(abs(P.x - 0.75)*size, abs(P.x - 0.25)*size); 
+    float r2 = max(abs(P.y - 0.75)*size, abs(P.y - 0.25)*size); 
+    float r3 = max(abs(P.x - 0.50)*size, abs(P.y - 0.50)*size); 
+    float r = max(min(r1,r2),r3);
+    r -= v_size/2;
+    return r;
+}
+"""
+
+tailed_arrow= """
+float marker(vec2 P, float size)
+{
+
+   //arrow_right
+    float r1 = abs(P.x -.50)*size + abs(P.y -.5)*size - v_size/2; 
+    float r2 = abs(P.x -.25)*size + abs(P.y -.5)*size - v_size/2; 
+    float arrow = max(r1,-r2);
+
+    //hbar
+    float r3 = (abs(P.y-.5)*2+.3)*v_size-v_size/2;
+    float r4 = (P.x -.775)*size;
+    float r6 = abs(P.x -.5)*size-v_size/2;
+    float limit = (P.x -.5)*size + abs(P.y -.5)*size - v_size/2; 
+    float hbar = max(limit,max(max(r3,r4),r6));
+
+    return min(arrow,hbar);
+}
+"""
+
+
diff --git a/examples/demo/show-markers.py b/examples/demo/show-markers.py
new file mode 100644
index 0000000..7af8fae
--- /dev/null
+++ b/examples/demo/show-markers.py
@@ -0,0 +1,117 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# vispy: gallery 2
+
+""" Display markers at different sizes and line thicknessess.
+"""
+
+import os
+import sys
+
+import numpy as np
+from vispy.gloo import gl
+
+from vispy import app
+from vispy.util.transforms import ortho
+from vispy.gloo import Program
+from vispy.gloo import VertexBuffer
+
+sys.path.insert(0, os.path.dirname(__file__))
+import markers
+
+
+n = 540
+data = np.zeros(n, dtype = [ ('a_position', np.float32, 3),
+                             ('a_fg_color', np.float32, 4),
+                             ('a_bg_color', np.float32, 4),
+                             ('a_size',     np.float32, 1),
+                             ('a_linewidth',np.float32, 1) ])
+data['a_fg_color']  = 0,0,0,1
+data['a_bg_color']  = 1,1,1,1
+data['a_linewidth'] = 1
+u_antialias = 1
+
+radius, theta, dtheta = 255.0, 0.0, 5.5/180.0*np.pi
+for i in range(500):
+    theta += dtheta
+    x = 256    + radius*np.cos(theta)
+    y = 256+32 + radius*np.sin(theta)
+    r = 10.1-i*0.02;
+    radius -= 0.45
+    data['a_position'][i] = x,y,0
+    data['a_size'][i] = 2*r
+
+for i in range(40):
+    r = 4
+    thickness = (i+1)/10.0
+    x = 20+i*12.5 - 2*r
+    y = 16
+    data['a_position'][500+i] = x,y,0
+    data['a_size'][500+i] = 2*r
+    data['a_linewidth'][500+i] = thickness
+
+
+class Canvas(app.Canvas):
+    def __init__(self):
+        app.Canvas.__init__(self)
+
+        # This size is used for comparison with agg (via matplotlib)
+        self.size = 512,512+2*32
+        self.title = "Markers demo [press space to change marker]"
+
+        self.vbo = VertexBuffer(data)
+        self.view = np.eye(4,dtype=np.float32)
+        self.model = np.eye(4,dtype=np.float32)
+        self.projection = ortho(0, self.size[0], 0, self.size[1], -1, 1)
+        self.programs = [
+            Program(markers.vert, markers.frag + markers.tailed_arrow),
+            Program(markers.vert, markers.frag + markers.disc),
+            Program(markers.vert, markers.frag + markers.diamond),
+            Program(markers.vert, markers.frag + markers.square),
+            Program(markers.vert, markers.frag + markers.cross),
+            Program(markers.vert, markers.frag + markers.arrow),
+            Program(markers.vert, markers.frag + markers.vbar),
+            Program(markers.vert, markers.frag + markers.hbar),
+            Program(markers.vert, markers.frag + markers.clobber),
+            Program(markers.vert, markers.frag + markers.ring) ]
+                                    
+        for program in self.programs:
+            program.set_vars(self.vbo,
+                             u_antialias = u_antialias,
+                             u_size = 1,
+                             u_model = self.model,
+                             u_view = self.view,
+                             u_projection = self.projection)
+        self.index = 0
+        self.program = self.programs[self.index]
+
+    def on_initialize(self, event):
+        gl.glClearColor(1,1,1,1)
+        gl.glDisable(gl.GL_DEPTH_TEST)
+        gl.glEnable(gl.GL_BLEND)
+        gl.glBlendFunc (gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA)
+
+    def on_key_press(self,event):
+        if event.text == ' ':
+            self.index = (self.index+1) % (len(self.programs))
+            self.program = self.programs[self.index]
+            self.program['u_projection'] = self.projection
+            self.program['u_size'] = self.u_size
+            self.update()
+
+    def on_resize(self, event):
+        width, height = event.size
+        gl.glViewport(0, 0, width, height)
+        self.projection = ortho( 0, width, 0, height, -100, 100 )
+        self.u_size = width/512.0
+        self.program['u_projection'] = self.projection
+        self.program['u_size'] = self.u_size
+
+    def on_paint(self, event):
+        gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
+        self.program.draw(gl.GL_POINTS)
+
+if __name__ == '__main__':
+    canvas = Canvas()
+    canvas.show()
+    app.run()
diff --git a/examples/glsl-sandbox-cube.py b/examples/glsl-sandbox-cube.py
new file mode 100644
index 0000000..28a77da
--- /dev/null
+++ b/examples/glsl-sandbox-cube.py
@@ -0,0 +1,184 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+A GLSL sandbox application based on the spinning cube. Requires PySide
+or PyQt4.
+"""
+
+import numpy as np
+from vispy import app, gloo, dataio
+from vispy.util.transforms import perspective, translate, rotate
+from vispy.gloo import gl
+
+# Force using qt and take QtCore+QtGui from backend module,
+# since we do not know whether PySide or PyQt4 is used
+app.use('qt')
+QtCore = app.default_app.backend_module.QtCore, 
+QtGui = app.default_app.backend_module.QtGui 
+
+
+VERT_CODE = """
+uniform   mat4 u_model;
+uniform   mat4 u_view;
+uniform   mat4 u_projection;
+
+attribute vec3 a_position;
+attribute vec2 a_texcoord;
+
+varying vec2 v_texcoord;
+
+void main()
+{
+    v_texcoord = a_texcoord;
+    gl_Position = u_projection * u_view * u_model * vec4(a_position,1.0);
+    //gl_Position = vec4(a_position,1.0);
+    
+}
+"""
+
+
+FRAG_CODE = """
+uniform sampler2D u_texture;
+varying vec2 v_texcoord;
+
+void main()
+{   
+    float ty = v_texcoord.y;
+    float tx = sin(ty*50.0)*0.01 + v_texcoord.x;
+    gl_FragColor = texture2D(u_texture, vec2(tx, ty));
+}
+"""
+
+
+# Read cube data
+positions, faces, normals, texcoords = dataio.read_mesh('cube.obj')
+colors = np.random.uniform(0,1,positions.shape).astype('float32')
+
+faces_buffer = gloo.ElementBuffer(faces.astype(np.uint16))
+
+
+class Canvas(app.Canvas):
+    
+    def __init__(self, **kwargs):
+        app.Canvas.__init__(self, **kwargs)
+        self.geometry = 0, 0, 400, 400
+        
+        self.program = gloo.Program(VERT_CODE, FRAG_CODE)
+        
+        # Set attributes
+        self.program['a_position'] = gloo.VertexBuffer(positions)
+        self.program['a_texcoord'] = gloo.VertexBuffer(texcoords)
+        
+        self.program['u_texture'] = gloo.Texture2D(dataio.crate())
+        
+        # Handle transformations
+        self.init_transforms()
+        
+        self.timer = app.Timer(1.0/60)
+        self.timer.connect(self.update_transforms)
+        self.timer.start()
+        
+    
+    def on_initialize(self, event):
+        gl.glClearColor(1,1,1,1)
+        gl.glEnable(gl.GL_DEPTH_TEST)
+    
+    
+    def on_resize(self, event):
+        width, height = event.size
+        gl.glViewport(0, 0, width, height)
+        self.projection = perspective( 45.0, width/float(height), 2.0, 10.0 )
+        self.program['u_projection'] = self.projection
+    
+    
+    def on_paint(self, event):
+        
+        gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
+        self.program.draw(gl.GL_TRIANGLES, faces_buffer)
+    
+    
+    def init_transforms(self):
+        self.view       = np.eye(4,dtype=np.float32)
+        self.model      = np.eye(4,dtype=np.float32)
+        self.projection = np.eye(4,dtype=np.float32)
+        
+        self.theta = 0
+        self.phi = 0
+        
+        translate(self.view, 0,0,-5)
+        self.program['u_model'] = self.model
+        self.program['u_view'] = self.view
+    
+    
+    def update_transforms(self,event):
+        self.theta += .5
+        self.phi += .5
+        self.model = np.eye(4, dtype=np.float32)
+        rotate(self.model, self.theta, 0,0,1)
+        rotate(self.model, self.phi,   0,1,0)
+        self.program['u_model'] = self.model
+        self.update()
+
+
+
+class TextField(QtGui.QPlainTextEdit):
+    def __init__(self, parent):
+        QtGui.QPlainTextEdit.__init__(self, parent)
+        # Set font to monospaced (TypeWriter)
+        font = QtGui.QFont('')
+        font.setStyleHint(font.TypeWriter, font.PreferDefault)
+        font.setPointSize(8)
+        self.setFont(font)
+
+
+
+class MainWindow(QtGui.QWidget):
+    def __init__(self):
+        QtGui.QWidget.__init__(self, None)
+        
+        self.setMinimumSize(600, 400)
+        
+        # Create two labels and a button
+        self.vertLabel = QtGui.QLabel("Vertex code", self)
+        self.fragLabel = QtGui.QLabel("Fragment code", self)
+        self.theButton = QtGui.QPushButton("Compile!", self)
+        self.theButton.clicked.connect(self.on_compile)
+        
+        # Create two editors
+        self.vertEdit = TextField(self)
+        self.vertEdit.setPlainText(VERT_CODE)
+        self.fragEdit = TextField(self)
+        self.fragEdit.setPlainText(FRAG_CODE)
+        
+        # Create a canvas
+        self.canvas = Canvas()
+        self.canvas.create_native()
+        self.canvas.native.setParent(self)
+        
+        # Layout
+        hlayout = QtGui.QHBoxLayout(self)
+        self.setLayout(hlayout)
+        vlayout = QtGui.QVBoxLayout()
+        #
+        hlayout.addLayout(vlayout, 1)
+        hlayout.addWidget(self.canvas.native, 1)
+        #
+        vlayout.addWidget(self.vertLabel, 0)
+        vlayout.addWidget(self.vertEdit, 1)
+        vlayout.addWidget(self.fragLabel, 0)
+        vlayout.addWidget(self.fragEdit, 1)
+        vlayout.addWidget(self.theButton, 0)
+    
+    
+    def on_compile(self):
+        vert_code = str(self.vertEdit.toPlainText())
+        frag_code = str(self.fragEdit.toPlainText())
+        self.canvas.program.shaders[0].set_code(vert_code)
+        self.canvas.program.shaders[1].set_code(frag_code)
+    
+
+if __name__ == '__main__':
+    app.create()
+    m = MainWindow()
+    m.show()
+    app.run()
diff --git a/examples/howto/animate-images.py b/examples/howto/animate-images.py
new file mode 100644
index 0000000..9c9df67
--- /dev/null
+++ b/examples/howto/animate-images.py
@@ -0,0 +1,117 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# vispy: gallery 2
+""" 
+Example demonstrating showing a, image with a fixed ratio.
+"""
+
+import time
+import numpy as np
+
+from vispy.util.transforms import ortho
+from vispy import gloo
+from vispy import app
+from vispy.gloo import gl
+
+
+# Image to be displayed
+W,H = 64,48
+I = np.random.uniform(0,1,(W,H)).astype(np.float32)
+
+# A simple texture quad
+data = np.zeros(4, dtype=[ ('a_position', np.float32, 2), 
+                           ('a_texcoord', np.float32, 2) ])
+data['a_position'] = np.array([[0, 0], [W, 0], [0, H], [W, H]])
+data['a_texcoord'] = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
+
+
+VERT_SHADER = """
+// Uniforms
+uniform mat4 u_model;
+uniform mat4 u_view;
+uniform mat4 u_projection;
+uniform float u_antialias;
+
+// Attributes
+attribute vec2 a_position;
+attribute vec2 a_texcoord;
+
+// Varyings
+varying vec2 v_texcoord;
+
+// Main
+void main (void)
+{
+    v_texcoord = a_texcoord;
+    gl_Position = u_projection * u_view * u_model * vec4(a_position,0.0,1.0);
+}
+"""
+
+FRAG_SHADER = """
+uniform sampler2D u_texture;
+varying vec2 v_texcoord;
+void main()
+{    
+    gl_FragColor = texture2D(u_texture, v_texcoord);
+    gl_FragColor.a = 1.0;
+}
+
+"""
+
+
+class Canvas(app.Canvas):
+    
+    def __init__(self):
+        app.Canvas.__init__(self)
+        self.size = W*5,H*5
+
+        self.program = gloo.Program(VERT_SHADER, FRAG_SHADER)
+        self.texture = gloo.Texture2D(I)
+        self.texture.set_filter(gl.GL_NEAREST, gl.GL_NEAREST)
+        
+        self.program['u_texture'] = self.texture
+        self.program.set_vars(gloo.VertexBuffer(data))
+
+        self.view = np.eye(4,dtype=np.float32)
+        self.model = np.eye(4,dtype=np.float32)
+        self.projection = np.eye(4,dtype=np.float32)
+
+        self.program['u_model'] = self.model
+        self.program['u_view'] = self.view
+        self.projection = ortho(0, W, 0, H, -1, 1)
+        self.program['u_projection'] = self.projection
+    
+    def on_initialize(self, event):
+        gl.glClearColor(1,1,1,1)
+    
+    def on_resize(self, event):
+        width, height = event.size
+        gl.glViewport(0, 0, width, height)
+        self.projection = ortho( 0, width, 0, height, -100, 100 )
+        self.program['u_projection'] = self.projection
+
+        # Compute thje new size of the quad 
+        r = width/float(height)
+        R = W/float(H)
+        if r < R:
+            w,h = width, width/R
+            x,y = 0, int((height-h)/2)
+        else:
+            w,h = height*R, height
+            x,y = int((width-w)/2), 0
+        data['a_position'] = np.array([[x, y], [x+w, y], [x, y+h], [x+w, y+h]])
+        self.program.set_vars(gloo.VertexBuffer(data))
+
+    
+    def on_paint(self, event):
+        gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
+        I[...] = np.random.uniform(0,1,(W,H)).astype(np.float32)
+        self.texture.set_data(I)
+        self.program.draw(gl.GL_TRIANGLE_STRIP)
+        self.update()
+
+
+if __name__ == '__main__':
+    c = Canvas()
+    c.show()
+    app.run()
diff --git a/examples/howto/animate-shape.py b/examples/howto/animate-shape.py
new file mode 100644
index 0000000..de7ff78
--- /dev/null
+++ b/examples/howto/animate-shape.py
@@ -0,0 +1,114 @@
+# #!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# vispy: gallery 3
+
+""" 
+Example demonstrating showing a quad. Like hello_quad1.py, but now
+with Texture2D and VertexBuffer, and optionally using an ElementBuffer to
+draw the vertices.
+"""
+
+import time
+import numpy as np
+
+from vispy import gloo
+from vispy import app
+from vispy.gloo import gl
+
+
+# Create a texture
+im1 = np.zeros((100,100,3), 'float64')
+im1[:50,:,0] = 1.0
+im1[:,:50,1] = 1.0
+im1[50:,50:,2] = 1.0
+
+# Create vetices and texture coords, combined in one array for high performance
+vertex_data = np.zeros(4, dtype=[   ('a_position', np.float32, 3), 
+                                    ('a_texcoord', np.float32, 2) ])
+vertex_data['a_position'] = np.array([ [-0.8, -0.8, 0.0], [+0.7, -0.7, 0.0],  
+                                       [-0.7, +0.7, 0.0], [+0.8, +0.8, 0.0,] ])
+vertex_data['a_texcoord'] = np.array([    [0.0, 0.0], [0.0, 1.0], 
+                                          [1.0, 0.0], [1.0, 1.0] ])
+
+# Create indices and an ElementBuffer for it
+indices = np.array([0,1,2, 1,2,3], np.uint16)
+indices_buffer = gloo.ElementBuffer(indices)
+client_indices_buffer = gloo.ElementBuffer(indices, client=True)
+
+
+VERT_SHADER = """ // simple vertex shader
+
+attribute vec3 a_position;
+attribute vec2 a_texcoord;
+uniform float sizeFactor;
+//attribute float sizeFactor;
+
+void main (void) {
+    // Pass tex coords
+    gl_TexCoord[0] = vec4(a_texcoord.x, a_texcoord.y, 0.0, 0.0);
+    // Calculate position
+    gl_Position = sizeFactor*vec4(a_position.x, a_position.y, a_position.z,
+                                                        1.0/sizeFactor);
+}
+"""
+
+FRAG_SHADER = """ // simple fragment shader
+uniform sampler2D texture1;
+
+void main()
+{    
+    gl_FragColor = texture2D(texture1, gl_TexCoord[0].st);
+}
+
+"""
+
+
+class Canvas(app.Canvas):
+    
+    def __init__(self):
+        app.Canvas.__init__(self)
+        
+        # Create program
+        self._program = gloo.Program( VERT_SHADER, FRAG_SHADER)
+        
+        # Create vertex buffer
+        self._vbo = gloo.VertexBuffer(vertex_data)
+        
+        # Set uniforms, samplers, attributes
+        # We create one VBO with all vertex data (array of structures)
+        # and create two views from it for the attributes.
+        self._program['texture1'] = gloo.Texture2D(im1)
+        self._program.set_vars(self._vbo)  # This does: 
+        #self._program['a_position'] = self._vbo['a_position']
+        #self._program['a_texcoords'] = self._vbo['a_texcoords']
+        
+    
+    def on_initialize(self, event):
+        gl.glClearColor(1,1,1,1)
+    
+    
+    def on_resize(self, event):
+        width, height = event.size
+        gl.glViewport(0, 0, width, height)
+    
+    
+    def on_paint(self, event):
+        
+        # Clear
+        gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
+        
+        # Draw
+        self._program['sizeFactor'] = 0.5 + np.sin(time.time()*3)*0.2
+        
+        # Draw (pick one!)
+        #self._program.draw(gl.GL_TRIANGLE_STRIP)
+        self._program.draw(gl.GL_TRIANGLES, indices_buffer)
+        #self._program.draw(gl.GL_TRIANGLES, client_indices_buffer)  # Not recommended
+        
+        self.update()
+
+
+if __name__ == '__main__':
+    c = Canvas()
+    c.show()
+    app.run()
diff --git a/examples/howto/client-buffers.py b/examples/howto/client-buffers.py
new file mode 100644
index 0000000..1efd888
--- /dev/null
+++ b/examples/howto/client-buffers.py
@@ -0,0 +1,187 @@
+# #!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# This code of this example should be considered public domain.
+
+""" 
+This is the boids demo coded using a client buffer. This means that
+the data is *not* stored on the GPU in a vertex buffer object, but
+instead send to the GPU on each draw.
+
+Note that in general you should avoid client buffers and use
+vertex buffers. This is example just demonstrates the technique.
+
+In this particular example the attribute data is updated on each draw,
+so the performance of both methods should be more or less similar.
+
+The main difference is that one should use the ``client=True`` keyword
+argument when creating a VertexBuffer or ElementBuffer.
+
+"""
+
+import time
+import numpy as np
+from scipy.spatial import cKDTree
+
+from vispy.gloo import gl
+from vispy import app
+from vispy.gloo import Program, VertexBuffer, ElementBuffer
+
+
+# Create boids
+n = 1000
+particles = np.zeros(2+n, [ ('position',   'f4', 3),
+                            ('position_1', 'f4', 3),
+                            ('position_2', 'f4', 3),
+                            ('velocity',   'f4', 3),
+                            ('color',      'f4', 4),
+                            ('size',       'f4', 1)] )
+boids    = particles[2:]
+target   = particles[0]
+predator = particles[1]
+
+boids['position'] = np.random.uniform(-0.25, +0.25, (n,3))
+boids['velocity'] = np.random.uniform(-0.00, +0.00, (n,3))
+boids['size'] = 4
+boids['color'] = 1,1,1,1
+
+target['size'] = 16
+target['color'][:] = 1,1,0,1
+predator['size'] = 16
+predator['color'][:] = 1,0,0,1
+
+
+VERT_SHADER = """
+attribute vec3 position;
+attribute vec4 color;
+attribute float size;
+
+varying vec4 v_color;
+void main (void) {
+    gl_Position = vec4(position, 1.0);
+    v_color = color;
+    gl_PointSize = size;
+}
+"""
+
+FRAG_SHADER = """
+varying vec4 v_color;
+void main()
+{    
+    float x = 2.0*gl_PointCoord.x - 1.0;
+    float y = 2.0*gl_PointCoord.y - 1.0;
+    float a = 1.0 - (x*x + y*y);
+    gl_FragColor = vec4(v_color.rgb, a*v_color.a);
+}
+
+"""
+
+
+class Canvas(app.Canvas):
+    
+    def __init__(self):
+        app.Canvas.__init__(self)
+        
+        # Time
+        self._t = time.time()
+        self._pos = 0.0, 0.0
+        self._button = None
+        
+        # Create program
+        self.program = Program(VERT_SHADER, FRAG_SHADER)
+        self.program['color'] = VertexBuffer(particles['color'], client=True)
+        self.program['size'] = VertexBuffer(particles['size'], client=True)
+
+    def on_initialize(self, event):
+        gl.glClearColor(0,0,0,1);
+        
+        gl.glEnable(gl.GL_BLEND)
+        gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE)
+    
+    def on_resize(self, event):
+        width, height = event.size
+        gl.glViewport(0, 0, width, height)
+        
+    def on_mouse_press(self, event):
+        self._button = event.button
+        self.on_mouse_move(event)
+    
+    def on_mouse_release(self, event):
+        self._button = None
+        self.on_mouse_move(event)
+    
+    def on_mouse_move(self, event):
+        if not self._button:
+            return
+        w, h = self.size
+        x, y = event.pos
+        sx = 2*x/float(w) -1.0
+        sy = - (2*y/float(h) -1.0)
+        
+        if self._button == 1:
+            target['position'][:] = sx, sy, 0
+        elif self._button  == 2:
+            predator['position'][:] = sx, sy, 0
+
+    
+    def on_paint(self, event):
+        gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
+        
+        # Draw
+        self.program['position'] = VertexBuffer(particles['position'], client=True)
+        self.program.draw(gl.GL_POINTS)
+        
+        # Next iteration
+        self._t = self.iteration(time.time() - self._t)
+
+        # Invoke a new draw
+        self.update()
+    
+    
+    def iteration(self, dt):
+        t = self._t 
+        
+        t += 0.5*dt
+        #target[...] = np.array([np.sin(t),np.sin(2*t),np.cos(3*t)])*.1
+    
+        t += 0.5*dt
+        #predator[...] = np.array([np.sin(t),np.sin(2*t),np.cos(3*t)])*.2
+    
+        boids['position_2'] = boids['position_1']
+        boids['position_1'] = boids['position']
+        n = len(boids)
+        P = boids['position']
+        V = boids['velocity']
+    
+        # Cohesion: steer to move toward the average position of local flockmates
+        C = -(P - P.sum(axis=0)/n)
+    
+        # Alignment: steer towards the average heading of local flockmates
+        A = -(V - V.sum(axis=0)/n)
+    
+        # Repulsion: steer to avoid crowding local flockmates
+        D,I = cKDTree(P).query(P,5)
+        M = np.repeat(D < 0.05, 3, axis=1).reshape(n,5,3)
+        Z = np.repeat(P,5,axis=0).reshape(n,5,3)
+        R = -((P[I]-Z)*M).sum(axis=1)
+    
+        # Target : Follow target
+        T = target['position'] - P
+    
+        # Predator : Move away from predator
+        dP = P - predator['position']
+        D = np.maximum(0, 0.3 - np.sqrt(dP[:,0]**2 +dP[:,1]**2+dP[:,2]**2) )
+        D = np.repeat(D,3,axis=0).reshape(n,3)
+        dP *= D
+    
+        #boids['velocity'] += 0.0005*C + 0.01*A + 0.01*R + 0.0005*T + 0.0025*dP
+        boids['velocity'] += 0.0005*C + 0.01*A + 0.01*R + 0.0005*T + 0.025*dP
+        boids['position'] += boids['velocity']
+
+        return t
+
+
+if __name__ == '__main__':
+    c = Canvas()
+    c.show()
+    app.run()
+    
diff --git a/examples/howto/display-lines.py b/examples/howto/display-lines.py
new file mode 100644
index 0000000..93fc28a
--- /dev/null
+++ b/examples/howto/display-lines.py
@@ -0,0 +1,138 @@
+# #!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# vispy: gallery 2
+
+""" Show a bunch of lines. 
+This example demonstrates how multiple line-pieces can be drawn
+using one call, by discarting some fragments.
+"""
+
+import numpy as np
+from vispy import gloo
+from vispy import app
+from vispy.gloo import gl
+from vispy.util.transforms import perspective, translate, rotate
+
+#app.use('glut')
+
+# Create vetices 
+n = 100
+a_position = np.random.uniform(-1,1,(n,3)).astype(np.float32)
+a_id = np.random.randint(0,30,(n,1))
+a_id = np.sort(a_id,axis=0).astype(np.float32)
+
+
+VERT_SHADER = """
+uniform mat4 u_model;
+uniform mat4 u_view;
+uniform mat4 u_projection;
+attribute vec3 a_position;
+attribute float a_id;
+varying float v_id;
+void main (void) {
+    v_id = a_id;
+    gl_Position = u_projection * u_view * u_model * vec4(a_position,1.0);
+}
+"""
+
+FRAG_SHADER = """
+varying float v_id;
+void main()
+{
+    float f = fract(v_id);
+    // The second useless test is needed on OSX 10.8 (fuck)
+    if( (f > 0.0001) && (f < .9999f) )
+        discard;
+    else
+        gl_FragColor = vec4(0,0,0,1);
+}
+"""
+
+
+
+class Canvas(app.Canvas):
+
+    # ---------------------------------
+    def __init__(self):
+        app.Canvas.__init__(self)
+
+        self.program = gloo.Program(VERT_SHADER, FRAG_SHADER)
+        
+        # Set uniform and attribute
+        self.program['a_id'] = gloo.VertexBuffer(a_id)
+        self.program['a_position'] = gloo.VertexBuffer(a_position)
+
+        self.view       = np.eye(4,dtype=np.float32)
+        self.model      = np.eye(4,dtype=np.float32)
+        self.projection = np.eye(4,dtype=np.float32)
+
+        self.translate = 5
+        translate(self.view, 0,0, -self.translate)
+        self.program['u_model'] = self.model
+        self.program['u_view'] = self.view
+
+        self.theta = 0
+        self.phi = 0
+
+        self.timer = app.Timer(1.0/60)
+        self.timer.connect(self.on_timer)
+        #self.timer.start()
+
+
+
+    # ---------------------------------
+    def on_initialize(self, event):
+        gl.glClearColor(1,1,1,1)
+        gl.glEnable(gl.GL_DEPTH_TEST)
+        gl.glEnable(gl.GL_BLEND)
+        gl.glBlendFunc (gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA)
+
+
+    # ---------------------------------
+    def on_key_press(self,event):
+        if event.text == ' ':
+            if self.timer.running:
+                self.timer.stop()
+            else:
+                self.timer.start()
+
+
+    # ---------------------------------
+    def on_timer(self,event):
+        self.theta += .5
+        self.phi += .5
+        self.model = np.eye(4, dtype=np.float32)
+        rotate(self.model, self.theta, 0,0,1)
+        rotate(self.model, self.phi,   0,1,0)
+        self.program['u_model'] = self.model
+        self.update()
+
+
+    # ---------------------------------
+    def on_resize(self, event):
+        width, height = event.size
+        gl.glViewport(0, 0, width, height)
+        self.projection = perspective( 45.0, width/float(height), 1.0, 1000.0 )
+        self.program['u_projection'] = self.projection
+
+
+    # ---------------------------------
+    def on_mouse_wheel(self, event):
+        self.translate +=event.delta[1]
+        self.translate = max(2,self.translate)
+        self.view       = np.eye(4,dtype=np.float32)
+        translate(self.view, 0,0, -self.translate)
+        self.program['u_view'] = self.view
+        self.update()
+
+
+    # ---------------------------------
+    def on_paint(self, event):
+        gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
+        self.program.draw(gl.GL_LINE_STRIP)
+
+
+if __name__ == '__main__':
+    c = Canvas()
+    c.show()
+    app.run()
diff --git a/examples/howto/display-points.py b/examples/howto/display-points.py
new file mode 100644
index 0000000..13ecfe7
--- /dev/null
+++ b/examples/howto/display-points.py
@@ -0,0 +1,104 @@
+# #!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# vispy: gallery 2
+
+""" Simple example plotting 2D points.
+"""
+
+from vispy import gloo
+from vispy import app
+from vispy.gloo import gl
+import numpy as np
+
+# Create vetices 
+n = 10000
+v_position = 0.25 * np.random.randn(n, 2).astype(np.float32)
+v_color = np.random.uniform(0,1,(n,3)).astype(np.float32)
+v_size  = np.random.uniform(2,12,(n,1)).astype(np.float32)
+
+VERT_SHADER = """
+attribute vec3  a_position;
+attribute vec3  a_color;
+attribute float a_size;
+
+varying vec4 v_fg_color;
+varying vec4 v_bg_color;
+varying float v_radius;
+varying float v_linewidth;
+varying float v_antialias;
+
+void main (void) {
+    v_radius = a_size;
+    v_linewidth = 1.0;
+    v_antialias = 1.0;
+    v_fg_color  = vec4(0.0,0.0,0.0,0.5);
+    v_bg_color  = vec4(a_color,    1.0);
+
+    gl_Position = vec4(a_position, 1.0);
+    gl_PointSize = 2.0*(v_radius + v_linewidth + 1.5*v_antialias);
+}
+"""
+
+FRAG_SHADER = """
+varying vec4 v_fg_color;
+varying vec4 v_bg_color;
+varying float v_radius;
+varying float v_linewidth;
+varying float v_antialias;
+void main()
+{    
+    float size = 2*(v_radius + v_linewidth + 1.5*v_antialias);
+    float t = v_linewidth/2.0-v_antialias;
+    float r = length((gl_PointCoord.xy - vec2(0.5,0.5))*size);
+    float d = abs(r - v_radius) - t;
+    if( d < 0.0 )
+        gl_FragColor = v_fg_color;
+    else
+    {
+        float alpha = d/v_antialias;
+        alpha = exp(-alpha*alpha);
+        if (r > v_radius)
+            gl_FragColor = vec4(v_fg_color.rgb, alpha*v_fg_color.a);
+        else
+            gl_FragColor = mix(v_bg_color, v_fg_color, alpha);
+    }
+}
+"""
+
+
+
+class Canvas(app.Canvas):
+    def __init__(self):
+        app.Canvas.__init__(self)
+
+        self.program = gloo.Program(VERT_SHADER, FRAG_SHADER)
+        
+        # Set uniform and attribute
+        self.program['a_color']    = gloo.VertexBuffer(v_color)
+        self.program['a_position'] = gloo.VertexBuffer(v_position)
+        self.program['a_size']     = gloo.VertexBuffer(v_size)
+    
+    
+    def on_initialize(self, event):
+        gl.glClearColor(1,1,1,1)
+        
+        gl.glEnable(gl.GL_BLEND)
+        gl.glBlendFunc (gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA)
+        
+    
+    def on_resize(self, event):
+        width, height = event.size
+        gl.glViewport(0, 0, width, height)
+    
+    
+    def on_paint(self, event):
+        # Clear
+        gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
+        # Draw
+        self.program.draw(gl.GL_POINTS)
+
+
+if __name__ == '__main__':
+    c = Canvas()
+    c.show()
+    app.run()
diff --git a/examples/howto/display-shape.py b/examples/howto/display-shape.py
new file mode 100644
index 0000000..e3c0060
--- /dev/null
+++ b/examples/howto/display-shape.py
@@ -0,0 +1,66 @@
+# #!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# vispy: gallery 2
+
+""" 
+Simple example demonstrating showing a quad.
+gloo objects that this example demonstrates: Program.
+"""
+
+from vispy import gloo
+from vispy import app
+from vispy.gloo import gl
+import numpy as np
+
+# Create vetices 
+vPosition = np.array([  [-0.8, -0.8, 0.0],  [+0.7, -0.7, 0.0],  
+                        [-0.7, +0.7, 0.0],  [+0.8, +0.8, 0.0,] ], np.float32)
+
+
+VERT_SHADER = """ // simple vertex shader
+attribute vec3 a_position;
+void main (void) {
+    gl_Position = vec4(a_position, 1.0);
+}
+"""
+
+FRAG_SHADER = """ // simple fragment shader
+uniform vec4 u_color;
+void main()
+{    
+    gl_FragColor = u_color;
+}
+"""
+
+
+class Canvas(app.Canvas):
+    
+    def __init__(self):
+        app.Canvas.__init__(self)
+        
+        # Create program
+        self._program = gloo.Program(VERT_SHADER, FRAG_SHADER)
+        
+        # Set uniform and attribute
+        self._program['u_color'] = 0.2, 1.0, 0.4, 1
+        self._program['a_position'] = gloo.VertexBuffer(vPosition)
+    
+    
+    def on_initialize(self, event):
+        gl.glClearColor(1,1,1,1)
+    
+    
+    def on_resize(self, event):
+        width, height = event.size
+        gl.glViewport(0, 0, width, height)
+    
+    
+    def on_paint(self, event):
+        gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
+        self._program.draw(gl.GL_TRIANGLE_STRIP)
+    
+
+if __name__ == '__main__':
+    c = Canvas()
+    c.show()
+    app.run()
diff --git a/examples/howto/hello-fbo.py b/examples/howto/hello-fbo.py
new file mode 100644
index 0000000..557bdf5
--- /dev/null
+++ b/examples/howto/hello-fbo.py
@@ -0,0 +1,130 @@
+# #!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# vispy: gallery 3
+
+""" 
+Minimal example demonstrating the use of frame buffer objects (FBO).
+This example blurs the output image.
+"""
+
+
+from vispy import gloo
+from vispy import app
+from vispy.gloo import gl
+import numpy as np
+
+# Create vetices 
+vPosition = np.array([  [-0.8, -0.8, 0.0],  [+0.7, -0.7, 0.0],  
+                        [-0.7, +0.7, 0.0],  [+0.8, +0.8, 0.0,] ], np.float32)
+vPosition_full = np.array([  [-1.0, -1.0, 0.0],  [+1.0, -1.0, 0.0],  
+                             [-1.0, +1.0, 0.0],  [+1.0, +1.0, 0.0,] ], np.float32)
+vTexcoord = np.array([  [0.0, 0.0], [0.0, 1.0], 
+                        [1.0, 0.0], [1.0, 1.0] ], np.float32)
+
+# For initial quad
+VERT_SHADER1 = """
+attribute vec3 a_position;
+void main (void) {
+    gl_Position = vec4(a_position, 1.0);
+}
+"""
+
+FRAG_SHADER1 = """
+uniform vec4 u_color;
+void main()
+{    
+    gl_FragColor = u_color;
+}
+"""
+
+# To render the result of the FBO
+VERT_SHADER2 = """
+attribute vec3 a_position;
+attribute vec2 a_texcoord;
+varying vec2 v_texcoord;
+void main (void) {
+    // Pass tex coords
+    v_texcoord = a_texcoord;
+    // Calculate position
+    gl_Position = vec4(a_position.x, a_position.y, a_position.z, 1.0);
+}
+"""
+
+FRAG_SHADER2 = """
+uniform sampler2D u_texture1;
+varying vec2 v_texcoord;
+const float c_zero = 0.0;
+const int c_sze = 5;
+void main()
+{    
+    float scalefactor = 1.0 / (c_sze * c_sze * 4 + 1);
+    gl_FragColor = vec4(c_zero, c_zero, c_zero, 1.0);
+    for (int y=-c_sze; y<=c_sze; y++) {
+        for (int x=-c_sze; x<=c_sze; x++) { 
+            vec2 step = vec2(x,y) * 0.01;
+            vec3 color = texture2D(u_texture1, v_texcoord.st+step).rgb;
+            gl_FragColor.rgb += color * scalefactor;
+        }
+    }
+}
+"""
+
+
+SIZE = 50
+
+class Canvas(app.Canvas):
+    
+    def __init__(self):
+        app.Canvas.__init__(self)
+        self.size = 560, 420
+        
+        # Create texture to render to
+        self._rendertex = gloo.Texture2D()
+        
+        # Create FBO, attach the color buffer and depth buffer
+        self._fbo = gloo.FrameBuffer(self._rendertex, gloo.RenderBuffer())
+        
+        # Create program to render a shape
+        self._program1 = gloo.Program(  gloo.VertexShader(VERT_SHADER1), 
+                                        gloo.FragmentShader(FRAG_SHADER1) )
+        self._program1['u_color'] = 0.9, 1.0, 0.4, 1
+        self._program1['a_position'] = gloo.VertexBuffer(vPosition)
+        
+        # Create program to render FBO result
+        self._program2 = gloo.Program(  gloo.VertexShader(VERT_SHADER2), 
+                                        gloo.FragmentShader(FRAG_SHADER2) )
+        self._program2['a_position'] = gloo.VertexBuffer(vPosition)
+        self._program2['a_texcoord'] =  gloo.VertexBuffer(vTexcoord)
+        self._program2['u_texture1'] = self._rendertex
+    
+    
+    def on_resize(self, event):
+        width, height = event.size
+        gl.glViewport(0, 0, width, height)
+    
+    
+    def on_paint(self, event):
+        
+        # Set geometry (is no-op if the size does not change)
+        self._fbo.set_size(*self.size)
+        
+        # Draw the same scene as as in hello_quad.py, but draw it to the FBO
+        with self._fbo:
+            # Init
+            gl.glClearColor(0,0.0,0.5,1);
+            gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
+            # Draw
+            self._program1.draw(gl.GL_TRIANGLE_STRIP)
+        
+        # Now draw result to a full-screen quad
+        # Init
+        gl.glClearColor(1,1,1,1);
+        gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
+        # Draw
+        self._program2.draw(gl.GL_TRIANGLE_STRIP)
+    
+
+if __name__ == '__main__':
+    c = Canvas()
+    c.show()
+    app.run()
diff --git a/examples/howto/rotate-cube.py b/examples/howto/rotate-cube.py
new file mode 100644
index 0000000..92c51cd
--- /dev/null
+++ b/examples/howto/rotate-cube.py
@@ -0,0 +1,173 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# vispy: gallery 50
+"""
+This example shows how to display 3D objects.
+You should see a colored outlined spinning cube.
+"""
+
+import numpy as np
+from vispy import app, gloo
+from vispy.gloo import gl
+from vispy.util.transforms import perspective, translate, rotate
+
+
+vert = """
+// Uniforms
+// ------------------------------------
+uniform   mat4 u_model;
+uniform   mat4 u_view;
+uniform   mat4 u_projection;
+uniform   vec4 u_color;
+
+// Attributes
+// ------------------------------------
+attribute vec3 a_position;
+attribute vec4 a_color;
+
+// Varying
+// ------------------------------------
+varying vec4 v_color;
+
+void main()
+{
+    v_color = a_color * u_color;
+    gl_Position = u_projection * u_view * u_model * vec4(a_position,1.0);
+}
+"""
+
+
+frag = """
+// Varying
+// ------------------------------------
+varying vec4 v_color;
+
+void main()
+{
+    gl_FragColor = v_color;
+}
+"""
+
+
+# -----------------------------------------------------------------------------
+def cube():
+    """
+    Build vertices for a colored cube.
+
+    V  is the vertices
+    I1 is the indices for a filled cube (use with GL_TRIANGLES)
+    I2 is the indices for an outline cube (use with GL_LINES)
+    """
+    vtype = [('a_position', np.float32, 3),
+             ('a_normal'  , np.float32, 3),
+             ('a_color',    np.float32, 4)] 
+    # Vertices positions
+    v = [ [ 1, 1, 1],  [-1, 1, 1],  [-1,-1, 1], [ 1,-1, 1],
+          [ 1,-1,-1],  [ 1, 1,-1],  [-1, 1,-1], [-1,-1,-1] ]
+    # Face Normals
+    n = [ [ 0, 0, 1],  [ 1, 0, 0],  [ 0, 1, 0] ,
+          [-1, 0, 1],  [ 0,-1, 0],  [ 0, 0,-1] ]
+    # Vertice colors
+    c = [ [0, 1, 1, 1], [0, 0, 1, 1], [0, 0, 0, 1], [0, 1, 0, 1],
+          [1, 1, 0, 1], [1, 1, 1, 1], [1, 0, 1, 1], [1, 0, 0, 1] ];
+
+    V =  np.array([(v[0],n[0],c[0]), (v[1],n[0],c[1]), (v[2],n[0],c[2]), (v[3],n[0],c[3]),
+                   (v[0],n[1],c[0]), (v[3],n[1],c[3]), (v[4],n[1],c[4]), (v[5],n[1],c[5]),
+                   (v[0],n[2],c[0]), (v[5],n[2],c[5]), (v[6],n[2],c[6]), (v[1],n[2],c[1]),
+                   (v[1],n[3],c[1]), (v[6],n[3],c[6]), (v[7],n[3],c[7]), (v[2],n[3],c[2]),
+                   (v[7],n[4],c[7]), (v[4],n[4],c[4]), (v[3],n[4],c[3]), (v[2],n[4],c[2]),
+                   (v[4],n[5],c[4]), (v[7],n[5],c[7]), (v[6],n[5],c[6]), (v[5],n[5],c[5]) ],
+                  dtype = vtype)
+    I1 = np.resize( np.array([0,1,2,0,2,3], dtype=np.uint32), 6*(2*3))
+    I1 += np.repeat( 4*np.arange(2*3), 6)
+
+    I2 = np.resize( np.array([0,1,1,2,2,3,3,0], dtype=np.uint32), 6*(2*4))
+    I2 += np.repeat( 4*np.arange(6), 8)
+
+    return V, I1, I2
+
+
+
+# -----------------------------------------------------------------------------
+class Canvas(app.Canvas):
+    
+    def __init__(self):
+        app.Canvas.__init__(self)
+        self.size = 800, 600
+        
+        self.vertices, self.filled, self.outline = cube()
+        self.filled_buf = gloo.ElementBuffer(self.filled)
+        self.outline_buf = gloo.ElementBuffer(self.outline)
+        
+        self.program = gloo.Program(vert, frag)
+        self.program.set_vars(gloo.VertexBuffer(self.vertices))
+
+        self.view       = np.eye(4,dtype=np.float32)
+        self.model      = np.eye(4,dtype=np.float32)
+        self.projection = np.eye(4,dtype=np.float32)
+
+        translate(self.view, 0,0,-5)
+        self.program['u_model'] = self.model
+        self.program['u_view'] = self.view
+
+        self.theta = 0
+        self.phi = 0
+
+        self._timer = app.Timer(1.0/60)
+        self._timer.connect(self.on_timer)
+        self._timer.start()
+
+    # ---------------------------------
+    def on_initialize(self, event):
+        gl.glClearColor(1,1,1,1)
+        gl.glEnable(gl.GL_DEPTH_TEST)
+        gl.glPolygonOffset( 1, 1 )
+        # gl.glEnable( gl.GL_LINE_SMOOTH )
+
+
+    # ---------------------------------
+    def on_timer(self,event):
+        self.theta += .5
+        self.phi += .5
+        self.model = np.eye(4, dtype=np.float32)
+        rotate(self.model, self.theta, 0,0,1)
+        rotate(self.model, self.phi,   0,1,0)
+        self.program['u_model'] = self.model
+        self.update()
+
+
+    # ---------------------------------
+    def on_resize(self, event):
+        width, height = event.size
+        gl.glViewport(0, 0, width, height)
+        self.projection = perspective( 45.0, width/float(height), 2.0, 10.0 )
+        self.program['u_projection'] = self.projection
+
+    # ---------------------------------
+    def on_paint(self, event):
+        gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
+
+        
+        with self.program as prog:
+            # Filled cube
+            gl.glDisable( gl.GL_BLEND )
+            gl.glEnable( gl.GL_DEPTH_TEST )
+            gl.glEnable( gl.GL_POLYGON_OFFSET_FILL )
+            prog['u_color'] = 1,1,1,1
+            prog.draw(gl.GL_TRIANGLES, self.filled_buf)
+
+            # Outline
+            gl.glDisable( gl.GL_POLYGON_OFFSET_FILL )
+            gl.glEnable( gl.GL_BLEND )
+            gl.glDepthMask( gl.GL_FALSE )
+            prog['u_color'] = 0,0,0,1
+            prog.draw(gl.GL_LINES, self.outline_buf)
+            gl.glDepthMask( gl.GL_TRUE )        
+
+
+    
+# -----------------------------------------------------------------------------
+if __name__ == '__main__':
+    c = Canvas()
+    c.show()
+    app.run()
diff --git a/examples/howto/split-screen.py b/examples/howto/split-screen.py
new file mode 100644
index 0000000..d3ef163
--- /dev/null
+++ b/examples/howto/split-screen.py
@@ -0,0 +1,181 @@
+# #!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# vispy: gallery 2
+
+""" Example demonstrating two viewports in one window, and how one can
+propagate events to the viewports. Note that stuff like this will be
+part of the future higher lever visuals layer.
+"""
+
+import numpy as np
+from vispy import app, gloo
+from vispy.gloo import gl
+from vispy.util.event import EmitterGroup
+
+
+VERT_SHADER = """
+attribute vec3 a_position;
+uniform float u_size;
+uniform vec4 u_color;
+
+varying vec4 v_color;
+void main (void) {
+    gl_Position = vec4(a_position, 1.0);
+    v_color = u_color;
+    gl_PointSize = u_size;
+}
+"""
+
+FRAG_SHADER = """
+varying vec4 v_color;
+void main()
+{    
+    float x = 2.0*gl_PointCoord.x - 1.0;
+    float y = 2.0*gl_PointCoord.y - 1.0;
+    float a = 1.0 - (x*x + y*y);
+    gl_FragColor = vec4(v_color.rgb, a*v_color.a);
+}
+
+"""
+
+
+class ViewPort(object):
+    """ Represents a rectangular region on the screen.
+    Resize and mouse events are propagated to here, as well 
+    as the paint event.
+    """
+    def __init__(self, bgcolor):
+        self._bgcolor = bgcolor
+        self.events = EmitterGroup(source=self,
+                        resize=app.canvas.ResizeEvent,
+                        mouse_press=app.canvas.MouseEvent,
+                        mouse_release=app.canvas.MouseEvent,
+                        mouse_move=app.canvas.MouseEvent, 
+                        mouse_wheel=app.canvas.MouseEvent,
+                        )
+        
+        # Create program
+        self.program = gloo.Program(VERT_SHADER, FRAG_SHADER)
+        self.program['u_size'] = 20.0
+        self.program['u_color'] = bgcolor
+        
+        # Create position
+        self.vbo = gloo.VertexBuffer(('', 'float32', 3))
+        self.program['a_position'] = self.vbo
+        
+        # Init
+        self._pos = 25, 25
+        self._size = 1, 1
+    
+    
+    def on_mouse_move(self, event):
+        self._pos = event.pos
+    
+    
+    def on_mouse_press(self, event):
+        self.program['u_size'] = 30.0
+    
+    
+    def on_mouse_release(self, event):
+        self.program['u_size'] = 20.0
+    
+    
+    def on_resize(self, event):
+        self._size = event.size
+    
+    
+    def on_paint(self):
+        x = 2.0 * self._pos[0] / self._size[0] - 1.0
+        y = 2.0 * self._pos[1] / self._size[1] - 1.0
+        data = np.array([[x,-y,0]], np.float32)
+        self.vbo.set_data(data)
+        self.program.draw(gl.GL_POINTS)
+
+
+
+class Canvas(app.Canvas):
+    
+    def __init__(self):
+        app.Canvas.__init__(self)
+        self.size = 400, 200
+        self.left = ViewPort( (1.0, 0.5, 0.5, 1.0) )
+        self.right = ViewPort( (0.5, 1.0, 0.5, 1.0) )
+    
+    
+    def on_initialize(self, event):
+        gl.glClearColor(0,0,0,1)
+        gl.glEnable(gl.GL_BLEND)
+        gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE)
+    
+    
+    def on_paint(self, event):
+        # Paint events are "manually" propagated to the viewport instances,
+        # because we first want to set the glViewport for each one.
+        
+        # Prepare
+        gl.glClear(gl.GL_COLOR_BUFFER_BIT)
+        w1 = self.size[0] // 2
+        w2 = self.size[0] - w1
+        # Left
+        gl.glViewport(0, 0, w1, self.size[1])
+        self.left.on_paint()
+        # Right
+        gl.glViewport(w1, 0, w2, self.size[1])
+        self.right.on_paint()
+        
+        # Invoke new draw
+        self.update()
+    
+    
+    def on_resize(self, event):
+        w1 = event.size[0]//2
+        w2 = event.size[0] - w1
+        h = event.size[1]
+        self.left.events.resize( size=(w1,h) )
+        self.right.events.resize( size=(w2,h) )
+    
+    
+    def on_mouse_press(self, event):
+        viewport, pos = self._get_viewport(event.pos)
+        self._re_emit_mouse_event(viewport.events.mouse_press, pos, event)
+    
+    
+    def on_mouse_release(self, event):
+        viewport, pos = self._get_viewport(event.pos)
+        self._re_emit_mouse_event(viewport.events.mouse_release, pos, event)
+    
+    
+    def on_mouse_move(self, event):
+        viewport, pos = self._get_viewport(event.pos)
+        self._re_emit_mouse_event(viewport.events.mouse_move, pos, event)
+    
+    
+    def on_mouse_wheel(self, event):
+        viewport, pos = self._get_viewport(event.pos)
+        self._re_emit_mouse_event(viewport.events.mouse_wheel, pos, event)
+    
+    
+    def _get_viewport(self, pos):
+        """ Based on a given position, get the viewport and the pos
+        relative to that viewport.
+        """
+        halfwidth = self.size[0]//2
+        if pos[0] < halfwidth:
+            viewport = self.left
+            pos = pos
+        else:
+            viewport = self.right
+            pos = pos[0]-halfwidth, pos[1]
+        return viewport, pos
+    
+    
+    def _re_emit_mouse_event(self, emitter, pos, event):
+        emitter(pos=pos, button=event.button, modifiers=event.modifiers, 
+                                                        delta=event.delta )
+
+
+
+if __name__ == '__main__':
+    canvas = Canvas()
+    canvas.show()
+    app.run()
diff --git a/examples/howto/start.py b/examples/howto/start.py
new file mode 100644
index 0000000..fef1f06
--- /dev/null
+++ b/examples/howto/start.py
@@ -0,0 +1,17 @@
+# #!/usr/bin/env python
+# -*- coding: utf-8 -*-
+""" Probably the simplest vispy example
+"""
+
+from vispy import app
+from vispy.gloo import gl
+
+c = app.Canvas(show=True)
+
+ at c.connect
+def on_paint(event):
+    gl.glClearColor(0.2, 0.4, 0.6, 1.0)
+    gl.glClear(gl.GL_COLOR_BUFFER_BIT)
+
+if __name__ == '__main__':
+    app.run()
diff --git a/examples/rawgl/rawgl-cube.py b/examples/rawgl/rawgl-cube.py
new file mode 100644
index 0000000..a01f0cc
--- /dev/null
+++ b/examples/rawgl/rawgl-cube.py
@@ -0,0 +1,202 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+This example is equivalent to the spinning cube example,
+but does not use vispy.gloo, but the raw GL API.
+
+Show spinning cube using VBO's, transforms and textures. 
+The use of vertex and element buffer can be turned on or off.
+
+"""
+
+import numpy as np
+from vispy.util.transforms import perspective, translate, rotate
+from vispy import app, dataio
+from vispy.gloo import gl
+
+
+VERT_CODE = """
+uniform mat4 u_model;
+uniform mat4 u_view;
+uniform mat4 u_projection;
+
+attribute vec3 a_position;
+attribute vec2 a_texcoord;
+
+varying vec2 v_texcoord;
+
+void main()
+{
+    v_texcoord = a_texcoord;
+    gl_Position = u_projection * u_view * u_model * vec4(a_position,1.0);
+}
+"""
+
+
+FRAG_CODE = """
+uniform sampler2D u_texture;
+varying vec2 v_texcoord;
+void main()
+{
+    float ty = v_texcoord.y;
+    float tx = sin(ty*20.0)*0.05 + v_texcoord.x;
+    gl_FragColor = texture2D(u_texture, vec2(tx, ty));
+}
+"""
+
+
+# Read cube data (replace 'cube.obj' with 'teapot.obj'
+positions, faces, normals, texcoords = dataio.read_mesh('cube.obj')
+colors = np.random.uniform(0,1,positions.shape).astype('float32')
+
+
+class Canvas(app.Canvas):
+    def __init__(self):
+        app.Canvas.__init__(self)
+        self.size = 400, 400
+        self.init_transforms()
+        
+        self.timer = app.Timer(1.0/60)
+        self.timer.connect(self.update_transforms)
+        self.timer.start()
+    
+    
+    def on_initialize(self, event):
+        gl.glClearColor(1,1,1,1)
+        gl.glEnable(gl.GL_DEPTH_TEST)
+        
+        # Create shader program
+        self._prog_handle = gl.glCreateProgram()
+
+        # Create vertex shader
+        shader = gl.glCreateShader(gl.GL_VERTEX_SHADER)
+        gl.glShaderSource(shader, VERT_CODE)
+        gl.glCompileShader(shader)
+        status = gl.glGetShaderiv(shader, gl.GL_COMPILE_STATUS)
+        if not status:
+            # We could show more useful info here, but that takes a few lines
+            raise RuntimeError('Vertex shader did not compile.')
+        else:
+            gl.glAttachShader(self._prog_handle, shader)
+        
+        # Create fragment shader
+        shader = gl.glCreateShader(gl.GL_FRAGMENT_SHADER)
+        gl.glShaderSource(shader, FRAG_CODE)
+        gl.glCompileShader(shader)
+        status = gl.glGetShaderiv(shader, gl.GL_COMPILE_STATUS)
+        if not status:
+            # We could show more useful info here, but that takes a few lines
+            raise RuntimeError('Fragment shader did not compile.')
+        else:
+            gl.glAttachShader(self._prog_handle, shader)
+        
+        # Link
+        gl.glLinkProgram(self._prog_handle)
+        status = gl.glGetProgramiv(self._prog_handle, gl.GL_LINK_STATUS)
+        if not status:
+            # We could show more useful info here, but that takes a few lines
+            raise RuntimeError('Program did not link.')
+        
+        # Create texture
+        im = dataio.crate()
+        self._tex_handle = gl.glGenTextures(1)
+        gl.glPixelStorei(gl.GL_UNPACK_ALIGNMENT, 1)
+        gl.glBindTexture(gl.GL_TEXTURE_2D, self._tex_handle)
+        gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, gl.GL_RGB, 
+            im.shape[1], im.shape[0], 0, gl.GL_RGB, gl.GL_UNSIGNED_BYTE, im)
+        gl.glTexParameter(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MIN_FILTER, gl.GL_LINEAR)
+        gl.glTexParameter(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAG_FILTER, gl.GL_LINEAR)
+        
+        if use_buffers:
+            # Create vertex buffer
+            self._positions_handle = gl.glGenBuffers(1)
+            gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self._positions_handle)
+            gl.glBufferData(gl.GL_ARRAY_BUFFER, positions.nbytes, positions, gl.GL_DYNAMIC_DRAW)
+            #
+            self._texcoords_handle = gl.glGenBuffers(1)
+            gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self._texcoords_handle)
+            gl.glBufferData(gl.GL_ARRAY_BUFFER, texcoords.nbytes, texcoords, gl.GL_DYNAMIC_DRAW)
+            
+            # Create buffer for faces
+            self._faces_handle = gl.glGenBuffers(1)
+            gl.glBindBuffer(gl.GL_ELEMENT_ARRAY_BUFFER, self._faces_handle)
+            gl.glBufferData(gl.GL_ELEMENT_ARRAY_BUFFER, faces.nbytes, faces, gl.GL_DYNAMIC_DRAW)
+    
+    
+    def on_resize(self, event):
+        w, h = event.size
+        gl.glViewport(0, 0, w, h)
+        self.projection = perspective( 45.0, w/float(h), 2.0, 10.0 )
+    
+    
+    def on_paint(self, event):
+        
+        gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
+        
+        # Activate program  and texture
+        gl.glUseProgram(self._prog_handle)
+        gl.glBindTexture(gl.GL_TEXTURE_2D, self._tex_handle)
+        
+        # Set attributes (again, the loc can be cached)
+        loc = gl.glGetAttribLocation(self._prog_handle, 'a_position'.encode('utf-8'))
+        gl.glEnableVertexAttribArray(loc)
+        if use_buffers:
+            gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self._positions_handle)
+            gl.glVertexAttribPointer(loc, 3, gl.GL_FLOAT, False, 0, None)
+        else:
+            gl.glBindBuffer(gl.GL_ARRAY_BUFFER, 0)  # 0 means do not use buffer
+            gl.glVertexAttribPointer(loc, 3, gl.GL_FLOAT, False, 0, positions)
+        #
+        loc = gl.glGetAttribLocation(self._prog_handle, 'a_texcoord'.encode('utf-8'))
+        gl.glEnableVertexAttribArray(loc)
+        if use_buffers:
+            gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self._texcoords_handle)
+            gl.glVertexAttribPointer(loc, 2, gl.GL_FLOAT, False, 0, None)
+        else:
+            gl.glBindBuffer(gl.GL_ARRAY_BUFFER, 0)  # 0 means do not use buffer
+            gl.glVertexAttribPointer(loc, 2, gl.GL_FLOAT, False, 0, texcoords)
+        
+        # Set uniforms (note that you could cache the locations)
+        loc = gl.glGetUniformLocation(self._prog_handle, 'u_view'.encode('utf-8'))
+        gl.glUniformMatrix4fv(loc, 1, False, self.view)
+        loc = gl.glGetUniformLocation(self._prog_handle, 'u_model'.encode('utf-8'))
+        gl.glUniformMatrix4fv(loc, 1, False, self.model)
+        loc = gl.glGetUniformLocation(self._prog_handle, 'u_projection'.encode('utf-8'))
+        gl.glUniformMatrix4fv(loc, 1, False, self.projection)
+        
+        # Draw
+        if use_buffers:
+            gl.glBindBuffer(gl.GL_ELEMENT_ARRAY_BUFFER, self._faces_handle)
+            gl.glDrawElements(gl.GL_TRIANGLES, faces.size, gl.GL_UNSIGNED_INT, None)
+        else:
+            gl.glDrawElements(gl.GL_TRIANGLES, faces.size, gl.GL_UNSIGNED_INT, faces)
+    
+    
+    def init_transforms(self):
+        self.view       = np.eye(4,dtype=np.float32)
+        self.model      = np.eye(4,dtype=np.float32)
+        self.projection = np.eye(4,dtype=np.float32)
+        
+        self.theta = 0
+        self.phi = 0
+        
+        translate(self.view, 0,0,-5)
+    
+    
+    def update_transforms(self, event):
+        self.theta += .5
+        self.phi += .5
+        self.model = np.eye(4, dtype=np.float32)
+        rotate(self.model, self.theta, 0,0,1)
+        rotate(self.model, self.phi,   0,1,0)
+        
+        self.update()
+
+
+if __name__ == '__main__':
+    use_buffers = False
+    
+    c = Canvas()
+    c.show()
+    app.run()
+    
\ No newline at end of file
diff --git a/examples/rawgl/rawgl-fireworks.py b/examples/rawgl/rawgl-fireworks.py
new file mode 100644
index 0000000..ae25782
--- /dev/null
+++ b/examples/rawgl/rawgl-fireworks.py
@@ -0,0 +1,222 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+This example is equivalent to the fireworks example,
+but does not use vispy.gloo, but the raw GL API.
+
+Example demonstrating simulation of fireworks using point sprites.
+(adapted from the "OpenGL ES 2.0 Programming Guide")
+
+This example demonstrates a series of explosions that last one second. The
+visualization during the explosion is highly optimized using a Vertex Buffer
+Object (VBO). After each explosion, vertex data for the next explosion are
+calculated, such that each explostion is unique.
+"""
+
+import time
+import numpy as np
+import ctypes
+
+from vispy import app, dataio
+from vispy.gloo import gl
+
+
+# Create a texture
+radius = 32
+im1 = np.random.normal(0.8, 0.3, (radius*2+1, radius*2+1))
+
+# Mask it with a disk
+L = np.linspace(-radius, radius, 2 * radius + 1)
+(X, Y) = np.meshgrid(L, L)
+im1 *= np.array((X**2 + Y**2) <= radius * radius, dtype='float32')
+
+# Set number of particles, you should be able to scale this to 100000
+N = 10000
+
+# Create vertex data container
+vertex_data = np.zeros((N,), dtype=[('a_lifetime', np.float32, 1),
+                                    ('a_startPosition', np.float32, 3),
+                                    ('a_endPosition', np.float32, 3)])
+
+
+VERT_CODE = """
+uniform float u_time;
+uniform vec3 u_centerPosition;
+attribute float a_lifetime;
+attribute vec3 a_startPosition;
+attribute vec3 a_endPosition;
+varying float v_lifetime;
+
+void main () {
+    if (u_time <= a_lifetime)
+    {
+        gl_Position.xyz = a_startPosition + (u_time * a_endPosition);
+        gl_Position.xyz += u_centerPosition;
+        gl_Position.y -= 1.0 * u_time * u_time;
+        gl_Position.w = 1.0;
+    }
+    else
+        gl_Position = vec4(-1000, -1000, 0, 0);
+    
+    v_lifetime = 1.0 - (u_time / a_lifetime);
+    v_lifetime = clamp(v_lifetime, 0.0, 1.0);
+    gl_PointSize = (v_lifetime * v_lifetime) * 40.0;
+}
+"""
+
+FRAG_CODE = """
+uniform sampler2D s_texture;
+uniform vec4 u_color;
+varying float v_lifetime;
+
+void main()
+{    
+    vec4 texColor;
+    texColor = texture2D(s_texture, gl_PointCoord);
+    gl_FragColor = vec4(u_color) * texColor;
+    gl_FragColor.a *= v_lifetime;
+}
+"""
+
+
+class Canvas(app.Canvas):
+    def __init__(self):
+        app.Canvas.__init__(self)
+        self.size = 400, 400
+        self._starttime = time.time()
+        self._new_explosion()
+    
+    
+    def on_initialize(self, event):
+        gl.glClearColor(0,0,0,1);
+        
+        # Enable blending
+        gl.glEnable(gl.GL_BLEND)
+        gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE)
+        
+        # Create shader program
+        self._prog_handle = gl.glCreateProgram()
+
+        # Create vertex shader
+        shader = gl.glCreateShader(gl.GL_VERTEX_SHADER)
+        gl.glShaderSource_compat(shader, VERT_CODE)
+        gl.glCompileShader(shader)
+        status = gl.glGetShaderiv(shader, gl.GL_COMPILE_STATUS)
+        if not status:
+            # We could show more useful info here, but that takes a few lines
+            raise RuntimeError('Vertex shader did not compile.')
+        else:
+            gl.glAttachShader(self._prog_handle, shader)
+        
+        # Create fragment shader
+        shader = gl.glCreateShader(gl.GL_FRAGMENT_SHADER)
+        need_enabled = gl.glShaderSource_compat(shader, FRAG_CODE)
+        gl.glCompileShader(shader)
+        status = gl.glGetShaderiv(shader, gl.GL_COMPILE_STATUS)
+        if not status:
+            # We could show more useful info here, but that takes a few lines
+            raise RuntimeError('Fragment shader did not compile.')
+        else:
+            gl.glAttachShader(self._prog_handle, shader)
+        
+        # Enable point sprites
+        for enum in need_enabled:
+            gl.glEnable(enum)
+        
+        # Link
+        gl.glLinkProgram(self._prog_handle)
+        status = gl.glGetProgramiv(self._prog_handle, gl.GL_LINK_STATUS)
+        if not status:
+            # We could show more useful info here, but that takes a few lines
+            raise RuntimeError('Program did not link.')
+        
+        # Create texture
+        self._tex_handle = gl.glGenTextures(1)
+        gl.glPixelStorei(gl.GL_UNPACK_ALIGNMENT, 1)
+        gl.glBindTexture(gl.GL_TEXTURE_2D, self._tex_handle)
+        gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, gl.GL_LUMINANCE, 
+            im1.shape[1], im1.shape[0], 0, gl.GL_LUMINANCE, gl.GL_FLOAT,
+             im1.astype(np.float32))
+        gl.glTexParameter(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MIN_FILTER, gl.GL_LINEAR)
+        gl.glTexParameter(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAG_FILTER, gl.GL_LINEAR)
+        
+        # Create vertex buffer
+        self._vbo_handle = gl.glGenBuffers(1)
+    
+    
+    def on_paint(self, event):
+        
+        # Technically, we would only need to set u_time on every draw,
+        # because the program is enabled at the beginning and never disabled.
+        # In vispy, the program is re-enabled at each draw though and we
+        # want to keep the code similar.
+        
+        gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
+        
+        # Activate program  and texture
+        gl.glUseProgram(self._prog_handle)
+        gl.glBindTexture(gl.GL_TEXTURE_2D, self._tex_handle)
+        
+        # Update VBO
+        gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self._vbo_handle)
+        gl.glBufferData(gl.GL_ARRAY_BUFFER, vertex_data.nbytes, vertex_data, gl.GL_DYNAMIC_DRAW)
+        
+        # Set attributes (again, the loc can be cached)
+        loc = gl.glGetAttribLocation(self._prog_handle, 'a_lifetime'.encode('utf-8'))
+        gl.glEnableVertexAttribArray(loc)
+        gl.glVertexAttribPointer(loc, 1, gl.GL_FLOAT, False, 7*4, ctypes.c_voidp(0))
+        #
+        loc = gl.glGetAttribLocation(self._prog_handle, 'a_startPosition'.encode('utf-8'))
+        gl.glEnableVertexAttribArray(loc)
+        gl.glVertexAttribPointer(loc, 3, gl.GL_FLOAT, False, 7*4, ctypes.c_voidp(1*4))
+        #
+        loc = gl.glGetAttribLocation(self._prog_handle, 'a_endPosition'.encode('utf-8'))
+        gl.glEnableVertexAttribArray(loc)
+        gl.glVertexAttribPointer(loc, 3, gl.GL_FLOAT, False, 7*4, ctypes.c_voidp(4*4))
+        #
+        loc = gl.glGetUniformLocation(self._prog_handle, 'u_color'.encode('utf-8'))
+        gl.glUniform4f(loc, *self._color)
+        
+        # Set unforms
+        loc = gl.glGetUniformLocation(self._prog_handle, 'u_time'.encode('utf-8'))
+        gl.glUniform1f(loc, time.time()-self._starttime)
+        #
+        loc = gl.glGetUniformLocation(self._prog_handle, 'u_centerPosition'.encode('utf-8'))
+        gl.glUniform3f(loc, *self._centerpos)
+        
+        # Draw
+        gl.glDrawArrays(gl.GL_POINTS, 0, N)
+        
+        # New explosion?
+        if time.time() - self._starttime > 1.5:
+            self._new_explosion()
+            
+        # Redraw as fast as we can
+        self.update()
+    
+    
+    def _new_explosion(self):
+        # New centerpos
+        self._centerpos = np.random.uniform(-0.5, 0.5, (3,))
+        
+        # New color, scale alpha with N
+        alpha = 1.0 / N**0.08
+        color = np.random.uniform(0.1, 0.9, (3,))
+        self._color = tuple(color)+ (alpha,)
+        
+        # Create new vertex data
+        vertex_data['a_lifetime'] = np.random.normal(2.0, 0.5, (N,))
+        vertex_data['a_startPosition'] = np.random.normal(0.0, 0.2, (N,3))
+        vertex_data['a_endPosition'] = np.random.normal(0.0, 1.2, (N,3))
+        
+        # Set time to zero
+        self._starttime = time.time()
+        
+        
+
+
+if __name__ == '__main__':
+    c = Canvas()
+    c.show()
+    app.run()
+    
\ No newline at end of file
diff --git a/examples/spinning-cube2.py b/examples/spinning-cube2.py
new file mode 100644
index 0000000..db9cf56
--- /dev/null
+++ b/examples/spinning-cube2.py
@@ -0,0 +1,121 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# vispy: gallery 50
+"""
+Show spinning cube using VBO's, and transforms, and texturing.
+"""
+
+import numpy as np
+from vispy import app, gloo, dataio
+from vispy.util.transforms import perspective, translate, rotate
+from vispy.gloo import gl
+
+
+VERT_CODE = """
+uniform   mat4 u_model;
+uniform   mat4 u_view;
+uniform   mat4 u_projection;
+
+attribute vec3 a_position;
+attribute vec2 a_texcoord;
+
+varying vec2 v_texcoord;
+
+void main()
+{
+    v_texcoord = a_texcoord;
+    gl_Position = u_projection * u_view * u_model * vec4(a_position,1.0);
+    //gl_Position = vec4(a_position,1.0);
+    
+}
+"""
+
+
+FRAG_CODE = """
+uniform sampler2D u_texture;
+varying vec2 v_texcoord;
+
+void main()
+{   
+    float ty = v_texcoord.y;
+    float tx = sin(ty*50.0)*0.01 + v_texcoord.x;
+    gl_FragColor = texture2D(u_texture, vec2(tx, ty));
+}
+"""
+
+
+# Read cube data
+positions, faces, normals, texcoords = dataio.read_mesh('cube.obj')
+colors = np.random.uniform(0,1,positions.shape).astype('float32')
+
+faces_buffer = gloo.ElementBuffer(faces.astype(np.uint16))
+
+
+class Canvas(app.Canvas):
+    
+    def __init__(self, **kwargs):
+        app.Canvas.__init__(self, **kwargs)
+        self.geometry = 0, 0, 400, 400
+        
+        self.program = gloo.Program(VERT_CODE, FRAG_CODE)
+        
+        # Set attributes
+        self.program['a_position'] = gloo.VertexBuffer(positions)
+        self.program['a_texcoord'] = gloo.VertexBuffer(texcoords)
+        
+        self.program['u_texture'] = gloo.Texture2D(dataio.crate())
+        
+        # Handle transformations
+        self.init_transforms()
+        
+        self.timer = app.Timer(1.0/60)
+        self.timer.connect(self.update_transforms)
+        self.timer.start()
+        
+    
+    def on_initialize(self, event):
+        gl.glClearColor(1,1,1,1)
+        gl.glEnable(gl.GL_DEPTH_TEST)
+    
+    
+    def on_resize(self, event):
+        width, height = event.size
+        gl.glViewport(0, 0, width, height)
+        self.projection = perspective( 45.0, width/float(height), 2.0, 10.0 )
+        self.program['u_projection'] = self.projection
+    
+    
+    def on_paint(self, event):
+        
+        gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
+        
+        self.program.draw(gl.GL_TRIANGLES, faces_buffer)
+    
+    
+    def init_transforms(self):
+        self.view       = np.eye(4,dtype=np.float32)
+        self.model      = np.eye(4,dtype=np.float32)
+        self.projection = np.eye(4,dtype=np.float32)
+        
+        self.theta = 0
+        self.phi = 0
+        
+        translate(self.view, 0,0,-5)
+        self.program['u_model'] = self.model
+        self.program['u_view'] = self.view
+    
+    
+    def update_transforms(self,event):
+        self.theta += .5
+        self.phi += .5
+        self.model = np.eye(4, dtype=np.float32)
+        rotate(self.model, self.theta, 0,0,1)
+        rotate(self.model, self.phi,   0,1,0)
+        self.program['u_model'] = self.model
+        self.update()
+
+
+if __name__ == '__main__':
+    c = Canvas()
+    c.show()
+    app.run()
diff --git a/examples/texturing.py b/examples/texturing.py
new file mode 100644
index 0000000..408bc6c
--- /dev/null
+++ b/examples/texturing.py
@@ -0,0 +1,112 @@
+# #!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# vispy: gallery 2
+
+""" 
+Example demonstrating the use of textures in vispy.gloo.
+Three textures are created and combined in the fragment shader.
+"""
+
+from vispy.gloo import Program, Texture2D, VertexBuffer, ElementBuffer
+from vispy import app, dataio
+from vispy.gloo import gl
+
+
+import numpy as np
+
+# Texture 1
+im1 = dataio.crate()
+
+# Texture with bumbs (to muliply with im1)
+im2 = np.ones((20,20), 'float32')
+im2[::3,::3] = 0.5
+
+# Texture with a plus sign (to subtract from im1)
+im3 = np.zeros((30,30), 'float32')
+im3[10,:] = 1.0
+im3[:,10] = 1.0
+
+
+# Create vetices and texture coords in two separate arrays.
+# Note that combining both in one array (as in hello_quad2)
+# results in better performance.
+positions = np.array([  [-0.8, -0.8, 0.0], [+0.7, -0.7, 0.0],  
+                        [-0.7, +0.7, 0.0], [+0.8, +0.8, 0.0,] ], np.float32)
+texcoords = np.array([  [1.0, 1.0], [0.0, 1.0], 
+                        [1.0, 0.0], [0.0, 0.0]], np.float32)
+
+
+VERT_SHADER = """ // texture vertex shader
+
+attribute vec3 a_position;
+attribute vec2 a_texcoord;
+
+varying vec2 v_texcoord;
+
+void main (void) {
+    // Pass tex coords
+    v_texcoord = a_texcoord;
+    // Calculate position
+    gl_Position = vec4(a_position.x, a_position.y, a_position.z, 1.0);
+}
+"""
+
+FRAG_SHADER = """ // texture fragment shader
+uniform sampler2D u_texture1;
+uniform sampler2D u_texture2;
+uniform sampler2D u_texture3;
+
+varying vec2 v_texcoord;
+
+void main()
+{    
+    vec4 clr1 = texture2D(u_texture1, v_texcoord);
+    vec4 clr2 = texture2D(u_texture2, v_texcoord);
+    vec4 clr3 = texture2D(u_texture3, v_texcoord);
+    gl_FragColor.rgb = clr1.rgb * clr2.r - clr3.r;
+    gl_FragColor.a = 1.0;
+}
+"""
+
+
+
+class Canvas(app.Canvas):
+    
+    def __init__(self):
+        app.Canvas.__init__(self)
+        
+        # Create program
+        self._program = Program(VERT_SHADER, FRAG_SHADER)
+        
+        # Set uniforms and samplers
+        self._program['a_position'] = VertexBuffer(positions)
+        self._program['a_texcoord'] = VertexBuffer(texcoords)
+        #
+        self._program['u_texture1'] = Texture2D(im1)
+        self._program['u_texture2'] = Texture2D(im2)
+        self._program['u_texture3'] = Texture2D(im3)
+    
+    
+    def on_initialize(self, event):
+        gl.glClearColor(1,1,1,1)
+    
+    
+    def on_resize(self, event):
+        width, height = event.size
+        gl.glViewport(0, 0, width, height)
+    
+    
+    def on_paint(self, event):
+        
+        # Clear
+        gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
+        
+        # Draw shape with texture, nested context
+        self._program.draw(gl.GL_TRIANGLE_STRIP)
+        
+
+
+if __name__ == '__main__':
+    c = Canvas()
+    c.show()
+    app.run()
diff --git a/make/__init__.py b/make/__init__.py
new file mode 100644
index 0000000..8209ac1
--- /dev/null
+++ b/make/__init__.py
@@ -0,0 +1,6 @@
+"""
+By putting make.py in a package, we can do "python make" instead of
+"python make.py".
+"""
+from __future__ import print_function, division
+from .make import Maker
\ No newline at end of file
diff --git a/make/__main__.py b/make/__main__.py
new file mode 100644
index 0000000..9690a45
--- /dev/null
+++ b/make/__main__.py
@@ -0,0 +1,13 @@
+
+from __future__ import print_function, division
+
+import os
+import sys
+from make import Maker
+
+START_DIR = os.path.abspath(os.getcwd())
+
+try:
+    Maker(sys.argv)
+finally:
+    os.chdir(START_DIR)
\ No newline at end of file
diff --git a/make/make.py b/make/make.py
new file mode 100644
index 0000000..875c20e
--- /dev/null
+++ b/make/make.py
@@ -0,0 +1,406 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# Copyright (c) 2013, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for mo
+
+"""
+Convenience tools for vispy developers
+
+    make.py command [arg]
+
+"""
+
+from __future__ import print_function, division
+
+import sys
+import os
+import shutil
+import subprocess
+import re
+
+# Save where we came frome and where this module lives
+START_DIR = os.path.abspath(os.getcwd())
+THIS_DIR = os.path.abspath(os.path.dirname(__file__))
+
+# Get root directory of the package, by looking for setup.py
+for subdir in ['.', '..']:
+    ROOT_DIR = os.path.abspath(os.path.join(THIS_DIR, subdir))
+    if os.path.isfile(os.path.join(ROOT_DIR, 'setup.py')):
+        break
+else:
+    sys.exit('Cannot find root dir')
+
+
+# Define directories and repos of interest
+DOC_DIR = os.path.join(ROOT_DIR, 'doc')
+#
+WEBSITE_DIR = os.path.join(ROOT_DIR, '_website')
+WEBSITE_REPO = 'git at github.com:vispy/vispy-website'
+#
+PAGES_DIR =  os.path.join(ROOT_DIR, '_gh-pages')
+PAGES_REPO = 'git at github.com:vispy/vispy.github.com.git'
+#
+IMAGES_DIR = os.path.join(ROOT_DIR, '_images')
+IMAGES_REPO = 'git at github.com:vispy/images.git'
+
+
+class Maker:
+    """ Collection of make commands.
+    
+    To create a new command, create a method with a short name, give it
+    a docstring, and make it do something useful :)
+    
+    """
+    
+    def __init__(self, argv):
+        """ Parse command line arguments. """
+        # Get function to call
+        if len(argv) == 1:
+            func, arg = self.help, ''
+        else:
+            command = argv[1].strip()
+            arg = ' '.join(argv[2:]).strip()
+            func = getattr(self, command, None)
+        # Call it if we can
+        if func is not None:
+            func(arg)
+        else:
+            sys.exit('Invalid command: "%s"' % command)
+    
+    
+    def help(self, arg):
+        """ Show help message. Use 'help X' to get more help on command X. """
+        if arg:
+            command = arg
+            func = getattr(self, command, None)
+            if func is not None:
+                doc = getattr(self, command).__doc__.strip()
+                print('make.py %s [arg]\n\n        %s' % (command, doc))
+                print()
+            else:
+                sys.exit('Cannot show help on unknown command: "%s"' % command)
+            
+        else:
+            print(__doc__.strip()+ '\n\nCommands:\n')
+            for command in sorted(dir(self)):
+                if command.startswith('_'):
+                    continue
+                preamble = command.ljust(9)
+                #doc = getattr(self, command).__doc__.splitlines()[0].strip()
+                doc = getattr(self, command).__doc__.strip()
+                print(' %s  %s' % (preamble, doc))
+            print()
+    
+    
+    def doc(self, arg):
+        """ Make API documentation. Subcommands:
+            * html - build html
+            * show - show the docs in your browser
+        """
+        # Prepare
+        build_dir = os.path.join(DOC_DIR, '_build')
+        if not arg:
+            return self.help('doc')
+        # Go
+        if 'html' == arg:
+            sphinx_clean(build_dir)
+            sphinx_build(DOC_DIR, build_dir)
+        elif 'show' == arg:
+            sphinx_show(os.path.join(build_dir, 'html'))
+        else:
+            sys.exit('Command "doc" does not have subcommand "%s"' % arg)
+    
+    
+    def website(self, arg):
+        """ Build website. Website source is put in '_website'. Subcommands:
+            * html - build html
+            * show - show the website in your browser
+            * upload - upload (commit+push) the resulting html to github
+        """
+        # Prepare
+        build_dir = os.path.join(WEBSITE_DIR, '_build')
+        html_dir = os.path.join(build_dir, 'html')
+        if not arg:
+           return self.help('website')
+        
+        # Clone repo for website if needed, make up-to-date otherwise
+        if not os.path.isdir(WEBSITE_DIR):
+            os.chdir(ROOT_DIR)
+            sh("git clone %s %s" % (WEBSITE_REPO, WEBSITE_DIR))
+        else:
+            print('Updating website repo')
+            os.chdir(WEBSITE_DIR)
+            sh('git pull')
+        
+        # Go
+        if 'html' == arg:
+            sphinx_clean(build_dir)
+            sphinx_build(WEBSITE_DIR, build_dir)
+            sphinx_copy_pages(html_dir, PAGES_DIR, PAGES_REPO)
+        elif 'show' == arg:
+            sphinx_show(PAGES_DIR)
+        elif 'upload' == arg:
+            sphinx_upload(PAGES_DIR)
+            print()
+            print("Do not forget to also commit+push your changes to '_website'")
+        else:
+            sys.exit('Command "website" does not have subcommand "%s"' % arg)
+
+    
+    def test(self, arg):
+        """ Run all unit tests using nose. """
+        os.chdir(ROOT_DIR)
+        sys.argv[1:] = []
+        import nose
+        result = nose.run()
+    
+    
+    def images(self, arg):
+        """ Create images (screenshots). Subcommands:
+            * gallery - make screenshots for the gallery
+            * test - make screenshots for testing
+            * upload - upload the images repository
+        """
+        if not arg:
+           return self.help('images')
+        
+        # Clone repo for images if needed, make up-to-date otherwise
+        if not os.path.isdir(IMAGES_DIR):
+            os.chdir(ROOT_DIR)
+            sh("git clone %s %s" % (IMAGES_REPO, IMAGES_DIR))
+        else:
+            print('Updating images repo')
+            os.chdir(IMAGES_DIR)
+            sh('git pull')
+        
+        # Create subdirs if needed
+        for subdir in ['gallery', 'thumbs', 'test']:
+            subdir = os.path.join(IMAGES_DIR, subdir)
+            if not os.path.isdir(subdir):
+                os.mkdir(subdir)
+        
+        # Go
+        if arg == 'gallery':
+            self._images_screenshots()
+            self._images_thumbnails()
+        elif arg == 'test':
+            rsys.exit('images test command not yet implemented')
+        elif arg == 'upload':
+            sphinx_upload(IMAGES_DIR)
+        else:
+            sys.exit('Command "website" does not have subcommand "%s"' % arg)
+    
+    
+    def _images_screenshots(self):
+        # Prepare
+        import imp
+        from vispy.util.dataio import imsave, _screenshot
+        examples_dir = os.path.join(ROOT_DIR, 'examples')
+        gallery_dir = os.path.join(IMAGES_DIR, 'gallery')
+        
+        # Process all files ...
+        for filename, name in get_example_filenames(examples_dir):
+            name = name.replace('/', '__')  # We use flat names
+            imagefilename = os.path.join(gallery_dir, name+'.png')
+            
+            # Check if should make a screenshot
+            frames = []
+            lines = open(filename, 'rt').read().splitlines()
+            for line in lines[:10]:
+                if line.startswith('# vispy:') and 'gallery' in line:
+                    # Get what frames to grab
+                    frames = line.split('gallery')[1].strip()
+                    frames = frames or '0'
+                    frames = [int(i) for i in frames.split(':')]
+                    if not frames:
+                        frames = [0]
+                    if len(frames)>1:
+                        frames = list(range(*frames))
+                    break
+            else:
+                continue  # gallery hint not found
+            
+            # Check if we need to take a sceenshot
+            if os.path.isfile(imagefilename):
+                print('Screenshot for %s already present (skip).' % name)
+                continue
+            
+            # Import module and prepare
+            m = imp.load_source('vispy_example_'+name, filename)
+            m.done = False
+            m.frame = -1
+            m.images = []
+            
+            # Create a canvas and grab a screenshot
+            def grabscreenshot(event):
+                if m.done: return  # Grab only once
+                m.frame += 1
+                if m.frame in frames:
+                    frames.remove(m.frame)
+                    print('Grabbing a screenshot for %s' % name)
+                    im = _screenshot((0, 0, c.size[0], c.size[1]))
+                    m.images.append(im)
+                if not frames:
+                    m.done = True
+            c = m.Canvas()
+            c.events.paint.connect(grabscreenshot)
+            c.show()
+            while not m.done:
+                m.app.process_events()
+            c.close()
+            
+            # Save
+            imsave(imagefilename, m.images[0])  # Alwats show one image
+            if len(m.images) > 1:
+                import imageio  # multiple gif not properly supported yet
+                imageio.mimsave(imagefilename[:-3]+'.gif', m.images)
+    
+    
+    def _images_thumbnails(self):
+        from vispy.util.dataio import imsave, imread
+        from skimage.transform import resize
+        import numpy as np
+        gallery_dir = os.path.join(IMAGES_DIR, 'gallery')
+        thumbs_dir = os.path.join(IMAGES_DIR, 'thumbs')
+        for fname in os.listdir(gallery_dir):
+            filename1 = os.path.join(gallery_dir, fname)
+            filename2 = os.path.join(thumbs_dir, fname)
+            #
+            im = imread(filename1)
+            newx = 200
+            newy = int( newx * im.shape[0] / im.shape[1])
+            im = (resize(im, (newy, newx), 2)*255).astype(np.uint8)
+            imsave(filename2, im)
+            print('Created thumbnail %s' % fname)
+
+
+
+## Functions used by the maker
+
+if sys.version_info[0] < 3:
+    input = raw_input
+
+
+def sh(cmd):
+    """Execute command in a subshell, return status code."""
+    return subprocess.check_call(cmd, shell=True)
+
+
+def sh2(cmd):
+    """Execute command in a subshell, return stdout.
+    Stderr is unbuffered from the subshell."""
+    p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
+    out = p.communicate()[0]
+    retcode = p.returncode
+    if retcode:
+        raise subprocess.CalledProcessError(retcode, cmd)
+    else:
+        return out.rstrip().decode('utf-8', 'ignore')
+
+
+def sphinx_clean(build_dir):
+        if os.path.isdir(build_dir):
+            shutil.rmtree(build_dir)
+        os.mkdir(build_dir)
+        print('Cleared build directory.')
+
+    
+def sphinx_build(src_dir, build_dir):
+    import sphinx
+    sphinx.main((   'sphinx-build',  # Dummy 
+                    '-b', 'html', 
+                    '-d', os.path.join(build_dir, 'doctrees'),
+                    src_dir,  # Source
+                    os.path.join(build_dir, 'html'),  # Dest
+                ))
+    print("Build finished. The HTML pages are in %s/html." % build_dir)
+
+
+def sphinx_show(html_dir):
+    index_html = os.path.join(html_dir, 'index.html')
+    if not os.path.isfile(index_html):
+        sys.exit('Cannot show pages, build the html first.')
+    import webbrowser
+    webbrowser.open_new_tab(index_html)
+
+
+def sphinx_copy_pages(html_dir, pages_dir, pages_repo):
+    # Create the pages repo if needed
+    if not os.path.isdir(pages_dir):
+        os.chdir(ROOT_DIR)
+        sh("git clone %s %s" % (pages_repo, pages_dir))
+    # Ensure that its up to date
+    os.chdir(pages_dir)
+    sh('git checkout master -q')
+    sh('git pull -q')
+    # This is pretty unforgiving: we unconditionally nuke the destination
+    # directory, and then copy the html tree in there
+    tmp_git_dir = os.path.join(ROOT_DIR, pages_dir+'_git')
+    shutil.move(os.path.join(pages_dir, '.git'), tmp_git_dir)
+    try:
+        shutil.rmtree(pages_dir)
+        shutil.copytree(html_dir, pages_dir)
+        shutil.move(tmp_git_dir, os.path.join(pages_dir, '.git'))
+    finally:
+        if os.path.isdir(tmp_git_dir):
+            shutil.rmtree(tmp_git_dir)
+    # Copy individual files
+    for fname in ['CNAME', 'README.md', 'conf.py', '.nojekyll', 'Makefile']:
+        shutil.copyfile(os.path.join(WEBSITE_DIR, fname), 
+                        os.path.join(pages_dir, fname))
+    # Messages
+    os.chdir(pages_dir)
+    sh('git status')
+    print()
+    print("Website copied to _gh-pages. Above you can see its status:")
+    print("  Run 'make.py website show' to view.")
+    print("  Run 'make.py website upload' to commit and push.")
+
+
+def sphinx_upload(repo_dir):
+    # Check head
+    os.chdir(repo_dir)
+    status = sh2('git status | head -1')
+    branch = re.match('\# On branch (.*)$', status).group(1)
+    if branch != 'master':
+        e = 'On %r, git branch is %r, MUST be "master"' % (repo_dir,
+                                                            branch)
+        raise RuntimeError(e)
+    # Show repo and ask confirmation
+    print()
+    print('You are about to commit to:')
+    sh('git config --get remote.origin.url')
+    print()
+    print('Most recent 3 commits:')
+    sys.stdout.flush()
+    sh('git --no-pager log --oneline -n 3')
+    ok = input('Are you sure you want to commit and push? (y/[n]): ')
+    ok = ok or 'n'
+    # If ok, add, commit, push
+    if ok.lower() == 'y':
+        sh('git add .')
+        sh('git commit -am"Update (automated commit)"')
+        print()
+        sh('git push')
+
+
+def get_example_filenames(example_dir):
+    """ Yield (filename, name) elements for all examples. The examples
+    are organized in directories, therefore the name can contain a 
+    forward slash.
+    """
+    for (dirpath, dirnames, filenames) in os.walk(example_dir):
+        for fname in filenames:
+            if not fname.endswith('.py'): continue
+            filename = os.path.join(dirpath, fname)
+            name = filename[len(example_dir):].lstrip('/\\')[:-3]
+            name = name.replace('\\', '/')
+            yield filename, name
+
+
+if __name__ == '__main__':
+    try:
+        Maker(sys.argv)
+        # Maker(('bla', 'gallery'))
+    finally:
+        os.chdir(START_DIR)
diff --git a/nosetests.py b/nosetests.py
new file mode 100644
index 0000000..aabfe68
--- /dev/null
+++ b/nosetests.py
@@ -0,0 +1,6 @@
+""" Run tests using nose.
+For when nosetests is not on your PATH.
+"""
+
+import nose
+result = nose.run()
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..410d99e
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,105 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2013, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+""" Vispy setup script.
+
+Steps to do a new release:
+
+Preparations:
+  * Test on Windows, Linux, Mac
+  * Make release notes
+  * Update API documentation and other docs that need updating.
+
+Test installation:
+  * clear the build and dist dir (if they exist)
+  * python setup.py register -r http://testpypi.python.org/pypi
+  * python setup.py sdist upload -r http://testpypi.python.org/pypi 
+  * pip install -i http://testpypi.python.org/pypi
+
+Define the version:
+  * update __version__ in __init__.py
+  * Tag the tip changeset as version x.x
+
+Generate and upload package (preferably on Windows)
+  * python setup.py register
+  * python setup.py sdist upload
+  * python setup.py bdist_wininst upload
+
+Announcing:
+  * It can be worth waiting a day for eager users to report critical bugs
+  * Announce in scipy-user, vispy mailing list, G+
+  
+"""
+
+import os
+from distutils.core import setup
+
+name = 'vispy'
+description = 'Interactive visualization in Python'
+
+
+# Get version and docstring
+__version__ = None
+__doc__ = ''
+docStatus = 0 # Not started, in progress, done
+initFile = os.path.join(os.path.dirname(__file__), 'vispy', '__init__.py')
+for line in open(initFile).readlines():
+    if (line.startswith('__version__')):
+        exec(line.strip())
+    elif line.startswith('"""'):
+        if docStatus == 0:
+            docStatus = 1
+            line = line.lstrip('"')
+        elif docStatus == 1:
+            docStatus = 2
+    if docStatus == 1:
+        __doc__ += line
+
+
+setup(
+    name = name,
+    version = __version__,
+    author = 'Vispy contributers',
+    author_email = 'vispy at googlegroups.com',
+    license = '(new) BSD',
+    
+    url = 'http://vispy.org',
+    download_url = 'https://pypi.python.org/pypi/vispy',    
+    keywords = "visualization OpenGl ES medical imaging 3D plotting numpy bigdata",
+    description = description,
+    long_description = __doc__,
+    
+    platforms = 'any',
+    provides = ['vispy'],
+    install_requires = ['numpy', 'pyOpenGl'],
+    
+    packages = ['vispy',
+                'vispy.util', 
+                'vispy.util.dataio', 
+                'vispy.app',
+                'vispy.app.backends',
+                'vispy.gloo', 
+                'vispy.gloo.gl',
+               ],
+    package_dir = {'vispy': 'vispy'},
+    package_data = {'vispy': ['data/*']},
+    zip_safe = False,
+    
+    classifiers=[
+          'Development Status :: 3 - Alpha',
+          'Intended Audience :: Science/Research',
+          'Intended Audience :: Education',
+          'Intended Audience :: Developers',
+          'Topic :: Scientific/Engineering :: Visualization',
+          'License :: OSI Approved :: BSD License',
+          'Operating System :: MacOS :: MacOS X',
+          'Operating System :: Microsoft :: Windows',
+          'Operating System :: POSIX',
+          'Programming Language :: Python',
+          'Programming Language :: Python :: 2.6',
+          'Programming Language :: Python :: 2.7',
+          'Programming Language :: Python :: 3.2',
+          'Programming Language :: Python :: 3.3',
+          ],
+    )
diff --git a/stdeb.cfg b/stdeb.cfg
new file mode 100644
index 0000000..bde0e47
--- /dev/null
+++ b/stdeb.cfg
@@ -0,0 +1,7 @@
+[DEFAULT]
+Package: python-vispy
+XS-Python-Version: >= 2.6
+Maintainer: Jerome Kieffer <jerome.kieffer at esrf.fr>
+Build-Depends: python-opengl
+Recommends: python-numpy 
+
diff --git a/vispy/__init__.py b/vispy/__init__.py
new file mode 100644
index 0000000..9478f56
--- /dev/null
+++ b/vispy/__init__.py
@@ -0,0 +1,104 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2013, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+"""
+Vispy is a collaborative project that has the goal to allow more sharing
+of code between visualization projects based on OpenGL. It does this
+by providing powerful interfaces to OpenGL, at different levels of
+abstraction and generality.
+
+These layers are:
+  * vispy.gloo.gl: raw OpenGL ES 2.0 API
+  * vispy.gloo: Object oriented GL API
+  * vispy.visuals: Higher level visualization objects (work in progress)
+  * ... more to come
+
+Further, vispy comes with a powerful event system and a small
+application framework that works on multiple backends. This allows easy
+creation of figures, and enables integrating visualizations in a GUI
+application.
+
+For more information see http://vispy.org.
+
+"""
+
+from __future__ import print_function, division, absolute_import
+
+# Definition of the version number
+__version__ = '0.2.1'
+
+
+from vispy.util.event import EmitterGroup, EventEmitter, Event
+from vispy.util import keys, dataio
+
+
+class ConfigEvent(Event):
+    """ Event indicating a configuration change. 
+    
+    This class has a 'changes' attribute which is a dict of all name:value 
+    pairs that have changed in the configuration.
+    """
+    def __init__(self, changes):
+        Event.__init__(self, type='config_change')
+        self.changes = changes
+        
+        
+class Config(object):
+    """ Container for global settings used application-wide in vispy.
+    
+    Events:
+    -------
+    Config.events.changed - Emits ConfigEvent whenever the configuration changes.
+    """
+    def __init__(self):
+        self.events = EmitterGroup(source=self)
+        self.events['changed'] = EventEmitter(event_class=ConfigEvent, source=self)
+        self._config = {}
+    
+    def __getitem__(self, item):
+        return self._config[item]
+    
+    def __setitem__(self, item, val):
+        self._config[item] = val
+        # inform any listeners that a configuration option has changed
+        self.events.changed(changes={item:val})
+        
+    def update(self, **kwds):
+        self._config.update(kwds)
+        self.events.changed(changes=kwds)
+
+    def __repr__(self):
+        return repr(self._config)
+
+config = Config()
+config.update(
+    default_backend='qt',
+    qt_lib= 'any',  # options are 'pyqt', 'pyside', or 'any'
+    show_warnings=False,
+    gl_debug=False,
+)
+
+
+def parse_command_line_arguments():
+    """ Transform vispy specific command line args to vispy config.
+    Put into a function so that any variables dont leak in the vispy namespace.
+    """
+    import getopt, sys
+    # Get command line args for vispy
+    argnames = ['vispy-backend', 'vispy-gl-debug']
+    try:
+        opts, args = getopt.getopt(sys.argv[1:], '', argnames)
+    except getopt.GetoptError:
+        opts = []
+    # Use them to set the config values
+    for o, a in opts:
+        if o.startswith('--vispy'):
+            if o == '--vispy-backend':
+                config['default_backend'] = a
+                print('backend', a)
+            elif o == '--vispy-gl-debug':
+                config['gl_debug'] = True
+            else:
+                print("Unsupported vispy flag: %s" % o)
+parse_command_line_arguments()
diff --git a/vispy/app/__init__.py b/vispy/app/__init__.py
new file mode 100644
index 0000000..29a949f
--- /dev/null
+++ b/vispy/app/__init__.py
@@ -0,0 +1,65 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2013, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+""" 
+The app module defines three classes: Application, Canvas, and Timer. 
+On loading, vispy creates a default Application instance which can be used
+via functions in the module's namespace.
+"""
+
+from __future__ import print_function, division, absolute_import
+
+import vispy
+
+from .application import Application, ApplicationBackend
+from .canvas import Canvas, CanvasBackend
+from .timer import Timer, TimerBackend
+
+# Create default application instance
+default_app = Application()
+
+
+def use(backend_name=None):
+    """ Select a backend by name. If the backend name is omitted, will
+    chose a suitable backend automatically. It is an error to try to
+    select a particular backend if one is already selected. Available
+    backends: 'PySide', 'PyQt4', 'Glut', 'Pyglet', 'qt'. The latter
+    will use PySide or PyQt4, whichever works.
+    
+    If a backend name is provided, and that backend could not be loaded,
+    an error is raised.
+    
+    If no backend name is provided, this function will first check if
+    the GUI toolkit corresponding to each backend is already imported,
+    and try that backend first. If this is unsuccessful, it will try
+    the 'default_backend' provided in the vispy config. If still not
+    succesful, it will try each backend in a predetermined order.
+    """
+    return default_app.use(backend_name)
+
+
+def create():
+    """ Create the native application.
+    """
+    return default_app.create()
+
+
+def run():
+    """ Enter the native GUI event loop. 
+    """
+    return default_app.run()
+
+
+def quit():
+    """ Quit the native GUI event loop.
+    """
+    return default_app.quit()
+
+
+def process_events():
+    """ Process all pending GUI events. If the mainloop is not
+    running, this should be done regularly to keep the visualization
+    interactive and to keep the event system going.
+    """
+    return default_app.process_events()
diff --git a/vispy/app/application.py b/vispy/app/application.py
new file mode 100644
index 0000000..5c658b9
--- /dev/null
+++ b/vispy/app/application.py
@@ -0,0 +1,204 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2013, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+""" 
+Implements the global singleton app object.
+
+"""
+
+from __future__ import print_function, division, absolute_import
+
+import sys
+
+import vispy
+from vispy.app.backends import BACKENDS, BACKENDMAP, ATTEMPTED_BACKENDS
+
+
+
+class Application(object):
+    """ Representation of the vispy application. This wraps a native 
+    GUI application instance. Vispy has a default instance of this class
+    at vispy.app.default_app.
+    
+    There are multiple stages for an Application object:
+        * Backend-less - the state when it is just initialized
+        * Backend selected - use() has been successfully called. Note that
+          the Canvas calls use() without arguments reight before creating
+          its backend widget.
+        * Native application is created - the Canvas probes the 
+          Application,native property to ensure that there is a native 
+          application right before a native widget is created.
+    
+    """
+    
+    def __init__(self):
+        self._backend_module = None
+        self._backend = None
+    
+    def __repr__(self):
+        name = self.backend_name
+        if not name:
+            return '<Vispy app with no backend>'
+        else:
+            return '<Vispy app, wrapping the %s GUI toolkit>' % name
+    
+    @property
+    def backend_name(self):
+        """ The name of the GUI backend that this app wraps.
+        """
+        if self._backend is not None:
+            return self._backend._vispy_get_backend_name()
+        else:
+            return ''
+    
+    @property
+    def backend_module(self):
+        """ The module object that defines the backend.
+        """
+        return self._backend_module
+    
+    def process_events(self):
+        """ Process all pending GUI events. If the mainloop is not
+        running, this should be done regularly to keep the visualization
+        interactive and to keep the event system going.
+        """
+        return self._backend._vispy_process_events()
+    
+    def create(self):
+        """ Create the native application.
+        """
+        self.native
+    
+    def run(self):
+        """ Enter the native GUI event loop. 
+        """
+        return self._backend._vispy_run()
+    
+    def quit(self):
+        """ Quit the native GUI event loop.
+        """
+        return self._backend._vispy_quit()
+    
+    @property
+    def native(self):
+        """ The native GUI application instance.
+        """
+        return self._backend._vispy_get_native_app()
+
+    
+    def use(self, backend_name=None):
+        """ Select a backend by name. If the backend name is omitted,
+        will chose a suitable backend automatically. It is an error to
+        try to select a particular backend if one is already selected.
+        Available backends: 'PySide', 'PyQt4', 'Glut', 'Pyglet', 'qt'. 
+        The latter will use PySide or PyQt4, whichever works.
+        
+        If a backend name is provided, and that backend could not be 
+        loaded, an error is raised.
+        
+        If no backend name is provided, this function will first check
+        if the GUI toolkit corresponding to each backend is already
+        imported, and try that backend first. If this is unsuccessful,
+        it will try the 'default_backend' provided in the vispy config.
+        If still not succesful, it will try each backend in a
+        predetermined order.
+        
+        """
+        import vispy.app
+        
+        # Should we try and load any backend, or just this specific one?
+        try_others = backend_name is None
+        
+        # Check if already selected
+        if self._backend is not None:
+            if backend_name and backend_name.lower() != self.backend_name.lower():
+                raise RuntimeError('Can only select a backend once.')
+            return
+        
+        
+        # Get backends to try ...
+        backends_to_try = []
+        if not try_others:
+            # Test if given name is ok
+            if backend_name.lower() not in BACKENDMAP.keys():
+                raise ValueError('Backend name not known: "%s"' % backend_name)
+            # Add it
+            backends_to_try.append(backend_name.lower())
+        else:
+            # See if a backend is loaded
+            for name, module_name, native_module_name in BACKENDS:
+                if native_module_name and native_module_name in sys.modules:
+                    backends_to_try.append(name.lower())
+            # See if a default is given
+            default_backend = vispy.config['default_backend'].lower()
+            if default_backend.lower() in BACKENDMAP.keys():
+                if default_backend not in backends_to_try:
+                    backends_to_try.append(default_backend)
+            # After this, try each one
+            for name, module_name, native_module_name in BACKENDS:
+                name = name.lower()
+                if name not in backends_to_try:
+                    backends_to_try.append(name)
+        
+        
+        # Now try each one
+        for key in backends_to_try:
+            # Get info for this backend
+            try:
+                name, module_name, native_module_name = BACKENDMAP[key]
+            except KeyError:
+                print('This should not happen, unknown backend: "".' % key)
+                continue
+            # Get backend module name
+            mod_name = 'vispy.app.backends.' + module_name
+            # Try to import it ...
+
+            try:
+                ATTEMPTED_BACKENDS.append(name)
+                __import__(mod_name)
+            except ImportError as err:
+                msg = 'Could not import backend "%s":\n%s' % (name, str(err))
+                if not try_others:
+                    raise RuntimeError(msg)
+            except Exception as err:
+                msg = 'Error while importing backend "%s":\n%s' % (name, str(err))
+                if not try_others:
+                    raise RuntimeError(msg)
+                else:
+                    print(msg)
+            else:
+                # Success!
+                self._backend_module = getattr(vispy.app.backends, module_name)
+                break
+        else:
+            raise RuntimeError('Could not import any of the backends.')
+        
+        # Store classes for app backend and canvas backend 
+        self._backend = self.backend_module.ApplicationBackend()
+
+
+class ApplicationBackend(object):
+    """ ApplicationBackend()
+    
+    Abstract class that provides an interface between backends and Application.
+    Each backend must implement a subclass of ApplicationBackend, and
+    implement all its _vispy_xxx methods.
+    """
+    
+    def _vispy_get_backend_name(self):
+        raise NotImplementedError()
+    
+    def _vispy_process_events(self):
+        raise NotImplementedError()
+    
+    def _vispy_run(self):
+        raise NotImplementedError()
+    
+    def _vispy_quit(self):
+        raise NotImplementedError()
+    
+    def _vispy_get_native_app(self):
+        # Should return the native application object
+        return self
+
diff --git a/vispy/app/backends/__init__.py b/vispy/app/backends/__init__.py
new file mode 100644
index 0000000..33c7c71
--- /dev/null
+++ b/vispy/app/backends/__init__.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2013, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+""" vispy.app.backends
+
+The backend modules are dynamically imported when needed. This module
+defines a small description of each supported backend, so that for
+instance we can test whether the GUI toolkit for a backend is already
+imported. This stuff is mostly used in the Application.use method.
+"""
+
+# Define backends: name, vispy.app.backends.xxx module, native module name.
+# This is the order in which they are attempted to be imported.
+BACKENDS = [    ('Test', 'nonexistent', 'foo.bar.lalalala'), # For testing
+                ('Qt', 'qt', None),  # Meta backend
+                ('Glut', 'glut', 'OpenGL.GLUT'),
+                ('Pyglet', 'pyglet', 'pyglet'),
+                ('PySide', 'qt', 'PySide'),
+                ('PyQt4', 'qt', 'PyQt4'),
+            ]
+
+# Map of the lowercase backend names to the backend descriptions above
+# so that we can look up its properties if we only have a name.
+BACKENDMAP = dict([(be[0].lower(), be) for be in BACKENDS])
+
+# List of attempted backends. For logging and for communicating to the backends.
+ATTEMPTED_BACKENDS = []
diff --git a/vispy/app/backends/glut.py b/vispy/app/backends/glut.py
new file mode 100644
index 0000000..0a7bf1b
--- /dev/null
+++ b/vispy/app/backends/glut.py
@@ -0,0 +1,432 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2013, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+"""
+vispy backend for glut.
+"""
+
+from __future__ import print_function, division, absolute_import
+
+from vispy.util.event import Event
+from vispy import app
+from vispy import keys
+import vispy.util.ptime as ptime
+import vispy
+
+import OpenGL.error
+import OpenGL.GLUT as glut
+
+
+# glut.GLUT_ACTIVE_SHIFT: keys.SHIFT,
+# glut.GLUT_ACTIVE_CTRL: keys.CONTROL,
+# glut.GLUT_ACTIVE_ALT: keys.ALT,
+# -1: keys.META,
+    
+# Map native keys to vispy keys
+KEYMAP = {
+    -1: keys.SHIFT,
+    -2: keys.CONTROL,
+    -3: keys.ALT,
+    -4: keys.META,
+    
+    glut.GLUT_KEY_LEFT: keys.LEFT,
+    glut.GLUT_KEY_UP: keys.UP,
+    glut.GLUT_KEY_RIGHT: keys.RIGHT,
+    glut.GLUT_KEY_DOWN: keys.DOWN,
+    glut.GLUT_KEY_PAGE_UP: keys.PAGEUP,
+    glut.GLUT_KEY_PAGE_DOWN: keys.PAGEDOWN,
+    
+    glut.GLUT_KEY_INSERT: keys.INSERT,
+    chr(127): keys.DELETE,
+    glut.GLUT_KEY_HOME: keys.HOME,
+    glut.GLUT_KEY_END: keys.END,
+    
+    chr(27): keys.ESCAPE,
+    chr(8): keys.BACKSPACE,
+    
+    glut.GLUT_KEY_F1: keys.F1,
+    glut.GLUT_KEY_F2: keys.F2,
+    glut.GLUT_KEY_F3: keys.F3,
+    glut.GLUT_KEY_F4: keys.F4,
+    glut.GLUT_KEY_F5: keys.F5,
+    glut.GLUT_KEY_F6: keys.F6,
+    glut.GLUT_KEY_F7: keys.F7,
+    glut.GLUT_KEY_F8: keys.F8,
+    glut.GLUT_KEY_F9: keys.F9,
+    glut.GLUT_KEY_F10: keys.F10,
+    glut.GLUT_KEY_F11: keys.F11,
+    glut.GLUT_KEY_F12: keys.F12,
+    
+    ' ': keys.SPACE,
+    '\r': keys.ENTER,
+    '\n': keys.ENTER,
+    '\t': keys.TAB,
+}
+
+
+BUTTONMAP = {   glut.GLUT_LEFT_BUTTON:1, 
+                glut.GLUT_RIGHT_BUTTON:2, 
+                glut.GLUT_MIDDLE_BUTTON:3
+            }
+
+
+ALL_WINDOWS = []
+
+class ApplicationBackend(app.ApplicationBackend):
+    def __init__(self):
+        app.ApplicationBackend.__init__(self)
+        self._inizialized = False
+        self._windows = []
+        
+    
+    def _vispy_get_backend_name(self):
+        return 'Glut'
+    
+    def _vispy_process_events(self):
+        pass # not possible?
+    
+    def _vispy_run(self):
+        self._vispy_get_native_app() # Force exist
+        return glut.glutMainLoop()
+    
+    def _vispy_quit(self):
+        global ALL_WINDOWS
+        for win in ALL_WINDOWS:
+            win._vispy_close()
+    
+    def _vispy_get_native_app(self):
+        import sys, ctypes
+        from OpenGL import platform
+
+        # HiDPI support for retina display
+        # This requires glut from http://iihm.imag.fr/blanch/software/glut-macosx/
+        if sys.platform == 'darwin':
+            try:
+                glutInitDisplayString = platform.createBaseFunction( 
+                    'glutInitDisplayString', dll=platform.GLUT, resultType=None, 
+                    argTypes=[ctypes.c_char_p],
+                    doc='glutInitDisplayString(  ) -> None', 
+                argNames=() )
+                text = ctypes.c_char_p("rgba stencil double samples=8 hidpi")
+                glutInitDisplayString(text)
+            except:
+                pass
+        if not self._inizialized:
+            glut.glutInit() # todo: maybe allow user to give args?
+            self._inizialized = True
+        return glut
+
+
+
+class CanvasBackend(app.CanvasBackend):
+    """ GLUT backend for Canvas abstract class."""
+    
+    def __init__(self, name='glut window', *args, **kwargs):
+        app.CanvasBackend.__init__(self)
+        self._id = glut.glutCreateWindow(name)
+        global ALL_WINDOWS
+        ALL_WINDOWS.append(self)
+        
+        # Cache of modifiers so we can send modifiers along with mouse motion
+        self._modifiers_cache = ()
+        
+        # Note: this seems to cause the canvas to ignore calls to show()
+        # about half of the time. 
+        #glut.glutHideWindow()  # Start hidden, like the other backends
+        
+        # Register callbacks
+        glut.glutDisplayFunc(self.on_draw)
+        glut.glutReshapeFunc(self.on_resize)
+        #glut.glutVisibilityFunc(self.on_show)
+        glut.glutKeyboardFunc(self.on_key_press)
+        glut.glutSpecialFunc(self.on_key_press)
+        glut.glutKeyboardUpFunc(self.on_key_release)
+        glut.glutMouseFunc(self.on_mouse_action)
+        glut.glutMotionFunc(self.on_mouse_motion)
+        glut.glutPassiveMotionFunc(self.on_mouse_motion)
+        
+        # Set close function. See issue #10. For some reason, the function
+        # can still not exist even if we checked its boolean status.
+        closeFuncSet = False
+        if bool(glut.glutWMCloseFunc): # OSX specific test
+            try:
+                glut.glutWMCloseFunc(self.on_close)
+                closeFuncSet = True
+            except OpenGL.error.NullFunctionError:
+                pass
+        if not closeFuncSet:
+            try:
+                glut.glutCloseFunc(self.on_close)
+                closeFuncSet = True
+            except OpenGL.error.NullFunctionError:
+                pass
+        
+        #glut.glutFunc(self.on_)
+        
+        self._initialized = False
+        
+        # LC: I think initializing here makes it more consistent with other backends
+        glut.glutTimerFunc(0, self._emit_initialize, None)
+        
+    def _emit_initialize(self, _=None):
+        if not self._initialized:
+            self._initialized = True
+            self._vispy_canvas.events.initialize()
+        
+    def _vispy_set_current(self):  
+        # Make this the current context
+        glut.glutSetWindow(self._id)
+    
+    def _vispy_swap_buffers(self):  
+        # Swap front and back buffer
+        glut.glutSetWindow(self._id)
+        glut.glutSwapBuffers()
+    
+    def _vispy_set_title(self, title):  
+        # Set the window title. Has no effect for widgets
+        glut.glutSetWindow(self._id)
+        glut.glutSetWindowTitle(title)
+    
+    def _vispy_set_size(self, w, h):
+        # Set size of the widget or window
+        glut.glutSetWindow(self._id)
+        glut.glutReshapeWindow(w, h)
+    
+    def _vispy_set_position(self, x, y):
+        # Set position of the widget or window. May have no effect for widgets
+        glut.glutSetWindow(self._id)
+        glut.glutPositionWindow(x, y)
+    
+    def _vispy_set_visible(self, visible):
+        # Show or hide the window or widget
+        glut.glutSetWindow(self._id)
+        if visible:
+            glut.glutShowWindow()
+        else:
+            glut.glutHideWindow()
+    
+    def _vispy_update(self):
+        # Invoke a redraw
+        glut.glutSetWindow(self._id)
+        glut.glutPostRedisplay()
+    
+    def _vispy_close(self):
+        # Force the window or widget to shut down
+        glut.glutDestroyWindow(self._id)
+    
+    def _vispy_get_geometry(self):
+        # Should return widget (x, y, w, h)
+        glut.glutSetWindow(self._id)
+        x = glut.glutGet(glut.GLUT_WINDOW_X)
+        y = glut.glutGet(glut.GLUT_WINDOW_Y)
+        w = glut.glutGet(glut.GLUT_WINDOW_WIDTH)
+        h = glut.glutGet(glut.GLUT_WINDOW_HEIGHT)
+        return x, y, w, h
+
+    def _vispy_get_size(self):
+        glut.glutSetWindow(self._id)
+        w = glut.glutGet(glut.GLUT_WINDOW_WIDTH)
+        h = glut.glutGet(glut.GLUT_WINDOW_HEIGHT)
+        return w, h
+
+    def _vispy_get_position(self):
+        glut.glutSetWindow(self._id)
+        x = glut.glutGet(glut.GLUT_WINDOW_X)
+        y = glut.glutGet(glut.GLUT_WINDOW_Y)
+        return x, y
+    
+    def on_resize(self, w, h):
+        if self._vispy_canvas is None:
+            return
+        self._vispy_canvas.events.resize(size=(w,h))
+    
+    def on_close(self):
+        if self._vispy_canvas is None:
+            return
+        self._vispy_canvas.events.close()
+    
+    def on_draw(self, dummy=None):
+        if self._vispy_canvas is None:
+            return
+        if not self._initialized:
+            # The timer that we set may not have fired just yet
+            self._emit_initialize()
+        
+        #w = glut.glutGet(glut.GLUT_WINDOW_WIDTH)
+        #h = glut.glutGet(glut.GLUT_WINDOW_HEIGHT)
+        self._vispy_canvas.events.paint(region=None)  #(0, 0, w, h))
+    
+    def on_mouse_action(self, button, state, x, y):
+        if self._vispy_canvas is None:
+            return
+        action = {glut.GLUT_UP:'release', glut.GLUT_DOWN:'press'}[state]
+        mod = self._modifiers(False)
+        
+        if button < 3:
+            # Mouse click event
+            button = BUTTONMAP.get(button, 0)
+            if action == 'press':
+                self._vispy_mouse_press(pos=(x,y), button=button, modifiers=mod)
+            else:
+                self._vispy_mouse_release(pos=(x,y), button=button, modifiers=mod)
+        
+        elif button in (3, 4):
+            # Wheel event
+            deltay = 1.0 if button==3 else -1.0
+            self._vispy_canvas.events.mouse_wheel(
+                pos=(x, y),
+                delta=(0.0, deltay),
+                modifiers=mod,
+                )
+    
+    def on_mouse_motion(self, x, y):
+        if self._vispy_canvas is None:
+            return
+        self._vispy_mouse_move(
+            pos=(x, y),
+            modifiers=self._modifiers(False),
+            )
+    
+    
+    def on_key_press(self, key, x, y):
+        key, text = self._process_key(key)
+        self._vispy_canvas.events.key_press(
+                key=key, 
+                text=text,
+                modifiers=self._modifiers(),
+            )
+    
+    def on_key_release(self, key, x, y):
+        key, text = self._process_key(key)
+        self._vispy_canvas.events.key_release(
+                key=key, 
+                text=text,
+                modifiers=self._modifiers()
+            )
+
+    def _process_key(self, key):
+        if key in KEYMAP:
+            if isinstance(key, int):
+                return KEYMAP[key], ''
+            else:
+                return KEYMAP[key], key
+        elif isinstance(key, int):
+            return None, '' # unsupported special char
+        else:
+            return keys.Key(key.upper()), key
+    
+    def _modifiers(self, query_now=True):
+        if query_now:
+            glutmod = glut.glutGetModifiers()
+            mod = ()
+            if glut.GLUT_ACTIVE_SHIFT & glutmod:
+                mod += keys.SHIFT,
+            if glut.GLUT_ACTIVE_CTRL & glutmod:
+                mod += keys.CONTROL,
+            if glut.GLUT_ACTIVE_ALT & glutmod:
+                mod += keys.ALT,
+            self._modifiers_cache = mod
+        return self._modifiers_cache
+
+
+import weakref
+
+def _glut_callback(id):
+    # Get weakref wrapper for timer
+    timer = TimerBackend._timers.get(id, None)
+    if timer is None:
+        return
+    # Get timer object
+    timer = timer()
+    if timer is None:
+        return
+    # Kick it!
+    if timer._vispy_timer._running:
+        timer._vispy_timer._timeout()
+        ms = int(timer._vispy_timer._interval*1000)
+        glut.glutTimerFunc(ms, _glut_callback, timer._id)
+
+
+#class TimerBackend(app.TimerBackend):
+    #_counter = 0
+    #_timers = {}
+    
+    #def __init__(self, vispy_timer):
+        #app.TimerBackend.__init__(self, vispy_timer)
+        ## Give this timer a unique id
+        #TimerBackend._counter += 1
+        #self._id = TimerBackend._counter
+        ## Store this timer (using a weak ref)
+        #self._timers[self._id] = weakref.ref(self)
+    
+    #@classmethod
+    #def _glut_callback(cls, id):
+        ## Get weakref wrapper for timer
+        #timer = cls._timers.get(id, None)
+        #if timer is None:
+            #return
+        ## Get timer object
+        #timer = timer()
+        #if timer is None:
+            #return
+        ## Kick it!
+        #if timer._vispy_timer._running:
+            #timer._vispy_timer._timeout()
+            #ms = int(timer._vispy_timer._interval*1000)
+            #glut.glutTimerFunc(ms, TimerBackend._glut_callback, timer._id)
+    
+    #def _vispy_start(self, interval):
+        ##glut.glutTimerFunc(int(interval*1000), TimerBackend._glut_callback, self._id)
+        #glut.glutTimerFunc(int(interval*1000), _glut_callback, self._id)
+    
+    #def _vispy_stop(self):
+        #pass
+    
+    #def _vispy_get_native_timer(self):
+        #return glut # or self?
+
+# Note: we could also build a timer using glutTimerFunc, but this causes trouble
+# because timer callbacks appear to take precedence over all others. Thus,
+# a fast timer can block new display events.
+class TimerBackend(app.TimerBackend):
+    _initialized = False
+    _schedule = []
+    
+    def __init__(self, vispy_timer):
+        app.TimerBackend.__init__(self, vispy_timer)
+        self._init_class()
+
+    @classmethod
+    def _init_class(cls):
+        if cls._initialized:
+            return
+        glut.glutIdleFunc(cls._idle_callback)
+        cls._initialized = True
+    
+    @classmethod
+    def _idle_callback(cls):
+        now = ptime.time()
+        new_schedule = []
+        
+        ## see whether there are any timers ready
+        while len(cls._schedule) > 0 and cls._schedule[0][0] <= now:
+            timer = cls._schedule.pop(0)[1]
+            timer._vispy_timer._timeout()
+            if timer._vispy_timer.running:
+                new_schedule.append((now+timer._vispy_timer.interval, timer))
+                
+        ## schedule next round of timeouts
+        if len(new_schedule) > 0:
+            cls._schedule.extend(new_schedule)    
+            cls._schedule.sort()
+                
+    def _vispy_start(self, interval):
+        now = ptime.time()
+        self._schedule.append((now + interval, self))
+    
+    def _vispy_stop(self):
+        pass
+    
+    def _vispy_get_native_timer(self):
+        return None  # glut has no native timer objects.
diff --git a/vispy/app/backends/pyglet.py b/vispy/app/backends/pyglet.py
new file mode 100644
index 0000000..71bbbbd
--- /dev/null
+++ b/vispy/app/backends/pyglet.py
@@ -0,0 +1,343 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2013, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+"""
+vispy backend for pyglet.
+"""
+
+# absolute import is important here, since this module is called pyglet :)
+from __future__ import print_function, division, absolute_import
+
+from vispy.util.event import Event
+from vispy import app
+from vispy import keys
+import vispy
+
+import pyglet.window
+import pyglet.app
+import pyglet.clock
+
+
+# Map native keys to vispy keys
+KEYMAP = {
+    pyglet.window.key.LSHIFT: keys.SHIFT,
+    pyglet.window.key.RSHIFT: keys.SHIFT,
+    pyglet.window.key.LCTRL: keys.CONTROL,
+    pyglet.window.key.RCTRL: keys.CONTROL,
+    pyglet.window.key.LALT: keys.ALT,
+    pyglet.window.key.RALT: keys.ALT,
+    pyglet.window.key.LMETA: keys.META,
+    pyglet.window.key.RMETA: keys.META,
+    
+    pyglet.window.key.LEFT: keys.LEFT,
+    pyglet.window.key.UP: keys.UP,
+    pyglet.window.key.RIGHT: keys.RIGHT,
+    pyglet.window.key.DOWN: keys.DOWN,
+    pyglet.window.key.PAGEUP: keys.PAGEUP,
+    pyglet.window.key.PAGEDOWN: keys.PAGEDOWN,
+    
+    pyglet.window.key.INSERT: keys.INSERT,
+    pyglet.window.key.DELETE: keys.DELETE,
+    pyglet.window.key.HOME: keys.HOME,
+    pyglet.window.key.END: keys.END,
+    
+    pyglet.window.key.ESCAPE: keys.ESCAPE,
+    pyglet.window.key.BACKSPACE: keys.BACKSPACE,
+    
+    pyglet.window.key.F1: keys.F1,
+    pyglet.window.key.F2: keys.F2,
+    pyglet.window.key.F3: keys.F3,
+    pyglet.window.key.F4: keys.F4,
+    pyglet.window.key.F5: keys.F5,
+    pyglet.window.key.F6: keys.F6,
+    pyglet.window.key.F7: keys.F7,
+    pyglet.window.key.F8: keys.F8,
+    pyglet.window.key.F9: keys.F9,
+    pyglet.window.key.F10: keys.F10,
+    pyglet.window.key.F11: keys.F11,
+    pyglet.window.key.F12: keys.F12,
+    
+    pyglet.window.key.SPACE: keys.SPACE,
+    pyglet.window.key.ENTER: keys.ENTER, # == pyglet.window.key.RETURN
+    pyglet.window.key.NUM_ENTER: keys.ENTER,
+    pyglet.window.key.TAB: keys.TAB,
+}
+
+
+BUTTONMAP = {   pyglet.window.mouse.LEFT:1, 
+                pyglet.window.mouse.RIGHT:2,
+                pyglet.window.mouse.MIDDLE:3
+            }
+
+
+class ApplicationBackend(app.ApplicationBackend):
+    
+    def __init__(self):
+        app.ApplicationBackend.__init__(self)
+    
+    def _vispy_get_backend_name(self):
+        return 'Pyglet'
+    
+    def _vispy_process_events(self):
+        return pyglet.app.platform_event_loop.step(0.0)
+    
+    def _vispy_run(self):
+        return pyglet.app.run()
+    
+    def _vispy_quit(self):
+        return pyglet.app.exit()
+    
+    def _vispy_get_native_app(self):
+        return pyglet.app
+
+
+
+class CanvasBackend(pyglet.window.Window, app.CanvasBackend):
+    """ Pyglet backend for Canvas abstract class."""
+    
+    def __init__(self, *args, **kwargs):
+        app.CanvasBackend.__init__(self)
+        # Initialize native widget, but default hidden and resizable
+        kwargs['visible'] = kwargs.get('visible', False)
+        kwargs['resizable'] = kwargs.get('resizable', True) 
+        kwargs['vsync'] = kwargs.get('vsync', 0) 
+        pyglet.window.Window.__init__(self, *args, **kwargs)
+        
+        # We keep track of modifier keys so we can pass them to mouse_motion
+        self._current_modifiers = set()
+        #self._buttons_accepted = 0
+        self._draw_ok = False  # whether it is ok to draw yet
+        self._pending_position = None
+    
+    # Override these ...  
+    def flip(self):
+        # Is called by event loop after each draw
+        pass  
+    def on_draw(self):
+        # Is called by event loop after each event, whatever event ... really
+        if not self._draw_ok:
+            self._draw_ok = True
+            self.our_paint_func()
+    def draw_mouse_cursor(self):
+        # Prevent legacy OpenGL
+        pass 
+    
+    
+    def _vispy_set_current(self):  
+        # Make this the current context
+        self.switch_to()
+    
+    def _vispy_swap_buffers(self):  
+        # Swap front and back buffer
+        pyglet.window.Window.flip(self)
+    
+    def _vispy_set_title(self, title):  
+        # Set the window title. Has no effect for widgets
+        self.set_caption(title)
+    
+    def _vispy_set_size(self, w, h):
+        # Set size of the widget or window
+        self.set_size(w, h)
+    
+    def _vispy_set_position(self, x, y):
+        # Set positionof the widget or window. May have no effect for widgets
+        if self._draw_ok:
+            self.set_location(x, y)
+        else:
+            self._pending_position = x, y
+    
+    def _vispy_set_visible(self, visible):
+        # Show or hide the window or widget
+        self.set_visible(visible)
+    
+    def _vispy_update(self):
+        # Invoke a redraw
+        pyglet.clock.schedule_once(self.our_paint_func, 0.0)
+    
+    def _vispy_close(self):
+        # Force the window or widget to shut down
+        self.close()
+    
+    def _vispy_get_geometry(self):
+        # Should return widget (x, y, w, h)
+        xy = self.get_location()
+        wh = self.get_size()
+        return xy + wh
+
+    def _vispy_get_size(self):
+        x,y = self.get_size()
+        return x,y
+
+    def _vispy_get_position(self):
+        w,h = self.get_location()
+        return w,h
+    
+    
+    def on_show(self):
+        if self._vispy_canvas is None:
+            return
+        self._vispy_canvas.events.initialize()
+        # Set location now if we must. For some reason we get weird 
+        # offsets in viewport if set_location is called before the
+        # widget is shown.
+        if self._pending_position:
+            x,y = self._pending_position
+            self._pending_position = None
+            self.set_location(x,y)
+        # Redraw
+        self._vispy_update()
+    
+    def on_close(self):
+        if self._vispy_canvas is None:
+            return
+        self._vispy_canvas.events.close()
+        self.close() # Or the window wont close
+    
+    def on_resize(self, w, h):
+        if self._vispy_canvas is None:
+            return
+        self._vispy_canvas.events.resize(size=(w,h))
+        #self._vispy_update()
+    
+    def our_paint_func(self, dummy=None):
+        if not self._draw_ok or self._vispy_canvas is None:
+            return
+        self._vispy_canvas.events.paint(region=None)#(0, 0, self.width, self.height))
+    
+    
+    def on_mouse_press(self, x, y, button, modifiers=None):
+        if self._vispy_canvas is None:
+            return
+        ev2 = self._vispy_mouse_press(
+            pos=(x, self.get_size()[1] - y),
+            button=BUTTONMAP.get(button, 0),
+            modifiers=self._modifiers(),
+            )
+#         if ev2.handled:
+#             self._buttons_accepted |= button
+    
+    def on_mouse_release(self, x, y, button, modifiers=None):
+        if self._vispy_canvas is None:
+            return
+        if True:#(button & self._buttons_accepted) > 0:
+            self._vispy_mouse_release(
+                pos=(x, self.get_size()[1] - y),
+                button=BUTTONMAP.get(button, 0),
+                modifiers=self._modifiers(),
+                )
+            #self._buttons_accepted &= ~button
+    
+    def on_mouse_motion(self, x, y, dx, dy):
+        if self._vispy_canvas is None:
+            return
+        self._vispy_mouse_move(
+            pos=(x, self.get_size()[1] - y),
+            modifiers=self._modifiers(),
+            )
+
+    def on_mouse_drag(self, x, y, dx, dy, button, modifiers):
+        self.on_mouse_motion(x, y, dx, dy)
+    
+    def on_mouse_scroll(self, x, y, scroll_x, scroll_y):
+        if self._vispy_canvas is None:
+            return
+        self._vispy_canvas.events.mouse_wheel(
+            delta=(float(scroll_x), float(scroll_y)),
+            pos=(x, y),
+            modifiers=self._modifiers(),
+            )
+    
+    
+    def on_key_press(self, key, modifiers):
+        # Process modifiers
+        if key in ( pyglet.window.key.LCTRL, pyglet.window.key.RCTRL, 
+                    pyglet.window.key.LALT, pyglet.window.key.RALT,
+                    pyglet.window.key.LSHIFT, pyglet.window.key.RSHIFT):
+            self._current_modifiers.add(key)
+        # Get txt
+        try:
+            text = chr(key)
+        except Exception:
+            text = ''
+        # Emit
+        self._vispy_canvas.events.key_press(
+                key=self._processKey(key), 
+                text='',  # Handlers that trigger on text wont see this event
+                modifiers=self._modifiers(modifiers),
+            )
+    
+    
+    def on_text(self, text):
+        # Typically this is called after on_key_press and before 
+        # on_key_release
+        self._vispy_canvas.events.key_press(
+                key=None, # Handlers that trigger on key wont see this event
+                text=text,
+                modifiers=self._modifiers(),
+            )
+    
+    
+    def on_key_release(self, key, modifiers):
+        # Process modifiers
+        if key in ( pyglet.window.key.LCTRL, pyglet.window.key.RCTRL, 
+                    pyglet.window.key.LALT, pyglet.window.key.RALT,
+                    pyglet.window.key.LSHIFT, pyglet.window.key.RSHIFT):
+            self._current_modifiers.discard(key)
+        # Get txt
+        try:
+            text = chr(key)
+        except Exception:
+            text = ''
+        # Emit
+        self._vispy_canvas.events.key_release(
+                key= self._processKey(key), 
+                text=text,
+                modifiers=self._modifiers(modifiers)
+            )
+    
+    def _processKey(self, key):
+        if 97 <= key <= 122:
+            key -= 32
+        if key in KEYMAP:
+            return KEYMAP[key]
+        elif key>=32 and key <= 127:
+            return keys.Key(chr(key))
+        else:
+            return None 
+    
+    def _modifiers(self, pygletmod=None):
+        mod = ()
+        if pygletmod is None:
+            pygletmod = self._current_modifiers
+        if isinstance(pygletmod, set):
+            for key in pygletmod:
+                mod += KEYMAP[key],
+        else:
+            if pygletmod & pyglet.window.key.MOD_SHIFT:
+                mod += keys.SHIFT,
+            if pygletmod & pyglet.window.key.MOD_CTRL:
+                mod += keys.CONTROL,
+            if pygletmod & pyglet.window.key.MOD_ALT:
+                mod += keys.ALT,
+        return mod
+
+
+
+class TimerBackend(app.TimerBackend):
+    
+    def _vispy_start(self, interval):
+        interval = self._vispy_timer._interval
+        if self._vispy_timer.max_iterations == 1:
+            pyglet.clock.schedule_once(self._vispy_timer._timeout, interval)
+        else:
+            # seems pyglet does not give the expected behavior when interval==0
+            if interval == 0:
+                interval = 1e-9
+            pyglet.clock.schedule_interval(self._vispy_timer._timeout, interval)
+    
+    def _vispy_stop(self):
+        pyglet.clock.unschedule(self._vispy_timer._timeout)
+    
+    def _vispy_get_native_timer(self):
+        return pyglet.clock
diff --git a/vispy/app/backends/qt.py b/vispy/app/backends/qt.py
new file mode 100644
index 0000000..6e2a1d4
--- /dev/null
+++ b/vispy/app/backends/qt.py
@@ -0,0 +1,322 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2013, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+"""
+vispy backend for Qt (PySide and PyQt4).
+"""
+
+from __future__ import print_function, division, absolute_import
+
+import vispy
+from vispy import app
+from vispy import keys
+from vispy.app.backends import ATTEMPTED_BACKENDS
+from vispy.util.six import text_type
+
+# Get what qt lib to try
+if len(ATTEMPTED_BACKENDS):
+    qt_lib = ATTEMPTED_BACKENDS[-1].lower()
+    if qt_lib.lower() == 'qt':
+        qt_lib = vispy.config['qt_lib'].lower()
+else:
+    qt_lib  = 'any'
+
+# Import PySide or PyQt4
+if qt_lib in ('any', 'qt'):
+    try: 
+        from PyQt4 import QtGui, QtCore, QtOpenGL
+    except ImportError:
+        from PySide import QtGui, QtCore, QtOpenGL
+elif qt_lib in ('pyqt', 'pyqt4'):
+    from PyQt4 import QtGui, QtCore, QtOpenGL
+elif qt_lib == 'pyside':
+    from PySide import QtGui, QtCore, QtOpenGL
+else:
+    raise Exception("Do not recognize Qt library '%s'. Options are 'pyqt4', 'pyside', or 'qt'])." % str(qt_lib))
+
+# todo: add support for distinguishing left and right shift/ctrl/alt keys.
+# Linux scan codes:  (left, right)
+#   Shift  50, 62
+#   Ctrl   37, 105
+#   Alt    64, 108
+KEYMAP = {
+    QtCore.Qt.Key_Shift: keys.SHIFT,
+    QtCore.Qt.Key_Control: keys.CONTROL,
+    QtCore.Qt.Key_Alt: keys.ALT,
+    QtCore.Qt.Key_AltGr: keys.ALT,
+    QtCore.Qt.Key_Meta: keys.META,
+    
+    QtCore.Qt.Key_Left: keys.LEFT,
+    QtCore.Qt.Key_Up: keys.UP,
+    QtCore.Qt.Key_Right: keys.RIGHT,
+    QtCore.Qt.Key_Down: keys.DOWN,
+    QtCore.Qt.Key_PageUp: keys.PAGEUP,
+    QtCore.Qt.Key_PageDown: keys.PAGEDOWN,
+    
+    QtCore.Qt.Key_Insert: keys.INSERT,
+    QtCore.Qt.Key_Delete: keys.DELETE,
+    QtCore.Qt.Key_Home: keys.HOME,
+    QtCore.Qt.Key_End: keys.END,
+    
+    QtCore.Qt.Key_Escape: keys.ESCAPE,
+    QtCore.Qt.Key_Backspace: keys.BACKSPACE,
+    
+    QtCore.Qt.Key_F1: keys.F1,
+    QtCore.Qt.Key_F2: keys.F2,
+    QtCore.Qt.Key_F3: keys.F3,
+    QtCore.Qt.Key_F4: keys.F4,
+    QtCore.Qt.Key_F5: keys.F5,
+    QtCore.Qt.Key_F6: keys.F6,
+    QtCore.Qt.Key_F7: keys.F7,
+    QtCore.Qt.Key_F8: keys.F8,
+    QtCore.Qt.Key_F9: keys.F9,
+    QtCore.Qt.Key_F10: keys.F10,
+    QtCore.Qt.Key_F11: keys.F11,
+    QtCore.Qt.Key_F12: keys.F12,
+    
+    QtCore.Qt.Key_Space: keys.SPACE,
+    QtCore.Qt.Key_Enter: keys.ENTER,
+    QtCore.Qt.Key_Return: keys.ENTER,
+    QtCore.Qt.Key_Tab: keys.TAB,
+}
+
+BUTTONMAP = {0:0, 1:1, 2:2, 4:3, 8:4, 16:5}
+
+
+class ApplicationBackend(app.ApplicationBackend):
+    
+    def __init__(self):
+        app.ApplicationBackend.__init__(self)
+    
+    def _vispy_get_backend_name(self):
+        if 'pyside' in QtCore.__name__.lower():
+            return 'PySide (qt)'
+        else:
+            return 'PyQt4 (qt)'
+    
+    def _vispy_process_events(self):
+        app = self._vispy_get_native_app()
+        app.flush()
+        app.processEvents()
+    
+    def _vispy_run(self):
+        app = self._vispy_get_native_app()
+        if hasattr(app, '_in_event_loop') and app._in_event_loop:
+            pass # Already in event loop
+        else:
+            return app.exec_()
+    
+    def _vispy_quit(self):
+        return self._vispy_get_native_app().quit()
+    
+    def _vispy_get_native_app(self):
+        # Get native app in save way. Taken from guisupport.py
+        app = QtGui.QApplication.instance()
+        if app is None:
+            app = QtGui.QApplication([''])
+        # Store so it won't be deleted, but not on a visvis object,
+        # or an application may produce error when closed
+        QtGui._qApp = app
+        # Return
+        return app
+
+
+
+class CanvasBackend(QtOpenGL.QGLWidget, app.CanvasBackend):
+    """Qt backend for Canvas abstract class."""
+    
+    def __init__(self, *args, **kwargs):
+        app.CanvasBackend.__init__(self)
+        QtOpenGL.QGLWidget.__init__(self, *args, **kwargs)
+        self.setAutoBufferSwap(False) # to make consistent with other backends
+        self.setMouseTracking(True)
+    
+    def _vispy_set_current(self):  
+        # Make this the current context
+        self.makeCurrent()
+    
+    def _vispy_swap_buffers(self):  
+        # Swap front and back buffer
+        self.swapBuffers()
+    
+    def _vispy_set_title(self, title):  
+        # Set the window title. Has no effect for widgets
+        self.setWindowTitle(title)
+    
+    def _vispy_set_size(self, w, h):
+        # Set size of the widget or window
+        self.resize(w, h)
+    
+    def _vispy_set_position(self, x, y):
+        # Set location of the widget or window. May have no effect for widgets
+        self.move(x, y)
+    
+    def _vispy_set_visible(self, visible):
+        # Show or hide the window or widget
+        if visible:
+            self.show()
+        else:
+            self.hide()
+    
+    def _vispy_update(self):
+        # Invoke a redraw
+        self.update()
+    
+    def _vispy_close(self):
+        # Force the window or widget to shut down
+        self.close()
+    
+    def _vispy_get_geometry(self):
+        # Should return widget (x, y, w, h)
+        g = self.geometry()
+        return (g.x(), g.y(), g.width(), g.height())
+
+    def _vispy_get_position(self):
+        g = self.geometry()
+        return g.x(), g.y()
+
+    def _vispy_get_size(self):
+        g = self.geometry()
+        return g.width(), g.height()
+    
+    def initializeGL(self):
+        if self._vispy_canvas is None:
+            return
+        self._vispy_canvas.events.initialize()
+        
+    def resizeGL(self, w, h):
+        if self._vispy_canvas is None:
+            return
+        self._vispy_canvas.events.resize(size=(w,h))
+
+    def paintGL(self):
+        if self._vispy_canvas is None:
+            return
+        self._vispy_canvas.events.paint(region=None)#(0, 0, self.width(), self.height()))
+    
+    def closeEvent(self, ev):
+        if self._vispy_canvas is None:
+            return
+        self._vispy_canvas.events.close()
+    
+    def mousePressEvent(self, ev):
+        if self._vispy_canvas is None:
+            return
+        self._vispy_mouse_press(
+            native=ev,
+            pos=(ev.pos().x(), ev.pos().y()),
+            button=BUTTONMAP.get(ev.button(), 0),
+            modifiers = self._modifiers(ev),
+            )
+            
+    def mouseReleaseEvent(self, ev):
+        if self._vispy_canvas is None:
+            return
+        self._vispy_mouse_release(
+            native=ev,
+            pos=(ev.pos().x(), ev.pos().y()),
+            button=BUTTONMAP[ev.button()],
+            modifiers = self._modifiers(ev),
+            )
+
+    def mouseMoveEvent(self, ev):
+        if self._vispy_canvas is None:
+            return
+        self._vispy_mouse_move(
+            native=ev,
+            pos=(ev.pos().x(), ev.pos().y()),
+            modifiers=self._modifiers(ev),
+            )
+        
+    def wheelEvent(self, ev):
+        if self._vispy_canvas is None:
+            return
+        # Get scrolling
+        deltax, deltay = 0.0, 0.0
+        if ev.orientation == QtCore.Qt.Horizontal:
+            deltax = ev.delta()/120.0
+        else:
+            deltay = ev.delta()/120.0
+        # Emit event
+        self._vispy_canvas.events.mouse_wheel(
+            native=ev,
+            delta=(deltax, deltay),
+            pos=(ev.pos().x(), ev.pos().y()),
+            modifiers=self._modifiers(ev),
+            )
+    
+    
+    def keyPressEvent(self, ev):
+        self._vispy_canvas.events.key_press(
+            native = ev,
+            key = self._processKey(ev), 
+            text = text_type(ev.text()),
+            modifiers = self._modifiers(ev),
+            )
+    
+    def keyReleaseEvent(self, ev):
+        #if ev.isAutoRepeat():
+            #return # Skip release auto repeat events
+        self._vispy_canvas.events.key_release(
+            native = ev,
+            key = self._processKey(ev), 
+            text = text_type(ev.text()),
+            modifiers = self._modifiers(ev),
+            )
+    
+    def _processKey(self, event):
+        # evaluates the keycode of qt, and transform to vispy key.
+        key = int(event.key())
+        if key in KEYMAP:
+            return KEYMAP[key]
+        elif key>=32 and key <= 127:
+            return keys.Key(chr(key))
+        else:
+            return None
+    
+    def _modifiers(self, event):
+        # Convert the QT modifier state into a tuple of active modifier keys.
+        mod = ()
+        qtmod = event.modifiers()
+        if QtCore.Qt.ShiftModifier & qtmod:
+            mod += keys.SHIFT,
+        if QtCore.Qt.ControlModifier & qtmod:
+            mod += keys.CONTROL,
+        if QtCore.Qt.AltModifier & qtmod:
+            mod += keys.ALT,
+        if QtCore.Qt.MetaModifier & qtmod:
+            mod += keys.META,
+        return mod
+
+
+
+# class QtMouseEvent(MouseEvent):
+#     # special subclass of MouseEvent for propagating acceptance info back to Qt.
+#     @MouseEvent.handled.setter
+#     def handled(self, val):
+#         self._handled = val
+#         if val:
+#             self.qt_event.accept()
+#         else:
+#             self.qt_event.ignore()
+
+
+class TimerBackend(app.TimerBackend, QtCore.QTimer):
+    def __init__(self, vispy_timer):
+        if QtGui.QApplication.instance() is None:
+            global QAPP
+            QAPP = QtGui.QApplication([])
+        app.TimerBackend.__init__(self, vispy_timer)
+        QtCore.QTimer.__init__(self)
+        self.timeout.connect(self._vispy_timeout)
+        
+    def _vispy_start(self, interval):
+        self.start(interval*1000.)
+        
+    def _vispy_stop(self):
+        self.stop()
+        
+    def _vispy_timeout(self):
+        self._vispy_timer._timeout()
+
diff --git a/vispy/app/backends/template.py b/vispy/app/backends/template.py
new file mode 100644
index 0000000..4c5eac9
--- /dev/null
+++ b/vispy/app/backends/template.py
@@ -0,0 +1,170 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2013, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+""" This module provides an template for creating backends for vispy.
+It clearly indicates what methods should be implemented and what events
+should be emitted.
+"""
+
+from __future__ import print_function, division, absolute_import
+
+import vispy
+from vispy import app
+from vispy import keys
+
+# Map native keys to vispy keys
+KEYMAP = {
+    -1: keys.SHIFT,
+    -2: keys.CONTROL,
+    -3: keys.ALT,
+    -4: keys.META,
+    
+    -5: keys.LEFT,
+    -6: keys.UP,
+    -7: keys.RIGHT,
+    -8: keys.DOWN,
+    -9: keys.PAGEUP,
+    -10: keys.PAGEDOWN,
+    
+    -11: keys.INSERT,
+    -12: keys.DELETE,
+    -13: keys.HOME,
+    -14: keys.END,
+    
+    -15: keys.ESCAPE,
+    -16: keys.BACKSPACE,
+    
+    -17: keys.SPACE,
+    -18: keys.ENTER,
+    -19: keys.TAB,
+    
+    -20: keys.F1,
+    -21: keys.F2,
+    -22: keys.F3,
+    -23: keys.F4,
+    -24: keys.F5,
+    -25: keys.F6,
+    -26: keys.F7,
+    -27: keys.F8,
+    -28: keys.F9,
+    -29: keys.F10,
+    -30: keys.F11,
+    -31: keys.F12,
+}
+
+
+
+class ApplicationBackend(app.ApplicationBackend):
+    
+    def __init__(self):
+        app.ApplicationBackend.__init__(self)
+    
+    def _vispy_get_backend_name(self):
+        return 'ThisBackendsName' 
+    
+    def _vispy_process_events(self):
+        raise NotImplementedError()
+    
+    def _vispy_run(self):
+        raise NotImplementedError()
+    
+    def _vispy_quit(self):
+        raise NotImplementedError()
+    
+    def _vispy_get_native_app(self):
+        raise NotImplementedError()
+
+
+
+class CanvasBackend(app.CanvasBackend):  # You can mix this class with the native widget
+    
+    def __init__(self, vispy_canvas, *args, **kwargs):
+        #NativeWidgetClass.__init__(self, *args, **kwargs)
+        app.CanvasBackend.__init__(self, vispy_canvas)
+    
+    def _vispy_set_current(self):  
+        # Make this the current context
+        raise NotImplementedError()
+    
+    def _vispy_swap_buffers(self):  
+        # Swap front and back buffer
+        raise NotImplementedError()
+    
+    def _vispy_set_title(self, title):  
+        # Set the window title. Has no effect for widgets
+        raise NotImplementedError()
+    
+    def _vispy_set_size(self, w, h):
+        # Set size of the widget or window
+        raise NotImplementedError()
+    
+    def _vispy_set_position(self, x, y):
+        # Set location of the widget or window. May have no effect for widgets
+        raise NotImplementedError()
+    
+    def _vispy_set_visible(self, visible):
+        # Show or hide the window or widget
+        raise NotImplementedError()
+    
+    def _vispy_update(self):
+        # Invoke a redraw
+        raise NotImplementedError()
+    
+    def _vispy_close(self):
+        # Force the window or widget to shut down
+        raise NotImplementedError()
+    
+    def _vispy_get_size(self):
+        # Should return widget size
+        raise NotImplementedError()
+    
+    def _vispy_get_position(self):
+        # Should return widget (x, y, w, h)
+        raise NotImplementedError()
+    
+    def _vispy_get_native_canvas(self):
+        # Should return the native widget object.
+        # If this is self, this method can be omitted.
+        return self
+    
+    
+    def events_to_emit(self):
+        """ Shown here in one method, but most backends will probably
+        have one method for each event.
+        """
+        if self._vispy_canvas is None:
+            return
+        
+        self._vispy_canvas.events.initialize()
+        self._vispy_canvas.events.resize(size=(w,h)) 
+        self._vispy_canvas.events.paint(region=None) 
+        self._vispy_canvas.events.close()  
+        
+        self._vispy_canvas.events.mouse_press(pos=(x, y), button=1, modifiers=())
+        self._vispy_canvas.events.mouse_release(pos=(x, y), button=1, modifiers=())
+        self._vispy_canvas.events.mouse_move(pos=(x, y), modifiers=())
+        self._vispy_canvas.events.mouse_wheel(pos=(x, y), delta=(0,0), modifiers=())
+        
+        self._vispy_canvas.events.key_press(key=key, text=text, modifiers=())
+        self._vispy_canvas.events.key_release(key=key, text=text, modifiers=())
+
+
+
+class TimerBackend(app.TimerBackend):  # Can be mixed with native timer class
+    def __init__(self, vispy_timer):
+        app.TimerBackend.__init__(self, vispy_timer)
+    
+    def _vispy_start(self, interval):
+        raise NotImplementedError()
+    
+    def _vispy_stop(self):
+        raise NotImplementedError()
+    
+    def _vispy_timeout(self):
+        raise NotImplementedError()
+    
+    def _vispy_get_native_timer(self):
+        # Should return the native widget object.
+        # If this is self, this method can be omitted.
+        return self
diff --git a/vispy/app/canvas.py b/vispy/app/canvas.py
new file mode 100644
index 0000000..02a2f00
--- /dev/null
+++ b/vispy/app/canvas.py
@@ -0,0 +1,616 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2013, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+from __future__ import print_function, division, absolute_import
+
+from vispy.util.event import EmitterGroup, Event
+import vispy
+import numpy as np
+
+# todo: add functions for asking about current mouse/keyboard state
+# todo: add hover enter/exit events
+# todo: add focus events
+
+
+
+class Canvas(object):
+    """ Representation of a GUI element that can be rendered to by an OpenGL
+    context. The args and kwargs are used to instantiate the native widget.
+    
+    Receives the following events:
+    initialize, resize, paint, 
+    mouse_press, mouse_release, mouse_move, mouse_wheel,
+    key_press, key_release,
+    stylus, touch, close
+    
+    Keyword arguments
+    -----------------
+    title :: str
+        The widget title
+    app :: Application
+        Give vispy Application instance to use as a backend.
+        (vispy.app is used by default.)
+    create_native :: bool
+        Whether to create the widget immediately. Default True.
+    size :: (width, height)
+        The size of the window.
+    position :: (x, y)
+        The position of the window in screen coordinates.
+    show :: bool
+        Whether to show the widget immediately. Default False.
+    autoswap :: bool
+        Whether to swap the buffers automatically after a paint event.
+        Default True.
+    
+    """
+    
+    def __init__(self, *args, **kwargs):
+        self.events = EmitterGroup(source=self, 
+                        initialize=Event, 
+                        resize=ResizeEvent,
+                        paint=PaintEvent,
+                        mouse_press=MouseEvent,
+                        mouse_release=MouseEvent,
+                        mouse_move=MouseEvent, 
+                        mouse_wheel=MouseEvent,
+                        key_press=KeyEvent,
+                        key_release=KeyEvent,
+                        stylus=Event,
+                        touch=Event,
+                        close=Event,
+                        )
+        
+        # Store input and initialize backend attribute
+        self._args = args
+        self._kwargs = kwargs
+        self._backend = None
+        
+        # Extract kwargs that are for us
+        # Most are used in _set_backend
+        self._our_kwargs = {}
+        self._our_kwargs['title'] = kwargs.pop('title', 'Vispy canvas')
+        self._our_kwargs['show'] = kwargs.pop('show', False)
+        self._our_kwargs['autoswap'] = kwargs.pop('autoswap', True)
+        self._our_kwargs['size'] = kwargs.pop('size', (800,600))
+        self._our_kwargs['position'] = kwargs.pop('position', None)
+        
+        # Initialise some values
+        self._title = ''
+        
+        # Get app instance 
+        self._app = kwargs.pop('app', vispy.app.default_app)
+        
+        # Create widget now
+        if 'native' in kwargs:
+            self._set_backend(kwargs.pop('native'))
+        else:
+            self.create_native()
+    
+    
+    def create_native(self):
+        """ Create the native widget if not already done so. If the widget
+        is already created, this function does nothing.
+        """
+        if self._backend is None:
+            # Make sure that the app is active
+            self._app.use()
+            self._app.native
+            # Instantiate the backed with the right class
+            self._set_backend(self._app.backend_module.CanvasBackend(*self._args, **self._kwargs))
+
+            # Clean up
+            del self._args 
+            del self._kwargs
+    
+    def _set_backend(self, backend):
+        self._backend = backend
+        if backend is not None:
+            backend._vispy_canvas = self
+        else:
+            return
+        
+        # Initialize it
+        self.title = self._our_kwargs['title']
+        self.size = self._our_kwargs['size']
+        if self._our_kwargs['position']:
+            self.position = self._our_kwargs['position']
+        if self._our_kwargs['autoswap']:
+            fun = lambda x:self._backend._vispy_swap_buffers()
+            self.events.paint.callbacks.append(fun)  # Append callback to end
+        if self._our_kwargs['show']:
+            self.show()
+        
+    
+    @property
+    def app(self):
+        """ The vispy Application instance on which this Canvas is based.
+        """
+        return self._app
+    
+    
+    @property
+    def native(self):
+        """ The native widget object on which this Canvas is based.
+        """
+        return self._backend._vispy_get_native_canvas()
+    
+    
+    def connect(self, fun):
+        """ Connect a function to an event. The name of the function
+        should be on_X, with X the name of the event (e.g. 'on_paint').
+        
+        This method is typically used as a decorater on a function
+        definition for an event handler.
+        """
+        # Get and check name
+        name = fun.__name__
+        if not name.startswith('on_'):
+            raise ValueError('When connecting a function based on its name, the name should start with "on_"')
+        eventname = name[3:]
+        # Get emitter
+        try:
+            emitter = self.events[eventname]
+        except KeyError:
+            raise ValueError('Event "%s" not available on this canvas.' % eventname)
+        # Connect
+        emitter.connect(fun)
+    
+
+
+    # ---------------------------------------------------------------- size ---
+    @property
+    def size(self):
+        """ The size of canvas/window """
+        return self._backend._vispy_get_size()
+    
+    @size.setter
+    def size(self, size):
+        return self._backend._vispy_set_size(size[0],size[1])
+    
+    
+    # ------------------------------------------------------------ position ---
+    @property
+    def position(self):
+        """ The position of canvas/window relative to screen """
+        return self._backend._vispy_get_position()
+        
+    @position.setter
+    def position(self, position):
+        return self._backend._vispy_set_position(position[0],position[1])
+
+    # --------------------------------------------------------------- title ---
+    @property
+    def title(self):
+        """ The title of canvas/window """
+        return self._title
+    
+    @title.setter
+    def title(self, title):
+        self._title = title
+        self._backend._vispy_set_title(title)
+    
+
+    def swap_buffers(self):
+        """ Swap GL buffers such that the offscreen buffer becomes visible.
+        """
+        #if not self._our_kwargs['autoswap']:
+        self._backend._vispy_swap_buffers()
+    
+    
+    def resize(self, w, h):
+        """ Resize the canvas givan size """
+
+        return self._backend._vispy_set_size(w, h)
+
+     
+    def move(self, x, y):
+        """ Move the widget or window to the given position """ 
+
+        self._backend._vispy_set_position(x,y)
+
+    
+    def show(self, visible=True):
+        """ Show (or hide) the canvas """
+
+        return self._backend._vispy_set_visible(visible)
+    
+
+    def update(self):
+        """ Inform the backend that the Canvas needs to be repainted """
+
+        return self._backend._vispy_update()
+
+    
+    def close(self):
+        """ Close the canvas """
+
+        self._backend._vispy_close()
+    
+    
+    #def mouse_event(self, event):
+        #"""Called when a mouse input event has occurred (the mouse has moved,
+        #a button was pressed/released, or the wheel has moved)."""
+        
+    #def key_event(self, event):
+        #"""Called when a keyboard event has occurred (a key was pressed or 
+        #released while the canvas has focus)."""
+        
+    #def touch_event(self, event):
+        #"""Called when the user touches the screen over a Canvas.
+        
+        #Event properties:
+        
+            #event.touches
+                #[ (x,y,pressure), ... ]
+        #"""
+        
+    #def stylus_event(self, event):
+        #"""Called when a stylus has been used to interact with the Canvas.
+        
+        #Event properties:
+        
+            #event.device
+            #event.pos  (x,y)
+            #event.pressure
+            #event.angle
+            
+        #"""
+        
+
+    #def initialize_event(self, event):
+        #"""Called when the OpenGL context is initialy made available for this 
+        #Canvas."""
+        
+    #def resize_event(self, event):
+        #"""Called when the Canvas is resized.
+        
+        #Event properties:
+        
+            #event.size  (w,h)
+        #"""
+        
+    #def paint_event(self, event):
+        #"""Called when all or part of the Canvas needs to be repainted.
+        
+        #Event properties:
+        
+            #event.region  (x,y,w,h) region of Canvas requiring repaint
+        #"""
+    
+
+
+class CanvasBackend(object):
+    """ CanvasBackend(vispy_canvas, *args, **kwargs)
+    
+    Abstract class that provides an interface between backends and Canvas.
+    Each backend must implement a subclass of CanvasBackend, and
+    implement all its _vispy_xxx methods. Also, also a backend must
+    make sure to generate the following events: 'initialize', 'resize',
+    'paint', 'mouse_press', 'mouse_release', 'mouse_move',
+    'mouse_wheel', 'key_press', 'key_release', 'close'.
+    """
+    
+    def __init__(self, *args, **kwargs):
+        # Initially the backend starts out with no canvas.
+        # Canvas takes care of setting this for us.
+        self._vispy_canvas = None
+        
+        # Data used in the construction of new mouse events
+        self._vispy_mouse_data = {
+            'buttons': [],
+            'press_event': None,
+            'last_event': None,
+            }
+    
+    def _vispy_set_current(self):  
+        # todo: this is currently not used internally
+        # --> I think the backends should call this themselves before emitting the paint event
+        # Make this the current context
+        raise NotImplementedError()
+    
+    def _vispy_swap_buffers(self):  
+        # Swap front and back buffer
+        raise NotImplementedError()
+    
+    def _vispy_set_title(self, title):  
+        # Set the window title. Has no effect for widgets
+        raise NotImplementedError()
+    
+    def _vispy_set_size(self, w, h):
+        # Set size of the widget or window
+        raise NotImplementedError()
+    
+    def _vispy_set_position(self, x, y):
+        # Set location of the widget or window. May have no effect for widgets
+        raise NotImplementedError()
+    
+    def _vispy_set_visible(self, visible):
+        # Show or hide the window or widget
+        raise NotImplementedError()
+    
+    def _vispy_update(self):
+        # Invoke a redraw
+        raise NotImplementedError()
+    
+    def _vispy_close(self):
+        # Force the window or widget to shut down
+        raise NotImplementedError()
+    
+    def _vispy_get_size(self):
+        # Should return widget size
+        raise NotImplementedError()
+
+    def _vispy_get_position(self):
+        # Should return widget position
+        raise NotImplementedError()
+    
+    def _vispy_get_native_canvas(self):
+        # Should return the native widget object
+        # Most backends would not need to implement this
+        return self
+
+    def _vispy_mouse_press(self, **kwds):
+        # default method for delivering mouse press events to the canvas
+        kwds.update(self._vispy_mouse_data)
+        ev = self._vispy_canvas.events.mouse_press(**kwds)
+        if self._vispy_mouse_data['press_event'] is None:
+            self._vispy_mouse_data['press_event'] = ev
+            
+        self._vispy_mouse_data['buttons'].append(ev.button)
+        self._vispy_mouse_data['last_event'] = ev
+        return ev
+    
+    def _vispy_mouse_move(self, **kwds):
+        # default method for delivering mouse move events to the canvas
+        kwds.update(self._vispy_mouse_data)
+        
+        # Break the chain of prior mouse events if no buttons are pressed
+        # (this means that during a mouse drag, we have full access to every
+        # move event generated since the drag started)
+        if self._vispy_mouse_data['press_event'] is None:
+            last_event = self._vispy_mouse_data['last_event']
+            if last_event is not None:
+                last_event._forget_last_event()
+        
+        ev = self._vispy_canvas.events.mouse_move(**kwds)
+        self._vispy_mouse_data['last_event'] = ev
+        return ev
+    
+    def _vispy_mouse_release(self, **kwds):
+        # default method for delivering mouse release events to the canvas
+        kwds.update(self._vispy_mouse_data)
+        ev = self._vispy_canvas.events.mouse_release(**kwds)
+        if ev.button == self._vispy_mouse_data['press_event'].button:
+            self._vispy_mouse_data['press_event'] = None
+            
+        self._vispy_mouse_data['buttons'].remove(ev.button)
+        self._vispy_mouse_data['last_event'] = ev
+        return ev
+    
+    
+
+## Event subclasses specific to the Canvas
+
+
+class MouseEvent(Event):
+    """ Class describing mouse events.
+    
+    Note that each event object has an attribute for each of the input
+    arguments listed below.
+    
+    Input arguments
+    ---------------
+    type : str
+       String indicating the event type (e.g. mouse_press, key_release)
+    pos : (int, int)
+        The position of the mouse (in screen coordinates).
+    button : int
+        The button that generated this event (can be None).
+        Left=1, right=2, middle=3.
+    buttons : [int, ...]
+        The list of buttons depressed during this event. 
+    modifiers : tuple of Key instances
+        Tuple that specifies which modifier keys were pressed down at the
+        time of the event (shift, control, alt, meta).
+    delta : (float, float)
+        The amount of scrolling in horizontal and vertical direction. One 
+        "tick" corresponds to a delta of 1.0.
+    press_event : MouseEvent 
+        The press event that was generated at the start of the current drag,
+        if any.
+    last_event : MouseEvent
+        The MouseEvent immediately preceding the current event. During drag
+        operations, all generated events retain their last_event properties,
+        allowing the entire drag to be reconstructed. 
+    native : object (optional)
+       The native GUI event object
+    **kwds : keyword arguments
+        All extra keyword arguments become attributes of the event object.
+    
+    """
+    
+    def __init__(self, type, pos=None, button=None, buttons=None, 
+                 modifiers=None, delta=None, last_event=None, press_event=None,
+                 **kwds):
+        Event.__init__(self, type, **kwds)
+        self._pos = (0,0) if (pos is None) else (pos[0], pos[1])
+        self._button = int(button) if (button is not None) else 0
+        self._buttons = [] if (buttons is None) else buttons
+        self._modifiers = tuple( modifiers or () )
+        self._delta = (0.0,0.0) if (delta is None) else (delta[0], delta[1])
+        self._last_event = last_event
+        self._press_event = press_event
+    
+    @property
+    def pos(self):
+        return self._pos
+    
+    @property
+    def button(self):
+        return self._button
+    
+    @property
+    def buttons(self):
+        return self._buttons
+    
+    @property
+    def modifiers(self):
+        return self._modifiers
+    
+    @property
+    def delta(self):
+        return self._delta
+
+    @property
+    def press_event(self):
+        return self._press_event
+
+    @property
+    def last_event(self):
+        return self._last_event
+
+    def _forget_last_event(self):
+        # Needed to break otherwise endless last-event chains
+        self._last_event = None
+
+    @property
+    def is_dragging(self):
+        """ Indicates whether this event is part of a mouse drag operation.
+        """
+        return self.press_event is not None
+
+    def drag_events(self):
+        """ Return a list of all mouse events in the current drag operation.
+        
+        Returns None if there is no current drag operation.
+        """
+        if not self.is_dragging:
+            return None
+        
+        event = self
+        events = []
+        while True:
+            if event is None:
+                break
+            events.append(event)
+            event = event.last_event
+            
+        return events[::-1]
+
+    def trail(self):
+        """ Return an (N, 2) array of mouse coordinates for every event in the 
+        current mouse drag operation. 
+        
+        Returns None if there is no current drag operation.
+        """
+        events = self.drag_events()
+        if events is None:
+            return None
+        
+        trail = np.empty((len(events), 2), dtype=int)
+        for i, ev in enumerate(events):
+            trail[i] = ev.pos
+            
+        return trail
+        
+        
+
+class KeyEvent(Event):
+    """ Class describing mouse events.
+    
+    Note that each event object has an attribute for each of the input
+    arguments listed below.
+    
+    Input arguments
+    ---------------
+    type : str
+       String indicating the event type (e.g. mouse_press, key_release)
+    key : vispy.keys.Key instance
+        The Key object for this event. Can be compared to string names.
+    text : str
+        The text representation of the key (can be an empty string).
+    modifiers : tuple of Key instances
+        Tuple that specifies which modifier keys were pressed down at the
+        time of the event (shift, control, alt, meta).
+    native : object (optional)
+       The native GUI event object
+    **kwds : keyword arguments
+        All extra keyword arguments become attributes of the event object.
+    """
+    
+    def __init__(self, type, key=None, text='', modifiers=None, **kwds):
+        Event.__init__(self, type, **kwds)
+        self._key = key
+        self._text = text
+        self._modifiers = tuple( modifiers or () )
+    
+    @property
+    def key(self):
+        return self._key
+    
+    @property
+    def text(self):
+        return self._text
+    
+    @property
+    def modifiers(self):
+        return self._modifiers
+
+
+
+class ResizeEvent(Event):
+    """ Class describing canvas resize events.
+    
+    Note that each event object has an attribute for each of the input
+    arguments listed below.
+    
+    Input arguments
+    ---------------
+    type : str
+       String indicating the event type (e.g. mouse_press, key_release)
+    size : (int, int)
+        The new size of the Canvas.
+    native : object (optional)
+       The native GUI event object
+    **kwds : extra keyword arguments
+        All extra keyword arguments become attributes of the event object.
+    """
+    
+    def __init__(self, type, size=None, **kwds):
+        Event.__init__(self, type, **kwds)
+        self._size = tuple(size)
+    
+    @property
+    def size(self):
+        return self._size
+
+
+class PaintEvent(Event):
+    """ Class describing canvas paint events.
+    This type of event is sent to Canvas.events.paint when a repaint 
+    is required.
+    
+    Note that each event object has an attribute for each of the input
+    arguments listed below.
+    
+    Input arguments
+    ---------------
+    type : str
+       String indicating the event type (e.g. mouse_press, key_release)
+    region : (int, int, int, int) or None
+        The region of the canvas which needs to be repainted (x, y, w, h). 
+        If None, the entire canvas must be repainted.
+    native : object (optional)
+       The native GUI event object
+    **kwds : extra keyword arguments
+        All extra keyword arguments become attributes of the event object.
+    """
+    
+    def __init__(self, type, region=None, **kwds):
+        Event.__init__(self, type, **kwds)
+        self._region = region
+    
+    @property 
+    def region(self):
+        return self._region
diff --git a/vispy/app/tests/qt-designer.ui b/vispy/app/tests/qt-designer.ui
new file mode 100644
index 0000000..c84cae6
--- /dev/null
+++ b/vispy/app/tests/qt-designer.ui
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>Form</class>
+ <widget class="QWidget" name="Form">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>609</width>
+    <height>432</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Form</string>
+  </property>
+  <layout class="QGridLayout" name="gridLayout">
+   <item row="0" column="0">
+    <widget class="QLabel" name="label">
+     <property name="text">
+      <string>For testing that vispy Canvas can be embedded in Qt designer GUI</string>
+     </property>
+    </widget>
+   </item>
+   <item row="1" column="0">
+    <widget class="QFrame" name="frame">
+     <property name="frameShape">
+      <enum>QFrame::StyledPanel</enum>
+     </property>
+     <property name="frameShadow">
+      <enum>QFrame::Raised</enum>
+     </property>
+     <layout class="QGridLayout" name="gridLayout_2">
+      <item row="0" column="0">
+       <widget class="CanvasBackend" name="canvas" native="true">
+        <property name="sizePolicy">
+         <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+          <horstretch>0</horstretch>
+          <verstretch>0</verstretch>
+         </sizepolicy>
+        </property>
+       </widget>
+      </item>
+     </layout>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <customwidgets>
+  <customwidget>
+   <class>CanvasBackend</class>
+   <extends>QWidget</extends>
+   <header>vispy.app.backends.qt</header>
+   <container>1</container>
+  </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/vispy/app/tests/test_backends.py b/vispy/app/tests/test_backends.py
new file mode 100644
index 0000000..b40803c
--- /dev/null
+++ b/vispy/app/tests/test_backends.py
@@ -0,0 +1,140 @@
+""" Tests to quickly see if the backends look good.
+This tests only to see if all the necessary methods are implemented,
+whether all the right events are mentioned, and whether the keymap 
+contains all keys that should be supported.
+
+This test basically checks whether nothing was forgotten, not that the
+implementation is corect.
+ 
+"""
+
+import sys
+
+import vispy
+from vispy import keys
+
+class BaseTestmodule:
+    
+    def __init__(self, module=None):
+        self._module = module
+        if module is None:
+            print("Skipping %s." % self.__class__.__name__)
+            self.test_events = lambda : None
+            self.test_keymap = lambda : None
+            self.test_methods = lambda : None
+    
+    
+    def test_keymap(self):
+        """ Test that the keymap contains all keys supported by vispy.
+        """
+        keymap = self._module.KEYMAP
+        vispy_keys = keymap.values()
+        for keyname in dir(keys):
+            if keyname.upper() != keyname:
+                continue
+            key = getattr(keys, keyname)
+            assert key in vispy_keys
+    
+    
+    def test_methods(self):
+        """ Test that all _vispy_x methods are there.
+        """
+        exceptions = ('_vispy_get_native_canvas', '_vispy_get_native_timer', '_vispy_get_native_app', 
+                      '_vispy_mouse_move', '_vispy_mouse_press', '_vispy_mouse_release')
+        
+        Klass = self._module.CanvasBackend
+        KlassRef = vispy.app.canvas.CanvasBackend
+        for key in dir(KlassRef):
+            if not key.startswith('__'):
+                method = getattr(Klass, key)
+                if key not in exceptions:
+                    if hasattr(method, '__module__'):
+                        mod_str = method.__module__ # Py3k
+                    else:
+                        mod_str = method.im_func.__module__
+                    assert mod_str == self._module.__name__, "Method %s.%s not defined in %s"%(Klass, key, self._module.__name__)
+        
+        Klass = self._module.TimerBackend
+        KlassRef = vispy.app.timer.TimerBackend
+        for key in dir(KlassRef):
+            if not key.startswith('__'):
+                method = getattr(Klass, key)
+                if key not in exceptions:
+                    if hasattr(method, '__module__'): 
+                        assert method.__module__ == self._module.__name__ # Py3k
+                    else:
+                        assert method.im_func.__module__ == self._module.__name__
+        
+        Klass = self._module.ApplicationBackend
+        KlassRef = vispy.app.application.ApplicationBackend
+        for key in dir(KlassRef):
+            if not key.startswith('__'):
+                method = getattr(Klass, key)
+                if key not in exceptions:
+                    if hasattr(method, '__module__'): 
+                        assert method.__module__ == self._module.__name__ # Py3k
+                    else:
+                        assert method.im_func.__module__ == self._module.__name__
+    
+    
+    def test_events(self):
+        """ Test that all events seem to be emitted.
+        """
+        # Get text
+        fname = self._module.__file__.strip('c')
+        text = open(fname, 'rb').read().decode('utf-8')
+        
+        canvas = vispy.app.Canvas(native=None)
+        # Stylus and touch are ignored because they are not yet implemented.
+        # Mouse events are emitted from the CanvasBackend base class.
+        ignore = set(['stylus', 'touch', 'mouse_press', 'mouse_move', 'mouse_release'])
+        eventNames = set(canvas.events._emitters.keys()) - ignore
+        
+        for name in eventNames:
+            assert 'events.%s'%name in text, 'events.%s does not appear in %s'%(name, fname)
+
+
+
+class Test_TemplateBackend(BaseTestmodule):
+    def __init__(self):
+        from vispy.app.backends import template
+        BaseTestmodule.__init__(self, template)
+
+class Test_QtBackend(BaseTestmodule):
+    def __init__(self):
+        from vispy.app.backends import qt
+        BaseTestmodule.__init__(self, qt)
+
+class Test_PygletBackend(BaseTestmodule):
+    def __init__(self):
+        if sys.version_info[0] == 3:
+            pyglet = None
+        else:
+            try:
+                from vispy.app.backends import pyglet
+            except Exception as err:
+                print("Error imporing pyglet:\n%s" % str(err))
+                pyglet = None
+        BaseTestmodule.__init__(self, pyglet)
+
+class Test_GlutBackend(BaseTestmodule):
+    def __init__(self):
+        from vispy.app.backends import glut
+        BaseTestmodule.__init__(self, glut)
+
+
+
+if __name__ == '__main__':
+    
+    for klass in [  Test_TemplateBackend, 
+                    Test_QtBackend, 
+                    Test_PygletBackend,
+                    Test_GlutBackend
+                  ]:
+        test = klass()
+        test.test_keymap()
+        test.test_methods()
+        test.test_events()
+        print('ok %s' % klass.__name__)
+    
+    
\ No newline at end of file
diff --git a/vispy/app/tests/test_qt.py b/vispy/app/tests/test_qt.py
new file mode 100644
index 0000000..3ec663a
--- /dev/null
+++ b/vispy/app/tests/test_qt.py
@@ -0,0 +1,53 @@
+# Import PyQt4, vispy will see this and use that as a backend
+# Also import QtOpenGL, because vispy needs it.
+
+# This is a strange test: vispy does not need designer or uic stuff to run!
+
+
+try:
+    from PyQt4 import QtCore, QtGui, QtOpenGL, uic
+    test_uic = True
+except ImportError:
+    from PySide import QtCore, QtGui, QtOpenGL
+    test_uic = False
+
+
+import OpenGL.GL as gl
+from vispy.app import Canvas
+import os
+
+
+app = QtGui.QApplication([])
+
+def test_qt_designer():
+    """Embed Canvas via Qt Designer"""
+    
+    if not test_uic:
+        return
+    
+    path = os.path.dirname(__file__)
+    WindowTemplate, TemplateBaseClass = uic.loadUiType(os.path.join(path, 'qt-designer.ui'))
+    
+    class MainWindow(TemplateBaseClass):  
+        def __init__(self):
+            TemplateBaseClass.__init__(self)
+            
+            self.ui = WindowTemplate()
+            self.ui.setupUi(self)
+            self.show()
+    
+    global win
+    win = MainWindow()
+    win.show()
+    canvas = Canvas(native=win.ui.canvas)
+    
+    @canvas.events.paint.connect
+    def on_paint(ev):
+        gl.glClearColor(0.0, 0.0, 0.0, 0.0)
+        gl.glClear(gl.GL_COLOR_BUFFER_BIT)
+        canvas.swap_buffers()
+
+
+
+if __name__ == '__main__':
+    test_qt_designer()
diff --git a/vispy/app/timer.py b/vispy/app/timer.py
new file mode 100644
index 0000000..5d04540
--- /dev/null
+++ b/vispy/app/timer.py
@@ -0,0 +1,155 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2013, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+from __future__ import print_function, division, absolute_import
+
+import vispy
+from vispy.util.event import Event, EmitterGroup
+from vispy.util.ptime import time as precision_time
+
+
+class Timer(object):
+    """Timer used to schedule events in the future or on a repeating schedule.
+    """
+    def __init__(self, interval=0.0, connect=None, iterations=-1, start=False, app=None):
+        self.events = EmitterGroup(source=self, 
+                        start=Event, 
+                        stop=Event,
+                        timeout=Event)
+        #self.connect = self.events.timeout.connect
+        #self.disconnect = self.events.timeout.disconnect
+        
+        # Get app instance and make sure that it has an associated backend 
+        self._app = vispy.app.default_app if app is None else app
+        self._app.use()
+        
+        # Instantiate the backed with the right class
+        self._backend = self._app.backend_module.TimerBackend(self)
+        
+        self._interval = interval
+        self._running = False
+        self._last_emit_time = None
+        self.iter_count = 0
+        self.max_iterations = iterations
+        if connect is not None:
+            self.connect(connect)
+        if start:
+            self.start()
+            
+        
+    @property
+    def app(self):
+        """ The vispy Application instance on which this Timer is based.
+        """
+        return self._app
+    
+
+    @property
+    def interval(self):
+        return self._interval
+
+    @interval.setter
+    def interval(self, val):
+        self._interval = val
+        if self.running:
+            self.stop()
+            self.start()
+
+    @property
+    def running(self):
+        return self._running
+
+    def start(self, interval=None, iterations=None):
+        """Start the timer. 
+
+        A timeout event will be generated every *interval* seconds.
+        If *interval* is None, then self.interval will be used.
+        
+        If *iterations* is specified, the timer will stop after 
+        emitting that number of events. If unspecified, then 
+        the previous value of self.iterations will be used. If the value is 
+        negative, then the timer will continue running until stop() is called.
+        """
+        self.iter_count = 0
+        if interval is not None:
+            self.interval = interval
+        if iterations is not None:
+            self.max_iterations = iterations
+        self._backend._vispy_start(self.interval)
+        self._running = True
+        self._last_emit_time = None
+        self.events.start(type='timer_start')
+        
+        
+    def stop(self):
+        """Stop the timer."""
+        self._backend._vispy_stop()
+        self._running = False
+        self.events.stop(type='timer_stop')
+        
+    # use timer.app.run() and .quit() instead.
+    #def run_event_loop(self):
+        #"""Execute the event loop for this Timer's backend.
+        #"""
+        #return self._backend._vispy_run()
+        
+    #def quit_event_loop(self):
+        #"""Exit the event loop for this Timer's backend.
+        #"""
+        #return self._backend._vispy_quit()
+    
+    @property
+    def native(self):
+        """ The native timer on which this Timer is based.
+        """
+        return self._backend._vispy_get_native_timer()
+    
+    def _timeout(self, *args):
+        # called when the backend timer has triggered.
+        if not self.running:
+            return
+        if self.max_iterations >= 0 and self.iter_count >= self.max_iterations:
+            self.stop()
+            return
+        
+        # compute dt since last event
+        now = precision_time()
+        if self._last_emit_time is None:
+            dt = None
+        else:
+            dt = now - self._last_emit_time
+        self._last_emit_time = now
+        
+        self.events.timeout(type='timer_timeout', iteration=self.iter_count, dt=dt)
+        self.iter_count += 1
+    
+    def connect(self, callback):
+        """ Alias for self.events.timeout.connect() """
+        return self.events.timeout.connect(callback)
+
+    def disconnect(self, callback=None):
+        """ Alias for self.events.timeout.disconnect() """
+        return self.events.timeout.disconnect(callback)
+
+
+class TimerBackend(object):
+    """ TimerBackend(vispy_timer)
+    
+    Abstract class that provides an interface between backends and Timer.
+    Each backend must implement a subclass of TimerBackend, and
+    implement all its _vispy_xxx methods.
+    """
+    def __init__(self, vispy_timer):
+        self._vispy_timer = vispy_timer
+
+    def _vispy_start(self, interval):
+        raise Exception("Method must be reimplemented in subclass.")
+    
+    def _vispy_stop(self):
+        raise Exception("Method must be reimplemented in subclass.")
+    
+    def _vispy_get_native_timer(self):
+        # Should return the native timer object
+        # Most backends would not need to implement this
+        return self
diff --git a/vispy/data/crate.bz2 b/vispy/data/crate.bz2
new file mode 100644
index 0000000..e3fe0ec
Binary files /dev/null and b/vispy/data/crate.bz2 differ
diff --git a/vispy/data/cube.obj b/vispy/data/cube.obj
new file mode 100644
index 0000000..1496c12
--- /dev/null
+++ b/vispy/data/cube.obj
@@ -0,0 +1,36 @@
+# A cube with normals and texcoords
+# from http://www.3dvia.com/models/B10B55A7B98B9DAF
+
+v -0.8 -0.8 -0.8
+v -0.8 -0.8  0.8
+v -0.8  0.8 -0.8
+v -0.8  0.8  0.8
+v  0.8 -0.8 -0.8
+v  0.8 -0.8  0.8
+v  0.8  0.8 -0.8
+v  0.8  0.8  0.8
+
+vn 0.0 0.0 1.0
+vn 0.0 0.0 -1.0
+vn 0.0 1.0 0.0
+vn 0.0 -1.0 0.0
+vn 1.0 0.0 0.0
+vn -1.0 0.0 0.0
+
+vt 0.0 0.0
+vt 1.0 0.0
+vt 1.0 1.0
+vt 0.0 1.0
+
+f 1/1/2 7/3/2 5/2/2
+f 1/1/2 3/4/2 7/3/2
+f 1/1/6 4/3/6 3/4/6
+f 1/1/6 2/2/6 4/3/6
+f 3/1/3 8/3/3 7/4/3
+f 3/1/3 4/2/3 8/3/3
+f 5/2/5 7/3/5 8/4/5
+f 5/2/5 8/4/5 6/1/5
+f 1/1/4 5/2/4 6/3/4
+f 1/1/4 6/3/4 2/4/4
+f 2/1/1 6/2/1 8/3/1
+f 2/1/1 8/3/1 4/4/1 
diff --git a/vispy/data/triceratops.obj b/vispy/data/triceratops.obj
new file mode 100644
index 0000000..51f93db
--- /dev/null
+++ b/vispy/data/triceratops.obj
@@ -0,0 +1,8492 @@
+v 0.576047 -0.0207502 -0.0851141
+v 0.582657 0.0181254 -0.0958034
+v 0.611766 0.0140973 -0.0836525
+v 0.622981 -0.00535083 -0.0755727
+v 0.630173 -0.0164247 -0.0651383
+v 0.623962 -0.0285185 -0.0484837
+v 0.581535 -0.0343882 -0.0542854
+v 0.676262 -0.0312426 -0.0486167
+v 0.69614 -0.0339758 -0.0527803
+v 0.713868 -0.0478463 -0.04239
+v 0.711244 -0.0554077 -0.0277395
+v 0.670052 -0.0433364 -0.0319622
+v 0.752225 -0.0658573 -0.0420207
+v 0.768474 -0.0644552 -0.0452483
+v 0.790753 -0.0807575 -0.0359299
+v 0.780794 -0.0853621 -0.0258271
+v 0.749601 -0.0734186 -0.0273702
+v 0.810925 -0.0923977 -0.0301267
+v 0.827761 -0.107068 -0.0240051
+v 0.81872 -0.108307 -0.0165094
+v 0.800967 -0.0970024 -0.0200238
+v 0.834816 -0.0839809 -0.0386981
+v 0.84774 -0.099019 -0.0347387
+v 0.839736 -0.102786 -0.0301013
+v 0.822899 -0.088115 -0.0362228
+v 0.79648 -0.0499854 -0.0487877
+v 0.818441 -0.0682315 -0.0421538
+v 0.806524 -0.0723655 -0.0396785
+v 0.784245 -0.0560631 -0.0489968
+v 0.726063 -0.0471219 -0.0586022
+v 0.708335 -0.0332513 -0.0689926
+v 0.723217 -0.0262983 -0.0772712
+v 0.742313 -0.0457197 -0.0618299
+v 0.753541 -0.0166503 -0.0685877
+v 0.770074 -0.0309011 -0.0602207
+v 0.757839 -0.0369788 -0.0604298
+v 0.738744 -0.0175573 -0.0758711
+v 0.696612 -0.0199735 -0.0812516
+v 0.689438 -0.0046001 -0.0940319
+v 0.711495 -0.0130206 -0.0895302
+v 0.701193 0.0288043 -0.113563
+v 0.724579 0.0173796 -0.105416
+v 0.716097 0.00197425 -0.0970896
+v 0.69404 0.0103947 -0.101591
+v 0.754661 0.000742036 -0.0802363
+v 0.750385 -0.00399737 -0.0779984
+v 0.735588 -0.00490446 -0.0852818
+v 0.744068 0.0105008 -0.0936087
+v 0.660085 -0.0135357 -0.0666186
+v 0.652894 -0.00246182 -0.0770531
+v 0.67279 -0.000895342 -0.0835626
+v 0.679964 -0.0162689 -0.0707822
+v 0.636279 0.032501 -0.098434
+v 0.64936 0.046988 -0.108604
+v 0.674542 0.0330288 -0.108836
+v 0.667389 0.0146192 -0.0968638
+v 0.647493 0.0130529 -0.0903543
+v 0.715875 0.0468756 -0.12486
+v 0.732089 0.0653857 -0.127397
+v 0.751673 0.0487022 -0.116885
+v 0.739259 0.035451 -0.116713
+v 0.790085 -0.0121727 -0.0702755
+v 0.770646 0.0023075 -0.0775391
+v 0.760054 0.0120663 -0.0909114
+v 0.772467 0.0253176 -0.0910834
+v 0.806825 0.0027077 -0.0743329
+v 0.741922 0.0780551 -0.118163
+v 0.755544 0.0900024 -0.112463
+v 0.769304 0.0784857 -0.105032
+v 0.761505 0.0613715 -0.107651
+v 0.789422 0.0631307 -0.089787
+v 0.806341 0.0540301 -0.0812234
+v 0.815981 0.0234067 -0.0756558
+v 0.781625 0.0460166 -0.0924063
+v 0.834918 0.0723073 -0.0769397
+v 0.842406 0.0799015 -0.0768737
+v 0.848669 0.0805448 -0.0706486
+v 0.849805 0.0793847 -0.0561671
+v 0.863357 0.0556351 -0.0495064
+v 0.868637 0.0350517 -0.0626156
+v 0.842375 0.0314157 -0.0696811
+v 0.832737 0.0620391 -0.0752487
+v 0.887134 -0.00383458 -0.0562682
+v 0.894698 -0.0235227 -0.0561018
+v 0.884015 -0.0246732 -0.0594027
+v 0.860873 -0.00747048 -0.0633336
+v 0.873977 -0.0396965 -0.0612603
+v 0.874085 -0.0515735 -0.0609413
+v 0.864776 -0.0548995 -0.0588801
+v 0.850835 -0.0224937 -0.0651911
+v 0.850244 -0.0591788 -0.0565272
+v 0.841543 -0.0620689 -0.0522849
+v 0.819562 -0.0416536 -0.0587808
+v 0.836302 -0.0267731 -0.0628382
+v 0.836888 -0.0655695 -0.046296
+v 0.829685 -0.0679102 -0.0428167
+v 0.807726 -0.0496642 -0.0494506
+v 0.814908 -0.0451542 -0.0527919
+v 0.776474 -0.0240446 -0.0563729
+v 0.759941 -0.00979379 -0.06474
+v 0.764217 -0.00505426 -0.0669778
+v 0.783656 -0.0195345 -0.0597143
+v 0.866252 -0.0920335 -0.0451935
+v 0.854929 -0.0760992 -0.0494432
+v 0.863629 -0.0732091 -0.0536854
+v 0.876787 -0.0891576 -0.0491339
+v 0.884321 -0.0681036 -0.0565018
+v 0.892839 -0.0756263 -0.0545574
+v 0.88817 -0.0873781 -0.0498891
+v 0.875012 -0.0714296 -0.0544407
+v 0.90328 -0.0779141 -0.0518626
+v 0.909933 -0.0709615 -0.0491736
+v 0.914466 -0.087542 -0.0365663
+v 0.89861 -0.0896659 -0.0471943
+v 0.91419 -0.106008 -0.0325425
+v 0.906372 -0.128466 -0.0263738
+v 0.898029 -0.127439 -0.0334149
+v 0.898334 -0.108131 -0.0431704
+v 0.880566 -0.128127 -0.0325178
+v 0.876218 -0.111424 -0.040573
+v 0.886754 -0.108548 -0.0445133
+v 0.886448 -0.127856 -0.0347578
+v 0.923541 -0.0513648 -0.0407513
+v 0.928486 -0.0592706 -0.0295434
+v 0.921266 -0.0764229 -0.0329886
+v 0.916734 -0.0598424 -0.0455958
+v 0.903237 -0.0696353 -0.0584457
+v 0.90239 -0.0585382 -0.0608268
+v 0.909889 -0.0626828 -0.0557568
+v 0.903579 -0.0485073 -0.0594384
+v 0.906483 -0.0390542 -0.0560197
+v 0.91108 -0.0526519 -0.0543683
+v 0.916419 -0.0349141 -0.0461604
+v 0.92105 -0.0412219 -0.0439558
+v 0.914242 -0.0496995 -0.0488003
+v 0.909645 -0.0361018 -0.0504517
+v 0.895841 -0.042064 -0.0606768
+v 0.888061 -0.0337614 -0.0605592
+v 0.898743 -0.0326108 -0.0572581
+v 0.912806 -0.0181271 -0.0447116
+v 0.913112 -0.0273841 -0.0460861
+v 0.906338 -0.0285718 -0.0503774
+v 0.898775 -0.00888366 -0.0505438
+v 0.888993 -0.0472648 -0.0614704
+v 0.881321 -0.0508391 -0.0610339
+v 0.881212 -0.0389622 -0.0613528
+v 0.896079 -0.0580182 -0.0619827
+v 0.896926 -0.0691153 -0.0596016
+v 0.888408 -0.0615925 -0.0615461
+v 0.861121 -0.113105 -0.0318934
+v 0.86449 -0.123852 -0.0228061
+v 0.853116 -0.116872 -0.027256
+v 0.86202 -0.0940344 -0.0373215
+v 0.856418 -0.0954789 -0.0341324
+v 0.843493 -0.0804408 -0.0380917
+v 0.850695 -0.0781 -0.041571
+v 0.8745 -0.128523 -0.0244549
+v 0.872445 -0.129893 -0.0174138
+v 0.867919 -0.124012 -0.0202337
+v 0.864551 -0.113265 -0.029321
+v 0.870151 -0.11182 -0.03251
+v 0.934088 -0.0434631 -0.0375663
+v 0.945955 -0.0320448 -0.0298556
+v 0.948923 -0.0372909 -0.0227218
+v 0.939032 -0.0513688 -0.0263582
+v 0.925286 -0.0328002 -0.0419267
+v 0.920654 -0.0264924 -0.0441313
+v 0.920348 -0.0172353 -0.0427567
+v 0.933972 -0.0144184 -0.0351286
+v 0.937153 -0.0213819 -0.0342161
+v 0.919607 -0.00700796 -0.0411337
+v 0.917947 0.00143508 -0.0299441
+v 0.931852 0.000491755 -0.0263716
+v 0.93323 -0.0041911 -0.0335054
+v 0.911702 -0.00196081 -0.0422383
+v 0.897672 0.00728263 -0.0480705
+v 0.892392 0.027866 -0.0349612
+v 0.910042 0.00648223 -0.0310489
+v 0.800145 0.0884009 -0.0906073
+v 0.811861 0.078336 -0.0841408
+v 0.80968 0.0680677 -0.0824498
+v 0.792761 0.0771682 -0.0910134
+v 0.780289 0.122819 -0.104417
+v 0.785555 0.110768 -0.100833
+v 0.778171 0.0995353 -0.10124
+v 0.76441 0.111052 -0.108671
+v 0.763778 0.127118 -0.108441
+v 0.716434 0.103536 -0.129269
+v 0.719491 0.12768 -0.131771
+v 0.744095 0.122852 -0.113529
+v 0.744728 0.106786 -0.11376
+v 0.731106 0.094839 -0.119459
+v 0.71376 0.155493 -0.134362
+v 0.700639 0.190569 -0.135557
+v 0.737515 0.180772 -0.114909
+v 0.752584 0.162607 -0.111627
+v 0.738365 0.150664 -0.116121
+v 0.794915 0.137724 -0.0997083
+v 0.784788 0.134045 -0.102565
+v 0.768277 0.138344 -0.106589
+v 0.782497 0.150287 -0.102095
+v 0.801135 0.186853 -0.0953298
+v 0.810408 0.17283 -0.0924177
+v 0.791007 0.167071 -0.0979993
+v 0.775939 0.185238 -0.101282
+v 0.821578 0.160312 -0.0914248
+v 0.828481 0.153556 -0.0795355
+v 0.830291 0.140656 -0.0809618
+v 0.825295 0.138677 -0.0911548
+v 0.814597 0.14199 -0.0946199
+v 0.802179 0.154553 -0.0970064
+v 0.833624 0.156279 -0.0454886
+v 0.82885 0.152376 -0.0399772
+v 0.836034 0.13708 -0.0359786
+v 0.837019 0.135614 -0.0590838
+v 0.835207 0.148514 -0.0576574
+v 0.844647 0.124564 -0.038729
+v 0.85168 0.105716 -0.0475265
+v 0.850544 0.106877 -0.062008
+v 0.845631 0.123099 -0.061834
+v 0.83888 0.107106 -0.0848898
+v 0.835234 0.121993 -0.0886838
+v 0.840231 0.123972 -0.0784908
+v 0.845145 0.107749 -0.0786647
+v 0.821288 0.158364 -0.0248201
+v 0.817375 0.157633 -0.0146207
+v 0.828473 0.143067 -0.0208215
+v 0.817845 0.117011 -0.0931558
+v 0.813099 0.12905 -0.0957786
+v 0.823797 0.125735 -0.0923135
+v 0.832516 0.106216 -0.0859681
+v 0.822917 0.112378 -0.0906043
+v 0.82887 0.121103 -0.089762
+v 0.821076 0.0875969 -0.0838967
+v 0.818966 0.101353 -0.088467
+v 0.828564 0.0951912 -0.0838306
+v 0.80289 0.0994867 -0.0935797
+v 0.812496 0.103177 -0.0916836
+v 0.814606 0.0894216 -0.0871133
+v 0.805003 0.125255 -0.0995278
+v 0.809749 0.113217 -0.0969051
+v 0.800143 0.109526 -0.0988011
+v 0.794877 0.121577 -0.102385
+v 0.667476 0.0826285 -0.134592
+v 0.690814 0.0852932 -0.136912
+v 0.705488 0.0765964 -0.127102
+v 0.689272 0.0580863 -0.124565
+v 0.664089 0.0720455 -0.124334
+v 0.578928 0.0793589 -0.121524
+v 0.579872 0.119507 -0.134188
+v 0.601375 0.130084 -0.152426
+v 0.623444 0.109143 -0.148482
+v 0.624504 0.100401 -0.129801
+v 0.621118 0.0898179 -0.119542
+v 0.608037 0.0753309 -0.109372
+v 0.846717 0.230278 -0.0882894
+v 0.849679 0.226407 -0.0936541
+v 0.805109 0.208019 -0.0969624
+v 0.804839 0.216963 -0.0871053
+v 0.790252 0.206402 -0.0988133
+v 0.765057 0.204786 -0.104766
+v 0.758431 0.219716 -0.0896436
+v 0.782709 0.214948 -0.0812842
+v 0.789982 0.215345 -0.0889562
+v 0.793692 0.215565 -0.055224
+v 0.801947 0.207412 -0.0426134
+v 0.808657 0.209705 -0.0478152
+v 0.800966 0.215963 -0.062896
+v 0.851807 0.217529 -0.0600574
+v 0.853212 0.229358 -0.0707414
+v 0.811332 0.216044 -0.0695573
+v 0.819024 0.209785 -0.0544763
+v 0.793551 0.207185 -0.0362351
+v 0.785295 0.215338 -0.0488459
+v 0.76102 0.220106 -0.0572052
+v 0.753657 0.225535 -0.0296924
+v 0.784807 0.206213 -0.0259622
+v 0.692329 0.218842 -0.134681
+v 0.681309 0.249473 -0.118561
+v 0.72258 0.223976 -0.0989112
+v 0.729205 0.209046 -0.114033
+v 0.671415 0.260979 -0.0879033
+v 0.663157 0.275281 -0.0517475
+v 0.705325 0.24091 -0.0407407
+v 0.712687 0.235481 -0.0682535
+v 0.816897 0.172876 -0.0300207
+v 0.80779 0.190186 -0.0323626
+v 0.799046 0.189214 -0.0220896
+v 0.812984 0.172145 -0.0198213
+v 0.694152 0.106881 -0.172311
+v 0.695157 0.120551 -0.17421
+v 0.702789 0.127782 -0.154964
+v 0.699731 0.103637 -0.152463
+v 0.689975 0.138933 -0.176597
+v 0.680471 0.158036 -0.179473
+v 0.697608 0.146164 -0.15735
+v 0.658782 0.198222 -0.183412
+v 0.675333 0.19997 -0.161013
+v 0.688456 0.164895 -0.159818
+v 0.671318 0.176767 -0.18194
+v 0.662904 0.0893201 -0.149639
+v 0.661845 0.0980632 -0.168319
+v 0.680664 0.0952296 -0.171807
+v 0.686243 0.0919851 -0.151959
+v 0.6749 0.108718 -0.210499
+v 0.682504 0.104847 -0.208421
+v 0.684402 0.100743 -0.193109
+v 0.665583 0.103577 -0.189622
+v 0.687475 0.111655 -0.20747
+v 0.686079 0.119668 -0.209
+v 0.690376 0.121221 -0.194058
+v 0.689371 0.107551 -0.192159
+v 0.664336 0.149416 -0.218298
+v 0.66424 0.158322 -0.218383
+v 0.67161 0.158162 -0.202592
+v 0.681115 0.13906 -0.199716
+v 0.676819 0.137507 -0.214658
+v 0.669213 0.141379 -0.216736
+v 0.636151 0.19521 -0.221798
+v 0.635735 0.202813 -0.220773
+v 0.64465 0.201077 -0.20506
+v 0.657186 0.179622 -0.203589
+v 0.649815 0.179782 -0.219379
+v 0.640528 0.186625 -0.221357
+v 0.608995 0.241853 -0.223179
+v 0.608885 0.254644 -0.221445
+v 0.619394 0.248883 -0.205491
+v 0.629235 0.224047 -0.206995
+v 0.62032 0.225783 -0.222708
+v 0.613136 0.232034 -0.22364
+v 0.637792 0.245718 -0.182396
+v 0.664184 0.222631 -0.161501
+v 0.647634 0.220882 -0.1839
+v 0.593718 0.311849 -0.213563
+v 0.596722 0.319916 -0.207732
+v 0.606595 0.308655 -0.192465
+v 0.612379 0.284473 -0.201087
+v 0.601871 0.290234 -0.217041
+v 0.595228 0.300251 -0.2169
+v 0.647378 0.271387 -0.141345
+v 0.658399 0.240756 -0.157465
+v 0.632008 0.263844 -0.17836
+v 0.626222 0.288026 -0.169737
+v 0.588105 0.355466 -0.184539
+v 0.590198 0.360974 -0.176124
+v 0.59872 0.346244 -0.16293
+v 0.602417 0.328678 -0.1785
+v 0.592545 0.339939 -0.193769
+v 0.588251 0.347044 -0.19149
+v 0.616361 0.32858 -0.142076
+v 0.641213 0.294375 -0.129255
+v 0.620057 0.311014 -0.157647
+v 0.582262 0.394227 -0.14156
+v 0.585281 0.397494 -0.129834
+v 0.592686 0.377595 -0.121291
+v 0.596132 0.365012 -0.144007
+v 0.58761 0.379741 -0.157201
+v 0.583463 0.3875 -0.151957
+v 0.631103 0.317161 -0.0757958
+v 0.639361 0.302858 -0.111952
+v 0.614509 0.337065 -0.124774
+v 0.611064 0.349647 -0.102056
+v 0.579561 0.421196 -0.072749
+v 0.58151 0.42217 -0.0591262
+v 0.588915 0.399085 -0.0566873
+v 0.591806 0.392737 -0.0880208
+v 0.5844 0.412635 -0.0965633
+v 0.581313 0.417762 -0.0866825
+v 0.599712 0.36394 -0.0493498
+v 0.622642 0.325104 -0.0544227
+v 0.602603 0.357591 -0.0806833
+v 0.85811 0.18798 -0.0845435
+v 0.871996 0.191174 -0.075983
+v 0.836322 0.163218 -0.0749888
+v 0.829421 0.169974 -0.0868782
+v 0.857356 0.209881 -0.0929579
+v 0.850748 0.195476 -0.0910193
+v 0.822058 0.17747 -0.093354
+v 0.812785 0.191493 -0.0962662
+v 0.820787 0.173818 -0.0327004
+v 0.825561 0.177721 -0.0382118
+v 0.818389 0.193419 -0.040244
+v 0.81168 0.191128 -0.0350423
+v 0.829746 0.18564 -0.0432998
+v 0.855358 0.209084 -0.0509129
+v 0.822574 0.201339 -0.045332
+v 0.874344 0.189209 -0.0577722
+v 0.862699 0.192462 -0.0522226
+v 0.837087 0.169018 -0.0446094
+v 0.83867 0.161254 -0.0567782
+v 0.97942 0.26567 -0.0740806
+v 0.939763 0.238213 -0.0749245
+v 0.925878 0.235019 -0.0834851
+v 0.932485 0.249424 -0.0854236
+v 0.980473 0.272216 -0.075667
+v 0.966568 0.27569 -0.0802361
+v 0.97431 0.276951 -0.0791133
+v 0.926324 0.254159 -0.0888699
+v 0.923361 0.258029 -0.0835053
+v 0.968235 0.269219 -0.067374
+v 0.962066 0.270829 -0.0721829
+v 0.91886 0.253168 -0.075452
+v 0.917455 0.241339 -0.0647679
+v 0.937967 0.231591 -0.068233
+v 0.977623 0.259048 -0.0673891
+v 0.977101 0.262723 -0.0652893
+v 0.926323 0.234844 -0.0626832
+v 0.998425 0.281337 -0.0590517
+v 0.998947 0.277661 -0.0611515
+v 1 0.284208 -0.0627379
+v 0.987218 0.287574 -0.0664573
+v 0.993386 0.285963 -0.0616483
+v 0.994961 0.288834 -0.0653345
+v 0.562846 0.146496 -0.159012
+v 0.53498 0.156766 -0.168813
+v 0.539052 0.172784 -0.17112
+v 0.57293 0.181108 -0.183003
+v 0.584348 0.157073 -0.177249
+v 0.614828 0.201047 -0.227817
+v 0.622012 0.194796 -0.226885
+v 0.626389 0.186212 -0.226443
+v 0.626485 0.177305 -0.226358
+v 0.610658 0.17021 -0.204356
+v 0.59924 0.194246 -0.210109
+v 0.640919 0.150326 -0.22101
+v 0.650207 0.143483 -0.219033
+v 0.655084 0.135445 -0.217471
+v 0.656479 0.127432 -0.215941
+v 0.647162 0.12229 -0.195065
+v 0.625092 0.14323 -0.199008
+v 0.532796 0.195328 -0.166184
+v 0.529777 0.206316 -0.160677
+v 0.558983 0.225442 -0.173039
+v 0.566674 0.203653 -0.178067
+v 0.595674 0.248121 -0.223968
+v 0.602316 0.238104 -0.22411
+v 0.606458 0.228285 -0.224571
+v 0.606872 0.220681 -0.225596
+v 0.591285 0.213881 -0.207888
+v 0.583594 0.235669 -0.202861
+v 0.576008 0.31417 -0.215252
+v 0.580301 0.307066 -0.21753
+v 0.581812 0.295466 -0.220866
+v 0.58192 0.282676 -0.222601
+v 0.569841 0.270224 -0.201494
+v 0.565151 0.290837 -0.192333
+v 0.526975 0.216737 -0.149093
+v 0.517056 0.230364 -0.135098
+v 0.55149 0.256475 -0.152295
+v 0.556181 0.235863 -0.161456
+v 0.514641 0.241203 -0.122375
+v 0.523266 0.256599 -0.105746
+v 0.547328 0.283633 -0.124635
+v 0.549076 0.267313 -0.139571
+v 0.57493 0.365053 -0.186255
+v 0.579076 0.357294 -0.191498
+v 0.579223 0.348872 -0.19845
+v 0.576219 0.340806 -0.204281
+v 0.565362 0.317471 -0.181362
+v 0.563613 0.333791 -0.166425
+v 0.522199 0.262364 -0.0927838
+v 0.52178 0.266242 -0.081756
+v 0.545183 0.300484 -0.0893337
+v 0.54626 0.2894 -0.111672
+v 0.56901 0.404451 -0.134472
+v 0.572096 0.399324 -0.144353
+v 0.573297 0.392596 -0.15475
+v 0.571203 0.387089 -0.163166
+v 0.559887 0.355826 -0.143337
+v 0.55881 0.366911 -0.120998
+v 0.521594 0.272108 -0.0616352
+v 0.515111 0.2792 -0.0411964
+v 0.54297 0.313002 -0.0395901
+v 0.544995 0.30635 -0.069213
+v 0.566768 0.423301 -0.0629455
+v 0.570948 0.421824 -0.0756311
+v 0.572701 0.418389 -0.0895646
+v 0.569681 0.415122 -0.101291
+v 0.559483 0.377584 -0.0878167
+v 0.557456 0.384235 -0.0581939
+v 0.472985 0.265817 -0.0976914
+v 0.46436 0.250422 -0.11432
+v 0.403255 0.252973 -0.119857
+v 0.411418 0.288975 -0.0736005
+v 0.466084 0.276786 -0.0662247
+v 0.472568 0.269694 -0.0866637
+v 0.337273 0.268625 -0.124331
+v 0.261359 0.290649 -0.123093
+v 0.260621 0.329781 -0.0762694
+v 0.345436 0.304627 -0.078075
+v 0.163048 0.322953 -0.111397
+v 0.0831232 0.343283 -0.105596
+v 0.0720979 0.381687 -0.0596858
+v 0.16231 0.362084 -0.0645728
+v 0.00557176 0.353601 -0.0977449
+v -0.053449 0.366592 -0.0845468
+v -0.0714394 0.39371 -0.0489281
+v -0.00545322 0.392004 -0.0518346
+v -0.104736 0.360902 -0.0807722
+v -0.148066 0.336912 -0.0904635
+v -0.183784 0.368407 -0.0423029
+v -0.122727 0.388019 -0.0451533
+v -0.26861 0.280877 -0.0811362
+v -0.310748 0.276773 -0.0501689
+v -0.319926 0.298978 -0.0294488
+v -0.245309 0.341585 -0.0346759
+v -0.209591 0.31009 -0.0828366
+v -0.421831 0.184385 -0.0392033
+v -0.476858 0.171062 -0.0248621
+v -0.484308 0.196083 -0.0126633
+v -0.403621 0.235738 -0.0161673
+v -0.394443 0.213531 -0.0368873
+v -0.559605 0.145964 -0.0241277
+v -0.642501 0.132401 -0.0224275
+v -0.648274 0.152238 -0.0132349
+v -0.567054 0.170985 -0.0119288
+v -0.725655 0.126304 -0.0283864
+v -0.808427 0.121864 -0.0259901
+v -0.813936 0.136968 -0.0194066
+v -0.731427 0.146139 -0.0191939
+v -0.891836 0.115885 -0.0208818
+v -0.94897 0.109072 -0.0147629
+v -0.953499 0.118734 -0.0115744
+v -0.897346 0.13099 -0.0142983
+v 0.469462 0.187592 -0.17019
+v 0.472481 0.176604 -0.175697
+v 0.468409 0.160585 -0.173389
+v 0.456341 0.148418 -0.174614
+v 0.425092 0.125748 -0.190888
+v 0.385389 0.149725 -0.191778
+v 0.398436 0.203771 -0.161733
+v 0.459543 0.201219 -0.156196
+v 0.286045 0.162894 -0.196864
+v 0.251768 0.184193 -0.194686
+v 0.251872 0.240278 -0.165128
+v 0.327785 0.218254 -0.166366
+v 0.314739 0.164207 -0.19641
+v 0.183056 0.209852 -0.192321
+v 0.113097 0.223421 -0.193322
+v 0.103235 0.286267 -0.156963
+v 0.18316 0.265937 -0.162763
+v 0.0379705 0.236462 -0.192033
+v -0.031136 0.221939 -0.19526
+v -0.0742436 0.28831 -0.152167
+v -0.0309125 0.3123 -0.142475
+v 0.0281083 0.299308 -0.155673
+v -0.162653 0.148343 -0.214621
+v -0.223942 0.174002 -0.191037
+v -0.22589 0.229951 -0.160757
+v -0.166871 0.259164 -0.162457
+v -0.123763 0.192793 -0.205551
+v -0.345733 0.138049 -0.116105
+v -0.371675 0.14408 -0.0944412
+v -0.378215 0.1765 -0.0851964
+v -0.350828 0.205648 -0.0828805
+v -0.30869 0.209752 -0.113848
+v -0.306742 0.153802 -0.144127
+v -0.413756 0.127765 -0.0686773
+v -0.472867 0.115882 -0.0540008
+v -0.475323 0.146864 -0.0450915
+v -0.420296 0.160186 -0.0594325
+v -0.5531 0.0933116 -0.045209
+v -0.636768 0.0886306 -0.0396022
+v -0.638453 0.110732 -0.0345994
+v -0.555556 0.124295 -0.0362997
+v -0.722223 0.0881458 -0.0363686
+v -0.79124 0.0925846 -0.0314337
+v -0.80668 0.105806 -0.0289694
+v -0.723908 0.110246 -0.0313658
+v -0.87031 0.0873857 -0.0242309
+v -0.88564 0.0764033 -0.0218583
+v -0.941572 0.0854415 -0.015544
+v -0.942884 0.0937936 -0.0156479
+v -0.88575 0.100608 -0.0217666
+v 0.544372 0.120984 -0.147153
+v 0.543429 0.0808354 -0.134488
+v 0.518292 0.0878563 -0.152538
+v 0.504438 0.119087 -0.158178
+v 0.516506 0.131253 -0.156954
+v 0.479378 0.0452714 -0.18116
+v 0.45546 0.0854283 -0.189983
+v 0.486707 0.108099 -0.17371
+v 0.50056 0.0768689 -0.16807
+v 0.295293 0.0478861 -0.213558
+v 0.27718 0.0547564 -0.208545
+v 0.288438 0.10429 -0.211899
+v 0.317133 0.105604 -0.211445
+v 0.384818 0.06774 -0.212121
+v 0.348002 0.0196559 -0.220104
+v 0.323275 0.033999 -0.215124
+v 0.345115 0.0917166 -0.213011
+v 0.228806 0.0665868 -0.200028
+v 0.240857 0.129673 -0.201263
+v 0.275133 0.108375 -0.203441
+v 0.263874 0.0588406 -0.200087
+v 0.261305 -0.0556344 -0.193205
+v 0.245792 -0.0703064 -0.179843
+v 0.231452 -0.0504134 -0.175672
+v 0.258548 -0.00945726 -0.192157
+v 0.276661 -0.0163276 -0.19717
+v 0.289788 -0.0682498 -0.208111
+v 0.27707 -0.0553525 -0.199733
+v 0.292427 -0.0160458 -0.203697
+v 0.317153 -0.0303889 -0.208677
+v 0.216051 -0.0406214 -0.172168
+v 0.190109 -0.034387 -0.175021
+v 0.208079 0.00808077 -0.188594
+v 0.243147 0.00033461 -0.188652
+v 0.109029 0.0908947 -0.211762
+v 0.112971 0.160987 -0.211578
+v 0.18293 0.147416 -0.210578
+v 0.17088 0.0843303 -0.209343
+v 0.13647 -0.035022 -0.177431
+v 0.0993357 -0.0314635 -0.178104
+v 0.0925896 0.0140103 -0.193423
+v 0.15444 0.00744576 -0.191004
+v -0.0138478 0.0883142 -0.214291
+v -0.0342089 0.144302 -0.213146
+v 0.0348976 0.158826 -0.209919
+v 0.0309554 0.0887337 -0.210103
+v -0.00997565 0.0249013 -0.197222
+v 0.0348265 0.0253207 -0.193034
+v 0.0415728 -0.020153 -0.177714
+v 0.0269715 -0.0382201 -0.157489
+v -0.0214319 -0.0281835 -0.173861
+v -0.0263302 0.00227883 -0.199612
+v 0.163195 -0.0667994 -0.154984
+v 0.141394 -0.0849567 -0.134214
+v 0.085518 -0.0750734 -0.138284
+v 0.100118 -0.0570064 -0.158509
+v 0.137253 -0.0605649 -0.157835
+v 0.126592 -0.119251 -0.0722606
+v 0.0665519 -0.116303 -0.0714682
+v 0.0780949 -0.0898146 -0.112663
+v 0.133971 -0.0996978 -0.108594
+v -0.0503152 -0.108258 -0.086544
+v -0.0344234 -0.0762221 -0.131902
+v 0.013981 -0.0862587 -0.115529
+v 0.00243789 -0.112747 -0.0743341
+v 0.206712 -0.110316 -0.119698
+v 0.215321 -0.134772 -0.102076
+v 0.213837 -0.133601 -0.0735281
+v 0.175347 -0.119835 -0.0782957
+v 0.182726 -0.100281 -0.114629
+v 0.229621 -0.0852329 -0.158913
+v 0.217466 -0.0935324 -0.139042
+v 0.19348 -0.0834972 -0.133972
+v 0.215281 -0.0653399 -0.154742
+v 0.53689 0.0164765 -0.134004
+v 0.53028 -0.022399 -0.123315
+v 0.501072 -0.0414964 -0.140517
+v 0.49057 -0.00810008 -0.165144
+v 0.511752 0.0234975 -0.152054
+v 0.377812 -0.00756125 -0.220514
+v 0.414628 0.0405227 -0.212531
+v 0.438547 0.000365768 -0.203708
+v 0.401816 -0.034316 -0.216265
+v 0.437931 -0.0996417 -0.175847
+v 0.426121 -0.0740355 -0.19748
+v 0.462852 -0.0393537 -0.184924
+v 0.473353 -0.0727501 -0.160296
+v 0.535439 -0.0440069 -0.0917995
+v 0.540928 -0.057645 -0.0609709
+v 0.506718 -0.0820054 -0.0674174
+v 0.506232 -0.0631043 -0.109001
+v 0.47519 -0.110538 -0.086518
+v 0.443164 -0.133721 -0.0950432
+v 0.43928 -0.118528 -0.143652
+v 0.474702 -0.0916365 -0.128101
+v 0.356615 -0.147117 -0.194422
+v 0.34533 -0.12602 -0.211582
+v 0.383059 -0.0972259 -0.205799
+v 0.39487 -0.122832 -0.184166
+v 0.361654 -0.184092 -0.135721
+v 0.361647 -0.167403 -0.168908
+v 0.399902 -0.143119 -0.158651
+v 0.403785 -0.158312 -0.110044
+v 0.373195 -0.174646 -0.103737
+v 0.328774 -0.102488 -0.221625
+v 0.315135 -0.0847998 -0.219527
+v 0.3425 -0.0469389 -0.220093
+v 0.366504 -0.0736936 -0.215842
+v 0.315522 -0.201333 -0.073112
+v 0.341875 -0.201397 -0.0954934
+v 0.353416 -0.191952 -0.0635101
+v 0.320788 -0.194281 -0.0515689
+v 0.24251 -0.169078 -0.0769711
+v 0.272865 -0.189957 -0.0655741
+v 0.278131 -0.182905 -0.0440309
+v 0.241025 -0.167907 -0.0484228
+v -0.0927307 -0.107135 -0.1319
+v -0.0824576 -0.0845019 -0.170127
+v -0.0524454 -0.0720305 -0.151083
+v -0.0683383 -0.104067 -0.105725
+v -0.188929 -0.124596 -0.0678925
+v -0.140986 -0.125019 -0.089478
+v -0.116594 -0.12195 -0.0633032
+v -0.142351 -0.13087 -0.0357239
+v -0.18342 -0.120491 -0.0397017
+v -0.288275 -0.0895305 -0.0739375
+v -0.251441 -0.105811 -0.0659419
+v -0.245932 -0.101707 -0.0377512
+v -0.289914 -0.0795559 -0.0355675
+v -0.299079 -0.0708774 -0.055158
+v -0.312977 -0.0556063 -0.122405
+v -0.307087 -0.0685238 -0.0971418
+v -0.317892 -0.0498707 -0.0783621
+v -0.326112 -0.0271673 -0.0960659
+v -0.37407 0.00187976 -0.0422651
+v -0.34344 -0.0118415 -0.0660774
+v -0.335219 -0.034545 -0.0483737
+v -0.326054 -0.0432233 -0.0287831
+v -0.369547 -0.0230476 -0.0232196
+v -0.478969 -0.0117248 -0.00662729
+v -0.474941 0.00682689 -0.0196353
+v -0.415555 0.00667335 -0.0267954
+v -0.411033 -0.018254 -0.00774989
+v -0.379824 0.0819365 -0.0880022
+v -0.353883 0.0759055 -0.109666
+v -0.34527 0.0434951 -0.117431
+v -0.349893 0.0243332 -0.096138
+v -0.380523 0.0380545 -0.0723258
+v -0.469631 0.0378864 -0.0451106
+v -0.468657 0.0697311 -0.0532706
+v -0.409547 0.0816149 -0.067947
+v -0.410246 0.037733 -0.0522707
+v -0.281457 -0.0375015 -0.186139
+v -0.302771 -0.0391935 -0.152212
+v -0.315907 -0.0107546 -0.125873
+v -0.311284 0.00840737 -0.147167
+v -0.279693 0.000282003 -0.179226
+v -0.286845 0.0921601 -0.171536
+v -0.285632 0.0358713 -0.183339
+v -0.317224 0.0439965 -0.15128
+v -0.325837 0.0764069 -0.143514
+v -0.104377 -0.0451091 -0.217144
+v -0.13755 -0.0158359 -0.237966
+v -0.122159 0.031985 -0.229387
+v -0.0792628 -0.00217542 -0.223851
+v -0.0743644 -0.0326377 -0.198101
+v -0.103693 0.0600254 -0.227785
+v -0.0648025 0.104475 -0.218714
+v -0.0444414 0.0484875 -0.219859
+v -0.060796 0.025865 -0.222249
+v -0.19191 0.0197337 -0.242021
+v -0.236596 0.0369239 -0.22166
+v -0.23781 0.0932134 -0.209857
+v -0.17652 0.0675545 -0.233442
+v -0.199027 -0.0274044 -0.247113
+v -0.205674 -0.0583036 -0.25182
+v -0.219569 -0.0606616 -0.253697
+v -0.245477 -0.0479978 -0.233664
+v -0.243713 -0.0102142 -0.226752
+v -0.258323 -0.14543 -0.255062
+v -0.247794 -0.0788197 -0.238951
+v -0.221885 -0.0914835 -0.258983
+v -0.230145 -0.151363 -0.266025
+v -0.163251 -0.121153 -0.242252
+v -0.172397 -0.137449 -0.243335
+v -0.189831 -0.176373 -0.253828
+v -0.206165 -0.163272 -0.264265
+v -0.197906 -0.103392 -0.257223
+v -0.184011 -0.101034 -0.255345
+v -0.15966 -0.0511523 -0.246766
+v -0.126486 -0.0804254 -0.225944
+v -0.145545 -0.10217 -0.238379
+v -0.166307 -0.0820513 -0.251471
+v -0.119715 -0.121441 -0.155853
+v -0.126938 -0.131488 -0.180172
+v -0.128502 -0.120553 -0.206514
+v -0.109443 -0.0988078 -0.194079
+v -0.554039 0.0295572 -0.0410156
+v -0.63897 0.034944 -0.0411615
+v -0.636733 0.0567209 -0.0435688
+v -0.553065 0.0614019 -0.0491755
+v -0.80566 0.0628366 -0.0299543
+v -0.790331 0.0738189 -0.032327
+v -0.721314 0.0693806 -0.0372619
+v -0.72355 0.0476036 -0.0348547
+v -0.560898 -0.00725362 -0.017607
+v -0.64564 0.00118615 -0.0203534
+v -0.641802 0.0166848 -0.0307609
+v -0.556872 0.0112981 -0.030615
+v -0.734448 0.0139314 -0.020359
+v -0.815297 0.0332607 -0.0190594
+v -0.812719 0.044663 -0.025866
+v -0.730609 0.0294301 -0.0307665
+v -0.890367 0.0552206 -0.0138549
+v -0.94479 0.0700912 -0.0111591
+v -0.943721 0.0756612 -0.0143476
+v -0.887789 0.066623 -0.0206617
+v -0.265294 -0.331979 -0.295331
+v -0.286819 -0.329957 -0.282884
+v -0.282921 -0.245349 -0.267962
+v -0.254741 -0.251282 -0.278924
+v -0.222091 -0.338779 -0.292851
+v -0.238057 -0.334532 -0.290063
+v -0.227505 -0.253834 -0.273655
+v -0.21117 -0.266935 -0.263219
+v -0.209501 -0.331131 -0.270273
+v -0.214409 -0.331446 -0.286059
+v -0.306336 -0.0767791 -0.173598
+v -0.285022 -0.0750871 -0.207524
+v -0.295552 -0.141698 -0.223636
+v -0.311609 -0.141741 -0.19796
+v -0.311091 -0.324422 -0.263433
+v -0.320429 -0.324253 -0.240694
+v -0.323251 -0.239857 -0.222836
+v -0.307193 -0.239814 -0.248511
+v -0.302292 -0.0942807 -0.121389
+v -0.308182 -0.0813633 -0.146652
+v -0.313455 -0.146324 -0.171015
+v -0.304988 -0.154091 -0.148414
+v -0.321312 -0.329106 -0.215815
+v -0.314001 -0.333311 -0.197631
+v -0.315668 -0.252478 -0.175355
+v -0.324133 -0.244711 -0.197956
+v -0.248661 -0.120228 -0.0922972
+v -0.285494 -0.103947 -0.100293
+v -0.28819 -0.163758 -0.127317
+v -0.259579 -0.173989 -0.12213
+v -0.294207 -0.345273 -0.180151
+v -0.273421 -0.349172 -0.174066
+v -0.267262 -0.274673 -0.152687
+v -0.295873 -0.264442 -0.157875
+v -0.15151 -0.136318 -0.124251
+v -0.199454 -0.135894 -0.102666
+v -0.210372 -0.189655 -0.132498
+v -0.187127 -0.197307 -0.157381
+v -0.168459 -0.156482 -0.153549
+v -0.158734 -0.146364 -0.148571
+v -0.226675 -0.354959 -0.184348
+v -0.21891 -0.342402 -0.196324
+v -0.215372 -0.285954 -0.185029
+v -0.238616 -0.278303 -0.160145
+v -0.244775 -0.352803 -0.181523
+v -0.184259 -0.206127 -0.233022
+v -0.166825 -0.167204 -0.222529
+v -0.16584 -0.171962 -0.200083
+v -0.184508 -0.212786 -0.203915
+v -0.206405 -0.33823 -0.231498
+v -0.200949 -0.339319 -0.256362
+v -0.202617 -0.275122 -0.249309
+v -0.202867 -0.281782 -0.220202
+v -0.137936 -0.138928 -0.219629
+v -0.136372 -0.149863 -0.193287
+v -0.146098 -0.159981 -0.198264
+v -0.147082 -0.155222 -0.220711
+v -0.260305 -0.39941 -0.309169
+v -0.24571 -0.432487 -0.28787
+v -0.281828 -0.397388 -0.296721
+v -0.259318 -0.432487 -0.262656
+v -0.304775 -0.397218 -0.248769
+v -0.295436 -0.397388 -0.271507
+v -0.257909 -0.432487 -0.24125
+v -0.296055 -0.401422 -0.209178
+v -0.303366 -0.397218 -0.227362
+v -0.248983 -0.432487 -0.232282
+v -0.266342 -0.40532 -0.194125
+v -0.287129 -0.401422 -0.20021
+v -0.223204 -0.432487 -0.22693
+v -0.211776 -0.424982 -0.197271
+v -0.222463 -0.407476 -0.191598
+v -0.240564 -0.40532 -0.188773
+v -0.187932 -0.402091 -0.200919
+v -0.188948 -0.395101 -0.20427
+v -0.20332 -0.372405 -0.206844
+v -0.211085 -0.384962 -0.194867
+v -0.200398 -0.402468 -0.200539
+v -0.162949 -0.433091 -0.213581
+v -0.171461 -0.424605 -0.207416
+v -0.183927 -0.424982 -0.207036
+v -0.195355 -0.432487 -0.236697
+v -0.157853 -0.426101 -0.226789
+v -0.156837 -0.433091 -0.223439
+v -0.189243 -0.432487 -0.246554
+v -0.153672 -0.433091 -0.241335
+v -0.155933 -0.426101 -0.234544
+v -0.165475 -0.424975 -0.297936
+v -0.161844 -0.433268 -0.292551
+v -0.194858 -0.432487 -0.280652
+v -0.170184 -0.433151 -0.310322
+v -0.168647 -0.424858 -0.304425
+v -0.187684 -0.425006 -0.325986
+v -0.181088 -0.433151 -0.323202
+v -0.205761 -0.432487 -0.293533
+v -0.198487 -0.425284 -0.325874
+v -0.239321 -0.39941 -0.31956
+v -0.223355 -0.403658 -0.322349
+v -0.217454 -0.425284 -0.330604
+v -0.224728 -0.432487 -0.298263
+v -0.192744 -0.395102 -0.314592
+v -0.194281 -0.403395 -0.320489
+v -0.205086 -0.403673 -0.320378
+v -0.210986 -0.382047 -0.312122
+v -0.203305 -0.374714 -0.30533
+v -0.174184 -0.395332 -0.289318
+v -0.172298 -0.402974 -0.295171
+v -0.175469 -0.402857 -0.30166
+v -0.182066 -0.394711 -0.304444
+v -0.192626 -0.374322 -0.295181
+v -0.187719 -0.374007 -0.279396
+v -0.175172 -0.399069 -0.219657
+v -0.16666 -0.407555 -0.225822
+v -0.164741 -0.407556 -0.233576
+v -0.168745 -0.399914 -0.240576
+v -0.184088 -0.377464 -0.247094
+v -0.189544 -0.376375 -0.222231
+v 0.337163 -0.210494 -0.142074
+v 0.324252 -0.234896 -0.149139
+v 0.321706 -0.226976 -0.178491
+v 0.337155 -0.193805 -0.175262
+v 0.295077 -0.203241 -0.209
+v 0.286511 -0.2089 -0.224518
+v 0.278464 -0.194195 -0.227762
+v 0.272407 -0.168879 -0.228055
+v 0.276458 -0.160613 -0.227698
+v 0.299241 -0.148974 -0.222932
+v 0.310527 -0.17007 -0.205772
+v 0.279535 -0.143782 -0.230272
+v 0.270248 -0.133552 -0.225146
+v 0.288678 -0.114454 -0.223408
+v 0.302317 -0.132141 -0.225506
+v 0.254515 -0.119716 -0.215553
+v 0.241225 -0.120033 -0.199351
+v 0.244713 -0.102392 -0.192074
+v 0.260227 -0.0877198 -0.205437
+v 0.272944 -0.100617 -0.213815
+v 0.208288 -0.179992 -0.166539
+v 0.215953 -0.183722 -0.138183
+v 0.217024 -0.157143 -0.122611
+v 0.208415 -0.132687 -0.140233
+v 0.22057 -0.124387 -0.160104
+v 0.217081 -0.142029 -0.167381
+v 0.210378 -0.151667 -0.166948
+v 0.236913 -0.22968 -0.126701
+v 0.235099 -0.262146 -0.130276
+v 0.26135 -0.27003 -0.120046
+v 0.261989 -0.242474 -0.113423
+v 0.226285 -0.130526 -0.19513
+v 0.239575 -0.130208 -0.211332
+v 0.231509 -0.143635 -0.211276
+v 0.219581 -0.140164 -0.194698
+v 0.23026 -0.196506 -0.216539
+v 0.21232 -0.18691 -0.197211
+v 0.21441 -0.158585 -0.19762
+v 0.226338 -0.162057 -0.214199
+v 0.269386 -0.202158 -0.231408
+v 0.253949 -0.206223 -0.228501
+v 0.250027 -0.171775 -0.226161
+v 0.263328 -0.176842 -0.2317
+v 0.254771 -0.137433 -0.226734
+v 0.264058 -0.147662 -0.23186
+v 0.260006 -0.155927 -0.232217
+v 0.246705 -0.15086 -0.226678
+v 0.266901 -0.290591 -0.228163
+v 0.268801 -0.261259 -0.22814
+v 0.284238 -0.257194 -0.231047
+v 0.292284 -0.271898 -0.227802
+v 0.300374 -0.293711 -0.21582
+v 0.296113 -0.310045 -0.221876
+v 0.279309 -0.305611 -0.229271
+v 0.310139 -0.255318 -0.202909
+v 0.309663 -0.282789 -0.206445
+v 0.301574 -0.260976 -0.218426
+v 0.314899 -0.287309 -0.156178
+v 0.31495 -0.285701 -0.183326
+v 0.315426 -0.25823 -0.17979
+v 0.317972 -0.266148 -0.150439
+v 0.288299 -0.259776 -0.107407
+v 0.28766 -0.287331 -0.11403
+v 0.305019 -0.286908 -0.128901
+v 0.308092 -0.265748 -0.123162
+v 0.298954 -0.215861 -0.0831932
+v 0.292604 -0.234354 -0.0968838
+v 0.312396 -0.240326 -0.112639
+v 0.325308 -0.215924 -0.105575
+v 0.238251 -0.325107 -0.225414
+v 0.229013 -0.339854 -0.224886
+v 0.2188 -0.336065 -0.205355
+v 0.213465 -0.29131 -0.203841
+v 0.230122 -0.294077 -0.220874
+v 0.242529 -0.309097 -0.221981
+v 0.299565 -0.356885 -0.227344
+v 0.290035 -0.361559 -0.236649
+v 0.276398 -0.345352 -0.231743
+v 0.280677 -0.329342 -0.22831
+v 0.297481 -0.333777 -0.220916
+v 0.313502 -0.302943 -0.165735
+v 0.309281 -0.317269 -0.171342
+v 0.309293 -0.31767 -0.19894
+v 0.313554 -0.301336 -0.192884
+v 0.311144 -0.35938 -0.181372
+v 0.312933 -0.360732 -0.202065
+v 0.303647 -0.355828 -0.211624
+v 0.301563 -0.332721 -0.205195
+v 0.301552 -0.33232 -0.177597
+v 0.28735 -0.299663 -0.115608
+v 0.300487 -0.313565 -0.136084
+v 0.304708 -0.299239 -0.130478
+v 0.276149 -0.349768 -0.117052
+v 0.294328 -0.353678 -0.123533
+v 0.302632 -0.354915 -0.144891
+v 0.29304 -0.327855 -0.141115
+v 0.279903 -0.313952 -0.120639
+v 0.236405 -0.209947 -0.104902
+v 0.261481 -0.222741 -0.0916233
+v 0.267831 -0.204248 -0.0779327
+v 0.237476 -0.183367 -0.0893297
+v 0.234786 -0.341729 -0.116531
+v 0.254061 -0.339575 -0.103997
+v 0.257814 -0.303759 -0.107585
+v 0.231562 -0.295877 -0.117814
+v 0.206825 -0.229916 -0.174136
+v 0.206209 -0.266076 -0.176453
+v 0.212676 -0.266112 -0.149354
+v 0.214491 -0.233646 -0.14578
+v 0.208778 -0.337311 -0.17533
+v 0.213135 -0.338444 -0.145434
+v 0.209911 -0.292591 -0.146717
+v 0.203443 -0.292555 -0.173816
+v 0.232946 -0.239932 -0.223855
+v 0.231046 -0.269263 -0.223879
+v 0.21439 -0.266496 -0.206846
+v 0.215006 -0.230335 -0.204528
+v 0.247477 -0.432487 -0.186849
+v 0.227732 -0.401687 -0.152391
+v 0.223376 -0.400554 -0.182288
+v 0.252082 -0.423534 -0.234892
+v 0.253336 -0.432487 -0.214658
+v 0.229234 -0.400554 -0.210096
+v 0.239447 -0.404342 -0.229626
+v 0.288127 -0.432487 -0.131402
+v 0.269631 -0.425122 -0.106016
+v 0.25866 -0.399533 -0.107007
+v 0.239386 -0.401687 -0.11954
+v 0.259131 -0.432487 -0.153999
+v 0.275831 -0.385857 -0.247909
+v 0.265592 -0.403589 -0.24774
+v 0.252956 -0.384399 -0.242475
+v 0.262194 -0.369651 -0.243004
+v 0.293827 -0.411329 -0.245282
+v 0.282556 -0.407665 -0.244868
+v 0.292794 -0.389933 -0.245038
+v 0.302324 -0.385259 -0.235733
+v 0.307169 -0.407644 -0.240324
+v 0.335646 -0.401378 -0.219637
+v 0.332861 -0.40667 -0.225534
+v 0.320429 -0.403488 -0.228245
+v 0.315584 -0.381102 -0.223653
+v 0.324871 -0.386006 -0.214095
+v 0.263996 -0.432487 -0.223704
+v 0.262743 -0.423534 -0.243939
+v 0.274013 -0.427197 -0.244352
+v 0.310357 -0.423513 -0.236469
+v 0.286998 -0.432487 -0.220779
+v 0.297015 -0.427197 -0.241427
+v 0.334356 -0.426695 -0.227337
+v 0.340505 -0.434243 -0.222554
+v 0.32756 -0.432487 -0.19176
+v 0.298564 -0.432487 -0.214358
+v 0.321924 -0.423513 -0.230047
+v 0.355323 -0.434243 -0.208062
+v 0.358108 -0.428951 -0.202165
+v 0.359926 -0.428215 -0.193809
+v 0.361388 -0.433507 -0.188482
+v 0.342378 -0.432487 -0.177268
+v 0.35068 -0.414912 -0.201014
+v 0.344531 -0.407364 -0.205796
+v 0.333755 -0.391993 -0.200254
+v 0.331966 -0.390639 -0.179561
+v 0.348653 -0.408651 -0.186629
+v 0.352496 -0.414176 -0.192657
+v 0.343911 -0.427829 -0.130335
+v 0.342352 -0.433587 -0.125672
+v 0.335278 -0.432487 -0.146701
+v 0.350965 -0.433741 -0.142651
+v 0.347417 -0.427983 -0.13679
+v 0.328457 -0.404224 -0.123762
+v 0.338048 -0.412552 -0.13007
+v 0.341556 -0.412705 -0.136525
+v 0.340389 -0.407181 -0.142019
+v 0.326433 -0.389402 -0.14988
+v 0.318129 -0.388165 -0.128523
+v 0.332354 -0.433587 -0.113037
+v 0.322763 -0.42526 -0.10673
+v 0.306784 -0.425122 -0.108681
+v 0.32528 -0.432487 -0.134066
+v 0.307315 -0.403166 -0.102632
+v 0.308873 -0.397408 -0.107294
+v 0.298544 -0.381349 -0.112054
+v 0.280365 -0.377439 -0.105573
+v 0.291336 -0.403028 -0.104582
+v 0.675962 0.125057 -0.225408
+v 0.670679 0.131637 -0.223159
+v 0.678284 0.127766 -0.22108
+v 0.682463 0.11439 -0.217849
+v 0.678746 0.119694 -0.223706
+v 0.681068 0.122404 -0.219379
+v 0.673363 0.116215 -0.220213
+v 0.677251 0.117648 -0.223993
+v 0.680969 0.112344 -0.218135
+v 0.666432 0.129853 -0.22338
+v 0.671715 0.123274 -0.225629
+v 0.667826 0.12184 -0.221849
+v 0.643617 0.169923 -0.234793
+v 0.638173 0.168921 -0.231649
+v 0.638077 0.177828 -0.231734
+v 0.651615 0.171108 -0.228228
+v 0.647868 0.170047 -0.233264
+v 0.642327 0.177952 -0.230205
+v 0.656048 0.155751 -0.227844
+v 0.652204 0.163596 -0.232965
+v 0.655951 0.164658 -0.227928
+v 0.642512 0.160811 -0.230042
+v 0.647957 0.161812 -0.233186
+v 0.6518 0.153967 -0.228064
+v 0.619792 0.217008 -0.236348
+v 0.61537 0.215736 -0.233325
+v 0.614954 0.223339 -0.2323
+v 0.624146 0.218216 -0.231088
+v 0.621799 0.218135 -0.236068
+v 0.616963 0.224466 -0.23202
+v 0.629196 0.203707 -0.231531
+v 0.626433 0.21123 -0.235487
+v 0.62878 0.21131 -0.230506
+v 0.617761 0.209833 -0.233992
+v 0.622183 0.211105 -0.237015
+v 0.624945 0.203583 -0.23306
+v 0.599909 0.264758 -0.234048
+v 0.602531 0.252796 -0.230916
+v 0.595887 0.262812 -0.230774
+v 0.591643 0.285991 -0.228628
+v 0.595775 0.275146 -0.233636
+v 0.591753 0.273201 -0.230362
+v 0.602321 0.277413 -0.227578
+v 0.599808 0.276584 -0.232444
+v 0.595677 0.28743 -0.227436
+v 0.604538 0.253923 -0.230636
+v 0.601916 0.265885 -0.233768
+v 0.604428 0.266714 -0.228902
+v 0.58794 0.338328 -0.214683
+v 0.582884 0.335901 -0.213041
+v 0.585888 0.343967 -0.207212
+v 0.592896 0.336313 -0.207397
+v 0.590654 0.337778 -0.212591
+v 0.588602 0.343417 -0.205119
+v 0.591148 0.322227 -0.217425
+v 0.59191 0.331759 -0.216788
+v 0.594152 0.330293 -0.211595
+v 0.582821 0.327893 -0.21634
+v 0.587877 0.33032 -0.217982
+v 0.587114 0.320789 -0.218617
+v 0.588518 0.383679 -0.174065
+v 0.585333 0.387591 -0.164916
+v 0.589479 0.379831 -0.170161
+v 0.588164 0.368682 -0.184264
+v 0.589297 0.378037 -0.179753
+v 0.590257 0.37419 -0.175849
+v 0.581302 0.376991 -0.181112
+v 0.586582 0.378587 -0.181845
+v 0.58545 0.369232 -0.186357
+v 0.582276 0.389123 -0.165756
+v 0.585463 0.385211 -0.174904
+v 0.580183 0.383615 -0.174172
+v 0.582959 0.420146 -0.114323
+v 0.581396 0.420718 -0.102116
+v 0.584483 0.415591 -0.111996
+v 0.581728 0.407773 -0.133724
+v 0.583224 0.415594 -0.124325
+v 0.584747 0.411039 -0.121998
+v 0.575586 0.414431 -0.124684
+v 0.580167 0.417126 -0.125164
+v 0.578672 0.409305 -0.134564
+v 0.578806 0.420907 -0.102982
+v 0.58037 0.420334 -0.115189
+v 0.575788 0.41764 -0.114708
+v 0.572829 0.436578 -0.0483799
+v 0.57355 0.431209 -0.0611815
+v 0.569369 0.432687 -0.0484959
+v 0.570042 0.434839 -0.0222783
+v 0.571552 0.437756 -0.0357851
+v 0.568092 0.433865 -0.0359011
+v 0.577302 0.433082 -0.034964
+v 0.574631 0.437478 -0.0357851
+v 0.57312 0.434561 -0.0222783
+v 0.576139 0.431021 -0.0603151
+v 0.575417 0.43639 -0.0475135
+v 0.578088 0.431994 -0.0466924
+v -0.17056 -0.398186 -0.260113
+v -0.168299 -0.405177 -0.266904
+v -0.168738 -0.405353 -0.271169
+v -0.172369 -0.39706 -0.276554
+v -0.185903 -0.375736 -0.266631
+v -0.160484 -0.42545 -0.265384
+v -0.156481 -0.433091 -0.258384
+v -0.19205 -0.432487 -0.263603
+v -0.159036 -0.433268 -0.275502
+v -0.160924 -0.425627 -0.26965
+v 0.34735 -0.413324 -0.166462
+v 0.345886 -0.408032 -0.171789
+v 0.329199 -0.390021 -0.164721
+v 0.343156 -0.4078 -0.15686
+v 0.346703 -0.413559 -0.16272
+v 0.357837 -0.433507 -0.173198
+v 0.353994 -0.427982 -0.16717
+v 0.353349 -0.428217 -0.163429
+v 0.354515 -0.433741 -0.157935
+v 0.338829 -0.432487 -0.161985
+v 0.363307 -0.426265 -0.150101
+v 0.358852 -0.423284 -0.156915
+v 0.355304 -0.417526 -0.151054
+v 0.355067 -0.422737 -0.138026
+v 0.361902 -0.42595 -0.142567
+v 0.3539 -0.417212 -0.143519
+v 0.361591 -0.436251 -0.144021
+v 0.364878 -0.433706 -0.142702
+v 0.358043 -0.430492 -0.138161
+v 0.362226 -0.430726 -0.157274
+v 0.366681 -0.433706 -0.150461
+v 0.363393 -0.436251 -0.151781
+v 0.368899 -0.425796 -0.180249
+v 0.364576 -0.423127 -0.187029
+v 0.360733 -0.417602 -0.181001
+v 0.36079 -0.42258 -0.16814
+v 0.367495 -0.425482 -0.172714
+v 0.359328 -0.417288 -0.173466
+v 0.368008 -0.435547 -0.174527
+v 0.370869 -0.432924 -0.173074
+v 0.364165 -0.430022 -0.168499
+v 0.368347 -0.430254 -0.187614
+v 0.372671 -0.432924 -0.180834
+v 0.369809 -0.435547 -0.182286
+v 0.354914 -0.425023 -0.228123
+v 0.345681 -0.420042 -0.229805
+v 0.348466 -0.41475 -0.223908
+v 0.359125 -0.425338 -0.212099
+v 0.359426 -0.428062 -0.221095
+v 0.352976 -0.417789 -0.216882
+v 0.360111 -0.437756 -0.21858
+v 0.363196 -0.43519 -0.22168
+v 0.362897 -0.432465 -0.212684
+v 0.34644 -0.430208 -0.23072
+v 0.355673 -0.43519 -0.229037
+v 0.352588 -0.437756 -0.225937
+v -0.173174 -0.418638 -0.326732
+v -0.181494 -0.415363 -0.327051
+v -0.179956 -0.40707 -0.321153
+v -0.167939 -0.415017 -0.313218
+v -0.167753 -0.418439 -0.32158
+v -0.174535 -0.406871 -0.316002
+v -0.166013 -0.43448 -0.320519
+v -0.164288 -0.429608 -0.322983
+v -0.164476 -0.426187 -0.314621
+v -0.178144 -0.426335 -0.329841
+v -0.169824 -0.429608 -0.329523
+v -0.171548 -0.43448 -0.327058
+v -0.151738 -0.419077 -0.288915
+v -0.158422 -0.415368 -0.293749
+v -0.16031 -0.407727 -0.287896
+v -0.155758 -0.416897 -0.276032
+v -0.150816 -0.419954 -0.282434
+v -0.159388 -0.408604 -0.281416
+v -0.149902 -0.434831 -0.281113
+v -0.146849 -0.430246 -0.281663
+v -0.15179 -0.427189 -0.27526
+v -0.154959 -0.426538 -0.295153
+v -0.148273 -0.430246 -0.290318
+v -0.151328 -0.434831 -0.289768
+v -0.14249 -0.429979 -0.247515
+v -0.147302 -0.427311 -0.240017
+v -0.145041 -0.434301 -0.246807
+v -0.150471 -0.42666 -0.262462
+v -0.143916 -0.429979 -0.256171
+v -0.146467 -0.434301 -0.255463
+v -0.1567 -0.409376 -0.256443
+v -0.147884 -0.419686 -0.256943
+v -0.154438 -0.416366 -0.263234
+v -0.151774 -0.417895 -0.239525
+v -0.146962 -0.420564 -0.247024
+v -0.155778 -0.410254 -0.246524
+v -0.156578 -0.420278 -0.208158
+v -0.157533 -0.417895 -0.216261
+v -0.166045 -0.409409 -0.210096
+v -0.172022 -0.414384 -0.198933
+v -0.163571 -0.418264 -0.200346
+v -0.173038 -0.407394 -0.202284
+v -0.155148 -0.4343 -0.208397
+v -0.155209 -0.429693 -0.203644
+v -0.16366 -0.425814 -0.202232
+v -0.153061 -0.42731 -0.216752
+v -0.152106 -0.429693 -0.208649
+v -0.152045 -0.4343 -0.213402
+v 0.343319 -0.42439 -0.109229
+v 0.344546 -0.422274 -0.11866
+v 0.334955 -0.413946 -0.112353
+v 0.323454 -0.416244 -0.0993298
+v 0.333376 -0.420929 -0.100868
+v 0.325013 -0.410486 -0.103992
+v 0.340888 -0.435788 -0.107717
+v 0.34122 -0.432145 -0.102949
+v 0.331296 -0.427461 -0.10141
+v 0.347522 -0.43003 -0.118795
+v 0.346295 -0.432145 -0.109363
+v 0.345963 -0.435788 -0.114132
+v 0.347522 -0.43003 0.118794
+v 0.345963 -0.435788 0.114132
+v 0.346295 -0.432145 0.109363
+v 0.340888 -0.435788 0.107717
+v 0.331296 -0.427461 0.10141
+v 0.34122 -0.432145 0.102948
+v 0.323454 -0.416244 0.0993291
+v 0.325013 -0.410486 0.103992
+v 0.333376 -0.420929 0.100868
+v 0.334955 -0.413946 0.112352
+v 0.344546 -0.422274 0.11866
+v 0.343319 -0.42439 0.109228
+v -0.153061 -0.42731 0.216752
+v -0.152045 -0.4343 0.213402
+v -0.152106 -0.429693 0.208648
+v -0.155148 -0.4343 0.208396
+v -0.16366 -0.425814 0.202232
+v -0.155209 -0.429693 0.203643
+v -0.172022 -0.414384 0.198933
+v -0.173038 -0.407394 0.202284
+v -0.163571 -0.418264 0.200345
+v -0.166045 -0.409409 0.210095
+v -0.157533 -0.417895 0.21626
+v -0.156578 -0.420278 0.208157
+v -0.151774 -0.417895 0.239525
+v -0.155778 -0.410254 0.246524
+v -0.146962 -0.420564 0.247023
+v -0.1567 -0.409376 0.256443
+v -0.154438 -0.416366 0.263233
+v -0.147884 -0.419686 0.256942
+v -0.150471 -0.42666 0.262462
+v -0.146467 -0.434301 0.255463
+v -0.143916 -0.429979 0.256171
+v -0.145041 -0.434301 0.246807
+v -0.147302 -0.427311 0.240016
+v -0.14249 -0.429979 0.247515
+v -0.154959 -0.426538 0.295152
+v -0.151328 -0.434831 0.289768
+v -0.148273 -0.430246 0.290317
+v -0.149902 -0.434831 0.281113
+v -0.15179 -0.427189 0.275259
+v -0.146849 -0.430246 0.281661
+v -0.155758 -0.416897 0.276032
+v -0.159388 -0.408604 0.281415
+v -0.150816 -0.419954 0.282434
+v -0.16031 -0.407727 0.287895
+v -0.158422 -0.415368 0.293749
+v -0.151738 -0.419077 0.288914
+v -0.178144 -0.426335 0.32984
+v -0.171548 -0.43448 0.327057
+v -0.169824 -0.429608 0.329522
+v -0.166013 -0.43448 0.320519
+v -0.164476 -0.426187 0.314621
+v -0.164288 -0.429608 0.322982
+v -0.167939 -0.415017 0.313218
+v -0.174535 -0.406871 0.316001
+v -0.167753 -0.418439 0.321579
+v -0.179956 -0.40707 0.321153
+v -0.181494 -0.415363 0.327049
+v -0.173174 -0.418638 0.326731
+v 0.34644 -0.430208 0.230719
+v 0.352588 -0.437756 0.225937
+v 0.355673 -0.43519 0.229037
+v 0.360111 -0.437756 0.21858
+v 0.362897 -0.432465 0.212682
+v 0.363196 -0.43519 0.22168
+v 0.359125 -0.425338 0.212099
+v 0.352976 -0.417789 0.216881
+v 0.359426 -0.428062 0.221095
+v 0.348466 -0.41475 0.223907
+v 0.345681 -0.420042 0.229804
+v 0.354914 -0.425023 0.228121
+v 0.368347 -0.430254 0.187613
+v 0.369809 -0.435547 0.182285
+v 0.372671 -0.432924 0.180834
+v 0.368008 -0.435547 0.174526
+v 0.364165 -0.430022 0.168499
+v 0.370869 -0.432924 0.173074
+v 0.36079 -0.42258 0.168139
+v 0.359328 -0.417288 0.173466
+v 0.367495 -0.425482 0.172714
+v 0.360733 -0.417602 0.181001
+v 0.364576 -0.423127 0.187028
+v 0.368899 -0.425796 0.180249
+v 0.362226 -0.430726 0.157274
+v 0.363393 -0.436251 0.15178
+v 0.366681 -0.433706 0.15046
+v 0.361591 -0.436251 0.144021
+v 0.358043 -0.430492 0.138159
+v 0.364878 -0.433706 0.142701
+v 0.355067 -0.422737 0.138025
+v 0.3539 -0.417212 0.143519
+v 0.361902 -0.42595 0.142567
+v 0.355304 -0.417526 0.151054
+v 0.358852 -0.423284 0.156914
+v 0.363307 -0.426265 0.150101
+v 0.357837 -0.433507 0.173198
+v 0.338829 -0.432487 0.161984
+v 0.354515 -0.433741 0.157935
+v 0.353349 -0.428217 0.163428
+v 0.353994 -0.427982 0.167169
+v 0.343156 -0.4078 0.156859
+v 0.329199 -0.390021 0.164719
+v 0.345886 -0.408032 0.171789
+v 0.34735 -0.413324 0.166461
+v 0.346703 -0.413559 0.16272
+v -0.19205 -0.432487 0.263602
+v -0.156481 -0.433091 0.258384
+v -0.160484 -0.42545 0.265382
+v -0.160924 -0.425627 0.269649
+v -0.159036 -0.433268 0.275502
+v -0.17056 -0.398186 0.260113
+v -0.185903 -0.375736 0.266631
+v -0.172369 -0.39706 0.276553
+v -0.168738 -0.405353 0.271169
+v -0.168299 -0.405177 0.266903
+v 0.576139 0.431021 0.0603145
+v 0.578088 0.431994 0.0466918
+v 0.575417 0.43639 0.0475129
+v 0.577302 0.433082 0.0349634
+v 0.57312 0.434561 0.0222778
+v 0.574631 0.437478 0.0357846
+v 0.570042 0.434839 0.0222778
+v 0.568092 0.433865 0.0359005
+v 0.571552 0.437756 0.0357846
+v 0.569369 0.432687 0.0484952
+v 0.57355 0.431209 0.061181
+v 0.572829 0.436578 0.0483793
+v 0.578806 0.420907 0.102982
+v 0.575788 0.41764 0.114707
+v 0.58037 0.420334 0.115188
+v 0.575586 0.414431 0.124682
+v 0.578672 0.409305 0.134564
+v 0.580167 0.417126 0.125163
+v 0.581728 0.407773 0.133724
+v 0.584747 0.411039 0.121998
+v 0.583224 0.415594 0.124323
+v 0.584483 0.415591 0.111996
+v 0.581396 0.420718 0.102115
+v 0.582959 0.420146 0.114322
+v 0.582276 0.389123 0.165756
+v 0.580183 0.383615 0.174171
+v 0.585463 0.385211 0.174903
+v 0.581302 0.376991 0.181112
+v 0.58545 0.369232 0.186356
+v 0.586582 0.378587 0.181845
+v 0.588164 0.368682 0.184264
+v 0.590257 0.37419 0.175848
+v 0.589297 0.378037 0.179752
+v 0.589479 0.379831 0.17016
+v 0.585333 0.387591 0.164916
+v 0.588518 0.383679 0.174065
+v 0.582821 0.327893 0.216339
+v 0.587114 0.320789 0.218617
+v 0.587877 0.33032 0.21798
+v 0.591148 0.322227 0.217425
+v 0.594152 0.330293 0.211594
+v 0.59191 0.331759 0.216788
+v 0.592896 0.336313 0.207396
+v 0.588602 0.343417 0.205119
+v 0.590654 0.337778 0.21259
+v 0.585888 0.343967 0.207211
+v 0.582884 0.335901 0.213041
+v 0.58794 0.338328 0.214683
+v 0.604538 0.253923 0.230636
+v 0.604428 0.266714 0.2289
+v 0.601916 0.265885 0.233767
+v 0.602321 0.277413 0.227577
+v 0.595677 0.28743 0.227435
+v 0.599808 0.276584 0.232444
+v 0.591643 0.285991 0.228628
+v 0.591753 0.273201 0.230362
+v 0.595775 0.275146 0.233636
+v 0.595887 0.262812 0.230773
+v 0.602531 0.252796 0.230916
+v 0.599909 0.264758 0.234047
+v 0.617761 0.209833 0.233991
+v 0.624945 0.203583 0.233059
+v 0.622183 0.211105 0.237015
+v 0.629196 0.203707 0.231531
+v 0.62878 0.21131 0.230506
+v 0.626433 0.21123 0.235485
+v 0.624146 0.218216 0.231087
+v 0.616963 0.224466 0.23202
+v 0.621799 0.218135 0.236068
+v 0.614954 0.223339 0.232298
+v 0.61537 0.215736 0.233324
+v 0.619792 0.217008 0.236347
+v 0.642512 0.160811 0.230042
+v 0.6518 0.153967 0.228064
+v 0.647957 0.161812 0.233185
+v 0.656048 0.155751 0.227843
+v 0.655951 0.164658 0.227927
+v 0.652204 0.163596 0.232963
+v 0.651615 0.171108 0.228228
+v 0.642327 0.177952 0.230205
+v 0.647868 0.170047 0.233264
+v 0.638077 0.177828 0.231734
+v 0.638173 0.168921 0.231649
+v 0.643617 0.169923 0.234792
+v 0.666432 0.129853 0.223379
+v 0.667826 0.12184 0.221849
+v 0.671715 0.123274 0.225628
+v 0.673363 0.116215 0.220212
+v 0.680969 0.112344 0.218134
+v 0.677251 0.117648 0.223992
+v 0.682463 0.11439 0.217848
+v 0.681068 0.122404 0.219379
+v 0.678746 0.119694 0.223706
+v 0.678284 0.127766 0.221079
+v 0.670679 0.131637 0.223159
+v 0.675962 0.125057 0.225406
+v 0.307315 -0.403166 0.102631
+v 0.291336 -0.403028 0.104582
+v 0.280365 -0.377439 0.105572
+v 0.298544 -0.381349 0.112053
+v 0.308873 -0.397408 0.107294
+v 0.306784 -0.425122 0.10868
+v 0.322763 -0.42526 0.10673
+v 0.332354 -0.433587 0.113037
+v 0.32528 -0.432487 0.134065
+v 0.341556 -0.412705 0.136525
+v 0.338048 -0.412552 0.13007
+v 0.328457 -0.404224 0.123762
+v 0.318129 -0.388165 0.128522
+v 0.326433 -0.389402 0.149879
+v 0.340389 -0.407181 0.142018
+v 0.343911 -0.427829 0.130334
+v 0.347417 -0.427983 0.13679
+v 0.350965 -0.433741 0.14265
+v 0.335278 -0.432487 0.146701
+v 0.342352 -0.433587 0.125671
+v 0.35068 -0.414912 0.201013
+v 0.352496 -0.414176 0.192657
+v 0.348653 -0.408651 0.186628
+v 0.331966 -0.390639 0.17956
+v 0.333755 -0.391993 0.200254
+v 0.344531 -0.407364 0.205796
+v 0.359926 -0.428215 0.193808
+v 0.358108 -0.428951 0.202164
+v 0.355323 -0.434243 0.208061
+v 0.342378 -0.432487 0.177268
+v 0.361388 -0.433507 0.188481
+v 0.334356 -0.426695 0.227336
+v 0.321924 -0.423513 0.230047
+v 0.298564 -0.432487 0.214357
+v 0.32756 -0.432487 0.19176
+v 0.340505 -0.434243 0.222554
+v 0.310357 -0.423513 0.236469
+v 0.297015 -0.427197 0.241426
+v 0.286998 -0.432487 0.220778
+v 0.263996 -0.432487 0.223703
+v 0.274013 -0.427197 0.244351
+v 0.262743 -0.423534 0.243938
+v 0.324871 -0.386006 0.214095
+v 0.315584 -0.381102 0.223653
+v 0.320429 -0.403488 0.228245
+v 0.332861 -0.40667 0.225533
+v 0.335646 -0.401378 0.219637
+v 0.292794 -0.389933 0.245037
+v 0.282556 -0.407665 0.244868
+v 0.293827 -0.411329 0.245281
+v 0.307169 -0.407644 0.240324
+v 0.302324 -0.385259 0.235733
+v 0.252956 -0.384399 0.242473
+v 0.265592 -0.403589 0.24774
+v 0.275831 -0.385857 0.247909
+v 0.262194 -0.369651 0.243003
+v 0.239386 -0.401687 0.119539
+v 0.25866 -0.399533 0.107006
+v 0.269631 -0.425122 0.106016
+v 0.288127 -0.432487 0.131402
+v 0.259131 -0.432487 0.153998
+v 0.239447 -0.404342 0.229626
+v 0.229234 -0.400554 0.210095
+v 0.253336 -0.432487 0.214657
+v 0.252082 -0.423534 0.234892
+v 0.247477 -0.432487 0.186848
+v 0.223376 -0.400554 0.182286
+v 0.227732 -0.401687 0.152391
+v 0.21439 -0.266496 0.206845
+v 0.231046 -0.269263 0.223878
+v 0.232946 -0.239932 0.223855
+v 0.215006 -0.230335 0.204527
+v 0.203443 -0.292555 0.173816
+v 0.209911 -0.292591 0.146717
+v 0.213135 -0.338444 0.145433
+v 0.208778 -0.337311 0.17533
+v 0.206825 -0.229916 0.174135
+v 0.214491 -0.233646 0.145778
+v 0.212676 -0.266112 0.149353
+v 0.206209 -0.266076 0.176452
+v 0.257814 -0.303759 0.107584
+v 0.254061 -0.339575 0.103997
+v 0.234786 -0.341729 0.116531
+v 0.231562 -0.295877 0.117813
+v 0.267831 -0.204248 0.0779321
+v 0.261481 -0.222741 0.0916226
+v 0.236405 -0.209947 0.104901
+v 0.237476 -0.183367 0.0893291
+v 0.29304 -0.327855 0.141115
+v 0.302632 -0.354915 0.14489
+v 0.294328 -0.353678 0.123532
+v 0.276149 -0.349768 0.117051
+v 0.279903 -0.313952 0.120639
+v 0.28735 -0.299663 0.115607
+v 0.304708 -0.299239 0.130478
+v 0.300487 -0.313565 0.136083
+v 0.301563 -0.332721 0.205195
+v 0.303647 -0.355828 0.211624
+v 0.312933 -0.360732 0.202065
+v 0.311144 -0.35938 0.181371
+v 0.301552 -0.33232 0.177596
+v 0.313502 -0.302943 0.165735
+v 0.313554 -0.301336 0.192882
+v 0.309293 -0.31767 0.198939
+v 0.309281 -0.317269 0.17134
+v 0.280677 -0.329342 0.22831
+v 0.276398 -0.345352 0.231743
+v 0.290035 -0.361559 0.236648
+v 0.299565 -0.356885 0.227344
+v 0.297481 -0.333777 0.220916
+v 0.238251 -0.325107 0.225414
+v 0.242529 -0.309097 0.221981
+v 0.230122 -0.294077 0.220873
+v 0.213465 -0.29131 0.20384
+v 0.2188 -0.336065 0.205354
+v 0.229013 -0.339854 0.224885
+v 0.325308 -0.215924 0.105574
+v 0.312396 -0.240326 0.112638
+v 0.292604 -0.234354 0.0968831
+v 0.298954 -0.215861 0.0831927
+v 0.288299 -0.259776 0.107406
+v 0.308092 -0.265748 0.123162
+v 0.305019 -0.286908 0.128901
+v 0.28766 -0.287331 0.11403
+v 0.317972 -0.266148 0.150439
+v 0.315426 -0.25823 0.17979
+v 0.31495 -0.285701 0.183325
+v 0.314899 -0.287309 0.156178
+v 0.301574 -0.260976 0.218426
+v 0.309663 -0.282789 0.206444
+v 0.310139 -0.255318 0.202909
+v 0.300374 -0.293711 0.21582
+v 0.292284 -0.271898 0.227801
+v 0.284238 -0.257194 0.231047
+v 0.268801 -0.261259 0.22814
+v 0.266901 -0.290591 0.228162
+v 0.279309 -0.305611 0.229271
+v 0.296113 -0.310045 0.221876
+v 0.254771 -0.137433 0.226734
+v 0.246705 -0.15086 0.226678
+v 0.260006 -0.155927 0.232217
+v 0.264058 -0.147662 0.23186
+v 0.263328 -0.176842 0.231699
+v 0.250027 -0.171775 0.226159
+v 0.253949 -0.206223 0.2285
+v 0.269386 -0.202158 0.231407
+v 0.226338 -0.162057 0.214197
+v 0.21441 -0.158585 0.19762
+v 0.21232 -0.18691 0.197211
+v 0.23026 -0.196506 0.216539
+v 0.219581 -0.140164 0.194697
+v 0.231509 -0.143635 0.211275
+v 0.239575 -0.130208 0.211331
+v 0.226285 -0.130526 0.19513
+v 0.261989 -0.242474 0.113423
+v 0.26135 -0.27003 0.120046
+v 0.235099 -0.262146 0.130275
+v 0.236913 -0.22968 0.126701
+v 0.22057 -0.124387 0.160103
+v 0.208415 -0.132687 0.140232
+v 0.217024 -0.157143 0.12261
+v 0.215953 -0.183722 0.138182
+v 0.208288 -0.179992 0.166538
+v 0.210378 -0.151667 0.166948
+v 0.217081 -0.142029 0.16738
+v 0.260227 -0.0877198 0.205436
+v 0.244713 -0.102392 0.192074
+v 0.241225 -0.120033 0.19935
+v 0.254515 -0.119716 0.215552
+v 0.272944 -0.100617 0.213815
+v 0.302317 -0.132141 0.225506
+v 0.288678 -0.114454 0.223407
+v 0.270248 -0.133552 0.225146
+v 0.279535 -0.143782 0.230271
+v 0.310527 -0.17007 0.205771
+v 0.299241 -0.148974 0.222932
+v 0.276458 -0.160613 0.227698
+v 0.272407 -0.168879 0.228055
+v 0.278464 -0.194195 0.227762
+v 0.286511 -0.2089 0.224517
+v 0.295077 -0.203241 0.209
+v 0.337163 -0.210494 0.142074
+v 0.337155 -0.193805 0.175261
+v 0.321706 -0.226976 0.17849
+v 0.324252 -0.234896 0.149138
+v -0.164741 -0.407556 0.233575
+v -0.16666 -0.407555 0.225821
+v -0.175172 -0.399069 0.219656
+v -0.189544 -0.376375 0.22223
+v -0.184088 -0.377464 0.247093
+v -0.168745 -0.399914 0.240575
+v -0.175469 -0.402857 0.30166
+v -0.172298 -0.402974 0.29517
+v -0.174184 -0.395332 0.289318
+v -0.187719 -0.374007 0.279395
+v -0.192626 -0.374322 0.295181
+v -0.182066 -0.394711 0.304443
+v -0.210986 -0.382047 0.312122
+v -0.205086 -0.403673 0.320378
+v -0.194281 -0.403395 0.320488
+v -0.192744 -0.395102 0.314592
+v -0.203305 -0.374714 0.305329
+v -0.239321 -0.39941 0.31956
+v -0.224728 -0.432487 0.298262
+v -0.217454 -0.425284 0.330604
+v -0.223355 -0.403658 0.322349
+v -0.198487 -0.425284 0.325874
+v -0.205761 -0.432487 0.293532
+v -0.181088 -0.433151 0.323202
+v -0.187684 -0.425006 0.325985
+v -0.170184 -0.433151 0.310321
+v -0.194858 -0.432487 0.280651
+v -0.161844 -0.433268 0.292551
+v -0.165475 -0.424975 0.297935
+v -0.168647 -0.424858 0.304425
+v -0.153672 -0.433091 0.241335
+v -0.189243 -0.432487 0.246554
+v -0.156837 -0.433091 0.223439
+v -0.157853 -0.426101 0.226789
+v -0.155933 -0.426101 0.234544
+v -0.183927 -0.424982 0.207036
+v -0.171461 -0.424605 0.207416
+v -0.162949 -0.433091 0.21358
+v -0.195355 -0.432487 0.236696
+v -0.211085 -0.384962 0.194866
+v -0.20332 -0.372405 0.206843
+v -0.188948 -0.395101 0.204269
+v -0.187932 -0.402091 0.200918
+v -0.200398 -0.402468 0.200538
+v -0.240564 -0.40532 0.188773
+v -0.222463 -0.407476 0.191598
+v -0.211776 -0.424982 0.197269
+v -0.223204 -0.432487 0.22693
+v -0.287129 -0.401422 0.20021
+v -0.266342 -0.40532 0.194124
+v -0.248983 -0.432487 0.232282
+v -0.303366 -0.397218 0.227362
+v -0.296055 -0.401422 0.209177
+v -0.257909 -0.432487 0.241249
+v -0.295436 -0.397388 0.271507
+v -0.304775 -0.397218 0.248768
+v -0.259318 -0.432487 0.262656
+v -0.260305 -0.39941 0.309167
+v -0.281828 -0.397388 0.29672
+v -0.24571 -0.432487 0.28787
+v -0.147082 -0.155222 0.22071
+v -0.146098 -0.159981 0.198264
+v -0.136372 -0.149863 0.193287
+v -0.137936 -0.138928 0.219628
+v -0.206405 -0.33823 0.231498
+v -0.202867 -0.281782 0.220202
+v -0.202617 -0.275122 0.249308
+v -0.200949 -0.339319 0.256361
+v -0.184259 -0.206127 0.233021
+v -0.184508 -0.212786 0.203915
+v -0.16584 -0.171962 0.200082
+v -0.166825 -0.167204 0.222528
+v -0.238616 -0.278303 0.160144
+v -0.215372 -0.285954 0.185027
+v -0.21891 -0.342402 0.196323
+v -0.226675 -0.354959 0.184347
+v -0.244775 -0.352803 0.181522
+v -0.187127 -0.197307 0.157381
+v -0.210372 -0.189655 0.132498
+v -0.199454 -0.135894 0.102665
+v -0.15151 -0.136318 0.12425
+v -0.158734 -0.146364 0.14857
+v -0.168459 -0.156482 0.153548
+v -0.295873 -0.264442 0.157875
+v -0.267262 -0.274673 0.152686
+v -0.273421 -0.349172 0.174065
+v -0.294207 -0.345273 0.180151
+v -0.259579 -0.173989 0.12213
+v -0.28819 -0.163758 0.127317
+v -0.285494 -0.103947 0.100292
+v -0.248661 -0.120228 0.0922967
+v -0.324133 -0.244711 0.197955
+v -0.315668 -0.252478 0.175355
+v -0.314001 -0.333311 0.197631
+v -0.321312 -0.329106 0.215814
+v -0.304988 -0.154091 0.148413
+v -0.313455 -0.146324 0.171014
+v -0.308182 -0.0813633 0.146652
+v -0.302292 -0.0942807 0.121388
+v -0.307193 -0.239814 0.248511
+v -0.323251 -0.239857 0.222835
+v -0.320429 -0.324253 0.240694
+v -0.311091 -0.324422 0.263432
+v -0.311609 -0.141741 0.19796
+v -0.295552 -0.141698 0.223635
+v -0.285022 -0.0750871 0.207524
+v -0.306336 -0.0767791 0.173598
+v -0.227505 -0.253834 0.273655
+v -0.238057 -0.334532 0.290062
+v -0.222091 -0.338779 0.29285
+v -0.214409 -0.331446 0.286059
+v -0.209501 -0.331131 0.270272
+v -0.21117 -0.266935 0.263218
+v -0.265294 -0.331979 0.29533
+v -0.254741 -0.251282 0.278924
+v -0.282921 -0.245349 0.267962
+v -0.286819 -0.329957 0.282883
+v -0.566315 -0.0220899 0.00403869
+v -0.651057 -0.0136502 0.006785
+v -0.651057 -0.0136502 -0.00678557
+v -0.566315 -0.0220899 -0.00403926
+v -0.734492 0.00320589 0.006785
+v -0.815341 0.0225352 0.00548528
+v -0.815341 0.0225352 -0.00548596
+v -0.734492 0.00320589 -0.00678557
+v -0.891427 0.0482909 0.00548528
+v -0.94585 0.0631614 0.00278944
+v -0.94585 0.0631614 -0.00279
+v -0.891427 0.0482909 -0.00548596
+v -0.887789 0.066623 0.0206612
+v -0.943721 0.0756612 0.014347
+v -0.94479 0.0700912 0.0111585
+v -0.890367 0.0552206 0.0138544
+v -0.730609 0.0294301 0.0307659
+v -0.812719 0.044663 0.0258655
+v -0.815297 0.0332607 0.0190587
+v -0.734448 0.0139314 0.0203584
+v -0.556872 0.0112981 0.0306145
+v -0.641802 0.0166848 0.0307603
+v -0.64564 0.00118615 0.0203528
+v -0.560898 -0.00725362 0.0176064
+v -0.721314 0.0693806 0.0372613
+v -0.790331 0.0738189 0.0323264
+v -0.80566 0.0628366 0.0299537
+v -0.72355 0.0476036 0.0348541
+v -0.553065 0.0614019 0.049175
+v -0.636733 0.0567209 0.0435681
+v -0.63897 0.034944 0.0411609
+v -0.554039 0.0295572 0.0410151
+v -0.109443 -0.0988078 0.194078
+v -0.128502 -0.120553 0.206513
+v -0.126938 -0.131488 0.180172
+v -0.119715 -0.121441 0.155852
+v -0.166307 -0.0820513 0.251471
+v -0.145545 -0.10217 0.238379
+v -0.126486 -0.0804254 0.225943
+v -0.15966 -0.0511523 0.246764
+v -0.197906 -0.103392 0.257223
+v -0.206165 -0.163272 0.264264
+v -0.189831 -0.176373 0.253827
+v -0.172397 -0.137449 0.243334
+v -0.163251 -0.121153 0.242252
+v -0.184011 -0.101034 0.255344
+v -0.230145 -0.151363 0.266024
+v -0.221885 -0.0914835 0.258983
+v -0.247794 -0.0788197 0.23895
+v -0.258323 -0.14543 0.255062
+v -0.243713 -0.0102142 0.226751
+v -0.245477 -0.0479978 0.233664
+v -0.219569 -0.0606616 0.253696
+v -0.205674 -0.0583036 0.251819
+v -0.199027 -0.0274044 0.247112
+v -0.19191 0.0197337 0.24202
+v -0.17652 0.0675545 0.233441
+v -0.23781 0.0932134 0.209857
+v -0.236596 0.0369239 0.22166
+v -0.060796 0.025865 0.222249
+v -0.0444414 0.0484875 0.219859
+v -0.0648025 0.104475 0.218714
+v -0.103693 0.0600254 0.227784
+v -0.0743644 -0.0326377 0.1981
+v -0.0792628 -0.00217542 0.223851
+v -0.122159 0.031985 0.229386
+v -0.13755 -0.0158359 0.237964
+v -0.104377 -0.0451091 0.217144
+v -0.325837 0.0764069 0.143514
+v -0.317224 0.0439965 0.151279
+v -0.285632 0.0358713 0.183337
+v -0.286845 0.0921601 0.171536
+v -0.315907 -0.0107546 0.125872
+v -0.302771 -0.0391935 0.152212
+v -0.281457 -0.0375015 0.186138
+v -0.279693 0.000282003 0.179226
+v -0.311284 0.00840737 0.147167
+v -0.410246 0.037733 0.0522701
+v -0.409547 0.0816149 0.0679464
+v -0.468657 0.0697311 0.05327
+v -0.469631 0.0378864 0.0451101
+v -0.379824 0.0819365 0.0880015
+v -0.380523 0.0380545 0.0723252
+v -0.349893 0.0243332 0.0961375
+v -0.34527 0.0434951 0.117431
+v -0.353883 0.0759055 0.109666
+v -0.413585 -0.0283055 0.00516117
+v -0.481521 -0.0217763 0.00403869
+v -0.481521 -0.0217763 -0.00403926
+v -0.413585 -0.0283055 -0.00516174
+v -0.478969 -0.0117248 0.00662673
+v -0.411033 -0.018254 0.00774932
+v -0.415555 0.00667335 0.0267948
+v -0.474941 0.00682689 0.0196348
+v -0.27374 -0.0880583 0.0107247
+v -0.27374 -0.0880583 -0.0107254
+v -0.229758 -0.110209 -0.012909
+v -0.229758 -0.110209 0.0129085
+v -0.306556 -0.0656472 -0.0107254
+v -0.306556 -0.0656472 0.0107247
+v -0.35005 -0.0454714 0.00516117
+v -0.35005 -0.0454714 -0.00516174
+v -0.335219 -0.034545 0.0483731
+v -0.34344 -0.0118415 0.0660767
+v -0.37407 0.00187976 0.0422645
+v -0.369547 -0.0230476 0.0232191
+v -0.326054 -0.0432233 0.0287826
+v -0.326112 -0.0271673 0.0960653
+v -0.317892 -0.0498707 0.0783616
+v -0.307087 -0.0685238 0.0971411
+v -0.312977 -0.0556063 0.122404
+v -0.288275 -0.0895305 0.0739369
+v -0.299079 -0.0708774 0.0551574
+v -0.289914 -0.0795559 0.0355669
+v -0.245932 -0.101707 0.0377506
+v -0.251441 -0.105811 0.0659414
+v -0.144234 -0.131664 0.00893062
+v -0.185302 -0.121285 0.0129085
+v -0.185302 -0.121285 -0.012909
+v -0.144234 -0.131664 -0.00893118
+v -0.116594 -0.12195 0.0633025
+v -0.140986 -0.125019 0.0894774
+v -0.188929 -0.124596 0.0678919
+v -0.18342 -0.120491 0.0397011
+v -0.142351 -0.13087 0.0357233
+v -0.0927307 -0.107135 0.1319
+v -0.0683383 -0.104067 0.105725
+v -0.0524454 -0.0720305 0.151082
+v -0.0824576 -0.0845019 0.170126
+v -0.0218044 -0.135578 0.0243
+v -0.0745575 -0.131089 0.0365099
+v -0.100315 -0.140009 0.00893062
+v -0.100315 -0.140009 -0.00893118
+v -0.0745575 -0.131089 -0.0365105
+v -0.0218044 -0.135578 -0.0243006
+v 0.125076 -0.139595 0.0250923
+v 0.0650356 -0.136647 0.0243
+v 0.0650356 -0.136647 -0.0243006
+v 0.125076 -0.139595 -0.025093
+v 0.204016 -0.159946 0.0203248
+v 0.165526 -0.146179 0.0250923
+v 0.165526 -0.146179 -0.025093
+v 0.204016 -0.159946 -0.0203253
+v 0.278686 -0.188537 0.0159329
+v 0.24158 -0.173538 0.0203248
+v 0.24158 -0.173538 -0.0203253
+v 0.278686 -0.188537 -0.0159335
+v 0.241025 -0.167907 0.0484222
+v 0.278131 -0.182905 0.0440304
+v 0.272865 -0.189957 0.0655735
+v 0.24251 -0.169078 0.0769705
+v 0.352324 -0.186927 0.0278741
+v 0.319696 -0.189256 0.0159329
+v 0.319696 -0.189256 -0.0159335
+v 0.352324 -0.186927 -0.0278747
+v 0.320788 -0.194281 0.0515683
+v 0.353416 -0.191952 0.0635096
+v 0.341875 -0.201397 0.0954929
+v 0.315522 -0.201333 0.0731115
+v 0.409981 -0.166484 0.0341807
+v 0.37939 -0.18282 0.0278741
+v 0.37939 -0.18282 -0.0278747
+v 0.409981 -0.166484 -0.0341814
+v 0.478143 -0.123963 0.0256555
+v 0.446116 -0.147146 0.0341807
+v 0.446116 -0.147146 -0.0341814
+v 0.478143 -0.123963 -0.0256561
+v 0.533415 -0.0793295 0.019209
+v 0.499206 -0.10369 0.0256555
+v 0.499206 -0.10369 -0.0256561
+v 0.533415 -0.0793295 -0.0192096
+v 0.366504 -0.0736936 0.215842
+v 0.3425 -0.0469389 0.220093
+v 0.315135 -0.0847998 0.219526
+v 0.328774 -0.102488 0.221625
+v 0.399902 -0.143119 0.158651
+v 0.361647 -0.167403 0.168908
+v 0.361654 -0.184092 0.13572
+v 0.373195 -0.174646 0.103737
+v 0.403785 -0.158312 0.110043
+v 0.356615 -0.147117 0.194421
+v 0.39487 -0.122832 0.184165
+v 0.383059 -0.0972259 0.205799
+v 0.34533 -0.12602 0.211581
+v 0.474702 -0.0916365 0.128101
+v 0.43928 -0.118528 0.14365
+v 0.443164 -0.133721 0.0950427
+v 0.47519 -0.110538 0.0865173
+v 0.535439 -0.0440069 0.0917989
+v 0.506232 -0.0631043 0.109
+v 0.506718 -0.0820054 0.0674169
+v 0.540928 -0.057645 0.0609703
+v 0.473353 -0.0727501 0.160296
+v 0.462852 -0.0393537 0.184924
+v 0.426121 -0.0740355 0.197479
+v 0.437931 -0.0996417 0.175846
+v 0.401816 -0.034316 0.216263
+v 0.438547 0.000365768 0.203706
+v 0.414628 0.0405227 0.21253
+v 0.377812 -0.00756125 0.220514
+v 0.53689 0.0164765 0.134004
+v 0.511752 0.0234975 0.152053
+v 0.49057 -0.00810008 0.165143
+v 0.501072 -0.0414964 0.140515
+v 0.53028 -0.022399 0.123314
+v 0.19348 -0.0834972 0.133972
+v 0.217466 -0.0935324 0.139041
+v 0.229621 -0.0852329 0.158913
+v 0.215281 -0.0653399 0.154742
+v 0.206712 -0.110316 0.119698
+v 0.182726 -0.100281 0.114628
+v 0.175347 -0.119835 0.0782951
+v 0.213837 -0.133601 0.0735276
+v 0.215321 -0.134772 0.102076
+v 0.013981 -0.0862587 0.115528
+v -0.0344234 -0.0762221 0.131902
+v -0.0503152 -0.108258 0.0865434
+v 0.00243789 -0.112747 0.0743334
+v 0.133971 -0.0996978 0.108593
+v 0.0780949 -0.0898146 0.112663
+v 0.0665519 -0.116303 0.0714676
+v 0.126592 -0.119251 0.07226
+v 0.100118 -0.0570064 0.158508
+v 0.085518 -0.0750734 0.138282
+v 0.141394 -0.0849567 0.134213
+v 0.163195 -0.0667994 0.154982
+v 0.137253 -0.0605649 0.157834
+v 0.0269715 -0.0382201 0.157489
+v 0.0415728 -0.020153 0.177714
+v 0.0348265 0.0253207 0.193033
+v -0.00997565 0.0249013 0.197221
+v -0.0263302 0.00227883 0.199612
+v -0.0214319 -0.0281835 0.173861
+v 0.0309554 0.0887337 0.210102
+v 0.0348976 0.158826 0.209919
+v -0.0342089 0.144302 0.213145
+v -0.0138478 0.0883142 0.21429
+v 0.15444 0.00744576 0.191004
+v 0.0925896 0.0140103 0.193422
+v 0.0993357 -0.0314635 0.178104
+v 0.13647 -0.035022 0.17743
+v 0.109029 0.0908947 0.211762
+v 0.17088 0.0843303 0.209343
+v 0.18293 0.147416 0.210578
+v 0.112971 0.160987 0.211578
+v 0.243147 0.00033461 0.188652
+v 0.208079 0.00808077 0.188594
+v 0.190109 -0.034387 0.17502
+v 0.216051 -0.0406214 0.172168
+v 0.292427 -0.0160458 0.203696
+v 0.27707 -0.0553525 0.199732
+v 0.289788 -0.0682498 0.20811
+v 0.317153 -0.0303889 0.208676
+v 0.261305 -0.0556344 0.193205
+v 0.276661 -0.0163276 0.19717
+v 0.258548 -0.00945726 0.192157
+v 0.231452 -0.0504134 0.175672
+v 0.245792 -0.0703064 0.179843
+v 0.275133 0.108375 0.203441
+v 0.240857 0.129673 0.201262
+v 0.228806 0.0665868 0.200028
+v 0.263874 0.0588406 0.200087
+v 0.345115 0.0917166 0.213011
+v 0.323275 0.033999 0.215124
+v 0.348002 0.0196559 0.220103
+v 0.384818 0.06774 0.21212
+v 0.295293 0.0478861 0.213558
+v 0.317133 0.105604 0.211445
+v 0.288438 0.10429 0.211898
+v 0.27718 0.0547564 0.208545
+v 0.50056 0.0768689 0.168069
+v 0.486707 0.108099 0.173709
+v 0.45546 0.0854283 0.189982
+v 0.479378 0.0452714 0.181159
+v 0.504438 0.119087 0.158177
+v 0.518292 0.0878563 0.152537
+v 0.543429 0.0808354 0.134488
+v 0.544372 0.120984 0.147152
+v 0.516506 0.131253 0.156954
+v -0.88575 0.100608 0.021766
+v -0.942884 0.0937936 0.0156473
+v -0.941572 0.0854415 0.0155435
+v -0.88564 0.0764033 0.0218576
+v -0.87031 0.0873857 0.0242303
+v -0.723908 0.110246 0.0313652
+v -0.80668 0.105806 0.0289687
+v -0.79124 0.0925846 0.031433
+v -0.722223 0.0881458 0.036368
+v -0.555556 0.124295 0.036299
+v -0.638453 0.110732 0.0345989
+v -0.636768 0.0886306 0.0396017
+v -0.5531 0.0933116 0.0452084
+v -0.420296 0.160186 0.0594319
+v -0.475323 0.146864 0.0450909
+v -0.472867 0.115882 0.0540003
+v -0.413756 0.127765 0.0686767
+v -0.30869 0.209752 0.113847
+v -0.350828 0.205648 0.0828798
+v -0.378215 0.1765 0.0851958
+v -0.371675 0.14408 0.0944406
+v -0.345733 0.138049 0.116105
+v -0.306742 0.153802 0.144127
+v -0.166871 0.259164 0.162457
+v -0.22589 0.229951 0.160756
+v -0.223942 0.174002 0.191036
+v -0.162653 0.148343 0.214621
+v -0.123763 0.192793 0.20555
+v 0.0281083 0.299308 0.155673
+v -0.0309125 0.3123 0.142475
+v -0.0742436 0.28831 0.152166
+v -0.031136 0.221939 0.19526
+v 0.0379705 0.236462 0.192032
+v 0.18316 0.265937 0.162762
+v 0.103235 0.286267 0.156963
+v 0.113097 0.223421 0.193322
+v 0.183056 0.209852 0.192321
+v 0.327785 0.218254 0.166365
+v 0.251872 0.240278 0.165127
+v 0.251768 0.184193 0.194685
+v 0.286045 0.162894 0.196863
+v 0.314739 0.164207 0.19641
+v 0.459543 0.201219 0.156195
+v 0.398436 0.203771 0.161732
+v 0.385389 0.149725 0.191777
+v 0.425092 0.125748 0.190886
+v 0.456341 0.148418 0.174613
+v 0.468409 0.160585 0.173389
+v 0.472481 0.176604 0.175697
+v 0.469462 0.187592 0.17019
+v -1 0.0990352 0.00289318
+v -1 0.0990352 -0.00289386
+v -0.995473 0.0893738 -0.00608237
+v -0.994152 0.0810211 -0.00597851
+v -0.995225 0.0754511 -0.00279
+v -0.995225 0.0754511 0.00278944
+v -0.994152 0.0810211 0.00597794
+v -0.995473 0.0893738 0.0060818
+v -0.897346 0.13099 0.0142977
+v -0.953499 0.118734 0.0115739
+v -0.94897 0.109072 0.0147624
+v -0.891836 0.115885 0.0208811
+v -0.961002 0.132441 0.00289318
+v -0.904847 0.144698 0.00561714
+v -0.904847 0.144698 -0.00561771
+v -0.961002 0.132441 -0.00289386
+v -0.731427 0.146139 0.0191934
+v -0.813936 0.136968 0.019406
+v -0.808427 0.121864 0.0259894
+v -0.725655 0.126304 0.0283859
+v -0.820639 0.152341 0.00561714
+v -0.73813 0.161514 0.00540445
+v -0.73813 0.161514 -0.00540502
+v -0.820639 0.152341 -0.00561771
+v -0.567054 0.170985 0.0119282
+v -0.648274 0.152238 0.0132344
+v -0.642501 0.132401 0.0224269
+v -0.559605 0.145964 0.0241271
+v -0.658867 0.178478 0.00540445
+v -0.577647 0.197226 0.0040983
+v -0.577647 0.197226 -0.00409886
+v -0.658867 0.178478 -0.00540502
+v -0.403621 0.235738 0.0161666
+v -0.484308 0.196083 0.0126627
+v -0.476858 0.171062 0.0248615
+v -0.421831 0.184385 0.0392026
+v -0.394443 0.213531 0.0368867
+v -0.497933 0.224058 0.0040983
+v -0.417246 0.263712 0.00760222
+v -0.417246 0.263712 -0.0076029
+v -0.497933 0.224058 -0.00409886
+v -0.245309 0.341585 0.0346753
+v -0.319926 0.298978 0.0294482
+v -0.310748 0.276773 0.0501682
+v -0.26861 0.280877 0.0811356
+v -0.209591 0.31009 0.082836
+v -0.340653 0.307156 0.00760222
+v -0.266037 0.349761 0.0128294
+v -0.266037 0.349761 -0.01283
+v -0.340653 0.307156 -0.0076029
+v -0.122727 0.388019 0.0451528
+v -0.183784 0.368407 0.0423024
+v -0.148066 0.336912 0.090463
+v -0.104736 0.360902 0.0807715
+v -0.189386 0.386677 0.0128294
+v -0.128329 0.40629 0.0156798
+v -0.128329 0.40629 -0.0156804
+v -0.189386 0.386677 -0.01283
+v -0.00545322 0.392005 0.0518339
+v -0.0714394 0.39371 0.0489274
+v -0.053449 0.366592 0.0845462
+v 0.00557176 0.353601 0.0977443
+v -0.0735606 0.415313 0.0156798
+v -0.00757445 0.413608 0.0185863
+v -0.00757445 0.413607 -0.0185869
+v -0.0735606 0.415313 -0.0156804
+v 0.16231 0.362084 0.0645722
+v 0.0720979 0.381687 0.0596853
+v 0.0831232 0.343283 0.105596
+v 0.163048 0.322953 0.111396
+v 0.0727649 0.40239 0.0185863
+v 0.162977 0.382787 0.0234733
+v 0.162977 0.382787 -0.0234739
+v 0.0727649 0.40239 -0.0185869
+v 0.345436 0.304627 0.0780744
+v 0.260621 0.329781 0.0762688
+v 0.261359 0.290649 0.123093
+v 0.337273 0.268625 0.124331
+v 0.267625 0.358211 0.0234733
+v 0.35244 0.333059 0.0252789
+v 0.35244 0.333059 -0.0252794
+v 0.267625 0.358211 -0.0234739
+v 0.466084 0.276786 0.0662242
+v 0.411418 0.288975 0.0735999
+v 0.403255 0.252973 0.119856
+v 0.46436 0.250422 0.114319
+v 0.472985 0.265817 0.0976909
+v 0.472568 0.269694 0.0866631
+v 0.422762 0.30498 -0.0252794
+v 0.422762 0.30498 0.0252789
+v 0.477429 0.29279 0.0179031
+v 0.477429 0.29279 -0.0179037
+v 0.553209 0.388154 -0.0162976
+v 0.553209 0.388154 0.0162969
+v 0.562521 0.42722 0.0210485
+v 0.564469 0.428194 0.00742566
+v 0.564469 0.428194 -0.00742634
+v 0.562521 0.42722 -0.0210491
+v 0.510768 0.285349 0.0179031
+v 0.538626 0.319152 0.0162969
+v 0.538626 0.319152 -0.0162976
+v 0.510768 0.285349 -0.0179037
+v 0.557456 0.384235 0.0581933
+v 0.559483 0.377584 0.087816
+v 0.569681 0.415122 0.10129
+v 0.572701 0.418389 0.089564
+v 0.570948 0.421824 0.0756305
+v 0.566768 0.423301 0.0629448
+v 0.521594 0.272108 0.0616347
+v 0.544995 0.30635 0.0692123
+v 0.54297 0.313002 0.0395896
+v 0.515111 0.2792 0.0411957
+v 0.55881 0.366911 0.120998
+v 0.559887 0.355826 0.143335
+v 0.571203 0.387089 0.163166
+v 0.573297 0.392596 0.15475
+v 0.572096 0.399324 0.144353
+v 0.56901 0.404451 0.134471
+v 0.522199 0.262364 0.0927832
+v 0.54626 0.2894 0.111672
+v 0.545183 0.300484 0.0893332
+v 0.52178 0.266242 0.0817554
+v 0.563613 0.333791 0.166424
+v 0.565362 0.317471 0.181361
+v 0.576219 0.340806 0.20428
+v 0.579223 0.348872 0.19845
+v 0.579076 0.357294 0.191498
+v 0.57493 0.365053 0.186255
+v 0.514641 0.241203 0.122374
+v 0.549076 0.267313 0.139571
+v 0.547328 0.283633 0.124634
+v 0.523266 0.256599 0.105746
+v 0.556181 0.235863 0.161455
+v 0.55149 0.256475 0.152293
+v 0.517056 0.230364 0.135097
+v 0.526975 0.216737 0.149092
+v 0.565151 0.290837 0.192332
+v 0.569841 0.270224 0.201494
+v 0.58192 0.282676 0.2226
+v 0.581812 0.295466 0.220866
+v 0.580301 0.307066 0.217529
+v 0.576008 0.31417 0.215252
+v 0.583594 0.235669 0.202861
+v 0.591285 0.213881 0.207888
+v 0.606872 0.220681 0.225595
+v 0.606458 0.228285 0.224571
+v 0.602316 0.238104 0.224109
+v 0.595674 0.248121 0.223967
+v 0.532796 0.195328 0.166182
+v 0.566674 0.203653 0.178067
+v 0.558983 0.225442 0.173039
+v 0.529777 0.206316 0.160676
+v 0.640919 0.150326 0.22101
+v 0.625092 0.14323 0.199007
+v 0.647162 0.12229 0.195063
+v 0.656479 0.127432 0.215941
+v 0.655084 0.135445 0.217471
+v 0.650207 0.143483 0.219033
+v 0.59924 0.194246 0.210109
+v 0.610658 0.17021 0.204354
+v 0.626485 0.177305 0.226357
+v 0.626389 0.186212 0.226443
+v 0.622012 0.194796 0.226884
+v 0.614828 0.201047 0.227816
+v 0.562846 0.146496 0.159012
+v 0.584348 0.157073 0.177249
+v 0.57293 0.181108 0.183002
+v 0.539052 0.172784 0.171119
+v 0.53498 0.156766 0.168812
+v 0.987218 0.287574 0.0664567
+v 0.994961 0.288834 0.0653339
+v 0.993386 0.285963 0.0616477
+v 1 0.284208 0.0627373
+v 0.998947 0.277661 0.0611509
+v 0.998425 0.281337 0.0590512
+v 0.937967 0.231591 0.0682324
+v 0.926323 0.234844 0.0626827
+v 0.977101 0.262723 0.0652886
+v 0.977623 0.259048 0.0673884
+v 0.917455 0.241339 0.0647673
+v 0.91886 0.253168 0.0754515
+v 0.962066 0.270829 0.0721824
+v 0.968235 0.269219 0.0673733
+v 0.923361 0.258029 0.0835047
+v 0.926324 0.254159 0.0888693
+v 0.97431 0.276951 0.0791128
+v 0.966568 0.27569 0.0802355
+v 0.925878 0.235019 0.0834845
+v 0.939763 0.238213 0.0749239
+v 0.97942 0.26567 0.0740801
+v 0.980473 0.272216 0.0756664
+v 0.932485 0.249424 0.0854229
+v 0.83867 0.161254 0.0567776
+v 0.837087 0.169018 0.0446087
+v 0.862699 0.192462 0.052222
+v 0.874344 0.189209 0.0577716
+v 0.829746 0.18564 0.0432992
+v 0.822574 0.201339 0.0453313
+v 0.855358 0.209084 0.0509124
+v 0.81168 0.191128 0.0350416
+v 0.818389 0.193419 0.0402434
+v 0.825561 0.177721 0.0382112
+v 0.820787 0.173818 0.0326998
+v 0.812785 0.191493 0.0962655
+v 0.822058 0.17747 0.0933535
+v 0.850748 0.195476 0.0910187
+v 0.857356 0.209881 0.0929572
+v 0.829421 0.169974 0.0868776
+v 0.836322 0.163218 0.0749883
+v 0.871996 0.191174 0.0759823
+v 0.85811 0.18798 0.0845428
+v 0.658159 0.280753 -0.0227459
+v 0.658159 0.280753 0.0227454
+v 0.700327 0.246383 0.0117385
+v 0.700327 0.246383 -0.011739
+v 0.600106 0.36263 -0.017673
+v 0.600106 0.36263 0.0176725
+v 0.623036 0.323794 0.0227454
+v 0.623036 0.323794 -0.0227459
+v 0.578893 0.425791 0.0201114
+v 0.586298 0.402706 0.0176725
+v 0.586298 0.402706 -0.017673
+v 0.578893 0.425791 -0.020112
+v 0.574712 0.427268 -0.00742634
+v 0.574712 0.427268 0.00742566
+v 0.599712 0.36394 0.0493493
+v 0.602603 0.357591 0.0806827
+v 0.622642 0.325104 0.0544222
+v 0.5844 0.412635 0.0965626
+v 0.591806 0.392737 0.0880202
+v 0.588915 0.399085 0.0566866
+v 0.58151 0.42217 0.0591256
+v 0.579561 0.421196 0.0727484
+v 0.581313 0.417762 0.0866819
+v 0.611064 0.349647 0.102056
+v 0.614509 0.337065 0.124773
+v 0.639361 0.302858 0.111951
+v 0.631103 0.317161 0.0757951
+v 0.58761 0.379741 0.157201
+v 0.596132 0.365012 0.144007
+v 0.592686 0.377595 0.12129
+v 0.585281 0.397494 0.129833
+v 0.582262 0.394227 0.141559
+v 0.583463 0.3875 0.151956
+v 0.616361 0.32858 0.142076
+v 0.620057 0.311014 0.157646
+v 0.641213 0.294375 0.129253
+v 0.592545 0.339939 0.193767
+v 0.602417 0.328678 0.178499
+v 0.59872 0.346244 0.162929
+v 0.590198 0.360974 0.176123
+v 0.588105 0.355466 0.184539
+v 0.588251 0.347044 0.19149
+v 0.626222 0.288026 0.169736
+v 0.632008 0.263844 0.17836
+v 0.658399 0.240756 0.157465
+v 0.647378 0.271387 0.141344
+v 0.601871 0.290234 0.217041
+v 0.612379 0.284473 0.201087
+v 0.606595 0.308655 0.192464
+v 0.596722 0.319916 0.207732
+v 0.593718 0.311849 0.213562
+v 0.595228 0.300251 0.216899
+v 0.637792 0.245718 0.182396
+v 0.647634 0.220882 0.1839
+v 0.664184 0.222631 0.161501
+v 0.62032 0.225783 0.222708
+v 0.629235 0.224047 0.206994
+v 0.619394 0.248883 0.20549
+v 0.608885 0.254644 0.221444
+v 0.608995 0.241853 0.223178
+v 0.613136 0.232034 0.22364
+v 0.649815 0.179782 0.219378
+v 0.657186 0.179622 0.203588
+v 0.64465 0.201077 0.20506
+v 0.635735 0.202813 0.220773
+v 0.636151 0.19521 0.221798
+v 0.640528 0.186625 0.221356
+v 0.676819 0.137507 0.214657
+v 0.681115 0.13906 0.199715
+v 0.67161 0.158162 0.202592
+v 0.66424 0.158322 0.218382
+v 0.664336 0.149416 0.218298
+v 0.669213 0.141379 0.216736
+v 0.687475 0.111655 0.207469
+v 0.689371 0.107551 0.192159
+v 0.690376 0.121221 0.194058
+v 0.686079 0.119668 0.209
+v 0.665583 0.103577 0.189622
+v 0.684402 0.100743 0.193109
+v 0.682504 0.104847 0.20842
+v 0.6749 0.108718 0.210498
+v 0.662904 0.0893201 0.149638
+v 0.686243 0.0919851 0.151959
+v 0.680664 0.0952296 0.171807
+v 0.661845 0.0980632 0.168319
+v 0.671318 0.176767 0.18194
+v 0.688456 0.164895 0.159817
+v 0.675333 0.19997 0.161012
+v 0.658782 0.198222 0.183412
+v 0.689975 0.138933 0.176596
+v 0.697608 0.146164 0.15735
+v 0.680471 0.158036 0.179472
+v 0.699731 0.103637 0.152463
+v 0.702789 0.127782 0.154963
+v 0.695157 0.120551 0.174209
+v 0.694152 0.106881 0.17231
+v 0.812984 0.172145 0.0198207
+v 0.799046 0.189214 0.022089
+v 0.80779 0.190186 0.0323619
+v 0.816897 0.172876 0.0300201
+v 0.793501 0.182148 -0.00800886
+v 0.793501 0.182148 0.00800818
+v 0.807439 0.16508 0.00573997
+v 0.807439 0.16508 -0.00574053
+v 0.736515 0.229882 0.0117385
+v 0.767664 0.21056 0.00800818
+v 0.767664 0.21056 -0.00800886
+v 0.736515 0.229882 -0.011739
+v 0.671415 0.260979 0.0879027
+v 0.712687 0.235481 0.0682529
+v 0.705325 0.24091 0.04074
+v 0.663157 0.275281 0.051747
+v 0.729205 0.209046 0.114033
+v 0.72258 0.223976 0.0989107
+v 0.681309 0.249473 0.11856
+v 0.692329 0.218842 0.134681
+v 0.76102 0.220106 0.0572046
+v 0.785295 0.215338 0.0488453
+v 0.793551 0.207185 0.0362346
+v 0.784807 0.206213 0.0259615
+v 0.753657 0.225535 0.0296918
+v 0.819024 0.209785 0.0544758
+v 0.811332 0.216044 0.0695567
+v 0.853212 0.229358 0.0707408
+v 0.851807 0.217529 0.0600568
+v 0.793692 0.215565 0.0552235
+v 0.800966 0.215963 0.0628955
+v 0.808657 0.209705 0.0478145
+v 0.801947 0.207412 0.0426128
+v 0.790252 0.206402 0.0988128
+v 0.789982 0.215345 0.0889557
+v 0.782709 0.214948 0.0812837
+v 0.758431 0.219716 0.0896431
+v 0.765057 0.204786 0.104765
+v 0.804839 0.216963 0.0871047
+v 0.805109 0.208019 0.0969617
+v 0.849679 0.226407 0.0936534
+v 0.846717 0.230278 0.0882888
+v 0.608037 0.0753309 0.109372
+v 0.621118 0.0898179 0.119542
+v 0.624504 0.100401 0.129801
+v 0.623444 0.109143 0.148481
+v 0.601375 0.130084 0.152426
+v 0.579872 0.119507 0.134188
+v 0.578928 0.0793589 0.121523
+v 0.689272 0.0580863 0.124564
+v 0.705488 0.0765964 0.127102
+v 0.690814 0.0852932 0.136912
+v 0.667476 0.0826285 0.134592
+v 0.664089 0.0720455 0.124332
+v 0.794877 0.121577 0.102384
+v 0.800143 0.109526 0.0988006
+v 0.809749 0.113217 0.0969045
+v 0.805003 0.125255 0.0995273
+v 0.80289 0.0994867 0.093579
+v 0.814606 0.0894216 0.0871127
+v 0.812496 0.103177 0.091683
+v 0.821076 0.0875969 0.0838961
+v 0.828564 0.0951912 0.08383
+v 0.818966 0.101353 0.0884664
+v 0.832516 0.106216 0.0859674
+v 0.82887 0.121103 0.0897615
+v 0.822917 0.112378 0.0906038
+v 0.823797 0.125735 0.0923129
+v 0.813099 0.12905 0.095778
+v 0.817845 0.117011 0.0931552
+v 0.81872 0.156726 0.00573997
+v 0.829817 0.142161 0.0119408
+v 0.829817 0.142161 -0.0119413
+v 0.81872 0.156726 -0.00574053
+v 0.821288 0.158364 0.0248195
+v 0.828473 0.143067 0.0208209
+v 0.817375 0.157633 0.0146201
+v 0.845145 0.107749 0.0786641
+v 0.840231 0.123972 0.0784902
+v 0.835234 0.121993 0.0886831
+v 0.83888 0.107106 0.0848892
+v 0.844647 0.124564 0.0387283
+v 0.845631 0.123099 0.0618335
+v 0.850544 0.106877 0.0620074
+v 0.85168 0.105716 0.047526
+v 0.835207 0.148514 0.0576568
+v 0.837019 0.135614 0.0590832
+v 0.836034 0.13708 0.0359781
+v 0.82885 0.152376 0.0399767
+v 0.833624 0.156279 0.045488
+v 0.821578 0.160312 0.0914243
+v 0.802179 0.154553 0.0970058
+v 0.814597 0.14199 0.0946192
+v 0.825295 0.138677 0.0911542
+v 0.830291 0.140656 0.0809612
+v 0.828481 0.153556 0.0795349
+v 0.775939 0.185238 0.101282
+v 0.791007 0.167071 0.0979986
+v 0.810408 0.17283 0.0924171
+v 0.801135 0.186853 0.0953292
+v 0.782497 0.150287 0.102094
+v 0.768277 0.138344 0.106588
+v 0.784788 0.134045 0.102564
+v 0.794915 0.137724 0.0997078
+v 0.71376 0.155493 0.134362
+v 0.738365 0.150664 0.11612
+v 0.752584 0.162607 0.111627
+v 0.737515 0.180772 0.114908
+v 0.700639 0.190569 0.135557
+v 0.731106 0.094839 0.119459
+v 0.744728 0.106786 0.113759
+v 0.744095 0.122852 0.113529
+v 0.719491 0.12768 0.13177
+v 0.716434 0.103536 0.129269
+v 0.76441 0.111052 0.108671
+v 0.778171 0.0995353 0.101239
+v 0.785555 0.110768 0.100833
+v 0.780289 0.122819 0.104416
+v 0.763778 0.127118 0.10844
+v 0.792761 0.0771682 0.0910129
+v 0.80968 0.0680677 0.0824493
+v 0.811861 0.078336 0.0841403
+v 0.800145 0.0884009 0.0906066
+v 0.849686 0.103109 0.0207383
+v 0.863239 0.0793597 0.0140776
+v 0.863239 0.0793597 -0.0140781
+v 0.849686 0.103109 -0.020739
+v 0.842653 0.121957 -0.0119413
+v 0.842653 0.121957 0.0119408
+v 0.890993 0.0314229 0.0140776
+v 0.908644 0.010039 0.0101651
+v 0.908644 0.010039 -0.0101657
+v 0.890993 0.0314229 -0.0140781
+v 0.911702 -0.00196081 0.0422377
+v 0.910042 0.00648223 0.0310482
+v 0.892392 0.027866 0.0349607
+v 0.897672 0.00728263 0.04807
+v 0.914554 0.00525645 0.0101651
+v 0.92846 0.00431324 0.00659252
+v 0.92846 0.00431324 -0.00659309
+v 0.914554 0.00525645 -0.0101657
+v 0.919607 -0.00700796 0.041133
+v 0.93323 -0.0041911 0.0335048
+v 0.931852 0.000491755 0.026371
+v 0.917947 0.00143508 0.0299436
+v 0.925286 -0.0328002 0.0419261
+v 0.937153 -0.0213819 0.0342155
+v 0.933972 -0.0144184 0.0351279
+v 0.920348 -0.0172353 0.0427561
+v 0.920654 -0.0264924 0.0441307
+v 0.96607 -0.00470114 0.012814
+v 0.969037 -0.00994721 0.00568013
+v 0.969037 -0.00994721 -0.0056807
+v 0.96607 -0.00470114 -0.0128145
+v 0.962889 0.00226223 -0.0137269
+v 0.961511 0.0069452 -0.00659309
+v 0.961511 0.0069452 0.00659252
+v 0.962889 0.00226223 0.0137264
+v 0.949024 -0.0423663 0.00568013
+v 0.939132 -0.0564442 0.00931648
+v 0.939132 -0.0564442 -0.00931716
+v 0.949024 -0.0423663 -0.0056807
+v 0.934088 -0.0434631 0.0375657
+v 0.939032 -0.0513688 0.0263577
+v 0.948923 -0.0372909 0.0227212
+v 0.945955 -0.0320448 0.0298551
+v 0.872347 -0.133605 0.0043531
+v 0.867821 -0.127724 0.0071729
+v 0.867821 -0.127724 -0.00717346
+v 0.872347 -0.133605 -0.00435366
+v 0.870151 -0.11182 0.0325095
+v 0.864551 -0.113265 0.0293204
+v 0.867919 -0.124012 0.0202331
+v 0.872445 -0.129893 0.0174133
+v 0.8745 -0.128523 0.0244543
+v 0.850695 -0.0781 0.0415705
+v 0.843493 -0.0804408 0.0380911
+v 0.856418 -0.0954789 0.0341317
+v 0.86202 -0.0940344 0.0373208
+v 0.861121 -0.113105 0.0318929
+v 0.853116 -0.116872 0.0272554
+v 0.86449 -0.123852 0.0228054
+v 0.896079 -0.0580182 0.0619822
+v 0.888408 -0.0615925 0.0615455
+v 0.896926 -0.0691153 0.0596011
+v 0.888993 -0.0472648 0.0614699
+v 0.881212 -0.0389622 0.0613522
+v 0.881321 -0.0508391 0.0610332
+v 0.898775 -0.00888366 0.0505432
+v 0.906338 -0.0285718 0.0503768
+v 0.913112 -0.0273841 0.0460856
+v 0.912806 -0.0181271 0.0447109
+v 0.895841 -0.042064 0.0606762
+v 0.898743 -0.0326108 0.0572575
+v 0.888061 -0.0337614 0.0605585
+v 0.909645 -0.0361018 0.0504511
+v 0.914242 -0.0496995 0.0487997
+v 0.92105 -0.0412219 0.0439552
+v 0.916419 -0.0349141 0.0461599
+v 0.903579 -0.0485073 0.0594377
+v 0.91108 -0.0526519 0.0543677
+v 0.906483 -0.0390542 0.0560191
+v 0.903237 -0.0696353 0.0584452
+v 0.909889 -0.0626828 0.0557562
+v 0.90239 -0.0585382 0.0608262
+v 0.936868 -0.0650123 0.00931648
+v 0.929649 -0.0821647 0.0127618
+v 0.929649 -0.0821647 -0.0127624
+v 0.936868 -0.0650123 -0.00931716
+v 0.916734 -0.0598424 0.0455952
+v 0.921266 -0.0764229 0.032988
+v 0.928486 -0.0592706 0.0295427
+v 0.923541 -0.0513648 0.0407508
+v 0.927638 -0.109506 0.0127618
+v 0.919821 -0.131965 0.00659309
+v 0.919821 -0.131965 -0.00659365
+v 0.927638 -0.109506 -0.0127624
+v 0.891228 -0.151306 0.00659309
+v 0.882885 -0.15028 0.0136341
+v 0.877002 -0.150551 0.0113941
+v 0.874948 -0.15192 0.0043531
+v 0.874948 -0.15192 -0.00435366
+v 0.877002 -0.150551 -0.0113947
+v 0.882885 -0.15028 -0.0136347
+v 0.891228 -0.151306 -0.00659365
+v 0.886448 -0.127856 0.0347571
+v 0.886754 -0.108548 0.0445127
+v 0.876218 -0.111424 0.0405724
+v 0.880566 -0.128127 0.0325171
+v 0.91419 -0.106008 0.0325419
+v 0.898334 -0.108131 0.0431698
+v 0.898029 -0.127439 0.0334143
+v 0.906372 -0.128466 0.0263733
+v 0.90328 -0.0779141 0.051862
+v 0.89861 -0.0896659 0.0471936
+v 0.914466 -0.087542 0.0365658
+v 0.909933 -0.0709615 0.049173
+v 0.884321 -0.0681036 0.0565013
+v 0.875012 -0.0714296 0.0544401
+v 0.88817 -0.0873781 0.0498884
+v 0.892839 -0.0756263 0.0545568
+v 0.863629 -0.0732091 0.0536849
+v 0.854929 -0.0760992 0.0494426
+v 0.866252 -0.0920335 0.0451929
+v 0.876787 -0.0891576 0.0491333
+v 0.776474 -0.0240446 0.0563724
+v 0.783656 -0.0195345 0.0597137
+v 0.764217 -0.00505426 0.0669772
+v 0.759941 -0.00979379 0.0647393
+v 0.836888 -0.0655695 0.0462954
+v 0.814908 -0.0451542 0.0527913
+v 0.807726 -0.0496642 0.04945
+v 0.829685 -0.0679102 0.042816
+v 0.850244 -0.0591788 0.0565266
+v 0.836302 -0.0267731 0.0628377
+v 0.819562 -0.0416536 0.0587802
+v 0.841543 -0.0620689 0.0522843
+v 0.873977 -0.0396965 0.0612597
+v 0.850835 -0.0224937 0.0651905
+v 0.864776 -0.0548995 0.0588795
+v 0.874085 -0.0515735 0.0609407
+v 0.887134 -0.00383458 0.0562675
+v 0.860873 -0.00747048 0.063333
+v 0.884015 -0.0246732 0.0594021
+v 0.894698 -0.0235227 0.0561011
+v 0.832737 0.0620391 0.0752481
+v 0.842375 0.0314157 0.0696806
+v 0.868637 0.0350517 0.062615
+v 0.863357 0.0556351 0.0495058
+v 0.849805 0.0793847 0.0561666
+v 0.848669 0.0805448 0.070648
+v 0.842406 0.0799015 0.0768731
+v 0.834918 0.0723073 0.0769392
+v 0.789422 0.0631307 0.0897864
+v 0.781625 0.0460166 0.0924056
+v 0.815981 0.0234067 0.0756551
+v 0.806341 0.0540301 0.0812227
+v 0.741922 0.0780551 0.118162
+v 0.761505 0.0613715 0.10765
+v 0.769304 0.0784857 0.105031
+v 0.755544 0.0900024 0.112463
+v 0.772467 0.0253176 0.0910829
+v 0.760054 0.0120663 0.0909108
+v 0.770646 0.0023075 0.0775385
+v 0.790085 -0.0121727 0.0702749
+v 0.806825 0.0027077 0.0743323
+v 0.715875 0.0468756 0.12486
+v 0.739259 0.035451 0.116712
+v 0.751673 0.0487022 0.116885
+v 0.732089 0.0653857 0.127396
+v 0.647493 0.0130529 0.0903536
+v 0.667389 0.0146192 0.0968633
+v 0.674542 0.0330288 0.108835
+v 0.64936 0.046988 0.108604
+v 0.636279 0.032501 0.0984335
+v 0.660085 -0.0135357 0.066618
+v 0.679964 -0.0162689 0.0707816
+v 0.67279 -0.000895342 0.083562
+v 0.652894 -0.00246182 0.0770524
+v 0.744068 0.0105008 0.0936081
+v 0.735588 -0.00490446 0.0852812
+v 0.750385 -0.00399737 0.0779978
+v 0.754661 0.000742036 0.0802357
+v 0.69404 0.0103947 0.101591
+v 0.716097 0.00197425 0.097089
+v 0.724579 0.0173796 0.105416
+v 0.701193 0.0288043 0.113563
+v 0.696612 -0.0199735 0.081251
+v 0.711495 -0.0130206 0.0895295
+v 0.689438 -0.0046001 0.0940314
+v 0.738744 -0.0175573 0.0758704
+v 0.757839 -0.0369787 0.0604291
+v 0.770074 -0.0309011 0.06022
+v 0.753541 -0.0166503 0.0685871
+v 0.726063 -0.0471219 0.0586016
+v 0.742313 -0.0457197 0.0618292
+v 0.723217 -0.0262983 0.0772705
+v 0.708335 -0.0332513 0.0689919
+v 0.784245 -0.0560631 0.0489963
+v 0.806524 -0.0723655 0.0396779
+v 0.818441 -0.0682315 0.0421532
+v 0.79648 -0.0499854 0.0487872
+v 0.822899 -0.088115 0.0362222
+v 0.839736 -0.102786 0.0301007
+v 0.84774 -0.0990189 0.0347382
+v 0.834816 -0.0839809 0.0386975
+v 0.845178 -0.124275 0.0116227
+v 0.836137 -0.125513 0.00412697
+v 0.836137 -0.125513 -0.00412754
+v 0.845178 -0.124275 -0.0116234
+v 0.856552 -0.131255 -0.00717346
+v 0.856552 -0.131255 0.0071729
+v 0.815238 -0.111862 0.00412697
+v 0.797485 -0.100558 0.0076414
+v 0.797485 -0.100558 -0.00764196
+v 0.815238 -0.111862 -0.00412754
+v 0.810925 -0.0923977 0.0301261
+v 0.800967 -0.0970024 0.0200232
+v 0.81872 -0.108307 0.0165088
+v 0.827761 -0.107068 0.0240046
+v 0.772187 -0.0875506 0.0076414
+v 0.740994 -0.0756071 0.00918451
+v 0.740994 -0.0756071 -0.00918508
+v 0.772187 -0.0875506 -0.00764196
+v 0.752225 -0.0658573 0.04202
+v 0.749601 -0.0734186 0.0273695
+v 0.780794 -0.0853621 0.0258264
+v 0.790753 -0.0807575 0.0359293
+v 0.768474 -0.0644552 0.0452477
+v 0.700045 -0.0704911 0.00918451
+v 0.658852 -0.05842 0.0134072
+v 0.658852 -0.05842 -0.0134079
+v 0.700045 -0.0704911 -0.00918508
+v 0.676262 -0.0312426 0.0486161
+v 0.670052 -0.0433364 0.0319616
+v 0.711244 -0.0554077 0.0277389
+v 0.713868 -0.0478463 0.0423893
+v 0.69614 -0.0339758 0.0527797
+v 0.581535 -0.0343882 0.0542849
+v 0.623962 -0.0285185 0.0484832
+v 0.630173 -0.0164247 0.0651377
+v 0.622981 -0.00535083 0.075572
+v 0.611766 0.0140973 0.0836519
+v 0.582657 0.0181254 0.0958029
+v 0.576047 -0.0207502 0.0851135
+v 0.619478 -0.04808 0.0134072
+v 0.577051 -0.0539497 0.019209
+v 0.577051 -0.0539497 -0.0192096
+v 0.619478 -0.04808 -0.0134079
+v 0.981451 0.00729448 0.00273175
+v 0.980024 0.00981772 0.00616286
+v 0.978493 0.0131669 0.00660178
+v 0.97783 0.0154192 0.00317067
+v 0.97783 0.0154192 -0.00317123
+v 0.978493 0.0131669 -0.00660234
+v 0.980024 0.00981772 -0.00616342
+v 0.981451 0.00729448 -0.00273231
+f 2806 2810 2815
+f 2806 2815 2821
+f 2797 2801 2811
+f 2805 2797 2811
+f 2789 2793 2802
+f 2796 2789 2802
+f 2788 2783 2794
+f 2792 2775 2803
+f 2778 2775 2792
+f 2767 2771 2804
+f 2774 2767 2804
+f 2756 2760 2764
+f 2756 2764 2772
+f 2766 2756 2772
+f 2763 2752 2813
+f 2773 2763 2813
+f 2747 2753 2759
+f 2759 2753 2765
+f 2754 2746 2817
+f 2809 2751 2816
+f 2738 2743 2761
+f 2755 2738 2761
+f 2730 2734 2744
+f 2737 2730 2744
+f 2710 2714 2718
+f 2710 2718 2722
+f 2710 2722 2731
+f 2710 2731 2741
+f 2709 2694 2715
+f 2697 2694 2709
+f 2682 2686 2690
+f 2682 2690 2695
+f 2682 2695 2700
+f 2681 2674 2687
+f 2673 2670 2688
+f 2662 2666 2691
+f 2685 2662 2691
+f 2669 2662 2685
+f 2652 2656 2659
+f 2652 2659 2692
+f 2665 2652 2692
+f 2649 2645 2720
+f 2645 2649 2657
+f 2651 2645 2657
+f 2642 2650 2713
+f 2713 2650 2719
+f 2639 2643 2693
+f 2693 2643 2716
+f 2658 2640 2689
+f 2689 2640 2696
+f 2636 2779 2795
+f 2782 2636 2795
+f 2623 2628 2637
+f 2623 2637 2787
+f 2800 2770 2812
+f 2591 2580 2724
+f 2585 2580 2591
+f 2644 2592 2717
+f 2717 2592 2723
+f 2721 2576 2732
+f 2729 2571 2735
+f 2575 2571 2729
+f 2570 2566 2736
+f 2557 2561 2574
+f 2561 2567 2574
+f 2547 2553 2562
+f 2556 2547 2562
+f 2534 2538 2542
+f 2534 2542 2550
+f 2539 2533 2726
+f 2579 2540 2725
+f 2527 2531 2543
+f 2537 2527 2543
+f 2527 2537 2584
+f 2521 2535 2549
+f 2523 2521 2549
+f 2536 2518 2727
+f 2520 2518 2536
+f 2515 2577 2728
+f 2517 2515 2728
+f 2511 2572 2578
+f 2514 2511 2578
+f 2510 2558 2573
+f 2513 2524 2548
+f 2513 2548 2559
+f 2733 2506 2745
+f 2565 2506 2733
+f 2509 2499 2749
+f 2742 2505 2762
+f 2505 2748 2762
+f 2750 2498 2818
+f 2482 2486 2490
+f 2482 2490 2494
+f 2469 2473 2492
+f 2476 2469 2492
+f 2457 2461 2465
+f 2457 2465 2479
+f 2601 2653 2668
+f 2618 2601 2668
+f 2493 2472 2563
+f 2552 2493 2563
+f 2446 2450 2453
+f 2446 2453 2568
+f 2560 2446 2568
+f 2452 2442 2507
+f 2452 2507 2569
+f 2434 2438 2443
+f 2434 2443 2455
+f 2428 2435 2454
+f 2449 2428 2454
+f 2445 2422 2451
+f 2422 2429 2451
+f 2413 2416 2423
+f 2413 2423 2448
+f 2403 2407 2417
+f 2412 2403 2417
+f 2394 2397 2408
+f 2402 2394 2408
+f 2384 2388 2398
+f 2393 2384 2398
+f 2375 2378 2389
+f 2383 2375 2389
+f 2365 2369 2379
+f 2374 2365 2379
+f 2361 2366 2471
+f 2366 2376 2471
+f 2376 2386 2471
+f 2385 2395 2405
+f 2385 2405 2474
+f 2468 2385 2474
+f 2475 2404 2564
+f 2404 2414 2564
+f 2414 2447 2564
+f 2464 2362 2480
+f 2362 2470 2480
+f 2526 2456 2532
+f 2462 2456 2526
+f 2546 2353 2554
+f 2356 2353 2546
+f 2495 2489 2555
+f 2352 2495 2555
+f 2348 2478 2488
+f 2348 2458 2478
+f 2541 2357 2551
+f 2341 2357 2541
+f 2485 2477 2491
+f 2530 2459 2544
+f 2459 2351 2544
+f 2441 2500 2508
+f 2302 2308 2314
+f 2302 2314 2502
+f 2292 2298 2315
+f 2307 2292 2315
+f 2272 2278 2282
+f 2272 2282 2285
+f 2262 2268 2279
+f 2271 2262 2279
+f 2252 2258 2269
+f 2261 2252 2269
+f 2242 2248 2259
+f 2251 2242 2259
+f 1508 1514 1517
+f 1508 1511 1514
+f 1496 1502 1505
+f 1496 1499 1502
+f 1484 1490 1493
+f 1484 1487 1490
+f 1472 1478 1481
+f 1472 1475 1478
+f 2286 2281 2291
+f 2291 2281 2299
+f 1460 1466 1469
+f 1460 1463 1466
+f 1424 1430 1433
+f 1424 1427 1430
+f 1436 1442 1445
+f 1436 1439 1442
+f 1448 1454 1457
+f 1448 1451 1454
+f 2444 2437 2501
+f 2437 2303 2501
+f 2228 2232 2238
+f 2228 2223 2232
+f 2220 2224 2227
+f 2220 2215 2224
+f 2212 2216 2219
+f 2212 2207 2216
+f 2204 2208 2211
+f 2204 2199 2208
+f 2196 2200 2203
+f 2196 2190 2200
+f 2187 2191 2195
+f 2187 2181 2191
+f 2178 2182 2186
+f 2178 2173 2182
+f 2170 2174 2177
+f 2170 2165 2174
+f 2162 2166 2169
+f 2162 2157 2166
+f 2149 2158 2161
+f 2226 2142 2233
+f 2136 2142 2226
+f 2218 2137 2225
+f 2132 2137 2218
+f 2210 2133 2217
+f 2127 2133 2210
+f 2202 2128 2209
+f 2194 2129 2201
+f 2122 2129 2194
+f 2116 2123 2193
+f 2185 2117 2192
+f 2112 2118 2184
+f 2176 2113 2183
+f 2108 2113 2176
+f 2168 2109 2175
+f 2104 2109 2168
+f 2160 2105 2167
+f 2099 2105 2160
+f 2156 2100 2159
+f 2091 2094 2145
+f 2140 2087 2143
+f 2087 2082 2143
+f 2078 2088 2139
+f 2074 2083 2086
+f 2074 2069 2083
+f 2081 2075 2089
+f 2065 2075 2081
+f 2062 2066 2080
+f 2062 2057 2066
+f 2054 2064 2134
+f 2131 2054 2134
+f 2063 2079 2135
+f 2135 2079 2138
+f 2048 2042 2059
+f 2060 2046 2067
+f 2039 2043 2047
+f 2034 2039 2047
+f 2038 2030 2044
+f 2030 2025 2044
+f 2045 2028 2068
+f 2068 2028 2076
+f 2090 2021 2095
+f 2017 2013 2093
+f 2013 2022 2093
+f 2012 2009 2023
+f 2004 2009 2012
+f 2001 2005 2015
+f 2001 1995 2005
+f 2002 2014 2016
+f 1991 2002 2016
+f 1992 2019 2084
+f 2072 1992 2084
+f 2085 2018 2092
+f 2085 2092 2144
+f 2007 1988 2010
+f 1983 1988 2007
+f 1999 1984 2006
+f 1979 1984 1999
+f 1976 1980 1998
+f 1976 1971 1980
+f 1968 1972 1975
+f 1968 1963 1972
+f 1964 1967 2032
+f 1959 1964 2032
+f 1960 2031 2041
+f 1955 1960 2041
+f 2037 1956 2040
+f 1949 1956 2037
+f 1946 1950 2036
+f 1946 1940 1950
+f 1944 1936 1951
+f 1928 1922 1932
+f 1926 1919 1933
+f 1919 1914 1933
+f 1911 1920 1925
+f 1911 1906 1920
+f 1902 1912 1924
+f 1902 1897 1912
+f 1903 1923 1927
+f 1892 1903 1927
+f 1896 1889 1904
+f 1905 1888 2120
+f 1934 1937 1943
+f 1934 1917 1937
+f 1947 2035 2052
+f 1883 1947 2052
+f 1879 1884 2051
+f 1880 2050 2056
+f 1882 1876 1885
+f 1876 1882 2125
+f 1891 1877 2124
+f 2121 1891 2124
+f 1878 1890 1895
+f 1870 1878 1895
+f 1867 1860 1872
+f 1865 1856 1873
+f 1887 1858 1948
+f 1858 1852 1948
+f 1875 1859 1886
+f 1874 1859 1875
+f 1881 2055 2130
+f 2126 1881 2130
+f 2053 2049 2061
+f 2049 2058 2061
+f 1898 1901 2119
+f 2115 1898 2119
+f 2111 1899 2114
+f 1848 1899 2111
+f 2107 1849 2110
+f 1844 1849 2107
+f 1900 1851 1913
+f 1851 1840 1913
+f 1847 1841 1850
+f 1836 1841 1847
+f 1837 1846 2102
+f 1832 1837 2102
+f 1833 2101 2155
+f 1829 1834 2154
+f 1828 1825 1835
+f 1835 1825 1838
+f 1824 1821 1839
+f 1839 1821 1842
+f 1843 1820 1907
+f 1843 1907 1910
+f 2103 1845 2106
+f 1817 1861 1866
+f 1817 1810 1861
+f 1807 1818 1869
+f 1807 1802 1818
+f 1799 1803 1806
+f 1799 1794 1803
+f 1791 1795 1798
+f 1791 1786 1795
+f 1781 1787 1790
+f 1781 1775 1787
+f 1772 1768 1780
+f 1768 1776 1780
+f 1774 1763 1863
+f 1764 1773 1785
+f 1765 1784 1854
+f 1766 1853 1857
+f 1766 1857 1864
+f 1805 1761 1819
+f 1757 1761 1805
+f 1797 1758 1804
+f 1754 1758 1797
+f 1789 1755 1796
+f 1751 1755 1789
+f 1779 1752 1788
+f 1747 1752 1779
+f 1742 1748 1778
+f 1746 1738 1749
+f 1734 1412 1741
+f 1412 1729 1741
+f 1729 1725 1741
+f 1725 1721 1741
+f 1741 1721 1762
+f 1759 1741 1762
+f 1756 1741 1759
+f 1753 1741 1756
+f 1750 1741 1753
+f 1716 1722 1724
+f 1792 1801 1931
+f 1801 1929 1931
+f 1800 1893 1930
+f 1800 1809 1893
+f 1782 1793 1935
+f 1782 1935 1942
+f 1808 1868 1894
+f 1868 1871 1894
+f 1769 1771 1862
+f 1815 1769 1862
+f 1767 1743 1777
+f 1706 1743 1767
+f 1783 1941 1945
+f 1855 1783 1945
+f 1418 1707 1770
+f 1418 1770 1814
+f 1712 1418 1814
+f 1719 1713 1813
+f 1723 1715 1812
+f 1760 1720 1816
+f 1720 1811 1816
+f 1700 1996 2000
+f 1692 1700 2000
+f 1994 1693 2003
+f 1688 1693 1994
+f 1683 2070 2073
+f 1689 1993 2071
+f 1687 1689 2071
+f 2027 1684 2077
+f 1676 1684 2027
+f 1677 2026 2029
+f 1665 1668 1681
+f 1661 1657 1669
+f 1664 1661 1669
+f 1658 1660 1695
+f 1691 1659 1694
+f 1682 1671 1685
+f 1686 1656 1690
+f 1670 1656 1686
+f 1650 1646 1697
+f 1648 1643 1701
+f 1698 1648 1701
+f 1639 1635 1642
+f 1642 1635 1702
+f 1629 1623 1654
+f 1620 1644 1649
+f 1644 1647 1649
+f 1627 1621 1655
+f 1614 1621 1627
+f 1619 1612 1645
+f 1612 1640 1645
+f 1618 1613 1622
+f 1606 1613 1618
+f 1699 1977 1997
+f 1699 1634 1977
+f 1637 1969 1978
+f 1637 1602 1969
+f 1605 1678 2033
+f 1970 1605 2033
+f 1603 1636 1638
+f 1603 1638 1672
+f 1641 1611 1673
+f 1611 1610 1673
+f 1610 1598 1673
+f 1595 1604 1675
+f 1604 1595 1679
+f 1591 1596 1674
+f 1601 1591 1674
+f 1597 1590 1631
+f 1586 1597 1631
+f 1594 1589 1680
+f 1589 1666 1680
+f 1652 1662 1667
+f 1588 1652 1667
+f 1584 1580 1593
+f 1593 1580 1632
+f 1585 1592 1600
+f 1574 1585 1600
+f 1579 1570 1633
+f 1573 1624 1628
+f 1572 1565 1625
+f 1569 1561 1626
+f 1561 1615 1626
+f 1558 1555 1567
+f 1554 1550 1568
+f 1550 1562 1568
+f 1587 1630 1653
+f 1663 1651 1696
+f 1560 1542 1616
+f 1531 1607 1617
+f 1541 1531 1617
+f 1408 1531 1541
+f 1530 1521 1608
+f 1520 1575 1609
+f 1575 1599 1609
+f 1559 1571 1582
+f 1559 1566 1571
+f 1551 1556 1557
+f 1551 1557 1581
+f 1551 1581 1583
+f 1578 1551 1583
+f 1519 1523 1576
+f 1403 1547 1552
+f 1403 1552 1577
+f 1526 1403 1577
+f 1403 1526 1536
+f 2814 1987 2822
+f 2011 1987 2814
+f 2257 2236 2270
+f 2277 2234 2283
+f 2234 2141 2283
+f 2146 2098 2317
+f 2313 2097 2503
+f 2504 2096 2819
+f 2096 2020 2819
+f 2024 2008 2820
+f 2297 2147 2316
+f 2284 2148 2300
+f 2267 2235 2280
+f 2247 2239 2260
+f 2239 2231 2260
+f 1515 1513 2436
+f 2427 1515 2436
+f 1512 1510 2439
+f 2433 1512 2439
+f 1509 1507 2304
+f 1509 2304 2440
+f 1506 1516 2432
+f 2305 1506 2432
+f 1503 1501 2426
+f 2310 1503 2426
+f 1500 1498 2430
+f 2421 1500 2430
+f 1497 1495 2306
+f 1497 2306 2431
+f 1494 1504 2309
+f 2301 1494 2309
+f 1491 1489 2420
+f 2294 1491 2420
+f 1488 1486 2424
+f 2415 1488 2424
+f 1485 1483 2311
+f 1485 2311 2425
+f 1482 1492 2293
+f 1482 2293 2312
+f 1479 1477 2287
+f 1479 2287 2296
+f 1476 1474 2411
+f 2288 1476 2411
+f 1473 1471 2418
+f 2406 1473 2418
+f 1470 1480 2295
+f 1470 2295 2419
+f 1467 1465 2401
+f 2274 1467 2401
+f 1464 1462 2409
+f 2396 1464 2409
+f 1461 1459 2289
+f 1461 2289 2410
+f 1458 1468 2273
+f 1458 2273 2290
+f 1455 1453 2399
+f 2387 1455 2399
+f 1452 1450 2275
+f 1452 2275 2400
+f 1449 1447 2263
+f 1449 2263 2276
+f 1446 1456 2392
+f 2264 1446 2392
+f 1443 1441 2390
+f 2377 1443 2390
+f 1440 1438 2265
+f 1440 2265 2391
+f 1437 1435 2253
+f 1437 2253 2266
+f 1434 1444 2382
+f 2254 1434 2382
+f 1431 1429 2243
+f 1431 2243 2256
+f 1428 1426 2373
+f 2244 1428 2373
+f 1425 1423 2380
+f 2368 1425 2380
+f 1422 1432 2255
+f 1422 2255 2381
+f 1375 1373 1543
+f 1375 1543 1564
+f 1372 1370 1545
+f 1538 1372 1545
+f 1366 1376 1563
+f 1549 1366 1563
+f 1363 1361 1714
+f 1363 1714 1718
+f 1360 1358 1732
+f 1709 1360 1732
+f 1354 1364 1717
+f 1354 1717 1727
+f 1351 1349 1419
+f 1351 1419 1711
+f 1348 1346 1415
+f 1348 1415 1420
+f 1342 1352 1710
+f 1342 1710 1731
+f 1336 1334 1421
+f 1414 1336 1421
+f 1333 1331 1708
+f 1417 1333 1708
+f 1330 1340 1737
+f 1703 1330 1737
+f 1315 1313 1522
+f 1315 1522 1529
+f 1312 1310 1524
+f 1518 1312 1524
+f 1309 1307 1537
+f 1525 1309 1537
+f 1306 1316 1528
+f 1306 1528 1533
+f 1074 1295 1303
+f 1074 1080 1295
+f 1297 1089 1302
+f 1089 1086 1302
+f 1296 1079 1299
+f 1079 1090 1299
+f 905 1271 1279
+f 905 878 1271
+f 1276 1190 1281
+f 1190 906 1281
+f 1273 1195 1278
+f 1195 1191 1278
+f 879 1259 1267
+f 879 898 1259
+f 1261 1192 1266
+f 1192 1199 1266
+f 1260 897 1263
+f 897 1193 1263
+f 884 1247 1255
+f 884 893 1247
+f 1249 899 1254
+f 899 883 1254
+f 1248 892 1251
+f 892 900 1251
+f 1058 1235 1243
+f 1058 1048 1235
+f 1240 1063 1245
+f 1063 1059 1245
+f 1237 1068 1242
+f 1068 1064 1242
+f 1236 1047 1239
+f 1047 1069 1239
+f 1065 1223 1231
+f 1065 1073 1223
+f 1225 1200 1230
+f 1200 1206 1230
+f 1224 1072 1227
+f 1072 1201 1227
+f 1207 1211 1219
+f 1207 1204 1211
+f 1213 1081 1218
+f 1081 1078 1218
+f 1212 1203 1215
+f 1203 1082 1215
+f 363 1179 1187
+f 363 476 1179
+f 1189 1184 2371
+f 364 1189 2371
+f 1186 1181 2245
+f 1186 2245 2372
+f 1180 475 1183
+f 1183 475 2246
+f 477 1167 1175
+f 477 368 1167
+f 1172 465 1177
+f 465 478 1177
+f 1169 353 1174
+f 353 466 1174
+f 1168 367 1171
+f 367 354 1171
+f 467 1155 1163
+f 467 358 1155
+f 1160 455 1165
+f 455 468 1165
+f 1157 344 1162
+f 344 456 1162
+f 1156 357 1159
+f 357 345 1159
+f 441 1143 1151
+f 441 458 1143
+f 1148 334 1153
+f 334 442 1153
+f 1145 348 1150
+f 348 335 1150
+f 1144 457 1147
+f 457 349 1147
+f 325 1131 1139
+f 325 436 1131
+f 1136 338 1141
+f 338 326 1141
+f 1133 443 1138
+f 443 339 1138
+f 1132 435 1135
+f 435 444 1135
+f 419 1119 1127
+f 419 438 1119
+f 1124 319 1129
+f 319 420 1129
+f 1121 329 1126
+f 329 320 1126
+f 1120 437 1123
+f 437 330 1123
+f 425 1107 1115
+f 425 422 1107
+f 1112 313 1117
+f 313 426 1117
+f 1109 323 1114
+f 323 314 1114
+f 1108 421 1111
+f 421 324 1111
+f 427 1095 1103
+f 427 318 1095
+f 1100 305 1105
+f 305 428 1105
+f 1097 309 1102
+f 309 306 1102
+f 1096 317 1099
+f 317 310 1099
+f 472 485 2250
+f 485 2240 2250
+f 461 452 481
+f 447 432 525
+f 431 416 526
+f 650 1 662
+f 249 2 649
+f 576 249 649
+f 414 250 575
+f 527 415 579
+f 451 448 532
+f 482 451 532
+f 471 462 486
+f 1990 663 2823
+f 663 7 2823
+f 1033 1060 1067
+f 1033 1067 1209
+f 1076 1033 1209
+f 1033 1076 1088
+f 1034 1087 1093
+f 1030 1052 1056
+f 1030 1056 1061
+f 1037 1030 1061
+f 1026 1030 1037
+f 1029 1039 1043
+f 1029 1043 1053
+f 1001 1011 1092
+f 1011 1035 1092
+f 1084 1002 1091
+f 993 1003 1083
+f 993 1083 1202
+f 1071 993 1202
+f 1051 994 1070
+f 948 915 958
+f 956 982 1023
+f 1046 1049 1055
+f 1055 1049 1062
+f 1054 1042 1057
+f 984 995 1045
+f 1045 995 1050
+f 1038 985 1044
+f 978 986 1041
+f 1032 979 1040
+f 1019 1027 1036
+f 1010 1019 1036
+f 1018 980 1028
+f 1028 980 1031
+f 949 957 1022
+f 944 949 1022
+f 929 945 1014
+f 1014 945 1025
+f 1021 1015 1024
+f 981 1021 1024
+f 1013 1016 1020
+f 1013 937 1016
+f 930 936 1006
+f 936 930 1017
+f 971 938 998
+f 998 938 1012
+f 1005 998 1012
+f 939 975 1007
+f 939 970 975
+f 687 931 1009
+f 687 641 931
+f 683 688 1008
+f 974 683 1008
+f 674 684 977
+f 909 674 977
+f 990 999 1004
+f 997 990 1004
+f 966 972 989
+f 989 972 1000
+f 961 991 996
+f 988 961 996
+f 960 964 992
+f 964 967 992
+f 983 962 987
+f 969 910 973
+f 973 910 976
+f 963 913 968
+f 913 911 968
+f 959 914 965
+f 941 921 952
+f 924 921 941
+f 934 925 940
+f 920 917 953
+f 916 951 954
+f 950 947 955
+f 947 942 955
+f 935 943 946
+f 640 646 932
+f 597 926 933
+f 645 597 933
+f 680 922 928
+f 601 680 928
+f 596 602 927
+f 671 918 923
+f 679 671 923
+f 675 912 919
+f 670 675 919
+f 792 797 849
+f 849 797 888
+f 889 796 895
+f 896 801 901
+f 842 907 1194
+f 902 842 1194
+f 800 842 902
+f 768 696 826
+f 691 696 768
+f 833 867 908
+f 841 833 908
+f 799 837 843
+f 799 760 837
+f 727 752 803
+f 752 755 803
+f 695 818 827
+f 695 701 818
+f 705 728 802
+f 705 802 811
+f 700 706 819
+f 706 810 819
+f 887 890 894
+f 852 850 855
+f 855 850 891
+f 886 855 891
+f 881 855 886
+f 855 881 1197
+f 876 855 1197
+f 873 855 876
+f 861 855 873
+f 858 855 861
+f 869 862 872
+f 832 863 868
+f 823 859 864
+f 836 823 864
+f 815 856 860
+f 822 815 860
+f 807 853 857
+f 814 807 857
+f 793 851 854
+f 806 793 854
+f 758 770 845
+f 758 766 770
+f 769 831 846
+f 830 839 847
+f 838 759 848
+f 829 834 840
+f 840 834 844
+f 821 824 835
+f 828 821 835
+f 813 816 825
+f 820 813 825
+f 805 808 817
+f 812 805 817
+f 754 794 809
+f 804 754 809
+f 757 761 798
+f 795 757 798
+f 570 567 777
+f 780 714 1823
+f 1823 714 1908
+f 784 781 1827
+f 781 1822 1827
+f 788 785 1831
+f 785 1826 1831
+f 789 1830 2153
+f 572 790 2152
+f 776 786 791
+f 571 776 791
+f 773 782 787
+f 779 773 787
+f 723 715 783
+f 772 723 783
+f 563 774 778
+f 566 563 778
+f 559 724 775
+f 562 559 775
+f 558 718 725
+f 558 553 718
+f 609 615 620
+f 620 615 622
+f 551 618 742
+f 551 543 618
+f 749 737 764
+f 745 737 749
+f 736 692 771
+f 765 736 771
+f 763 750 767
+f 756 751 762
+f 733 746 753
+f 731 733 753
+f 732 557 747
+f 557 548 747
+f 547 738 748
+f 738 547 741
+f 617 621 743
+f 626 739 744
+f 637 693 740
+f 625 637 740
+f 1916 699 1938
+f 702 699 1916
+f 719 552 735
+f 730 720 734
+f 710 721 729
+f 708 710 729
+f 709 716 726
+f 722 709 726
+f 1909 713 1921
+f 717 713 1909
+f 712 703 1915
+f 712 1915 1918
+f 707 704 711
+f 1939 698 1952
+f 697 636 1953
+f 694 636 697
+f 1954 633 1957
+f 639 633 1954
+f 1958 643 1961
+f 632 643 1958
+f 1962 690 1965
+f 642 690 1962
+f 1966 686 1973
+f 689 686 1966
+f 1974 678 1981
+f 685 678 1974
+f 1982 667 1985
+f 677 667 1982
+f 1986 664 1989
+f 666 664 1986
+f 588 529 655
+f 529 581 655
+f 604 654 681
+f 604 589 654
+f 659 672 682
+f 657 659 682
+f 658 668 676
+f 673 658 676
+f 651 665 669
+f 661 651 669
+f 580 652 656
+f 656 652 660
+f 583 577 653
+f 605 598 627
+f 627 598 648
+f 635 628 647
+f 644 635 647
+f 629 634 638
+f 624 629 638
+f 613 606 631
+f 623 614 630
+f 538 534 611
+f 534 593 611
+f 542 610 619
+f 542 539 610
+f 592 607 616
+f 612 592 616
+f 585 599 608
+f 595 585 608
+f 584 590 603
+f 600 584 603
+f 533 586 594
+f 537 530 591
+f 587 537 591
+f 528 578 582
+f 522 573 2151
+f 518 568 574
+f 521 518 574
+f 514 564 569
+f 517 514 569
+f 509 560 565
+f 513 509 565
+f 508 554 561
+f 512 504 555
+f 503 549 556
+f 500 544 550
+f 507 500 550
+f 499 496 545
+f 492 540 546
+f 495 492 546
+f 488 535 541
+f 491 488 541
+f 483 531 536
+f 487 483 536
+f 523 2150 2164
+f 519 524 2172
+f 524 2163 2172
+f 515 520 2180
+f 520 2171 2180
+f 510 516 2189
+f 516 2179 2189
+f 505 511 2198
+f 511 2188 2198
+f 501 506 2206
+f 506 2197 2206
+f 497 502 2214
+f 502 2205 2214
+f 493 498 2222
+f 498 2213 2222
+f 489 494 2230
+f 494 2221 2230
+f 484 490 2237
+f 490 2229 2237
+f 302 252 429
+f 308 302 429
+f 1154 1158 1161
+f 1154 1161 1164
+f 1166 1170 1173
+f 1166 1173 1176
+f 1178 1182 1185
+f 1178 1185 1188
+f 1142 1146 1149
+f 1142 1149 1152
+f 440 433 445
+f 445 433 450
+f 1130 1134 1137
+f 1130 1137 1140
+f 1118 1122 1125
+f 1118 1125 1128
+f 1106 1110 1113
+f 1106 1113 1116
+f 1094 1098 1101
+f 1094 1101 1104
+f 2241 480 2249
+f 480 473 2249
+f 470 474 479
+f 470 463 474
+f 460 464 469
+f 460 453 464
+f 446 454 459
+f 446 449 454
+f 424 434 439
+f 424 417 434
+f 251 423 430
+f 251 418 423
+f 244 253 301
+f 225 213 380
+f 286 225 380
+f 265 263 274
+f 207 374 390
+f 216 207 390
+f 266 273 287
+f 266 287 383
+f 260 258 379
+f 202 260 379
+f 375 203 378
+f 206 203 375
+f 226 289 2463
+f 226 2463 2529
+f 276 284 2467
+f 284 2363 2467
+f 278 194 341
+f 194 298 341
+f 298 332 341
+f 340 351 360
+f 282 340 360
+f 282 279 340
+f 2360 283 2367
+f 283 359 2367
+f 359 370 2367
+f 2364 369 2370
+f 369 365 2370
+f 362 366 371
+f 362 355 366
+f 350 356 361
+f 350 346 356
+f 343 347 352
+f 343 336 347
+f 331 337 342
+f 331 327 337
+f 297 328 333
+f 297 321 328
+f 300 295 315
+f 300 315 322
+f 294 311 316
+f 294 291 311
+f 290 307 312
+f 290 303 307
+f 293 188 304
+f 188 245 304
+f 292 296 299
+f 193 292 299
+f 193 189 292
+f 261 205 281
+f 205 195 281
+f 162 134 166
+f 123 134 162
+f 288 277 2460
+f 2460 277 2466
+f 275 280 285
+f 275 262 280
+f 259 268 271
+f 259 264 268
+f 53 3 255
+f 41 55 58
+f 58 55 247
+f 248 54 254
+f 192 59 246
+f 67 59 192
+f 198 229 240
+f 198 210 229
+f 183 199 243
+f 237 184 242
+f 179 184 237
+f 234 180 239
+f 75 180 234
+f 231 76 236
+f 221 76 231
+f 230 222 233
+f 209 222 230
+f 214 227 2528
+f 214 2528 2583
+f 217 214 2583
+f 78 218 2582
+f 219 77 224
+f 208 220 223
+f 208 215 220
+f 201 204 211
+f 201 196 204
+f 187 190 200
+f 190 197 200
+f 186 68 191
+f 182 69 185
+f 71 69 182
+f 82 72 181
+f 83 80 143
+f 143 80 176
+f 79 2581 2588
+f 177 79 2588
+f 13 10 30
+f 159 2624 2786
+f 151 159 2786
+f 24 152 2785
+f 19 24 2785
+f 111 108 127
+f 127 108 148
+f 107 88 149
+f 88 145 149
+f 87 85 146
+f 85 138 146
+f 139 84 142
+f 136 139 142
+f 136 131 139
+f 129 132 135
+f 126 129 135
+f 126 112 129
+f 113 125 2663
+f 113 2663 2672
+f 115 113 2672
+f 116 2671 2680
+f 122 117 2679
+f 114 118 121
+f 106 114 121
+f 106 109 114
+f 105 89 110
+f 91 89 105
+f 81 86 90
+f 81 90 94
+f 66 81 94
+f 66 73 81
+f 65 70 74
+f 65 60 70
+f 48 61 64
+f 48 42 61
+f 8 5 49
+f 50 4 57
+f 44 39 56
+f 39 51 56
+f 38 31 52
+f 31 9 52
+f 40 43 47
+f 37 40 47
+f 37 32 40
+f 29 33 36
+f 29 14 33
+f 25 15 28
+f 18 15 25
+f 20 2784 2791
+f 21 2790 2799
+f 16 21 2799
+f 17 2798 2808
+f 11 17 2808
+f 12 2807 2824
+f 6 12 2824
+f 2 3 4
+f 2 4 5
+f 2 5 6
+f 2 6 7
+f 1 2 7
+f 9 10 11
+f 9 11 12
+f 8 9 12
+f 14 15 16
+f 14 16 17
+f 13 14 17
+f 19 20 21
+f 18 19 21
+f 23 24 25
+f 22 23 25
+f 27 28 29
+f 26 27 29
+f 31 32 33
+f 30 31 33
+f 35 36 37
+f 34 35 37
+f 38 39 40
+f 42 43 44
+f 41 42 44
+f 46 47 48
+f 45 46 48
+f 50 51 52
+f 49 50 52
+f 54 55 56
+f 54 56 57
+f 53 54 57
+f 59 60 61
+f 58 59 61
+f 63 64 65
+f 63 65 66
+f 62 63 66
+f 68 69 70
+f 67 68 70
+f 72 73 74
+f 71 72 74
+f 76 77 78
+f 76 78 79
+f 76 79 80
+f 76 80 81
+f 76 81 82
+f 75 76 82
+f 84 85 86
+f 83 84 86
+f 88 89 90
+f 87 88 90
+f 92 93 94
+f 91 92 94
+f 104 105 106
+f 103 104 106
+f 108 109 110
+f 107 108 110
+f 112 113 114
+f 111 112 114
+f 116 117 118
+f 115 116 118
+f 120 121 122
+f 119 120 122
+f 124 125 126
+f 123 124 126
+f 127 128 129
+f 130 131 132
+f 134 135 136
+f 133 134 136
+f 137 138 139
+f 141 142 143
+f 140 141 143
+f 144 145 146
+f 147 148 149
+f 150 151 152
+f 176 177 178
+f 175 176 178
+f 180 181 182
+f 179 180 182
+f 184 185 186
+f 184 186 187
+f 183 184 187
+f 189 190 191
+f 189 191 192
+f 188 189 192
+f 194 195 196
+f 194 196 197
+f 193 194 197
+f 199 200 201
+f 198 199 201
+f 203 204 205
+f 202 203 205
+f 207 208 209
+f 207 209 210
+f 207 210 211
+f 206 207 211
+f 213 214 215
+f 213 215 216
+f 212 213 216
+f 218 219 220
+f 217 218 220
+f 222 223 224
+f 221 222 224
+f 225 226 227
+f 228 229 230
+f 231 232 233
+f 234 235 236
+f 237 238 239
+f 241 242 243
+f 240 241 243
+f 245 246 247
+f 245 247 248
+f 244 245 248
+f 250 251 252
+f 250 252 253
+f 250 253 254
+f 250 254 255
+f 249 250 255
+f 261 262 263
+f 261 263 264
+f 260 261 264
+f 266 267 268
+f 265 266 268
+f 274 275 276
+f 274 276 277
+f 273 274 277
+f 279 280 281
+f 278 279 281
+f 283 284 285
+f 282 283 285
+f 287 288 289
+f 286 287 289
+f 291 292 293
+f 290 291 293
+f 294 295 296
+f 298 299 300
+f 297 298 300
+f 302 303 304
+f 301 302 304
+f 306 307 308
+f 305 306 308
+f 310 311 312
+f 309 310 312
+f 314 315 316
+f 314 316 317
+f 314 317 318
+f 313 314 318
+f 320 321 322
+f 320 322 323
+f 320 323 324
+f 319 320 324
+f 326 327 328
+f 326 328 329
+f 326 329 330
+f 325 326 330
+f 331 332 333
+f 335 336 337
+f 335 337 338
+f 335 338 339
+f 334 335 339
+f 341 342 343
+f 340 341 343
+f 345 346 347
+f 345 347 348
+f 345 348 349
+f 344 345 349
+f 350 351 352
+f 354 355 356
+f 354 356 357
+f 354 357 358
+f 353 354 358
+f 360 361 362
+f 359 360 362
+f 364 365 366
+f 364 366 367
+f 364 367 368
+f 363 364 368
+f 369 370 371
+f 381 382 383
+f 380 381 383
+f 415 416 417
+f 415 417 418
+f 414 415 418
+f 420 421 422
+f 420 422 423
+f 420 423 424
+f 419 420 424
+f 426 427 428
+f 426 428 429
+f 426 429 430
+f 425 426 430
+f 432 433 434
+f 431 432 434
+f 436 437 438
+f 436 438 439
+f 436 439 440
+f 435 436 440
+f 442 443 444
+f 442 444 445
+f 442 445 446
+f 441 442 446
+f 448 449 450
+f 447 448 450
+f 452 453 454
+f 451 452 454
+f 456 457 458
+f 456 458 459
+f 456 459 460
+f 455 456 460
+f 462 463 464
+f 461 462 464
+f 466 467 468
+f 466 468 469
+f 466 469 470
+f 465 466 470
+f 472 473 474
+f 471 472 474
+f 476 477 478
+f 476 478 479
+f 476 479 480
+f 475 476 480
+f 482 483 484
+f 482 484 485
+f 482 485 486
+f 481 482 486
+f 488 489 490
+f 487 488 490
+f 492 493 494
+f 491 492 494
+f 496 497 498
+f 495 496 498
+f 500 501 502
+f 499 500 502
+f 504 505 506
+f 504 506 507
+f 503 504 507
+f 509 510 511
+f 509 511 512
+f 508 509 512
+f 514 515 516
+f 513 514 516
+f 518 519 520
+f 517 518 520
+f 522 523 524
+f 521 522 524
+f 526 527 528
+f 526 528 529
+f 526 529 530
+f 526 530 531
+f 526 531 532
+f 525 526 532
+f 534 535 536
+f 534 536 537
+f 533 534 537
+f 539 540 541
+f 538 539 541
+f 543 544 545
+f 543 545 546
+f 542 543 546
+f 548 549 550
+f 548 550 551
+f 547 548 551
+f 553 554 555
+f 553 555 556
+f 553 556 557
+f 552 553 557
+f 559 560 561
+f 558 559 561
+f 563 564 565
+f 562 563 565
+f 567 568 569
+f 566 567 569
+f 571 572 573
+f 571 573 574
+f 570 571 574
+f 576 577 578
+f 576 578 579
+f 575 576 579
+f 581 582 583
+f 580 581 583
+f 585 586 587
+f 584 585 587
+f 589 590 591
+f 588 589 591
+f 593 594 595
+f 592 593 595
+f 597 598 599
+f 597 599 600
+f 596 597 600
+f 602 603 604
+f 601 602 604
+f 606 607 608
+f 605 606 608
+f 610 611 612
+f 609 610 612
+f 614 615 616
+f 613 614 616
+f 618 619 620
+f 617 618 620
+f 622 623 624
+f 622 624 625
+f 622 625 626
+f 621 622 626
+f 628 629 630
+f 628 630 631
+f 627 628 631
+f 633 634 635
+f 632 633 635
+f 637 638 639
+f 636 637 639
+f 641 642 643
+f 641 643 644
+f 640 641 644
+f 646 647 648
+f 645 646 648
+f 650 651 652
+f 650 652 653
+f 649 650 653
+f 655 656 657
+f 654 655 657
+f 659 660 661
+f 658 659 661
+f 663 664 665
+f 662 663 665
+f 667 668 669
+f 666 667 669
+f 671 672 673
+f 670 671 673
+f 675 676 677
+f 675 677 678
+f 674 675 678
+f 680 681 682
+f 679 680 682
+f 684 685 686
+f 683 684 686
+f 688 689 690
+f 687 688 690
+f 692 693 694
+f 691 692 694
+f 696 697 698
+f 696 698 699
+f 695 696 699
+f 701 702 703
+f 701 703 704
+f 700 701 704
+f 706 707 708
+f 705 706 708
+f 710 711 712
+f 710 712 713
+f 709 710 713
+f 715 716 717
+f 714 715 717
+f 719 720 721
+f 719 721 722
+f 718 719 722
+f 724 725 726
+f 723 724 726
+f 728 729 730
+f 728 730 731
+f 727 728 731
+f 733 734 735
+f 732 733 735
+f 737 738 739
+f 737 739 740
+f 736 737 740
+f 742 743 744
+f 741 742 744
+f 746 747 748
+f 745 746 748
+f 750 751 752
+f 750 752 753
+f 749 750 753
+f 755 756 757
+f 754 755 757
+f 759 760 761
+f 759 761 762
+f 759 762 763
+f 758 759 763
+f 765 766 767
+f 764 765 767
+f 769 770 771
+f 768 769 771
+f 773 774 775
+f 772 773 775
+f 777 778 779
+f 776 777 779
+f 781 782 783
+f 780 781 783
+f 785 786 787
+f 784 785 787
+f 789 790 791
+f 788 789 791
+f 793 794 795
+f 792 793 795
+f 797 798 799
+f 797 799 800
+f 797 800 801
+f 796 797 801
+f 803 804 805
+f 802 803 805
+f 807 808 809
+f 806 807 809
+f 811 812 813
+f 810 811 813
+f 815 816 817
+f 814 815 817
+f 819 820 821
+f 818 819 821
+f 823 824 825
+f 822 823 825
+f 827 828 829
+f 827 829 830
+f 827 830 831
+f 826 827 831
+f 833 834 835
+f 833 835 836
+f 832 833 836
+f 838 839 840
+f 837 838 840
+f 842 843 844
+f 841 842 844
+f 846 847 848
+f 845 846 848
+f 849 850 851
+f 852 853 854
+f 855 856 857
+f 858 859 860
+f 862 863 864
+f 861 862 864
+f 866 867 868
+f 866 868 869
+f 865 866 869
+f 871 872 873
+f 870 871 873
+f 875 876 877
+f 875 877 878
+f 874 875 878
+f 880 881 882
+f 880 882 883
+f 879 880 883
+f 885 886 887
+f 884 885 887
+f 889 890 891
+f 888 889 891
+f 893 894 895
+f 893 895 896
+f 892 893 896
+f 898 899 900
+f 898 900 901
+f 898 901 902
+f 897 898 902
+f 904 905 906
+f 904 906 907
+f 904 907 908
+f 903 904 908
+f 910 911 912
+f 909 910 912
+f 914 915 916
+f 914 916 917
+f 914 917 918
+f 914 918 919
+f 913 914 919
+f 921 922 923
+f 920 921 923
+f 925 926 927
+f 925 927 928
+f 924 925 928
+f 930 931 932
+f 930 932 933
+f 930 933 934
+f 930 934 935
+f 929 930 935
+f 937 938 939
+f 936 937 939
+f 941 942 943
+f 940 941 943
+f 945 946 947
+f 944 945 947
+f 949 950 951
+f 948 949 951
+f 953 954 955
+f 952 953 955
+f 957 958 959
+f 957 959 960
+f 957 960 961
+f 957 961 962
+f 956 957 962
+f 963 964 965
+f 967 968 969
+f 966 967 969
+f 971 972 973
+f 970 971 973
+f 975 976 977
+f 974 975 977
+f 979 980 981
+f 979 981 982
+f 979 982 983
+f 978 979 983
+f 985 986 987
+f 985 987 988
+f 984 985 988
+f 990 991 992
+f 989 990 992
+f 994 995 996
+f 994 996 997
+f 993 994 997
+f 998 999 1000
+f 1002 1003 1004
+f 1002 1004 1005
+f 1001 1002 1005
+f 1007 1008 1009
+f 1006 1007 1009
+f 1011 1012 1013
+f 1010 1011 1013
+f 1015 1016 1017
+f 1014 1015 1017
+f 1019 1020 1021
+f 1018 1019 1021
+f 1023 1024 1025
+f 1022 1023 1025
+f 1026 1027 1028
+f 1030 1031 1032
+f 1029 1030 1032
+f 1034 1035 1036
+f 1034 1036 1037
+f 1033 1034 1037
+f 1039 1040 1041
+f 1038 1039 1041
+f 1043 1044 1045
+f 1043 1045 1046
+f 1042 1043 1046
+f 1048 1049 1050
+f 1048 1050 1051
+f 1047 1048 1051
+f 1052 1053 1054
+f 1055 1056 1057
+f 1059 1060 1061
+f 1059 1061 1062
+f 1058 1059 1062
+f 1064 1065 1066
+f 1064 1066 1067
+f 1063 1064 1067
+f 1069 1070 1071
+f 1069 1071 1072
+f 1069 1072 1073
+f 1068 1069 1073
+f 1075 1076 1077
+f 1075 1077 1078
+f 1074 1075 1078
+f 1080 1081 1082
+f 1080 1082 1083
+f 1080 1083 1084
+f 1079 1080 1084
+f 1086 1087 1088
+f 1085 1086 1088
+f 1090 1091 1092
+f 1090 1092 1093
+f 1089 1090 1093
+f 1094 1095 1096
+f 1097 1098 1099
+f 1100 1101 1102
+f 1103 1104 1105
+f 1106 1107 1108
+f 1109 1110 1111
+f 1112 1113 1114
+f 1115 1116 1117
+f 1118 1119 1120
+f 1121 1122 1123
+f 1124 1125 1126
+f 1127 1128 1129
+f 1130 1131 1132
+f 1133 1134 1135
+f 1136 1137 1138
+f 1139 1140 1141
+f 1142 1143 1144
+f 1145 1146 1147
+f 1148 1149 1150
+f 1151 1152 1153
+f 1154 1155 1156
+f 1157 1158 1159
+f 1160 1161 1162
+f 1163 1164 1165
+f 1166 1167 1168
+f 1169 1170 1171
+f 1172 1173 1174
+f 1175 1176 1177
+f 1178 1179 1180
+f 1181 1182 1183
+f 1184 1185 1186
+f 1187 1188 1189
+f 1191 1192 1193
+f 1191 1193 1194
+f 1190 1191 1194
+f 1196 1197 1198
+f 1196 1198 1199
+f 1195 1196 1199
+f 1201 1202 1203
+f 1201 1203 1204
+f 1200 1201 1204
+f 1206 1207 1208
+f 1206 1208 1209
+f 1205 1206 1209
+f 1403 1404 1405
+f 1403 1405 1406
+f 1402 1403 1406
+f 1408 1409 1410
+f 1408 1410 1411
+f 1407 1408 1411
+f 1413 1414 1415
+f 1413 1415 1416
+f 1412 1413 1416
+f 1418 1419 1420
+f 1418 1420 1421
+f 1417 1418 1421
+f 1422 1423 1424
+f 1425 1426 1427
+f 1428 1429 1430
+f 1431 1432 1433
+f 1434 1435 1436
+f 1437 1438 1439
+f 1440 1441 1442
+f 1443 1444 1445
+f 1446 1447 1448
+f 1449 1450 1451
+f 1452 1453 1454
+f 1455 1456 1457
+f 1458 1459 1460
+f 1461 1462 1463
+f 1464 1465 1466
+f 1467 1468 1469
+f 1470 1471 1472
+f 1473 1474 1475
+f 1476 1477 1478
+f 1479 1480 1481
+f 1482 1483 1484
+f 1485 1486 1487
+f 1488 1489 1490
+f 1491 1492 1493
+f 1494 1495 1496
+f 1497 1498 1499
+f 1500 1501 1502
+f 1503 1504 1505
+f 1506 1507 1508
+f 1509 1510 1511
+f 1512 1513 1514
+f 1515 1516 1517
+f 1519 1520 1521
+f 1519 1521 1522
+f 1518 1519 1522
+f 1524 1525 1526
+f 1523 1524 1526
+f 1528 1529 1530
+f 1528 1530 1531
+f 1528 1531 1532
+f 1527 1528 1532
+f 1534 1535 1536
+f 1534 1536 1537
+f 1533 1534 1537
+f 1539 1540 1541
+f 1539 1541 1542
+f 1539 1542 1543
+f 1538 1539 1543
+f 1545 1546 1547
+f 1545 1547 1548
+f 1544 1545 1548
+f 1550 1551 1552
+f 1550 1552 1553
+f 1549 1550 1553
+f 1554 1555 1556
+f 1557 1558 1559
+f 1561 1562 1563
+f 1561 1563 1564
+f 1560 1561 1564
+f 1566 1567 1568
+f 1566 1568 1569
+f 1565 1566 1569
+f 1571 1572 1573
+f 1570 1571 1573
+f 1575 1576 1577
+f 1575 1577 1578
+f 1574 1575 1578
+f 1580 1581 1582
+f 1579 1580 1582
+f 1583 1584 1585
+f 1587 1588 1589
+f 1586 1587 1589
+f 1591 1592 1593
+f 1590 1591 1593
+f 1595 1596 1597
+f 1594 1595 1597
+f 1599 1600 1601
+f 1598 1599 1601
+f 1603 1604 1605
+f 1602 1603 1605
+f 1607 1608 1609
+f 1607 1609 1610
+f 1606 1607 1610
+f 1611 1612 1613
+f 1615 1616 1617
+f 1615 1617 1618
+f 1614 1615 1618
+f 1620 1621 1622
+f 1619 1620 1622
+f 1624 1625 1626
+f 1624 1626 1627
+f 1623 1624 1627
+f 1629 1630 1631
+f 1629 1631 1632
+f 1629 1632 1633
+f 1628 1629 1633
+f 1635 1636 1637
+f 1634 1635 1637
+f 1639 1640 1641
+f 1638 1639 1641
+f 1643 1644 1645
+f 1642 1643 1645
+f 1646 1647 1648
+f 1650 1651 1652
+f 1650 1652 1653
+f 1650 1653 1654
+f 1650 1654 1655
+f 1649 1650 1655
+f 1657 1658 1659
+f 1656 1657 1659
+f 1661 1662 1663
+f 1660 1661 1663
+f 1665 1666 1667
+f 1664 1665 1667
+f 1669 1670 1671
+f 1668 1669 1671
+f 1673 1674 1675
+f 1672 1673 1675
+f 1677 1678 1679
+f 1677 1679 1680
+f 1677 1680 1681
+f 1677 1681 1682
+f 1676 1677 1682
+f 1684 1685 1686
+f 1684 1686 1687
+f 1683 1684 1687
+f 1689 1690 1691
+f 1688 1689 1691
+f 1693 1694 1695
+f 1693 1695 1696
+f 1693 1696 1697
+f 1693 1697 1698
+f 1692 1693 1698
+f 1700 1701 1702
+f 1699 1700 1702
+f 1704 1705 1706
+f 1704 1706 1707
+f 1704 1707 1708
+f 1703 1704 1708
+f 1710 1711 1712
+f 1710 1712 1713
+f 1710 1713 1714
+f 1709 1710 1714
+f 1716 1717 1718
+f 1716 1718 1719
+f 1715 1716 1719
+f 1721 1722 1723
+f 1720 1721 1723
+f 1725 1726 1727
+f 1724 1725 1727
+f 1729 1730 1731
+f 1729 1731 1732
+f 1728 1729 1732
+f 1734 1735 1736
+f 1734 1736 1737
+f 1733 1734 1737
+f 1739 1740 1741
+f 1738 1739 1741
+f 1743 1744 1745
+f 1743 1745 1746
+f 1742 1743 1746
+f 1748 1749 1750
+f 1747 1748 1750
+f 1751 1752 1753
+f 1754 1755 1756
+f 1757 1758 1759
+f 1760 1761 1762
+f 1764 1765 1766
+f 1763 1764 1766
+f 1768 1769 1770
+f 1767 1768 1770
+f 1772 1773 1774
+f 1771 1772 1774
+f 1776 1777 1778
+f 1776 1778 1779
+f 1775 1776 1779
+f 1781 1782 1783
+f 1781 1783 1784
+f 1781 1784 1785
+f 1780 1781 1785
+f 1787 1788 1789
+f 1786 1787 1789
+f 1791 1792 1793
+f 1790 1791 1793
+f 1795 1796 1797
+f 1794 1795 1797
+f 1799 1800 1801
+f 1798 1799 1801
+f 1803 1804 1805
+f 1802 1803 1805
+f 1807 1808 1809
+f 1806 1807 1809
+f 1811 1812 1813
+f 1811 1813 1814
+f 1811 1814 1815
+f 1810 1811 1815
+f 1817 1818 1819
+f 1816 1817 1819
+f 1821 1822 1823
+f 1820 1821 1823
+f 1825 1826 1827
+f 1824 1825 1827
+f 1829 1830 1831
+f 1828 1829 1831
+f 1833 1834 1835
+f 1832 1833 1835
+f 1837 1838 1839
+f 1836 1837 1839
+f 1841 1842 1843
+f 1840 1841 1843
+f 1845 1846 1847
+f 1844 1845 1847
+f 1849 1850 1851
+f 1848 1849 1851
+f 1853 1854 1855
+f 1852 1853 1855
+f 1857 1858 1859
+f 1856 1857 1859
+f 1861 1862 1863
+f 1861 1863 1864
+f 1861 1864 1865
+f 1860 1861 1865
+f 1867 1868 1869
+f 1866 1867 1869
+f 1871 1872 1873
+f 1871 1873 1874
+f 1870 1871 1874
+f 1876 1877 1878
+f 1875 1876 1878
+f 1880 1881 1882
+f 1879 1880 1882
+f 1884 1885 1886
+f 1884 1886 1887
+f 1883 1884 1887
+f 1889 1890 1891
+f 1888 1889 1891
+f 1893 1894 1895
+f 1893 1895 1896
+f 1892 1893 1896
+f 1898 1899 1900
+f 1897 1898 1900
+f 1902 1903 1904
+f 1902 1904 1905
+f 1901 1902 1905
+f 1907 1908 1909
+f 1906 1907 1909
+f 1911 1912 1913
+f 1910 1911 1913
+f 1915 1916 1917
+f 1914 1915 1917
+f 1919 1920 1921
+f 1918 1919 1921
+f 1923 1924 1925
+f 1923 1925 1926
+f 1922 1923 1926
+f 1928 1929 1930
+f 1927 1928 1930
+f 1932 1933 1934
+f 1932 1934 1935
+f 1931 1932 1935
+f 1937 1938 1939
+f 1936 1937 1939
+f 1941 1942 1943
+f 1941 1943 1944
+f 1940 1941 1944
+f 1946 1947 1948
+f 1945 1946 1948
+f 1950 1951 1952
+f 1950 1952 1953
+f 1950 1953 1954
+f 1949 1950 1954
+f 1956 1957 1958
+f 1955 1956 1958
+f 1960 1961 1962
+f 1959 1960 1962
+f 1964 1965 1966
+f 1963 1964 1966
+f 1968 1969 1970
+f 1967 1968 1970
+f 1972 1973 1974
+f 1971 1972 1974
+f 1976 1977 1978
+f 1975 1976 1978
+f 1980 1981 1982
+f 1979 1980 1982
+f 1984 1985 1986
+f 1983 1984 1986
+f 1988 1989 1990
+f 1987 1988 1990
+f 1992 1993 1994
+f 1991 1992 1994
+f 1996 1997 1998
+f 1996 1998 1999
+f 1995 1996 1999
+f 2001 2002 2003
+f 2000 2001 2003
+f 2005 2006 2007
+f 2004 2005 2007
+f 2009 2010 2011
+f 2008 2009 2011
+f 2013 2014 2015
+f 2012 2013 2015
+f 2017 2018 2019
+f 2016 2017 2019
+f 2021 2022 2023
+f 2021 2023 2024
+f 2020 2021 2024
+f 2026 2027 2028
+f 2025 2026 2028
+f 2030 2031 2032
+f 2030 2032 2033
+f 2029 2030 2033
+f 2035 2036 2037
+f 2034 2035 2037
+f 2039 2040 2041
+f 2038 2039 2041
+f 2043 2044 2045
+f 2043 2045 2046
+f 2042 2043 2046
+f 2048 2049 2050
+f 2048 2050 2051
+f 2048 2051 2052
+f 2047 2048 2052
+f 2054 2055 2056
+f 2053 2054 2056
+f 2058 2059 2060
+f 2057 2058 2060
+f 2062 2063 2064
+f 2061 2062 2064
+f 2066 2067 2068
+f 2065 2066 2068
+f 2070 2071 2072
+f 2069 2070 2072
+f 2074 2075 2076
+f 2074 2076 2077
+f 2073 2074 2077
+f 2079 2080 2081
+f 2078 2079 2081
+f 2083 2084 2085
+f 2082 2083 2085
+f 2087 2088 2089
+f 2086 2087 2089
+f 2091 2092 2093
+f 2090 2091 2093
+f 2095 2096 2097
+f 2095 2097 2098
+f 2094 2095 2098
+f 2100 2101 2102
+f 2100 2102 2103
+f 2099 2100 2103
+f 2105 2106 2107
+f 2104 2105 2107
+f 2109 2110 2111
+f 2108 2109 2111
+f 2113 2114 2115
+f 2112 2113 2115
+f 2117 2118 2119
+f 2117 2119 2120
+f 2117 2120 2121
+f 2116 2117 2121
+f 2123 2124 2125
+f 2123 2125 2126
+f 2122 2123 2126
+f 2128 2129 2130
+f 2128 2130 2131
+f 2127 2128 2131
+f 2133 2134 2135
+f 2132 2133 2135
+f 2137 2138 2139
+f 2137 2139 2140
+f 2136 2137 2140
+f 2142 2143 2144
+f 2142 2144 2145
+f 2142 2145 2146
+f 2142 2146 2147
+f 2142 2147 2148
+f 2141 2142 2148
+f 2150 2151 2152
+f 2150 2152 2153
+f 2150 2153 2154
+f 2150 2154 2155
+f 2150 2155 2156
+f 2149 2150 2156
+f 2158 2159 2160
+f 2157 2158 2160
+f 2162 2163 2164
+f 2161 2162 2164
+f 2166 2167 2168
+f 2165 2166 2168
+f 2170 2171 2172
+f 2169 2170 2172
+f 2174 2175 2176
+f 2173 2174 2176
+f 2178 2179 2180
+f 2177 2178 2180
+f 2182 2183 2184
+f 2182 2184 2185
+f 2181 2182 2185
+f 2187 2188 2189
+f 2186 2187 2189
+f 2191 2192 2193
+f 2191 2193 2194
+f 2190 2191 2194
+f 2196 2197 2198
+f 2195 2196 2198
+f 2200 2201 2202
+f 2199 2200 2202
+f 2204 2205 2206
+f 2203 2204 2206
+f 2208 2209 2210
+f 2207 2208 2210
+f 2212 2213 2214
+f 2211 2212 2214
+f 2216 2217 2218
+f 2215 2216 2218
+f 2220 2221 2222
+f 2219 2220 2222
+f 2224 2225 2226
+f 2223 2224 2226
+f 2228 2229 2230
+f 2227 2228 2230
+f 2232 2233 2234
+f 2232 2234 2235
+f 2232 2235 2236
+f 2231 2232 2236
+f 2238 2239 2240
+f 2237 2238 2240
+f 2242 2243 2244
+f 2242 2244 2245
+f 2242 2245 2246
+f 2241 2242 2246
+f 2248 2249 2250
+f 2247 2248 2250
+f 2252 2253 2254
+f 2252 2254 2255
+f 2252 2255 2256
+f 2251 2252 2256
+f 2258 2259 2260
+f 2257 2258 2260
+f 2262 2263 2264
+f 2262 2264 2265
+f 2262 2265 2266
+f 2261 2262 2266
+f 2268 2269 2270
+f 2267 2268 2270
+f 2272 2273 2274
+f 2272 2274 2275
+f 2272 2275 2276
+f 2271 2272 2276
+f 2278 2279 2280
+f 2277 2278 2280
+f 2282 2283 2284
+f 2281 2282 2284
+f 2286 2287 2288
+f 2286 2288 2289
+f 2286 2289 2290
+f 2285 2286 2290
+f 2292 2293 2294
+f 2292 2294 2295
+f 2292 2295 2296
+f 2291 2292 2296
+f 2298 2299 2300
+f 2297 2298 2300
+f 2302 2303 2304
+f 2302 2304 2305
+f 2302 2305 2306
+f 2301 2302 2306
+f 2308 2309 2310
+f 2308 2310 2311
+f 2308 2311 2312
+f 2307 2308 2312
+f 2314 2315 2316
+f 2314 2316 2317
+f 2313 2314 2317
+f 2349 2350 2351
+f 2348 2349 2351
+f 2361 2362 2363
+f 2360 2361 2363
+f 2365 2366 2367
+f 2364 2365 2367
+f 2369 2370 2371
+f 2369 2371 2372
+f 2369 2372 2373
+f 2368 2369 2373
+f 2374 2375 2376
+f 2378 2379 2380
+f 2378 2380 2381
+f 2378 2381 2382
+f 2377 2378 2382
+f 2384 2385 2386
+f 2383 2384 2386
+f 2388 2389 2390
+f 2388 2390 2391
+f 2388 2391 2392
+f 2387 2388 2392
+f 2393 2394 2395
+f 2397 2398 2399
+f 2397 2399 2400
+f 2397 2400 2401
+f 2396 2397 2401
+f 2403 2404 2405
+f 2402 2403 2405
+f 2407 2408 2409
+f 2407 2409 2410
+f 2407 2410 2411
+f 2406 2407 2411
+f 2412 2413 2414
+f 2416 2417 2418
+f 2416 2418 2419
+f 2416 2419 2420
+f 2415 2416 2420
+f 2422 2423 2424
+f 2422 2424 2425
+f 2422 2425 2426
+f 2421 2422 2426
+f 2428 2429 2430
+f 2428 2430 2431
+f 2428 2431 2432
+f 2427 2428 2432
+f 2434 2435 2436
+f 2433 2434 2436
+f 2438 2439 2440
+f 2437 2438 2440
+f 2442 2443 2444
+f 2441 2442 2444
+f 2446 2447 2448
+f 2445 2446 2448
+f 2449 2450 2451
+f 2453 2454 2455
+f 2452 2453 2455
+f 2457 2458 2459
+f 2456 2457 2459
+f 2461 2462 2463
+f 2460 2461 2463
+f 2465 2466 2467
+f 2464 2465 2467
+f 2469 2470 2471
+f 2468 2469 2471
+f 2473 2474 2475
+f 2472 2473 2475
+f 2477 2478 2479
+f 2477 2479 2480
+f 2476 2477 2480
+f 2486 2487 2488
+f 2485 2486 2488
+f 2490 2491 2492
+f 2490 2492 2493
+f 2489 2490 2493
+f 2499 2500 2501
+f 2499 2501 2502
+f 2499 2502 2503
+f 2499 2503 2504
+f 2498 2499 2504
+f 2506 2507 2508
+f 2506 2508 2509
+f 2505 2506 2509
+f 2511 2512 2513
+f 2510 2511 2513
+f 2514 2515 2516
+f 2517 2518 2519
+f 2520 2521 2522
+f 2523 2524 2525
+f 2527 2528 2529
+f 2526 2527 2529
+f 2530 2531 2532
+f 2534 2535 2536
+f 2533 2534 2536
+f 2538 2539 2540
+f 2537 2538 2540
+f 2542 2543 2544
+f 2542 2544 2545
+f 2541 2542 2545
+f 2547 2548 2549
+f 2547 2549 2550
+f 2547 2550 2551
+f 2546 2547 2551
+f 2553 2554 2555
+f 2552 2553 2555
+f 2557 2558 2559
+f 2556 2557 2559
+f 2561 2562 2563
+f 2561 2563 2564
+f 2560 2561 2564
+f 2566 2567 2568
+f 2566 2568 2569
+f 2565 2566 2569
+f 2571 2572 2573
+f 2571 2573 2574
+f 2570 2571 2574
+f 2576 2577 2578
+f 2575 2576 2578
+f 2580 2581 2582
+f 2580 2582 2583
+f 2580 2583 2584
+f 2579 2580 2584
+f 2586 2587 2588
+f 2585 2586 2588
+f 2590 2591 2592
+f 2589 2590 2592
+f 2635 2636 2637
+f 2638 2639 2640
+f 2641 2642 2643
+f 2645 2646 2647
+f 2644 2645 2647
+f 2648 2649 2650
+f 2652 2653 2654
+f 2651 2652 2654
+f 2655 2656 2657
+f 2658 2659 2660
+f 2662 2663 2664
+f 2661 2662 2664
+f 2666 2667 2668
+f 2665 2666 2668
+f 2670 2671 2672
+f 2669 2670 2672
+f 2674 2675 2676
+f 2674 2676 2677
+f 2674 2677 2678
+f 2674 2678 2679
+f 2674 2679 2680
+f 2673 2674 2680
+f 2682 2683 2684
+f 2681 2682 2684
+f 2686 2687 2688
+f 2685 2686 2688
+f 2690 2691 2692
+f 2689 2690 2692
+f 2694 2695 2696
+f 2693 2694 2696
+f 2698 2699 2700
+f 2697 2698 2700
+f 2710 2711 2712
+f 2709 2710 2712
+f 2714 2715 2716
+f 2713 2714 2716
+f 2718 2719 2720
+f 2717 2718 2720
+f 2722 2723 2724
+f 2722 2724 2725
+f 2722 2725 2726
+f 2722 2726 2727
+f 2722 2727 2728
+f 2721 2722 2728
+f 2730 2731 2732
+f 2729 2730 2732
+f 2734 2735 2736
+f 2733 2734 2736
+f 2738 2739 2740
+f 2738 2740 2741
+f 2737 2738 2741
+f 2743 2744 2745
+f 2742 2743 2745
+f 2747 2748 2749
+f 2747 2749 2750
+f 2746 2747 2750
+f 2752 2753 2754
+f 2751 2752 2754
+f 2756 2757 2758
+f 2755 2756 2758
+f 2760 2761 2762
+f 2759 2760 2762
+f 2763 2764 2765
+f 2767 2768 2769
+f 2766 2767 2769
+f 2771 2772 2773
+f 2770 2771 2773
+f 2775 2776 2777
+f 2774 2775 2777
+f 2779 2780 2781
+f 2778 2779 2781
+f 2783 2784 2785
+f 2783 2785 2786
+f 2783 2786 2787
+f 2782 2783 2787
+f 2789 2790 2791
+f 2788 2789 2791
+f 2793 2794 2795
+f 2792 2793 2795
+f 2797 2798 2799
+f 2796 2797 2799
+f 2801 2802 2803
+f 2801 2803 2804
+f 2800 2801 2804
+f 2806 2807 2808
+f 2805 2806 2808
+f 2810 2811 2812
+f 2810 2812 2813
+f 2809 2810 2813
+f 2815 2816 2817
+f 2815 2817 2818
+f 2815 2818 2819
+f 2815 2819 2820
+f 2814 2815 2820
+f 2822 2823 2824
+f 2821 2822 2824
+f 7 6 2823
+f 2823 6 2824
+f 5 8 12
+f 6 5 12
+f 2807 11 2808
+f 12 11 2807
+f 10 13 17
+f 11 10 17
+f 2798 16 2799
+f 17 16 2798
+f 15 18 21
+f 16 15 21
+f 2790 20 2791
+f 21 20 2790
+f 2784 19 2785
+f 20 19 2784
+f 19 18 24
+f 24 18 25
+f 28 14 29
+f 15 14 28
+f 22 25 27
+f 27 25 28
+f 13 30 33
+f 14 13 33
+f 36 32 37
+f 33 32 36
+f 26 29 35
+f 35 29 36
+f 31 38 40
+f 32 31 40
+f 43 39 44
+f 40 39 43
+f 47 42 48
+f 43 42 47
+f 34 37 46
+f 46 37 47
+f 10 9 30
+f 30 9 31
+f 8 49 52
+f 9 8 52
+f 39 38 51
+f 51 38 52
+f 56 50 57
+f 51 50 56
+f 41 44 55
+f 55 44 56
+f 3 53 57
+f 4 3 57
+f 5 4 49
+f 49 4 50
+f 41 58 61
+f 42 41 61
+f 64 60 65
+f 61 60 64
+f 45 48 63
+f 63 48 64
+f 59 67 70
+f 60 59 70
+f 69 71 74
+f 70 69 74
+f 66 65 73
+f 73 65 74
+f 81 72 82
+f 73 72 81
+f 80 83 86
+f 81 80 86
+f 85 87 90
+f 86 85 90
+f 89 91 94
+f 90 89 94
+f 62 66 93
+f 93 66 94
+f 92 91 104
+f 104 91 105
+f 88 107 110
+f 89 88 110
+f 106 105 109
+f 109 105 110
+f 108 111 114
+f 109 108 114
+f 113 115 118
+f 114 113 118
+f 121 117 122
+f 118 117 121
+f 103 106 120
+f 120 106 121
+f 2679 116 2680
+f 117 116 2679
+f 119 122 2678
+f 2678 122 2679
+f 2671 115 2672
+f 116 115 2671
+f 125 112 126
+f 113 112 125
+f 2663 124 2664
+f 125 124 2663
+f 111 127 129
+f 112 111 129
+f 128 130 132
+f 129 128 132
+f 135 131 136
+f 132 131 135
+f 123 126 134
+f 134 126 135
+f 130 137 139
+f 131 130 139
+f 85 84 138
+f 138 84 139
+f 142 83 143
+f 84 83 142
+f 133 136 141
+f 141 136 142
+f 137 144 146
+f 138 137 146
+f 88 87 145
+f 145 87 146
+f 144 147 149
+f 145 144 149
+f 108 107 148
+f 148 107 149
+f 147 127 148
+f 128 127 147
+f 23 150 152
+f 24 23 152
+f 2785 151 2786
+f 152 151 2785
+f 2587 177 2588
+f 178 177 2587
+f 80 79 176
+f 176 79 177
+f 2581 78 2582
+f 79 78 2581
+f 140 143 175
+f 175 143 176
+f 181 71 182
+f 72 71 181
+f 75 82 180
+f 180 82 181
+f 185 68 186
+f 69 68 185
+f 179 182 184
+f 184 182 185
+f 191 67 192
+f 68 67 191
+f 187 186 190
+f 190 186 191
+f 189 193 197
+f 190 189 197
+f 200 196 201
+f 197 196 200
+f 183 187 199
+f 199 187 200
+f 204 195 205
+f 196 195 204
+f 203 206 211
+f 204 203 211
+f 198 201 210
+f 210 201 211
+f 215 207 216
+f 208 207 215
+f 214 217 220
+f 215 214 220
+f 223 219 224
+f 220 219 223
+f 209 208 222
+f 222 208 223
+f 78 77 218
+f 218 77 219
+f 76 221 224
+f 77 76 224
+f 2582 217 2583
+f 218 217 2582
+f 213 225 227
+f 214 213 227
+f 2528 226 2529
+f 227 226 2528
+f 210 209 229
+f 229 209 230
+f 221 231 233
+f 222 221 233
+f 228 230 232
+f 232 230 233
+f 75 234 236
+f 76 75 236
+f 232 231 235
+f 235 231 236
+f 179 237 239
+f 180 179 239
+f 235 234 238
+f 238 234 239
+f 242 183 243
+f 184 183 242
+f 238 237 241
+f 241 237 242
+f 198 240 243
+f 199 198 243
+f 240 228 241
+f 229 228 240
+f 246 58 247
+f 59 58 246
+f 188 192 245
+f 245 192 246
+f 55 54 247
+f 247 54 248
+f 254 53 255
+f 54 53 254
+f 244 248 253
+f 253 248 254
+f 2 249 255
+f 3 2 255
+f 258 260 264
+f 259 258 264
+f 263 265 268
+f 264 263 268
+f 263 262 274
+f 274 262 275
+f 280 261 281
+f 262 261 280
+f 279 282 285
+f 280 279 285
+f 276 275 284
+f 284 275 285
+f 2466 276 2467
+f 277 276 2466
+f 289 288 2463
+f 288 2460 2463
+f 273 277 287
+f 287 277 288
+f 194 278 281
+f 195 194 281
+f 202 205 260
+f 260 205 261
+f 292 188 293
+f 189 188 292
+f 291 294 296
+f 292 291 296
+f 299 295 300
+f 296 295 299
+f 194 193 298
+f 298 193 299
+f 244 301 304
+f 245 244 304
+f 290 293 303
+f 303 293 304
+f 307 302 308
+f 303 302 307
+f 306 309 312
+f 307 306 312
+f 291 290 311
+f 311 290 312
+f 316 310 317
+f 311 310 316
+f 295 294 315
+f 315 294 316
+f 322 314 323
+f 315 314 322
+f 297 300 321
+f 321 300 322
+f 328 320 329
+f 321 320 328
+f 327 331 333
+f 328 327 333
+f 298 297 332
+f 332 297 333
+f 337 326 338
+f 327 326 337
+f 342 336 343
+f 337 336 342
+f 332 331 341
+f 341 331 342
+f 347 335 348
+f 336 335 347
+f 346 350 352
+f 347 346 352
+f 340 343 351
+f 351 343 352
+f 356 345 357
+f 346 345 356
+f 361 355 362
+f 356 355 361
+f 351 350 360
+f 360 350 361
+f 366 354 367
+f 355 354 366
+f 365 369 371
+f 366 365 371
+f 359 362 370
+f 370 362 371
+f 2370 364 2371
+f 365 364 2370
+f 370 369 2367
+f 369 2364 2367
+f 284 283 2363
+f 283 2360 2363
+f 359 282 360
+f 283 282 359
+f 340 278 341
+f 279 278 340
+f 225 286 289
+f 226 225 289
+f 207 206 374
+f 374 206 375
+f 378 202 379
+f 203 202 378
+f 382 266 383
+f 267 266 382
+f 286 380 383
+f 287 286 383
+f 273 265 274
+f 266 265 273
+f 213 212 380
+f 380 212 381
+f 301 252 302
+f 253 252 301
+f 250 414 418
+f 251 250 418
+f 423 417 424
+f 418 417 423
+f 422 425 430
+f 423 422 430
+f 252 251 429
+f 429 251 430
+f 416 431 434
+f 417 416 434
+f 439 433 440
+f 434 433 439
+f 419 424 438
+f 438 424 439
+f 449 445 450
+f 446 445 449
+f 448 451 454
+f 449 448 454
+f 459 453 460
+f 454 453 459
+f 441 446 458
+f 458 446 459
+f 452 461 464
+f 453 452 464
+f 469 463 470
+f 464 463 469
+f 455 460 468
+f 468 460 469
+f 462 471 474
+f 463 462 474
+f 479 473 480
+f 474 473 479
+f 465 470 478
+f 478 470 479
+f 2249 472 2250
+f 473 472 2249
+f 475 480 2246
+f 480 2241 2246
+f 428 308 429
+f 305 308 428
+f 313 318 426
+f 426 318 427
+f 319 324 420
+f 420 324 421
+f 325 330 436
+f 436 330 437
+f 334 339 442
+f 442 339 443
+f 444 440 445
+f 435 440 444
+f 432 447 450
+f 433 432 450
+f 344 349 456
+f 456 349 457
+f 476 368 477
+f 363 368 476
+f 466 358 467
+f 353 358 466
+f 2229 489 2230
+f 490 489 2229
+f 485 484 2240
+f 484 2237 2240
+f 483 487 490
+f 484 483 490
+f 2221 493 2222
+f 494 493 2221
+f 488 491 494
+f 489 488 494
+f 2213 497 2214
+f 498 497 2213
+f 492 495 498
+f 493 492 498
+f 2205 501 2206
+f 502 501 2205
+f 496 499 502
+f 497 496 502
+f 2197 505 2198
+f 506 505 2197
+f 506 500 507
+f 501 500 506
+f 2188 510 2189
+f 511 510 2188
+f 511 504 512
+f 505 504 511
+f 2179 515 2180
+f 516 515 2179
+f 509 513 516
+f 510 509 516
+f 2171 519 2172
+f 520 519 2171
+f 514 517 520
+f 515 514 520
+f 2163 523 2164
+f 524 523 2163
+f 518 521 524
+f 519 518 524
+f 2150 522 2151
+f 523 522 2150
+f 488 487 535
+f 535 487 536
+f 531 482 532
+f 483 482 531
+f 536 530 537
+f 531 530 536
+f 492 491 540
+f 540 491 541
+f 534 538 541
+f 535 534 541
+f 496 495 545
+f 545 495 546
+f 539 542 546
+f 540 539 546
+f 500 499 544
+f 544 499 545
+f 503 507 549
+f 549 507 550
+f 550 543 551
+f 544 543 550
+f 504 503 555
+f 555 503 556
+f 556 548 557
+f 549 548 556
+f 508 512 554
+f 554 512 555
+f 509 508 560
+f 560 508 561
+f 553 558 561
+f 554 553 561
+f 514 513 564
+f 564 513 565
+f 559 562 565
+f 560 559 565
+f 518 517 568
+f 568 517 569
+f 563 566 569
+f 564 563 569
+f 522 521 573
+f 573 521 574
+f 567 570 574
+f 568 567 574
+f 2151 572 2152
+f 573 572 2151
+f 582 577 583
+f 578 577 582
+f 529 528 581
+f 581 528 582
+f 578 527 579
+f 528 527 578
+f 584 587 590
+f 590 587 591
+f 533 537 586
+f 586 537 587
+f 529 588 591
+f 530 529 591
+f 534 533 593
+f 593 533 594
+f 594 585 595
+f 586 585 594
+f 596 600 602
+f 602 600 603
+f 585 584 599
+f 599 584 600
+f 603 589 604
+f 590 589 603
+f 592 595 607
+f 607 595 608
+f 598 605 608
+f 599 598 608
+f 609 612 615
+f 615 612 616
+f 593 592 611
+f 611 592 612
+f 606 613 616
+f 607 606 616
+f 610 538 611
+f 539 538 610
+f 619 609 620
+f 610 609 619
+f 543 542 618
+f 618 542 619
+f 624 623 629
+f 629 623 630
+f 615 614 622
+f 622 614 623
+f 630 613 631
+f 614 613 630
+f 605 627 631
+f 606 605 631
+f 625 624 637
+f 637 624 638
+f 634 628 635
+f 629 628 634
+f 638 633 639
+f 634 633 638
+f 640 644 646
+f 646 644 647
+f 632 635 643
+f 643 635 644
+f 647 627 648
+f 628 627 647
+f 597 645 648
+f 598 597 648
+f 576 649 653
+f 577 576 653
+f 580 583 652
+f 652 583 653
+f 660 651 661
+f 652 651 660
+f 657 656 659
+f 659 656 660
+f 581 580 655
+f 655 580 656
+f 658 661 668
+f 668 661 669
+f 650 662 665
+f 651 650 665
+f 664 666 669
+f 665 664 669
+f 670 673 675
+f 675 673 676
+f 659 658 672
+f 672 658 673
+f 676 667 677
+f 668 667 676
+f 654 657 681
+f 681 657 682
+f 671 679 682
+f 672 671 682
+f 654 588 655
+f 589 588 654
+f 601 604 680
+f 680 604 681
+f 667 666 1985
+f 1985 666 1986
+f 1989 663 1990
+f 664 663 1989
+f 678 677 1981
+f 1981 677 1982
+f 686 685 1973
+f 1973 685 1974
+f 674 678 684
+f 684 678 685
+f 690 689 1965
+f 1965 689 1966
+f 683 686 688
+f 688 686 689
+f 643 642 1961
+f 1961 642 1962
+f 641 687 690
+f 642 641 690
+f 633 632 1957
+f 1957 632 1958
+f 636 639 1953
+f 1953 639 1954
+f 691 694 696
+f 696 694 697
+f 637 636 693
+f 693 636 694
+f 698 697 1952
+f 1952 697 1953
+f 699 698 1938
+f 1938 698 1939
+f 708 707 710
+f 710 707 711
+f 700 704 706
+f 706 704 707
+f 711 703 712
+f 704 703 711
+f 713 712 1921
+f 712 1918 1921
+f 1915 702 1916
+f 703 702 1915
+f 714 717 1908
+f 1908 717 1909
+f 709 713 716
+f 716 713 717
+f 718 722 725
+f 725 722 726
+f 710 709 721
+f 721 709 722
+f 715 723 726
+f 716 715 726
+f 705 708 728
+f 728 708 729
+f 729 720 730
+f 721 720 729
+f 734 719 735
+f 720 719 734
+f 731 730 733
+f 733 730 734
+f 557 732 735
+f 552 557 735
+f 553 552 718
+f 718 552 719
+f 695 699 701
+f 701 699 702
+f 626 625 739
+f 739 625 740
+f 692 736 740
+f 693 692 740
+f 621 626 743
+f 743 626 744
+f 738 741 744
+f 739 738 744
+f 621 620 622
+f 617 620 621
+f 618 617 742
+f 742 617 743
+f 741 551 742
+f 547 551 741
+f 737 745 748
+f 738 737 748
+f 548 547 747
+f 747 547 748
+f 733 732 746
+f 746 732 747
+f 727 731 752
+f 752 731 753
+f 745 749 753
+f 746 745 753
+f 757 756 761
+f 761 756 762
+f 752 751 755
+f 755 751 756
+f 762 750 763
+f 751 750 762
+f 758 763 766
+f 766 763 767
+f 749 764 767
+f 750 749 767
+f 766 765 770
+f 770 765 771
+f 737 736 764
+f 764 736 765
+f 691 768 771
+f 692 691 771
+f 559 558 724
+f 724 558 725
+f 563 562 774
+f 774 562 775
+f 723 772 775
+f 724 723 775
+f 567 566 777
+f 777 566 778
+f 778 773 779
+f 774 773 778
+f 773 772 782
+f 782 772 783
+f 714 780 783
+f 715 714 783
+f 776 779 786
+f 786 779 787
+f 781 784 787
+f 782 781 787
+f 572 571 790
+f 790 571 791
+f 776 570 777
+f 571 570 776
+f 785 788 791
+f 786 785 791
+f 2152 789 2153
+f 790 789 2152
+f 1830 788 1831
+f 789 788 1830
+f 1826 784 1827
+f 785 784 1826
+f 1822 780 1823
+f 781 780 1822
+f 792 795 797
+f 797 795 798
+f 754 757 794
+f 794 757 795
+f 798 760 799
+f 761 760 798
+f 805 804 808
+f 808 804 809
+f 755 754 803
+f 803 754 804
+f 793 806 809
+f 794 793 809
+f 813 812 816
+f 816 812 817
+f 802 805 811
+f 811 805 812
+f 807 814 817
+f 808 807 817
+f 821 820 824
+f 824 820 825
+f 810 813 819
+f 819 813 820
+f 815 822 825
+f 816 815 825
+f 829 828 834
+f 834 828 835
+f 818 821 827
+f 827 821 828
+f 835 823 836
+f 824 823 835
+f 833 841 844
+f 834 833 844
+f 837 840 843
+f 843 840 844
+f 830 829 839
+f 839 829 840
+f 839 838 847
+f 847 838 848
+f 760 759 837
+f 837 759 838
+f 758 845 848
+f 759 758 848
+f 831 830 846
+f 846 830 847
+f 770 769 845
+f 845 769 846
+f 768 826 831
+f 769 768 831
+f 807 806 853
+f 853 806 854
+f 792 849 851
+f 793 792 851
+f 850 852 854
+f 851 850 854
+f 815 814 856
+f 856 814 857
+f 852 855 857
+f 853 852 857
+f 823 822 859
+f 859 822 860
+f 855 858 860
+f 856 855 860
+f 832 836 863
+f 863 836 864
+f 858 861 864
+f 859 858 864
+f 833 832 867
+f 867 832 868
+f 868 862 869
+f 863 862 868
+f 865 869 871
+f 871 869 872
+f 872 861 873
+f 862 861 872
+f 849 888 891
+f 850 849 891
+f 887 886 890
+f 890 886 891
+f 882 881 885
+f 885 881 886
+f 1197 880 1198
+f 881 880 1197
+f 870 873 875
+f 875 873 876
+f 894 889 895
+f 890 889 894
+f 884 887 893
+f 893 887 894
+f 879 883 898
+f 898 883 899
+f 900 896 901
+f 892 896 900
+f 874 878 904
+f 904 878 905
+f 1193 902 1194
+f 897 902 1193
+f 867 866 908
+f 866 903 908
+f 810 705 811
+f 706 705 810
+f 701 700 818
+f 818 700 819
+f 802 727 803
+f 728 727 802
+f 696 695 826
+f 826 695 827
+f 800 799 842
+f 842 799 843
+f 842 841 907
+f 907 841 908
+f 801 800 901
+f 901 800 902
+f 796 801 895
+f 895 801 896
+f 797 796 888
+f 888 796 889
+f 671 670 918
+f 918 670 919
+f 674 909 912
+f 675 674 912
+f 911 913 919
+f 912 911 919
+f 680 679 922
+f 922 679 923
+f 917 920 923
+f 918 917 923
+f 597 596 926
+f 926 596 927
+f 927 601 928
+f 602 601 927
+f 921 924 928
+f 922 921 928
+f 646 645 932
+f 932 645 933
+f 933 925 934
+f 926 925 933
+f 641 640 931
+f 931 640 932
+f 946 942 947
+f 943 942 946
+f 929 935 945
+f 945 935 946
+f 934 940 943
+f 935 934 943
+f 941 952 955
+f 942 941 955
+f 951 950 954
+f 954 950 955
+f 944 947 949
+f 949 947 950
+f 917 916 953
+f 953 916 954
+f 915 948 951
+f 916 915 951
+f 921 920 952
+f 952 920 953
+f 940 924 941
+f 925 924 940
+f 960 959 964
+f 964 959 965
+f 915 914 958
+f 958 914 959
+f 913 963 965
+f 914 913 965
+f 968 910 969
+f 911 910 968
+f 964 963 967
+f 967 963 968
+f 976 909 977
+f 910 909 976
+f 970 973 975
+f 975 973 976
+f 966 969 972
+f 972 969 973
+f 978 983 986
+f 986 983 987
+f 956 962 982
+f 982 962 983
+f 987 961 988
+f 962 961 987
+f 966 989 992
+f 967 966 992
+f 961 960 991
+f 991 960 992
+f 984 988 995
+f 995 988 996
+f 996 990 997
+f 991 990 996
+f 971 998 1000
+f 972 971 1000
+f 990 989 999
+f 999 989 1000
+f 993 997 1003
+f 1003 997 1004
+f 1004 998 1005
+f 999 998 1004
+f 683 974 977
+f 684 683 977
+f 975 974 1007
+f 1007 974 1008
+f 1008 687 1009
+f 688 687 1008
+f 930 1006 1009
+f 931 930 1009
+f 970 938 971
+f 939 938 970
+f 936 939 1006
+f 1006 939 1007
+f 1001 1005 1011
+f 1011 1005 1012
+f 1012 937 1013
+f 938 937 1012
+f 929 1014 1017
+f 930 929 1017
+f 937 936 1016
+f 1016 936 1017
+f 1020 1015 1021
+f 1016 1015 1020
+f 1010 1013 1019
+f 1019 1013 1020
+f 982 981 1023
+f 1023 981 1024
+f 980 1018 1021
+f 981 980 1021
+f 1024 1014 1025
+f 1015 1014 1024
+f 944 1022 1025
+f 945 944 1025
+f 957 948 958
+f 949 948 957
+f 1022 956 1023
+f 957 956 1022
+f 1031 979 1032
+f 980 979 1031
+f 1026 1028 1030
+f 1030 1028 1031
+f 1019 1018 1027
+f 1027 1018 1028
+f 1011 1010 1035
+f 1035 1010 1036
+f 1036 1026 1037
+f 1027 1026 1036
+f 1029 1032 1039
+f 1039 1032 1040
+f 1040 978 1041
+f 979 978 1040
+f 985 1038 1041
+f 986 985 1041
+f 1039 1038 1043
+f 1043 1038 1044
+f 1044 984 1045
+f 985 984 1044
+f 1050 994 1051
+f 995 994 1050
+f 1046 1045 1049
+f 1049 1045 1050
+f 1046 1055 1057
+f 1042 1046 1057
+f 1052 1054 1056
+f 1056 1054 1057
+f 1043 1042 1053
+f 1053 1042 1054
+f 1048 1058 1062
+f 1049 1048 1062
+f 1056 1055 1061
+f 1061 1055 1062
+f 1065 1064 1073
+f 1064 1068 1073
+f 1060 1059 1067
+f 1059 1063 1067
+f 1069 1051 1070
+f 1047 1051 1069
+f 1074 1078 1080
+f 1080 1078 1081
+f 1208 1076 1209
+f 1077 1076 1208
+f 1201 1071 1202
+f 1072 1071 1201
+f 1087 1086 1093
+f 1086 1089 1093
+f 1076 1075 1088
+f 1075 1085 1088
+f 1090 1084 1091
+f 1079 1084 1090
+f 1070 993 1071
+f 994 993 1070
+f 1083 1002 1084
+f 1003 1002 1083
+f 1091 1001 1092
+f 1002 1001 1091
+f 1092 1034 1093
+f 1035 1034 1092
+f 1030 1029 1052
+f 1052 1029 1053
+f 1033 1037 1060
+f 1060 1037 1061
+f 1087 1033 1088
+f 1034 1033 1087
+f 1 7 662
+f 662 7 663
+f 472 471 485
+f 485 471 486
+f 461 481 486
+f 462 461 486
+f 452 451 481
+f 481 451 482
+f 447 525 532
+f 448 447 532
+f 416 415 526
+f 526 415 527
+f 414 575 579
+f 415 414 579
+f 575 249 576
+f 250 249 575
+f 649 1 650
+f 2 1 649
+f 432 431 525
+f 525 431 526
+f 318 317 1095
+f 1095 317 1096
+f 310 309 1099
+f 309 1097 1099
+f 1098 1096 1099
+f 1094 1096 1098
+f 306 305 1102
+f 305 1100 1102
+f 1101 1097 1102
+f 1098 1097 1101
+f 428 427 1105
+f 427 1103 1105
+f 1104 1100 1105
+f 1101 1100 1104
+f 1095 1094 1103
+f 1103 1094 1104
+f 422 421 1107
+f 1107 421 1108
+f 324 323 1111
+f 323 1109 1111
+f 1110 1108 1111
+f 1106 1108 1110
+f 314 313 1114
+f 313 1112 1114
+f 1113 1109 1114
+f 1110 1109 1113
+f 426 425 1117
+f 425 1115 1117
+f 1116 1112 1117
+f 1113 1112 1116
+f 1107 1106 1115
+f 1115 1106 1116
+f 438 437 1119
+f 1119 437 1120
+f 330 329 1123
+f 329 1121 1123
+f 1122 1120 1123
+f 1118 1120 1122
+f 320 319 1126
+f 319 1124 1126
+f 1125 1121 1126
+f 1122 1121 1125
+f 420 419 1129
+f 419 1127 1129
+f 1128 1124 1129
+f 1125 1124 1128
+f 1119 1118 1127
+f 1127 1118 1128
+f 436 435 1131
+f 1131 435 1132
+f 444 443 1135
+f 443 1133 1135
+f 1134 1132 1135
+f 1130 1132 1134
+f 339 338 1138
+f 338 1136 1138
+f 1137 1133 1138
+f 1134 1133 1137
+f 326 325 1141
+f 325 1139 1141
+f 1140 1136 1141
+f 1137 1136 1140
+f 1131 1130 1139
+f 1139 1130 1140
+f 458 457 1143
+f 1143 457 1144
+f 349 348 1147
+f 348 1145 1147
+f 1146 1144 1147
+f 1142 1144 1146
+f 335 334 1150
+f 334 1148 1150
+f 1149 1145 1150
+f 1146 1145 1149
+f 442 441 1153
+f 441 1151 1153
+f 1152 1148 1153
+f 1149 1148 1152
+f 1143 1142 1151
+f 1151 1142 1152
+f 358 357 1155
+f 1155 357 1156
+f 345 344 1159
+f 344 1157 1159
+f 1158 1156 1159
+f 1154 1156 1158
+f 456 455 1162
+f 455 1160 1162
+f 1161 1157 1162
+f 1158 1157 1161
+f 468 467 1165
+f 467 1163 1165
+f 1164 1160 1165
+f 1161 1160 1164
+f 1155 1154 1163
+f 1163 1154 1164
+f 368 367 1167
+f 1167 367 1168
+f 354 353 1171
+f 353 1169 1171
+f 1170 1168 1171
+f 1166 1168 1170
+f 466 465 1174
+f 465 1172 1174
+f 1173 1169 1174
+f 1170 1169 1173
+f 478 477 1177
+f 477 1175 1177
+f 1176 1172 1177
+f 1173 1172 1176
+f 1167 1166 1175
+f 1175 1166 1176
+f 476 475 1179
+f 1179 475 1180
+f 2245 1183 2246
+f 1181 1183 2245
+f 1182 1180 1183
+f 1178 1180 1182
+f 2371 1186 2372
+f 1184 1186 2371
+f 1185 1181 1186
+f 1182 1181 1185
+f 364 363 1189
+f 363 1187 1189
+f 1188 1184 1189
+f 1185 1184 1188
+f 1179 1178 1187
+f 1187 1178 1188
+f 907 906 1194
+f 906 1190 1194
+f 877 876 1196
+f 1196 876 1197
+f 1192 1191 1199
+f 1191 1195 1199
+f 1083 1082 1202
+f 1202 1082 1203
+f 1067 1066 1209
+f 1066 1205 1209
+f 1206 1204 1207
+f 1200 1204 1206
+f 1204 1203 1211
+f 1211 1203 1212
+f 1082 1081 1215
+f 1081 1213 1215
+f 1073 1072 1223
+f 1223 1072 1224
+f 1201 1200 1227
+f 1200 1225 1227
+f 1048 1047 1235
+f 1235 1047 1236
+f 1069 1068 1239
+f 1068 1237 1239
+f 1064 1063 1242
+f 1063 1240 1242
+f 1059 1058 1245
+f 1058 1243 1245
+f 893 892 1247
+f 1247 892 1248
+f 900 899 1251
+f 899 1249 1251
+f 885 884 1257
+f 884 1255 1257
+f 898 897 1259
+f 1259 897 1260
+f 1193 1192 1263
+f 1192 1261 1263
+f 1191 1190 1278
+f 1190 1276 1278
+f 906 905 1281
+f 905 1279 1281
+f 1080 1079 1295
+f 1295 1079 1296
+f 1090 1089 1299
+f 1089 1297 1299
+f 1306 1533 1537
+f 1307 1306 1537
+f 1524 1309 1525
+f 1310 1309 1524
+f 1312 1518 1522
+f 1313 1312 1522
+f 1528 1315 1529
+f 1316 1315 1528
+f 1330 1703 1708
+f 1331 1330 1708
+f 1333 1417 1421
+f 1334 1333 1421
+f 1419 1348 1420
+f 1349 1348 1419
+f 1710 1351 1711
+f 1352 1351 1710
+f 1726 1354 1727
+f 1355 1354 1726
+f 1360 1709 1714
+f 1361 1360 1714
+f 1717 1363 1718
+f 1364 1363 1717
+f 1372 1538 1543
+f 1373 1372 1543
+f 1563 1375 1564
+f 1376 1375 1563
+f 1547 1402 1548
+f 1403 1402 1547
+f 1531 1407 1532
+f 1408 1407 1531
+f 1420 1414 1421
+f 1415 1414 1420
+f 1733 1412 1734
+f 1413 1412 1733
+f 1707 1417 1708
+f 1418 1417 1707
+f 1432 1424 1433
+f 1422 1424 1432
+f 1424 1423 1427
+f 1423 1425 1427
+f 2380 1422 2381
+f 1423 1422 2380
+f 1427 1426 1430
+f 1426 1428 1430
+f 1425 2368 2373
+f 1426 1425 2373
+f 1430 1429 1433
+f 1429 1431 1433
+f 2243 1428 2244
+f 1429 1428 2243
+f 2255 1431 2256
+f 1432 1431 2255
+f 1444 1436 1445
+f 1434 1436 1444
+f 1436 1435 1439
+f 1435 1437 1439
+f 2253 1434 2254
+f 1435 1434 2253
+f 1439 1438 1442
+f 1438 1440 1442
+f 2265 1437 2266
+f 1438 1437 2265
+f 1442 1441 1445
+f 1441 1443 1445
+f 2390 1440 2391
+f 1441 1440 2390
+f 1443 2377 2382
+f 1444 1443 2382
+f 1456 1448 1457
+f 1446 1448 1456
+f 1448 1447 1451
+f 1447 1449 1451
+f 2263 1446 2264
+f 1447 1446 2263
+f 1451 1450 1454
+f 1450 1452 1454
+f 2275 1449 2276
+f 1450 1449 2275
+f 1454 1453 1457
+f 1453 1455 1457
+f 2399 1452 2400
+f 1453 1452 2399
+f 1455 2387 2392
+f 1456 1455 2392
+f 1468 1460 1469
+f 1458 1460 1468
+f 1460 1459 1463
+f 1459 1461 1463
+f 2289 1458 2290
+f 1459 1458 2289
+f 1463 1462 1466
+f 1462 1464 1466
+f 2409 1461 2410
+f 1462 1461 2409
+f 1466 1465 1469
+f 1465 1467 1469
+f 1464 2396 2401
+f 1465 1464 2401
+f 2273 1467 2274
+f 1468 1467 2273
+f 1480 1472 1481
+f 1470 1472 1480
+f 1472 1471 1475
+f 1471 1473 1475
+f 2418 1470 2419
+f 1471 1470 2418
+f 1475 1474 1478
+f 1474 1476 1478
+f 1473 2406 2411
+f 1474 1473 2411
+f 1478 1477 1481
+f 1477 1479 1481
+f 2287 1476 2288
+f 1477 1476 2287
+f 2295 1479 2296
+f 1480 1479 2295
+f 1492 1484 1493
+f 1482 1484 1492
+f 1484 1483 1487
+f 1483 1485 1487
+f 2311 1482 2312
+f 1483 1482 2311
+f 1487 1486 1490
+f 1486 1488 1490
+f 2424 1485 2425
+f 1486 1485 2424
+f 1490 1489 1493
+f 1489 1491 1493
+f 1488 2415 2420
+f 1489 1488 2420
+f 2293 1491 2294
+f 1492 1491 2293
+f 1504 1496 1505
+f 1494 1496 1504
+f 1496 1495 1499
+f 1495 1497 1499
+f 1494 2301 2306
+f 1495 1494 2306
+f 1499 1498 1502
+f 1498 1500 1502
+f 2430 1497 2431
+f 1498 1497 2430
+f 1502 1501 1505
+f 1501 1503 1505
+f 1500 2421 2426
+f 1501 1500 2426
+f 2309 1503 2310
+f 1504 1503 2309
+f 1516 1508 1517
+f 1506 1508 1516
+f 1508 1507 1511
+f 1507 1509 1511
+f 2304 1506 2305
+f 1507 1506 2304
+f 1511 1510 1514
+f 1510 1512 1514
+f 2439 1509 2440
+f 1510 1509 2439
+f 1514 1513 1517
+f 1513 1515 1517
+f 1512 2433 2436
+f 1513 1512 2436
+f 1515 2427 2432
+f 1516 1515 2432
+f 2147 2297 2300
+f 2148 2147 2300
+f 2020 2024 2819
+f 2819 2024 2820
+f 2097 2096 2503
+f 2503 2096 2504
+f 2098 2097 2317
+f 2097 2313 2317
+f 2316 2146 2317
+f 2147 2146 2316
+f 2141 2148 2283
+f 2283 2148 2284
+f 2234 2277 2280
+f 2235 2234 2280
+f 2236 2235 2270
+f 2235 2267 2270
+f 2236 2257 2260
+f 2231 2236 2260
+f 2011 2814 2820
+f 2008 2011 2820
+f 2822 1990 2823
+f 1987 1990 2822
+f 2240 2239 2250
+f 2239 2247 2250
+f 1523 1526 1576
+f 1576 1526 1577
+f 1577 1551 1578
+f 1552 1551 1577
+f 1581 1559 1582
+f 1557 1559 1581
+f 1520 1519 1575
+f 1575 1519 1576
+f 1521 1520 1608
+f 1608 1520 1609
+f 1531 1530 1607
+f 1607 1530 1608
+f 1542 1541 1616
+f 1616 1541 1617
+f 1522 1521 1529
+f 1529 1521 1530
+f 1536 1525 1537
+f 1526 1525 1536
+f 1523 1518 1524
+f 1519 1518 1523
+f 1409 1408 1540
+f 1540 1408 1541
+f 1404 1403 1535
+f 1535 1403 1536
+f 1533 1527 1534
+f 1528 1527 1533
+f 1543 1542 1564
+f 1542 1560 1564
+f 1552 1546 1553
+f 1547 1546 1552
+f 1544 1538 1545
+f 1539 1538 1544
+f 1550 1554 1556
+f 1551 1550 1556
+f 1550 1549 1562
+f 1562 1549 1563
+f 1566 1558 1567
+f 1559 1558 1566
+f 1557 1555 1558
+f 1556 1555 1557
+f 1555 1554 1567
+f 1567 1554 1568
+f 1568 1561 1569
+f 1562 1561 1568
+f 1561 1560 1615
+f 1615 1560 1616
+f 1565 1569 1625
+f 1625 1569 1626
+f 1571 1565 1572
+f 1566 1565 1571
+f 1573 1572 1624
+f 1624 1572 1625
+f 1570 1573 1633
+f 1573 1628 1633
+f 1570 1579 1582
+f 1571 1570 1582
+f 1574 1578 1585
+f 1578 1583 1585
+f 1599 1574 1600
+f 1575 1574 1599
+f 1592 1584 1593
+f 1585 1584 1592
+f 1583 1580 1584
+f 1581 1580 1583
+f 1580 1579 1632
+f 1632 1579 1633
+f 1588 1587 1652
+f 1652 1587 1653
+f 1652 1651 1662
+f 1662 1651 1663
+f 1589 1588 1666
+f 1666 1588 1667
+f 1586 1589 1597
+f 1589 1594 1597
+f 1590 1593 1631
+f 1631 1593 1632
+f 1630 1586 1631
+f 1587 1586 1630
+f 1600 1591 1601
+f 1592 1591 1600
+f 1591 1590 1596
+f 1596 1590 1597
+f 1674 1595 1675
+f 1596 1595 1674
+f 1595 1594 1679
+f 1679 1594 1680
+f 1598 1601 1673
+f 1673 1601 1674
+f 1609 1598 1610
+f 1599 1598 1609
+f 1603 1672 1675
+f 1604 1603 1675
+f 1638 1641 1672
+f 1672 1641 1673
+f 1605 1604 1678
+f 1678 1604 1679
+f 1602 1605 1969
+f 1969 1605 1970
+f 1636 1602 1637
+f 1603 1602 1636
+f 1634 1637 1977
+f 1977 1637 1978
+f 1606 1610 1613
+f 1610 1611 1613
+f 1617 1606 1618
+f 1607 1606 1617
+f 1612 1619 1622
+f 1613 1612 1622
+f 1612 1611 1640
+f 1640 1611 1641
+f 1614 1618 1621
+f 1621 1618 1622
+f 1626 1614 1627
+f 1615 1614 1626
+f 1620 1649 1655
+f 1621 1620 1655
+f 1620 1619 1644
+f 1644 1619 1645
+f 1623 1627 1654
+f 1654 1627 1655
+f 1653 1629 1654
+f 1630 1629 1653
+f 1628 1623 1629
+f 1624 1623 1628
+f 1639 1642 1645
+f 1640 1639 1645
+f 1638 1635 1639
+f 1636 1635 1638
+f 1635 1634 1702
+f 1634 1699 1702
+f 1647 1643 1648
+f 1644 1643 1647
+f 1643 1642 1701
+f 1701 1642 1702
+f 1646 1648 1697
+f 1697 1648 1698
+f 1696 1650 1697
+f 1651 1650 1696
+f 1649 1646 1650
+f 1647 1646 1649
+f 1671 1670 1685
+f 1685 1670 1686
+f 1690 1659 1691
+f 1656 1659 1690
+f 1660 1663 1695
+f 1695 1663 1696
+f 1694 1658 1695
+f 1659 1658 1694
+f 1661 1664 1667
+f 1662 1661 1667
+f 1660 1657 1661
+f 1658 1657 1660
+f 1657 1656 1669
+f 1669 1656 1670
+f 1668 1671 1681
+f 1681 1671 1682
+f 1680 1665 1681
+f 1666 1665 1680
+f 1665 1664 1668
+f 1668 1664 1669
+f 1677 2029 2033
+f 1678 1677 2033
+f 1676 1682 1684
+f 1684 1682 1685
+f 2026 1676 2027
+f 1677 1676 2026
+f 1687 1686 1689
+f 1689 1686 1690
+f 1683 1687 2070
+f 2070 1687 2071
+f 1683 2073 2077
+f 1684 1683 2077
+f 1688 1691 1693
+f 1693 1691 1694
+f 1993 1688 1994
+f 1689 1688 1993
+f 1692 1698 1700
+f 1700 1698 1701
+f 1700 1699 1996
+f 1996 1699 1997
+f 1692 2000 2003
+f 1693 1692 2003
+f 1811 1723 1812
+f 1720 1723 1811
+f 1812 1719 1813
+f 1715 1719 1812
+f 1813 1712 1814
+f 1713 1712 1813
+f 1706 1767 1770
+f 1707 1706 1770
+f 1814 1769 1815
+f 1770 1769 1814
+f 1941 1782 1942
+f 1783 1782 1941
+f 1809 1808 1893
+f 1893 1808 1894
+f 1792 1931 1935
+f 1793 1792 1935
+f 1801 1800 1929
+f 1929 1800 1930
+f 1743 1705 1744
+f 1706 1705 1743
+f 1419 1418 1711
+f 1711 1418 1712
+f 1736 1703 1737
+f 1704 1703 1736
+f 1714 1713 1718
+f 1718 1713 1719
+f 1731 1709 1732
+f 1710 1709 1731
+f 1716 1724 1727
+f 1717 1716 1727
+f 1716 1715 1722
+f 1722 1715 1723
+f 1740 1734 1741
+f 1735 1734 1740
+f 1412 1416 1729
+f 1729 1416 1730
+f 1728 1725 1729
+f 1726 1725 1728
+f 1724 1721 1725
+f 1722 1721 1724
+f 1721 1720 1762
+f 1720 1760 1762
+f 1738 1741 1749
+f 1749 1741 1750
+f 1745 1738 1746
+f 1739 1738 1745
+f 1742 1746 1748
+f 1748 1746 1749
+f 1777 1742 1778
+f 1743 1742 1777
+f 1747 1750 1752
+f 1752 1750 1753
+f 1778 1747 1779
+f 1748 1747 1778
+f 1751 1753 1755
+f 1755 1753 1756
+f 1788 1751 1789
+f 1752 1751 1788
+f 1754 1756 1758
+f 1758 1756 1759
+f 1796 1754 1797
+f 1755 1754 1796
+f 1757 1759 1761
+f 1761 1759 1762
+f 1761 1760 1819
+f 1760 1816 1819
+f 1804 1757 1805
+f 1758 1757 1804
+f 1784 1783 1854
+f 1854 1783 1855
+f 1853 1765 1854
+f 1766 1765 1853
+f 1784 1764 1785
+f 1765 1764 1784
+f 1763 1766 1863
+f 1863 1766 1864
+f 1862 1774 1863
+f 1771 1774 1862
+f 1773 1763 1774
+f 1764 1763 1773
+f 1772 1780 1785
+f 1773 1772 1785
+f 1771 1768 1772
+f 1769 1768 1771
+f 1768 1767 1776
+f 1776 1767 1777
+f 1775 1779 1787
+f 1787 1779 1788
+f 1781 1790 1793
+f 1782 1781 1793
+f 1780 1775 1781
+f 1776 1775 1780
+f 1786 1789 1795
+f 1795 1789 1796
+f 1791 1798 1801
+f 1792 1791 1801
+f 1790 1786 1791
+f 1787 1786 1790
+f 1794 1797 1803
+f 1803 1797 1804
+f 1799 1806 1809
+f 1800 1799 1809
+f 1798 1794 1799
+f 1795 1794 1798
+f 1802 1805 1818
+f 1818 1805 1819
+f 1868 1807 1869
+f 1808 1807 1868
+f 1806 1802 1807
+f 1803 1802 1806
+f 1810 1815 1861
+f 1861 1815 1862
+f 1817 1866 1869
+f 1818 1817 1869
+f 1816 1810 1817
+f 1811 1810 1816
+f 1820 1823 1907
+f 1907 1823 1908
+f 1821 1824 1827
+f 1822 1821 1827
+f 1821 1820 1842
+f 1842 1820 1843
+f 1825 1828 1831
+f 1826 1825 1831
+f 1825 1824 1838
+f 1838 1824 1839
+f 2153 1829 2154
+f 1830 1829 2153
+f 1829 1828 1834
+f 1834 1828 1835
+f 2154 1833 2155
+f 1834 1833 2154
+f 1832 1835 1837
+f 1837 1835 1838
+f 1846 1845 2102
+f 2102 1845 2103
+f 2101 1832 2102
+f 1833 1832 2101
+f 1836 1839 1841
+f 1841 1839 1842
+f 1846 1836 1847
+f 1837 1836 1846
+f 1840 1843 1913
+f 1843 1910 1913
+f 1850 1840 1851
+f 1841 1840 1850
+f 1844 1847 1849
+f 1849 1847 1850
+f 2106 1844 2107
+f 1845 1844 2106
+f 1848 1851 1899
+f 1899 1851 1900
+f 2110 1848 2111
+f 1849 1848 2110
+f 2114 1898 2115
+f 1899 1898 2114
+f 1852 1855 1948
+f 1855 1945 1948
+f 1886 1858 1887
+f 1859 1858 1886
+f 1857 1852 1858
+f 1853 1852 1857
+f 1856 1859 1873
+f 1873 1859 1874
+f 1864 1856 1865
+f 1857 1856 1864
+f 1860 1865 1872
+f 1872 1865 1873
+f 1871 1867 1872
+f 1868 1867 1871
+f 1866 1860 1867
+f 1861 1860 1866
+f 1870 1874 1878
+f 1874 1875 1878
+f 1894 1870 1895
+f 1871 1870 1894
+f 1890 1877 1891
+f 1878 1877 1890
+f 2124 1876 2125
+f 1877 1876 2124
+f 1876 1875 1885
+f 1885 1875 1886
+f 1882 1881 2125
+f 2125 1881 2126
+f 2055 1880 2056
+f 1881 1880 2055
+f 2050 2049 2056
+f 2049 2053 2056
+f 1879 1882 1884
+f 1884 1882 1885
+f 2050 1879 2051
+f 1880 1879 2050
+f 1883 1887 1947
+f 1947 1887 1948
+f 2051 1883 2052
+f 1884 1883 2051
+f 1917 1916 1937
+f 1937 1916 1938
+f 1942 1934 1943
+f 1935 1934 1942
+f 2119 1905 2120
+f 1901 1905 2119
+f 1888 1891 2120
+f 2120 1891 2121
+f 1895 1889 1896
+f 1890 1889 1895
+f 1889 1888 1904
+f 1904 1888 1905
+f 1892 1896 1903
+f 1903 1896 1904
+f 1892 1927 1930
+f 1893 1892 1930
+f 1897 1900 1912
+f 1912 1900 1913
+f 1923 1902 1924
+f 1903 1902 1923
+f 1901 1897 1902
+f 1898 1897 1901
+f 1906 1909 1920
+f 1920 1909 1921
+f 1924 1911 1925
+f 1912 1911 1924
+f 1910 1906 1911
+f 1907 1906 1910
+f 1914 1917 1933
+f 1933 1917 1934
+f 1925 1919 1926
+f 1920 1919 1925
+f 1918 1914 1919
+f 1915 1914 1918
+f 1922 1926 1932
+f 1932 1926 1933
+f 1931 1928 1932
+f 1929 1928 1931
+f 1927 1922 1928
+f 1923 1922 1927
+f 1936 1939 1951
+f 1951 1939 1952
+f 1943 1936 1944
+f 1937 1936 1943
+f 1940 1944 1950
+f 1950 1944 1951
+f 2035 1946 2036
+f 1947 1946 2035
+f 1945 1940 1946
+f 1941 1940 1945
+f 1949 1954 1956
+f 1956 1954 1957
+f 2036 1949 2037
+f 1950 1949 2036
+f 1955 1958 1960
+f 1960 1958 1961
+f 2040 1955 2041
+f 1956 1955 2040
+f 1959 1962 1964
+f 1964 1962 1965
+f 1967 1970 2032
+f 2032 1970 2033
+f 2031 1959 2032
+f 1960 1959 2031
+f 1963 1966 1972
+f 1972 1966 1973
+f 1968 1975 1978
+f 1969 1968 1978
+f 1967 1963 1968
+f 1964 1963 1967
+f 1971 1974 1980
+f 1980 1974 1981
+f 1997 1976 1998
+f 1977 1976 1997
+f 1975 1971 1976
+f 1972 1971 1975
+f 1979 1982 1984
+f 1984 1982 1985
+f 1998 1979 1999
+f 1980 1979 1998
+f 1983 1986 1988
+f 1988 1986 1989
+f 1988 1987 2010
+f 2010 1987 2011
+f 2006 1983 2007
+f 1984 1983 2006
+f 2071 1992 2072
+f 1993 1992 2071
+f 2019 2018 2084
+f 2084 2018 2085
+f 1991 1994 2002
+f 2002 1994 2003
+f 1991 2016 2019
+f 1992 1991 2019
+f 1995 1999 2005
+f 2005 1999 2006
+f 2014 2001 2015
+f 2002 2001 2014
+f 2000 1995 2001
+f 1996 1995 2000
+f 2004 2007 2009
+f 2009 2007 2010
+f 2009 2008 2023
+f 2023 2008 2024
+f 2004 2012 2015
+f 2005 2004 2015
+f 2092 2017 2093
+f 2018 2017 2092
+f 2016 2013 2017
+f 2014 2013 2016
+f 2013 2012 2022
+f 2022 2012 2023
+f 2021 2090 2093
+f 2022 2021 2093
+f 2021 2020 2095
+f 2095 2020 2096
+f 2028 2027 2076
+f 2076 2027 2077
+f 2025 2028 2044
+f 2044 2028 2045
+f 2030 2038 2041
+f 2031 2030 2041
+f 2029 2025 2030
+f 2026 2025 2029
+f 2034 2037 2039
+f 2039 2037 2040
+f 2039 2038 2043
+f 2043 2038 2044
+f 2034 2047 2052
+f 2035 2034 2052
+f 2046 2045 2067
+f 2067 2045 2068
+f 2042 2046 2059
+f 2059 2046 2060
+f 2058 2048 2059
+f 2049 2048 2058
+f 2047 2042 2048
+f 2043 2042 2047
+f 2130 2054 2131
+f 2055 2054 2130
+f 2054 2053 2064
+f 2053 2061 2064
+f 2064 2063 2134
+f 2134 2063 2135
+f 2057 2060 2066
+f 2066 2060 2067
+f 2079 2062 2080
+f 2063 2062 2079
+f 2061 2057 2062
+f 2058 2057 2061
+f 2065 2068 2075
+f 2075 2068 2076
+f 2080 2065 2081
+f 2066 2065 2080
+f 2069 2072 2083
+f 2083 2072 2084
+f 2074 2086 2089
+f 2075 2074 2089
+f 2073 2069 2074
+f 2070 2069 2073
+f 2078 2081 2088
+f 2088 2081 2089
+f 2138 2078 2139
+f 2079 2078 2138
+f 2082 2085 2143
+f 2143 2085 2144
+f 2139 2087 2140
+f 2088 2087 2139
+f 2086 2082 2087
+f 2083 2082 2086
+f 2094 2098 2145
+f 2145 2098 2146
+f 2144 2091 2145
+f 2092 2091 2144
+f 2091 2090 2094
+f 2094 2090 2095
+f 2155 2100 2156
+f 2101 2100 2155
+f 2099 2103 2105
+f 2105 2103 2106
+f 2159 2099 2160
+f 2100 2099 2159
+f 2104 2107 2109
+f 2109 2107 2110
+f 2167 2104 2168
+f 2105 2104 2167
+f 2108 2111 2113
+f 2113 2111 2114
+f 2175 2108 2176
+f 2109 2108 2175
+f 2112 2115 2118
+f 2118 2115 2119
+f 2183 2112 2184
+f 2113 2112 2183
+f 2184 2117 2185
+f 2118 2117 2184
+f 2116 2121 2123
+f 2123 2121 2124
+f 2192 2116 2193
+f 2117 2116 2192
+f 2122 2126 2129
+f 2129 2126 2130
+f 2193 2122 2194
+f 2123 2122 2193
+f 2201 2128 2202
+f 2129 2128 2201
+f 2127 2131 2133
+f 2133 2131 2134
+f 2209 2127 2210
+f 2128 2127 2209
+f 2132 2135 2137
+f 2137 2135 2138
+f 2217 2132 2218
+f 2133 2132 2217
+f 2136 2140 2142
+f 2142 2140 2143
+f 2142 2141 2233
+f 2233 2141 2234
+f 2225 2136 2226
+f 2137 2136 2225
+f 2149 2156 2158
+f 2158 2156 2159
+f 2149 2161 2164
+f 2150 2149 2164
+f 2157 2160 2166
+f 2166 2160 2167
+f 2162 2169 2172
+f 2163 2162 2172
+f 2161 2157 2162
+f 2158 2157 2161
+f 2165 2168 2174
+f 2174 2168 2175
+f 2170 2177 2180
+f 2171 2170 2180
+f 2169 2165 2170
+f 2166 2165 2169
+f 2173 2176 2182
+f 2182 2176 2183
+f 2178 2186 2189
+f 2179 2178 2189
+f 2177 2173 2178
+f 2174 2173 2177
+f 2181 2185 2191
+f 2191 2185 2192
+f 2187 2195 2198
+f 2188 2187 2198
+f 2186 2181 2187
+f 2182 2181 2186
+f 2190 2194 2200
+f 2200 2194 2201
+f 2196 2203 2206
+f 2197 2196 2206
+f 2195 2190 2196
+f 2191 2190 2195
+f 2199 2202 2208
+f 2208 2202 2209
+f 2204 2211 2214
+f 2205 2204 2214
+f 2203 2199 2204
+f 2200 2199 2203
+f 2207 2210 2216
+f 2216 2210 2217
+f 2212 2219 2222
+f 2213 2212 2222
+f 2211 2207 2212
+f 2208 2207 2211
+f 2215 2218 2224
+f 2224 2218 2225
+f 2220 2227 2230
+f 2221 2220 2230
+f 2219 2215 2220
+f 2216 2215 2219
+f 2223 2226 2232
+f 2232 2226 2233
+f 2232 2231 2238
+f 2238 2231 2239
+f 2237 2228 2238
+f 2229 2228 2237
+f 2227 2223 2228
+f 2224 2223 2227
+f 2265 2264 2391
+f 2391 2264 2392
+f 2255 2254 2381
+f 2381 2254 2382
+f 2372 2244 2373
+f 2245 2244 2372
+f 2400 2274 2401
+f 2275 2274 2400
+f 2281 2284 2299
+f 2299 2284 2300
+f 2287 2286 2296
+f 2286 2291 2296
+f 2410 2288 2411
+f 2289 2288 2410
+f 2419 2294 2420
+f 2295 2294 2419
+f 2425 2310 2426
+f 2311 2310 2425
+f 2431 2305 2432
+f 2306 2305 2431
+f 2304 2303 2440
+f 2303 2437 2440
+f 2242 2251 2256
+f 2243 2242 2256
+f 2242 2241 2248
+f 2248 2241 2249
+f 2248 2247 2259
+f 2259 2247 2260
+f 2252 2261 2266
+f 2253 2252 2266
+f 2252 2251 2258
+f 2258 2251 2259
+f 2258 2257 2269
+f 2269 2257 2270
+f 2262 2271 2276
+f 2263 2262 2276
+f 2262 2261 2268
+f 2268 2261 2269
+f 2268 2267 2279
+f 2279 2267 2280
+f 2272 2285 2290
+f 2273 2272 2290
+f 2272 2271 2278
+f 2278 2271 2279
+f 2278 2277 2282
+f 2282 2277 2283
+f 2282 2281 2285
+f 2285 2281 2286
+f 2292 2307 2312
+f 2293 2292 2312
+f 2292 2291 2298
+f 2298 2291 2299
+f 2298 2297 2315
+f 2315 2297 2316
+f 2501 2302 2502
+f 2303 2302 2501
+f 2302 2301 2308
+f 2308 2301 2309
+f 2308 2307 2314
+f 2314 2307 2315
+f 2314 2313 2502
+f 2502 2313 2503
+f 2441 2444 2500
+f 2500 2444 2501
+f 2544 2350 2545
+f 2351 2350 2544
+f 2478 2477 2488
+f 2477 2485 2488
+f 2348 2351 2458
+f 2458 2351 2459
+f 2349 2348 2487
+f 2487 2348 2488
+f 2353 2352 2554
+f 2554 2352 2555
+f 2356 2546 2551
+f 2357 2356 2551
+f 2456 2459 2532
+f 2459 2530 2532
+f 2462 2526 2529
+f 2463 2462 2529
+f 2362 2464 2467
+f 2363 2362 2467
+f 2405 2404 2474
+f 2474 2404 2475
+f 2386 2385 2471
+f 2385 2468 2471
+f 2470 2361 2471
+f 2362 2361 2470
+f 2361 2360 2366
+f 2366 2360 2367
+f 2365 2374 2376
+f 2366 2365 2376
+f 2365 2364 2369
+f 2369 2364 2370
+f 2369 2368 2379
+f 2379 2368 2380
+f 2375 2383 2386
+f 2376 2375 2386
+f 2375 2374 2378
+f 2378 2374 2379
+f 2378 2377 2389
+f 2389 2377 2390
+f 2384 2393 2395
+f 2385 2384 2395
+f 2384 2383 2388
+f 2388 2383 2389
+f 2388 2387 2398
+f 2398 2387 2399
+f 2394 2402 2405
+f 2395 2394 2405
+f 2394 2393 2397
+f 2397 2393 2398
+f 2397 2396 2408
+f 2408 2396 2409
+f 2403 2412 2414
+f 2404 2403 2414
+f 2403 2402 2407
+f 2407 2402 2408
+f 2407 2406 2417
+f 2417 2406 2418
+f 2447 2413 2448
+f 2414 2413 2447
+f 2413 2412 2416
+f 2416 2412 2417
+f 2416 2415 2423
+f 2423 2415 2424
+f 2422 2445 2448
+f 2423 2422 2448
+f 2422 2421 2429
+f 2429 2421 2430
+f 2428 2449 2451
+f 2429 2428 2451
+f 2428 2427 2435
+f 2435 2427 2436
+f 2454 2434 2455
+f 2435 2434 2454
+f 2434 2433 2438
+f 2438 2433 2439
+f 2438 2437 2443
+f 2443 2437 2444
+f 2442 2452 2455
+f 2443 2442 2455
+f 2442 2441 2507
+f 2507 2441 2508
+f 2446 2560 2564
+f 2447 2446 2564
+f 2446 2445 2450
+f 2450 2445 2451
+f 2450 2449 2453
+f 2453 2449 2454
+f 2453 2452 2568
+f 2568 2452 2569
+f 2493 2552 2555
+f 2489 2493 2555
+f 2472 2475 2563
+f 2563 2475 2564
+f 2478 2457 2479
+f 2458 2457 2478
+f 2457 2456 2461
+f 2461 2456 2462
+f 2461 2460 2465
+f 2465 2460 2466
+f 2465 2464 2479
+f 2479 2464 2480
+f 2469 2476 2480
+f 2470 2469 2480
+f 2469 2468 2473
+f 2473 2468 2474
+f 2473 2472 2492
+f 2492 2472 2493
+f 2491 2476 2492
+f 2477 2476 2491
+f 2486 2485 2490
+f 2490 2485 2491
+f 2490 2489 2494
+f 2494 2489 2495
+f 2498 2504 2818
+f 2818 2504 2819
+f 2508 2499 2509
+f 2500 2499 2508
+f 2499 2498 2749
+f 2749 2498 2750
+f 2748 2509 2749
+f 2505 2509 2748
+f 2506 2565 2569
+f 2507 2506 2569
+f 2506 2505 2745
+f 2505 2742 2745
+f 2513 2512 2524
+f 2524 2512 2525
+f 2510 2513 2558
+f 2558 2513 2559
+f 2511 2514 2516
+f 2512 2511 2516
+f 2511 2510 2572
+f 2572 2510 2573
+f 2515 2517 2519
+f 2516 2515 2519
+f 2515 2514 2577
+f 2577 2514 2578
+f 2518 2520 2522
+f 2519 2518 2522
+f 2518 2517 2727
+f 2727 2517 2728
+f 2521 2523 2525
+f 2522 2521 2525
+f 2521 2520 2535
+f 2535 2520 2536
+f 2548 2523 2549
+f 2524 2523 2548
+f 2583 2527 2584
+f 2528 2527 2583
+f 2527 2526 2531
+f 2531 2526 2532
+f 2531 2530 2543
+f 2543 2530 2544
+f 2540 2579 2584
+f 2537 2540 2584
+f 2533 2536 2726
+f 2726 2536 2727
+f 2725 2539 2726
+f 2540 2539 2725
+f 2549 2534 2550
+f 2535 2534 2549
+f 2534 2533 2538
+f 2538 2533 2539
+f 2538 2537 2542
+f 2542 2537 2543
+f 2542 2541 2550
+f 2550 2541 2551
+f 2547 2556 2559
+f 2548 2547 2559
+f 2547 2546 2553
+f 2553 2546 2554
+f 2553 2552 2562
+f 2562 2552 2563
+f 2573 2557 2574
+f 2558 2557 2573
+f 2557 2556 2561
+f 2561 2556 2562
+f 2561 2560 2567
+f 2567 2560 2568
+f 2566 2570 2574
+f 2567 2566 2574
+f 2566 2565 2736
+f 2565 2733 2736
+f 2571 2575 2578
+f 2572 2571 2578
+f 2571 2570 2735
+f 2735 2570 2736
+f 2576 2721 2728
+f 2577 2576 2728
+f 2576 2575 2732
+f 2575 2729 2732
+f 2592 2644 2647
+f 2589 2592 2647
+f 2580 2585 2588
+f 2581 2580 2588
+f 2580 2579 2724
+f 2724 2579 2725
+f 2723 2591 2724
+f 2592 2591 2723
+f 2586 2593 2596
+f 2587 2586 2596
+f 2586 2585 2590
+f 2590 2585 2591
+f 2615 2661 2664
+f 2616 2615 2664
+f 2786 2623 2787
+f 2624 2623 2786
+f 2636 2782 2787
+f 2637 2636 2787
+f 2636 2635 2779
+f 2779 2635 2780
+f 2638 2640 2660
+f 2640 2658 2660
+f 2639 2693 2696
+f 2640 2639 2696
+f 2639 2638 2643
+f 2638 2641 2643
+f 2642 2713 2716
+f 2643 2642 2716
+f 2642 2641 2650
+f 2641 2648 2650
+f 2645 2651 2654
+f 2646 2645 2654
+f 2645 2644 2720
+f 2644 2717 2720
+f 2719 2649 2720
+f 2650 2649 2719
+f 2649 2648 2657
+f 2648 2655 2657
+f 2652 2665 2668
+f 2653 2652 2668
+f 2652 2651 2656
+f 2656 2651 2657
+f 2656 2655 2659
+f 2659 2655 2660
+f 2659 2658 2692
+f 2658 2689 2692
+f 2662 2669 2672
+f 2663 2662 2672
+f 2662 2661 2666
+f 2666 2661 2667
+f 2666 2665 2691
+f 2691 2665 2692
+f 2670 2673 2680
+f 2671 2670 2680
+f 2670 2669 2688
+f 2669 2685 2688
+f 2674 2681 2684
+f 2675 2674 2684
+f 2674 2673 2687
+f 2687 2673 2688
+f 2699 2682 2700
+f 2683 2682 2699
+f 2682 2681 2686
+f 2686 2681 2687
+f 2686 2685 2690
+f 2690 2685 2691
+f 2690 2689 2695
+f 2695 2689 2696
+f 2694 2697 2700
+f 2695 2694 2700
+f 2694 2693 2715
+f 2715 2693 2716
+f 2697 2709 2712
+f 2698 2697 2712
+f 2740 2710 2741
+f 2711 2710 2740
+f 2710 2709 2714
+f 2714 2709 2715
+f 2714 2713 2718
+f 2718 2713 2719
+f 2718 2717 2722
+f 2722 2717 2723
+f 2722 2721 2731
+f 2731 2721 2732
+f 2730 2737 2741
+f 2731 2730 2741
+f 2730 2729 2734
+f 2734 2729 2735
+f 2734 2733 2744
+f 2744 2733 2745
+f 2738 2755 2758
+f 2739 2738 2758
+f 2738 2737 2743
+f 2743 2737 2744
+f 2743 2742 2761
+f 2761 2742 2762
+f 2816 2754 2817
+f 2751 2754 2816
+f 2746 2750 2817
+f 2817 2750 2818
+f 2747 2759 2762
+f 2748 2747 2762
+f 2747 2746 2753
+f 2753 2746 2754
+f 2752 2763 2765
+f 2753 2752 2765
+f 2752 2751 2813
+f 2751 2809 2813
+f 2812 2773 2813
+f 2770 2773 2812
+f 2756 2766 2769
+f 2757 2756 2769
+f 2756 2755 2760
+f 2760 2755 2761
+f 2760 2759 2764
+f 2764 2759 2765
+f 2764 2763 2772
+f 2772 2763 2773
+f 2767 2774 2777
+f 2768 2767 2777
+f 2767 2766 2771
+f 2771 2766 2772
+f 2771 2770 2804
+f 2770 2800 2804
+f 2775 2778 2781
+f 2776 2775 2781
+f 2775 2774 2803
+f 2803 2774 2804
+f 2778 2792 2795
+f 2779 2778 2795
+f 2783 2788 2791
+f 2784 2783 2791
+f 2783 2782 2794
+f 2794 2782 2795
+f 2789 2796 2799
+f 2790 2789 2799
+f 2789 2788 2793
+f 2793 2788 2794
+f 2793 2792 2802
+f 2802 2792 2803
+f 2797 2805 2808
+f 2798 2797 2808
+f 2797 2796 2801
+f 2801 2796 2802
+f 2801 2800 2811
+f 2811 2800 2812
+f 2806 2821 2824
+f 2807 2806 2824
+f 2806 2805 2810
+f 2810 2805 2811
+f 2810 2809 2815
+f 2815 2809 2816
+f 2815 2814 2821
+f 2821 2814 2822
+f 2702 2706 2711
+f 2702 2711 2740
+f 2704 2757 2769
+f 2703 2739 2758
+f 2707 2701 2768
+f 2707 2768 2777
+f 2632 2708 2776
+f 2632 2776 2781
+f 2631 2698 2705
+f 2705 2698 2712
+f 2627 2633 2780
+f 2635 2627 2780
+f 2634 2626 2683
+f 2634 2683 2699
+f 2629 2622 2676
+f 2630 2675 2684
+f 157 119 2678
+f 2625 158 2677
+f 153 103 161
+f 103 120 161
+f 150 154 160
+f 150 23 154
+f 95 92 156
+f 92 104 156
+f 22 96 155
+f 22 27 96
+f 97 26 99
+f 26 35 99
+f 45 63 101
+f 34 46 100
+f 62 98 102
+f 62 93 98
+f 96 97 98
+f 95 96 98
+f 100 101 102
+f 99 100 102
+f 154 155 156
+f 153 154 156
+f 158 159 160
+f 158 160 161
+f 157 158 161
+f 2623 2624 2625
+f 2622 2623 2625
+f 2627 2628 2629
+f 2627 2629 2630
+f 2626 2627 2630
+f 2632 2633 2634
+f 2631 2632 2634
+f 2702 2703 2704
+f 2701 2702 2704
+f 2706 2707 2708
+f 2705 2706 2708
+f 92 95 98
+f 93 92 98
+f 97 99 102
+f 98 97 102
+f 63 62 101
+f 101 62 102
+f 100 45 101
+f 46 45 100
+f 35 34 99
+f 99 34 100
+f 27 26 96
+f 96 26 97
+f 155 95 156
+f 96 95 155
+f 23 22 154
+f 154 22 155
+f 103 153 156
+f 104 103 156
+f 160 153 161
+f 154 153 160
+f 151 150 159
+f 159 150 160
+f 119 157 161
+f 120 119 161
+f 2624 158 2625
+f 159 158 2624
+f 2677 157 2678
+f 158 157 2677
+f 2675 2629 2676
+f 2630 2629 2675
+f 2622 2625 2676
+f 2676 2625 2677
+f 2623 2622 2628
+f 2628 2622 2629
+f 2626 2630 2683
+f 2683 2630 2684
+f 2627 2635 2637
+f 2628 2627 2637
+f 2627 2626 2633
+f 2633 2626 2634
+f 2631 2634 2698
+f 2698 2634 2699
+f 2780 2632 2781
+f 2633 2632 2780
+f 2632 2631 2708
+f 2631 2705 2708
+f 2776 2707 2777
+f 2708 2707 2776
+f 2768 2704 2769
+f 2701 2704 2768
+f 2704 2703 2757
+f 2757 2703 2758
+f 2739 2702 2740
+f 2703 2702 2739
+f 2702 2701 2706
+f 2706 2701 2707
+f 2706 2705 2711
+f 2711 2705 2712
+f 2655 2648 2660
+f 2648 2641 2660
+f 2641 2638 2660
+f 130 128 137
+f 137 128 147
+f 144 137 147
+f 2615 2619 2667
+f 2661 2615 2667
+f 2614 2607 2620
+f 2606 2602 2621
+f 2598 2603 2613
+f 2594 2599 2612
+f 2586 2590 2600
+f 2593 2586 2600
+f 2597 2589 2604
+f 2604 2589 2647
+f 2605 2646 2654
+f 2346 2349 2487
+f 2481 2346 2487
+f 2336 2354 2359
+f 2340 2333 2496
+f 2355 2340 2496
+f 2329 2483 2497
+f 2332 2329 2497
+f 2325 2343 2347
+f 2325 2347 2484
+f 2328 2325 2484
+f 2319 2334 2339
+f 2321 2319 2339
+f 2318 2330 2335
+f 2345 2342 2545
+f 2350 2345 2545
+f 2320 2323 2326
+f 2320 2326 2331
+f 2344 2324 2358
+f 2324 2337 2358
+f 2327 2322 2338
+f 405 391 409
+f 387 373 392
+f 387 392 404
+f 400 408 412
+f 400 406 408
+f 384 381 389
+f 381 212 389
+f 396 401 411
+f 410 397 413
+f 395 397 410
+f 385 388 407
+f 403 385 407
+f 269 385 403
+f 399 270 402
+f 256 270 399
+f 394 376 398
+f 376 257 398
+f 372 377 393
+f 272 382 386
+f 272 267 382
+f 133 141 167
+f 168 140 171
+f 171 140 175
+f 178 2587 2596
+f 172 178 2596
+f 173 2595 2611
+f 169 174 2610
+f 163 170 2609
+f 164 2608 2617
+f 165 2616 2664
+f 124 165 2664
+f 163 164 165
+f 162 163 165
+f 167 168 169
+f 167 169 170
+f 166 167 170
+f 172 173 174
+f 171 172 174
+f 257 258 259
+f 256 257 259
+f 270 271 272
+f 269 270 272
+f 373 374 375
+f 372 373 375
+f 377 378 379
+f 376 377 379
+f 384 385 386
+f 388 389 390
+f 387 388 390
+f 392 393 394
+f 392 394 395
+f 391 392 395
+f 397 398 399
+f 396 397 399
+f 401 402 403
+f 400 401 403
+f 405 406 407
+f 404 405 407
+f 408 409 410
+f 411 412 413
+f 2318 2319 2320
+f 2321 2322 2323
+f 2325 2326 2327
+f 2324 2325 2327
+f 2329 2330 2331
+f 2328 2329 2331
+f 2333 2334 2335
+f 2332 2333 2335
+f 2337 2338 2339
+f 2337 2339 2340
+f 2336 2337 2340
+f 2342 2343 2344
+f 2341 2342 2344
+f 2345 2346 2347
+f 2353 2354 2355
+f 2352 2353 2355
+f 2357 2358 2359
+f 2356 2357 2359
+f 2482 2483 2484
+f 2481 2482 2484
+f 2495 2496 2497
+f 2494 2495 2497
+f 2594 2595 2596
+f 2593 2594 2596
+f 2598 2599 2600
+f 2597 2598 2600
+f 2602 2603 2604
+f 2602 2604 2605
+f 2601 2602 2605
+f 2829 2828 2830
+f 2828 2827 2830
+f 2827 2826 2830
+f 2826 2825 2830
+f 2830 2825 2832
+f 2831 2830 2832
+f 2615 2616 2617
+f 2614 2615 2617
+f 2619 2620 2621
+f 2618 2619 2621
+f 123 162 165
+f 124 123 165
+f 2616 164 2617
+f 165 164 2616
+f 2608 163 2609
+f 164 163 2608
+f 162 166 170
+f 163 162 170
+f 2609 169 2610
+f 170 169 2609
+f 168 171 174
+f 169 168 174
+f 2610 173 2611
+f 174 173 2610
+f 2595 172 2596
+f 173 172 2595
+f 171 175 178
+f 172 171 178
+f 141 140 167
+f 167 140 168
+f 271 267 272
+f 268 267 271
+f 256 259 270
+f 270 259 271
+f 166 133 167
+f 134 133 166
+f 372 375 377
+f 377 375 378
+f 257 376 379
+f 258 257 379
+f 381 384 386
+f 382 381 386
+f 269 272 385
+f 385 272 386
+f 212 216 389
+f 389 216 390
+f 373 387 390
+f 374 373 390
+f 373 372 392
+f 392 372 393
+f 393 376 394
+f 377 376 393
+f 398 256 399
+f 257 256 398
+f 395 394 397
+f 397 394 398
+f 402 269 403
+f 270 269 402
+f 396 399 401
+f 401 399 402
+f 388 384 389
+f 385 384 388
+f 387 404 407
+f 388 387 407
+f 400 403 406
+f 406 403 407
+f 391 395 409
+f 409 395 410
+f 396 411 413
+f 397 396 413
+f 408 410 412
+f 412 410 413
+f 411 400 412
+f 401 400 411
+f 408 405 409
+f 406 405 408
+f 404 391 405
+f 392 391 404
+f 2324 2327 2337
+f 2337 2327 2338
+f 2323 2322 2326
+f 2326 2322 2327
+f 2318 2320 2330
+f 2330 2320 2331
+f 2319 2321 2323
+f 2320 2319 2323
+f 2319 2318 2334
+f 2334 2318 2335
+f 2338 2321 2339
+f 2322 2321 2338
+f 2325 2328 2331
+f 2326 2325 2331
+f 2325 2324 2343
+f 2343 2324 2344
+f 2343 2342 2347
+f 2342 2345 2347
+f 2329 2332 2335
+f 2330 2329 2335
+f 2329 2328 2483
+f 2483 2328 2484
+f 2339 2333 2340
+f 2334 2333 2339
+f 2333 2332 2496
+f 2496 2332 2497
+f 2336 2340 2354
+f 2354 2340 2355
+f 2358 2336 2359
+f 2337 2336 2358
+f 2341 2344 2357
+f 2357 2344 2358
+f 2341 2541 2545
+f 2342 2341 2545
+f 2346 2481 2484
+f 2347 2346 2484
+f 2346 2345 2349
+f 2349 2345 2350
+f 2352 2355 2495
+f 2495 2355 2496
+f 2353 2356 2359
+f 2354 2353 2359
+f 2601 2605 2653
+f 2653 2605 2654
+f 2482 2494 2497
+f 2483 2482 2497
+f 2482 2481 2486
+f 2486 2481 2487
+f 2646 2604 2647
+f 2605 2604 2646
+f 2590 2589 2600
+f 2589 2597 2600
+f 2611 2594 2612
+f 2595 2594 2611
+f 2594 2593 2599
+f 2599 2593 2600
+f 2612 2598 2613
+f 2599 2598 2612
+f 2598 2597 2603
+f 2603 2597 2604
+f 2602 2606 2613
+f 2603 2602 2613
+f 2602 2601 2621
+f 2601 2618 2621
+f 2607 2614 2617
+f 2608 2607 2617
+f 2607 2606 2620
+f 2620 2606 2621
+f 2615 2614 2619
+f 2619 2614 2620
+f 2619 2618 2667
+f 2667 2618 2668
+f 2606 2825 2826
+f 2606 2607 2825
+f 2613 2826 2827
+f 2613 2606 2826
+f 2612 2827 2828
+f 2612 2613 2827
+f 2611 2828 2829
+f 2611 2612 2828
+f 2610 2829 2830
+f 2610 2611 2829
+f 2609 2830 2831
+f 2609 2610 2830
+f 2608 2831 2832
+f 2608 2609 2831
+f 2825 2607 2832
+f 2607 2608 2832
+f 2512 2516 2525
+f 2516 2519 2525
+f 2519 2522 2525
+f 228 238 241
+f 228 232 238
+f 232 235 238
+f 1356 1362 1365
+f 1356 1359 1362
+f 1344 1350 1353
+f 1344 1347 1350
+f 1320 1326 1329
+f 1320 1323 1326
+f 1368 1374 1377
+f 1368 1371 1374
+f 1392 1398 1401
+f 1392 1395 1398
+f 1308 1314 1317
+f 1308 1311 1314
+f 1332 1338 1341
+f 1332 1335 1338
+f 1380 1386 1389
+f 1380 1383 1386
+f 1399 1397 1532
+f 1407 1399 1532
+f 1396 1394 1534
+f 1527 1396 1534
+f 1393 1391 1404
+f 1393 1404 1535
+f 1390 1400 1411
+f 1405 1390 1411
+f 1387 1385 1409
+f 1387 1409 1540
+f 1384 1382 1406
+f 1384 1406 1410
+f 1381 1379 1548
+f 1402 1381 1548
+f 1378 1388 1539
+f 1378 1539 1544
+f 1369 1367 1553
+f 1546 1369 1553
+f 1357 1355 1726
+f 1357 1726 1728
+f 1345 1343 1730
+f 1416 1345 1730
+f 1339 1337 1413
+f 1339 1413 1733
+f 1327 1325 1744
+f 1705 1327 1744
+f 1324 1322 1739
+f 1324 1739 1745
+f 1321 1319 1735
+f 1321 1735 1740
+f 1318 1328 1704
+f 1318 1704 1736
+f 1300 1085 1305
+f 1085 1075 1305
+f 874 1283 1291
+f 874 904 1283
+f 1288 870 1293
+f 870 875 1293
+f 1285 865 1290
+f 865 871 1290
+f 1284 903 1287
+f 903 866 1287
+f 1272 877 1275
+f 877 1196 1275
+f 1264 1198 1269
+f 1198 880 1269
+f 1252 882 1257
+f 882 885 1257
+f 1228 1205 1233
+f 1205 1066 1233
+f 1216 1077 1221
+f 1077 1208 1221
+f 1222 1226 1229
+f 1222 1229 1232
+f 1270 1274 1277
+f 1270 1277 1280
+f 1294 1298 1301
+f 1294 1301 1304
+f 1210 1214 1217
+f 1210 1217 1220
+f 1234 1238 1241
+f 1234 1241 1244
+f 1282 1286 1289
+f 1282 1289 1292
+f 1258 1262 1265
+f 1258 1265 1268
+f 1246 1250 1253
+f 1246 1253 1256
+f 1210 1211 1212
+f 1213 1214 1215
+f 1216 1217 1218
+f 1219 1220 1221
+f 1222 1223 1224
+f 1225 1226 1227
+f 1228 1229 1230
+f 1231 1232 1233
+f 1234 1235 1236
+f 1237 1238 1239
+f 1240 1241 1242
+f 1243 1244 1245
+f 1246 1247 1248
+f 1249 1250 1251
+f 1252 1253 1254
+f 1255 1256 1257
+f 1258 1259 1260
+f 1261 1262 1263
+f 1264 1265 1266
+f 1267 1268 1269
+f 1270 1271 1272
+f 1273 1274 1275
+f 1276 1277 1278
+f 1279 1280 1281
+f 1282 1283 1284
+f 1285 1286 1287
+f 1288 1289 1290
+f 1291 1292 1293
+f 1294 1295 1296
+f 1297 1298 1299
+f 1300 1301 1302
+f 1303 1304 1305
+f 1306 1307 1308
+f 1309 1310 1311
+f 1312 1313 1314
+f 1315 1316 1317
+f 1318 1319 1320
+f 1321 1322 1323
+f 1324 1325 1326
+f 1327 1328 1329
+f 1330 1331 1332
+f 1333 1334 1335
+f 1336 1337 1338
+f 1339 1340 1341
+f 1342 1343 1344
+f 1345 1346 1347
+f 1348 1349 1350
+f 1351 1352 1353
+f 1354 1355 1356
+f 1357 1358 1359
+f 1360 1361 1362
+f 1363 1364 1365
+f 1366 1367 1368
+f 1369 1370 1371
+f 1372 1373 1374
+f 1375 1376 1377
+f 1378 1379 1380
+f 1381 1382 1383
+f 1384 1385 1386
+f 1387 1388 1389
+f 1390 1391 1392
+f 1393 1394 1395
+f 1396 1397 1398
+f 1399 1400 1401
+f 1214 1212 1215
+f 1210 1212 1214
+f 1078 1077 1218
+f 1077 1216 1218
+f 1217 1213 1218
+f 1214 1213 1217
+f 1208 1207 1221
+f 1207 1219 1221
+f 1220 1216 1221
+f 1217 1216 1220
+f 1211 1210 1219
+f 1219 1210 1220
+f 1226 1224 1227
+f 1222 1224 1226
+f 1206 1205 1230
+f 1205 1228 1230
+f 1229 1225 1230
+f 1226 1225 1229
+f 1066 1065 1233
+f 1065 1231 1233
+f 1232 1228 1233
+f 1229 1228 1232
+f 1223 1222 1231
+f 1231 1222 1232
+f 1238 1236 1239
+f 1234 1236 1238
+f 1241 1237 1242
+f 1238 1237 1241
+f 1244 1240 1245
+f 1241 1240 1244
+f 1235 1234 1243
+f 1243 1234 1244
+f 1250 1248 1251
+f 1246 1248 1250
+f 883 882 1254
+f 882 1252 1254
+f 1253 1249 1254
+f 1250 1249 1253
+f 1256 1252 1257
+f 1253 1252 1256
+f 1247 1246 1255
+f 1255 1246 1256
+f 1262 1260 1263
+f 1258 1260 1262
+f 1199 1198 1266
+f 1198 1264 1266
+f 1265 1261 1266
+f 1262 1261 1265
+f 880 879 1269
+f 879 1267 1269
+f 1268 1264 1269
+f 1265 1264 1268
+f 1259 1258 1267
+f 1267 1258 1268
+f 878 877 1271
+f 1271 877 1272
+f 1196 1195 1275
+f 1195 1273 1275
+f 1274 1272 1275
+f 1270 1272 1274
+f 1277 1273 1278
+f 1274 1273 1277
+f 1280 1276 1281
+f 1277 1276 1280
+f 1271 1270 1279
+f 1279 1270 1280
+f 904 903 1283
+f 1283 903 1284
+f 866 865 1287
+f 865 1285 1287
+f 1286 1284 1287
+f 1282 1284 1286
+f 871 870 1290
+f 870 1288 1290
+f 1289 1285 1290
+f 1286 1285 1289
+f 875 874 1293
+f 874 1291 1293
+f 1292 1288 1293
+f 1289 1288 1292
+f 1283 1282 1291
+f 1291 1282 1292
+f 1298 1296 1299
+f 1294 1296 1298
+f 1086 1085 1302
+f 1085 1300 1302
+f 1301 1297 1302
+f 1298 1297 1301
+f 1075 1074 1305
+f 1074 1303 1305
+f 1304 1300 1305
+f 1301 1300 1304
+f 1295 1294 1303
+f 1303 1294 1304
+f 1316 1308 1317
+f 1306 1308 1316
+f 1308 1307 1311
+f 1307 1309 1311
+f 1311 1310 1314
+f 1310 1312 1314
+f 1314 1313 1317
+f 1313 1315 1317
+f 1328 1320 1329
+f 1318 1320 1328
+f 1320 1319 1323
+f 1319 1321 1323
+f 1735 1318 1736
+f 1319 1318 1735
+f 1323 1322 1326
+f 1322 1324 1326
+f 1739 1321 1740
+f 1322 1321 1739
+f 1326 1325 1329
+f 1325 1327 1329
+f 1744 1324 1745
+f 1325 1324 1744
+f 1704 1327 1705
+f 1328 1327 1704
+f 1340 1332 1341
+f 1330 1332 1340
+f 1332 1331 1335
+f 1331 1333 1335
+f 1335 1334 1338
+f 1334 1336 1338
+f 1338 1337 1341
+f 1337 1339 1341
+f 1413 1336 1414
+f 1337 1336 1413
+f 1339 1733 1737
+f 1340 1339 1737
+f 1352 1344 1353
+f 1342 1344 1352
+f 1344 1343 1347
+f 1343 1345 1347
+f 1730 1342 1731
+f 1343 1342 1730
+f 1347 1346 1350
+f 1346 1348 1350
+f 1415 1345 1416
+f 1346 1345 1415
+f 1350 1349 1353
+f 1349 1351 1353
+f 1364 1356 1365
+f 1354 1356 1364
+f 1356 1355 1359
+f 1355 1357 1359
+f 1359 1358 1362
+f 1358 1360 1362
+f 1357 1728 1732
+f 1358 1357 1732
+f 1362 1361 1365
+f 1361 1363 1365
+f 1376 1368 1377
+f 1366 1368 1376
+f 1368 1367 1371
+f 1367 1369 1371
+f 1366 1549 1553
+f 1367 1366 1553
+f 1371 1370 1374
+f 1370 1372 1374
+f 1545 1369 1546
+f 1370 1369 1545
+f 1374 1373 1377
+f 1373 1375 1377
+f 1388 1380 1389
+f 1378 1380 1388
+f 1380 1379 1383
+f 1379 1381 1383
+f 1378 1544 1548
+f 1379 1378 1548
+f 1383 1382 1386
+f 1382 1384 1386
+f 1381 1402 1406
+f 1382 1381 1406
+f 1386 1385 1389
+f 1385 1387 1389
+f 1409 1384 1410
+f 1385 1384 1409
+f 1539 1387 1540
+f 1388 1387 1539
+f 1400 1392 1401
+f 1390 1392 1400
+f 1392 1391 1395
+f 1391 1393 1395
+f 1404 1390 1405
+f 1391 1390 1404
+f 1395 1394 1398
+f 1394 1396 1398
+f 1534 1393 1535
+f 1394 1393 1534
+f 1398 1397 1401
+f 1397 1399 1401
+f 1396 1527 1532
+f 1397 1396 1532
+f 1399 1407 1411
+f 1400 1399 1411
+f 1406 1405 1410
+f 1410 1405 1411
diff --git a/vispy/gloo/__init__.py b/vispy/gloo/__init__.py
new file mode 100644
index 0000000..5b7d2c7
--- /dev/null
+++ b/vispy/gloo/__init__.py
@@ -0,0 +1,88 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2013, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+"""
+Object oriented interface to OpenGL.
+
+This module implements classes for the things that are "objetcs" in
+OpenGL, such as textures, FBO's, VBO's and shaders. Further, some
+convenience classes are implemented (like the collection class?).
+
+This set of classes provides a friendly (Pythonic) interface
+to OpenGL, and is designed to provide OpenGL's full functionality.
+
+All classes inherit from GLObject, which provide a basic interface,
+enabling activatinge and deleting the object. Central to each
+visualization is the Program. Other objects, such as Texture2D and
+VertexBuffer should be set as uniforms and attributes of the Program
+object.
+
+Example::
+    
+    # Init
+    program = gloo.Program(vertex_source, fragment_source)
+    program['a_position'] = gloo.VertexBuffer(my_positions_array)
+    program['s_texture'] = gloo.Texture2D(my_image)
+    ...
+    
+    # Paint event handler
+    program['u_color'] = 0.0, 1.0, 0.0
+    program.draw(gl.GL_TRIANGLES)
+
+.. Note::
+    
+    With vispy.gloo we strive to offer a Python interface that provides
+    the full functionality of OpenGL. However, this layer is a work in
+    progress and there are yet a few known limitations. Most notably:
+    
+    * TextureCubeMap is not yet implemented
+    * FBO's can only do 2D textures (not 3D textures or cube maps)
+    * Sharing of Shaders and RenderBuffers (between multiple Program's and
+      FrameBuffers, respecitively) is not well supported.
+    * No support for compressed textures.
+
+"""
+
+from __future__ import print_function, division, absolute_import
+
+from vispy.util.six import string_types
+from . import gl
+
+
+def ext_available(extension_name):
+    """ Get whether an extension is available. 
+    For now, this always returns True...
+    """
+    return True # for now
+
+
+def convert_to_enum(param, allow_none=False):
+    """ Convert parameter (e.g. a string) to GL enum. 
+    """
+    if isinstance(param, string_types):
+        param = param.upper()
+        if not param.startswith('GL'):
+            param = 'GL_' + param
+        try:
+            param = getattr(gl, param)
+        except AttributeError:
+            raise ValueError('Unknown GL enum: "%s".' % param)
+    elif isinstance(param, int):
+        pass  # We assume this is a valid enum
+    elif param is None and allow_none:
+        pass
+    else:
+        raise ValueError('Invalid type for GL enum: %r.' % type(param))
+    return param
+
+
+from .globject import GLObject
+
+from .buffer import VertexBuffer, ElementBuffer
+#from .buffer import ClientVertexBuffer, ClientElementBuffer
+from .texture import Texture2D, Texture3D, TextureCubeMap
+from .shader import VertexShader, FragmentShader
+from .framebuffer import FrameBuffer, RenderBuffer
+from .program import Program
+
diff --git a/vispy/gloo/buffer.py b/vispy/gloo/buffer.py
new file mode 100644
index 0000000..e75b8a6
--- /dev/null
+++ b/vispy/gloo/buffer.py
@@ -0,0 +1,870 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2013, Vispy Development Team. All Rights Reserved.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+# -----------------------------------------------------------------------------
+""" Definition of VertexBuffer, ElemenBuffer and client buffer classes. """
+
+from __future__ import print_function, division, absolute_import
+
+import sys
+import numpy as np
+
+from vispy.util import is_string
+from . import gl
+from . import GLObject
+from . import ext_available
+
+
+
+# ------------------------------------------------------------ Buffer class ---
+class Buffer(GLObject):
+    """ Interface to upload buffer data to the GPU. This class is shape
+    and dtype agnostic and considers the arrays as byte data.
+    
+    In general, you will want to use the VertexBuffer or ElementBuffer.
+    
+    Parameters
+    ----------
+    target : GLENUM
+        gl.GL_ARRAY_BUFFER or gl.GL_ELEMENT_ARRAY_BUFFER
+    data : ndarray
+        The data to set. Optional.
+    """
+
+
+    def __init__(self, target, data=None):
+        """ Initialize buffer into default state. """
+
+        GLObject.__init__(self)
+        
+        # Store and check target
+        if target not in (gl.GL_ARRAY_BUFFER, gl.GL_ELEMENT_ARRAY_BUFFER):
+            raise ValueError("Invalid target for buffer object.")
+        self._target = target
+        
+        # Total bytes consumed by the elements of the buffer
+        self._nbytes = 0
+        
+        # Indicate if a resize has been requested
+        self._need_resize = False
+        
+        # Buffer usage (GL_STATIC_DRAW, G_STREAM_DRAW or GL_DYNAMIC_DRAW)
+        self._usage = gl.GL_DYNAMIC_DRAW
+
+        # Set data
+        self._pending_data = []
+        if data is not None:
+            self.set_data(data)
+    
+    
+    def set_nbytes(self, nbytes):
+        """ Set how many bytes should be available for the buffer. 
+        """
+        nbytes = int(nbytes)
+        
+        # Set new bytes
+        if self._nbytes != nbytes:
+            self._nbytes = int(nbytes)
+            self._need_resize = True
+        
+        # Clear pending subdata
+        self._pending_data = []
+    
+    
+    def set_data(self, data):
+        """ Set the bytes data. This accepts a numpy array,
+        but the data is not checked for dtype or shape.
+        
+        Parameters
+        ----------
+        data : ndarray
+            The data to set.
+        """
+        
+        # Check data is a numpy array
+        if not isinstance(data, np.ndarray):
+            raise ValueError("Data should be a numpy array.")
+        
+        # Set shape if necessary
+        self.set_nbytes(data.nbytes)
+        
+        # Set pending!
+        nbytes = data.nbytes
+        self._pending_data.append( (data, nbytes, 0) )
+        self._need_update = True
+    
+    
+    def set_subdata(self, offset, data):
+        """ Update a region of the buffer.
+        
+        Parameters
+        ----------
+        offset : int
+            The offset (in bytes) at which to set the given data.
+        data : ndarray
+            The data to set.
+        
+        """
+        
+        # Check some size has been allocated
+        if not self._nbytes:
+            raise RuntimeError("Cannot set subdata if there is no space allocated.")
+            
+        # Check data is a numpy array
+        if not isinstance(data, np.ndarray):
+            raise ValueError("Data should be a numpy array.")
+        
+        # Get offset and nbytes
+        offset = int(offset)
+        nbytes = data.nbytes
+        
+        # Check
+        if offset < 0:
+            raise ValueError("Offset must be > 0.")
+        if (offset+nbytes) > self._nbytes:
+            raise ValueError("Offseted data is too big for buffer.")
+        
+        # Set pending!
+        self._pending_data.append( (data, nbytes, offset) )
+        self._need_update = True
+    
+    
+    @property
+    def nbytes(self):
+        """ The buffer size (in bytes). """
+        return self._nbytes
+
+    
+    def _create(self):
+        """ Create buffer on GPU """
+        if not self._handle:
+            self._handle = gl.glGenBuffers(1)
+    
+    
+    def _delete(self):
+        """ Delete buffer from GPU """
+        gl.glDeleteBuffers(1 , [self._handle])
+    
+    
+    def _activate(self):
+        """ Bind the buffer to some target """
+        gl.glBindBuffer(self._target, self._handle)
+
+
+    def _deactivate(self):
+        """ Unbind the current bound buffer """
+        gl.glBindBuffer(self._target, 0)
+
+
+    def _update(self):
+        """ Upload all pending data to GPU. """
+        
+        # Bind buffer now 
+        gl.glBindBuffer(self._target, self._handle)
+       
+        # Allocate new size if necessary
+        if self._need_resize:
+            # This will only allocate the buffer on GPU
+            # WARNING: we should check if this operation is ok
+            gl.glBufferData(self._target, self._nbytes, None, self._usage)
+            # debug
+            #print("Creating a new buffer (%d) of %d bytes"
+            #        % (self._handle, self._nbytes))
+            self._need_resize = False
+        
+        # Upload data
+        while self._pending_data:
+            data, nbytes, offset = self._pending_data.pop(0)
+            # debug
+            # print("Uploading %d bytes at offset %d to buffer (%d)"
+            #        % (nbytes, offset, self._handle))
+            try:
+                gl.glBufferSubData(self._target, offset, nbytes, data)
+            except Exception as error:
+                # This might be due to a driver error (seen on ATI), issue #64.
+                # We try to detect this, and if we can use glBufferData instead
+                if (    hasattr(error, 'err') and 
+                        error.err == gl.GL_INVALID_VALUE and 
+                        offset == 0 and nbytes == self._nbytes ):
+                    gl.glBufferData(self._target, nbytes, data, self._usage)
+                else:
+                    raise
+
+
+
+
+# ------------------------------------------------------ DataBuffer class ---
+class DataBuffer(Buffer):
+    """ Interface to upload buffer data to the GPU. This class is based
+    on :class:`buffer.Buffer`, and adds awareness of shape, dtype and striding.
+    
+    In general, you will want to use the VertexBuffer or ElementBuffer.
+    
+    Parameters
+    ----------
+    target : GLENUM
+        gl.GL_ARRAY_BUFFER or gl.GL_ELEMENT_ARRAY_BUFFER
+    data : ndarray or dtype
+        The data to set. See docs of VertexBuffer and ElementBuffer for
+        details.
+    
+    """
+
+
+    def __init__(self, target, data):
+        """ Initialize the buffer """
+        Buffer.__init__(self, target)
+        
+        # Default offset is 0, only really used for View
+        self._offset = 0
+        
+        # Allow smart initialziatin
+        if is_string(data):
+            data = np.dtype(data)  # with a string, e.g. "float32"
+        elif isinstance(data, tuple):
+            data = np.dtype([data])  # With a tuple, e.g. ('a', np.float32, 3)
+        elif isinstance(data, list):
+            data = np.dtype(data)  # With a list of the above tuples
+        elif isinstance(data, type) and issubclass(data, np.generic):
+            data = np.dtype(data)  # With e.g. np.float32
+        
+        # Initialize
+        if isinstance(data, np.ndarray):
+            # Fix dtype, vsize, stride. Initialize count
+            array_info = self._parse_array(data)
+            self._dtype, self._vsize, self._stride, self._count = array_info
+            # Set data now
+            if not isinstance(self, (ClientVertexBuffer, ClientElementBuffer)):
+                self.set_data(data)  
+        elif isinstance(data, np.dtype):
+            # Fix dtype, vsize, stride. Initialize count
+            self._dtype, self._vsize, self._stride = self._parse_dtype(data)
+            self._count = 0
+        else:
+            raise ValueError("DataBuffer needs array or dtype to initialize.")
+        
+        # Check data type
+        if self.dtype.fields:
+            for name in self.dtype.names:
+                dtype = self.dtype[name].base
+                if dtype.name not in self.DTYPE2GTYPE:
+                    raise TypeError("Data type not allowed for %s: %s" % 
+                                    (self.__class__.__name__, dtype.name) )
+        else:
+            if self.dtype.name not in self.DTYPE2GTYPE:
+                    raise TypeError("Data type not allowed for %s: %s" % 
+                                (self.__class__.__name__, self.dtype.name) )
+        
+    
+    
+    def _parse_array(self, data):
+        """ Return (dtype, vsize, stride, count), given an array.
+        NEED OVERLOADING
+        """
+        raise NotImplementedError()
+    
+    
+    def _parse_dtype(self, dtype):
+        """ Return (dtype, vsize, stride), given a dtype.
+        NEED OVERLOADING
+        """
+        raise NotImplementedError()
+    
+    
+    @property
+    def dtype(self):
+        """ The buffer data type. """
+        return self._dtype
+    
+    
+    @property
+    def vsize(self):
+        """ The vector size of each vertex in the buffer. This can be
+        1, 2, 3 or 4, corresponding with float, vec2, vec3, vec4. """
+        return self._vsize
+    
+    
+    @property
+    def stride(self):
+        """ The number of bytes separating two elements. """
+        return self._stride
+    
+    
+    @property
+    def count(self):
+        """ The number of vertices in the buffer. """
+        return self._count
+    
+    
+    @property
+    def offset(self):
+        """ The byte offset in the buffer. """
+        return self._offset
+    
+    
+    
+    def __setitem__(self, key, data):
+        """ Set data (deferred operation) """
+        
+        # Deal with slices that have None or negatives in them
+        if isinstance(key, slice):
+            start = key.start or 0
+            if start < 0:
+                start = self._stride + start
+            step = key.step or 1
+            assert step > 0
+            stop = key.stop or self._stride
+            if stop < 0:
+                stop = self._stride + stop
+        
+        # Check ellipsis (... notation)
+        if key == Ellipsis:
+            start = 0
+            nbytes = data.nbytes
+        # If key is not a slice
+        elif not isinstance(key, slice) or step > 1:
+            raise ValueError("Can only set contiguous block of data.")
+        # Else we're happy
+        else:
+            nbytes = (stop - start) * self._stride
+        
+        # Check we have the right amount of data
+        if data.nbytes < nbytes:
+            raise ValueError("Not enough data.")
+        elif data.nbytes > nbytes:
+            raise ValueError("Too much data.")
+        
+        # WARNING: Do we check data type here or do we cast the data to the
+        # same internal dtype ? This would make a silent copy of the data which
+        # can be problematic in some cases.
+        if data.dtype != self.dtype:
+            data = data.astype(self.dtype)  # astype() always makes a copy
+        # Set
+        self.set_subdata(start, data)
+    
+    
+    def __getitem__(self, key):
+        """ Create a view on this buffer. """
+        
+        if not is_string(key):
+            raise ValueError("Can only get access to a named field")
+        
+        # Get dtype, e.g. ('x', '<f4', 2)  so it has the vsize!
+        dtype = self._dtype[key]  # not .base! 
+        offset = self._dtype.fields[key][1]
+        
+        return VertexBufferView(dtype, base=self, offset=offset)
+    
+    
+    def set_count(self, count):
+        """ Set the number of vertices for this buffer. This will
+        allocate data and discard any pending subdata.
+        
+        Parameters
+        ----------
+        count : int
+            The new size of the buffer; the number of vertices.
+        
+        """
+        
+        # Set count
+        self._count = int(count)
+        
+        # Update bytes
+        nbytes = self._count * self._stride
+        self.set_nbytes(nbytes)
+    
+    
+    def set_data(self, data):
+        """ Set the data for this buffer. Any pending data is discarted.
+        The dtype and vsize of this buffer should be respected.
+        
+        Parameters
+        ----------
+        data :: np.ndarray
+            The data to upload.
+        
+        """
+        
+        # Check data is a numpy array
+        if not isinstance(data, np.ndarray):
+            raise ValueError("Data should be a numpy array.")
+        
+        # If data is a structure array with a unique field
+        # we get this unique field as data
+        while data.dtype.fields and len(data.dtype.fields) == 1:
+            data = data[data.dtype.names[0]]
+        
+        # Get props of the given data
+        dtype, vsize, stride, count = self._parse_array(data)
+        
+        # Check dtype and vsize to see whether it is a match
+        if dtype != self.dtype:
+            raise ValueError('Given data must match dtype of the buffer.')
+        elif vsize != self.vsize:
+            raise ValueError('Given data must match vsize of the buffer.')
+        
+        # Update count
+        self.set_count(count)
+        
+        # Update stride for this newly given data
+        self._stride = stride
+        
+        # Update data
+        Buffer.set_data(self, data)
+    
+    
+    def set_subdata(self, offset, data):
+        """ Set subdata. The dtype and vsize of this buffer should be
+        respected. And the data must fit in the current buffer.
+        
+        Parameters
+        ----------
+        offset : int
+            The offset (in vertex indices) to set the data for.
+        data : np.ndarray
+            The data to update.
+        """
+        
+        # If data is a structure array with a unique field
+        # we get this unique field as data
+        while data.dtype.fields and len(data.dtype.fields) == 1:
+            data = data[data.dtype.names[0]]
+        
+        # Get props of the given data and check whether it's a match
+        dtype, vsize, stride, count = self._parse_array(data)
+        if dtype != self.dtype:
+            raise ValueError('Given data must match dtype of the buffer.')
+        elif vsize != self.vsize:
+            raise ValueError('Given data must match vsize of the buffer.')
+        elif stride != self.stride:
+            raise ValueError('Given data must match stride of the buffer.')
+        
+        # Test whether it fits
+        if offset < 0:
+            raise ValueError('Offset in set_subdata should be >= 0.')
+        elif offset + count > self.count:
+            raise ValueError('Offset + data does not fit in this buffer.')
+        
+        # Turn attribute-offset into a byte offset
+        offset = int(offset)
+        byte_offset = offset * self._stride
+        
+        # Upload
+        Buffer.set_subdata(self, byte_offset, data)
+
+
+
+# ------------------------------------------------------ ElementBuffer class ---
+class ElementBuffer(DataBuffer):
+    """ The ElementBuffer allows to specify which element of a
+    VertexBuffer are to be used in a shader program. 
+    Inherits :class:`buffer.DataBuffer`.
+    
+    The given data must be of unsigned integer type. The shape of the
+    data is ignored; each element in the array is simply considered a
+    vertex index.
+    
+    Parameters
+    ----------
+    data : ndarray or dtype
+        Specify the data, or the type of the data. The dtype can also
+        be something that evaluates to a dtype, such as a 'uint32' or
+        np.uint8.
+    client : bool
+        Should be given as a keyword argument. If True, a
+        ClientElementBuffer is used instead, which is a lightweight
+        wrapper class for storing element data in CPU memory.
+    
+    Example
+    -------
+    indices = np.zeros(100, dtype=np.uint16)
+    buffer = ElementBuffer(indices)
+    program = Program(...)
+
+    program.draw(gl.GL_TRIANGLES, indices)
+    ...
+    """
+    
+    # We need a DTYPE->GL map for the element buffer. Used in program.draw()
+    DTYPE2GTYPE = { 'uint8': gl.GL_UNSIGNED_BYTE,
+                    'uint16': gl.GL_UNSIGNED_SHORT,
+                    'uint32': gl.GL_UNSIGNED_INT,
+                    }
+    
+    
+    def __new__(cls, *args, **kwargs):
+        if cls is ElementBuffer and kwargs.get('client', False):
+            return object.__new__(ClientElementBuffer)  # __init__ will be called
+        else:
+            return object.__new__(cls)  # __init__ will be called
+    
+    
+    def __init__(self, data, client=False):
+        DataBuffer.__init__(self, gl.GL_ELEMENT_ARRAY_BUFFER, data)
+    
+    
+    def _parse_array(self, data):
+        """ Return (dtype, vsize, stride, count), given an array.
+        """
+        
+        # Check data
+        if data.dtype.fields:
+            raise ValueError('ElementBuffer does not support structured arrays.')
+        
+        # Set dtype, vsize and stride
+        dtype, vsize, stride = self._parse_dtype(data.dtype)
+        
+        # Count is simply the size
+        count = data.size
+        
+        return dtype, vsize, stride, count
+    
+    
+    def _parse_dtype(self, dtype):
+        """ Return (dtype, vsize, stride), given a dtype.
+        """
+        
+        # Check data
+        if dtype.fields:
+            raise ValueError('ElementBuffer does not support structured dtype.')
+        
+        # Get base dtype, this will turn ('x', '<f4', 3) into np.float32
+        dtype_ = dtype.base
+        
+        # vsize is one, the ElementBuffer contains indices, which are scalars
+        vsize = 1
+        
+        # Get stride
+        stride = dtype.itemsize * vsize  # == dtype.itemsize
+        
+        return dtype_, vsize, stride 
+
+
+
+# ------------------------------------------------------ VertexBuffer class ---
+class VertexBuffer(DataBuffer):
+    """ The VertexBuffer represents any kind of vertex data, and can also
+    represent an array-of-structures approach. 
+    Inherits :class:`buffer.DataBuffer`.
+    
+    The shape of the given data is interpreted in the following way: 
+    If a normal array of one dimension is given, the vector-size (vsize)
+    is considered 1. Otherwise, data.shape[-1] is considered the vsize,
+    and the other dimensions are "collapsed" to get the vertex count.
+    If the data is a structured array, the number of elements in each
+    item is used as the vector-size (vsize). 
+    
+    Parameters
+    ----------
+    data : ndarray or dtype
+        Specify the data, or the type of the data. The dtype can also
+        be something that evaluates to a dtype, such as a 'uint32' or
+        np.uint8. If a structured array or dtype is given, and there
+        are more than 1 elements in the structure, this buffer is a
+        "structured" buffer. The corresponding items can be obtained
+        by indexing this buffer using their name. In most cases
+        one can use program.set_vars(structured_buffer) to map the
+        item names to their GLSL attribute names automatically.
+    client : bool
+        Should be given as a keyword argument. If True, a
+        ClientVertexBuffer is used instead, which is a lightweight
+        wrapper class for storing vertex data in CPU memory.
+    
+    Example
+    -------
+    dtype = np.dtype( [ ('position', np.float32, 3),
+                        ('texcoord', np.float32, 2),
+                        ('color',    np.float32, 4) ] )
+    data = np.zeros(100, dtype=dtype)
+    
+    program = Program(...)
+
+    program.set_vars(VertexBuffer(data))
+    """
+    
+    # Note that we do not actually use this, except the keys to test
+    # whether a data type is allowed; we parse the gtype from the
+    # attribute data.
+    DTYPE2GTYPE = { 'int8': gl.GL_BYTE,
+                    'uint8': gl.GL_UNSIGNED_BYTE,
+                    'uint16': gl.GL_UNSIGNED_SHORT,
+                    'int16': gl.GL_SHORT,
+                    'float32': gl.GL_FLOAT,
+                    'float16': gl.ext.GL_HALF_FLOAT,
+                    }
+
+    
+    def __new__(cls, *args, **kwargs):
+        if cls is VertexBuffer and kwargs.get('client', False):
+            return object.__new__(ClientVertexBuffer)  # __init__ will be called
+        else:
+            return object.__new__(cls)  # __init__ will be called
+    
+        
+    def __init__(self, data, client=False):
+        DataBuffer.__init__(self, gl.GL_ARRAY_BUFFER, data)
+    
+    
+    def _parse_array(self, data):
+        """ Return (dtype, vsize, stride, count), given an array.
+        """
+        
+        # If data is a structure array with a unique field
+        # we get this unique field as data
+        while data.dtype.fields and len(data.dtype.fields) == 1:
+            data = data[data.dtype.names[0]]
+        
+        # Set dtype, vsize and stride
+        dtype, vsize, stride = self._parse_dtype(data.dtype)
+        
+        # Determine count and vsize
+        if dtype.fields:
+            # Structured array, vsize is already set
+            # Count is simply the number of elements in the base array 
+            count = data.size
+            
+        else:
+            # Normal array, we reset vsize using the data
+            
+            if data.ndim <= 1:
+                # We take it the vector size is 1
+                vsize = 1
+                # Count is simply the number of elements.
+                count = data.size
+            else:
+                # Vector size is last dimension
+                vsize = data.shape[-1]
+                # Count is product of all dimensions except last
+                count = int(np.prod(data.shape[:-1]))
+        
+        # Set stride
+        if data.base is None:
+            # There is no base. PyOpenGL will upload our data as-is,
+            # so we use strides of the numpy array. This occurs in most
+            # situations, but also if we do VertexBuffer(data) where
+            # data is a structured array.
+            stride = data.strides[0]
+        else:
+            # There is a base, PyOpenGL will make a local copy before
+            # uploading the data. Therefore data.strides[0] will be
+            # incorrect; we need to calcualate the stride that the local
+            # copy will have. This can differ fron data.strides[0] when
+            # we do e.g. VertexBuffer( data['a_position'] ).
+            stride = dtype.itemsize * vsize
+        
+        # Done
+        return dtype, vsize, stride, count
+    
+    
+    def _parse_dtype(self, dtype):
+        """ Return (dtype, vsize, stride), given a dtype.
+        """
+        
+        # If dtype is a structure with a unique field
+        # we get this unique field as dtype
+        while dtype.fields and len(dtype.fields) == 1:
+            dtype = dtype[dtype.names[0]]
+        
+        # Get base dtype, this will turn ('x', '<f4', 3) into np.float32
+        dtype_ = dtype.base
+        
+        # Determine count and vsize
+        if dtype.fields:
+            # Structured array: Vector size is 1: one element of this 
+            # structured dtype per vertex
+            vsize = 1
+            # Or ... We set the sun of all vsizes
+            # No! because "stride = data.itemsize * vsize" will then fail!
+            #shapes = [dtype[name].shape for name in dtype.names]
+            #sizes = [int(np.prod(s)) for s in shapes]
+            #vsize = sum(sizes)
+        
+        elif dtype.shape:
+            # e.g. ('x', '<f4', 3): 
+            # Vector size is simply the number of elements in the dtype
+            vsize = int(np.prod(dtype.shape))
+        
+        else:
+            # Plain dtype, assume scalar value
+            vsize = 1
+        
+        # Get stride. Note that this will always be overriden by _parse_array
+        stride = dtype.itemsize * vsize
+        
+        return dtype_, vsize, stride 
+
+
+
+# ------------------------------------------------------ VertexBuffer class ---
+class VertexBufferView(VertexBuffer):
+    """ A VertexBufferView is a view on a VertexBuffer. It cannot be
+    used to set shape or data. You generally do not use this class
+    directly, but create an instance of this class by indexing in a
+    structured VertexBuffer.
+    """
+    
+    def __init__(self, dtype, base, offset):
+        """ Initialize the view """
+        assert isinstance(dtype, np.dtype)
+        VertexBuffer.__init__(self, dtype)
+        
+        self._base = base
+        self._offset = int(offset)
+        self._stride = base.stride  # Override this
+    
+    
+    def set_count(self, *args, **kwargs):
+        raise RuntimeError('Cannot set count on a %s.' % self.__class__.__name__)
+        
+    def set_data(self, *args, **kwargs):
+        raise RuntimeError('Cannot set data on a %s.' % self.__class__.__name__)
+    
+    def set_subdata(self, *args, **kwargs):
+        raise RuntimeError('Cannot set subdata on a %s.' % self.__class__.__name__)
+    
+    
+    @property
+    def handle(self):
+        # Handle on base buffer. (avoid showing up in docs)
+        self._handle = self._base._handle
+        return self._handle
+    
+    
+    @property
+    def stride(self):
+        """ Byte number separating two elements. """
+        self._stride = self._base.stride
+        return self._stride
+    
+    
+    @property
+    def count(self):
+        """ Number of vertices in the buffer. """
+        self._count = self._base.count
+        return self._count
+   
+
+    @property
+    def base(self):
+        """ Vertex buffer base of this view. """
+        return self._base
+
+
+    def _create(self):
+        """ Create buffer on GPU """
+        self._base._create()
+        self._handle = self._base._handle
+    
+    
+    def _delete(self):
+        """ Delete base buffer from GPU. """
+        self._base.delete()
+    
+    
+    def _activate(self):
+        """ Bind the base buffer to some target """
+        self._base.activate()
+
+
+    def _deactivate(self):
+        """ Unbind the base buffer """
+        self._base.deactivate()
+
+
+    def _update(self):
+        """ Update base buffer. """
+        pass  # base._update is called from base.activate
+
+
+
+
+# ------------------------------------------------ ClientVertexBuffer class ---
+class ClientVertexBuffer(VertexBuffer):
+    """
+    A client buffer is a buffer that only exists (permanently) on the CPU. It
+    cannot be modified nor uploaded into a GPU buffer. It merely serves as
+    passing direct data during a drawing operations.
+    
+    Note this kind of buffer is in general inefficient since data is
+    uploaded at each draw.
+    """
+    
+    def __init__(self, data, client=True):
+        """ Initialize the buffer. """
+        if not isinstance(data, np.ndarray):
+            raise ValueError('ClientVertexBuffer needs a numpy array.')
+        VertexBuffer.__init__(self, data)
+        self._data = data
+    
+    
+    @property
+    def data(self):
+        """ Buffer data. """
+        return self._data
+    
+    
+    def set_count(self, *args, **kwargs):
+        raise RuntimeError('Cannot set count on a %s.' % self.__class__.__name__)
+        
+    def set_data(self, *args, **kwargs):
+        raise RuntimeError('Cannot set data on a %s.' % self.__class__.__name__)
+    
+    def set_subdata(self, *args, **kwargs):
+        raise RuntimeError('Cannot set subdata on a %s.' % self.__class__.__name__)
+    
+    
+    def __getitem__(self, key):        pass
+    def __setitem__(self, key, data):  pass
+    def _create(self):                 pass
+    def _delete(self):                 pass
+    def _activate(self):               pass
+    def _deactivate(self):             pass
+    def _update(self):                 pass
+
+
+
+# ----------------------------------------------- ClientElementBuffer class ---
+class ClientElementBuffer(ElementBuffer):
+    """
+    A client buffer is a buffer that only exists (permanently) on the CPU. It
+    cannot be modified nor uploaded into a GPU buffer. It merely serves as
+    passing direct data during a drawing operations.
+    
+    Note this kind of buffer is in general inefficient since data is
+    uploaded at each draw.
+    """
+    
+    def __init__(self, data, client=True):
+        """ Initialize the buffer. """
+        if not isinstance(data, np.ndarray):
+            raise ValueError('ClientElementBuffer needs a numpy array.')
+        ElementBuffer.__init__(self, data)
+        self._data = data
+    
+    
+    @property
+    def data(self):
+        """ Buffer data. """
+        return self._data
+    
+    
+    def set_count(self, *args, **kwargs):
+        raise RuntimeError('Cannot set count on a %s.' % self.__class__.__name__)
+        
+    def set_data(self, *args, **kwargs):
+        raise RuntimeError('Cannot set data on a %s.' % self.__class__.__name__)
+    
+    def set_subdata(self, *args, **kwargs):
+        raise RuntimeError('Cannot set subdata on a %s.' % self.__class__.__name__)
+    
+    
+    def __getitem__(self, key):        pass
+    def __setitem__(self, key, data):  pass
+    def _create(self):                 pass
+    def _delete(self):                 pass
+    def _activate(self):               pass
+    def _deactivate(self):             pass
+    def _update(self):                 pass
+
+
diff --git a/vispy/gloo/framebuffer.py b/vispy/gloo/framebuffer.py
new file mode 100644
index 0000000..c232b94
--- /dev/null
+++ b/vispy/gloo/framebuffer.py
@@ -0,0 +1,351 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2013, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+""" Definition of Frame Buffer Object class and RenderBuffer class.
+
+"""
+
+from __future__ import print_function, division, absolute_import
+
+import sys
+import numpy as np
+
+from . import gl
+from . import GLObject, ext_available, convert_to_enum
+from . import Texture2D
+
+# todo: check and test all _delete methods
+
+# todo: we need a way to keep track of who uses a RenderBuffer,
+# so that it can be deleted when the last object stops using it.
+# Same for Shader class.
+# todo: support for 3D texture (need extension)
+# todo: support Cubemap
+
+
+class FrameBufferError(RuntimeError):
+    """ Raised when something goes wrong that depens on state that was set 
+    earlier (due to deferred loading).
+    """
+    pass
+
+
+
+class RenderBuffer(GLObject):
+    """ Representation of a render buffer, to be attached to a
+    FrameBuffer object.
+    """
+    
+    _COLOR_FORMATS = (gl.GL_RGB565, gl.GL_RGBA4, gl.GL_RGB5_A1,
+                      gl.ext.GL_RGB8, gl.ext.GL_RGBA8)
+    _DEPTH_FORMATS = (gl.GL_DEPTH_COMPONENT16, 
+                      gl.ext.GL_DEPTH_COMPONENT24, gl.ext.GL_DEPTH_COMPONENT32)
+    _STENCIL_FORMATS = (gl.GL_STENCIL_INDEX8, 
+                        gl.ext.GL_STENCIL_INDEX1, gl.ext.GL_STENCIL_INDEX4)
+    
+    _FORMATS = (_COLOR_FORMATS + _DEPTH_FORMATS + _STENCIL_FORMATS +
+                (gl.ext.GL_DEPTH24_STENCIL8,) )
+    
+    
+    def __init__(self, shape=None, format=None):
+        GLObject.__init__(self)
+        
+        # Parameters
+        self._shape = None
+        self._format = None
+        
+        # Set storage now?
+        if shape is not None:
+            self.set_shape(shape, format=format)
+    
+    
+    def set_shape(self, shape, format=None):
+        """ Allocate storage for this render buffer.
+        
+        This function can be repeatedly called without much cost if
+        the shape is not changed.
+        
+        In general, it's easier to just call FrameBuffer.set_size()
+        to allocate space for all attachements.
+        
+        Parameters
+        ----------
+        shape : tuple
+            The shape of the "virtual" data. Note that shape[0] is height.
+        format : str
+            The format representation of the data. If not given or None,
+            it is determined automatically depending on the shape and
+            the kind of atatchment. Can be RGB565, RGBA4, RGB5_A1, RGB8, 
+            RGBA8, DEPTH_COMPONENT16, DEPTH_COMPONENT24, DEPTH_COMPONENT32,
+            STENCIL_INDEX8, STENCIL_INDEX1, STENCIL_INDEX4. The OpenGL enum 
+            can also be given.
+        
+        """
+        # Set ndim
+        ndim = 2
+        
+        # Convert format
+        format = convert_to_enum(format, True)
+        
+        # Check shape
+        if not isinstance(shape, tuple):
+            raise ValueError("Shape must be a tuple.")
+        if not ( (len(shape) == ndim) or 
+                  len(shape) == ndim+1 and shape[-1] <= 4 ):
+            raise ValueError("Shape has invalid dimensions.")
+        shape = tuple([int(i) for i in shape])
+        if not all([i>0 for i in shape]):
+            raise ValueError("Cannot have negative shape.")
+        
+        # Is this already my shape?
+        if format is None or format is self._format:
+            if self._shape is not None:
+                if self._shape[:ndim] == shape[:ndim]:
+                    return
+        
+        # Check format
+        if format is None:
+            # Set later by FBO. 
+            # Note here, because default differs per color/depth/stencil
+            pass  
+        elif format not in self._FORMATS:
+            raise ValueError('Given format not supported for RenderBuffer storage.')
+        
+        # Set pending data
+        self._shape = shape
+        self._format = format or self._format
+        self._need_update = True
+    
+    
+    def _create(self):
+        self._handle = gl.glGenRenderbuffers(1)
+    
+    
+    def _delete(self):
+       gl.glDeleteRenderbuffers([self._handle])
+    
+    
+    def _activate(self):
+        gl.glBindRenderbuffer(gl.GL_RENDERBUFFER, self._handle)
+    
+    
+    def _deactivate(self):
+        gl.glBindRenderbuffer(gl.GL_RENDERBUFFER, 0)
+    
+    
+    def _update(self):
+        
+        # Enable now
+        gl.glBindRenderbuffer(gl.GL_RENDERBUFFER, self._handle)
+        
+        # Get data
+        shape, format =  self._shape, self._format
+        if shape is None or format is None:
+            return
+        # Check size
+        MAX = gl.glGetIntegerv(gl.GL_MAX_RENDERBUFFER_SIZE)
+        if shape[0] > MAX or shape[1] > MAX:
+            raise FrameBufferError('Cannot create a render buffer of %ix%i (max is %i).' % (shape[1], shape[0], MAX))
+        # Set 
+        gl.glRenderbufferStorage(gl.GL_RENDERBUFFER, format, shape[1], shape[0])
+    
+
+
+
+class FrameBuffer(GLObject):
+    """ Representation of a frame buffer object (a.k.a. FBO).
+    FrameBuffers allow off-screen rendering instead of to the screen.
+    This is for instance used for special effects and post-processing.
+    """
+    
+    def __init__(self, color=None, depth=None, stencil=None):
+        GLObject.__init__(self)
+        
+        # Init pending attachments
+        self._pending_attachments = []
+        self._attachment_color = None
+        self._attachment_depth = None
+        self._attachment_stencil = None
+        
+        # Set given attachments
+        if color is not None:
+            self.attach_color(color)
+        elif depth is not None:
+            self.attach_depth(depth)
+        elif stencil is not None:
+            self.attach_stencil(stencil)
+    
+    
+    @property
+    def color_attachment(self):
+        """ Get the color attachement.
+        """
+        return self._attachment_color
+    
+    
+    @property
+    def depth_attachment(self):
+        """ Get the depth attachement.
+        """
+        return self._attachment_depth
+    
+    
+    @property
+    def stencil_attachment(self):
+        """ Get the stencil attachement.
+        """
+        return self._attachment_stencil
+    
+    
+    def attach_color(self, object, level=0):
+        """ Attach a RenderBuffer of Texture instance to collect
+        color output for this FrameBuffer. Pass None for object
+        to detach the attachment. If a texture is given,
+        level specifies the mipmap level (default 0).
+        """
+        attachment = gl.GL_COLOR_ATTACHMENT0
+        if not isinstance(level, int):
+            raise ValueError("Level must be an int")
+        
+        if object is None or object == 0:
+            # Detach
+            self._attachment_color = None
+            self._pending_attachments.append( (attachment, 0, None) )
+        elif isinstance(object, RenderBuffer):
+            # Render buffer
+            self._attachment_color = object
+            object._format = object._format or gl.GL_RGB565  # GL_RGB565 or GL_RGBA4
+            self._pending_attachments.append( (attachment, object, None) )
+        elif isinstance(object, Texture2D):
+            # Texture
+            self._attachment_color = object
+            self._pending_attachments.append( (attachment, object, level) )
+        else:
+            raise ValueError('Can only attach a RenderBuffer of Texture to a FrameBuffer.')
+        self._need_update = True
+    
+    
+    def attach_depth(self, object, level=0):
+        """ Attach a RenderBuffer of Texture instance to collect
+        depth output for this FrameBuffer. Pass None for object
+        to detach the attachment. If a texture is given,
+        level specifies the mipmap level (default 0).
+        """
+        attachment = gl.GL_DEPTH_ATTACHMENT
+        if not isinstance(level, int):
+            raise ValueError("Level must be an int")
+        
+        if object is None or object == 0:
+            # Detach
+            self._attachment_depth = None
+            self._pending_attachments.append( (attachment, 0, None) )
+        elif isinstance(object, RenderBuffer):
+            # Render buffer
+            object._format = object._format or gl.GL_DEPTH_COMPONENT16
+            self._attachment_depth = object
+            self._pending_attachments.append( (attachment, object, None) )
+        elif isinstance(object, Texture2D):
+            # Texture
+            self._attachment_depth = object
+            self._attachment_depth = object
+            self._pending_attachments.append( (attachment, object, level) )
+        else:
+            raise ValueError('Can only attach a RenderBuffer or Texture to a FrameBuffer.')
+        self._need_update = True
+    
+    
+    def attach_stencil(self, object):
+        """ Attach a RenderBuffer instance to collect stencil output for
+        this FrameBuffer. Pass None for object to detach the
+        attachment.
+        """
+        attachment = gl.GL_STENCIL_ATTACHMENT
+        if object is None or object == 0:
+            # Detach
+            self._attachment_stencil = None
+            self._pending_attachments.append( (attachment, 0, None) )
+        elif isinstance(object, RenderBuffer):
+            object._format = object._format or gl.GL_STENCIL_INDEX8
+            # Detach
+            self._attachment_stencil = object
+            self._pending_attachments.append( (attachment, object, None) )
+        else:
+            raise ValueError('For stencil data, can only attach a RenderBuffer to a FrameBuffer.')
+        self._need_update = True
+    
+    
+    def set_size(self, width, height):
+        """ Convenience function to set the space allocated for all
+        attachments in use.
+        """
+        shape = height, width, 3
+        for attachment in ( self._attachment_color, 
+                            self._attachment_depth,
+                            self._attachment_stencil):
+            if isinstance(attachment, Texture2D):
+                attachment.set_shape(shape)
+            elif isinstance(attachment, RenderBuffer):
+                attachment.set_shape(shape)
+    
+    
+    def _create(self):
+        self._handle = gl.glGenFramebuffers(1)
+    
+    
+    def _delete(self):
+       gl.glDeleteFramebuffers([self._handle])
+    
+    
+    def _activate(self):
+        gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self._handle)
+    
+    
+    def _deactivate(self):
+        gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0)
+    
+    
+    def _update(self):
+        
+        # We need to activate before we can add attachements
+        gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self._handle)
+        
+        # Attach any RenderBuffers or Textures
+        # Note that we only enable the object briefly to attach it.
+        # After that, the object does not need to be bound.
+        while self._pending_attachments:
+            attachment, object, level = self._pending_attachments.pop(0)
+            if object == 0:
+                gl.glFramebufferRenderbuffer(gl.GL_FRAMEBUFFER, attachment,
+                                            gl.GL_RENDERBUFFER, 0)
+            elif isinstance(object, RenderBuffer):
+                with object:
+                    gl.glFramebufferRenderbuffer(gl.GL_FRAMEBUFFER, attachment,
+                                            gl.GL_RENDERBUFFER, object.handle)
+            elif isinstance(object, Texture2D):
+                # note that we use private variable _target from Texture
+                with object:
+                    gl.glFramebufferTexture2D(gl.GL_FRAMEBUFFER, attachment,
+                                    object._target, object.handle, level)
+            else:
+                raise FrameBufferError('Invalid attachment. This should not happen.')
+        
+        # Check
+        if True:
+            res = gl.glCheckFramebufferStatus(gl.GL_FRAMEBUFFER)
+            if res == gl.GL_FRAMEBUFFER_COMPLETE:
+                pass
+            elif res == 0:
+                raise FrameBufferError('Target not equal to GL_FRAMEBUFFER')
+            elif res == gl.GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
+                raise FrameBufferError('FrameBuffer attachments are incomplete.')
+            elif res == gl.GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
+                raise FrameBufferError('No valid attachments in the FrameBuffer.')
+            elif res == gl.GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS:
+                raise FrameBufferError('attachments do not have the same width and height.')   
+            #elif res == gl.GL_FRAMEBUFFER_INCOMPLETE_FORMATS:  # Not in our namespace?
+            #    raise FrameBufferError('Internal format of attachment is not renderable.')
+            elif res == gl.GL_FRAMEBUFFER_UNSUPPORTED:
+                raise FrameBufferError('Combination of internal formats used by attachments is not supported.')
+    
+
diff --git a/vispy/gloo/gl/__init__.py b/vispy/gloo/gl/__init__.py
new file mode 100644
index 0000000..6572ef2
--- /dev/null
+++ b/vispy/gloo/gl/__init__.py
@@ -0,0 +1,76 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2013, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+"""
+The raw API to OpenGL ES 2.0. There are multiple implementations of this API,
+available as submodules of this module. This module is a copy of one of these
+submodule implementations.
+"""
+
+# NOTE: modules in this package that start with one underscore are autogenerated
+
+from __future__ import print_function, division, absolute_import
+
+import vispy
+
+
+class _GL_ENUM(int):
+    """ Type to represent OpenGL constants.
+    """
+    def __new__(cls, name, value):
+        base = int.__new__(cls, value)
+        base.name = name
+        return base
+    def __repr__( self ):
+        return self.name
+
+
+def _make_debug_wrapper(funcname, func):
+    def cb(*args, **kwds):
+        argstr = ', '.join(list(map(repr,args)) + ['%s=%s' % item for item in kwds.items()])
+        print("%s(%s)" % (funcname, argstr))
+        ret = func(*args, **kwds)
+        print( " <= %s" % repr(ret))
+        return ret
+    return cb
+
+
+
+def use(target='desktop'):
+    """ Let Vispy use the target OpenGL ES 2.0 implementation.
+    Currently, only "desktop" is supported.
+    """
+    debug = vispy.config['gl_debug']
+    
+    # Select modules to import names from
+    if target == 'desktop':
+        from . import desktop as mod
+    else:
+        raise ValueError('Invalid target to load OpenGL API from.')
+    
+    # Import functions here
+    NS = globals()
+    funcnames = [name for name in dir(mod) if name.startswith('gl')]
+    for name in funcnames:
+        func = getattr(mod, name)
+        if debug:
+            func = _make_debug_wrapper(name, func)
+        NS[name] = func
+    
+    # Import functions in ext
+    NS = ext.__dict__
+    funcnames = [name for name in dir(mod.ext) if name.startswith('gl')]
+    for name in funcnames:
+        func = getattr(mod.ext, name)
+        if debug:
+            func = _make_debug_wrapper(name, func)
+        NS[name] = func
+
+
+# Import ext namespace and constants
+from . import ext
+from ._constants import *
+
+# Fill this namespace with functions
+use()
diff --git a/vispy/gloo/gl/_constants.py b/vispy/gloo/gl/_constants.py
new file mode 100644
index 0000000..6fc4522
--- /dev/null
+++ b/vispy/gloo/gl/_constants.py
@@ -0,0 +1,315 @@
+""" 
+
+THIS CODE IS AUTO-GENERATED. DO NOT EDIT.
+
+Constants for OpenGL ES 2.0.
+
+"""
+
+from __future__ import print_function, division, absolute_import
+
+from . import _GL_ENUM
+
+
+GL_ACTIVE_ATTRIBUTES = _GL_ENUM('GL_ACTIVE_ATTRIBUTES', 35721)
+GL_ACTIVE_ATTRIBUTE_MAX_LENGTH = _GL_ENUM('GL_ACTIVE_ATTRIBUTE_MAX_LENGTH', 35722)
+GL_ACTIVE_TEXTURE = _GL_ENUM('GL_ACTIVE_TEXTURE', 34016)
+GL_ACTIVE_UNIFORMS = _GL_ENUM('GL_ACTIVE_UNIFORMS', 35718)
+GL_ACTIVE_UNIFORM_MAX_LENGTH = _GL_ENUM('GL_ACTIVE_UNIFORM_MAX_LENGTH', 35719)
+GL_ALIASED_LINE_WIDTH_RANGE = _GL_ENUM('GL_ALIASED_LINE_WIDTH_RANGE', 33902)
+GL_ALIASED_POINT_SIZE_RANGE = _GL_ENUM('GL_ALIASED_POINT_SIZE_RANGE', 33901)
+GL_ALPHA = _GL_ENUM('GL_ALPHA', 6406)
+GL_ALPHA_BITS = _GL_ENUM('GL_ALPHA_BITS', 3413)
+GL_ALWAYS = _GL_ENUM('GL_ALWAYS', 519)
+GL_ARRAY_BUFFER = _GL_ENUM('GL_ARRAY_BUFFER', 34962)
+GL_ARRAY_BUFFER_BINDING = _GL_ENUM('GL_ARRAY_BUFFER_BINDING', 34964)
+GL_ATTACHED_SHADERS = _GL_ENUM('GL_ATTACHED_SHADERS', 35717)
+GL_BACK = _GL_ENUM('GL_BACK', 1029)
+GL_BLEND = _GL_ENUM('GL_BLEND', 3042)
+GL_BLEND_COLOR = _GL_ENUM('GL_BLEND_COLOR', 32773)
+GL_BLEND_DST_ALPHA = _GL_ENUM('GL_BLEND_DST_ALPHA', 32970)
+GL_BLEND_DST_RGB = _GL_ENUM('GL_BLEND_DST_RGB', 32968)
+GL_BLEND_EQUATION = _GL_ENUM('GL_BLEND_EQUATION', 32777)
+GL_BLEND_EQUATION_ALPHA = _GL_ENUM('GL_BLEND_EQUATION_ALPHA', 34877)
+GL_BLEND_EQUATION_RGB = _GL_ENUM('GL_BLEND_EQUATION_RGB', 32777)
+GL_BLEND_SRC_ALPHA = _GL_ENUM('GL_BLEND_SRC_ALPHA', 32971)
+GL_BLEND_SRC_RGB = _GL_ENUM('GL_BLEND_SRC_RGB', 32969)
+GL_BLUE_BITS = _GL_ENUM('GL_BLUE_BITS', 3412)
+GL_BOOL = _GL_ENUM('GL_BOOL', 35670)
+GL_BOOL_VEC2 = _GL_ENUM('GL_BOOL_VEC2', 35671)
+GL_BOOL_VEC3 = _GL_ENUM('GL_BOOL_VEC3', 35672)
+GL_BOOL_VEC4 = _GL_ENUM('GL_BOOL_VEC4', 35673)
+GL_BUFFER_SIZE = _GL_ENUM('GL_BUFFER_SIZE', 34660)
+GL_BUFFER_USAGE = _GL_ENUM('GL_BUFFER_USAGE', 34661)
+GL_BYTE = _GL_ENUM('GL_BYTE', 5120)
+GL_CCW = _GL_ENUM('GL_CCW', 2305)
+GL_CLAMP_TO_EDGE = _GL_ENUM('GL_CLAMP_TO_EDGE', 33071)
+GL_COLOR_ATTACHMENT0 = _GL_ENUM('GL_COLOR_ATTACHMENT0', 36064)
+GL_COLOR_BUFFER_BIT = _GL_ENUM('GL_COLOR_BUFFER_BIT', 16384)
+GL_COLOR_CLEAR_VALUE = _GL_ENUM('GL_COLOR_CLEAR_VALUE', 3106)
+GL_COLOR_WRITEMASK = _GL_ENUM('GL_COLOR_WRITEMASK', 3107)
+GL_COMPILE_STATUS = _GL_ENUM('GL_COMPILE_STATUS', 35713)
+GL_COMPRESSED_TEXTURE_FORMATS = _GL_ENUM('GL_COMPRESSED_TEXTURE_FORMATS', 34467)
+GL_CONSTANT_ALPHA = _GL_ENUM('GL_CONSTANT_ALPHA', 32771)
+GL_CONSTANT_COLOR = _GL_ENUM('GL_CONSTANT_COLOR', 32769)
+GL_CULL_FACE = _GL_ENUM('GL_CULL_FACE', 2884)
+GL_CULL_FACE_MODE = _GL_ENUM('GL_CULL_FACE_MODE', 2885)
+GL_CURRENT_PROGRAM = _GL_ENUM('GL_CURRENT_PROGRAM', 35725)
+GL_CURRENT_VERTEX_ATTRIB = _GL_ENUM('GL_CURRENT_VERTEX_ATTRIB', 34342)
+GL_CW = _GL_ENUM('GL_CW', 2304)
+GL_DECR = _GL_ENUM('GL_DECR', 7683)
+GL_DECR_WRAP = _GL_ENUM('GL_DECR_WRAP', 34056)
+GL_DELETE_STATUS = _GL_ENUM('GL_DELETE_STATUS', 35712)
+GL_DEPTH_ATTACHMENT = _GL_ENUM('GL_DEPTH_ATTACHMENT', 36096)
+GL_DEPTH_BITS = _GL_ENUM('GL_DEPTH_BITS', 3414)
+GL_DEPTH_BUFFER_BIT = _GL_ENUM('GL_DEPTH_BUFFER_BIT', 256)
+GL_DEPTH_CLEAR_VALUE = _GL_ENUM('GL_DEPTH_CLEAR_VALUE', 2931)
+GL_DEPTH_COMPONENT = _GL_ENUM('GL_DEPTH_COMPONENT', 6402)
+GL_DEPTH_COMPONENT16 = _GL_ENUM('GL_DEPTH_COMPONENT16', 33189)
+GL_DEPTH_FUNC = _GL_ENUM('GL_DEPTH_FUNC', 2932)
+GL_DEPTH_RANGE = _GL_ENUM('GL_DEPTH_RANGE', 2928)
+GL_DEPTH_TEST = _GL_ENUM('GL_DEPTH_TEST', 2929)
+GL_DEPTH_WRITEMASK = _GL_ENUM('GL_DEPTH_WRITEMASK', 2930)
+GL_DITHER = _GL_ENUM('GL_DITHER', 3024)
+GL_DONT_CARE = _GL_ENUM('GL_DONT_CARE', 4352)
+GL_DST_ALPHA = _GL_ENUM('GL_DST_ALPHA', 772)
+GL_DST_COLOR = _GL_ENUM('GL_DST_COLOR', 774)
+GL_DYNAMIC_DRAW = _GL_ENUM('GL_DYNAMIC_DRAW', 35048)
+GL_ELEMENT_ARRAY_BUFFER = _GL_ENUM('GL_ELEMENT_ARRAY_BUFFER', 34963)
+GL_ELEMENT_ARRAY_BUFFER_BINDING = _GL_ENUM('GL_ELEMENT_ARRAY_BUFFER_BINDING', 34965)
+GL_EQUAL = _GL_ENUM('GL_EQUAL', 514)
+GL_ES_VERSION_2_0 = _GL_ENUM('GL_ES_VERSION_2_0', 1)
+GL_EXTENSIONS = _GL_ENUM('GL_EXTENSIONS', 7939)
+GL_FALSE = _GL_ENUM('GL_FALSE', 0)
+GL_FASTEST = _GL_ENUM('GL_FASTEST', 4353)
+GL_FIXED = _GL_ENUM('GL_FIXED', 5132)
+GL_FLOAT = _GL_ENUM('GL_FLOAT', 5126)
+GL_FLOAT_MAT2 = _GL_ENUM('GL_FLOAT_MAT2', 35674)
+GL_FLOAT_MAT3 = _GL_ENUM('GL_FLOAT_MAT3', 35675)
+GL_FLOAT_MAT4 = _GL_ENUM('GL_FLOAT_MAT4', 35676)
+GL_FLOAT_VEC2 = _GL_ENUM('GL_FLOAT_VEC2', 35664)
+GL_FLOAT_VEC3 = _GL_ENUM('GL_FLOAT_VEC3', 35665)
+GL_FLOAT_VEC4 = _GL_ENUM('GL_FLOAT_VEC4', 35666)
+GL_FRAGMENT_SHADER = _GL_ENUM('GL_FRAGMENT_SHADER', 35632)
+GL_FRAMEBUFFER = _GL_ENUM('GL_FRAMEBUFFER', 36160)
+GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME = _GL_ENUM('GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME', 36049)
+GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE = _GL_ENUM('GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE', 36048)
+GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE = _GL_ENUM('GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE', 36051)
+GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL = _GL_ENUM('GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL', 36050)
+GL_FRAMEBUFFER_BINDING = _GL_ENUM('GL_FRAMEBUFFER_BINDING', 36006)
+GL_FRAMEBUFFER_COMPLETE = _GL_ENUM('GL_FRAMEBUFFER_COMPLETE', 36053)
+GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT = _GL_ENUM('GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT', 36054)
+GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = _GL_ENUM('GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS', 36057)
+GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = _GL_ENUM('GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT', 36055)
+GL_FRAMEBUFFER_UNSUPPORTED = _GL_ENUM('GL_FRAMEBUFFER_UNSUPPORTED', 36061)
+GL_FRONT = _GL_ENUM('GL_FRONT', 1028)
+GL_FRONT_AND_BACK = _GL_ENUM('GL_FRONT_AND_BACK', 1032)
+GL_FRONT_FACE = _GL_ENUM('GL_FRONT_FACE', 2886)
+GL_FUNC_ADD = _GL_ENUM('GL_FUNC_ADD', 32774)
+GL_FUNC_REVERSE_SUBTRACT = _GL_ENUM('GL_FUNC_REVERSE_SUBTRACT', 32779)
+GL_FUNC_SUBTRACT = _GL_ENUM('GL_FUNC_SUBTRACT', 32778)
+GL_GENERATE_MIPMAP_HINT = _GL_ENUM('GL_GENERATE_MIPMAP_HINT', 33170)
+GL_GEQUAL = _GL_ENUM('GL_GEQUAL', 518)
+GL_GREATER = _GL_ENUM('GL_GREATER', 516)
+GL_GREEN_BITS = _GL_ENUM('GL_GREEN_BITS', 3411)
+GL_HIGH_FLOAT = _GL_ENUM('GL_HIGH_FLOAT', 36338)
+GL_HIGH_INT = _GL_ENUM('GL_HIGH_INT', 36341)
+GL_IMPLEMENTATION_COLOR_READ_FORMAT = _GL_ENUM('GL_IMPLEMENTATION_COLOR_READ_FORMAT', 35739)
+GL_IMPLEMENTATION_COLOR_READ_TYPE = _GL_ENUM('GL_IMPLEMENTATION_COLOR_READ_TYPE', 35738)
+GL_INCR = _GL_ENUM('GL_INCR', 7682)
+GL_INCR_WRAP = _GL_ENUM('GL_INCR_WRAP', 34055)
+GL_INFO_LOG_LENGTH = _GL_ENUM('GL_INFO_LOG_LENGTH', 35716)
+GL_INT = _GL_ENUM('GL_INT', 5124)
+GL_INT_VEC2 = _GL_ENUM('GL_INT_VEC2', 35667)
+GL_INT_VEC3 = _GL_ENUM('GL_INT_VEC3', 35668)
+GL_INT_VEC4 = _GL_ENUM('GL_INT_VEC4', 35669)
+GL_INVALID_ENUM = _GL_ENUM('GL_INVALID_ENUM', 1280)
+GL_INVALID_FRAMEBUFFER_OPERATION = _GL_ENUM('GL_INVALID_FRAMEBUFFER_OPERATION', 1286)
+GL_INVALID_OPERATION = _GL_ENUM('GL_INVALID_OPERATION', 1282)
+GL_INVALID_VALUE = _GL_ENUM('GL_INVALID_VALUE', 1281)
+GL_INVERT = _GL_ENUM('GL_INVERT', 5386)
+GL_KEEP = _GL_ENUM('GL_KEEP', 7680)
+GL_LEQUAL = _GL_ENUM('GL_LEQUAL', 515)
+GL_LESS = _GL_ENUM('GL_LESS', 513)
+GL_LINEAR = _GL_ENUM('GL_LINEAR', 9729)
+GL_LINEAR_MIPMAP_LINEAR = _GL_ENUM('GL_LINEAR_MIPMAP_LINEAR', 9987)
+GL_LINEAR_MIPMAP_NEAREST = _GL_ENUM('GL_LINEAR_MIPMAP_NEAREST', 9985)
+GL_LINES = _GL_ENUM('GL_LINES', 1)
+GL_LINE_LOOP = _GL_ENUM('GL_LINE_LOOP', 2)
+GL_LINE_STRIP = _GL_ENUM('GL_LINE_STRIP', 3)
+GL_LINE_WIDTH = _GL_ENUM('GL_LINE_WIDTH', 2849)
+GL_LINK_STATUS = _GL_ENUM('GL_LINK_STATUS', 35714)
+GL_LOW_FLOAT = _GL_ENUM('GL_LOW_FLOAT', 36336)
+GL_LOW_INT = _GL_ENUM('GL_LOW_INT', 36339)
+GL_LUMINANCE = _GL_ENUM('GL_LUMINANCE', 6409)
+GL_LUMINANCE_ALPHA = _GL_ENUM('GL_LUMINANCE_ALPHA', 6410)
+GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS = _GL_ENUM('GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS', 35661)
+GL_MAX_CUBE_MAP_TEXTURE_SIZE = _GL_ENUM('GL_MAX_CUBE_MAP_TEXTURE_SIZE', 34076)
+GL_MAX_FRAGMENT_UNIFORM_VECTORS = _GL_ENUM('GL_MAX_FRAGMENT_UNIFORM_VECTORS', 36349)
+GL_MAX_RENDERBUFFER_SIZE = _GL_ENUM('GL_MAX_RENDERBUFFER_SIZE', 34024)
+GL_MAX_TEXTURE_IMAGE_UNITS = _GL_ENUM('GL_MAX_TEXTURE_IMAGE_UNITS', 34930)
+GL_MAX_TEXTURE_SIZE = _GL_ENUM('GL_MAX_TEXTURE_SIZE', 3379)
+GL_MAX_VARYING_VECTORS = _GL_ENUM('GL_MAX_VARYING_VECTORS', 36348)
+GL_MAX_VERTEX_ATTRIBS = _GL_ENUM('GL_MAX_VERTEX_ATTRIBS', 34921)
+GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS = _GL_ENUM('GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS', 35660)
+GL_MAX_VERTEX_UNIFORM_VECTORS = _GL_ENUM('GL_MAX_VERTEX_UNIFORM_VECTORS', 36347)
+GL_MAX_VIEWPORT_DIMS = _GL_ENUM('GL_MAX_VIEWPORT_DIMS', 3386)
+GL_MEDIUM_FLOAT = _GL_ENUM('GL_MEDIUM_FLOAT', 36337)
+GL_MEDIUM_INT = _GL_ENUM('GL_MEDIUM_INT', 36340)
+GL_MIRRORED_REPEAT = _GL_ENUM('GL_MIRRORED_REPEAT', 33648)
+GL_NEAREST = _GL_ENUM('GL_NEAREST', 9728)
+GL_NEAREST_MIPMAP_LINEAR = _GL_ENUM('GL_NEAREST_MIPMAP_LINEAR', 9986)
+GL_NEAREST_MIPMAP_NEAREST = _GL_ENUM('GL_NEAREST_MIPMAP_NEAREST', 9984)
+GL_NEVER = _GL_ENUM('GL_NEVER', 512)
+GL_NICEST = _GL_ENUM('GL_NICEST', 4354)
+GL_NONE = _GL_ENUM('GL_NONE', 0)
+GL_NOTEQUAL = _GL_ENUM('GL_NOTEQUAL', 517)
+GL_NO_ERROR = _GL_ENUM('GL_NO_ERROR', 0)
+GL_NUM_COMPRESSED_TEXTURE_FORMATS = _GL_ENUM('GL_NUM_COMPRESSED_TEXTURE_FORMATS', 34466)
+GL_NUM_SHADER_BINARY_FORMATS = _GL_ENUM('GL_NUM_SHADER_BINARY_FORMATS', 36345)
+GL_ONE = _GL_ENUM('GL_ONE', 1)
+GL_ONE_MINUS_CONSTANT_ALPHA = _GL_ENUM('GL_ONE_MINUS_CONSTANT_ALPHA', 32772)
+GL_ONE_MINUS_CONSTANT_COLOR = _GL_ENUM('GL_ONE_MINUS_CONSTANT_COLOR', 32770)
+GL_ONE_MINUS_DST_ALPHA = _GL_ENUM('GL_ONE_MINUS_DST_ALPHA', 773)
+GL_ONE_MINUS_DST_COLOR = _GL_ENUM('GL_ONE_MINUS_DST_COLOR', 775)
+GL_ONE_MINUS_SRC_ALPHA = _GL_ENUM('GL_ONE_MINUS_SRC_ALPHA', 771)
+GL_ONE_MINUS_SRC_COLOR = _GL_ENUM('GL_ONE_MINUS_SRC_COLOR', 769)
+GL_OUT_OF_MEMORY = _GL_ENUM('GL_OUT_OF_MEMORY', 1285)
+GL_PACK_ALIGNMENT = _GL_ENUM('GL_PACK_ALIGNMENT', 3333)
+GL_POINTS = _GL_ENUM('GL_POINTS', 0)
+GL_POLYGON_OFFSET_FACTOR = _GL_ENUM('GL_POLYGON_OFFSET_FACTOR', 32824)
+GL_POLYGON_OFFSET_FILL = _GL_ENUM('GL_POLYGON_OFFSET_FILL', 32823)
+GL_POLYGON_OFFSET_UNITS = _GL_ENUM('GL_POLYGON_OFFSET_UNITS', 10752)
+GL_RED_BITS = _GL_ENUM('GL_RED_BITS', 3410)
+GL_RENDERBUFFER = _GL_ENUM('GL_RENDERBUFFER', 36161)
+GL_RENDERBUFFER_ALPHA_SIZE = _GL_ENUM('GL_RENDERBUFFER_ALPHA_SIZE', 36179)
+GL_RENDERBUFFER_BINDING = _GL_ENUM('GL_RENDERBUFFER_BINDING', 36007)
+GL_RENDERBUFFER_BLUE_SIZE = _GL_ENUM('GL_RENDERBUFFER_BLUE_SIZE', 36178)
+GL_RENDERBUFFER_DEPTH_SIZE = _GL_ENUM('GL_RENDERBUFFER_DEPTH_SIZE', 36180)
+GL_RENDERBUFFER_GREEN_SIZE = _GL_ENUM('GL_RENDERBUFFER_GREEN_SIZE', 36177)
+GL_RENDERBUFFER_HEIGHT = _GL_ENUM('GL_RENDERBUFFER_HEIGHT', 36163)
+GL_RENDERBUFFER_INTERNAL_FORMAT = _GL_ENUM('GL_RENDERBUFFER_INTERNAL_FORMAT', 36164)
+GL_RENDERBUFFER_RED_SIZE = _GL_ENUM('GL_RENDERBUFFER_RED_SIZE', 36176)
+GL_RENDERBUFFER_STENCIL_SIZE = _GL_ENUM('GL_RENDERBUFFER_STENCIL_SIZE', 36181)
+GL_RENDERBUFFER_WIDTH = _GL_ENUM('GL_RENDERBUFFER_WIDTH', 36162)
+GL_RENDERER = _GL_ENUM('GL_RENDERER', 7937)
+GL_REPEAT = _GL_ENUM('GL_REPEAT', 10497)
+GL_REPLACE = _GL_ENUM('GL_REPLACE', 7681)
+GL_RGB = _GL_ENUM('GL_RGB', 6407)
+GL_RGB565 = _GL_ENUM('GL_RGB565', 36194)
+GL_RGB5_A1 = _GL_ENUM('GL_RGB5_A1', 32855)
+GL_RGBA = _GL_ENUM('GL_RGBA', 6408)
+GL_RGBA4 = _GL_ENUM('GL_RGBA4', 32854)
+GL_SAMPLER_2D = _GL_ENUM('GL_SAMPLER_2D', 35678)
+GL_SAMPLER_CUBE = _GL_ENUM('GL_SAMPLER_CUBE', 35680)
+GL_SAMPLES = _GL_ENUM('GL_SAMPLES', 32937)
+GL_SAMPLE_ALPHA_TO_COVERAGE = _GL_ENUM('GL_SAMPLE_ALPHA_TO_COVERAGE', 32926)
+GL_SAMPLE_BUFFERS = _GL_ENUM('GL_SAMPLE_BUFFERS', 32936)
+GL_SAMPLE_COVERAGE = _GL_ENUM('GL_SAMPLE_COVERAGE', 32928)
+GL_SAMPLE_COVERAGE_INVERT = _GL_ENUM('GL_SAMPLE_COVERAGE_INVERT', 32939)
+GL_SAMPLE_COVERAGE_VALUE = _GL_ENUM('GL_SAMPLE_COVERAGE_VALUE', 32938)
+GL_SCISSOR_BOX = _GL_ENUM('GL_SCISSOR_BOX', 3088)
+GL_SCISSOR_TEST = _GL_ENUM('GL_SCISSOR_TEST', 3089)
+GL_SHADER_BINARY_FORMATS = _GL_ENUM('GL_SHADER_BINARY_FORMATS', 36344)
+GL_SHADER_COMPILER = _GL_ENUM('GL_SHADER_COMPILER', 36346)
+GL_SHADER_SOURCE_LENGTH = _GL_ENUM('GL_SHADER_SOURCE_LENGTH', 35720)
+GL_SHADER_TYPE = _GL_ENUM('GL_SHADER_TYPE', 35663)
+GL_SHADING_LANGUAGE_VERSION = _GL_ENUM('GL_SHADING_LANGUAGE_VERSION', 35724)
+GL_SHORT = _GL_ENUM('GL_SHORT', 5122)
+GL_SRC_ALPHA = _GL_ENUM('GL_SRC_ALPHA', 770)
+GL_SRC_ALPHA_SATURATE = _GL_ENUM('GL_SRC_ALPHA_SATURATE', 776)
+GL_SRC_COLOR = _GL_ENUM('GL_SRC_COLOR', 768)
+GL_STATIC_DRAW = _GL_ENUM('GL_STATIC_DRAW', 35044)
+GL_STENCIL_ATTACHMENT = _GL_ENUM('GL_STENCIL_ATTACHMENT', 36128)
+GL_STENCIL_BACK_FAIL = _GL_ENUM('GL_STENCIL_BACK_FAIL', 34817)
+GL_STENCIL_BACK_FUNC = _GL_ENUM('GL_STENCIL_BACK_FUNC', 34816)
+GL_STENCIL_BACK_PASS_DEPTH_FAIL = _GL_ENUM('GL_STENCIL_BACK_PASS_DEPTH_FAIL', 34818)
+GL_STENCIL_BACK_PASS_DEPTH_PASS = _GL_ENUM('GL_STENCIL_BACK_PASS_DEPTH_PASS', 34819)
+GL_STENCIL_BACK_REF = _GL_ENUM('GL_STENCIL_BACK_REF', 36003)
+GL_STENCIL_BACK_VALUE_MASK = _GL_ENUM('GL_STENCIL_BACK_VALUE_MASK', 36004)
+GL_STENCIL_BACK_WRITEMASK = _GL_ENUM('GL_STENCIL_BACK_WRITEMASK', 36005)
+GL_STENCIL_BITS = _GL_ENUM('GL_STENCIL_BITS', 3415)
+GL_STENCIL_BUFFER_BIT = _GL_ENUM('GL_STENCIL_BUFFER_BIT', 1024)
+GL_STENCIL_CLEAR_VALUE = _GL_ENUM('GL_STENCIL_CLEAR_VALUE', 2961)
+GL_STENCIL_FAIL = _GL_ENUM('GL_STENCIL_FAIL', 2964)
+GL_STENCIL_FUNC = _GL_ENUM('GL_STENCIL_FUNC', 2962)
+GL_STENCIL_INDEX8 = _GL_ENUM('GL_STENCIL_INDEX8', 36168)
+GL_STENCIL_PASS_DEPTH_FAIL = _GL_ENUM('GL_STENCIL_PASS_DEPTH_FAIL', 2965)
+GL_STENCIL_PASS_DEPTH_PASS = _GL_ENUM('GL_STENCIL_PASS_DEPTH_PASS', 2966)
+GL_STENCIL_REF = _GL_ENUM('GL_STENCIL_REF', 2967)
+GL_STENCIL_TEST = _GL_ENUM('GL_STENCIL_TEST', 2960)
+GL_STENCIL_VALUE_MASK = _GL_ENUM('GL_STENCIL_VALUE_MASK', 2963)
+GL_STENCIL_WRITEMASK = _GL_ENUM('GL_STENCIL_WRITEMASK', 2968)
+GL_STREAM_DRAW = _GL_ENUM('GL_STREAM_DRAW', 35040)
+GL_SUBPIXEL_BITS = _GL_ENUM('GL_SUBPIXEL_BITS', 3408)
+GL_TEXTURE = _GL_ENUM('GL_TEXTURE', 5890)
+GL_TEXTURE0 = _GL_ENUM('GL_TEXTURE0', 33984)
+GL_TEXTURE1 = _GL_ENUM('GL_TEXTURE1', 33985)
+GL_TEXTURE10 = _GL_ENUM('GL_TEXTURE10', 33994)
+GL_TEXTURE11 = _GL_ENUM('GL_TEXTURE11', 33995)
+GL_TEXTURE12 = _GL_ENUM('GL_TEXTURE12', 33996)
+GL_TEXTURE13 = _GL_ENUM('GL_TEXTURE13', 33997)
+GL_TEXTURE14 = _GL_ENUM('GL_TEXTURE14', 33998)
+GL_TEXTURE15 = _GL_ENUM('GL_TEXTURE15', 33999)
+GL_TEXTURE16 = _GL_ENUM('GL_TEXTURE16', 34000)
+GL_TEXTURE17 = _GL_ENUM('GL_TEXTURE17', 34001)
+GL_TEXTURE18 = _GL_ENUM('GL_TEXTURE18', 34002)
+GL_TEXTURE19 = _GL_ENUM('GL_TEXTURE19', 34003)
+GL_TEXTURE2 = _GL_ENUM('GL_TEXTURE2', 33986)
+GL_TEXTURE20 = _GL_ENUM('GL_TEXTURE20', 34004)
+GL_TEXTURE21 = _GL_ENUM('GL_TEXTURE21', 34005)
+GL_TEXTURE22 = _GL_ENUM('GL_TEXTURE22', 34006)
+GL_TEXTURE23 = _GL_ENUM('GL_TEXTURE23', 34007)
+GL_TEXTURE24 = _GL_ENUM('GL_TEXTURE24', 34008)
+GL_TEXTURE25 = _GL_ENUM('GL_TEXTURE25', 34009)
+GL_TEXTURE26 = _GL_ENUM('GL_TEXTURE26', 34010)
+GL_TEXTURE27 = _GL_ENUM('GL_TEXTURE27', 34011)
+GL_TEXTURE28 = _GL_ENUM('GL_TEXTURE28', 34012)
+GL_TEXTURE29 = _GL_ENUM('GL_TEXTURE29', 34013)
+GL_TEXTURE3 = _GL_ENUM('GL_TEXTURE3', 33987)
+GL_TEXTURE30 = _GL_ENUM('GL_TEXTURE30', 34014)
+GL_TEXTURE31 = _GL_ENUM('GL_TEXTURE31', 34015)
+GL_TEXTURE4 = _GL_ENUM('GL_TEXTURE4', 33988)
+GL_TEXTURE5 = _GL_ENUM('GL_TEXTURE5', 33989)
+GL_TEXTURE6 = _GL_ENUM('GL_TEXTURE6', 33990)
+GL_TEXTURE7 = _GL_ENUM('GL_TEXTURE7', 33991)
+GL_TEXTURE8 = _GL_ENUM('GL_TEXTURE8', 33992)
+GL_TEXTURE9 = _GL_ENUM('GL_TEXTURE9', 33993)
+GL_TEXTURE_2D = _GL_ENUM('GL_TEXTURE_2D', 3553)
+GL_TEXTURE_BINDING_2D = _GL_ENUM('GL_TEXTURE_BINDING_2D', 32873)
+GL_TEXTURE_BINDING_CUBE_MAP = _GL_ENUM('GL_TEXTURE_BINDING_CUBE_MAP', 34068)
+GL_TEXTURE_CUBE_MAP = _GL_ENUM('GL_TEXTURE_CUBE_MAP', 34067)
+GL_TEXTURE_CUBE_MAP_NEGATIVE_X = _GL_ENUM('GL_TEXTURE_CUBE_MAP_NEGATIVE_X', 34070)
+GL_TEXTURE_CUBE_MAP_NEGATIVE_Y = _GL_ENUM('GL_TEXTURE_CUBE_MAP_NEGATIVE_Y', 34072)
+GL_TEXTURE_CUBE_MAP_NEGATIVE_Z = _GL_ENUM('GL_TEXTURE_CUBE_MAP_NEGATIVE_Z', 34074)
+GL_TEXTURE_CUBE_MAP_POSITIVE_X = _GL_ENUM('GL_TEXTURE_CUBE_MAP_POSITIVE_X', 34069)
+GL_TEXTURE_CUBE_MAP_POSITIVE_Y = _GL_ENUM('GL_TEXTURE_CUBE_MAP_POSITIVE_Y', 34071)
+GL_TEXTURE_CUBE_MAP_POSITIVE_Z = _GL_ENUM('GL_TEXTURE_CUBE_MAP_POSITIVE_Z', 34073)
+GL_TEXTURE_MAG_FILTER = _GL_ENUM('GL_TEXTURE_MAG_FILTER', 10240)
+GL_TEXTURE_MIN_FILTER = _GL_ENUM('GL_TEXTURE_MIN_FILTER', 10241)
+GL_TEXTURE_WRAP_S = _GL_ENUM('GL_TEXTURE_WRAP_S', 10242)
+GL_TEXTURE_WRAP_T = _GL_ENUM('GL_TEXTURE_WRAP_T', 10243)
+GL_TRIANGLES = _GL_ENUM('GL_TRIANGLES', 4)
+GL_TRIANGLE_FAN = _GL_ENUM('GL_TRIANGLE_FAN', 6)
+GL_TRIANGLE_STRIP = _GL_ENUM('GL_TRIANGLE_STRIP', 5)
+GL_TRUE = _GL_ENUM('GL_TRUE', 1)
+GL_UNPACK_ALIGNMENT = _GL_ENUM('GL_UNPACK_ALIGNMENT', 3317)
+GL_UNSIGNED_BYTE = _GL_ENUM('GL_UNSIGNED_BYTE', 5121)
+GL_UNSIGNED_INT = _GL_ENUM('GL_UNSIGNED_INT', 5125)
+GL_UNSIGNED_SHORT = _GL_ENUM('GL_UNSIGNED_SHORT', 5123)
+GL_UNSIGNED_SHORT_4_4_4_4 = _GL_ENUM('GL_UNSIGNED_SHORT_4_4_4_4', 32819)
+GL_UNSIGNED_SHORT_5_5_5_1 = _GL_ENUM('GL_UNSIGNED_SHORT_5_5_5_1', 32820)
+GL_UNSIGNED_SHORT_5_6_5 = _GL_ENUM('GL_UNSIGNED_SHORT_5_6_5', 33635)
+GL_VALIDATE_STATUS = _GL_ENUM('GL_VALIDATE_STATUS', 35715)
+GL_VENDOR = _GL_ENUM('GL_VENDOR', 7936)
+GL_VERSION = _GL_ENUM('GL_VERSION', 7938)
+GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING = _GL_ENUM('GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING', 34975)
+GL_VERTEX_ATTRIB_ARRAY_ENABLED = _GL_ENUM('GL_VERTEX_ATTRIB_ARRAY_ENABLED', 34338)
+GL_VERTEX_ATTRIB_ARRAY_NORMALIZED = _GL_ENUM('GL_VERTEX_ATTRIB_ARRAY_NORMALIZED', 34922)
+GL_VERTEX_ATTRIB_ARRAY_POINTER = _GL_ENUM('GL_VERTEX_ATTRIB_ARRAY_POINTER', 34373)
+GL_VERTEX_ATTRIB_ARRAY_SIZE = _GL_ENUM('GL_VERTEX_ATTRIB_ARRAY_SIZE', 34339)
+GL_VERTEX_ATTRIB_ARRAY_STRIDE = _GL_ENUM('GL_VERTEX_ATTRIB_ARRAY_STRIDE', 34340)
+GL_VERTEX_ATTRIB_ARRAY_TYPE = _GL_ENUM('GL_VERTEX_ATTRIB_ARRAY_TYPE', 34341)
+GL_VERTEX_SHADER = _GL_ENUM('GL_VERTEX_SHADER', 35633)
+GL_VIEWPORT = _GL_ENUM('GL_VIEWPORT', 2978)
+GL_ZERO = _GL_ENUM('GL_ZERO', 0)
diff --git a/vispy/gloo/gl/_constants_ext.py b/vispy/gloo/gl/_constants_ext.py
new file mode 100644
index 0000000..d350945
--- /dev/null
+++ b/vispy/gloo/gl/_constants_ext.py
@@ -0,0 +1,96 @@
+""" 
+
+THIS CODE IS AUTO-GENERATED. DO NOT EDIT.
+
+Constants for OpenGL ES 2.0.
+
+"""
+
+from __future__ import print_function, division, absolute_import
+
+from . import _GL_ENUM
+
+
+GL_ALPHA8 = _GL_ENUM('GL_ALPHA8', 32828)
+GL_BUFFER_ACCESS = _GL_ENUM('GL_BUFFER_ACCESS', 35003)
+GL_BUFFER_MAPPED = _GL_ENUM('GL_BUFFER_MAPPED', 35004)
+GL_BUFFER_MAP_POINTER = _GL_ENUM('GL_BUFFER_MAP_POINTER', 35005)
+GL_DEPTH24_STENCIL8 = _GL_ENUM('GL_DEPTH24_STENCIL8', 35056)
+GL_DEPTH24_STENCIL8 = _GL_ENUM('GL_DEPTH24_STENCIL8', 35056)
+GL_DEPTH_COMPONENT16 = _GL_ENUM('GL_DEPTH_COMPONENT16', 33189)
+GL_DEPTH_COMPONENT24 = _GL_ENUM('GL_DEPTH_COMPONENT24', 33190)
+GL_DEPTH_COMPONENT32 = _GL_ENUM('GL_DEPTH_COMPONENT32', 33191)
+GL_DEPTH_COMPONENT32 = _GL_ENUM('GL_DEPTH_COMPONENT32', 33191)
+GL_DEPTH_STENCIL = _GL_ENUM('GL_DEPTH_STENCIL', 34041)
+GL_DEPTH_STENCIL = _GL_ENUM('GL_DEPTH_STENCIL', 34041)
+GL_EGL_image = _GL_ENUM('GL_EGL_image', 1)
+GL_EGL_image_external = _GL_ENUM('GL_EGL_image_external', 1)
+GL_ETC1_RGB8 = _GL_ENUM('GL_ETC1_RGB8', 36196)
+GL_FRAGMENT_SHADER_DERIVATIVE_HINT = _GL_ENUM('GL_FRAGMENT_SHADER_DERIVATIVE_HINT', 35723)
+GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_3D_ZOFFSET = _GL_ENUM('GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_3D_ZOFFSET', 36052)
+GL_FRAMEBUFFER_UNDEFINED = _GL_ENUM('GL_FRAMEBUFFER_UNDEFINED', 33305)
+GL_HALF_FLOAT = _GL_ENUM('GL_HALF_FLOAT', 36193)
+GL_INT_10_10_10_2 = _GL_ENUM('GL_INT_10_10_10_2', 36343)
+GL_LUMINANCE4_ALPHA4 = _GL_ENUM('GL_LUMINANCE4_ALPHA4', 32835)
+GL_LUMINANCE8 = _GL_ENUM('GL_LUMINANCE8', 32832)
+GL_LUMINANCE8_ALPHA8 = _GL_ENUM('GL_LUMINANCE8_ALPHA8', 32837)
+GL_MAX_3D_TEXTURE_SIZE = _GL_ENUM('GL_MAX_3D_TEXTURE_SIZE', 32883)
+GL_NUM_PROGRAM_BINARY_FORMATS = _GL_ENUM('GL_NUM_PROGRAM_BINARY_FORMATS', 34814)
+GL_PALETTE4_R5_G6_B5 = _GL_ENUM('GL_PALETTE4_R5_G6_B5', 35730)
+GL_PALETTE4_RGB5_A1 = _GL_ENUM('GL_PALETTE4_RGB5_A1', 35732)
+GL_PALETTE4_RGB8 = _GL_ENUM('GL_PALETTE4_RGB8', 35728)
+GL_PALETTE4_RGBA4 = _GL_ENUM('GL_PALETTE4_RGBA4', 35731)
+GL_PALETTE4_RGBA8 = _GL_ENUM('GL_PALETTE4_RGBA8', 35729)
+GL_PALETTE8_R5_G6_B5 = _GL_ENUM('GL_PALETTE8_R5_G6_B5', 35735)
+GL_PALETTE8_RGB5_A1 = _GL_ENUM('GL_PALETTE8_RGB5_A1', 35737)
+GL_PALETTE8_RGB8 = _GL_ENUM('GL_PALETTE8_RGB8', 35733)
+GL_PALETTE8_RGBA4 = _GL_ENUM('GL_PALETTE8_RGBA4', 35736)
+GL_PALETTE8_RGBA8 = _GL_ENUM('GL_PALETTE8_RGBA8', 35734)
+GL_PROGRAM_BINARY_FORMATS = _GL_ENUM('GL_PROGRAM_BINARY_FORMATS', 34815)
+GL_PROGRAM_BINARY_LENGTH = _GL_ENUM('GL_PROGRAM_BINARY_LENGTH', 34625)
+GL_REQUIRED_TEXTURE_IMAGE_UNITS = _GL_ENUM('GL_REQUIRED_TEXTURE_IMAGE_UNITS', 36200)
+GL_RGB565 = _GL_ENUM('GL_RGB565', 36194)
+GL_RGB5_A1 = _GL_ENUM('GL_RGB5_A1', 32855)
+GL_RGB8 = _GL_ENUM('GL_RGB8', 32849)
+GL_RGBA4 = _GL_ENUM('GL_RGBA4', 32854)
+GL_RGBA8 = _GL_ENUM('GL_RGBA8', 32856)
+GL_SAMPLER_3D = _GL_ENUM('GL_SAMPLER_3D', 35679)
+GL_SAMPLER_EXTERNAL = _GL_ENUM('GL_SAMPLER_EXTERNAL', 36198)
+GL_STENCIL_INDEX1 = _GL_ENUM('GL_STENCIL_INDEX1', 36166)
+GL_STENCIL_INDEX4 = _GL_ENUM('GL_STENCIL_INDEX4', 36167)
+GL_TEXTURE_3D = _GL_ENUM('GL_TEXTURE_3D', 32879)
+GL_TEXTURE_BINDING_3D = _GL_ENUM('GL_TEXTURE_BINDING_3D', 32874)
+GL_TEXTURE_BINDING_EXTERNAL = _GL_ENUM('GL_TEXTURE_BINDING_EXTERNAL', 36199)
+GL_TEXTURE_EXTERNAL = _GL_ENUM('GL_TEXTURE_EXTERNAL', 36197)
+GL_TEXTURE_WRAP_R = _GL_ENUM('GL_TEXTURE_WRAP_R', 32882)
+GL_UNSIGNED_INT_10_10_10_2 = _GL_ENUM('GL_UNSIGNED_INT_10_10_10_2', 36342)
+GL_UNSIGNED_INT_24_8 = _GL_ENUM('GL_UNSIGNED_INT_24_8', 34042)
+GL_UNSIGNED_INT_24_8 = _GL_ENUM('GL_UNSIGNED_INT_24_8', 34042)
+GL_VERTEX_ARRAY_BINDING = _GL_ENUM('GL_VERTEX_ARRAY_BINDING', 34229)
+GL_WRITE_ONLY = _GL_ENUM('GL_WRITE_ONLY', 35001)
+GL_compressed_ETC1_RGB8_texture = _GL_ENUM('GL_compressed_ETC1_RGB8_texture', 1)
+GL_compressed_paletted_texture = _GL_ENUM('GL_compressed_paletted_texture', 1)
+GL_depth24 = _GL_ENUM('GL_depth24', 1)
+GL_depth32 = _GL_ENUM('GL_depth32', 1)
+GL_depth_texture = _GL_ENUM('GL_depth_texture', 1)
+GL_element_index_uint = _GL_ENUM('GL_element_index_uint', 1)
+GL_fbo_render_mipmap = _GL_ENUM('GL_fbo_render_mipmap', 1)
+GL_fragment_precision_high = _GL_ENUM('GL_fragment_precision_high', 1)
+GL_get_program_binary = _GL_ENUM('GL_get_program_binary', 1)
+GL_mapbuffer = _GL_ENUM('GL_mapbuffer', 1)
+GL_packed_depth_stencil = _GL_ENUM('GL_packed_depth_stencil', 1)
+GL_required_internalformat = _GL_ENUM('GL_required_internalformat', 1)
+GL_rgb8_rgba8 = _GL_ENUM('GL_rgb8_rgba8', 1)
+GL_standard_derivatives = _GL_ENUM('GL_standard_derivatives', 1)
+GL_stencil1 = _GL_ENUM('GL_stencil1', 1)
+GL_stencil4 = _GL_ENUM('GL_stencil4', 1)
+GL_surfaceless_context = _GL_ENUM('GL_surfaceless_context', 1)
+GL_texture_3D = _GL_ENUM('GL_texture_3D', 1)
+GL_texture_float = _GL_ENUM('GL_texture_float', 1)
+GL_texture_float_linear = _GL_ENUM('GL_texture_float_linear', 1)
+GL_texture_half_float = _GL_ENUM('GL_texture_half_float', 1)
+GL_texture_half_float_linear = _GL_ENUM('GL_texture_half_float_linear', 1)
+GL_texture_npot = _GL_ENUM('GL_texture_npot', 1)
+GL_vertex_array_object = _GL_ENUM('GL_vertex_array_object', 1)
+GL_vertex_half_float = _GL_ENUM('GL_vertex_half_float', 1)
+GL_vertex_type_10_10_10_2 = _GL_ENUM('GL_vertex_type_10_10_10_2', 1)
diff --git a/vispy/gloo/gl/_desktop.py b/vispy/gloo/gl/_desktop.py
new file mode 100644
index 0000000..888a1b5
--- /dev/null
+++ b/vispy/gloo/gl/_desktop.py
@@ -0,0 +1,158 @@
+""" 
+
+THIS CODE IS AUTO-GENERATED. DO NOT EDIT.
+
+OpenGL ES 2.0 API based on desktop OpenGL (via pyOpenGL).
+
+"""
+
+from __future__ import print_function, division, absolute_import
+
+from ._constants import *
+
+
+_glfunctions = [
+    "glActiveTexture",
+    "glAttachShader",
+    "glBindAttribLocation",
+    "glBindBuffer",
+    "glBindFramebuffer",
+    "glBindRenderbuffer",
+    "glBindTexture",
+    "glBlendColor",
+    "glBlendEquation",
+    "glBlendEquationSeparate",
+    "glBlendFunc",
+    "glBlendFuncSeparate",
+    "glBufferData",
+    "glBufferSubData",
+    "glCheckFramebufferStatus",
+    "glClear",
+    "glClearColor",
+    "glClearDepthf",
+    "glClearStencil",
+    "glColorMask",
+    "glCompileShader",
+    "glCompressedTexImage2D",
+    "glCompressedTexSubImage2D",
+    "glCopyTexImage2D",
+    "glCopyTexSubImage2D",
+    "glCreateProgram",
+    "glCreateShader",
+    "glCullFace",
+    "glDeleteBuffers",
+    "glDeleteFramebuffers",
+    "glDeleteProgram",
+    "glDeleteRenderbuffers",
+    "glDeleteShader",
+    "glDeleteTextures",
+    "glDepthFunc",
+    "glDepthMask",
+    "glDepthRangef",
+    "glDetachShader",
+    "glDisable",
+    "glDisableVertexAttribArray",
+    "glDrawArrays",
+    "glDrawElements",
+    "glEnable",
+    "glEnableVertexAttribArray",
+    "glFinish",
+    "glFlush",
+    "glFramebufferRenderbuffer",
+    "glFramebufferTexture2D",
+    "glFrontFace",
+    "glGenBuffers",
+    "glGenFramebuffers",
+    "glGenRenderbuffers",
+    "glGenTextures",
+    "glGenerateMipmap",
+    "glGetActiveAttrib",
+    "glGetActiveUniform",
+    "glGetAttachedShaders",
+    "glGetAttribLocation",
+    "glGetBooleanv",
+    "glGetBufferParameteriv",
+    "glGetError",
+    "glGetFloatv",
+    "glGetFramebufferAttachmentParameteriv",
+    "glGetIntegerv",
+    "glGetProgramInfoLog",
+    "glGetProgramiv",
+    "glGetRenderbufferParameteriv",
+    "glGetShaderInfoLog",
+    "glGetShaderPrecisionFormat",
+    "glGetShaderSource",
+    "glGetShaderiv",
+    "glGetString",
+    "glGetTexParameterfv",
+    "glGetTexParameteriv",
+    "glGetUniformLocation",
+    "glGetUniformfv",
+    "glGetUniformiv",
+    "glGetVertexAttribPointerv",
+    "glGetVertexAttribfv",
+    "glGetVertexAttribiv",
+    "glHint",
+    "glIsBuffer",
+    "glIsEnabled",
+    "glIsFramebuffer",
+    "glIsProgram",
+    "glIsRenderbuffer",
+    "glIsShader",
+    "glIsTexture",
+    "glLineWidth",
+    "glLinkProgram",
+    "glPixelStorei",
+    "glPolygonOffset",
+    "glReadPixels",
+    "glReleaseShaderCompiler",
+    "glRenderbufferStorage",
+    "glSampleCoverage",
+    "glScissor",
+    "glShaderBinary",
+    "glShaderSource",
+    "glStencilFunc",
+    "glStencilFuncSeparate",
+    "glStencilMask",
+    "glStencilMaskSeparate",
+    "glStencilOp",
+    "glStencilOpSeparate",
+    "glTexImage2D",
+    "glTexParameter",
+    "glTexParameterf",
+    "glTexParameterfv",
+    "glTexParameteri",
+    "glTexParameteriv",
+    "glTexSubImage2D",
+    "glUniform1f",
+    "glUniform1fv",
+    "glUniform1i",
+    "glUniform1iv",
+    "glUniform2f",
+    "glUniform2fv",
+    "glUniform2i",
+    "glUniform2iv",
+    "glUniform3f",
+    "glUniform3fv",
+    "glUniform3i",
+    "glUniform3iv",
+    "glUniform4f",
+    "glUniform4fv",
+    "glUniform4i",
+    "glUniform4iv",
+    "glUniformMatrix2fv",
+    "glUniformMatrix3fv",
+    "glUniformMatrix4fv",
+    "glUseProgram",
+    "glValidateProgram",
+    "glVertexAttrib1f",
+    "glVertexAttrib1fv",
+    "glVertexAttrib2f",
+    "glVertexAttrib2fv",
+    "glVertexAttrib3f",
+    "glVertexAttrib3fv",
+    "glVertexAttrib4f",
+    "glVertexAttrib4fv",
+    "glVertexAttribPointer",
+    "glViewport",
+    ]
diff --git a/vispy/gloo/gl/_desktop_ext.py b/vispy/gloo/gl/_desktop_ext.py
new file mode 100644
index 0000000..970778f
--- /dev/null
+++ b/vispy/gloo/gl/_desktop_ext.py
@@ -0,0 +1,30 @@
+""" 
+
+THIS CODE IS AUTO-GENERATED. DO NOT EDIT.
+
+OpenGL ES 2.0 API based on desktop OpenGL (via pyOpenGL).
+
+"""
+
+from __future__ import print_function, division, absolute_import
+
+from ._constants_ext import *
+
+
+_glfunctions = [
+    "glBindVertexArray",
+    "glCompressedTexImage3D",
+    "glCompressedTexSubImage3D",
+    "glCopyTexSubImage3D",
+    "glDeleteVertexArrays",
+    "glFramebufferTexture3D",
+    "glGenVertexArrays",
+    "glGetBufferPointerv",
+    "glGetProgramBinary",
+    "glIsVertexArray",
+    "glMapBuffer",
+    "glProgramBinary",
+    "glTexImage3D",
+    "glTexSubImage3D",
+    "glUnmapBuffer",
+    ]
diff --git a/vispy/gloo/gl/desktop.py b/vispy/gloo/gl/desktop.py
new file mode 100644
index 0000000..7869ea3
--- /dev/null
+++ b/vispy/gloo/gl/desktop.py
@@ -0,0 +1,165 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2013, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+""" vispy.gloo.gl.desktop: namespace for OpenGL ES 2.0 API based on the
+desktop OpenGL implementation.
+"""
+
+from __future__ import print_function, division, absolute_import
+
+from OpenGL import GL as _GL
+import OpenGL.GL.framebufferobjects as FBO
+
+from . import _GL_ENUM
+from . import _desktop, _desktop_ext
+
+# Prepare namespace with constants and ext
+from ._constants import *
+ext = _desktop_ext
+
+
+
+def _make_unavailable_func(funcname):
+    def cb(*args, **kwds):
+       raise RuntimeError('OpenGL API call "%s" is not available.' % funcname)
+    return cb
+
+
+def _get_function_from_pyopengl(funcname):
+    """ Try getting the given function from PyOpenGL, return
+    a dummy function (that prints a warning when called) if it 
+    could not be found.
+    """
+    func = None
+    # Get function from GL
+    try:
+        func = getattr(_GL, funcname)
+    except AttributeError:
+        # Get function from FBO
+
+        try:
+            func = getattr(FBO, funcname)
+        except AttributeError:
+            # Some functions are known by a slightly different name
+            # e.g. glDepthRangef, glDepthRangef
+            if funcname.endswith('f'):
+                try:
+                    func = getattr(_GL, funcname[:-1])
+                except AttributeError:
+                    pass
+    
+    # Set dummy function if we could not find it
+    if func is None:
+        func = _make_unavailable_func(funcname)
+        if True or show_warnings:  
+            print('warning: %s not available' % funcname )
+    return func
+
+
+def _inject():
+    """ Get GL functions from pyopengl. Inject in *this* namespace
+    and the ext namespace.
+    Note the similatity with vispy.gloo.gl.use().
+    """
+    import vispy
+    show_warnings = vispy.config['show_warnings']
+    import OpenGL.GL.framebufferobjects as FBO
+    
+    # Import functions here
+    NS = globals()
+    funcnames = _desktop._glfunctions
+    for name in funcnames:
+        func = _get_function_from_pyopengl(name)
+        NS[name] = func
+    
+    # Import functions in ext
+    NS = ext.__dict__
+    funcnames = _desktop_ext._glfunctions
+    for name in funcnames:
+        func = _get_function_from_pyopengl(name)
+        NS[name] = func
+
+
+def _fix():
+    """ Apply some fixes and patches.
+    """
+    NS = globals()
+    # Fix glGetActiveAttrib, since if its just the ctypes function
+    if (    'glGetActiveAttrib' in NS and 
+            hasattr(NS['glGetActiveAttrib'], 'restype') ):
+        import ctypes
+        def new_glGetActiveAttrib(program, index):
+            # Prepare
+            bufsize = 32
+            length = ctypes.c_int()
+            size = ctypes.c_int()
+            type = ctypes.c_int()
+            name = ctypes.create_string_buffer(bufsize)
+            # Call
+            _GL.glGetActiveAttrib(program, index, 
+                    bufsize, ctypes.byref(length), ctypes.byref(size), 
+                    ctypes.byref(type), name)
+            # Return Python objects
+            #return name.value.decode('utf-8'), size.value, type.value
+            return name.value, size.value, type.value
+        
+        # Patch
+        NS['glGetActiveAttrib'] = new_glGetActiveAttrib
+    
+    
+    # Monkey-patch pyopengl to fix a bug in glBufferSubData
+    import sys
+    if sys.version_info > (3,):
+        buffersubdatafunc = NS['glBufferSubData']
+        if hasattr(buffersubdatafunc, 'wrapperFunction'):
+            buffersubdatafunc = buffersubdatafunc.wrapperFunction
+        _m = sys.modules[buffersubdatafunc.__module__]
+        _m.long = int
+
+
+# Apply
+_inject()
+_fix()
+
+
+## Compatibility functions
+
+
+def glShaderSource_compat(handle, code):
+    """ This version of glShaderSource applies small modifications
+    to the given GLSL code in order to make it more compatible between
+    desktop and ES2.0 implementations. Specifically:
+      * It sets the #version pragma (if none is given already)
+      * It returns a (possibly empty) set of enums that should be enabled
+        (for automatically enabling point sprites)
+    """
+    
+    # Make a string
+    if isinstance(code, (list, tuple)):
+        code = '\n'.join(code)
+    
+    # Determine whether this is a vertex or fragment shader
+    code_ = '\n' + code
+    is_vertex = '\nattribute' in code_
+    is_fragment = not is_vertex
+    
+    # Determine whether to write the #version pragma
+    write_version = True
+    for line in code.splitlines():
+        if line.startswith('#version'):
+            write_version = False
+            print('For compatibility accross different GL backends, ' + 
+                    'avoid using the #version pragma.')
+    if write_version:
+        code = '#version 120\n#line 0\n' + code
+    
+    # Do the call
+    glShaderSource(handle, [code])
+    
+    # Determine whether to activate point sprites
+    enums = set()
+    if is_fragment and  'gl_PointCoord' in code:
+        enums.add( _GL_ENUM('GL_VERTEX_PROGRAM_POINT_SIZE', 34370) )
+        enums.add( _GL_ENUM('GL_POINT_SPRITE', 34913) )
+    return enums
diff --git a/vispy/gloo/gl/ext.py b/vispy/gloo/gl/ext.py
new file mode 100644
index 0000000..261d7d9
--- /dev/null
+++ b/vispy/gloo/gl/ext.py
@@ -0,0 +1,9 @@
+""" Namespace for functions and constants corresponding to 
+OpenGL ES 2.0 extensions.
+"""
+
+from __future__ import print_function, division, absolute_import
+
+from ._constants_ext import *
+
+# Filled with functions when vispy.gloo.gl is imported
diff --git a/vispy/gloo/globject.py b/vispy/gloo/globject.py
new file mode 100644
index 0000000..beb17e2
--- /dev/null
+++ b/vispy/gloo/globject.py
@@ -0,0 +1,153 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2013, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+""" Definition of the base class for all gloo objects.
+"""
+
+import vispy
+
+class GLObject(object):
+    """ Base class for classes that wrap an OpenGL object.
+    All GLObject's can be used as a context manager to enable them,
+    although some are better used by setting them as a uniform or
+    attribute of a Program.
+    
+    All GLObject's apply deferred (a.k.a. lazy) loading, which means
+    that the objects can be created and data can be set even if no
+    OpenGL context is available yet. 
+    
+    There are a few exceptions, most notably when enabling an object
+    by using it as a context manager, and the delete method. In these
+    cases, the called should ensure that the proper OpenGL context is
+    current.
+    """
+    
+    # Internal id counter to keep track of created objects
+    _idcount = 0
+    
+    def __init__(self):
+        
+        # The type of object (e.g. GL_TEXTURE_2D or GL_ARRAY_BUFFER)
+        # Used by some GLObjects internally
+        self._target = 0
+        
+        # Name of this object on the GPU ( >0 means there is an OpenGL object)
+        # GLObjects should set this in _enable()
+        self._handle = 0
+        
+        # Whether this object needs an update
+        # GLObjects can set this to True to receive a call to _update()
+        self._need_update = False
+        
+        # Whether the object is in a state that it can be used
+        # Is set to True if _update() returns without errors
+        self._valid = False
+        
+        
+        # Error counters (only used here)
+        self._error_enter = 0  # Track error on __enter__
+        self._error_exit = 0  # track errors on __exit__
+        
+        # Object internal id (for e.g. debugging)
+        GLObject._idcount += 1
+        self._id = GLObject._idcount
+    
+    
+    def __enter__(self):
+        """ Entering context  """
+
+        try:
+            # Try to enable, reset error state on success
+            self.activate()
+            self._error_enter = 0
+        except Exception:
+            # Error: increase error state. If this is the first error, raise
+            self._error_enter += 1
+            if self._error_enter == 1:
+                raise
+        return self
+    
+    
+    def __exit__(self, type, value, traceback):
+        """ Leaving context  """
+        returnval = None
+        if value is None:
+            # Reset error state on success
+            self._error_exit = 0
+        else:
+            # Error: increase error state. If not the first error, suppress
+            self._error_exit += 1
+            if self._error_enter or self._error_exit > 1: 
+                returnval = True  # Suppress error
+        self.deactivate()
+        return returnval
+    
+    
+    def __del__(self):
+        """ Delete the object from OpenGl memory. """
+        # You never know when this is goint to happen. The window might
+        # already be closed and no OpenGL context might be available.
+        # So we try, but suppress errors unless the user explicity asks them
+        try:
+            self.delete()
+        except Exception as err:
+            if vispy.config['show_warnings']:
+                print('Error deleting %r: %s' % (self, err))
+    
+    
+    def delete(self):
+        """ Delete the object from OpenGl memory. """
+
+        # Only delete object if it was created on GPU
+        if self._handle:
+            self._delete()
+        # Reset
+        self._handle = 0
+        self._valid = False
+    
+    
+    def activate(self):
+        """ Activate the object (a GL context must be available).
+        Note that the object can also be activated (and automatically
+        deactivated) by using it as a context manager.
+        """
+
+        # Ensure that the GPU equivalent of this object exists 
+        if not self.handle:
+            try:
+                self._create()
+            except Exception:
+                raise RuntimeError('Could not create %r, perhaps there is no OpenGL context?' % self)
+        # Perform an update if necessary
+        if self._need_update:
+            self._update()  # If it does not raise an error, assume valid
+            self._need_update = False
+            self._valid = True
+        # Activate
+        self._activate()
+    
+    
+    def deactivate(self):
+        """ Deactivate the object.
+        """
+
+        return self._deactivate()
+    
+    
+    @property
+    def handle(self):
+        """ Name of this object in GPU.
+        """
+
+        return self._handle
+    
+    
+    # Subclasses may need to overload methods below
+
+    def _create(self):     pass
+    def _delete(self):     pass
+    def _update(self):     pass
+    def _activate(self):   pass
+    def _deactivate(self): pass
+
diff --git a/vispy/gloo/program.py b/vispy/gloo/program.py
new file mode 100644
index 0000000..9e9c9f5
--- /dev/null
+++ b/vispy/gloo/program.py
@@ -0,0 +1,651 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2013, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+""" Definition of shader program.
+
+This code is inspired by similar classes from Pygly.
+
+"""
+
+from __future__ import print_function, division, absolute_import
+
+import re
+import sys
+import weakref
+
+import numpy as np
+
+from . import gl
+from . import GLObject, ext_available, convert_to_enum
+from . import VertexBuffer, ElementBuffer
+from .buffer import ClientElementBuffer
+from .variable import Attribute, Uniform
+from .shader import VertexShader, FragmentShader
+from vispy.util import is_string
+
+
+
+class ProgramError(RuntimeError):
+    """ Raised when something goes wrong that depens on state that was set 
+    earlier (due to deferred loading).
+    """
+    pass
+
+
+
+class Program(GLObject):
+    """ Representation of a shader program. It combines (links) a 
+    vertex and a fragment shaders to compose a complete program.
+    On (normal, non-ES 2.0) implementations, multiple shaders of 
+    each type are allowed).
+    
+    Objects of this class are also used to set the uniforms and
+    attributes that are used by the shaders. To do so, simply add
+    attributes to the `uniforms` and `attributes` members. The names
+    of the added attributes should match with those used in the shaders.
+    
+    Parameters
+    ----------
+    vert : Shader object or string
+        The VertexShader for this program. The string can be a file name
+        or the source of the shading code. It can also be a list of 
+        shaders, but this is not supported on genuine OpenGL ES 2.0.
+    frag : Shader object or string
+        The FragmentShader for this program. The string can be a file name
+        or the source of the shading code. It can also be a list of 
+        shaders, but this is not supported on genuine OpenGL ES 2.0.
+    
+    """
+    
+    def __init__(self, vert=None, frag=None):
+        GLObject.__init__(self)
+        
+        # Manage enabled state (i.e. activated)
+        self._active = False
+        self._activated_objects = []
+        
+        # List of varying names to use for feedback
+        self._feedback_vars = []
+        
+        # Inis lists of shaders
+        self._verts = []
+        self._frags = []
+        
+        # Containers for uniforms and attributes. 
+        # name -> Variable
+        self._attributes = {}
+        self._uniforms = {}
+        
+        # Keep track of which names are active (queried after linking)
+        # name -> location
+        self._active_attributes = {}
+        self._active_uniforms = {}
+        
+        # Keep track of number of vertices
+        self._vertex_count = None
+        
+        shaders = []
+        
+        # Get all vertex shaders
+        if vert is None:
+            pass
+        elif isinstance(vert, VertexShader):
+            shaders.append(vert)
+        elif is_string(vert):
+            shaders.append(VertexShader(vert))
+        elif isinstance(vert, (list, tuple)):
+            for shader in vert:
+                if is_string(shader):
+                    shader = VertexShader(shader)
+                shaders.append(shader)
+        else:
+            raise ValueError('Invalid value for VertexShader "%r"' % vert)
+        
+        # Get all fragment shaders
+        if frag is None:
+            pass
+        elif isinstance(frag, FragmentShader):
+            shaders.append(frag)
+        elif is_string(frag):
+            shaders.append(FragmentShader(frag))
+        elif isinstance(frag, (list, tuple)):
+            for shader in frag:
+                if is_string(shader):
+                    shader = FragmentShader(shader)
+                shaders.append(shader)
+        else:
+            raise ValueError('Invalid value for FragmentShader "%r"' % frag)
+        
+        # Attach shaders now
+        if shaders:
+            self.attach(*shaders)
+    
+    
+    def __repr__(self):
+        return "<%s %d>" % (self.__class__.__name__, self._id)
+    
+    
+    def attach(self, *shaders):
+        """ Attach one or more vertex/fragment shaders to the program. 
+        
+        Paramaters
+        ----------
+        *shaders : Shader objects
+            The VertexShader or FragmentShader to attach.
+        
+        """
+        # Tuple or list given?
+        if len(shaders)==1 and isinstance(shaders[0], (list,tuple)):
+            shaders = shaders[0]
+        
+        # Process each shader
+        for shader in shaders:
+            if isinstance(shader, VertexShader):
+                self._verts.append(shader)
+            elif isinstance(shader, FragmentShader):
+                self._frags.append(shader)
+            else:
+                ValueError('Invalid value for shader "%r"' % shader)
+        
+        # Ensure uniqueness of shaders
+        self._verts = list(set(self._verts))
+        self._frags = list(set(self._frags))
+
+        # Update dirty flag to induce a new build when necessary
+        self._need_update = True
+
+        # Build uniforms and attributes
+        self._build_uniforms()
+        self._build_attributes()
+
+    
+    def detach(self, *shaders):
+        """ Detach one or several vertex/fragment shaders from the program.
+        
+        Paramaters
+        ----------
+        *shaders : Shader objects
+            The VertexShader or FragmentShader to detach.
+        
+        """
+        # Tuple or list given?
+        if len(shaders)==1 and isinstance(shaders[0], (list,tuple)):
+            shaders = shaders[0]
+        
+        # Process each shader
+        for shader in shaders:
+            if isinstance(shader, VertexShader):
+                if shader in self._verts:
+                    self._verts.remove(shader)
+                else:
+                    raise ShaderException("Shader is not attached to the program")
+            elif isinstance(shader, FragmentShader):
+                if shader in self._frags:
+                    self._frags.remove(shader)
+                else:
+                    raise ShaderException("Shader is not attached to the program")
+            else:
+                ValueError('Invalid value for shader "%r"' % shader)
+        
+        # Update dirty flag to induce a new build when necessary
+        self._need_update = True
+        
+        # Build uniforms and attributes
+        self._build_uniforms()
+        self._build_attributes()
+    
+    
+    @property
+    def shaders(self):
+        """ List of shaders associated with this shading program.
+        """
+        return self._verts + self._frags
+    
+    
+    def __setitem__(self, name, data):
+        """ Behave a bit like a dict to assign attributes and uniforms.
+        This is the preferred way for the user to set uniforms and attributes.
+        """
+        if name in self._uniforms.keys():
+            # Set data and invalidate vertex count
+            self._uniforms[name].set_data(data)
+            self._vertex_count = None
+        elif name in self._attributes.keys():
+            # Set data
+            self._attributes[name].set_data(data)
+        else:
+            raise NameError("Unknown uniform or attribute: %s" % name)
+    
+    
+    def set_vars(self, vars=None, **keyword_vars):
+        """ Set variables from a dict-like object. vars can be a dict
+        or a structured numpy array. This is a convenience function
+        that is more or less equivalent to:
+        ``for name in vars: program[name] = vars[name]``
+        
+        """
+        D = {}
+        
+        # Process kwargs
+        D.update(keyword_vars)
+        
+        # Process vars
+        if vars is None:
+            pass
+        elif isinstance(vars, np.ndarray):
+            # Structured array
+            if vars.dtype.fields:
+                print("Warning: attribute data given as a structured " +
+                        "array, you probably want to use a VertexBuffer.")
+                for k in vars.dtype.fields:
+                    D[k] = vars[k]
+            else:
+                raise ValueError("Program.set_attr accepts a structured " +
+                    "array, but normal arrays must be given as keyword args.")
+        
+        elif isinstance(vars, VertexBuffer):
+            # Vertex buffer
+            if vars.dtype.names:
+                for k in vars.dtype.names:
+                    if k not in self._attributes.keys():
+                        print('Dropping "%s" item; '
+                                'it is not a known attribute.' % k)
+                    else:
+                        D[k] = vars[k]
+            else:
+                raise ValueError('Can only set attributes with a ' + 
+                                    'structured VertexBuffer.')
+        
+        elif isinstance(vars, dict):
+            # Dict
+            for k in vars:
+                if not (k in self._attributes.keys() or 
+                        k in self._uniforms.keys() ):
+                    print('Dropping "%s" item; '
+                            'it is not a known attribute/uniform.' % k)
+                else:
+                    D[k] = vars[k]
+        else:
+            raise ValueError("Don't know how to use attribute of type %r" %
+                                        type(vars))
+        
+        # Apply each
+        for name, data in D.items():
+            self[name] = data
+    
+    
+    @property
+    def attributes(self):
+        """ A list of all Attribute objects associated with this program
+        (sorted by name).
+        """
+        return list( sorted(self._attributes.values(), key=lambda x:x.name ) )
+    
+    
+    @property
+    def uniforms(self):
+        """ A list of all Uniform objects associated with this program
+        (sorted by name).
+        """
+        return list( sorted(self._uniforms.values(), key=lambda x:x.name ) )
+    
+    
+    def _get_vertex_count(self):
+        """ Get count of the number of vertices.
+        The count will only be recalculated if necessary, so if the
+        attributes have not changed, this function call should be quick.
+        """
+        if self._vertex_count is None:
+            count = None
+            for attribute in self.attributes:
+                # Check if valid count 
+                if attribute.count is None:
+                    continue
+                # Update count
+                if count is None:
+                    count = attribute.count
+                else:
+                    #if count != attribute.count:
+                    #    print('Warning: attributes have unequal number of vertices.')
+                    count = min(count, attribute.count)
+            self._vertex_count = count
+        
+        # Return
+        return self._vertex_count
+    
+    
+    def _build_attributes(self):
+        """ Build the attribute objects.
+        Called when shader is atatched/detached.
+        """
+        # Get all attributes (There are no attribute in fragment shaders)
+        v_attributes, f_attributes = [], []
+        for shader in self._verts:
+            v_attributes.extend(shader._get_attributes())
+        attributes = list(set(v_attributes + f_attributes))
+        
+        # Create Attribute objects for each one
+        self._attributes = {}
+        for (name, gtype) in attributes:
+            attribute = Attribute(name, gtype)
+            self._attributes[name] = attribute
+    
+    
+    def _build_uniforms(self):
+        """ Build the uniform objects. 
+        Called when shader is atatched/detached.
+        """
+        # Get al; uniformes
+        v_uniforms, f_uniforms = [], []
+        for shader in self._verts:
+            v_uniforms.extend(shader._get_uniforms())
+        for shader in self._frags:
+            f_uniforms.extend(shader._get_uniforms())
+        uniforms = list(set(v_uniforms + f_uniforms))
+        
+        # Create Uniform ojects for each one
+        self._uniforms = {}
+        for (name, gtype) in uniforms:
+            uniform = Uniform(name, gtype)
+            self._uniforms[name] = uniform
+        
+    
+    def _mark_active_attributes(self):
+        """ Mark which attributes are active and set the location.
+        Called after linking. 
+        """
+
+        count = gl.glGetProgramiv(self.handle, gl.GL_ACTIVE_ATTRIBUTES)
+        
+        # This match a name of the form "name[size]" (= array)
+        regex = re.compile("""(?P<name>\w+)\s*(\[(?P<size>\d+)\])""")
+        
+        # Find active attributes
+        self._active_attributes = {}
+        for i in range(count):
+            name, size, gtype = gl.glGetActiveAttrib(self.handle, i)
+            loc = gl.glGetAttribLocation(self._handle, name)
+            name = name.decode('utf-8')
+            # This checks if the attribute is an array
+            # Name will be something like xxx[0] instead of xxx
+            m = regex.match(name)
+            # When attribute is an array, size corresponds to the highest used index
+            if m:
+                name = m.group('name')
+                if size >= 1:
+                    for i in range(size):
+                        name = '%s[%d]' % (m.group('name'),i)
+                        self._active_attributes[name] = loc
+            else:
+                self._active_attributes[name] = loc
+        
+        # Mark these as active (loc non-None means active)
+        for attribute in self._attributes.values():
+            attribute._loc = self._active_attributes.get(attribute.name, None)
+    
+    
+    def _mark_active_uniforms(self):
+        """ Mark which uniforms are actve and set the location, 
+        for textures also set texture unit.
+        Called after linking. 
+        """
+
+        count = gl.glGetProgramiv(self.handle, gl.GL_ACTIVE_UNIFORMS)
+        
+        # This match a name of the form "name[size]" (= array)
+        regex = re.compile("""(?P<name>\w+)\s*(\[(?P<size>\d+)\])\s*""")
+        
+        # Find active uniforms
+        self._active_uniforms = {}
+        for i in range(count):
+            name, size, gtype = gl.glGetActiveUniform(self.handle, i)
+            loc = gl.glGetUniformLocation(self._handle, name)
+            name = name.decode('utf-8')
+            # This checks if the uniform is an array
+            # Name will be something like xxx[0] instead of xxx
+            m = regex.match(name)
+            # When uniform is an array, size corresponds to the highest used index
+            if m:
+                name = m.group('name')
+                if size >= 1:
+                    for i in range(size):
+                        name = '%s[%d]' % (m.group('name'),i)
+                        self._active_uniforms[name] = loc
+            else:
+                self._active_uniforms[name] = loc
+        
+        # Mark these as active (loc non-None means active)
+        texture_count = 0
+        for uniform in self._uniforms.values():
+            uniform._loc = self._active_uniforms.get(uniform.name, None)
+            if uniform._loc is not None:
+                if uniform._textureClass:
+                    uniform._texture_unit = texture_count
+                    texture_count += 1
+    
+    
+    
+    ## Behaver like a GLObject
+    
+    def _create(self):
+        self._handle = gl.glCreateProgram()
+    
+    
+    def _delete(self):
+        gl.glDeleteProgram(self._handle)
+    
+    
+    def _activate(self):
+        """
+          * Activate ourselves
+          * Prepare for activating other objects.
+          * Upload pending variables.
+        """
+        # Use this program!
+        gl.glUseProgram(self._handle)
+        
+        # Mark as enabled, prepare to enable other objects
+        self._active = True
+        self._activated_objects = []
+        
+        # Check if one of our shaders nees an updata.
+        # If so, we force ourselve to update, which will re-attach 
+        # and activate all shaders.
+        shaders_need_update = False
+        for shader in self.shaders:
+            shaders_need_update = shaders_need_update or shader._need_update
+        
+        # Update?
+        if shaders_need_update:
+            self._need_update = True
+            # Recursive, but beware to deactivate first, or we get
+            # "stuck" in a false activated state
+            self._deactivate()
+            self.activate()
+    
+    
+    def _deactivate(self):
+        """ Deactivate any objects that were activated on our behalf,
+        and then deactivate ourself.
+        """
+        self._active = False
+        for ob in reversed(self._activated_objects):
+            ob.deactivate()
+        self._activated_objects = []
+        gl.glUseProgram(0)
+    
+    
+    def _update(self):
+        """ Called when the object is activated and the _need_update
+        flag is set
+        """
+        # Check if we have something to link
+        if not self._verts:
+            raise ProgramError("No vertex shader has been given")
+        if not self._frags:
+            raise ProgramError("No fragment shader has been given")
+        
+        # Detach any attached shaders
+        attached = gl.glGetAttachedShaders(self._handle)
+        for handle in attached:
+            gl.glDetachShader(self._handle, handle)
+        
+        # Attach and activate vertex and fragment shaders
+        for shader in self.shaders:
+            shader.activate()
+            gl.glAttachShader(self._handle, shader.handle)
+        
+        # Only proceed if all shaders compiled ok
+        oks = [shader._valid for shader in self.shaders]
+        if not (oks and all(oks)):
+            raise ProgramError('Shaders did not compile.')
+        
+        # Link the program
+        # todo: should there be a try-except around this?
+        gl.glLinkProgram(self._handle)
+        if not gl.glGetProgramiv(self._handle, gl.GL_LINK_STATUS):
+            errors = gl.glGetProgramInfoLog(self._handle)
+            errormsg = self._get_error(errors, 4)
+            #parse_shader_errors(errors)
+            raise ProgramError('Error linking %r:\n'%self + errormsg)
+        
+        # Mark all active attributes and uniforms
+        self._mark_active_attributes()
+        self._mark_active_uniforms()
+        
+        # Invalidate all uniforms and attributes
+        for var in self._uniforms.values():
+            var.invalidate()
+        for var in self._attributes.values():
+            var.invalidate()
+    
+    
+    def  _get_error(self, errors, indentation=0):
+        """ Get linking error in a somewhat nicer format.
+        """
+        # Init
+        if not is_string(errors):
+            errors = errors.decode('utf-8', 'replace')
+        # Parse lines
+        results = [line for line in errors.splitlines() if line]
+        # Add indentation and return
+        results = [' '*indentation + r for r in results]
+        return '\n'.join(results)
+    
+    
+    ## Drawing and enabling
+    
+    
+    def activate_object(self, object):
+        """ Activate an object, e.g. a texture. The program
+        will make sure that the object is disabled again.
+        Can only be called while Program is active.
+        """
+        if not self._active:
+            raise ProgramError("Program cannot enable an object if not self being enabled.")
+        object.activate()
+        self._activated_objects.append(object)
+    
+    
+    
+    def draw(self, mode, subset=None):
+        """ Draw the vertices in the specified mode.
+        
+        If the program is not active, it is activated to do the
+        drawing and then deactivated.
+        
+        Parameters
+        ----------
+        mode : str
+            POINTS, LINES, LINE_STRIP, LINE_LOOP, TRIANGLES, TRIANGLE_STRIP, 
+            TRIANGLE_FAN. Case insensitive. Alternatively, the real GL enum
+            can also be given.
+        subset : {ElementBuffer, tuple}
+            The subset of vertices to draw. This can be an ElementBuffer
+            that specifies the indices of the vertices to draw, or a
+            tuple that specifies the slice: (start, end). The second
+            element in this tuple can be None to specify the maximum
+            length. If the subset is not given or None, all vertices
+            are drawn.
+        """
+        
+        # Check if active. If not, call recursively, but activated
+        if not self._active:
+            with self:
+                if self._error_enter:
+                    return  # Do not draw if activation went wrong!
+                return self.draw(mode, subset)
+        
+        # Check mode
+        mode = convert_to_enum(mode)
+        if mode not in [gl.GL_POINTS, gl.GL_LINES, gl.GL_LINE_STRIP, 
+                        gl.GL_LINE_LOOP, gl.GL_TRIANGLES, gl.GL_TRIANGLE_STRIP, 
+                        gl.GL_TRIANGLE_FAN]:
+            raise ValueError('Given mode is invalid: %r.' % mode)
+        
+        # Allow subset None
+        if subset is None:
+            subset = (0, None)
+        
+        # Upload any attributes and uniforms if necessary
+        for variable in (self.attributes + self.uniforms):
+            if variable.active:
+                variable.upload(self)
+        
+        # Enable any other stuff
+        need_enabled = set()
+        for shader in self._verts + self._frags:
+            need_enabled.update(shader._need_enabled)
+        for enum in need_enabled:
+            gl.glEnable(enum)
+        
+        if isinstance(subset, ElementBuffer):
+            # Draw elements
+            
+            # Prepare pointer or offset
+            if isinstance(subset, ClientElementBuffer):
+                ptr = subset.data
+            else:
+                ptr = None  # Note that this can also be a ctypes.pointer offset
+            
+            # Activate
+            self.activate_object(subset)
+            # Prepare
+            gltype = ElementBuffer.DTYPE2GTYPE[subset.dtype.name]
+            if gltype == gl.GL_UNSIGNED_INT and not ext_available('element_index_uint'):
+                raise ValueError('element_index_uint extension needed for uint32 ElementBuffer.')
+            # Draw
+            gl.glDrawElements(mode, subset.count, gltype, ptr) 
+        
+        
+        elif isinstance(subset, tuple):
+            # Draw arrays
+            
+            # Check tuple
+            ok = [isinstance(i, (int, type(None))) for i in subset]
+            if len(subset) != 2 or not all(ok):
+                raise ValueError('Subset must be a two-element tuple with '
+                                                    'interegers or None.')
+            # Get start, end, refcount
+            start, end = subset
+            start = start or 0
+            refcount = self._get_vertex_count()
+            # Determine count
+            if end is None:
+                count = refcount
+                if count is None:
+                    raise ProgramError("Could not determine element count for draw.")
+            else:
+                count = end - start
+                if refcount and count > refcount:
+                    raise ValueError('Count is larger than known number of vertices.')
+            # Draw
+            gl.glDrawArrays(mode, start, count)
+        
+        else:
+            raise ValueError('Given subset is of invalid type: %r.' % type(subset))
+        
+        # Clean up
+        for enum in need_enabled:
+            gl.glDisable(enum)
diff --git a/vispy/gloo/shader.py b/vispy/gloo/shader.py
new file mode 100644
index 0000000..8750761
--- /dev/null
+++ b/vispy/gloo/shader.py
@@ -0,0 +1,343 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Copyright (c) 2013, Vispy Development Team. All rights reserved.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+# -----------------------------------------------------------------------------
+"""
+VertexShader and FragmentShader classes.
+
+These classes are almost never created explicitly but are created implicitly
+from within a Program object.
+
+Example
+-------
+
+  vert = "some code"
+  frag = "some code"
+
+  program = Program(vert,frag)
+"""
+from __future__ import print_function, division
+
+import re
+import os
+import numpy as np
+
+from vispy.util import is_string
+from . import gl
+from . import GLObject
+
+
+
+# ------------------------------------------------------- class ShaderError ---
+class ShaderError(RuntimeError):
+    """ Shader error class """
+    pass
+
+
+
+
+
+# ------------------------------------------------------------ class Shader ---
+class Shader(GLObject):
+    """ Abstract shader class.
+    """
+
+    # Conversion of known uniform and attribute types to GL constants
+    _gtypes = {
+        'float':       gl.GL_FLOAT,
+        'vec2':        gl.GL_FLOAT_VEC2,
+        'vec3':        gl.GL_FLOAT_VEC3,
+        'vec4':        gl.GL_FLOAT_VEC4,
+        'int':         gl.GL_INT,
+        'ivec2':       gl.GL_INT_VEC2,
+        'ivec3':       gl.GL_INT_VEC3,
+        'ivec4':       gl.GL_INT_VEC4,
+        'bool':        gl.GL_BOOL,
+        'bvec2':       gl.GL_BOOL_VEC2,
+        'bvec3':       gl.GL_BOOL_VEC3,
+        'bvec4':       gl.GL_BOOL_VEC4,
+        'mat2':        gl.GL_FLOAT_MAT2,
+        'mat3':        gl.GL_FLOAT_MAT3,
+        'mat4':        gl.GL_FLOAT_MAT4,
+        'sampler2D':   gl.GL_SAMPLER_2D,
+        'sampler3D':   gl.ext.GL_SAMPLER_3D,
+        'samplerCube': gl.GL_SAMPLER_CUBE }
+    
+    
+    def __init__(self, target, code=None):
+        """
+        Create the shader and store code.
+        """
+
+        GLObject.__init__(self)
+        
+        # Check and store target
+        if target not in [gl.GL_VERTEX_SHADER, gl.GL_FRAGMENT_SHADER]:
+            raise ValueError('Target must be vertex or fragment shader.')
+        self._target = target
+        
+        # For auto-enabling point sprites
+        self._need_enabled = set()
+        
+        # Set code
+        self._code = None
+        self._source = None
+        if code is not None:
+            self.set_code(code)
+    
+    
+    def __repr__(self):
+        return "<%s %d (%s)>" % (self.__class__.__name__, self._id, self._source)
+    
+    
+    def set_code(self, code, source=None):
+        """ Set the code for this shader.
+        
+        Parameters
+        ----------
+        code : str
+            The GLSL source code, or a filename that contains the code.
+        source : str
+            A specifier where the code came from. If not given, 
+            "<string>" is used, or the filename where the code is loaded
+            from. Optional.
+        """
+        
+        if not is_string(code):
+            raise TypeError('Code must be a string (%s)' % type(code))
+            
+        # Set code and source
+        if os.path.isfile(code):
+            with open(code, 'rb') as file:
+                self._code   = file.read().decode('utf-8')
+                self._source = os.path.basename(code)
+        else:
+            self._code   = code
+            self._source = '<string>'
+        
+        # Set given source?
+        if source is not None:
+            if not is_string(source):
+                raise TypeError('Source must be a string (%s)' % type(source))
+            self._source = source
+        
+        # Set flags
+        self._need_update = True
+    
+    
+    @property
+    def code(self):
+        """ The GLSL code of this shader.
+        """
+        return self._code
+    
+    
+    @property
+    def source(self):
+        """ The source of the code for this shader 
+        (as in where it came from, not the source code).
+        """
+        return self._source
+    
+    
+    def _get_attributes(self):
+        """
+        Extract attributes (name and type) from code.
+        """
+
+        attributes = []
+        regex = re.compile("""\s*attribute\s+(?P<type>\w+)\s+"""
+                           """(?P<name>\w+)\s*(\[(?P<size>\d+)\])?\s*;""")
+        for m in re.finditer(regex,self._code):
+            size = -1
+            gtype = Shader._gtypes[m.group('type')]
+            if m.group('size'):
+                size = int(m.group('size'))
+            if size >= 1:
+                for i in range(size):
+                    name = '%s[%d]' % (m.group('name'),i)
+                    attributess.append((name, gtype))
+            else:
+                attributes.append((m.group('name'), gtype))
+
+        return attributes
+    
+    
+    def _get_uniforms(self):
+        """
+        Extract uniforms (name and type) from code.
+        """
+
+        uniforms = []
+        regex = re.compile("""\s*uniform\s+(?P<type>\w+)\s+"""
+                           """(?P<name>\w+)\s*(\[(?P<size>\d+)\])?\s*;""")
+        for m in re.finditer(regex,self._code):
+            size = -1
+            gtype = Shader._gtypes[m.group('type')]
+            if m.group('size'):
+                size = int(m.group('size'))
+            if size >= 1:
+                for i in range(size):
+                    name = '%s[%d]' % (m.group('name'),i)
+                    uniforms.append((name, gtype))
+            else:
+                uniforms.append((m.group('name'), gtype))
+
+        return uniforms
+    
+    
+    def _create(self):
+        """
+        Create the shader.
+        """
+        self._handle = gl.glCreateShader(self._target)
+    
+    
+    def _delete(self):
+        """
+        Delete the shader.
+        """
+
+        gl.glDeleteShader(self._handle)
+
+    
+    def _update(self):
+        """
+        Compile the shader.
+        """
+
+        # Check if we have source code
+        if not self._code:
+            raise ShaderError('No source code given for shader.')
+        
+        # Set source
+        # Note, some implementations cannot deal with a sequence of chars
+        #gl.glShaderSource(self._handle, self._code)
+        #gl.glShaderSource(self._handle, [self._code])  
+        
+        # More compativle variant (also deals with above chars problem)
+        self._need_enabled = gl.glShaderSource_compat(self._handle, self._code)
+        
+        # Compile the shader
+        # todo: can this raise exception?
+        gl.glCompileShader(self._handle)
+
+        # Check the compile status
+        status = gl.glGetShaderiv(self._handle, gl.GL_COMPILE_STATUS)
+        if not status:
+            errors = gl.glGetShaderInfoLog(self._handle)
+            errormsg = self._get_error(errors, 4)
+            raise ShaderError("Error compiling %r:\n"%self + errormsg)
+
+
+    def _parse_error(self, error):
+        """
+        Parses a single GLSL error and extracts the line number and error
+        description.
+        
+        Parameters
+        ----------
+        error : str
+            An error string as returned byt the compilation process
+        """
+        
+        # Nvidia
+        # 0(7): error C1008: undefined variable "MV"
+        match = re.match( r'(\d+)\((\d+)\)\s*:\s(.*)', error )
+        if match:
+            return int(match.group(2)), match.group(3)
+        
+        # ATI / Intel
+        # ERROR: 0:131: '{' : syntax error parse error
+        match = re.match( r'ERROR:\s(\d+):(\d+):\s(.*)', error )
+        if match:
+            return int(match.group(2)), match.group( 3 )
+    
+        # Nouveau
+        # 0:28(16): error: syntax error, unexpected ')', expecting '('
+        match = re.match( r'(\d+):(\d+)\((\d+)\):\s(.*)', error )
+        if match:
+            return int(match.group(2)), match.group(4)
+        
+        # Other ...
+        return None, error
+
+
+    def _get_error(self, errors, indentation=0):
+        """
+        Get error and show the faulty line + some context
+        
+        Parameters
+        ----------
+        error : str
+            An error string as returned byt the compilation process
+        
+        lineno: int
+            Line where error occurs
+        """
+        
+        # Init
+        if not is_string(errors):
+            errors = errors.decode('utf-8', 'replace')
+        results = []
+        lines = None
+        if self._code:
+            lines = [line.strip() for line in self._code.split('\n')]
+        
+        for error in errors.split('\n'):
+            # Strip; skip empy lines
+            error = error.strip()
+            if not error:
+                continue
+            # Separate line number from description (if we can)
+            linenr, error = self._parse_error(error)
+            if None in (linenr, lines):
+                results.append('%s' % error)
+            else:
+                results.append('on line %i: %s' % (linenr, error))
+                if linenr>0 and linenr < len(lines):
+                    results.append('  %s' % lines[linenr-1])
+        
+        # Add indentation and return
+        results = [' '*indentation + r for r in results]
+        return '\n'.join(results)
+
+
+# ------------------------------------------------------ class VertexShader ---
+class VertexShader(Shader):
+    """ Vertex shader class. Inherits :class:`shader.Shader`.
+    
+    Parameters
+    ----------
+    code : str
+        The GLSL source code, or a filename that contains the code.
+   
+    """
+
+    def __init__(self, code=None):
+        """
+        Create the shader.
+        """
+        
+        Shader.__init__(self, gl.GL_VERTEX_SHADER, code)
+
+
+
+# ---------------------------------------------------- class FragmentShader ---
+class FragmentShader(Shader):
+    """ Fragment shader class. Inherits :class:`shader.Shader`.
+    
+    Parameters
+    ----------
+    code : str
+        The GLSL source code, or a filename that contains the code.
+    
+    """
+
+    def __init__(self, code=None):
+        """
+        Create the shader.
+        """
+        
+        Shader.__init__(self, gl.GL_FRAGMENT_SHADER, code)
diff --git a/vispy/gloo/tests/test_buffer.py b/vispy/gloo/tests/test_buffer.py
new file mode 100644
index 0000000..f940c7e
--- /dev/null
+++ b/vispy/gloo/tests/test_buffer.py
@@ -0,0 +1,410 @@
+# -----------------------------------------------------------------------------
+# VisPy - Copyright (c) 2013, Vispy Development Team
+# All rights reserved.
+# -----------------------------------------------------------------------------
+import unittest
+import numpy as np
+from vispy.gloo import gl
+
+from vispy.gloo.buffer import Buffer
+from vispy.gloo.buffer import VertexBuffer, ClientVertexBuffer
+from vispy.gloo.buffer import ElementBuffer, ClientElementBuffer
+
+
+
+# -----------------------------------------------------------------------------
+class BufferTest(unittest.TestCase):
+
+    def test_init(self):
+        buffer = Buffer(target=gl.GL_ARRAY_BUFFER)
+        assert buffer._handle      == 0
+        assert buffer._need_update == False
+        assert buffer._valid       == False
+        assert buffer._nbytes      == 0
+        assert buffer._usage       == gl.GL_DYNAMIC_DRAW
+
+    
+    def test_pending_data(self):
+        data = np.zeros(100, np.float32)
+
+        buffer = Buffer(target=gl.GL_ARRAY_BUFFER)
+        self.assertEqual(len(buffer._pending_data), 0)
+        
+        buffer = Buffer(data=data, target=gl.GL_ARRAY_BUFFER)
+        self.assertEqual(len(buffer._pending_data), 1)
+
+        buffer.set_data(data)
+        self.assertEqual(len(buffer._pending_data), 1)
+
+        buffer.set_subdata(0, data[:50])
+        self.assertEqual(len(buffer._pending_data), 2)
+
+        buffer.set_data(data)
+        self.assertEqual(len(buffer._pending_data), 1)
+    
+    
+    def test_setting_size(self):
+        data = np.zeros(100, np.float32)
+        buffer = Buffer(target=gl.GL_ARRAY_BUFFER)
+        
+        buffer.set_data(data)
+        self.assertEqual(buffer.nbytes, data.nbytes)
+        
+        buffer.set_data( np.zeros(200, np.float32))
+        self.assertEqual(buffer.nbytes, 200*4)
+        
+        buffer.set_nbytes(10)
+        self.assertEqual(buffer.nbytes, 10)
+        
+        buffer.set_nbytes(20)
+        self.assertEqual(buffer.nbytes, 20)
+    
+    
+    def test_setting_subdata(self):
+        
+        data = np.zeros(100, np.float32)
+        buffer = Buffer(target=gl.GL_ARRAY_BUFFER)
+        
+        # Set subdata when no data is set
+        with self.assertRaises(RuntimeError):
+            buffer.set_subdata(0, data)
+        
+        # Set nbytes and try again
+        buffer.set_nbytes(data.nbytes)
+        buffer.set_subdata(0, data)
+        
+        # Subpart
+        buffer.set_subdata(0, data[:50])
+        buffer.set_subdata(50, data[50:])
+        
+        # Too big
+        with self.assertRaises(ValueError):
+            buffer.set_subdata(1, data)
+        
+        # Weird
+        with self.assertRaises(ValueError):
+            buffer.set_subdata(-1, data)
+        
+        # Weirder
+        with self.assertRaises(ValueError):
+            buffer.set_subdata(1000000, data)
+    
+        
+    def test_wrong_data(self):
+        buffer = Buffer(target=gl.GL_ARRAY_BUFFER)
+        
+        # String
+        with self.assertRaises(ValueError):
+            buffer.set_data('foo')
+        
+        # Bytes
+        some_bytes = 'foo'.encode('utf-8')
+        with self.assertRaises(ValueError):
+            buffer.set_data(some_bytes)
+        
+        # Now with subdata
+        data = np.zeros(100, np.float32)
+        buffer.set_data(data)
+        
+        
+        # String
+        with self.assertRaises(ValueError):
+            buffer.set_subdata(0, 'foo')
+        with self.assertRaises(ValueError):
+            buffer.set_subdata('foo', data)
+        
+        # Bytes
+        some_bytes = 'foo'.encode('utf-8')
+        with self.assertRaises(ValueError):
+            buffer.set_subdata(0, some_bytes)
+        with self.assertRaises(ValueError):
+            buffer.set_subdata(some_bytes, data)
+
+
+
+# -----------------------------------------------------------------------------
+class VertexBufferTest(unittest.TestCase):
+
+    def test_init(self):
+        data = np.zeros(100, np.float32)
+        buffer = VertexBuffer(data=data)
+        assert buffer.count == 100
+        assert buffer.vsize == 1
+        assert buffer.dtype == np.float32
+    
+    
+    def test_init_with_data(self):
+        
+        for dtype in (np.float32, np.uint8, np.int16):
+        
+            data = np.zeros(100, dtype)
+            buffer = VertexBuffer(data)
+            assert buffer.count == 100
+            assert buffer.vsize == 1
+            assert buffer.dtype == dtype
+            
+            data = np.zeros((100, 1), dtype)
+            buffer = VertexBuffer(data)
+            assert buffer.count == 100
+            assert buffer.vsize == 1
+            assert buffer.dtype == dtype
+    
+            data = np.zeros((100,4), dtype)
+            buffer = VertexBuffer(data)
+            assert buffer.count == 100
+            assert buffer.vsize == 4
+            assert buffer.dtype == dtype
+    
+    
+    def test_init_with_structured_data(self):
+        
+        # Singular 1
+        data = np.zeros(100, [('a', np.float32, 1)])
+        buffer = VertexBuffer(data)
+        assert buffer.count == 100
+        assert buffer.vsize == 1
+        assert buffer.dtype == np.float32
+        
+        # Singular 2
+        data = np.zeros(100, [('a', np.float32, 4)])
+        buffer = VertexBuffer(data)
+        assert buffer.count == 100
+        assert buffer.vsize == 4
+        assert buffer.dtype == np.float32
+        
+        
+        # Multple
+        data = np.zeros(100, [ ('a', np.float32, 1),
+                               ('b', np.uint8, 2),
+                               ('c', np.int16, 3) ] )
+        buffer = VertexBuffer(data)
+        
+        assert buffer.vsize == 1
+        assert buffer.dtype == data.dtype
+        
+        assert buffer['a'].vsize == 1
+        assert buffer['a'].dtype == np.float32
+
+        assert buffer['b'].vsize == 2
+        assert buffer['b'].dtype == np.uint8
+
+        assert buffer['c'].vsize == 3
+        assert buffer['c'].dtype == np.int16
+    
+    
+    def test_init_with_dtype(self):
+        
+        # Single element, this is simply unraveled
+        dtype = np.dtype([('a',np.float32,4)])
+        buffer = VertexBuffer(dtype)
+        assert buffer.count == 0
+        assert buffer.vsize == 4
+        assert buffer.dtype == np.float32
+        
+        # Short notation specific to VertexBuffer
+        buffer = VertexBuffer(('a',np.float32,4))
+        assert buffer.vsize == 4
+        assert buffer.dtype == np.float32
+        
+        # Plain dtype descriptor
+        buffer = VertexBuffer(np.float32)
+        assert buffer.vsize == 1
+        assert buffer.dtype == np.float32
+        
+        # String dtype descirptor
+        buffer = VertexBuffer('float32')
+        assert buffer.vsize == 1
+        assert buffer.dtype == np.float32
+        
+        # Multiple elements
+        dtype = dtype=[('a',np.float32,4), ('b',np.uint8,2)]
+        buffer = VertexBuffer(dtype)
+        assert buffer.count == 0
+        assert buffer.vsize == 1
+        assert buffer.dtype == np.dtype(dtype)
+        #
+        subbuffer = buffer['a']
+        assert subbuffer.count == 0
+        assert subbuffer.vsize == 4
+        assert subbuffer.dtype == np.float32
+        #
+        subbuffer = buffer['b']
+        assert subbuffer.count == 0
+        assert subbuffer.vsize == 2
+        assert subbuffer.dtype == np.uint8
+    
+    
+    def test_resize(self):
+        
+        # Resize allowed with set_data (and offset=0)
+        V = VertexBuffer(np.float32)
+        V.set_data(np.ones(200, np.float32))
+        assert V.count == 200
+        
+        V.set_data(np.ones(300, np.float32))
+        assert V.count == 300
+        
+        # Resize not allowed with set_subdata
+        with self.assertRaises(ValueError):
+            V.set_subdata(0, np.ones(400, np.float32))
+    
+
+    def test_offset(self):
+        dtype = np.dtype( [ ('position', np.float32, 3),
+                            ('texcoord', np.float32, 2),
+                            ('color',    np.float32, 4) ] )
+        data = np.zeros(100, dtype=dtype)
+        buffer = VertexBuffer(data)
+        
+        assert buffer['position'].offset == 0
+        assert buffer['texcoord'].offset == 3*np.dtype(np.float32).itemsize
+        assert buffer['color'].offset    == (3+2)*np.dtype(np.float32).itemsize
+    
+
+    def test_stride(self):
+        dtype = np.dtype( [ ('position', np.float32, 3),
+                            ('texcoord', np.float32, 2),
+                            ('color',    np.float32, 4) ] )
+        data = np.zeros(100, dtype=dtype)
+        buffer = VertexBuffer(data)
+
+        assert buffer['position'].stride == 9*np.dtype(np.float32).itemsize
+        assert buffer['texcoord'].stride == 9*np.dtype(np.float32).itemsize
+        assert buffer['color'].stride    == 9*np.dtype(np.float32).itemsize
+
+
+        buffer = VertexBuffer(data['position'])
+        assert buffer.offset == 0
+        assert buffer.stride == 3*np.dtype(np.float32).itemsize
+    
+
+    def test_setitem(self):
+        dtype = np.dtype( [ ('position', np.float32, 3),
+                            ('texcoord', np.float32, 2),
+                            ('color',    np.float32, 4) ] )
+        data = np.zeros(100, dtype=dtype)
+        buffer = VertexBuffer(data)
+
+        with self.assertRaises(ValueError):
+            buffer['color'] = data['color']
+        
+        buffer[...] = data
+        assert len(buffer._pending_data) == 2
+
+        buffer[10:20] = data[10:20]
+        assert len(buffer._pending_data) == 3
+    
+        # Discart all pending data
+        buffer.set_data(data)
+        assert len(buffer._pending_data) == 1
+        
+        with self.assertRaises(ValueError):
+            buffer[10:20] = data[10:19]
+
+        with self.assertRaises(ValueError):
+            buffer[10:20] = data[10:21]
+    
+    
+    def test_set_data_on_view(self):
+        
+        dtype = np.dtype( [ ('a', np.float32, 3),
+                            ('b', np.float32, 2),
+                            ('c',    np.float32, 4) ] )
+        data = np.zeros(100, dtype=dtype)
+        buffer = VertexBuffer(data)
+        
+        with self.assertRaises(RuntimeError):
+            buffer['a'].set_count(100)
+        
+        with self.assertRaises(RuntimeError):
+            buffer['a'].set_data(data['a'])
+        
+        with self.assertRaises(RuntimeError):
+            buffer['a'].set_subdata(data['a'])
+
+    
+    def test_client_buffer(self):
+        data = np.zeros((100, 3), dtype=np.float32)
+        buffer = ClientVertexBuffer(data)
+        self.assertIs(buffer.data, data)
+        
+        with self.assertRaises(RuntimeError):
+            buffer.set_data(data)
+        with self.assertRaises(RuntimeError):
+            buffer.set_subdata(data)
+    
+    
+    def test_typechecking(self):
+        
+        # VertexBuffer supports these
+        for dtype in (np.uint8, np.int8, np.uint16, np.int16,
+                      np.float32, np.float16):
+            buffer = VertexBuffer(dtype)
+        
+        # VertexBuffer does *not* support these
+        float128 = getattr(np, 'float128', np.float64)  # may not exist
+        for dtype in (np.uint32, np.int32, np.float64, float128):
+            with self.assertRaises(TypeError):
+                buffer = VertexBuffer(dtype)
+
+
+# -----------------------------------------------------------------------------
+class ElementBufferTest(unittest.TestCase):
+
+    def test_init(self):
+        data = np.zeros(100, np.uint32)
+        buffer = ElementBuffer(data=data)
+        assert buffer.count == 100
+        assert buffer.dtype == np.uint32
+
+    
+    def test_shape_agnostic(self):
+        
+        data = np.zeros(100, np.uint32)
+        buffer = ElementBuffer(data=data)
+        assert buffer.count == data.size
+        assert buffer.vsize == 1
+        
+        data.shape = 50, 2
+        buffer = ElementBuffer(data=data)
+        assert buffer.count == data.size
+        assert buffer.vsize == 1
+        
+        data.shape = 10, 5, 2
+        buffer = ElementBuffer(data=data)
+        assert buffer.count == data.size
+        assert buffer.vsize == 1
+    
+    
+    def test_typechecking(self):
+        
+        # Elementbuffer does support for structured arrays
+        data = np.zeros(100, [('index', np.uint32,1)])
+        with self.assertRaises(ValueError):
+            buffer = ElementBuffer(data=data)
+        
+        # ElementBuffer supports these
+        for dtype in (np.uint8, np.uint16, np.uint32):
+            buffer = ElementBuffer(dtype)
+        
+        # ElementBuffer does *not* support these
+        for dtype in (np.int8, np.int16, np.int32, np.float32, np.float64):
+            with self.assertRaises(TypeError):
+                buffer = ElementBuffer(dtype)
+        
+    
+    
+    def test_client_buffer(self):
+        data = np.zeros((100, 3), dtype=np.uint32)
+        buffer = ClientElementBuffer(data)
+        self.assertIs(buffer.data, data)
+        
+        with self.assertRaises(RuntimeError):
+            buffer.set_data(data)
+        with self.assertRaises(RuntimeError):
+            buffer.set_subdata(data)
+
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/vispy/gloo/tests/test_framebuffer.py b/vispy/gloo/tests/test_framebuffer.py
new file mode 100644
index 0000000..9f9c0ce
--- /dev/null
+++ b/vispy/gloo/tests/test_framebuffer.py
@@ -0,0 +1,206 @@
+# -----------------------------------------------------------------------------
+# VisPy - Copyright (c) 2013, Vispy Development Team
+# All rights reserved.
+# -----------------------------------------------------------------------------
+import unittest
+import numpy as np
+from vispy.gloo import gl
+
+from vispy.gloo.framebuffer import RenderBuffer, FrameBuffer
+from vispy.gloo.texture import Texture2D
+
+
+class RenderBufferTest(unittest.TestCase):
+    
+    def test_init(self):
+        # Init without args
+        buffer = RenderBuffer()
+        self.assertEqual(buffer._handle, 0)
+        self.assertEqual(buffer._need_update, False)
+        self.assertEqual(buffer._valid, False)
+    
+        # Init with shape
+        buffer = RenderBuffer((100, 100))
+        self.assertEqual(buffer._need_update, True)
+    
+    
+    def test_setting_shape(self):
+        buffer = RenderBuffer()
+        
+        # No format
+        buffer.set_shape((100,100))
+        self.assertEqual(buffer._shape, (100,100))
+        self.assertEqual(buffer._format, None)
+        #
+        buffer.set_shape((100,100, 1))
+        self.assertEqual(buffer._shape, (100,100))
+        self.assertEqual(buffer._format, None)
+        #
+        buffer.set_shape((100,100, 3))
+        self.assertEqual(buffer._shape, (100,100))
+        self.assertEqual(buffer._format, None)
+        
+        # With invalid shape
+        with self.assertRaises(ValueError):
+            buffer.set_shape(None)
+        with self.assertRaises(ValueError):
+            buffer.set_shape(4)
+        with self.assertRaises(ValueError):
+            buffer.set_shape('test')
+        with self.assertRaises(ValueError):
+            buffer.set_shape((100,))
+        with self.assertRaises(ValueError):
+            buffer.set_shape((100,100,9))
+        with self.assertRaises(ValueError):
+            buffer.set_shape((100,100,3, 3))
+        
+        # With valid format
+        buffer.set_shape((100,100), gl.ext.GL_RGBA8)
+        self.assertEqual(buffer._shape, (100,100))
+        self.assertEqual(buffer._format, gl.ext.GL_RGBA8)
+        #
+        buffer.set_shape((100,100), gl.GL_DEPTH_COMPONENT16)
+        self.assertEqual(buffer._shape, (100,100))
+        self.assertEqual(buffer._format, gl.GL_DEPTH_COMPONENT16)
+        
+        # With invalid format
+        with self.assertRaises(ValueError):
+            buffer.set_shape((100,100), gl.GL_LUMINANCE)
+        with self.assertRaises(ValueError):
+            buffer.set_shape((100,100), gl.GL_RGB)
+    
+    
+    def test_resetting_shape(self):
+        buffer = RenderBuffer()
+        
+        # Set same shape
+        buffer.set_shape((100,100))
+        self.assertEqual(buffer._need_update, True)
+        buffer._need_update = False
+        #
+        buffer.set_shape((100,100))
+        self.assertEqual(buffer._need_update, False)
+        #
+        buffer.set_shape((100,100, 1))
+        self.assertEqual(buffer._need_update, False)
+        buffer.set_shape((100,100, 3))
+        self.assertEqual(buffer._need_update, False)
+        
+        # Set different shape
+        buffer.set_shape((100,101))
+        self.assertEqual(buffer._need_update, True)
+
+
+
+class FrameBufferTest(unittest.TestCase):
+    
+    def test_init(self):
+        # Init without args
+        fbo = FrameBuffer()
+        self.assertEqual(fbo._handle, 0)
+        self.assertEqual(fbo._need_update, False)
+        self.assertEqual(fbo._valid, False)
+        
+        # Init with args
+        fbo = FrameBuffer(RenderBuffer())
+        self.assertEqual(fbo._need_update, True)
+    
+    
+    def test_attaching(self):
+        fbo = FrameBuffer()
+        buffer = RenderBuffer()
+        texture = Texture2D()
+        
+        # Attaching color
+        fbo = FrameBuffer()
+        fbo.attach_color(buffer)
+        self.assertEqual(fbo._attachment_color, buffer)
+        self.assertEqual(len(fbo._pending_attachments), 1)
+        #
+        fbo.attach_color(texture)
+        self.assertEqual(fbo._attachment_color, texture)
+        self.assertEqual(len(fbo._pending_attachments), 2)
+        #
+        fbo.attach_color(None)
+        self.assertEqual(fbo._attachment_color, None)
+        self.assertEqual(len(fbo._pending_attachments), 3)
+        #
+        with self.assertRaises(ValueError):
+            fbo.attach_color("test")
+        with self.assertRaises(ValueError):
+            fbo.attach_color(3)
+        
+        # Attaching depth
+        fbo = FrameBuffer()
+        fbo.attach_depth(buffer)
+        self.assertEqual(fbo._attachment_depth, buffer)
+        self.assertEqual(len(fbo._pending_attachments), 1)
+        #
+        fbo.attach_depth(texture)
+        self.assertEqual(fbo._attachment_depth, texture)
+        self.assertEqual(len(fbo._pending_attachments), 2)
+        #
+        fbo.attach_depth(None)
+        self.assertEqual(fbo._attachment_depth, None)
+        self.assertEqual(len(fbo._pending_attachments), 3)
+        #
+        with self.assertRaises(ValueError):
+            fbo.attach_depth("test")
+        with self.assertRaises(ValueError):
+            fbo.attach_depth(3)
+        
+        # Attach stencil
+        fbo = FrameBuffer()
+        fbo.attach_stencil(buffer)
+        self.assertEqual(fbo._attachment_stencil, buffer)
+        self.assertEqual(len(fbo._pending_attachments), 1)
+        #
+        with self.assertRaises(ValueError):
+            fbo.attach_stencil(texture)
+        #
+        fbo.attach_stencil(None)
+        self.assertEqual(fbo._attachment_stencil, None)
+        self.assertEqual(len(fbo._pending_attachments), 2)
+        #
+        with self.assertRaises(ValueError):
+            fbo.attach_stencil("test")
+        with self.assertRaises(ValueError):
+            fbo.attach_stencil(3)
+    
+    
+    def test_level(self):
+        fbo = FrameBuffer()
+        buffer = RenderBuffer()
+        texture = Texture2D()
+        
+        # Valid level
+        fbo.attach_color(texture, 1)
+        self.assertEqual(fbo._pending_attachments[-1][2], 1)
+        fbo.attach_color(texture, 2)
+        self.assertEqual(fbo._pending_attachments[-1][2], 2)
+        
+        # Invalid level
+        with self.assertRaises(ValueError):
+            fbo.attach_color(texture, 1.1)
+        with self.assertRaises(ValueError):
+            fbo.attach_color(texture, "test")
+    
+    
+    def test_auto_format(self):
+        fbo = FrameBuffer()
+        
+        buffer = RenderBuffer((100,100))
+        fbo.attach_color(buffer)
+        self.assertEqual(buffer._format, gl.GL_RGB565)
+        
+        buffer = RenderBuffer((100,100))
+        fbo.attach_depth(buffer)
+        self.assertEqual(buffer._format, gl.GL_DEPTH_COMPONENT16)
+        
+        buffer = RenderBuffer((100,100))
+        fbo.attach_stencil(buffer)
+        self.assertEqual(buffer._format, gl.GL_STENCIL_INDEX8)
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/vispy/gloo/tests/test_globject.py b/vispy/gloo/tests/test_globject.py
new file mode 100644
index 0000000..2297edc
--- /dev/null
+++ b/vispy/gloo/tests/test_globject.py
@@ -0,0 +1,217 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Vispy - Copyright (c) 2013, Vispy Development Team. All rights reserved.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+# -----------------------------------------------------------------------------
+import unittest
+
+import numpy as np
+
+from vispy.gloo import gl
+from vispy import gloo
+
+
+def _dummy(*args, **kwargs):
+    """ Dummy method to replace all GL calls with.
+    Return 1 for glGenTextures etc.
+    """
+    return 1
+
+def _dummy_glGetProgramiv(handle, mode):
+    if mode in (gl.GL_ACTIVE_ATTRIBUTES, gl.GL_ACTIVE_UNIFORMS):
+        return 0
+    else:
+        return 1
+
+def _dummy_glGetAttachedShaders(*args, **kwargs):
+    return ()
+
+
+
+class GLObjectTest(unittest.TestCase):
+    
+    def setUp(self):
+        #print('Dummyfying gl namespace.')
+        for key in dir(gl):
+            if key.startswith('gl'):
+                setattr(gl, key, _dummy)
+        #
+        for key in dir(gl.ext):
+            if key.startswith('gl'):
+                setattr(gl.ext, key, _dummy)
+        # 
+        gl.glGetProgramiv = _dummy_glGetProgramiv
+        gl.glGetAttachedShaders = _dummy_glGetAttachedShaders
+    
+    
+    def tearDown(self):
+        gl.use('desktop')
+    
+    
+    def _fix_ob(self, ob):
+        """ Modify object so we can keep track of its calls to _create,
+        _update, etc.
+        """
+        ob._actions = []
+        
+        def _create():
+            ob._actions.append('create')
+            return ob.__class__._create(ob)
+        def _activate():
+            ob._actions.append('activate')
+            return ob.__class__._activate(ob)
+        def _deactivate():
+            ob._actions.append('deactivate')
+            return ob.__class__._deactivate(ob)
+        def _update():
+            ob._actions.append('update')
+            return ob.__class__._update(ob)
+        def _delete():
+            ob._actions.append('delete')
+            return ob.__class__._delete(ob)
+        
+        
+        ob._create = _create
+        ob._activate = _activate
+        ob._deactivate = _deactivate
+        ob._update = _update
+        ob._delete = _delete
+    
+    
+    def test_init(self):
+        obj = gloo.GLObject()
+        assert obj._handle  == 0
+        assert obj._need_update == False
+        assert obj._valid  == False
+        # assert obj._id   == 1
+    
+    
+    def test_buffer(self):
+        
+        # Some data that we need
+        data = np.zeros(100, np.uint16)
+        im2 = np.zeros((50,50), np.uint16)
+        im3 = np.zeros((20,20, 20), np.uint16)
+        shaders = gloo.VertexShader("x"), gloo.FragmentShader("x")
+         
+        items = [
+            # Buffers
+            (gloo.buffer.Buffer(target=gl.GL_ARRAY_BUFFER), 'set_data', data),
+            (gloo.buffer.VertexBuffer(np.uint16), 'set_data', data),
+            (gloo.buffer.ElementBuffer(np.uint16), 'set_data', data),
+            # Textures
+            (gloo.Texture2D(), 'set_data', im2),
+            (gloo.Texture3D(), 'set_data', im3),
+            # FBO stuff
+            (gloo.RenderBuffer(), 'set_shape', (1,1)),
+            (gloo.FrameBuffer(), 'attach_color',  gloo.RenderBuffer((1,1)) ),
+            # Shader stuff
+            (gloo.VertexShader(), 'set_code', "x"),
+            (gloo.FragmentShader(), 'set_code', "x"),
+            (gloo.Program(), 'attach', shaders),
+            ]
+        
+        for ob, funcname, value in items:
+            self._fix_ob(ob)
+            #print('Testing GLObject compliance for %s' % ob.__class__.__name__)
+            
+            # Initially a clear state 
+            self.assertEqual(ob._need_update, False)
+            
+            # Set value, now we should have a "dirty" state
+            x = ob
+            for part in funcname.split('.'):
+                x = getattr(x, part)
+            x(value)
+            self.assertEqual(ob._need_update, True)
+            
+            # Activate the object
+            ob.activate()
+            # Now we should be activated
+            self.assertEqual(len(ob._actions), 3)
+            self.assertEqual(ob._actions[0], 'create')
+            self.assertEqual(ob._actions.count('update'), 1)
+            self.assertEqual(ob._actions.count('activate'), 1)
+            
+            # Deactivate
+            ob.deactivate()
+            # Now we should be deactivated
+            self.assertEqual(len(ob._actions), 4)
+            self.assertEqual(ob._actions[-1], 'deactivate')
+            
+            # Activate some more
+            for i in range(10):
+                ob.activate()
+            
+            # Create and update should not have been called
+            self.assertEqual(ob._actions.count('create'), 1)
+            self.assertEqual(ob._actions.count('update'), 1)
+    
+    
+    def test_buffer_view(self):
+        """ Same as above, but special case for VertexBufferView that
+        needs special attention.
+        """
+        
+        # Some "data"
+        data = np.zeros(100, np.uint16)
+        baseBuffer = gloo.buffer.VertexBuffer(np.uint16)
+        
+        # Create object
+        ob = gloo.buffer.VertexBufferView(np.dtype(np.uint16), baseBuffer, 0)
+        funcname = 'base.set_data'
+        value = data
+        
+        self._fix_ob(ob)
+        self._fix_ob(baseBuffer)
+        
+        # Initially a clear state 
+        self.assertEqual(ob._need_update, False)
+        
+        # Set value, now we should have a "dirty" state
+        x = ob
+        for part in funcname.split('.'):
+            x = getattr(x, part)
+        x(value)
+        self.assertEqual(ob.base._need_update, True)
+       
+        # Activate the object
+        ob.activate()
+        # Now we should be activated
+        self.assertEqual(len(ob._actions), 2)
+        self.assertEqual(ob._actions[0], 'create')
+        self.assertEqual(ob._actions.count('activate'), 1)
+        # Now we should be activated
+        self.assertEqual(len(ob.base._actions), 3)
+        self.assertEqual(ob.base._actions[0], 'create')
+        self.assertEqual(ob.base._actions.count('update'), 1)
+        self.assertEqual(ob.base._actions.count('activate'), 1)
+        
+        # Deactivate
+        ob.deactivate()
+        # Now we should be deactivated
+        self.assertEqual(len(ob.base._actions), 4)
+        self.assertEqual(ob.base._actions[-1], 'deactivate')
+        
+        # Activate some more
+        for i in range(10):
+            ob.activate()
+        
+        # Create and update should not have been called
+        self.assertEqual(ob._actions.count('create'), 1)
+        self.assertEqual(ob._actions.count('update'), 0)
+        
+        # Create and update should not have been called
+        self.assertEqual(ob.base._actions.count('create'), 1)
+        self.assertEqual(ob.base._actions.count('update'), 1)
+    
+    
+    def test_foo(self):
+        pass
+    
+    
+ 
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/vispy/gloo/tests/test_program.py b/vispy/gloo/tests/test_program.py
new file mode 100644
index 0000000..c9657bd
--- /dev/null
+++ b/vispy/gloo/tests/test_program.py
@@ -0,0 +1,155 @@
+# -----------------------------------------------------------------------------
+# VisPy - Copyright (c) 2013, Vispy Development Team
+# All rights reserved.
+# -----------------------------------------------------------------------------
+import unittest
+import numpy as np
+
+from vispy.gloo import gl
+from vispy.gloo.program import Program
+from vispy.gloo.shader import VertexShader
+from vispy.gloo.shader import FragmentShader
+from vispy.gloo.buffer import VertexBuffer
+from vispy.gloo.buffer import ClientVertexBuffer
+
+
+
+
+# -----------------------------------------------------------------------------
+class ProgramTest(unittest.TestCase):
+
+    def test_init(self):
+        program = Program()
+        assert program._handle == 0
+        assert program._need_update == False
+        assert program._valid  == False
+        assert program.shaders == []
+
+    def test_delete_no_context(self):
+        program = Program()
+        program.delete()
+
+    def test_init_from_string(self):
+        program = Program("A","B")
+        assert len(program.shaders) == 2
+        assert program.shaders[0].code == "A"
+        assert program.shaders[1].code == "B"
+
+    def test_init_from_shader(self):
+        program = Program(VertexShader("A"),FragmentShader("B"))
+        assert len(program.shaders) == 2
+        assert program.shaders[0].code == "A"
+        assert program.shaders[1].code == "B"
+
+    def test_unique_shader(self):
+        vert = VertexShader("A")
+        frag = FragmentShader("B")
+        program = Program([vert,vert],[frag,frag,frag])
+        assert len(program.shaders) == 2
+
+    def test_uniform(self):
+        vert = VertexShader("uniform float A;")
+        frag = FragmentShader("uniform float A; uniform vec4 B;")
+        program = Program(vert,frag)
+        assert program.uniforms[0].name == 'A'
+        assert program.uniforms[0].gtype == gl.GL_FLOAT
+        assert program.uniforms[1].name == 'B'
+        assert program.uniforms[1].gtype == gl.GL_FLOAT_VEC4
+
+    def test_attributes(self):
+        vert = VertexShader("attribute float A;")
+        frag = FragmentShader("")
+        program = Program(vert,frag)
+        assert program.attributes[0].name == 'A'
+        assert program.attributes[0].gtype == gl.GL_FLOAT
+    
+    def test_attach(self):
+        vert = VertexShader("A")
+        frag = FragmentShader("B")
+        program = Program(vert)
+        program.attach(frag)
+        assert len(program.shaders) == 2
+        assert program.shaders[0].code == "A"
+        assert program.shaders[1].code == "B"
+
+    def test_detach(self):
+        vert = VertexShader("A")
+        frag = FragmentShader("B")
+        program = Program(vert, frag)
+        program.detach(frag)
+        assert len(program.shaders) == 1
+        assert program.shaders[0].code == "A"
+
+    def test_failed_build(self):
+        vert = VertexShader("A")
+        frag = FragmentShader("B")
+
+        program = Program(vert = vert)
+        with self.assertRaises(RuntimeError):
+            program.activate()
+
+        program = Program(frag = frag)
+        with self.assertRaises(RuntimeError):
+            program.activate()
+
+    def test_setitem(self):
+        vert = VertexShader("")
+        frag = FragmentShader("")
+
+        program = Program(vert,frag)
+        with self.assertRaises(NameError):
+            program["A"] = 1
+
+    def test_set_uniform_vec4(self):
+        vert = VertexShader("uniform vec4 color;")
+        frag = FragmentShader("")
+
+        program = Program(vert,frag)
+        program["color"] = 1,1,1,1
+
+    def test_set_attribute_float(self):
+
+        vert = VertexShader("attribute float f;")
+        frag = FragmentShader("")
+
+        program = Program(vert,frag)
+        program["f"] = VertexBuffer(np.zeros(100, dtype=np.float32))
+        assert program._attributes["f"].count == 100
+
+        program = Program(vert,frag)
+        program["f"] = ClientVertexBuffer(np.zeros((100,1,1), dtype=np.float32))
+        assert program._attributes["f"].count == 100
+
+        program = Program(vert,frag)
+        with self.assertRaises(ValueError):
+            program["f"] = np.zeros((100,1,1), dtype=np.float32)
+
+
+
+    def test_set_attribute_vec4(self):
+        vert = VertexShader("attribute vec4 color;")
+        frag = FragmentShader("")
+
+        program = Program(vert,frag)
+        with self.assertRaises(ValueError):
+            program["color"] = np.array(3, dtype=np.float32)
+
+        program = Program(vert,frag)
+        with self.assertRaises(ValueError):
+            program["color"] = np.array((100,5), dtype=np.float32)
+
+        program = Program(vert,frag)
+        program["color"] = ClientVertexBuffer(np.zeros((100,4), dtype=np.float32))
+        assert program._attributes["color"].count == 100
+
+        program = Program(vert,frag)
+        program["color"] = ClientVertexBuffer(np.zeros((100,1,4), dtype=np.float32))
+        assert program._attributes["color"].count == 100
+
+        program = Program(vert,frag)
+        program["color"] = ClientVertexBuffer(np.zeros(100, dtype=(np.float32,4)))
+        assert program._attributes["color"].count == 100
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/vispy/gloo/tests/test_shader.py b/vispy/gloo/tests/test_shader.py
new file mode 100644
index 0000000..a0b017c
--- /dev/null
+++ b/vispy/gloo/tests/test_shader.py
@@ -0,0 +1,89 @@
+# -----------------------------------------------------------------------------
+# VisPy - Copyright (c) 2013, Vispy Development Team
+# All rights reserved.
+# -----------------------------------------------------------------------------
+import unittest
+from vispy.gloo import gl
+
+from vispy.gloo.shader import ShaderError
+from vispy.gloo.shader import VertexShader
+from vispy.gloo.shader import FragmentShader
+
+
+
+# -----------------------------------------------------------------------------
+class VertexShaderTest(unittest.TestCase):
+
+    def test_init(self):
+        shader = VertexShader()
+        assert shader._target == gl.GL_VERTEX_SHADER
+
+# -----------------------------------------------------------------------------
+class FragmentShaderTest(unittest.TestCase):
+
+    def test_init(self):
+        shader = FragmentShader()
+        assert shader._target == gl.GL_FRAGMENT_SHADER
+
+
+# -----------------------------------------------------------------------------
+class ShaderTest(unittest.TestCase):
+
+    def test_init(self):
+        shader = VertexShader()
+        assert shader._handle == 0
+        assert shader._need_update == False
+        assert shader._valid == False
+        assert shader.code    == None
+        assert shader.source  == None
+
+    def test_sourcecode(self):
+        code = "/* Code */"
+        shader = VertexShader(code)
+        assert shader.code == code
+        assert shader.source == "<string>"
+
+    def test_setcode(self):
+        shader = VertexShader()
+        shader.set_code("")
+        assert shader._need_update == True
+
+    def test_empty_build(self):
+        shader = VertexShader()
+        assert shader._code is None
+        # This needs a context, because _init() will be called first
+        #with self.assertRaises(ShaderError):
+        #    shader.activate()
+
+    def test_delete_no_context(self):
+        shader = VertexShader()
+        shader.delete()
+
+    def test_uniform_float(self):
+        shader = VertexShader("uniform float color;")
+        uniforms = shader._get_uniforms()
+        assert uniforms == [ ("color", gl.GL_FLOAT) ]
+ 
+    def test_uniform_vec4(self):
+        shader = VertexShader("uniform vec4 color;")
+        uniforms = shader._get_uniforms()
+        assert uniforms == [ ("color", gl.GL_FLOAT_VEC4) ]
+ 
+    def test_uniform_array(self):
+        shader = VertexShader("uniform float color[2];")
+        uniforms=shader._get_uniforms()
+        assert uniforms == [ ("color[0]", gl.GL_FLOAT),
+                             ("color[1]", gl.GL_FLOAT)  ]
+ 
+    def test_attribute_float(self):
+        shader = VertexShader("attribute float color;")
+        attributes = shader._get_attributes()
+        assert attributes == [ ("color", gl.GL_FLOAT) ]
+ 
+    def test_attribute_vec4(self):
+        shader = VertexShader("attribute vec4 color;")
+        attributes = shader._get_attributes()
+        assert attributes == [ ("color", gl.GL_FLOAT_VEC4) ]
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/vispy/gloo/tests/test_texture.py b/vispy/gloo/tests/test_texture.py
new file mode 100644
index 0000000..f6eaf9f
--- /dev/null
+++ b/vispy/gloo/tests/test_texture.py
@@ -0,0 +1,260 @@
+# -----------------------------------------------------------------------------
+# VisPy - Copyright (c) 2013, Vispy Development Team
+# All rights reserved.
+# -----------------------------------------------------------------------------
+import unittest
+import numpy as np
+from vispy.gloo import gl
+from vispy.gloo.texture import Texture, Texture2D, Texture3D
+
+
+
+class TextureBasetests:
+    
+    def test_init(self):
+        
+        data = np.zeros(self._shape0, np.float32)
+        tex = self._klass(data)
+        self.assertEqual(tex._levels[0].format, gl.GL_LUMINANCE)
+        self.assertEqual(tex._levels[0].shape, data.shape)
+    
+    
+    def test_allocating_data(self):
+        
+        tex = self._klass()
+        
+        # Luminance
+        tex.set_shape(self._shape0)
+        self.assertEqual(tex._levels[0].format, gl.GL_LUMINANCE)
+        self.assertEqual(tex._levels[0].shape, self._shape0)
+        
+        # Luminance again, but now an explicit color channel
+        tex.set_shape(self._shape1)
+        self.assertEqual(tex._levels[0].format, gl.GL_LUMINANCE)
+        self.assertEqual(tex._levels[0].shape, self._shape1)
+        
+        # Luminance + alpha
+        tex.set_shape(self._shape2)
+        self.assertEqual(tex._levels[0].format, gl.GL_LUMINANCE_ALPHA)
+        self.assertEqual(tex._levels[0].shape, self._shape2)
+        
+        # RGB
+        tex.set_shape(self._shape3)
+        self.assertEqual(tex._levels[0].format, gl.GL_RGB)
+        self.assertEqual(tex._levels[0].shape, self._shape3)
+        
+        # RGBA
+        tex.set_shape(self._shape4)
+        self.assertEqual(tex._levels[0].format, gl.GL_RGBA)
+        self.assertEqual(tex._levels[0].shape, self._shape4)
+        
+        # Alpha
+        tex.set_shape(self._shape0, format=gl.GL_ALPHA)
+        self.assertEqual(tex._levels[0].format, gl.GL_ALPHA)
+        self.assertEqual(tex._levels[0].shape, self._shape0)
+    
+    
+    def test_setting_data(self):
+        
+        tex = self._klass()
+        
+        # Luminance
+        data = np.zeros(self._shape0, np.float32)
+        tex.set_data(data)
+        self.assertEqual(tex._levels[0].format, gl.GL_LUMINANCE)
+        self.assertEqual(tex._levels[0].shape, data.shape)
+        
+        # Luminance again, but now an explicit color channel
+        data = np.zeros(self._shape1, np.float32)
+        tex.set_data(data)
+        self.assertEqual(tex._levels[0].format, gl.GL_LUMINANCE)
+        self.assertEqual(tex._levels[0].shape, data.shape)
+        
+        # Luminance + alpha
+        data = np.zeros(self._shape2, np.float32)
+        tex.set_data(data)
+        self.assertEqual(tex._levels[0].format, gl.GL_LUMINANCE_ALPHA)
+        self.assertEqual(tex._levels[0].shape, data.shape)
+        
+        # RGB
+        data = np.zeros(self._shape3, np.float32)
+        tex.set_data(data)
+        self.assertEqual(tex._levels[0].format, gl.GL_RGB)
+        self.assertEqual(tex._levels[0].shape, data.shape)
+        
+        # RGBA
+        data = np.zeros(self._shape4, np.float32)
+        tex.set_data(data)
+        self.assertEqual(tex._levels[0].format, gl.GL_RGBA)
+        self.assertEqual(tex._levels[0].shape, data.shape)
+        
+        # Alpha
+        data = np.zeros(self._shape0, np.float32)
+        tex.set_data(data, format=gl.GL_ALPHA)
+        self.assertEqual(tex._levels[0].format, gl.GL_ALPHA)
+        self.assertEqual(tex._levels[0].shape, data.shape)
+    
+    
+    def test_invalid_shape(self):
+        
+        tex = self._klass()
+        
+        data = np.zeros(self._shape5, np.float32)
+        with self.assertRaises(ValueError):
+            tex.set_data(data)
+        
+        # Format and shape mismatch
+        data = np.zeros(self._shape1, np.float32)
+        with self.assertRaises(ValueError):
+            tex.set_data(data, format=gl.GL_RGB)
+        
+        # Format and shape mismatch
+        data = np.zeros(self._shape3, np.float32)
+        with self.assertRaises(ValueError):
+            tex.set_data(data, format=gl.GL_RGBA)
+        
+        # Format and shape mismatch
+        data = np.zeros(self._shape4, np.float32)
+        with self.assertRaises(ValueError):
+            tex.set_data(data, format=gl.GL_RGB)
+        
+        # Format and shape mismatch
+        data = np.zeros(self._shape3, np.float32)
+        with self.assertRaises(ValueError):
+            tex.set_data(data, format=gl.GL_ALPHA)
+    
+    def test_dtype(self):
+        pass  # No use, we convert dtype if necessary
+    
+    
+    def test_subdata(self):
+        
+        tex = self._klass()
+        data = np.zeros(self._shape0, np.float32)
+        offset1 = [0 for i in self._shape0]
+        offset2 = [10 for i in self._shape0]
+        
+        # No pending data now
+        self.assertEqual(len(tex._levels), 0)
+        
+        # No data allocated yet
+        with self.assertRaises(RuntimeError):
+            tex.set_subdata(offset1, data)
+        
+        # Allocate data
+        tex.set_shape(data.shape)
+        
+        # Pending data
+        self.assertEqual(tex._levels[0].need_resize, True)
+        self.assertEqual(len(tex._levels[0].pending_data), 0)
+        
+        # Now it should work
+        tex.set_subdata(offset1, data)
+        self.assertEqual(len(tex._levels[0].pending_data), 1)
+        #
+        tex.set_subdata(offset1, data)
+        self.assertEqual(len(tex._levels[0].pending_data), 2)
+        
+        # Reset using set_shape  (no pending data)
+        tex.set_shape(data.shape)
+        self.assertEqual(len(tex._levels[0].pending_data), 0)
+        
+        # Again
+        tex.set_subdata(offset1, data)
+        tex.set_subdata(offset1, data)
+        self.assertEqual(len(tex._levels[0].pending_data), 2)
+        
+        # Reset using set_data (1 pending data)
+        tex.set_data(data)
+        self.assertEqual(len(tex._levels[0].pending_data), 1)
+
+    
+    def test_subdata_shape_and_format(self):
+        
+        tex = self._klass()
+        data = np.zeros(self._shape0, np.float32)
+        offset1 = [0 for i in self._shape0]
+        offset2 = [9 for i in self._shape0]
+        offset3 = [-1 for i in self._shape0]
+        offset4 = [11 for i in self._shape0]
+        
+        # Allocate data
+        tex.set_shape([i+10 for i in self._shape0])
+        
+        # Some stuff that should work
+        tex.set_subdata(offset1, data)
+        tex.set_subdata(offset2, data)
+        
+        # Some stuff that should not (shape)
+        with self.assertRaises(ValueError):
+            tex.set_subdata(offset3, data)
+        with self.assertRaises(ValueError):
+            tex.set_subdata(offset4, data)
+        
+        # More stuff that should not (format)
+        with self.assertRaises(ValueError):
+            tex.set_subdata(offset1, data, format=gl.GL_RGB)
+        with self.assertRaises(ValueError):
+            tex.set_subdata(offset1, data, format=gl.GL_LUMINANCE_ALPHA)
+        with self.assertRaises(ValueError):
+            tex.set_subdata(offset1, data,format= gl.GL_LUMINANCE_ALPHA)
+        
+        # But this should work
+        tex.set_shape([i+10 for i in self._shape0], format=gl.GL_ALPHA)
+        tex.set_subdata(offset1, data)
+    
+    
+    def test_levels(self):
+        
+        # Create tex
+        tex = self._klass()
+        self.assertEqual(len(tex._levels), 0)
+        
+        # Create level 0
+        tex.set_shape(self._shape3)
+        self.assertEqual(len(tex._levels), 1)
+        self.assertIn(0, tex._levels)
+        
+        # Create level 2
+        tex.set_shape(self._shape3, 2)
+        self.assertEqual(len(tex._levels), 2)
+        self.assertIn(0, tex._levels)
+        self.assertIn(2, tex._levels)
+        
+        # Create level 4
+        tex.set_shape(self._shape3, 4)
+        self.assertEqual(len(tex._levels), 3)
+        self.assertIn(0, tex._levels)
+        self.assertIn(2, tex._levels)
+        self.assertIn(4, tex._levels)
+    
+
+
+class Texture2DTest(TextureBasetests, unittest.TestCase):
+    def setUp(self):
+        self._klass = Texture2D
+        self._shape0 = 100, 100
+        self._shape1 = 100, 100, 1
+        self._shape2 = 100, 100, 2
+        self._shape3 = 100, 100, 3
+        self._shape4 = 100, 100, 4
+        self._shape5 = 100, 100, 5
+
+
+
+class Texture3DTest(TextureBasetests, unittest.TestCase):
+    def setUp(self):
+        self._klass = Texture3D
+        self._shape0 = 10, 10, 10
+        self._shape1 = 10, 10, 10, 1
+        self._shape2 = 10, 10, 10, 2
+        self._shape3 = 10, 10, 10, 3
+        self._shape4 = 10, 10, 10, 4
+        self._shape5 = 10, 10, 10, 5
+
+
+if __name__ == "__main__":
+    unittest.main()
+#     t = Texture2DTest()
+#     t.setUp()
+#     t.test_invalid_shape()
diff --git a/vispy/gloo/tests/test_variable.py b/vispy/gloo/tests/test_variable.py
new file mode 100644
index 0000000..14ed025
--- /dev/null
+++ b/vispy/gloo/tests/test_variable.py
@@ -0,0 +1,127 @@
+# -----------------------------------------------------------------------------
+# VisPy - Copyright (c) 2013, Vispy Development Team
+# All rights reserved.
+# -----------------------------------------------------------------------------
+import unittest
+import numpy as np
+from vispy.gloo import gl
+
+from vispy.gloo.variable import Uniform
+from vispy.gloo.variable import Variable
+from vispy.gloo.variable import Attribute
+
+
+# -----------------------------------------------------------------------------
+class VariableTest(unittest.TestCase):
+
+    def test_init(self):
+        variable = Variable("A", gl.GL_FLOAT)
+        assert variable._dirty == False
+        assert variable.name    == "A"
+        assert variable.data    is None
+        assert variable.gtype   == gl.GL_FLOAT
+        assert variable.active  == False
+
+
+    def test_init_wrong_type(self):
+        with self.assertRaises(ValueError):
+            v = Variable("A", gl.GL_INT_VEC2)
+        with self.assertRaises(ValueError):
+            v = Variable("A", gl.GL_INT_VEC3)
+        with self.assertRaises(ValueError):
+            v = Variable("A", gl.GL_INT_VEC4)
+
+        with self.assertRaises(ValueError):
+            v = Variable("A", gl.GL_BOOL_VEC2)
+        with self.assertRaises(ValueError):
+            v = Variable("A", gl.GL_BOOL_VEC3)
+        with self.assertRaises(ValueError):
+            v = Variable("A", gl.GL_BOOL_VEC4)
+
+
+
+# -----------------------------------------------------------------------------
+class UniformTest(unittest.TestCase):
+
+    def test_init(self):
+        uniform = Uniform("A", gl.GL_FLOAT)
+        assert uniform.texture_unit == -1
+
+    def test_float(self):
+        uniform = Uniform("A", gl.GL_FLOAT)
+        assert uniform.dtype == np.float32
+        assert uniform.size == 1
+
+    def test_vec2(self):
+        uniform = Uniform("A", gl.GL_FLOAT_VEC2)
+        assert uniform.dtype == np.float32
+        assert uniform.size == 2
+
+    def test_vec3(self):
+        uniform = Uniform("A", gl.GL_FLOAT_VEC2)
+        assert uniform.dtype == np.float32
+        assert uniform.size == 2
+
+    def test_vec4(self):
+        uniform = Uniform("A", gl.GL_FLOAT_VEC2)
+        assert uniform.dtype == np.float32
+        assert uniform.size == 2
+
+    def test_int(self):
+        uniform = Uniform("A", gl.GL_INT)
+        assert uniform.dtype == np.int32
+        assert uniform.size == 1
+
+    def test_mat2(self):
+        uniform = Uniform("A", gl.GL_FLOAT_MAT2)
+        assert uniform.dtype == np.float32
+        assert uniform.size == 4
+
+    def test_mat3(self):
+        uniform = Uniform("A", gl.GL_FLOAT_MAT3)
+        assert uniform.dtype == np.float32
+        assert uniform.size == 9
+
+    def test_mat4(self):
+        uniform = Uniform("A", gl.GL_FLOAT_MAT4)
+        assert uniform.dtype == np.float32
+        assert uniform.size == 16
+
+    def test_set(self):
+        uniform = Uniform("A", gl.GL_FLOAT_VEC4)
+
+        uniform.set_data(1)
+        assert (uniform.data == 1).all()
+
+        uniform.set_data([1,2,3,4])
+        assert (uniform.data == [1,2,3,4]).all()
+
+    def test_set_exception(self):
+        uniform = Uniform("A", gl.GL_FLOAT_VEC4)
+
+        with self.assertRaises(ValueError):
+            uniform.set_data([1,2])
+
+        with self.assertRaises(ValueError):
+            uniform.set_data([1,2,3,4,5])
+
+
+# -----------------------------------------------------------------------------
+class AttributeTest(unittest.TestCase):
+
+    def test_init(self):
+        attribute = Attribute("A", gl.GL_FLOAT)
+        assert attribute.size == 1
+
+    def test_set_generic(self):
+        attribute = Attribute("A", gl.GL_FLOAT_VEC4)
+
+        attribute.set_data([1,2,3,4])
+        assert type(attribute.data) is np.ndarray
+
+        attribute.set_data(1)
+        assert type(attribute.data) is np.ndarray
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/vispy/gloo/texture.py b/vispy/gloo/texture.py
new file mode 100644
index 0000000..59a26c7
--- /dev/null
+++ b/vispy/gloo/texture.py
@@ -0,0 +1,724 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2013, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+""" Definition of Texture class.
+
+This code is inspired by similar classes from Visvis and Pygly.
+
+"""
+
+# todo: implement ext_available
+# todo: make a Texture1D that makes a nicer interface to a 2D texture
+# todo: same for Texture3D?
+# todo: Cubemap texture
+# todo: mipmapping, allow creating mipmaps
+# todo: compressed textures?
+
+
+from __future__ import print_function, division, absolute_import
+
+import sys
+import numpy as np
+
+from vispy.util.six import string_types
+from . import gl
+from . import GLObject, ext_available, convert_to_enum
+
+
+
+
+
+class TextureError(RuntimeError):
+    """ Raised when something goes wrong that depens on state that was set 
+    earlier (due to deferred loading).
+    """
+    pass
+
+
+
+class TextureLevel(object):
+    """ Minimal class to hold together some values that together
+    represent one texture level.
+    """
+    def __init__(self, level):
+        self.level = level  # 0, 1, 2, etc.
+        self.format = None  # GL_RGB, GL_LUMINANCE etc.
+        self.shape = None  # Shape of a corresponding numpy array, zyx-order
+        self.need_resize = False  # Whether shape or format has changed
+        self.pending_data = []  # Data to upload
+    
+    
+    def set(self, shape, format):
+        """ Set shape and format. Return True if this requires a resize.
+        """
+        # Discart pending data
+        self.pending_data = []
+        # If nothing changed, early exit. Otherwise we need a resize
+        if (self.shape == shape) and (self.format == format):
+            return False
+        else:
+            self.shape = shape
+            self.format = format
+            self.need_resize = True
+            return True
+
+
+    
+class Texture(GLObject):
+    """ Representation of an OpenGL texture. 
+    
+    """
+    
+    # Dict that maps numpy datatypes to openGL ES 2.0 data types
+    # We use strings to be more failsafe; e.g. np.float128 does not always exist
+    DTYPE2GTYPE = { 'uint8': gl.GL_UNSIGNED_BYTE,
+                    'float16': gl.ext.GL_HALF_FLOAT, # Needs GL_OES_texture_half_float
+                    'float32': gl.GL_FLOAT,  # Needs GL_OES_texture_float
+            }
+    
+    
+    def __init__(self, target, data=None, format=None, clim=None):
+        GLObject.__init__(self)
+        
+        # Store target (i.e. the texture type)
+        if target not in [gl.GL_TEXTURE_2D, gl.ext.GL_TEXTURE_3D]:
+            raise ValueError('Unsupported target "%r"' % target)
+        self._target = target
+        
+        # Keep track of levels: dict of TextureLevel instances
+        # Each texLevel stores shape, format, pending data, need_resize
+        self._levels = {}
+        
+        # The parameters that apply to this texture. One variable to 
+        # keep track of pending parameters, the other for resetting
+        # parameters if its re-uploaded.
+        self._texture_params = {}
+        self._pending_params = {}
+        
+        # Set default parameters for min and mag filter, otherwise an
+        # image is not shown by default, since the default min_filter
+        # is GL_NEAREST_MIPMAP_LINEAR
+        # Note that mipmapping is not allowed unless the texture_npot
+        # extension is available.
+        self.set_filter(gl.GL_LINEAR, gl.GL_LINEAR)
+        
+        # Set default parameter for clamping. CLAMP_TO_EDGE since that
+        # is required if a texture is used as a render target.
+        # Also, in OpenGL ES 2.0, wrapping must be CLAMP_TO_EDGE if 
+        # textures are not a power of two, unless the texture_npot extension
+        # is available.
+        if self._target == gl.ext.GL_TEXTURE_3D:
+            self.set_wrapping(gl.GL_CLAMP_TO_EDGE, gl.GL_CLAMP_TO_EDGE,
+                                            gl.GL_CLAMP_TO_EDGE)
+        else:
+            self.set_wrapping(gl.GL_CLAMP_TO_EDGE, gl.GL_CLAMP_TO_EDGE)
+        
+        # Reset status; set_filter and set_wrapping were called.
+        self._need_update = False  
+        
+        # Set data?
+        if data is None:
+            pass
+        elif isinstance(data, np.ndarray):
+            self.set_data(data, format=format, clim=clim)
+        elif isinstance(data, tuple):
+            self.set_shape(data, format=format)
+        else:
+            raise ValueError('Invalid value to initialize Texture with.')
+    
+    
+    def set_filter(self, mag_filter, min_filter):
+        """ Set interpolation filters. EIther parameter can be None to 
+        not (re)set it.
+        
+        Parameters
+        ----------
+        mag_filter : str
+            The magnification filter (when texels are larger than screen
+            pixels). Can be NEAREST, LINEAR. The OpenGL enum can also be given.
+        min_filter : str
+            The minification filter (when texels are smaller than screen 
+            pixels). For this filter, mipmapping can be applied to perform
+            antialiasing (if mipmaps are available for this texture).
+            Can be NEAREST, LINEAR, NEAREST_MIPMAP_NEAREST,
+            NEAREST_MIPMAP_LINEAR, LINEAR_MIPMAP_NEAREST,
+            LINEAR_MIPMAP_LINEAR. The OpenGL enum can also be given.
+        """
+        # Allow strings
+        mag_filter = convert_to_enum(mag_filter, True)
+        min_filter = convert_to_enum(min_filter, True)
+        # Check
+        assert mag_filter in (None, gl.GL_NEAREST, gl.GL_LINEAR)
+        assert min_filter in (None, gl.GL_NEAREST, gl.GL_LINEAR, 
+                    gl.GL_NEAREST_MIPMAP_NEAREST, gl.GL_NEAREST_MIPMAP_LINEAR,
+                    gl.GL_LINEAR_MIPMAP_NEAREST, gl.GL_LINEAR_MIPMAP_LINEAR)
+        
+        # Set 
+        if mag_filter is not None:
+            self._pending_params[gl.GL_TEXTURE_MAG_FILTER] = mag_filter
+            self._texture_params[gl.GL_TEXTURE_MAG_FILTER] = mag_filter
+        if min_filter is not None:
+            self._pending_params[gl.GL_TEXTURE_MIN_FILTER] = min_filter
+            self._texture_params[gl.GL_TEXTURE_MIN_FILTER] = min_filter
+        
+        self._need_update = True
+    
+    
+    def set_wrapping(self, wrapx, wrapy, wrapz=None):
+        """ Set texture coordinate wrapping. 
+        
+        Parameters
+        ----------
+        wrapx : str
+            The wrapping mode in the x-direction. Can be GL_REPEAT,
+            GL_CLAMP_TO_EDGE, GL_MIRRORED_REPEAT. The OpenGL enum can also 
+            be given.
+        wrapy : str
+            Dito for y.
+        wrapz : str
+            Dito for z. Only makes sense for 3D textures, and requires
+            the texture_3d extension. Optional.
+        """
+        # Allow strings
+        wrapx = convert_to_enum(wrapx, True)
+        wrapy = convert_to_enum(wrapy, True)
+        wrapz = convert_to_enum(wrapz, True)
+        # Check
+        assert wrapx in (None, gl.GL_REPEAT, gl.GL_CLAMP_TO_EDGE, gl.GL_MIRRORED_REPEAT)
+        assert wrapy in (None, gl.GL_REPEAT, gl.GL_CLAMP_TO_EDGE, gl.GL_MIRRORED_REPEAT)
+        assert wrapz in (None, gl.GL_REPEAT, gl.GL_CLAMP_TO_EDGE, gl.GL_MIRRORED_REPEAT)
+        
+        # Set 
+        if wrapx is not None:
+            self._pending_params[gl.GL_TEXTURE_WRAP_S] = wrapx
+            self._texture_params[gl.GL_TEXTURE_WRAP_S] = wrapx
+        if wrapy is not None:
+            self._pending_params[gl.GL_TEXTURE_WRAP_T] = wrapy
+            self._texture_params[gl.GL_TEXTURE_WRAP_T] = wrapy
+        if wrapz is not None:
+            self._pending_params[gl.ext.GL_TEXTURE_WRAP_R] = wrapz
+            self._texture_params[gl.ext.GL_TEXTURE_WRAP_R] = wrapz
+        
+        self._need_update = True
+    
+    
+    def set_shape(self, shape, level=0, format=None):
+        """ Allocate storage for this texture. This is useful if the texture
+        is used as a render target for an FBO. 
+        
+        A call that only uses the shape argument does not result in an
+        action if the call would not change the shape.
+        
+        Parameters
+        ----------
+        shape : tuple
+            The shape of the "virtual" data. By specifying e.g. (20,20,3) for
+            a Texture2D, one implicitly sets the format to GL_RGB. Note
+            that shape[0] is height.
+        level : int
+            The mipmap level. Default 0.
+        format : str
+            The format representation of the data. If not given or None,
+            it is decuced from the given data. Can be RGB, RGBA, LUMINANCE, 
+            LUMINANCE_ALPHA, ALPHA. The OpenGL enum can also be given.
+        """
+        
+        # Check level, get texLevel instance
+        assert isinstance(level, int) and level >= 0
+        texLevel = self._levels.get(level, None)
+        if texLevel is None:
+            texLevel = self._levels[level] = TextureLevel(level)
+        
+        # Get ndim
+        MAP = {gl.GL_TEXTURE_2D:2, gl.ext.GL_TEXTURE_3D:3}
+        ndim = MAP.get(self._target, 0)
+        
+        # Check shape
+        shape = tuple([int(i) for i in shape])
+        if not len(shape) in (ndim, ndim+1):
+            raise ValueError('Shape must be ndim or ndim+1.')
+        if not all([i>0 for i in shape]):
+            raise ValueError('Shape cannot contain elements <= 0.')
+        
+        # Check format
+        if format is None:
+            format = get_formats(shape, self._target)[0]
+        else:
+            format = convert_to_enum(format)
+            if format not in get_formats(shape, self._target):
+                raise ValueError('Given format does not match with shape.')
+        
+        # Set new shape and format, force new update if necessary
+        self._need_update = texLevel.set(shape, format)
+    
+    
+    def set_data(self, data, level=0, format=None, clim=None):
+        """ Set the data for this texture. This method can be called at any
+        time (even if there is no context yet).
+        
+        It is relatively cheap to call this function multiple times,
+        only the last data is set right before drawing. If the shape
+        of the given data matches the shape of the current texture, the
+        data is updated in a fast manner.
+        
+        Parameters
+        ----------
+        data : numpy array
+            The texture data to set.
+        level : int
+            The mipmap level. Default 0.
+        format : str
+            The format representation of the data. If not given or None,
+            it is decuced from the given data. Can be RGB, RGBA, LUMINANCE, 
+            LUMINANCE_ALPHA, ALPHA. The OpenGL enum can also be given.
+        clim : (min, max)
+            Contrast limits for the data. If specified, min will end
+            up being 0.0 (black) and max will end up as 1.0 (white).
+            If not given or None, clim is determined automatically. For
+            floats they become (0.0, 1.0). For integers the are mapped to
+            the full range of the type. 
+        
+        """
+        
+        # Check level, get texLevel instance
+        assert isinstance(level, int) and level >= 0
+        texLevel = self._levels.get(level, None)
+        if texLevel is None:
+            texLevel = self._levels[level] = TextureLevel(level)
+        
+        # Get ndim
+        MAP = {gl.GL_TEXTURE_2D:2, gl.ext.GL_TEXTURE_3D:3}
+        ndim = MAP.get(self._target, 0)
+        
+        # Check data
+        shape = data.shape
+        if not isinstance(data, np.ndarray):
+            raise ValueError("Data should be a numpy array.")
+        if not data.ndim in (ndim, ndim+1):
+            raise ValueError('Data shape must be ndim or ndim+1.')
+        
+        # Check format
+        if format is None:
+            format = get_formats(shape, self._target)[0]
+        else:
+            format = convert_to_enum(format)
+            if format not in get_formats(shape, self._target):
+                raise ValueError('Given format does not match with shape.')
+        
+        # Check clim
+        assert clim is None or (isinstance(clim, tuple) and len(clim)==2)
+        
+        # Get offset of all zeros
+        offset = [0 for i in data.shape[:ndim]]
+        
+        # Set new shape and format, does not cause a resize if not necessary
+        texLevel.set(shape, format)
+        
+        # Set pending data
+        texLevel.pending_data.append( (data, clim, offset) )
+        self._need_update = True
+    
+    
+    def set_subdata(self, offset, data, level=0, format=None, clim=None):
+        """ Set a region of data for this texture. This method can be
+        called at any time (even if there is no context yet). 
+        
+        In contrast to set_data(), each call to this method results in
+        an OpenGL api call.
+        
+        Parameters
+        ----------
+        offset : tuple
+            The offset for each dimension, to update part of the texture.
+        data : numpy array
+            The texture data to set. The data (with offset) cannot exceed
+            the boundaries of the current texture.
+        level : int
+            The mipmap level. Default 0.
+        format : OpenGL enum
+            The format representation of the data. If not given or None,
+            it is decuced from the given data. Can be RGB, RGBA, LUMINANCE, 
+            LUMINANCE_ALPHA, ALPHA. The OpenGL enum can also be given.
+        clim : (min, max)
+            Contrast limits for the data. If specified, min will end
+            up being 0.0 (black) and max will end up as 1.0 (white).
+            If not given or None, clim is determined automatically. For
+            floats they become (0.0, 1.0). For integers the are mapped to
+            the full range of the type. 
+        
+        """
+        
+        # Check level, get texLevel instance
+        assert isinstance(level, int) and level >= 0
+        texLevel = self._levels.get(level, None)
+        
+        # Is there data?
+        if not texLevel or not texLevel.shape:
+            raise RuntimeError('Cannot set subdata if there is no '
+                                                    'texture allocated yet.')
+        
+        # Get ndim
+        MAP = {gl.GL_TEXTURE_2D:2, gl.ext.GL_TEXTURE_3D:3}
+        ndim = MAP.get(self._target, 0)
+        
+        # Check data
+        shape = data.shape
+        if not isinstance(data, np.ndarray):
+            raise ValueError("Data should be a numpy array.")
+        if not data.ndim in (ndim, ndim+1):
+            raise ValueError('Data shape must be ndim or ndim+1.')
+            
+        # Check offset
+        offset = tuple([int(i) for i in offset])
+        if not len(offset) == ndim:
+            raise ValueError('Offset must match with number of dimensions.')
+        if not all([i>=0 for i in offset]):
+            raise ValueError('Offset cannot contain elements < 0.')
+        fits = [(offset[i]+shape[i]<=texLevel.shape[i]) for i in range(ndim)]
+        if not all(fits):
+            raise ValueError("Given subdata does not fit in the existing texture.")
+        
+        # Get format if not given
+        if format is None:
+            if texLevel.format not in get_formats(shape, self._target):
+                raise ValueError('Subdata shape does not match with current format.')
+        else:
+            format = convert_to_enum(format)
+            if format != texLevel.format:
+                raise ValueError('Subdata must have the same format as the existing data.')
+        
+        # Check clim
+        assert clim is None or (isinstance(clim, tuple) and len(clim)==2)
+        
+        # Set pending data
+        texLevel.pending_data.append( (data, clim, offset) )
+        self._need_update = True
+    
+    
+    def _create(self):
+        self._handle = gl.glGenTextures(1)
+    
+    
+    def _delete(self):
+        gl.glDeleteTextures([self._handle])
+    
+    
+    def _activate(self):
+        gl.glBindTexture(self._target, self._handle)
+    
+    
+    def _deactivate(self):
+        gl.glBindTexture(self._target, 0)
+    
+    
+    def _update(self):
+        
+        # If we use a 3D texture, we need an extension
+        if self._target == gl.ext.GL_TEXTURE_3D:
+            if not ext_available('GL_texture_3D'):
+                raise TextureError('3D Texture not available.')
+        
+        
+        # For each level ...
+        for texLevel in self._levels.values():
+            
+            # Need to resize?
+            if texLevel.need_resize:
+                texLevel.need_resize = False
+                new_texture_created = False
+                if self._valid and len(self._levels) == 1:
+                    # We delete the existing texture first. In theory this
+                    # should not be necessary, but some implementations cause
+                    # memory leaks otherwise.
+                    new_texture_created = True
+                    self.delete() 
+                    self._create()
+                # Allocate texture on GPU
+                gl.glBindTexture(self._target, self._handle)  #self._activate()
+                self._allocate_shape(texLevel)
+                # If not ok, warn (one time)
+                if not gl.glIsTexture(self._handle):
+                    self._handle = 0
+                    print('Warning enabling texture, the texture is not valid.')
+                    return
+                if new_texture_created:
+                    # We have a new texture: apply all parameters that were set
+                    for param, value in self._texture_params.items():
+                        gl.glTexParameter(self._target, param, value)
+                        self._pending_params = {} # We just applied all 
+            
+            # Need to update some data?
+            while texLevel.pending_data:
+                data, clim, offset = texLevel.pending_data.pop(0)
+                # Apply clim and convert data type to one supported by OpenGL
+                data = convert_data(data, clim)
+                # Upload
+                gl.glBindTexture(self._target, self._handle)  # self._activate()
+                self._upload_data(data, texLevel, offset)
+        
+        # Check
+        #if not gl.glIsTexture(self._handle): 
+        #    raise TextureError('This should not happen (texture is invalid)')
+        
+        # Need to update any parameters?
+        gl.glBindTexture(self._target, self._handle)  # self._activate()
+        while self._pending_params:
+            param, value = self._pending_params.popitem()
+            gl.glTexParameter(self._target, param, value)
+    
+    
+    def _allocate_shape(self, texLevel):
+        """ Allocate space for the current texture object. 
+        It should have been verified that the texture will fit.
+        """
+        
+        # Get parameters that we need
+        target = self._target
+        shape, format, level = texLevel.shape, texLevel.format, texLevel.level
+        
+        # Determine function and target from texType
+        D = {   #gl.GL_TEXTURE_1D: (gl.glTexImage1D, 1),
+                gl.GL_TEXTURE_2D: (gl.glTexImage2D, 2),
+                gl.ext.GL_TEXTURE_3D: (gl.ext.glTexImage3D, 3)}
+        uploadFun, ndim = D[target]
+        
+        # Determine type
+        gltype = gl.GL_UNSIGNED_BYTE
+        
+        # Build args list
+        size = [i for i in reversed( shape[:ndim] )]
+        args = [target, level, format] + size + [0, format, gltype, None]
+        
+        # Call
+        uploadFun(*tuple(args))
+    
+    
+    def _upload_data(self, data, texLevel, offset):
+        """ Upload a texture to the current texture object. 
+        It should have been verified that the texture will fit.
+        """
+        
+        # Get parameters that we need
+        target = self._target
+        format, level = texLevel.format, texLevel.level
+        alignment = self._get_alignment(data.shape[-1])
+        
+        # Determine function and target from texType
+        D = {   #gl.GL_TEXTURE_1D: (gl.glTexSubImage1D, 1),
+                gl.GL_TEXTURE_2D: (gl.glTexSubImage2D, 2),
+                gl.ext.GL_TEXTURE_3D: (gl.ext.glTexSubImage3D, 3)}
+        uploadFun, ndim = D[target]
+        
+        # Reverse and check offset
+        offset = offset[::-1] #[i for i in offset]
+        assert len(offset) == ndim
+        
+        # Determine type
+        thetype = data.dtype.name
+        if thetype not in self.DTYPE2GTYPE:  # Note that we convert if necessary
+            raise TextureError("Cannot translate datatype %s to GL." % thetype)
+        gltype = self.DTYPE2GTYPE[thetype]
+        
+        # Build args list
+        size = [i for i in reversed( data.shape[:ndim] )]
+        args = [target, level] + offset + size + [format, gltype, data]
+        
+        # Check the alignment of the texture
+        if alignment != 4:
+            gl.glPixelStorei(gl.GL_UNPACK_ALIGNMENT, alignment)
+        
+        # Call
+        uploadFun(*tuple(args))
+        
+        # Check if we need to reset our pixel store state
+        if alignment != 4:
+            gl.glPixelStorei(gl.GL_UNPACK_ALIGNMENT, 4)
+    
+    
+    # from pylgy
+    def _get_alignment(self, width):
+        """Determines a textures byte alignment.
+    
+        If the width isn't a power of 2
+        we need to adjust the byte alignment of the image.
+        The image height is unimportant
+    
+        http://www.opengl.org/wiki/Common_Mistakes#Texture_upload_and_pixel_reads
+        """
+        
+        # we know the alignment is appropriate
+        # if we can divide the width by the
+        # alignment cleanly
+        # valid alignments are 1,2,4 and 8
+        # put 4 first, since it's the default
+        alignments = [4,8,2,1]
+        for alignment in alignments:
+            if width % alignment == 0:
+                return alignment
+        
+
+
+class Texture2D(Texture):
+    """ Representation of a 2D texture. Inherits :class:`texture.Texture`.
+    """
+    def __init__(self, *args, **kwargs):
+        Texture.__init__(self, gl.GL_TEXTURE_2D, *args, **kwargs)
+
+
+
+class Texture3D(Texture):
+    """ Representation of a 3D texture. Note that for this the
+    GL_texture_3D extension needs to be available. 
+    Inherits :class:`texture.Texture`.
+    """
+    def __init__(self, *args, **kwargs):
+        Texture.__init__(self, gl.ext.GL_TEXTURE_3D, *args, **kwargs)
+
+
+
+class TextureCubeMap(Texture):
+    """ Representation of a cube map, to store texture data for the
+    6 sided of a cube. Used for instance to create environment mappings.
+    Inherits :class:`texture.Texture`.
+    
+    This class is not yet implemented.
+    """
+    # Note that width and height for these textures should be equal
+    def __init__(self, *args, **kwargs):
+        raise NotImplementedError()
+        Texture.__init__(self, gl.GL_TEXTURE_CUBE_MAP, *args, **kwargs)
+
+
+
+## Utility functions
+
+
+def get_formats(shape, target):
+    """ Get formats for the texture, based on the target and the shape.
+    If the shape does not match with the texture type, an exception is
+    raised. 
+    """
+    
+    if target == gl.GL_TEXTURE_2D:
+        if len(shape)==2:
+            return gl.GL_LUMINANCE, gl.GL_ALPHA
+        elif len(shape)==3 and shape[2]==1:
+            return gl.GL_LUMINANCE, 
+        elif len(shape)==3 and shape[2]==2:
+            return gl.GL_LUMINANCE_ALPHA,
+        elif len(shape)==3 and shape[2]==3:
+            return gl.GL_RGB,
+        elif len(shape)==3 and shape[2]==4:
+            return gl.GL_RGBA,
+        else:
+            shapestr = 'x'.join([str(i) for i in shape])
+            raise ValueError("Cannot determine format for %s texture from shape %s." %
+                                                        ('2D', shapestr) )
+    
+    elif target == gl.ext.GL_TEXTURE_3D:
+        if len(shape)==3:
+            return gl.GL_LUMINANCE, gl.GL_ALPHA
+        elif len(shape)==4 and shape[3]==1:
+            return gl.GL_LUMINANCE,
+        elif len(shape)==4 and shape[3]==2:
+            return gl.GL_LUMINANCE_ALPHA,
+        elif len(shape)==4 and shape[3]==3:
+            return gl.GL_RGB,
+        elif len(shape)==4 and shape[3]==4:
+            return gl.GL_RGBA,
+        else:
+            shapestr = 'x'.join([str(i) for i in shape])
+            raise ValueError("Cannot determine format for %s texture from shape %s." %
+                                                        ('3D', shapestr) )
+    
+    else:
+        raise ValueError("Cannot determine format with these dimensions.")
+    
+
+
+def convert_data(data, clim=None):
+    """ Convert data to a type that OpenGL can deal with.
+    Also applies contrast limits if given.
+    """
+    
+    # Prepare
+    FLOAT32_SUPPORT = ext_available('texture_float')
+    FLOAT16_SUPPORT = ext_available('texture_half_float')
+    CONVERTING = False
+    
+    # Determine clim if not given, copy or make float32 if necessary.
+    # Copies may be necessary because following operations are in-place.
+    if data.dtype.name == 'bool':
+        # Bools are ... unsigned ints
+        data = data.astype(np.uint8)
+        clim = None
+    elif data.dtype.name == 'uint8':
+        # Uint8 is what we need! If clim is None, no action required
+        if clim is not None:
+            data = data.astype(np.float32)
+    elif data.dtype.name == 'float16':
+        # Float16 may be allowed. If clim is None, no action is required
+        if clim is not None:
+            data = data.copy()
+        elif not FLOAT16_SUPPORT:
+            CONVERTING = True
+            data = data.copy()
+    elif data.dtype.name == 'float32':
+        # Float32 may be allowed. If clim is None, no action is required
+        if clim is not None:
+            data = data.copy()
+        elif not FLOAT32_SUPPORT:
+            CONVERTING = True
+            data = data.copy()
+    elif 'float' in data.dtype.name:
+        # All other floats are converted with relative ease
+        CONVERTING = True
+        data = data.astype(np.float32)
+    elif data.dtype.name.startswith('int'):
+        # Integers, we need to parse the dtype
+        CONVERTING = True
+        if clim is None:
+            max = 2**int(data.dtype.name[3:])
+            clim = -max//2, max//2-1
+        data = data.astype(np.float32)
+    elif data.dtype.name.startswith('uint'):
+        # Unsigned integers, we need to parse the dtype
+        CONVERTING = True
+        if clim is None:
+            max = 2**int(data.dtype.name[4:])
+            clim = 0, max//2
+        data = data.astype(np.float32)
+    else:
+        raise TextureError('Could not convert data type %s.' % data.dtype.name)
+    
+    # Apply limits if necessary
+    if clim is not None:
+        assert isinstance(clim, tuple)
+        assert len(clim) == 2
+        if clim[0] != 0.0:
+            data -= clim[0]
+        if clim[1]-clim[0] != 1.0:
+            data *= 1.0 / (clim[1]-clim[0])
+    
+    #if CONVERTING:
+    #    print('Warning, converting data.')
+    
+    # Convert if necessary
+    if data.dtype == np.uint8:
+        pass  # Always possible
+    elif data.dtype == np.float16 and FLOAT16_SUPPORT:
+        pass  # Yeah
+    elif data.dtype == np.float32 and FLOAT32_SUPPORT:
+        pass  # Yeah
+    elif data.dtype in (np.float16, np.float32):
+        # Arg, convert. Don't forget to clip
+        data *= 256.0
+        data[data<0.0] = 0.0
+        data[data>256.0] = 256.0
+        data = data.astype(np.uint8)
+    else:
+        raise TextureError('Error converting data type. This should not happen.')
+    
+    # Done
+    return data
diff --git a/vispy/gloo/variable.py b/vispy/gloo/variable.py
new file mode 100644
index 0000000..f94cba3
--- /dev/null
+++ b/vispy/gloo/variable.py
@@ -0,0 +1,458 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2013, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+""" Classes for uniform and attribute variables.
+
+These classes are mainly containers, the only gl functionality is
+the uploading of gl data to the GPU.
+
+"""
+
+from __future__ import print_function, division, absolute_import
+
+import ctypes
+import numpy as np
+
+from . import gl
+from . import GLObject
+from .buffer import ClientVertexBuffer, VertexBuffer, VertexBufferView
+from .texture import Texture, Texture2D, TextureCubeMap, Texture3D
+from vispy.util.six import string_types
+
+# todo: support arrays of uniforms
+
+gl_typeinfo = {
+    gl.GL_FLOAT        : ( 1, gl.GL_FLOAT,        np.float32),      
+    gl.GL_FLOAT_VEC2   : ( 2, gl.GL_FLOAT,        np.float32),      
+    gl.GL_FLOAT_VEC3   : ( 3, gl.GL_FLOAT,        np.float32),      
+    gl.GL_FLOAT_VEC4   : ( 4, gl.GL_FLOAT,        np.float32),      
+    gl.GL_INT          : ( 1, gl.GL_INT,          np.int32),        
+    gl.GL_INT_VEC2     : ( 2, gl.GL_INT,          np.int32),        
+    gl.GL_INT_VEC3     : ( 3, gl.GL_INT,          np.int32),        
+    gl.GL_INT_VEC4     : ( 4, gl.GL_INT,          np.int32),        
+    gl.GL_BOOL         : ( 1, gl.GL_BOOL,         np.bool),         
+    gl.GL_BOOL_VEC2    : ( 2, gl.GL_BOOL,         np.bool),         
+    gl.GL_BOOL_VEC3    : ( 3, gl.GL_BOOL,         np.bool),         
+    gl.GL_BOOL_VEC4    : ( 4, gl.GL_BOOL,         np.bool),         
+    gl.GL_FLOAT_MAT2   : ( 4, gl.GL_FLOAT,        np.float32),      
+    gl.GL_FLOAT_MAT3   : ( 9, gl.GL_FLOAT,        np.float32),      
+    gl.GL_FLOAT_MAT4   : (16, gl.GL_FLOAT,        np.float32),      
+    gl.GL_SAMPLER_2D   : ( 1, gl.GL_UNSIGNED_INT, np.uint32),
+    gl.ext.GL_SAMPLER_3D   : ( 1, gl.GL_UNSIGNED_INT, np.uint32),
+    gl.GL_SAMPLER_CUBE : ( 1, gl.GL_UNSIGNED_INT, np.uint32), }
+
+
+class VariableError(RuntimeError):
+    """ Raised when something goes wrong that depens on state that was set 
+    earlier (due to deferred loading).
+    """
+    pass
+
+
+
+class Variable(object):
+    """
+    A variable is an interface between a program and some data.
+    """
+
+    # NOTE: Variable, Uniform, Attribute are FRIENDS of Program,
+    # and Program manimpulates the private attributes of these objects.
+
+    # ---------------------------------
+    def __init__(self, name, gtype):
+        """ Initialize the data into default state """
+        
+        # Make sure variable type is allowed (for ES 2.0 shader)
+        if gtype not in  [ gl.GL_FLOAT,        gl.GL_FLOAT_VEC2,
+                           gl.GL_FLOAT_VEC3,   gl.GL_FLOAT_VEC4,
+                           gl.GL_INT,          gl.GL_BOOL,
+                           gl.GL_FLOAT_MAT2,   gl.GL_FLOAT_MAT3,
+                           gl.GL_FLOAT_MAT4,   gl.GL_SAMPLER_2D,
+                           gl.GL_SAMPLER_CUBE]:
+            raise ValueError("Unknown variable type")
+        
+        # Name of this variable in the program
+        self._name = name
+        
+        # GL type and size (i.e. size of the vector)
+        self._gtype = gtype
+        self._size, _, self._dtype = gl_typeinfo[self._gtype]
+        
+        # CPU data
+        self._data = None
+        
+        # Location of the variable in the program slots
+        self._loc = None
+        
+        # Whether an upload is required
+        self._dirty = False
+        
+        # To suppress warnings
+        self._show_warning_notset = True
+    
+    
+    def invalidate(self):
+        """ Set dirty flag, to force setting the variable on the Program
+        object. """
+        self._dirty = True
+    
+    
+    @property
+    def name(self):
+        """ The name of the variable. """
+        return self._name
+    
+    @property
+    def gtype(self):
+        """ The type of the underlying variable (as a GL constant). """
+        return self._gtype
+
+    @property
+    def dtype(self):
+        """ The type of the underlying variable (as a np dtype). """
+        return self._dtype
+    
+    @property
+    def size(self):
+        """ The size of the variable (i.e. size of the vector in GLSL). """
+        return self._size
+    
+    @property
+    def active(self):
+        """ Whether this variable is active in the program. """
+        return self._loc is not None
+
+    
+    @property
+    def data(self):
+        """ The data for this variable (ndarray, VertexBuffer or Texture). """
+        return self._data
+
+
+
+
+# ----------------------------------------------------------- Uniform class ---
+class Uniform(Variable):
+    """ A Uniform represents a program uniform variable. """
+    
+    # NOTE: Variable, Uniform, Attribute are FRIENDS of Program,
+    # and Program manimpulates the private attributes of these objects.
+    
+    _ufunctions = { 
+        gl.GL_FLOAT:        (gl.glUniform1fv, 1),
+        gl.GL_FLOAT_VEC2:   (gl.glUniform2fv, 2),
+        gl.GL_FLOAT_VEC3:   (gl.glUniform3fv, 3),
+        gl.GL_FLOAT_VEC4:   (gl.glUniform4fv, 4),
+        gl.GL_INT:          (gl.glUniform1iv, 1),
+        gl.GL_INT_VEC2:     (gl.glUniform2iv, 2),
+        gl.GL_INT_VEC3:     (gl.glUniform3iv, 3),
+        gl.GL_INT_VEC4:     (gl.glUniform4iv, 4),
+        gl.GL_BOOL:         (gl.glUniform1iv, 1),
+        gl.GL_BOOL_VEC2:    (gl.glUniform2iv, 2),
+        gl.GL_BOOL_VEC3:    (gl.glUniform3iv, 3),
+        gl.GL_BOOL_VEC4:    (gl.glUniform4iv, 4),
+        gl.GL_FLOAT_MAT2:   (gl.glUniformMatrix2fv, 4),
+        gl.GL_FLOAT_MAT3:   (gl.glUniformMatrix3fv, 9),
+        gl.GL_FLOAT_MAT4:   (gl.glUniformMatrix4fv, 16),
+        gl.GL_SAMPLER_2D:   (gl.glUniform1i, 1),
+        gl.GL_SAMPLER_CUBE: (gl.glUniform1i, 1),
+        gl.ext.GL_SAMPLER_3D: (gl.glUniform1i, 1),
+        }
+
+
+    def __init__(self, name, gtype):
+        Variable.__init__(self, name, gtype)
+        
+        # Get ufunc
+        self._ufunction, self._numel = Uniform._ufunctions[self._gtype]
+        
+        # For textures:
+        self._texture_unit = -1  # Set by Program
+        self._textureClass = {  gl.GL_SAMPLER_2D: Texture2D, 
+                                gl.GL_SAMPLER_CUBE: TextureCubeMap, 
+                                gl.ext.GL_SAMPLER_3D: Texture3D,}.get(gtype, None)
+    
+    
+    @property
+    def texture_unit(self):
+        """ The texture unit (only valid if this uniform is a sampler/texture.
+        """
+        return self._texture_unit
+    
+    
+    def set_data(self, data):
+        """ Set data for this uniform. Data can be anything that can be
+        conveted to a numpy array. If the uniform is a sampler, a Texture
+        object is required.
+        """
+        
+        if self._gtype in (gl.GL_SAMPLER_2D, gl.GL_SAMPLER_CUBE, gl.ext.GL_SAMPLER_3D):
+            # Textures need special handling
+            if isinstance(data, Texture):
+                self._data = data
+            else:
+                raise ValueError('Expected a Texture for uniform %s.' % self.name)
+        else:
+            # Try to put it inside the array
+            if self._data is None:
+                size, _, dtype = gl_typeinfo[self._gtype]
+                self._data = np.zeros(size, dtype)
+            try:
+                if not isinstance(data, np.ndarray):
+                    data = np.array(data)
+                self._data[...] = data.ravel() 
+            except ValueError:
+                raise ValueError("Wrong data format for uniform %s" % self.name)
+        
+        # Mark variable as dirty
+        self._dirty = True
+    
+    
+    def upload(self, program):
+        """ Actual upload of data to GPU memory """
+        
+        # If there is not data, there is no point in uploading
+        if self._data is None:
+            if self._show_warning_notset:
+                print("Value for uniform '%s' is not set." % self.name)
+                self._show_warning_notset = False
+            return
+        
+        # Check active status (mandatory)
+        if self._loc is None:
+            raise VariableError("Uniform is not active")
+        
+        #  WARNING : Uniform are supposed to keep their value between program
+        #           activation/deactivation (from the GL documentation). It has
+        #           been tested on some machines but if it is not the case on
+        #           every machine, we can expect nasty bugs from these early
+        #           returns
+            
+        # Matrices (need a transpose argument)
+        if self._gtype in (gl.GL_FLOAT_MAT2, gl.GL_FLOAT_MAT3, gl.GL_FLOAT_MAT4):
+            if not self._dirty:
+                return
+            # OpenGL ES 2.0 does not support transpose
+            transpose = False 
+            self._ufunction(self._loc, 1, transpose, self._data)
+            self._dirty = False
+            
+        # Textures (need to get texture count)
+        elif self._gtype in (gl.GL_SAMPLER_2D, gl.GL_SAMPLER_CUBE):
+            # Always enable texture
+            texture = self.data
+            unit = self.texture_unit
+            gl.glActiveTexture(gl.GL_TEXTURE0 + unit)
+            program.activate_object(texture)
+            # Upload uniform only of needed
+            if not self._dirty:
+                return
+            gl.glUniform1i(self._loc, unit)
+            
+        # Regular uniform
+        else:
+            if not self._dirty:
+                return
+            self._ufunction(self._loc, 1, self._data)
+        
+        # Mark as uploaded
+        self._dirty = False
+
+
+
+
+
+# --------------------------------------------------------- Attribute class ---
+class Attribute(Variable):
+    """
+    An Attribute represents a program attribute variable.
+    """
+    
+    # NOTE: Variable, Uniform, Attribute are FRIENDS of Program,
+    # and Program manimpulates the private attributes of these objects.
+    
+    _afunctions = { 
+        gl.GL_FLOAT:        gl.glVertexAttrib1f,
+        gl.GL_FLOAT_VEC2:   gl.glVertexAttrib2f,
+        gl.GL_FLOAT_VEC3:   gl.glVertexAttrib3f,
+        gl.GL_FLOAT_VEC4:   gl.glVertexAttrib4f
+    }
+
+
+    def __init__(self, name, gtype):
+        Variable.__init__(self, name, gtype)
+        
+        # Count number of vertices
+        self._count = 0
+        
+        # Whether this attribure is generic
+        self._generic = False
+    
+    
+    @property
+    def count(self):
+        """ The number of vertices for this attribute. 
+        May be None for generic attributes. """
+        if self._generic or self._data is None:
+            return None
+        else:
+            return self._data.count
+    
+
+    def set_data(self, data):
+        """ Set data for this attribute. """
+        
+        # No magic conversion to VertexBuffer here. If we want it, we 
+        # should do it in Program.__setitem__
+        
+        # Data is a tuple with size <= 4, we assume this designates a generate
+        # vertex attribute.
+        if (isinstance(data, (float, int)) or
+            (isinstance(data, (tuple, list)) and len(data) in [1,2,3,4])):
+            # Get dtype, should be float32 for ES 2.0, see issue #9
+            _, _, dtype = gl_typeinfo[self._gtype]
+            if dtype != np.float32:
+                print('Warning: OpenGL ES 2.0 only supports float attributes.')
+            # Let numpy convert the data for us
+            self._data = np.array(data, dtype=dtype)
+            self._data.shape = self._data.size,
+            # Set generic and afunc
+            self._generic = True
+            self._afunction = Attribute._afunctions[self._gtype]
+        
+        elif isinstance(data, (ClientVertexBuffer, VertexBuffer)):
+            # Just store the Buffer
+            self._data = data
+            self._generic = False
+        elif isinstance(data, np.ndarray):
+            raise ValueError('Cannot set attribute data using numpy arrays: ' + 
+                            'use tuple, ClientVertexBuffer or VertexBuffer instead. ')
+        else:
+            raise ValueError('Wrong data for attribute.')
+        
+        # Mark variable as dirty
+        self._dirty = True
+    
+
+    def upload(self, program):
+        """ Actual upload of data to GPU memory  """
+        
+        # If there is not data, there is no point in uploading
+        if self._data is None:
+            if self._show_warning_notset:
+                print("Value for attribute '%s' is not set." % self.name)
+                self._show_warning_notset = False
+            return
+        
+        # Check active status (mandatory)
+        if self._loc is None:
+            raise VariableError("Attribute is not active")
+        
+        # Note: It has been seen (on my (AK) machine) that attributes
+        # are *not* local to the program object; the splitscreen example
+        # is broken if we use the early exits here.
+        
+        # todo: instead of setting dirty to true, remove early exits
+        # (leaving for now for testing on other machines)
+        self._dirty = True
+        
+        # Generic vertex attribute (all vertices receive the same value)
+        if self._generic:
+            
+            # Tell OpenGL to use the constant value
+            gl.glDisableVertexAttribArray(self._loc)
+            
+            # Early exit
+            if not self._dirty:
+                return
+            
+            # Apply
+            self._afunction(self._loc, *self._data)
+
+        # Client side array
+        elif isinstance(self._data, ClientVertexBuffer):
+            
+            # Tell OpenGL to use the array and not the glVertexAttrib* value
+            gl.glEnableVertexAttribArray(self._loc)
+            
+            # Disable any VBO
+            gl.glBindBuffer(gl.GL_ARRAY_BUFFER, 0)
+            
+            # Early exit (pointer to CPU-data is still known by Program)
+            if not self._dirty:
+                return
+            
+            # Get numpy array from its container
+            data = self._data.data
+            
+            # Check attribute format against data format
+            size, gtype, _ = gl_typeinfo[self._gtype]
+            #if self._gtype != self._data._gtype: 
+            #    raise ValueError("Data not compatible with attribute type")
+            offset = 0
+            stride = self._data.stride
+
+            # Apply (first disable any previous VertexBuffer)
+            gl.glVertexAttribPointer(self._loc, size, gtype, False, stride, data)
+        
+        # Regular vertex buffer or vertex buffer view
+        else:
+            
+            data = self._data
+            # todo: check offset = -1?
+            
+            # Tell OpenGL to use the array and not the glVertexAttrib* value
+            gl.glEnableVertexAttribArray(self._loc)
+            
+            # Enable the VBO
+            program.activate_object(data)
+            
+            # Early exit
+            if not self._dirty:
+                return
+            
+            # Check attribute format against data format
+            size, gtype, _ = gl_typeinfo[self._gtype]
+            #if self._gtype != self._data._gtype: 
+            #    raise ValueError("Data not compatible with attribute type")
+            offset = self._data.offset
+            stride = self._data.stride
+
+            #size, gtype, dtype = gl_typeinfo[self._gtype]
+            #offset, stride = data._offset, data._stride  # view_params not on VertexBuffer
+
+            # Make offset a pointer, or it will be interpreted as a small array
+            offset = ctypes.c_void_p(offset)
+                
+            # Apply
+            gl.glVertexAttribPointer(self._loc, size, gtype,  False, stride, offset)
+        
+        # Mark as uploaded
+        self._dirty = False
+        #print('upload attribute %s' % self.name, self._loc)
+    
+
+# -----------------------------------------------------------------------------
+if __name__ == '__main__':
+
+    u = Uniform(0, "color", "vec4")
+    a = Attribute(0, "color", "vec4")
+
+    # Check setting data
+    color = 1,1,1,1
+    u.set_data(color)
+
+    # Check size mismatch
+#    color = 0,0,0
+#    u.set_data(color)
+
+
+    # Check setting data
+    color = 1,1,1,1
+    a.set_data(color)
+
+    # Check size mismatch
+    color = 0,0,0
+    a.set_data(color)
+
diff --git a/vispy/shaders/__init__.py b/vispy/shaders/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/vispy/shaders/shaders.py b/vispy/shaders/shaders.py
new file mode 100644
index 0000000..b165285
--- /dev/null
+++ b/vispy/shaders/shaders.py
@@ -0,0 +1,393 @@
+from OpenGL.GL import *
+from OpenGL.GL import shaders
+import re
+
+## For centralizing and managing vertex/fragment shader programs.
+
+def initShaders():
+    global Shaders
+    Shaders = [
+        ShaderProgram(None, []),
+        
+        ## increases fragment alpha as the normal turns orthogonal to the view
+        ## this is useful for viewing shells that enclose a volume (such as isosurfaces)
+        ShaderProgram('balloon', [
+            VertexShader("""
+                varying vec3 normal;
+                void main() {
+                    // compute here for use in fragment shader
+                    normal = normalize(gl_NormalMatrix * gl_Normal);
+                    gl_FrontColor = gl_Color;
+                    gl_BackColor = gl_Color;
+                    gl_Position = ftransform();
+                }
+            """),
+            FragmentShader("""
+                varying vec3 normal;
+                void main() {
+                    vec4 color = gl_Color;
+                    color.w = min(color.w + 2.0 * color.w * pow(normal.x*normal.x + normal.y*normal.y, 5.0), 1.0);
+                    gl_FragColor = color;
+                }
+            """)
+        ]),
+        
+        ## colors fragments based on face normals relative to view
+        ## This means that the colors will change depending on how the view is rotated
+        ShaderProgram('viewNormalColor', [   
+            VertexShader("""
+                varying vec3 normal;
+                void main() {
+                    // compute here for use in fragment shader
+                    normal = normalize(gl_NormalMatrix * gl_Normal);
+                    gl_FrontColor = gl_Color;
+                    gl_BackColor = gl_Color;
+                    gl_Position = ftransform();
+                }
+            """),
+            FragmentShader("""
+                varying vec3 normal;
+                void main() {
+                    vec4 color = gl_Color;
+                    color.x = (normal.x + 1.0) * 0.5;
+                    color.y = (normal.y + 1.0) * 0.5;
+                    color.z = (normal.z + 1.0) * 0.5;
+                    gl_FragColor = color;
+                }
+            """)
+        ]),
+        
+        ## colors fragments based on absolute face normals.
+        ShaderProgram('normalColor', [   
+            VertexShader("""
+                varying vec3 normal;
+                void main() {
+                    // compute here for use in fragment shader
+                    normal = normalize(gl_Normal);
+                    gl_FrontColor = gl_Color;
+                    gl_BackColor = gl_Color;
+                    gl_Position = ftransform();
+                }
+            """),
+            FragmentShader("""
+                varying vec3 normal;
+                void main() {
+                    vec4 color = gl_Color;
+                    color.x = (normal.x + 1.0) * 0.5;
+                    color.y = (normal.y + 1.0) * 0.5;
+                    color.z = (normal.z + 1.0) * 0.5;
+                    gl_FragColor = color;
+                }
+            """)
+        ]),
+        
+        ## very simple simulation of lighting. 
+        ## The light source position is always relative to the camera.
+        ShaderProgram('shaded', [   
+            VertexShader("""
+                varying vec3 normal;
+                void main() {
+                    // compute here for use in fragment shader
+                    normal = normalize(gl_NormalMatrix * gl_Normal);
+                    gl_FrontColor = gl_Color;
+                    gl_BackColor = gl_Color;
+                    gl_Position = ftransform();
+                }
+            """),
+            FragmentShader("""
+                varying vec3 normal;
+                void main() {
+                    float p = dot(normal, normalize(vec3(1.0, -1.0, -1.0)));
+                    p = p < 0. ? 0. : p * 0.8;
+                    vec4 color = gl_Color;
+                    color.x = color.x * (0.2 + p);
+                    color.y = color.y * (0.2 + p);
+                    color.z = color.z * (0.2 + p);
+                    gl_FragColor = color;
+                }
+            """)
+        ]),
+        
+        ## colors get brighter near edges of object
+        ShaderProgram('edgeHilight', [   
+            VertexShader("""
+                varying vec3 normal;
+                void main() {
+                    // compute here for use in fragment shader
+                    normal = normalize(gl_NormalMatrix * gl_Normal);
+                    gl_FrontColor = gl_Color;
+                    gl_BackColor = gl_Color;
+                    gl_Position = ftransform();
+                }
+            """),
+            FragmentShader("""
+                varying vec3 normal;
+                void main() {
+                    vec4 color = gl_Color;
+                    float s = pow(normal.x*normal.x + normal.y*normal.y, 2.0);
+                    color.x = color.x + s * (1.0-color.x);
+                    color.y = color.y + s * (1.0-color.y);
+                    color.z = color.z + s * (1.0-color.z);
+                    gl_FragColor = color;
+                }
+            """)
+        ]),
+        
+        ## colors fragments by z-value.
+        ## This is useful for coloring surface plots by height.
+        ## This shader uses a uniform called "colorMap" to determine how to map the colors:
+        ##    red   = pow(z * colorMap[0] + colorMap[1], colorMap[2])
+        ##    green = pow(z * colorMap[3] + colorMap[4], colorMap[5])
+        ##    blue  = pow(z * colorMap[6] + colorMap[7], colorMap[8])
+        ## (set the values like this: shader['uniformMap'] = array([...])
+        ShaderProgram('heightColor', [
+            VertexShader("""
+                varying vec4 pos;
+                void main() {
+                    gl_FrontColor = gl_Color;
+                    gl_BackColor = gl_Color;
+                    pos = gl_Vertex;
+                    gl_Position = ftransform();
+                }
+            """),
+            FragmentShader("""
+                uniform float colorMap[9];
+                varying vec4 pos;
+                //out vec4 gl_FragColor;   // only needed for later glsl versions
+                //in vec4 gl_Color;
+                void main() {
+                    vec4 color = gl_Color;
+                    color.x = colorMap[0] * (pos.z + colorMap[1]);
+                    if (colorMap[2] != 1.0)
+                        color.x = pow(color.x, colorMap[2]);
+                    color.x = color.x < 0. ? 0. : (color.x > 1. ? 1. : color.x);
+                    
+                    color.y = colorMap[3] * (pos.z + colorMap[4]);
+                    if (colorMap[5] != 1.0)
+                        color.y = pow(color.y, colorMap[5]);
+                    color.y = color.y < 0. ? 0. : (color.y > 1. ? 1. : color.y);
+                    
+                    color.z = colorMap[6] * (pos.z + colorMap[7]);
+                    if (colorMap[8] != 1.0)
+                        color.z = pow(color.z, colorMap[8]);
+                    color.z = color.z < 0. ? 0. : (color.z > 1. ? 1. : color.z);
+                    
+                    color.w = 1.0;
+                    gl_FragColor = color;
+                }
+            """),
+        ], uniforms={'colorMap': [1, 1, 1, 1, 0.5, 1, 1, 0, 1]}),
+        ShaderProgram('pointSprite', [   ## allows specifying point size using normal.x
+            ## See:
+            ##
+            ##  http://stackoverflow.com/questions/9609423/applying-part-of-a-texture-sprite-sheet-texture-map-to-a-point-sprite-in-ios
+            ##  http://stackoverflow.com/questions/3497068/textured-points-in-opengl-es-2-0
+            ##
+            ##
+            VertexShader("""
+                void main() {
+                    gl_FrontColor=gl_Color;
+                    gl_PointSize = gl_Normal.x;
+                    gl_Position = ftransform();
+                } 
+            """),
+            #FragmentShader("""
+                ##version 120
+                #uniform sampler2D texture;
+                #void main ( )
+                #{
+                    #gl_FragColor = texture2D(texture, gl_PointCoord) * gl_Color;
+                #}
+            #""")
+        ]),
+    ]
+
+
+CompiledShaderPrograms = {}
+    
+def getShaderProgram(name):
+    return ShaderProgram.names[name]
+
+class Shader(object):
+    def __init__(self, shaderType, code):
+        self.shaderType = shaderType
+        self.code = code
+        self.compiled = None
+        
+    def shader(self):
+        if self.compiled is None:
+            try:
+                self.compiled = shaders.compileShader(self.code, self.shaderType)
+            except RuntimeError as exc:
+                ## Format compile errors a bit more nicely
+                if len(exc.args) == 3:
+                    err, code, typ = exc.args
+                    if not err.startswith('Shader compile failure'):
+                        raise
+                    code = code[0].split('\n')
+                    err, c, msgs = err.partition(':')
+                    err = err + '\n'
+                    msgs = msgs.split('\n')
+                    errNums = [()] * len(code)
+                    for i, msg in enumerate(msgs):
+                        msg = msg.strip()
+                        if msg == '':
+                            continue
+                        m = re.match(r'(\d+\:)?\d+\((\d+)\)', msg)
+                        if m is not None:
+                            line = int(m.groups()[1])
+                            errNums[line-1] = errNums[line-1] + (str(i+1),)
+                            #code[line-1] = '%d\t%s' % (i+1, code[line-1])
+                        err = err + "%d %s\n" % (i+1, msg)
+                    errNums = [','.join(n) for n in errNums]
+                    maxlen = max(map(len, errNums))
+                    code = [errNums[i] + " "*(maxlen-len(errNums[i])) + line for i, line in enumerate(code)]
+                    err = err + '\n'.join(code)
+                    raise Exception(err)
+                else:
+                    raise
+        return self.compiled
+
+class VertexShader(Shader):
+    def __init__(self, code):
+        Shader.__init__(self, GL_VERTEX_SHADER, code)
+        
+class FragmentShader(Shader):
+    def __init__(self, code):
+        Shader.__init__(self, GL_FRAGMENT_SHADER, code)
+        
+        
+        
+
+class ShaderProgram(object):
+    names = {}
+    
+    def __init__(self, name, shaders, uniforms=None):
+        self.name = name
+        ShaderProgram.names[name] = self
+        self.shaders = shaders
+        self.prog = None
+        self.blockData = {}
+        self.uniformData = {}
+        
+        ## parse extra options from the shader definition
+        if uniforms is not None:
+            for k,v in uniforms.items():
+                self[k] = v
+        
+    def setBlockData(self, blockName, data):
+        if data is None:
+            del self.blockData[blockName]
+        else:
+            self.blockData[blockName] = data
+
+    def setUniformData(self, uniformName, data):
+        if data is None:
+            del self.uniformData[uniformName]
+        else:
+            self.uniformData[uniformName] = data
+            
+    def __setitem__(self, item, val):
+        self.setUniformData(item, val)
+        
+    def __delitem__(self, item):
+        self.setUniformData(item, None)
+
+    def program(self):
+        if self.prog is None:
+            try:
+                compiled = [s.shader() for s in self.shaders]  ## compile all shaders
+                self.prog = shaders.compileProgram(*compiled)  ## compile program
+            except:
+                self.prog = -1
+                raise
+        return self.prog
+        
+    def __enter__(self):
+        if len(self.shaders) > 0 and self.program() != -1:
+            glUseProgram(self.program())
+            
+            try:
+                ## load uniform values into program
+                for uniformName, data in self.uniformData.items():
+                    loc = self.uniform(uniformName)
+                    if loc == -1:
+                        raise Exception('Could not find uniform variable "%s"' % uniformName)
+                    glUniform1fv(loc, len(data), data)
+                    
+                ### bind buffer data to program blocks
+                #if len(self.blockData) > 0:
+                    #bindPoint = 1
+                    #for blockName, data in self.blockData.items():
+                        ### Program should have a uniform block declared:
+                        ### 
+                        ### layout (std140) uniform blockName {
+                        ###     vec4 diffuse;
+                        ### };
+                        
+                        ### pick any-old binding point. (there are a limited number of these per-program
+                        #bindPoint = 1
+                        
+                        ### get the block index for a uniform variable in the shader
+                        #blockIndex = glGetUniformBlockIndex(self.program(), blockName)
+                        
+                        ### give the shader block a binding point
+                        #glUniformBlockBinding(self.program(), blockIndex, bindPoint)
+                        
+                        ### create a buffer
+                        #buf = glGenBuffers(1)
+                        #glBindBuffer(GL_UNIFORM_BUFFER, buf)
+                        #glBufferData(GL_UNIFORM_BUFFER, size, data, GL_DYNAMIC_DRAW)
+                        ### also possible to use glBufferSubData to fill parts of the buffer
+                        
+                        ### bind buffer to the same binding point
+                        #glBindBufferBase(GL_UNIFORM_BUFFER, bindPoint, buf)
+            except:
+                glUseProgram(0)
+                raise
+                    
+            
+        
+    def __exit__(self, *args):
+        if len(self.shaders) > 0:
+            glUseProgram(0)
+        
+    def uniform(self, name):
+        """Return the location integer for a uniform variable in this program"""
+        return glGetUniformLocation(self.program(), name)
+
+    #def uniformBlockInfo(self, blockName):
+        #blockIndex = glGetUniformBlockIndex(self.program(), blockName)
+        #count = glGetActiveUniformBlockiv(self.program(), blockIndex, GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS)
+        #indices = []
+        #for i in range(count):
+            #indices.append(glGetActiveUniformBlockiv(self.program(), blockIndex, GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES))
+        
+class HeightColorShader(ShaderProgram):
+    def __enter__(self):
+        ## Program should have a uniform block declared:
+        ## 
+        ## layout (std140) uniform blockName {
+        ##     vec4 diffuse;
+        ##     vec4 ambient;
+        ## };
+        
+        ## pick any-old binding point. (there are a limited number of these per-program
+        bindPoint = 1
+        
+        ## get the block index for a uniform variable in the shader
+        blockIndex = glGetUniformBlockIndex(self.program(), "blockName")
+        
+        ## give the shader block a binding point
+        glUniformBlockBinding(self.program(), blockIndex, bindPoint)
+        
+        ## create a buffer
+        buf = glGenBuffers(1)
+        glBindBuffer(GL_UNIFORM_BUFFER, buf)
+        glBufferData(GL_UNIFORM_BUFFER, size, data, GL_DYNAMIC_DRAW)
+        ## also possible to use glBufferSubData to fill parts of the buffer
+        
+        ## bind buffer to the same binding point
+        glBindBufferBase(GL_UNIFORM_BUFFER, bindPoint, buf)
+        
+initShaders()
\ No newline at end of file
diff --git a/vispy/shaders/transforms.py b/vispy/shaders/transforms.py
new file mode 100644
index 0000000..39dea11
--- /dev/null
+++ b/vispy/shaders/transforms.py
@@ -0,0 +1,334 @@
+from vispy.gloo import Program, VertexShader, FragmentShader, VertexBuffer
+#from OpenGL.GL import *
+import OpenGL.GL as gl
+import numpy as np
+
+
+class TransformChain(VertexShader):
+    """ Transform shader built from a series of Transform shaders.
+    
+    Will collapse adjacent transforms if possible.
+    """
+    def __init__(self, transforms=None, function=None):
+        super(TransformChain, self).__init__()
+        if transforms is None:
+            transforms = []
+        self._transforms = transforms
+        self._enabled = True
+        self._function = function
+        
+        
+    @property
+    def transforms(self):
+        return self._transforms
+    
+    @transforms.setter
+    def transforms(self, tr):
+        if self._enabled:
+            raise RuntimeError("Shader is already enabled; cannot modify.")
+        if not isinstance(tr, list):
+            raise TypeError("Transform chain must be a list")
+        self._transforms = tr
+    
+    def append(self, tr):
+        if self._enabled:
+            raise RuntimeError("Shader is already enabled; cannot modify.")
+        self._transforms.append(tr)
+        
+    def prepend(self, tr):
+        if self._enabled:
+            raise RuntimeError("Shader is already enabled; cannot modify.")
+        self._transforms.insert(0, tr)
+        
+    def _enable(self):
+        self._enabled = True
+        self.set_source(self._generate_source())
+        super(TransformChain, self)._enable()
+        
+    def _generate_source(self):
+        transform_indexes = {}
+        variable_decl = []
+        func_calls = []
+        for tr in self._transforms[::-1]:
+            if tr.function not in transform_indexes:
+                ind = 0
+                argtypes = ['vec4'] + [d for a,d,n in tr.arguments]
+                variable_decl.append("vec4 %s(%s);\n" % (tr.function, ", ".join(argtypes)))
+            else:
+                ind = transform_indexes[tr.function]
+            transform_indexes[tr.function] = ind + 1
+            args = []
+            sig_decl = ['vec4']
+            for atype, dtype, name in tr.arguments:
+                varname = '%s_%s_%d' % (tr.function, name, ind)
+                variable_decl.append("%s %s %s;\n" % (atype, dtype, varname))
+                # tell the transform the names of uniforms/atributes that will be passed as arguments.
+                tr._arg_map[name] = varname 
+                args.append(varname)
+                sig_decl.append(dtype)
+            
+                
+            func_calls.append("    pos = %s(pos, %s);\n" % (tr.function, ", ".join(args)))
+            
+        source = (
+        "#version 120\n" +
+        "\n" +
+        "".join(variable_decl) +
+        "\n" +
+        "vec4 %s(vec4 pos) {\n" % self._function +
+        "".join(func_calls) +
+        "    return pos;\n" +
+        "}\n")
+        #print("====================")
+        #print(source)
+        return source
+        
+    def _disable(self):
+        self._enabled = False
+        super(TransformChain, self)._disable()
+        
+    def _on_attach(self, program):
+        attached = set()
+        for tr in self.transforms:
+            name = tr.function
+            if name in attached:
+                continue
+            attached.add(name)
+            program.attach_shader(tr)
+
+    def _on_enabling(self, program):
+        super(TransformChain, self)._on_enabling(program)
+        for tr in self.transforms:
+            tr._apply_variables(program)
+        
+class Transform(VertexShader):
+    """ Generic template class for vertex transformation shaders
+    
+    All Transform classes perform a different type of transformation,
+    and include both GLSL and python code for performing the forward
+    and reverse mappings.
+    
+    In general, transformations should only ever be done using the
+    map() and imap() methods. In some cases, imap() may return None.
+    """
+    source = None    # Default source code for this shader.
+    function = None  # The name of the mapping function as defined in the shader source.
+                     # By default, this is set to ClassName_map
+    arguments = []   # List of arguments accepted by the mapping function.
+                     # The format is (uniform|attribute, type, name)
+    
+    def __init__(self, source=None):
+        if source is None:
+            source = self.__class__.source
+        self._arg_map = {} # {argument_name: attribute/uniform_name} set by TransformChain
+        super(Transform, self).__init__(source)
+    
+    def map(self, coords):
+        pass
+    
+    def imap(self, coords):
+        pass
+    
+    def __mul__(self, tr):
+        #return TransformChain([self, tr])
+        return NotImplemented
+        
+    def __apply_variables(self, program):
+        # Send uniforms/attributes for this transform to currently-enabled program, if any.
+        pass
+
+
+class NullTransform(Transform):
+    """ Transform having no effect on coordinates.
+    """
+    source = """
+        #version 120
+
+        vec4 NullTransform_map(vec4 pos) {
+            return pos;
+        }
+        """
+    function = 'NullTransform_map'
+
+
+
+class STTransform(Transform):
+    """ Transform perfoming only scale and translate
+    """
+    source = """
+        #version 120
+
+        vec4 STTransform_map(vec4 pos, vec3 scale, vec3 translate) {
+            return (pos * vec4(scale, 1)) + vec4(translate, 0);
+        }
+        """
+    function = 'STTransform_map'
+    arguments = [
+        ('uniform', 'vec3', 'scale'),
+        ('uniform', 'vec3', 'translate'),
+        ]
+    
+    def __init__(self, scale=None, translate=None):
+        super(STTransform, self).__init__()
+            
+        self._scale = np.ones(3, dtype=np.float32)
+        self._translate = np.zeros(3, dtype=np.float32)
+        
+        self.scale = (1.0, 1.0, 1.0) if scale is None else scale
+        self.translate = (0.0, 0.0, 0.0) if translate is None else translate
+    
+    def map(self, coords):
+        n = coords.shape[-1]
+        return coords * self.scale[:n] + self.translate[:n]
+    
+    def imap(self, coords):
+        n = coords.shape[-1]
+        return (coords - self.translate[:n]) / self.scale[:n]
+            
+    @property
+    def scale(self):
+        return self._scale.copy()
+    
+    @scale.setter
+    def scale(self, s):
+        self._scale[:len(s)] = s
+        self._scale[len(s):] = 1.0
+        #self._update()
+        
+    @property
+    def translate(self):
+        return self._translate.copy()
+    
+    @translate.setter
+    def translate(self, t):
+        self._translate[:len(t)] = t
+        self._translate[len(t):] = 0.0
+            
+    def _apply_variables(self, program):
+        # Send uniforms to currently-enabled program, if any.
+        program.uniforms[self._arg_map['scale']] = self.scale
+        program.uniforms[self._arg_map['translate']] = self.translate
+    
+    def as_affine(self):
+        m = AffineTransform()
+        m.scale(self.scale)
+        m.translate(self.translate)
+        return m
+    
+    def __mul__(self, tr):
+        if isinstance(tr, STTransform):
+            s = self.scale * tr.scale
+            t = self.translate + (tr.translate * self.scale)
+            return STTransform(scale=s, translate=t)
+        elif isinstance(tr, AffineTransform):
+            return self.as_affine() * tr
+        else:
+            return NotImplemented
+            
+    def __repr__(self):
+        return "<STTransform scale=%s translate=%s>" % (self.scale, self.translate)
+
+class AffineTransform(Transform):
+    def __init__(self):
+        self.matrix = np.eye(4)
+        self.inverse = None
+        Transform.__init__(self, """
+            #version 120
+
+            uniform mat4 transform;
+
+            vec4 global_transform(vec4 pos) {
+                return transform * pos;
+            }
+            """)
+    
+    def map(self, coords):
+        return np.dot(self.matrix, coords)
+    
+    def imap(self, coords):
+        if self.inverse is None:
+            self.inverse = np.linalg.invert(self.matrix)
+        return np.dot(self.inverse, coords)
+
+
+class SRTTransform(Transform):
+    """ Transform performing scale, rotate, and translate
+    """
+    
+class ProjectionTransform(Transform):
+    @classmethod
+    def frustum(cls, l, r, t, b, n, f):
+        pass
+
+class LogTransform(Transform):
+    """ Transform perfoming logarithmic transformation on three axes.
+    """
+    source = """
+        #version 120
+
+        vec4 LogTransform_map(vec4 pos, vec3 base) {
+            vec4 p1 = pos;
+            if(base.x > 1.0)
+                p1.x = log(p1.x) / log(base.x);
+            if(base.y > 1.0)
+                p1.y = log(p1.y) / log(base.y);
+            if(base.z > 1.0)
+                p1.z = log(p1.z) / log(base.z);
+            return p1;
+        }
+        """
+    function = 'LogTransform_map'
+    arguments = [
+        ('uniform', 'vec3', 'base'),
+        ]
+    
+    def __init__(self, base=None):
+        super(LogTransform, self).__init__()
+        self._base = np.zeros(3, dtype=np.float32)
+        self.base = (0.0, 0.0, 0.0) if base is None else base
+        
+    def map(self, coords):
+        ret = np.empty(coords.shape, coords.dtype)
+        base = self.base
+        for i in range(ret.shape[-1]):
+            if base[i] > 1.0:
+                ret[...,i] = np.log(coords[...,i]) / np.log(base[i])
+            else:
+                ret[...,i] = coords[...,i]
+        return ret
+    
+    def imap(self, coords):
+        ret = np.empty(coords.shape, coords.dtype)
+        base = self.base
+        for i in range(ret.shape[-1]):
+            if base[i] > 1.0:
+                ret[...,i] = base[i] ** coords[...,i]
+            else:
+                ret[...,i] = coords[...,i]
+        return ret
+            
+    @property
+    def base(self):
+        return self._base.copy()
+    
+    @base.setter
+    def base(self, s):
+        self._base[:len(s)] = s
+        self._base[len(s):] = 0.0
+            
+    def _apply_variables(self, program):
+        # Send uniforms to currently-enabled program, if any.
+        program.uniforms[self._arg_map['base']] = self.base
+    
+    def __repr__(self):
+        return "<LogTransform base=%s>" % (self.base)
+
+class PolarTransform(Transform):
+    pass
+
+class BilinearTransform(Transform):
+    pass
+
+class WarpTransform(Transform):
+    """ Multiple bilinear transforms in a grid arrangement.
+    """
diff --git a/vispy/util/__init__.py b/vispy/util/__init__.py
new file mode 100644
index 0000000..a7ef253
--- /dev/null
+++ b/vispy/util/__init__.py
@@ -0,0 +1,11 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2013, Vispy Development Team. All Rights Reserved.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+""" Utilities for Vispy. A collection of modules that are used in
+one or more Vispy sub-packages.
+"""
+
+from vispy.util.six import string_types
+
+def is_string(s): return isinstance(s, string_types)
diff --git a/vispy/util/color.py b/vispy/util/color.py
new file mode 100644
index 0000000..12eec53
--- /dev/null
+++ b/vispy/util/color.py
@@ -0,0 +1,5 @@
+"""
+Functions for handling color values
+ - conversion between colorspaces (0.0-1.0 vs 0-255; RGB/RGBA/HSV; etc.)
+ - define shorthand for specifying colors (single letters, short names, etc)
+"""
diff --git a/vispy/util/dataio/__init__.py b/vispy/util/dataio/__init__.py
new file mode 100644
index 0000000..6d0605b
--- /dev/null
+++ b/vispy/util/dataio/__init__.py
@@ -0,0 +1,132 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2013, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+""" Reading and writing of data like images and meshes.
+"""
+
+import os
+import bz2
+import numpy as np
+
+THISDIR = os.path.dirname(os.path.abspath(__file__))
+DATA_DIR = os.path.join(os.path.dirname(THISDIR), '../data')
+
+
+# So we can demo image data without needing an image reading library
+def crate():
+    """ Return an image of a crate (256x256 RGB).
+    """
+    with open(os.path.join(DATA_DIR, 'crate.bz2'), 'rb') as f:
+        bb = f.read()
+    a = np.frombuffer(bz2.decompress(bb), np.uint8)
+    a.shape = 256, 256, 3
+    return a
+
+
+# def _write_image_blob(im, fname):
+#     bb = bz2.compress(im.tostring())
+#     with open(os.path.join(DATA_DIR, fname), 'wb') as f:
+#         f.write(bb) 
+    
+
+
+def read_mesh(fname, format=None):
+    """ Read mesh data from file.
+    returns (vertices, faces, normals, texcoords)
+    texcoords and faces may be None.
+    
+    Mesh files that ship with vispy always work: 'triceratops.obj'.
+    """
+    # Check file
+    if not os.path.isfile(fname):
+        # Maybe we have it?
+        fname_ = os.path.join(DATA_DIR, fname)
+        if os.path.isfile(fname_):
+            fname = fname_
+        else:
+            raise ValueError('File does not exist: %s' % fname)
+    
+    # Check format
+    if format is None:
+        format = os.path.splitext(fname)[1]
+    format = format.strip('. ').upper()
+    
+    if format == 'OBJ':
+        from .wavefront import WavefrontReader
+        return WavefrontReader.read(fname)
+    elif not format:
+        raise ValueError('read_mesh needs could not determine format.')
+    else:
+        raise ValueError('read_mesh does not understand format %s.' % format)
+
+
+def imread(filename, format=None):
+    """ Function to read image data. Requires imageio or PIL.
+    """
+    # Import imageio or PIL
+    imageio = PIL = None
+    try:
+        import imageio
+    except ImportError:
+        try:
+            import PIL.Image
+        except ImportError:
+            pass
+     
+    if imageio is not None:
+        return imageio.imread(filename, format)
+    elif PIL is not None:
+        im = PIL.Image.open(filename)
+        if im.mode == 'P':
+            im = im.convert()
+        # Make numpy array
+        a = np.asarray(im)
+        if len(a.shape)==0:
+            raise MemoryError("Too little memory to convert PIL image to array")
+    else:
+        raise RuntimeError("imread requires the imageio or PIL package.")
+
+
+def imsave(filename, im, format=None):
+    """ Function to save image data. Requires imageio or PIL.
+    """
+    # Import imageio or PIL
+    imageio = PIL = None
+    try:
+        import imageio
+    except ImportError:
+        try:
+            import PIL.Image
+        except ImportError:
+            pass
+     
+    if imageio is not None:
+        return imageio.imsave(filename, im, format)
+    elif PIL is not None:
+        pim = PIL.Image.fromarray(im)
+        pim.save(filename, format)
+    else:
+        raise RuntimeError("imsave requires the imageio or PIL package.")
+
+
+
+def _screenshot(viewport=None):
+    """ Take a screenshot using glReadPixels. Not sure where to put this 
+    yet, so a private function for now. Used in make.py.
+    """
+    import numpy as np
+    from vispy.gloo import gl
+    #gl.glReadBuffer(gl.GL_BACK)  Not avaliable in ES 2.0
+    if viewport is None:
+        viewport = gl.glGetIntegerv(gl.GL_VIEWPORT)
+    x,y,w,h = viewport
+    gl.glPixelStorei(gl.GL_PACK_ALIGNMENT, 1)  # PACK, not UNPACK
+    im = gl.glReadPixels(x, y, w, h, gl.GL_RGB, gl.GL_UNSIGNED_BYTE)
+    gl.glPixelStorei(gl.GL_PACK_ALIGNMENT, 4)
+    # reshape, flip, and return
+    if not isinstance(im, np.ndarray):
+        im = np.frombuffer(im, np.uint8)
+    im.shape = h,w,3
+    im = np.flipud(im)
+    return im
diff --git a/vispy/util/dataio/wavefront.py b/vispy/util/dataio/wavefront.py
new file mode 100644
index 0000000..6b206da
--- /dev/null
+++ b/vispy/util/dataio/wavefront.py
@@ -0,0 +1,365 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2013, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+# This module was taken from visvis
+""" 
+This module produces functionality to read and write wavefront (.OBJ) files.
+
+http://en.wikipedia.org/wiki/Wavefront_.obj_file
+
+The wavefront format is quite powerfull and allows a wide variety of surfaces
+to be described.
+
+This implementation does only supports mesh stuff, so no nurbs etc. Further,
+material properties are ignored, although this might be implemented later,
+
+The classes are written with compatibility of Python3 in mind.
+
+"""
+
+import time
+import numpy as np
+
+
+
+class WavefrontReader(object):
+    
+    def __init__(self, f):
+        self._f = f
+        
+        # Original vertices, normals and texture coords.
+        # These are not necessarily of the same length.
+        self._v = []
+        self._vn = []
+        self._vt = []
+        
+        # Final vertices, normals and texture coords.
+        # All three lists are of the same length, as opengl wants it.
+        self._vertices = []
+        self._normals = []
+        self._texcords = []
+        
+        # The faces, indices to vertex/normal/texcords arrays.
+        self._faces = []
+        
+        # Dictionary to keep track of processed face data, so we can 
+        # convert the original v/vn/vn to the final vertices/normals/texcords.
+        self._facemap = {}
+    
+    
+    @classmethod
+    def read(cls, fname, check='ignored'):
+        """ read(fname)
+        
+        This classmethod is the entry point for reading OBJ files.
+        
+        Parameters
+        ----------
+        fname : string
+            The name of the file to read.
+        
+        """
+        
+        t0 = time.time()
+        
+        # Open file
+        f = open(fname, 'rb')
+        try:
+            reader = WavefrontReader(f)
+            while True:
+                reader.readLine()
+        except EOFError:
+            pass
+        finally:
+            f.close()
+        
+        # Done
+        mesh = reader.finish()
+        #print('reading mesh took ' + str(time.time()-t0) + ' seconds')
+        return mesh
+    
+    
+    def readLine(self):
+        """ The method that reads a line and processes it.
+        """
+        
+        # Read line
+        line = self._f.readline().decode('ascii', 'ignore')
+        if not line:
+            raise EOFError()
+        line = line.strip()
+        
+        if line.startswith('v '):
+            #self._vertices.append( *self.readTuple(line) )
+            self._v.append( self.readTuple(line) )
+        elif line.startswith('vt '):
+            self._vt.append( self.readTuple(line, 3) )
+        elif line.startswith('vn '):
+            self._vn.append( self.readTuple(line) )
+        elif line.startswith('f '):
+            self._faces.append( self.readFace(line) )
+        elif line.startswith('#'):
+            pass # Comment
+        elif line.startswith('mtllib '):
+            print('Notice reading .OBJ: material properties are ignored.')
+        elif line.startswith('g ') or line.startswith('s '):
+            pass # Ignore groups and smoothing groups 
+        elif line.startswith('o '):
+            pass # Ignore object names
+        elif line.startswith('usemtl '):
+            pass # Ignore material
+        elif not line.strip():
+            pass
+        else:
+            print('Notice reading .OBJ: ignoring %s command.' % line.strip())
+    
+    
+    def readTuple(self, line, n=3):
+        """ Reads a tuple of numbers. e.g. vertices, normals or teture coords.
+        """
+        numbers = [num for num in line.split(' ') if num]
+        return [float(num) for num in numbers[1:n+1]]
+    
+    
+    def readFace(self, line):
+        """ Each face consists of three or more sets of indices. Each set
+        consists of 1, 2 or 3 indices to vertices/normals/texcords.
+        """
+        
+        # Get parts (skip first)
+        indexSets = [num for num in line.split(' ') if num][1:]
+        
+        final_face = []
+        for indexSet in indexSets:
+            
+            # Did we see this exact index earlier? If so, it's easy
+            final_index = self._facemap.get(indexSet)
+            if final_index is not None:
+                final_face.append(final_index)
+                continue
+            
+            # If not, we need to sync the vertices/normals/texcords ...
+            
+            # Get and store final index
+            final_index = len(self._vertices)
+            final_face.append(final_index)
+            self._facemap[indexSet] = final_index
+            
+            # What indices were given?
+            indices = [i for i in indexSet.split('/')]
+            
+            # Store new set of vertex/normal/texcords.
+            # If there is a single face that does not specify the texcord
+            # index, the texcords are ignored. Likewise for the normals.
+            if True:
+                vertex_index = self._absint(indices[0], len(self._v))
+                self._vertices.append( self._v[vertex_index] )
+            if self._texcords is not None:
+                if len(indices) > 1 and indices[1]:
+                    texcord_index = self._absint(indices[1], len(self._vt))
+                    self._texcords.append( self._vt[texcord_index] )
+                else:
+                    if self._texcords:
+                        print('Warning reading OBJ: ignoring texture coordinates because it is not specified for all faces.')
+                    self._texcords = None
+            if self._normals is not None:
+                if len(indices) > 2 and indices[2]:
+                    normal_index = self._absint(indices[2], len(self._vn))
+                    self._normals.append( self._vn[normal_index] )
+                else:
+                    if self._normals:
+                        print('Warning reading OBJ: ignoring normals because it is not specified for all faces.')
+                    self._normals = None
+        
+        # Check face
+        if self._faces and len(self._faces[0]) != len(final_face):
+            raise RuntimeError('Vispy requires that all faces are either triangles or quads.')
+        
+        # Done
+        return final_face
+    
+    
+    def _absint(self, i, ref):
+        i = int(i)
+        if i>0 :
+            return i-1
+        else:
+            return ref+i
+    
+    
+    def _calculate_normals(self):
+        vertices, faces = self._vertices, self._faces
+        if faces is None:
+            faces = np.arange(0, vertices.size, dtype=np.uint32)
+        # Build normals
+        T = vertices[faces]
+        N = np.cross(T[::,1 ]-T[::,0], T[::,2]-T[::,0])
+        L = np.sqrt(N[:,0]**2+N[:,1]**2+N[:,2]**2)
+        N /= L[:, np.newaxis]
+        normals = np.zeros(vertices.shape)
+        normals[faces[:,0]] += N
+        normals[faces[:,1]] += N
+        normals[faces[:,2]] += N
+        L = np.sqrt(normals[:,0]**2+normals[:,1]**2+normals[:,2]**2)
+        normals /= L[:, np.newaxis]
+        return normals
+    
+
+    def finish(self):
+        """ Converts gathere lists to numpy arrays and creates 
+        BaseMesh instance.
+        """
+        if True:
+            self._vertices = np.array(self._vertices, 'float32')
+        if self._faces:
+            self._faces = np.array(self._faces, 'uint32')
+        else:
+            # Use vertices only
+            self._vertices = np.array(self._v, 'float32')
+            self._faces = None
+        if self._normals:
+            self._normals = np.array(self._normals, 'float32')
+        else:
+            self._normals = self._calculate_normals()
+        if self._texcords:
+            self._texcords = np.array(self._texcords, 'float32')
+        else:
+            self._texcords = None
+        
+        return self._vertices, self._faces, self._normals, self._texcords
+    
+
+
+class WavefrontWriter(object):
+    
+    def __init__(self, f):
+        self._f = f
+    
+    
+    @classmethod
+    def write(cls, fname, vertices, faces, normals, texcoords, name=''):
+        """ This classmethod is the entry point for writing mesh data to OBJ.
+        
+        Parameters
+        ----------
+        fname : string
+            The filename to write to.
+        vertices : numpy array
+            The vertex data
+        faces : numpy array
+            The face data
+        texcoords : numpy array
+            The texture coordinate per vertex
+        name : string
+            The name of the object (e.g. 'teapot')
+        
+        """
+        
+        # Open file
+        f = open(fname, 'wb')
+        try:
+            writer = WavefrontWriter(f)
+            writer.writeMesh(vertices, faces, normals, texcoords, name)
+        except EOFError:
+            pass
+        finally:
+            f.close()
+    
+    
+    def writeLine(self, text):
+        """ Simple writeLine function to write a line of code to the file.
+        The encoding is done here, and a newline character is added.
+        """
+        text += '\n'
+        self._f.write(text.encode('ascii'))
+    
+    
+    def writeTuple(self, val, what):
+        """ Writes a tuple of numbers (on one line).
+        """
+        # Limit to three values. so RGBA data drops the alpha channel
+        # Format can handle up to 3 texcords
+        val = val[:3]
+        # Make string
+        val = ' '.join([str(v) for v in val])
+        # Write line
+        self.writeLine('%s %s' % (what, val))
+
+    
+    def writeFace(self, val, what='f'):
+        """ Write the face info to the net line.
+        """
+        # OBJ counts from 1
+        val = [v+1 for v in val]
+        # Make string
+        if self._hasValues and self._hasNormals:
+            val = ' '.join(['%i/%i/%i' % (v,v,v) for v in val])
+        elif self._hasNormals:
+            val = ' '.join(['%i//%i' % (v,v) for v in val])
+        elif self._hasValues:
+            val = ' '.join(['%i/%i' % (v,v) for v in val])
+        else:
+            val = ' '.join(['%i' % v for v in val])
+        # Write line
+        self.writeLine('%s %s' % (what, val))
+    
+    
+    def writeMesh(self, vertices, faces, normals, values, name=''):
+        """ Write the given mesh instance.
+        """
+        
+        # Store properties
+        self._hasNormals = normals is not None
+        self._hasValues = values is not None
+        self._hasFaces = faces is not None
+        
+        # Get faces and number of vertices
+        if faces is None:
+            faces = np.arange(len(vertices))
+        
+        # Reshape faces
+        Nfaces = faces.size / 3
+        faces = faces.reshape((Nfaces, 3))
+        
+        # Number of vertices
+        N = vertices.shape[0]
+        
+        # Get string with stats
+        stats = []
+        stats.append('%i vertices' % N)
+        if self._hasValues:
+            stats.append('%i texcords' % N)
+        else:
+            stats.append('no texcords')
+        if self._hasNormals:
+            stats.append('%i normals' % N)
+        else:
+            stats.append('no normals')
+        stats.append('%i faces' % faces.shape[0])
+        
+        
+        # Write header
+        self.writeLine('# Wavefront OBJ file')
+        self.writeLine('# Created by vispy.')
+        self.writeLine('#')
+        if name:
+            self.writeLine('# object %s' % name)
+        else:
+            self.writeLine('# unnamed object')
+        self.writeLine('# %s' % ', '.join(stats))
+        self.writeLine('')
+        
+        # Write data
+        if True:
+            for i in range(N):
+                self.writeTuple(vertices[i], 'v')
+        if self._hasNormals:
+            for i in range(N):
+                self.writeTuple(_normals[i], 'vn')   
+        if self._hasValues:
+            for i in range(N):
+                self.writeTuple(values[i], 'vt')
+        if True:
+            for i in range(faces.shape[0]):
+                self.writeFace(faces[i])
diff --git a/vispy/util/event.py b/vispy/util/event.py
new file mode 100644
index 0000000..d454195
--- /dev/null
+++ b/vispy/util/event.py
@@ -0,0 +1,543 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2013, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+""" 
+The event modules implements the classes that make up the event system.
+The Event class and its subclasses are used to represent "stuff that happens".
+The EventEmitter class provides an interface to connect to events and
+to emit events. The EmitterGroup groups EventEmitter objects.
+
+For more information see http://github.com/vispy/vispy/wiki/API_Events
+
+"""
+
+from __future__ import print_function, division, absolute_import
+
+import sys
+if sys.version_info < (2, 7):
+    from vispy.util.ordereddict import OrderedDict
+else:
+    from collections import OrderedDict
+import inspect
+import weakref
+import vispy
+
+
+
+class Event(object):
+    """Class describing events that occur and can be reacted to with callbacks.
+    Each event instance contains information about a single event that has
+    occurred such as a key press, mouse motion, timer activation, etc.
+    
+    Subclasses: :class:`KeyEvent`, :class:`MouseEvent`, :class:`TouchEvent`, 
+    :class:`StylusEvent`
+    
+    The creation of events and passing of events to the appropriate callback
+    functions in the responsibility of :class:`EventEmitter` instances.
+    
+    Note that each event object has an attribute for each of the input
+    arguments listed below.
+    
+    Input arguments
+    ---------------
+    type : str
+       String indicating the event type (e.g. mouse_press, key_release)
+    native : object (optional)
+       The native GUI event object
+    **kwds : keyword arguments
+        All extra keyword arguments become attributes of the event object.
+    
+    """
+    def __init__(self, type, native=None, **kwds):
+        # stack of all sources this event has been emitted through
+        self._sources = [] 
+        self._handled = False
+        self._blocked = False
+        # Store args
+        self._type = type
+        self._native = native
+        for k,v in kwds.items():
+            setattr(self, k, v)
+    
+    @property
+    def source(self):
+        """ The object that the event applies to (i.e. the source of the event).
+        """
+        return self._sources[-1] if self._sources else None
+
+    @property
+    def sources(self):
+        """ List of objects that the event applies to (i.e. are or have
+        been a source of the event). Can contain multiple objects in case
+        the event traverses a hierarchy of objects.
+        """
+        return self._sources
+    
+    def _push_source(self, source):
+        self._sources.append(source)
+        
+    def _pop_source(self):
+        return self._sources.pop()
+        
+    @property
+    def type(self):
+        # No docstring; documeted in class docstring
+        return self._type
+    
+    @property
+    def native(self):
+        # No docstring; documeted in class docstring
+        return self._native
+    
+    @property
+    def handled(self):
+        """This boolean property indicates whether the event has already been 
+        acted on by an event handler. Since many handlers may have access to the 
+        same events, it is recommended that each check whether the event has 
+        already been handled as well as set handled=True if it decides to 
+        act on the event. 
+        """
+        return self._handled
+    
+    @handled.setter
+    def handled(self, val):
+        self._handled = bool(val)
+        
+    @property
+    def blocked(self):
+        """This boolean property indicates whether the event will be delivered
+        to event callbacks. If it is set to True, then no further callbacks
+        will receive the event. When possible, it is recommended to use
+        Event.handled rather than Event.blocked.
+        """
+        return self._blocked
+    
+    @blocked.setter
+    def blocked(self, val):
+        self._blocked = bool(val)
+    
+    def __repr__(self):
+        # Try to generate a nice string representation of the event that 
+        # includes the interesting properties.
+        global _event_repr_depth # need to keep track of depth because it is
+                                 # very difficult to avoid excessive recursion.
+        _event_repr_depth += 1
+        try:
+            if _event_repr_depth > 2:
+                return "<...>"
+            attrs = []
+            for name in dir(self):
+                if name.startswith('_'):
+                    continue
+                # select only properties
+                if not hasattr(type(self), name) or not isinstance(getattr(type(self), name), property):
+                    continue
+                attr = getattr(self, name)
+                
+                attrs.append("%s=%s" % (name, attr))
+            return "<%s %s>" % (self.__class__.__name__, " ".join(attrs))
+        finally:
+            _event_repr_depth -= 1
+
+_event_repr_depth = 0
+
+class EventEmitter(object):
+    """Encapsulates a list of event callbacks. 
+    
+    Each instance of EventEmitter represents the source of a stream of similar
+    events, such as mouse click events or timer activation events. For
+    example, the following diagram shows the propagation of a mouse click
+    event to the list of callbacks that are registered to listen for that event::
+    
+    
+       User clicks    |Canvas creates             |Canvas invokes its                  |EventEmitter invokes 
+       mouse on       |MouseEvent:                |'mouse_press' EventEmitter:         |callbacks in sequence:
+       Canvas         |                           |                                    |
+                   -->|event = MouseEvent(...) -->|Canvas.events.mouse_press(event) -->|callback1(event)
+                      |                           |                                 -->|callback2(event)
+                      |                           |                                 -->|callback3(event)
+    
+    Callback functions may be added or removed from an EventEmitter using 
+    :func:`connect() <vispy.event.EventEmitter.connect>` or 
+    :func:`disconnect() <vispy.event.EventEmitter.disconnect>`. 
+    
+    Calling an instance of EventEmitter will cause each of its callbacks 
+    to be invoked in sequence. All callbacks are invoked with a single
+    argument which will be an instance of :class:`Event <vispy.event.Event>`. 
+    
+    EventEmitters are generally created by an EmitterGroup instance. 
+    
+    Input arguments
+    ---------------
+    source : object
+        The object that the generated events apply to. All emitted Events will
+        have their .source property set to this value.
+    type : str or None
+        String indicating the event type (e.g. mouse_press, key_release)
+    event_class : subclass of Event
+        The class of events that this emitter will generate.
+        
+        
+    Attributes
+    ----------
+    
+    ignore_callback_errors : bool
+        If True, exceptions raised while invoking callbacks will be caught by
+        the emitter, allowing it to continue invoking other callbacks.
+    print_callback_errors : bool
+        If True, the emitter prints a message and stack trace whenever a 
+        callback raises an exception. (assumes ignore_callback_errors=True)
+        
+    """
+    def __init__(self, source=None, type=None, event_class=Event):
+        self.callbacks = []
+        self.blocked = 0
+        self._emitting = False  # used to detect emitter loops
+        self.source = source
+        self.default_args = {}
+        if type is not None:
+            self.default_args['type'] = type
+            
+        assert inspect.isclass(event_class)
+        self.event_class = event_class
+        
+        self.ignore_callback_errors = True
+        self.print_callback_errors = True
+        
+    @property
+    def source(self):
+        """ The object that events generated by this emitter apply to.
+        """
+        return None if self._source is None else self._source()  # get object behind weakref
+    
+    @source.setter
+    def source(self, s):
+        if s is None:
+            self._source = None
+        else:
+            self._source = weakref.ref(s)
+    
+    def connect(self, callback):
+        """Connect this emitter to a new callback. 
+        
+        *callback* may be either a callable object or a tuple 
+        (object, attr_name) where object.attr_name will point to a callable
+        object.
+        
+        If the callback is already connected, then the request is ignored.
+        
+        The new callback will be added to the beginning of the callback list; 
+        thus the callback that is connected _last_ will be the _first_ to 
+        receive events from the emitter.
+        """
+        if callback in self.callbacks:
+            return
+        self.callbacks.insert(0, callback)
+        return callback  ## allows connect to be used as a decorator
+    
+    def disconnect(self, callback=None):
+        """Disconnect a callback from this emitter.
+        
+        If no callback is specified, then _all_ callbacks are removed.
+        If the callback was not already connected, then the call does nothing.
+        """
+        if callback is None:
+            self.callbacks = []
+        else:
+            try:
+                self.callbacks.remove(callback)
+            except ValueError:
+                pass
+            
+    def __call__(self, *args, **kwds):
+        """ __call__(**kwds)
+        Invoke all callbacks for this emitter.
+        
+        Emit a new event object, created with the given keyword
+        arguments, which must match with the input arguments of the
+        corresponding event class. Note that the 'type' argument is
+        filled in by the emitter.
+        
+        Alternatively, the emitter can also be called with an Event
+        instance as the only argument. In this case, the specified
+        Event will be used rather than generating a new one. This allows 
+        customized Event instances to be emitted and also allows EventEmitters
+        to be chained by connecting one directly to another.
+        
+        Note that the same Event instance is sent to all callbacks.
+        This allows some level of communication between the callbacks
+        (notably, via Event.handled) but also requires that callbacks
+        be careful not to inadvertently modify the Event. 
+        """
+        if self._emitting:
+            raise RuntimeError('EventEmitter loop detected!')
+        
+        # create / massage event as needed
+        event = self._prepare_event(*args, **kwds)
+        
+        # Add our source to the event; remove it after all callbacks have been invoked.
+        event._push_source(self.source)
+        self._emitting = True
+        try:
+            if self.blocked > 0:
+                return event
+            
+            for cb in self.callbacks:
+                if isinstance(cb, tuple):
+                    cb = getattr(cb[0], cb[1], None)
+                    if cb is None:
+                        continue
+                    
+                try:
+                    cb(event)
+                except:
+                    # get traceback and store (so we can do postmortem debugging)
+                    type, value, tb = sys.exc_info()
+                    tb = tb.tb_next # Skip *this* frame
+                    sys.last_type = type
+                    sys.last_value = value
+                    sys.last_traceback = tb
+                    # Handle
+                    if self.ignore_callback_errors:
+                        if self.print_callback_errors:
+                            sys.excepthook(type, value, tb)
+                            print("Error invoking callback for event: %s" % str(event))
+                    else:
+                        raise
+                
+                if event.blocked:
+                    break
+        finally:
+            self._emitting = False
+            if event._pop_source() != self.source:
+                raise RuntimeError("Event source-stack mismatch.")
+            
+        return event
+    
+    def _prepare_event(self, *args, **kwds):
+        ## When emitting, this method is called to create or otherwise alter
+        ## an event before it is sent to callbacks. Subclasses may extend
+        ## this method to make custom modifications to the event.
+        if len(args) == 1 and not kwds and isinstance(args[0], Event):
+            event = args[0]
+            # Ensure that the given event matches what we want to emit
+            assert isinstance(event, self.event_class)
+        elif not args:
+            args = self.default_args.copy()
+            args.update(kwds)
+            event = self.event_class(**args)
+        else:
+            raise ValueError("Event emitters can be called with an Event instance or with keyword arguments only.")
+        return event
+    
+    def block(self):
+        """Block this emitter. Any attempts to emit an event while blocked
+        will be silently ignored. 
+        
+        Calls to block are cumulative; the emitter must be unblocked the same
+        number of times as it is blocked. 
+        """
+        self.blocked += 1
+        
+    def unblock(self):
+        """ Unblock this emitter. See :func:`event.EventEmitter.block`.
+        """
+        self.blocked = max(0, self.blocked-1)
+
+    def blocker(self):
+        """Return an EventBlocker to be used in 'with' statements::
+        
+               with emitter.blocker():
+                   ..do stuff; no events will be emitted..
+        
+        """
+        return EventBlocker(self)
+
+
+class EmitterGroup(EventEmitter):
+    """EmitterGroup instances manage a set of related 
+    :class:`EventEmitters <vispy.event.EventEmitter>`. 
+    Its primary purpose is to provide organization for objects
+    that make use of multiple emitters and to reduce the boilerplate code needed
+    to initialize those emitters with default connections.
+    
+    EmitterGroup instances are usually stored as an 'events' attribute on 
+    objects that use multiple emitters. For example::
+                     
+         EmitterGroup  EventEmitter
+                 |       |
+        Canvas.events.mouse_press
+        Canvas.events.resized
+        Canvas.events.key_press
+    
+    EmitterGroup is also a subclass of 
+    :class:`EventEmitters <vispy.event.EventEmitter>`, 
+    allowing it to emit its own
+    events. Any callback that connects directly to the EmitterGroup will 
+    receive _all_ of the events generated by the group's emitters.
+    
+    Input arguments
+    ---------------
+    source : object
+        The object that the generated events apply to.  
+    auto_connect : bool
+        If *auto_connect* is True (default), then one connection will
+        be made for each emitter that looks like 
+        :func:`emitter.connect((source, 'on_'+event_name)) 
+        <vispy.event.EventEmitter.connect>`.
+        This provides a simple mechanism for automatically connecting a large
+        group of emitters to default callbacks.
+    emitters : keyword arguments
+        See the :func:`add <vispy.event.EmitterGroup.add>` method.
+    
+    """
+    def __init__(self, source=None, auto_connect=True, **emitters):
+        EventEmitter.__init__(self, source)
+        
+        self.auto_connect = auto_connect
+        self.auto_connect_format = "on_%s"
+        self._emitters = OrderedDict()
+        self._emitters_connected = False  # whether the sub-emitters have 
+                                          # been connected to the group
+                                          
+        self.add(**emitters)
+    
+    def __getitem__(self, name):
+        """
+        Return the emitter assigned to the specified name. 
+        Note that emitters may also be retrieved as an attribute of the 
+        EmitterGroup.
+        """
+        return self._emitters[name]
+        
+    def __setitem__(self, name, emitter):
+        """
+        Alias for EmitterGroup.add(name=emitter)
+        """
+        self.add(**{name: emitter})
+    
+    
+    def add(self, auto_connect=None, **kwds):
+        """ Add one or more EventEmitter instances to this emitter group.
+        Each keyword argument may be specified as either an EventEmitter 
+        instance or an Event subclass, in which case an EventEmitter will be 
+        generated automatically. Thus::
+        
+            # This statement: 
+            group.add(mouse_press=MouseEvent, 
+                      mouse_release=MouseEvent)
+            
+            # ..is equivalent to this statement:
+            group.add(mouse_press=EventEmitter(group.source, 'mouse_press', MouseEvent), 
+                      mouse_release=EventEmitter(group.source, 'mouse_press', MouseEvent))
+        """
+        if auto_connect is None:
+            auto_connect = self.auto_connect
+        
+        # check all names before adding anything
+        for name in kwds:
+            if name in self._emitters:
+                raise ValueError("EmitterGroup already has an emitter named '%s'" % name)
+            elif hasattr(self, name):
+                raise ValueError("The name '%s' cannot be used as an emitter; it is already an attribute of EmitterGroup" % name)
+            
+        # add each emitter specified in the keyword arguments
+        for name, emitter in kwds.items():
+            if emitter is None:
+                emitter = Event
+            
+            if inspect.isclass(emitter) and issubclass(emitter, Event):
+                emitter = EventEmitter(source=self.source, type=name, event_class=emitter)
+            elif not isinstance(emitter, EventEmitter):
+                raise Exception('Emitter must be specified as either an EventEmitter instance or Event subclass')
+            
+            emitter.source = self.source # give this emitter the same source as the group.
+            
+            setattr(self, name, emitter)
+            self._emitters[name] = emitter
+            
+            if auto_connect and self.source is not None:
+                emitter.connect((self.source, self.auto_connect_format % name))
+                
+            # If emitters are connected to the group already, then this one should
+            # be connected as well.
+            if self._emitters_connected:
+                emitter.connect(self)
+                
+
+    @property
+    def emitters(self):
+        """ List of current emitters in this group.
+        """
+        return self._emitters
+    
+    def __iter__(self):
+        """
+        Iterates over the names of emitters in this group.
+        """
+        for k in self._emitters:
+            yield k
+    
+    def block_all(self):
+        """ Block all emitters in this group.
+        """
+        self.block()
+        for em in self._emitters.values():
+            em.block()
+    
+    def unblock_all(self):
+        """ Unblock all emitters in this group.
+        """
+        self.unblock()
+        for em in self._emitters.values():
+            em.unblock()
+    
+    def connect(self, callback):
+        """ Connect the callback to the event group. The callback will receive
+        events from _all_ of the emitters in the group.
+        
+        See :func:`EventEmitter.connect() <vispy.event.EventEmitter.connect>` for
+        arguments.
+        """
+        self._connect_emitters(True)
+        return EventEmitter.connect(self, callback)
+
+    def disconnect(self, callback=None):
+        """ Disconnect the callback from this group. See 
+        :func:`connect() <vispy.event.EmitterGroup.connect>` and 
+        :func:`EventEmitter.connect() <vispy.event.EventEmitter.connect>` for
+        more information.
+        """
+        ret = EventEmitter.disconnect(self, callback)
+        if len(self.callbacks) == 0:
+            self._connect_emitters(False)
+        return ret
+    
+    def _connect_emitters(self, connect):
+        # Connect/disconnect all sub-emitters from the group. This allows the
+        # group to emit an event whenever _any_ of the sub-emitters emit, 
+        # while simultaneously eliminating the overhead if nobody is listening.
+        if connect:
+            for emitter in self:
+                self[emitter].connect(self)
+        else:
+            for emitter in self:
+                self[emitter].disconnect(self)
+            
+        self._emitters_connected = connect
+        
+            
+class EventBlocker(object):
+    """ Represents a block for an EventEmitter to be used in a context
+    manager (i.e. 'with' statement).
+    """
+    def __init__(self, target):
+        self.target = target
+        
+    def __enter__(self):
+        self.target.block()
+        
+    def __exit__(self, *args):
+        self.target.unblock()
+
diff --git a/vispy/util/keys.py b/vispy/util/keys.py
new file mode 100644
index 0000000..3c3bce7
--- /dev/null
+++ b/vispy/util/keys.py
@@ -0,0 +1,105 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2013, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+""" Define constants for keys.
+
+Each key constant is defined as a Key object, which allows comparison with
+strings (e.g. 'A', 'Escape', 'Shift'). This enables handling of key events
+without using the key constants explicitly (e.g. ``if ev.key == 'Left':``).
+
+In addition, key objects that represent characters can be matched to
+the integer ordinal (e.g. 32 for space, 65 for A). This behavior is mainly
+intended as a compatibility measure.
+
+"""
+
+from __future__ import print_function, division, absolute_import
+
+import sys
+from vispy.util.six import string_types
+
+
+# class Key(str):
+#     """ A string that can only be compared to another string. 
+#     More strict that a simple string to avoid bugs trying to compare to int.
+#     """
+#     def __new__(cls, s, *alternatives):
+#         if not isinstance(s, string_types):
+#             raise ValueError('KeyConsant must be a string')
+#         s = str.__new__(cls, s)
+#         s._alternatives = alternatives
+#         return s
+#     def __eq__(self, other):
+#         if not isinstance(other, string_types):
+#             raise ValueError('Key constants can only be compared to strings.')
+#         return XX.__eq__(self, other) or other in self._alternatives
+
+class Key:
+    """ A Key object represents the identity of a certain key. It
+    represents one or more names that the key in question is known by.
+    
+    A Key object can be compared to one of its string names (case
+    insensitive), to the integer ordinal of the key (only for keys that
+    represent characters), and to another Key instance.
+    """
+    def __init__(self, *names):
+        self._names = names
+        self._names_upper = tuple([v.upper() for v in names])
+    
+    @property
+    def name(self):
+        """ The name of the key.
+        """
+        return self._names[0]
+    
+    def __repr__(self):        
+        return "<Key %s>" % ', '.join([repr(v) for v in self._names])
+    
+    def __eq__(self, other):
+        if isinstance(other, string_types):
+            return other.upper() in self._names_upper
+        elif isinstance(other, Key):
+            return self._names[0] == other
+        elif isinstance(other, int):
+            return other in [ord(v) for v in self._names_upper if len(v)==1]
+        else:
+            raise ValueError('Key constants can only be compared to str, int and Key.')
+
+
+SHIFT = Key('Shift')
+CONTROL = Key('Control')
+ALT = Key('Alt')
+META = Key('Meta')  # That Mac thingy
+
+UP = Key('Up')
+DOWN = Key('Down')
+LEFT = Key('Left')
+RIGHT = Key('Right')
+PAGEUP = Key('PageUp')
+PAGEDOWN = Key('PageDown')
+
+INSERT = Key('Insert')
+DELETE = Key('Delete')
+HOME = Key('Home')
+END = Key('End')
+
+ESCAPE = Key('Escape')
+BACKSPACE = Key('Backspace')
+
+F1 = Key('F1')
+F2 = Key('F2')
+F3 = Key('F3')
+F4 = Key('F4')
+F5 = Key('F5')
+F6 = Key('F6')
+F7 = Key('F7')
+F8 = Key('F8')
+F9 = Key('F9')
+F10 = Key('F10')
+F11 = Key('F11')
+F12 = Key('F12')
+
+SPACE = Key('Space', ' ')
+ENTER = Key('Enter', 'Return', '\n')
+TAB = Key('Tab', '\t')
diff --git a/vispy/util/ordereddict.py b/vispy/util/ordereddict.py
new file mode 100644
index 0000000..5b0303f
--- /dev/null
+++ b/vispy/util/ordereddict.py
@@ -0,0 +1,127 @@
+# Copyright (c) 2009 Raymond Hettinger
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+#     The above copyright notice and this permission notice shall be
+#     included in all copies or substantial portions of the Software.
+#
+#     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+#     EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+#     OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+#     NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+#     HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+#     WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+#     FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+#     OTHER DEALINGS IN THE SOFTWARE.
+
+from UserDict import DictMixin
+
+class OrderedDict(dict, DictMixin):
+
+    def __init__(self, *args, **kwds):
+        if len(args) > 1:
+            raise TypeError('expected at most 1 arguments, got %d' % len(args))
+        try:
+            self.__end
+        except AttributeError:
+            self.clear()
+        self.update(*args, **kwds)
+
+    def clear(self):
+        self.__end = end = []
+        end += [None, end, end]         # sentinel node for doubly linked list
+        self.__map = {}                 # key --> [key, prev, next]
+        dict.clear(self)
+
+    def __setitem__(self, key, value):
+        if key not in self:
+            end = self.__end
+            curr = end[1]
+            curr[2] = end[1] = self.__map[key] = [key, curr, end]
+        dict.__setitem__(self, key, value)
+
+    def __delitem__(self, key):
+        dict.__delitem__(self, key)
+        key, prev, next = self.__map.pop(key)
+        prev[2] = next
+        next[1] = prev
+
+    def __iter__(self):
+        end = self.__end
+        curr = end[2]
+        while curr is not end:
+            yield curr[0]
+            curr = curr[2]
+
+    def __reversed__(self):
+        end = self.__end
+        curr = end[1]
+        while curr is not end:
+            yield curr[0]
+            curr = curr[1]
+
+    def popitem(self, last=True):
+        if not self:
+            raise KeyError('dictionary is empty')
+        if last:
+            key = reversed(self).next()
+        else:
+            key = iter(self).next()
+        value = self.pop(key)
+        return key, value
+
+    def __reduce__(self):
+        items = [[k, self[k]] for k in self]
+        tmp = self.__map, self.__end
+        del self.__map, self.__end
+        inst_dict = vars(self).copy()
+        self.__map, self.__end = tmp
+        if inst_dict:
+            return (self.__class__, (items,), inst_dict)
+        return self.__class__, (items,)
+
+    def keys(self):
+        return list(self)
+
+    setdefault = DictMixin.setdefault
+    update = DictMixin.update
+    pop = DictMixin.pop
+    values = DictMixin.values
+    items = DictMixin.items
+    iterkeys = DictMixin.iterkeys
+    itervalues = DictMixin.itervalues
+    iteritems = DictMixin.iteritems
+
+    def __repr__(self):
+        if not self:
+            return '%s()' % (self.__class__.__name__,)
+        return '%s(%r)' % (self.__class__.__name__, self.items())
+
+    def copy(self):
+        return self.__class__(self)
+
+    @classmethod
+    def fromkeys(cls, iterable, value=None):
+        d = cls()
+        for key in iterable:
+            d[key] = value
+        return d
+
+    def __eq__(self, other):
+        if isinstance(other, OrderedDict):
+            if len(self) != len(other):
+                return False
+            for p, q in  zip(self.items(), other.items()):
+                if p != q:
+                    return False
+            return True
+        return dict.__eq__(self, other)
+
+    def __ne__(self, other):
+        return not self == other
diff --git a/vispy/util/ptime.py b/vispy/util/ptime.py
new file mode 100644
index 0000000..36ef0da
--- /dev/null
+++ b/vispy/util/ptime.py
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2013, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+"""
+ptime.py -  Precision time function made os-independent (should have been taken care of by python)
+"""
+
+from __future__ import print_function, division, absolute_import
+
+import sys
+import time as systime
+START_TIME = None
+time = None
+
+def winTime():
+    """Return the current time in seconds with high precision (windows version, use Manager.time() to stay platform independent)."""
+    return systime.clock() + START_TIME
+    #return systime.time()
+
+def unixTime():
+    """Return the current time in seconds with high precision (unix version, use Manager.time() to stay platform independent)."""
+    return systime.time()
+
+if sys.platform.startswith('win'):
+    cstart = systime.clock()  ### Required to start the clock in windows
+    START_TIME = systime.time() - cstart
+    
+    time = winTime
+else:
+    time = unixTime
+
diff --git a/vispy/util/six.py b/vispy/util/six.py
new file mode 100644
index 0000000..eae3145
--- /dev/null
+++ b/vispy/util/six.py
@@ -0,0 +1,404 @@
+"""Utilities for writing code that runs on Python 2 and 3"""
+
+# Copyright (c) 2010-2013 Benjamin Peterson
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to
+# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+# the Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+import operator
+import sys
+import types
+
+__author__ = "Benjamin Peterson <benjamin at python.org>"
+__version__ = "1.3.0"
+
+
+# True if we are running on Python 3.
+PY3 = sys.version_info[0] == 3
+
+if PY3:
+    string_types = str,
+    integer_types = int,
+    class_types = type,
+    text_type = str
+    binary_type = bytes
+
+    MAXSIZE = sys.maxsize
+else:
+    string_types = basestring,
+    integer_types = (int, long)
+    class_types = (type, types.ClassType)
+    text_type = unicode
+    binary_type = str
+
+    if sys.platform.startswith("java"):
+        # Jython always uses 32 bits.
+        MAXSIZE = int((1 << 31) - 1)
+    else:
+        # It's possible to have sizeof(long) != sizeof(Py_ssize_t).
+        class X(object):
+            def __len__(self):
+                return 1 << 31
+        try:
+            len(X())
+        except OverflowError:
+            # 32-bit
+            MAXSIZE = int((1 << 31) - 1)
+        else:
+            # 64-bit
+            MAXSIZE = int((1 << 63) - 1)
+            del X
+
+
+def _add_doc(func, doc):
+    """Add documentation to a function."""
+    func.__doc__ = doc
+
+
+def _import_module(name):
+    """Import module, returning the module after the last dot."""
+    __import__(name)
+    return sys.modules[name]
+
+
+class _LazyDescr(object):
+
+    def __init__(self, name):
+        self.name = name
+
+    def __get__(self, obj, tp):
+        result = self._resolve()
+        setattr(obj, self.name, result)
+        # This is a bit ugly, but it avoids running this again.
+        delattr(tp, self.name)
+        return result
+
+
+class MovedModule(_LazyDescr):
+
+    def __init__(self, name, old, new=None):
+        super(MovedModule, self).__init__(name)
+        if PY3:
+            if new is None:
+                new = name
+            self.mod = new
+        else:
+            self.mod = old
+
+    def _resolve(self):
+        return _import_module(self.mod)
+
+
+class MovedAttribute(_LazyDescr):
+
+    def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None):
+        super(MovedAttribute, self).__init__(name)
+        if PY3:
+            if new_mod is None:
+                new_mod = name
+            self.mod = new_mod
+            if new_attr is None:
+                if old_attr is None:
+                    new_attr = name
+                else:
+                    new_attr = old_attr
+            self.attr = new_attr
+        else:
+            self.mod = old_mod
+            if old_attr is None:
+                old_attr = name
+            self.attr = old_attr
+
+    def _resolve(self):
+        module = _import_module(self.mod)
+        return getattr(module, self.attr)
+
+
+
+class _MovedItems(types.ModuleType):
+    """Lazy loading of moved objects"""
+
+
+_moved_attributes = [
+    MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"),
+    MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"),
+    MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"),
+    MovedAttribute("map", "itertools", "builtins", "imap", "map"),
+    MovedAttribute("reload_module", "__builtin__", "imp", "reload"),
+    MovedAttribute("reduce", "__builtin__", "functools"),
+    MovedAttribute("StringIO", "StringIO", "io"),
+    MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"),
+    MovedAttribute("zip", "itertools", "builtins", "izip", "zip"),
+
+    MovedModule("builtins", "__builtin__"),
+    MovedModule("configparser", "ConfigParser"),
+    MovedModule("copyreg", "copy_reg"),
+    MovedModule("http_cookiejar", "cookielib", "http.cookiejar"),
+    MovedModule("http_cookies", "Cookie", "http.cookies"),
+    MovedModule("html_entities", "htmlentitydefs", "html.entities"),
+    MovedModule("html_parser", "HTMLParser", "html.parser"),
+    MovedModule("http_client", "httplib", "http.client"),
+    MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"),
+    MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"),
+    MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"),
+    MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"),
+    MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"),
+    MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"),
+    MovedModule("cPickle", "cPickle", "pickle"),
+    MovedModule("queue", "Queue"),
+    MovedModule("reprlib", "repr"),
+    MovedModule("socketserver", "SocketServer"),
+    MovedModule("tkinter", "Tkinter"),
+    MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"),
+    MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"),
+    MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"),
+    MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"),
+    MovedModule("tkinter_tix", "Tix", "tkinter.tix"),
+    MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"),
+    MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"),
+    MovedModule("tkinter_colorchooser", "tkColorChooser",
+                "tkinter.colorchooser"),
+    MovedModule("tkinter_commondialog", "tkCommonDialog",
+                "tkinter.commondialog"),
+    MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"),
+    MovedModule("tkinter_font", "tkFont", "tkinter.font"),
+    MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"),
+    MovedModule("tkinter_tksimpledialog", "tkSimpleDialog",
+                "tkinter.simpledialog"),
+    MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"),
+    MovedModule("winreg", "_winreg"),
+]
+for attr in _moved_attributes:
+    setattr(_MovedItems, attr.name, attr)
+del attr
+
+moves = sys.modules[__name__ + ".moves"] = _MovedItems("moves")
+
+
+def add_move(move):
+    """Add an item to six.moves."""
+    setattr(_MovedItems, move.name, move)
+
+
+def remove_move(name):
+    """Remove item from six.moves."""
+    try:
+        delattr(_MovedItems, name)
+    except AttributeError:
+        try:
+            del moves.__dict__[name]
+        except KeyError:
+            raise AttributeError("no such move, %r" % (name,))
+
+
+if PY3:
+    _meth_func = "__func__"
+    _meth_self = "__self__"
+
+    _func_closure = "__closure__"
+    _func_code = "__code__"
+    _func_defaults = "__defaults__"
+    _func_globals = "__globals__"
+
+    _iterkeys = "keys"
+    _itervalues = "values"
+    _iteritems = "items"
+    _iterlists = "lists"
+else:
+    _meth_func = "im_func"
+    _meth_self = "im_self"
+
+    _func_closure = "func_closure"
+    _func_code = "func_code"
+    _func_defaults = "func_defaults"
+    _func_globals = "func_globals"
+
+    _iterkeys = "iterkeys"
+    _itervalues = "itervalues"
+    _iteritems = "iteritems"
+    _iterlists = "iterlists"
+
+
+try:
+    advance_iterator = next
+except NameError:
+    def advance_iterator(it):
+        return it.next()
+next = advance_iterator
+
+
+try:
+    callable = callable
+except NameError:
+    def callable(obj):
+        return any("__call__" in klass.__dict__ for klass in type(obj).__mro__)
+
+
+if PY3:
+    def get_unbound_function(unbound):
+        return unbound
+
+    Iterator = object
+else:
+    def get_unbound_function(unbound):
+        return unbound.im_func
+
+    class Iterator(object):
+
+        def next(self):
+            return type(self).__next__(self)
+
+    callable = callable
+_add_doc(get_unbound_function,
+         """Get the function out of a possibly unbound function""")
+
+
+get_method_function = operator.attrgetter(_meth_func)
+get_method_self = operator.attrgetter(_meth_self)
+get_function_closure = operator.attrgetter(_func_closure)
+get_function_code = operator.attrgetter(_func_code)
+get_function_defaults = operator.attrgetter(_func_defaults)
+get_function_globals = operator.attrgetter(_func_globals)
+
+
+def iterkeys(d, **kw):
+    """Return an iterator over the keys of a dictionary."""
+    return iter(getattr(d, _iterkeys)(**kw))
+
+def itervalues(d, **kw):
+    """Return an iterator over the values of a dictionary."""
+    return iter(getattr(d, _itervalues)(**kw))
+
+def iteritems(d, **kw):
+    """Return an iterator over the (key, value) pairs of a dictionary."""
+    return iter(getattr(d, _iteritems)(**kw))
+
+def iterlists(d, **kw):
+    """Return an iterator over the (key, [values]) pairs of a dictionary."""
+    return iter(getattr(d, _iterlists)(**kw))
+
+
+if PY3:
+    def b(s):
+        return s.encode("latin-1")
+    def u(s):
+        return s
+    if sys.version_info[1] <= 1:
+        def int2byte(i):
+            return bytes((i,))
+    else:
+        # This is about 2x faster than the implementation above on 3.2+
+        int2byte = operator.methodcaller("to_bytes", 1, "big")
+    import io
+    StringIO = io.StringIO
+    BytesIO = io.BytesIO
+else:
+    def b(s):
+        return s
+    def u(s):
+        return unicode(s, "unicode_escape")
+    int2byte = chr
+    import StringIO
+    StringIO = BytesIO = StringIO.StringIO
+_add_doc(b, """Byte literal""")
+_add_doc(u, """Text literal""")
+
+
+if PY3:
+    import builtins
+    exec_ = getattr(builtins, "exec")
+
+
+    def reraise(tp, value, tb=None):
+        if value.__traceback__ is not tb:
+            raise value.with_traceback(tb)
+        raise value
+
+
+    print_ = getattr(builtins, "print")
+    del builtins
+
+else:
+    def exec_(_code_, _globs_=None, _locs_=None):
+        """Execute code in a namespace."""
+        if _globs_ is None:
+            frame = sys._getframe(1)
+            _globs_ = frame.f_globals
+            if _locs_ is None:
+                _locs_ = frame.f_locals
+            del frame
+        elif _locs_ is None:
+            _locs_ = _globs_
+        exec("""exec _code_ in _globs_, _locs_""")
+
+
+    exec_("""def reraise(tp, value, tb=None):
+    raise tp, value, tb
+""")
+
+
+    def print_(*args, **kwargs):
+        """The new-style print function."""
+        fp = kwargs.pop("file", sys.stdout)
+        if fp is None:
+            return
+        def write(data):
+            if not isinstance(data, basestring):
+                data = str(data)
+            fp.write(data)
+        want_unicode = False
+        sep = kwargs.pop("sep", None)
+        if sep is not None:
+            if isinstance(sep, unicode):
+                want_unicode = True
+            elif not isinstance(sep, str):
+                raise TypeError("sep must be None or a string")
+        end = kwargs.pop("end", None)
+        if end is not None:
+            if isinstance(end, unicode):
+                want_unicode = True
+            elif not isinstance(end, str):
+                raise TypeError("end must be None or a string")
+        if kwargs:
+            raise TypeError("invalid keyword arguments to print()")
+        if not want_unicode:
+            for arg in args:
+                if isinstance(arg, unicode):
+                    want_unicode = True
+                    break
+        if want_unicode:
+            newline = unicode("\n")
+            space = unicode(" ")
+        else:
+            newline = "\n"
+            space = " "
+        if sep is None:
+            sep = space
+        if end is None:
+            end = newline
+        for i, arg in enumerate(args):
+            if i:
+                write(sep)
+            write(arg)
+        write(end)
+
+_add_doc(reraise, """Reraise an exception.""")
+
+
+def with_metaclass(meta, base=object):
+    """Create a base class with a metaclass."""
+    return meta("NewBase", (base,), {})
diff --git a/vispy/util/test/test_emitter_group.py b/vispy/util/test/test_emitter_group.py
new file mode 100644
index 0000000..d6b58ba
--- /dev/null
+++ b/vispy/util/test/test_emitter_group.py
@@ -0,0 +1,205 @@
+from vispy.util.event import Event, EventEmitter, EmitterGroup
+import unittest
+import copy
+import functools
+
+class BasicEvent(Event):
+    pass
+
+class TypedEvent(Event):
+    def __init__(self, **kwds):
+        kwds['type'] = 'typed_event'
+        Event.__init__(self, **kwds)
+
+class TestGroups(unittest.TestCase):
+    def test_group_construction(self):
+        """EmitterGroup basic construction"""
+        grp = EmitterGroup(em1=Event,
+                           em2=BasicEvent,
+                           em3=TypedEvent)
+                           
+        grp.em1.connect(self.record_event)
+        grp.em2.connect(self.record_event)
+        grp.em3.connect(self.record_event)
+        self.result = None
+        ev = grp.em1()
+        self.assert_result(event=ev, type='em1', event_class=Event)
+        ev = grp.em2()
+        self.assert_result(event=ev, type='em2', event_class=BasicEvent)
+        ev = grp.em3()
+        self.assert_result(event=ev, type='typed_event', event_class=TypedEvent)
+        
+    def test_group_add_emitter(self):
+        """EmitterGroup.add"""
+        grp = EmitterGroup(em1=Event)
+        grp.em1.connect(self.record_event)
+        self.result = None
+        ev = grp.em1()
+        self.assert_result(event=ev, type='em1')
+        
+        grp.add(em2=BasicEvent)
+        grp.em2.connect(self.record_event)
+        ev = grp.em2()
+        self.assert_result(event=ev, type='em2', event_class=BasicEvent)
+        
+        grp.add(em3=TypedEvent)
+        grp.em3.connect(self.record_event)
+        ev = grp.em3(test_key=2)
+        self.assert_result(event=ev, type='typed_event', event_class=TypedEvent, test_key=2)
+        
+        try:
+            grp.add(em3=Event)
+            assert False, "Double-added emitter"
+        except ValueError:
+            pass
+        
+        try:
+            grp.add(add=Event)
+            assert False, "Added event with invalid name"
+        except ValueError:
+            pass
+        
+    def test_group_block(self):
+        """EmitterGroup.block_all"""
+        grp = EmitterGroup(em1=Event, em2=Event)
+        def cb(ev):
+            self.result = 1
+        grp.em1.connect(self.record_event)
+        grp.em2.connect(self.record_event)
+        grp.connect(cb)
+        
+        self.result = None
+        grp.block_all()
+        try:
+            grp.em1()
+            grp.em2()
+            grp(type='test_event')
+        finally:
+            grp.unblock_all()
+        assert self.result is None
+        
+    def test_group_disconnect(self):
+        """EmitterGroup.disconnect"""
+        grp = EmitterGroup(em1=Event)
+        
+        assert len(grp.em1.callbacks) == 0, grp.em1.callbacks
+        grp.connect(self.record_event)
+        assert len(grp.em1.callbacks) == 1
+        grp.add(em2=Event)
+        assert len(grp.em2.callbacks) == 1
+        grp.disconnect()
+        assert len(grp.em1.callbacks) == 0
+        assert len(grp.em2.callbacks) == 0
+        
+    def test_group_autoconnect(self):
+        """EmitterGroup auto-connect"""
+        class Source:
+            def on_em1(self, ev):
+                self.result = 1
+            def em2_event(self, ev):
+                self.result = 2
+            def em3_event(self, ev):
+                self.result = 3
+        src = Source()
+        grp = EmitterGroup(source=src, em1=Event, auto_connect=False)
+        src.result = None
+        grp.em1()
+        assert src.result is None
+        
+        grp = EmitterGroup(source=src, em1=Event, auto_connect=True)
+        src.result = None
+        grp.em1()
+        assert src.result == 1
+        
+        grp.auto_connect_format = "%s_event"
+        grp.add(em2=Event)
+        src.result = None
+        grp.em2()
+        assert src.result == 2
+        
+        grp.add(em3=Event, auto_connect=False)
+        src.result = None
+        grp.em3()
+        assert src.result is None
+        
+        
+    def test_add_custom_emitter(self):
+        class Emitter(EventEmitter):
+            def _prepare_event(self, *args, **kwds):
+                ev = super(Emitter, self)._prepare_event(*args, **kwds)
+                ev.test_key = 1
+                return ev
+
+        class Source:
+            pass
+        src = Source()
+
+        grp = EmitterGroup(source=src, em1=Emitter(type='test_event1'))
+        grp.em1.connect(self.record_event)
+        self.result = None
+        ev = grp.em1()
+        self.assert_result(event=ev, test_key=1, type='test_event1', source=src)
+        
+        grp.add(em2=Emitter(type='test_event2'))
+        grp.em2.connect(self.record_event)
+        self.result = None
+        ev = grp.em2()
+        self.assert_result(event=ev, test_key=1, type='test_event2', source=src)
+        
+    def test_group_connect(self):
+        grp = EmitterGroup(source=self, em1=Event)
+        grp.connect(self.record_event)
+        self.result = None
+        ev = grp.em1(test_key=1)
+        self.assert_result(event=ev, source=self, sources=[self, self], test_key=1)
+                
+    def record_event(self, ev, key=None):
+        # get a copy of all event attributes because these may change
+        # as the event is passed around; we want to know exactly what the event
+        # looked like when it reached this callback.
+        names = [name for name in dir(ev) if name[0] != '_']
+        attrs = {}
+        for name in names:
+            val = getattr(ev,name)
+            if name == 'source':
+                attrs[name] = val
+            elif name == 'sources':
+                attrs[name] = val[:]
+            else:
+                try:
+                    attrs[name] = copy.deepcopy(val)
+                except:
+                    try:
+                        attrs[name] = copy.copy(val)
+                    except:
+                        attrs[name] = val
+        if key is None:
+            self.result = ev, attrs
+        else:
+            if not hasattr(self, 'result') or self.result is None:
+                self.result = {}
+            self.result[key] = ev, attrs
+        
+    def assert_result(self, key=None, **kwds):
+        assert (hasattr(self, 'result') and self.result is not None), "No event recorded"
+        
+        if key is None:
+            event, event_attrs = self.result
+        else:
+            event, event_attrs = self.result[key]
+        
+        assert isinstance(event, Event), "Emitted object is not Event instance"
+        
+        for name,val in kwds.items():
+            if name == 'event':
+                assert event is val, "Event objects do not match"
+            
+            elif name == 'event_class':
+                assert isinstance(event, val), "Emitted object is not instance of %s"%val.__name__
+                
+            else:
+                attr = event_attrs[name]
+                assert (attr == val), "Event.%s != %s  (%s)"%(name,str(val),str(attr))
+        
+        
+            
diff --git a/vispy/util/test/test_event_emitter.py b/vispy/util/test/test_event_emitter.py
new file mode 100644
index 0000000..ea75ae2
--- /dev/null
+++ b/vispy/util/test/test_event_emitter.py
@@ -0,0 +1,421 @@
+from vispy.util.event import Event, EventEmitter
+import unittest
+import copy
+import functools
+
+class BasicEvent(Event):
+    pass
+
+class TypedEvent(Event):
+    def __init__(self, **kwds):
+        kwds['type'] = 'typed_event'
+        Event.__init__(self, **kwds)
+
+class TestEmitters(unittest.TestCase):
+    
+    def test_emitter(self):
+        """Emitter constructed with no arguments"""
+        em = EventEmitter()
+        
+        # type must be specified when emitting since Event requires type argument 
+        # and the emitter was constructed without it.
+        try:
+            em()
+            assert False, "Emitting event with no type should have failed."
+        except TypeError:
+            pass
+        
+        # See that emitted event has all of the properties we expect
+        ev = self.try_emitter(em, type='test_event')  
+        self.assert_result(event=ev, event_class=Event, source=None, type='test_event', sources=[None])
+        
+    
+    def test_emitter_source(self):
+        """Emitter constructed with source argument"""
+        em = EventEmitter(source=self)
+        ev = self.try_emitter(em, type='test_event')  
+        self.assert_result(event=ev, event_class=Event, source=self, type='test_event', sources=[self])
+        
+        # overriding source should fail:
+        try:
+            ev = em(type='test_event', source=None)
+            assert False, "Should not be able to specify source when emitting"
+        except AttributeError:
+            pass
+        
+    def test_emitter_type(self):
+        """Emitter constructed with type argument"""
+        em = EventEmitter(type='asdf')
+        ev = self.try_emitter(em)
+        self.assert_result(event=ev, event_class=Event, source=None, type='asdf', sources=[None])
+        
+        # overriding type is ok:
+        ev = self.try_emitter(em, type='qwer')
+        self.assert_result(event=ev, event_class=Event, source=None, type='qwer', sources=[None])
+
+    def test_emitter_type(self):
+        """Emitter constructed with event_class argument"""
+        em = EventEmitter(event_class=BasicEvent)
+        ev = self.try_emitter(em, type='test_event')
+        self.assert_result(event=ev, event_class=BasicEvent, source=None, type='test_event', sources=[None])
+        
+        # specifying non-event class should fail (eventually):
+        class X:
+            def __init__(self, *args, **kwds):
+                self.blocked=False
+            def _push_source(self, s):
+                pass
+            def _pop_source(self):
+                pass
+            
+        try:
+            em = EventEmitter(event_class=X)
+            ev = self.try_emitter(em, type='test_event')
+            self.assert_result() ## checks event type
+            assert False, "Should not be able to construct emitter with non-Event class"
+        except:
+            pass
+        
+    def test_event_kwargs(self):
+        """Extra Event kwargs"""
+        em = EventEmitter(type='test_event')
+        em.default_args['key1'] = 'test1'
+        em.connect(self.record_event)
+        self.result = None
+        em(key2='test2')
+        self.assert_result(key1='test1', key2='test2')
+        
+    def test_prebuilt_event(self):
+        """Emit pre-built event"""
+        em = EventEmitter(type='test_event')
+        em.default_args['key1'] = 'test1'
+        em.connect(self.record_event)
+        
+        self.result = None
+        ev = Event(type='my_type')
+        em(ev)
+        self.assert_result(event=ev, type='my_type')
+        assert not hasattr(self.result[0], 'key1')
+        
+    def test_emitter_subclass(self):
+        """EventEmitter subclassing"""
+        class MyEmitter(EventEmitter):
+            def _prepare_event(self, *args, **kwds):
+                ev = super(MyEmitter, self)._prepare_event(*args, **kwds)
+                ev.test_tag = 1
+                return ev
+        em = MyEmitter(type='test_event')
+        em.connect(self.record_event)
+        self.result = None
+        em()
+        self.assert_result(test_tag=1)
+
+    def test_typed_event(self):
+        """Emit Event class with pre-specified type"""
+        em = EventEmitter(event_class=TypedEvent)
+        ev = self.try_emitter(em) # no need to specify type here
+        self.assert_result(event=ev, event_class=TypedEvent, source=None, type='typed_event', sources=[None])
+
+    def test_disconnect(self):
+        """Emitter disconnection"""
+        em = EventEmitter(type='test_event')
+        def cb1(ev):
+            self.result = 1
+        def cb2(ev):
+            self.result = 2
+            
+        em.connect((self, 'record_event'))
+        em.connect(cb1)
+        em.connect(cb2)
+        self.result = None
+        em.disconnect(cb2)
+        ev = em()
+        self.assert_result(event=ev)
+        
+        self.result = None
+        em.disconnect((self, 'record_event'))
+        ev = em()
+        assert self.result == 1
+        
+        self.result = None
+        em.connect(cb1)
+        em.connect(cb2)
+        em.connect((self, 'record_event'))
+        em.disconnect()
+        em()
+        assert self.result == None
+        
+    def test_reconnect(self):
+        """Ignore callback reconnect"""
+        em = EventEmitter(type='test_event')
+        def cb(ev):
+            self.result += 1
+            
+        em.connect(cb)
+        em.connect(cb)  # second connection should do nothing.
+        self.result = 0
+        em()
+        assert self.result == 1
+        
+    def test_decorator_connection(self):
+        """Connection by decorator"""
+        em = EventEmitter(type='test_event')
+            
+        @em.connect
+        def cb(ev):
+            self.result = 1
+            
+        self.result = None
+        em()
+        assert self.result == 1
+        
+    def test_chained_emitters(self):
+        """Chained emitters"""
+        em1 = EventEmitter(source=None, type='test_event1')
+        em2 = EventEmitter(source=self, type='test_event2')
+        em1.connect(em2)
+        em1.connect(self.record_event)
+        self.result = None
+        ev = em1()
+        self.assert_result(event=ev, event_class=Event, source=None, type='test_event1', sources=[None])
+
+        # sources look different from second emitter, but type is the same.
+        em1.disconnect(self.record_event)
+        em2.connect(self.record_event)
+        self.result = None
+        ev = em1()
+        self.assert_result(event=ev, event_class=Event, source=self, type='test_event1', sources=[None,self])
+
+    def test_emitter_error_handling(self):
+        """Emitter error handling"""
+        em = EventEmitter(type='test_event')
+        em.print_callback_errors = False
+        def cb(ev):
+            raise Exception('test')
+        
+        # first callback fails; second callback still runs.
+        em.connect(self.record_event)
+        em.connect(cb)
+        self.result = None
+        ev = em()
+        self.assert_result(event=ev)
+        
+        # this time we should get an exception
+        self.result = None
+        em.ignore_callback_errors = False
+        try:
+            em()
+            assert False, "Emission should have raised exception"
+        except Exception as err:
+            if str(err) != 'test':
+                raise
+        
+    def test_emission_order(self):
+        """Event emission order"""
+        em = EventEmitter(type='test_event')
+        def cb1(ev):
+            self.result = 1
+        def cb2(ev):
+            self.result = 2
+            
+        em.connect(cb1)
+        em.connect(cb2)
+        self.result = None
+        em()
+        assert self.result == 1, "Events emitted in wrong order"
+
+        em.disconnect()
+        em.connect(cb2)
+        em.connect(cb1)
+        self.result = None
+        em()
+        assert self.result == 2, "Events emitted in wrong order"
+        
+    def test_multiple_callbacks(self):
+        """Multiple emitter callbacks"""
+        em = EventEmitter(type='test_event')
+        em.connect(functools.partial(self.record_event, key=1))
+        em.connect(functools.partial(self.record_event, key=2))
+        em.connect(functools.partial(self.record_event, key=3))
+        ev = em()
+        self.assert_result(key=1, event=ev, sources=[None])
+        self.assert_result(key=2, event=ev, sources=[None])
+        self.assert_result(key=3, event=ev, sources=[None])
+
+    def test_symbolic_callback(self):
+        """Symbolic callbacks"""
+        em = EventEmitter(type='test_event')
+        em.connect((self, 'record_event'))
+        ev = em()
+        self.assert_result(event=ev)
+        
+        # now check overriding the connected method
+        def cb(ev):
+            self.result = 1
+            
+        self.result = None
+        orig_method = self.record_event
+        try:
+            self.record_event = cb
+            em()
+            assert self.result == 1
+        finally:
+            self.record_event = orig_method
+
+    def test_source_stack_integrity(self):
+        """Emitter checks source stack"""
+        em = EventEmitter(type='test_event')
+        
+        def cb(ev):
+            ev._sources.append('x')
+        em.connect(cb)
+        
+        try:
+            em()
+        except RuntimeError as err:
+            if str(err) != 'Event source-stack mismatch.':
+                raise
+            
+        em.disconnect()
+        def cb(ev):
+            ev._sources = []
+        em.connect(cb)
+        
+        try:
+            em()
+        except IndexError:
+            pass
+            
+    def test_emitter_loop(self):
+        """Catch emitter loops"""
+        em1 = EventEmitter(type='test_event1')
+        em2 = EventEmitter(type='test_event2')
+        em1.ignore_callback_errors = False
+        em2.ignore_callback_errors = False
+        
+        ## cross-connect emitters; when we emit, an exception should be raised
+        ## indicating an event loop.
+        em1.connect(em2)
+        em2.connect(em1)
+        try:
+            em1()
+        except RuntimeError as err:
+            if str(err) != 'EventEmitter loop detected!':
+                raise err
+            
+    def test_emitter_block(self):
+        """EventEmitter.blocker"""
+        em = EventEmitter(type='test_event')
+        em.connect(self.record_event)
+        self.result = None
+        
+        with em.blocker():
+            em()
+        assert self.result is None
+        
+        ev = em()
+        self.assert_result(event=ev)
+        
+    def test_event_handling(self):
+        """Event.handled"""
+        em = EventEmitter(type='test_event')
+        def cb1(ev):
+            ev.handled = True
+        def cb2(ev):
+            assert ev.handled
+            self.result = 1
+        em.connect(cb2)
+        em.connect(cb1)
+        self.result = None
+        em()
+        assert self.result == 1
+        
+        
+    def test_event_block(self):
+        """Event.blocked"""
+        em = EventEmitter(type='test_event')
+        def cb1(ev):
+            ev.handled = True
+            self.result = 1
+        def cb2(ev):
+            ev.blocked = True
+            self.result = 2
+            
+        em.connect(self.record_event)
+        em.connect(cb1)
+        self.result = None
+        em()
+        self.assert_result()
+        
+        em.connect(cb2)
+        self.result = None
+        em()
+        assert self.result == 2
+        
+    def try_emitter(self, em, **kwds):
+        em.connect(self.record_event)
+        self.result = None
+        return em(**kwds)
+        
+    def record_event(self, ev, key=None):
+        # get a copy of all event attributes because these may change
+        # as the event is passed around; we want to know exactly what the event
+        # looked like when it reached this callback.
+        names = [name for name in dir(ev) if name[0] != '_']
+        attrs = {}
+        for name in names:
+            val = getattr(ev,name)
+            if name == 'source':
+                attrs[name] = val
+            elif name == 'sources':
+                attrs[name] = val[:]
+            else:
+                try:
+                    attrs[name] = copy.deepcopy(val)
+                except:
+                    try:
+                        attrs[name] = copy.copy(val)
+                    except:
+                        attrs[name] = val
+        if key is None:
+            self.result = ev, attrs
+        else:
+            if not hasattr(self, 'result') or self.result is None:
+                self.result = {}
+            self.result[key] = ev, attrs
+        
+    def assert_result(self, key=None, **kwds):
+        assert (hasattr(self, 'result') and self.result is not None), "No event recorded"
+        
+        if key is None:
+            event, event_attrs = self.result
+        else:
+            event, event_attrs = self.result[key]
+        
+        assert isinstance(event, Event), "Emitted object is not Event instance"
+        
+        for name,val in kwds.items():
+            if name == 'event':
+                assert event is val, "Event objects do not match"
+            
+            elif name == 'event_class':
+                assert isinstance(event, val), "Emitted object is not instance of %s"%val.__name__
+                
+            else:
+                attr = event_attrs[name]
+                assert (attr == val), "Event.%s != %s  (%s)"%(name,str(val),str(attr))
+        
+        
+        
+#if __name__ == '__main__':
+    #import sys
+    #test = EventTest()
+    
+    #for name in [n for n in dir(test) if n.endswith('_test')]:
+        #try:
+            #getattr(test, name)()
+            #print name, 'OK'
+        #except:
+            #sys.excepthook(*sys.exc_info())
+            #print name, 'FAILED'
+            
diff --git a/vispy/util/test/test_vispy.py b/vispy/util/test/test_vispy.py
new file mode 100644
index 0000000..88a8f57
--- /dev/null
+++ b/vispy/util/test/test_vispy.py
@@ -0,0 +1,6 @@
+""" Tests to ensure that base vispy namespace functions correctly,
+including configuration options.
+"""
+
+
+
diff --git a/vispy/util/transforms.py b/vispy/util/transforms.py
new file mode 100644
index 0000000..79e667a
--- /dev/null
+++ b/vispy/util/transforms.py
@@ -0,0 +1,148 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+Very simple transformation library that is needed for some examples.
+"""
+
+import math
+import numpy
+import numpy as np
+
+
+def translate(M, x, y=None, z=None):
+    """
+    translate produces a translation by (x, y, z) . 
+    
+    Parameters
+    ----------
+    x, y, z
+        Specify the x, y, and z coordinates of a translation vector.
+    """
+    if y is None: y = x
+    if z is None: z = x
+    T = [[ 1, 0, 0, x],
+         [ 0, 1, 0, y],
+         [ 0, 0, 1, z],
+         [ 0, 0, 0, 1]]
+    T = np.array(T, dtype=np.float32).T
+    M[...] = np.dot(M,T)
+
+
+def scale(M, x, y=None, z=None):
+    """
+    scale produces a non uniform scaling along the x, y, and z axes. The three
+    parameters indicate the desired scale factor along each of the three axes.
+
+    Parameters
+    ----------
+    x, y, z
+        Specify scale factors along the x, y, and z axes, respectively.
+    """
+    if y is None: y = x
+    if z is None: z = x
+    S = [[ x, 0, 0, 0],
+         [ 0, y, 0, 0],
+         [ 0, 0, z, 0],
+         [ 0, 0, 0, 1]]
+    S = np.array(S,dtype=np.float32).T
+    M[...] = np.dot(M,S)
+
+
+def xrotate(M,theta):
+    t = math.pi*theta/180
+    cosT = math.cos( t )
+    sinT = math.sin( t )
+    R = numpy.array(
+        [[ 1.0,  0.0,  0.0, 0.0 ],
+         [ 0.0, cosT,-sinT, 0.0 ],
+         [ 0.0, sinT, cosT, 0.0 ],
+         [ 0.0,  0.0,  0.0, 1.0 ]], dtype=np.float32)
+    M[...] = np.dot(M,R)
+
+def yrotate(M,theta):
+    t = math.pi*theta/180
+    cosT = math.cos( t )
+    sinT = math.sin( t )
+    R = numpy.array(
+        [[ cosT,  0.0, sinT, 0.0 ],
+         [ 0.0,   1.0,  0.0, 0.0 ],
+         [-sinT,  0.0, cosT, 0.0 ],
+         [ 0.0,  0.0,  0.0, 1.0 ]], dtype=np.float32)
+    M[...] = np.dot(M,R)
+
+def zrotate(M,theta):
+    t = math.pi*theta/180
+    cosT = math.cos( t )
+    sinT = math.sin( t )
+    R = numpy.array(
+        [[ cosT,-sinT, 0.0, 0.0 ],
+         [ sinT, cosT, 0.0, 0.0 ],
+         [ 0.0,  0.0,  1.0, 0.0 ],
+         [ 0.0,  0.0,  0.0, 1.0 ]], dtype=np.float32)
+    M[...] = np.dot(M,R)
+
+
+def rotate(M, angle, x, y, z, point=None):
+    """
+    rotate produces a rotation of angle degrees around the vector (x, y, z).
+    
+    Parameters
+    ----------
+    M
+       Current transformation as a numpy array
+
+    angle
+       Specifies the angle of rotation, in degrees.
+
+    x, y, z
+        Specify the x, y, and z coordinates of a vector, respectively.
+    """
+    angle = math.pi*angle/180
+    c,s = math.cos(angle), math.sin(angle)
+    n = math.sqrt(x*x+y*y+z*z)
+    x /= n
+    y /= n
+    z /= n
+    cx,cy,cz = (1-c)*x, (1-c)*y, (1-c)*z
+    R = numpy.array([[ cx*x + c  , cy*x - z*s, cz*x + y*s, 0],
+                     [ cx*y + z*s, cy*y + c  , cz*y - x*s, 0],
+                     [ cx*z - y*s, cy*z + x*s, cz*z + c,   0],
+                     [          0,          0,        0,   1]]).T
+    M[...] = np.dot(M,R)
+
+
+def ortho( left, right, bottom, top, znear, zfar ):
+    assert( right  != left )
+    assert( bottom != top  )
+    assert( znear  != zfar )
+    
+    M = np.zeros((4,4), dtype=np.float32)
+    M[0,0] = +2.0/(right-left)
+    M[3,0] = -(right+left)/float(right-left)
+    M[1,1] = +2.0/(top-bottom)
+    M[3,1] = -(top+bottom)/float(top-bottom)
+    M[2,2] = -2.0/(zfar-znear)
+    M[3,2] = -(zfar+znear)/float(zfar-znear)
+    M[3,3] = 1.0
+    return M
+        
+def frustum( left, right, bottom, top, znear, zfar ):
+    assert( right  != left )
+    assert( bottom != top  )
+    assert( znear  != zfar )
+
+    M = np.zeros((4,4), dtype=np.float32)
+    M[0,0] = +2.0*znear/(right-left)
+    M[2,0] = (right+left)/(right-left)
+    M[1,1] = +2.0*znear/(top-bottom)
+    M[3,1] = (top+bottom)/(top-bottom)
+    M[2,2] = -(zfar+znear)/(zfar-znear)
+    M[3,2] = -2.0*znear*zfar/(zfar-znear)
+    M[2,3] = -1.0
+    return M
+
+def perspective(fovy, aspect, znear, zfar):
+    assert( znear != zfar )
+    h = np.tan(fovy / 360.0 * np.pi) * znear
+    w = h * aspect
+    return frustum( -w, w, -h, h, znear, zfar )
diff --git a/vispy/visuals/README b/vispy/visuals/README
new file mode 100644
index 0000000..8e540ef
--- /dev/null
+++ b/vispy/visuals/README
@@ -0,0 +1,4 @@
+These are the simplest graphical components, all subclasses of primitives.base. 
+Each defines its own code for drawing to OpenGL, SVG, EPS, etc.
+The compound graphical components are usually made by subclassing or combining primitives, although 
+they have all the same low-level capabilities that primitives have.
diff --git a/vispy/visuals/__init__.py b/vispy/visuals/__init__.py
new file mode 100644
index 0000000..d5d8909
--- /dev/null
+++ b/vispy/visuals/__init__.py
@@ -0,0 +1,2 @@
+from .visual import Visual, GLOptions
+from .line import LineVisual
\ No newline at end of file
diff --git a/vispy/visuals/base.py b/vispy/visuals/base.py
new file mode 100644
index 0000000..22b10d7
--- /dev/null
+++ b/vispy/visuals/base.py
@@ -0,0 +1,17 @@
+"""
+Base class for visualization objects. Anything that can be displayed or placed in a scenegraph hierarchy will ultimately inherit from this class.
+
+Should define:
+ - methods for drawing to OpenGL based on version
+ - methods for drawing to SVG / EPS, etc.
+ - set of matrix transformations:
+      - each visualization object should have at least two transformation matrices
+          - one (or more) 'external' matrix is to allow the object to be transformed by the user
+          - one 'internal' matrix allows the object to internally define its own coordinate system
+      - total transform is simply the product of all matrices in the list
+        (however objects should be free to redefine this behavior where appropriate) 
+ - object hierarchy information--parent and children
+ - basic cascading stylesheet system (see util/stylesheet)
+ - basic user interaction - mouse, keyboard, tablet, focus, etc.
+      (but note that a scenegraph would be required to deliver events to each object)
+"""
diff --git a/vispy/visuals/box.py b/vispy/visuals/box.py
new file mode 100644
index 0000000..15adab1
--- /dev/null
+++ b/vispy/visuals/box.py
@@ -0,0 +1,6 @@
+"""
+A box is a visualization object with a defined rectangular boundary.
+This is used mainly as a child of a layout -- anything you might want to stack in a grid or a table should 
+inherit from this class.
+
+"""
diff --git a/vispy/visuals/camera.py b/vispy/visuals/camera.py
new file mode 100644
index 0000000..8243708
--- /dev/null
+++ b/vispy/visuals/camera.py
@@ -0,0 +1,8 @@
+"""
+Class defining a camera frustum.
+
+I'm including this as a subclass of primitive because 1) it will generally exist in the same coordinate system with other primitives
+and 2) it can be useful for the camera to be able draw its shape. 
+
+Cameras are usually defined by primitives.view_box
+"""
diff --git a/vispy/visuals/curve.py b/vispy/visuals/curve.py
new file mode 100644
index 0000000..e69de29
diff --git a/vispy/visuals/data.py b/vispy/visuals/data.py
new file mode 100644
index 0000000..918c870
--- /dev/null
+++ b/vispy/visuals/data.py
@@ -0,0 +1,116 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2013, Vispy Development Team. All Rights Reserved.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+# -----------------------------------------------------------------------------
+""" Definition of the Data class """
+
+from __future__ import print_function, division, absolute_import
+
+import sys
+import numpy as np
+
+from . import gl
+from . import GLObject
+from . import VertexBuffer
+
+
+
+# ------------------------------------------------------------ Data class ---
+class Data(GLObject):
+    """The Data class allows to encapsulate several vertex buffers at once
+
+    Example
+    -------
+
+    data = Data(1000, [('a_position', np.float32,3),
+                       ('a_color',    np.float32,4)])
+    data['a_color'] = 0,0,0,1
+    """
+
+    def __init__(self, size, *args):
+        """ Initialize Data into default state. """
+
+        GLObject.__init__(self)
+        self._data = {}
+
+        for dtype in args:
+            dtype = np.dtype(dtype)
+            if not dtype.fields:
+                raise TypeError('dtype must be structured type')
+
+            array  = np.empty(size, dtype)
+            buffer = VertexBuffer(array)
+            for name in dtype.names:
+                self._data[name] = array,buffer 
+
+    @property
+    def data(self):
+        """ Return a dictionnay of all vertex buffers """
+
+        return dict( (name,buffer[name]) for name,(array,buffer) in self._data.items())
+
+
+    def __setitem__(self, key, value):
+        """ """
+
+        if key not in self._data.keys():
+            raise AttributeError('Unknown attribute')
+
+        array, buffer = self._data[key]
+        array[key][...] = value
+
+        # We mark the base buffer for a full update
+        buffer.set_data(array)
+
+
+    def __getitem__(self, key):
+        """ Return the underlying numpy array. """
+
+        if key not in self._data.keys():
+            raise AttributeError('Unknown attribute')
+
+        array, buffer = self._data[key]
+        # We mark the base buffer for a full update since we do not
+        # control whether the array will be changed afterwards
+        buffer.set_data(array)
+        return array[key]
+
+
+    def __call__(self, key):
+        """ Return the underlying vertex buffer. """
+
+        if key not in self._data.keys():
+            raise AttributeError('Unknown attribute')
+
+        array, buffer = self._data[key]
+        return buffer[key]
+
+
+    def keys(self):
+        """ Return a list of known attributes. """
+
+        return self._data.keys()
+
+
+
+
+# -----------------------------------------------------------------------------
+if __name__ == '__main__':
+
+    data = Data(100, [ ('a_position', np.float32, 3),
+                       ('a_color', np.float32, 4) ],
+                     [ ('a_rot', np.float32, 4) ] )
+    data['a_position'] = np.ones((100,3))
+    data['a_color'][::2] = 0,0,0,1
+    data['a_rot'][::2] = 0,0,0,1
+
+    # Return the underlying numpy array
+    print( type(data['a_color']) )
+
+    # Return the underlying vertex buffer
+    print( type(data('a_color')) )
+    
+    # We could then use
+    # program.attach(data)
+
+    print( data.data )
diff --git a/vispy/visuals/image.py b/vispy/visuals/image.py
new file mode 100644
index 0000000..e69de29
diff --git a/vispy/visuals/layout.py b/vispy/visuals/layout.py
new file mode 100644
index 0000000..b9afedc
--- /dev/null
+++ b/vispy/visuals/layout.py
@@ -0,0 +1,4 @@
+"""
+Subclass of box that is used to automate the arrangement of child items into a grid / table layout.
+
+"""
diff --git a/vispy/visuals/light.py b/vispy/visuals/light.py
new file mode 100644
index 0000000..e69de29
diff --git a/vispy/visuals/line.py b/vispy/visuals/line.py
new file mode 100644
index 0000000..1409d22
--- /dev/null
+++ b/vispy/visuals/line.py
@@ -0,0 +1,369 @@
+from vispy.gloo import Program, VertexShader, FragmentShader, VertexBuffer, Texture2D
+import OpenGL.GL as gl
+import vispy.shaders.transforms as transforms
+import numpy as np
+from .visual import Visual
+
+XYPositionVertexShader = VertexShader("""
+    // XY position vertex shader
+    attribute vec2 in_position;
+    uniform float in_z_position;
+    
+    vec4 global_position() {
+        return vec4(in_position, in_z_position, 1);            
+    }
+""")
+
+
+#def b2f(f):
+    #b = np.array([f], dtype=np.float32).view(np.uint8)
+    #print b
+    #s = float(1 - 2 * (b[3] >> 7))
+    #e = float(((b[3]&127) << 1) | ((b[2]&128) >> 7)) - 127
+    #p = float(b[0] + 256 * b[1] + 65536 * (b[2] & 127))
+    #f = s * ((p*1.1920928955078125e-07)+1) * 2**e
+    #if f > 5.1e38:
+        #return np.nan
+    #if f < -5.1e38:
+        #return -np.nan
+    #return f
+
+XYTexPositionVertexShader = VertexShader("""
+    #extension GL_ARB_shader_bit_encoding : enable
+    
+    // XY position vertex shader using texture and attribute index
+    // single-precision float positions are represented as 4-byte RGBA 
+    uniform sampler2D in_position;
+    uniform int in_position_size;
+    
+    vec4 read_position(int vert_index) {
+        float v_index_clipped = max(0., min(float(in_position_size), float(vert_index)));
+        float index = v_index_clipped / float(in_position_size - 1);
+        vec4 xv = texture2D(in_position, vec2(0, index));
+        vec4 yv = texture2D(in_position, vec2(1, index));
+        xv = (xv + 0.002) * 255.;
+        yv = (yv + 0.002) * 255.;
+        int xi = int(xv.r) + (int(xv.g) * 256) + (int(xv.b) * 65536) + (int(xv.a) * 16777216);
+        int yi = int(yv.r) + (int(yv.g) * 256) + (int(yv.b) * 65536) + (int(yv.a) * 16777216);
+        float x = intBitsToFloat(xi);
+        float y = intBitsToFloat(yi);
+        return vec4(x, y, 0.0, 1.0);
+    }
+""")
+
+XYZPositionVertexShader = VertexShader("""
+    // XYZ position vertex shader
+    attribute vec3 in_position;
+    
+    vec4 global_position() {
+        return vec4(in_position, 1);            
+    }
+""")
+
+UniformColorVertexShader = VertexShader("""
+    // Uniform color vertex shader
+    uniform vec4 in_color;
+    
+    vec4 global_vertex_color(vec4 pos) {
+        return in_color;
+    }
+""")
+
+RGBColorVertexShader = VertexShader("""
+    // RGB color vertex shader
+    attribute vec3 in_color;
+    uniform float in_alpha;
+    
+    vec4 global_vertex_color(vec4 pos) {
+        return vec4(in_color, in_alpha);            
+    }
+""")
+
+RGBAColorVertexShader = VertexShader("""
+    // RGBA color vertex shader
+    attribute vec4 in_color;
+    
+    vec4 global_vertex_color(vec4 pos) {
+        return vec4(in_color);
+    }
+""")
+
+NullColorFragmentShader = FragmentShader("""
+    // Null color fragment shader
+    vec4 global_fragment_color(vec4 vert_color, vec4 position) {
+        return vert_color;
+    }
+""")
+
+line_vertex_shader = VertexShader("""
+// LineVisual main vertex shader (LINE_STRIP version)
+
+// Generic, pluggable shader architecture
+// * declares global callbacks that may be redefined by linking different shaders together
+// * avoids use of potentially expensive conditionals inside the program
+
+#version 120
+
+// returns current vertex position as vec4
+vec4 global_position();
+
+// prototype for the transformation to be customized
+vec4 global_transform(vec4);
+
+vec4 global_vertex_color(vec4);
+
+
+varying vec4 vert_color;
+varying vec4 position;
+varying vec4 raw_position;
+
+void main(void) 
+{
+    // All vertex shaders should implement this line to allow
+    // customizable transformation.
+    raw_position = global_position();
+    position = global_transform(raw_position);
+    gl_Position = position;
+    vert_color = global_vertex_color(position);
+}
+""")
+
+line_fragment_shader = FragmentShader("""
+// LineVisual main fragment shader
+#version 120
+
+// Returns fragment color given vertex color and position
+// todo: might want access to view, document, and device coordinates here.
+vec4 global_fragment_color(vec4, vec4);
+
+varying vec4 vert_color;
+varying vec4 position;
+varying vec4 raw_position;
+
+void main(void)
+{
+    gl_FragColor = global_fragment_color(vert_color, position);
+}
+""")
+
+
+tri_vertex_shader = VertexShader("""
+// LineVisual main vertex shader (TRIANGLE_STRIP version)
+
+// Generic, pluggable shader architecture
+// * declares global callbacks that may be redefined by linking different shaders together
+// * avoids use of potentially expensive conditionals inside the program
+
+#version 120
+
+// returns vertex position at index as vec4
+vec4 read_position(int);
+
+// prototype for the transformation to be customized
+vec4 global_transform(vec4);
+
+vec4 global_vertex_color(vec4);
+
+attribute float in_index; // current vertex index
+
+varying vec4 vert_color;
+varying vec4 position;
+varying vec4 raw_position;
+
+vec2 intersection(vec2 a0, vec2 a1, vec2 b0, vec2 b1) {
+    vec2 a = a1 - a0;
+    vec2 b = b1 - b0;
+    float den = (b.x * a.y - b.y * a.x);
+    if (abs(den) > 1e-10) {
+        float n = ((b0.y - a0.y) * a.x - (b0.x - a0.x) * a.y) / den;
+        return b0 + n * b;
+    }
+    else {
+        return a1;
+    }
+}
+
+void main(void) 
+{
+    // All vertex shaders should implement this line to allow
+    // customizable transformation.
+    //raw_position = global_position();
+    
+    int v_index = int((in_index+0.5) / 2.0);
+    int v_side = int(mod((in_index+0.5), 2.0));
+    
+    // 3 vertexes in data (raw) coordinate system
+    vec4 rpos0 = read_position(v_index-1);
+    vec4 rpos1 = read_position(v_index);
+    vec4 rpos2 = read_position(v_index+1);
+    
+    // 3 vertexes in screen coordinate system
+    vec4 pos0 = global_transform(rpos0);
+    vec4 pos1 = global_transform(rpos1);
+    vec4 pos2 = global_transform(rpos2);
+    
+    // Direction to offset from line segment
+    float dir = 1. - (v_side * 2.);
+    float width = 5.0 * dir / 800;  // TODO: 800 does not belong here
+    
+    // Vectors orthogonal to line segments
+    vec2 l1 = pos1.xy - pos0.xy;
+    vec2 l2 = pos2.xy - pos1.xy;
+    vec2 l1_ortho = normalize(vec2(l1.y, -l1.x));
+    vec2 l2_ortho = normalize(vec2(l2.y, -l2.x));
+    
+    // vertexes of offset line segments
+    vec2 p0a = pos0.xy + l1_ortho * width;
+    vec2 p1a = pos1.xy + l1_ortho * width;
+    vec2 p1b = pos1.xy + l2_ortho * width;
+    vec2 p2b = pos2.xy + l2_ortho * width;
+    
+    // Intersection
+    vec2 i = intersection(p0a, p1a, p1b, p2b);
+    position = vec4(i.x, i.y, pos1.z, 1);
+    //position = vec4(pos1.x, pos1.y+width, pos1.z, 1);
+    
+    gl_Position = position;
+    vert_color = global_vertex_color(position);
+}
+""")
+
+tri_fragment_shader = FragmentShader("""
+// LineVisual main fragment shader
+#version 120
+
+// Returns fragment color given vertex color and position
+// todo: might want access to view, document, and device coordinates here.
+vec4 global_fragment_color(vec4, vec4);
+
+varying vec4 vert_color;
+varying vec4 position;
+varying vec4 raw_position;
+
+void main(void)
+{
+    gl_FragColor = global_fragment_color(vert_color, position);
+}
+""")
+        
+class LineVisual(Visual):
+    def __init__(self, **kwds):
+        Visual.__init__(self)
+        self.set_gl_options({
+            gl.GL_LINE_SMOOTH: True,
+            gl.GL_BLEND: True,
+            'glBlendFunc': (gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA),
+            'glHint': (gl.GL_LINE_SMOOTH_HINT, gl.GL_NICEST),
+            'glLineWidth': (1,),
+            })
+            
+        self._opts = {
+            'color': (1, 1, 1, 1),
+            'width': 1,
+            'mode': 'fast',   # maybe 'fast_update', 'fast_redraw', and 'quality' ?
+            }
+        #self._program = ShaderProgram(
+                            #VertexShader(vertex_shader), 
+                            #self._Visual__transform,
+                            #FragmentShader(fragment_shader))
+        self._program = None
+        self.set_data(**kwds)
+        
+    def update(self):
+        self._program = None
+        
+    def set_data(self, **kwds):
+        """
+        Keyword arguments:
+        pos     (N, 2-3) array
+        color   (3-4) or (N, 3-4) array
+        width   scalar or (N,) array
+        """
+        self._opts.update(kwds)
+        typ = [('pos', np.float32, self._opts['pos'].shape[-1])]
+        if isinstance(self._opts['color'], np.ndarray):
+            typ.append(('color', np.float32, self._opts['color'].shape[-1]))
+        self._data = np.empty(self._opts['pos'].shape[:-1], typ)
+        self._data['pos'] = self._opts['pos']
+        if isinstance(self._opts['color'], np.ndarray):
+            self._data['color'] = self._opts['color']
+        
+        self.vbo = VertexBuffer(data=self._data)
+        #tex = np.zeros((len(self._data), 1, 3), dtype=np.float32)
+        #tex[...,:2] = self._data['pos'][:,np.newaxis]
+        
+        # recast float to RGBA bytes
+        d = self._data['pos'].astype(np.float32).view(np.ubyte).reshape(len(self._data), 2, 4)
+        self.ptex = Texture2D(d)
+        self.ptex.set_filter(gl.GL_NEAREST, gl.GL_NEAREST)
+        
+        self.indexes = VertexBuffer(data=np.arange(len(self._data)*2, dtype=np.float32))
+
+    def _generate_program(self):
+        if self._opts['mode'] == 'fast':
+            main_vshader = line_vertex_shader
+            main_fshader = line_fragment_shader
+            if self._opts['pos'].shape[-1] == 2:
+                posShader = XYPositionVertexShader
+            else:
+                posShader = XYZPositionVertexShader
+        else:
+            main_vshader = tri_vertex_shader
+            main_fshader = tri_fragment_shader
+            posShader = XYTexPositionVertexShader
+        
+        if isinstance(self._opts['color'], tuple):
+            colorShader = UniformColorVertexShader
+        elif self._opts['color'].shape[-1] == 3:
+            colorShader = RGBColorVertexShader
+        else:
+            colorShader = RGBAColorVertexShader
+            
+        self._program = ShaderProgram(
+            main_vshader, 
+            self.transform_chain(),
+            main_fshader,
+            posShader,
+            colorShader,
+            NullColorFragmentShader,
+            )
+            
+        #self._program._feedback_vars = ['position']
+            
+        
+    def draw(self):
+        Visual.draw(self)
+        
+        if self._program is None:
+            self._generate_program()
+        
+        with self._program:
+            if self._opts['mode'] == 'fast':
+                self._program.attributes['in_position'] = self.vbo['pos']
+                if self._opts['pos'].shape[-1] == 2:
+                    self._program.uniforms['in_z_position'] = 1.0
+            else:
+                self._program.attributes['in_index'] = self.indexes
+                self._program.uniforms['in_position'] = self.ptex
+                self._program.uniforms['in_position_size'] = len(self._data)
+
+            if isinstance(self._opts['color'], tuple):
+                self._program.uniforms['in_color'] = self._opts['color']
+            elif self._opts['color'].shape[-1] == 3:
+                self._program.uniforms['in_alpha'] = 1.0;
+                self._program.attributes['in_color'] = self.vbo['color']
+            else:
+                self._program.attributes['in_color'] = self.vbo['color']
+                
+            if self._opts['mode'] == 'fast':
+                #gl.glDrawArrays(gl.GL_LINE_STRIP, 0, self._data.size)
+                self._program.draw_arrays(gl.GL_LINE_STRIP)
+            else:
+                self._program.draw_arrays(gl.GL_TRIANGLE_STRIP)
+                
+            
+            fb = np.zeros((len(self._data), 4), dtype=np.float32)
+            #self._program.feedback_arrays(fb, gl.GL_LINE_STRIP)
+        
+        
+
diff --git a/vispy/visuals/mesh.py b/vispy/visuals/mesh.py
new file mode 100644
index 0000000..e622500
--- /dev/null
+++ b/vispy/visuals/mesh.py
@@ -0,0 +1,7 @@
+"""
+For drawing triangles. 
+
+It should be possible to have multiple meshes render together in a single pass
+so they have the option of sorting triangles for translucency.
+
+"""
diff --git a/vispy/visuals/particle.py b/vispy/visuals/particle.py
new file mode 100644
index 0000000..dcb8871
--- /dev/null
+++ b/vispy/visuals/particle.py
@@ -0,0 +1,5 @@
+"""
+Efficient rendering of particles / markers / points
+ - point sprite mode for displaying pre-rendered textures; sizes are specified in screen coordinates
+ - absolute size mode--point sizes are specified in real coordinates
+"""
diff --git a/vispy/visuals/text.py b/vispy/visuals/text.py
new file mode 100644
index 0000000..e69de29
diff --git a/vispy/visuals/view_box.py b/vispy/visuals/view_box.py
new file mode 100644
index 0000000..8b6d172
--- /dev/null
+++ b/vispy/visuals/view_box.py
@@ -0,0 +1,9 @@
+"""
+Subclass of box which displays children using a user-specified coordinate system.
+ - scale / pan / rotate by mouse or keyboard
+   (this should be completely configurable with nice defaults; nobody ever agrees on the best way to do this)
+ - manages one or more camera objects defining the internal coordinate system
+ - clipping to boundaries
+ - ideally, changes internal to a view box should cause _only_ that box to be redrawn. This is very important
+   if you have, for example, a grid of 20x20 plots
+"""
diff --git a/vispy/visuals/visual.py b/vispy/visuals/visual.py
new file mode 100644
index 0000000..b83d9dc
--- /dev/null
+++ b/vispy/visuals/visual.py
@@ -0,0 +1,149 @@
+import OpenGL.GL as gl
+import vispy.shaders.transforms as transforms
+from vispy.util.six import string_types
+import numpy as np
+    
+
+"""
+Names assigned to commonly-used combinations of GL flags
+"""
+GLOptions = {
+    'opaque': {
+        gl.GL_DEPTH_TEST: True,
+        gl.GL_BLEND: False,
+        gl.GL_ALPHA_TEST: False,
+        gl.GL_CULL_FACE: False,
+    },
+    'translucent': {
+        gl.GL_DEPTH_TEST: True,
+        gl.GL_BLEND: True,
+        gl.GL_ALPHA_TEST: False,
+        gl.GL_CULL_FACE: False,
+        'glBlendFunc': (gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA),
+    },
+    'additive': {
+        gl.GL_DEPTH_TEST: False,
+        gl.GL_BLEND: True,
+        gl.GL_ALPHA_TEST: False,
+        gl.GL_CULL_FACE: False,
+        'glBlendFunc': (gl.GL_SRC_ALPHA, gl.GL_ONE),
+    },
+}
+
+
+class Visual(object):
+    """
+    Base class for low-level visuals. 
+    
+    Provides:
+    
+    * Methods for establishing GL state before drawing
+      (ie, glEnable/Disable and related calls)
+    * Methods for determining coordinate transformations to be applied to vertex data
+    
+    """
+    def __init__(self):
+        self.__transforms = []
+        self.__gl_opts = {}
+        
+    @property
+    def transforms(self):
+        return self.__transforms[:]
+    
+    @transforms.setter
+    def transforms(self, tr):
+        assert isinstance(tr, list)
+        self.__transforms = tr
+        self.update()
+        
+    def transform_chain(self):
+        return transforms.TransformChain(self.transforms, function='global_transform')
+        
+    def draw(self):
+        """ Draw this item.
+        """
+        # do glEnable/Disable and related calls
+        self.setup_gl_state() 
+        
+        # draw here..
+    
+    def init_gl(self):
+        pass
+    
+    @classmethod
+    def draw_multi(self, visuals):
+        """
+        Draw multiple visuals in a single pass.
+        
+        Requires items in *visuals* to be compatible.
+        Subclasses are not required to reimplement this method.
+        """
+        pass
+
+    def set_gl_options(self, opts):
+        """
+        Set the OpenGL state options to use immediately before drawing this item.
+        (Note that subclasses must call setup_gl_state before painting for this to work)
+        
+        The simplest way to invoke this method is to pass in the name of
+        a predefined set of options (see the GLOptions variable):
+        
+        ============= ======================================================
+        opaque        Enables depth testing and disables blending
+        translucent   Enables depth testing and blending
+                      Elements must be drawn sorted back-to-front for
+                      translucency to work correctly.
+        additive      Disables depth testing, enables blending.
+                      Colors are added together, so sorting is not required.
+        ============= ======================================================
+        
+        It is also possible to specify any arbitrary settings as a dictionary. 
+        This may consist of {'functionName': (args...)} pairs where functionName must 
+        be a callable attribute of OpenGL.GL, or {GL_STATE_VAR: bool} pairs 
+        which will be interpreted as calls to glEnable or glDisable(GL_STATE_VAR).
+        
+        For example::
+            
+            {
+                GL_ALPHA_TEST: True,
+                GL_CULL_FACE: False,
+                'glBlendFunc': (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA),
+            }
+            
+        
+        """
+        if isinstance(opts, string_types):
+            opts = GLOptions[opts]
+        self.__gl_opts = opts.copy()
+        self.update()
+        
+    def update(self):
+        """ Called when the Visual needs to be redrawn
+        """
+        
+    def update_gl_options(self, opts):
+        """
+        Modify the OpenGL state options to use immediately before drawing this item.
+        *opts* must be a dictionary as specified by setGLOptions.
+        Values may also be None, in which case the key will be ignored.
+        """
+        self.__gl_opts.update(opts)
+        
+    def setup_gl_state(self):
+        """
+        This method is responsible for preparing the GL state options needed to render 
+        this item (blending, depth testing, etc). The method is called immediately before painting the item.
+        """
+        for k,v in self.__gl_opts.items():
+            if v is None:
+                continue
+            if isinstance(k, string_types):
+                func = getattr(gl, k)
+                func(*v)
+            else:
+                if v is True:
+                    gl.glEnable(k)
+                else:
+                    gl.glDisable(k)
+
+
diff --git a/vispy/visuals/volume.py b/vispy/visuals/volume.py
new file mode 100644
index 0000000..e69de29

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-science/packages/vispy.git



More information about the debian-science-commits mailing list